1use 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#[derive(Clone, Debug, Eq, PartialEq)]
23pub struct ThinTargetParams {
24 pub pool: Device,
26 pub thin_id: ThinDevId,
28 pub external_origin_dev: Option<Device>,
31}
32
33impl ThinTargetParams {
34 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#[derive(Clone, Debug, Eq, PartialEq)]
103pub struct ThinDevTargetTable {
104 pub table: TargetLine<ThinTargetParams>,
106}
107
108impl ThinDevTargetTable {
109 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#[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 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#[derive(Clone, Debug)]
197pub struct ThinDevWorkingStatus {
198 pub nr_mapped_sectors: Sectors,
200 pub highest_mapped_sector: Option<Sectors>,
202}
203
204impl ThinDevWorkingStatus {
205 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)]
218pub enum ThinStatus {
220 Working(Box<ThinDevWorkingStatus>),
223 Error,
225 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
257impl ThinDev {
259 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 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 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 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 pub fn id(&self) -> ThinDevId {
383 self.table.table.params.thin_id
384 }
385
386 pub fn status(&self, dm: &DM, options: DmOptions) -> DmResult<ThinStatus> {
388 status!(self, dm, options)
389 }
390
391 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 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 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 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 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 assert_matches!(
528 ThinDev::new(&dm, &id, None, td_size, &tp, thin_id),
529 Err(DmError::Core(Error::Ioctl(_, _, _, _)))
530 );
531
532 assert!(device_exists(&dm, &id).unwrap());
534
535 assert_matches!(ThinDev::setup(&dm, &id, None, td_size, &tp, thin_id), Ok(_));
537 udev_settle().unwrap();
538
539 assert_matches!(ThinDev::setup(&dm, &id, None, td_size, &tp, thin_id), Ok(_));
541
542 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 fn test_udev_userspace(paths: &[&Path]) {
554 fn validate(path_uuid: &Uuid, devnode: &Path) {
556 udev_settle().unwrap();
557
558 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 fn set_new_fs_uuid(devnode: &Path) -> Uuid {
567 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 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 xfs_create_fs(&td.devnode(), Some(uuid)).unwrap();
600
601 udev_settle().unwrap();
603
604 validate(&uuid, &td.devnode());
605
606 td.teardown(&dm).unwrap();
608 td = ThinDev::setup(&dm, &id, None, tp.size(), &tp, thin_id).unwrap();
609 validate(&uuid, &td.devnode());
610
611 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(&ss_new_uuid, &ss.devnode());
621 validate(&uuid, &td.devnode());
622
623 ss.destroy(&dm, &tp).unwrap();
625 td.destroy(&dm, &tp).unwrap();
626 tp.teardown(&dm).unwrap();
627 }
628
629 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 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 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 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 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 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 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 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 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 let mut td = ThinDev::setup(&dm, &thin_name, None, tp.size(), &tp, thin_id).unwrap();
862 td.destroy(&dm, &tp).unwrap();
863
864 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}