fiemap/
lib.rs

1use std::fmt;
2use std::fs::File;
3use std::io::{Error, ErrorKind, Result};
4use std::os::fd::{AsFd, AsRawFd};
5use std::os::raw::{c_int, c_ulong};
6use std::path::Path;
7
8const FS_IOC_FIEMAP: c_ulong = 0xC020660B;
9const PAGESIZE: usize = 8;
10
11unsafe extern "C" {
12    fn ioctl(fd: c_int, request: c_ulong, ...) -> c_int;
13}
14
15#[derive(Debug)]
16pub struct Fiemap<T> {
17    _file: T,
18    fd: c_int,
19    fiemap: C_fiemap,
20    cur_idx: usize,
21    size: u32,
22    ended: bool,
23}
24
25/// Get fiemap for the path and return an iterator of extents.
26///
27/// Same as [`Fiemap::new_from_path`].
28pub fn fiemap<P: AsRef<Path>>(filepath: P) -> Result<Fiemap<File>> {
29    Fiemap::new_from_path(filepath)
30}
31
32impl Fiemap<File> {
33    /// Creates a new [`Fiemap`] from a file path, opening the file in
34    /// read-only mode. See [`File::open`] and [`Fiemap::new`].
35    pub fn new_from_path(filepath: impl AsRef<Path>) -> Result<Self> {
36        let file = File::open(filepath)?;
37
38        Ok(Self::new(file))
39    }
40}
41
42impl<T: AsFd> Fiemap<T> {
43    /// Creates a new [`Fiemap`] from an [`AsFd`] object, which could be [`File`]
44    /// or its reference.
45    pub fn new(fd: T) -> Self {
46        let raw_fd = fd.as_fd().as_raw_fd();
47
48        Self {
49            _file: fd,
50            fd: raw_fd,
51            fiemap: C_fiemap::new(),
52            cur_idx: 0,
53            size: 0,
54            ended: false,
55        }
56    }
57
58    fn get_extents(&mut self) -> Result<()> {
59        let req = &mut self.fiemap;
60        if self.size != 0 {
61            let last = req.fm_extents[self.size as usize - 1];
62            req.fm_start = last.fe_logical + last.fe_length;
63        }
64
65        let rc = unsafe { ioctl(self.fd, FS_IOC_FIEMAP, req as *mut _) };
66        if rc != 0 {
67            Err(Error::last_os_error())
68        } else {
69            self.cur_idx = 0;
70            self.size = req.fm_mapped_extents;
71            if req.fm_mapped_extents == 0
72                || req.fm_extents[req.fm_mapped_extents as usize - 1]
73                    .fe_flags
74                    .contains(FiemapExtentFlags::LAST)
75            {
76                self.ended = true;
77            }
78            Ok(())
79        }
80    }
81}
82
83impl<T: AsFd> Iterator for Fiemap<T> {
84    type Item = Result<FiemapExtent>;
85    fn next(&mut self) -> Option<Self::Item> {
86        if self.cur_idx >= self.size as usize {
87            if self.ended {
88                return None;
89            }
90
91            while let Err(e) = self.get_extents() {
92                if e.kind() == ErrorKind::Interrupted {
93                    continue;
94                }
95                self.ended = true;
96                return Some(Err(e));
97            }
98
99            if self.size == 0 {
100                // we didn't get any more extents
101                return None;
102            }
103        }
104
105        let idx = self.cur_idx;
106        self.cur_idx += 1;
107        Some(Ok(self.fiemap.fm_extents[idx]))
108    }
109}
110
111#[derive(Debug)]
112#[repr(C)]
113struct C_fiemap {
114    fm_start: u64,
115    fm_length: u64,
116    fm_flags: u32,
117    fm_mapped_extents: u32,
118    fm_extent_count: u32,
119    fm_reserved: u32,
120    fm_extents: [FiemapExtent; PAGESIZE],
121}
122
123impl C_fiemap {
124    fn new() -> Self {
125        Self {
126            fm_start: 0,
127            fm_length: u64::MAX,
128            fm_flags: 0,
129            fm_mapped_extents: 0,
130            fm_extent_count: PAGESIZE as u32,
131            fm_reserved: 0,
132            fm_extents: [FiemapExtent::new(); PAGESIZE],
133        }
134    }
135}
136
137#[repr(C)]
138#[derive(Copy, Clone)]
139pub struct FiemapExtent {
140    pub fe_logical: u64,
141    pub fe_physical: u64,
142    pub fe_length: u64,
143    fe_reserved64: [u64; 2],
144    pub fe_flags: FiemapExtentFlags,
145    fe_reserved: [u32; 3],
146}
147
148impl FiemapExtent {
149    fn new() -> Self {
150        Self {
151            fe_logical: 0,
152            fe_physical: 0,
153            fe_length: 0,
154            fe_reserved64: [0; 2],
155            fe_flags: FiemapExtentFlags::empty(),
156            fe_reserved: [0; 3],
157        }
158    }
159}
160
161impl fmt::Debug for FiemapExtent {
162    fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> {
163        f.debug_struct("FiemapExtent")
164            .field("fe_logical", &self.fe_logical)
165            .field("fe_physical", &self.fe_physical)
166            .field("fe_length", &self.fe_length)
167            .field("fe_flags", &self.fe_flags)
168            .finish()
169    }
170}
171
172bitflags::bitflags! {
173  #[derive(Copy, Clone, Debug)]
174  pub struct FiemapExtentFlags: u32 {
175    #[doc = "Last extent in file."]
176    const LAST           = 0x00000001;
177    #[doc = "Data location unknown."]
178    const UNKNOWN        = 0x00000002;
179    #[doc = "Location still pending. Sets EXTENT_UNKNOWN."]
180    const DELALLOC       = 0x00000004;
181    #[doc = "Data can not be read while fs is unmounted"]
182    const ENCODED        = 0x00000008;
183    #[doc = "Data is encrypted by fs. Sets EXTENT_NO_BYPASS."]
184    const DATA_ENCRYPTED = 0x00000080;
185    #[doc = "Extent offsets may not be block aligned."]
186    const NOT_ALIGNED    = 0x00000100;
187    #[doc = "Data mixed with metadata. Sets EXTENT_NOT_ALIGNED."]
188    const DATA_INLINE    = 0x00000200;
189    #[doc = "Multiple files in block. Sets EXTENT_NOT_ALIGNED."]
190    const DATA_TAIL      = 0x00000400;
191    #[doc = "Space allocated, but no data (i.e. zero)."]
192    const UNWRITTEN      = 0x00000800;
193    #[doc = "File does not natively support extents. Result merged for efficiency."]
194    const MERGED         = 0x00001000;
195    #[doc = "Space shared with other files."]
196    const SHARED         = 0x00002000;
197  }
198}