1use std::{
9 fmt,
10 ops::Deref,
11 path::{Path, PathBuf},
12 str::FromStr,
13};
14
15use crate::{
16 core::{devnode_to_devno, DevId, Device, DeviceInfo, DmFlags, DmName, DmOptions, DmUuid, DM},
17 result::{DmError, DmResult, ErrorEnum},
18 units::Sectors,
19};
20
21fn err_func(err_msg: &str) -> DmError {
22 DmError::Dm(ErrorEnum::Invalid, err_msg.into())
23}
24
25const DM_TARGET_TYPE_LEN: usize = 16;
27
28str_id!(TargetType, TargetTypeBuf, DM_TARGET_TYPE_LEN, err_func);
29
30pub trait TargetParams: Clone + fmt::Debug + fmt::Display + Eq + FromStr + PartialEq {
32 fn param_str(&self) -> String;
34
35 fn target_type(&self) -> TargetTypeBuf;
37}
38
39#[derive(Clone, Debug, Eq, PartialEq)]
41pub struct TargetLine<T: TargetParams> {
42 pub start: Sectors,
44 pub length: Sectors,
46 pub params: T,
48}
49
50impl<T: TargetParams> TargetLine<T> {
51 pub fn new(start: Sectors, length: Sectors, params: T) -> TargetLine<T> {
53 TargetLine {
54 start,
55 length,
56 params,
57 }
58 }
59}
60
61pub trait TargetTable: Clone + fmt::Debug + fmt::Display + Eq + PartialEq + Sized {
63 fn from_raw_table(table: &[(u64, u64, String, String)]) -> DmResult<Self>;
65
66 fn to_raw_table(&self) -> Vec<(u64, u64, String, String)>;
68}
69
70pub trait DmDevice<T: TargetTable> {
72 fn device(&self) -> Device;
74
75 fn devnode(&self) -> PathBuf;
77
78 fn equivalent_tables(left: &T, right: &T) -> DmResult<bool>;
80
81 fn read_kernel_table(dm: &DM, id: &DevId<'_>) -> DmResult<T> {
83 let (_, table) =
84 dm.table_status(id, DmOptions::default().set_flags(DmFlags::DM_STATUS_TABLE))?;
85 T::from_raw_table(&table)
86 }
87
88 fn name(&self) -> &DmName;
90
91 fn resume(&mut self, dm: &DM) -> DmResult<()> {
93 dm.device_suspend(&DevId::Name(self.name()), DmOptions::private())?;
94 Ok(())
95 }
96
97 fn size(&self) -> Sectors;
99
100 fn suspend(&mut self, dm: &DM, options: DmOptions) -> DmResult<()> {
102 dm.device_suspend(
103 &DevId::Name(self.name()),
104 DmOptions::default()
105 .set_flags(DmFlags::DM_SUSPEND | options.flags())
106 .set_udev_flags(options.udev_flags()),
107 )?;
108 Ok(())
109 }
110
111 fn table(&self) -> &T;
113
114 fn table_load(&self, dm: &DM, table: &T, options: DmOptions) -> DmResult<()> {
116 dm.table_load(&DevId::Name(self.name()), &table.to_raw_table(), options)?;
117 Ok(())
118 }
119
120 fn teardown(&mut self, dm: &DM) -> DmResult<()>;
122
123 fn uuid(&self) -> Option<&DmUuid>;
126}
127
128pub fn message<T: TargetTable, D: DmDevice<T>>(dm: &DM, target: &D, msg: &str) -> DmResult<()> {
130 dm.target_msg(&DevId::Name(target.name()), None, msg)?;
131 Ok(())
132}
133
134pub fn device_create<T: TargetTable>(
137 dm: &DM,
138 name: &DmName,
139 uuid: Option<&DmUuid>,
140 table: &T,
141 suspend_options: DmOptions,
142) -> DmResult<DeviceInfo> {
143 dm.device_create(name, uuid, DmOptions::default())?;
144
145 let id = DevId::Name(name);
146 let dev_info = match dm.table_load(&id, &table.to_raw_table(), DmOptions::default()) {
147 Err(e) => {
148 dm.device_remove(&id, DmOptions::default())?;
149 return Err(e);
150 }
151 Ok(dev_info) => dev_info,
152 };
153 dm.device_suspend(&id, suspend_options)?;
154
155 Ok(dev_info)
156}
157
158pub fn device_match<T: TargetTable, D: DmDevice<T>>(
160 dm: &DM,
161 dev: &D,
162 uuid: Option<&DmUuid>,
163) -> DmResult<()> {
164 let kernel_table = D::read_kernel_table(dm, &DevId::Name(dev.name()))?;
165 let device_table = dev.table();
166 if !D::equivalent_tables(&kernel_table, device_table)? {
167 let err_msg = format!(
168 "Specified new table \"{device_table:?}\" does not match kernel table \"{kernel_table:?}\""
169 );
170
171 return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
172 }
173
174 if dev.uuid() != uuid {
175 let err_msg = format!(
176 "Specified uuid \"{:?}\" does not match kernel uuuid \"{:?}\"",
177 uuid,
178 dev.uuid()
179 );
180
181 return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
182 }
183 Ok(())
184}
185
186pub fn device_exists(dm: &DM, name: &DmName) -> DmResult<bool> {
188 dm.list_devices()
189 .map(|l| l.iter().any(|(n, _, _)| &**n == name))
190}
191
192pub fn parse_device(val: &str, desc: &str) -> DmResult<Device> {
194 let device = if val.starts_with('/') {
195 devnode_to_devno(Path::new(val))?
196 .ok_or_else(|| {
197 DmError::Dm(
198 ErrorEnum::Invalid,
199 format!("Failed to parse \"{desc}\" from input \"{val}\""),
200 )
201 })?
202 .into()
203 } else {
204 val.parse::<Device>()?
205 };
206 Ok(device)
207}
208
209pub fn parse_value<T>(val: &str, desc: &str) -> DmResult<T>
211where
212 T: FromStr,
213{
214 val.parse::<T>().map_err(|_| {
215 DmError::Dm(
216 ErrorEnum::Invalid,
217 format!("Failed to parse value for \"{desc}\" from input \"{val}\""),
218 )
219 })
220}
221
222pub fn get_status_line_fields(status_line: &str, number_required: usize) -> DmResult<Vec<&str>> {
225 let status_vals = status_line.split(' ').collect::<Vec<_>>();
226 let length = status_vals.len();
227 if length < number_required {
228 return Err(DmError::Dm(
229 ErrorEnum::Invalid,
230 format!(
231 "Insufficient number of fields for status; requires at least {number_required}, found only {length} in status line \"{status_line}\""
232 ),
233 ));
234 }
235 Ok(status_vals)
236}
237
238pub fn get_status(status_lines: &[(u64, u64, String, String)]) -> DmResult<String> {
241 let length = status_lines.len();
242 if length != 1 {
243 return Err(DmError::Dm(
244 ErrorEnum::Invalid,
245 format!(
246 "Incorrect number of lines for status; expected 1, found {} in status result \"{}\"",
247 length,
248 status_lines.iter().map(|(s, l, t, v)| format!("{s} {l} {t} {v}")).collect::<Vec<String>>().join(", ")
249 ),
250 ));
251 }
252 Ok(status_lines
253 .first()
254 .expect("if length != 1, already returned")
255 .3
256 .to_owned())
257}
258
259pub fn make_unexpected_value_error(value_index: usize, value: &str, item_name: &str) -> DmError {
263 DmError::Dm(
264 ErrorEnum::Invalid,
265 format!(
266 "Kernel returned unexpected {value_index}th value \"{value}\" for item representing {item_name} in status result"
267 ),
268 )
269}