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
25pub fn fiemap<P: AsRef<Path>>(filepath: P) -> Result<Fiemap<File>> {
29 Fiemap::new_from_path(filepath)
30}
31
32impl Fiemap<File> {
33 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 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 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}