disk_types/
partition.rs

1use crate::{
2    device::BlockDeviceExt,
3    fs::FileSystem::{self, *},
4    usage::sectors_used,
5};
6use libparted::PartitionFlag;
7use os_detect::{detect_os_from_device, OS};
8use std::{io, path::Path};
9use sys_mount::*;
10use tempdir::TempDir;
11
12/// Trait to provide methods for interacting with partition-based block device.
13pub trait PartitionExt: BlockDeviceExt {
14    /// Defines the file system that this device was partitioned with.
15    fn get_file_system(&self) -> Option<FileSystem>;
16
17    /// Ped partition flags that this disk has been assigned.
18    fn get_partition_flags(&self) -> &[PartitionFlag];
19
20    /// The label of the partition, if it has one.
21    fn get_partition_label(&self) -> Option<&str>;
22
23    /// Whether this partition is primary, logical, or extended.
24    ///
25    /// This only applies to MBR partition tables. Partitions are always `Primary` on GPT.
26    fn get_partition_type(&self) -> PartitionType;
27
28    /// The sector where this partition ends on the parent block device.
29    fn get_sector_end(&self) -> u64;
30
31    /// The sector where this partition begins on the parent block device..
32    fn get_sector_start(&self) -> u64;
33
34    /// Returns the length of the partition in sectors.
35    fn get_sectors(&self) -> u64 { self.get_sector_end() - self.get_sector_start() }
36
37    /// True if the partition is an ESP partition.
38    fn is_esp_partition(&self) -> bool {
39        self.get_file_system().map_or(false, |fs| {
40            (fs == Fat16 || fs == Fat32)
41                && self.get_partition_flags().contains(&PartitionFlag::PED_PARTITION_ESP)
42        })
43    }
44
45    /// True if the partition is compatible for Linux to be installed on it.
46    fn is_linux_compatible(&self) -> bool {
47        self.get_file_system().map_or(false, |fs| match fs {
48            Exfat | Ntfs | Fat16 | Fat32 | Lvm | Luks | Swap => false,
49            Btrfs | Xfs | Ext2 | Ext3 | Ext4 | F2fs => true,
50        })
51    }
52
53    /// True if this is a LUKS partition
54    fn is_luks(&self) -> bool { self.get_file_system().map_or(false, |fs| fs == FileSystem::Luks) }
55
56    /// True if the partition is a swap partition.
57    fn is_swap(&self) -> bool { self.get_file_system().map_or(false, |fs| fs == FileSystem::Swap) }
58
59    /// Mount the file system at a temporary directory, and allow the caller to scan it.
60    fn probe<T, F>(&self, mut func: F) -> T
61    where
62        F: FnMut(Option<(&Path, UnmountDrop<Mount>)>) -> T,
63    {
64        let mount =
65            self.get_file_system().and_then(|fs| TempDir::new("distinst").ok().map(|t| (fs, t)));
66
67        if let Some((fs, tempdir)) = mount {
68            let fs = match fs {
69                FileSystem::Fat16 | FileSystem::Fat32 => "vfat",
70                fs => fs.into(),
71            };
72
73            // Mount the FS to the temporary directory
74            let base = tempdir.path();
75            if let Ok(m) = Mount::new(self.get_device_path(), base, fs, MountFlags::empty(), None) {
76                return func(Some((base, m.into_unmount_drop(UnmountFlags::DETACH))));
77            }
78        }
79
80        func(None)
81    }
82
83    /// Detects if an OS is installed to this partition, and if so, what the OS
84    /// is named.
85    fn probe_os(&self) -> Option<OS> {
86        self.get_file_system().and_then(|fs| detect_os_from_device(self.get_device_path(), fs))
87    }
88
89    /// True if the sectors in the compared partition differs from the source.
90    fn sectors_differ_from<P: PartitionExt>(&self, other: &P) -> bool {
91        self.get_sector_start() != other.get_sector_start()
92            || self.get_sector_end() != other.get_sector_end()
93    }
94
95    /// True if the given sector lies within this partition.
96    fn sector_lies_within(&self, sector: u64) -> bool {
97        sector >= self.get_sector_start() && sector <= self.get_sector_end()
98    }
99
100    /// True if there is an overlap in sectors between both partitions.
101    fn sectors_overlap(&self, start: u64, end: u64) -> bool {
102        let pstart = self.get_sector_start();
103        let pend = self.get_sector_end();
104        !((start < pstart && end < pstart) || (start > pend && end > pend))
105    }
106
107    /// Executes a given file system's dump command to obtain the minimum shrink size
108    ///
109    /// The return value is measured in sectors normalized to the logical sector size
110    /// of the partition.
111    ///
112    /// Returns `io::ErrorKind::NotFound` if getting usage is not supported.
113    fn sectors_used(&self) -> io::Result<u64> {
114        let sector_size = self.get_logical_block_size();
115        self.get_file_system()
116            .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "no file system"))
117            // Fetch the 512-byte sector size
118            .and_then(|fs| sectors_used(self.get_device_path(), fs))
119            // Then normalize it to the actual sector size
120            .map(move |sectors| sectors / (sector_size / 512))
121    }
122}
123
124/// Defines whether the partition is a primary, logical, or extended partition.
125///
126/// # Note
127///
128/// This only applies for MBR partition tables.
129#[derive(Debug, PartialEq, Clone, Copy, Hash)]
130pub enum PartitionType {
131    Primary,
132    Logical,
133    Extended,
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    struct Fake {
141        start_sector: u64,
142        end_sector:   u64,
143        filesystem:   Option<FileSystem>,
144        name:         Option<String>,
145        part_type:    PartitionType,
146        flags:        Vec<PartitionFlag>,
147    }
148
149    impl Default for Fake {
150        fn default() -> Fake {
151            Self {
152                start_sector: 0,
153                end_sector:   1,
154                filesystem:   None,
155                name:         None,
156                part_type:    PartitionType::Primary,
157                flags:        Vec::new(),
158            }
159        }
160    }
161
162    impl BlockDeviceExt for Fake {
163        fn get_device_name(&self) -> &str { "fictional" }
164
165        fn get_device_path(&self) -> &Path { Path::new("/dev/fictional")  }
166    }
167
168    impl PartitionExt for Fake {
169        fn get_file_system(&self) -> Option<FileSystem> { self.filesystem }
170
171        fn get_partition_flags(&self) -> &[PartitionFlag] { &self.flags }
172
173        fn get_partition_label(&self) -> Option<&str> { self.name.as_ref().map(|s| s.as_str()) }
174
175        fn get_partition_type(&self) -> PartitionType { self.part_type }
176
177        fn get_sector_end(&self) -> u64 { self.end_sector }
178
179        fn get_sector_start(&self) -> u64 { self.start_sector }
180    }
181
182    #[test]
183    fn sector_lies_within() {
184        let mut part = Fake::default();
185        part.start_sector = 100_000;
186        part.end_sector = 10_000_000;
187
188        assert!(part.sector_lies_within(100_000));
189        assert!(part.sector_lies_within(10_000_000));
190        assert!(part.sector_lies_within(5_000_000));
191        assert!(!part.sector_lies_within(99_999));
192        assert!(!part.sector_lies_within(10_000_001));
193    }
194
195    #[test]
196    fn sectors_overlap() {
197        let mut part = Fake::default();
198        part.start_sector = 100_000;
199        part.end_sector = 10_000_000;
200
201        assert!(!part.sectors_overlap(0, 99999));
202        assert!(part.sectors_overlap(0, 100_000));
203        assert!(part.sectors_overlap(0, 100_001));
204        assert!(part.sectors_overlap(200_000, 1_000_000));
205        assert!(part.sectors_overlap(9_999_999, 11_000_000));
206        assert!(part.sectors_overlap(10_000_000, 11_000_000));
207        assert!(!part.sectors_overlap(10_000_001, 11_000_000));
208        assert!(part.sectors_overlap(0, 20_000_000))
209    }
210}