e_utils/fs/options/
unix.rs

1use 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/// 文件属性
13#[repr(u32)]
14#[derive(Debug, Clone)]
15pub enum FileAttribute {
16  /// 无权限
17  Empty = 0,
18}
19/// 共享
20#[repr(u32)]
21#[derive(Debug, Clone)]
22pub enum FileShare {
23  /// 只读
24  Read = 1,
25  /// 只写
26  Write = 2,
27  /// 只删
28  Delete = 4,
29  /// 读写
30  ReadWrite = 1 | 2,
31  /// 所有
32  All = 1 | 2 | 4,
33  /// 无权限
34  Empty = 0,
35}
36/// This will override the read, write, and append flags on the OpenOptions structure. This method provides fine-grained control over the permissions to read, write and append data, attributes (like hidden and system), and extended attributes.
37#[repr(u32)]
38#[derive(Debug, Clone)]
39pub enum FileAccess {
40  /// 无权限
41  Empty = 0,
42}
43
44/// 安全
45#[repr(u32)]
46#[derive(Debug, Clone)]
47pub enum FileSecurity {
48  /// 无权限
49  Empty = 0,
50}
51/// 安全
52#[repr(u32)]
53#[derive(Debug, Clone)]
54pub enum FileCustom {
55  /// 无权限
56  Empty = 0,
57}
58
59///
60pub(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}
71///
72pub(crate) fn lock_shared(file: &File) -> Result<()> {
73  flock(file, libc::LOCK_SH)
74}
75///
76pub(crate) fn lock_exclusive(file: &File) -> Result<()> {
77  flock(file, libc::LOCK_EX)
78}
79///
80pub(crate) fn try_lock_shared(file: &File) -> Result<()> {
81  flock(file, libc::LOCK_SH | libc::LOCK_NB)
82}
83///
84pub(crate) fn try_lock_exclusive(file: &File) -> Result<()> {
85  flock(file, libc::LOCK_EX | libc::LOCK_NB)
86}
87///
88pub(crate) fn unlock(file: &File) -> Result<()> {
89  flock(file, libc::LOCK_UN)
90}
91///
92pub(crate) fn lock_error() -> Error {
93  Error::from_raw_os_error(libc::EWOULDBLOCK)
94}
95///
96#[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/// Simulate flock() using fcntl(); primarily for Oracle Solaris.
107#[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  // In non-blocking mode, use F_SETLK for cmd, F_SETLKW otherwise, and don't forget to clear
120  // LOCK_NB.
121  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    // Translate EACCES to EWOULDBLOCK
136    -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}
143///
144pub(crate) fn allocated_size(file: &File) -> Result<u64> {
145  file.metadata().map(|m| m.blocks() as u64 * 512)
146}
147
148///
149#[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///
159#[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      // Unable to allocate contiguous disk space; attempt to allocate non-contiguously.
175      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///
191#[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  // No file allocation API available, just set the length if necessary.
200  if len > file.metadata()?.len() as u64 {
201    file.set_len(len)
202  } else {
203    Ok(())
204  }
205}
206///
207pub(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    // danburkert/fs2-rs#1: cast is necessary for platforms where c_char != u8.
216    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///
229#[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  /// The duplicate method returns a file with a new file descriptor.
237  #[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  /// The duplicate method should preservesthe close on exec flag.
251  #[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  /// Tests that locking a file descriptor will replace any existing locks
270  /// held on the file descriptor.
271  #[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    // Creating a shared lock will drop an exclusive lock.
287    file1.lock_exclusive().unwrap();
288    file1.lock_shared().unwrap();
289    file2.lock_shared().unwrap();
290
291    // Attempting to replace a shared lock with an exclusive lock will fail
292    // with multiple lock holders, and remove the original shared lock.
293    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  /// Tests that locks are shared among duplicated file descriptors.
301  #[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    // Create a lock through fd1, then replace it through fd2.
318    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    // Either of the file descriptors should be able to unlock.
326    file1.unlock().unwrap();
327    file3.lock_shared().unwrap();
328  }
329}