blob: 1581d3a702d792e1c2ed687c6e439ad8185c36cc [file] [log] [blame]
use crate::errno::Errno;
use libc::{self, c_char, c_int, c_uint, size_t, ssize_t};
use std::ffi::OsString;
#[cfg(not(target_os = "redox"))]
use std::os::raw;
use std::os::unix::ffi::OsStringExt;
use std::os::unix::io::RawFd;
use crate::sys::stat::Mode;
use crate::{NixPath, Result};
#[cfg(any(target_os = "android", target_os = "linux"))]
use std::ptr; // For splice and copy_file_range
#[cfg(any(target_os = "android", target_os = "linux"))]
use crate::sys::uio::IoVec; // For vmsplice
#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
any(target_os = "wasi", target_env = "wasi"),
target_env = "uclibc",
target_env = "freebsd"
))]
pub use self::posix_fadvise::*;
#[cfg(not(target_os = "redox"))]
libc_bitflags! {
pub struct AtFlags: c_int {
AT_REMOVEDIR;
AT_SYMLINK_FOLLOW;
AT_SYMLINK_NOFOLLOW;
#[cfg(any(target_os = "android", target_os = "linux"))]
AT_NO_AUTOMOUNT;
#[cfg(any(target_os = "android", target_os = "linux"))]
AT_EMPTY_PATH;
}
}
libc_bitflags!(
/// Configuration options for opened files.
pub struct OFlag: c_int {
/// Mask for the access mode of the file.
O_ACCMODE;
/// Use alternate I/O semantics.
#[cfg(target_os = "netbsd")]
O_ALT_IO;
/// Open the file in append-only mode.
O_APPEND;
/// Generate a signal when input or output becomes possible.
O_ASYNC;
/// Closes the file descriptor once an `execve` call is made.
///
/// Also sets the file offset to the beginning of the file.
O_CLOEXEC;
/// Create the file if it does not exist.
O_CREAT;
/// Try to minimize cache effects of the I/O for this file.
#[cfg(any(target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "linux",
target_os = "netbsd"))]
O_DIRECT;
/// If the specified path isn't a directory, fail.
O_DIRECTORY;
/// Implicitly follow each `write()` with an `fdatasync()`.
#[cfg(any(target_os = "android",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd"))]
O_DSYNC;
/// Error out if a file was not created.
O_EXCL;
/// Open for execute only.
#[cfg(target_os = "freebsd")]
O_EXEC;
/// Open with an exclusive file lock.
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "redox"))]
O_EXLOCK;
/// Same as `O_SYNC`.
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
all(target_os = "linux", not(target_env = "musl")),
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "redox"))]
O_FSYNC;
/// Allow files whose sizes can't be represented in an `off_t` to be opened.
#[cfg(any(target_os = "android", target_os = "linux"))]
O_LARGEFILE;
/// Do not update the file last access time during `read(2)`s.
#[cfg(any(target_os = "android", target_os = "linux"))]
O_NOATIME;
/// Don't attach the device as the process' controlling terminal.
#[cfg(not(target_os = "redox"))]
O_NOCTTY;
/// Same as `O_NONBLOCK`.
#[cfg(not(target_os = "redox"))]
O_NDELAY;
/// `open()` will fail if the given path is a symbolic link.
O_NOFOLLOW;
/// When possible, open the file in nonblocking mode.
O_NONBLOCK;
/// Don't deliver `SIGPIPE`.
#[cfg(target_os = "netbsd")]
O_NOSIGPIPE;
/// Obtain a file descriptor for low-level access.
///
/// The file itself is not opened and other file operations will fail.
#[cfg(any(target_os = "android", target_os = "linux", target_os = "redox"))]
O_PATH;
/// Only allow reading.
///
/// This should not be combined with `O_WRONLY` or `O_RDWR`.
O_RDONLY;
/// Allow both reading and writing.
///
/// This should not be combined with `O_WRONLY` or `O_RDONLY`.
O_RDWR;
/// Similar to `O_DSYNC` but applies to `read`s instead.
#[cfg(any(target_os = "linux", target_os = "netbsd", target_os = "openbsd"))]
O_RSYNC;
/// Skip search permission checks.
#[cfg(target_os = "netbsd")]
O_SEARCH;
/// Open with a shared file lock.
#[cfg(any(target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "redox"))]
O_SHLOCK;
/// Implicitly follow each `write()` with an `fsync()`.
#[cfg(not(target_os = "redox"))]
O_SYNC;
/// Create an unnamed temporary file.
#[cfg(any(target_os = "android", target_os = "linux"))]
O_TMPFILE;
/// Truncate an existing regular file to 0 length if it allows writing.
O_TRUNC;
/// Restore default TTY attributes.
#[cfg(target_os = "freebsd")]
O_TTY_INIT;
/// Only allow writing.
///
/// This should not be combined with `O_RDONLY` or `O_RDWR`.
O_WRONLY;
}
);
// The conversion is not identical on all operating systems.
#[allow(clippy::identity_conversion)]
pub fn open<P: ?Sized + NixPath>(path: &P, oflag: OFlag, mode: Mode) -> Result<RawFd> {
let fd = path.with_nix_path(|cstr| {
unsafe { libc::open(cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint) }
})?;
Errno::result(fd)
}
// The conversion is not identical on all operating systems.
#[allow(clippy::identity_conversion)]
#[cfg(not(target_os = "redox"))]
pub fn openat<P: ?Sized + NixPath>(
dirfd: RawFd,
path: &P,
oflag: OFlag,
mode: Mode,
) -> Result<RawFd> {
let fd = path.with_nix_path(|cstr| {
unsafe { libc::openat(dirfd, cstr.as_ptr(), oflag.bits(), mode.bits() as c_uint) }
})?;
Errno::result(fd)
}
#[cfg(not(target_os = "redox"))]
pub fn renameat<P1: ?Sized + NixPath, P2: ?Sized + NixPath>(
old_dirfd: Option<RawFd>,
old_path: &P1,
new_dirfd: Option<RawFd>,
new_path: &P2,
) -> Result<()> {
let res = old_path.with_nix_path(|old_cstr| {
new_path.with_nix_path(|new_cstr| unsafe {
libc::renameat(
at_rawfd(old_dirfd),
old_cstr.as_ptr(),
at_rawfd(new_dirfd),
new_cstr.as_ptr(),
)
})
})??;
Errno::result(res).map(drop)
}
fn wrap_readlink_result(mut v: Vec<u8>, len: ssize_t) -> Result<OsString> {
unsafe { v.set_len(len as usize) }
v.shrink_to_fit();
Ok(OsString::from_vec(v.to_vec()))
}
fn readlink_maybe_at<P: ?Sized + NixPath>(
dirfd: Option<RawFd>,
path: &P,
v: &mut Vec<u8>,
) -> Result<libc::ssize_t> {
path.with_nix_path(|cstr| unsafe {
match dirfd {
#[cfg(target_os = "redox")]
Some(_) => unreachable!(),
#[cfg(not(target_os = "redox"))]
Some(dirfd) => libc::readlinkat(
dirfd,
cstr.as_ptr(),
v.as_mut_ptr() as *mut c_char,
v.capacity() as size_t,
),
None => libc::readlink(
cstr.as_ptr(),
v.as_mut_ptr() as *mut c_char,
v.capacity() as size_t,
),
}
})
}
fn inner_readlink<P: ?Sized + NixPath>(dirfd: Option<RawFd>, path: &P) -> Result<OsString> {
let mut v = Vec::with_capacity(libc::PATH_MAX as usize);
// simple case: result is strictly less than `PATH_MAX`
let res = readlink_maybe_at(dirfd, path, &mut v)?;
let len = Errno::result(res)?;
debug_assert!(len >= 0);
if (len as usize) < v.capacity() {
return wrap_readlink_result(v, res);
}
// Uh oh, the result is too long...
// Let's try to ask lstat how many bytes to allocate.
let reported_size = super::sys::stat::lstat(path)
.and_then(|x| Ok(x.st_size))
.unwrap_or(0);
let mut try_size = if reported_size > 0 {
// Note: even if `lstat`'s apparently valid answer turns out to be
// wrong, we will still read the full symlink no matter what.
reported_size as usize + 1
} else {
// If lstat doesn't cooperate, or reports an error, be a little less
// precise.
(libc::PATH_MAX as usize).max(128) << 1
};
loop {
v.reserve_exact(try_size);
let res = readlink_maybe_at(dirfd, path, &mut v)?;
let len = Errno::result(res)?;
debug_assert!(len >= 0);
if (len as usize) < v.capacity() {
break wrap_readlink_result(v, res);
} else {
// Ugh! Still not big enough!
match try_size.checked_shl(1) {
Some(next_size) => try_size = next_size,
// It's absurd that this would happen, but handle it sanely
// anyway.
None => break Err(super::Error::Sys(Errno::ENAMETOOLONG)),
}
}
}
}
pub fn readlink<P: ?Sized + NixPath>(path: &P) -> Result<OsString> {
inner_readlink(None, path)
}
#[cfg(not(target_os = "redox"))]
pub fn readlinkat<P: ?Sized + NixPath>(dirfd: RawFd, path: &P) -> Result<OsString> {
inner_readlink(Some(dirfd), path)
}
/// Computes the raw fd consumed by a function of the form `*at`.
#[cfg(not(target_os = "redox"))]
pub(crate) fn at_rawfd(fd: Option<RawFd>) -> raw::c_int {
match fd {
None => libc::AT_FDCWD,
Some(fd) => fd,
}
}
#[cfg(any(target_os = "android", target_os = "linux"))]
libc_bitflags!(
/// Additional flags for file sealing, which allows for limiting operations on a file.
pub struct SealFlag: c_int {
/// Prevents further calls to `fcntl()` with `F_ADD_SEALS`.
F_SEAL_SEAL;
/// The file cannot be reduced in size.
F_SEAL_SHRINK;
/// The size of the file cannot be increased.
F_SEAL_GROW;
/// The file contents cannot be modified.
F_SEAL_WRITE;
}
);
libc_bitflags!(
/// Additional configuration flags for `fcntl`'s `F_SETFD`.
pub struct FdFlag: c_int {
/// The file descriptor will automatically be closed during a successful `execve(2)`.
FD_CLOEXEC;
}
);
#[cfg(not(target_os = "redox"))]
#[derive(Debug, Eq, Hash, PartialEq)]
pub enum FcntlArg<'a> {
F_DUPFD(RawFd),
F_DUPFD_CLOEXEC(RawFd),
F_GETFD,
F_SETFD(FdFlag), // FD_FLAGS
F_GETFL,
F_SETFL(OFlag), // O_NONBLOCK
F_SETLK(&'a libc::flock),
F_SETLKW(&'a libc::flock),
F_GETLK(&'a mut libc::flock),
#[cfg(any(target_os = "linux", target_os = "android"))]
F_OFD_SETLK(&'a libc::flock),
#[cfg(any(target_os = "linux", target_os = "android"))]
F_OFD_SETLKW(&'a libc::flock),
#[cfg(any(target_os = "linux", target_os = "android"))]
F_OFD_GETLK(&'a mut libc::flock),
#[cfg(any(target_os = "android", target_os = "linux"))]
F_ADD_SEALS(SealFlag),
#[cfg(any(target_os = "android", target_os = "linux"))]
F_GET_SEALS,
#[cfg(any(target_os = "macos", target_os = "ios"))]
F_FULLFSYNC,
#[cfg(any(target_os = "linux", target_os = "android"))]
F_GETPIPE_SZ,
#[cfg(any(target_os = "linux", target_os = "android"))]
F_SETPIPE_SZ(c_int),
// TODO: Rest of flags
}
#[cfg(target_os = "redox")]
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
pub enum FcntlArg {
F_DUPFD(RawFd),
F_DUPFD_CLOEXEC(RawFd),
F_GETFD,
F_SETFD(FdFlag), // FD_FLAGS
F_GETFL,
F_SETFL(OFlag), // O_NONBLOCK
}
pub use self::FcntlArg::*;
// TODO: Figure out how to handle value fcntl returns
pub fn fcntl(fd: RawFd, arg: FcntlArg) -> Result<c_int> {
let res = unsafe {
match arg {
F_DUPFD(rawfd) => libc::fcntl(fd, libc::F_DUPFD, rawfd),
F_DUPFD_CLOEXEC(rawfd) => libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, rawfd),
F_GETFD => libc::fcntl(fd, libc::F_GETFD),
F_SETFD(flag) => libc::fcntl(fd, libc::F_SETFD, flag.bits()),
F_GETFL => libc::fcntl(fd, libc::F_GETFL),
F_SETFL(flag) => libc::fcntl(fd, libc::F_SETFL, flag.bits()),
#[cfg(not(target_os = "redox"))]
F_SETLK(flock) => libc::fcntl(fd, libc::F_SETLK, flock),
#[cfg(not(target_os = "redox"))]
F_SETLKW(flock) => libc::fcntl(fd, libc::F_SETLKW, flock),
#[cfg(not(target_os = "redox"))]
F_GETLK(flock) => libc::fcntl(fd, libc::F_GETLK, flock),
#[cfg(any(target_os = "android", target_os = "linux"))]
F_OFD_SETLK(flock) => libc::fcntl(fd, libc::F_OFD_SETLK, flock),
#[cfg(any(target_os = "android", target_os = "linux"))]
F_OFD_SETLKW(flock) => libc::fcntl(fd, libc::F_OFD_SETLKW, flock),
#[cfg(any(target_os = "android", target_os = "linux"))]
F_OFD_GETLK(flock) => libc::fcntl(fd, libc::F_OFD_GETLK, flock),
#[cfg(any(target_os = "android", target_os = "linux"))]
F_ADD_SEALS(flag) => libc::fcntl(fd, libc::F_ADD_SEALS, flag.bits()),
#[cfg(any(target_os = "android", target_os = "linux"))]
F_GET_SEALS => libc::fcntl(fd, libc::F_GET_SEALS),
#[cfg(any(target_os = "macos", target_os = "ios"))]
F_FULLFSYNC => libc::fcntl(fd, libc::F_FULLFSYNC),
#[cfg(any(target_os = "linux", target_os = "android"))]
F_GETPIPE_SZ => libc::fcntl(fd, libc::F_GETPIPE_SZ),
#[cfg(any(target_os = "linux", target_os = "android"))]
F_SETPIPE_SZ(size) => libc::fcntl(fd, libc::F_SETPIPE_SZ, size),
}
};
Errno::result(res)
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum FlockArg {
LockShared,
LockExclusive,
Unlock,
LockSharedNonblock,
LockExclusiveNonblock,
UnlockNonblock,
}
#[cfg(not(target_os = "redox"))]
pub fn flock(fd: RawFd, arg: FlockArg) -> Result<()> {
use self::FlockArg::*;
let res = unsafe {
match arg {
LockShared => libc::flock(fd, libc::LOCK_SH),
LockExclusive => libc::flock(fd, libc::LOCK_EX),
Unlock => libc::flock(fd, libc::LOCK_UN),
LockSharedNonblock => libc::flock(fd, libc::LOCK_SH | libc::LOCK_NB),
LockExclusiveNonblock => libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB),
UnlockNonblock => libc::flock(fd, libc::LOCK_UN | libc::LOCK_NB),
}
};
Errno::result(res).map(drop)
}
#[cfg(any(target_os = "android", target_os = "linux"))]
libc_bitflags! {
/// Additional flags to `splice` and friends.
pub struct SpliceFFlags: c_uint {
/// Request that pages be moved instead of copied.
///
/// Not applicable to `vmsplice`.
SPLICE_F_MOVE;
/// Do not block on I/O.
SPLICE_F_NONBLOCK;
/// Hint that more data will be coming in a subsequent splice.
///
/// Not applicable to `vmsplice`.
SPLICE_F_MORE;
/// Gift the user pages to the kernel.
///
/// Not applicable to `splice`.
SPLICE_F_GIFT;
}
}
/// Copy a range of data from one file to another
///
/// The `copy_file_range` system call performs an in-kernel copy between
/// file descriptors `fd_in` and `fd_out` without the additional cost of
/// transferring data from the kernel to user space and then back into the
/// kernel. It copies up to `len` bytes of data from file descriptor `fd_in` to
/// file descriptor `fd_out`, overwriting any data that exists within the
/// requested range of the target file.
///
/// If the `off_in` and/or `off_out` arguments are used, the values
/// will be mutated to reflect the new position within the file after
/// copying. If they are not used, the relevant filedescriptors will be seeked
/// to the new position.
///
/// On successful completion the number of bytes actually copied will be
/// returned.
#[cfg(any(target_os = "android", target_os = "linux"))]
pub fn copy_file_range(
fd_in: RawFd,
off_in: Option<&mut libc::loff_t>,
fd_out: RawFd,
off_out: Option<&mut libc::loff_t>,
len: usize,
) -> Result<usize> {
let off_in = off_in
.map(|offset| offset as *mut libc::loff_t)
.unwrap_or(ptr::null_mut());
let off_out = off_out
.map(|offset| offset as *mut libc::loff_t)
.unwrap_or(ptr::null_mut());
let ret = unsafe {
libc::syscall(
libc::SYS_copy_file_range,
fd_in,
off_in,
fd_out,
off_out,
len,
0,
)
};
Errno::result(ret).map(|r| r as usize)
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn splice(
fd_in: RawFd,
off_in: Option<&mut libc::loff_t>,
fd_out: RawFd,
off_out: Option<&mut libc::loff_t>,
len: usize,
flags: SpliceFFlags,
) -> Result<usize> {
let off_in = off_in
.map(|offset| offset as *mut libc::loff_t)
.unwrap_or(ptr::null_mut());
let off_out = off_out
.map(|offset| offset as *mut libc::loff_t)
.unwrap_or(ptr::null_mut());
let ret = unsafe { libc::splice(fd_in, off_in, fd_out, off_out, len, flags.bits()) };
Errno::result(ret).map(|r| r as usize)
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn tee(fd_in: RawFd, fd_out: RawFd, len: usize, flags: SpliceFFlags) -> Result<usize> {
let ret = unsafe { libc::tee(fd_in, fd_out, len, flags.bits()) };
Errno::result(ret).map(|r| r as usize)
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn vmsplice(fd: RawFd, iov: &[IoVec<&[u8]>], flags: SpliceFFlags) -> Result<usize> {
let ret = unsafe {
libc::vmsplice(
fd,
iov.as_ptr() as *const libc::iovec,
iov.len(),
flags.bits(),
)
};
Errno::result(ret).map(|r| r as usize)
}
#[cfg(any(target_os = "linux"))]
libc_bitflags!(
/// Mode argument flags for fallocate determining operation performed on a given range.
pub struct FallocateFlags: c_int {
/// File size is not changed.
///
/// offset + len can be greater than file size.
FALLOC_FL_KEEP_SIZE;
/// Deallocates space by creating a hole.
///
/// Must be ORed with FALLOC_FL_KEEP_SIZE. Byte range starts at offset and continues for len bytes.
FALLOC_FL_PUNCH_HOLE;
/// Removes byte range from a file without leaving a hole.
///
/// Byte range to collapse starts at offset and continues for len bytes.
FALLOC_FL_COLLAPSE_RANGE;
/// Zeroes space in specified byte range.
///
/// Byte range starts at offset and continues for len bytes.
FALLOC_FL_ZERO_RANGE;
/// Increases file space by inserting a hole within the file size.
///
/// Does not overwrite existing data. Hole starts at offset and continues for len bytes.
FALLOC_FL_INSERT_RANGE;
/// Shared file data extants are made private to the file.
///
/// Gaurantees that a subsequent write will not fail due to lack of space.
FALLOC_FL_UNSHARE_RANGE;
}
);
/// Manipulates file space.
///
/// Allows the caller to directly manipulate the allocated disk space for the
/// file referred to by fd.
#[cfg(any(target_os = "linux"))]
pub fn fallocate(
fd: RawFd,
mode: FallocateFlags,
offset: libc::off_t,
len: libc::off_t,
) -> Result<()> {
let res = unsafe { libc::fallocate(fd, mode.bits(), offset, len) };
Errno::result(res).map(drop)
}
#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
any(target_os = "wasi", target_env = "wasi"),
target_env = "uclibc",
target_env = "freebsd"
))]
mod posix_fadvise {
use crate::errno::Errno;
use libc;
use std::os::unix::io::RawFd;
use crate::Result;
libc_enum! {
#[repr(i32)]
pub enum PosixFadviseAdvice {
POSIX_FADV_NORMAL,
POSIX_FADV_SEQUENTIAL,
POSIX_FADV_RANDOM,
POSIX_FADV_NOREUSE,
POSIX_FADV_WILLNEED,
POSIX_FADV_DONTNEED,
}
}
pub fn posix_fadvise(
fd: RawFd,
offset: libc::off_t,
len: libc::off_t,
advice: PosixFadviseAdvice,
) -> Result<libc::c_int> {
let res = unsafe { libc::posix_fadvise(fd, offset, len, advice as libc::c_int) };
Errno::result(res)
}
}
#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "emscripten",
target_os = "fuchsia",
any(target_os = "wasi", target_env = "wasi"),
target_os = "freebsd"
))]
pub fn posix_fallocate(fd: RawFd, offset: libc::off_t, len: libc::off_t) -> Result<()> {
let res = unsafe { libc::posix_fallocate(fd, offset, len) };
match Errno::result(res) {
Err(err) => Err(err),
Ok(0) => Ok(()),
Ok(errno) => Err(crate::Error::Sys(Errno::from_i32(errno))),
}
}