devicemapper/
lineardev.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
5use std::{collections::HashSet, fmt, path::PathBuf, str::FromStr};
6
7use crate::{
8    core::{DevId, Device, DeviceInfo, DmFlags, DmName, DmOptions, DmUuid, DM},
9    result::{DmError, DmResult, ErrorEnum},
10    shared::{
11        device_create, device_exists, device_match, parse_device, parse_value, DmDevice,
12        TargetLine, TargetParams, TargetTable, TargetTypeBuf,
13    },
14    units::Sectors,
15};
16
17const FLAKEY_TARGET_NAME: &str = "flakey";
18const LINEAR_TARGET_NAME: &str = "linear";
19
20/// Struct representing params for a linear target
21#[derive(Clone, Debug, Eq, PartialEq)]
22pub struct LinearTargetParams {
23    /// Device on which this segment resides.
24    pub device: Device,
25    /// Start offset in device on which this segment resides.
26    pub start_offset: Sectors,
27}
28
29impl LinearTargetParams {
30    /// Create a new LinearTargetParams struct
31    pub fn new(device: Device, start_offset: Sectors) -> LinearTargetParams {
32        LinearTargetParams {
33            device,
34            start_offset,
35        }
36    }
37}
38
39impl fmt::Display for LinearTargetParams {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        write!(f, "{} {}", LINEAR_TARGET_NAME, self.param_str())
42    }
43}
44
45impl FromStr for LinearTargetParams {
46    type Err = DmError;
47
48    fn from_str(s: &str) -> DmResult<LinearTargetParams> {
49        let vals = s.split(' ').collect::<Vec<_>>();
50        if vals.len() != 3 {
51            let err_msg = format!(
52                "expected 3 values in params string \"{}\", found {}",
53                s,
54                vals.len()
55            );
56            return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
57        }
58
59        if vals[0] != LINEAR_TARGET_NAME {
60            let err_msg = format!(
61                "Expected a linear target entry but found target type {}",
62                vals[0]
63            );
64            return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
65        }
66
67        let device = parse_device(vals[1], "block device for linear target")?;
68        let start = Sectors(parse_value(vals[2], "physical start offset")?);
69
70        Ok(LinearTargetParams::new(device, start))
71    }
72}
73
74impl TargetParams for LinearTargetParams {
75    fn param_str(&self) -> String {
76        format!("{} {}", self.device, *self.start_offset)
77    }
78
79    fn target_type(&self) -> TargetTypeBuf {
80        TargetTypeBuf::new(LINEAR_TARGET_NAME.into()).expect("LINEAR_TARGET_NAME is valid")
81    }
82}
83
84#[derive(Debug, Hash, Clone, Eq, PartialEq)]
85pub enum Direction {
86    Reads,
87    Writes,
88}
89
90impl fmt::Display for Direction {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        match self {
93            Direction::Reads => write!(f, "r"),
94            Direction::Writes => write!(f, "w"),
95        }
96    }
97}
98
99impl FromStr for Direction {
100    type Err = DmError;
101    fn from_str(s: &str) -> DmResult<Direction> {
102        if s == "r" {
103            Ok(Direction::Reads)
104        } else if s == "w" {
105            Ok(Direction::Writes)
106        } else {
107            let err_msg = format!("Expected r or w, found {s}");
108            Err(DmError::Dm(ErrorEnum::Invalid, err_msg))
109        }
110    }
111}
112
113/// Flakey target optional feature parameters:
114/// If no feature parameters are present, during the periods of
115/// unreliability, all I/O returns errors.
116#[derive(Debug, Hash, Clone, Eq, PartialEq)]
117pub enum FeatureArg {
118    /// drop_writes:
119    ///
120    /// All write I/O is silently ignored.
121    /// Read I/O is handled correctly.
122    DropWrites,
123    /// error_writes:
124    ///
125    /// All write I/O is failed with an error signalled.
126    /// Read I/O is handled correctly.
127    ErrorWrites,
128    /// corrupt_bio_byte <Nth_byte> <direction> <value> <flags>:
129    ///
130    /// During <down interval>, replace <Nth_byte> of the data of
131    /// each matching bio with <value>.
132    ///
133    /// <Nth_byte>: The offset of the byte to replace.
134    ///             Counting starts at 1, to replace the first byte.
135    /// <direction>: Either 'r' to corrupt reads or 'w' to corrupt writes.
136    ///             'w' is incompatible with drop_writes.
137    /// <value>:    The value (from 0-255) to write.
138    /// <flags>:    Perform the replacement only if bio->bi_opf has all the
139    ///             selected flags set.
140    CorruptBioByte(u64, Direction, u8, u64),
141}
142
143impl fmt::Display for FeatureArg {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        match self {
146            FeatureArg::DropWrites => write!(f, "drop_writes"),
147            FeatureArg::ErrorWrites => write!(f, "error_writes"),
148            FeatureArg::CorruptBioByte(offset, direction, value, flags) => {
149                write!(f, "corrupt_bio_byte {offset} {direction} {value} {flags}")
150            }
151        }
152    }
153}
154
155/// Target params for flakey target
156#[derive(Clone, Debug, Eq, PartialEq)]
157pub struct FlakeyTargetParams {
158    /// The device on which this segment resides
159    pub device: Device,
160    /// The starting offset of this segments in the device.
161    pub start_offset: Sectors,
162    /// Interval during which flakey target is up, in seconds
163    /// DM source type is unsigned, so restrict to u32.
164    pub up_interval: u32,
165    /// Interval during which flakey target is down, in seconds
166    /// DM source type is unsigned, so restrict to u32.
167    pub down_interval: u32,
168    /// Optional feature arguments
169    pub feature_args: HashSet<FeatureArg>,
170}
171
172impl FlakeyTargetParams {
173    /// Create a new flakey target param struct.
174    pub fn new(
175        device: Device,
176        start_offset: Sectors,
177        up_interval: u32,
178        down_interval: u32,
179        feature_args: Vec<FeatureArg>,
180    ) -> FlakeyTargetParams {
181        FlakeyTargetParams {
182            device,
183            start_offset,
184            up_interval,
185            down_interval,
186            feature_args: feature_args.into_iter().collect::<HashSet<_>>(),
187        }
188    }
189}
190
191impl fmt::Display for FlakeyTargetParams {
192    /// Generate params to be passed to DM.  The format of the params is:
193    ///
194    /// ```plain
195    /// <dev path> <offset> <up interval> <down interval> [<num_features> [<feature arguments>]]
196    /// ```
197    ///
198    /// Mandatory parameters:
199    ///
200    /// * `<dev path>`: Full pathname to the underlying block-device, or a
201    ///                "major:minor" device-number.
202    /// * `<offset>`: Starting sector within the device.
203    /// * `<up interval>`: Number of seconds device is available.
204    /// * `<down interval>`: Number of seconds device returns errors.
205    ///
206    /// Optional feature parameters:
207    ///  If no feature parameters are present, during the periods of
208    ///  unreliability, all I/O returns errors.
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        write!(f, "{} {}", FLAKEY_TARGET_NAME, self.param_str())
211    }
212}
213
214impl FromStr for FlakeyTargetParams {
215    type Err = DmError;
216
217    fn from_str(s: &str) -> DmResult<FlakeyTargetParams> {
218        fn parse_feature_args(vals: &[&str]) -> DmResult<Vec<FeatureArg>> {
219            let mut vals_iter = vals.iter();
220            let mut result: Vec<FeatureArg> = Vec::new();
221            while let Some(x) = vals_iter.next() {
222                match x {
223                    &"drop_writes" => result.push(FeatureArg::DropWrites),
224                    &"error_writes" => result.push(FeatureArg::ErrorWrites),
225                    &"corrupt_bio_byte" => {
226                        let offset = vals_iter
227                            .next()
228                            .ok_or({
229                                let err_msg = "corrupt_bio_byte takes 4 parameters";
230                                DmError::Dm(ErrorEnum::Invalid, err_msg.to_string())
231                            })
232                            .and_then(|s| parse_value::<u64>(s, "offset"))?;
233
234                        let direction = vals_iter
235                            .next()
236                            .ok_or({
237                                let err_msg = "corrupt_bio_byte takes 4 parameters";
238                                DmError::Dm(ErrorEnum::Invalid, err_msg.to_string())
239                            })
240                            .and_then(|s| parse_value::<Direction>(s, "direction"))?;
241
242                        let value = vals_iter
243                            .next()
244                            .ok_or({
245                                let err_msg = "corrupt_bio_byte takes 4 parameters";
246                                DmError::Dm(ErrorEnum::Invalid, err_msg.to_string())
247                            })
248                            .and_then(|s| parse_value::<u8>(s, "value"))?;
249
250                        let flags = vals_iter
251                            .next()
252                            .ok_or({
253                                let err_msg = "corrupt_bio_byte takes 4 parameters";
254                                DmError::Dm(ErrorEnum::Invalid, err_msg.to_string())
255                            })
256                            .and_then(|s| parse_value::<u64>(s, "flags"))?;
257
258                        result.push(FeatureArg::CorruptBioByte(offset, direction, value, flags));
259                    }
260                    x => {
261                        let err_msg = format!("{x} is an unrecognized feature parameter");
262                        return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
263                    }
264                }
265            }
266
267            Ok(result)
268        }
269
270        let vals = s.split(' ').collect::<Vec<_>>();
271
272        if vals.len() < 5 {
273            let err_msg = format!(
274                "expected at least five values in params string \"{}\", found {}",
275                s,
276                vals.len()
277            );
278            return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
279        }
280
281        if vals[0] != FLAKEY_TARGET_NAME {
282            let err_msg = format!(
283                "Expected a flakey target entry but found target type {}",
284                vals[0]
285            );
286            return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
287        }
288
289        let device = parse_device(vals[1], "block device for flakey target")?;
290        let start_offset = Sectors(parse_value(vals[2], "physical start offset")?);
291
292        let up_interval = parse_value(vals[3], "up interval")?;
293        let down_interval = parse_value(vals[4], "down interval")?;
294
295        let feature_args = if vals.len() == 5 {
296            vec![]
297        } else {
298            parse_feature_args(
299                &vals[6..6 + parse_value::<usize>(vals[5], "number of feature args")?],
300            )?
301        };
302
303        Ok(FlakeyTargetParams::new(
304            device,
305            start_offset,
306            up_interval,
307            down_interval,
308            feature_args,
309        ))
310    }
311}
312
313impl TargetParams for FlakeyTargetParams {
314    fn param_str(&self) -> String {
315        let feature_args = if self.feature_args.is_empty() {
316            "0".to_owned()
317        } else {
318            format!(
319                "{} {}",
320                self.feature_args.len(),
321                self.feature_args
322                    .iter()
323                    .map(|x| x.to_string())
324                    .collect::<Vec<_>>()
325                    .join(" ")
326            )
327        };
328
329        format!(
330            "{} {} {} {} {}",
331            self.device, *self.start_offset, self.up_interval, self.down_interval, feature_args
332        )
333    }
334
335    fn target_type(&self) -> TargetTypeBuf {
336        TargetTypeBuf::new(FLAKEY_TARGET_NAME.into()).expect("FLAKEY_TARGET_NAME is valid")
337    }
338}
339
340/// Target params for linear dev. These are either flakey or linear.
341#[derive(Clone, Debug, Eq, PartialEq)]
342pub enum LinearDevTargetParams {
343    /// A flakey target
344    Flakey(FlakeyTargetParams),
345    /// A linear target
346    Linear(LinearTargetParams),
347}
348
349impl fmt::Display for LinearDevTargetParams {
350    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351        match *self {
352            LinearDevTargetParams::Flakey(ref flakey) => flakey.fmt(f),
353            LinearDevTargetParams::Linear(ref linear) => linear.fmt(f),
354        }
355    }
356}
357
358impl FromStr for LinearDevTargetParams {
359    type Err = DmError;
360
361    fn from_str(s: &str) -> DmResult<LinearDevTargetParams> {
362        let target_type = Some(s.split_once(' ').map_or(s, |x| x.0)).ok_or_else(|| {
363            DmError::Dm(
364                ErrorEnum::Invalid,
365                format!("target line string \"{s}\" did not contain any values"),
366            )
367        })?;
368        if target_type == FLAKEY_TARGET_NAME {
369            Ok(LinearDevTargetParams::Flakey(
370                s.parse::<FlakeyTargetParams>()?,
371            ))
372        } else if target_type == LINEAR_TARGET_NAME {
373            Ok(LinearDevTargetParams::Linear(
374                s.parse::<LinearTargetParams>()?,
375            ))
376        } else {
377            Err(DmError::Dm(
378                ErrorEnum::Invalid,
379                format!("unexpected target type \"{target_type}\""),
380            ))
381        }
382    }
383}
384
385impl TargetParams for LinearDevTargetParams {
386    fn param_str(&self) -> String {
387        match *self {
388            LinearDevTargetParams::Flakey(ref flakey) => flakey.param_str(),
389            LinearDevTargetParams::Linear(ref linear) => linear.param_str(),
390        }
391    }
392
393    fn target_type(&self) -> TargetTypeBuf {
394        match *self {
395            LinearDevTargetParams::Flakey(ref flakey) => flakey.target_type(),
396            LinearDevTargetParams::Linear(ref linear) => linear.target_type(),
397        }
398    }
399}
400
401/// A target table for a linear device. Such a table allows flakey targets
402/// as well as linear targets.
403#[derive(Clone, Debug, Eq, PartialEq)]
404pub struct LinearDevTargetTable {
405    /// The device's table
406    pub table: Vec<TargetLine<LinearDevTargetParams>>,
407}
408
409impl LinearDevTargetTable {
410    /// Make a new LinearDevTargetTable from a suitable vec
411    pub fn new(table: Vec<TargetLine<LinearDevTargetParams>>) -> LinearDevTargetTable {
412        LinearDevTargetTable { table }
413    }
414}
415
416impl fmt::Display for LinearDevTargetTable {
417    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
418        for line in &self.table {
419            writeln!(f, "{} {} {}", *line.start, *line.length, line.params)?;
420        }
421        Ok(())
422    }
423}
424
425impl TargetTable for LinearDevTargetTable {
426    fn from_raw_table(table: &[(u64, u64, String, String)]) -> DmResult<LinearDevTargetTable> {
427        Ok(LinearDevTargetTable {
428            table: table
429                .iter()
430                .map(|x| -> DmResult<TargetLine<LinearDevTargetParams>> {
431                    Ok(TargetLine::new(
432                        Sectors(x.0),
433                        Sectors(x.1),
434                        format!("{} {}", x.2, x.3).parse::<LinearDevTargetParams>()?,
435                    ))
436                })
437                .collect::<DmResult<Vec<_>>>()?,
438        })
439    }
440
441    fn to_raw_table(&self) -> Vec<(u64, u64, String, String)> {
442        self.table
443            .iter()
444            .map(|x| {
445                (
446                    *x.start,
447                    *x.length,
448                    x.params.target_type().to_string(),
449                    x.params.param_str(),
450                )
451            })
452            .collect::<Vec<_>>()
453    }
454}
455
456/// A DM construct of combined Segments
457#[derive(Debug)]
458pub struct LinearDev {
459    /// Data about the device
460    dev_info: Box<DeviceInfo>,
461    table: LinearDevTargetTable,
462}
463
464impl DmDevice<LinearDevTargetTable> for LinearDev {
465    fn device(&self) -> Device {
466        device!(self)
467    }
468
469    fn devnode(&self) -> PathBuf {
470        devnode!(self)
471    }
472
473    // Since linear devices have no default or configuration parameters,
474    // and the ordering of segments matters, two linear devices represent
475    // the same linear device only if their tables match exactly.
476    fn equivalent_tables(
477        left: &LinearDevTargetTable,
478        right: &LinearDevTargetTable,
479    ) -> DmResult<bool> {
480        Ok(left == right)
481    }
482
483    fn name(&self) -> &DmName {
484        name!(self)
485    }
486
487    fn size(&self) -> Sectors {
488        self.table.table.iter().map(|l| l.length).sum()
489    }
490
491    fn table(&self) -> &LinearDevTargetTable {
492        table!(self)
493    }
494
495    fn teardown(&mut self, dm: &DM) -> DmResult<()> {
496        dm.device_remove(&DevId::Name(self.name()), DmOptions::default())?;
497        Ok(())
498    }
499
500    fn uuid(&self) -> Option<&DmUuid> {
501        uuid!(self)
502    }
503}
504
505/// Use DM to concatenate a list of segments together into a
506/// linear block device of continuous sectors.
507impl LinearDev {
508    /// Construct a new block device by concatenating the given segments
509    /// into linear space.
510    /// If the device is already known to the kernel, just verifies that the
511    /// segments argument passed exactly matches the kernel data.
512    ///
513    /// Warning: If the segments overlap, this method will succeed. However,
514    /// the behavior of the linear device in that case should be treated as
515    /// undefined.
516    ///
517    /// Note: A linear device is just a mapping in the kernel from locations
518    /// in that device to locations on other devices which are specified by
519    /// their device number. There is usually a device node so that data can
520    /// be read from and written to the device. Other than that, it really
521    /// has no existence. Consequently, there is no conflict in overloading
522    /// this method to mean both "make a wholly new device" and "establish
523    /// the existence of the requested device". Of course, a linear device
524    /// is usually expected to hold data, so it is important to get the
525    /// mapping just right.
526    pub fn setup(
527        dm: &DM,
528        name: &DmName,
529        uuid: Option<&DmUuid>,
530        table: Vec<TargetLine<LinearDevTargetParams>>,
531    ) -> DmResult<LinearDev> {
532        let table = LinearDevTargetTable::new(table);
533        let dev = if device_exists(dm, name)? {
534            let dev_info = dm.device_info(&DevId::Name(name))?;
535            let dev = LinearDev {
536                dev_info: Box::new(dev_info),
537                table,
538            };
539            device_match(dm, &dev, uuid)?;
540            dev
541        } else {
542            let dev_info = device_create(dm, name, uuid, &table, DmOptions::private())?;
543            LinearDev {
544                dev_info: Box::new(dev_info),
545                table,
546            }
547        };
548        Ok(dev)
549    }
550
551    /// Set the segments for this linear device.
552    /// This action puts the device in a state where it is ready to be resumed.
553    /// Warning: It is the client's responsibility to make sure the designated
554    /// segments are compatible with the device's existing segments.
555    /// If they are not, this function will still succeed, but some kind of
556    /// data corruption will be the inevitable result.
557    pub fn set_table(
558        &mut self,
559        dm: &DM,
560        table: Vec<TargetLine<LinearDevTargetParams>>,
561    ) -> DmResult<()> {
562        let table = LinearDevTargetTable::new(table);
563        self.suspend(dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))?;
564        self.table_load(dm, &table, DmOptions::default())?;
565        self.table = table;
566        Ok(())
567    }
568
569    /// Set the name for this LinearDev.
570    pub fn set_name(&mut self, dm: &DM, name: &DmName) -> DmResult<()> {
571        if self.name() == name {
572            return Ok(());
573        }
574        dm.device_rename(self.name(), &DevId::Name(name))?;
575        self.dev_info = Box::new(dm.device_info(&DevId::Name(name))?);
576        Ok(())
577    }
578}
579
580#[cfg(test)]
581mod tests {
582    use std::{clone::Clone, fs::OpenOptions, path::Path};
583
584    use crate::{
585        core::{devnode_to_devno, Device},
586        testing::{blkdev_size, test_name, test_with_spec},
587    };
588
589    use super::*;
590
591    /// Verify that a new linear dev with 0 segments fails.
592    fn test_empty(_paths: &[&Path]) {
593        assert_matches!(
594            LinearDev::setup(
595                &DM::new().unwrap(),
596                &test_name("new").expect("valid format"),
597                None,
598                vec![],
599            ),
600            Err(_)
601        );
602    }
603
604    /// Verify that setting an empty table on an existing DM device fails.
605    fn test_empty_table_set(paths: &[&Path]) {
606        assert!(!paths.is_empty());
607
608        let dm = DM::new().unwrap();
609        let name = test_name("name").expect("valid format");
610        let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap());
611        let params = LinearTargetParams::new(dev, Sectors(0));
612        let table = vec![TargetLine::new(
613            Sectors(0),
614            Sectors(1),
615            LinearDevTargetParams::Linear(params),
616        )];
617        let mut ld = LinearDev::setup(&dm, &name, None, table).unwrap();
618
619        assert_matches!(ld.set_table(&dm, vec![]), Err(_));
620        ld.resume(&dm).unwrap();
621        ld.teardown(&dm).unwrap();
622    }
623
624    /// Verify that id rename succeeds.
625    fn test_rename_id(paths: &[&Path]) {
626        assert!(!paths.is_empty());
627
628        let dm = DM::new().unwrap();
629        let name = test_name("name").expect("valid format");
630        let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap());
631        let params = LinearTargetParams::new(dev, Sectors(0));
632        let table = vec![TargetLine::new(
633            Sectors(0),
634            Sectors(1),
635            LinearDevTargetParams::Linear(params),
636        )];
637        let mut ld = LinearDev::setup(&dm, &name, None, table).unwrap();
638
639        ld.set_name(&dm, &name).unwrap();
640        assert_eq!(ld.name(), &*name);
641
642        ld.teardown(&dm).unwrap();
643    }
644
645    /// Verify that after a rename, the device has the new name.
646    fn test_rename(paths: &[&Path]) {
647        assert!(!paths.is_empty());
648
649        let dm = DM::new().unwrap();
650        let name = test_name("name").expect("valid format");
651        let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap());
652        let params = LinearTargetParams::new(dev, Sectors(0));
653        let table = vec![TargetLine::new(
654            Sectors(0),
655            Sectors(1),
656            LinearDevTargetParams::Linear(params),
657        )];
658        let mut ld = LinearDev::setup(&dm, &name, None, table).unwrap();
659
660        let new_name = test_name("new_name").expect("valid format");
661        ld.set_name(&dm, &new_name).unwrap();
662        assert_eq!(ld.name(), &*new_name);
663
664        ld.teardown(&dm).unwrap();
665    }
666
667    /// Verify that passing the same segments two times gets two segments.
668    /// Verify that the size of the devnode is the size of the sum of the
669    /// ranges of the segments. Verify that the table contains entries for both
670    /// segments.
671    fn test_duplicate_segments(paths: &[&Path]) {
672        assert!(!paths.is_empty());
673
674        let dm = DM::new().unwrap();
675        let name = test_name("name").expect("valid format");
676        let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap());
677        let params = LinearTargetParams::new(dev, Sectors(0));
678        let table = vec![
679            TargetLine::new(
680                Sectors(0),
681                Sectors(1),
682                LinearDevTargetParams::Linear(params.clone()),
683            ),
684            TargetLine::new(
685                Sectors(1),
686                Sectors(1),
687                LinearDevTargetParams::Linear(params),
688            ),
689        ];
690        let range: Sectors = table.iter().map(|s| s.length).sum();
691        let count = table.len();
692        let mut ld = LinearDev::setup(&dm, &name, None, table).unwrap();
693
694        let table = LinearDev::read_kernel_table(&dm, &DevId::Name(ld.name()))
695            .unwrap()
696            .table;
697        assert_eq!(table.len(), count);
698        assert_matches!(table[0].params, LinearDevTargetParams::Linear(ref device) if device.device == dev);
699        assert_matches!(table[1].params, LinearDevTargetParams::Linear(ref device) if device.device == dev);
700
701        assert_eq!(
702            blkdev_size(&OpenOptions::new().read(true).open(ld.devnode()).unwrap()).sectors(),
703            range
704        );
705
706        ld.teardown(&dm).unwrap();
707    }
708
709    /// Use five segments, each distinct. If parsing works correctly,
710    /// default table should match extracted table.
711    fn test_several_segments(paths: &[&Path]) {
712        assert!(!paths.is_empty());
713
714        let dm = DM::new().unwrap();
715        let name = test_name("name").expect("valid format");
716        let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap());
717        let table = (0..5)
718            .map(|n| {
719                TargetLine::new(
720                    Sectors(n),
721                    Sectors(1),
722                    LinearDevTargetParams::Linear(LinearTargetParams::new(dev, Sectors(n))),
723                )
724            })
725            .collect::<Vec<_>>();
726        let mut ld = LinearDev::setup(&dm, &name, None, table.clone()).unwrap();
727
728        let loaded_table = LinearDev::read_kernel_table(&dm, &DevId::Name(ld.name())).unwrap();
729        assert!(
730            LinearDev::equivalent_tables(&LinearDevTargetTable::new(table), &loaded_table).unwrap()
731        );
732
733        ld.teardown(&dm).unwrap();
734    }
735
736    /// Verify that constructing a second dev with the same name succeeds
737    /// only if it has the same list of segments.
738    fn test_same_name(paths: &[&Path]) {
739        assert!(!paths.is_empty());
740
741        let dm = DM::new().unwrap();
742        let name = test_name("name").expect("valid format");
743        let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap());
744        let params = LinearTargetParams::new(dev, Sectors(0));
745        let table = vec![TargetLine::new(
746            Sectors(0),
747            Sectors(1),
748            LinearDevTargetParams::Linear(params),
749        )];
750        let mut ld = LinearDev::setup(&dm, &name, None, table.clone()).unwrap();
751        let params2 = LinearTargetParams::new(dev, Sectors(1));
752        let table2 = vec![TargetLine::new(
753            Sectors(0),
754            Sectors(1),
755            LinearDevTargetParams::Linear(params2),
756        )];
757        assert_matches!(LinearDev::setup(&dm, &name, None, table2), Err(_));
758        assert_matches!(LinearDev::setup(&dm, &name, None, table), Ok(_));
759        ld.teardown(&dm).unwrap();
760    }
761
762    /// Verify constructing a second linear dev with the same segment succeeds.
763    fn test_same_segment(paths: &[&Path]) {
764        assert!(!paths.is_empty());
765
766        let dm = DM::new().unwrap();
767        let name = test_name("name").expect("valid format");
768        let ersatz = test_name("ersatz").expect("valid format");
769        let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap());
770        let params = LinearTargetParams::new(dev, Sectors(0));
771        let table = vec![TargetLine::new(
772            Sectors(0),
773            Sectors(1),
774            LinearDevTargetParams::Linear(params),
775        )];
776        let mut ld = LinearDev::setup(&dm, &name, None, table.clone()).unwrap();
777        let ld2 = LinearDev::setup(&dm, &ersatz, None, table);
778        assert_matches!(ld2, Ok(_));
779
780        ld2.unwrap().teardown(&dm).unwrap();
781        ld.teardown(&dm).unwrap();
782    }
783
784    /// Verify that suspending and immediately resuming doesn't fail.
785    fn test_suspend(paths: &[&Path]) {
786        assert!(!paths.is_empty());
787
788        let dm = DM::new().unwrap();
789        let name = test_name("name").expect("valid format");
790        let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap());
791        let params = LinearTargetParams::new(dev, Sectors(0));
792        let table = vec![TargetLine::new(
793            Sectors(0),
794            Sectors(1),
795            LinearDevTargetParams::Linear(params),
796        )];
797        let mut ld = LinearDev::setup(&dm, &name, None, table).unwrap();
798
799        ld.suspend(&dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))
800            .unwrap();
801        ld.suspend(&dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))
802            .unwrap();
803        ld.resume(&dm).unwrap();
804        ld.resume(&dm).unwrap();
805
806        ld.teardown(&dm).unwrap();
807    }
808
809    #[test]
810    fn test_flakey_target_params_zero() {
811        let result = "flakey 8:32 0 16 2 0"
812            .parse::<FlakeyTargetParams>()
813            .unwrap();
814        assert_eq!(result.feature_args, HashSet::new());
815    }
816
817    #[test]
818    fn test_flakey_target_params_none() {
819        let result = "flakey 8:32 0 16 2".parse::<FlakeyTargetParams>().unwrap();
820        assert_eq!(result.feature_args, HashSet::new());
821    }
822
823    #[test]
824    fn test_flakey_target_params_drop_writes() {
825        let result = "flakey 8:32 0 16 2 1 drop_writes"
826            .parse::<FlakeyTargetParams>()
827            .unwrap();
828        let expected = [FeatureArg::DropWrites]
829            .iter()
830            .cloned()
831            .collect::<HashSet<_>>();
832        assert_eq!(result.feature_args, expected);
833    }
834
835    #[test]
836    fn test_flakey_target_params_error_writes() {
837        let result = "flakey 8:32 0 16 2 1 error_writes"
838            .parse::<FlakeyTargetParams>()
839            .unwrap();
840        let expected = [FeatureArg::ErrorWrites]
841            .iter()
842            .cloned()
843            .collect::<HashSet<_>>();
844        assert_eq!(result.feature_args, expected);
845    }
846
847    #[test]
848    fn test_flakey_target_params_corrupt_bio_byte_reads() {
849        let result = "flakey 8:32 0 16 2 5 corrupt_bio_byte 32 r 1 0"
850            .parse::<FlakeyTargetParams>()
851            .unwrap();
852        let expected = [FeatureArg::CorruptBioByte(32, Direction::Reads, 1, 0)]
853            .iter()
854            .cloned()
855            .collect::<HashSet<_>>();
856        assert_eq!(result.feature_args, expected);
857    }
858
859    #[test]
860    fn test_flakey_target_params_corrupt_bio_byte_writes() {
861        let result = "flakey 8:32 0 16 2 5 corrupt_bio_byte 224 w 0 32"
862            .parse::<FlakeyTargetParams>()
863            .unwrap();
864        let expected = [FeatureArg::CorruptBioByte(224, Direction::Writes, 0, 32)]
865            .iter()
866            .cloned()
867            .collect::<HashSet<_>>();
868        assert_eq!(result.feature_args, expected);
869    }
870
871    #[test]
872    fn test_flakey_target_params_corrupt_bio_byte_and_drop_writes() {
873        let result = "flakey 8:32 0 16 2 6 corrupt_bio_byte 32 r 1 0 drop_writes"
874            .parse::<FlakeyTargetParams>()
875            .unwrap();
876        let expected = [
877            FeatureArg::CorruptBioByte(32, Direction::Reads, 1, 0),
878            FeatureArg::DropWrites,
879        ]
880        .iter()
881        .cloned()
882        .collect::<HashSet<_>>();
883        assert_eq!(result.feature_args, expected);
884    }
885
886    #[test]
887    fn test_flakey_target_params_drop_writes_and_corrupt_bio_byte() {
888        let result = "flakey 8:32 0 16 2 6 corrupt_bio_byte 32 r 1 0 drop_writes"
889            .parse::<FlakeyTargetParams>()
890            .unwrap();
891        let expected = [
892            FeatureArg::DropWrites,
893            FeatureArg::CorruptBioByte(32, Direction::Reads, 1, 0),
894        ]
895        .iter()
896        .cloned()
897        .collect::<HashSet<_>>();
898        assert_eq!(result.feature_args, expected);
899    }
900
901    #[test]
902    fn test_flakey_target_params_error_writes_and_drop_writes() {
903        let result = "flakey 8:32 0 16 2 2 error_writes drop_writes"
904            .parse::<FlakeyTargetParams>()
905            .unwrap();
906        let expected = [FeatureArg::ErrorWrites, FeatureArg::DropWrites]
907            .iter()
908            .cloned()
909            .collect::<HashSet<_>>();
910        assert_eq!(result.feature_args, expected);
911    }
912
913    #[test]
914    fn loop_test_duplicate_segments() {
915        test_with_spec(1, test_duplicate_segments);
916    }
917
918    #[test]
919    fn loop_test_empty() {
920        test_with_spec(0, test_empty);
921    }
922
923    #[test]
924    fn loop_test_empty_table_set() {
925        test_with_spec(1, test_empty_table_set);
926    }
927
928    #[test]
929    fn loop_test_rename() {
930        test_with_spec(1, test_rename);
931    }
932
933    #[test]
934    fn loop_test_rename_id() {
935        test_with_spec(1, test_rename_id);
936    }
937
938    #[test]
939    fn loop_test_same_name() {
940        test_with_spec(1, test_same_name);
941    }
942
943    #[test]
944    fn loop_test_segment() {
945        test_with_spec(1, test_same_segment);
946    }
947
948    #[test]
949    fn loop_test_several_segments() {
950        test_with_spec(1, test_several_segments);
951    }
952
953    #[test]
954    fn loop_test_suspend() {
955        test_with_spec(1, test_suspend);
956    }
957}