feat(stat): stat/mkdir/chmod and mode macros

This commit is contained in:
2026-09-05 16:35:50 -04:00
parent 87ab610a9b
commit 98f4c3169c
11 changed files with 935 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/stat.h>
#include "../internal/syscall.h"
#include "stat_impl.h"
/*
* chmod/chmodat over SYS_fchmodat (the *at form is the modern kernel ABI)
* and fchmod over SYS_fchmod. chmod() passes AT_FDCWD and no flags; the
* kernel silently ignores the file-type bits of mode and applies only the
* permission + special bits.
*/
int
fchmodat(int fd, const char *path, mode_t mode, int flag)
{
return syscall_ret(__syscall4(SYS_fchmodat, fd, (long)path, (long)mode, flag));
}
int
chmod(const char *path, mode_t mode)
{
return fchmodat(VLIBC_STAT_AT_FDCWD, path, mode, 0);
}
int
fchmod(int fd, mode_t mode)
{
return syscall_ret(__syscall2(SYS_fchmod, fd, (long)mode));
}
+40
View File
@@ -0,0 +1,40 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/stat.h>
#include "../internal/syscall.h"
#include "stat_impl.h"
/*
* chown family over SYS_fchownat (the *at form is the modern kernel ABI)
* and fchown over SYS_fchown. A uid/gid of (uid_t)-1 (or (gid_t)-1) means
* "leave unchanged" — the kernel checks for the all-ones value, so the
* wrapper passes the arguments through as-is. chown() passes AT_FDCWD and
* no flags; lchown() adds AT_SYMLINK_NOFOLLOW (its whole point: the link
* itself is chowned, not the target).
*/
int
fchownat(int fd, const char *path, uid_t owner, gid_t group, int flag)
{
return syscall_ret(__syscall5(SYS_fchownat, fd, (long)path, (long)owner, (long)group, flag));
}
int
chown(const char *path, uid_t owner, gid_t group)
{
return fchownat(VLIBC_STAT_AT_FDCWD, path, owner, group, 0);
}
int
lchown(const char *path, uid_t owner, gid_t group)
{
return fchownat(VLIBC_STAT_AT_FDCWD, path, owner, group, VLIBC_STAT_AT_SYMLINK_NOFOLLOW);
}
int
fchown(int fd, uid_t owner, gid_t group)
{
return syscall_ret(__syscall3(SYS_fchown, fd, (long)owner, (long)group));
}
+39
View File
@@ -0,0 +1,39 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/stat.h>
#include "../internal/syscall.h"
#include "stat_impl.h"
/*
* The stat family over SYS_newfstatat (the modern *at form; SYS_stat/
* SYS_lstat exist only for compatibility) and SYS_fstat for the
* descriptor-based form. stat()/lstat() are newfstatat on AT_FDCWD with
* flag 0 / AT_SYMLINK_NOFOLLOW; the kernel fills the full 144-byte
* struct stat pinned by the static assertions in <sys/stat.h>.
*/
int
fstatat(int fd, const char *path, struct stat *buf, int flag)
{
return syscall_ret(__syscall4(SYS_newfstatat, fd, (long)path, (long)buf, flag));
}
int
stat(const char *path, struct stat *buf)
{
return fstatat(VLIBC_STAT_AT_FDCWD, path, buf, 0);
}
int
lstat(const char *path, struct stat *buf)
{
return fstatat(VLIBC_STAT_AT_FDCWD, path, buf, VLIBC_STAT_AT_SYMLINK_NOFOLLOW);
}
int
fstat(int fd, struct stat *buf)
{
return syscall_ret(__syscall2(SYS_fstat, fd, (long)buf));
}
+24
View File
@@ -0,0 +1,24 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/stat.h>
#include "../internal/syscall.h"
/*
* mkdir over SYS_mkdir and mkdirat over SYS_mkdirat (the *at form takes
* the directory fd, so mkdir() cannot simply delegate to it — SYS_mkdir
* is the direct ABI). The kernel applies the process umask to mode.
*/
int
mkdirat(int fd, const char *path, mode_t mode)
{
return syscall_ret(__syscall3(SYS_mkdirat, fd, (long)path, (long)mode));
}
int
mkdir(const char *path, mode_t mode)
{
return syscall_ret(__syscall2(SYS_mkdir, (long)path, (long)mode));
}
+25
View File
@@ -0,0 +1,25 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/stat.h>
#include "../internal/syscall.h"
#include "stat_impl.h"
/*
* mkfifo/mkfifoat over SYS_mknodat with the type bits fixed to S_IFIFO:
* creating a FIFO is mknodat(..., mode | S_IFIFO, dev = 0). POSIX defines
* mkfifo with no device argument, so the dev slot is always 0 here.
*/
int
mkfifoat(int fd, const char *path, mode_t mode)
{
return syscall_ret(__syscall4(SYS_mknodat, fd, (long)path, (long)(mode | S_IFIFO), 0));
}
int
mkfifo(const char *path, mode_t mode)
{
return mkfifoat(VLIBC_STAT_AT_FDCWD, path, mode);
}
+31
View File
@@ -0,0 +1,31 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/stat.h>
#include "../internal/syscall.h"
#include "stat_impl.h"
#if VLIBC_LEVEL_GE(2)
/*
* mknod/mknodat over SYS_mknodat (XSI, level 2). The mode argument must
* carry a file-type bit (S_IFIFO, S_IFCHR, S_IFBLK, S_IFREG, ...); dev is
* the raw device id for the S_IFCHR/S_IFBLK cases and is otherwise
* ignored by the kernel. Creating device nodes requires privilege
* (CAP_MKNOD); creating a FIFO or a regular file does not.
*/
int
mknodat(int fd, const char *path, mode_t mode, dev_t dev)
{
return syscall_ret(__syscall4(SYS_mknodat, fd, (long)path, (long)mode, (long)dev));
}
int
mknod(const char *path, mode_t mode, dev_t dev)
{
return mknodat(VLIBC_STAT_AT_FDCWD, path, mode, dev);
}
#endif /* VLIBC_LEVEL_GE(2) */
+22
View File
@@ -0,0 +1,22 @@
#ifndef VLIBC_STAT_INTERNAL_H
#define VLIBC_STAT_INTERNAL_H
/*
* vlibc — internal constants for the stat wrappers (temporary).
*
* include/fcntl.h is owned by todo 21; until it lands, the *at wrappers
* need AT_FDCWD and AT_SYMLINK_NOFOLLOW locally. These names carry the
* VLIBC_STAT_ prefix so they cannot collide with the real constants todo
* 21 will publish. The values are kernel UAPI facts
* (asm-generic/fcntl.h), transcribed, not invented:
*
* AT_FDCWD -100, AT_SYMLINK_NOFOLLOW 0x100.
*/
/* *at syscall base directory (kernel UAPI). */
#define VLIBC_STAT_AT_FDCWD (-100)
/* *at flag: operate on the link itself, not its target (kernel UAPI). */
#define VLIBC_STAT_AT_SYMLINK_NOFOLLOW 0x100
#endif /* VLIBC_STAT_INTERNAL_H */
+18
View File
@@ -0,0 +1,18 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/stat.h>
#include "../internal/syscall.h"
/*
* umask over SYS_umask. The kernel always returns the previous mask (even
* through the syscall_ret error translation, which never fires here —
* umask cannot fail), so no special handling is needed.
*/
mode_t
umask(mode_t cmask)
{
return (mode_t)syscall_ret(__syscall1(SYS_umask, (long)cmask));
}
+26
View File
@@ -0,0 +1,26 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/stat.h>
#include "../internal/syscall.h"
/*
* utimensat/futimens over SYS_utimensat. A NULL times array is passed
* through unchanged: the kernel sets both timestamps to the current time
* (the POSIX contract). futimens() is utimensat on the descriptor itself
* — Linux applies utimensat(fd, NULL, times, 0) to the file referenced by
* fd when the path is NULL, which is exactly futimens(fd, times).
*/
int
utimensat(int fd, const char *path, const struct timespec times[2], int flag)
{
return syscall_ret(__syscall4(SYS_utimensat, fd, (long)path, (long)times, flag));
}
int
futimens(int fd, const struct timespec times[2])
{
return syscall_ret(__syscall4(SYS_utimensat, fd, 0, (long)times, 0));
}