Merge branch 'fchmodat' into 'master'

Implement fchmodat

See merge request redox-os/relibc!714
This commit is contained in:
Jeremy Soller
2025-09-21 08:16:49 -06:00
10 changed files with 295 additions and 0 deletions
+13
View File
@@ -82,6 +82,19 @@ pub extern "C" fn fchmod(fildes: c_int, mode: mode_t) -> c_int {
Sys::fchmod(fildes, mode).map(|()| 0).or_minus_one_errno()
}
#[no_mangle]
pub unsafe extern "C" fn fchmodat(
dirfd: c_int,
path: *const c_char,
mode: mode_t,
flags: c_int,
) -> c_int {
let path = CStr::from_nullable_ptr(path);
Sys::fchmodat(dirfd, path, mode, flags)
.map(|()| 0)
.or_minus_one_errno()
}
#[no_mangle]
pub unsafe extern "C" fn fstat(fildes: c_int, buf: *mut stat) -> c_int {
let buf = Out::nonnull(buf);
+13
View File
@@ -171,6 +171,19 @@ impl Pal for Sys {
e_raw(unsafe { syscall!(FCHMOD, fildes, mode) }).map(|_| ())
}
fn fchmodat(dirfd: c_int, path: Option<CStr>, mode: mode_t, flags: c_int) -> Result<()> {
e_raw(unsafe {
syscall!(
FCHMODAT,
dirfd,
path.map_or(core::ptr::null(), |p| p.as_ptr()),
mode,
flags
)
})
.map(|_| ())
}
fn fchown(fildes: c_int, owner: uid_t, group: gid_t) -> Result<()> {
e_raw(unsafe { syscall!(FCHOWN, fildes, owner, group) }).map(|_| ())
}
+1
View File
@@ -67,6 +67,7 @@ pub trait Pal {
fn fchdir(fildes: c_int) -> Result<()>;
fn fchmod(fildes: c_int, mode: mode_t) -> Result<()>;
fn fchmodat(dirfd: c_int, path: Option<CStr>, mode: mode_t, flags: c_int) -> Result<()>;
fn fchown(fildes: c_int, owner: uid_t, group: gid_t) -> Result<()>;
+13
View File
@@ -259,6 +259,19 @@ impl Pal for Sys {
Ok(())
}
fn fchmodat(dirfd: c_int, path: Option<CStr>, mode: mode_t, flags: c_int) -> Result<()> {
const MASK: c_int = !(fcntl::AT_SYMLINK_NOFOLLOW | fcntl::AT_EMPTY_PATH);
if MASK & flags != 0 {
return Err(Errno(EOPNOTSUPP));
}
let path = path
.and_then(|cs| str::from_utf8(cs.to_bytes()).ok())
.ok_or(Errno(ENOENT))?;
let file = cap_path_at(dirfd, path, flags, 0)?;
syscall::fchmod(*file as usize, mode as u16)?;
Ok(())
}
fn fchown(fd: c_int, owner: uid_t, group: gid_t) -> Result<()> {
syscall::fchown(fd as usize, owner as u32, group as u32)?;
Ok(())
+1
View File
@@ -109,6 +109,7 @@ EXPECT_NAMES=\
string/stpcpy \
string/stpncpy \
strings \
sys_stat/chmod \
sys_stat/lstat \
sys_stat/fstatat \
sys_syslog/syslog \
+254
View File
@@ -0,0 +1,254 @@
#define _GNU_SOURCE // Linux to run locally
#include <fcntl.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
/* #if defined(__linux__) && !defined(__redox__) */
/* #include <linux/version.h> */
/* #else */
/* #define KERNEL_VERSION(x,y,z) (0) */
/* #endif */
__attribute__((nonnull(2)))
static bool check_mode(
int dir,
const char path[],
int flags,
mode_t expected
) {
struct stat stat = {0};
if (fstatat(dir, path, &stat, flags) == -1) {
perror("fstatat");
return false;
}
if ((stat.st_mode & 0777) != expected) {
fprintf(
stderr,
"Mode mismatch\n\tExpected: %o\n\tActual: %o\n",
expected,
stat.st_mode
);
return false;
}
return true;
}
int main(void) {
int status = EXIT_FAILURE;
char template[] = "/tmp/chmtest.XXXXXX";
if (!mkdtemp(template)) {
perror("mkdtemp");
goto bye;
}
size_t len = sizeof(template) - 1;
// Create a file and a link to chmod.
const char file_name[] = "king_crimson";
char file_path[PATH_MAX] = {0};
memcpy(file_path, template, len);
file_path[len] = '/';
memcpy(&file_path[len + 1], file_name, sizeof(file_name));
int filefd = open(file_path, O_CREAT);
if (filefd == -1) {
perror("open (file, O_CREAT)");
goto rmtempdir;
}
const char link_name[] = "red";
char link_path[PATH_MAX] = {0};
memcpy(link_path, template, len);
link_path[len] = '/';
memcpy(&link_path[len + 1], link_name, sizeof(link_name));
if (symlink(file_path, link_path) == -1) {
perror("symlink");
goto rmfiles;
}
#ifdef __redox__
int linkfd = open(link_path, O_PATH | O_NOFOLLOW | O_SYMLINK);
if (linkfd == -1) {
perror("open (link, O_NOFOLLOW)");
goto rmfiles;
}
#endif
int dir = open(template, O_DIRECTORY);
if (dir == -1) {
perror("open (temp dir)");
goto closelink;
}
// chmod
// File
if (chmod(file_path, 0666) == -1) {
perror("chmod file 0666");
goto closetemp;
}
if (!check_mode(dir, file_name, 0, 0666)) {
fprintf(stderr, "Context: chmod %s\n", file_name);
goto closetemp;
}
// Link (deferenced)
if (chmod(link_path, 0777) == -1) {
perror("chmod link 0777");
goto closetemp;
}
if (!check_mode(dir, file_name, 0, 0777)) {
fprintf(stderr, "Context: chmod %s\n", link_name);
goto closetemp;
}
// Dir
if (chmod(template, 0766) == -1) {
perror("chmod directory 0766");
goto closetemp;
}
if (!check_mode(dir, "", AT_EMPTY_PATH, 0766)) {
fprintf(stderr, "Context: chmod %s\n", template);
goto closetemp;
}
// fchmod
// File
if (fchmod(filefd, 0644) == -1) {
perror("fchmod file 0644");
goto closetemp;
}
if (!check_mode(dir, file_name, 0, 0644)) {
fprintf(stderr, "Context: fchmod %s\n", file_name);
goto closetemp;
}
// Link (not followed)
#ifdef __redox__
if (fchmod(linkfd, 0666) == -1) {
perror("fchmod link 0666");
goto closetemp;
}
if (!check_mode(dir, link_name, AT_SYMLINK_NOFOLLOW, 0666)) {
fprintf(stderr, "Context: fchmod %s\n", link_name);
goto closetemp;
}
#endif
// Directory
if (fchmod(dir, 0777) == -1) {
perror("fchmod directory 0777");
goto closetemp;
}
if (!check_mode(dir, "", AT_EMPTY_PATH, 0777)) {
fprintf(stderr, "Context: fchmod %s\n", template);
goto closetemp;
}
// fchmodat
// File (relative)
if (fchmodat(dir, file_name, 0666, 0) == -1) {
perror("fchmodat directory 0666");
goto closetemp;
}
if (!check_mode(dir, file_name, 0, 0666)) {
fprintf(stderr, "Context: fchmodat %s\n", file_name);
goto closetemp;
}
// File (absolute)
if (fchmodat(dir, file_path, 0777, 0) == -1) {
perror("fchmodat file 0777");
goto closetemp;
}
if (!check_mode(dir, file_name, 0, 0777)) {
fprintf(stderr, "Context: fchmodat %s\n", file_path);
}
// Link (followed)
if (fchmodat(dir, link_name, 0666, 0) == -1) {
perror("fchmodat link 0666");
goto closetemp;
}
if (!check_mode(dir, file_name, 0, 0666)) {
fprintf(stderr, "Context: fchmodat %s\n", link_name);
goto closetemp;
}
// Link (not followed)
#ifdef __redox__
if (fchmodat(dir, link_name, 0777, AT_SYMLINK_NOFOLLOW) == -1) {
perror("fchmodat link 0777");
goto closetemp;
}
if (!check_mode(dir, link_name, AT_SYMLINK_NOFOLLOW, 0777)) {
fprintf(stderr, "Context: fchmodat %s\n", link_name);
goto closetemp;
}
#endif
// Directory
// AT_EMPTY_PATH support is relatively new so this fails in CI.
#if defined(__redox__) // || LINUX_VERSION_CODE >= KERNEL_VERSION(6,6,0)
if (fchmodat(dir, "", 0700, AT_EMPTY_PATH) == -1) {
perror("fchmodat directory 0700");
goto closetemp;
}
if (!check_mode(dir, "", AT_EMPTY_PATH, 0700)) {
fprintf(stderr, "Context: fchmodat %s\n", template);
goto closetemp;
}
// Directory (cwd)
char old_cwd[PATH_MAX] = {0};
if (!getcwd(old_cwd, PATH_MAX)) {
perror("getcwd");
goto closetemp;
}
if (chdir(template) == -1) {
perror("chdir (temp dir)");
goto closetemp;
}
if (fchmodat(AT_FDCWD, "", 0777, AT_EMPTY_PATH) == -1) {
perror("fchmodat cwd 0777");
goto closetemp;
}
if (!check_mode(AT_FDCWD, "", AT_EMPTY_PATH, 0777)) {
fprintf(stderr, "Context: fchmodat %s\n", template);
goto closetemp;
}
if (chdir(old_cwd) == -1) {
perror("chdir (old cwd)");
goto closetemp;
}
#endif
status = EXIT_SUCCESS;
closetemp:
close(dir);
closelink:
#ifdef __redox__
close(linkfd);
#endif
rmfiles:
close(filefd);
unlink(file_path);
unlink(link_path);
rmtempdir:
rmdir(template);
bye:
return status;
}