devicemapper/
thindev.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::{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, get_status, get_status_line_fields, message,
12        parse_device, parse_value, DmDevice, TargetLine, TargetParams, TargetTable, TargetTypeBuf,
13    },
14    thindevid::ThinDevId,
15    thinpooldev::ThinPoolDev,
16    units::Sectors,
17};
18
19const THIN_TARGET_NAME: &str = "thin";
20
21/// Struct representing params for a thin target
22#[derive(Clone, Debug, Eq, PartialEq)]
23pub struct ThinTargetParams {
24    /// Thin pool for the given thin device
25    pub pool: Device,
26    /// Thin ID
27    pub thin_id: ThinDevId,
28    /// Optional block device outside of pool to be treated as a read-only snapshot
29    /// origin
30    pub external_origin_dev: Option<Device>,
31}
32
33impl ThinTargetParams {
34    /// Create a new ThinTargetParams struct
35    pub fn new(
36        pool: Device,
37        thin_id: ThinDevId,
38        external_origin_dev: Option<Device>,
39    ) -> ThinTargetParams {
40        ThinTargetParams {
41            pool,
42            thin_id,
43            external_origin_dev,
44        }
45    }
46}
47
48impl fmt::Display for ThinTargetParams {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        write!(f, "{} {}", THIN_TARGET_NAME, self.param_str())
51    }
52}
53
54impl FromStr for ThinTargetParams {
55    type Err = DmError;
56
57    fn from_str(s: &str) -> DmResult<ThinTargetParams> {
58        let vals = s.split(' ').collect::<Vec<_>>();
59        let len = vals.len();
60        if !(3..=4).contains(&len) {
61            let err_msg = format!("expected 3 or 4 values in params string \"{s}\", found {len}");
62            return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
63        }
64
65        if vals[0] != THIN_TARGET_NAME {
66            let err_msg = format!(
67                "Expected a thin target entry but found target type {}",
68                vals[0]
69            );
70            return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
71        }
72
73        Ok(ThinTargetParams::new(
74            parse_device(vals[1], "thinpool device for thin target")?,
75            vals[2].parse::<ThinDevId>()?,
76            if len == 3 {
77                None
78            } else {
79                Some(parse_device(
80                    vals[3],
81                    "external origin device for thin snapshot",
82                )?)
83            },
84        ))
85    }
86}
87
88impl TargetParams for ThinTargetParams {
89    fn param_str(&self) -> String {
90        match self.external_origin_dev {
91            None => format!("{} {}", self.pool, self.thin_id),
92            Some(dev) => format!("{} {} {}", self.pool, self.thin_id, dev),
93        }
94    }
95
96    fn target_type(&self) -> TargetTypeBuf {
97        TargetTypeBuf::new(THIN_TARGET_NAME.into()).expect("THIN_TARGET_NAME is valid")
98    }
99}
100
101/// A target table for a thin device.
102#[derive(Clone, Debug, Eq, PartialEq)]
103pub struct ThinDevTargetTable {
104    /// The device's table
105    pub table: TargetLine<ThinTargetParams>,
106}
107
108impl ThinDevTargetTable {
109    /// Make a new ThinDevTargetTable from required input
110    pub fn new(start: Sectors, length: Sectors, params: ThinTargetParams) -> ThinDevTargetTable {
111        ThinDevTargetTable {
112            table: TargetLine::new(start, length, params),
113        }
114    }
115}
116
117impl fmt::Display for ThinDevTargetTable {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        let table = &self.table;
120        writeln!(f, "{} {} {}", *table.start, *table.length, table.params)
121    }
122}
123
124impl TargetTable for ThinDevTargetTable {
125    fn from_raw_table(table: &[(u64, u64, String, String)]) -> DmResult<ThinDevTargetTable> {
126        if table.len() != 1 {
127            let err_msg = format!(
128                "ThinDev table should have exactly one line, has {} lines",
129                table.len()
130            );
131            return Err(DmError::Dm(ErrorEnum::Invalid, err_msg));
132        }
133        let line = table.first().expect("table.len() == 1");
134        Ok(ThinDevTargetTable::new(
135            Sectors(line.0),
136            Sectors(line.1),
137            format!("{} {}", line.2, line.3).parse::<ThinTargetParams>()?,
138        ))
139    }
140
141    fn to_raw_table(&self) -> Vec<(u64, u64, String, String)> {
142        to_raw_table_unique!(self)
143    }
144}
145
146/// DM construct for a thin block device
147#[derive(Debug)]
148pub struct ThinDev {
149    dev_info: Box<DeviceInfo>,
150    table: ThinDevTargetTable,
151}
152
153impl DmDevice<ThinDevTargetTable> for ThinDev {
154    fn device(&self) -> Device {
155        device!(self)
156    }
157
158    fn devnode(&self) -> PathBuf {
159        devnode!(self)
160    }
161
162    // This method is incomplete. It is expected that it will be refined so
163    // that it will return true in more cases, i.e., to be less stringent.
164    fn equivalent_tables(left: &ThinDevTargetTable, right: &ThinDevTargetTable) -> DmResult<bool> {
165        Ok(left == right)
166    }
167
168    fn name(&self) -> &DmName {
169        name!(self)
170    }
171
172    fn resume(&mut self, dm: &DM) -> DmResult<()> {
173        dm.device_suspend(&DevId::Name(self.name()), DmOptions::default())?;
174        Ok(())
175    }
176
177    fn size(&self) -> Sectors {
178        self.table.table.length
179    }
180
181    fn table(&self) -> &ThinDevTargetTable {
182        table!(self)
183    }
184
185    fn teardown(&mut self, dm: &DM) -> DmResult<()> {
186        dm.device_remove(&DevId::Name(self.name()), DmOptions::default())?;
187        Ok(())
188    }
189
190    fn uuid(&self) -> Option<&DmUuid> {
191        uuid!(self)
192    }
193}
194
195/// Status values for a thin device that is working
196#[derive(Clone, Debug)]
197pub struct ThinDevWorkingStatus {
198    /// The number of mapped sectors
199    pub nr_mapped_sectors: Sectors,
200    /// The highest mapped sector if any.
201    pub highest_mapped_sector: Option<Sectors>,
202}
203
204impl ThinDevWorkingStatus {
205    /// Make a new ThinDevWorkingStatus struct
206    pub fn new(
207        nr_mapped_sectors: Sectors,
208        highest_mapped_sector: Option<Sectors>,
209    ) -> ThinDevWorkingStatus {
210        ThinDevWorkingStatus {
211            nr_mapped_sectors,
212            highest_mapped_sector,
213        }
214    }
215}
216
217#[derive(Clone, Debug)]
218/// Thin device status.
219pub enum ThinStatus {
220    /// Thin device is good. Includes number of mapped sectors, and
221    /// highest mapped sector.
222    Working(Box<ThinDevWorkingStatus>),
223    /// Devicemapper has reported that it could not obtain the status
224    Error,
225    /// Thin device is failed.
226    Fail,
227}
228
229impl FromStr for ThinStatus {
230    type Err = DmError;
231
232    fn from_str(status_line: &str) -> DmResult<ThinStatus> {
233        if status_line.starts_with("Error") {
234            return Ok(ThinStatus::Error);
235        }
236
237        if status_line.starts_with("Fail") {
238            return Ok(ThinStatus::Fail);
239        }
240
241        let status_vals = get_status_line_fields(status_line, 2)?;
242
243        let count = Sectors(parse_value(status_vals[0], "sector count")?);
244
245        let highest = if count == Sectors(0) {
246            None
247        } else {
248            Some(Sectors(parse_value(status_vals[1], "highest used sector")?))
249        };
250
251        Ok(ThinStatus::Working(Box::new(ThinDevWorkingStatus::new(
252            count, highest,
253        ))))
254    }
255}
256
257/// support use of DM for thin provisioned devices over pools
258impl ThinDev {
259    /// Create a ThinDev using thin_pool as the backing store.
260    /// If the specified thin_id is already in use by the thin pool an error
261    /// is returned. If the device is already among the list of devices that
262    /// dm is aware of, return an error.
263    pub fn new(
264        dm: &DM,
265        name: &DmName,
266        uuid: Option<&DmUuid>,
267        length: Sectors,
268        thin_pool: &ThinPoolDev,
269        thin_id: ThinDevId,
270    ) -> DmResult<ThinDev> {
271        message(dm, thin_pool, &format!("create_thin {thin_id}"))?;
272
273        if device_exists(dm, name)? {
274            let err_msg = "Uncreated device should not be known to kernel";
275            return Err(DmError::Dm(ErrorEnum::Invalid, err_msg.into()));
276        }
277
278        let thin_pool_device = thin_pool.device();
279        let table = ThinDev::gen_default_table(length, thin_pool_device, thin_id);
280        let dev_info = device_create(dm, name, uuid, &table, DmOptions::default())?;
281
282        Ok(ThinDev {
283            dev_info: Box::new(dev_info),
284            table,
285        })
286    }
287
288    /// Set up a thin device which already belongs to the given thin_pool.
289    /// The thin device is identified by the thin_id, which is already
290    /// known to the pool.
291    ///
292    /// If the device is already known to kernel, just verify that specified
293    /// data matches and return an error if it does not.
294    ///
295    /// If the device has no thin id already registered with the thin pool
296    /// an error is returned.
297    pub fn setup(
298        dm: &DM,
299        name: &DmName,
300        uuid: Option<&DmUuid>,
301        length: Sectors,
302        thin_pool: &ThinPoolDev,
303        thin_id: ThinDevId,
304    ) -> DmResult<ThinDev> {
305        let thin_pool_device = thin_pool.device();
306        let table = ThinDev::gen_default_table(length, thin_pool_device, thin_id);
307        let dev = if device_exists(dm, name)? {
308            let dev_info = dm.device_info(&DevId::Name(name))?;
309            let dev = ThinDev {
310                dev_info: Box::new(dev_info),
311                table,
312            };
313            device_match(dm, &dev, uuid)?;
314            dev
315        } else {
316            let dev_info = device_create(dm, name, uuid, &table, DmOptions::default())?;
317            ThinDev {
318                dev_info: Box::new(dev_info),
319                table,
320            }
321        };
322        Ok(dev)
323    }
324
325    /// Create a snapshot of a ThinDev.  Once created a snapshot
326    /// is the same as any other thin provisioned device.  There is
327    /// no need to track any connection between the source and the
328    /// snapshot.
329    pub fn snapshot(
330        &self,
331        dm: &DM,
332        snapshot_name: &DmName,
333        snapshot_uuid: Option<&DmUuid>,
334        thin_pool: &ThinPoolDev,
335        snapshot_thin_id: ThinDevId,
336    ) -> DmResult<ThinDev> {
337        let source_id = DevId::Name(self.name());
338        dm.device_suspend(
339            &source_id,
340            DmOptions::default().set_flags(DmFlags::DM_SUSPEND),
341        )?;
342        message(
343            dm,
344            thin_pool,
345            &format!(
346                "create_snap {} {}",
347                snapshot_thin_id, self.table.table.params.thin_id
348            ),
349        )?;
350        dm.device_suspend(&source_id, DmOptions::default())?;
351        let table = ThinDev::gen_default_table(self.size(), thin_pool.device(), snapshot_thin_id);
352        let dev_info = Box::new(device_create(
353            dm,
354            snapshot_name,
355            snapshot_uuid,
356            &table,
357            DmOptions::default(),
358        )?);
359        Ok(ThinDev { dev_info, table })
360    }
361
362    /// Generate a table to be passed to DM. The format of the table
363    /// entries is:
364    /// <start (0)> <length> "thin" <thin device specific string>
365    /// where the thin device specific string has the format:
366    /// <thinpool maj:min> <thin_id>
367    /// There is exactly one entry in the table.
368    /// Various defaults are hard coded in the method.
369    fn gen_default_table(
370        length: Sectors,
371        thin_pool: Device,
372        thin_id: ThinDevId,
373    ) -> ThinDevTargetTable {
374        ThinDevTargetTable::new(
375            Sectors::default(),
376            length,
377            ThinTargetParams::new(thin_pool, thin_id, None),
378        )
379    }
380
381    /// return the thin id of the linear device
382    pub fn id(&self) -> ThinDevId {
383        self.table.table.params.thin_id
384    }
385
386    /// Get the current status of the thin device.
387    pub fn status(&self, dm: &DM, options: DmOptions) -> DmResult<ThinStatus> {
388        status!(self, dm, options)
389    }
390
391    /// Set the table for the thin device's target
392    pub fn set_table(&mut self, dm: &DM, table: TargetLine<ThinTargetParams>) -> DmResult<()> {
393        let table = ThinDevTargetTable::new(table.start, table.length, table.params);
394        self.suspend(dm, DmOptions::default().set_flags(DmFlags::DM_NOFLUSH))?;
395        self.table_load(dm, &table, DmOptions::default())?;
396        self.resume(dm)?;
397
398        self.table = table;
399        Ok(())
400    }
401
402    /// Tear down the DM device, and also delete resources associated
403    /// with its thin id from the thinpool.
404    pub fn destroy(&mut self, dm: &DM, thin_pool: &ThinPoolDev) -> DmResult<()> {
405        let thin_id = self.table.table.params.thin_id;
406        self.teardown(dm)?;
407        message(dm, thin_pool, &format!("delete {thin_id}"))?;
408        Ok(())
409    }
410}
411
412#[cfg(test)]
413mod tests {
414
415    use std::{
416        fs::{canonicalize, OpenOptions},
417        io::Write,
418        path::Path,
419    };
420
421    use nix::mount::{mount, umount2, MntFlags, MsFlags};
422    use uuid::Uuid;
423
424    use crate::{
425        consts::IEC,
426        core::errors::Error,
427        shared::DmDevice,
428        testing::{
429            blkdev_size, test_name, test_string, test_uuid, test_with_spec, udev_settle,
430            xfs_create_fs, xfs_set_uuid,
431        },
432        thinpooldev::{minimal_thinpool, ThinPoolStatus},
433        units::DataBlocks,
434    };
435
436    use super::*;
437
438    const MIN_THIN_DEV_SIZE: Sectors = Sectors(1);
439
440    /// Verify that specifying a size of 0 Sectors will cause a failure.
441    fn test_zero_size(paths: &[&Path]) {
442        assert!(!paths.is_empty());
443
444        let dm = DM::new().unwrap();
445        let mut tp = minimal_thinpool(&dm, paths[0]);
446
447        assert_matches!(
448            ThinDev::new(
449                &dm,
450                &test_name("name").expect("is valid DM name"),
451                None,
452                Sectors(0),
453                &tp,
454                ThinDevId::new_u64(0).expect("is below limit")
455            ),
456            Err(_)
457        );
458
459        udev_settle().unwrap();
460        tp.teardown(&dm).unwrap();
461    }
462
463    /// Verify that setting up a thin device without first calling new()
464    /// causes an error. The underlying reason is that the thin pool hasn't
465    /// been informed about the thin device by messaging the value of the
466    /// thin id.
467    fn test_setup_without_new(paths: &[&Path]) {
468        assert!(!paths.is_empty());
469
470        let dm = DM::new().unwrap();
471        let mut tp = minimal_thinpool(&dm, paths[0]);
472
473        let td_size = MIN_THIN_DEV_SIZE;
474        assert_matches!(
475            ThinDev::setup(
476                &dm,
477                &test_name("name").expect("is valid DM name"),
478                None,
479                td_size,
480                &tp,
481                ThinDevId::new_u64(0).expect("is below limit")
482            ),
483            Err(DmError::Core(Error::Ioctl(_, _, _, _)))
484        );
485
486        tp.teardown(&dm).unwrap();
487    }
488
489    /// Verify success when constructing a new ThinDev. Check that the
490    /// status of the device is as expected. Verify that it is now possible
491    /// to call setup() on the thin dev specifying the same name and id.
492    /// Verify that calling new() for the second time fails. Verify that
493    /// setup() is idempotent, calling setup() twice in succession succeeds.
494    /// Verify that setup() succeeds on an existing device, whether or not
495    /// it has been torn down.
496    fn test_basic(paths: &[&Path]) {
497        assert!(!paths.is_empty());
498
499        let dm = DM::new().unwrap();
500        let mut tp = minimal_thinpool(&dm, paths[0]);
501        let thin_id = ThinDevId::new_u64(0).expect("is below limit");
502        let id = test_name("name").expect("is valid DM name");
503
504        let td_size = MIN_THIN_DEV_SIZE;
505        let mut td = ThinDev::new(&dm, &id, None, td_size, &tp, thin_id).unwrap();
506
507        udev_settle().unwrap();
508
509        let table = ThinDev::read_kernel_table(&dm, &DevId::Name(td.name()))
510            .unwrap()
511            .table;
512
513        assert_eq!(table.params.pool, tp.device());
514        assert_eq!(table.params.thin_id, thin_id);
515
516        assert_matches!(
517            td.status(&dm, DmOptions::default()).unwrap(),
518            ThinStatus::Error | ThinStatus::Working(_)
519        );
520
521        assert_eq!(
522            blkdev_size(&OpenOptions::new().read(true).open(td.devnode()).unwrap()),
523            td_size.bytes()
524        );
525
526        // New thindev w/ same id fails.
527        assert_matches!(
528            ThinDev::new(&dm, &id, None, td_size, &tp, thin_id),
529            Err(DmError::Core(Error::Ioctl(_, _, _, _)))
530        );
531
532        // Verify that the device of that name does exist.
533        assert!(device_exists(&dm, &id).unwrap());
534
535        // Setting up the just created thin dev succeeds.
536        assert_matches!(ThinDev::setup(&dm, &id, None, td_size, &tp, thin_id), Ok(_));
537        udev_settle().unwrap();
538
539        // Setting up the just created thin dev once more succeeds.
540        assert_matches!(ThinDev::setup(&dm, &id, None, td_size, &tp, thin_id), Ok(_));
541
542        // Teardown the thindev, then set it back up.
543        td.teardown(&dm).unwrap();
544        let mut td = ThinDev::setup(&dm, &id, None, td_size, &tp, thin_id).unwrap();
545        udev_settle().unwrap();
546
547        td.destroy(&dm, &tp).unwrap();
548        tp.teardown(&dm).unwrap();
549    }
550
551    /// Test thin device create, load, and snapshot and make sure that all is well with udev
552    /// db and symlink generation.
553    fn test_udev_userspace(paths: &[&Path]) {
554        // Confirm that the correct symlink has been constructed.
555        fn validate(path_uuid: &Uuid, devnode: &Path) {
556            udev_settle().unwrap();
557
558            // Make sure the uuid symlink was created
559            let symlink = PathBuf::from(format!("/dev/disk/by-uuid/{path_uuid}"));
560            assert!(symlink.exists());
561
562            assert_eq!(*devnode, canonicalize(symlink).unwrap());
563        }
564
565        // Set the FS with devnode to a new auto generated UUID
566        fn set_new_fs_uuid(devnode: &Path) -> Uuid {
567            // Tmp mount & umount to complete the XFS transactions so that we can change the UUID
568            let tmp_dir = tempfile::Builder::new()
569                .prefix(&test_string("test_udev_userspace_mp"))
570                .tempdir()
571                .unwrap();
572            mount(
573                Some(devnode),
574                tmp_dir.path(),
575                Some("xfs"),
576                MsFlags::empty(),
577                None as Option<&str>,
578            )
579            .unwrap();
580            umount2(tmp_dir.path(), MntFlags::MNT_DETACH).unwrap();
581
582            // Set the fs UUID to something new
583            let new_uuid = Uuid::new_v4();
584            xfs_set_uuid(devnode, &new_uuid).unwrap();
585            new_uuid
586        }
587
588        let dm = DM::new().unwrap();
589        let mut tp = minimal_thinpool(&dm, paths[0]);
590        let thin_id = ThinDevId::new_u64(0).expect("is below limit");
591        let id = test_name("udev_test_thin_dev").expect("is valid DM name");
592
593        let mut td = ThinDev::new(&dm, &id, None, tp.size(), &tp, thin_id).unwrap();
594        udev_settle().unwrap();
595
596        let uuid = Uuid::new_v4();
597
598        // Create the XFS FS on top of the thin device
599        xfs_create_fs(&td.devnode(), Some(uuid)).unwrap();
600
601        // Synchronize with udev processing triggered by xfs_create_fs()
602        udev_settle().unwrap();
603
604        validate(&uuid, &td.devnode());
605
606        // Teardown the thindev, then set it back up and make sure all is well with udev
607        td.teardown(&dm).unwrap();
608        td = ThinDev::setup(&dm, &id, None, tp.size(), &tp, thin_id).unwrap();
609        validate(&uuid, &td.devnode());
610
611        // Create a snapshot and make sure we get correct actions in user space WRT udev
612        let ss_id = ThinDevId::new_u64(1).expect("is below limit");
613        let ss_name = test_name("snap_name").expect("is valid DM name");
614        let mut ss = td.snapshot(&dm, &ss_name, None, &tp, ss_id).unwrap();
615        udev_settle().unwrap();
616
617        let ss_new_uuid = set_new_fs_uuid(&ss.devnode());
618
619        // Validate that the symlink for original and snapshot are correct
620        validate(&ss_new_uuid, &ss.devnode());
621        validate(&uuid, &td.devnode());
622
623        // Tear everything down
624        ss.destroy(&dm, &tp).unwrap();
625        td.destroy(&dm, &tp).unwrap();
626        tp.teardown(&dm).unwrap();
627    }
628
629    /// Verify success when taking a snapshot of a ThinDev.  Check that
630    /// the size of the snapshot is the same as the source.
631    /// Verify that empty thindev has no data usage.
632    fn test_snapshot(paths: &[&Path]) {
633        assert!(!paths.is_empty());
634        let td_size = MIN_THIN_DEV_SIZE;
635        let dm = DM::new().unwrap();
636        let mut tp = minimal_thinpool(&dm, paths[0]);
637
638        let orig_data_usage = match tp.status(&dm, DmOptions::default()).unwrap() {
639            ThinPoolStatus::Working(ref status) => status.usage.used_data,
640            ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
641            ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
642        };
643
644        assert_eq!(orig_data_usage, DataBlocks(0));
645
646        // Create new ThinDev as source for snapshot
647        let thin_id = ThinDevId::new_u64(0).expect("is below limit");
648        let thin_name = test_name("name").expect("is valid DM name");
649        let mut td = ThinDev::new(&dm, &thin_name, None, td_size, &tp, thin_id).unwrap();
650        udev_settle().unwrap();
651
652        let data_usage_1 = match tp.status(&dm, DmOptions::default()).unwrap() {
653            ThinPoolStatus::Working(ref status) => status.usage.used_data,
654            ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
655            ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
656        };
657
658        assert_eq!(data_usage_1, DataBlocks(0));
659
660        // Create a snapshot of the source
661        let ss_id = ThinDevId::new_u64(1).expect("is below limit");
662        let ss_name = test_name("snap_name").expect("is valid DM name");
663        let mut ss = td.snapshot(&dm, &ss_name, None, &tp, ss_id).unwrap();
664        udev_settle().unwrap();
665
666        let data_usage_2 = match tp.status(&dm, DmOptions::default()).unwrap() {
667            ThinPoolStatus::Working(ref status) => status.usage.used_data,
668            ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
669            ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
670        };
671
672        assert_eq!(data_usage_2, DataBlocks(0));
673
674        // Verify the source and the snapshot are the same size.
675        assert_eq!(td.size(), ss.size());
676
677        ss.destroy(&dm, &tp).unwrap();
678        td.destroy(&dm, &tp).unwrap();
679        tp.teardown(&dm).unwrap();
680    }
681
682    /// Verify no failures when creating a thindev from a pool, mounting a
683    /// filesystem on the thin device, and writing to that filesystem.
684    /// Verify reasonable usage behavior.
685    fn test_filesystem(paths: &[&Path]) {
686        assert!(!paths.is_empty());
687
688        let dm = DM::new().unwrap();
689        let mut tp = minimal_thinpool(&dm, paths[0]);
690
691        let thin_id = ThinDevId::new_u64(0).expect("is below limit");
692        let thin_name = test_name("name").expect("is valid DM name");
693        let mut td = ThinDev::new(&dm, &thin_name, None, tp.size(), &tp, thin_id).unwrap();
694        udev_settle().unwrap();
695
696        let orig_data_usage = match tp.status(&dm, DmOptions::default()).unwrap() {
697            ThinPoolStatus::Working(ref status) => status.usage.used_data,
698            ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
699            ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
700        };
701        assert_eq!(orig_data_usage, DataBlocks(0));
702
703        xfs_create_fs(&td.devnode(), None).unwrap();
704
705        let data_usage_1 = match tp.status(&dm, DmOptions::default()).unwrap() {
706            ThinPoolStatus::Working(ref status) => status.usage.used_data,
707            ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
708            ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
709        };
710        assert!(data_usage_1 > DataBlocks(0));
711
712        let tmp_dir = tempfile::Builder::new()
713            .prefix(&test_string("devicemapper_testing"))
714            .tempdir()
715            .unwrap();
716        mount(
717            Some(&td.devnode()),
718            tmp_dir.path(),
719            Some("xfs"),
720            MsFlags::empty(),
721            None as Option<&str>,
722        )
723        .unwrap();
724
725        for i in 0..100 {
726            let file_path = tmp_dir.path().join(format!("devicemapper_test{i}.txt"));
727            writeln!(
728                &OpenOptions::new()
729                    .create(true)
730                    .truncate(true)
731                    .write(true)
732                    .open(file_path)
733                    .unwrap(),
734                "data"
735            )
736            .unwrap();
737        }
738        umount2(tmp_dir.path(), MntFlags::MNT_DETACH).unwrap();
739
740        let data_usage_2 = match tp.status(&dm, DmOptions::default()).unwrap() {
741            ThinPoolStatus::Working(ref status) => status.usage.used_data,
742            ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
743            ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
744        };
745        assert!(data_usage_2 > data_usage_1);
746
747        td.destroy(&dm, &tp).unwrap();
748        tp.teardown(&dm).unwrap();
749    }
750
751    /// Verify reasonable usage behavior when taking a snapshot of a thindev
752    /// with an existing filesystem. In particular, just taking a snapshot
753    /// should not increase the pool usage at all.
754    /// If ThindevA is one GiB and ThindevB is 1 TiB, the making a filesystem
755    /// on ThindevB consumes at least 32 times the space as making a filesystem
756    /// on ThindevA. Verify that setting the UUID of a snapshot causes the
757    /// snapshot to consume approximately the same amount of space as its
758    /// source.
759    fn test_snapshot_usage(paths: &[&Path]) {
760        assert!(!paths.is_empty());
761
762        let dm = DM::new().unwrap();
763        let mut tp = minimal_thinpool(&dm, paths[0]);
764
765        let thin_id = ThinDevId::new_u64(0).expect("is below limit");
766        let thin_name = test_name("name").expect("is valid DM name");
767        let mut td =
768            ThinDev::new(&dm, &thin_name, None, Sectors(2 * IEC::Mi), &tp, thin_id).unwrap();
769        udev_settle().unwrap();
770
771        let orig_data_usage = match tp.status(&dm, DmOptions::default()).unwrap() {
772            ThinPoolStatus::Working(ref status) => status.usage.used_data,
773            ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
774            ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
775        };
776        assert_eq!(orig_data_usage, DataBlocks(0));
777
778        xfs_create_fs(&td.devnode(), None).unwrap();
779
780        let data_usage_1 = match tp.status(&dm, DmOptions::default()).unwrap() {
781            ThinPoolStatus::Working(ref status) => status.usage.used_data,
782            ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
783            ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
784        };
785        assert!(data_usage_1 > DataBlocks(0));
786
787        // Create a snapshot of the source
788        let ss_id = ThinDevId::new_u64(1).expect("is below limit");
789        let ss_name = test_name("snap_name").expect("is valid DM name");
790        let ss_uuid = test_uuid("snap_uuid").expect("is valid DM uuid");
791        let mut ss = td
792            .snapshot(&dm, &ss_name, Some(&ss_uuid), &tp, ss_id)
793            .unwrap();
794        udev_settle().unwrap();
795
796        let data_usage_2 = match tp.status(&dm, DmOptions::default()).unwrap() {
797            ThinPoolStatus::Working(ref status) => status.usage.used_data,
798            ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
799            ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
800        };
801        assert_eq!(data_usage_2, data_usage_1);
802
803        xfs_set_uuid(&ss.devnode(), &Uuid::new_v4()).unwrap();
804
805        // Setting the uuid of the snapshot filesystem bumps the usage,
806        // but does not increase the usage quite as much as establishing
807        // the origin.
808        let data_usage_3 = match tp.status(&dm, DmOptions::default()).unwrap() {
809            ThinPoolStatus::Working(ref status) => status.usage.used_data,
810            ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
811            ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
812        };
813        assert!(data_usage_3 - data_usage_2 > DataBlocks(0));
814        assert!(data_usage_3 - data_usage_2 < data_usage_1);
815        assert!(data_usage_3 - data_usage_2 > data_usage_1 / 2usize);
816
817        let thin_id = ThinDevId::new_u64(2).expect("is below limit");
818        let thin_name = test_name("name1").expect("is valid DM name");
819        let mut td1 =
820            ThinDev::new(&dm, &thin_name, None, Sectors(2 * IEC::Gi), &tp, thin_id).unwrap();
821        udev_settle().unwrap();
822
823        let data_usage_4 = match tp.status(&dm, DmOptions::default()).unwrap() {
824            ThinPoolStatus::Working(ref status) => status.usage.used_data,
825            ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
826            ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
827        };
828        assert_eq!(data_usage_4, data_usage_3);
829
830        xfs_create_fs(&td1.devnode(), None).unwrap();
831
832        let data_usage_5 = match tp.status(&dm, DmOptions::default()).unwrap() {
833            ThinPoolStatus::Working(ref status) => status.usage.used_data,
834            ThinPoolStatus::Error => panic!("devicemapper could not obtain thin pool status"),
835            ThinPoolStatus::Fail => panic!("failed to get thinpool status"),
836        };
837        assert!(data_usage_5 - data_usage_4 > 32usize * data_usage_1);
838
839        ss.destroy(&dm, &tp).unwrap();
840        td1.destroy(&dm, &tp).unwrap();
841        td.destroy(&dm, &tp).unwrap();
842        tp.teardown(&dm).unwrap();
843    }
844
845    /// Verify that destroy() actually deallocates the space from the
846    /// thinpool, by attempting to reinstantiate it using the same thin id and
847    /// verifying that it fails.
848    fn test_thindev_destroy(paths: &[&Path]) {
849        assert!(!paths.is_empty());
850
851        let dm = DM::new().unwrap();
852        let mut tp = minimal_thinpool(&dm, paths[0]);
853
854        let thin_id = ThinDevId::new_u64(0).expect("is below limit");
855        let thin_name = test_name("name").expect("is valid DM name");
856
857        let mut td = ThinDev::new(&dm, &thin_name, None, tp.size(), &tp, thin_id).unwrap();
858        td.teardown(&dm).unwrap();
859
860        // This should work
861        let mut td = ThinDev::setup(&dm, &thin_name, None, tp.size(), &tp, thin_id).unwrap();
862        td.destroy(&dm, &tp).unwrap();
863
864        // This should fail
865        assert_matches!(
866            ThinDev::setup(&dm, &thin_name, None, tp.size(), &tp, thin_id),
867            Err(DmError::Core(Error::Ioctl(_, _, _, _)))
868        );
869
870        tp.teardown(&dm).unwrap();
871    }
872
873    #[test]
874    fn loop_test_basic() {
875        test_with_spec(1, test_basic);
876    }
877
878    #[test]
879    fn loop_test_basic_udev() {
880        test_with_spec(1, test_udev_userspace);
881    }
882
883    #[test]
884    fn loop_test_zero_size() {
885        test_with_spec(1, test_zero_size);
886    }
887
888    #[test]
889    fn loop_test_setup_without_new() {
890        test_with_spec(1, test_setup_without_new);
891    }
892
893    #[test]
894    fn loop_test_snapshot() {
895        test_with_spec(1, test_snapshot);
896    }
897
898    #[test]
899    fn loop_test_snapshot_usage() {
900        test_with_spec(1, test_snapshot_usage);
901    }
902
903    #[test]
904    fn loop_test_filesystem() {
905        test_with_spec(1, test_filesystem);
906    }
907
908    #[test]
909    fn loop_test_thindev_destroy() {
910        test_with_spec(1, test_thindev_destroy);
911    }
912}