From 0ff79b80a2d65a5d2d93f410b1a091dd176a14c9 Mon Sep 17 00:00:00 2001 From: Connor-GH Date: Mon, 20 Apr 2026 10:58:25 -0500 Subject: [PATCH] add unlinkat(2), symlinkat(2), linkat(2) and expose openat(2) `linkat(2)` doesn't have `AT_EMPTY_PATH` as a valid flag in this implementation because it isn't POSIX. We have it (and have support for it), but it is more effort to add it. If we need it at some point, it can be added in about 3 lines. `openat(2)` previously wasn't exposed, and William was not aware of `Sys::openat`'s existence. We use it under the hood for `mkfifoat(2)` and friends, so expose it as a libc API. This helps to pass more os-test tests. Lastly, the 3 implemented syscalls here help pass some os-test tests. --- src/header/fcntl/mod.rs | 13 ++++++++++- src/header/fcntl/redox.rs | 1 + src/header/unistd/mod.rs | 37 +++++++++++++++++++++++++++++++- src/platform/linux/mod.rs | 29 ++++++++++++++----------- src/platform/pal/mod.rs | 9 ++++++++ src/platform/redox/mod.rs | 45 ++++++++++++++++++++++++++++++++++++--- 6 files changed, 116 insertions(+), 18 deletions(-) diff --git a/src/header/fcntl/mod.rs b/src/header/fcntl/mod.rs index 6a4db2fafc..28455c94cf 100644 --- a/src/header/fcntl/mod.rs +++ b/src/header/fcntl/mod.rs @@ -80,6 +80,17 @@ pub unsafe extern "C" fn fcntl(fildes: c_int, cmd: c_int, mut __valist: ...) -> /// See . #[unsafe(no_mangle)] pub unsafe extern "C" fn open(path: *const c_char, oflag: c_int, mut __valist: ...) -> c_int { + unsafe { openat(AT_FDCWD, path, oflag, __valist) } +} + +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn openat( + fd: c_int, + path: *const c_char, + oflag: c_int, + mut __valist: ... +) -> c_int { let mode = if oflag & O_CREAT == O_CREAT /* || oflag & O_TMPFILE == O_TMPFILE */ { @@ -89,7 +100,7 @@ pub unsafe extern "C" fn open(path: *const c_char, oflag: c_int, mut __valist: . }; let path = unsafe { CStr::from_ptr(path) }; - Sys::open(path, oflag, mode).or_minus_one_errno() + Sys::openat(fd, path, oflag, mode).or_minus_one_errno() } #[unsafe(no_mangle)] diff --git a/src/header/fcntl/redox.rs b/src/header/fcntl/redox.rs index 7ac6b33e22..c374743d51 100644 --- a/src/header/fcntl/redox.rs +++ b/src/header/fcntl/redox.rs @@ -31,5 +31,6 @@ pub const O_NDELAY: c_int = O_NONBLOCK; // Flags for capability based "at" functions pub const AT_FDCWD: c_int = -100; pub const AT_SYMLINK_NOFOLLOW: c_int = 0x0200; +pub const AT_SYMLINK_FOLLOW: c_int = 0x2000; pub const AT_EMPTY_PATH: c_int = 0x4000; pub const AT_REMOVEDIR: c_int = 0x200; diff --git a/src/header/unistd/mod.rs b/src/header/unistd/mod.rs index d3bc890f48..90d544f567 100644 --- a/src/header/unistd/mod.rs +++ b/src/header/unistd/mod.rs @@ -575,7 +575,7 @@ pub unsafe extern "C" fn gethostname(mut name: *mut c_char, mut len: size_t) -> /// See . #[unsafe(no_mangle)] pub unsafe extern "C" fn getlogin() -> *mut c_char { - const LOGIN_LEN: usize = 256; + const LOGIN_LEN: usize = limits::LOGIN_NAME_MAX as usize; static mut LOGIN: [c_char; LOGIN_LEN] = [0; LOGIN_LEN]; if getlogin_r((&raw mut LOGIN).cast(), LOGIN_LEN) == 0 { (&raw mut LOGIN).cast() @@ -705,6 +705,22 @@ pub unsafe extern "C" fn link(path1: *const c_char, path2: *const c_char) -> c_i Sys::link(path1, path2).map(|()| 0).or_minus_one_errno() } +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn linkat( + fd1: c_int, + path1: *const c_char, + fd2: c_int, + path2: *const c_char, + flags: c_int, +) -> c_int { + let path1 = unsafe { CStr::from_ptr(path1) }; + let path2 = unsafe { CStr::from_ptr(path2) }; + Sys::linkat(fd1, path1, fd2, path2, flags) + .map(|()| 0) + .or_minus_one_errno() +} + /// See . #[unsafe(no_mangle)] pub unsafe extern "C" fn lockf(fildes: c_int, function: c_int, size: off_t) -> c_int { @@ -1008,6 +1024,16 @@ pub unsafe extern "C" fn symlink(path1: *const c_char, path2: *const c_char) -> Sys::symlink(path1, path2).map(|()| 0).or_minus_one_errno() } +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn symlinkat(path1: *const c_char, fd: c_int, path2: *const c_char) -> c_int { + let path1 = unsafe { CStr::from_ptr(path1) }; + let path2 = unsafe { CStr::from_ptr(path2) }; + Sys::symlinkat(path1, fd, path2) + .map(|()| 0) + .or_minus_one_errno() +} + /// See . #[unsafe(no_mangle)] pub extern "C" fn sync() { @@ -1121,6 +1147,15 @@ pub unsafe extern "C" fn unlink(path: *const c_char) -> c_int { Sys::unlink(path).map(|()| 0).or_minus_one_errno() } +/// See . +#[unsafe(no_mangle)] +pub unsafe extern "C" fn unlinkat(fd: c_int, path: *const c_char, flags: c_int) -> c_int { + let path = unsafe { CStr::from_ptr(path) }; + Sys::unlinkat(fd, path, flags) + .map(|()| 0) + .or_minus_one_errno() +} + /// See . /// /// # Deprecation diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index b53c95b5c6..59a6adf60a 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -487,17 +487,12 @@ impl Pal for Sys { } fn link(path1: CStr, path2: CStr) -> Result<()> { - e_raw(unsafe { - syscall!( - LINKAT, - AT_FDCWD, - path1.as_ptr(), - AT_FDCWD, - path2.as_ptr(), - 0 - ) - }) - .map(|_| ()) + Sys::linkat(AT_FDCWD, path1, AT_FDCWD, path2, 0) + } + + fn linkat(fd1: c_int, path1: CStr, fd2: c_int, path2: CStr, flags: c_int) -> Result<()> { + e_raw(unsafe { syscall!(LINKAT, fd1, path1.as_ptr(), fd2, path2.as_ptr(), flags) }) + .map(|_| ()) } fn lseek(fildes: c_int, offset: off_t, whence: c_int) -> Result { @@ -817,7 +812,11 @@ impl Pal for Sys { } fn symlink(path1: CStr, path2: CStr) -> Result<()> { - e_raw(unsafe { syscall!(SYMLINKAT, path1.as_ptr(), AT_FDCWD, path2.as_ptr()) }).map(|_| ()) + Sys::symlinkat(path1, AT_FDCWD, path2) + } + + fn symlinkat(path1: CStr, fd: c_int, path2: CStr) -> Result<()> { + e_raw(unsafe { syscall!(SYMLINKAT, path1.as_ptr(), fd, path2.as_ptr()) }).map(|_| ()) } fn sync() -> Result<()> { @@ -874,7 +873,11 @@ impl Pal for Sys { } fn unlink(path: CStr) -> Result<()> { - e_raw(unsafe { syscall!(UNLINKAT, AT_FDCWD, path.as_ptr(), 0) }).map(|_| ()) + Sys::unlinkat(AT_FDCWD, path, 0) + } + + fn unlinkat(fd: c_int, path: CStr, flags: c_int) -> Result<()> { + e_raw(unsafe { syscall!(UNLINKAT, fd, path.as_ptr(), flags) }).map(|_| ()) } fn waitpid(pid: pid_t, stat_loc: Option>, options: c_int) -> Result { diff --git a/src/platform/pal/mod.rs b/src/platform/pal/mod.rs index 3b8c119f85..3ef0dc1cac 100644 --- a/src/platform/pal/mod.rs +++ b/src/platform/pal/mod.rs @@ -225,6 +225,9 @@ pub trait Pal { /// Platform implementation of [`link()`](crate::header::unistd::link) from [`unistd.h`](crate::header::unistd). fn link(path1: CStr, path2: CStr) -> Result<()>; + /// Platform implementation of [`linkat()`](crate::header::unistd::linkat) from [`unistd.h`](crate::header::unistd). + fn linkat(fd1: c_int, oldpath: CStr, fd2: c_int, newpath: CStr, flags: c_int) -> Result<()>; + /// Platform implementation of [`lseek()`](crate::header::unistd::lseek) from [`unistd.h`](crate::header::unistd). fn lseek(fildes: c_int, offset: off_t, whence: c_int) -> Result; @@ -370,6 +373,9 @@ pub trait Pal { /// Platform implementation of [`symlink()`](crate::header::unistd::symlink) from [`unistd.h`](crate::header::unistd). fn symlink(path1: CStr, path2: CStr) -> Result<()>; + /// Platform implementation of [`symlinkat()`](crate::header::unistd::symlinkat) from [`unistd.h`](crate::header::unistd). + fn symlinkat(path1: CStr, fd: c_int, path2: CStr) -> Result<()>; + /// Platform implementation of [`sync()`](crate::header::unistd::sync) from [`unistd.h`](crate::header::unistd). fn sync() -> Result<()>; @@ -400,6 +406,9 @@ pub trait Pal { /// Platform implementation of [`unlink()`](crate::header::unistd::unlink) from [`unistd.h`](crate::header::unistd). fn unlink(path: CStr) -> Result<()>; + /// Platform implementation of [`unlinkat()`](crate::header::unistd::unlinkat) from [`unistd.h`](crate::header::unistd). + fn unlinkat(fd: c_int, path: CStr, flags: c_int) -> Result<()>; + /// Platform implementation of [`waitpid()`](crate::header::sys_wait::waitpid) from [`sys/wait.h`](crate::header::sys_wait). fn waitpid(pid: pid_t, stat_loc: Option>, options: c_int) -> Result; diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs index dc04059870..f04aa84416 100644 --- a/src/platform/redox/mod.rs +++ b/src/platform/redox/mod.rs @@ -32,8 +32,8 @@ use crate::{ ENOSYS, EOPNOTSUPP, EPERM, }, fcntl::{ - self, AT_EMPTY_PATH, AT_FDCWD, AT_SYMLINK_NOFOLLOW, F_GETLK, F_OFD_GETLK, F_OFD_SETLK, - F_RDLCK, F_SETLK, F_SETLKW, F_UNLCK, F_WRLCK, flock, + self, AT_EMPTY_PATH, AT_FDCWD, AT_REMOVEDIR, AT_SYMLINK_FOLLOW, AT_SYMLINK_NOFOLLOW, + F_GETLK, F_OFD_GETLK, F_OFD_SETLK, F_RDLCK, F_SETLK, F_SETLKW, F_UNLCK, F_WRLCK, flock, }, limits, pthread::{pthread_cancel, pthread_create}, @@ -772,6 +772,29 @@ impl Pal for Sys { Ok(()) } + fn linkat(fd1: c_int, oldpath: CStr, fd2: c_int, newpath: CStr, flags: c_int) -> Result<()> { + // make sure the flags passed are valid. + // valid states: AT_SYMLINK_FOLLOW, or 0. + if (flags & !(AT_SYMLINK_FOLLOW)) != 0 { + return Err(Errno(EINVAL)); + } + let newpath = newpath.to_str().map_err(|_| Errno(EINVAL))?; + + // By default, we don't follow the symlink if there is one. + // We only follow it if AT_SYMLINK_FOLLOW is passed in flags. + // We represent this by setting O_NOFOLLOW by default, and clearing it + // if AT_SYMLINK_FOLLOW is present. + let mut oflags = fcntl::O_PATH | fcntl::O_CLOEXEC | fcntl::O_NOFOLLOW; + if (flags & AT_SYMLINK_FOLLOW) == AT_SYMLINK_FOLLOW { + oflags &= !fcntl::O_NOFOLLOW; + } + + let file = File::openat(fd1, oldpath, oflags)?; + let newpath = openat2_path(fd2, newpath, 0)?; + syscall::flink(*file as usize, newpath)?; + Ok(()) + } + fn lseek(fd: c_int, offset: off_t, whence: c_int) -> Result { Ok(syscall::lseek(fd as usize, offset as isize, whence as usize)? as off_t) } @@ -1240,7 +1263,12 @@ impl Pal for Sys { } fn symlink(path1: CStr, path2: CStr) -> Result<()> { - let mut file = File::create( + Sys::symlinkat(path1, AT_FDCWD, path2) + } + + fn symlinkat(path1: CStr, fd: c_int, path2: CStr) -> Result<()> { + let mut file = File::createat( + fd, path2, fcntl::O_WRONLY | fcntl::O_SYMLINK | fcntl::O_CLOEXEC, 0o777, @@ -1511,6 +1539,17 @@ impl Pal for Sys { Ok(()) } + fn unlinkat(fd: c_int, path: CStr, flags: c_int) -> Result<()> { + if (flags & !AT_REMOVEDIR) != 0 { + return Err(Errno(EINVAL)); + } + let path = path.to_str().map_err(|_| Errno(EINVAL))?; + let path = openat2_path(fd, path, 0)?; + let canon = canonicalize(&path)?; + redox_rt::sys::unlink(&canon, flags.try_into().map_err(|_| Errno(EINVAL))?)?; + Ok(()) + } + fn waitpid(pid: pid_t, stat_loc: Option>, options: c_int) -> Result { let res = None; let mut status = 0;