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