mz_ore/pool/file/sys/
linux.rs1use std::ffi::CString;
19use std::fs::{File, OpenOptions};
20use std::io;
21use std::os::fd::AsRawFd;
22use std::os::unix::ffi::OsStrExt;
23use std::os::unix::fs::{FileExt, OpenOptionsExt};
24use std::path::Path;
25use std::sync::atomic::{AtomicU64, Ordering};
26
27use crate::cast::CastFrom;
28
29fn c_path(path: &Path) -> io::Result<CString> {
32 CString::new(path.as_os_str().as_bytes())
33 .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))
34}
35
36const OPEN_NAMED_ATTEMPTS: usize = 8;
39
40fn off_t(offset: u64) -> io::Result<libc::off_t> {
43 libc::off_t::try_from(offset).map_err(|_| io::Error::from_raw_os_error(libc::EFBIG))
44}
45
46pub(in crate::pool::file) fn is_memory_backed(path: &Path) -> io::Result<bool> {
51 let path = c_path(path)?;
52 let mut st = std::mem::MaybeUninit::<libc::statfs>::uninit();
53 if unsafe { libc::statfs(path.as_ptr(), st.as_mut_ptr()) } != 0 {
56 return Err(io::Error::last_os_error());
57 }
58 let f_type = unsafe { st.assume_init() }.f_type;
60 const TMPFS_MAGIC: u32 = 0x0102_1994;
63 const RAMFS_MAGIC: u32 = 0x8584_58f6;
64 let f_type = i128::from(f_type);
65 Ok(f_type == i128::from(TMPFS_MAGIC) || f_type == i128::from(RAMFS_MAGIC))
66}
67
68pub(in crate::pool::file) fn available_bytes(path: &Path) -> io::Result<u64> {
70 let path = c_path(path)?;
71 let mut st = std::mem::MaybeUninit::<libc::statvfs>::uninit();
72 if unsafe { libc::statvfs(path.as_ptr(), st.as_mut_ptr()) } != 0 {
75 return Err(io::Error::last_os_error());
76 }
77 let st = unsafe { st.assume_init() };
79 #[allow(clippy::useless_conversion)]
81 let (bavail, frsize) = (u64::from(st.f_bavail), u64::from(st.f_frsize));
82 Ok(bavail.saturating_mul(frsize))
83}
84
85pub(in crate::pool::file) fn open_anonymous(
88 dir: &Path,
89 class: usize,
90 direct: bool,
91) -> io::Result<File> {
92 let direct = if direct { libc::O_DIRECT } else { 0 };
93 let tmpfile = match tmpfile_fault() {
95 Some(err) => Err(err),
96 None => OpenOptions::new()
97 .read(true)
98 .write(true)
99 .mode(0o600)
100 .custom_flags(libc::O_TMPFILE | direct)
101 .open(dir),
102 };
103 match tmpfile {
104 Err(err) if matches!(err.raw_os_error(), Some(libc::EOPNOTSUPP | libc::EISDIR)) => {
105 static NONCE: AtomicU64 = AtomicU64::new(0);
111 let mut attempts = 0;
112 let (file, path) = loop {
113 let nanos = std::time::SystemTime::now()
114 .duration_since(std::time::UNIX_EPOCH)
115 .map_or(0, |d| d.as_nanos());
116 let path = dir.join(format!(
117 ".mz-pool-extents-{}-{nanos}-{class}-{}",
118 std::process::id(),
119 NONCE.fetch_add(1, Ordering::Relaxed),
120 ));
121 match OpenOptions::new()
122 .read(true)
123 .write(true)
124 .create_new(true)
125 .mode(0o600)
126 .custom_flags(direct)
127 .open(&path)
128 {
129 Ok(file) => break (file, path),
130 Err(err)
131 if err.kind() == io::ErrorKind::AlreadyExists
132 && attempts + 1 < OPEN_NAMED_ATTEMPTS =>
133 {
134 attempts += 1;
135 }
136 Err(err) => return Err(err),
137 }
138 };
139 std::fs::remove_file(&path)?;
140 Ok(file)
141 }
142 result => result,
143 }
144}
145
146fn tmpfile_fault() -> Option<io::Error> {
148 #[cfg(test)]
149 if let Some(result) = crate::pool::file::testing::fault::inject(
150 crate::pool::file::testing::fault::Op::OpenTmpfile,
151 ) {
152 return Some(result.expect_err("an OpenTmpfile fault carries an errno"));
153 }
154 None
155}
156
157pub(in crate::pool::file) fn fallocate(file: &File, offset: u64, len: usize) -> io::Result<()> {
159 #[cfg(test)]
160 if let Some(result) =
161 crate::pool::file::testing::fault::inject(crate::pool::file::testing::fault::Op::Fallocate)
162 {
163 return result.map(|_| ());
164 }
165 fallocate_mode(file, 0, offset, len)
166}
167
168pub(in crate::pool::file) fn punch_hole(file: &File, offset: u64, len: usize) -> io::Result<()> {
171 #[cfg(test)]
172 if let Some(result) =
173 crate::pool::file::testing::fault::inject(crate::pool::file::testing::fault::Op::Punch)
174 {
175 return result.map(|_| ());
176 }
177 fallocate_mode(
178 file,
179 libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_KEEP_SIZE,
180 offset,
181 len,
182 )
183}
184
185fn fallocate_mode(file: &File, mode: libc::c_int, offset: u64, len: usize) -> io::Result<()> {
186 let offset = off_t(offset)?;
187 let len = off_t(u64::cast_from(len))?;
188 loop {
189 if unsafe { libc::fallocate(file.as_raw_fd(), mode, offset, len) } == 0 {
192 return Ok(());
193 }
194 let err = io::Error::last_os_error();
195 if err.kind() != io::ErrorKind::Interrupted {
196 return Err(err);
197 }
198 }
199}
200
201pub(in crate::pool::file) fn pwrite(file: &File, buf: &[u8], offset: u64) -> io::Result<usize> {
203 #[cfg(test)]
204 if let Some(result) =
205 crate::pool::file::testing::fault::inject(crate::pool::file::testing::fault::Op::Write)
206 {
207 return result;
208 }
209 file.write_at(buf, offset)
210}
211
212pub(in crate::pool::file) fn pread(file: &File, buf: &mut [u8], offset: u64) -> io::Result<usize> {
214 #[cfg(test)]
215 if let Some(result) =
216 crate::pool::file::testing::fault::inject(crate::pool::file::testing::fault::Op::Read)
217 {
218 return result;
219 }
220 file.read_at(buf, offset)
221}
222
223pub(in crate::pool::file) fn writeback_and_drop(
226 file: &File,
227 offset: u64,
228 len: usize,
229) -> io::Result<()> {
230 let range_offset = off_t(offset)?;
231 let range_len = off_t(u64::cast_from(len))?;
232 let synced = unsafe {
234 libc::sync_file_range(
235 file.as_raw_fd(),
236 range_offset,
237 range_len,
238 libc::SYNC_FILE_RANGE_WAIT_BEFORE
239 | libc::SYNC_FILE_RANGE_WRITE
240 | libc::SYNC_FILE_RANGE_WAIT_AFTER,
241 )
242 };
243 if synced != 0 {
244 return Err(io::Error::last_os_error());
245 }
246 drop_cache(file, offset, len)
247}
248
249pub(in crate::pool::file) fn drop_cache(file: &File, offset: u64, len: usize) -> io::Result<()> {
251 let offset = off_t(offset)?;
252 let len = off_t(u64::cast_from(len))?;
253 let errno =
255 unsafe { libc::posix_fadvise(file.as_raw_fd(), offset, len, libc::POSIX_FADV_DONTNEED) };
256 match errno {
257 0 => Ok(()),
258 errno => Err(io::Error::from_raw_os_error(errno)),
259 }
260}