e_utils/fs/options/
unix.rs1use std::ffi::CString;
2use std::fs::File;
3use std::io::{Error, ErrorKind, Result};
4use std::mem;
5use std::os::unix::ffi::OsStrExt;
6use std::os::unix::fs::MetadataExt;
7use std::os::unix::io::{AsRawFd, FromRawFd};
8use std::path::Path;
9
10use super::FsStats;
11
12#[repr(u32)]
14#[derive(Debug, Clone)]
15pub enum FileAttribute {
16 Empty = 0,
18}
19#[repr(u32)]
21#[derive(Debug, Clone)]
22pub enum FileShare {
23 Read = 1,
25 Write = 2,
27 Delete = 4,
29 ReadWrite = 1 | 2,
31 All = 1 | 2 | 4,
33 Empty = 0,
35}
36#[repr(u32)]
38#[derive(Debug, Clone)]
39pub enum FileAccess {
40 Empty = 0,
42}
43
44#[repr(u32)]
46#[derive(Debug, Clone)]
47pub enum FileSecurity {
48 Empty = 0,
50}
51#[repr(u32)]
53#[derive(Debug, Clone)]
54pub enum FileCustom {
55 Empty = 0,
57}
58
59pub(crate) fn duplicate(file: &File) -> Result<File> {
61 unsafe {
62 let fd = libc::dup(file.as_raw_fd());
63
64 if fd < 0 {
65 Err(Error::last_os_error())
66 } else {
67 Ok(File::from_raw_fd(fd))
68 }
69 }
70}
71pub(crate) fn lock_shared(file: &File) -> Result<()> {
73 flock(file, libc::LOCK_SH)
74}
75pub(crate) fn lock_exclusive(file: &File) -> Result<()> {
77 flock(file, libc::LOCK_EX)
78}
79pub(crate) fn try_lock_shared(file: &File) -> Result<()> {
81 flock(file, libc::LOCK_SH | libc::LOCK_NB)
82}
83pub(crate) fn try_lock_exclusive(file: &File) -> Result<()> {
85 flock(file, libc::LOCK_EX | libc::LOCK_NB)
86}
87pub(crate) fn unlock(file: &File) -> Result<()> {
89 flock(file, libc::LOCK_UN)
90}
91pub(crate) fn lock_error() -> Error {
93 Error::from_raw_os_error(libc::EWOULDBLOCK)
94}
95#[cfg(not(target_os = "solaris"))]
97fn flock(file: &File, flag: libc::c_int) -> Result<()> {
98 let ret = unsafe { libc::flock(file.as_raw_fd(), flag) };
99 if ret < 0 {
100 Err(Error::last_os_error())
101 } else {
102 Ok(())
103 }
104}
105
106#[cfg(target_os = "solaris")]
108fn flock(file: &File, flag: libc::c_int) -> Result<()> {
109 let mut fl = libc::flock {
110 l_whence: 0,
111 l_start: 0,
112 l_len: 0,
113 l_type: 0,
114 l_pad: [0; 4],
115 l_pid: 0,
116 l_sysid: 0,
117 };
118
119 let (cmd, operation) = match flag & libc::LOCK_NB {
122 0 => (libc::F_SETLKW, flag),
123 _ => (libc::F_SETLK, flag & !libc::LOCK_NB),
124 };
125
126 match operation {
127 libc::LOCK_SH => fl.l_type |= libc::F_RDLCK,
128 libc::LOCK_EX => fl.l_type |= libc::F_WRLCK,
129 libc::LOCK_UN => fl.l_type |= libc::F_UNLCK,
130 _ => return Err(Error::from_raw_os_error(libc::EINVAL)),
131 }
132
133 let ret = unsafe { libc::fcntl(file.as_raw_fd(), cmd, &fl) };
134 match ret {
135 -1 => match Error::last_os_error().raw_os_error() {
137 Some(libc::EACCES) => return Err(lock_error()),
138 _ => return Err(Error::last_os_error()),
139 },
140 _ => Ok(()),
141 }
142}
143pub(crate) fn allocated_size(file: &File) -> Result<u64> {
145 file.metadata().map(|m| m.blocks() as u64 * 512)
146}
147
148#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "android"))]
150pub(crate) fn allocate(file: &File, len: u64) -> Result<()> {
151 let ret = unsafe { libc::posix_fallocate(file.as_raw_fd(), 0, len as libc::off_t) };
152 if ret == 0 {
153 Ok(())
154 } else {
155 Err(Error::last_os_error())
156 }
157}
158#[cfg(any(target_os = "macos", target_os = "ios"))]
160pub(crate) fn allocate(file: &File, len: u64) -> Result<()> {
161 let stat = file.metadata()?;
162
163 if len > stat.blocks() as u64 * 512 {
164 let mut fstore = libc::fstore_t {
165 fst_flags: libc::F_ALLOCATECONTIG,
166 fst_posmode: libc::F_PEOFPOSMODE,
167 fst_offset: 0,
168 fst_length: len as libc::off_t,
169 fst_bytesalloc: 0,
170 };
171
172 let ret = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_PREALLOCATE, &fstore) };
173 if ret == -1 {
174 fstore.fst_flags = libc::F_ALLOCATEALL;
176 let ret = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_PREALLOCATE, &fstore) };
177 if ret == -1 {
178 return Err(Error::last_os_error());
179 }
180 }
181 }
182
183 if len > stat.size() as u64 {
184 file.set_len(len)
185 } else {
186 Ok(())
187 }
188}
189
190#[cfg(any(
192 target_os = "openbsd",
193 target_os = "netbsd",
194 target_os = "dragonfly",
195 target_os = "solaris",
196 target_os = "haiku"
197))]
198pub(crate) fn allocate(file: &File, len: u64) -> Result<()> {
199 if len > file.metadata()?.len() as u64 {
201 file.set_len(len)
202 } else {
203 Ok(())
204 }
205}
206pub(crate) fn statvfs(path: &Path) -> Result<FsStats> {
208 let cstr = match CString::new(path.as_os_str().as_bytes()) {
209 Ok(cstr) => cstr,
210 Err(..) => return Err(Error::new(ErrorKind::InvalidInput, "path contained a null")),
211 };
212
213 unsafe {
214 let mut stat: libc::statvfs = mem::zeroed();
215 if libc::statvfs(cstr.as_ptr() as *const _, &mut stat) != 0 {
217 Err(Error::last_os_error())
218 } else {
219 Ok(FsStats {
220 free_space: stat.f_frsize as u64 * stat.f_bfree as u64,
221 available_space: stat.f_frsize as u64 * stat.f_bavail as u64,
222 total_space: stat.f_frsize as u64 * stat.f_blocks as u64,
223 allocation_granularity: stat.f_frsize as u64,
224 })
225 }
226 }
227}
228#[cfg(test)]
230mod test {
231 use std::fs::{self, File};
232 use std::os::unix::io::AsRawFd;
233
234 use crate::fs::options::{lock_contended_error, FileExt};
235
236 #[test]
238 fn duplicate_new_fd() {
239 let tempdir = tempdir::TempDir::new("fs2").unwrap();
240 let path = tempdir.path().join("fs2");
241 let file1 = fs::OpenOptions::new()
242 .write(true)
243 .create(true)
244 .open(&path)
245 .unwrap();
246 let file2 = file1.duplicate().unwrap();
247 assert!(file1.as_raw_fd() != file2.as_raw_fd());
248 }
249
250 #[test]
252 fn duplicate_cloexec() {
253 fn flags(file: &File) -> libc::c_int {
254 unsafe { libc::fcntl(file.as_raw_fd(), libc::F_GETFL, 0) }
255 }
256
257 let tempdir = tempdir::TempDir::new("fs2").unwrap();
258 let path = tempdir.path().join("fs2");
259 let file1 = fs::OpenOptions::new()
260 .write(true)
261 .create(true)
262 .open(&path)
263 .unwrap();
264 let file2 = file1.duplicate().unwrap();
265
266 assert_eq!(flags(&file1), flags(&file2));
267 }
268
269 #[test]
272 fn lock_replace() {
273 let tempdir = tempdir::TempDir::new("fs2").unwrap();
274 let path = tempdir.path().join("fs2");
275 let file1 = fs::OpenOptions::new()
276 .write(true)
277 .create(true)
278 .open(&path)
279 .unwrap();
280 let file2 = fs::OpenOptions::new()
281 .write(true)
282 .create(true)
283 .open(&path)
284 .unwrap();
285
286 file1.lock_exclusive().unwrap();
288 file1.lock_shared().unwrap();
289 file2.lock_shared().unwrap();
290
291 assert_eq!(
294 file2.try_lock_exclusive().unwrap_err().raw_os_error(),
295 lock_contended_error().raw_os_error()
296 );
297 file1.lock_shared().unwrap();
298 }
299
300 #[test]
302 fn lock_duplicate() {
303 let tempdir = tempdir::TempDir::new("fs2").unwrap();
304 let path = tempdir.path().join("fs2");
305 let file1 = fs::OpenOptions::new()
306 .write(true)
307 .create(true)
308 .open(&path)
309 .unwrap();
310 let file2 = file1.duplicate().unwrap();
311 let file3 = fs::OpenOptions::new()
312 .write(true)
313 .create(true)
314 .open(&path)
315 .unwrap();
316
317 file1.lock_shared().unwrap();
319 file2.lock_exclusive().unwrap();
320 assert_eq!(
321 file3.try_lock_shared().unwrap_err().raw_os_error(),
322 lock_contended_error().raw_os_error()
323 );
324
325 file1.unlock().unwrap();
327 file3.lock_shared().unwrap();
328 }
329}