feat(fcntl): fcntl/open flag handling
This commit is contained in:
+206
@@ -0,0 +1,206 @@
|
|||||||
|
#ifndef VLIBC_FCNTL_H
|
||||||
|
#define VLIBC_FCNTL_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — <fcntl.h>.
|
||||||
|
*
|
||||||
|
* File control, record locks, and the open-flag constants (POSIX.1-2008 plus
|
||||||
|
* the Linux x86_64 additions). Every O_*, F_*, AT_* and POSIX_FADV_* value
|
||||||
|
* below is a Linux kernel UAPI fact for x86_64 (asm-generic/fcntl.h), transcribed in
|
||||||
|
* hex with the kernel's octal form in a comment — not an invented number.
|
||||||
|
* O_TMPFILE and O_SYNC are defined as flag combinations exactly as the
|
||||||
|
* kernel defines them.
|
||||||
|
*
|
||||||
|
* Note on the *at constants: on x86_64 AT_EACCESS == AT_REMOVEDIR == 0x200;
|
||||||
|
* the kernel disambiguates by syscall (faccessat vs unlinkat), so both names
|
||||||
|
* carry the same value here.
|
||||||
|
*
|
||||||
|
* open/openat are deliberately NOT declared here: POSIX places them in this
|
||||||
|
* header, but this project's single declaration site is <unistd.h> (todo 19;
|
||||||
|
* see its header comment). creat() lives here — its canonical POSIX home.
|
||||||
|
*
|
||||||
|
* Level 2 (muslmimic/XSI): lockf plus F_LOCK/F_TLOCK/F_ULOCK/F_TEST. POSIX
|
||||||
|
* puts lockf in <unistd.h>; that file is todo 19's and out of scope here,
|
||||||
|
* and musl declares lockf in <fcntl.h> as well — the constants accompany the
|
||||||
|
* declaration so the header stays self-contained. A later integration pass
|
||||||
|
* may re-export the same declaration from <unistd.h> (identical, harmless).
|
||||||
|
*
|
||||||
|
* None of these declarations carry an intent attribute: every function
|
||||||
|
* performs I/O with side effects and reports failures through errno (or,
|
||||||
|
* for posix_fadvise/posix_fallocate, an error-number return), so const/pure
|
||||||
|
* would be unsound — the same rationale unistd.h documents for its I/O
|
||||||
|
* family.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vlibc/features.h>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* ---- open(2) flag bits ---- */
|
||||||
|
|
||||||
|
/* Mask for the O_RDONLY/O_WRONLY/O_RDWR access mode. */
|
||||||
|
#define O_ACCMODE 0x3
|
||||||
|
|
||||||
|
#define O_RDONLY 0x0 /* 00000000 */
|
||||||
|
#define O_WRONLY 0x1 /* 00000001 */
|
||||||
|
#define O_RDWR 0x2 /* 00000002 */
|
||||||
|
|
||||||
|
#define O_CREAT 0x40 /* 00000100 */
|
||||||
|
#define O_EXCL 0x80 /* 00000200 */
|
||||||
|
#define O_NOCTTY 0x100 /* 00000400 */
|
||||||
|
#define O_TRUNC 0x200 /* 00001000 */
|
||||||
|
#define O_APPEND 0x400 /* 00002000 */
|
||||||
|
#define O_NONBLOCK 0x800 /* 00004000 */
|
||||||
|
#define O_DSYNC 0x1000 /* 00010000 */
|
||||||
|
#define O_ASYNC 0x2000 /* 00020000 */
|
||||||
|
#define O_DIRECT 0x4000 /* 00040000 */
|
||||||
|
#define O_LARGEFILE 0x8000 /* 00100000 */
|
||||||
|
|
||||||
|
#define O_DIRECTORY 0x10000 /* 00200000 */
|
||||||
|
#define O_NOFOLLOW 0x20000 /* 00400000 */
|
||||||
|
#define O_NOATIME 0x40000 /* 01000000 */
|
||||||
|
#define O_CLOEXEC 0x80000 /* 02000000 */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* __O_SYNC/__O_TMPFILE are Linux-kernel names in the implementation-reserved
|
||||||
|
* namespace (this libc IS "the implementation", and the kernel UAPI mandates
|
||||||
|
* the exact spellings) — the NOLINT below is the house waiver for that.
|
||||||
|
*/
|
||||||
|
#define __O_SYNC 0x100000 /* NOLINT(bugprone-reserved-identifier) 04000000 */
|
||||||
|
#define O_SYNC (__O_SYNC | O_DSYNC)
|
||||||
|
#define O_PATH 0x200000 /* 010000000 */
|
||||||
|
#define __O_TMPFILE 0x400000 /* NOLINT(bugprone-reserved-identifier) 020000000 */
|
||||||
|
#define O_TMPFILE (__O_TMPFILE | O_DIRECTORY)
|
||||||
|
|
||||||
|
/* ---- fcntl(2) commands ---- */
|
||||||
|
|
||||||
|
#define F_DUPFD 0
|
||||||
|
#define F_GETFD 1
|
||||||
|
#define F_SETFD 2
|
||||||
|
#define F_GETFL 3
|
||||||
|
#define F_SETFL 4
|
||||||
|
#define F_GETLK 5
|
||||||
|
#define F_SETLK 6
|
||||||
|
#define F_SETLKW 7
|
||||||
|
#define F_SETOWN 8
|
||||||
|
#define F_GETOWN 9
|
||||||
|
#define F_SETSIG 10
|
||||||
|
#define F_GETSIG 11
|
||||||
|
|
||||||
|
/* Linux-specific owner-identity commands (constants only; struct f_owner_ex
|
||||||
|
* is out of POSIX scope and not provided). */
|
||||||
|
#define F_SETOWN_EX 15
|
||||||
|
#define F_GETOWN_EX 16
|
||||||
|
#define F_GETOWNER_UIDS 17
|
||||||
|
|
||||||
|
/* Open-file-description (OFD) locks, Linux 3.15+. */
|
||||||
|
#define F_OFD_GETLK 36
|
||||||
|
#define F_OFD_SETLK 37
|
||||||
|
#define F_OFD_SETLKW 38
|
||||||
|
|
||||||
|
#define F_DUPFD_CLOEXEC 1030 /* F_LINUX_SPECIFIC_BASE (1024) + 6 */
|
||||||
|
|
||||||
|
/* Close the descriptor on exec — the F_SETFD/F_GETFD flag. */
|
||||||
|
#define FD_CLOEXEC 1
|
||||||
|
|
||||||
|
/* Record-lock types (struct flock l_type). */
|
||||||
|
#define F_RDLCK 0
|
||||||
|
#define F_WRLCK 1
|
||||||
|
#define F_UNLCK 2
|
||||||
|
|
||||||
|
/* ---- *at(2) base-directory and behavior flags ---- */
|
||||||
|
|
||||||
|
#define AT_FDCWD (-100)
|
||||||
|
#define AT_SYMLINK_NOFOLLOW 0x100
|
||||||
|
#define AT_REMOVEDIR 0x200
|
||||||
|
#define AT_SYMLINK_FOLLOW 0x400
|
||||||
|
#define AT_EACCESS 0x200
|
||||||
|
|
||||||
|
/* ---- posix_fadvise(2) advice values ---- */
|
||||||
|
|
||||||
|
#define POSIX_FADV_NORMAL 0
|
||||||
|
#define POSIX_FADV_RANDOM 1
|
||||||
|
#define POSIX_FADV_SEQUENTIAL 2
|
||||||
|
#define POSIX_FADV_WILLNEED 3
|
||||||
|
#define POSIX_FADV_DONTNEED 4
|
||||||
|
#define POSIX_FADV_NOREUSE 5
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Record-lock descriptor, ABI-identical to the x86_64 kernel layout
|
||||||
|
* (asm-generic/fcntl.h: short, short, long, long, int — 32 bytes with
|
||||||
|
* natural padding). On x86_64 the LFS and non-LFS layouts are one struct,
|
||||||
|
* so there is no separate flock64 here.
|
||||||
|
*/
|
||||||
|
struct flock
|
||||||
|
{
|
||||||
|
short l_type; /* F_RDLCK, F_WRLCK, or F_UNLCK */
|
||||||
|
short l_whence; /* SEEK_SET, SEEK_CUR, or SEEK_END (from <unistd.h>) */
|
||||||
|
off_t l_start; /* relative offset of the locked region */
|
||||||
|
off_t l_len; /* region length; 0 means through EOF */
|
||||||
|
pid_t l_pid; /* PID of the process holding the lock (F_GETLK) */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Perform one of the F_* control operations on fildes. Only the commands
|
||||||
|
* that take a third argument read it from the varargs; all others pass 0,
|
||||||
|
* which the kernel ignores. Return the command-specific result, or -1 with
|
||||||
|
* errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fcntl(int fildes, int cmd, ...);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Equivalent to open(path, O_WRONLY | O_CREAT | O_TRUNC, mode): create
|
||||||
|
* path for writing, truncating any existing file, with mode masked by the
|
||||||
|
* process umask. Return a file descriptor, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
creat(const char *path, mode_t mode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Announce an expected access pattern for the range [offset, offset+len) of
|
||||||
|
* fd (len 0 means through EOF). Unlike the rest of the family this returns
|
||||||
|
* an error NUMBER directly — 0 on success, else the positive errno value
|
||||||
|
* (e.g. EBADF, ESPIPE) — and errno is untouched (POSIX).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
posix_fadvise(int fd, off_t offset, off_t len, int advice);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ensure storage is allocated for the range [offset, offset+len) of fd,
|
||||||
|
* growing the file as needed. Returns an error number directly (0 on
|
||||||
|
* success) and leaves errno untouched, like posix_fadvise (POSIX).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
posix_fallocate(int fd, off_t offset, off_t len);
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/* lockf() commands (POSIX XSI; the kernel has no lockf syscall — these are
|
||||||
|
* userspace cmd values). */
|
||||||
|
#define F_ULOCK 0 /* unlock a previously locked region */
|
||||||
|
#define F_LOCK 1 /* lock a region, blocking until available */
|
||||||
|
#define F_TLOCK 2 /* try to lock; -1 with EACCES/EAGAIN if held */
|
||||||
|
#define F_TEST 3 /* test a region for another process's lock */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Apply or remove an exclusive POSIX record lock on [current offset,
|
||||||
|
* current offset + len) of fd (len 0 means through EOF). XSI. Return 0, or
|
||||||
|
* -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
lockf(int fd, int cmd, off_t len);
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VLIBC_FCNTL_H */
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
@@ -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) */
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,457 @@
|
|||||||
|
/*
|
||||||
|
* vlibc — fcntl test (todo 21).
|
||||||
|
*
|
||||||
|
* Exercises the fcntl.h surface end to end:
|
||||||
|
*
|
||||||
|
* 1. fcntl F_GETFL/F_SETFL on a temp file: the access mode matches
|
||||||
|
* O_RDWR and F_SETFL can add O_NONBLOCK.
|
||||||
|
* 2. F_DUPFD with arg 10 returns a descriptor >= 10 that shares the file
|
||||||
|
* description (a write through the dup is visible as a shared offset
|
||||||
|
* through the original); F_DUPFD_CLOEXEC returns >= 20 with
|
||||||
|
* FD_CLOEXEC already set.
|
||||||
|
* 3. F_GETFD starts at 0; F_SETFD FD_CLOEXEC then F_GETFD == FD_CLOEXEC.
|
||||||
|
* 4. posix_fallocate preallocates (size >= 4096 via lseek SEEK_END) and
|
||||||
|
* returns an error NUMBER directly (EINVAL for len 0 — no errno
|
||||||
|
* involved, so this is safe in the default mode).
|
||||||
|
* 5. posix_fadvise returns 0 on the happy paths.
|
||||||
|
* 6. open with O_CLOEXEC leaves FD_CLOEXEC set.
|
||||||
|
* 7. creat creates, truncates a pre-existing file, and honors the mode
|
||||||
|
* (write + reopen + read round trip; mode itself needs stat, todo 22).
|
||||||
|
*
|
||||||
|
* Level-2 gated section: lockf — F_TLOCK/F_TEST/F_LOCK/F_ULOCK over the
|
||||||
|
* fcntl record locks, including the second-descriptor conflict and the
|
||||||
|
* same-process F_TEST short-circuit.
|
||||||
|
*
|
||||||
|
* The negative paths that make the LIBRARY write errno (fcntl with a bad
|
||||||
|
* fd, lockf F_TLOCK conflicts, lockf bad cmd) are bracketed with a
|
||||||
|
* save/restore of host-TCB slot 1 (task 13 technique) in the default mode;
|
||||||
|
* the test itself NEVER reads errno. The -f mode runs the failure
|
||||||
|
* scenarios and exits via raw SYS_exit_group (house pattern).
|
||||||
|
*
|
||||||
|
* All diagnostics go through raw SYS_write (no stdio): under -Iinclude the
|
||||||
|
* vlibc public headers shadow GCC's internal ones, so a host header would
|
||||||
|
* not compile. Not part of the library proper; compiled manually for this
|
||||||
|
* todo (the tests/ + make check wiring is owned by a later todo).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include "../include/fcntl.h"
|
||||||
|
|
||||||
|
#include "../include/unistd.h"
|
||||||
|
|
||||||
|
#include "../src/internal/syscall.h"
|
||||||
|
|
||||||
|
static int failures;
|
||||||
|
|
||||||
|
/* Write a NUL-terminated string to fd via the raw syscall layer. The
|
||||||
|
* optimize attribute keeps GCC from lowering the length loop into a
|
||||||
|
* strlen call, which would leave a vlibc-owned symbol undefined in this
|
||||||
|
* host-linked standalone binary (house idiom, see src/string). */
|
||||||
|
static __attribute__((optimize("no-tree-loop-distribute-patterns"))) void
|
||||||
|
say(int fd, const char *s)
|
||||||
|
{
|
||||||
|
long n = 0;
|
||||||
|
|
||||||
|
while (s[n] != '\0')
|
||||||
|
{
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
__syscall3(SYS_write, fd, (long)s, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write v in decimal to fd. */
|
||||||
|
static void
|
||||||
|
say_dec(int fd, unsigned long v) // NOLINT(bugprone-easily-swappable-parameters)
|
||||||
|
{
|
||||||
|
char buf[24];
|
||||||
|
int i = (int)sizeof(buf);
|
||||||
|
|
||||||
|
buf[--i] = '\0';
|
||||||
|
do
|
||||||
|
{
|
||||||
|
buf[--i] = (char)('0' + (v % 10));
|
||||||
|
v /= 10;
|
||||||
|
} while (v != 0);
|
||||||
|
__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
check(int cond, const char *what)
|
||||||
|
{
|
||||||
|
if (cond)
|
||||||
|
{
|
||||||
|
say(1, "PASS: ");
|
||||||
|
say(1, what);
|
||||||
|
say(1, "\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(2, "FAIL: ");
|
||||||
|
say(2, what);
|
||||||
|
say(2, "\n");
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Host-TCB slot-1 bracket: the library's errno write on a negative path
|
||||||
|
* lands at %fs:0+8, glibc's dtv pointer. Save and restore it around each
|
||||||
|
* such call; only vlibc/raw-syscall code runs in between (task 13
|
||||||
|
* technique). Only the level-2 lockf scenarios have such negative paths in
|
||||||
|
* the default mode, so the helpers are gated with them.
|
||||||
|
*/
|
||||||
|
static unsigned long
|
||||||
|
tcb_slot1(void)
|
||||||
|
{
|
||||||
|
return *(unsigned long *)((char *)__builtin_thread_pointer() + 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
tcb_slot1_set(unsigned long value)
|
||||||
|
{
|
||||||
|
*(unsigned long *)((char *)__builtin_thread_pointer() + 8) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
/* 1+2+3: F_GETFL/F_SETFL, F_DUPFD(_CLOEXEC), F_GETFD/F_SETFD. */
|
||||||
|
static void
|
||||||
|
cmd_scenarios(int fd)
|
||||||
|
{
|
||||||
|
char b[1];
|
||||||
|
int dupfd;
|
||||||
|
int cloexec;
|
||||||
|
int fl;
|
||||||
|
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fl = fcntl(fd, F_GETFL);
|
||||||
|
check(fl >= 0, "F_GETFL on a valid descriptor returns flags");
|
||||||
|
check((fl & O_ACCMODE) == O_RDWR, "F_GETFL access mode matches the O_RDWR open");
|
||||||
|
check(fcntl(fd, F_SETFL, fl | O_NONBLOCK) == 0, "F_SETFL adding O_NONBLOCK returns 0");
|
||||||
|
check((fcntl(fd, F_GETFL) & O_NONBLOCK) != 0, "F_GETFL now reports O_NONBLOCK");
|
||||||
|
|
||||||
|
check(fcntl(fd, F_GETFD) == 0, "F_GETFD starts at 0 on a plain open");
|
||||||
|
check(fcntl(fd, F_SETFD, FD_CLOEXEC) == 0, "F_SETFD FD_CLOEXEC returns 0");
|
||||||
|
check(fcntl(fd, F_GETFD) == FD_CLOEXEC, "F_GETFD reports FD_CLOEXEC after F_SETFD");
|
||||||
|
|
||||||
|
dupfd = fcntl(fd, F_DUPFD, 10);
|
||||||
|
check(dupfd >= 10, "F_DUPFD with arg 10 returns a descriptor >= 10");
|
||||||
|
if (dupfd < 10)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(lseek(fd, 0, SEEK_SET) == 0, "lseek to 0 before the shared-offset probe");
|
||||||
|
check(__syscall3(SYS_write, dupfd, (long)"Q", 1) == 1, "write through the dup writes 1 byte");
|
||||||
|
check(lseek(fd, 0, SEEK_CUR) == 1, "the dup shares the file offset with the original");
|
||||||
|
check(lseek(fd, 0, SEEK_SET) == 0, "lseek back to 0 for the shared-offset read-back");
|
||||||
|
check(__syscall3(SYS_read, fd, (long)b, 1) == 1 && b[0] == 'Q',
|
||||||
|
"the byte written through the dup reads back through the original");
|
||||||
|
check(close(dupfd) == 0, "close of the F_DUPFD descriptor returns 0");
|
||||||
|
|
||||||
|
cloexec = fcntl(fd, F_DUPFD_CLOEXEC, 20);
|
||||||
|
check(cloexec >= 20, "F_DUPFD_CLOEXEC with arg 20 returns a descriptor >= 20");
|
||||||
|
if (cloexec >= 20)
|
||||||
|
{
|
||||||
|
check(fcntl(cloexec, F_GETFD) == FD_CLOEXEC, "F_DUPFD_CLOEXEC sets FD_CLOEXEC");
|
||||||
|
check(close(cloexec) == 0, "close of the F_DUPFD_CLOEXEC descriptor returns 0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4+5. posix_fallocate/posix_fadvise error-number and sizing behavior. */
|
||||||
|
static void
|
||||||
|
fallocate_scenarios(int fd)
|
||||||
|
{
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* len 0 → kernel EINVAL (22), returned DIRECTLY by the POSIX
|
||||||
|
* convention — no errno write, so no host-TCB bracket is needed. */
|
||||||
|
check(posix_fallocate(fd, 0, 0) == 22, "posix_fallocate with len 0 returns EINVAL directly");
|
||||||
|
check(posix_fallocate(fd, 0, 4096) == 0, "posix_fallocate(fd, 0, 4096) returns 0");
|
||||||
|
check(lseek(fd, 0, SEEK_END) >= 4096, "lseek SEEK_END >= 4096 after posix_fallocate");
|
||||||
|
check(lseek(fd, 0, SEEK_SET) == 0, "lseek back to 0 after the size check");
|
||||||
|
check(posix_fadvise(fd, 0, 0, POSIX_FADV_NORMAL) == 0,
|
||||||
|
"posix_fadvise POSIX_FADV_NORMAL returns 0");
|
||||||
|
check(posix_fadvise(fd, 0, 4096, POSIX_FADV_WILLNEED) == 0,
|
||||||
|
"posix_fadvise POSIX_FADV_WILLNEED returns 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 6. O_CLOEXEC on open leaves FD_CLOEXEC visible. */
|
||||||
|
static void
|
||||||
|
cloexec_open_scenario(const char *path)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
fd = open(path, O_RDWR | O_CLOEXEC);
|
||||||
|
check(fd >= 0, "open with O_CLOEXEC succeeds");
|
||||||
|
if (fd >= 0)
|
||||||
|
{
|
||||||
|
check((fcntl(fd, F_GETFD) & FD_CLOEXEC) != 0, "O_CLOEXEC open has FD_CLOEXEC set");
|
||||||
|
check(close(fd) == 0, "close of the O_CLOEXEC descriptor returns 0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 7. creat: create, truncate on re-create, write/reopen/read round trip. */
|
||||||
|
static void
|
||||||
|
creat_scenario(const char *path)
|
||||||
|
{
|
||||||
|
char b[5];
|
||||||
|
long n;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
fd = creat(path, 0600);
|
||||||
|
check(fd >= 0, "creat creates the file and returns a descriptor");
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(__syscall3(SYS_write, fd, (long)"hello", 5) == 5, "write 5 bytes through the creat fd");
|
||||||
|
check(close(fd) == 0, "close of the creat fd returns 0");
|
||||||
|
|
||||||
|
/* Re-create: O_TRUNC must reset the contents. */
|
||||||
|
fd = creat(path, 0600);
|
||||||
|
check(fd >= 0, "creat on the existing file re-opens it");
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(__syscall3(SYS_write, fd, (long)"yo", 2) == 2, "write 2 bytes through the re-creat fd");
|
||||||
|
check(close(fd) == 0, "close of the re-creat fd returns 0");
|
||||||
|
|
||||||
|
fd = open(path, O_RDONLY);
|
||||||
|
check(fd >= 0, "reopen the creat file read-only");
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(lseek(fd, 0, SEEK_END) == 2, "creat truncated the previous contents (size 2)");
|
||||||
|
check(lseek(fd, 0, SEEK_SET) == 0, "lseek back to 0 for the read-back");
|
||||||
|
n = __syscall3(SYS_read, fd, (long)b, 5);
|
||||||
|
check(n == 2 && b[0] == 'y' && b[1] == 'o', "the re-created file holds the new bytes");
|
||||||
|
check(close(fd) == 0, "close of the read-only descriptor returns 0");
|
||||||
|
check(__syscall3(SYS_unlinkat, AT_FDCWD, (long)path, 0) == 0,
|
||||||
|
"unlink of the creat file returns 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Level-2 gate: lockf over the fcntl record locks.
|
||||||
|
*
|
||||||
|
* Conflict semantics note: on modern Linux (since the 2013 POSIX-lock
|
||||||
|
* rework, kernel ~3.15) fcntl record locks are OWNED BY THE PROCESS
|
||||||
|
* (fl_owner = the files_struct), not by the open file description — so two
|
||||||
|
* descriptors of the same process never conflict, exactly as POSIX says.
|
||||||
|
* The cross-process conflict below is therefore exercised with a child
|
||||||
|
* process via raw SYS_pipe/SYS_fork/SYS_wait4 (the process wrappers are
|
||||||
|
* todo 20's, not available here). Verified empirically: F_SETLK through a
|
||||||
|
* second descriptor of the same process succeeds on this kernel (and under
|
||||||
|
* glibc), so a same-process two-fd conflict test would be wrong.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
lockf_scenarios(const char *path, int fd)
|
||||||
|
{
|
||||||
|
unsigned long saved;
|
||||||
|
int sig[2];
|
||||||
|
int go[2];
|
||||||
|
char b = 0;
|
||||||
|
long st = 0;
|
||||||
|
long child;
|
||||||
|
int fd2;
|
||||||
|
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(lseek(fd, 0, SEEK_SET) == 0, "lseek the main fd to 0 for lockf");
|
||||||
|
|
||||||
|
/* Same process, same fd: overlapping requests merge, never conflict. */
|
||||||
|
check(lockf(fd, F_TLOCK, 0) == 0, "lockf F_TLOCK from offset 0 succeeds");
|
||||||
|
check(lockf(fd, F_TLOCK, 0) == 0, "a second F_TLOCK by the same process merges");
|
||||||
|
check(lockf(fd, F_TEST, 0) == 0, "F_TEST by the holding process itself returns 0");
|
||||||
|
check(lockf(fd, F_ULOCK, 0) == 0, "F_ULOCK releases the region");
|
||||||
|
check(lockf(fd, F_LOCK, 16) == 0, "F_LOCK of 16 bytes acquires without contention");
|
||||||
|
check(lockf(fd, F_TEST, 16) == 0, "F_TEST of the region held by this process returns 0");
|
||||||
|
check(lockf(fd, F_ULOCK, 16) == 0, "F_ULOCK of the 16-byte region returns 0");
|
||||||
|
saved = tcb_slot1();
|
||||||
|
check(lockf(fd, 999, 0) == -1, "lockf with an invalid cmd returns -1");
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cross-process conflict. Two pipes (not one): a single pipe would let
|
||||||
|
* the child's own read steal the handshake byte it just wrote — both
|
||||||
|
* processes park on the same read end and the kernel wakes one of
|
||||||
|
* them, so the byte is not guaranteed to reach the parent. Each side
|
||||||
|
* closes the end it must never touch.
|
||||||
|
*/
|
||||||
|
check(__syscall1(SYS_pipe, (long)sig) == 0, "pipe for the lockf signal returns 0");
|
||||||
|
check(__syscall1(SYS_pipe, (long)go) == 0, "pipe for the lockf go returns 0");
|
||||||
|
child = __syscall0(SYS_fork);
|
||||||
|
if (child == 0)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Child: hold the lock, tell the parent, wait for the go. Exits
|
||||||
|
* through the raw syscall; if the lock cannot be taken, "F" is
|
||||||
|
* sent instead so the parent never blocks forever.
|
||||||
|
*/
|
||||||
|
__syscall1(SYS_close, sig[0]);
|
||||||
|
__syscall1(SYS_close, go[1]);
|
||||||
|
fd2 = open(path, O_RDWR);
|
||||||
|
if (fd2 < 0 || lockf(fd2, F_LOCK, 0) != 0)
|
||||||
|
{
|
||||||
|
__syscall3(SYS_write, sig[1], (long)"F", 1);
|
||||||
|
__syscall1(SYS_exit_group, 1);
|
||||||
|
return; /* not reached */
|
||||||
|
}
|
||||||
|
__syscall3(SYS_write, sig[1], (long)"L", 1);
|
||||||
|
__syscall3(SYS_read, go[0], (long)&b, 1);
|
||||||
|
__syscall1(SYS_exit_group, 0);
|
||||||
|
return; /* not reached */
|
||||||
|
}
|
||||||
|
check(child > 0, "fork returned a child pid");
|
||||||
|
if (child < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
__syscall1(SYS_close, sig[1]);
|
||||||
|
__syscall1(SYS_close, go[0]);
|
||||||
|
__syscall3(SYS_read, sig[0], (long)&b, 1);
|
||||||
|
check(b == 'L', "the child acquired the lock");
|
||||||
|
if (b == 'L')
|
||||||
|
{
|
||||||
|
saved = tcb_slot1();
|
||||||
|
check(lockf(fd, F_TLOCK, 0) == -1, "F_TLOCK conflicts with the child's lock -> -1");
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
saved = tcb_slot1();
|
||||||
|
check(lockf(fd, F_TEST, 0) == -1, "F_TEST finds the child's lock -> -1");
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
}
|
||||||
|
__syscall3(SYS_write, go[1], (long)"G", 1);
|
||||||
|
__syscall4(SYS_wait4, child, (long)&st, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Failure scenarios (-f): every assertion is on the return value only, and
|
||||||
|
* the process exits through raw SYS_exit_group because fcntl/creat/lockf
|
||||||
|
* write errno on these paths (host-TCB hazard). EBADF is 9 and comes back
|
||||||
|
* DIRECTLY from posix_fadvise/posix_fallocate — no errno involved there.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
failure_scenarios(void)
|
||||||
|
{
|
||||||
|
int rc = 0;
|
||||||
|
|
||||||
|
if (fcntl(-1, F_GETFL) != -1)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: fcntl(-1, F_GETFL) did not return -1\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: fcntl(-1, F_GETFL) -> -1\n");
|
||||||
|
}
|
||||||
|
if (fcntl(-1, F_SETFL, O_NONBLOCK) != -1)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: fcntl(-1, F_SETFL, O_NONBLOCK) did not return -1\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: fcntl(-1, F_SETFL, O_NONBLOCK) -> -1\n");
|
||||||
|
}
|
||||||
|
if (creat("/nonexistent/vlibc/t21", 0600) != -1)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: creat on a nonexistent directory did not return -1\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: creat on a nonexistent directory -> -1\n");
|
||||||
|
}
|
||||||
|
if (posix_fallocate(-1, 0, 4096) != 9)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: posix_fallocate(-1, 0, 4096) did not return EBADF (9)\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: posix_fallocate(-1, 0, 4096) -> EBADF (9) directly\n");
|
||||||
|
}
|
||||||
|
if (posix_fadvise(-1, 0, 0, POSIX_FADV_NORMAL) != 9)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: posix_fadvise(-1, 0, 0, POSIX_FADV_NORMAL) did not return EBADF (9)\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: posix_fadvise(-1, 0, 0, POSIX_FADV_NORMAL) -> EBADF (9) directly\n");
|
||||||
|
}
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
if (lockf(-1, F_TLOCK, 0) != -1)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: lockf(-1, F_TLOCK, 0) did not return -1\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: lockf(-1, F_TLOCK, 0) -> -1\n");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
const char *path = "/tmp/vlibc-t21";
|
||||||
|
const char *cpath = "/tmp/vlibc-t21-creat";
|
||||||
|
int rc;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* The failure scenarios write errno inside the library; under the
|
||||||
|
* host libc that slot is glibc's private TLS state, so leave via
|
||||||
|
* the raw syscall without running host cleanup.
|
||||||
|
*/
|
||||||
|
rc = failure_scenarios();
|
||||||
|
__syscall1(SYS_exit_group, rc);
|
||||||
|
return rc; /* not reached */
|
||||||
|
}
|
||||||
|
|
||||||
|
fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0600);
|
||||||
|
check(fd >= 0, "open O_RDWR|O_CREAT|O_TRUNC 0600 returns a descriptor");
|
||||||
|
cmd_scenarios(fd);
|
||||||
|
fallocate_scenarios(fd);
|
||||||
|
cloexec_open_scenario(path);
|
||||||
|
creat_scenario(cpath);
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
lockf_scenarios(path, fd);
|
||||||
|
#endif
|
||||||
|
if (fd >= 0)
|
||||||
|
{
|
||||||
|
check(close(fd) == 0, "close of the main temp file returns 0");
|
||||||
|
}
|
||||||
|
check(__syscall3(SYS_unlinkat, AT_FDCWD, (long)path, 0) == 0,
|
||||||
|
"unlink of the temp file returns 0");
|
||||||
|
|
||||||
|
if (failures > 0)
|
||||||
|
{
|
||||||
|
say(2, "FAILED (");
|
||||||
|
say_dec(2, (unsigned long)failures);
|
||||||
|
say(2, " check(s))\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
say(1, "all fcntl tests passed\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user