1pub mod args;
4pub mod bindings;
5pub mod ioctl;
6pub mod opal;
7
8use anyhow::{Result, anyhow};
9use once_cell::sync::OnceCell;
10use std::{
11 fs,
12 io::{self, Read},
13};
14
15trait OpalBackend: Sync {
21 fn is_opal_device(&self, dev: &str) -> Result<bool>;
22 fn unlock_device(&self, dev: &str, pw: &str) -> Result<()>;
23 fn lock_device(&self, dev: &str, pw: &str) -> Result<()>;
24 fn device_locked(&self, dev: &str) -> Result<bool>;
25 fn get_locking_features(&self, dev: &str) -> Result<u16>;
26 fn print_locking_features(&self, f: u16);
27}
28
29struct RealBackend;
30
31impl OpalBackend for RealBackend {
32 fn is_opal_device(&self, dev: &str) -> Result<bool> {
33 opal::is_opal_device(dev)
34 }
35 fn unlock_device(&self, dev: &str, pw: &str) -> Result<()> {
36 opal::unlock_device(dev, pw)
37 }
38 fn lock_device(&self, dev: &str, pw: &str) -> Result<()> {
39 opal::lock_device(dev, pw)
40 }
41 fn device_locked(&self, dev: &str) -> Result<bool> {
42 opal::device_locked(dev)
43 }
44 fn get_locking_features(&self, dev: &str) -> Result<u16> {
45 opal::get_locking_features(dev)
46 }
47 fn print_locking_features(&self, f: u16) {
48 opal::print_locking_features(f)
49 }
50}
51
52static BACKEND: OnceCell<&'static (dyn OpalBackend + Sync)> = OnceCell::new();
54static REAL: RealBackend = RealBackend;
55
56fn backend() -> &'static (dyn OpalBackend + Sync) {
57 *BACKEND.get_or_init(|| &REAL as &(dyn OpalBackend + Sync))
58}
59
60pub fn use_mock_backend() {
65 static MOCK: BuiltinMockBackend = BuiltinMockBackend;
66 BACKEND.get_or_init(|| &MOCK);
67}
68
69struct BuiltinMockBackend;
71
72impl OpalBackend for BuiltinMockBackend {
73 fn is_opal_device(&self, dev: &str) -> Result<bool> {
74 if dev.starts_with("/dev/nvme") {
76 Ok(true)
77 } else {
78 Err(anyhow!("{} does not support OPAL locking", dev))
79 }
80 }
81
82 fn unlock_device(&self, dev: &str, pw: &str) -> Result<()> {
83 if pw.is_empty() {
84 return Err(anyhow!("empty password"));
85 }
86 if !dev.starts_with("/dev/nvme") {
87 return Err(anyhow!("invalid device"));
88 }
89 Ok(())
90 }
91
92 fn lock_device(&self, dev: &str, pw: &str) -> Result<()> {
93 if pw.is_empty() {
94 return Err(anyhow!("empty password"));
95 }
96 if !dev.starts_with("/dev/nvme") {
97 return Err(anyhow!("invalid device"));
98 }
99 Ok(())
100 }
101
102 fn device_locked(&self, dev: &str) -> Result<bool> {
103 if !dev.starts_with("/dev/nvme") {
104 return Err(anyhow!("invalid device"));
105 }
106 Ok(dev.ends_with('0'))
108 }
109
110 fn get_locking_features(&self, _dev: &str) -> Result<u16> {
111 Ok(0x001F)
113 }
114
115 fn print_locking_features(&self, f: u16) {
116 println!("[mock] Locking features: 0x{:04x}", f);
117 }
118}
119
120fn read_key_arg(key_arg: Option<String>) -> Result<String> {
125 if let Some(arg) = key_arg {
126 if arg == "-" {
127 let mut buf = String::new();
128 io::stdin().read_to_string(&mut buf)?;
129 return Ok(buf.trim_end_matches(['\n', '\r']).to_string());
130 } else if fs::metadata(&arg).is_ok() {
131 let s = fs::read_to_string(&arg)?;
132 return Ok(s.trim_end_matches(['\n', '\r']).to_string());
133 } else {
134 return Ok(arg);
135 }
136 }
137
138 if let Ok(k) = std::env::var("SED_KEY") {
139 return Ok(k.trim_end_matches(['\n', '\r']).to_string());
140 }
141
142 Err(anyhow!("No key provided (stdin, file, or SED_KEY env var)"))
143}
144
145pub fn do_unlock(device: String, key_arg: Option<String>) -> Result<()> {
150 let key = read_key_arg(key_arg)?;
151 if !backend().is_opal_device(&device)? {
152 return Err(anyhow!("{device} does not support OPAL locking"));
153 }
154 backend().unlock_device(&device, &key)
155}
156
157pub fn do_lock(device: String, key_arg: Option<String>) -> Result<()> {
158 let key = read_key_arg(key_arg)?;
159 if !backend().is_opal_device(&device)? {
160 return Err(anyhow!("{device} does not support OPAL locking"));
161 }
162 backend().lock_device(&device, &key)
163}
164
165pub fn do_status(device: String) -> Result<()> {
166 if !backend().is_opal_device(&device)? {
167 return Err(anyhow!("{device} does not support OPAL locking"));
168 }
169 let locked = backend().device_locked(&device)?;
170 let features = backend().get_locking_features(&device)?;
171 backend().print_locking_features(features);
172 println!(
173 "{} is currently {}",
174 device,
175 if locked { "LOCKED" } else { "UNLOCKED" }
176 );
177 Ok(())
178}