Skip to content

Commit

Permalink
Add a rustix::fs::ABS constant. (#1189)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sunfishcode committed Oct 27, 2024
1 parent c004964 commit ec78479
Show file tree
Hide file tree
Showing 11 changed files with 145 additions and 40 deletions.
13 changes: 4 additions & 9 deletions src/backend/libc/fs/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -595,14 +595,9 @@ pub(crate) fn stat(path: &CStr) -> io::Result<Stat> {
)
))]
{
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),
}
}
Expand Down Expand Up @@ -636,13 +631,13 @@ pub(crate) fn lstat(path: &CStr) -> io::Result<Stat> {
))]
{
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),
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/backend/linux_raw/c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/backend/linux_raw/conv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ impl<'a, Num: ArgNumber> From<BorrowedFd<'a>> 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")]
Expand Down
5 changes: 3 additions & 2 deletions src/fs/at.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
17 changes: 0 additions & 17 deletions src/fs/cwd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,6 @@ 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) };

/// Return the value of [`CWD`].
#[deprecated(note = "Use `CWD` in place of `cwd()`.")]
pub const fn cwd() -> BorrowedFd<'static> {
Expand Down
8 changes: 4 additions & 4 deletions src/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ 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 <https://git.haiku-os.org/haiku/commit/?id=b8caef69155fbe87def579305622b9718d7779dc>
mod cwd;
#[cfg(all(feature = "alloc", not(any(target_os = "espidf", target_os = "redox"))))]
mod dir;
Expand Down Expand Up @@ -57,6 +55,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(
Expand All @@ -76,8 +76,6 @@ 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 <https://git.haiku-os.org/haiku/commit/?id=b8caef69155fbe87def579305622b9718d7779dc>
pub use cwd::*;
#[cfg(all(feature = "alloc", not(any(target_os = "espidf", target_os = "redox"))))]
pub use dir::{Dir, DirEntry};
Expand Down Expand Up @@ -124,6 +122,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(
Expand Down
73 changes: 73 additions & 0 deletions src/fs/special.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
2 changes: 1 addition & 1 deletion src/process/chdir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub fn fchdir<Fd: AsFd>(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.
///
Expand Down
4 changes: 0 additions & 4 deletions tests/fs/cwd.rs

This file was deleted.

2 changes: 1 addition & 1 deletion tests/fs/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)]
Expand Down
57 changes: 57 additions & 0 deletions tests/fs/special.rs
Original file line number Diff line number Diff line change
@@ -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
}
}

0 comments on commit ec78479

Please sign in to comment.