diff --git a/Cargo.toml b/Cargo.toml index fa073a6..d383338 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ once_cell = "^1" # MSRV(1.65): Update to >=0.4.1 which uses let_else. 0.4.0 was broken. open-enum = { version = "=0.3.0", optional = true } rand = { version = "^0.8", optional = true } -rustix = { version = "^0.38", features = ["fs"] } +rustix = { version = "^0.38", features = ["fs", "process", "thread", "mount"] } thiserror = "^1" [dev-dependencies] @@ -65,8 +65,6 @@ errno = "^0.3" tempfile = "^3" paste = "^1" pretty_assertions = "^1" -# Enable the "process" feature for getcwd() in our tests. -rustix = { version = "^0.38", features = ["process"] } [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = [ diff --git a/src/flags.rs b/src/flags.rs index 99f54b7..5d8fb53 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -123,6 +123,12 @@ bitflags! { } } +impl Into for OpenFlags { + fn into(self) -> rustix::fs::OFlags { + rustix::fs::OFlags::from_bits_retain(self.bits() as u32) + } +} + impl OpenFlags { /// Grab the access mode bits from the flags. /// @@ -192,6 +198,12 @@ bitflags! { } } +impl Into for RenameFlags { + fn into(self) -> rustix::fs::RenameFlags { + rustix::fs::RenameFlags::from_bits_retain(self.bits()) + } +} + impl RenameFlags { /// Is this set of RenameFlags supported by the running kernel? pub fn is_supported(self) -> bool { diff --git a/src/procfs.rs b/src/procfs.rs index f34c25d..b4deee0 100644 --- a/src/procfs.rs +++ b/src/procfs.rs @@ -25,19 +25,25 @@ use crate::{ error::{Error, ErrorExt, ErrorImpl, ErrorKind}, flags::{OpenFlags, ResolverFlags}, resolvers::procfs::ProcfsResolver, - syscalls::{self, FsmountFlags, FsopenFlags, OpenTreeFlags}, - utils, + syscalls, + utils::{self, FdExt}, }; use std::{ fs::File, io::Error as IOError, - os::unix::io::{AsFd, BorrowedFd, OwnedFd}, + os::unix::{ + fs::MetadataExt, + io::{AsFd, BorrowedFd, OwnedFd}, + }, path::{Path, PathBuf}, }; use once_cell::sync::Lazy; -use rustix::fs::{self as rustix_fs, Access, AtFlags}; +use rustix::{ + fs::{self as rustix_fs, Access, AtFlags}, + mount::{FsMountFlags, FsOpenFlags, MountAttrFlags, OpenTreeFlags}, +}; /// A `procfs` handle to which is used globally by libpathrs. // MSRV(1.80): Use LazyLock. @@ -181,7 +187,7 @@ impl ProcfsHandle { /// against racing attackers changing the mount table and is guaranteed to /// have no overmounts because it is a brand-new procfs. pub(crate) fn new_fsopen(subset: bool) -> Result { - let sfd = syscalls::fsopen("proc", FsopenFlags::FSOPEN_CLOEXEC).map_err(|err| { + let sfd = syscalls::fsopen("proc", FsOpenFlags::FSOPEN_CLOEXEC).map_err(|err| { ErrorImpl::RawOsError { operation: "create procfs suberblock".into(), source: err, @@ -202,8 +208,10 @@ impl ProcfsHandle { syscalls::fsmount( &sfd, - FsmountFlags::FSMOUNT_CLOEXEC, - libc::MS_NODEV | libc::MS_NOEXEC | libc::MS_NOSUID, + FsMountFlags::FSMOUNT_CLOEXEC, + MountAttrFlags::MOUNT_ATTR_NODEV + | MountAttrFlags::MOUNT_ATTR_NOEXEC + | MountAttrFlags::MOUNT_ATTR_NOSUID, ) .map_err(|err| { ErrorImpl::RawOsError { @@ -243,7 +251,7 @@ impl ProcfsHandle { syscalls::openat( syscalls::AT_FDCWD, "/proc", - libc::O_PATH | libc::O_DIRECTORY, + OpenFlags::O_PATH | OpenFlags::O_DIRECTORY, 0, ) .map_err(|err| { @@ -386,7 +394,7 @@ impl ProcfsHandle { // for the ProcfsHandle::{new_fsopen,new_open_tree} cases. verify_same_mnt(parent_mnt_id, &parent, trailing)?; - syscalls::openat_follow(parent, trailing, oflags.bits(), 0) + syscalls::openat_follow(parent, trailing, oflags, 0) .map(File::from) .map_err(|err| { ErrorImpl::RawOsError { @@ -511,9 +519,7 @@ impl ProcfsHandle { // And make sure it's the root of procfs. The root directory is // guaranteed to have an inode number of PROC_ROOT_INO. If this check // ever stops working, it's a kernel regression. - let ino = syscalls::fstatat(&inner, "") - .expect("fstat(/proc) should work") - .st_ino; + let ino = inner.metadata().expect("fstat(/proc) should work").ino(); if ino != Self::PROC_ROOT_INO { Err(ErrorImpl::SafetyViolation { description: format!( @@ -553,14 +559,14 @@ pub(crate) fn verify_is_procfs(fd: Fd) -> Result<(), Error> { source: err, })? .f_type; - if fs_type != libc::PROC_SUPER_MAGIC { + if fs_type != rustix_fs::PROC_SUPER_MAGIC { Err(ErrorImpl::OsError { operation: "verify lookup is still on a procfs mount".into(), source: IOError::from_raw_os_error(libc::EXDEV), }) .wrap(format!( "fstype mismatch in restricted procfs resolver (f_type is 0x{fs_type:X}, not 0x{:X})", - libc::PROC_SUPER_MAGIC, + rustix_fs::PROC_SUPER_MAGIC, ))? } Ok(()) diff --git a/src/resolvers/opath/impl.rs b/src/resolvers/opath/impl.rs index f1c91bf..5a8e29e 100644 --- a/src/resolvers/opath/impl.rs +++ b/src/resolvers/opath/impl.rs @@ -38,7 +38,7 @@ use crate::{ error::{Error, ErrorExt, ErrorImpl}, - flags::ResolverFlags, + flags::{OpenFlags, ResolverFlags}, procfs::GLOBAL_PROCFS_HANDLE, resolvers::{opath::SymlinkStack, PartialLookup, MAX_SYMLINK_TRAVERSALS}, syscalls, @@ -262,15 +262,19 @@ fn do_resolve>( // Get our next element. // MSRV(1.69): Remove &*. - match syscalls::openat(&*current, &part, libc::O_PATH | libc::O_NOFOLLOW, 0).map_err( - |err| { - ErrorImpl::RawOsError { - operation: "open next component of resolution".into(), - source: err, - } - .into() - }, - ) { + match syscalls::openat( + &*current, + &part, + OpenFlags::O_PATH | OpenFlags::O_NOFOLLOW, + 0, + ) + .map_err(|err| { + ErrorImpl::RawOsError { + operation: "open next component of resolution".into(), + source: err, + } + .into() + }) { Err(err) => { return Ok(PartialLookup::Partial { handle: current, diff --git a/src/resolvers/procfs.rs b/src/resolvers/procfs.rs index c00ac8d..76819c8 100644 --- a/src/resolvers/procfs.rs +++ b/src/resolvers/procfs.rs @@ -181,12 +181,16 @@ fn opath_resolve>( } // Get our next element. - let next = syscalls::openat(¤t, &part, libc::O_PATH | libc::O_NOFOLLOW, 0).map_err( - |err| ErrorImpl::RawOsError { - operation: "open next component of resolution".into(), - source: err, - }, - )?; + let next = syscalls::openat( + ¤t, + &part, + OpenFlags::O_PATH | OpenFlags::O_NOFOLLOW, + 0, + ) + .map_err(|err| ErrorImpl::RawOsError { + operation: "open next component of resolution".into(), + source: err, + })?; // Check that the next component is on the same mountpoint. // NOTE: If the root is the host /proc mount, this is only safe if there @@ -256,7 +260,7 @@ fn opath_resolve>( // continue walking). && oflags.intersection(OpenFlags::O_PATH | OpenFlags::O_NOFOLLOW | OpenFlags::O_DIRECTORY) != OpenFlags::O_PATH { - match syscalls::openat(¤t, &part, oflags.bits() | libc::O_NOFOLLOW, 0) { + match syscalls::openat(¤t, &part, oflags | OpenFlags::O_NOFOLLOW, 0) { Ok(final_reopen) => { // Re-verify the next component is on the same mount. procfs::verify_same_mnt(root_mnt_id, &final_reopen, "") diff --git a/src/root.rs b/src/root.rs index 4f1b8ac..c285c56 100644 --- a/src/root.rs +++ b/src/root.rs @@ -39,7 +39,7 @@ use std::{ path::{Path, PathBuf}, }; -use libc::dev_t; +use rustix::fs::{self as rustix_fs, AtFlags}; /// An inode type to be created with [`Root::create`]. #[derive(Clone, Debug)] @@ -85,12 +85,12 @@ pub enum InodeType { /// Character device, as in [`mknod(2)`] with `S_IFCHR`. /// /// [`mknod(2)`]: http://man7.org/linux/man-pages/man2/mknod.2.html - CharacterDevice(Permissions, dev_t), + CharacterDevice(Permissions, rustix_fs::Dev), /// Block device, as in [`mknod(2)`] with `S_IFBLK`. /// /// [`mknod(2)`]: http://man7.org/linux/man-pages/man2/mknod.2.html - BlockDevice(Permissions, dev_t), + BlockDevice(Permissions, rustix_fs::Dev), // XXX: Does this really make sense? //// "Detached" unix socket, as in [`mknod(2)`] with `S_IFSOCK`. //// @@ -158,7 +158,7 @@ impl Root { let file = syscalls::openat( syscalls::AT_FDCWD, path, - libc::O_PATH | libc::O_DIRECTORY, + OpenFlags::O_PATH | OpenFlags::O_DIRECTORY, 0, ) .map_err(|err| ErrorImpl::RawOsError { @@ -757,7 +757,7 @@ impl RootRef<'_> { name: "target".into(), description: "hardlink target has trailing slash".into(), })?; - syscalls::linkat(olddir, oldname, dir, name, 0) + syscalls::linkat(olddir, oldname, dir, name, AtFlags::empty()) } InodeType::Fifo(perm) => { let mode = perm.mode() & !libc::S_IFMT; @@ -827,7 +827,7 @@ impl RootRef<'_> { // O_NOFOLLOW. We might want to expose that here, though because it // can't be done with the emulated backend that might be a bad idea. flags.insert(OpenFlags::O_CREAT); - let fd = syscalls::openat(dir, name, flags.bits(), perm.mode()).map_err(|err| { + let fd = syscalls::openat(dir, name, flags, perm.mode()).map_err(|err| { ErrorImpl::RawOsError { operation: "pathrs create_file".into(), source: err, @@ -1003,8 +1003,8 @@ impl RootRef<'_> { })?; let flags = match inode_type { - RemoveInodeType::Regular => 0, - RemoveInodeType::Directory => libc::AT_REMOVEDIR, + RemoveInodeType::Regular => AtFlags::empty(), + RemoveInodeType::Directory => AtFlags::REMOVEDIR, }; syscalls::unlinkat(dir, name, flags).map_err(|err| { ErrorImpl::RawOsError { @@ -1118,7 +1118,7 @@ impl RootRef<'_> { description: "rename destination path has trailing slash".into(), })?; - syscalls::renameat2(src_dir, src_name, dst_dir, dst_name, rflags.bits()).map_err(|err| { + syscalls::renameat2(src_dir, src_name, dst_dir, dst_name, rflags).map_err(|err| { ErrorImpl::RawOsError { operation: "pathrs rename".into(), source: err, diff --git a/src/syscalls.rs b/src/syscalls.rs index 14c6999..47fd557 100644 --- a/src/syscalls.rs +++ b/src/syscalls.rs @@ -21,24 +21,31 @@ #![allow(unsafe_code)] use crate::{ - flags::OpenFlags, + flags::{OpenFlags, RenameFlags}, utils::{FdExt, ToCString}, }; use std::{ - ffi::{CString, OsStr}, + ffi::OsStr, fmt, io::Error as IOError, + mem::MaybeUninit, os::unix::{ ffi::OsStrExt, io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}, }, path::{Path, PathBuf}, - ptr, }; -use libc::{c_int, c_uint, dev_t, mode_t, stat, statfs}; use once_cell::sync::Lazy; +use rustix::{ + fs::{ + self as rustix_fs, AtFlags, Dev, FileType, Mode, RawMode, Stat, StatFs, Statx, StatxFlags, + }, + io::Errno, + mount::{self as rustix_mount, FsMountFlags, FsOpenFlags, MountAttrFlags, OpenTreeFlags}, + process as rustix_process, thread as rustix_thread, +}; // TODO: Figure out how we can put a backtrace here (it seems we can't use // thiserror's backtrace support without nightly Rust because thiserror @@ -48,7 +55,7 @@ use once_cell::sync::Lazy; // MSRV(1.65): Use std::backtrace::Backtrace. // SAFETY: AT_FDCWD is always a valid file descriptor. -pub(crate) const AT_FDCWD: BorrowedFd<'static> = unsafe { BorrowedFd::borrow_raw(libc::AT_FDCWD) }; +pub(crate) const AT_FDCWD: BorrowedFd<'static> = rustix_fs::CWD; // SAFETY: BADFD is not a valid file descriptor, but it's not -1. #[cfg(test)] pub(crate) const BADFD: BorrowedFd<'static> = unsafe { BorrowedFd::borrow_raw(-libc::EBADF) }; @@ -63,7 +70,7 @@ pub(crate) const BADFD: BorrowedFd<'static> = unsafe { BorrowedFd::borrow_raw(-l /// Note that the file descriptor value is very unlikely to reference a live /// file descriptor. Its value is only used for informational purposes. #[derive(Clone, Debug)] -pub(crate) struct FrozenFd(c_int, Option); +pub(crate) struct FrozenFd(RawFd, Option); // TODO: Should probably be a pub(crate) impl. impl From for FrozenFd { @@ -98,13 +105,17 @@ impl fmt::Display for FrozenFd { /// [`Error`]: crate::error::Error #[derive(thiserror::Error, Debug)] pub(crate) enum Error { - #[error("openat({dirfd}, {path}, 0x{flags:x}, 0o{mode:o})")] + // NOTE: This is temporary until the issue is fixed in rustix. + #[error("invalid file descriptor {fd} (see for more details)")] + InvalidFd { fd: RawFd, source: Errno }, + + #[error("openat({dirfd}, {path}, {flags:?}, 0o{mode:o})")] Openat { dirfd: FrozenFd, path: PathBuf, flags: OpenFlags, mode: u32, - source: IOError, + source: Errno, }, #[error("openat2({dirfd}, {path}, {how}, {size})")] @@ -113,14 +124,14 @@ pub(crate) enum Error { path: PathBuf, how: OpenHow, size: usize, - source: IOError, + source: Errno, }, #[error("readlinkat({dirfd}, {path})")] Readlinkat { dirfd: FrozenFd, path: PathBuf, - source: IOError, + source: Errno, }, #[error("mkdirat({dirfd}, {path}, 0o{mode:o})")] @@ -128,7 +139,7 @@ pub(crate) enum Error { dirfd: FrozenFd, path: PathBuf, mode: u32, - source: IOError, + source: Errno, }, #[error("mknodat({dirfd}, {path}, 0o{mode:o}, {major}:{minor})")] @@ -138,25 +149,25 @@ pub(crate) enum Error { mode: u32, major: u32, minor: u32, - source: IOError, + source: Errno, }, - #[error("unlinkat({dirfd}, {path}, 0x{flags:x})")] + #[error("unlinkat({dirfd}, {path}, {flags:?})")] Unlinkat { dirfd: FrozenFd, path: PathBuf, - flags: i32, - source: IOError, + flags: AtFlags, + source: Errno, }, - #[error("linkat({olddirfd}, {oldpath}, {newdirfd}, {newpath}, 0x{flags:x})")] + #[error("linkat({old_dirfd}, {old_path}, {new_dirfd}, {new_path}, {flags:?})")] Linkat { - olddirfd: FrozenFd, - oldpath: PathBuf, - newdirfd: FrozenFd, - newpath: PathBuf, - flags: i32, - source: IOError, + old_dirfd: FrozenFd, + old_path: PathBuf, + new_dirfd: FrozenFd, + new_path: PathBuf, + flags: AtFlags, + source: Errno, }, #[error("symlinkat({dirfd}, {path}, {target})")] @@ -164,72 +175,72 @@ pub(crate) enum Error { dirfd: FrozenFd, path: PathBuf, target: PathBuf, - source: IOError, + source: Errno, }, - #[error("renameat({olddirfd}, {oldpath}, {newdirfd}, {newpath})")] + #[error("renameat({old_dirfd}, {old_path}, {new_dirfd}, {new_path})")] Renameat { - olddirfd: FrozenFd, - oldpath: PathBuf, - newdirfd: FrozenFd, - newpath: PathBuf, - source: IOError, + old_dirfd: FrozenFd, + old_path: PathBuf, + new_dirfd: FrozenFd, + new_path: PathBuf, + source: Errno, }, - #[error("renameat2({olddirfd}, {oldpath}, {newdirfd}, {newpath}, 0x{flags:x})")] + #[error("renameat2({old_dirfd}, {old_path}, {new_dirfd}, {new_path}, {flags:?})")] Renameat2 { - olddirfd: FrozenFd, - oldpath: PathBuf, - newdirfd: FrozenFd, - newpath: PathBuf, - flags: u32, - source: IOError, + old_dirfd: FrozenFd, + old_path: PathBuf, + new_dirfd: FrozenFd, + new_path: PathBuf, + flags: RenameFlags, + source: Errno, }, #[error("fstatfs({fd})")] - Fstatfs { fd: FrozenFd, source: IOError }, + Fstatfs { fd: FrozenFd, source: Errno }, #[error("fstatat({dirfd}, {path}, 0x{flags:x})")] Fstatat { dirfd: FrozenFd, path: PathBuf, - flags: i32, - source: IOError, + flags: AtFlags, + source: Errno, }, #[error("statx({dirfd}, {path}, flags=0x{flags:x}, mask=0x{mask:x})")] Statx { dirfd: FrozenFd, path: PathBuf, - flags: i32, - mask: u32, - source: IOError, + flags: AtFlags, + mask: StatxFlags, + source: Errno, }, #[error("fsopen({fstype}, {flags:?})")] Fsopen { fstype: String, - flags: FsopenFlags, - source: IOError, + flags: FsOpenFlags, + source: Errno, }, #[error("fsconfig({sfd}, FSCONFIG_CMD_CREATE)")] - FsconfigCreate { sfd: FrozenFd, source: IOError }, + FsconfigCreate { sfd: FrozenFd, source: Errno }, #[error("fsconfig({sfd}, FSCONFIG_SET_STRING, {key:?}, {value:?})")] FsconfigSetString { sfd: FrozenFd, key: String, value: String, - source: IOError, + source: Errno, }, - #[error("fsmount({sfd}, {flags:?}, 0x{mount_attrs:x})")] + #[error("fsmount({sfd}, {flags:?}, {mount_attrs:?})")] Fsmount { sfd: FrozenFd, - flags: FsmountFlags, - mount_attrs: u64, - source: IOError, + flags: FsMountFlags, + mount_attrs: MountAttrFlags, + source: Errno, }, #[error("open_tree({dirfd}, {path}, {flags:?})")] @@ -237,14 +248,15 @@ pub(crate) enum Error { dirfd: FrozenFd, path: PathBuf, flags: OpenTreeFlags, - source: IOError, + source: Errno, }, } impl Error { - pub(crate) fn root_cause(&self) -> &IOError { + fn errno(&self) -> &Errno { // XXX: This should probably be a macro... match self { + Error::InvalidFd { source, .. } => source, Error::Openat { source, .. } => source, Error::Openat2 { source, .. } => source, Error::Readlinkat { source, .. } => source, @@ -265,12 +277,30 @@ impl Error { Error::OpenTree { source, .. } => source, } } + + // TODO: Switch to returning &Errno. + pub(crate) fn root_cause(&self) -> IOError { + IOError::from_raw_os_error(self.errno().raw_os_error()) + } +} + +// FIXME: Temporary fix for rustix panicking if a passed file descriptor is +// non-standard . +trait HotfixRustixFd: Sized { + fn hotfix_rustix_fd(self) -> Result; } -// TODO: We probably want to switch to rustix for most of these wrappers, though -// the interfaces provided by rustix are slightly non-ergonomic. I much -// prefer these simpler C-like bindings. We also have the ability to check -// for support of each syscall. +impl HotfixRustixFd for Fd { + fn hotfix_rustix_fd(self) -> Result { + match self.as_fd().as_raw_fd() { + libc::AT_FDCWD | 0.. => Ok(self), + fd => Err(Error::InvalidFd { + fd, + source: Errno::BADF, + }), + } + } +} /// Wrapper for `openat(2)` which auto-sets `O_CLOEXEC | O_NOCTTY`. /// @@ -279,29 +309,25 @@ impl Error { pub(crate) fn openat_follow>( dirfd: Fd, path: P, - flags: c_int, - mode: mode_t, + mut flags: OpenFlags, + mode: RawMode, // TODO: Should we take rustix::fs::Mode directly? ) -> Result { - let dirfd = dirfd.as_fd(); + let dirfd = dirfd.as_fd().hotfix_rustix_fd()?; let path = path.as_ref(); - let flags = libc::O_CLOEXEC | libc::O_NOCTTY | flags; - // SAFETY: Obviously safe-to-use Linux syscall. - let fd = unsafe { libc::openat(dirfd.as_raw_fd(), path.to_c_string().as_ptr(), flags, mode) }; - let err = IOError::last_os_error(); + // O_CLOEXEC is needed for obvious reasons, and O_NOCTTY ensures that a + // malicious file won't take control of our terminal. + flags.insert(OpenFlags::O_CLOEXEC | OpenFlags::O_NOCTTY); - if fd >= 0 { - // SAFETY: We know it's a real file descriptor. - Ok(unsafe { OwnedFd::from_raw_fd(fd) }) - } else { - Err(Error::Openat { + rustix_fs::openat(dirfd, path, flags.into(), Mode::from_raw_mode(mode)).map_err(|errno| { + Error::Openat { dirfd: dirfd.into(), path: path.into(), - flags: OpenFlags::from_bits_retain(flags), - mode, - source: err, - }) - } + flags, + mode: mode, + source: errno, + } + }) } /// Wrapper for `openat(2)` which auto-sets `O_CLOEXEC | O_NOCTTY | O_NOFOLLOW`. @@ -311,10 +337,11 @@ pub(crate) fn openat_follow>( pub(crate) fn openat>( dirfd: Fd, path: P, - flags: c_int, - mode: mode_t, + mut flags: OpenFlags, + mode: RawMode, // TODO: Should we take rustix::fs::Mode directly? ) -> Result { - openat_follow(dirfd, path, libc::O_NOFOLLOW | flags, mode) + flags.insert(OpenFlags::O_NOFOLLOW); + openat_follow(dirfd, path, flags, mode) } /// Wrapper for `readlinkat(2)`. @@ -323,36 +350,33 @@ pub(crate) fn openat>( /// argument of `readlinkat(2)`. We need the dirfd argument, so we need a /// wrapper. pub(crate) fn readlinkat>(dirfd: Fd, path: P) -> Result { - let dirfd = dirfd.as_fd(); + let dirfd = dirfd.as_fd().hotfix_rustix_fd()?; let path = path.as_ref(); - // If the contents of the symlink are larger than this, we raise a - // SafetyViolation to avoid DoS vectors (because there is no way to get the - // size of a symlink beforehand, you just have to read it). - let mut buffer = [0_u8; 32 * libc::PATH_MAX as usize]; - // SAFETY: Obviously safe-to-use Linux syscall. - let len = unsafe { - libc::readlinkat( - dirfd.as_raw_fd(), - path.to_c_string().as_ptr(), - buffer.as_mut_ptr() as *mut i8, - buffer.len(), - ) - }; - let mut err = IOError::last_os_error(); - let maybe_truncated = len >= (buffer.len() as isize); - if len < 0 || maybe_truncated { - if maybe_truncated { - err = IOError::from_raw_os_error(libc::ENAMETOOLONG); - } + // If the contents of the symlink are larger than this, we bail out avoid + // DoS vectors (because there is no way to get the size of a symlink + // beforehand, you just have to read it). + let mut linkbuf: [MaybeUninit; 32 * 4096] = + [const { MaybeUninit::uninit() }; 32 * libc::PATH_MAX as usize]; + + let (target, trailing) = + rustix_fs::readlinkat_raw(dirfd, path, &mut linkbuf[..]).map_err(|errno| { + Error::Readlinkat { + dirfd: dirfd.into(), + path: path.into(), + source: errno, + } + })?; + + if trailing.is_empty() { + // The buffer was too small, return an error. Err(Error::Readlinkat { dirfd: dirfd.into(), path: path.into(), - source: err, + source: Errno::NAMETOOLONG, }) } else { - let content = OsStr::from_bytes(&buffer[..(len as usize)]); - Ok(PathBuf::from(content)) + Ok(PathBuf::from(OsStr::from_bytes(target))) } } @@ -363,29 +387,21 @@ pub(crate) fn readlinkat>(dirfd: Fd, path: P) -> Result pub(crate) fn mkdirat>( dirfd: Fd, path: P, - mode: mode_t, + mode: RawMode, // TODO: Should we take rustix::fs::Mode directly? ) -> Result<(), Error> { - let dirfd = dirfd.as_fd(); + let dirfd = dirfd.as_fd().hotfix_rustix_fd()?; let path = path.as_ref(); - // SAFETY: Obviously safe-to-use Linux syscall. - let ret = unsafe { libc::mkdirat(dirfd.as_raw_fd(), path.to_c_string().as_ptr(), mode) }; - let err = IOError::last_os_error(); - if ret >= 0 { - Ok(()) - } else { - Err(Error::Mkdirat { - dirfd: dirfd.into(), - path: path.into(), - mode, - source: err, - }) - } + rustix_fs::mkdirat(dirfd, path, Mode::from_raw_mode(mode)).map_err(|errno| Error::Mkdirat { + dirfd: dirfd.into(), + path: path.into(), + mode, + source: errno, + }) } -pub(crate) fn devmajorminor(dev: dev_t) -> (c_uint, c_uint) { - // SAFETY: Obviously safe-to-use libc function. - unsafe { (libc::major(dev), libc::minor(dev)) } +pub(crate) fn devmajorminor(dev: Dev) -> (u32, u32) { + (rustix_fs::major(dev), rustix_fs::minor(dev)) } /// Wrapper for `mknodat(2)`. @@ -395,28 +411,27 @@ pub(crate) fn devmajorminor(dev: dev_t) -> (c_uint, c_uint) { pub(crate) fn mknodat>( dirfd: Fd, path: P, - mode: mode_t, - dev: dev_t, + raw_mode: RawMode, // TODO: Should we take rustix::fs::{Mode,FileType} directly? + dev: Dev, ) -> Result<(), Error> { - let dirfd = dirfd.as_fd(); + let dirfd = dirfd.as_fd().hotfix_rustix_fd()?; let path = path.as_ref(); - // SAFETY: Obviously safe-to-use Linux syscall. - let ret = unsafe { libc::mknodat(dirfd.as_raw_fd(), path.to_c_string().as_ptr(), mode, dev) }; - let err = IOError::last_os_error(); + let (file_type, mode) = ( + FileType::from_raw_mode(raw_mode), + Mode::from_raw_mode(raw_mode), + ); - if ret >= 0 { - Ok(()) - } else { + rustix_fs::mknodat(dirfd, path, file_type, mode, dev).map_err(|errno| { let (major, minor) = devmajorminor(dev); - Err(Error::Mknodat { + Error::Mknodat { dirfd: dirfd.into(), path: path.into(), - mode, + mode: raw_mode, major, minor, - source: err, - }) - } + source: errno, + } + }) } /// Wrapper for `unlinkat(2)`. @@ -426,24 +441,17 @@ pub(crate) fn mknodat>( pub(crate) fn unlinkat>( dirfd: Fd, path: P, - flags: c_int, + flags: AtFlags, ) -> Result<(), Error> { - let dirfd = dirfd.as_fd(); + let dirfd = dirfd.as_fd().hotfix_rustix_fd()?; let path = path.as_ref(); - // SAFETY: Obviously safe-to-use Linux syscall. - let ret = unsafe { libc::unlinkat(dirfd.as_raw_fd(), path.to_c_string().as_ptr(), flags) }; - let err = IOError::last_os_error(); - if ret >= 0 { - Ok(()) - } else { - Err(Error::Unlinkat { - dirfd: dirfd.into(), - path: path.into(), - flags, - source: err, - }) - } + rustix_fs::unlinkat(dirfd, path, flags).map_err(|errno| Error::Unlinkat { + dirfd: dirfd.into(), + path: path.into(), + flags, + source: errno, + }) } /// Wrapper for `linkat(2)`. @@ -451,38 +459,25 @@ pub(crate) fn unlinkat>( /// This is needed because Rust doesn't provide a way to access the dirfd /// argument of `linkat(2)`. We need the dirfd argument, so we need a wrapper. pub(crate) fn linkat, Fd2: AsFd, P2: AsRef>( - olddirfd: Fd1, - oldpath: P1, - newdirfd: Fd2, - newpath: P2, - flags: c_int, + old_dirfd: Fd1, + old_path: P1, + new_dirfd: Fd2, + new_path: P2, + flags: AtFlags, ) -> Result<(), Error> { - let (olddirfd, newdirfd) = (olddirfd.as_fd(), newdirfd.as_fd()); - let (oldpath, newpath) = (oldpath.as_ref(), newpath.as_ref()); - // SAFETY: Obviously safe-to-use Linux syscall. - let ret = unsafe { - libc::linkat( - olddirfd.as_raw_fd(), - oldpath.to_c_string().as_ptr(), - newdirfd.as_raw_fd(), - newpath.to_c_string().as_ptr(), - flags, - ) - }; - let err = IOError::last_os_error(); - - if ret >= 0 { - Ok(()) - } else { - Err(Error::Linkat { - olddirfd: olddirfd.into(), - oldpath: oldpath.into(), - newdirfd: newdirfd.into(), - newpath: newpath.into(), + let (old_dirfd, old_path) = (old_dirfd.as_fd().hotfix_rustix_fd()?, old_path.as_ref()); + let (new_dirfd, new_path) = (new_dirfd.as_fd().hotfix_rustix_fd()?, new_path.as_ref()); + + rustix_fs::linkat(old_dirfd, old_path, new_dirfd, new_path, flags).map_err(|errno| { + Error::Linkat { + old_dirfd: old_dirfd.into(), + old_path: old_path.into(), + new_dirfd: new_dirfd.into(), + new_path: new_path.into(), flags, - source: err, - }) - } + source: errno, + } + }) } /// Wrapper for `symlinkat(2)`. @@ -495,28 +490,15 @@ pub(crate) fn symlinkat, Fd: AsFd, P2: AsRef>( dirfd: Fd, path: P2, ) -> Result<(), Error> { - let dirfd = dirfd.as_fd(); - let (target, path) = (target.as_ref(), path.as_ref()); - // SAFETY: Obviously safe-to-use Linux syscall. - let ret = unsafe { - libc::symlinkat( - target.to_c_string().as_ptr(), - dirfd.as_raw_fd(), - path.to_c_string().as_ptr(), - ) - }; - let err = IOError::last_os_error(); + let (dirfd, path) = (dirfd.as_fd().hotfix_rustix_fd()?, path.as_ref()); + let target = target.as_ref(); - if ret >= 0 { - Ok(()) - } else { - Err(Error::Symlinkat { - dirfd: dirfd.into(), - path: path.into(), - target: target.into(), - source: err, - }) - } + rustix_fs::symlinkat(target, dirfd, path).map_err(|errno| Error::Symlinkat { + dirfd: dirfd.into(), + path: path.into(), + target: target.into(), + source: errno, + }) } /// Wrapper for `renameat(2)`. @@ -524,40 +506,26 @@ pub(crate) fn symlinkat, Fd: AsFd, P2: AsRef>( /// This is needed because Rust doesn't provide a way to access the dirfd /// argument of `renameat(2)`. We need the dirfd argument, so we need a wrapper. pub(crate) fn renameat, Fd2: AsFd, P2: AsRef>( - olddirfd: Fd1, - oldpath: P1, - newdirfd: Fd2, - newpath: P2, + old_dirfd: Fd1, + old_path: P1, + new_dirfd: Fd2, + new_path: P2, ) -> Result<(), Error> { - let (olddirfd, newdirfd) = (olddirfd.as_fd(), newdirfd.as_fd()); - let (oldpath, newpath) = (oldpath.as_ref(), newpath.as_ref()); - // SAFETY: Obviously safe-to-use Linux syscall. - let ret = unsafe { - libc::renameat( - olddirfd.as_raw_fd(), - oldpath.to_c_string().as_ptr(), - newdirfd.as_raw_fd(), - newpath.to_c_string().as_ptr(), - ) - }; - let err = IOError::last_os_error(); + let (old_dirfd, old_path) = (old_dirfd.as_fd().hotfix_rustix_fd()?, old_path.as_ref()); + let (new_dirfd, new_path) = (new_dirfd.as_fd().hotfix_rustix_fd()?, new_path.as_ref()); - if ret >= 0 { - Ok(()) - } else { - Err(Error::Renameat { - olddirfd: olddirfd.into(), - oldpath: oldpath.into(), - newdirfd: newdirfd.into(), - newpath: newpath.into(), - source: err, - }) - } + rustix_fs::renameat(old_dirfd, old_path, new_dirfd, new_path).map_err(|errno| Error::Renameat { + old_dirfd: old_dirfd.into(), + old_path: old_path.into(), + new_dirfd: new_dirfd.into(), + new_path: new_path.into(), + source: errno, + }) } // MSRV(1.80): Use LazyLock. pub(crate) static RENAME_FLAGS_SUPPORTED: Lazy = Lazy::new(|| { - match renameat2(AT_FDCWD, ".", AT_FDCWD, ".", libc::RENAME_EXCHANGE) { + match renameat2(AT_FDCWD, ".", AT_FDCWD, ".", RenameFlags::RENAME_EXCHANGE) { Ok(_) => true, // We expect EBUSY, but just to be safe we only check for ENOSYS. Err(err) => err.root_cause().raw_os_error() != Some(libc::ENOSYS), @@ -569,137 +537,77 @@ pub(crate) static RENAME_FLAGS_SUPPORTED: Lazy = Lazy::new(|| { /// This is needed because Rust doesn't provide any interface for `renameat2(2)` /// (especially not an interface for the dirfd). pub(crate) fn renameat2, Fd2: AsFd, P2: AsRef>( - olddirfd: Fd1, - oldpath: P1, - newdirfd: Fd2, - newpath: P2, - flags: c_uint, + old_dirfd: Fd1, + old_path: P1, + new_dirfd: Fd2, + new_path: P2, + flags: RenameFlags, ) -> Result<(), Error> { // Use renameat(2) if no flags are specified. - if flags == 0 { - return renameat(olddirfd, oldpath, newdirfd, newpath); + if flags.is_empty() { + return renameat(old_dirfd, old_path, new_dirfd, new_path); } - let (olddirfd, newdirfd) = (olddirfd.as_fd(), newdirfd.as_fd()); - let (oldpath, newpath) = (oldpath.as_ref(), newpath.as_ref()); - // SAFETY: Obviously safe-to-use Linux syscall. - let ret = unsafe { - libc::renameat2( - olddirfd.as_raw_fd(), - oldpath.to_c_string().as_ptr(), - newdirfd.as_raw_fd(), - newpath.to_c_string().as_ptr(), - flags, - ) - }; - let err = IOError::last_os_error(); + let (old_dirfd, old_path) = (old_dirfd.as_fd().hotfix_rustix_fd()?, old_path.as_ref()); + let (new_dirfd, new_path) = (new_dirfd.as_fd().hotfix_rustix_fd()?, new_path.as_ref()); - if ret >= 0 { - Ok(()) - } else { - Err(Error::Renameat2 { - olddirfd: olddirfd.into(), - oldpath: oldpath.into(), - newdirfd: newdirfd.into(), - newpath: newpath.into(), + rustix_fs::renameat_with(old_dirfd, old_path, new_dirfd, new_path, flags.into()).map_err( + |errno| Error::Renameat2 { + old_dirfd: old_dirfd.into(), + old_path: old_path.into(), + new_dirfd: new_dirfd.into(), + new_path: new_path.into(), flags, - source: err, - }) - } + source: errno, + }, + ) } /// Wrapper for `fstatfs(2)`. /// /// This is needed because Rust doesn't provide any interface for `fstatfs(2)`. -pub(crate) fn fstatfs(fd: Fd) -> Result { - // SAFETY: repr(C) struct without internal references is definitely valid. C - // callers are expected to zero it as well. - let mut buf: statfs = unsafe { std::mem::zeroed() }; - let fd = fd.as_fd(); - // SAFETY: Obviously safe-to-use Linux syscall. - let ret = unsafe { libc::fstatfs(fd.as_raw_fd(), &mut buf as *mut statfs) }; - let err = IOError::last_os_error(); +pub(crate) fn fstatfs(fd: Fd) -> Result { + let fd = fd.as_fd().hotfix_rustix_fd()?; - if ret >= 0 { - Ok(buf) - } else { - Err(Error::Fstatfs { - fd: fd.into(), - source: err, - }) - } + rustix_fs::fstatfs(fd).map_err(|errno| Error::Fstatfs { + fd: fd.into(), + source: errno, + }) } /// Wrapper for `fstatat(2)`, which auto-sets `AT_NO_AUTOMOUNT | /// AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH`. /// /// This is needed because Rust doesn't provide any interface for `fstatat(2)`. -pub(crate) fn fstatat>(dirfd: Fd, path: P) -> Result { - // SAFETY: repr(C) struct without internal references is definitely valid. C - // callers are expected to zero it as well. - let mut buf: stat = unsafe { std::mem::zeroed() }; - let dirfd = dirfd.as_fd(); +pub(crate) fn fstatat>(dirfd: Fd, path: P) -> Result { + let dirfd = dirfd.as_fd().hotfix_rustix_fd()?; let path = path.as_ref(); - let flags = libc::AT_NO_AUTOMOUNT | libc::AT_SYMLINK_NOFOLLOW | libc::AT_EMPTY_PATH; - - // SAFETY: Obviously safe-to-use Linux syscall. - let ret = unsafe { - libc::fstatat( - dirfd.as_raw_fd(), - path.to_c_string().as_ptr(), - &mut buf as *mut stat, - flags, - ) - }; - let err = IOError::last_os_error(); + let flags = AtFlags::NO_AUTOMOUNT | AtFlags::SYMLINK_NOFOLLOW | AtFlags::EMPTY_PATH; - if ret >= 0 { - Ok(buf) - } else { - Err(Error::Fstatat { - dirfd: dirfd.into(), - path: path.into(), - flags, - source: err, - }) - } + rustix_fs::statat(dirfd, path, flags).map_err(|errno| Error::Fstatat { + dirfd: dirfd.into(), + path: path.into(), + flags, + source: errno, + }) } pub(crate) fn statx>( dirfd: Fd, path: P, - mask: u32, -) -> Result { - // SAFETY: repr(C) struct without internal references is definitely valid. C - // callers are expected to zero it as well. - let mut buf: libc::statx = unsafe { std::mem::zeroed() }; - let dirfd = dirfd.as_fd(); + mask: StatxFlags, +) -> Result { + let dirfd = dirfd.as_fd().hotfix_rustix_fd()?; let path = path.as_ref(); - let flags = libc::AT_NO_AUTOMOUNT | libc::AT_SYMLINK_NOFOLLOW | libc::AT_EMPTY_PATH; - - // SAFETY: Obviously safe-to-use Linux syscall. - let ret = unsafe { - libc::statx( - dirfd.as_raw_fd(), - path.to_c_string().as_ptr(), - flags, - mask, - &mut buf as *mut _, - ) - }; - let err = IOError::last_os_error(); + let flags = AtFlags::NO_AUTOMOUNT | AtFlags::SYMLINK_NOFOLLOW | AtFlags::EMPTY_PATH; - if ret >= 0 { - Ok(buf) - } else { - Err(Error::Statx { - dirfd: dirfd.into(), - path: path.into(), - flags, - mask, - source: err, - }) - } + rustix_fs::statx(dirfd, path, flags, mask).map_err(|errno| Error::Statx { + dirfd: dirfd.into(), + path: path.into(), + flags, + mask, + source: errno, + }) } // MSRV(1.80): Use LazyLock. @@ -728,7 +636,6 @@ bitflags! { } /// Arguments for how `openat2` should open the target path. -// TODO: Maybe switch to libc::open_how? #[repr(C)] #[derive(Clone, Debug, Default)] pub struct OpenHow { @@ -763,12 +670,14 @@ impl fmt::Display for OpenHow { } } +// NOTE: rustix's openat2 wrapper is not extensible-friendly so we use our own +// for now. See . pub(crate) fn openat2>( dirfd: Fd, path: P, how: &OpenHow, ) -> Result { - let dirfd = dirfd.as_fd(); + let dirfd = dirfd.as_fd().hotfix_rustix_fd()?; let path = path.as_ref(); // Add O_CLOEXEC explicitly. No need for O_NOFOLLOW because @@ -797,89 +706,46 @@ pub(crate) fn openat2>( path: path.into(), how, size: std::mem::size_of::(), - source: err, + source: err + .raw_os_error() + .map(Errno::from_raw_os_error) + .expect("syscall failure must result in a real OS error"), }) } } #[cfg(test)] -pub(crate) fn getpid() -> libc::pid_t { - // SAFETY: Obviously safe libc function. - unsafe { libc::getpid() } +pub(crate) fn getpid() -> rustix_process::RawPid { + rustix_process::Pid::as_raw(Some(rustix_process::getpid())) } -pub(crate) fn gettid() -> libc::pid_t { - // SAFETY: Obviously safe libc function. - unsafe { libc::gettid() } +pub(crate) fn gettid() -> rustix_process::RawPid { + rustix_process::Pid::as_raw(Some(rustix_thread::gettid())) } -pub(crate) fn geteuid() -> libc::uid_t { - // SAFETY: Obviously safe libc function. - unsafe { libc::geteuid() } +pub(crate) fn geteuid() -> rustix_process::RawUid { + rustix_process::geteuid().as_raw() } #[cfg(test)] -pub(crate) fn getegid() -> libc::gid_t { - // SAFETY: Obviously safe libc function. - unsafe { libc::getegid() } +pub(crate) fn getegid() -> rustix_process::RawGid { + rustix_process::getegid().as_raw() } #[cfg(test)] pub(crate) fn getcwd() -> Result { let buffer = Vec::with_capacity(libc::PATH_MAX as usize); - Ok(OsStr::from_bytes(rustix::process::getcwd(buffer)?.to_bytes()).into()) + Ok(OsStr::from_bytes(rustix_process::getcwd(buffer)?.to_bytes()).into()) } -bitflags! { - #[derive(Default, PartialEq, Eq, Debug, Clone, Copy)] - pub struct FsopenFlags: i32 { - const FSOPEN_CLOEXEC = 0x1; - } - - #[derive(Default, PartialEq, Eq, Debug, Clone, Copy)] - pub struct FsmountFlags: i32 { - const FSMOUNT_CLOEXEC = 0x1; - } - - #[derive(Default, PartialEq, Eq, Debug, Clone, Copy)] - pub struct OpenTreeFlags: i32 { - const AT_RECURSIVE = libc::AT_RECURSIVE; - const OPEN_TREE_CLOEXEC = libc::O_CLOEXEC; - const OPEN_TREE_CLONE = 0x1; - } -} - -#[repr(i32)] -#[allow(dead_code)] -enum FsconfigCmd { - SetFlag = 0x0, // FSCONFIG_SET_FLAG - SetString = 0x1, // FSCONFIG_SET_STRING - SetBinary = 0x2, // FSCONFIG_SET_BINARY - SetPath = 0x3, // FSCONFIG_SET_PATH - SetPathEmpty = 0x4, // FSCONFIG_SET_PATH_EMPTY - SetFd = 0x5, // FSCONFIG_SET_FD - Create = 0x6, // FSCONFIG_CREATE - Reconfigure = 0x7, // FSCONFIG_RECONFIGURE -} - -pub(crate) fn fsopen>(fstype: S, flags: FsopenFlags) -> Result { +pub(crate) fn fsopen>(fstype: S, flags: FsOpenFlags) -> Result { let fstype = fstype.as_ref(); - let c_fstype = CString::new(fstype).expect("fsopen argument should be valid C string"); - // SAFETY: Obviously safe-to-use Linux syscall. - let fd = unsafe { libc::syscall(libc::SYS_fsopen, c_fstype.as_ptr(), flags.bits()) } as RawFd; - let err = IOError::last_os_error(); - - if fd >= 0 { - // SAFETY: We know it's a real file descriptor. - Ok(unsafe { OwnedFd::from_raw_fd(fd) }) - } else { - Err(Error::Fsopen { - fstype: fstype.into(), - flags, - source: err, - }) - } + rustix_mount::fsopen(fstype, flags).map_err(|errno| Error::Fsopen { + fstype: fstype.into(), + flags, + source: errno, + }) } pub(crate) fn fsconfig_set_string, V: AsRef>( @@ -887,93 +753,40 @@ pub(crate) fn fsconfig_set_string, V: AsRef>( key: K, value: V, ) -> Result<(), Error> { - let sfd = sfd.as_fd(); + let sfd = sfd.as_fd().hotfix_rustix_fd()?; let key = key.as_ref(); - let c_key = CString::new(key).expect("fsconfig_set_string key should be valid C string"); let value = value.as_ref(); - let c_value = CString::new(value).expect("fsconfig_set_string value should be valid C string"); - // SAFETY: Obviously safe-to-use Linux syscall. - let ret = unsafe { - libc::syscall( - libc::SYS_fsconfig, - sfd.as_raw_fd(), - FsconfigCmd::SetString, - c_key.as_ptr(), - c_value.as_ptr(), - 0, - ) - }; - let err = IOError::last_os_error(); - - if ret >= 0 { - Ok(()) - } else { - Err(Error::FsconfigSetString { - sfd: sfd.into(), - key: key.into(), - value: value.into(), - source: err, - }) - } + rustix_mount::fsconfig_set_string(sfd, key, value).map_err(|errno| Error::FsconfigSetString { + sfd: sfd.into(), + key: key.into(), + value: value.into(), + source: errno, + }) } -// clippy doesn't understand that we need to specify a type for ptr::null() here -// because libc::syscall() is variadic. -#[allow(clippy::unnecessary_cast)] pub(crate) fn fsconfig_create(sfd: Fd) -> Result<(), Error> { - let sfd = sfd.as_fd(); - // SAFETY: Obviously safe-to-use Linux syscall. - let ret = unsafe { - libc::syscall( - libc::SYS_fsconfig, - sfd.as_raw_fd(), - FsconfigCmd::Create, - ptr::null() as *const (), - ptr::null() as *const (), - 0, - ) - }; - let err = IOError::last_os_error(); + let sfd = sfd.as_fd().hotfix_rustix_fd()?; - if ret >= 0 { - Ok(()) - } else { - Err(Error::FsconfigCreate { - sfd: sfd.into(), - source: err, - }) - } + rustix_mount::fsconfig_create(sfd).map_err(|errno| Error::FsconfigCreate { + sfd: sfd.into(), + source: errno, + }) } pub(crate) fn fsmount( sfd: Fd, - flags: FsmountFlags, - mount_attrs: u64, + flags: FsMountFlags, + mount_attrs: MountAttrFlags, ) -> Result { - let sfd = sfd.as_fd(); - // SAFETY: Obviously safe-to-use Linux syscall. - let fd = unsafe { - libc::syscall( - libc::SYS_fsmount, - sfd.as_raw_fd(), - flags.bits(), - mount_attrs, - ) - } as RawFd; - let err = IOError::last_os_error(); + let sfd = sfd.as_fd().hotfix_rustix_fd()?; - if fd >= 0 { - // SAFETY: We know it's a real file descriptor. - Ok(unsafe { OwnedFd::from_raw_fd(fd) }) - } else { - Err(Error::Fsmount { - sfd: sfd.into(), - flags, - mount_attrs, - source: err, - }) - } + rustix_mount::fsmount(sfd, flags, mount_attrs).map_err(|errno| Error::Fsmount { + sfd: sfd.into(), + flags, + mount_attrs, + source: errno, + }) } pub(crate) fn open_tree>( @@ -981,30 +794,13 @@ pub(crate) fn open_tree>( path: P, flags: OpenTreeFlags, ) -> Result { - let dirfd = dirfd.as_fd(); + let dirfd = dirfd.as_fd().hotfix_rustix_fd()?; let path = path.as_ref(); - let c_path = path.to_c_string(); - // SAFETY: Obviously safe-to-use Linux syscall. - let fd = unsafe { - libc::syscall( - libc::SYS_open_tree, - dirfd.as_raw_fd(), - c_path.as_ptr(), - flags.bits(), - ) - } as RawFd; - let err = IOError::last_os_error(); - - if fd >= 0 { - // SAFETY: We know it's a real file descriptor. - Ok(unsafe { OwnedFd::from_raw_fd(fd) }) - } else { - Err(Error::OpenTree { - dirfd: dirfd.into(), - path: path.into(), - flags, - source: err, - }) - } + rustix_mount::open_tree(dirfd, path, flags).map_err(|errno| Error::OpenTree { + dirfd: dirfd.into(), + path: path.into(), + flags, + source: errno, + }) } diff --git a/src/tests/common/mntns.rs b/src/tests/common/mntns.rs index cb80947..9867d48 100644 --- a/src/tests/common/mntns.rs +++ b/src/tests/common/mntns.rs @@ -17,60 +17,19 @@ * along with this program. If not, see . */ +use crate::{flags::OpenFlags, syscalls, utils::FdExt}; + use std::{ - ffi::CString, fs::File, - io::Error as IOError, - os::unix::io::{AsRawFd, RawFd}, + os::unix::io::{AsFd, AsRawFd}, path::{Path, PathBuf}, - ptr, }; -use crate::{syscalls, utils::ToCString}; - -use anyhow::Error; -use libc::c_int; - -unsafe fn unshare(flags: c_int) -> Result<(), IOError> { - // SAFETY: Caller guarantees that this unshare operation is safe. - let ret = unsafe { libc::unshare(flags) }; - let err = IOError::last_os_error(); - if ret >= 0 { - Ok(()) - } else { - Err(err) - } -} - -unsafe fn setns(fd: RawFd, flags: c_int) -> Result<(), IOError> { - // SAFETY: Caller guarantees that this setns operation is safe. - let ret = unsafe { libc::setns(fd, flags) }; - let err = IOError::last_os_error(); - if ret >= 0 { - Ok(()) - } else { - Err(err) - } -} - -fn make_slave>(path: P) -> Result<(), IOError> { - // SAFETY: Obviously safe syscall. - let ret = unsafe { - libc::mount( - ptr::null(), - path.as_ref().to_c_string().as_ptr(), - ptr::null(), - libc::MS_SLAVE | libc::MS_REC, - ptr::null(), - ) - }; - let err = IOError::last_os_error(); - if ret >= 0 { - Ok(()) - } else { - Err(err) - } -} +use anyhow::{Context, Error}; +use rustix::{ + mount::{self as rustix_mount, MountFlags, MountPropagationFlags}, + thread::{self as rustix_thread, LinkNameSpaceType, UnshareFlags}, +}; #[derive(Debug, Clone)] pub(crate) enum MountType { @@ -80,42 +39,50 @@ pub(crate) enum MountType { pub(in crate::tests) fn mount>(dst: P, ty: MountType) -> Result<(), Error> { let dst = dst.as_ref(); - let dst_file = syscalls::openat(syscalls::AT_FDCWD, dst, libc::O_NOFOLLOW | libc::O_PATH, 0)?; - let dst_path = CString::new(format!("/proc/self/fd/{}", dst_file.as_raw_fd()))?; - - let ret = match ty { - MountType::Tmpfs => unsafe { - libc::mount( - // MSRV(1.77): Use c"". - CString::new("")?.as_ptr(), - dst_path.as_ptr(), - // MSRV(1.77): Use c"tmpfs". - CString::new("tmpfs")?.as_ptr(), - 0, - ptr::null(), + let dst_file = syscalls::openat( + syscalls::AT_FDCWD, + dst, + OpenFlags::O_NOFOLLOW | OpenFlags::O_PATH, + 0, + )?; + let dst_path = format!("/proc/self/fd/{}", dst_file.as_raw_fd()); + + match ty { + MountType::Tmpfs => rustix_mount::mount2( + None::<&Path>, + &dst_path, + Some("tmpfs"), + MountFlags::empty(), + None, + ) + .with_context(|| { + format!( + "mount tmpfs on {:?}", + dst_file + .as_unsafe_path_unchecked() + .unwrap_or(dst_path.into()) ) - }, + }), MountType::Bind { src } => { - let src_file = - syscalls::openat(syscalls::AT_FDCWD, src, libc::O_NOFOLLOW | libc::O_PATH, 0)?; - let src_path = CString::new(format!("/proc/self/fd/{}", src_file.as_raw_fd()))?; - unsafe { - libc::mount( - src_path.as_ptr(), - dst_path.as_ptr(), - ptr::null(), - libc::MS_BIND, - ptr::null(), + let src_file = syscalls::openat( + syscalls::AT_FDCWD, + src, + OpenFlags::O_NOFOLLOW | OpenFlags::O_PATH, + 0, + )?; + let src_path = format!("/proc/self/fd/{}", src_file.as_raw_fd()); + rustix_mount::mount_bind(&src_path, &dst_path).with_context(|| { + format!( + "bind-mount {:?} -> {:?}", + src_file + .as_unsafe_path_unchecked() + .unwrap_or(src_path.into()), + dst_file + .as_unsafe_path_unchecked() + .unwrap_or(dst_path.into()) ) - } + }) } - }; - let err = IOError::last_os_error(); - - if ret >= 0 { - Ok(()) - } else { - Err(err.into()) } } @@ -127,15 +94,18 @@ where // TODO: Run this in a subprocess. - unsafe { unshare(libc::CLONE_FS | libc::CLONE_NEWNS) } + rustix_thread::unshare(UnshareFlags::FS | UnshareFlags::NEWNS) .expect("unable to create a mount namespace"); // Mark / as MS_SLAVE to avoid DoSing the host. - make_slave("/")?; + rustix_mount::mount_change( + "/", + MountPropagationFlags::SLAVE | MountPropagationFlags::REC, + )?; let ret = func(); - unsafe { setns(old_ns.as_raw_fd(), libc::CLONE_NEWNS) } + rustix_thread::move_into_link_name_space(old_ns.as_fd(), Some(LinkNameSpaceType::Mount)) .expect("unable to rejoin old namespace"); ret diff --git a/src/tests/common/root.rs b/src/tests/common/root.rs index 6c3f326..3bd3469 100644 --- a/src/tests/common/root.rs +++ b/src/tests/common/root.rs @@ -17,32 +17,14 @@ * along with this program. If not, see . */ -use std::{ - ffi::CString, - fs, io, - os::unix::{ffi::OsStrExt, fs as unixfs}, - path::Path, -}; +use std::{fs, os::unix::fs as unixfs, path::Path}; + +use crate::syscalls; use anyhow::{Context, Error}; use rustix::fs::{self as rustix_fs, AtFlags, OFlags, CWD}; -#[cfg(feature = "_test_as_root")] -use rustix::process::{Gid, Uid}; use tempfile::TempDir; -fn mknod>(path: P, mode: libc::mode_t, dev: libc::dev_t) -> Result<(), io::Error> { - let path = CString::new(path.as_ref().as_os_str().as_bytes()).expect("CString::new failed"); - // SAFETY: Obviously safe-to-use Linux syscall. - let ret = unsafe { libc::mknod(path.as_ptr(), mode, dev) }; - let err = io::Error::last_os_error(); - - if ret >= 0 { - Ok(()) - } else { - Err(err) - } -} - macro_rules! create_inode { // "/foo/bar" @ chmod 0o755 (@do $path:expr, chmod $mode:expr) => { @@ -57,8 +39,8 @@ macro_rules! create_inode { CWD, $path, // SAFETY: We pick valid uids and gids for this. - Some(unsafe { Uid::from_raw($uid) }), - Some(unsafe { Gid::from_raw($gid) }), + Some(unsafe { ::rustix::process::Uid::from_raw($uid) }), + Some(unsafe { ::rustix::process::Gid::from_raw($gid) }), AtFlags::SYMLINK_NOFOLLOW, ) .with_context(|| format!("chown {}:{} {}", $uid, $gid, $path.display()))?; @@ -70,7 +52,7 @@ macro_rules! create_inode { CWD, $path, // SAFETY: We pick valid uids and gids for this. - Some(unsafe { Uid::from_raw($uid) }), + Some(unsafe { ::rustix::process::Uid::from_raw($uid) }), None, AtFlags::SYMLINK_NOFOLLOW, ) @@ -84,7 +66,7 @@ macro_rules! create_inode { $path, // SAFETY: We pick valid uids and gids for this. None, - Some(unsafe { Gid::from_raw($gid) }), + Some(unsafe { ::rustix::process::Gid::from_raw($gid) }), AtFlags::SYMLINK_NOFOLLOW, ) .with_context(|| format!("chown :{} {}", $gid, $path.display()))?; @@ -108,7 +90,7 @@ macro_rules! create_inode { }; // "/foo/bar" => fifo ($path:expr => fifo $(, {$($extra:tt)*})*) => { - mknod($path, libc::S_IFIFO | 0o644, 0) + syscalls::mknodat(rustix_fs::CWD, $path, libc::S_IFIFO | 0o644, 0) .with_context(|| format!("mkfifo {}", $path.display()))?; $( create_inode!(@do $path, $($extra)*); @@ -116,7 +98,7 @@ macro_rules! create_inode { }; // "/foo/bar" => sock ($path:expr => sock $(,{$($extra:tt)*})*) => { - mknod($path, libc::S_IFSOCK | 0o644, 0) + syscalls::mknodat(rustix_fs::CWD, $path, libc::S_IFSOCK | 0o644, 0) .with_context(|| format!("mksock {}", $path.display()))?; $( create_inode!(@do $path, $($extra)*); diff --git a/src/tests/test_procfs.rs b/src/tests/test_procfs.rs index c4121f2..3fb3925 100644 --- a/src/tests/test_procfs.rs +++ b/src/tests/test_procfs.rs @@ -24,11 +24,12 @@ use crate::{ flags::OpenFlags, procfs::{ProcfsBase, ProcfsHandle}, resolvers::procfs::ProcfsResolver, - syscalls::{self, OpenTreeFlags}, + syscalls, }; use utils::ExpectedResult; use anyhow::Error; +use rustix::mount::OpenTreeFlags; macro_rules! procfs_tests { // Create the actual test functions. diff --git a/src/tests/test_root_ops.rs b/src/tests/test_root_ops.rs index 69db371..3c72f26 100644 --- a/src/tests/test_root_ops.rs +++ b/src/tests/test_root_ops.rs @@ -436,8 +436,11 @@ mod utils { }; use anyhow::Error; - use libc::mode_t; use pretty_assertions::{assert_eq, assert_ne}; + use rustix::{ + fs::{Mode, RawMode}, + process as rustix_process, + }; fn root_roundtrip(root: R) -> Result { let root_clone = root.try_clone()?; @@ -455,12 +458,12 @@ mod utils { root: R, path: P, inode_type: InodeType, - expected_result: Result<(&str, mode_t), ErrorKind>, + expected_result: Result<(&str, RawMode), ErrorKind>, ) -> Result<(), Error> { let path = path.as_ref(); // Just clear the umask so all of the tests can use all of the // permission bits. - let _ = unsafe { libc::umask(0) }; + let _ = rustix_process::umask(Mode::empty()); // Update the expected path to have the rootdir as a prefix. let root_dir = root.as_fd().as_unsafe_path_unchecked()?; @@ -528,7 +531,7 @@ mod utils { let path = path.as_ref(); // Just clear the umask so all of the tests can use all of the // permission bits. - let _ = unsafe { libc::umask(0) }; + let _ = rustix_process::umask(Mode::empty()); // Get a handle to the original path if it existed beforehand. let pre_create_handle = root.resolve_nofollow(path); // do not unwrap diff --git a/src/utils/dir.rs b/src/utils/dir.rs index 167c9a1..5615eb5 100644 --- a/src/utils/dir.rs +++ b/src/utils/dir.rs @@ -19,6 +19,7 @@ use crate::{ error::{Error, ErrorExt, ErrorImpl}, + flags::OpenFlags, syscalls, }; @@ -28,16 +29,16 @@ use std::{ path::Path, }; -use rustix::fs::Dir; +use rustix::fs::{AtFlags, Dir}; fn remove_inode(dirfd: Fd, name: &Path) -> Result<(), Error> { let dirfd = dirfd.as_fd(); // To ensure we return a useful error, we try both unlink and rmdir and // try to avoid returning EISDIR/ENOTDIR if both failed. - syscalls::unlinkat(dirfd, name, 0) + syscalls::unlinkat(dirfd, name, AtFlags::empty()) .or_else(|unlink_err| { - syscalls::unlinkat(dirfd, name, libc::AT_REMOVEDIR).map_err(|rmdir_err| { + syscalls::unlinkat(dirfd, name, AtFlags::REMOVEDIR).map_err(|rmdir_err| { if rmdir_err.root_cause().raw_os_error() == Some(libc::ENOTDIR) { unlink_err } else { @@ -74,7 +75,7 @@ pub(crate) fn remove_all(dirfd: Fd, name: &Path) -> Result<(), Error> // try to make this loop forever by consistently creating inodes, but // there's not much we can do about it and I suspect they would eventually // lose the race. - let subdir = syscalls::openat(dirfd, name, libc::O_DIRECTORY, 0).map_err(|err| { + let subdir = syscalls::openat(dirfd, name, OpenFlags::O_DIRECTORY, 0).map_err(|err| { ErrorImpl::RawOsError { operation: "open directory to scan entries".into(), source: err, diff --git a/src/utils/fd.rs b/src/utils/fd.rs index 340beb9..7c6db60 100644 --- a/src/utils/fd.rs +++ b/src/utils/fd.rs @@ -33,7 +33,9 @@ use std::{ path::{Path, PathBuf}, }; -pub(crate) struct Metadata(libc::stat); +use rustix::fs::{self as rustix_fs, StatExt, StatxFlags}; + +pub(crate) struct Metadata(rustix_fs::Stat); impl Metadata { pub(crate) fn is_symlink(&self) -> bool { @@ -75,27 +77,27 @@ impl MetadataExt for Metadata { } fn atime(&self) -> i64 { - self.0.st_atime + self.0.atime() } fn atime_nsec(&self) -> i64 { - self.0.st_atime_nsec + self.0.st_atime_nsec as i64 } fn mtime(&self) -> i64 { - self.0.st_mtime + self.0.mtime() } fn mtime_nsec(&self) -> i64 { - self.0.st_mtime_nsec + self.0.st_mtime_nsec as i64 } fn ctime(&self) -> i64 { - self.0.st_ctime + self.0.ctime() } fn ctime_nsec(&self) -> i64 { - self.0.st_ctime_nsec + self.0.st_ctime_nsec as i64 } fn blksize(&self) -> u64 { @@ -177,7 +179,7 @@ const DANGEROUS_FILESYSTEMS: [i64; 2] = [ 0x5a3c_69f0, // apparmorfs ]; -impl FdExt for T { +impl FdExt for Fd { fn metadata(&self) -> Result { let stat = syscalls::fstatat(self.as_fd(), "").map_err(|err| ErrorImpl::RawOsError { operation: "get fd metadata".into(), @@ -261,15 +263,18 @@ pub(crate) fn fetch_mnt_id>( // [2]: Linux commit 990d6c2d7aee ("vfs: Add name to file handle conversion support") // [3]: Linux commit 64343119d7b8 ("exportfs: support encoding non-decodeable file handles by default") - const STATX_MNT_ID_UNIQUE: u32 = 0x4000; - let want_mask = libc::STATX_MNT_ID | STATX_MNT_ID_UNIQUE; + const STATX_MNT_ID_UNIQUE: StatxFlags = StatxFlags::from_bits_retain(0x4000); + let want_mask = StatxFlags::MNT_ID | STATX_MNT_ID_UNIQUE; match syscalls::statx(dirfd, path, want_mask) { - Ok(stx) => Ok(if stx.stx_mask & want_mask != 0 { - Some(stx.stx_mnt_id) - } else { - None - }), + Ok(stx) => { + let got_mask = StatxFlags::from_bits_retain(stx.stx_mask); + Ok(if got_mask.intersects(want_mask) { + Some(stx.stx_mnt_id) + } else { + None + }) + } Err(err) => match err.root_cause().raw_os_error() { // We have to handle STATX_MNT_ID not being supported on pre-5.8 // kernels, so treat an ENOSYS or EINVAL the same so that we can