feat(fcntl): fcntl/open flag handling

This commit is contained in:
2026-09-05 16:46:34 -04:00
parent 6eaacd4448
commit c9e9676d38
7 changed files with 857 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <fcntl.h>
#include "../internal/syscall.h"
/*
* creat ≡ open(path, O_WRONLY | O_CREAT | O_TRUNC, mode) (POSIX). Done as
* a direct SYS_openat rather than a call to open(): the flag set is fixed,
* the mode is always supplied, and this keeps src/fcntl self-contained
* with no inter-object dependency on src/unistd. The kernel applies the
* process umask to mode.
*/
int
creat(const char *path, mode_t mode)
{
return syscall_ret(
__syscall4(SYS_openat, AT_FDCWD, (long)path, O_WRONLY | O_CREAT | O_TRUNC, mode));
}
+55
View File
@@ -0,0 +1,55 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdarg.h>
#include <fcntl.h>
#include "../internal/syscall.h"
/*
* fcntl over SYS_fcntl (72). The third argument is read from the varargs
* only for the commands POSIX defines one for (F_DUPFD, F_DUPFD_CLOEXEC,
* F_SETFD, F_SETFL, the lock commands, and the owner/signal commands);
* every other command passes 0, which the kernel ignores. Pointers (the
* struct flock * of F_GETLK/F_SETLK/F_SETLKW/F_OFD_*) ride the varargs
* slot as a long — on x86_64 long and void * share one GPR slot, so the
* read is ABI-exact.
*
* F_DUPFD_CLOEXEC passes straight through: the x86_64 kernel has supported
* it as a single native operation since 2.6.24, so no F_DUPFD + F_SETFD
* fallback is needed. F_SETFL needs no O_LARGEFILE massaging on x86_64
* (that is a 32-bit compat concern only).
*/
int
fcntl(int fildes, int cmd, ...)
{
long arg = 0;
va_list ap;
switch (cmd)
{
case F_DUPFD:
case F_DUPFD_CLOEXEC:
case F_SETFD:
case F_SETFL:
case F_GETLK:
case F_SETLK:
case F_SETLKW:
case F_SETOWN:
case F_SETSIG:
case F_GETOWN_EX:
case F_SETOWN_EX:
case F_OFD_GETLK:
case F_OFD_SETLK:
case F_OFD_SETLKW:
va_start(ap, cmd);
arg = va_arg(ap, long);
va_end(ap);
break;
default:
break;
}
return syscall_ret(__syscall3(SYS_fcntl, fildes, cmd, arg));
}
+70
View File
@@ -0,0 +1,70 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include "../internal/syscall.h"
#if VLIBC_LEVEL_GE(2)
/*
* lockf (XSI) over the fcntl record-lock commands: the kernel has no lockf
* syscall, so every cmd maps to a struct flock operation on the range
* [current offset, current offset + len) — the POSIX definition. len 0
* means through EOF, which the kernel's l_len 0 convention already
* expresses.
*
* The region start uses l_whence = SEEK_CUR with l_start = 0: the kernel
* resolves the current file offset at syscall time, atomically with the
* lock operation, so no lseek round-trip (and no TOCTOU window) is needed.
*
* F_TEST probes with F_GETLK using a read lock (the probe type that
* conflicts with any exclusive lock): if the kernel reports no lock, or
* the reported holder is this process itself (a different descriptor of
* ours may hold it — POSIX says our own process never conflicts with
* itself), the region is lockable. The pid is read via raw SYS_getpid;
* the process wrappers are todo 20's and not a dependency of this file.
*/
int
lockf(int fd, int cmd, off_t len)
{
struct flock lk;
lk.l_type = F_WRLCK;
lk.l_whence = SEEK_CUR;
lk.l_start = 0;
lk.l_len = len;
switch (cmd)
{
case F_TEST:
lk.l_type = F_RDLCK;
if (fcntl(fd, F_GETLK, &lk) == -1)
{
return -1;
}
if (lk.l_type == F_UNLCK || lk.l_pid == (pid_t)__syscall0(SYS_getpid))
{
return 0;
}
errno = EACCES;
return -1;
case F_ULOCK:
lk.l_type = F_UNLCK;
return fcntl(fd, F_SETLK, &lk);
case F_LOCK:
return fcntl(fd, F_SETLKW, &lk);
case F_TLOCK:
return fcntl(fd, F_SETLK, &lk);
default:
errno = EINVAL;
return -1;
}
}
#endif /* VLIBC_LEVEL_GE(2) */
+24
View File
@@ -0,0 +1,24 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <fcntl.h>
#include "../internal/syscall.h"
/*
* posix_fadvise over SYS_fadvise64 (221). POSIX return convention, unlike
* the rest of the family: an error NUMBER directly — 0 on success, else
* the positive errno value (EBADF, ESPIPE, EINVAL, ...) — and errno is
* left untouched. The raw syscall result is therefore mapped without the
* syscall_ret() translation (which would write errno): a negative return
* is negated, 0 stays 0. On x86_64 the syscall takes the offset as one
* 64-bit value (fd, offset, len, advice) — no lo/hi split.
*/
int
posix_fadvise(int fd, off_t offset, off_t len, int advice)
{
long r = __syscall4(SYS_fadvise64, fd, offset, len, advice);
return r < 0 ? (int)-r : 0;
}
+24
View File
@@ -0,0 +1,24 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <fcntl.h>
#include "../internal/syscall.h"
/*
* posix_fallocate over SYS_fallocate (285). The kernel signature is
* (fd, mode, offset, len); mode is 0 — POSIX exposes no FALLOC_FL_* flags,
* so the plain allocate-and-grow operation is all there is (the kernel
* rejects any other mode with EOPNOTSUPP/EINVAL, which is exactly what
* POSIX wants). Same error-number return convention as posix_fadvise:
* 0 on success, the positive errno value on failure, errno untouched —
* so the raw result is negated directly, no syscall_ret().
*/
int
posix_fallocate(int fd, off_t offset, off_t len)
{
long r = __syscall4(SYS_fallocate, fd, 0, offset, len);
return r < 0 ? (int)-r : 0;
}