nix/
fcntl.rs

1use crate::errno::Errno;
2use libc::{self, c_char, c_int, c_uint, size_t, ssize_t};
3use std::ffi::OsString;
4#[cfg(not(target_os = "redox"))]
5use std::os::raw;
6use std::os::unix::ffi::OsStringExt;
7use std::os::unix::io::RawFd;
8
9#[cfg(any(target_os = "android", target_os = "linux"))]
10use std::ptr; // For splice and copy_file_range
11#[cfg(feature = "fs")]
12use crate::{
13    NixPath,
14    Result,
15    sys::stat::Mode
16};
17
18#[cfg(any(
19    target_os = "linux",
20    target_os = "android",
21    target_os = "emscripten",
22    target_os = "fuchsia",
23    any(target_os = "wasi", target_env = "wasi"),
24    target_env = "uclibc",
25    target_os = "freebsd"
26))]
27#[cfg(feature = "fs")]
28pub use self::posix_fadvise::{PosixFadviseAdvice, posix_fadvise};
29
30#[cfg(not(target_os = "redox"))]
31#[cfg(any(feature = "fs", feature = "process"))]
32libc_bitflags! {
33    #[cfg_attr(docsrs, doc(cfg(any(feature = "fs", feature = "process"))))]
34    pub struct AtFlags: c_int {
35        AT_REMOVEDIR;
36        AT_SYMLINK_FOLLOW;
37        AT_SYMLINK_NOFOLLOW;
38        #[cfg(any(target_os = "android", target_os = "linux"))]
39        AT_NO_AUTOMOUNT;
40        #[cfg(any(target_os = "android", target_os = "linux"))]
41        AT_EMPTY_PATH;
42        #[cfg(any(target_os = "illumos", target_os = "solaris"))]
43        AT_EACCESS;
44    }
45}
46
47#[cfg(any(feature = "fs", feature = "term"))]
48libc_bitflags!(
49    /// Configuration options for opened files.
50    #[cfg_attr(docsrs, doc(cfg(any(feature = "fs", feature = "term"))))]
51    pub struct OFlag: c_int {
52        /// Mask for the access mode of the file.
53        O_ACCMODE;
54        /// Use alternate I/O semantics.
55        #[cfg(target_os = "netbsd")]
56        #[cfg_attr(docsrs, doc(cfg(all())))]
57        O_ALT_IO;
58        /// Open the file in append-only mode.
59        O_APPEND;
60        /// Generate a signal when input or output becomes possible.
61        #[cfg(not(any(target_os = "illumos", target_os = "solaris")))]
62        #[cfg_attr(docsrs, doc(cfg(all())))]
63        O_ASYNC;
64        /// Closes the file descriptor once an `execve` call is made.
65        ///
66        /// Also sets the file offset to the beginning of the file.
67        O_CLOEXEC;
68        /// Create the file if it does not exist.
69        O_CREAT;
70        /// Try to minimize cache effects of the I/O for this file.
71        #[cfg(any(target_os = "android",
72                  target_os = "dragonfly",
73                  target_os = "freebsd",
74                  target_os = "linux",
75                  target_os = "netbsd"))]
76        #[cfg_attr(docsrs, doc(cfg(all())))]
77        O_DIRECT;
78        /// If the specified path isn't a directory, fail.
79        #[cfg(not(any(target_os = "illumos", target_os = "solaris")))]
80        #[cfg_attr(docsrs, doc(cfg(all())))]
81        O_DIRECTORY;
82        /// Implicitly follow each `write()` with an `fdatasync()`.
83        #[cfg(any(target_os = "android",
84                  target_os = "ios",
85                  target_os = "linux",
86                  target_os = "macos",
87                  target_os = "netbsd",
88                  target_os = "openbsd"))]
89        #[cfg_attr(docsrs, doc(cfg(all())))]
90        O_DSYNC;
91        /// Error out if a file was not created.
92        O_EXCL;
93        /// Open for execute only.
94        #[cfg(target_os = "freebsd")]
95        #[cfg_attr(docsrs, doc(cfg(all())))]
96        O_EXEC;
97        /// Open with an exclusive file lock.
98        #[cfg(any(target_os = "dragonfly",
99                  target_os = "freebsd",
100                  target_os = "ios",
101                  target_os = "macos",
102                  target_os = "netbsd",
103                  target_os = "openbsd",
104                  target_os = "redox"))]
105        #[cfg_attr(docsrs, doc(cfg(all())))]
106        O_EXLOCK;
107        /// Same as `O_SYNC`.
108        #[cfg(any(target_os = "dragonfly",
109                  target_os = "freebsd",
110                  target_os = "ios",
111                  all(target_os = "linux", not(target_env = "musl")),
112                  target_os = "macos",
113                  target_os = "netbsd",
114                  target_os = "openbsd",
115                  target_os = "redox"))]
116        #[cfg_attr(docsrs, doc(cfg(all())))]
117        O_FSYNC;
118        /// Allow files whose sizes can't be represented in an `off_t` to be opened.
119        #[cfg(any(target_os = "android", target_os = "linux"))]
120        #[cfg_attr(docsrs, doc(cfg(all())))]
121        O_LARGEFILE;
122        /// Do not update the file last access time during `read(2)`s.
123        #[cfg(any(target_os = "android", target_os = "linux"))]
124        #[cfg_attr(docsrs, doc(cfg(all())))]
125        O_NOATIME;
126        /// Don't attach the device as the process' controlling terminal.
127        #[cfg(not(target_os = "redox"))]
128        #[cfg_attr(docsrs, doc(cfg(all())))]
129        O_NOCTTY;
130        /// Same as `O_NONBLOCK`.
131        #[cfg(not(target_os = "redox"))]
132        #[cfg_attr(docsrs, doc(cfg(all())))]
133        O_NDELAY;
134        /// `open()` will fail if the given path is a symbolic link.
135        O_NOFOLLOW;
136        /// When possible, open the file in nonblocking mode.
137        O_NONBLOCK;
138        /// Don't deliver `SIGPIPE`.
139        #[cfg(target_os = "netbsd")]
140        #[cfg_attr(docsrs, doc(cfg(all())))]
141        O_NOSIGPIPE;
142        /// Obtain a file descriptor for low-level access.
143        ///
144        /// The file itself is not opened and other file operations will fail.
145        #[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
146        #[cfg_attr(docsrs, doc(cfg(all())))]
147        O_PATH;
148        /// Only allow reading.
149        ///
150        /// This should not be combined with `O_WRONLY` or `O_RDWR`.
151        O_RDONLY;
152        /// Allow both reading and writing.
153        ///
154        /// This should not be combined with `O_WRONLY` or `O_RDONLY`.
155        O_RDWR;
156        /// Similar to `O_DSYNC` but applies to `read`s instead.
157        #[cfg(any(target_os = "linux", target_os = "netbsd", target_os = "openbsd"))]
158        #[cfg_attr(docsrs, doc(cfg(all())))]
159        O_RSYNC;
160        /// Skip search permission checks.
161        #[cfg(target_os = "netbsd")]
162        #[cfg_attr(docsrs, doc(cfg(all())))]
163        O_SEARCH;
164        /// Open with a shared file lock.
165        #[cfg(any(target_os = "dragonfly",
166                  target_os = "freebsd",
167                  target_os = "ios",
168                  target_os = "macos",
169                  target_os = "netbsd",
170                  target_os = "openbsd",
171                  target_os = "redox"))]
172        #[cfg_attr(docsrs, doc(cfg(all())))]
173        O_SHLOCK;
174        /// Implicitly follow each `write()` with an `fsync()`.
175        #[cfg(not(target_os = "redox"))]
176        #[cfg_attr(docsrs, doc(cfg(all())))]
177        O_SYNC;
178        /// Create an unnamed temporary file.
179        #[cfg(any(target_os = "android", target_os = "linux"))]
180        #[cfg_attr(docsrs, doc(cfg(all())))]
181        O_TMPFILE;
182        /// Truncate an existing regular file to 0 length if it allows writing.
183        O_TRUNC;
184        /// Restore default TTY attributes.
185        #[cfg(target_os = "freebsd")]
186        #[cfg_attr(docsrs, doc(cfg(all())))]
187        O_TTY_INIT;
188        /// Only allow writing.
189        ///
190        /// This should not be combined with `O_RDONLY` or `O_RDWR`.
191        O_WRONLY;
192    }
193);
194
195feature! {
196#![feature = "fs"]
197
198// The conversion is not identical on all operating systems.
199#[allow(clippy::useless_conversion)]
200pub fn open<P: ?Sized + NixPath>(path: &P, oflag: OFlag, mode: Mode) -> Result<RawFd> {
201    let fd = path.with_nix_path(|cstr| {
202        unsafe { libc::open(cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint) }
203    })?;
204
205    Errno::result(fd)
206}
207
208// The conversion is not identical on all operating systems.
209#[allow(clippy::useless_conversion)]
210#[cfg(not(target_os = "redox"))]
211pub fn openat<P: ?Sized + NixPath>(
212    dirfd: RawFd,
213    path: &P,
214    oflag: OFlag,
215    mode: Mode,
216) -> Result<RawFd> {
217    let fd = path.with_nix_path(|cstr| {
218        unsafe { libc::openat(dirfd, cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint) }
219    })?;
220    Errno::result(fd)
221}
222
223#[cfg(not(target_os = "redox"))]
224pub fn renameat<P1: ?Sized + NixPath, P2: ?Sized + NixPath>(
225    old_dirfd: Option<RawFd>,
226    old_path: &P1,
227    new_dirfd: Option<RawFd>,
228    new_path: &P2,
229) -> Result<()> {
230    let res = old_path.with_nix_path(|old_cstr| {
231        new_path.with_nix_path(|new_cstr| unsafe {
232            libc::renameat(
233                at_rawfd(old_dirfd),
234                old_cstr.as_ptr(),
235                at_rawfd(new_dirfd),
236                new_cstr.as_ptr(),
237            )
238        })
239    })??;
240    Errno::result(res).map(drop)
241}
242}
243
244#[cfg(all(
245    target_os = "linux",
246    target_env = "gnu",
247))]
248#[cfg(feature = "fs")]
249libc_bitflags! {
250    #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
251    pub struct RenameFlags: u32 {
252        RENAME_EXCHANGE;
253        RENAME_NOREPLACE;
254        RENAME_WHITEOUT;
255    }
256}
257
258feature! {
259#![feature = "fs"]
260#[cfg(all(
261    target_os = "linux",
262    target_env = "gnu",
263))]
264pub fn renameat2<P1: ?Sized + NixPath, P2: ?Sized + NixPath>(
265    old_dirfd: Option<RawFd>,
266    old_path: &P1,
267    new_dirfd: Option<RawFd>,
268    new_path: &P2,
269    flags: RenameFlags,
270) -> Result<()> {
271    let res = old_path.with_nix_path(|old_cstr| {
272        new_path.with_nix_path(|new_cstr| unsafe {
273            libc::renameat2(
274                at_rawfd(old_dirfd),
275                old_cstr.as_ptr(),
276                at_rawfd(new_dirfd),
277                new_cstr.as_ptr(),
278                flags.bits(),
279            )
280        })
281    })??;
282    Errno::result(res).map(drop)
283}
284
285fn wrap_readlink_result(mut v: Vec<u8>, len: ssize_t) -> Result<OsString> {
286    unsafe { v.set_len(len as usize) }
287    v.shrink_to_fit();
288    Ok(OsString::from_vec(v.to_vec()))
289}
290
291fn readlink_maybe_at<P: ?Sized + NixPath>(
292    dirfd: Option<RawFd>,
293    path: &P,
294    v: &mut Vec<u8>,
295) -> Result<libc::ssize_t> {
296    path.with_nix_path(|cstr| unsafe {
297        match dirfd {
298            #[cfg(target_os = "redox")]
299            Some(_) => unreachable!(),
300            #[cfg(not(target_os = "redox"))]
301            Some(dirfd) => libc::readlinkat(
302                dirfd,
303                cstr.as_ptr(),
304                v.as_mut_ptr() as *mut c_char,
305                v.capacity() as size_t,
306            ),
307            None => libc::readlink(
308                cstr.as_ptr(),
309                v.as_mut_ptr() as *mut c_char,
310                v.capacity() as size_t,
311            ),
312        }
313    })
314}
315
316fn inner_readlink<P: ?Sized + NixPath>(dirfd: Option<RawFd>, path: &P) -> Result<OsString> {
317    let mut v = Vec::with_capacity(libc::PATH_MAX as usize);
318    // simple case: result is strictly less than `PATH_MAX`
319    let res = readlink_maybe_at(dirfd, path, &mut v)?;
320    let len = Errno::result(res)?;
321    debug_assert!(len >= 0);
322    if (len as usize) < v.capacity() {
323        return wrap_readlink_result(v, res);
324    }
325    // Uh oh, the result is too long...
326    // Let's try to ask lstat how many bytes to allocate.
327    let reported_size = match dirfd {
328        #[cfg(target_os = "redox")]
329        Some(_) => unreachable!(),
330        #[cfg(any(target_os = "android", target_os = "linux"))]
331        Some(dirfd) => {
332            let flags = if path.is_empty() { AtFlags::AT_EMPTY_PATH } else { AtFlags::empty() };
333            super::sys::stat::fstatat(dirfd, path, flags | AtFlags::AT_SYMLINK_NOFOLLOW)
334        },
335        #[cfg(not(any(target_os = "android", target_os = "linux", target_os = "redox")))]
336        Some(dirfd) => super::sys::stat::fstatat(dirfd, path, AtFlags::AT_SYMLINK_NOFOLLOW),
337        None => super::sys::stat::lstat(path)
338    }
339        .map(|x| x.st_size)
340        .unwrap_or(0);
341    let mut try_size = if reported_size > 0 {
342        // Note: even if `lstat`'s apparently valid answer turns out to be
343        // wrong, we will still read the full symlink no matter what.
344        reported_size as usize + 1
345    } else {
346        // If lstat doesn't cooperate, or reports an error, be a little less
347        // precise.
348        (libc::PATH_MAX as usize).max(128) << 1
349    };
350    loop {
351        v.reserve_exact(try_size);
352        let res = readlink_maybe_at(dirfd, path, &mut v)?;
353        let len = Errno::result(res)?;
354        debug_assert!(len >= 0);
355        if (len as usize) < v.capacity() {
356            break wrap_readlink_result(v, res);
357        } else {
358            // Ugh! Still not big enough!
359            match try_size.checked_shl(1) {
360                Some(next_size) => try_size = next_size,
361                // It's absurd that this would happen, but handle it sanely
362                // anyway.
363                None => break Err(Errno::ENAMETOOLONG),
364            }
365        }
366    }
367}
368
369pub fn readlink<P: ?Sized + NixPath>(path: &P) -> Result<OsString> {
370    inner_readlink(None, path)
371}
372
373#[cfg(not(target_os = "redox"))]
374pub fn readlinkat<P: ?Sized + NixPath>(dirfd: RawFd, path: &P) -> Result<OsString> {
375    inner_readlink(Some(dirfd), path)
376}
377
378/// Computes the raw fd consumed by a function of the form `*at`.
379#[cfg(not(target_os = "redox"))]
380pub(crate) fn at_rawfd(fd: Option<RawFd>) -> raw::c_int {
381    match fd {
382        None => libc::AT_FDCWD,
383        Some(fd) => fd,
384    }
385}
386}
387
388#[cfg(any(target_os = "android", target_os = "linux"))]
389#[cfg(feature = "fs")]
390libc_bitflags!(
391    /// Additional flags for file sealing, which allows for limiting operations on a file.
392    #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
393    pub struct SealFlag: c_int {
394        /// Prevents further calls to `fcntl()` with `F_ADD_SEALS`.
395        F_SEAL_SEAL;
396        /// The file cannot be reduced in size.
397        F_SEAL_SHRINK;
398        /// The size of the file cannot be increased.
399        F_SEAL_GROW;
400        /// The file contents cannot be modified.
401        F_SEAL_WRITE;
402    }
403);
404
405#[cfg(feature = "fs")]
406libc_bitflags!(
407    /// Additional configuration flags for `fcntl`'s `F_SETFD`.
408    #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
409    pub struct FdFlag: c_int {
410        /// The file descriptor will automatically be closed during a successful `execve(2)`.
411        FD_CLOEXEC;
412    }
413);
414
415feature! {
416#![feature = "fs"]
417
418#[cfg(not(target_os = "redox"))]
419#[derive(Debug, Eq, Hash, PartialEq)]
420#[non_exhaustive]
421pub enum FcntlArg<'a> {
422    F_DUPFD(RawFd),
423    F_DUPFD_CLOEXEC(RawFd),
424    F_GETFD,
425    F_SETFD(FdFlag), // FD_FLAGS
426    F_GETFL,
427    F_SETFL(OFlag), // O_NONBLOCK
428    F_SETLK(&'a libc::flock),
429    F_SETLKW(&'a libc::flock),
430    F_GETLK(&'a mut libc::flock),
431    #[cfg(any(target_os = "linux", target_os = "android"))]
432    F_OFD_SETLK(&'a libc::flock),
433    #[cfg(any(target_os = "linux", target_os = "android"))]
434    F_OFD_SETLKW(&'a libc::flock),
435    #[cfg(any(target_os = "linux", target_os = "android"))]
436    F_OFD_GETLK(&'a mut libc::flock),
437    #[cfg(any(target_os = "android", target_os = "linux"))]
438    F_ADD_SEALS(SealFlag),
439    #[cfg(any(target_os = "android", target_os = "linux"))]
440    F_GET_SEALS,
441    #[cfg(any(target_os = "macos", target_os = "ios"))]
442    F_FULLFSYNC,
443    #[cfg(any(target_os = "linux", target_os = "android"))]
444    F_GETPIPE_SZ,
445    #[cfg(any(target_os = "linux", target_os = "android"))]
446    F_SETPIPE_SZ(c_int),
447    // TODO: Rest of flags
448}
449
450#[cfg(target_os = "redox")]
451#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
452#[non_exhaustive]
453pub enum FcntlArg {
454    F_DUPFD(RawFd),
455    F_DUPFD_CLOEXEC(RawFd),
456    F_GETFD,
457    F_SETFD(FdFlag), // FD_FLAGS
458    F_GETFL,
459    F_SETFL(OFlag), // O_NONBLOCK
460}
461pub use self::FcntlArg::*;
462
463// TODO: Figure out how to handle value fcntl returns
464pub fn fcntl(fd: RawFd, arg: FcntlArg) -> Result<c_int> {
465    let res = unsafe {
466        match arg {
467            F_DUPFD(rawfd) => libc::fcntl(fd, libc::F_DUPFD, rawfd),
468            F_DUPFD_CLOEXEC(rawfd) => libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, rawfd),
469            F_GETFD => libc::fcntl(fd, libc::F_GETFD),
470            F_SETFD(flag) => libc::fcntl(fd, libc::F_SETFD, flag.bits()),
471            F_GETFL => libc::fcntl(fd, libc::F_GETFL),
472            F_SETFL(flag) => libc::fcntl(fd, libc::F_SETFL, flag.bits()),
473            #[cfg(not(target_os = "redox"))]
474            F_SETLK(flock) => libc::fcntl(fd, libc::F_SETLK, flock),
475            #[cfg(not(target_os = "redox"))]
476            F_SETLKW(flock) => libc::fcntl(fd, libc::F_SETLKW, flock),
477            #[cfg(not(target_os = "redox"))]
478            F_GETLK(flock) => libc::fcntl(fd, libc::F_GETLK, flock),
479            #[cfg(any(target_os = "android", target_os = "linux"))]
480            F_OFD_SETLK(flock) => libc::fcntl(fd, libc::F_OFD_SETLK, flock),
481            #[cfg(any(target_os = "android", target_os = "linux"))]
482            F_OFD_SETLKW(flock) => libc::fcntl(fd, libc::F_OFD_SETLKW, flock),
483            #[cfg(any(target_os = "android", target_os = "linux"))]
484            F_OFD_GETLK(flock) => libc::fcntl(fd, libc::F_OFD_GETLK, flock),
485            #[cfg(any(target_os = "android", target_os = "linux"))]
486            F_ADD_SEALS(flag) => libc::fcntl(fd, libc::F_ADD_SEALS, flag.bits()),
487            #[cfg(any(target_os = "android", target_os = "linux"))]
488            F_GET_SEALS => libc::fcntl(fd, libc::F_GET_SEALS),
489            #[cfg(any(target_os = "macos", target_os = "ios"))]
490            F_FULLFSYNC => libc::fcntl(fd, libc::F_FULLFSYNC),
491            #[cfg(any(target_os = "linux", target_os = "android"))]
492            F_GETPIPE_SZ => libc::fcntl(fd, libc::F_GETPIPE_SZ),
493            #[cfg(any(target_os = "linux", target_os = "android"))]
494            F_SETPIPE_SZ(size) => libc::fcntl(fd, libc::F_SETPIPE_SZ, size),
495        }
496    };
497
498    Errno::result(res)
499}
500
501// TODO: convert to libc_enum
502#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
503#[non_exhaustive]
504pub enum FlockArg {
505    LockShared,
506    LockExclusive,
507    Unlock,
508    LockSharedNonblock,
509    LockExclusiveNonblock,
510    UnlockNonblock,
511}
512
513#[cfg(not(target_os = "redox"))]
514pub fn flock(fd: RawFd, arg: FlockArg) -> Result<()> {
515    use self::FlockArg::*;
516
517    let res = unsafe {
518        match arg {
519            LockShared => libc::flock(fd, libc::LOCK_SH),
520            LockExclusive => libc::flock(fd, libc::LOCK_EX),
521            Unlock => libc::flock(fd, libc::LOCK_UN),
522            LockSharedNonblock => libc::flock(fd, libc::LOCK_SH | libc::LOCK_NB),
523            LockExclusiveNonblock => libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB),
524            UnlockNonblock => libc::flock(fd, libc::LOCK_UN | libc::LOCK_NB),
525        }
526    };
527
528    Errno::result(res).map(drop)
529}
530}
531
532#[cfg(any(target_os = "android", target_os = "linux"))]
533#[cfg(feature = "zerocopy")]
534libc_bitflags! {
535    /// Additional flags to `splice` and friends.
536    #[cfg_attr(docsrs, doc(cfg(feature = "zerocopy")))]
537    pub struct SpliceFFlags: c_uint {
538        /// Request that pages be moved instead of copied.
539        ///
540        /// Not applicable to `vmsplice`.
541        SPLICE_F_MOVE;
542        /// Do not block on I/O.
543        SPLICE_F_NONBLOCK;
544        /// Hint that more data will be coming in a subsequent splice.
545        ///
546        /// Not applicable to `vmsplice`.
547        SPLICE_F_MORE;
548        /// Gift the user pages to the kernel.
549        ///
550        /// Not applicable to `splice`.
551        SPLICE_F_GIFT;
552    }
553}
554
555feature! {
556#![feature = "zerocopy"]
557
558/// Copy a range of data from one file to another
559///
560/// The `copy_file_range` system call performs an in-kernel copy between
561/// file descriptors `fd_in` and `fd_out` without the additional cost of
562/// transferring data from the kernel to user space and then back into the
563/// kernel. It copies up to `len` bytes of data from file descriptor `fd_in` to
564/// file descriptor `fd_out`, overwriting any data that exists within the
565/// requested range of the target file.
566///
567/// If the `off_in` and/or `off_out` arguments are used, the values
568/// will be mutated to reflect the new position within the file after
569/// copying. If they are not used, the relevant filedescriptors will be seeked
570/// to the new position.
571///
572/// On successful completion the number of bytes actually copied will be
573/// returned.
574#[cfg(any(target_os = "android", target_os = "linux"))]
575pub fn copy_file_range(
576    fd_in: RawFd,
577    off_in: Option<&mut libc::loff_t>,
578    fd_out: RawFd,
579    off_out: Option<&mut libc::loff_t>,
580    len: usize,
581) -> Result<usize> {
582    let off_in = off_in
583        .map(|offset| offset as *mut libc::loff_t)
584        .unwrap_or(ptr::null_mut());
585    let off_out = off_out
586        .map(|offset| offset as *mut libc::loff_t)
587        .unwrap_or(ptr::null_mut());
588
589    let ret = unsafe {
590        libc::syscall(
591            libc::SYS_copy_file_range,
592            fd_in,
593            off_in,
594            fd_out,
595            off_out,
596            len,
597            0,
598        )
599    };
600    Errno::result(ret).map(|r| r as usize)
601}
602
603#[cfg(any(target_os = "linux", target_os = "android"))]
604pub fn splice(
605    fd_in: RawFd,
606    off_in: Option<&mut libc::loff_t>,
607    fd_out: RawFd,
608    off_out: Option<&mut libc::loff_t>,
609    len: usize,
610    flags: SpliceFFlags,
611) -> Result<usize> {
612    let off_in = off_in
613        .map(|offset| offset as *mut libc::loff_t)
614        .unwrap_or(ptr::null_mut());
615    let off_out = off_out
616        .map(|offset| offset as *mut libc::loff_t)
617        .unwrap_or(ptr::null_mut());
618
619    let ret = unsafe { libc::splice(fd_in, off_in, fd_out, off_out, len, flags.bits()) };
620    Errno::result(ret).map(|r| r as usize)
621}
622
623#[cfg(any(target_os = "linux", target_os = "android"))]
624pub fn tee(fd_in: RawFd, fd_out: RawFd, len: usize, flags: SpliceFFlags) -> Result<usize> {
625    let ret = unsafe { libc::tee(fd_in, fd_out, len, flags.bits()) };
626    Errno::result(ret).map(|r| r as usize)
627}
628
629#[cfg(any(target_os = "linux", target_os = "android"))]
630pub fn vmsplice(
631    fd: RawFd,
632    iov: &[std::io::IoSlice<'_>],
633    flags: SpliceFFlags
634    ) -> Result<usize>
635{
636    let ret = unsafe {
637        libc::vmsplice(
638            fd,
639            iov.as_ptr() as *const libc::iovec,
640            iov.len(),
641            flags.bits(),
642        )
643    };
644    Errno::result(ret).map(|r| r as usize)
645}
646}
647
648#[cfg(any(target_os = "linux"))]
649#[cfg(feature = "fs")]
650libc_bitflags!(
651    /// Mode argument flags for fallocate determining operation performed on a given range.
652    #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
653    pub struct FallocateFlags: c_int {
654        /// File size is not changed.
655        ///
656        /// offset + len can be greater than file size.
657        FALLOC_FL_KEEP_SIZE;
658        /// Deallocates space by creating a hole.
659        ///
660        /// Must be ORed with FALLOC_FL_KEEP_SIZE. Byte range starts at offset and continues for len bytes.
661        FALLOC_FL_PUNCH_HOLE;
662        /// Removes byte range from a file without leaving a hole.
663        ///
664        /// Byte range to collapse starts at offset and continues for len bytes.
665        FALLOC_FL_COLLAPSE_RANGE;
666        /// Zeroes space in specified byte range.
667        ///
668        /// Byte range starts at offset and continues for len bytes.
669        FALLOC_FL_ZERO_RANGE;
670        /// Increases file space by inserting a hole within the file size.
671        ///
672        /// Does not overwrite existing data. Hole starts at offset and continues for len bytes.
673        FALLOC_FL_INSERT_RANGE;
674        /// Shared file data extants are made private to the file.
675        ///
676        /// Gaurantees that a subsequent write will not fail due to lack of space.
677        FALLOC_FL_UNSHARE_RANGE;
678    }
679);
680
681feature! {
682#![feature = "fs"]
683
684/// Manipulates file space.
685///
686/// Allows the caller to directly manipulate the allocated disk space for the
687/// file referred to by fd.
688#[cfg(any(target_os = "linux"))]
689#[cfg(feature = "fs")]
690pub fn fallocate(
691    fd: RawFd,
692    mode: FallocateFlags,
693    offset: libc::off_t,
694    len: libc::off_t,
695) -> Result<()> {
696    let res = unsafe { libc::fallocate(fd, mode.bits(), offset, len) };
697    Errno::result(res).map(drop)
698}
699
700/// Argument to [`fspacectl`] describing the range to zero.  The first member is
701/// the file offset, and the second is the length of the region.
702#[cfg(any(target_os = "freebsd"))]
703#[derive(Clone, Copy, Debug, Eq, PartialEq)]
704pub struct SpacectlRange(pub libc::off_t, pub libc::off_t);
705
706#[cfg(any(target_os = "freebsd"))]
707impl SpacectlRange {
708    #[inline]
709    pub fn is_empty(&self) -> bool {
710        self.1 == 0
711    }
712
713    #[inline]
714    pub fn len(&self) -> libc::off_t {
715        self.1
716    }
717
718    #[inline]
719    pub fn offset(&self) -> libc::off_t {
720        self.0
721    }
722}
723
724/// Punch holes in a file.
725///
726/// `fspacectl` instructs the file system to deallocate a portion of a file.
727/// After a successful operation, this region of the file will return all zeroes
728/// if read.  If the file system supports deallocation, then it may free the
729/// underlying storage, too.
730///
731/// # Arguments
732///
733/// - `fd`      -   File to operate on
734/// - `range.0` -   File offset at which to begin deallocation
735/// - `range.1` -   Length of the region to deallocate
736///
737/// # Returns
738///
739/// The operation may deallocate less than the entire requested region.  On
740/// success, it returns the region that still remains to be deallocated.  The
741/// caller should loop until the returned region is empty.
742///
743/// # Example
744///
745// no_run because it fails to link until FreeBSD 14.0
746/// ```no_run
747/// # use std::io::Write;
748/// # use std::os::unix::fs::FileExt;
749/// # use std::os::unix::io::AsRawFd;
750/// # use nix::fcntl::*;
751/// # use tempfile::tempfile;
752/// const INITIAL: &[u8] = b"0123456789abcdef";
753/// let mut f = tempfile().unwrap();
754/// f.write_all(INITIAL).unwrap();
755/// let mut range = SpacectlRange(3, 6);
756/// while (!range.is_empty()) {
757///     range = fspacectl(f.as_raw_fd(), range).unwrap();
758/// }
759/// let mut buf = vec![0; INITIAL.len()];
760/// f.read_exact_at(&mut buf, 0).unwrap();
761/// assert_eq!(buf, b"012\0\0\0\0\0\09abcdef");
762/// ```
763#[cfg(target_os = "freebsd")]
764pub fn fspacectl(fd: RawFd, range: SpacectlRange) -> Result<SpacectlRange> {
765    let mut rqsr = libc::spacectl_range{r_offset: range.0, r_len: range.1};
766    let res = unsafe { libc::fspacectl(
767            fd,
768            libc::SPACECTL_DEALLOC, // Only one command is supported ATM
769            &rqsr,
770            0,                      // No flags are currently supported
771            &mut rqsr
772    )};
773    Errno::result(res).map(|_| SpacectlRange(rqsr.r_offset, rqsr.r_len))
774}
775
776/// Like [`fspacectl`], but will never return incomplete.
777///
778/// # Arguments
779///
780/// - `fd`      -   File to operate on
781/// - `offset`  -   File offset at which to begin deallocation
782/// - `len`     -   Length of the region to deallocate
783///
784/// # Returns
785///
786/// Returns `()` on success.  On failure, the region may or may not be partially
787/// deallocated.
788///
789/// # Example
790///
791// no_run because it fails to link until FreeBSD 14.0
792/// ```no_run
793/// # use std::io::Write;
794/// # use std::os::unix::fs::FileExt;
795/// # use std::os::unix::io::AsRawFd;
796/// # use nix::fcntl::*;
797/// # use tempfile::tempfile;
798/// const INITIAL: &[u8] = b"0123456789abcdef";
799/// let mut f = tempfile().unwrap();
800/// f.write_all(INITIAL).unwrap();
801/// fspacectl_all(f.as_raw_fd(), 3, 6).unwrap();
802/// let mut buf = vec![0; INITIAL.len()];
803/// f.read_exact_at(&mut buf, 0).unwrap();
804/// assert_eq!(buf, b"012\0\0\0\0\0\09abcdef");
805/// ```
806#[cfg(target_os = "freebsd")]
807pub fn fspacectl_all(fd: RawFd, offset: libc::off_t, len: libc::off_t)
808    -> Result<()>
809{
810    let mut rqsr = libc::spacectl_range{r_offset: offset, r_len: len};
811    while rqsr.r_len > 0 {
812        let res = unsafe { libc::fspacectl(
813                fd,
814                libc::SPACECTL_DEALLOC, // Only one command is supported ATM
815                &rqsr,
816                0,                      // No flags are currently supported
817                &mut rqsr
818        )};
819        Errno::result(res)?;
820    }
821    Ok(())
822}
823
824#[cfg(any(
825    target_os = "linux",
826    target_os = "android",
827    target_os = "emscripten",
828    target_os = "fuchsia",
829    any(target_os = "wasi", target_env = "wasi"),
830    target_env = "uclibc",
831    target_os = "freebsd"
832))]
833mod posix_fadvise {
834    use crate::errno::Errno;
835    use std::os::unix::io::RawFd;
836    use crate::Result;
837
838    #[cfg(feature = "fs")]
839    libc_enum! {
840        #[repr(i32)]
841        #[non_exhaustive]
842        #[cfg_attr(docsrs, doc(cfg(feature = "fs")))]
843        pub enum PosixFadviseAdvice {
844            POSIX_FADV_NORMAL,
845            POSIX_FADV_SEQUENTIAL,
846            POSIX_FADV_RANDOM,
847            POSIX_FADV_NOREUSE,
848            POSIX_FADV_WILLNEED,
849            POSIX_FADV_DONTNEED,
850        }
851    }
852
853    feature! {
854    #![feature = "fs"]
855    pub fn posix_fadvise(
856        fd: RawFd,
857        offset: libc::off_t,
858        len: libc::off_t,
859        advice: PosixFadviseAdvice,
860    ) -> Result<()> {
861        let res = unsafe { libc::posix_fadvise(fd, offset, len, advice as libc::c_int) };
862
863        if res == 0 {
864            Ok(())
865        } else {
866            Err(Errno::from_i32(res))
867        }
868    }
869    }
870}
871
872#[cfg(any(
873    target_os = "linux",
874    target_os = "android",
875    target_os = "dragonfly",
876    target_os = "emscripten",
877    target_os = "fuchsia",
878    any(target_os = "wasi", target_env = "wasi"),
879    target_os = "freebsd"
880))]
881pub fn posix_fallocate(fd: RawFd, offset: libc::off_t, len: libc::off_t) -> Result<()> {
882    let res = unsafe { libc::posix_fallocate(fd, offset, len) };
883    match Errno::result(res) {
884        Err(err) => Err(err),
885        Ok(0) => Ok(()),
886        Ok(errno) => Err(Errno::from_i32(errno)),
887    }
888}
889}