feat(unistd): file I/O syscall wrappers

This commit is contained in:
2026-09-05 01:05:45 -04:00
parent 34408380f0
commit 7b11452b87
14 changed files with 1019 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
#include "unistd_impl.h"
/*
* access/faccessat over SYS_faccessat (the *at form is the modern kernel
* ABI; SYS_faccessat2 is deliberately not used — the classic syscall
* covers the POSIX amode set). access() passes AT_FDCWD and no flags.
*/
int
access(const char *path, int amode)
{
return syscall_ret(__syscall4(SYS_faccessat, VLIBC_UNISTD_AT_FDCWD, (long)path, amode, 0));
}
int
faccessat(int fd, const char *path, int amode, int flag)
{
return syscall_ret(__syscall4(SYS_faccessat, fd, (long)path, amode, flag));
}
+17
View File
@@ -0,0 +1,17 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
/*
* close: unbuffered pass-through. There is nothing to flush — vlibc I/O is
* unbuffered at this layer, and the stdio layer owns its own buffers.
*/
int
close(int fildes)
{
return syscall_ret(__syscall1(SYS_close, fildes));
}
+38
View File
@@ -0,0 +1,38 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
/*
* dup/dup2: descriptor duplication. The copies share the file description
* (position, status flags, locks) with the original. On x86_64 SYS_dup2 is
* a two-argument syscall.
*/
int
dup(int fildes)
{
return syscall_ret(__syscall1(SYS_dup, fildes));
}
int
dup2(int fildes, int fildes2)
{
return syscall_ret(__syscall2(SYS_dup2, fildes, fildes2));
}
#if VLIBC_LEVEL_GE(2)
/*
* dup3: dup2 with descriptor flags (O_CLOEXEC) applied atomically.
* Linux-specific; the kernel rejects fildes == fildes2 with EINVAL.
*/
int
dup3(int fildes, int fildes2, int flags)
{
return syscall_ret(__syscall3(SYS_dup3, fildes, fildes2, flags));
}
#endif /* VLIBC_LEVEL_GE(2) */
+23
View File
@@ -0,0 +1,23 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
/*
* fsync/fdatasync: flush a descriptor's dirty data (and for fsync, the
* metadata) to stable storage.
*/
int
fsync(int fildes)
{
return syscall_ret(__syscall1(SYS_fsync, fildes));
}
int
fdatasync(int fildes)
{
return syscall_ret(__syscall1(SYS_fdatasync, fildes));
}
+44
View File
@@ -0,0 +1,44 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <unistd.h>
#include "../internal/syscall.h"
/*
* lseek returns a full-width off_t: on x86_64 the offset is a 64-bit long
* that can legitimately exceed INT_MAX, so the value must not be routed
* through syscall_ret() (which narrows to int). The error translation is
* inlined here with the same semantics: errno set from -r and (off_t)-1 on
* error, errno untouched on success.
*/
off_t
lseek(int fildes, off_t offset, int whence)
{
long r = __syscall3(SYS_lseek, fildes, offset, whence);
if (r < 0 && r > -4096)
{
errno = (int)-r;
return (off_t)-1;
}
return (off_t)r;
}
#if VLIBC_LEVEL_GE(2)
/*
* lseek64: glibc LFS alias. On x86_64 the LFS and non-LFS off_t are
* identical (both 64-bit) and SYS_lseek is the single ABI, so this is a
* plain delegation. Provided for source compatibility only.
*/
off_t
lseek64(int fildes, off_t offset, int whence)
{
return lseek(fildes, offset, whence);
}
#endif /* VLIBC_LEVEL_GE(2) */
+51
View File
@@ -0,0 +1,51 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdarg.h>
#include <unistd.h>
#include "../internal/syscall.h"
#include "unistd_impl.h"
/*
* open/openat over SYS_openat (the *at syscall family is the modern kernel
* ABI; SYS_open exists only for compatibility and is not used). The
* optional fourth mode argument is read from the varargs only when oflag
* contains O_CREAT or O_TMPFILE — otherwise the caller supplied no mode
* and 0 is passed, which the kernel ignores. mode_t promotes to unsigned
* int in the varargs list.
*/
int
open(const char *path, int oflag, ...)
{
unsigned mode = 0;
if ((oflag & VLIBC_UNISTD_O_CREAT) != 0 || (oflag & VLIBC_UNISTD_O_TMPFILE) != 0)
{
va_list ap;
va_start(ap, oflag);
mode = va_arg(ap, unsigned int);
va_end(ap);
}
return syscall_ret(
__syscall4(SYS_openat, VLIBC_UNISTD_AT_FDCWD, (long)path, oflag, (long)mode));
}
int
openat(int fd, const char *path, int oflag, ...)
{
unsigned mode = 0;
if ((oflag & VLIBC_UNISTD_O_CREAT) != 0 || (oflag & VLIBC_UNISTD_O_TMPFILE) != 0)
{
va_list ap;
va_start(ap, oflag);
mode = va_arg(ap, unsigned int);
va_end(ap);
}
return syscall_ret(__syscall4(SYS_openat, fd, (long)path, oflag, (long)mode));
}
+31
View File
@@ -0,0 +1,31 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
/*
* pipe: plain POSIX pipe via SYS_pipe. Deliberately does NOT set
* O_CLOEXEC — pipe2 is the flag-taking variant (level 2).
*/
int
pipe(int fildes[2])
{
return syscall_ret(__syscall1(SYS_pipe, (long)fildes));
}
#if VLIBC_LEVEL_GE(2)
/*
* pipe2: pipe with descriptor flags (O_CLOEXEC) applied atomically.
* Linux-specific.
*/
int
pipe2(int fildes[2], int flags)
{
return syscall_ret(__syscall2(SYS_pipe2, (long)fildes, flags));
}
#endif /* VLIBC_LEVEL_GE(2) */
+25
View File
@@ -0,0 +1,25 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
/*
* pread/pwrite: positional I/O over SYS_pread64/SYS_pwrite64. On x86_64
* the offset is a single 64-bit register argument (off_t == long), so
* there is no lo/hi split. Positional I/O never touches the file position
* — the kernel handles that.
*/
ssize_t
pread(int fildes, void *buf, size_t nbyte, off_t offset)
{
return syscall_ret(__syscall4(SYS_pread64, fildes, (long)buf, (long)nbyte, offset));
}
ssize_t
pwrite(int fildes, const void *buf, size_t nbyte, off_t offset)
{
return syscall_ret(__syscall4(SYS_pwrite64, fildes, (long)buf, (long)nbyte, offset));
}
+25
View File
@@ -0,0 +1,25 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
/*
* read/write: unbuffered pass-through to the kernel. The raw result goes
* through syscall_ret(), which returns the byte count on success and -1
* with errno set on error; errno is untouched on success. No buffering, no
* argument inspection.
*/
ssize_t
read(int fildes, void *buf, size_t nbyte)
{
return syscall_ret(__syscall3(SYS_read, fildes, (long)buf, (long)nbyte));
}
ssize_t
write(int fildes, const void *buf, size_t nbyte)
{
return syscall_ret(__syscall3(SYS_write, fildes, (long)buf, (long)nbyte));
}
+18
View File
@@ -0,0 +1,18 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
/*
* sync: flush all filesystem caches to stable storage. The kernel's SYS_sync
* always returns 0 and the POSIX interface is void, so the raw result is
* deliberately not inspected (there is no failure to report).
*/
void
sync(void)
{
__syscall0(SYS_sync);
}
+30
View File
@@ -0,0 +1,30 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
/*
* ftruncate: truncate an open descriptor; POSIX base form.
*/
int
ftruncate(int fildes, off_t length)
{
return syscall_ret(__syscall2(SYS_ftruncate, fildes, length));
}
#if VLIBC_LEVEL_GE(2)
/*
* truncate: truncate by path name; XSI, so it is gated at level 2
* (ftruncate is the POSIX base form).
*/
int
truncate(const char *path, off_t length)
{
return syscall_ret(__syscall2(SYS_truncate, (long)path, length));
}
#endif /* VLIBC_LEVEL_GE(2) */
+33
View File
@@ -0,0 +1,33 @@
#ifndef VLIBC_UNISTD_INTERNAL_H
#define VLIBC_UNISTD_INTERNAL_H
/*
* vlibc — internal constants for the unistd wrappers (temporary).
*
* include/fcntl.h is owned by todo 21; until it lands, the open/openat and
* access wrappers need the O_* flag bits and AT_FDCWD locally. These names
* carry the VLIBC_UNISTD_ 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:
*
* O_RDONLY 0x0, O_WRONLY 0x1, O_RDWR 0x2, O_CREAT 0x40, O_EXCL 0x80,
* O_TRUNC 0x200, O_APPEND 0x400, O_CLOEXEC 0x80000,
* O_TMPFILE 0x410000 (__O_TMPFILE 0x400000 | O_DIRECTORY 0x10000),
* AT_FDCWD -100.
*/
/* fcntl open-flag bits (kernel UAPI; see the header comment above). */
#define VLIBC_UNISTD_O_RDONLY 0x0
#define VLIBC_UNISTD_O_WRONLY 0x1
#define VLIBC_UNISTD_O_RDWR 0x2
#define VLIBC_UNISTD_O_CREAT 0x40
#define VLIBC_UNISTD_O_EXCL 0x80
#define VLIBC_UNISTD_O_TRUNC 0x200
#define VLIBC_UNISTD_O_APPEND 0x400
#define VLIBC_UNISTD_O_CLOEXEC 0x80000
#define VLIBC_UNISTD_O_TMPFILE 0x410000
/* *at syscall base directory (kernel UAPI). */
#define VLIBC_UNISTD_AT_FDCWD (-100)
#endif /* VLIBC_UNISTD_INTERNAL_H */