From 873bac5c80155a816ef5720e5bfeeff5c2f9861e Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 18 Oct 2024 08:19:35 -0700 Subject: [PATCH] Add a `rustix::fs::ABS` constant. (#1189) Add a `rustix::fs::ABS` constant, which corresponds to the undocumented but commonly used convention of using `-EBADF` as the file descriptor in `openat` and similar calls. This makes them fail if the path is not absolute. Fixes #1187. --- src/backend/libc/fs/syscalls.rs | 13 ++---- src/backend/linux_raw/c.rs | 2 +- src/backend/linux_raw/conv.rs | 2 +- src/fs/at.rs | 5 ++- src/fs/cwd.rs | 29 ------------- src/fs/mod.rs | 12 ++---- src/fs/special.rs | 73 +++++++++++++++++++++++++++++++++ src/process/chdir.rs | 2 +- tests/fs/cwd.rs | 4 -- tests/fs/main.rs | 2 +- tests/fs/special.rs | 57 +++++++++++++++++++++++++ 11 files changed, 145 insertions(+), 56 deletions(-) delete mode 100644 src/fs/cwd.rs create mode 100644 src/fs/special.rs delete mode 100644 tests/fs/cwd.rs create mode 100644 tests/fs/special.rs diff --git a/src/backend/libc/fs/syscalls.rs b/src/backend/libc/fs/syscalls.rs index a6cfb95a0..2d2f4c674 100644 --- a/src/backend/libc/fs/syscalls.rs +++ b/src/backend/libc/fs/syscalls.rs @@ -595,14 +595,9 @@ pub(crate) fn stat(path: &CStr) -> io::Result { ) ))] { - match crate::fs::statx( - crate::fs::CWD, - path, - AtFlags::empty(), - StatxFlags::BASIC_STATS, - ) { + match crate::fs::statx(CWD, path, AtFlags::empty(), StatxFlags::BASIC_STATS) { Ok(x) => statx_to_stat(x), - Err(io::Errno::NOSYS) => statat_old(crate::fs::CWD, path, AtFlags::empty()), + Err(io::Errno::NOSYS) => statat_old(CWD, path, AtFlags::empty()), Err(err) => Err(err), } } @@ -636,13 +631,13 @@ pub(crate) fn lstat(path: &CStr) -> io::Result { ))] { match crate::fs::statx( - crate::fs::CWD, + CWD, path, AtFlags::SYMLINK_NOFOLLOW, StatxFlags::BASIC_STATS, ) { Ok(x) => statx_to_stat(x), - Err(io::Errno::NOSYS) => statat_old(crate::fs::CWD, path, AtFlags::SYMLINK_NOFOLLOW), + Err(io::Errno::NOSYS) => statat_old(CWD, path, AtFlags::SYMLINK_NOFOLLOW), Err(err) => Err(err), } } diff --git a/src/backend/linux_raw/c.rs b/src/backend/linux_raw/c.rs index c4e1001b7..08ded6b35 100644 --- a/src/backend/linux_raw/c.rs +++ b/src/backend/linux_raw/c.rs @@ -8,7 +8,7 @@ pub(crate) type size_t = usize; pub(crate) use linux_raw_sys::ctypes::*; -pub(crate) use linux_raw_sys::errno::EINVAL; +pub(crate) use linux_raw_sys::errno::{EBADF, EINVAL}; pub(crate) use linux_raw_sys::general::{__kernel_fd_set as fd_set, __FD_SETSIZE as FD_SETSIZE}; pub(crate) use linux_raw_sys::ioctl::{FIONBIO, FIONREAD}; // Import the kernel's `uid_t` and `gid_t` if they're 32-bit. diff --git a/src/backend/linux_raw/conv.rs b/src/backend/linux_raw/conv.rs index 269b7ade4..c8e8a4280 100644 --- a/src/backend/linux_raw/conv.rs +++ b/src/backend/linux_raw/conv.rs @@ -162,7 +162,7 @@ impl<'a, Num: ArgNumber> From> for ArgReg<'a, Num> { pub(super) unsafe fn raw_fd<'a, Num: ArgNumber>(fd: RawFd) -> ArgReg<'a, Num> { // Use `no_fd` when passing `-1` is intended. #[cfg(feature = "fs")] - debug_assert!(fd == crate::fs::CWD.as_raw_fd() || fd >= 0); + debug_assert!(fd == crate::fs::CWD.as_raw_fd() || fd == crate::fs::ABS.as_raw_fd() || fd >= 0); // Don't pass the `io_uring_register_files_skip` sentry value this way. #[cfg(feature = "io_uring")] diff --git a/src/fs/at.rs b/src/fs/at.rs index bf9628070..94db1baee 100644 --- a/src/fs/at.rs +++ b/src/fs/at.rs @@ -1,9 +1,10 @@ //! POSIX-style `*at` functions. //! //! The `dirfd` argument to these functions may be a file descriptor for a -//! directory, or the special value [`CWD`]. +//! directory, the special value [`CWD`], or the special value [`ABS`]. //! -//! [`cwd`]: crate::fs::CWD +//! [`CWD`]: crate::fs::CWD +//! [`ABS`]: crate::fs::ABS use crate::fd::OwnedFd; use crate::ffi::CStr; diff --git a/src/fs/cwd.rs b/src/fs/cwd.rs deleted file mode 100644 index 3b6092eec..000000000 --- a/src/fs/cwd.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! The `cwd` function, representing the current working directory. -//! -//! # Safety -//! -//! This file uses `AT_FDCWD`, which is a raw file descriptor, but which is -//! always valid. - -#![allow(unsafe_code)] - -use crate::backend; -use backend::c; -use backend::fd::{BorrowedFd, RawFd}; - -/// `AT_FDCWD`—A handle representing the current working directory. -/// -/// This is a file descriptor which refers to the process current directory -/// which can be used as the directory argument in `*at` functions such as -/// [`openat`]. -/// -/// # References -/// - [POSIX] -/// -/// [`openat`]: crate::fs::openat -/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/fcntl.h.html -// SAFETY: `AT_FDCWD` is a reserved value that is never dynamically -// allocated, so it'll remain valid for the duration of `'static`. -#[doc(alias = "AT_FDCWD")] -pub const CWD: BorrowedFd<'static> = - unsafe { BorrowedFd::<'static>::borrow_raw(c::AT_FDCWD as RawFd) }; diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 841ee7cae..07fd66479 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -6,10 +6,6 @@ mod at; mod constants; #[cfg(linux_kernel)] mod copy_file_range; -#[cfg(not(any(target_os = "espidf", target_os = "redox")))] -#[cfg(not(target_os = "haiku"))] -// Haiku needs -mod cwd; #[cfg(all(feature = "alloc", not(any(target_os = "espidf", target_os = "redox"))))] mod dir; #[cfg(not(any( @@ -54,6 +50,8 @@ mod raw_dir; mod seek_from; #[cfg(target_os = "linux")] mod sendfile; +#[cfg(not(any(target_os = "espidf", target_os = "redox")))] +mod special; #[cfg(linux_kernel)] mod statx; #[cfg(not(any( @@ -72,10 +70,6 @@ pub use at::*; pub use constants::*; #[cfg(linux_kernel)] pub use copy_file_range::copy_file_range; -#[cfg(not(any(target_os = "espidf", target_os = "redox")))] -#[cfg(not(target_os = "haiku"))] -// Haiku needs -pub use cwd::*; #[cfg(all(feature = "alloc", not(any(target_os = "espidf", target_os = "redox"))))] pub use dir::{Dir, DirEntry}; #[cfg(not(any( @@ -118,6 +112,8 @@ pub use raw_dir::{RawDir, RawDirEntry}; pub use seek_from::SeekFrom; #[cfg(target_os = "linux")] pub use sendfile::sendfile; +#[cfg(not(any(target_os = "espidf", target_os = "redox")))] +pub use special::*; #[cfg(linux_kernel)] pub use statx::statx; #[cfg(not(any( diff --git a/src/fs/special.rs b/src/fs/special.rs new file mode 100644 index 000000000..19d53f7e1 --- /dev/null +++ b/src/fs/special.rs @@ -0,0 +1,73 @@ +//! The `CWD` and `ABS` constants, representing the current working directory +//! and absolute-only paths, respectively. +//! +//! # Safety +//! +//! This file uses `AT_FDCWD`, which is a raw file descriptor, but which is +//! always valid, and `-EBADF`, which is an undocumented by commonly used +//! convention of passing a value which will always fail if the accompanying +//! path isn't absolute. + +#![allow(unsafe_code)] + +use crate::backend; +use backend::c; +use backend::fd::{BorrowedFd, RawFd}; + +/// `AT_FDCWD`—A handle representing the current working directory. +/// +/// This is a file descriptor which refers to the process current directory +/// which can be used as the directory argument in `*at` functions such as +/// [`openat`]. +/// +/// # References +/// - [POSIX] +/// +/// [`openat`]: crate::fs::openat +/// [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/fcntl.h.html +// SAFETY: `AT_FDCWD` is a reserved value that is never dynamically +// allocated, so it'll remain valid for the duration of `'static`. +#[doc(alias = "AT_FDCWD")] +pub const CWD: BorrowedFd<'static> = + unsafe { BorrowedFd::<'static>::borrow_raw(c::AT_FDCWD as RawFd) }; + +/// `-EBADF`—A handle that requires paths to be absolute. +/// +/// This is a file descriptor which refers to no directory, which can be used +/// as the directory argument in `*at` functions such as [`openat`], which +/// causes them to fail with [`BADF`] if the accompanying path is not absolute. +/// +/// This corresponds to the undocumented by commonly used convention of +/// passing `-EBADF` as the `dirfd` argument, which is ignored if the path +/// is absolute, and evokes an `EBADF` error otherwise. +/// +/// [`openat`]: crate::fs::openat +/// [`BADF`]: crate::io::Errno::BADF +// SAFETY: This `-EBADF` convention is commonly used, such as in lxc, so OS's +// aren't going to break it. +pub const ABS: BorrowedFd<'static> = + unsafe { BorrowedFd::<'static>::borrow_raw(c::EBADF.wrapping_neg() as RawFd) }; + +#[cfg(test)] +mod tests { + use super::*; + use crate::fd::AsRawFd; + + #[test] + fn test_cwd() { + assert!(CWD.as_raw_fd() != -1); + #[cfg(linux_kernel)] + #[cfg(feature = "io_uring")] + assert!(CWD.as_raw_fd() != linux_raw_sys::io_uring::IORING_REGISTER_FILES_SKIP); + } + + #[test] + fn test_abs() { + assert!(ABS.as_raw_fd() < 0); + assert!(ABS.as_raw_fd() != -1); + assert!(ABS.as_raw_fd() != c::AT_FDCWD); + #[cfg(linux_kernel)] + #[cfg(feature = "io_uring")] + assert!(ABS.as_raw_fd() != linux_raw_sys::io_uring::IORING_REGISTER_FILES_SKIP); + } +} diff --git a/src/process/chdir.rs b/src/process/chdir.rs index d38a33fda..04331ee01 100644 --- a/src/process/chdir.rs +++ b/src/process/chdir.rs @@ -40,7 +40,7 @@ pub fn fchdir(fd: Fd) -> io::Result<()> { backend::process::syscalls::fchdir(fd.as_fd()) } -/// `getCWD`—Return the current working directory. +/// `getcwd`—Return the current working directory. /// /// If `reuse` already has available capacity, reuse it if possible. /// diff --git a/tests/fs/cwd.rs b/tests/fs/cwd.rs deleted file mode 100644 index e9f345164..000000000 --- a/tests/fs/cwd.rs +++ /dev/null @@ -1,4 +0,0 @@ -/// Make sure we can use `cwd` in const contexts. -#[allow(dead_code)] -#[cfg(not(target_os = "redox"))] -const CWD: rustix::fd::BorrowedFd<'static> = rustix::fs::CWD; diff --git a/tests/fs/main.rs b/tests/fs/main.rs index db074fedb..8c8c0e5c0 100644 --- a/tests/fs/main.rs +++ b/tests/fs/main.rs @@ -5,7 +5,6 @@ #![cfg_attr(core_c_str, feature(core_c_str))] mod chmodat; -mod cwd; #[cfg(not(target_os = "redox"))] mod dir; mod fcntl; @@ -42,6 +41,7 @@ mod renameat; #[cfg(any(linux_kernel, target_os = "freebsd"))] mod seals; mod seek; +mod special; #[cfg(not(any(target_os = "haiku", target_os = "redox", target_os = "wasi")))] mod statfs; #[cfg(linux_kernel)] diff --git a/tests/fs/special.rs b/tests/fs/special.rs new file mode 100644 index 000000000..31c4cd2f0 --- /dev/null +++ b/tests/fs/special.rs @@ -0,0 +1,57 @@ +#[cfg(not(target_os = "wasi"))] +#[test] +fn test_special_fds() { + use rustix::fs::{fstat, open, openat, Mode, OFlags, Stat, ABS, CWD}; + use rustix::process::getcwd; + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + use std::path::PathBuf; + + let cwd_path = getcwd(Vec::new()).unwrap().into_bytes(); + let cwd_path = OsStr::from_bytes(&cwd_path).to_owned(); + let cwd_path = PathBuf::from(cwd_path); + + // Open the same file several ways using special constants and make sure + // we get the same file. + + // Use plain `open`. + let a = open("Cargo.toml", OFlags::RDONLY, Mode::empty()).unwrap(); + + // Use `CWD` with a relative path. + let b = openat(CWD, "Cargo.toml", OFlags::RDONLY, Mode::empty()).unwrap(); + + // Use `CWD` with an absolute path. + let c = openat( + CWD, + cwd_path.join("Cargo.toml"), + OFlags::RDONLY, + Mode::empty(), + ) + .unwrap(); + + // Use `ABS` with an absolute path. + let d = openat( + ABS, + cwd_path.join("Cargo.toml"), + OFlags::RDONLY, + Mode::empty(), + ) + .unwrap(); + + // Test that opening a relative path with `ABS` fails. + let err = openat(ABS, "Cargo.toml", OFlags::RDONLY, Mode::empty()).unwrap_err(); + assert_eq!(err, rustix::io::Errno::BADF); + + let a_stat = fstat(a).unwrap(); + let b_stat = fstat(b).unwrap(); + let c_stat = fstat(c).unwrap(); + let d_stat = fstat(d).unwrap(); + + assert!(same(&a_stat, &b_stat)); + assert!(same(&b_stat, &c_stat)); + assert!(same(&c_stat, &d_stat)); + + fn same(a: &Stat, b: &Stat) -> bool { + a.st_ino == b.st_ino && a.st_dev == b.st_dev + } +}