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, 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 FlakeyFeatureArg {
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 FlakeyFeatureArg {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        match self {
146            FlakeyFeatureArg::DropWrites => write!(f, "drop_writes"),
147            FlakeyFeatureArg::ErrorWrites => write!(f, "error_writes"),
148            FlakeyFeatureArg::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<FlakeyFeatureArg>,
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<FlakeyFeatureArg>,
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<FlakeyFeatureArg>> {
219            let mut vals_iter = vals.iter();
220            let mut result: Vec<FlakeyFeatureArg> = Vec::new();
221            while let Some(x) = vals_iter.next() {
222                match x {
223                    &"drop_writes" => result.push(FlakeyFeatureArg::DropWrites),
224                    &"error_writes" => result.push(FlakeyFeatureArg::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(FlakeyFeatureArg::CorruptBioByte(
259                            offset, direction, value, flags,
260                        ));
261                    }
262                    x => {
263                        let err_msg = format!("{x} is an unrecognized feature parameter");
264                        return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
265                    }
266                }
267            }
268
269            Ok(result)
270        }
271
272        let vals = s.split(' ').collect::<Vec<_>>();
273
274        if vals.len() < 5 {
275            let err_msg = format!(
276                "expected at least five values in params string \"{}\", found {}",
277                s,
278                vals.len()
279            );
280            return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
281        }
282
283        if vals[0] != FLAKEY_TARGET_NAME {
284            let err_msg = format!(
285                "Expected a flakey target entry but found target type {}",
286                vals[0]
287            );
288            return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
289        }
290
291        let device = parse_device(vals[1], "block device for flakey target")?;
292        let start_offset = Sectors(parse_value(vals[2], "physical start offset")?);
293
294        let up_interval = parse_value(vals[3], "up interval")?;
295        let down_interval = parse_value(vals[4], "down interval")?;
296
297        let feature_args = if vals.len() == 5 {
298            vec![]
299        } else {
300            parse_feature_args(
301                &vals[6..6 + parse_value::<usize>(vals[5], "number of feature args")?],
302            )?
303        };
304
305        Ok(FlakeyTargetParams::new(
306            device,
307            start_offset,
308            up_interval,
309            down_interval,
310            feature_args,
311        ))
312    }
313}
314
315impl TargetParams for FlakeyTargetParams {
316    fn param_str(&self) -> String {
317        let feature_args = if self.feature_args.is_empty() {
318            "0".to_owned()
319        } else {
320            format!(
321                "{} {}",
322                self.feature_args.len(),
323                self.feature_args
324                    .iter()
325                    .map(|x| x.to_string())
326                    .collect::<Vec<_>>()
327                    .join(" ")
328            )
329        };
330
331        format!(
332            "{} {} {} {} {}",
333            self.device, *self.start_offset, self.up_interval, self.down_interval, feature_args
334        )
335    }
336
337    fn target_type(&self) -> TargetTypeBuf {
338        TargetTypeBuf::new(FLAKEY_TARGET_NAME.into()).expect("FLAKEY_TARGET_NAME is valid")
339    }
340}
341
342/// Target params for linear dev. These are either flakey or linear.
343#[derive(Clone, Debug, Eq, PartialEq)]
344pub enum LinearDevTargetParams {
345    /// A flakey target
346    Flakey(FlakeyTargetParams),
347    /// A linear target
348    Linear(LinearTargetParams),
349}
350
351impl fmt::Display for LinearDevTargetParams {
352    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353        match *self {
354            LinearDevTargetParams::Flakey(ref flakey) => flakey.fmt(f),
355            LinearDevTargetParams::Linear(ref linear) => linear.fmt(f),
356        }
357    }
358}
359
360impl FromStr for LinearDevTargetParams {
361    type Err = DmError;
362
363    fn from_str(s: &str) -> DmResult<LinearDevTargetParams> {
364        let target_type = Some(s.split_once(' ').map_or(s, |x| x.0)).ok_or_else(|| {
365            DmError::Dm(
366                ErrorEnum::Invalid,
367                format!("target line string \"{s}\" did not contain any values"),
368            )
369        })?;
370        if target_type == FLAKEY_TARGET_NAME {
371            Ok(LinearDevTargetParams::Flakey(
372                s.parse::<FlakeyTargetParams>()?,
373            ))
374        } else if target_type == LINEAR_TARGET_NAME {
375            Ok(LinearDevTargetParams::Linear(
376                s.parse::<LinearTargetParams>()?,
377            ))
378        } else {
379            Err(DmError::Dm(
380                ErrorEnum::Invalid,
381                format!("unexpected target type \"{target_type}\""),
382            ))
383        }
384    }
385}
386
387impl TargetParams for LinearDevTargetParams {
388    fn param_str(&self) -> String {
389        match *self {
390            LinearDevTargetParams::Flakey(ref flakey) => flakey.param_str(),
391            LinearDevTargetParams::Linear(ref linear) => linear.param_str(),
392        }
393    }
394
395    fn target_type(&self) -> TargetTypeBuf {
396        match *self {
397            LinearDevTargetParams::Flakey(ref flakey) => flakey.target_type(),
398            LinearDevTargetParams::Linear(ref linear) => linear.target_type(),
399        }
400    }
401}
402
403/// A target table for a linear device. Such a table allows flakey targets
404/// as well as linear targets.
405#[derive(Clone, Debug, Eq, PartialEq)]
406pub struct LinearDevTargetTable {
407    /// The device's table
408    pub table: Vec<TargetLine<LinearDevTargetParams>>,
409}
410
411impl LinearDevTargetTable {
412    /// Make a new LinearDevTargetTable from a suitable vec
413    pub fn new(table: Vec<TargetLine<LinearDevTargetParams>>) -> LinearDevTargetTable {
414        LinearDevTargetTable { table }
415    }
416}
417
418impl fmt::Display for LinearDevTargetTable {
419    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420        for line in &self.table {
421            writeln!(f, "{} {} {}", *line.start, *line.length, line.params)?;
422        }
423        Ok(())
424    }
425}
426
427impl TargetTable for LinearDevTargetTable {
428    fn from_raw_table(table: &[(u64, u64, String, String)]) -> DmResult<LinearDevTargetTable> {
429        Ok(LinearDevTargetTable {
430            table: table
431                .iter()
432                .map(|x| -> DmResult<TargetLine<LinearDevTargetParams>> {
433                    Ok(TargetLine::new(
434                        Sectors(x.0),
435                        Sectors(x.1),
436                        format!("{} {}", x.2, x.3).parse::<LinearDevTargetParams>()?,
437                    ))
438                })
439                .collect::<DmResult<Vec<_>>>()?,
440        })
441    }
442
443    fn to_raw_table(&self) -> Vec<(u64, u64, String, String)> {
444        self.table
445            .iter()
446            .map(|x| {
447                (
448                    *x.start,
449                    *x.length,
450                    x.params.target_type().to_string(),
451                    x.params.param_str(),
452                )
453            })
454            .collect::<Vec<_>>()
455    }
456}
457
458/// A DM construct of combined Segments
459#[derive(Debug)]
460pub struct LinearDev {
461    /// Data about the device
462    dev_info: Box<DeviceInfo>,
463    table: LinearDevTargetTable,
464}
465
466impl DmDevice<LinearDevTargetTable> for LinearDev {
467    fn device(&self) -> Device {
468        device!(self)
469    }
470
471    fn devnode(&self) -> PathBuf {
472        devnode!(self)
473    }
474
475    // Since linear devices have no default or configuration parameters,
476    // and the ordering of segments matters, two linear devices represent
477    // the same linear device only if their tables match exactly.
478    fn equivalent_tables(
479        left: &LinearDevTargetTable,
480        right: &LinearDevTargetTable,
481    ) -> DmResult<bool> {
482        Ok(left == right)
483    }
484
485    fn name(&self) -> &DmName {
486        name!(self)
487    }
488
489    fn size(&self) -> Sectors {
490        self.table.table.iter().map(|l| l.length).sum()
491    }
492
493    fn table(&self) -> &LinearDevTargetTable {
494        table!(self)
495    }
496
497    fn teardown(&mut self, dm: &DM) -> DmResult<()> {
498        dm.device_remove(&DevId::Name(self.name()), DmOptions::default())?;
499        Ok(())
500    }
501
502    fn uuid(&self) -> Option<&DmUuid> {
503        uuid!(self)
504    }
505}
506
507/// Use DM to concatenate a list of segments together into a
508/// linear block device of continuous sectors.
509impl LinearDev {
510    /// Construct a new block device by concatenating the given segments
511    /// into linear space.
512    /// If the device is already known to the kernel, just verifies that the
513    /// segments argument passed exactly matches the kernel data.
514    ///
515    /// Warning: If the segments overlap, this method will succeed. However,
516    /// the behavior of the linear device in that case should be treated as
517    /// undefined.
518    ///
519    /// Note: A linear device is just a mapping in the kernel from locations
520    /// in that device to locations on other devices which are specified by
521    /// their device number. There is usually a device node so that data can
522    /// be read from and written to the device. Other than that, it really
523    /// has no existence. Consequently, there is no conflict in overloading
524    /// this method to mean both "make a wholly new device" and "establish
525    /// the existence of the requested device". Of course, a linear device
526    /// is usually expected to hold data, so it is important to get the
527    /// mapping just right.
528    pub fn setup(
529        dm: &DM,
530        name: &DmName,
531        uuid: Option<&DmUuid>,
532        table: Vec<TargetLine<LinearDevTargetParams>>,
533    ) -> DmResult<LinearDev> {
534        let table = LinearDevTargetTable::new(table);
535        let dev = if device_exists(dm, name)? {
536            let dev_info = dm.device_info(&DevId::Name(name))?;
537            let dev = LinearDev {
538                dev_info: Box::new(dev_info),
539                table,
540            };
541            device_match(dm, &dev, uuid)?;
542            dev
543        } else {
544            let dev_info = device_create(dm, name, uuid, &table, DmOptions::private())?;
545            LinearDev {
546                dev_info: Box::new(dev_info),
547                table,
548            }
549        };
550        Ok(dev)
551    }
552
553    /// Set the segments for this linear device.
554    /// This action puts the device in a state where it is ready to be resumed.
555    /// Warning: It is the client's responsibility to make sure the designated
556    /// segments are compatible with the device's existing segments.
557    /// If they are not, this function will still succeed, but some kind of
558    /// data corruption will be the inevitable result.
559    pub fn set_table(
560        &mut self,
561        dm: &DM,
562        table: Vec<TargetLine<LinearDevTargetParams>>,
563    ) -> DmResult<()> {
564        let table = LinearDevTargetTable::new(table);
565        self.table_load(dm, &table, DmOptions::default())?;
566        self.table = table;
567        Ok(())
568    }
569
570    /// Set the name for this LinearDev.
571    pub fn set_name(&mut self, dm: &DM, name: &DmName) -> DmResult<()> {
572        if self.name() == name {
573            return Ok(());
574        }
575        dm.device_rename(self.name(), &DevId::Name(name))?;
576        *self.dev_info = dm.device_info(&DevId::Name(name))?;
577        Ok(())
578    }
579}
580
581#[cfg(test)]
582mod tests {
583    use std::{clone::Clone, fs::OpenOptions, path::Path};
584
585    use crate::{
586        core::{devnode_to_devno, Device, DmFlags},
587        testing::{blkdev_size, test_name, test_with_spec},
588    };
589
590    use super::*;
591
592    /// Verify that a new linear dev with 0 segments fails.
593    fn test_empty(_paths: &[&Path]) {
594        assert_matches!(
595            LinearDev::setup(
596                &DM::new().unwrap(),
597                &test_name("new").expect("valid format"),
598                None,
599                vec![],
600            ),
601            Err(_)
602        );
603    }
604
605    /// Verify that setting an empty table on an existing DM device fails.
606    fn test_empty_table_set(paths: &[&Path]) {
607        assert!(!paths.is_empty());
608
609        let dm = DM::new().unwrap();
610        let name = test_name("name").expect("valid format");
611        let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap());
612        let params = LinearTargetParams::new(dev, Sectors(0));
613        let table = vec![TargetLine::new(
614            Sectors(0),
615            Sectors(1),
616            LinearDevTargetParams::Linear(params),
617        )];
618        let mut ld = LinearDev::setup(&dm, &name, None, table).unwrap();
619
620        assert_matches!(ld.set_table(&dm, vec![]), Err(_));
621        ld.resume(&dm).unwrap();
622        ld.teardown(&dm).unwrap();
623    }
624
625    /// Verify that id rename succeeds.
626    fn test_rename_id(paths: &[&Path]) {
627        assert!(!paths.is_empty());
628
629        let dm = DM::new().unwrap();
630        let name = test_name("name").expect("valid format");
631        let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap());
632        let params = LinearTargetParams::new(dev, Sectors(0));
633        let table = vec![TargetLine::new(
634            Sectors(0),
635            Sectors(1),
636            LinearDevTargetParams::Linear(params),
637        )];
638        let mut ld = LinearDev::setup(&dm, &name, None, table).unwrap();
639
640        ld.set_name(&dm, &name).unwrap();
641        assert_eq!(ld.name(), &*name);
642
643        ld.teardown(&dm).unwrap();
644    }
645
646    /// Verify that after a rename, the device has the new name.
647    fn test_rename(paths: &[&Path]) {
648        assert!(!paths.is_empty());
649
650        let dm = DM::new().unwrap();
651        let name = test_name("name").expect("valid format");
652        let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap());
653        let params = LinearTargetParams::new(dev, Sectors(0));
654        let table = vec![TargetLine::new(
655            Sectors(0),
656            Sectors(1),
657            LinearDevTargetParams::Linear(params),
658        )];
659        let mut ld = LinearDev::setup(&dm, &name, None, table).unwrap();
660
661        let new_name = test_name("new_name").expect("valid format");
662        ld.set_name(&dm, &new_name).unwrap();
663        assert_eq!(ld.name(), &*new_name);
664
665        ld.teardown(&dm).unwrap();
666    }
667
668    /// Verify that passing the same segments two times gets two segments.
669    /// Verify that the size of the devnode is the size of the sum of the
670    /// ranges of the segments. Verify that the table contains entries for both
671    /// segments.
672    fn test_duplicate_segments(paths: &[&Path]) {
673        assert!(!paths.is_empty());
674
675        let dm = DM::new().unwrap();
676        let name = test_name("name").expect("valid format");
677        let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap());
678        let params = LinearTargetParams::new(dev, Sectors(0));
679        let table = vec![
680            TargetLine::new(
681                Sectors(0),
682                Sectors(1),
683                LinearDevTargetParams::Linear(params.clone()),
684            ),
685            TargetLine::new(
686                Sectors(1),
687                Sectors(1),
688                LinearDevTargetParams::Linear(params),
689            ),
690        ];
691        let range: Sectors = table.iter().map(|s| s.length).sum();
692        let count = table.len();
693        let mut ld = LinearDev::setup(&dm, &name, None, table).unwrap();
694
695        let table = LinearDev::read_kernel_table(&dm, &DevId::Name(ld.name()))
696            .unwrap()
697            .table;
698        assert_eq!(table.len(), count);
699        assert_matches!(table[0].params, LinearDevTargetParams::Linear(ref device) if device.device == dev);
700        assert_matches!(table[1].params, LinearDevTargetParams::Linear(ref device) if device.device == dev);
701
702        assert_eq!(
703            blkdev_size(&OpenOptions::new().read(true).open(ld.devnode()).unwrap()).sectors(),
704            range
705        );
706
707        ld.teardown(&dm).unwrap();
708    }
709
710    /// Use five segments, each distinct. If parsing works correctly,
711    /// default table should match extracted table.
712    fn test_several_segments(paths: &[&Path]) {
713        assert!(!paths.is_empty());
714
715        let dm = DM::new().unwrap();
716        let name = test_name("name").expect("valid format");
717        let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap());
718        let table = (0..5)
719            .map(|n| {
720                TargetLine::new(
721                    Sectors(n),
722                    Sectors(1),
723                    LinearDevTargetParams::Linear(LinearTargetParams::new(dev, Sectors(n))),
724                )
725            })
726            .collect::<Vec<_>>();
727        let mut ld = LinearDev::setup(&dm, &name, None, table.clone()).unwrap();
728
729        let loaded_table = LinearDev::read_kernel_table(&dm, &DevId::Name(ld.name())).unwrap();
730        assert!(
731            LinearDev::equivalent_tables(&LinearDevTargetTable::new(table), &loaded_table).unwrap()
732        );
733
734        ld.teardown(&dm).unwrap();
735    }
736
737    /// Verify that constructing a second dev with the same name succeeds
738    /// only if it has the same list of segments.
739    fn test_same_name(paths: &[&Path]) {
740        assert!(!paths.is_empty());
741
742        let dm = DM::new().unwrap();
743        let name = test_name("name").expect("valid format");
744        let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap());
745        let params = LinearTargetParams::new(dev, Sectors(0));
746        let table = vec![TargetLine::new(
747            Sectors(0),
748            Sectors(1),
749            LinearDevTargetParams::Linear(params),
750        )];
751        let mut ld = LinearDev::setup(&dm, &name, None, table.clone()).unwrap();
752        let params2 = LinearTargetParams::new(dev, Sectors(1));
753        let table2 = vec![TargetLine::new(
754            Sectors(0),
755            Sectors(1),
756            LinearDevTargetParams::Linear(params2),
757        )];
758        assert_matches!(LinearDev::setup(&dm, &name, None, table2), Err(_));
759        assert_matches!(LinearDev::setup(&dm, &name, None, table), Ok(_));
760        ld.teardown(&dm).unwrap();
761    }
762
763    /// Verify constructing a second linear dev with the same segment succeeds.
764    fn test_same_segment(paths: &[&Path]) {
765        assert!(!paths.is_empty());
766
767        let dm = DM::new().unwrap();
768        let name = test_name("name").expect("valid format");
769        let ersatz = test_name("ersatz").expect("valid format");
770        let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap());
771        let params = LinearTargetParams::new(dev, Sectors(0));
772        let table = vec![TargetLine::new(
773            Sectors(0),
774            Sectors(1),
775            LinearDevTargetParams::Linear(params),
776        )];
777        let mut ld = LinearDev::setup(&dm, &name, None, table.clone()).unwrap();
778        let ld2 = LinearDev::setup(&dm, &ersatz, None, table);
779        assert_matches!(ld2, Ok(_));
780
781        ld2.unwrap().teardown(&dm).unwrap();
782        ld.teardown(&dm).unwrap();
783    }
784
785    /// Verify that suspending and immediately resuming doesn't fail.
786    fn test_suspend(paths: &[&Path]) {
787        assert!(!paths.is_empty());
788
789        let dm = DM::new().unwrap();
790        let name = test_name("name").expect("valid format");
791        let dev = Device::from(devnode_to_devno(paths[0]).unwrap().unwrap());
792        let params = LinearTargetParams::new(dev, Sectors(0));
793        let table = vec![TargetLine::new(
794            Sectors(0),
795            Sectors(1),
796            LinearDevTargetParams::Linear(params),
797        )];
798        let mut ld = LinearDev::setup(&dm, &name, None, table).unwrap();
799
800        ld.suspend(&dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))
801            .unwrap();
802        ld.suspend(&dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))
803            .unwrap();
804        ld.resume(&dm).unwrap();
805        ld.resume(&dm).unwrap();
806
807        ld.teardown(&dm).unwrap();
808    }
809
810    #[test]
811    fn test_flakey_target_params_zero() {
812        let result = "flakey 8:32 0 16 2 0"
813            .parse::<FlakeyTargetParams>()
814            .unwrap();
815        assert_eq!(result.feature_args, HashSet::new());
816    }
817
818    #[test]
819    fn test_flakey_target_params_none() {
820        let result = "flakey 8:32 0 16 2".parse::<FlakeyTargetParams>().unwrap();
821        assert_eq!(result.feature_args, HashSet::new());
822    }
823
824    #[test]
825    fn test_flakey_target_params_drop_writes() {
826        let result = "flakey 8:32 0 16 2 1 drop_writes"
827            .parse::<FlakeyTargetParams>()
828            .unwrap();
829        let expected = [FlakeyFeatureArg::DropWrites]
830            .iter()
831            .cloned()
832            .collect::<HashSet<_>>();
833        assert_eq!(result.feature_args, expected);
834    }
835
836    #[test]
837    fn test_flakey_target_params_error_writes() {
838        let result = "flakey 8:32 0 16 2 1 error_writes"
839            .parse::<FlakeyTargetParams>()
840            .unwrap();
841        let expected = [FlakeyFeatureArg::ErrorWrites]
842            .iter()
843            .cloned()
844            .collect::<HashSet<_>>();
845        assert_eq!(result.feature_args, expected);
846    }
847
848    #[test]
849    fn test_flakey_target_params_corrupt_bio_byte_reads() {
850        let result = "flakey 8:32 0 16 2 5 corrupt_bio_byte 32 r 1 0"
851            .parse::<FlakeyTargetParams>()
852            .unwrap();
853        let expected = [FlakeyFeatureArg::CorruptBioByte(32, Direction::Reads, 1, 0)]
854            .iter()
855            .cloned()
856            .collect::<HashSet<_>>();
857        assert_eq!(result.feature_args, expected);
858    }
859
860    #[test]
861    fn test_flakey_target_params_corrupt_bio_byte_writes() {
862        let result = "flakey 8:32 0 16 2 5 corrupt_bio_byte 224 w 0 32"
863            .parse::<FlakeyTargetParams>()
864            .unwrap();
865        let expected = [FlakeyFeatureArg::CorruptBioByte(
866            224,
867            Direction::Writes,
868            0,
869            32,
870        )]
871        .iter()
872        .cloned()
873        .collect::<HashSet<_>>();
874        assert_eq!(result.feature_args, expected);
875    }
876
877    #[test]
878    fn test_flakey_target_params_corrupt_bio_byte_and_drop_writes() {
879        let result = "flakey 8:32 0 16 2 6 corrupt_bio_byte 32 r 1 0 drop_writes"
880            .parse::<FlakeyTargetParams>()
881            .unwrap();
882        let expected = [
883            FlakeyFeatureArg::CorruptBioByte(32, Direction::Reads, 1, 0),
884            FlakeyFeatureArg::DropWrites,
885        ]
886        .iter()
887        .cloned()
888        .collect::<HashSet<_>>();
889        assert_eq!(result.feature_args, expected);
890    }
891
892    #[test]
893    fn test_flakey_target_params_drop_writes_and_corrupt_bio_byte() {
894        let result = "flakey 8:32 0 16 2 6 corrupt_bio_byte 32 r 1 0 drop_writes"
895            .parse::<FlakeyTargetParams>()
896            .unwrap();
897        let expected = [
898            FlakeyFeatureArg::DropWrites,
899            FlakeyFeatureArg::CorruptBioByte(32, Direction::Reads, 1, 0),
900        ]
901        .iter()
902        .cloned()
903        .collect::<HashSet<_>>();
904        assert_eq!(result.feature_args, expected);
905    }
906
907    #[test]
908    fn test_flakey_target_params_error_writes_and_drop_writes() {
909        let result = "flakey 8:32 0 16 2 2 error_writes drop_writes"
910            .parse::<FlakeyTargetParams>()
911            .unwrap();
912        let expected = [FlakeyFeatureArg::ErrorWrites, FlakeyFeatureArg::DropWrites]
913            .iter()
914            .cloned()
915            .collect::<HashSet<_>>();
916        assert_eq!(result.feature_args, expected);
917    }
918
919    #[test]
920    fn loop_test_duplicate_segments() {
921        test_with_spec(1, test_duplicate_segments);
922    }
923
924    #[test]
925    fn loop_test_empty() {
926        test_with_spec(0, test_empty);
927    }
928
929    #[test]
930    fn loop_test_empty_table_set() {
931        test_with_spec(1, test_empty_table_set);
932    }
933
934    #[test]
935    fn loop_test_rename() {
936        test_with_spec(1, test_rename);
937    }
938
939    #[test]
940    fn loop_test_rename_id() {
941        test_with_spec(1, test_rename_id);
942    }
943
944    #[test]
945    fn loop_test_same_name() {
946        test_with_spec(1, test_same_name);
947    }
948
949    #[test]
950    fn loop_test_segment() {
951        test_with_spec(1, test_same_segment);
952    }
953
954    #[test]
955    fn loop_test_several_segments() {
956        test_with_spec(1, test_several_segments);
957    }
958
959    #[test]
960    fn loop_test_suspend() {
961        test_with_spec(1, test_suspend);
962    }
963}