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.
This commit is contained in:
Connor-GH
2026-04-20 10:58:25 -05:00
parent aa8d1386a9
commit 0ff79b80a2
6 changed files with 116 additions and 18 deletions
+12 -1
View File
@@ -80,6 +80,17 @@ pub unsafe extern "C" fn fcntl(fildes: c_int, cmd: c_int, mut __valist: ...) ->
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/open.html>.
#[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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/openat.html>.
#[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)]
+1
View File
@@ -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;
+36 -1
View File
@@ -575,7 +575,7 @@ pub unsafe extern "C" fn gethostname(mut name: *mut c_char, mut len: size_t) ->
/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getlogin.html>.
#[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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/linkat.html>.
#[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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/lockf.html>.
#[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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/symlinkat.html>.
#[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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/sync.html>.
#[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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/unlinkat.html>.
#[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 <https://pubs.opengroup.org/onlinepubs/009695399/functions/usleep.html>.
///
/// # Deprecation
+16 -13
View File
@@ -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<off_t> {
@@ -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<Out<c_int>>, options: c_int) -> Result<pid_t> {
+9
View File
@@ -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<off_t>;
@@ -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<Out<c_int>>, options: c_int) -> Result<pid_t>;
+42 -3
View File
@@ -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<off_t> {
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<Out<'_, c_int>>, options: c_int) -> Result<pid_t> {
let res = None;
let mut status = 0;