472 lines
15 KiB
Rust
472 lines
15 KiB
Rust
//! POSIX-style `*at` functions.
|
|
//!
|
|
//! The `dirfd` argument to these functions may be a file descriptor for a
|
|
//! directory, or the special value [`CWD`].
|
|
//!
|
|
//! [`cwd`]: crate::fs::CWD
|
|
|
|
use crate::fd::OwnedFd;
|
|
use crate::ffi::CStr;
|
|
#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
|
|
use crate::fs::Access;
|
|
#[cfg(not(target_os = "espidf"))]
|
|
use crate::fs::AtFlags;
|
|
#[cfg(apple)]
|
|
use crate::fs::CloneFlags;
|
|
#[cfg(linux_kernel)]
|
|
use crate::fs::RenameFlags;
|
|
#[cfg(not(target_os = "espidf"))]
|
|
use crate::fs::Stat;
|
|
#[cfg(not(any(apple, target_os = "espidf", target_os = "vita", target_os = "wasi")))]
|
|
use crate::fs::{Dev, FileType};
|
|
#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
|
|
use crate::fs::{Gid, Uid};
|
|
use crate::fs::{Mode, OFlags};
|
|
use crate::{backend, io, path};
|
|
use backend::fd::{AsFd, BorrowedFd};
|
|
use core::mem::MaybeUninit;
|
|
use core::slice;
|
|
#[cfg(feature = "alloc")]
|
|
use {crate::ffi::CString, crate::path::SMALL_PATH_BUFFER_SIZE, alloc::vec::Vec};
|
|
#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
|
|
use {crate::fs::Timestamps, crate::timespec::Nsecs};
|
|
|
|
/// `UTIME_NOW` for use with [`utimensat`].
|
|
///
|
|
/// [`utimensat`]: crate::fs::utimensat
|
|
#[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "vita")))]
|
|
pub const UTIME_NOW: Nsecs = backend::c::UTIME_NOW as Nsecs;
|
|
|
|
/// `UTIME_OMIT` for use with [`utimensat`].
|
|
///
|
|
/// [`utimensat`]: crate::fs::utimensat
|
|
#[cfg(not(any(target_os = "espidf", target_os = "redox", target_os = "vita")))]
|
|
pub const UTIME_OMIT: Nsecs = backend::c::UTIME_OMIT as Nsecs;
|
|
|
|
/// `openat(dirfd, path, oflags, mode)`—Opens a file.
|
|
///
|
|
/// POSIX guarantees that `openat` will use the lowest unused file descriptor,
|
|
/// however it is not safe in general to rely on this, as file descriptors may
|
|
/// be unexpectedly allocated on other threads or in libraries.
|
|
///
|
|
/// The `Mode` argument is only significant when creating a file.
|
|
///
|
|
/// # References
|
|
/// - [POSIX]
|
|
/// - [Linux]
|
|
///
|
|
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/openat.html
|
|
/// [Linux]: https://man7.org/linux/man-pages/man2/openat.2.html
|
|
#[inline]
|
|
pub fn openat<P: path::Arg, Fd: AsFd>(
|
|
dirfd: Fd,
|
|
path: P,
|
|
oflags: OFlags,
|
|
create_mode: Mode,
|
|
) -> io::Result<OwnedFd> {
|
|
path.into_with_c_str(|path| {
|
|
backend::fs::syscalls::openat(dirfd.as_fd(), path, oflags, create_mode)
|
|
})
|
|
}
|
|
|
|
/// `readlinkat(fd, path)`—Reads the contents of a symlink.
|
|
///
|
|
/// If `reuse` already has available capacity, reuse it if possible.
|
|
///
|
|
/// # References
|
|
/// - [POSIX]
|
|
/// - [Linux]
|
|
///
|
|
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlinkat.html
|
|
/// [Linux]: https://man7.org/linux/man-pages/man2/readlinkat.2.html
|
|
#[cfg(feature = "alloc")]
|
|
#[inline]
|
|
pub fn readlinkat<P: path::Arg, Fd: AsFd, B: Into<Vec<u8>>>(
|
|
dirfd: Fd,
|
|
path: P,
|
|
reuse: B,
|
|
) -> io::Result<CString> {
|
|
path.into_with_c_str(|path| _readlinkat(dirfd.as_fd(), path, reuse.into()))
|
|
}
|
|
|
|
#[cfg(feature = "alloc")]
|
|
#[allow(unsafe_code)]
|
|
fn _readlinkat(dirfd: BorrowedFd<'_>, path: &CStr, mut buffer: Vec<u8>) -> io::Result<CString> {
|
|
buffer.clear();
|
|
buffer.reserve(SMALL_PATH_BUFFER_SIZE);
|
|
|
|
loop {
|
|
let nread =
|
|
backend::fs::syscalls::readlinkat(dirfd.as_fd(), path, buffer.spare_capacity_mut())?;
|
|
|
|
debug_assert!(nread <= buffer.capacity());
|
|
if nread < buffer.capacity() {
|
|
// SAFETY: From the [documentation]: “On success, these calls
|
|
// return the number of bytes placed in buf.”
|
|
//
|
|
// [documentation]: https://man7.org/linux/man-pages/man2/readlinkat.2.html
|
|
unsafe {
|
|
buffer.set_len(nread);
|
|
}
|
|
|
|
// SAFETY:
|
|
// - “readlink places the contents of the symbolic link pathname in
|
|
// the buffer buf”
|
|
// - [POSIX definition 3.271: Pathname]: “A string that is used to
|
|
// identify a file.”
|
|
// - [POSIX definition 3.375: String]: “A contiguous sequence of
|
|
// bytes terminated by and including the first null byte.”
|
|
// - “readlink does not append a terminating null byte to buf.”
|
|
//
|
|
// Thus, there will be no NUL bytes in the string.
|
|
//
|
|
// [POSIX definition 3.271: Pathname]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_271
|
|
// [POSIX definition 3.375: String]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_375
|
|
unsafe {
|
|
return Ok(CString::from_vec_unchecked(buffer));
|
|
}
|
|
}
|
|
|
|
// Use `Vec` reallocation strategy to grow capacity exponentially.
|
|
buffer.reserve(buffer.capacity() + 1);
|
|
}
|
|
}
|
|
|
|
/// `readlinkat(fd, path)`—Reads the contents of a symlink, without
|
|
/// allocating.
|
|
///
|
|
/// This is the "raw" version which avoids allocating, but which is
|
|
/// significantly trickier to use; most users should use plain [`readlinkat`].
|
|
///
|
|
/// This version writes bytes into the buffer and returns two slices, one
|
|
/// containing the written bytes, and one containing the remaining
|
|
/// uninitialized space. If the number of written bytes is equal to the length
|
|
/// of the buffer, it means the buffer wasn't big enough to hold the full
|
|
/// string, and callers should try again with a bigger buffer.
|
|
///
|
|
/// # References
|
|
/// - [POSIX]
|
|
/// - [Linux]
|
|
///
|
|
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/readlinkat.html
|
|
/// [Linux]: https://man7.org/linux/man-pages/man2/readlinkat.2.html
|
|
#[inline]
|
|
pub fn readlinkat_raw<P: path::Arg, Fd: AsFd>(
|
|
dirfd: Fd,
|
|
path: P,
|
|
buf: &mut [MaybeUninit<u8>],
|
|
) -> io::Result<(&mut [u8], &mut [MaybeUninit<u8>])> {
|
|
path.into_with_c_str(|path| _readlinkat_raw(dirfd.as_fd(), path, buf))
|
|
}
|
|
|
|
#[allow(unsafe_code)]
|
|
fn _readlinkat_raw<'a>(
|
|
dirfd: BorrowedFd<'_>,
|
|
path: &CStr,
|
|
buf: &'a mut [MaybeUninit<u8>],
|
|
) -> io::Result<(&'a mut [u8], &'a mut [MaybeUninit<u8>])> {
|
|
let n = backend::fs::syscalls::readlinkat(dirfd.as_fd(), path, buf)?;
|
|
unsafe {
|
|
Ok((
|
|
slice::from_raw_parts_mut(buf.as_mut_ptr().cast::<u8>(), n),
|
|
&mut buf[n..],
|
|
))
|
|
}
|
|
}
|
|
|
|
/// `mkdirat(fd, path, mode)`—Creates a directory.
|
|
///
|
|
/// # References
|
|
/// - [POSIX]
|
|
/// - [Linux]
|
|
///
|
|
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/mkdirat.html
|
|
/// [Linux]: https://man7.org/linux/man-pages/man2/mkdirat.2.html
|
|
#[inline]
|
|
pub fn mkdirat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, mode: Mode) -> io::Result<()> {
|
|
path.into_with_c_str(|path| backend::fs::syscalls::mkdirat(dirfd.as_fd(), path, mode))
|
|
}
|
|
|
|
/// `linkat(old_dirfd, old_path, new_dirfd, new_path, flags)`—Creates a hard
|
|
/// link.
|
|
///
|
|
/// # References
|
|
/// - [POSIX]
|
|
/// - [Linux]
|
|
///
|
|
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/linkat.html
|
|
/// [Linux]: https://man7.org/linux/man-pages/man2/linkat.2.html
|
|
#[cfg(not(target_os = "espidf"))]
|
|
#[inline]
|
|
pub fn linkat<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>(
|
|
old_dirfd: PFd,
|
|
old_path: P,
|
|
new_dirfd: QFd,
|
|
new_path: Q,
|
|
flags: AtFlags,
|
|
) -> io::Result<()> {
|
|
old_path.into_with_c_str(|old_path| {
|
|
new_path.into_with_c_str(|new_path| {
|
|
backend::fs::syscalls::linkat(
|
|
old_dirfd.as_fd(),
|
|
old_path,
|
|
new_dirfd.as_fd(),
|
|
new_path,
|
|
flags,
|
|
)
|
|
})
|
|
})
|
|
}
|
|
|
|
/// `unlinkat(fd, path, flags)`—Unlinks a file or remove a directory.
|
|
///
|
|
/// With the [`REMOVEDIR`] flag, this removes a directory. This is in place of
|
|
/// a `rmdirat` function.
|
|
///
|
|
/// # References
|
|
/// - [POSIX]
|
|
/// - [Linux]
|
|
///
|
|
/// [`REMOVEDIR`]: AtFlags::REMOVEDIR
|
|
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/unlinkat.html
|
|
/// [Linux]: https://man7.org/linux/man-pages/man2/unlinkat.2.html
|
|
#[cfg(not(target_os = "espidf"))]
|
|
#[inline]
|
|
pub fn unlinkat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, flags: AtFlags) -> io::Result<()> {
|
|
path.into_with_c_str(|path| backend::fs::syscalls::unlinkat(dirfd.as_fd(), path, flags))
|
|
}
|
|
|
|
/// `renameat(old_dirfd, old_path, new_dirfd, new_path)`—Renames a file or
|
|
/// directory.
|
|
///
|
|
/// # References
|
|
/// - [POSIX]
|
|
/// - [Linux]
|
|
///
|
|
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/renameat.html
|
|
/// [Linux]: https://man7.org/linux/man-pages/man2/renameat.2.html
|
|
#[inline]
|
|
pub fn renameat<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>(
|
|
old_dirfd: PFd,
|
|
old_path: P,
|
|
new_dirfd: QFd,
|
|
new_path: Q,
|
|
) -> io::Result<()> {
|
|
old_path.into_with_c_str(|old_path| {
|
|
new_path.into_with_c_str(|new_path| {
|
|
backend::fs::syscalls::renameat(
|
|
old_dirfd.as_fd(),
|
|
old_path,
|
|
new_dirfd.as_fd(),
|
|
new_path,
|
|
)
|
|
})
|
|
})
|
|
}
|
|
|
|
/// `renameat2(old_dirfd, old_path, new_dirfd, new_path, flags)`—Renames a
|
|
/// file or directory.
|
|
///
|
|
/// # References
|
|
/// - [Linux]
|
|
///
|
|
/// [Linux]: https://man7.org/linux/man-pages/man2/renameat2.2.html
|
|
#[cfg(linux_kernel)]
|
|
#[inline]
|
|
#[doc(alias = "renameat2")]
|
|
pub fn renameat_with<P: path::Arg, Q: path::Arg, PFd: AsFd, QFd: AsFd>(
|
|
old_dirfd: PFd,
|
|
old_path: P,
|
|
new_dirfd: QFd,
|
|
new_path: Q,
|
|
flags: RenameFlags,
|
|
) -> io::Result<()> {
|
|
old_path.into_with_c_str(|old_path| {
|
|
new_path.into_with_c_str(|new_path| {
|
|
backend::fs::syscalls::renameat2(
|
|
old_dirfd.as_fd(),
|
|
old_path,
|
|
new_dirfd.as_fd(),
|
|
new_path,
|
|
flags,
|
|
)
|
|
})
|
|
})
|
|
}
|
|
|
|
/// `symlinkat(old_path, new_dirfd, new_path)`—Creates a symlink.
|
|
///
|
|
/// # References
|
|
/// - [POSIX]
|
|
/// - [Linux]
|
|
///
|
|
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlinkat.html
|
|
/// [Linux]: https://man7.org/linux/man-pages/man2/symlinkat.2.html
|
|
#[inline]
|
|
pub fn symlinkat<P: path::Arg, Q: path::Arg, Fd: AsFd>(
|
|
old_path: P,
|
|
new_dirfd: Fd,
|
|
new_path: Q,
|
|
) -> io::Result<()> {
|
|
old_path.into_with_c_str(|old_path| {
|
|
new_path.into_with_c_str(|new_path| {
|
|
backend::fs::syscalls::symlinkat(old_path, new_dirfd.as_fd(), new_path)
|
|
})
|
|
})
|
|
}
|
|
|
|
/// `fstatat(dirfd, path, flags)`—Queries metadata for a file or directory.
|
|
///
|
|
/// [`Mode::from_raw_mode`] and [`FileType::from_raw_mode`] may be used to
|
|
/// interpret the `st_mode` field.
|
|
///
|
|
/// # References
|
|
/// - [POSIX]
|
|
/// - [Linux]
|
|
///
|
|
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fstatat.html
|
|
/// [Linux]: https://man7.org/linux/man-pages/man2/fstatat.2.html
|
|
/// [`Mode::from_raw_mode`]: crate::fs::Mode::from_raw_mode
|
|
/// [`FileType::from_raw_mode`]: crate::fs::FileType::from_raw_mode
|
|
#[cfg(not(target_os = "espidf"))]
|
|
#[inline]
|
|
#[doc(alias = "fstatat")]
|
|
pub fn statat<P: path::Arg, Fd: AsFd>(dirfd: Fd, path: P, flags: AtFlags) -> io::Result<Stat> {
|
|
path.into_with_c_str(|path| backend::fs::syscalls::statat(dirfd.as_fd(), path, flags))
|
|
}
|
|
|
|
/// `faccessat(dirfd, path, access, flags)`—Tests permissions for a file or
|
|
/// directory.
|
|
///
|
|
/// On Linux before 5.8, this function uses the `faccessat` system call which
|
|
/// doesn't support any flags. This function emulates support for the
|
|
/// [`AtFlags::EACCESS`] flag by checking whether the uid and gid of the
|
|
/// process match the effective uid and gid, in which case the `EACCESS` flag
|
|
/// can be ignored. In Linux 5.8 and beyond `faccessat2` is used, which
|
|
/// supports flags.
|
|
///
|
|
/// # References
|
|
/// - [POSIX]
|
|
/// - [Linux]
|
|
///
|
|
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/faccessat.html
|
|
/// [Linux]: https://man7.org/linux/man-pages/man2/faccessat.2.html
|
|
#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
|
|
#[inline]
|
|
#[doc(alias = "faccessat")]
|
|
pub fn accessat<P: path::Arg, Fd: AsFd>(
|
|
dirfd: Fd,
|
|
path: P,
|
|
access: Access,
|
|
flags: AtFlags,
|
|
) -> io::Result<()> {
|
|
path.into_with_c_str(|path| backend::fs::syscalls::accessat(dirfd.as_fd(), path, access, flags))
|
|
}
|
|
|
|
/// `utimensat(dirfd, path, times, flags)`—Sets file or directory timestamps.
|
|
///
|
|
/// # References
|
|
/// - [POSIX]
|
|
/// - [Linux]
|
|
///
|
|
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/utimensat.html
|
|
/// [Linux]: https://man7.org/linux/man-pages/man2/utimensat.2.html
|
|
#[cfg(not(any(target_os = "espidf", target_os = "vita")))]
|
|
#[inline]
|
|
pub fn utimensat<P: path::Arg, Fd: AsFd>(
|
|
dirfd: Fd,
|
|
path: P,
|
|
times: &Timestamps,
|
|
flags: AtFlags,
|
|
) -> io::Result<()> {
|
|
path.into_with_c_str(|path| backend::fs::syscalls::utimensat(dirfd.as_fd(), path, times, flags))
|
|
}
|
|
|
|
/// `fchmodat(dirfd, path, mode, flags)`—Sets file or directory permissions.
|
|
///
|
|
/// Platform support for flags varies widely, for example on Linux
|
|
/// [`AtFlags::SYMLINK_NOFOLLOW`] is not implemented and therefore
|
|
/// [`io::Errno::OPNOTSUPP`] will be returned.
|
|
///
|
|
/// # References
|
|
/// - [POSIX]
|
|
/// - [Linux]
|
|
///
|
|
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchmodat.html
|
|
/// [Linux]: https://man7.org/linux/man-pages/man2/fchmodat.2.html
|
|
#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
|
|
#[inline]
|
|
#[doc(alias = "fchmodat")]
|
|
pub fn chmodat<P: path::Arg, Fd: AsFd>(
|
|
dirfd: Fd,
|
|
path: P,
|
|
mode: Mode,
|
|
flags: AtFlags,
|
|
) -> io::Result<()> {
|
|
path.into_with_c_str(|path| backend::fs::syscalls::chmodat(dirfd.as_fd(), path, mode, flags))
|
|
}
|
|
|
|
/// `fclonefileat(src, dst_dir, dst, flags)`—Efficiently copies between files.
|
|
///
|
|
/// # References
|
|
/// - [Apple]
|
|
///
|
|
/// [Apple]: https://opensource.apple.com/source/xnu/xnu-3789.21.4/bsd/man/man2/clonefile.2.auto.html
|
|
#[cfg(apple)]
|
|
#[inline]
|
|
pub fn fclonefileat<Fd: AsFd, DstFd: AsFd, P: path::Arg>(
|
|
src: Fd,
|
|
dst_dir: DstFd,
|
|
dst: P,
|
|
flags: CloneFlags,
|
|
) -> io::Result<()> {
|
|
dst.into_with_c_str(|dst| {
|
|
backend::fs::syscalls::fclonefileat(src.as_fd(), dst_dir.as_fd(), dst, flags)
|
|
})
|
|
}
|
|
|
|
/// `mknodat(dirfd, path, mode, dev)`—Creates special or normal files.
|
|
///
|
|
/// # References
|
|
/// - [POSIX]
|
|
/// - [Linux]
|
|
///
|
|
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/mknodat.html
|
|
/// [Linux]: https://man7.org/linux/man-pages/man2/mknodat.2.html
|
|
#[cfg(not(any(apple, target_os = "espidf", target_os = "vita", target_os = "wasi")))]
|
|
#[inline]
|
|
pub fn mknodat<P: path::Arg, Fd: AsFd>(
|
|
dirfd: Fd,
|
|
path: P,
|
|
file_type: FileType,
|
|
mode: Mode,
|
|
dev: Dev,
|
|
) -> io::Result<()> {
|
|
path.into_with_c_str(|path| {
|
|
backend::fs::syscalls::mknodat(dirfd.as_fd(), path, file_type, mode, dev)
|
|
})
|
|
}
|
|
|
|
/// `fchownat(dirfd, path, owner, group, flags)`—Sets file or directory
|
|
/// ownership.
|
|
///
|
|
/// # References
|
|
/// - [POSIX]
|
|
/// - [Linux]
|
|
///
|
|
/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/fchownat.html
|
|
/// [Linux]: https://man7.org/linux/man-pages/man2/fchownat.2.html
|
|
#[cfg(not(any(target_os = "espidf", target_os = "wasi")))]
|
|
#[inline]
|
|
#[doc(alias = "fchownat")]
|
|
pub fn chownat<P: path::Arg, Fd: AsFd>(
|
|
dirfd: Fd,
|
|
path: P,
|
|
owner: Option<Uid>,
|
|
group: Option<Gid>,
|
|
flags: AtFlags,
|
|
) -> io::Result<()> {
|
|
path.into_with_c_str(|path| {
|
|
backend::fs::syscalls::chownat(dirfd.as_fd(), path, owner, group, flags)
|
|
})
|
|
}
|