devicemapper/
shared.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5// A module to contain functionality shared among the various types of
6// devices.
7
8use 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
25/// Number of bytes in Struct_dm_target_spec::target_type field.
26const DM_TARGET_TYPE_LEN: usize = 16;
27
28str_id!(TargetType, TargetTypeBuf, DM_TARGET_TYPE_LEN, err_func);
29
30/// The trait for properties of the params string of TargetType
31pub trait TargetParams: Clone + fmt::Debug + fmt::Display + Eq + FromStr + PartialEq {
32    /// Return the param string only
33    fn param_str(&self) -> String;
34
35    /// Return the target type
36    fn target_type(&self) -> TargetTypeBuf;
37}
38
39/// One line of a device mapper table.
40#[derive(Clone, Debug, Eq, PartialEq)]
41pub struct TargetLine<T: TargetParams> {
42    /// The start of the segment
43    pub start: Sectors,
44    /// The length of the segment
45    pub length: Sectors,
46    /// The target specific parameters
47    pub params: T,
48}
49
50impl<T: TargetParams> TargetLine<T> {
51    /// Make a new TargetLine struct
52    pub fn new(start: Sectors, length: Sectors, params: T) -> TargetLine<T> {
53        TargetLine {
54            start,
55            length,
56            params,
57        }
58    }
59}
60
61/// Manages a target's table
62pub trait TargetTable: Clone + fmt::Debug + fmt::Display + Eq + PartialEq + Sized {
63    /// Constructs a table from a raw table returned by DM::table_status()
64    fn from_raw_table(table: &[(u64, u64, String, String)]) -> DmResult<Self>;
65
66    /// Generates a table that can be loaded by DM::table_load()
67    fn to_raw_table(&self) -> Vec<(u64, u64, String, String)>;
68}
69
70/// A trait capturing some shared properties of DM devices.
71pub trait DmDevice<T: TargetTable> {
72    /// The device.
73    fn device(&self) -> Device;
74
75    /// The device's device node.
76    fn devnode(&self) -> PathBuf;
77
78    /// Check if tables indicate an equivalent device.
79    fn equivalent_tables(left: &T, right: &T) -> DmResult<bool>;
80
81    /// Read the devicemapper table
82    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    /// The device's name.
89    fn name(&self) -> &DmName;
90
91    /// Resume I/O on the device.
92    fn resume(&mut self, dm: &DM) -> DmResult<()> {
93        dm.device_suspend(&DevId::Name(self.name()), DmOptions::private())?;
94        Ok(())
95    }
96
97    /// The number of sectors available for user data.
98    fn size(&self) -> Sectors;
99
100    /// Suspend I/O on the device.
101    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    /// What the device thinks its table is.
112    fn table(&self) -> &T;
113
114    /// Load a table
115    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    /// Erase the kernel's memory of this device.
121    fn teardown(&mut self, dm: &DM) -> DmResult<()>;
122
123    /// The device's UUID, if available.
124    /// Note that the UUID is not any standard UUID format.
125    fn uuid(&self) -> Option<&DmUuid>;
126}
127
128/// Send a message that expects no reply to target device.
129pub 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
134/// Create a device, load a table, and resume it allowing the caller to specify the DmOptions for
135/// resuming.
136pub 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
158/// Verify that kernel data matches arguments passed.
159pub 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
186/// Check if a device of the given name exists.
187pub fn device_exists(dm: &DM, name: &DmName) -> DmResult<bool> {
188    dm.list_devices()
189        .map(|l| l.iter().any(|(n, _, _)| &**n == name))
190}
191
192/// Parse a device from either of a path or a maj:min pair
193pub 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
209/// Parse a value or return an error.
210pub 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
222/// Get fields for a single status line.
223/// Return an error if an insufficient number of fields are obtained.
224pub 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
238/// Get unique status element from a status result.
239/// Return an error if an incorrect number of lines is obtained.
240pub 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
259/// Construct an error when parsing yields an unexpected value.
260/// Indicate the location of the unexpected value, 1-indexed, its actual
261/// value, and the name of the expected thing.
262pub 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}