Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddcf8f39be
|
||
|
|
5af4197ee9
|
||
|
|
a2f7c33601
|
||
|
|
14c6fd6cf6
|
||
|
|
c9e9676d38
|
||
|
|
6eaacd4448
|
||
|
|
98f4c3169c
|
||
|
|
87ab610a9b
|
||
|
|
7b11452b87
|
||
|
|
34408380f0
|
||
|
|
22587bd545
|
@@ -0,0 +1,179 @@
|
|||||||
|
#ifndef VLIBC_DIRENT_H
|
||||||
|
#define VLIBC_DIRENT_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — <dirent.h>.
|
||||||
|
*
|
||||||
|
* Directory streams: opendir/fdopendir/readdir/rewinddir/closedir,
|
||||||
|
* seekdir/telldir/dirfd (POSIX.1-2008 base) iterate a directory via the
|
||||||
|
* SYS_getdents64 kernel ABI, exposing one entry at a time.
|
||||||
|
*
|
||||||
|
* Level 1 (onlyposix): the base family above.
|
||||||
|
* Level 2 (muslmimic): scandir, alphasort (XSI) and readdir_r
|
||||||
|
* (obsolescent). versionsort is GNU and is not
|
||||||
|
* provided.
|
||||||
|
*
|
||||||
|
* struct dirent mirrors the x86_64 getdents64 record layout (verified by
|
||||||
|
* the static assertions below): d_ino/d_off are 64-bit, d_reclen is the
|
||||||
|
* kernel record length, d_type carries the DT_* file type, and d_name is a
|
||||||
|
* 256-byte NUL-terminated name buffer (NAME_MAX 255 + NUL).
|
||||||
|
*
|
||||||
|
* DIR is an opaque handle whose layout lives in the internal header
|
||||||
|
* src/dirent/dirent_impl.h. None of these declarations carries an intent
|
||||||
|
* attribute: every function performs I/O with side effects and reports
|
||||||
|
* failures through errno, so const/pure would be unsound (the same
|
||||||
|
* rationale sys/stat.h documents for its I/O family).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vlibc/features.h>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* File type values for the d_type member of struct dirent (kernel UAPI). */
|
||||||
|
#define DT_UNKNOWN 0
|
||||||
|
#define DT_FIFO 1
|
||||||
|
#define DT_CHR 2
|
||||||
|
#define DT_DIR 4
|
||||||
|
#define DT_BLK 6
|
||||||
|
#define DT_REG 8
|
||||||
|
#define DT_LNK 10
|
||||||
|
#define DT_SOCK 12
|
||||||
|
#define DT_WHT 14
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One directory entry. Layout equals the x86_64 struct linux_dirent64
|
||||||
|
* fields 0..18 verbatim; the name then follows at offset 19, stored in the
|
||||||
|
* conventional 256-byte buffer.
|
||||||
|
*/
|
||||||
|
struct dirent
|
||||||
|
{
|
||||||
|
ino_t d_ino; /* 0: inode number */
|
||||||
|
off_t d_off; /* 8: offset of the next entry (seek cookie) */
|
||||||
|
unsigned short d_reclen; /* 16: length of the kernel record */
|
||||||
|
unsigned char d_type; /* 18: DT_* file type */
|
||||||
|
char d_name[256]; /* 19: NUL-terminated file name */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Pin the layout to the x86_64 kernel ABI. */
|
||||||
|
_Static_assert(sizeof(struct dirent) == 280, "struct dirent must match the x86_64 getdents64 layout");
|
||||||
|
_Static_assert(offsetof(struct dirent, d_off) == 8, "d_off must sit at offset 8");
|
||||||
|
_Static_assert(offsetof(struct dirent, d_type) == 18, "d_type must sit at offset 18");
|
||||||
|
_Static_assert(offsetof(struct dirent, d_name) == 19, "d_name must sit at offset 19");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Opaque directory stream handle. The struct tag stays usable from the
|
||||||
|
* internal layout header, which defines struct vlibc_DIR (see
|
||||||
|
* src/dirent/dirent_impl.h).
|
||||||
|
*/
|
||||||
|
typedef struct vlibc_DIR DIR;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Open the directory named by path for reading and return a directory
|
||||||
|
* stream positioned at its first entry, or NULL with errno set (a
|
||||||
|
* non-directory path fails with ENOTDIR). The stream owns a descriptor
|
||||||
|
* that closedir() releases.
|
||||||
|
*/
|
||||||
|
DIR *
|
||||||
|
opendir(const char *path);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like opendir(), but over the already-open descriptor fd, which must
|
||||||
|
* refer to a directory (validated with fstat). On failure NULL is returned
|
||||||
|
* with errno set and fd is left open and owned by the caller.
|
||||||
|
*/
|
||||||
|
DIR *
|
||||||
|
fdopendir(int fd);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the next directory entry of dir, or NULL at the end of the
|
||||||
|
* directory (errno untouched) or on error (errno set). The result points
|
||||||
|
* at storage owned by dir and is valid until the next call to readdir,
|
||||||
|
* rewinddir, seekdir, or closedir on the same stream.
|
||||||
|
*/
|
||||||
|
struct dirent *
|
||||||
|
readdir(DIR *dir);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reset dir to the beginning of the directory: the next readdir returns
|
||||||
|
* the first entry again. Never fails.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
rewinddir(DIR *dir);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Close dir, releasing its descriptor and storage. Return 0, or -1 with
|
||||||
|
* errno set if the underlying close fails.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
closedir(DIR *dir);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reposition dir so the next readdir resumes at the location loc, which
|
||||||
|
* must be a value previously returned by telldir (a getdents64 seek
|
||||||
|
* cookie). Never fails.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
seekdir(DIR *dir, long loc);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the current location of dir, for a later seekdir. The location is
|
||||||
|
* the point after the entry most recently returned by readdir; it becomes
|
||||||
|
* indeterminate after rewinddir or closedir.
|
||||||
|
*/
|
||||||
|
long
|
||||||
|
telldir(DIR *dir);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the descriptor underlying dir. The descriptor stays owned by the
|
||||||
|
* stream and remains valid until closedir.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
dirfd(DIR *dir);
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/* Level 2 (muslmimic): XSI and obsolescent. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reentrant readdir: store the next entry in *buf and set *result to buf;
|
||||||
|
* at the end of the directory *result is NULL. Returns 0 at end of
|
||||||
|
* directory, an error number on failure (errno is not used for the error
|
||||||
|
* report), and leaves errno unmodified on success. Obsolescent.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
readdir_r(DIR *restrict dir, struct dirent *restrict buf,
|
||||||
|
struct dirent **restrict result);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read the whole directory named by path and store a malloc'd array of
|
||||||
|
* malloc'd struct dirent copies in *res (both released with free; the
|
||||||
|
* array is NULL-terminated with one extra NULL pointer). Only entries for
|
||||||
|
* which sel is NULL or returns nonzero are kept; cmp, when non-NULL, sorts
|
||||||
|
* the array (alphasort is the strcmp-on-name comparator). Returns the
|
||||||
|
* number of entries, or -1 with errno set. XSI.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
scandir(const char *path, struct dirent ***res,
|
||||||
|
int (*sel)(const struct dirent *),
|
||||||
|
int (*cmp)(const struct dirent **, const struct dirent **));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Lexicographic comparator over the d_name fields of two struct dirent
|
||||||
|
* pointers, for use as scandir's cmp argument. XSI.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
alphasort(const struct dirent **a, const struct dirent **b);
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VLIBC_DIRENT_H */
|
||||||
+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,99 @@
|
|||||||
|
#ifndef VLIBC_POLL_H
|
||||||
|
#define VLIBC_POLL_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — <poll.h>.
|
||||||
|
*
|
||||||
|
* Poll multiple file descriptors for readiness. Every declaration here is a
|
||||||
|
* thin pass-through to the kernel: poll() blocks per its millisecond
|
||||||
|
* timeout, ppoll() takes a struct timespec and an optional signal mask
|
||||||
|
* directly, and both return the count of ready descriptors (0 on timeout,
|
||||||
|
* -1 with errno set on error).
|
||||||
|
*
|
||||||
|
* Level 1 (onlyposix): poll, nfds_t, struct pollfd, the POLL* event bits.
|
||||||
|
* Level 2 (muslmimic): ppoll (Linux-specific; POSIX has only poll).
|
||||||
|
*
|
||||||
|
* struct pollfd and the POLL* values mirror the kernel ABI (x86_64
|
||||||
|
* asm-generic/poll.h) exactly: the wrappers pass them to SYS_ppoll
|
||||||
|
* unmodified. POLLERR, POLLHUP and POLLNVAL are report-only — they can
|
||||||
|
* appear in revents even when not requested in events.
|
||||||
|
*
|
||||||
|
* The signal mask type ppoll takes is declared under the shared guard
|
||||||
|
* below; <signal.h> is its canonical POSIX home and will own the full sig*
|
||||||
|
* API.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vlibc/features.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Requestable event bits (poll()'s events field). */
|
||||||
|
#define POLLIN 0x001 /* readable */
|
||||||
|
#define POLLPRI 0x002 /* urgent readable (out-of-band) */
|
||||||
|
#define POLLOUT 0x004 /* writable */
|
||||||
|
|
||||||
|
/* Report-only event bits (poll()'s revents field). */
|
||||||
|
#define POLLERR 0x008 /* error condition */
|
||||||
|
#define POLLHUP 0x010 /* hung up */
|
||||||
|
#define POLLNVAL 0x020 /* invalid fd */
|
||||||
|
|
||||||
|
/* The number of struct pollfd entries poll()/ppoll() watch. */
|
||||||
|
typedef unsigned long nfds_t;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One descriptor watched by poll()/ppoll(). fd is the descriptor (negative
|
||||||
|
* to ignore), events the requested bits (POLLIN/POLLOUT), revents the bits
|
||||||
|
* the kernel reports (the requested bits plus any POLLERR/POLLHUP/POLLNVAL).
|
||||||
|
*/
|
||||||
|
struct pollfd
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
short events;
|
||||||
|
short revents;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait for readiness on the first nfds entries of fds, blocking up to
|
||||||
|
* timeout milliseconds (timeout < 0 blocks indefinitely, 0 never blocks).
|
||||||
|
* Return the number of entries with a nonzero revents, 0 on timeout, or -1
|
||||||
|
* with errno set on error.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
poll(struct pollfd *fds, nfds_t nfds, int timeout);
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
/* Level 2 (muslmimic): Linux-specific. */
|
||||||
|
|
||||||
|
#ifndef VLIBC_SIGSET_T_DEFINED
|
||||||
|
#define VLIBC_SIGSET_T_DEFINED
|
||||||
|
/*
|
||||||
|
* Signal mask type: a single 64-bit word — the x86_64 Linux sigset_t (see
|
||||||
|
* setjmp.h). <signal.h> is the canonical POSIX home for sigset_t and builds
|
||||||
|
* the sig* API on this same layout; the typedef is repeated in
|
||||||
|
* <sys/select.h> under this guard so the two headers stay consistent.
|
||||||
|
*/
|
||||||
|
typedef unsigned long sigset_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like poll(), but the timeout is a struct timespec and, when sigmask is
|
||||||
|
* not NULL, the given signal mask is atomically installed for the duration
|
||||||
|
* of the wait (the previous mask is restored before returning).
|
||||||
|
* Linux-specific.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout, const sigset_t *sigmask);
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VLIBC_POLL_H */
|
||||||
+432
@@ -0,0 +1,432 @@
|
|||||||
|
#ifndef VLIBC_STDIO_H
|
||||||
|
#define VLIBC_STDIO_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — <stdio.h>.
|
||||||
|
*
|
||||||
|
* Buffered stream I/O (todo 15). The FILE object is opaque to consumers;
|
||||||
|
* the layout lives in src/stdio/stdio_impl.h. stdin/stdout/stderr are
|
||||||
|
* pre-wired streams over fds 0/1/2 and are initialized lazily on first
|
||||||
|
* use. On Linux there is no text/binary distinction, so the 'b' mode
|
||||||
|
* character is accepted and ignored.
|
||||||
|
*
|
||||||
|
* Buffering modes for setvbuf: _IOFBF (fully buffered), _IOLBF (line
|
||||||
|
* buffered: flush on newline), _IONBF (unbuffered). A stream opened on a
|
||||||
|
* terminal defaults to line buffering for stdout and full buffering for
|
||||||
|
* stdin; stderr is always unbuffered.
|
||||||
|
*
|
||||||
|
* getc/putc/getchar/putchar are declared as functions (address-taking and
|
||||||
|
* #undef work) and additionally defined as macros over fgetc/fputc; the
|
||||||
|
* macro arguments are evaluated exactly once.
|
||||||
|
*
|
||||||
|
* Level 2 (muslmimic) adds tmpnam/ctermid/setbuffer/setlinebuf and the
|
||||||
|
* fopen64 name alias (identical ABI on LP64).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vlibc/features.h>
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The opaque stream object. The underlying struct tag is vlibc_FILE (see
|
||||||
|
* src/stdio/stdio_impl.h); consumers only ever use FILE *.
|
||||||
|
*/
|
||||||
|
typedef struct vlibc_FILE FILE;
|
||||||
|
|
||||||
|
/* The three pre-wired standard streams (fds 0, 1, 2). */
|
||||||
|
extern FILE *stdin;
|
||||||
|
extern FILE *stdout;
|
||||||
|
extern FILE *stderr;
|
||||||
|
|
||||||
|
/* End of file indicator for character functions. */
|
||||||
|
#define EOF (-1)
|
||||||
|
|
||||||
|
/* Minimum number of simultaneously open files. */
|
||||||
|
#define FOPEN_MAX 16
|
||||||
|
|
||||||
|
/* Default buffer size for setvbuf/setbuf. */
|
||||||
|
#define BUFSIZ 8192
|
||||||
|
|
||||||
|
/* Maximum length of a path argument for stdio functions. */
|
||||||
|
#define FILENAME_MAX 4096
|
||||||
|
|
||||||
|
/* Minimum number of distinct tmpnam-generated names. */
|
||||||
|
#define TMP_MAX 238328
|
||||||
|
|
||||||
|
/* Buffer sizes for tmpnam and ctermid results. */
|
||||||
|
#define L_tmpnam 20
|
||||||
|
#define L_ctermid 9
|
||||||
|
|
||||||
|
/* Seek positions for fseek/fseeko. */
|
||||||
|
#define SEEK_SET 0
|
||||||
|
#define SEEK_CUR 1
|
||||||
|
#define SEEK_END 2
|
||||||
|
|
||||||
|
/* setvbuf modes. */
|
||||||
|
#define _IOFBF 0 // NOLINT(bugprone-reserved-identifier)
|
||||||
|
#define _IOLBF 1 // NOLINT(bugprone-reserved-identifier)
|
||||||
|
#define _IONBF 2 // NOLINT(bugprone-reserved-identifier)
|
||||||
|
|
||||||
|
/* Opaque file position type for fgetpos/fsetpos. */
|
||||||
|
typedef off_t fpos_t;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Open the file at path with the given mode. The mode is 'r', 'w', or 'a',
|
||||||
|
* optionally followed by '+' (update: read and write), 'b' (ignored on
|
||||||
|
* Linux), and/or 'x' (exclusive create, C11). Returns NULL with errno set
|
||||||
|
* on failure.
|
||||||
|
*/
|
||||||
|
FILE *
|
||||||
|
fopen(const char *restrict path, const char *restrict mode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wrap an existing descriptor in a stream. The requested mode must be
|
||||||
|
* compatible with the descriptor's access mode (basic check via fcntl).
|
||||||
|
* The descriptor is not duplicated; fclose closes it.
|
||||||
|
*/
|
||||||
|
FILE *
|
||||||
|
fdopen(int fd, const char *mode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Rebind stream to path. The old descriptor is flushed and closed first.
|
||||||
|
* With path == NULL only the mode changes and the descriptor stays open.
|
||||||
|
* Returns NULL with errno set on failure.
|
||||||
|
*/
|
||||||
|
FILE *
|
||||||
|
freopen(const char *restrict path, const char *restrict mode, FILE *restrict stream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Flush pending output, close the descriptor, and release the stream.
|
||||||
|
* Returns EOF if flushing or closing failed.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fclose(FILE *stream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Flush pending output of stream. fflush(NULL) flushes all open streams
|
||||||
|
* with pending output. On a stream with no pending writes (read mode) the
|
||||||
|
* unread buffered data is discarded and the position rewound; this never
|
||||||
|
* corrupts the stream. Returns EOF on error.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fflush(FILE *stream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the buffer of stream to buf. buf == NULL selects unbuffered I/O;
|
||||||
|
* otherwise the buffer is used with full buffering and BUFSIZ size. Must
|
||||||
|
* be called before the first operation on the stream.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
setbuf(FILE *restrict stream, char *restrict buf);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the buffering mode of stream: _IOFBF, _IOLBF, or _IONBF. With
|
||||||
|
* buf != NULL the caller supplies size bytes of storage; with buf == NULL
|
||||||
|
* the buffer is allocated lazily on first use (size is then ignored,
|
||||||
|
* except that _IONBF needs no buffer). Returns 0, or -1 with errno EINVAL
|
||||||
|
* for an invalid mode or a non-NULL buf with size 0 (except _IONBF).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
setvbuf(FILE *restrict stream, char *restrict buf, int mode, size_t size);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read up to size * nmemb bytes in items of size bytes each. Returns the
|
||||||
|
* number of complete items read; fewer than nmemb means end of file or an
|
||||||
|
* error (distinguishable via feof/ferror). size or nmemb zero returns 0
|
||||||
|
* without touching the stream.
|
||||||
|
*/
|
||||||
|
size_t
|
||||||
|
fread(void *restrict ptr, size_t size, size_t nmemb, FILE *restrict stream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write size * nmemb bytes in items of size bytes each. Returns the number
|
||||||
|
* of complete items written; fewer than nmemb means an error (see
|
||||||
|
* ferror).
|
||||||
|
*/
|
||||||
|
size_t
|
||||||
|
fwrite(const void *restrict ptr, size_t size, size_t nmemb, FILE *restrict stream);
|
||||||
|
|
||||||
|
/* Read the next character as unsigned char, or EOF. */
|
||||||
|
int
|
||||||
|
fgetc(FILE *stream);
|
||||||
|
|
||||||
|
/* Write c as unsigned char; returns it, or EOF on error. */
|
||||||
|
int
|
||||||
|
fputc(int c, FILE *stream);
|
||||||
|
|
||||||
|
/* Same as fgetc/fputc; also provided as macros (arguments evaluated once). */
|
||||||
|
int
|
||||||
|
getc(FILE *stream);
|
||||||
|
|
||||||
|
int
|
||||||
|
putc(int c, FILE *stream);
|
||||||
|
|
||||||
|
int
|
||||||
|
getchar(void);
|
||||||
|
|
||||||
|
int
|
||||||
|
putchar(int c);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read at most n-1 characters into s, stopping after (and keeping) a
|
||||||
|
* newline, then NUL-terminate. Returns s, or NULL if no character was read
|
||||||
|
* (end of file or error).
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
fgets(char *restrict s, int n, FILE *restrict stream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write the NUL-terminated string s to stream (no trailing newline is
|
||||||
|
* added). Returns a non-negative value, or EOF on error.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fputs(const char *restrict s, FILE *restrict stream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Push c back onto the input stream; the next read returns it again. One
|
||||||
|
* byte of pushback is guaranteed. Returns c, or EOF on error (also for
|
||||||
|
* ungetc(EOF)). A successful seek discards the pushed-back character.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
ungetc(int c, FILE *stream);
|
||||||
|
|
||||||
|
/* Position the stream (see SEEK_SET/SEEK_CUR/SEEK_END); clears feof. */
|
||||||
|
int
|
||||||
|
fseek(FILE *stream, long offset, int whence);
|
||||||
|
|
||||||
|
int
|
||||||
|
fseeko(FILE *stream, off_t offset, int whence);
|
||||||
|
|
||||||
|
/* Current stream position, -1 with errno set on error. */
|
||||||
|
long
|
||||||
|
ftell(FILE *stream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Not marked pure: the implementation can allocate the stream buffer on
|
||||||
|
* first use (an observable side effect), so the compiler must not elide
|
||||||
|
* or reorder the call. (feof/ferror/fileno below are genuinely read-only
|
||||||
|
* and keep their pure attribute.)
|
||||||
|
*/
|
||||||
|
off_t
|
||||||
|
ftello(FILE *stream);
|
||||||
|
|
||||||
|
/* Rewind to the start and clear feof/ferror (equivalent to
|
||||||
|
* fseeko(stream, 0, SEEK_SET) + clearerr). */
|
||||||
|
void
|
||||||
|
rewind(FILE *stream);
|
||||||
|
|
||||||
|
/* Get/set the opaque position via fpos_t. */
|
||||||
|
int
|
||||||
|
fgetpos(FILE *restrict stream, fpos_t *restrict pos);
|
||||||
|
|
||||||
|
int
|
||||||
|
fsetpos(FILE *stream, const fpos_t *pos);
|
||||||
|
|
||||||
|
/* End-of-file and error indicators. */
|
||||||
|
__attribute__((pure)) int
|
||||||
|
feof(FILE *stream);
|
||||||
|
|
||||||
|
__attribute__((pure)) int
|
||||||
|
ferror(FILE *stream);
|
||||||
|
|
||||||
|
void
|
||||||
|
clearerr(FILE *stream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove the file at path (a directory is removed like rmdir). Returns 0,
|
||||||
|
* or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
remove(const char *path);
|
||||||
|
|
||||||
|
/* Rename oldpath to newpath. Returns 0, or -1 with errno set. */
|
||||||
|
int
|
||||||
|
rename(const char *oldpath, const char *newpath);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create an anonymous temporary file ("w+b"): the file is created in /tmp
|
||||||
|
* and unlinked immediately, so it disappears on close. Returns the stream,
|
||||||
|
* or NULL with errno set.
|
||||||
|
*/
|
||||||
|
FILE *
|
||||||
|
tmpfile(void);
|
||||||
|
|
||||||
|
/* The descriptor underlying the stream. */
|
||||||
|
__attribute__((pure)) int
|
||||||
|
fileno(FILE *stream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Run command in a subshell ("sh -c command") with a pipe attached to
|
||||||
|
* its standard output (mode "r") or standard input (mode "w"). Only the
|
||||||
|
* two POSIX modes are accepted (the glibc "re"/"we" close-on-exec
|
||||||
|
* extension is not). Returns the stream, or NULL with errno set.
|
||||||
|
* pclose closes the stream, waits for the shell, and returns its
|
||||||
|
* termination status (the raw wait status, e.g. 0 for "exit 0"), or -1
|
||||||
|
* when the stream was not opened by popen or the wait failed.
|
||||||
|
*/
|
||||||
|
FILE *
|
||||||
|
popen(const char *command, const char *mode);
|
||||||
|
|
||||||
|
int
|
||||||
|
pclose(FILE *stream);
|
||||||
|
|
||||||
|
/* getc/putc/getchar/putchar as macros over fgetc/fputc (see above). */
|
||||||
|
#define getc(stream) fgetc(stream)
|
||||||
|
#define putc(c, stream) fputc((c), (stream))
|
||||||
|
#define getchar() fgetc(stdin)
|
||||||
|
#define putchar(c) fputc((c), stdout)
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generate a name for a temporary file ("/tmp/vlibcXXXXXX" form; the file
|
||||||
|
* is NOT created). With s == NULL a static buffer is used. Not thread-safe,
|
||||||
|
* obsolescent.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
tmpnam(char *s);
|
||||||
|
|
||||||
|
/* Controlling terminal path: copies "/dev/tty" into s (or a static
|
||||||
|
* buffer when s == NULL) and returns it. */
|
||||||
|
char *
|
||||||
|
ctermid(char *s);
|
||||||
|
|
||||||
|
/* BSD: like setvbuf with _IOFBF/_IONBF and the given size. */
|
||||||
|
void
|
||||||
|
setbuffer(FILE *stream, char *buf, size_t size);
|
||||||
|
|
||||||
|
/* BSD: select line buffering (setvbuf with _IOLBF and NULL buffer). */
|
||||||
|
void
|
||||||
|
setlinebuf(FILE *stream);
|
||||||
|
|
||||||
|
/* glibc LFS name alias: identical to fopen on LP64. */
|
||||||
|
FILE *
|
||||||
|
fopen64(const char *restrict path, const char *restrict mode);
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* formatted output (todo 16) */
|
||||||
|
|
||||||
|
int
|
||||||
|
printf(const char *restrict format, ...);
|
||||||
|
int
|
||||||
|
fprintf(FILE *restrict stream, const char *restrict format, ...);
|
||||||
|
int
|
||||||
|
sprintf(char *restrict s, const char *restrict format, ...);
|
||||||
|
int
|
||||||
|
snprintf(char *restrict s, size_t n, const char *restrict format, ...);
|
||||||
|
int
|
||||||
|
vprintf(const char *restrict format, va_list ap);
|
||||||
|
int
|
||||||
|
vfprintf(FILE *restrict stream, const char *restrict format, va_list ap);
|
||||||
|
int
|
||||||
|
vsprintf(char *restrict s, const char *restrict format, va_list ap);
|
||||||
|
int
|
||||||
|
vsnprintf(char *restrict s, size_t n, const char *restrict format, va_list ap);
|
||||||
|
int
|
||||||
|
dprintf(int fd, const char *restrict format, ...);
|
||||||
|
int
|
||||||
|
vdprintf(int fd, const char *restrict format, va_list ap);
|
||||||
|
void
|
||||||
|
perror(const char *s);
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
int
|
||||||
|
asprintf(char **restrict strp, const char *restrict format, ...);
|
||||||
|
int
|
||||||
|
vasprintf(char **restrict strp, const char *restrict format, va_list ap);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* line input, stream locking, and memory streams (todo 18) */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read one line from stream into a malloc'd, NUL-terminated buffer. On the
|
||||||
|
* first call *lineptr may be NULL (a buffer is allocated); *n is the buffer
|
||||||
|
* capacity and grows as needed. The newline is kept in the output; embedded
|
||||||
|
* NUL bytes are preserved (the length comes from the return value, not
|
||||||
|
* strlen). Returns the number of bytes read including the delimiter, or -1
|
||||||
|
* at end of file (nothing read) or on error.
|
||||||
|
*/
|
||||||
|
ssize_t
|
||||||
|
getline(char **restrict lineptr, size_t *restrict n, FILE *restrict stream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like getline, but reads up to the given delimiter byte (which is kept in
|
||||||
|
* the output). Reading stops at end of file without the delimiter; a partial
|
||||||
|
* final line is still returned with its byte count.
|
||||||
|
*/
|
||||||
|
ssize_t
|
||||||
|
getdelim(char **restrict lineptr, size_t *restrict n, int delim, FILE *restrict stream);
|
||||||
|
|
||||||
|
/* Write s to stdout followed by a newline. Returns a non-negative value,
|
||||||
|
* or EOF on error. */
|
||||||
|
int
|
||||||
|
puts(const char *s);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Per-stream advisory locks (POSIX.1-2008 base). flockfile is recursive:
|
||||||
|
* the owning thread may lock the same stream any number of times, and
|
||||||
|
* funlockfile must be called once per successful flockfile. ftrylockfile
|
||||||
|
* acquires without blocking: 0 on success, nonzero when the stream is
|
||||||
|
* already locked. Single-threaded today; the lock fields become real
|
||||||
|
* blocking locks when the thread runtime lands.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
flockfile(FILE *stream);
|
||||||
|
|
||||||
|
int
|
||||||
|
ftrylockfile(FILE *stream);
|
||||||
|
|
||||||
|
void
|
||||||
|
funlockfile(FILE *stream);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Unlocked character I/O: the stream must already be locked by the caller
|
||||||
|
* (see flockfile). Identical to fgetc/fputc over stdin/stdout without
|
||||||
|
* taking the per-stream lock.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
getc_unlocked(FILE *stream);
|
||||||
|
|
||||||
|
int
|
||||||
|
getchar_unlocked(void);
|
||||||
|
|
||||||
|
int
|
||||||
|
putc_unlocked(int c, FILE *stream);
|
||||||
|
|
||||||
|
int
|
||||||
|
putchar_unlocked(int c);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Open a stream over the memory region buf of size bytes. With buf == NULL
|
||||||
|
* the stream allocates and owns a growable buffer of its own that is freed
|
||||||
|
* on fclose. Modes are fopen-like ('r', 'w', 'a', optional '+' and 'b',
|
||||||
|
* where 'b' is accepted but on Linux only disables the string semantics):
|
||||||
|
* 'w' truncates the current length to zero, 'a' positions at the end of the
|
||||||
|
* data currently in the buffer (the first NUL byte in string mode), and
|
||||||
|
* writing past the buffer size is an error. Returns the stream, or NULL
|
||||||
|
* with errno set.
|
||||||
|
*/
|
||||||
|
FILE *
|
||||||
|
fmemopen(void *buf, size_t size, const char *mode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Open a write-only dynamic memory stream. The stream owns a growable
|
||||||
|
* buffer; after fflush or fclose the buffer is NUL-terminated at the current
|
||||||
|
* data length, *ptr points at it (it may have moved), and *sizeloc holds the
|
||||||
|
* length. The caller releases the buffer with free() after fclose.
|
||||||
|
*/
|
||||||
|
FILE *
|
||||||
|
open_memstream(char **ptr, size_t *sizeloc);
|
||||||
|
|
||||||
|
#endif /* VLIBC_STDIO_H */
|
||||||
@@ -463,6 +463,19 @@ mkstemp(char *);
|
|||||||
char *
|
char *
|
||||||
mkdtemp(char *);
|
mkdtemp(char *);
|
||||||
|
|
||||||
|
/* process control (todo 20) */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pass string to the command language interpreter: "sh -c string".
|
||||||
|
* Returns the shell's wait status (e.g. 768 for "exit 3"), 1 when
|
||||||
|
* string is NULL (a shell is always available), or -1 with errno set
|
||||||
|
* when the child cannot be created or reaped. During the run, SIGCHLD
|
||||||
|
* is blocked and SIGINT/SIGQUIT are ignored in the caller, as POSIX
|
||||||
|
* requires.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
system(const char *string);
|
||||||
|
|
||||||
#if VLIBC_LEVEL_GE(2)
|
#if VLIBC_LEVEL_GE(2)
|
||||||
/* Level 2 (muslmimic): XSI and BSD environment extras. */
|
/* Level 2 (muslmimic): XSI and BSD environment extras. */
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
#ifndef VLIBC_SYS_SELECT_H
|
||||||
|
#define VLIBC_SYS_SELECT_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — <sys/select.h>.
|
||||||
|
*
|
||||||
|
* Synchronous I/O multiplexing: wait for readiness on file-descriptor sets
|
||||||
|
* with select() or pselect(). Both are thin wrappers over the SYS_pselect6
|
||||||
|
* kernel ABI, (nfds, readfds, writefds, exceptfds, struct timespec *,
|
||||||
|
* { sigset_t *, size_t } *): pselect passes its timespec and optional signal
|
||||||
|
* mask straight through, select converts its struct timeval timeout to a
|
||||||
|
* timespec and passes a NULL sigset pair.
|
||||||
|
*
|
||||||
|
* Level 1 (onlyposix): select, pselect, FD_* macros, fd_set, struct
|
||||||
|
* timeval.
|
||||||
|
*
|
||||||
|
* fd_set is the kernel's bitmap layout on x86_64: an array of unsigned long
|
||||||
|
* words, with descriptor d held in bit d % 64 of word d / 64. FD_SETSIZE
|
||||||
|
* caps the tracked descriptors at 1024 (16 words), so select()/pselect()
|
||||||
|
* must be called with nfds <= FD_SETSIZE and FD_SET() only ever receives
|
||||||
|
* descriptors below FD_SETSIZE. select() and pselect() report the number of
|
||||||
|
* ready descriptors, 0 on timeout, or -1 with errno set on error.
|
||||||
|
*
|
||||||
|
* The signal mask type is declared here under the shared guard; <signal.h>
|
||||||
|
* is its canonical POSIX home and will own the full sig* API.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vlibc/features.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Maximum descriptors select()/pselect() track (fd_set's bit capacity). */
|
||||||
|
#define FD_SETSIZE 1024
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A set of file descriptors, one bit per descriptor, stored in the kernel
|
||||||
|
* bitmap layout: an array of unsigned long words, bit d in word
|
||||||
|
* d / (8 * sizeof(unsigned long)). Descriptors 0..FD_SETSIZE-1 fit; no
|
||||||
|
* operation may touch a descriptor at or above FD_SETSIZE.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned long fds_bits[FD_SETSIZE / (8 * sizeof(unsigned long))];
|
||||||
|
} fd_set;
|
||||||
|
|
||||||
|
/* The word holding bit d of an fd_set, and that bit's mask within the word. */
|
||||||
|
#define VLIBC_FDS_WORD(d) ((d) / (8 * sizeof(unsigned long)))
|
||||||
|
#define VLIBC_FDS_MASK(d) (1UL << ((d) % (8 * sizeof(unsigned long))))
|
||||||
|
|
||||||
|
/* Clear every descriptor bit of set. */
|
||||||
|
#define FD_ZERO(set) \
|
||||||
|
do \
|
||||||
|
{ \
|
||||||
|
size_t fd_zero_i; \
|
||||||
|
for (fd_zero_i = 0; fd_zero_i < sizeof(fd_set) / sizeof(unsigned long); fd_zero_i++) \
|
||||||
|
{ \
|
||||||
|
(set)->fds_bits[fd_zero_i] = 0UL; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/* Add descriptor d to set. */
|
||||||
|
#define FD_SET(d, set) ((set)->fds_bits[VLIBC_FDS_WORD(d)] |= VLIBC_FDS_MASK(d))
|
||||||
|
|
||||||
|
/* Remove descriptor d from set. */
|
||||||
|
#define FD_CLR(d, set) ((set)->fds_bits[VLIBC_FDS_WORD(d)] &= ~VLIBC_FDS_MASK(d))
|
||||||
|
|
||||||
|
/* Nonzero when descriptor d is a member of set. */
|
||||||
|
#define FD_ISSET(d, set) ((set)->fds_bits[VLIBC_FDS_WORD(d)] & VLIBC_FDS_MASK(d))
|
||||||
|
|
||||||
|
#ifndef VLIBC_SIGSET_T_DEFINED
|
||||||
|
#define VLIBC_SIGSET_T_DEFINED
|
||||||
|
/*
|
||||||
|
* Signal mask type: a single 64-bit word — the x86_64 Linux sigset_t (see
|
||||||
|
* setjmp.h). pselect() and ppoll() only ever forward a pointer to the
|
||||||
|
* kernel, which reads the word directly. <signal.h> is the canonical POSIX
|
||||||
|
* home for sigset_t and builds the sig* API on this same layout; the
|
||||||
|
* typedef is repeated in <poll.h> under this guard so the two headers stay
|
||||||
|
* consistent.
|
||||||
|
*/
|
||||||
|
typedef unsigned long sigset_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Elapsed time in seconds and microseconds (see select()'s timeout). */
|
||||||
|
struct timeval
|
||||||
|
{
|
||||||
|
time_t tv_sec;
|
||||||
|
suseconds_t tv_usec;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait for readiness on the descriptors marked in readfds, writefds and
|
||||||
|
* exceptfds (each may be NULL), up to nfds descriptors (the highest
|
||||||
|
* descriptor in any set plus one). timeout is an upper bound on the wait; a
|
||||||
|
* NULL timeout blocks indefinitely, { 0, 0 } never blocks. On return each
|
||||||
|
* non-NULL set holds only its ready descriptors. Return the number of ready
|
||||||
|
* descriptors across the sets, 0 on timeout, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like select(), but the timeout is a struct timespec and, when sigmask is
|
||||||
|
* not NULL, the given signal mask is atomically installed for the duration
|
||||||
|
* of the wait (the previous mask is restored before returning).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
||||||
|
const struct timespec *timeout, const sigset_t *sigmask);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VLIBC_SYS_SELECT_H */
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
#ifndef VLIBC_SYS_STAT_H
|
||||||
|
#define VLIBC_SYS_STAT_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — <sys/stat.h>.
|
||||||
|
*
|
||||||
|
* File status: the x86_64 Linux struct stat layout and the S_* mode-bit
|
||||||
|
* macros, plus the stat/mkdir/chmod/utimensat/chown wrapper families
|
||||||
|
* (POSIX.1-2008 base). Every function is an unbuffered pass-through to the
|
||||||
|
* kernel: failures are reported as -1 with errno set by the syscall layer.
|
||||||
|
*
|
||||||
|
* Level 1 (onlyposix): stat, fstat, lstat, fstatat, mkdir, mkdirat,
|
||||||
|
* mkfifo, mkfifoat, chmod, fchmod, fchmodat, umask,
|
||||||
|
* utimensat, futimens, chown, fchown, lchown,
|
||||||
|
* fchownat (Issue 7 moved lchown from XSI to base).
|
||||||
|
* Level 2 (muslmimic): mknod, mknodat (XSI).
|
||||||
|
*
|
||||||
|
* struct stat matches the x86_64 kernel layout exactly (144 bytes), pinned
|
||||||
|
* by static assertions — the same layout src/stdio/stdio.c transcribed
|
||||||
|
* privately as stdio_stat for its fstat-based isatty(). The
|
||||||
|
* st_atime/st_mtime/st_ctime spellings are glibc-style macros onto
|
||||||
|
* st_atim.tv_sec &c for POSIX source compatibility.
|
||||||
|
*
|
||||||
|
* The AT_* flag constants (AT_FDCWD, AT_SYMLINK_NOFOLLOW, ...) belong to
|
||||||
|
* <fcntl.h> (todo 21) and are deliberately not defined here; the flag
|
||||||
|
* arguments below are plain int and take their values from that header.
|
||||||
|
*
|
||||||
|
* None of these declarations carry an intent attribute: every function
|
||||||
|
* performs I/O with side effects and reports failures through errno, so
|
||||||
|
* const/pure would be unsound.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vlibc/features.h>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Provisional struct timespec definition. The canonical home of struct
|
||||||
|
* timespec is <time.h> (todo 41); until it lands, sys/stat.h needs the
|
||||||
|
* definition for the st_*tim members and utimensat/futimens. Todo 41 must
|
||||||
|
* move (or reconcile) this definition — it currently exists nowhere else
|
||||||
|
* (include/threads.h only forward-declares it).
|
||||||
|
*/
|
||||||
|
struct timespec
|
||||||
|
{
|
||||||
|
time_t tv_sec; /* seconds */
|
||||||
|
long tv_nsec; /* nanoseconds (0..999999999) */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File status, x86_64 Linux kernel layout. Field widths are LP64: the
|
||||||
|
* comment column gives the byte offset of each member. The order and the
|
||||||
|
* two padding areas are kernel-ABI facts, not style choices.
|
||||||
|
*/
|
||||||
|
struct stat
|
||||||
|
{
|
||||||
|
dev_t st_dev; /* 0: device containing the file */
|
||||||
|
ino_t st_ino; /* 8: inode number */
|
||||||
|
nlink_t st_nlink; /* 16: hard link count */
|
||||||
|
mode_t st_mode; /* 24: file type + permissions */
|
||||||
|
uid_t st_uid; /* 28: owner user id */
|
||||||
|
gid_t st_gid; /* 32: owner group id */
|
||||||
|
unsigned int st_pad0; /* 36: kernel padding (int __pad0) */
|
||||||
|
dev_t st_rdev; /* 40: device id (if device file) */
|
||||||
|
off_t st_size; /* 48: size in bytes */
|
||||||
|
blksize_t st_blksize; /* 56: preferred I/O block size */
|
||||||
|
blkcnt_t st_blocks; /* 64: 512-byte blocks allocated */
|
||||||
|
struct timespec st_atim; /* 72: last access time */
|
||||||
|
struct timespec st_mtim; /* 88: last modification time */
|
||||||
|
struct timespec st_ctim; /* 104: last status change time */
|
||||||
|
long st_unused[3]; /* 120: kernel padding (__unused) */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Pin the kernel layout: sizeof and the two offsets stdio consumes. */
|
||||||
|
_Static_assert(sizeof(struct stat) == 144, "struct stat must match the x86_64 kernel layout");
|
||||||
|
_Static_assert(offsetof(struct stat, st_mode) == 24, "st_mode must sit at offset 24");
|
||||||
|
_Static_assert(offsetof(struct stat, st_size) == 48, "st_size must sit at offset 48");
|
||||||
|
|
||||||
|
/* File type bits (st_mode & S_IFMT). */
|
||||||
|
#define S_IFMT 0170000 /* type-of-file mask */
|
||||||
|
#define S_IFSOCK 0140000 /* socket */
|
||||||
|
#define S_IFLNK 0120000 /* symbolic link */
|
||||||
|
#define S_IFREG 0100000 /* regular file */
|
||||||
|
#define S_IFBLK 0060000 /* block device */
|
||||||
|
#define S_IFDIR 0040000 /* directory */
|
||||||
|
#define S_IFCHR 0020000 /* character device */
|
||||||
|
#define S_IFIFO 0010000 /* FIFO (named pipe) */
|
||||||
|
|
||||||
|
/* Special permission bits. */
|
||||||
|
#define S_ISUID 04000 /* set-user-id on execution */
|
||||||
|
#define S_ISGID 02000 /* set-group-id on execution */
|
||||||
|
#define S_ISVTX 01000 /* sticky bit (restricted deletion on dirs) */
|
||||||
|
|
||||||
|
/* Owner permission bits. */
|
||||||
|
#define S_IRWXU 0700 /* read, write, execute/search by owner */
|
||||||
|
#define S_IRUSR 0400 /* read permission, owner */
|
||||||
|
#define S_IWUSR 0200 /* write permission, owner */
|
||||||
|
#define S_IXUSR 0100 /* execute/search permission, owner */
|
||||||
|
|
||||||
|
/* Group permission bits. */
|
||||||
|
#define S_IRWXG 070 /* read, write, execute/search by group */
|
||||||
|
#define S_IRGRP 040 /* read permission, group */
|
||||||
|
#define S_IWGRP 020 /* write permission, group */
|
||||||
|
#define S_IXGRP 010 /* execute/search permission, group */
|
||||||
|
|
||||||
|
/* Others permission bits. */
|
||||||
|
#define S_IRWXO 07 /* read, write, execute/search by others */
|
||||||
|
#define S_IROTH 04 /* read permission, others */
|
||||||
|
#define S_IWOTH 02 /* write permission, others */
|
||||||
|
#define S_IXOTH 01 /* execute/search permission, others */
|
||||||
|
|
||||||
|
/* File type predicates over the type bits. */
|
||||||
|
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
|
||||||
|
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
|
||||||
|
#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
|
||||||
|
#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
|
||||||
|
#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
|
||||||
|
#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
|
||||||
|
#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
|
||||||
|
|
||||||
|
/* POSIX compatibility spellings of the struct timespec members. */
|
||||||
|
#define st_atime st_atim.tv_sec
|
||||||
|
#define st_mtime st_mtim.tv_sec
|
||||||
|
#define st_ctime st_ctim.tv_sec
|
||||||
|
|
||||||
|
/* Level 1 (POSIX base). */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Store the status of the file named by path into buf. Follows symbolic
|
||||||
|
* links; return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
stat(const char *path, struct stat *buf);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Store the status of the file descriptor fd into buf; return 0, or -1
|
||||||
|
* with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fstat(int fd, struct stat *buf);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like stat(), but a symbolic link is reported itself, not its target;
|
||||||
|
* return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
lstat(const char *path, struct stat *buf);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like stat(), but path is relative to the directory named by fd (use
|
||||||
|
* AT_FDCWD from <fcntl.h> for the current working directory); flag may
|
||||||
|
* hold AT_SYMLINK_NOFOLLOW to report the link itself. Return 0, or -1
|
||||||
|
* with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fstatat(int fd, const char *path, struct stat *buf, int flag);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the directory named by path with the access mode mode (masked by
|
||||||
|
* the process umask); return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
mkdir(const char *path, mode_t mode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like mkdir(), but path is relative to the directory named by fd (use
|
||||||
|
* AT_FDCWD for the current working directory); return 0, or -1 with errno
|
||||||
|
* set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
mkdirat(int fd, const char *path, mode_t mode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the FIFO (named pipe) named by path with mode; return 0, or -1
|
||||||
|
* with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
mkfifo(const char *path, mode_t mode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like mkfifo(), but path is relative to the directory named by fd (use
|
||||||
|
* AT_FDCWD for the current working directory); return 0, or -1 with errno
|
||||||
|
* set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
mkfifoat(int fd, const char *path, mode_t mode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change the access mode of the file named by path to mode; return 0, or
|
||||||
|
* -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
chmod(const char *path, mode_t mode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change the access mode of the file descriptor fd to mode; return 0, or
|
||||||
|
* -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fchmod(int fd, mode_t mode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like chmod(), but path is relative to the directory named by fd (use
|
||||||
|
* AT_FDCWD for the current working directory); flag may hold
|
||||||
|
* AT_SYMLINK_NOFOLLOW. Return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fchmodat(int fd, const char *path, mode_t mode, int flag);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the process file-mode creation mask to cmask and return the previous
|
||||||
|
* mask. Never fails.
|
||||||
|
*/
|
||||||
|
mode_t
|
||||||
|
umask(mode_t cmask);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the access and modification times of the file named by path
|
||||||
|
* (relative to fd, or AT_FDCWD) to times[0] (access) and times[1]
|
||||||
|
* (modification). A NULL times sets both to the current time; a tv_nsec
|
||||||
|
* of UTIME_NOW/UTIME_OMIT (from <time.h>) selects per-member behavior.
|
||||||
|
* Return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
utimensat(int fd, const char *path, const struct timespec times[2], int flag);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like utimensat() on the file descriptor fd (the path is implicit);
|
||||||
|
* return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
futimens(int fd, const struct timespec times[2]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change the owner and group of the file named by path to owner/group
|
||||||
|
* (a value of (uid_t)-1 leaves the current one unchanged). Follows
|
||||||
|
* symbolic links; return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
chown(const char *path, uid_t owner, gid_t group);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change the owner and group of the file descriptor fd; return 0, or -1
|
||||||
|
* with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fchown(int fd, uid_t owner, gid_t group);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like chown(), but a symbolic link is changed itself, not its target;
|
||||||
|
* return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
lchown(const char *path, uid_t owner, gid_t group);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like chown(), but path is relative to the directory named by fd (use
|
||||||
|
* AT_FDCWD for the current working directory); flag may hold
|
||||||
|
* AT_SYMLINK_NOFOLLOW. Return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fchownat(int fd, const char *path, uid_t owner, gid_t group, int flag);
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
/* Level 2 (muslmimic): XSI. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a special file named by path with mode (a file-type bit like
|
||||||
|
* S_IFIFO or S_IFCHR must be set) and device id dev; return 0, or -1 with
|
||||||
|
* errno set. Creating device nodes requires privilege. XSI.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
mknod(const char *path, mode_t mode, dev_t dev);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like mknod(), but path is relative to the directory named by fd (use
|
||||||
|
* AT_FDCWD for the current working directory); return 0, or -1 with errno
|
||||||
|
* set. XSI.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
mknodat(int fd, const char *path, mode_t mode, dev_t dev);
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VLIBC_SYS_STAT_H */
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
#ifndef VLIBC_SYS_WAIT_H
|
||||||
|
#define VLIBC_SYS_WAIT_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — <sys/wait.h>.
|
||||||
|
*
|
||||||
|
* Child-process status collection (POSIX.1-2008) and the wait status macros.
|
||||||
|
* Everything here is a pass-through to the kernel: wait/waitpid/wait3/wait4
|
||||||
|
* ride SYS_wait4, waitid rides SYS_waitid, and failures are reported as -1
|
||||||
|
* (or the child pid on success) with errno set by the syscall layer.
|
||||||
|
*
|
||||||
|
* Level 1 (onlyposix): wait, waitpid, waitid + the W* status macros.
|
||||||
|
* Level 2 (muslmimic): wait3, wait4 (XSI).
|
||||||
|
*
|
||||||
|
* The W* decode macros follow the kernel's wait status encoding: bits 0-6
|
||||||
|
* hold the terminating signal (or 0x7f for a stop), bit 7 the core-dump
|
||||||
|
* flag, bits 8-15 the exit status, and the word 0xffff marks a continued
|
||||||
|
* child. WEXITED/WSTOPPED/WNOWAIT are the waitid-only option bits (XSI;
|
||||||
|
* kernel-identical values) and WCOREDUMP is an XSI/Linux extension (bit
|
||||||
|
* 0x80) kept alongside the POSIX set for source compatibility.
|
||||||
|
*
|
||||||
|
* None of the declarations carry an intent attribute: every function has
|
||||||
|
* kernel-visible side effects and reports failures through errno.
|
||||||
|
*
|
||||||
|
* <signal.h> does not exist yet (a later todo owns it). The minimal
|
||||||
|
* siginfo_t below and the CLD_* constants live here under guards so that
|
||||||
|
* header can take them over without conflict; see the notes by each.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vlibc/features.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Selector type for waitid(). POSIX defines idtype_t as an integer type and
|
||||||
|
* places it in <sys/types.h>, which does not define it yet; it is provided
|
||||||
|
* here under a VLIBC_ guard so sys/types.h can take it over later without a
|
||||||
|
* redefinition. int matches the kernel's `which` argument on x86_64.
|
||||||
|
*/
|
||||||
|
#ifndef VLIBC_DEFINED_IDTYPE_T
|
||||||
|
#define VLIBC_DEFINED_IDTYPE_T
|
||||||
|
typedef int idtype_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* waitid() idtype selectors (kernel-identical). */
|
||||||
|
#define P_ALL 0 /* wait for any child */
|
||||||
|
#define P_PID 1 /* wait for the specific child */
|
||||||
|
#define P_PGID 2 /* wait for any child in the process group */
|
||||||
|
|
||||||
|
/* wait/waitpid option bits (kernel-identical). */
|
||||||
|
#define WNOHANG 1 /* do not block; return 0 if no child has exited */
|
||||||
|
#define WUNTRACED 2 /* also report stopped children */
|
||||||
|
#define WCONTINUED 8 /* also report continued children */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* waitid-only option bits (kernel-identical; XSI). WSTOPPED shares its
|
||||||
|
* value with WUNTRACED, so the two names are interchangeable where both
|
||||||
|
* apply.
|
||||||
|
*/
|
||||||
|
#define WEXITED 4 /* wait for exited children */
|
||||||
|
#define WSTOPPED 2 /* wait for stopped children */
|
||||||
|
#define WNOWAIT 0x01000000 /* report but do not reap */
|
||||||
|
|
||||||
|
/* Status decode macros. The argument is the raw wait status word. */
|
||||||
|
|
||||||
|
/* True when the child terminated normally via exit() or _exit(). */
|
||||||
|
#define WIFEXITED(s) (((s) & 0x7f) == 0)
|
||||||
|
|
||||||
|
/* Exit status of a normally terminated child (WIFEXITED true). */
|
||||||
|
#define WEXITSTATUS(s) (((s) & 0xff00) >> 8)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* True when the child was killed by a signal. The 0x7f exclusion keeps the
|
||||||
|
* stop code (0x7f) from being misread as a terminating signal.
|
||||||
|
*/
|
||||||
|
#define WIFSIGNALED(s) (((s) & 0x7f) != 0 && ((s) & 0x7f) != 0x7f)
|
||||||
|
|
||||||
|
/* Number of the signal that killed the child (WIFSIGNALED true). */
|
||||||
|
#define WTERMSIG(s) ((s) & 0x7f)
|
||||||
|
|
||||||
|
/* True when the child is stopped by a signal (WUNTRACED). */
|
||||||
|
#define WIFSTOPPED(s) (((s) & 0xff) == 0x7f)
|
||||||
|
|
||||||
|
/* Number of the signal that stopped the child (WIFSTOPPED true). */
|
||||||
|
#define WSTOPSIG(s) WEXITSTATUS(s)
|
||||||
|
|
||||||
|
/* True when the child was resumed by SIGCONT (WCONTINUED). */
|
||||||
|
#define WIFCONTINUED(s) ((s) == 0xffff)
|
||||||
|
|
||||||
|
/* True when the killed child dumped core (XSI/Linux extension, bit 0x80). */
|
||||||
|
#define WCOREDUMP(s) (((s) & 0x80) != 0)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* si_code values reported by waitid() (kernel-identical). POSIX defines
|
||||||
|
* these in <signal.h>, which does not exist yet; guarded per name so the
|
||||||
|
* future signal.h can define them without a redefinition warning.
|
||||||
|
*/
|
||||||
|
#ifndef CLD_EXITED
|
||||||
|
#define CLD_EXITED 1 /* child exited normally */
|
||||||
|
#define CLD_KILLED 2 /* child killed by a signal */
|
||||||
|
#define CLD_DUMPED 3 /* child killed by a signal and dumped core */
|
||||||
|
#define CLD_TRAPPED 4 /* child stopped by a trace event */
|
||||||
|
#define CLD_STOPPED 5 /* child stopped by a signal */
|
||||||
|
#define CLD_CONTINUED 6 /* child resumed by SIGCONT */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Minimal siginfo_t, sized and laid out to match the kernel's x86_64
|
||||||
|
* siginfo_t (128 bytes) for the fields waitid() fills: si_signo, si_errno,
|
||||||
|
* si_code at offsets 0/4/8, then the wait-fields si_pid/si_uid/si_status at
|
||||||
|
* offsets 16/20/24. The union is 8-aligned (si_utime/si_stime are 8-byte
|
||||||
|
* clock_t in both the kernel and glibc layouts), so it starts at offset 16
|
||||||
|
* with implicit padding after si_code. The kernel copies the full 128 bytes,
|
||||||
|
* so the size must stay 128; the pad member guarantees it and the static
|
||||||
|
* asserts pin the layout. The signal-handling fields (si_addr, si_value,
|
||||||
|
* timers, ...) are deliberately absent — the future <signal.h> owns the
|
||||||
|
* complete siginfo_t and must reconcile this guard.
|
||||||
|
*/
|
||||||
|
#ifndef VLIBC_INTERNAL_SIGINFO_DEFINED
|
||||||
|
#define VLIBC_INTERNAL_SIGINFO_DEFINED
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int si_signo;
|
||||||
|
int si_errno;
|
||||||
|
int si_code;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
pid_t si_pid; /* 16 */
|
||||||
|
uid_t si_uid; /* 20 */
|
||||||
|
int si_status; /* 24 */
|
||||||
|
long si_utime; /* 32 — 8-byte clock_t, matches the kernel/glibc ABI */
|
||||||
|
long si_stime; /* 40 */
|
||||||
|
};
|
||||||
|
int vlibc_siginfo_pad[28]; /* union sized 112 so the struct stays 128 */
|
||||||
|
};
|
||||||
|
} siginfo_t;
|
||||||
|
|
||||||
|
_Static_assert(sizeof(siginfo_t) == 128, "siginfo_t must match the kernel size");
|
||||||
|
_Static_assert(offsetof(siginfo_t, si_pid) == 16, "si_pid must sit at offset 16");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Level 1 (POSIX base). */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait for any child to terminate or stop and store its status in
|
||||||
|
* *stat_loc (NULL skips the store); return the child pid, or -1 with
|
||||||
|
* errno set. Equivalent to waitpid(-1, stat_loc, 0).
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
wait(int *stat_loc);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait for the child identified by pid (-1: any child, 0: any child in the
|
||||||
|
* calling process group, < -1: any child in the process group -pid) and
|
||||||
|
* store its status in *stat_loc (NULL skips the store). options are the
|
||||||
|
* WNOHANG/WUNTRACED/WCONTINUED bits. Return the child pid, 0 when WNOHANG
|
||||||
|
* found nothing, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
waitpid(pid_t pid, int *stat_loc, int options);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait for a child selected by idtype/id (P_ALL, P_PID, P_PGID) and fill
|
||||||
|
* *infop with the siginfo details (si_pid, si_uid, si_status and a CLD_*
|
||||||
|
* si_code); options are the WEXITED/WSTOPPED/WCONTINUED/WNOHANG/WNOWAIT
|
||||||
|
* bits. Return 0, or -1 with errno set. infop must point to at least
|
||||||
|
* 128 bytes (the kernel writes a full siginfo).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
/* Level 2 (muslmimic): XSI. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* struct rusage is defined by <sys/resource.h>, which does not exist yet;
|
||||||
|
* a forward declaration is enough to pass a pointer through to the kernel.
|
||||||
|
*/
|
||||||
|
struct rusage;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like waitpid(-1, stat_loc, options), and additionally store resource
|
||||||
|
* usage in *rusage (NULL skips the store); return the child pid, or -1
|
||||||
|
* with errno set. XSI.
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
wait3(int *stat_loc, int options, struct rusage *rusage);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like waitpid(), and additionally store resource usage in *rusage (NULL
|
||||||
|
* skips the store); return the child pid, or -1 with errno set. XSI.
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
wait4(pid_t pid, int *stat_loc, int options, struct rusage *rusage);
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VLIBC_SYS_WAIT_H */
|
||||||
@@ -0,0 +1,379 @@
|
|||||||
|
#ifndef VLIBC_UNISTD_H
|
||||||
|
#define VLIBC_UNISTD_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — <unistd.h>.
|
||||||
|
*
|
||||||
|
* File descriptors, file I/O, and the access/whence symbolic constants
|
||||||
|
* (POSIX.1-2008). Every function here is an unbuffered pass-through to the
|
||||||
|
* kernel: failures are reported as -1 (or the fd/offset on success) with
|
||||||
|
* errno set by the syscall layer.
|
||||||
|
*
|
||||||
|
* Level 1 (onlyposix): read, write, pread, pwrite, open, openat, close,
|
||||||
|
* lseek, dup, dup2, pipe, fsync, fdatasync, ftruncate,
|
||||||
|
* sync, access, faccessat.
|
||||||
|
* Level 2 (muslmimic): dup3, pipe2 (Linux extensions), truncate (XSI),
|
||||||
|
* lseek64 (glibc LFS alias of lseek on x86_64).
|
||||||
|
*
|
||||||
|
* Process control (todo 20) is declared further down:
|
||||||
|
*
|
||||||
|
* Level 1 (onlyposix): fork, exec family (execl/execlp/execle/execv/
|
||||||
|
* execvp/execve/fexecve), getpid, getppid, getuid,
|
||||||
|
* geteuid, getgid, getegid, setuid, seteuid, setgid,
|
||||||
|
* setegid, getgroups, setpgid, getpgrp, setsid.
|
||||||
|
* Level 2 (muslmimic): vfork, setpgrp (obsolescent), setgroups, getpgid,
|
||||||
|
* getsid, tcgetpgrp, tcsetpgrp (XSI).
|
||||||
|
*
|
||||||
|
* The open-flag constants (O_RDONLY, O_CREAT, O_CLOEXEC, ...) belong to
|
||||||
|
* <fcntl.h> and are deliberately not defined here; the oflag arguments below
|
||||||
|
* are plain int and take their values from that header. The optional mode
|
||||||
|
* argument of open/openat is a mode_t supplied only when oflag contains
|
||||||
|
* O_CREAT or O_TMPFILE.
|
||||||
|
*
|
||||||
|
* None of these declarations carry an intent attribute: every function
|
||||||
|
* performs I/O with side effects and reports failures through errno, so
|
||||||
|
* const/pure would be unsound. The process functions additionally must not
|
||||||
|
* be const/pure because their results are process state — marking getpid
|
||||||
|
* const, for example, would let the compiler hoist it across fork() and
|
||||||
|
* observe the parent's pid inside the child.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vlibc/features.h>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Level 1 (POSIX base). */
|
||||||
|
|
||||||
|
/* Access-check modes for access() and faccessat(). */
|
||||||
|
#define F_OK 0 /* existence only */
|
||||||
|
#define X_OK 1 /* execute (search for a directory) */
|
||||||
|
#define W_OK 2 /* write */
|
||||||
|
#define R_OK 4 /* read */
|
||||||
|
|
||||||
|
/* whence values for lseek() and lseek64() (also defined by <stdio.h>). */
|
||||||
|
#define SEEK_SET 0 /* from the beginning of the file */
|
||||||
|
#define SEEK_CUR 1 /* from the current position */
|
||||||
|
#define SEEK_END 2 /* from the end of the file */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read up to nbyte bytes from fildes into buf and return the number of
|
||||||
|
* bytes read, 0 at end of file, or -1 with errno set on error. Unbuffered.
|
||||||
|
*/
|
||||||
|
ssize_t
|
||||||
|
read(int fildes, void *buf, size_t nbyte);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write up to nbyte bytes from buf to fildes and return the number of
|
||||||
|
* bytes written, or -1 with errno set on error. Unbuffered.
|
||||||
|
*/
|
||||||
|
ssize_t
|
||||||
|
write(int fildes, const void *buf, size_t nbyte);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read nbyte bytes from fildes starting at offset, without changing the
|
||||||
|
* file position; return the number of bytes read, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
ssize_t
|
||||||
|
pread(int fildes, void *buf, size_t nbyte, off_t offset);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write nbyte bytes from buf to fildes starting at offset, without
|
||||||
|
* changing the file position; return the number of bytes written, or -1
|
||||||
|
* with errno set.
|
||||||
|
*/
|
||||||
|
ssize_t
|
||||||
|
pwrite(int fildes, const void *buf, size_t nbyte, off_t offset);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Open path with the oflag access mode (from <fcntl.h>) and return a file
|
||||||
|
* descriptor, or -1 with errno set. A mode argument is required — and read
|
||||||
|
* from the varargs — only when oflag contains O_CREAT or O_TMPFILE.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
open(const char *path, int oflag, ...);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like open(), but path is relative to the directory named by fd (use
|
||||||
|
* AT_FDCWD from <fcntl.h> for the current working directory). The mode
|
||||||
|
* varargs rule is the same as open().
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
openat(int fd, const char *path, int oflag, ...);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Close the file descriptor fildes; return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
close(int fildes);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reposition the file offset of fildes to offset per whence (SEEK_SET,
|
||||||
|
* SEEK_CUR, SEEK_END) and return the resulting offset, or (off_t)-1 with
|
||||||
|
* errno set. The full 64-bit offset is returned; errno is untouched on
|
||||||
|
* success.
|
||||||
|
*/
|
||||||
|
off_t
|
||||||
|
lseek(int fildes, off_t offset, int whence);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Duplicate fildes to the lowest-numbered free descriptor; return it, or
|
||||||
|
* -1 with errno set. The copy shares the file description (position,
|
||||||
|
* flags, locks) with the original.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
dup(int fildes);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Duplicate fildes onto fildes2, closing fildes2 first if it was open;
|
||||||
|
* return fildes2, or -1 with errno set. dup2(f, f) returns f without
|
||||||
|
* doing anything (POSIX).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
dup2(int fildes, int fildes2);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a pipe: fildes[0] becomes the read end, fildes[1] the write end.
|
||||||
|
* Return 0, or -1 with errno set. No descriptor flags are set (unlike
|
||||||
|
* pipe2, this is plain POSIX).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
pipe(int fildes[2]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Flush all buffered modifications of fildes and its metadata to stable
|
||||||
|
* storage; return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fsync(int fildes);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like fsync(), but may skip the metadata work needed only to preserve
|
||||||
|
* file contents; return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fdatasync(int fildes);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Truncate fildes to length bytes; return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
ftruncate(int fildes, off_t length);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Flush all filesystem caches to stable storage. Returns nothing.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
sync(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check accessibility of path under amode (R_OK, W_OK, X_OK, F_OK); return
|
||||||
|
* 0, or -1 with errno set. Uses the real IDs of the calling process.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
access(const char *path, int amode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like access(), but path is relative to the directory named by fd (use
|
||||||
|
* AT_FDCWD for the current working directory) and flag may hold
|
||||||
|
* AT_EACCESS; return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
faccessat(int fd, const char *path, int amode, int flag);
|
||||||
|
|
||||||
|
/* Level 1 (POSIX base): process control. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a child process that is a copy of the caller: fork() returns 0
|
||||||
|
* in the child, the child's pid in the parent, and -1 with errno set on
|
||||||
|
* failure.
|
||||||
|
*
|
||||||
|
* Around the fork, the atfork hook table runs: the prepare handlers in
|
||||||
|
* reverse registration order before the fork, then the child handlers in
|
||||||
|
* the child and the parent handlers in the parent, both in registration
|
||||||
|
* order (POSIX pthread_atfork protocol). The table is registered by
|
||||||
|
* pthread_atfork() (todo 45) and is empty — a no-op — in a process
|
||||||
|
* without threads.
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
fork(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Replace the calling process image. Success never returns; -1 with
|
||||||
|
* errno set otherwise. execl/execle/execlp take the arguments as a
|
||||||
|
* varargs list terminated by (char *)NULL (execle is followed by one
|
||||||
|
* final char *const envp[] argument); execv/execvp take an argv array.
|
||||||
|
* execvp and execlp search the PATH environment variable (default
|
||||||
|
* "/bin:/usr/bin") when file contains no '/'. execv/execvp/execl/execlp
|
||||||
|
* use the caller's environment; execve/execle/fexecve take envp.
|
||||||
|
*/
|
||||||
|
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
|
||||||
|
int
|
||||||
|
execl(const char *path, const char *arg0, ...);
|
||||||
|
|
||||||
|
int
|
||||||
|
execle(const char *path, const char *arg0, ...);
|
||||||
|
|
||||||
|
int
|
||||||
|
execlp(const char *file, const char *arg0, ...);
|
||||||
|
// NOLINTEND(bugprone-easily-swappable-parameters)
|
||||||
|
|
||||||
|
int
|
||||||
|
execv(const char *path, char *const argv[]);
|
||||||
|
|
||||||
|
int
|
||||||
|
execvp(const char *file, char *const argv[]);
|
||||||
|
|
||||||
|
int
|
||||||
|
execve(const char *path, char *const argv[], char *const envp[]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like execve, but the image is the open descriptor fd.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fexecve(int fd, char *const argv[], char *const envp[]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Process ids. getpid/getppid/getuid/geteuid/getgid/getegid read the
|
||||||
|
* kernel's per-process ids; setuid/seteuid/setgid/setegid change them.
|
||||||
|
* seteuid/setegid change only the effective id (setresuid/setresgid
|
||||||
|
* underneath: Linux has no seteuid/setegid syscall).
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
getpid(void);
|
||||||
|
|
||||||
|
pid_t
|
||||||
|
getppid(void);
|
||||||
|
|
||||||
|
uid_t
|
||||||
|
getuid(void);
|
||||||
|
|
||||||
|
uid_t
|
||||||
|
geteuid(void);
|
||||||
|
|
||||||
|
gid_t
|
||||||
|
getgid(void);
|
||||||
|
|
||||||
|
gid_t
|
||||||
|
getegid(void);
|
||||||
|
|
||||||
|
int
|
||||||
|
setuid(uid_t uid);
|
||||||
|
|
||||||
|
int
|
||||||
|
seteuid(uid_t euid);
|
||||||
|
|
||||||
|
int
|
||||||
|
setgid(gid_t gid);
|
||||||
|
|
||||||
|
int
|
||||||
|
setegid(gid_t egid);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Supplementary groups: getgroups fills grouplist with up to gidsetsize
|
||||||
|
* group ids and returns the total count (0 can be passed to count only);
|
||||||
|
* setgroups (level 2, XSI) installs the list.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
getgroups(int gidsetsize, gid_t grouplist[]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Session control: setpgid moves pid into process group pgid (0 means
|
||||||
|
* the caller / the caller's pid); getpgrp returns the caller's process
|
||||||
|
* group; setsid creates a new session with the caller as leader and
|
||||||
|
* returns the new session id.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
setpgid(pid_t pid, pid_t pgid);
|
||||||
|
|
||||||
|
pid_t
|
||||||
|
getpgrp(void);
|
||||||
|
|
||||||
|
pid_t
|
||||||
|
setsid(void);
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
/* Level 2 (muslmimic): Linux extensions + XSI + obsolescent. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like dup2(), but with descriptor flags (O_CLOEXEC from <fcntl.h>) applied
|
||||||
|
* atomically; return fildes2, or -1 with errno set. Linux-specific.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
dup3(int fildes, int fildes2, int flags);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like pipe(), but with descriptor flags (e.g. O_CLOEXEC) applied
|
||||||
|
* atomically; return 0, or -1 with errno set. Linux-specific.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
pipe2(int fildes[2], int flags);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Truncate the file named by path to length bytes; return 0, or -1 with
|
||||||
|
* errno set. XSI.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
truncate(const char *path, off_t length);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* glibc LFS alias of lseek(): on x86_64 the LFS and non-LFS off_t are
|
||||||
|
* identical (both 64-bit), so this simply calls lseek(). Provided for
|
||||||
|
* source compatibility only.
|
||||||
|
*/
|
||||||
|
off_t
|
||||||
|
lseek64(int fildes, off_t offset, int whence);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vfork (obsolescent): like fork(), but the child borrows the parent's
|
||||||
|
* address space until it execs or exits.
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
vfork(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* setpgrp (obsolescent): setpgid(0, 0) — the caller becomes the leader
|
||||||
|
* of its own process group.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
setpgrp(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* setgroups (XSI): install the supplementary group list of gidsetsize
|
||||||
|
* entries. Requires privilege.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
setgroups(size_t gidsetsize, const gid_t *grouplist);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getpgid/getsid (XSI): the process group / session of pid (0 = the
|
||||||
|
* caller).
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
getpgid(pid_t pid);
|
||||||
|
|
||||||
|
pid_t
|
||||||
|
getsid(pid_t pid);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Controlling-terminal foreground process group (XSI): tcgetpgrp reads
|
||||||
|
* the foreground group of the terminal on fildes; tcsetpgrp makes pgid
|
||||||
|
* the foreground group.
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
tcgetpgrp(int fildes);
|
||||||
|
|
||||||
|
int
|
||||||
|
tcsetpgrp(int fildes, pid_t pgid);
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VLIBC_UNISTD_H */
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* alphasort (todo 24, XSI): the scandir comparator that orders entries by
|
||||||
|
* strcmp on their names. Receives two pointers to the array's struct
|
||||||
|
* dirent pointers.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
alphasort(const struct dirent **a, const struct dirent **b)
|
||||||
|
{
|
||||||
|
return strcmp((*a)->d_name, (*b)->d_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
#include "../internal/malloc.h"
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
#include "dirent_impl.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* closedir (todo 24): release the descriptor and the stream storage. The
|
||||||
|
* descriptor is closed first so a close failure still reports -1, but the
|
||||||
|
* stream is freed either way (its errno is preserved across the free).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
closedir(DIR *dir)
|
||||||
|
{
|
||||||
|
struct vlibc_DIR *d = dir;
|
||||||
|
int fd = d->fd;
|
||||||
|
int r = syscall_ret(__syscall1(SYS_close, fd));
|
||||||
|
|
||||||
|
__libc_free(d);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#ifndef VLIBC_DIRENT_INTERNAL_H
|
||||||
|
#define VLIBC_DIRENT_INTERNAL_H
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — internal directory-stream state (todo 24).
|
||||||
|
*
|
||||||
|
* struct vlibc_DIR is the completion of the opaque DIR handle from
|
||||||
|
* <dirent.h>. readdir parses raw getdents64 records out of buf; the kernel
|
||||||
|
* fills buf with whole records and advances its own per-fd directory
|
||||||
|
* offset, so sequential iteration needs no lseek. seekdir/telldir hand the
|
||||||
|
* kernel's per-record d_off cookie (the offset of the NEXT entry) back and
|
||||||
|
* forth via lseek: d->de.d_off therefore always holds the resume point
|
||||||
|
* after the entry most recently returned by readdir.
|
||||||
|
*/
|
||||||
|
#define VLIBC_DIRENT_BUFSZ 2048
|
||||||
|
|
||||||
|
struct vlibc_DIR
|
||||||
|
{
|
||||||
|
int fd; /* directory descriptor, owned by the stream */
|
||||||
|
size_t buf_pos; /* next unparsed byte within buf */
|
||||||
|
size_t buf_end; /* first unused byte of buf */
|
||||||
|
struct dirent de; /* storage for the entry readdir returns */
|
||||||
|
char buf[VLIBC_DIRENT_BUFSZ];
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The kernel-side record produced by SYS_getdents64, transcribed as the
|
||||||
|
* x86_64 linux_dirent64 layout: ino at 0, off at 8, reclen at 16, type at
|
||||||
|
* 18, name at 19. The 256-byte name array makes the struct exactly the
|
||||||
|
* size of a maximal record: 19 + (255-name + NUL) = 275, padded by struct
|
||||||
|
* alignment to 280, which is also the largest d_reclen the kernel emits
|
||||||
|
* (every record length is rounded up to a multiple of 8). readdir memcpy's
|
||||||
|
* a whole record into this struct before reading the fields, so no
|
||||||
|
* unaligned or aliasing access ever happens.
|
||||||
|
*/
|
||||||
|
struct vlibc_linux_dirent64
|
||||||
|
{
|
||||||
|
ino_t d_ino;
|
||||||
|
off_t d_off;
|
||||||
|
unsigned short d_reclen;
|
||||||
|
unsigned char d_type;
|
||||||
|
char d_name[256];
|
||||||
|
};
|
||||||
|
|
||||||
|
_Static_assert(sizeof(struct vlibc_linux_dirent64) == 280,
|
||||||
|
"linux_dirent64 with a 256-byte name must be 280 bytes");
|
||||||
|
_Static_assert(offsetof(struct vlibc_linux_dirent64, d_name) == 19,
|
||||||
|
"linux_dirent64 name must sit at offset 19");
|
||||||
|
_Static_assert(offsetof(struct dirent, d_name) ==
|
||||||
|
offsetof(struct vlibc_linux_dirent64, d_name),
|
||||||
|
"dirent and linux_dirent64 must share the name offset");
|
||||||
|
|
||||||
|
#endif /* VLIBC_DIRENT_INTERNAL_H */
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
#include "dirent_impl.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* dirfd (todo 24): return the descriptor the stream reads through. The
|
||||||
|
* descriptor stays owned by the stream; closedir closes it.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
dirfd(DIR *dir)
|
||||||
|
{
|
||||||
|
struct vlibc_DIR *d = dir;
|
||||||
|
|
||||||
|
return d->fd;
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "../internal/malloc.h"
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
#include "dirent_impl.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* fdopendir (todo 24): validate the descriptor with SYS_fstat (POSIX
|
||||||
|
* requires the fd to name a directory), then allocate the stream state.
|
||||||
|
* On failure the descriptor is NOT closed — it stays owned by the caller,
|
||||||
|
* exactly as passed in.
|
||||||
|
*/
|
||||||
|
DIR *
|
||||||
|
fdopendir(int fd)
|
||||||
|
{
|
||||||
|
struct stat st;
|
||||||
|
DIR *d;
|
||||||
|
|
||||||
|
if (syscall_ret(__syscall2(SYS_fstat, fd, (long)&st)) < 0)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (!S_ISDIR(st.st_mode))
|
||||||
|
{
|
||||||
|
errno = ENOTDIR;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
d = __libc_malloc(sizeof *d);
|
||||||
|
if (d == NULL)
|
||||||
|
{
|
||||||
|
errno = ENOMEM;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
d->fd = fd;
|
||||||
|
d->buf_pos = 0;
|
||||||
|
d->buf_end = 0;
|
||||||
|
d->de.d_off = 0;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
#include "dirent_impl.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* opendir (todo 24): open the directory via SYS_openat with O_DIRECTORY so
|
||||||
|
* the kernel rejects non-directories with ENOTDIR before any stream state
|
||||||
|
* exists, then hand the descriptor to fdopendir (which re-validates with
|
||||||
|
* fstat). On fdopendir failure the descriptor is closed again: opendir
|
||||||
|
* never leaks.
|
||||||
|
*/
|
||||||
|
DIR *
|
||||||
|
opendir(const char *path)
|
||||||
|
{
|
||||||
|
int fd = syscall_ret(
|
||||||
|
__syscall3(SYS_openat, AT_FDCWD, (long)path, O_RDONLY | O_DIRECTORY));
|
||||||
|
DIR *d;
|
||||||
|
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
d = fdopendir(fd);
|
||||||
|
if (d == NULL)
|
||||||
|
{
|
||||||
|
syscall_ret(__syscall1(SYS_close, fd));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
#include "dirent_impl.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* readdir (todo 24): expose the entries the kernel wrote into the stream
|
||||||
|
* buffer by SYS_getdents64, one per call. When the buffer is exhausted a
|
||||||
|
* fresh getdents64 continues at the kernel's per-fd directory offset (no
|
||||||
|
* lseek needed for sequential iteration). A zero-length result is end of
|
||||||
|
* directory: NULL is returned with errno untouched. Each record is copied
|
||||||
|
* into the stream's own struct dirent, so the returned pointer stays valid
|
||||||
|
* until the next call on the same stream.
|
||||||
|
*/
|
||||||
|
struct dirent *
|
||||||
|
readdir(DIR *dir)
|
||||||
|
{
|
||||||
|
struct vlibc_DIR *d = dir;
|
||||||
|
struct vlibc_linux_dirent64 k;
|
||||||
|
unsigned short reclen;
|
||||||
|
size_t namelen;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
if (d->buf_pos >= d->buf_end)
|
||||||
|
{
|
||||||
|
long r =
|
||||||
|
__syscall3(SYS_getdents64, d->fd, (long)d->buf,
|
||||||
|
(long)sizeof(d->buf));
|
||||||
|
|
||||||
|
if (r <= 0)
|
||||||
|
{
|
||||||
|
syscall_ret(r);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
d->buf_pos = 0;
|
||||||
|
d->buf_end = (size_t)r;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d->buf_end - d->buf_pos < offsetof(struct vlibc_linux_dirent64, d_name))
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
memcpy(&reclen,
|
||||||
|
d->buf + d->buf_pos + offsetof(struct vlibc_linux_dirent64, d_reclen),
|
||||||
|
sizeof reclen);
|
||||||
|
if (reclen < offsetof(struct vlibc_linux_dirent64, d_name) + 2 ||
|
||||||
|
reclen > sizeof k)
|
||||||
|
{
|
||||||
|
/* The kernel always emits whole well-formed records; a broken
|
||||||
|
* length would otherwise walk off the buffer. Stop the scan. */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
memcpy(&k, d->buf + d->buf_pos, reclen);
|
||||||
|
d->buf_pos += reclen;
|
||||||
|
|
||||||
|
d->de.d_ino = k.d_ino;
|
||||||
|
d->de.d_off = k.d_off;
|
||||||
|
d->de.d_reclen = reclen;
|
||||||
|
d->de.d_type = k.d_type;
|
||||||
|
namelen = (size_t)reclen - offsetof(struct vlibc_linux_dirent64, d_name);
|
||||||
|
if (namelen >= sizeof(d->de.d_name))
|
||||||
|
{
|
||||||
|
namelen = sizeof(d->de.d_name) - 1;
|
||||||
|
}
|
||||||
|
memcpy(d->de.d_name, k.d_name, namelen);
|
||||||
|
d->de.d_name[namelen] = '\0';
|
||||||
|
return &d->de;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* readdir_r (todo 24, obsolescent): reentrant readdir. The result is
|
||||||
|
* copied into the caller's buf and *result points at it; at end of
|
||||||
|
* directory *result is NULL. Errors are reported as the return value
|
||||||
|
* (never through errno), and errno is preserved across successful calls.
|
||||||
|
* readdir_r is distinguished from end-of-directory by checking whether
|
||||||
|
* readdir moved errno.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
readdir_r(DIR *restrict dir, struct dirent *restrict buf,
|
||||||
|
struct dirent **restrict result)
|
||||||
|
{
|
||||||
|
struct dirent *de;
|
||||||
|
int saved = errno;
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
de = readdir(dir);
|
||||||
|
if (de != NULL)
|
||||||
|
{
|
||||||
|
memcpy(buf, de, sizeof *buf);
|
||||||
|
*result = buf;
|
||||||
|
errno = saved;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (errno != 0)
|
||||||
|
{
|
||||||
|
return errno;
|
||||||
|
}
|
||||||
|
errno = saved;
|
||||||
|
*result = NULL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
#include "dirent_impl.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* rewinddir (todo 24): return the stream to the start of the directory.
|
||||||
|
* SYS_lseek back to offset 0 resets the kernel's per-fd directory
|
||||||
|
* position; the cached records (if any) are dropped and the stored seek
|
||||||
|
* cookie is reset, so the next readdir refills from the first entry.
|
||||||
|
* rewinddir has no error return; a failed lseek leaves the stream
|
||||||
|
* positioned wherever the kernel is.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
rewinddir(DIR *dir)
|
||||||
|
{
|
||||||
|
struct vlibc_DIR *d = dir;
|
||||||
|
|
||||||
|
syscall_ret(__syscall3(SYS_lseek, d->fd, 0, SEEK_SET));
|
||||||
|
d->buf_pos = 0;
|
||||||
|
d->buf_end = 0;
|
||||||
|
d->de.d_off = 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "../internal/malloc.h"
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* scandir (todo 24, XSI): walk the whole directory and hand back a sorted,
|
||||||
|
* NULL-terminated array of heap copies. Entries are copied into
|
||||||
|
* minimal-size blocks (header fields plus the name), all released with the
|
||||||
|
* public free(); the array itself is a malloc'd growable buffer resized
|
||||||
|
* with realloc. "." and ".." are reported like any other entry.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Copy one entry into minimal heap storage sized for its name. */
|
||||||
|
static struct dirent *
|
||||||
|
dirent_dup(const struct dirent *de)
|
||||||
|
{
|
||||||
|
size_t nlen = strlen(de->d_name) + 1;
|
||||||
|
struct dirent *nd =
|
||||||
|
__libc_malloc(offsetof(struct dirent, d_name) + nlen);
|
||||||
|
|
||||||
|
if (nd == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
memcpy(nd, de, offsetof(struct dirent, d_name));
|
||||||
|
memcpy(nd->d_name, de->d_name, nlen);
|
||||||
|
return nd;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
scandir(const char *path, struct dirent ***res,
|
||||||
|
int (*sel)(const struct dirent *),
|
||||||
|
int (*cmp)(const struct dirent **, const struct dirent **))
|
||||||
|
{
|
||||||
|
DIR *d;
|
||||||
|
struct dirent *de;
|
||||||
|
struct dirent **names = NULL;
|
||||||
|
size_t cnt = 0;
|
||||||
|
size_t cap = 0;
|
||||||
|
|
||||||
|
d = opendir(path);
|
||||||
|
if (d == NULL)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
while ((de = readdir(d)) != NULL)
|
||||||
|
{
|
||||||
|
if (sel != NULL && !sel(de))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (cnt == cap)
|
||||||
|
{
|
||||||
|
size_t ncap = cap == 0 ? 8 : cap * 2;
|
||||||
|
|
||||||
|
if (ncap <= cap || ncap > (size_t)-1 / sizeof *names)
|
||||||
|
{
|
||||||
|
errno = ENOMEM;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
names = realloc(names, ncap * sizeof *names);
|
||||||
|
if (names == NULL)
|
||||||
|
{
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
cap = ncap;
|
||||||
|
}
|
||||||
|
names[cnt] = dirent_dup(de);
|
||||||
|
if (names[cnt] == NULL)
|
||||||
|
{
|
||||||
|
errno = ENOMEM;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
cnt++;
|
||||||
|
}
|
||||||
|
closedir(d);
|
||||||
|
|
||||||
|
if (cmp != NULL)
|
||||||
|
{
|
||||||
|
qsort(names, cnt, sizeof *names,
|
||||||
|
(int (*)(const void *, const void *))cmp);
|
||||||
|
}
|
||||||
|
names = realloc(names, (cnt + 1) * sizeof *names);
|
||||||
|
if (names == NULL)
|
||||||
|
{
|
||||||
|
goto fail_nofree;
|
||||||
|
}
|
||||||
|
names[cnt] = NULL;
|
||||||
|
*res = names;
|
||||||
|
return (int)cnt;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
while (cnt > 0)
|
||||||
|
{
|
||||||
|
__libc_free(names[--cnt]);
|
||||||
|
}
|
||||||
|
__libc_free(names);
|
||||||
|
closedir(d);
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
fail_nofree:
|
||||||
|
while (cnt > 0)
|
||||||
|
{
|
||||||
|
__libc_free(names[--cnt]);
|
||||||
|
}
|
||||||
|
__libc_free(names);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
#include "dirent_impl.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* seekdir (todo 24): reposition the stream to the directory location loc
|
||||||
|
* (a d_off cookie previously returned by telldir). SYS_lseek to that
|
||||||
|
* cookie makes the next SYS_getdents64 resume at the entry the cookie
|
||||||
|
* denotes; any records cached ahead of the seek point are dropped. The
|
||||||
|
* kernel accepts exactly the cookies getdents64 hands out, which is all
|
||||||
|
* telldir ever returns. seekdir has no error return; on a failed lseek
|
||||||
|
* the buffered records are kept so reading continues where it left off.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
seekdir(DIR *dir, long loc)
|
||||||
|
{
|
||||||
|
struct vlibc_DIR *d = dir;
|
||||||
|
|
||||||
|
if (syscall_ret(__syscall3(SYS_lseek, d->fd, (long)loc, SEEK_SET)) >= 0)
|
||||||
|
{
|
||||||
|
d->buf_pos = 0;
|
||||||
|
d->buf_end = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
#include "dirent_impl.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* telldir (todo 24): return the stream's current directory location.
|
||||||
|
* readdir stores the kernel's d_off cookie of every entry it hands out —
|
||||||
|
* the offset at which iteration resumes AFTER that entry — so the cookie
|
||||||
|
* of the most recently returned entry is exactly the location a matching
|
||||||
|
* seekdir must restore. A fresh or rewound stream reports 0, the start.
|
||||||
|
*/
|
||||||
|
long
|
||||||
|
telldir(DIR *dir)
|
||||||
|
{
|
||||||
|
struct vlibc_DIR *d = dir;
|
||||||
|
|
||||||
|
return (long)d->de.d_off;
|
||||||
|
}
|
||||||
@@ -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,111 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include "atfork_impl.h"
|
||||||
|
|
||||||
|
#include "../internal/malloc.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The atfork hook table (todo 20).
|
||||||
|
*
|
||||||
|
* The list is a doubly linked chain in registration order (head = oldest
|
||||||
|
* registration). fork() runs the phases through __vlibc_atfork_prepare/
|
||||||
|
* parent/child around the syscall; pthread_atfork (todo 45) appends
|
||||||
|
* entries via __vlibc_atfork_register. Nodes live on the malloc heap:
|
||||||
|
* registration happens long after the allocator is up, and fork() itself
|
||||||
|
* never allocates — the phases only walk the existing chain.
|
||||||
|
*
|
||||||
|
* Phase order (POSIX 1003.1-2008 pthread_atfork):
|
||||||
|
* - prepare: reverse registration order (tail to head);
|
||||||
|
* - parent and child: registration order (head to tail).
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct vlibc_atfork_entry *volatile __vlibc_atfork_list = 0;
|
||||||
|
|
||||||
|
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
|
||||||
|
int
|
||||||
|
__vlibc_atfork_register(void (*prepare)(void), void (*parent)(void), void (*child)(void))
|
||||||
|
{
|
||||||
|
struct vlibc_atfork_entry *node;
|
||||||
|
struct vlibc_atfork_entry *tail;
|
||||||
|
|
||||||
|
node = (struct vlibc_atfork_entry *)__libc_malloc(sizeof(*node));
|
||||||
|
if (node == 0)
|
||||||
|
{
|
||||||
|
errno = ENOMEM;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
node->prepare = prepare;
|
||||||
|
node->parent = parent;
|
||||||
|
node->child = child;
|
||||||
|
node->prev = 0;
|
||||||
|
node->next = 0;
|
||||||
|
|
||||||
|
/* Append at the tail so the chain stays in registration order. */
|
||||||
|
if (__vlibc_atfork_list == 0)
|
||||||
|
{
|
||||||
|
__vlibc_atfork_list = node;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
tail = __vlibc_atfork_list;
|
||||||
|
while (tail->next != 0)
|
||||||
|
{
|
||||||
|
tail = tail->next;
|
||||||
|
}
|
||||||
|
tail->next = node;
|
||||||
|
node->prev = tail;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// NOLINTEND(bugprone-easily-swappable-parameters)
|
||||||
|
|
||||||
|
void
|
||||||
|
__vlibc_atfork_prepare(void)
|
||||||
|
{
|
||||||
|
struct vlibc_atfork_entry *node = __vlibc_atfork_list;
|
||||||
|
|
||||||
|
/* Reverse registration order: walk to the tail, then back. */
|
||||||
|
while (node != 0 && node->next != 0)
|
||||||
|
{
|
||||||
|
node = node->next;
|
||||||
|
}
|
||||||
|
for (; node != 0; node = node->prev)
|
||||||
|
{
|
||||||
|
if (node->prepare != 0)
|
||||||
|
{
|
||||||
|
node->prepare();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
__vlibc_atfork_parent(void)
|
||||||
|
{
|
||||||
|
struct vlibc_atfork_entry *node;
|
||||||
|
|
||||||
|
/* Registration order. */
|
||||||
|
for (node = __vlibc_atfork_list; node != 0; node = node->next)
|
||||||
|
{
|
||||||
|
if (node->parent != 0)
|
||||||
|
{
|
||||||
|
node->parent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
__vlibc_atfork_child(void)
|
||||||
|
{
|
||||||
|
struct vlibc_atfork_entry *node;
|
||||||
|
|
||||||
|
/* Registration order. */
|
||||||
|
for (node = __vlibc_atfork_list; node != 0; node = node->next)
|
||||||
|
{
|
||||||
|
if (node->child != 0)
|
||||||
|
{
|
||||||
|
node->child();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
#ifndef VLIBC_PROCESS_ATFORK_IMPL_H
|
||||||
|
#define VLIBC_PROCESS_ATFORK_IMPL_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — internal atfork hook table (todo 20).
|
||||||
|
*
|
||||||
|
* fork() runs three hook phases around the clone operation:
|
||||||
|
*
|
||||||
|
* 1. prepare — in the parent, before the fork, in REVERSE registration
|
||||||
|
* order (POSIX 1003.1-2008 pthread_atfork: "the prepare handlers are
|
||||||
|
* called in the reverse order in which they were registered");
|
||||||
|
* 2. parent — in the parent, after the fork, in registration order;
|
||||||
|
* 3. child — in the child, after the fork, in registration order
|
||||||
|
* (POSIX: "the parent and child handlers are called in the order in
|
||||||
|
* which they were registered").
|
||||||
|
*
|
||||||
|
* The list is a doubly linked chain in registration order (head = oldest
|
||||||
|
* registration); the phases walk it head-to-tail or tail-to-head as the
|
||||||
|
* ordering above demands. It starts NULL — with no threads and no
|
||||||
|
* registrations every phase is a no-op, so fork() keeps working before
|
||||||
|
* todo 45 (pthread_atfork) lands. Todo 45 appends entries through
|
||||||
|
* __vlibc_atfork_register(); nothing else needs to change here.
|
||||||
|
*
|
||||||
|
* The names sit in the implementation-reserved namespace (this is the
|
||||||
|
* library's private seam, never public API), so the corresponding
|
||||||
|
* bugprone checks are waived per the house NOLINT convention.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include "../internal/libc.h"
|
||||||
|
|
||||||
|
// NOLINTBEGIN(bugprone-reserved-identifier)
|
||||||
|
|
||||||
|
struct vlibc_atfork_entry
|
||||||
|
{
|
||||||
|
void (*prepare)(void);
|
||||||
|
void (*parent)(void);
|
||||||
|
void (*child)(void);
|
||||||
|
struct vlibc_atfork_entry *prev; /* toward older registrations */
|
||||||
|
struct vlibc_atfork_entry *next; /* toward newer registrations */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Head of the registration list; NULL until the first registration. */
|
||||||
|
hidden extern struct vlibc_atfork_entry *volatile __vlibc_atfork_list;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Append a handler set to the list. Returns 0, or -1 with errno ENOMEM
|
||||||
|
* when the node cannot be allocated (pthread_atfork maps that to its
|
||||||
|
* error return). Only pthread_atfork (todo 45) calls this.
|
||||||
|
*/
|
||||||
|
hidden int
|
||||||
|
__vlibc_atfork_register(void (*prepare)(void), void (*parent)(void), void (*child)(void));
|
||||||
|
|
||||||
|
/* The three phases; fork() calls these around the syscall. */
|
||||||
|
hidden void
|
||||||
|
__vlibc_atfork_prepare(void);
|
||||||
|
|
||||||
|
hidden void
|
||||||
|
__vlibc_atfork_parent(void);
|
||||||
|
|
||||||
|
hidden void
|
||||||
|
__vlibc_atfork_child(void);
|
||||||
|
|
||||||
|
// NOLINTEND(bugprone-reserved-identifier)
|
||||||
|
|
||||||
|
#endif /* VLIBC_PROCESS_ATFORK_IMPL_H */
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* execl: execve with the arguments collected from the varargs list, which
|
||||||
|
* is terminated by (char *)NULL. POSIX sets no argument-count limit, so
|
||||||
|
* the list is counted first and staged on a VLA; GCC supports VLA in
|
||||||
|
* every mode vlibc targets (the C23 auto-VLA optionality does not bite
|
||||||
|
* here). The POSIX signature is fixed; the adjacent-parameter check is
|
||||||
|
* waived per the house NOLINT convention.
|
||||||
|
*/
|
||||||
|
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
|
||||||
|
int
|
||||||
|
execl(const char *path, const char *arg0, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
int argc;
|
||||||
|
int i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
va_start(ap, arg0);
|
||||||
|
argc = 1;
|
||||||
|
while (va_arg(ap, const char *) != 0)
|
||||||
|
{
|
||||||
|
argc++;
|
||||||
|
}
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
{
|
||||||
|
char *argv[argc + 1];
|
||||||
|
|
||||||
|
argv[0] = (char *)arg0;
|
||||||
|
va_start(ap, arg0);
|
||||||
|
for (i = 1; i <= argc; i++)
|
||||||
|
{
|
||||||
|
argv[i] = va_arg(ap, char *);
|
||||||
|
}
|
||||||
|
va_end(ap);
|
||||||
|
ret = execve(path, argv, environ);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
// NOLINTEND(bugprone-easily-swappable-parameters)
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* execle: like execl, but the varargs list is terminated by (char *)NULL
|
||||||
|
* followed by one final char *const envp[] argument. The argv staging
|
||||||
|
* uses a VLA (see execl.c); envp is read directly from the varargs. The
|
||||||
|
* POSIX signature is fixed; the adjacent-parameter check is waived per
|
||||||
|
* the house NOLINT convention.
|
||||||
|
*/
|
||||||
|
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
|
||||||
|
int
|
||||||
|
execle(const char *path, const char *arg0, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
int argc;
|
||||||
|
int i;
|
||||||
|
char **envp;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
va_start(ap, arg0);
|
||||||
|
argc = 1;
|
||||||
|
while (va_arg(ap, const char *) != 0)
|
||||||
|
{
|
||||||
|
argc++;
|
||||||
|
}
|
||||||
|
envp = va_arg(ap, char **);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
{
|
||||||
|
char *argv[argc + 1];
|
||||||
|
|
||||||
|
argv[0] = (char *)arg0;
|
||||||
|
va_start(ap, arg0);
|
||||||
|
for (i = 1; i <= argc; i++)
|
||||||
|
{
|
||||||
|
argv[i] = va_arg(ap, char *);
|
||||||
|
}
|
||||||
|
va_end(ap);
|
||||||
|
ret = execve(path, argv, envp);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
// NOLINTEND(bugprone-easily-swappable-parameters)
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* execlp: like execl, but the file is located through PATH (see
|
||||||
|
* execvp.c). The argv staging uses a VLA; the POSIX signature is fixed
|
||||||
|
* and the adjacent-parameter check is waived per the house NOLINT
|
||||||
|
* convention.
|
||||||
|
*/
|
||||||
|
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
|
||||||
|
int
|
||||||
|
execlp(const char *file, const char *arg0, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
int argc;
|
||||||
|
int i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
va_start(ap, arg0);
|
||||||
|
argc = 1;
|
||||||
|
while (va_arg(ap, const char *) != 0)
|
||||||
|
{
|
||||||
|
argc++;
|
||||||
|
}
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
{
|
||||||
|
char *argv[argc + 1];
|
||||||
|
|
||||||
|
argv[0] = (char *)arg0;
|
||||||
|
va_start(ap, arg0);
|
||||||
|
for (i = 1; i <= argc; i++)
|
||||||
|
{
|
||||||
|
argv[i] = va_arg(ap, char *);
|
||||||
|
}
|
||||||
|
va_end(ap);
|
||||||
|
ret = execvp(file, argv);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
// NOLINTEND(bugprone-easily-swappable-parameters)
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* execv: execve with the calling process's environment. environ is the
|
||||||
|
* global installed by the startup code (src/start/environ.c); the internal
|
||||||
|
* syscall layer is not needed here beyond execve itself.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
execv(const char *path, char *const argv[])
|
||||||
|
{
|
||||||
|
return execve(path, argv, environ);
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/* AT_EMPTY_PATH for execveat (fexecve): resolve the fd itself. */
|
||||||
|
#define VLIBC_PROC_AT_EMPTY_PATH 0x1000
|
||||||
|
|
||||||
|
/*
|
||||||
|
* execve: replace the process image with the file at path. Success never
|
||||||
|
* returns; -1 with errno set otherwise.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
execve(const char *path, char *const argv[], char *const envp[])
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall3(SYS_execve, (long)path, (long)argv, (long)envp));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* fexecve: like execve, but the image is the open descriptor fd. Runs the
|
||||||
|
* kernel's execveat with an empty path plus AT_EMPTY_PATH. A descriptor
|
||||||
|
* opened with O_PATH cannot be exec'd on kernels before 6.3; a regular
|
||||||
|
* open works everywhere.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fexecve(int fd, char *const argv[], char *const envp[])
|
||||||
|
{
|
||||||
|
return syscall_ret(
|
||||||
|
__syscall5(SYS_execveat, fd, (long)"", (long)argv, (long)envp, VLIBC_PROC_AT_EMPTY_PATH));
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* execvp: search PATH for file and execve it with the caller's
|
||||||
|
* environment. If file contains '/', execve is tried directly.
|
||||||
|
*
|
||||||
|
* PATH is read via getenv(); a missing PATH defaults to "/bin:/usr/bin"
|
||||||
|
* (the traditional pathconf default). An empty entry — a leading,
|
||||||
|
* trailing, or doubled colon — means the current directory, per POSIX.
|
||||||
|
*
|
||||||
|
* Error policy (POSIX XBD exec): ENOENT and ENOTDIR from a candidate are
|
||||||
|
* skipped while the rest of PATH is searched, and EACCES is remembered;
|
||||||
|
* any other error aborts immediately. When every candidate fails, errno
|
||||||
|
* is EACCES if any candidate failed with EACCES, else ENOENT.
|
||||||
|
*/
|
||||||
|
// NOLINTBEGIN(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
|
||||||
|
int
|
||||||
|
execvp(const char *file, char *const argv[])
|
||||||
|
{
|
||||||
|
const char *path;
|
||||||
|
const char *p;
|
||||||
|
size_t flen;
|
||||||
|
int saw_eacces;
|
||||||
|
|
||||||
|
if (file == 0 || file[0] == '\0')
|
||||||
|
{
|
||||||
|
errno = ENOENT;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (strchr(file, '/') != 0)
|
||||||
|
{
|
||||||
|
return execve(file, argv, environ);
|
||||||
|
}
|
||||||
|
path = getenv("PATH");
|
||||||
|
if (path == 0)
|
||||||
|
{
|
||||||
|
path = "/bin:/usr/bin";
|
||||||
|
}
|
||||||
|
flen = strlen(file);
|
||||||
|
saw_eacces = 0;
|
||||||
|
|
||||||
|
for (p = path; *p != '\0';)
|
||||||
|
{
|
||||||
|
const char *end = p;
|
||||||
|
size_t dlen;
|
||||||
|
|
||||||
|
while (*end != '\0' && *end != ':')
|
||||||
|
{
|
||||||
|
end++;
|
||||||
|
}
|
||||||
|
dlen = (size_t)(end - p);
|
||||||
|
|
||||||
|
{
|
||||||
|
char candidate[dlen + flen + 2]; /* dir + '/' + file + NUL */
|
||||||
|
long r;
|
||||||
|
|
||||||
|
if (dlen == 0)
|
||||||
|
{
|
||||||
|
/* Empty PATH entry: the current directory. */
|
||||||
|
memcpy(candidate, file, flen + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
memcpy(candidate, p, dlen);
|
||||||
|
candidate[dlen] = '/';
|
||||||
|
memcpy(candidate + dlen + 1, file, flen + 1);
|
||||||
|
}
|
||||||
|
r = __syscall3(SYS_execve, (long)candidate, (long)argv, (long)environ);
|
||||||
|
if (r < 0)
|
||||||
|
{
|
||||||
|
int e = (int)-r;
|
||||||
|
|
||||||
|
if (e == EACCES)
|
||||||
|
{
|
||||||
|
saw_eacces = 1;
|
||||||
|
}
|
||||||
|
else if (e != ENOENT && e != ENOTDIR)
|
||||||
|
{
|
||||||
|
/* A real error: stop the search and report it. */
|
||||||
|
errno = e;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (*end == '\0')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
p = end + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno = saw_eacces ? EACCES : ENOENT;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// NOLINTEND(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
#include "atfork_impl.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* fork(): create a child that is a copy of the caller.
|
||||||
|
*
|
||||||
|
* Hook invocation order (documented for todo 45, pthread_atfork):
|
||||||
|
*
|
||||||
|
* 1. __vlibc_atfork_prepare() — reverse registration order (POSIX),
|
||||||
|
* before the syscall;
|
||||||
|
* 2. SYS_fork;
|
||||||
|
* 3. __vlibc_atfork_child() in the child, __vlibc_atfork_parent() in
|
||||||
|
* the parent — both in registration order (POSIX).
|
||||||
|
*
|
||||||
|
* The hook list is NULL by default (no threads, no registrations), so a
|
||||||
|
* bare fork() costs exactly the three no-op walker calls; with a static
|
||||||
|
* link the compiler folds them to nothing.
|
||||||
|
*
|
||||||
|
* SYS_fork is sufficient here: it is the kernel's plain
|
||||||
|
* clone(SIGCHLD, 0). There is no thread runtime yet (todo 45); once there
|
||||||
|
* is, fork must switch to SYS_clone with a clearable child tid so that
|
||||||
|
* pthread_join can reap without SIGCHLD races.
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
fork(void)
|
||||||
|
{
|
||||||
|
pid_t ret;
|
||||||
|
|
||||||
|
__vlibc_atfork_prepare();
|
||||||
|
ret = syscall_ret(__syscall0(SYS_fork));
|
||||||
|
if (ret == 0)
|
||||||
|
{
|
||||||
|
/* Child: run the child phase (registration order). */
|
||||||
|
__vlibc_atfork_child();
|
||||||
|
}
|
||||||
|
else if (ret > 0)
|
||||||
|
{
|
||||||
|
/* Parent: run the parent phase (registration order). */
|
||||||
|
__vlibc_atfork_parent();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vfork(): like fork(), but POSIX lets the child borrow the parent's
|
||||||
|
* address space until it execs or exits (POSIX [OB], level 2).
|
||||||
|
*
|
||||||
|
* Implemented as fork(). Rationale (glibc makes the identical choice on
|
||||||
|
* Linux): a C vfork cannot honor the "no function call between vfork and
|
||||||
|
* exec" contract. The child's rsp is the parent's suspended rsp + 8
|
||||||
|
* (the child has already popped vfork's return address), so the child's
|
||||||
|
* first call — even into this library's own execve wrapper — writes its
|
||||||
|
* return address exactly over the parent's suspended return-address
|
||||||
|
* slot; when the parent resumes it returns through the clobbered slot
|
||||||
|
* into the child's continuation. Verified empirically (the resumed
|
||||||
|
* parent landed in the child's exec-failure path). The kernel's COW
|
||||||
|
* fork already makes fork() as cheap as vfork for all practical
|
||||||
|
* purposes, and the [OB] semantics differences (no parent suspension,
|
||||||
|
* no shared stack) are explicitly tolerated by the plan. The atfork
|
||||||
|
* hooks run, as they do for glibc's fork-based vfork.
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
vfork(void)
|
||||||
|
{
|
||||||
|
return fork();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Supplementary group access.
|
||||||
|
*
|
||||||
|
* getgroups (POSIX base): fill grouplist with up to gidsetsize
|
||||||
|
* supplementary group ids and return the count; with gidsetsize 0 only
|
||||||
|
* the count is returned and nothing is written.
|
||||||
|
*
|
||||||
|
* setgroups (XSI, level 2): install the given supplementary group list.
|
||||||
|
* Requires privilege (CAP_SETGID).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
getgroups(int gidsetsize, gid_t grouplist[])
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall2(SYS_getgroups, gidsetsize, (long)grouplist));
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
int
|
||||||
|
setgroups(size_t gidsetsize, const gid_t *grouplist)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall2(SYS_setgroups, (long)gidsetsize, (long)grouplist));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getpid/getppid: plain kernel reads.
|
||||||
|
*
|
||||||
|
* No const/pure attribute: the result is process state, not a pure
|
||||||
|
* function of the arguments. In particular, const would let the compiler
|
||||||
|
* hoist a getpid call across fork() and observe the parent's pid inside
|
||||||
|
* the child.
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
getpid(void)
|
||||||
|
{
|
||||||
|
return (pid_t)syscall_ret(__syscall0(SYS_getpid));
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t
|
||||||
|
getppid(void)
|
||||||
|
{
|
||||||
|
return (pid_t)syscall_ret(__syscall0(SYS_getppid));
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/* Foreground-process-group ioctls (x86_64 _IO/_IOR derivations). */
|
||||||
|
#define VLIBC_PROC_TIOCGPGRP 0x540F
|
||||||
|
#define VLIBC_PROC_TIOCSPGRP 0x5410
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Process group and session control (POSIX base): setpgid, getpgrp,
|
||||||
|
* setsid. The XSI/obsolescent extras (setpgrp, getpgid, getsid,
|
||||||
|
* tcgetpgrp, tcsetpgrp) are gated at level 2 below.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* setpgid: move pid (0 = the caller) into process group pgid (0 = pid's
|
||||||
|
* own value). A process can change groups for itself and its children;
|
||||||
|
* session leaders cannot change groups.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
setpgid(pid_t pid, pid_t pgid)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall2(SYS_setpgid, pid, pgid));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getpgrp: the caller's process group.
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
getpgrp(void)
|
||||||
|
{
|
||||||
|
return (pid_t)syscall_ret(__syscall0(SYS_getpgrp));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* setsid: make the caller the leader of a new session and process group,
|
||||||
|
* detaching it from the controlling terminal. Returns the new session id
|
||||||
|
* (the caller's pid), or -1 with errno EPERM when the caller is already
|
||||||
|
* a process group leader.
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
setsid(void)
|
||||||
|
{
|
||||||
|
return (pid_t)syscall_ret(__syscall0(SYS_setsid));
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/* Level 2 (muslmimic): XSI and obsolescent session extras. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* setpgrp (obsolescent): setpgid(0, 0) — the caller becomes the leader
|
||||||
|
* of its own process group.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
setpgrp(void)
|
||||||
|
{
|
||||||
|
return setpgid(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getpgid (XSI): the process group of pid (0 = the caller).
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
getpgid(pid_t pid)
|
||||||
|
{
|
||||||
|
return (pid_t)syscall_ret(__syscall1(SYS_getpgid, pid));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getsid (XSI): the session id of pid (0 = the caller).
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
getsid(pid_t pid)
|
||||||
|
{
|
||||||
|
return (pid_t)syscall_ret(__syscall1(SYS_getsid, pid));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* tcgetpgrp (XSI): the foreground process group of the terminal on
|
||||||
|
* fildes, or -1 with errno set (ENOTTY when fildes is not a terminal).
|
||||||
|
*/
|
||||||
|
pid_t
|
||||||
|
tcgetpgrp(int fildes)
|
||||||
|
{
|
||||||
|
int pgrp = 0;
|
||||||
|
|
||||||
|
if (syscall_ret(__syscall3(SYS_ioctl, fildes, VLIBC_PROC_TIOCGPGRP, (long)&pgrp)) < 0)
|
||||||
|
{
|
||||||
|
return (pid_t)-1;
|
||||||
|
}
|
||||||
|
return (pid_t)pgrp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* tcsetpgrp (XSI): make pgid the foreground process group of the
|
||||||
|
* terminal on fildes.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
tcsetpgrp(int fildes, pid_t pgid)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall3(SYS_ioctl, fildes, VLIBC_PROC_TIOCSPGRP, pgid));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* system(cmd): run "sh -c cmd" and return the shell's wait status.
|
||||||
|
*
|
||||||
|
* - NULL cmd: report whether a command processor exists. /bin/sh is
|
||||||
|
* assumed here, as everywhere in POSIX, so this always returns 1.
|
||||||
|
* - otherwise: fork, exec /bin/sh with {"sh", "-c", cmd}, wait for it,
|
||||||
|
* and return the raw wait4 status (e.g. 768 == "exit 3").
|
||||||
|
* - -1 with errno set when the child cannot be created or reaped.
|
||||||
|
*
|
||||||
|
* POSIX requires the parent to block SIGCHLD and ignore SIGINT/SIGQUIT
|
||||||
|
* while the shell runs. There is no signal.h yet (todo 28): the mask and
|
||||||
|
* dispositions are manipulated with raw rt_sigprocmask/rt_sigaction
|
||||||
|
* syscalls on the kernel's one-word sigset_t, so this file takes no
|
||||||
|
* dependency on the signal todo. The child restores the saved mask and
|
||||||
|
* dispositions before exec, matching glibc/musl behavior.
|
||||||
|
*
|
||||||
|
* The child runs only async-signal-safe code between fork and exec: the
|
||||||
|
* raw signal syscalls, execve, and the raw exit syscall. fork()'s atfork
|
||||||
|
* hooks run (prepare/child), which is what pthread_atfork (todo 45)
|
||||||
|
* wants. This fork-based implementation matches glibc; POSIX does not
|
||||||
|
* require system() to be async-signal-safe itself.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Kernel sigaction layout (x86_64): handler, flags, restorer, and the
|
||||||
|
* one-word sigset_t mask. This is the syscall ABI, NOT the <signal.h>
|
||||||
|
* struct — todo 28 owns that one.
|
||||||
|
*/
|
||||||
|
struct vlibc_sys_sigaction
|
||||||
|
{
|
||||||
|
void (*handler)(int);
|
||||||
|
unsigned long flags;
|
||||||
|
void (*restorer)(void);
|
||||||
|
unsigned long mask;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define VLIBC_PROC_SIG_IGN ((void (*)(int))1)
|
||||||
|
#define VLIBC_PROC_SIG_BLOCK 0
|
||||||
|
#define VLIBC_PROC_SIG_SETMASK 2
|
||||||
|
#define VLIBC_PROC_SIGINT 2
|
||||||
|
#define VLIBC_PROC_SIGQUIT 3
|
||||||
|
#define VLIBC_PROC_SIGCHLD 17
|
||||||
|
|
||||||
|
int
|
||||||
|
system(const char *string)
|
||||||
|
{
|
||||||
|
struct vlibc_sys_sigaction ignore;
|
||||||
|
struct vlibc_sys_sigaction old_int;
|
||||||
|
struct vlibc_sys_sigaction old_quit;
|
||||||
|
unsigned long block_mask = 1UL << (VLIBC_PROC_SIGCHLD - 1);
|
||||||
|
unsigned long old_mask = 0;
|
||||||
|
pid_t pid;
|
||||||
|
int status = 0;
|
||||||
|
|
||||||
|
if (string == 0)
|
||||||
|
{
|
||||||
|
/* A command processor is always available. */
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ignore.handler = VLIBC_PROC_SIG_IGN;
|
||||||
|
ignore.flags = 0;
|
||||||
|
ignore.restorer = 0;
|
||||||
|
ignore.mask = 0;
|
||||||
|
|
||||||
|
/* 1. Parent: ignore SIGINT/SIGQUIT, block SIGCHLD (all saved). */
|
||||||
|
if (syscall_ret(
|
||||||
|
__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGINT, (long)&ignore, (long)&old_int, 8)) < 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (syscall_ret(__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGQUIT, (long)&ignore, (long)&old_quit,
|
||||||
|
8)) < 0)
|
||||||
|
{
|
||||||
|
/* Undo the SIGINT change above. */
|
||||||
|
(void)syscall_ret(__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGINT, (long)&old_int, 0, 8));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (syscall_ret(__syscall4(SYS_rt_sigprocmask, VLIBC_PROC_SIG_BLOCK, (long)&block_mask,
|
||||||
|
(long)&old_mask, 8)) < 0)
|
||||||
|
{
|
||||||
|
(void)syscall_ret(__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGINT, (long)&old_int, 0, 8));
|
||||||
|
(void)syscall_ret(__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGQUIT, (long)&old_quit, 0, 8));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. Fork. */
|
||||||
|
pid = fork();
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
char *sh_argv[4];
|
||||||
|
|
||||||
|
/* Child: restore the saved state, then exec the shell. */
|
||||||
|
(void)__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGINT, (long)&old_int, 0, 8);
|
||||||
|
(void)__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGQUIT, (long)&old_quit, 0, 8);
|
||||||
|
(void)__syscall4(SYS_rt_sigprocmask, VLIBC_PROC_SIG_SETMASK, (long)&old_mask, 0, 8);
|
||||||
|
sh_argv[0] = "sh";
|
||||||
|
sh_argv[1] = "-c";
|
||||||
|
sh_argv[2] = (char *)string;
|
||||||
|
sh_argv[3] = 0;
|
||||||
|
execve("/bin/sh", sh_argv, environ);
|
||||||
|
__syscall1(SYS_exit_group, 127); /* exec failed */
|
||||||
|
}
|
||||||
|
if (pid < 0)
|
||||||
|
{
|
||||||
|
/* Restore the parent state; errno was set by fork(). */
|
||||||
|
(void)__syscall4(SYS_rt_sigprocmask, VLIBC_PROC_SIG_SETMASK, (long)&old_mask, 0, 8);
|
||||||
|
(void)__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGINT, (long)&old_int, 0, 8);
|
||||||
|
(void)__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGQUIT, (long)&old_quit, 0, 8);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. Parent: wait for the shell (raw SYS_wait4; todo 23 owns the
|
||||||
|
* public wrapper). */
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
long r = __syscall4(SYS_wait4, pid, (long)&status, 0, 0);
|
||||||
|
|
||||||
|
if (r < 0 && -r == EINTR)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (r < 0)
|
||||||
|
{
|
||||||
|
status = syscall_ret(r); /* -1 + errno */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. Parent: restore the saved state. */
|
||||||
|
(void)__syscall4(SYS_rt_sigprocmask, VLIBC_PROC_SIG_SETMASK, (long)&old_mask, 0, 8);
|
||||||
|
(void)__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGINT, (long)&old_int, 0, 8);
|
||||||
|
(void)__syscall4(SYS_rt_sigaction, VLIBC_PROC_SIGQUIT, (long)&old_quit, 0, 8);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Real/effective uid and gid accessors and mutators. None of the getters
|
||||||
|
* carries a const/pure attribute: the values are process state, not a
|
||||||
|
* pure function of the arguments, and the set functions change what the
|
||||||
|
* get functions read.
|
||||||
|
*/
|
||||||
|
uid_t
|
||||||
|
getuid(void)
|
||||||
|
{
|
||||||
|
return (uid_t)syscall_ret(__syscall0(SYS_getuid));
|
||||||
|
}
|
||||||
|
|
||||||
|
uid_t
|
||||||
|
geteuid(void)
|
||||||
|
{
|
||||||
|
return (uid_t)syscall_ret(__syscall0(SYS_geteuid));
|
||||||
|
}
|
||||||
|
|
||||||
|
gid_t
|
||||||
|
getgid(void)
|
||||||
|
{
|
||||||
|
return (gid_t)syscall_ret(__syscall0(SYS_getgid));
|
||||||
|
}
|
||||||
|
|
||||||
|
gid_t
|
||||||
|
getegid(void)
|
||||||
|
{
|
||||||
|
return (gid_t)syscall_ret(__syscall0(SYS_getegid));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* setuid: set the real, effective, and saved ids (kernel SYS_setuid).
|
||||||
|
* When unprivileged, uid must match the current real or effective id.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
setuid(uid_t uid)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall1(SYS_setuid, uid));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* seteuid: set only the effective id. Linux has no seteuid syscall; the
|
||||||
|
* kernel contract is setresuid(-1, euid, -1), which is what glibc and
|
||||||
|
* musl issue too.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
seteuid(uid_t euid)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall3(SYS_setresuid, -1, euid, -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
setgid(gid_t gid)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall1(SYS_setgid, gid));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
setegid(gid_t egid)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall3(SYS_setresgid, -1, egid, -1));
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* wait/waitpid/waitid/wait3/wait4 — child-process status collection.
|
||||||
|
*
|
||||||
|
* All four are pass-throughs to the kernel: wait/waitpid/wait3/wait4 ride
|
||||||
|
* SYS_wait4 (the kernel does the pid selection, the option masking, and the
|
||||||
|
* status-word encoding), waitid rides SYS_waitid (which fills a full
|
||||||
|
* 128-byte siginfo with the si_pid/si_uid/si_status details and a CLD_*
|
||||||
|
* si_code). The raw result goes through syscall_ret(), so a child pid (or
|
||||||
|
* waitid's 0) is returned on success and -1 with errno set on error;
|
||||||
|
* WNOHANG-with-no-child returns 0 from the kernel untouched.
|
||||||
|
*
|
||||||
|
* No argument inspection is needed: the kernel interprets pid == -1 (any
|
||||||
|
* child), 0 (own process group) and pid < -1 (process group) for wait4, and
|
||||||
|
* the which/id pair for waitid, exactly as POSIX specifies. The options are
|
||||||
|
* the kernel-identical W* bits from <sys/wait.h>. rusage passes through
|
||||||
|
* verbatim for wait3/wait4 (NULL is legal and skips the fill).
|
||||||
|
*/
|
||||||
|
|
||||||
|
pid_t
|
||||||
|
wait(int *stat_loc)
|
||||||
|
{
|
||||||
|
return (pid_t)syscall_ret(__syscall4(SYS_wait4, -1, (long)stat_loc, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t
|
||||||
|
waitpid(pid_t pid, int *stat_loc, int options)
|
||||||
|
{
|
||||||
|
return (pid_t)syscall_ret(__syscall4(SYS_wait4, pid, (long)stat_loc, options, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall5(SYS_waitid, idtype, id, (long)infop, options, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
/* Level 2 (muslmimic): XSI wait3/wait4. */
|
||||||
|
|
||||||
|
pid_t
|
||||||
|
wait3(int *stat_loc, int options, struct rusage *rusage)
|
||||||
|
{
|
||||||
|
return (pid_t)syscall_ret(__syscall4(SYS_wait4, -1, (long)stat_loc, options, (long)rusage));
|
||||||
|
}
|
||||||
|
|
||||||
|
pid_t
|
||||||
|
wait4(pid_t pid, int *stat_loc, int options, struct rusage *rusage)
|
||||||
|
{
|
||||||
|
return (pid_t)syscall_ret(__syscall4(SYS_wait4, pid, (long)stat_loc, options, (long)rusage));
|
||||||
|
}
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <poll.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* poll: plain POSIX poll over SYS_ppoll with a NULL sigmask (Linux has no
|
||||||
|
* separate poll syscall for >1024 fds; ppoll is the generic form). The
|
||||||
|
* millisecond timeout is converted to the struct timespec the kernel
|
||||||
|
* expects; a negative timeout means "block indefinitely" and stays NULL.
|
||||||
|
*
|
||||||
|
* The (nfds_t, int) parameter pair is the fixed POSIX signature, so the
|
||||||
|
* easily-swappable-parameters warning does not apply.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
poll(struct pollfd *fds, nfds_t nfds, // NOLINT(bugprone-easily-swappable-parameters)
|
||||||
|
int timeout)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
struct timespec *tsp = NULL;
|
||||||
|
|
||||||
|
if (timeout >= 0)
|
||||||
|
{
|
||||||
|
ts.tv_sec = timeout / 1000;
|
||||||
|
ts.tv_nsec = (long)(timeout % 1000) * 1000000L;
|
||||||
|
tsp = &ts;
|
||||||
|
}
|
||||||
|
return syscall_ret(__syscall5(SYS_ppoll, (long)fds, (long)nfds, (long)tsp, 0L, 0L));
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <poll.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ppoll: Linux-specific poll over SYS_ppoll with a struct timespec timeout
|
||||||
|
* and an optional signal mask (the kernel takes the mask and its size as
|
||||||
|
* separate trailing arguments; the size is only announced — as the 8-byte
|
||||||
|
* kernel sigset size — when a mask is actually given).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout, const sigset_t *sigmask)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall5(SYS_ppoll, (long)fds, (long)nfds, (long)timeout, (long)sigmask,
|
||||||
|
sigmask != NULL ? (long)sizeof(sigset_t) : 0L));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <sys/select.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* pselect: plain POSIX pselect over SYS_pselect6. Its 6th kernel argument is
|
||||||
|
* a pointer to a { sigset_t *, size_t } pair (the kernel reads two words);
|
||||||
|
* NULL means "leave the signal mask alone", so the pair is only supplied —
|
||||||
|
* and only the 8-byte kernel sigset size announced — when sigmask is given.
|
||||||
|
* The timeout passes through as a struct timespec (NULL = indefinite).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
||||||
|
const struct timespec *timeout, const sigset_t *sigmask)
|
||||||
|
{
|
||||||
|
long data[2];
|
||||||
|
|
||||||
|
data[0] = (long)sigmask;
|
||||||
|
data[1] = (long)sizeof(sigset_t);
|
||||||
|
return syscall_ret(__syscall6(SYS_pselect6, (long)nfds, (long)readfds, (long)writefds,
|
||||||
|
(long)exceptfds, (long)timeout,
|
||||||
|
sigmask != NULL ? (long)data : 0L));
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <sys/select.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* select: plain POSIX select over SYS_pselect6. The kernel wants a struct
|
||||||
|
* timespec, so the struct timeval timeout is converted here (NULL passes
|
||||||
|
* through as "block indefinitely") and the sigset pair stays NULL: select
|
||||||
|
* never changes the signal mask. select() and pselect() share this syscall
|
||||||
|
* on Linux; the 6th argument of pselect6 is a { sigset_t *, size_t } pair
|
||||||
|
* pointer that is NULL when the mask is left alone.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
struct timespec *tsp = NULL;
|
||||||
|
|
||||||
|
if (timeout != NULL)
|
||||||
|
{
|
||||||
|
if (timeout->tv_sec < 0 || timeout->tv_usec < 0 || timeout->tv_usec >= 1000000L)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
ts.tv_sec = timeout->tv_sec;
|
||||||
|
ts.tv_nsec = (long)timeout->tv_usec * 1000;
|
||||||
|
tsp = &ts;
|
||||||
|
}
|
||||||
|
return syscall_ret(__syscall6(SYS_pselect6, (long)nfds, (long)readfds, (long)writefds,
|
||||||
|
(long)exceptfds, (long)tsp, 0L));
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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) */
|
||||||
@@ -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 */
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
@@ -0,0 +1,503 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "../internal/malloc.h"
|
||||||
|
#include "stdio_impl.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — memory streams (todo 18): fmemopen and open_memstream.
|
||||||
|
*
|
||||||
|
* Both build a normal buffered vlibc_FILE whose backing "device" is a
|
||||||
|
* memory region instead of a descriptor: the FILE's ->mem points at the
|
||||||
|
* backend struct below, and the stdio core (stdio_flush/stdio_refill/
|
||||||
|
* stdio_discard_read/fseeko/fclose) diverts its descriptor operations to
|
||||||
|
* the stdio_mem_* hooks at the bottom of this file. The buffered data
|
||||||
|
* path (cache buffer, mode switches, position invariant) is unchanged;
|
||||||
|
* only the device is synthetic.
|
||||||
|
*
|
||||||
|
* Backend state: `data` is the region, `size` its capacity, `len` the
|
||||||
|
* current data length (bytes at or beyond len read as end of file), and
|
||||||
|
* `dpos` the device-head position — the analog of the kernel file offset,
|
||||||
|
* which for a real stream the kernel tracks and stdio re-anchors around.
|
||||||
|
* Keeping dpos explicit (rather than deriving it from FILE state) is what
|
||||||
|
* keeps memory streams exact even when reads span several cache refills.
|
||||||
|
*
|
||||||
|
* Two flavors:
|
||||||
|
*
|
||||||
|
* - fmemopen(buf, size, mode) over a caller buffer. The stream does NOT
|
||||||
|
* own buf unless buf is NULL, in which case it allocates a growable
|
||||||
|
* zeroed region of its own and releases it on fclose. Modes follow
|
||||||
|
* C23 7.23.5.6: 'r' reads the whole region (length = size), 'w'
|
||||||
|
* truncates the current length to zero, 'a' starts at the current
|
||||||
|
* data length (the first NUL byte in string mode, i.e. without 'b').
|
||||||
|
* A '+' enables both directions. Writes past a fixed buffer's size
|
||||||
|
* fail (partial-write discipline like a full disk).
|
||||||
|
* - open_memstream(ptr, sizeloc): a write-only stream over a growable
|
||||||
|
* buffer it owns. After fflush or fclose the buffer is NUL-terminated
|
||||||
|
* at the data length, *ptr names it (realloc may move it), and
|
||||||
|
* *sizeloc holds the length; the caller frees *ptr after fclose.
|
||||||
|
*
|
||||||
|
* Limits: memory streams are not registered with the stdio open_list
|
||||||
|
* (that registry is private to stdio.c), so fflush(NULL) does not reach
|
||||||
|
* them; an explicit fflush(stream) works. They are also not thread-safe
|
||||||
|
* beyond what the single-threaded FILE core already is.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Initial capacity for an open_memstream buffer. */
|
||||||
|
#define OMS_INITIAL 128
|
||||||
|
|
||||||
|
struct vlibc_memstream
|
||||||
|
{
|
||||||
|
FILE f; /* embedded stream; the public FILE * */
|
||||||
|
unsigned char *data; /* backing region */
|
||||||
|
size_t size; /* capacity of data */
|
||||||
|
size_t len; /* current data length (excludes any NUL) */
|
||||||
|
size_t dpos; /* device-head position in the region */
|
||||||
|
int fixed; /* writes past size fail (caller buffer) */
|
||||||
|
int own_data; /* release data on close (fmemopen NULL buf) */
|
||||||
|
int string_mode; /* no 'b': keep a NUL terminator at len */
|
||||||
|
char **user_ptr; /* open_memstream: caller's buffer slot */
|
||||||
|
size_t *user_len; /* open_memstream: caller's length slot */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Build the embedded FILE: cache buffer, flags, cursors — the same shape
|
||||||
|
* stdio_alloc_file produces for a descriptor stream, but with fd == -1
|
||||||
|
* and ->mem pointing back at the backend. Not registered in open_list
|
||||||
|
* (see the banner). The caller fills the region fields and ->pos.
|
||||||
|
*/
|
||||||
|
static struct vlibc_memstream *
|
||||||
|
ms_alloc(int m)
|
||||||
|
{
|
||||||
|
struct vlibc_memstream *ms = (struct vlibc_memstream *)__libc_malloc(sizeof(*ms));
|
||||||
|
|
||||||
|
if (ms == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
ms->f.buf = (unsigned char *)__libc_malloc(BUFSIZ);
|
||||||
|
if (ms->f.buf == NULL)
|
||||||
|
{
|
||||||
|
__libc_free(ms);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
ms->f.buf_size = BUFSIZ;
|
||||||
|
ms->f.rpos = ms->f.rstop = ms->f.buf;
|
||||||
|
ms->f.wpos = ms->f.buf;
|
||||||
|
ms->f.wstop = ms->f.buf + BUFSIZ;
|
||||||
|
ms->f.fd = -1;
|
||||||
|
ms->f.flags = m | F_OWNBUF | F_HEAP;
|
||||||
|
if ((m & F_READ) && (m & F_WRITE))
|
||||||
|
{
|
||||||
|
ms->f.flags |= F_RDWR;
|
||||||
|
}
|
||||||
|
ms->f.pos = 0;
|
||||||
|
ms->f.ungot = 0;
|
||||||
|
ms->f.lock = 0;
|
||||||
|
ms->f.lock_owner = NULL;
|
||||||
|
ms->f.next = NULL;
|
||||||
|
ms->f.mem = ms;
|
||||||
|
ms->data = NULL;
|
||||||
|
ms->size = 0;
|
||||||
|
ms->len = 0;
|
||||||
|
ms->dpos = 0;
|
||||||
|
ms->fixed = 0;
|
||||||
|
ms->own_data = 0;
|
||||||
|
ms->string_mode = 0;
|
||||||
|
ms->user_ptr = NULL;
|
||||||
|
ms->user_len = NULL;
|
||||||
|
return ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Release the FILE block (cache buffer + struct) on a construction error. */
|
||||||
|
static void
|
||||||
|
ms_free_file(struct vlibc_memstream *ms)
|
||||||
|
{
|
||||||
|
__libc_free(ms->f.buf);
|
||||||
|
__libc_free(ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Index of the first NUL byte in data[0, size), or size when none. */
|
||||||
|
static size_t
|
||||||
|
ms_nul_index(const struct vlibc_memstream *ms)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < ms->size; i++)
|
||||||
|
{
|
||||||
|
if (ms->data[i] == 0)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ms->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grow the owned region until it holds `need` bytes (doubling). */
|
||||||
|
static int
|
||||||
|
ms_grow(struct vlibc_memstream *ms, size_t need)
|
||||||
|
{
|
||||||
|
size_t ncap;
|
||||||
|
unsigned char *nd;
|
||||||
|
|
||||||
|
if (need <= ms->size)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
ncap = (ms->size > (size_t)-1 / 2) ? need : ms->size * 2;
|
||||||
|
if (ncap < need)
|
||||||
|
{
|
||||||
|
ncap = need;
|
||||||
|
}
|
||||||
|
nd = (unsigned char *)realloc(ms->data, ncap);
|
||||||
|
if (nd == NULL)
|
||||||
|
{
|
||||||
|
return 0; /* errno ENOMEM from realloc */
|
||||||
|
}
|
||||||
|
ms->data = nd;
|
||||||
|
ms->size = ncap;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copy n bytes from src into the region at offset at. Fixed regions take
|
||||||
|
* what fits (up to size - at); owned regions grow on demand. Extends len
|
||||||
|
* when the write passes the current end; owned regions zero the hole a
|
||||||
|
* seek past the end left behind, so stale allocator bytes never show up
|
||||||
|
* between the old length and the write. Returns the bytes written.
|
||||||
|
*/
|
||||||
|
static size_t
|
||||||
|
ms_write(struct vlibc_memstream *ms, size_t at, const unsigned char *src, size_t n)
|
||||||
|
{
|
||||||
|
size_t end;
|
||||||
|
|
||||||
|
if (ms->fixed)
|
||||||
|
{
|
||||||
|
if (at >= ms->size)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (n > ms->size - at)
|
||||||
|
{
|
||||||
|
n = ms->size - at;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!ms_grow(ms, at + n + 1))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (at > ms->len && !ms->fixed)
|
||||||
|
{
|
||||||
|
__builtin_memset(ms->data + ms->len, 0, at - ms->len);
|
||||||
|
}
|
||||||
|
__builtin_memcpy(ms->data + at, src, n);
|
||||||
|
end = at + n;
|
||||||
|
if (end > ms->len)
|
||||||
|
{
|
||||||
|
ms->len = end;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy up to n bytes from offset at; nothing (EOF) once past len. */
|
||||||
|
static size_t
|
||||||
|
ms_read(struct vlibc_memstream *ms, size_t at, unsigned char *dst, size_t n)
|
||||||
|
{
|
||||||
|
if (at >= ms->len)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (n > ms->len - at)
|
||||||
|
{
|
||||||
|
n = ms->len - at;
|
||||||
|
}
|
||||||
|
__builtin_memcpy(dst, ms->data + at, n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Post-flush sync: open_memstream publishes the (possibly moved) buffer
|
||||||
|
* and its NUL-terminated length; string-mode fmemopen keeps the caller's
|
||||||
|
* region readable as a string. */
|
||||||
|
static void
|
||||||
|
ms_sync(struct vlibc_memstream *ms)
|
||||||
|
{
|
||||||
|
if (ms->user_ptr != NULL)
|
||||||
|
{
|
||||||
|
ms->data[ms->len] = 0;
|
||||||
|
*ms->user_ptr = (char *)ms->data;
|
||||||
|
*ms->user_len = ms->len;
|
||||||
|
}
|
||||||
|
else if (ms->string_mode && ms->len < ms->size)
|
||||||
|
{
|
||||||
|
ms->data[ms->len] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Device hooks called by the stdio core (see stdio_impl.h). Each mirrors
|
||||||
|
* the descriptor half of the corresponding core helper, updating dpos the
|
||||||
|
* way a kernel would.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int
|
||||||
|
stdio_mem_flush(FILE *f)
|
||||||
|
{
|
||||||
|
struct vlibc_memstream *ms = f->mem;
|
||||||
|
size_t n = (size_t)(f->wpos - f->buf);
|
||||||
|
|
||||||
|
if (!(f->flags & F_WRITE))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (n != 0)
|
||||||
|
{
|
||||||
|
size_t at = (f->flags & F_APPEND) ? ms->len : ms->dpos;
|
||||||
|
size_t w = ms_write(ms, at, f->buf, n);
|
||||||
|
|
||||||
|
if (w < n)
|
||||||
|
{
|
||||||
|
/* Fixed region full or out of memory: keep the unwritten
|
||||||
|
* remainder buffered and report the error, exactly like a
|
||||||
|
* partial descriptor write. */
|
||||||
|
size_t keep = n - w;
|
||||||
|
|
||||||
|
__builtin_memmove(f->buf, f->buf + w, keep);
|
||||||
|
f->wpos = f->buf + keep;
|
||||||
|
f->pos = (off_t)(at + w);
|
||||||
|
ms->dpos = (size_t)f->pos;
|
||||||
|
f->flags |= F_ERR;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
f->pos = (off_t)(at + n);
|
||||||
|
ms->dpos = (size_t)f->pos;
|
||||||
|
}
|
||||||
|
f->wpos = f->buf;
|
||||||
|
ms_sync(ms);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
stdio_mem_refill(FILE *f)
|
||||||
|
{
|
||||||
|
struct vlibc_memstream *ms = f->mem;
|
||||||
|
size_t at = ms->dpos;
|
||||||
|
size_t n;
|
||||||
|
|
||||||
|
f->rpos = f->rstop = f->buf;
|
||||||
|
f->pos = (off_t)at; /* re-anchor buf[0] at the device head */
|
||||||
|
n = ms_read(ms, at, f->buf, f->buf_size);
|
||||||
|
if (n != 0)
|
||||||
|
{
|
||||||
|
f->rstop = f->buf + n;
|
||||||
|
ms->dpos = at + n; /* device read ahead of the logical position */
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
f->flags |= F_EOF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
stdio_mem_discard(FILE *f)
|
||||||
|
{
|
||||||
|
struct vlibc_memstream *ms = f->mem;
|
||||||
|
|
||||||
|
if (f->rpos < f->rstop)
|
||||||
|
{
|
||||||
|
/* Unread cached data: rewind the device head to the logical
|
||||||
|
* position (the byte at rpos) and re-anchor pos there. */
|
||||||
|
f->pos += (off_t)(f->rpos - f->buf);
|
||||||
|
ms->dpos = (size_t)f->pos;
|
||||||
|
}
|
||||||
|
f->rpos = f->rstop = f->buf;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
|
||||||
|
off_t
|
||||||
|
stdio_mem_lseek(FILE *f, off_t off, int whence)
|
||||||
|
{
|
||||||
|
struct vlibc_memstream *ms = f->mem;
|
||||||
|
off_t target;
|
||||||
|
|
||||||
|
if (whence == SEEK_SET)
|
||||||
|
{
|
||||||
|
target = off;
|
||||||
|
}
|
||||||
|
else if (whence == SEEK_END)
|
||||||
|
{
|
||||||
|
target = (off_t)ms->len + off;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
return (off_t)-1;
|
||||||
|
}
|
||||||
|
if (target < 0)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
return (off_t)-1;
|
||||||
|
}
|
||||||
|
ms->dpos = (size_t)target;
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
// NOLINTEND(bugprone-easily-swappable-parameters)
|
||||||
|
|
||||||
|
void
|
||||||
|
stdio_mem_close(FILE *f)
|
||||||
|
{
|
||||||
|
struct vlibc_memstream *ms = f->mem;
|
||||||
|
|
||||||
|
if (ms->user_ptr != NULL)
|
||||||
|
{
|
||||||
|
/* open_memstream: one last publish so the caller's pointer and
|
||||||
|
* size slots are current even without an explicit fflush
|
||||||
|
* (idempotent after a flush). */
|
||||||
|
ms->data[ms->len] = 0;
|
||||||
|
*ms->user_ptr = (char *)ms->data;
|
||||||
|
*ms->user_len = ms->len;
|
||||||
|
}
|
||||||
|
if (ms->own_data)
|
||||||
|
{
|
||||||
|
free(ms->data);
|
||||||
|
ms->data = NULL;
|
||||||
|
ms->own_data = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
|
||||||
|
FILE *
|
||||||
|
fmemopen(void *buf, size_t size, const char *mode)
|
||||||
|
{
|
||||||
|
int m = 0;
|
||||||
|
int update = 0;
|
||||||
|
int binary = 0;
|
||||||
|
size_t len = 0;
|
||||||
|
size_t i;
|
||||||
|
struct vlibc_memstream *ms;
|
||||||
|
|
||||||
|
if (mode == NULL || size == 0)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
switch (mode[0])
|
||||||
|
{
|
||||||
|
case 'r':
|
||||||
|
m |= F_READ;
|
||||||
|
break;
|
||||||
|
case 'w':
|
||||||
|
m |= F_WRITE;
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
m |= F_WRITE | F_APPEND;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errno = EINVAL;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
for (i = 1; mode[i] != '\0'; i++)
|
||||||
|
{
|
||||||
|
switch (mode[i])
|
||||||
|
{
|
||||||
|
case '+':
|
||||||
|
update = 1;
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
binary = 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errno = EINVAL;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
m |= F_READ | F_WRITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ms = ms_alloc(m);
|
||||||
|
if (ms == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (buf == NULL)
|
||||||
|
{
|
||||||
|
/* No caller buffer: allocate a zeroed, growable region of our
|
||||||
|
* own (released on fclose, see stdio_mem_close). */
|
||||||
|
ms->data = (unsigned char *)malloc(size);
|
||||||
|
if (ms->data == NULL)
|
||||||
|
{
|
||||||
|
ms_free_file(ms);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
__builtin_memset(ms->data, 0, size);
|
||||||
|
ms->fixed = 0;
|
||||||
|
ms->own_data = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ms->data = (unsigned char *)buf;
|
||||||
|
ms->fixed = 1;
|
||||||
|
ms->own_data = 0;
|
||||||
|
}
|
||||||
|
ms->size = size;
|
||||||
|
ms->string_mode = !binary;
|
||||||
|
|
||||||
|
/* Current data length per the open mode (see the banner). */
|
||||||
|
switch (mode[0])
|
||||||
|
{
|
||||||
|
case 'a':
|
||||||
|
len = binary ? size : ms_nul_index(ms);
|
||||||
|
break;
|
||||||
|
case 'r':
|
||||||
|
len = size;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
len = 0; /* 'w': truncated */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ms->len = len;
|
||||||
|
ms->dpos = (mode[0] == 'a') ? len : 0;
|
||||||
|
ms->f.pos = (off_t)ms->dpos;
|
||||||
|
return &ms->f;
|
||||||
|
}
|
||||||
|
// NOLINTEND(bugprone-easily-swappable-parameters)
|
||||||
|
|
||||||
|
FILE *
|
||||||
|
open_memstream(char **ptr, size_t *sizeloc)
|
||||||
|
{
|
||||||
|
struct vlibc_memstream *ms;
|
||||||
|
|
||||||
|
if (ptr == NULL || sizeloc == NULL)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
ms = ms_alloc(F_WRITE);
|
||||||
|
if (ms == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
ms->data = (unsigned char *)malloc(OMS_INITIAL);
|
||||||
|
if (ms->data == NULL)
|
||||||
|
{
|
||||||
|
ms_free_file(ms);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
ms->data[0] = 0; /* the buffer starts as the empty string */
|
||||||
|
ms->size = OMS_INITIAL;
|
||||||
|
ms->fixed = 0;
|
||||||
|
ms->own_data = 0; /* the caller takes the region after fclose */
|
||||||
|
ms->user_ptr = ptr;
|
||||||
|
ms->user_len = sizeloc;
|
||||||
|
*ptr = (char *)ms->data;
|
||||||
|
*sizeloc = 0;
|
||||||
|
return &ms->f;
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "stdio_impl.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — line input (todo 18): getdelim, getline, puts.
|
||||||
|
*
|
||||||
|
* getdelim reads one line (or one delimiter-terminated field) into a
|
||||||
|
* growable caller buffer. The buffer is owned by the caller and must be
|
||||||
|
* released with free(); the stream may reallocate it as the line grows,
|
||||||
|
* updating *lineptr and *n on every move so the caller's pointer never
|
||||||
|
* dangles, even when a later realloc fails and getdelim returns -1.
|
||||||
|
*
|
||||||
|
* Character storage uses the byte count, not strlen: embedded NUL bytes
|
||||||
|
* are preserved in the line, and the returned length is what matters (the
|
||||||
|
* buffer is still NUL-terminated after the last byte read).
|
||||||
|
*
|
||||||
|
* Reads go through fgetc, so a partial final line without the delimiter
|
||||||
|
* is returned with its length at end of file, exactly like fgets; a pure
|
||||||
|
* end-of-file (or an error) before any byte is -1. Distinguishing the two
|
||||||
|
* at a mid-line stop follows the fgets discipline: a genuine EOF keeps the
|
||||||
|
* partial line, a read error (F_ERR) discards it.
|
||||||
|
*
|
||||||
|
* puts is the one plain stdout line helper not owned by the todo-15 core
|
||||||
|
* (which took fputs): write s, then a newline. Returned value is
|
||||||
|
* non-negative on success and EOF on error, matching the C standard.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Initial capacity for a fresh getdelim buffer (both the *lineptr == NULL
|
||||||
|
* malloc case and the *n == 0 realloc-minimal case). */
|
||||||
|
#define GETDELIM_INITIAL 128
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
getdelim(char **restrict lineptr, size_t *restrict n, int delim, FILE *restrict stream)
|
||||||
|
{
|
||||||
|
char *buf;
|
||||||
|
size_t cap;
|
||||||
|
size_t used = 0;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
if (lineptr == NULL || n == NULL || stream == NULL)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
buf = *lineptr;
|
||||||
|
cap = *n;
|
||||||
|
if (buf == NULL)
|
||||||
|
{
|
||||||
|
/* No buffer yet: a fresh allocation, published right away so an
|
||||||
|
* early -1 (immediate end of file) still hands the caller a
|
||||||
|
* usable, freeable buffer. */
|
||||||
|
buf = malloc(GETDELIM_INITIAL);
|
||||||
|
if (buf == NULL)
|
||||||
|
{
|
||||||
|
return -1; /* errno ENOMEM from the allocator */
|
||||||
|
}
|
||||||
|
cap = GETDELIM_INITIAL;
|
||||||
|
*lineptr = buf;
|
||||||
|
*n = cap;
|
||||||
|
}
|
||||||
|
else if (cap == 0)
|
||||||
|
{
|
||||||
|
/* A zero-sized buffer is unusable: start it at the minimum. */
|
||||||
|
char *nbuf = realloc(buf, GETDELIM_INITIAL);
|
||||||
|
|
||||||
|
if (nbuf == NULL)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
buf = nbuf;
|
||||||
|
cap = GETDELIM_INITIAL;
|
||||||
|
*lineptr = buf;
|
||||||
|
*n = cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
if (used + 1 >= cap)
|
||||||
|
{
|
||||||
|
/* Room for one more byte plus the trailing NUL. Publish the
|
||||||
|
* grown buffer immediately so *lineptr never names a block
|
||||||
|
* this call has already freed. */
|
||||||
|
char *grown = realloc(buf, cap * 2);
|
||||||
|
|
||||||
|
if (grown == NULL)
|
||||||
|
{
|
||||||
|
*lineptr = buf;
|
||||||
|
*n = cap;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
buf = grown;
|
||||||
|
cap *= 2;
|
||||||
|
*lineptr = buf;
|
||||||
|
*n = cap;
|
||||||
|
}
|
||||||
|
c = fgetc(stream);
|
||||||
|
if (c == EOF)
|
||||||
|
{
|
||||||
|
if (used == 0 || (stream->flags & F_ERR))
|
||||||
|
{
|
||||||
|
/* End of file (or an error) before any byte, or a read
|
||||||
|
* error in the middle of the line. */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
/* Mid-line end of file: the partial line is the result. */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
buf[used++] = (char)c;
|
||||||
|
if (c == delim)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf[used] = '\0';
|
||||||
|
*lineptr = buf;
|
||||||
|
*n = cap;
|
||||||
|
return (ssize_t)used;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
getline(char **restrict lineptr, size_t *restrict n, FILE *restrict stream)
|
||||||
|
{
|
||||||
|
return getdelim(lineptr, n, '\n', stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
puts(const char *s)
|
||||||
|
{
|
||||||
|
if (fputs(s, stdout) == EOF)
|
||||||
|
{
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
return fputc('\n', stdout);
|
||||||
|
}
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
#include "stdio_impl.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — per-stream locking and unlocked character I/O (todo 18).
|
||||||
|
*
|
||||||
|
* flockfile/ftrylockfile/funlockfile are POSIX.1-2008 base. The process is
|
||||||
|
* single-threaded until the thread todo (#45) lands, so these maintain the
|
||||||
|
* ownership state that a real lock needs — recursion depth in ->lock, the
|
||||||
|
* owning thread's FS base in ->lock_owner — without any kernel primitive.
|
||||||
|
* flockfile is recursive: the same thread may lock a stream repeatedly and
|
||||||
|
* must unlock as many times. Once real threads exist, the #46 mutex/cond
|
||||||
|
* work makes flockfile block on ->lock when another thread owns it; the
|
||||||
|
* owner/count fields here already make the recursion decision.
|
||||||
|
*
|
||||||
|
* getc_unlocked/getchar_unlocked/putc_unlocked/putchar_unlocked are the
|
||||||
|
* four POSIX.1-2008 base _unlocked forms (the remaining GNU _unlocked
|
||||||
|
* forms are L3 and deliberately not provided). They assume the caller
|
||||||
|
* holds the stream lock and run the same buffered read/write core the
|
||||||
|
* locked fgetc/fputc use, via the hidden helpers of stdio_impl.h, without
|
||||||
|
* taking the lock themselves.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void
|
||||||
|
flockfile(FILE *stream)
|
||||||
|
{
|
||||||
|
void *self = __builtin_thread_pointer();
|
||||||
|
|
||||||
|
if (stream->lock == 0 || stream->lock_owner != self)
|
||||||
|
{
|
||||||
|
/* Fresh acquisition, or (once threads exist) a foreign owner:
|
||||||
|
* the #46 per-stream lock will block here until the stream is
|
||||||
|
* free. The single running thread is always the owner. */
|
||||||
|
stream->lock_owner = self;
|
||||||
|
stream->lock = 0;
|
||||||
|
}
|
||||||
|
stream->lock++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ftrylockfile(FILE *stream)
|
||||||
|
{
|
||||||
|
if (stream->lock > 0)
|
||||||
|
{
|
||||||
|
/* Already owned (recursively or by another thread): the #46 real
|
||||||
|
* lock would fail here without blocking. */
|
||||||
|
return EBUSY;
|
||||||
|
}
|
||||||
|
stream->lock_owner = __builtin_thread_pointer();
|
||||||
|
stream->lock = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
funlockfile(FILE *stream)
|
||||||
|
{
|
||||||
|
if (stream->lock > 0)
|
||||||
|
{
|
||||||
|
stream->lock--;
|
||||||
|
if (stream->lock == 0)
|
||||||
|
{
|
||||||
|
stream->lock_owner = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
getc_unlocked(FILE *stream)
|
||||||
|
{
|
||||||
|
stdio_init_if_needed(stream);
|
||||||
|
if (stream->flags & F_PUSHED)
|
||||||
|
{
|
||||||
|
stream->flags &= ~F_PUSHED;
|
||||||
|
return stream->ungot;
|
||||||
|
}
|
||||||
|
if (stream->flags & F_WRITE)
|
||||||
|
{
|
||||||
|
if (!(stream->flags & F_READ))
|
||||||
|
{
|
||||||
|
/* Reading a write-only stream. */
|
||||||
|
stream->flags |= F_ERR;
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
if (stdio_flush(stream) < 0)
|
||||||
|
{
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
stream->flags &= ~F_WRITE;
|
||||||
|
stream->flags |= F_READ;
|
||||||
|
}
|
||||||
|
stream->flags |= F_READ;
|
||||||
|
if (stream->rpos >= stream->rstop)
|
||||||
|
{
|
||||||
|
stdio_refill(stream);
|
||||||
|
}
|
||||||
|
if (stream->rpos >= stream->rstop)
|
||||||
|
{
|
||||||
|
return EOF; /* F_EOF or F_ERR set by refill */
|
||||||
|
}
|
||||||
|
return *stream->rpos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
getchar_unlocked(void)
|
||||||
|
{
|
||||||
|
return getc_unlocked(stdin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOLINTBEGIN(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
|
||||||
|
int
|
||||||
|
putc_unlocked(int c, FILE *stream)
|
||||||
|
{
|
||||||
|
stdio_init_if_needed(stream);
|
||||||
|
if (!(stream->flags & F_WRITE) && !(stream->flags & F_RDWR))
|
||||||
|
{
|
||||||
|
/* The open mode does not allow writes. */
|
||||||
|
stream->flags |= F_ERR;
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
if (stream->rpos < stream->rstop)
|
||||||
|
{
|
||||||
|
/* Pending read data: discard it before writing (the read -> write
|
||||||
|
* mode switch on an update stream). */
|
||||||
|
if (stdio_discard_read(stream) < 0)
|
||||||
|
{
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream->flags |= F_WRITE;
|
||||||
|
if (stream->flags & F_UNBUF)
|
||||||
|
{
|
||||||
|
unsigned char ch = (unsigned char)c;
|
||||||
|
long r = __syscall3(SYS_write, stream->fd, (long)&ch, 1);
|
||||||
|
|
||||||
|
if (r != 1)
|
||||||
|
{
|
||||||
|
if (r < 0)
|
||||||
|
{
|
||||||
|
(void)syscall_ret(r); /* sets errno */
|
||||||
|
}
|
||||||
|
stream->flags |= F_ERR;
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
stream->pos += 1;
|
||||||
|
return ch;
|
||||||
|
}
|
||||||
|
if (stream->wpos >= stream->wstop)
|
||||||
|
{
|
||||||
|
if (stdio_flush(stream) < 0)
|
||||||
|
{
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*stream->wpos++ = (unsigned char)c;
|
||||||
|
if ((stream->flags & F_LINEBUF) && c == '\n')
|
||||||
|
{
|
||||||
|
if (stdio_flush(stream) < 0)
|
||||||
|
{
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (unsigned char)c;
|
||||||
|
}
|
||||||
|
// NOLINTEND(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling)
|
||||||
|
|
||||||
|
int
|
||||||
|
putchar_unlocked(int c)
|
||||||
|
{
|
||||||
|
return putc_unlocked(c, stdout);
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "stdio_impl.h"
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* popen/pclose (POSIX base, todo 20): run "sh -c cmd" with a pipe
|
||||||
|
* attached to its stdin ("w") or stdout ("r").
|
||||||
|
*
|
||||||
|
* The stream is a todo-15 FILE built on the FILE core. popen does NOT go
|
||||||
|
* through the public fdopen(): todo 15's fdopen validates the requested
|
||||||
|
* mode against the descriptor's F_GETFL access mode by masking with
|
||||||
|
* O_RDWR, so a plain O_WRONLY descriptor is misread as O_RDONLY and a
|
||||||
|
* "w" fdopen is rejected — and the pipe write end is exactly that case
|
||||||
|
* (F_SETFL cannot promote a pipe end to O_RDWR). popen knows the access
|
||||||
|
* mode of its own freshly created pipe end, so it goes straight to the
|
||||||
|
* FILE core: stdio_parse_mode + stdio_alloc_file. Only the POSIX modes
|
||||||
|
* "r" and "w" are accepted; the glibc "re"/"we" close-on-exec extension
|
||||||
|
* is not. There is no sys/wait.h yet (todo 23): pclose waits via the
|
||||||
|
* raw SYS_wait4, and the child exits via the raw SYS_exit_group when
|
||||||
|
* the exec fails.
|
||||||
|
*
|
||||||
|
* Registry: a static table mapping FILE* to the shell pid. The todo-15
|
||||||
|
* FILE struct has no spare field for the pid, so pclose looks the
|
||||||
|
* stream up here. FOPEN_MAX (16) slots; a full table fails with EMFILE.
|
||||||
|
* Not thread-safe — fine while there is no thread runtime (todo 45).
|
||||||
|
*
|
||||||
|
* pclose closes the stream first, then waits: for "w" streams the close
|
||||||
|
* flushes the buffered writes and delivers EOF, letting the shell exit;
|
||||||
|
* waiting first could deadlock on a full pipe. A stream not opened by
|
||||||
|
* popen is still closed, and pclose returns -1 as POSIX requires. A wait
|
||||||
|
* failure (ECHILD) returns -1 with errno set.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct vlibc_popen_entry
|
||||||
|
{
|
||||||
|
FILE *stream;
|
||||||
|
pid_t pid;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct vlibc_popen_entry popen_table[FOPEN_MAX];
|
||||||
|
|
||||||
|
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
|
||||||
|
FILE *
|
||||||
|
popen(const char *command, const char *mode)
|
||||||
|
{
|
||||||
|
int fds[2] = {0}; /* written by SYS_pipe; the analyzer cannot model it */
|
||||||
|
int parent_fd;
|
||||||
|
int child_fd;
|
||||||
|
int read_mode;
|
||||||
|
int m;
|
||||||
|
int oflags;
|
||||||
|
pid_t pid;
|
||||||
|
FILE *f;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (mode[0] == 'r' && mode[1] == '\0')
|
||||||
|
{
|
||||||
|
read_mode = 1;
|
||||||
|
}
|
||||||
|
else if (mode[0] == 'w' && mode[1] == '\0')
|
||||||
|
{
|
||||||
|
read_mode = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
(void)stdio_parse_mode(mode, &m, &oflags); /* "r"/"w" always parse */
|
||||||
|
|
||||||
|
if (syscall_ret(__syscall1(SYS_pipe, (long)fds)) < 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (read_mode)
|
||||||
|
{
|
||||||
|
parent_fd = fds[0]; /* parent reads the shell's stdout */
|
||||||
|
child_fd = fds[1];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parent_fd = fds[1]; /* parent writes the shell's stdin */
|
||||||
|
child_fd = fds[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
pid = fork();
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
char *sh_argv[4];
|
||||||
|
int target = read_mode ? 1 : 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Child: wire the pipe end onto the shell's fd (0 for "w", 1
|
||||||
|
* for "r"), close everything else. The guards cover the
|
||||||
|
* (theoretical) case where the pipe handed us fd 0 or 1.
|
||||||
|
*/
|
||||||
|
if (parent_fd != target)
|
||||||
|
{
|
||||||
|
(void)__syscall1(SYS_close, parent_fd);
|
||||||
|
}
|
||||||
|
if (child_fd != target)
|
||||||
|
{
|
||||||
|
(void)__syscall2(SYS_dup2, child_fd, target);
|
||||||
|
(void)__syscall1(SYS_close, child_fd);
|
||||||
|
}
|
||||||
|
sh_argv[0] = "sh";
|
||||||
|
sh_argv[1] = "-c";
|
||||||
|
sh_argv[2] = (char *)command;
|
||||||
|
sh_argv[3] = 0;
|
||||||
|
execve("/bin/sh", sh_argv, environ);
|
||||||
|
__syscall1(SYS_exit_group, 127); /* exec failed */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parent. */
|
||||||
|
(void)__syscall1(SYS_close, child_fd);
|
||||||
|
if (pid < 0)
|
||||||
|
{
|
||||||
|
(void)__syscall1(SYS_close, parent_fd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
f = stdio_alloc_file(parent_fd, m);
|
||||||
|
if (f == 0)
|
||||||
|
{
|
||||||
|
/* Drop the descriptor and reap the shell we just spawned. */
|
||||||
|
(void)__syscall1(SYS_close, parent_fd);
|
||||||
|
(void)__syscall4(SYS_wait4, pid, 0, 0, 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < FOPEN_MAX; i++)
|
||||||
|
{
|
||||||
|
if (popen_table[i].stream == 0)
|
||||||
|
{
|
||||||
|
popen_table[i].stream = f;
|
||||||
|
popen_table[i].pid = pid;
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Registry full: drop the stream and reap the shell. */
|
||||||
|
(void)fclose(f);
|
||||||
|
(void)__syscall4(SYS_wait4, pid, 0, 0, 0);
|
||||||
|
errno = EMFILE;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// NOLINTEND(bugprone-easily-swappable-parameters)
|
||||||
|
|
||||||
|
int
|
||||||
|
pclose(FILE *stream)
|
||||||
|
{
|
||||||
|
pid_t pid = 0;
|
||||||
|
int found = 0;
|
||||||
|
int status = 0;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < FOPEN_MAX; i++)
|
||||||
|
{
|
||||||
|
if (popen_table[i].stream == stream)
|
||||||
|
{
|
||||||
|
pid = popen_table[i].pid;
|
||||||
|
popen_table[i].stream = 0;
|
||||||
|
popen_table[i].pid = 0;
|
||||||
|
found = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)fclose(stream);
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
/* Not a popen stream: POSIX wants -1. The stream is still
|
||||||
|
* closed. */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
long r = __syscall4(SYS_wait4, pid, (long)&status, 0, 0);
|
||||||
|
|
||||||
|
if (r < 0 && -r == EINTR)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (r < 0)
|
||||||
|
{
|
||||||
|
return syscall_ret(r); /* -1 + errno (e.g. ECHILD) */
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
+1247
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,150 @@
|
|||||||
|
#ifndef VLIBC_STDIO_STDIO_IMPL_H
|
||||||
|
#define VLIBC_STDIO_STDIO_IMPL_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — internal FILE layout and stream helpers (todo 15).
|
||||||
|
*
|
||||||
|
* struct vlibc_FILE is the object behind the opaque FILE of <stdio.h>.
|
||||||
|
* Position invariant (authoritative):
|
||||||
|
*
|
||||||
|
* - Read mode: `pos` is the file offset of buf[0]. The logical position
|
||||||
|
* (offset of the byte at rpos) is pos + (rpos - buf), and the kernel
|
||||||
|
* position is pos + (rstop - buf); with an empty buffer
|
||||||
|
* (rpos == rstop) the kernel sits exactly at the logical position.
|
||||||
|
* - Write mode: `pos` is the file offset of buf[0]; the next written byte
|
||||||
|
* lands at pos + (wpos - buf). With an empty buffer the kernel sits at
|
||||||
|
* pos. A flush advances pos by the number of bytes written.
|
||||||
|
*
|
||||||
|
* Mode switching: reading while write data is pending flushes it first;
|
||||||
|
* writing while unread buffered data is pending seeks the kernel back to
|
||||||
|
* the logical position (discarding the unread data). F_WRITE doubles as
|
||||||
|
* the current-mode marker and is cleared by the read switch; F_RDWR
|
||||||
|
* records the write capability of update streams so the write switch can
|
||||||
|
* re-set F_WRITE. F_PUSHED marks the single guaranteed ungetc pushback
|
||||||
|
* byte in `ungot`; a successful seek discards it.
|
||||||
|
*
|
||||||
|
* The helper prototypes below are shared with the formatted-I/O todos
|
||||||
|
* (16: vfprintf, 17: vfscanf) so they operate on the same buffer state.
|
||||||
|
*
|
||||||
|
* Locking (todo 18): the process is single-threaded until the thread
|
||||||
|
* todo (#45) lands, so flockfile/ftrylockfile/funlockfile only track
|
||||||
|
* ownership state here. `lock` is the recursion depth (0 = unlocked),
|
||||||
|
* `lock_owner` the owning thread's FS base while locked. When real
|
||||||
|
* threads arrive, the same todo turns `lock` into the actual per-stream
|
||||||
|
* lock word (blocking acquisition, mutex/cond state) and these two
|
||||||
|
* fields become the bookkeeping that makes flockfile recursive.
|
||||||
|
*
|
||||||
|
* Memory streams (todo 18): fmemopen/open_memstream build a FILE whose
|
||||||
|
* `mem` points at an opaque backend (defined in fmemopen.c) instead of a
|
||||||
|
* descriptor. Whenever `mem` is non-NULL the buffered core below calls
|
||||||
|
* back into fmemopen.c's stdio_mem_* hooks in place of every descriptor
|
||||||
|
* syscall, so the position invariant above is unchanged: the backend
|
||||||
|
* simply acts as an in-memory device with the same kernel-position
|
||||||
|
* semantics (reads advance it past the read-ahead, writes advance it on
|
||||||
|
* flush, discards rewind it to the logical position).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "../internal/libc.h"
|
||||||
|
|
||||||
|
/* Opaque memory-stream backend (fmemopen/open_memstream, todo 18). */
|
||||||
|
struct vlibc_memstream;
|
||||||
|
|
||||||
|
struct vlibc_FILE
|
||||||
|
{
|
||||||
|
unsigned char *buf; /* buffer base: library- or user-supplied */
|
||||||
|
unsigned char *rpos; /* read mode: next byte to deliver */
|
||||||
|
unsigned char *rstop; /* read mode: end of valid buffered data */
|
||||||
|
unsigned char *wpos; /* write mode: next byte to store */
|
||||||
|
unsigned char *wstop; /* write mode: end of the buffer */
|
||||||
|
int fd; /* underlying descriptor; -1 when closed */
|
||||||
|
int flags; /* F_* bits below */
|
||||||
|
size_t buf_size; /* usable buffer size; 0 = not yet sized */
|
||||||
|
off_t pos; /* position per the invariant above */
|
||||||
|
struct vlibc_FILE *next; /* registry link for fflush(NULL) */
|
||||||
|
unsigned char ungot; /* pushed-back byte when F_PUSHED */
|
||||||
|
int lock; /* flockfile recursion depth; 0 = unlocked */
|
||||||
|
void *lock_owner; /* owning thread's FS base while locked */
|
||||||
|
struct vlibc_memstream *mem; /* memory-stream backend, or NULL */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Stream flags. */
|
||||||
|
#define F_READ 0x001 /* reads are enabled by the open mode */
|
||||||
|
#define F_WRITE 0x002 /* writes are enabled by the open mode */
|
||||||
|
#define F_EOF 0x004 /* end of file has been seen */
|
||||||
|
#define F_ERR 0x008 /* an I/O error occurred */
|
||||||
|
#define F_LINEBUF 0x010 /* line buffered: flush on newline */
|
||||||
|
#define F_UNBUF 0x020 /* unbuffered: 1-byte buffer */
|
||||||
|
#define F_APPEND 0x040 /* append mode: writes land at end of file */
|
||||||
|
#define F_OWNBUF 0x080 /* buf was allocated by the library */
|
||||||
|
#define F_HEAP 0x100 /* the FILE struct itself is heap-allocated */
|
||||||
|
#define F_PUSHED 0x200 /* ungot holds a pushed-back byte */
|
||||||
|
#define F_RDWR 0x400 /* update stream: reads and writes both enabled */
|
||||||
|
|
||||||
|
/* Flush pending write data; returns 0, or -1 with F_ERR set. */
|
||||||
|
hidden int
|
||||||
|
stdio_flush(FILE *f);
|
||||||
|
|
||||||
|
/* Refill the read buffer from the descriptor (read mode only). */
|
||||||
|
hidden void
|
||||||
|
stdio_refill(FILE *f);
|
||||||
|
|
||||||
|
/* Discard unread buffered data, seeking the kernel back to pos. */
|
||||||
|
hidden int
|
||||||
|
stdio_discard_read(FILE *f);
|
||||||
|
|
||||||
|
/* Allocate the buffer lazily (std streams, setvbuf with NULL). */
|
||||||
|
hidden void
|
||||||
|
stdio_init_if_needed(FILE *f);
|
||||||
|
|
||||||
|
/* Allocate and register a FILE over an open descriptor. */
|
||||||
|
hidden FILE *
|
||||||
|
stdio_alloc_file(int fd, int m);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parse a fopen-style mode string. On success stores the F_READ/F_WRITE
|
||||||
|
* capability bits in *m, the openat flags in *oflags, and returns 1;
|
||||||
|
* returns 0 for an invalid mode.
|
||||||
|
*/
|
||||||
|
hidden int
|
||||||
|
stdio_parse_mode(const char *mode, int *m, int *oflags);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Raw lseek with errno translation. Separate from syscall_ret() because
|
||||||
|
* syscall_ret narrows results to int and would truncate large offsets.
|
||||||
|
*/
|
||||||
|
hidden off_t
|
||||||
|
stdio_lseek(int fd, off_t off, int whence);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Memory-stream device hooks (fmemopen.c, todo 18). The stdio core calls
|
||||||
|
* these in place of the descriptor syscalls whenever f->mem is set:
|
||||||
|
*
|
||||||
|
* - stdio_mem_flush: write the pending cache to the backing region
|
||||||
|
* (stdio_flush's device-write half, F_APPEND included).
|
||||||
|
* - stdio_mem_refill: read the next cache-full from the region
|
||||||
|
* (stdio_refill's device-read half).
|
||||||
|
* - stdio_mem_discard: rewind the device to the logical position,
|
||||||
|
* discarding unread cached data (stdio_discard_read's seek half).
|
||||||
|
* - stdio_mem_lseek: resolve a SEEK_SET/SEEK_END target against the
|
||||||
|
* region (used by fseeko instead of stdio_lseek on memory streams).
|
||||||
|
* - stdio_mem_close: run on fclose before the FILE is released
|
||||||
|
* (open_memstream's final NUL/size sync, owned-buffer release).
|
||||||
|
*/
|
||||||
|
hidden int
|
||||||
|
stdio_mem_flush(FILE *f);
|
||||||
|
|
||||||
|
hidden void
|
||||||
|
stdio_mem_refill(FILE *f);
|
||||||
|
|
||||||
|
hidden int
|
||||||
|
stdio_mem_discard(FILE *f);
|
||||||
|
|
||||||
|
hidden off_t
|
||||||
|
stdio_mem_lseek(FILE *f, off_t off, int whence);
|
||||||
|
|
||||||
|
hidden void
|
||||||
|
stdio_mem_close(FILE *f);
|
||||||
|
|
||||||
|
#endif /* VLIBC_STDIO_STDIO_IMPL_H */
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,842 @@
|
|||||||
|
/* internal unsigned 2^32-limb bignum core for printf float formatting (todo 16)
|
||||||
|
* clean-room implementation; nothing here is exported */
|
||||||
|
/*
|
||||||
|
* Invariants: limbs are little-endian (value = sum d[i] * 2^(32*i)); n == 0
|
||||||
|
* means value 0; no trailing-zero-limb invariant is kept -- callers normalize
|
||||||
|
* via vfpn_bn_norm. Shifts and rounding operate in place; allocation failure
|
||||||
|
* is reported as -1 and leaves errno to __libc_malloc.
|
||||||
|
*/
|
||||||
|
#include "../internal/malloc.h"
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
struct vfpn_bn
|
||||||
|
{
|
||||||
|
uint32_t fixed[640];
|
||||||
|
uint32_t *d;
|
||||||
|
size_t n;
|
||||||
|
size_t cap;
|
||||||
|
int heap;
|
||||||
|
};
|
||||||
|
|
||||||
|
static __attribute__((unused)) void
|
||||||
|
vfpn_bn_init(struct vfpn_bn *b)
|
||||||
|
{
|
||||||
|
b->d = b->fixed;
|
||||||
|
b->n = 0;
|
||||||
|
b->cap = 640;
|
||||||
|
b->heap = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static __attribute__((unused)) void
|
||||||
|
vfpn_bn_free(struct vfpn_bn *b)
|
||||||
|
{
|
||||||
|
if (b->heap)
|
||||||
|
{
|
||||||
|
__libc_free(b->d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static __attribute__((unused)) int
|
||||||
|
vfpn_bn_reserve(struct vfpn_bn *b, size_t need)
|
||||||
|
{
|
||||||
|
if (need <= b->cap)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t *new = __libc_malloc(need * sizeof(uint32_t));
|
||||||
|
if (!new)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t copy = (b->cap < need) ? b->cap : need;
|
||||||
|
__builtin_memcpy(new, b->d, copy * sizeof(uint32_t));
|
||||||
|
if (b->heap)
|
||||||
|
{
|
||||||
|
__libc_free(b->d);
|
||||||
|
}
|
||||||
|
|
||||||
|
b->d = new;
|
||||||
|
b->cap = need;
|
||||||
|
b->heap = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static __attribute__((unused)) void
|
||||||
|
vfpn_bn_norm(struct vfpn_bn *b)
|
||||||
|
{
|
||||||
|
while (b->n > 0 && b->d[b->n - 1] == 0)
|
||||||
|
{
|
||||||
|
b->n--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static __attribute__((unused)) int
|
||||||
|
vfpn_bn_set_u64(struct vfpn_bn *b, uint64_t v)
|
||||||
|
{
|
||||||
|
size_t need = 0;
|
||||||
|
for (uint64_t t = v; t != 0; t >>= 32)
|
||||||
|
{
|
||||||
|
need++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vfpn_bn_reserve(b, need) != 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
b->n = 0;
|
||||||
|
while (v != 0)
|
||||||
|
{
|
||||||
|
b->d[b->n++] = (uint32_t)v;
|
||||||
|
v >>= 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static __attribute__((unused)) int
|
||||||
|
vfpn_bn_mul_small(struct vfpn_bn *b, uint32_t m)
|
||||||
|
{
|
||||||
|
if (m == 0)
|
||||||
|
{
|
||||||
|
b->n = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vfpn_bn_reserve(b, b->n + 1) != 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t carry = 0;
|
||||||
|
for (size_t i = 0; i < b->n; i++)
|
||||||
|
{
|
||||||
|
uint64_t cur = (uint64_t)b->d[i] * m + carry;
|
||||||
|
b->d[i] = (uint32_t)cur;
|
||||||
|
carry = cur >> 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (carry != 0)
|
||||||
|
{
|
||||||
|
b->d[b->n++] = (uint32_t)carry;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static __attribute__((unused)) int
|
||||||
|
vfpn_bn_shl(struct vfpn_bn *b, size_t bits)
|
||||||
|
{
|
||||||
|
if (bits == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t wl = bits / 32;
|
||||||
|
size_t s = bits % 32;
|
||||||
|
size_t newn = b->n + wl + ((s != 0) ? 1 : 0);
|
||||||
|
if (vfpn_bn_reserve(b, newn) != 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s == 0)
|
||||||
|
{
|
||||||
|
for (size_t i = b->n; i-- > 0;)
|
||||||
|
{
|
||||||
|
b->d[i + wl] = b->d[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < wl; i++)
|
||||||
|
{
|
||||||
|
b->d[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (size_t j = newn; j-- > 0;)
|
||||||
|
{
|
||||||
|
uint64_t cur = 0;
|
||||||
|
if (j >= wl && j - wl < b->n)
|
||||||
|
{
|
||||||
|
cur |= (uint64_t)b->d[j - wl] << s;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j > wl && j - wl - 1 < b->n)
|
||||||
|
{
|
||||||
|
cur |= (uint64_t)b->d[j - wl - 1] >> (32 - s);
|
||||||
|
}
|
||||||
|
|
||||||
|
b->d[j] = (uint32_t)cur;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b->n = newn;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static __attribute__((unused)) int
|
||||||
|
vfpn_bn_add_one(struct vfpn_bn *b)
|
||||||
|
{
|
||||||
|
size_t i = 0;
|
||||||
|
while (i < b->n)
|
||||||
|
{
|
||||||
|
b->d[i]++;
|
||||||
|
if (b->d[i] != 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vfpn_bn_reserve(b, b->n + 1) != 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
b->d[b->n] = 1;
|
||||||
|
b->n++;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static __attribute__((unused)) int
|
||||||
|
vfpn_bn_is_zero(const struct vfpn_bn *b)
|
||||||
|
{
|
||||||
|
return b->n == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static __attribute__((unused)) int
|
||||||
|
vfpn_bn_cmp(const struct vfpn_bn *a, const struct vfpn_bn *b)
|
||||||
|
{
|
||||||
|
if (a->n < b->n)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a->n > b->n)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = a->n; i-- > 0;)
|
||||||
|
{
|
||||||
|
if (a->d[i] < b->d[i])
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a->d[i] > b->d[i])
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static __attribute__((unused)) int
|
||||||
|
vfpn_bn_shr_round(struct vfpn_bn *b, size_t bits)
|
||||||
|
{
|
||||||
|
if (bits == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t w = bits / 32;
|
||||||
|
size_t s = bits % 32;
|
||||||
|
|
||||||
|
if (w >= b->n)
|
||||||
|
{
|
||||||
|
/* Whole value discarded: result is 0, rounded up iff the original
|
||||||
|
* value exceeds 2^(bits-1). That can only happen when bits == 32*n
|
||||||
|
* (s == 0 and w == n), so compare the top limb against half. */
|
||||||
|
int round = 0;
|
||||||
|
if (s == 0 && w == b->n && b->n > 0)
|
||||||
|
{
|
||||||
|
uint32_t top = b->d[b->n - 1];
|
||||||
|
if (top > 0x80000000u)
|
||||||
|
{
|
||||||
|
round = 1;
|
||||||
|
}
|
||||||
|
else if (top == 0x80000000u)
|
||||||
|
{
|
||||||
|
for (size_t i = b->n - 1; i-- > 0;)
|
||||||
|
{
|
||||||
|
if (b->d[i] != 0)
|
||||||
|
{
|
||||||
|
round = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b->n = 0;
|
||||||
|
if (round)
|
||||||
|
{
|
||||||
|
return vfpn_bn_add_one(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Round bit and sticky are captured from the discarded low `bits` bits
|
||||||
|
* of the original value before the in-place shift. */
|
||||||
|
int round_bit;
|
||||||
|
int sticky;
|
||||||
|
if (s > 0)
|
||||||
|
{
|
||||||
|
round_bit = (int)((b->d[w] >> (s - 1)) & 1u);
|
||||||
|
sticky = (b->d[w] & ((1u << (s - 1)) - 1u)) != 0;
|
||||||
|
for (size_t i = 0; i < w; i++)
|
||||||
|
{
|
||||||
|
if (b->d[i] != 0)
|
||||||
|
{
|
||||||
|
sticky = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
round_bit = (int)((b->d[w - 1] >> 31) & 1u);
|
||||||
|
sticky = (b->d[w - 1] & 0x7FFFFFFFu) != 0;
|
||||||
|
for (size_t i = 0; i + 1 < w; i++)
|
||||||
|
{
|
||||||
|
if (b->d[i] != 0)
|
||||||
|
{
|
||||||
|
sticky = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* In-place right shift by `bits`; ascending so every source limb is
|
||||||
|
* still readable when its destination is written. */
|
||||||
|
size_t old_n = b->n;
|
||||||
|
if (s == 0)
|
||||||
|
{
|
||||||
|
for (size_t j = 0; j + w < old_n; j++)
|
||||||
|
{
|
||||||
|
b->d[j] = b->d[j + w];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (size_t j = 0; j + w < old_n; j++)
|
||||||
|
{
|
||||||
|
uint32_t lo = b->d[j + w] >> s;
|
||||||
|
uint32_t hi = (j + w + 1 < old_n) ? (b->d[j + w + 1] << (32 - s)) : 0;
|
||||||
|
b->d[j] = lo | hi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b->n = old_n - w;
|
||||||
|
vfpn_bn_norm(b);
|
||||||
|
|
||||||
|
int odd = (b->n > 0 && (b->d[0] & 1u)) ? 1 : 0;
|
||||||
|
if (round_bit && (sticky || odd))
|
||||||
|
{
|
||||||
|
return vfpn_bn_add_one(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static __attribute__((unused)) uint32_t
|
||||||
|
vfpn_bn_divmod_small_1e9(struct vfpn_bn *b)
|
||||||
|
{
|
||||||
|
uint64_t r = 0;
|
||||||
|
for (size_t i = b->n; i-- > 0;)
|
||||||
|
{
|
||||||
|
uint64_t cur = (r << 32) | b->d[i];
|
||||||
|
b->d[i] = (uint32_t)(cur / 1000000000ULL);
|
||||||
|
r = cur % 1000000000ULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
vfpn_bn_norm(b);
|
||||||
|
return (uint32_t)r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static __attribute__((unused)) int
|
||||||
|
vfpn_bn_pow5(struct vfpn_bn *b, size_t k)
|
||||||
|
{
|
||||||
|
int r = vfpn_bn_set_u64(b, 1);
|
||||||
|
if (r != 0)
|
||||||
|
{
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < k; i++)
|
||||||
|
{
|
||||||
|
r = vfpn_bn_mul_small(b, 5);
|
||||||
|
if (r != 0)
|
||||||
|
{
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- exact decimal rounding layer ------------------------------------ */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Number of bits needed to hold |b| (index of the highest set bit plus one);
|
||||||
|
* 0 for the value zero. The caller may leave a trailing zero limb in place,
|
||||||
|
* so the scan starts from the recorded length and skips down.
|
||||||
|
*/
|
||||||
|
static __attribute__((unused)) size_t
|
||||||
|
vfpn_bn_bitlen(const struct vfpn_bn *b)
|
||||||
|
{
|
||||||
|
if (b->n == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t i = b->n - 1;
|
||||||
|
while (i > 0 && b->d[i] == 0)
|
||||||
|
{
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b->d[i] == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return i * 32 + (size_t)(32 - __builtin_clz(b->d[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static __attribute__((unused)) int
|
||||||
|
vfpn_bn_get_bit(const struct vfpn_bn *b, size_t i)
|
||||||
|
{
|
||||||
|
return (int)((b->d[i / 32] >> (i % 32)) & 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set bit i of b. Zero-extends the limb array when the bit lies beyond the
|
||||||
|
* current length; ORing never clears, so the value only grows.
|
||||||
|
*/
|
||||||
|
static __attribute__((unused)) int
|
||||||
|
vfpn_bn_set_bit(struct vfpn_bn *b, size_t i, int bit)
|
||||||
|
{
|
||||||
|
if (!bit)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b->n <= i / 32)
|
||||||
|
{
|
||||||
|
size_t need = i / 32 + 1;
|
||||||
|
if (vfpn_bn_reserve(b, need) != 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t j = b->n; j < need; j++)
|
||||||
|
{
|
||||||
|
b->d[j] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
b->n = need;
|
||||||
|
}
|
||||||
|
|
||||||
|
b->d[i / 32] |= (1u << (i % 32));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Shift |b| left by one bit in place (limb ripple). The caller's cap must
|
||||||
|
* already make room for the possible extra top limb; the reserve here is
|
||||||
|
* only a safety net and normally returns immediately.
|
||||||
|
*/
|
||||||
|
static __attribute__((unused)) int
|
||||||
|
vfpn_bn_shl1(struct vfpn_bn *b)
|
||||||
|
{
|
||||||
|
if (b->n == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vfpn_bn_reserve(b, b->n + 1) != 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t carry = 0;
|
||||||
|
for (size_t i = 0; i < b->n; i++)
|
||||||
|
{
|
||||||
|
uint32_t cur = b->d[i];
|
||||||
|
b->d[i] = (cur << 1) | carry;
|
||||||
|
carry = cur >> 31;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (carry != 0)
|
||||||
|
{
|
||||||
|
b->d[b->n++] = carry;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In-place limb-wise subtraction a -= b. The caller guarantees a >= b, so
|
||||||
|
* the borrow always clears before the top of a; a is renormalized.
|
||||||
|
*/
|
||||||
|
static __attribute__((unused)) void
|
||||||
|
vfpn_bn_sub_inplace(struct vfpn_bn *a, const struct vfpn_bn *b)
|
||||||
|
{
|
||||||
|
uint32_t borrow = 0;
|
||||||
|
size_t i = 0;
|
||||||
|
for (; i < b->n; i++)
|
||||||
|
{
|
||||||
|
uint64_t diff = (uint64_t)a->d[i] - (uint64_t)b->d[i] - borrow;
|
||||||
|
a->d[i] = (uint32_t)diff;
|
||||||
|
borrow = (uint32_t)((diff >> 32) & 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (i < a->n && borrow != 0)
|
||||||
|
{
|
||||||
|
a->d[i]--;
|
||||||
|
borrow = (a->d[i] == 0xFFFFFFFFu) ? 1u : 0u;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
vfpn_bn_norm(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Q = N / D, R = N mod D by restoring binary long division. N may be zero;
|
||||||
|
* D must be nonzero. Q and R are written over whatever they held. The
|
||||||
|
* quotients bits are produced most-significant first; the loop runs over the
|
||||||
|
* dividend's bit positions from bitlen(N)-1 down to 0, pulling one new
|
||||||
|
* dividend bit into R and subtracting D whenever R >= D.
|
||||||
|
*/
|
||||||
|
static __attribute__((unused)) int
|
||||||
|
vfpn_divmod_binary(struct vfpn_bn *Q, struct vfpn_bn *R, const struct vfpn_bn *N,
|
||||||
|
const struct vfpn_bn *D)
|
||||||
|
{
|
||||||
|
if (vfpn_bn_is_zero(D))
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vfpn_bn_reserve(Q, N->n + 2) != 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vfpn_bn_reserve(R, N->n + 2) != 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Q->n = 0;
|
||||||
|
R->n = 0;
|
||||||
|
|
||||||
|
size_t nbits = vfpn_bn_bitlen(N);
|
||||||
|
for (size_t i = nbits; i-- > 0;)
|
||||||
|
{
|
||||||
|
if (vfpn_bn_shl1(R) != 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vfpn_bn_set_bit(R, 0, vfpn_bn_get_bit(N, i)) != 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vfpn_bn_cmp(R, D) >= 0)
|
||||||
|
{
|
||||||
|
vfpn_bn_sub_inplace(R, D);
|
||||||
|
if (vfpn_bn_set_bit(Q, i, 1) != 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vfpn_bn_norm(Q);
|
||||||
|
vfpn_bn_norm(R);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compare 2*a against b (both canonical base-2^32 digit strings) without
|
||||||
|
* building the doubled value: doubling a limb's carry is the next limb's
|
||||||
|
* (d[i] >> 31), which the loop below folds in, and an extra top digit
|
||||||
|
* appears when the original top limb had its high bit set. All digits of 2*a
|
||||||
|
* stay below 2^32, so a plain digitwise comparison is exact.
|
||||||
|
*/
|
||||||
|
static __attribute__((unused)) int
|
||||||
|
vfpn_bn_cmp_doubled(const struct vfpn_bn *a, const struct vfpn_bn *b)
|
||||||
|
{
|
||||||
|
if (a->n == 0)
|
||||||
|
{
|
||||||
|
return vfpn_bn_is_zero(b) ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t extra = (a->d[a->n - 1] >> 31) ? 1u : 0u;
|
||||||
|
size_t la = a->n + extra;
|
||||||
|
if (la != b->n)
|
||||||
|
{
|
||||||
|
return (la > b->n) ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = la; i-- > 0;)
|
||||||
|
{
|
||||||
|
uint32_t da;
|
||||||
|
if (extra != 0 && i == a->n)
|
||||||
|
{
|
||||||
|
da = 1; /* carry out of the doubled top limb */
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
da = (uint32_t)(a->d[i] << 1);
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
da += (a->d[i - 1] >> 31);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (da != b->d[i])
|
||||||
|
{
|
||||||
|
return (da < b->d[i]) ? -1 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* R = round_half_even(mant * 2^exp2 * 10^s), computed exactly as an integer
|
||||||
|
* bignum (mant > 0; s may be negative). s >= 0 scales R by 5^s and by a
|
||||||
|
* single power of two, rounding once at the end (nearest-even). s < 0 builds
|
||||||
|
* D = 5^(-s) and shifts either N or D so that value = N / D, divides exactly,
|
||||||
|
* and rounds the quotient half-even off the remainder: 2*rem > D rounds up,
|
||||||
|
* 2*rem == D rounds up only when the quotient is odd. Returns 0 or -1 on
|
||||||
|
* allocation failure (R is then unspecified).
|
||||||
|
*/
|
||||||
|
static __attribute__((unused)) int
|
||||||
|
vfpn_round_scale10(struct vfpn_bn *R, uint64_t mant, int exp2, long s)
|
||||||
|
{
|
||||||
|
struct vfpn_bn N;
|
||||||
|
struct vfpn_bn D;
|
||||||
|
struct vfpn_bn Q;
|
||||||
|
struct vfpn_bn rem;
|
||||||
|
vfpn_bn_init(&N);
|
||||||
|
vfpn_bn_init(&D);
|
||||||
|
vfpn_bn_init(&Q);
|
||||||
|
vfpn_bn_init(&rem);
|
||||||
|
|
||||||
|
if (s >= 0)
|
||||||
|
{
|
||||||
|
if (vfpn_bn_set_u64(R, mant) != 0)
|
||||||
|
{
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (long i = 0; i < s; i++)
|
||||||
|
{
|
||||||
|
if (vfpn_bn_mul_small(R, 5) != 0)
|
||||||
|
{
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long long sh = (long long)exp2 + (long long)s;
|
||||||
|
if (sh >= 0)
|
||||||
|
{
|
||||||
|
if (vfpn_bn_shl(R, (size_t)sh) != 0)
|
||||||
|
{
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (vfpn_bn_shr_round(R, (size_t)(-sh)) != 0)
|
||||||
|
{
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vfpn_bn_free(&N);
|
||||||
|
vfpn_bn_free(&D);
|
||||||
|
vfpn_bn_free(&Q);
|
||||||
|
vfpn_bn_free(&rem);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* value = mant * 2^exp2 / (2^t * 5^t) with t = -s: make it N / D. */
|
||||||
|
{
|
||||||
|
long long t = -(long long)s;
|
||||||
|
long long sh = (long long)exp2 - t;
|
||||||
|
|
||||||
|
if (vfpn_bn_pow5(&D, (size_t)t) != 0)
|
||||||
|
{
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vfpn_bn_set_u64(&N, mant) != 0)
|
||||||
|
{
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sh >= 0)
|
||||||
|
{
|
||||||
|
if (vfpn_bn_shl(&N, (size_t)sh) != 0)
|
||||||
|
{
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
vfpn_bn_norm(&N);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (vfpn_bn_shl(&D, (size_t)(-sh)) != 0)
|
||||||
|
{
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
vfpn_bn_norm(&D);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vfpn_divmod_binary(&Q, &rem, &N, &D) != 0)
|
||||||
|
{
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
int c = vfpn_bn_cmp_doubled(&rem, &D);
|
||||||
|
if (c > 0 || (c == 0 && Q.n > 0 && (Q.d[0] & 1u) != 0))
|
||||||
|
{
|
||||||
|
if (vfpn_bn_add_one(&Q) != 0)
|
||||||
|
{
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vfpn_bn_reserve(R, Q.n) != 0)
|
||||||
|
{
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Q.n > 0)
|
||||||
|
{
|
||||||
|
__builtin_memcpy(R->d, Q.d, Q.n * sizeof(uint32_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
R->n = Q.n;
|
||||||
|
}
|
||||||
|
|
||||||
|
vfpn_bn_free(&N);
|
||||||
|
vfpn_bn_free(&D);
|
||||||
|
vfpn_bn_free(&Q);
|
||||||
|
vfpn_bn_free(&rem);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
vfpn_bn_free(&N);
|
||||||
|
vfpn_bn_free(&D);
|
||||||
|
vfpn_bn_free(&Q);
|
||||||
|
vfpn_bn_free(&rem);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Decimal digits of |v| into out (most significant first, no terminating
|
||||||
|
* NUL); returns the digit count or -1 when outcap is too small. The value
|
||||||
|
* zero yields a single '0'. Groups of nine decimal digits are stripped from
|
||||||
|
* the low end into a fixed stack (a 640-limb value caps at ~6165 digits, far
|
||||||
|
* inside 700 groups), then re-emitted top group unpadded, lower groups
|
||||||
|
* zero-padded to nine.
|
||||||
|
*/
|
||||||
|
static __attribute__((unused)) int
|
||||||
|
vfpn_bn_to_dec(const struct vfpn_bn *v, char *out, size_t outcap)
|
||||||
|
{
|
||||||
|
if (vfpn_bn_is_zero(v))
|
||||||
|
{
|
||||||
|
if (outcap < 1)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
out[0] = '0';
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct vfpn_bn w;
|
||||||
|
vfpn_bn_init(&w);
|
||||||
|
if (vfpn_bn_reserve(&w, v->n) != 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
w.n = v->n;
|
||||||
|
if (v->n > 0)
|
||||||
|
{
|
||||||
|
__builtin_memcpy(w.d, v->d, v->n * sizeof(uint32_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t groups[700];
|
||||||
|
size_t ngroups = 0;
|
||||||
|
while (!vfpn_bn_is_zero(&w))
|
||||||
|
{
|
||||||
|
if (ngroups >= 700)
|
||||||
|
{
|
||||||
|
vfpn_bn_free(&w);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
groups[ngroups++] = vfpn_bn_divmod_small_1e9(&w);
|
||||||
|
}
|
||||||
|
|
||||||
|
vfpn_bn_free(&w);
|
||||||
|
|
||||||
|
size_t top = ngroups - 1;
|
||||||
|
uint32_t t = groups[top];
|
||||||
|
size_t tdig = 1;
|
||||||
|
for (uint32_t x = t / 10; x != 0; x /= 10)
|
||||||
|
{
|
||||||
|
tdig++;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ndigits = tdig + (ngroups - 1) * 9;
|
||||||
|
if (outcap < ndigits)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t pos = 0;
|
||||||
|
char tmp[10];
|
||||||
|
size_t ntmp = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
tmp[ntmp++] = (char)('0' + t % 10);
|
||||||
|
t /= 10;
|
||||||
|
} while (t != 0);
|
||||||
|
|
||||||
|
while (ntmp > 0)
|
||||||
|
{
|
||||||
|
out[pos++] = tmp[--ntmp];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t gi = ngroups - 1; gi-- > 0;)
|
||||||
|
{
|
||||||
|
uint32_t val = groups[gi];
|
||||||
|
for (int k = 8; k >= 0; k--)
|
||||||
|
{
|
||||||
|
out[pos + (size_t)k] = (char)('0' + val % 10);
|
||||||
|
val /= 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)ndigits;
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
@@ -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) */
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
@@ -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) */
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
@@ -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) */
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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) */
|
||||||
@@ -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 */
|
||||||
@@ -0,0 +1,423 @@
|
|||||||
|
/*
|
||||||
|
* vlibc — dirent test (todo 24).
|
||||||
|
*
|
||||||
|
* Exercises the dirent.h surface end to end against a scratch directory
|
||||||
|
* created with raw syscalls:
|
||||||
|
*
|
||||||
|
* 1. opendir on the temp dir lists exactly . .. alpha beta gamma; every
|
||||||
|
* entry carries a nonzero d_ino; . reports d_type DT_DIR and alpha
|
||||||
|
* DT_REG.
|
||||||
|
* 2. readdir past the end returns NULL; a repeated readdir stays NULL.
|
||||||
|
* 3. rewinddir re-lists the same five entries.
|
||||||
|
* 4. dirfd returns a usable descriptor; fdopendir succeeds on a raw
|
||||||
|
* directory descriptor.
|
||||||
|
* 5. Level 2: scandir + alphasort return the five entries sorted
|
||||||
|
* (. .. alpha beta gamma), NULL-terminated, with d_type copied, and
|
||||||
|
* the caller can free() every element and the array.
|
||||||
|
*
|
||||||
|
* The -f mode covers the failure paths, all asserted on return values
|
||||||
|
* only: opendir of a nonexistent directory returns NULL, fdopendir of a
|
||||||
|
* regular-file descriptor returns NULL, and a seekdir back to a saved
|
||||||
|
* telldir location resumes at the entry that originally followed it. The
|
||||||
|
* library writes errno on these paths, so -f exits through raw
|
||||||
|
* SYS_exit_group (house pattern); the default mode never triggers an
|
||||||
|
* errno-writing library path.
|
||||||
|
*
|
||||||
|
* All diagnostics go through raw SYS_write (no stdio); the test reads no
|
||||||
|
* host headers — under -Iinclude the vlibc public headers shadow GCC's
|
||||||
|
* internal ones. Not part of the library proper; compiled manually for
|
||||||
|
* this todo (the tests/ + make check wiring is owned by a later todo).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "../src/internal/syscall.h"
|
||||||
|
|
||||||
|
#define DIRPATH "/tmp/vlibc-t24"
|
||||||
|
|
||||||
|
static const char *const FILES[3] = {
|
||||||
|
DIRPATH "/alpha",
|
||||||
|
DIRPATH "/beta",
|
||||||
|
DIRPATH "/gamma",
|
||||||
|
};
|
||||||
|
|
||||||
|
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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drop any leftover scratch dir (files, then the dir itself); errors are
|
||||||
|
* fine when nothing exists yet. */
|
||||||
|
static void
|
||||||
|
rm_tree(void)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
__syscall3(SYS_unlinkat, AT_FDCWD, (long)FILES[i], 0);
|
||||||
|
}
|
||||||
|
__syscall3(SYS_unlinkat, AT_FDCWD, (long)DIRPATH, AT_REMOVEDIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create the scratch directory and its three one-byte files. */
|
||||||
|
static int
|
||||||
|
make_dir(void)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
rm_tree();
|
||||||
|
if ((int)__syscall3(SYS_mkdirat, AT_FDCWD, (long)DIRPATH, 0700) != 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
for (i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
fd = (int)__syscall4(SYS_openat, AT_FDCWD, (long)FILES[i],
|
||||||
|
O_WRONLY | O_CREAT | O_TRUNC, 0600);
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
__syscall3(SYS_write, fd, (long)"x", 1);
|
||||||
|
__syscall1(SYS_close, fd);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A full listing captured as copied names. */
|
||||||
|
struct listing
|
||||||
|
{
|
||||||
|
char names[8][64];
|
||||||
|
int count;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Drain dir, copying each entry name into ls. */
|
||||||
|
static void
|
||||||
|
fill_listing(DIR *d, struct listing *ls)
|
||||||
|
{
|
||||||
|
struct dirent *e;
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
while ((e = readdir(d)) != NULL)
|
||||||
|
{
|
||||||
|
if (n < 8)
|
||||||
|
{
|
||||||
|
strcpy(ls->names[n], e->d_name);
|
||||||
|
}
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
ls->count = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
has_name(const struct listing *ls, const char *name)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < ls->count; i++)
|
||||||
|
{
|
||||||
|
if (strcmp(ls->names[i], name) == 0)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
same_sets(const struct listing *x, const struct listing *y)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (x->count != y->count)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (i = 0; i < x->count; i++)
|
||||||
|
{
|
||||||
|
if (!has_name(y, x->names[i]))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scenarios 1-4: iteration, exhaustion, rewind, dirfd/fdopendir. */
|
||||||
|
static void
|
||||||
|
basic_scenarios(void)
|
||||||
|
{
|
||||||
|
struct listing first;
|
||||||
|
struct listing second;
|
||||||
|
DIR *d;
|
||||||
|
struct dirent *e;
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
d = opendir(DIRPATH);
|
||||||
|
check(d != NULL, "opendir on the temp directory succeeds");
|
||||||
|
if (d == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(dirfd(d) >= 0, "dirfd on the open stream returns a valid descriptor");
|
||||||
|
|
||||||
|
first.count = 0;
|
||||||
|
while ((e = readdir(d)) != NULL)
|
||||||
|
{
|
||||||
|
check(e->d_ino != 0, "every entry reports a nonzero d_ino");
|
||||||
|
if (strcmp(e->d_name, ".") == 0)
|
||||||
|
{
|
||||||
|
check(e->d_type == DT_DIR, "the . entry carries d_type DT_DIR");
|
||||||
|
}
|
||||||
|
if (strcmp(e->d_name, "alpha") == 0)
|
||||||
|
{
|
||||||
|
check(e->d_type == DT_REG, "the alpha file carries d_type DT_REG");
|
||||||
|
}
|
||||||
|
if (n < 8)
|
||||||
|
{
|
||||||
|
strcpy(first.names[n], e->d_name);
|
||||||
|
}
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
first.count = n;
|
||||||
|
check(first.count == 5, "the first pass lists exactly 5 entries");
|
||||||
|
check(readdir(d) == NULL, "readdir at end of directory returns NULL");
|
||||||
|
check(readdir(d) == NULL, "a further readdir at end still returns NULL");
|
||||||
|
check(has_name(&first, ".") && has_name(&first, "..") &&
|
||||||
|
has_name(&first, "alpha") && has_name(&first, "beta") &&
|
||||||
|
has_name(&first, "gamma"),
|
||||||
|
"the first pass sees . .. alpha beta gamma");
|
||||||
|
|
||||||
|
rewinddir(d);
|
||||||
|
fill_listing(d, &second);
|
||||||
|
check(second.count == 5, "after rewinddir the second pass lists 5 entries again");
|
||||||
|
check(same_sets(&first, &second), "the rewound listing matches the first listing");
|
||||||
|
check(closedir(d) == 0, "closedir returns 0");
|
||||||
|
|
||||||
|
{
|
||||||
|
int fd = (int)__syscall3(SYS_openat, AT_FDCWD, (long)DIRPATH,
|
||||||
|
O_RDONLY | O_DIRECTORY);
|
||||||
|
DIR *dd;
|
||||||
|
|
||||||
|
check(fd >= 0, "raw open of the directory succeeds");
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dd = fdopendir(fd);
|
||||||
|
check(dd != NULL, "fdopendir on a directory descriptor succeeds");
|
||||||
|
if (dd != NULL)
|
||||||
|
{
|
||||||
|
check(closedir(dd) == 0, "closedir after fdopendir returns 0");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
__syscall1(SYS_close, fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/* Scenario 5 (level 2): scandir + alphasort. */
|
||||||
|
static void
|
||||||
|
scandir_scenario(void)
|
||||||
|
{
|
||||||
|
struct dirent **names = NULL;
|
||||||
|
int n;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
n = scandir(DIRPATH, &names, NULL, alphasort);
|
||||||
|
check(n == 5, "scandir returns the 5 entries");
|
||||||
|
if (names == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(names[n] == NULL, "the scandir array is NULL-terminated");
|
||||||
|
check(strcmp(names[0]->d_name, ".") == 0 && strcmp(names[1]->d_name, "..") == 0,
|
||||||
|
"alphasort puts . and .. first in that order");
|
||||||
|
check(strcmp(names[2]->d_name, "alpha") == 0 &&
|
||||||
|
strcmp(names[3]->d_name, "beta") == 0 &&
|
||||||
|
strcmp(names[4]->d_name, "gamma") == 0,
|
||||||
|
"alphasort orders the files alpha beta gamma");
|
||||||
|
check(names[2]->d_type == DT_REG, "scandir copies d_type into its entries");
|
||||||
|
for (i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
free(names[i]);
|
||||||
|
}
|
||||||
|
free(names);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Failure scenarios (-f): every assertion is on the return value only.
|
||||||
|
* opendir/fdopendir write errno on these paths (host-TCB hazard), so the
|
||||||
|
* process exits through raw SYS_exit_group.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
failure_scenarios(void)
|
||||||
|
{
|
||||||
|
struct dirent *e1;
|
||||||
|
struct dirent *e2;
|
||||||
|
struct dirent *e3;
|
||||||
|
int rc = 0;
|
||||||
|
int fdf;
|
||||||
|
long loc;
|
||||||
|
DIR *d;
|
||||||
|
|
||||||
|
if (make_dir() != 0)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: cannot set up the temp directory\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (opendir("/nonexistent/vlibc-dir") != NULL)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: opendir on a nonexistent directory did not return NULL\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: opendir on a nonexistent directory -> NULL\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
fdf = (int)__syscall3(SYS_openat, AT_FDCWD, (long)FILES[0], O_RDONLY);
|
||||||
|
if (fdf >= 0)
|
||||||
|
{
|
||||||
|
d = fdopendir(fdf);
|
||||||
|
if (d != NULL)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: fdopendir on a regular-file descriptor did not return NULL\n");
|
||||||
|
rc = 1;
|
||||||
|
closedir(d);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: fdopendir on a regular-file descriptor -> NULL\n");
|
||||||
|
__syscall1(SYS_close, fdf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d = opendir(DIRPATH);
|
||||||
|
if (d == NULL)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: opendir failed for the seekdir round trip\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
e1 = readdir(d);
|
||||||
|
loc = telldir(d);
|
||||||
|
e2 = readdir(d);
|
||||||
|
if (e1 == NULL || e2 == NULL)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: not enough entries for the seekdir round trip\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
seekdir(d, loc);
|
||||||
|
e3 = readdir(d);
|
||||||
|
if (e3 != NULL && strcmp(e3->d_name, e2->d_name) == 0)
|
||||||
|
{
|
||||||
|
say(1, "PASS: seekdir back to a telldir location resumes at the same entry\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(2, "FAIL: seekdir did not resume at the telldir location\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(d);
|
||||||
|
}
|
||||||
|
rm_tree();
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
|
||||||
|
{
|
||||||
|
int rc = failure_scenarios();
|
||||||
|
|
||||||
|
__syscall1(SYS_exit_group, rc);
|
||||||
|
return rc; /* not reached */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (make_dir() != 0)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: cannot set up the temp directory\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
basic_scenarios();
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
scandir_scenario();
|
||||||
|
#endif
|
||||||
|
rm_tree();
|
||||||
|
|
||||||
|
if (failures > 0)
|
||||||
|
{
|
||||||
|
say(2, "FAILED (");
|
||||||
|
say_dec(2, (unsigned long)failures);
|
||||||
|
say(2, " check(s))\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
say(1, "all dirent tests passed\n");
|
||||||
|
return 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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,611 @@
|
|||||||
|
/*
|
||||||
|
* vlibc — getline/getdelim, stream locking, memory streams test (todo 18).
|
||||||
|
*
|
||||||
|
* Exercises the todo-18 additions end to end:
|
||||||
|
*
|
||||||
|
* 1. getline on a 1 MiB line returns 1000001 (delimiter included) with a
|
||||||
|
* NUL terminator, growing a NULL/zero-capacity buffer from scratch;
|
||||||
|
* the next call at end of file returns -1. Reuse keeps the grown
|
||||||
|
* buffer.
|
||||||
|
* 2. getdelim with ',' over "a,b,c\n" yields "a,", "b,", then the
|
||||||
|
* partial final field "c\n" at end of file, then -1.
|
||||||
|
* 3. Embedded NUL bytes survive: getline on "ab\0cd\n" returns 6 with
|
||||||
|
* the NUL at line[2] intact (length, not strlen).
|
||||||
|
* 4. A small preallocated buffer (malloc(4)) grows to fit a long line.
|
||||||
|
* 5. flockfile recursion: two nested flockfile calls, ftrylockfile
|
||||||
|
* failing (nonzero) under the recursion and succeeding (0) when the
|
||||||
|
* stream is free again.
|
||||||
|
* 6. getc_unlocked/putc_unlocked under a held flockfile move real data;
|
||||||
|
* getchar_unlocked/putchar_unlocked and puts work over freopen'd
|
||||||
|
* stdin/stdout.
|
||||||
|
* 7. fmemopen over a fixed buffer: write, rewind, read back
|
||||||
|
* byte-identical; seeking past the size reads EOF (no error);
|
||||||
|
* fmemopen(NULL, ...) grows and is freed by fclose (leak-checked);
|
||||||
|
* "a" starts at the current data length.
|
||||||
|
* 8. open_memstream: after fflush/fclose *sizeloc is the length and the
|
||||||
|
* buffer is NUL-terminated; *ptr tracks the (possibly moved) buffer.
|
||||||
|
* 9. fclose over many fmemopen/open_memstream cycles leaks nothing.
|
||||||
|
*
|
||||||
|
* Failure mode (-f): getline on a stream whose underlying descriptor was
|
||||||
|
* closed underneath it returns -1 (the read hits EBADF); the return value
|
||||||
|
* is asserted, never errno. -f exits via a raw SYS_exit_group before any
|
||||||
|
* host-libc cleanup runs (the tests/syscall_test.c discipline).
|
||||||
|
*
|
||||||
|
* errno is never READ here; the default mode keeps every library errno
|
||||||
|
* write out of the exercised paths. All diagnostics go through raw
|
||||||
|
* SYS_write and the only headers are vlibc's own.
|
||||||
|
*
|
||||||
|
* Not part of the library proper; compiled manually for this todo.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "../src/internal/syscall.h"
|
||||||
|
|
||||||
|
/* The allocator's heap-walk consistency probe (hidden; linked in via
|
||||||
|
* malloc.c). Returns the live block count, or (size_t)-1 on disagreement. */
|
||||||
|
extern size_t
|
||||||
|
__vlibc_malloc_check(void); // NOLINT(bugprone-reserved-identifier)
|
||||||
|
|
||||||
|
static int failures;
|
||||||
|
|
||||||
|
/* Write a NUL-terminated string to fd via the raw syscall layer. */
|
||||||
|
static 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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Byte-compare two buffers. noipa keeps -O2 from folding the check. */
|
||||||
|
// NOLINTBEGIN(clang-analyzer-unix.Stream)
|
||||||
|
static __attribute__((noipa)) int
|
||||||
|
mem_eq(const unsigned char *a, const unsigned char *b, unsigned long n)
|
||||||
|
{
|
||||||
|
unsigned long i;
|
||||||
|
|
||||||
|
for (i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
if (a[i] != b[i])
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Byte-compare two NUL-terminated strings. */
|
||||||
|
static __attribute__((noipa)) int
|
||||||
|
str_eq(const char *a, const char *b)
|
||||||
|
{
|
||||||
|
while (*a == *b && *a != '\0')
|
||||||
|
{
|
||||||
|
a++;
|
||||||
|
b++;
|
||||||
|
}
|
||||||
|
return *a == *b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read a whole small file through raw syscalls into buf; returns size. */
|
||||||
|
static long
|
||||||
|
raw_read_all(const char *path, char *buf, unsigned long cap)
|
||||||
|
{
|
||||||
|
long fd = __syscall4(SYS_openat, -100, (long)path, 0x0, 0);
|
||||||
|
long got = -1;
|
||||||
|
long total = 0;
|
||||||
|
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
while (total < (long)cap)
|
||||||
|
{
|
||||||
|
got = __syscall3(SYS_read, fd, (long)(buf + total), (long)(cap - (unsigned long)total));
|
||||||
|
if (got <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
total += got;
|
||||||
|
}
|
||||||
|
__syscall1(SYS_close, fd);
|
||||||
|
return got < 0 ? -1 : total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write path bytes to a fresh file, then reopen it for reading. */
|
||||||
|
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
|
||||||
|
static FILE *
|
||||||
|
write_file(const char *path, const char *data, unsigned long n)
|
||||||
|
{
|
||||||
|
FILE *f = fopen(path, "w");
|
||||||
|
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (fwrite(data, 1, n, f) != n)
|
||||||
|
{
|
||||||
|
(void)fclose(f);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (fclose(f) != 0)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
f = fopen(path, "r");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: write_file reopen\n");
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
// NOLINTEND(bugprone-easily-swappable-parameters)
|
||||||
|
|
||||||
|
static char onemeg[1000001];
|
||||||
|
|
||||||
|
/* 1/3/4: getline length, growth, EOF, embedded NUL. */
|
||||||
|
static void
|
||||||
|
line_scenario(void)
|
||||||
|
{
|
||||||
|
const char bigpath[] = "/tmp/vlibc-test-t18-bigline.txt";
|
||||||
|
const char nulpath[] = "/tmp/vlibc-test-t18-nul.txt";
|
||||||
|
const char longpath[] = "/tmp/vlibc-test-t18-long.txt";
|
||||||
|
const char longline[] = "this line is way longer than sixteen bytes\n";
|
||||||
|
FILE *f;
|
||||||
|
char *line;
|
||||||
|
size_t n;
|
||||||
|
ssize_t got;
|
||||||
|
unsigned long i;
|
||||||
|
|
||||||
|
for (i = 0; i < 1000000; i++)
|
||||||
|
{
|
||||||
|
onemeg[i] = 'x';
|
||||||
|
}
|
||||||
|
onemeg[1000000] = '\n';
|
||||||
|
f = fopen(bigpath, "w");
|
||||||
|
check(f != NULL, "bigline fopen w succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fwrite(onemeg, 1, sizeof(onemeg), f) == sizeof(onemeg), "bigline fwrite 1 MiB + newline");
|
||||||
|
check(fclose(f) == 0, "bigline fclose writer");
|
||||||
|
|
||||||
|
f = fopen(bigpath, "r");
|
||||||
|
check(f != NULL, "bigline fopen r succeeds");
|
||||||
|
line = NULL;
|
||||||
|
n = 0;
|
||||||
|
got = getline(&line, &n, f);
|
||||||
|
check(got == 1000001, "getline on the 1 MiB line returns 1000001");
|
||||||
|
check(line != NULL && n >= 1000002, "getline grew the buffer past 1 MiB + NUL");
|
||||||
|
if (line != NULL)
|
||||||
|
{
|
||||||
|
int allx = 1;
|
||||||
|
|
||||||
|
for (i = 0; i < 1000000; i++)
|
||||||
|
{
|
||||||
|
if (line[i] != 'x')
|
||||||
|
{
|
||||||
|
allx = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check(allx != 0, "the 1 MiB line content is intact");
|
||||||
|
check(line[1000000] == '\n', "the newline is kept at line[1000000]");
|
||||||
|
check(line[1000001] == '\0', "the line is NUL-terminated");
|
||||||
|
}
|
||||||
|
got = getline(&line, &n, f);
|
||||||
|
check(got == -1, "getline at end of file returns -1");
|
||||||
|
free(line);
|
||||||
|
check(fclose(f) == 0, "bigline fclose reader");
|
||||||
|
check(remove(bigpath) == 0, "bigline remove");
|
||||||
|
|
||||||
|
/* Embedded NUL bytes are data, not terminators. */
|
||||||
|
f = write_file(nulpath, "ab\0cd\n", 6);
|
||||||
|
if (f != NULL)
|
||||||
|
{
|
||||||
|
line = NULL;
|
||||||
|
n = 0;
|
||||||
|
got = getline(&line, &n, f);
|
||||||
|
check(got == 6, "getline over \"ab\\0cd\\n\" returns 6 bytes");
|
||||||
|
check(line != NULL && line[0] == 'a' && line[1] == 'b' && line[2] == '\0' &&
|
||||||
|
line[3] == 'c' && line[4] == 'd' && line[5] == '\n' && line[6] == '\0',
|
||||||
|
"embedded NUL survives and the line is still NUL-terminated");
|
||||||
|
free(line);
|
||||||
|
check(fclose(f) == 0, "nul fclose");
|
||||||
|
check(remove(nulpath) == 0, "nul remove");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A small preallocated buffer grows past its initial capacity. */
|
||||||
|
f = write_file(longpath, longline, sizeof(longline) - 1);
|
||||||
|
if (f != NULL)
|
||||||
|
{
|
||||||
|
line = malloc(4);
|
||||||
|
check(line != NULL, "small-buffer malloc");
|
||||||
|
n = 4;
|
||||||
|
got = getline(&line, &n, f);
|
||||||
|
check(got == (ssize_t)(sizeof(longline) - 1),
|
||||||
|
"getline with a 4-byte buffer reads the whole long line");
|
||||||
|
check(n > sizeof(longline) - 1, "the capacity grew past the line length");
|
||||||
|
check(line != NULL && str_eq(line, longline), "grown small buffer holds the line");
|
||||||
|
free(line);
|
||||||
|
check(fclose(f) == 0, "long fclose");
|
||||||
|
check(remove(longpath) == 0, "long remove");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2: getdelim with a custom delimiter. */
|
||||||
|
static void
|
||||||
|
delim_scenario(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-t18-delim.txt";
|
||||||
|
FILE *f;
|
||||||
|
char *line;
|
||||||
|
size_t n;
|
||||||
|
ssize_t got;
|
||||||
|
|
||||||
|
f = write_file(path, "a,b,c\n", 6);
|
||||||
|
check(f != NULL, "delim file setup");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
line = NULL;
|
||||||
|
n = 0;
|
||||||
|
got = getdelim(&line, &n, ',', f);
|
||||||
|
check(got == 2 && line != NULL && str_eq(line, "a,"), "getdelim first field is \"a,\"");
|
||||||
|
got = getdelim(&line, &n, ',', f);
|
||||||
|
check(got == 2 && line != NULL && str_eq(line, "b,"), "getdelim second field is \"b,\"");
|
||||||
|
got = getdelim(&line, &n, ',', f);
|
||||||
|
check(got == 2 && line != NULL && str_eq(line, "c\n"),
|
||||||
|
"getdelim returns the partial final field at end of file");
|
||||||
|
got = getdelim(&line, &n, ',', f);
|
||||||
|
check(got == -1, "getdelim at end of file returns -1");
|
||||||
|
free(line);
|
||||||
|
check(fclose(f) == 0, "delim fclose");
|
||||||
|
check(remove(path) == 0, "delim remove");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 5/6: stream locking and the four _unlocked forms. */
|
||||||
|
static void
|
||||||
|
lock_scenario(void)
|
||||||
|
{
|
||||||
|
const char rpath[] = "/tmp/vlibc-test-t18-lockr.txt";
|
||||||
|
const char wpath[] = "/tmp/vlibc-test-t18-lockw.txt";
|
||||||
|
char got[8];
|
||||||
|
FILE *f;
|
||||||
|
int r0;
|
||||||
|
int r1;
|
||||||
|
|
||||||
|
f = fopen(rpath, "w+");
|
||||||
|
check(f != NULL, "lock fopen w+ succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
r0 = ftrylockfile(f);
|
||||||
|
check(r0 == 0, "ftrylockfile on a free stream returns 0");
|
||||||
|
flockfile(f); /* recursion depth 2 */
|
||||||
|
flockfile(f); /* recursion depth 3 */
|
||||||
|
r1 = ftrylockfile(f);
|
||||||
|
check(r1 != 0, "ftrylockfile under the recursion returns nonzero");
|
||||||
|
funlockfile(f);
|
||||||
|
funlockfile(f);
|
||||||
|
funlockfile(f);
|
||||||
|
r0 = ftrylockfile(f);
|
||||||
|
check(r0 == 0, "ftrylockfile succeeds again after all unlocks");
|
||||||
|
funlockfile(f);
|
||||||
|
check(fclose(f) == 0, "lock fclose");
|
||||||
|
|
||||||
|
f = write_file(rpath, "MNO", 3);
|
||||||
|
check(f != NULL, "unlocked-read setup");
|
||||||
|
if (f != NULL)
|
||||||
|
{
|
||||||
|
flockfile(f);
|
||||||
|
check(getc_unlocked(f) == 'M', "getc_unlocked reads 'M' under the lock");
|
||||||
|
check(getc_unlocked(f) == 'N', "getc_unlocked reads 'N' under the lock");
|
||||||
|
funlockfile(f);
|
||||||
|
check(fgetc(f) == 'O', "the locked fgetc still reads 'O' after unlock");
|
||||||
|
check(fclose(f) == 0, "unlocked-read fclose");
|
||||||
|
check(remove(rpath) == 0, "unlocked-read remove");
|
||||||
|
}
|
||||||
|
|
||||||
|
f = fopen(wpath, "w");
|
||||||
|
check(f != NULL, "unlocked-write setup");
|
||||||
|
if (f != NULL)
|
||||||
|
{
|
||||||
|
flockfile(f);
|
||||||
|
check(putc_unlocked('Q', f) == 'Q', "putc_unlocked writes 'Q' under the lock");
|
||||||
|
check(putc_unlocked('R', f) == 'R', "putc_unlocked writes 'R' under the lock");
|
||||||
|
funlockfile(f);
|
||||||
|
check(fclose(f) == 0, "unlocked-write fclose");
|
||||||
|
check(raw_read_all(wpath, got, sizeof(got)) == 2 && got[0] == 'Q' && got[1] == 'R',
|
||||||
|
"putc_unlocked data reached the file");
|
||||||
|
check(remove(wpath) == 0, "unlocked-write remove");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 7: fmemopen over a fixed buffer, past-size EOF, NULL-buffer growth, 'a'. */
|
||||||
|
static void
|
||||||
|
fmemopen_scenario(void)
|
||||||
|
{
|
||||||
|
char buf[64];
|
||||||
|
char out[64];
|
||||||
|
FILE *f;
|
||||||
|
size_t r;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
f = fmemopen(buf, sizeof(buf), "w+");
|
||||||
|
check(f != NULL, "fmemopen w+ succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fputs("hello fmemopen", f) >= 0, "fputs into the fixed buffer");
|
||||||
|
check(ftello(f) == 14, "ftello == 14 after the write");
|
||||||
|
check(fseek(f, 0, SEEK_SET) == 0, "rewind via fseek(0, SEEK_SET)");
|
||||||
|
r = fread(out, 1, sizeof(out), f);
|
||||||
|
check(r == 14 &&
|
||||||
|
mem_eq((const unsigned char *)out, (const unsigned char *)"hello fmemopen", 14),
|
||||||
|
"fmemopen write/rewind/read is byte-identical");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == EOF && feof(f), "fmemopen read past the data returns EOF");
|
||||||
|
check(fclose(f) == 0, "fmemopen w+ fclose");
|
||||||
|
|
||||||
|
/* Seek beyond the buffer size, then read: EOF, not an error. */
|
||||||
|
f = fmemopen(buf, sizeof(buf), "w+");
|
||||||
|
if (f != NULL)
|
||||||
|
{
|
||||||
|
check(fputs("xyz", f) >= 0, "fmemopen setup writes xyz");
|
||||||
|
check(fseek(f, 100, SEEK_SET) == 0, "fseek past the size succeeds");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == EOF && feof(f) && ferror(f) == 0, "reading past the size is EOF with no error");
|
||||||
|
check(ftello(f) == 100, "ftello stays at the seek target");
|
||||||
|
check(fclose(f) == 0, "fmemopen past-size fclose");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 'a' positions and appends at the current data length. */
|
||||||
|
{
|
||||||
|
char abuf[64] = "hello";
|
||||||
|
FILE *g = fmemopen(abuf, sizeof(abuf), "a");
|
||||||
|
|
||||||
|
check(g != NULL, "fmemopen a succeeds");
|
||||||
|
if (g != NULL)
|
||||||
|
{
|
||||||
|
check(ftello(g) == 5, "fmemopen a starts at the current length");
|
||||||
|
check(fputs("XY", g) >= 0, "fmemopen a appends");
|
||||||
|
check(fclose(g) == 0, "fmemopen a fclose flushes");
|
||||||
|
check(mem_eq((const unsigned char *)abuf, (const unsigned char *)"helloXY", 7),
|
||||||
|
"the appended data landed in the caller buffer");
|
||||||
|
check(abuf[7] == '\0', "string-mode fmemopen NUL-terminates at the length");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* open_memstream: publish on flush, grow, NUL-terminate, free on close. */
|
||||||
|
static void
|
||||||
|
memstream_scenario(void)
|
||||||
|
{
|
||||||
|
char *p = NULL;
|
||||||
|
size_t z = 0;
|
||||||
|
FILE *f;
|
||||||
|
|
||||||
|
f = open_memstream(&p, &z);
|
||||||
|
check(f != NULL && p != NULL && z == 0, "open_memstream returns a stream and an empty buffer");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fputs("hello", f) >= 0, "open_memstream write 1");
|
||||||
|
check(fflush(f) == 0, "open_memstream fflush 1");
|
||||||
|
check(z == 5, "*sizeloc is the length after fflush");
|
||||||
|
check(p != NULL && p[5] == '\0' &&
|
||||||
|
mem_eq((const unsigned char *)p, (const unsigned char *)"hello", 5),
|
||||||
|
"the flushed buffer is NUL-terminated at the length");
|
||||||
|
check(fputs(" world", f) >= 0, "open_memstream write 2");
|
||||||
|
check(fflush(f) == 0, "open_memstream fflush 2");
|
||||||
|
check(z == 11 && p != NULL && p[11] == '\0',
|
||||||
|
"the grown length and NUL are published after the second fflush");
|
||||||
|
check(fclose(f) == 0, "open_memstream fclose");
|
||||||
|
check(z == 11 && p != NULL && p[11] == '\0', "fclose keeps the NUL-terminated result");
|
||||||
|
check(mem_eq((const unsigned char *)p, (const unsigned char *)"hello world", 11),
|
||||||
|
"the open_memstream content is correct");
|
||||||
|
free(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 9: no leaks across repeated memory-stream open/close cycles. */
|
||||||
|
static void
|
||||||
|
memstream_leak_scenario(void)
|
||||||
|
{
|
||||||
|
const char *data = "0123456789abcdef0123456789abcdef0123456789abcdef";
|
||||||
|
size_t before;
|
||||||
|
size_t after;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
before = __vlibc_malloc_check();
|
||||||
|
check(before != (size_t)-1, "allocator heap walk is consistent before the cycles");
|
||||||
|
if (before == (size_t)-1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
FILE *g;
|
||||||
|
char *p = NULL;
|
||||||
|
size_t z = 0;
|
||||||
|
|
||||||
|
g = fmemopen(NULL, 8, "w");
|
||||||
|
if (g == NULL || fputs(data, g) == EOF || fclose(g) != 0)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: fmemopen cycle failed\n");
|
||||||
|
failures++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
g = open_memstream(&p, &z);
|
||||||
|
if (g == NULL || fputs(data, g) == EOF || fclose(g) != 0)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: open_memstream cycle failed\n");
|
||||||
|
failures++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
free(p);
|
||||||
|
}
|
||||||
|
after = __vlibc_malloc_check();
|
||||||
|
check(after == before,
|
||||||
|
"fclose releases memory-stream FILEs and owned buffers (no leak over 100 cycles)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* puts, putchar_unlocked, getchar_unlocked over redirected std streams. */
|
||||||
|
static void
|
||||||
|
stream_redirect_scenario(void)
|
||||||
|
{
|
||||||
|
const char outpath[] = "/tmp/vlibc-test-t18-stdout.txt";
|
||||||
|
const char inpath[] = "/tmp/vlibc-test-t18-stdin.txt";
|
||||||
|
char buf[32];
|
||||||
|
long saved_out;
|
||||||
|
long saved_in;
|
||||||
|
long got;
|
||||||
|
int r1;
|
||||||
|
int r2;
|
||||||
|
int r3;
|
||||||
|
int r4;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
/* stdout: no check() output on fd 1 while it names the file. */
|
||||||
|
saved_out = __syscall1(SYS_dup, 1);
|
||||||
|
check(saved_out >= 0, "dup(1) saves the stdout descriptor");
|
||||||
|
r1 = (freopen(outpath, "w", stdout) == stdout);
|
||||||
|
r2 = (puts("AA") >= 0);
|
||||||
|
r3 = (putchar_unlocked('B') == 'B');
|
||||||
|
r4 = (fflush(stdout) == 0);
|
||||||
|
__syscall2(SYS_dup2, saved_out, 1);
|
||||||
|
__syscall1(SYS_close, saved_out);
|
||||||
|
check(r1, "freopen stdout to a file");
|
||||||
|
check(r2, "puts writes through stdout");
|
||||||
|
check(r3, "putchar_unlocked writes through stdout");
|
||||||
|
check(r4, "fflush(stdout) after the freopen");
|
||||||
|
got = raw_read_all(outpath, buf, sizeof(buf));
|
||||||
|
check(got == 4 && buf[0] == 'A' && buf[1] == 'A' && buf[2] == '\n' && buf[3] == 'B',
|
||||||
|
"puts + putchar_unlocked content reached the file as \"AA\\nB\"");
|
||||||
|
check(remove(outpath) == 0, "remove the stdout file");
|
||||||
|
|
||||||
|
/* stdin: seed a file, rebind stdin, read a char, restore. */
|
||||||
|
{
|
||||||
|
FILE *seed = write_file(inpath, "K", 1);
|
||||||
|
|
||||||
|
check(seed != NULL, "stdin seed file written");
|
||||||
|
if (seed != NULL)
|
||||||
|
{
|
||||||
|
(void)fclose(seed);
|
||||||
|
}
|
||||||
|
saved_in = __syscall1(SYS_dup, 0);
|
||||||
|
check(saved_in >= 0, "dup(0) saves the stdin descriptor");
|
||||||
|
r1 = (freopen(inpath, "r", stdin) == stdin);
|
||||||
|
c = getchar_unlocked();
|
||||||
|
r2 = (c == 'K');
|
||||||
|
__syscall2(SYS_dup2, saved_in, 0);
|
||||||
|
__syscall1(SYS_close, saved_in);
|
||||||
|
check(r1, "freopen stdin from the seed file");
|
||||||
|
check(r2, "getchar_unlocked reads the seed character");
|
||||||
|
check(remove(inpath) == 0, "remove the stdin file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Failure scenarios (-f). */
|
||||||
|
static int
|
||||||
|
failure_scenarios(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-t18-fail.txt";
|
||||||
|
FILE *f;
|
||||||
|
char *line = NULL;
|
||||||
|
size_t n = 0;
|
||||||
|
ssize_t got;
|
||||||
|
|
||||||
|
f = fopen(path, "w+");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: -f setup fopen failed\n");
|
||||||
|
failures++;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
check(fputs("data-data-data", f) >= 0, "-f setup fputs");
|
||||||
|
check(fflush(f) == 0, "-f setup fflush");
|
||||||
|
(void)__syscall1(SYS_close, fileno(f)); /* close the fd underneath */
|
||||||
|
got = getline(&line, &n, f);
|
||||||
|
check(got == -1, "getline on a closed-underneath stream returns -1");
|
||||||
|
free(line);
|
||||||
|
(void)fclose(f);
|
||||||
|
check(remove(path) == 0, "-f remove");
|
||||||
|
return failures > 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
// NOLINTEND(clang-analyzer-unix.Stream)
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
|
||||||
|
{
|
||||||
|
int rc = failure_scenarios();
|
||||||
|
|
||||||
|
__syscall1(SYS_exit_group, rc);
|
||||||
|
return rc; /* not reached */
|
||||||
|
}
|
||||||
|
|
||||||
|
line_scenario();
|
||||||
|
delim_scenario();
|
||||||
|
lock_scenario();
|
||||||
|
fmemopen_scenario();
|
||||||
|
memstream_scenario();
|
||||||
|
memstream_leak_scenario();
|
||||||
|
stream_redirect_scenario();
|
||||||
|
|
||||||
|
if (failures > 0)
|
||||||
|
{
|
||||||
|
say(2, "FAILED (");
|
||||||
|
say_dec(2, (unsigned long)failures);
|
||||||
|
say(2, " check(s))\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
say(1, "all getline/locking/memstream tests passed\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,331 @@
|
|||||||
|
/*
|
||||||
|
* vlibc — select/pselect/poll/ppoll test (todo 25).
|
||||||
|
*
|
||||||
|
* Exercises the fd_set macros and the multiplexing wrappers end to end:
|
||||||
|
*
|
||||||
|
* 1. FD_ZERO/FD_SET/FD_CLR/FD_ISSET round-trip, including bit 63/64 (the
|
||||||
|
* word boundary) and descriptor FD_SETSIZE-1, and fd_set has exactly
|
||||||
|
* FD_SETSIZE/8 bytes (the kernel bitmap size).
|
||||||
|
* 2. A pipe holding one byte: poll() returns 1 with POLLIN set,
|
||||||
|
* select()/pselect() return > 0 with the pipe bit set.
|
||||||
|
* 3. After the byte is read the pipe is empty: select()/pselect() with a
|
||||||
|
* 0 timeout return 0 and clear the set, poll() with timeout 0 returns
|
||||||
|
* 0, and no wait ever hangs.
|
||||||
|
* 4. Failure scenarios (only the return value, never errno): poll with an
|
||||||
|
* invalid fds pointer, poll(NULL, 1, 0), poll with an absurd nfds, and
|
||||||
|
* poll with a big nfds plus a bad pointer all return -1.
|
||||||
|
*
|
||||||
|
* Level-2 gated section: ppoll happy paths (data present / 0 timeout).
|
||||||
|
*
|
||||||
|
* The negative paths make the LIBRARY write errno (syscall_ret), which
|
||||||
|
* under a host-linked binary targets glibc's private dtv slot at %fs:0+8.
|
||||||
|
* In the default mode each such call is bracketed with a save/restore of
|
||||||
|
* that slot (task 13 technique) — only vlibc/raw-syscall code runs between
|
||||||
|
* the write and the restore. The test itself NEVER reads errno. The -f mode
|
||||||
|
* runs the failure scenarios and exits via raw SYS_exit_group (house
|
||||||
|
* pattern, tests/test_unistd_file.c).
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include "../include/poll.h"
|
||||||
|
#include "../include/sys/select.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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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).
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A deliberately invalid fds pointer: address 1 is never mapped. */
|
||||||
|
static struct pollfd *
|
||||||
|
bad_fds(void)
|
||||||
|
{
|
||||||
|
return (struct pollfd *)(unsigned long)1; // NOLINT(performance-no-int-to-ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1. fd_set macro round-trip. */
|
||||||
|
static void
|
||||||
|
fdset_scenarios(void)
|
||||||
|
{
|
||||||
|
fd_set s;
|
||||||
|
|
||||||
|
FD_ZERO(&s);
|
||||||
|
check(FD_ISSET(0, &s) == 0 && FD_ISSET(63, &s) == 0 && FD_ISSET(1023, &s) == 0,
|
||||||
|
"FD_ZERO leaves every bit clear");
|
||||||
|
FD_SET(7, &s);
|
||||||
|
FD_SET(1023, &s);
|
||||||
|
check(FD_ISSET(7, &s) != 0, "FD_ISSET sees FD_SET(7)");
|
||||||
|
check(FD_ISSET(1023, &s) != 0, "FD_ISSET sees FD_SET(1023) (last bit)");
|
||||||
|
check(FD_ISSET(8, &s) == 0 && FD_ISSET(1022, &s) == 0, "neighboring bits stay clear");
|
||||||
|
FD_CLR(7, &s);
|
||||||
|
check(FD_ISSET(7, &s) == 0 && FD_ISSET(1023, &s) != 0, "FD_CLR(7) clears only bit 7");
|
||||||
|
|
||||||
|
FD_ZERO(&s);
|
||||||
|
FD_SET(63, &s);
|
||||||
|
FD_SET(64, &s);
|
||||||
|
check(FD_ISSET(63, &s) != 0 && FD_ISSET(64, &s) != 0,
|
||||||
|
"FD_SET/FD_ISSET across the 64-bit word boundary");
|
||||||
|
check(FD_ISSET(62, &s) == 0 && FD_ISSET(65, &s) == 0, "word-boundary neighbors stay clear");
|
||||||
|
check(sizeof(fd_set) == (size_t)(FD_SETSIZE / 8), "fd_set is FD_SETSIZE/8 bytes");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2+3. poll/select/pselect on a pipe, with and without data. */
|
||||||
|
static void
|
||||||
|
poll_select_scenarios(void)
|
||||||
|
{
|
||||||
|
struct pollfd pfd;
|
||||||
|
struct timeval tv;
|
||||||
|
struct timespec ts;
|
||||||
|
fd_set r;
|
||||||
|
int pr;
|
||||||
|
char c;
|
||||||
|
int fds[2];
|
||||||
|
|
||||||
|
check(pipe(fds) == 0, "pipe returns 0");
|
||||||
|
check(write(fds[1], "x", 1) == 1, "write of one byte to the pipe returns 1");
|
||||||
|
pr = fds[0];
|
||||||
|
|
||||||
|
pfd.fd = pr;
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
check(poll(&pfd, 1, 1000) == 1, "poll on a pipe with data returns 1");
|
||||||
|
check((pfd.revents & POLLIN) != 0, "revents reports POLLIN");
|
||||||
|
|
||||||
|
FD_ZERO(&r);
|
||||||
|
FD_SET(pr, &r);
|
||||||
|
tv.tv_sec = 0;
|
||||||
|
tv.tv_usec = 0;
|
||||||
|
check(select(pr + 1, &r, NULL, NULL, &tv) > 0, "select finds the pipe readable");
|
||||||
|
check(FD_ISSET(pr, &r) != 0, "select leaves the pipe fd set in readfds");
|
||||||
|
|
||||||
|
FD_ZERO(&r);
|
||||||
|
FD_SET(pr, &r);
|
||||||
|
ts.tv_sec = 0;
|
||||||
|
ts.tv_nsec = 0;
|
||||||
|
check(pselect(pr + 1, &r, NULL, NULL, &ts, NULL) > 0,
|
||||||
|
"pselect with a NULL sigset finds the pipe readable");
|
||||||
|
check(FD_ISSET(pr, &r) != 0, "pselect leaves the pipe fd set in readfds");
|
||||||
|
|
||||||
|
check(read(pr, &c, 1) == 1 && c == 'x', "the piped byte reads back");
|
||||||
|
|
||||||
|
FD_ZERO(&r);
|
||||||
|
FD_SET(pr, &r);
|
||||||
|
tv.tv_sec = 0;
|
||||||
|
tv.tv_usec = 0;
|
||||||
|
check(select(pr + 1, &r, NULL, NULL, &tv) == 0,
|
||||||
|
"select on the now-empty pipe times out with 0");
|
||||||
|
check(FD_ISSET(pr, &r) == 0, "empty-pipe select clears the readfds bit");
|
||||||
|
|
||||||
|
pfd.fd = pr;
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
check(poll(&pfd, 1, 0) == 0, "poll with timeout 0 on the empty pipe returns 0");
|
||||||
|
check((pfd.revents & POLLIN) == 0, "no POLLIN on the empty pipe");
|
||||||
|
|
||||||
|
ts.tv_sec = 0;
|
||||||
|
ts.tv_nsec = 0;
|
||||||
|
check(pselect(pr + 1, NULL, NULL, NULL, &ts, NULL) == 0,
|
||||||
|
"pselect with a 0 timeout and all-NULL sets returns 0");
|
||||||
|
check(close(fds[0]) == 0 && close(fds[1]) == 0, "close of both pipe ends returns 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/* Level-2 gate proof: ppoll happy paths. */
|
||||||
|
static void
|
||||||
|
ppoll_scenarios(void)
|
||||||
|
{
|
||||||
|
struct pollfd pfd;
|
||||||
|
struct timespec ts;
|
||||||
|
char c;
|
||||||
|
int fds[2];
|
||||||
|
|
||||||
|
check(pipe(fds) == 0, "pipe returns 0 (ppoll)");
|
||||||
|
check(write(fds[1], "y", 1) == 1, "write of one byte returns 1 (ppoll)");
|
||||||
|
pfd.fd = fds[0];
|
||||||
|
pfd.events = POLLIN;
|
||||||
|
ts.tv_sec = 1;
|
||||||
|
ts.tv_nsec = 0;
|
||||||
|
check(ppoll(&pfd, 1, &ts, NULL) == 1, "ppoll on a pipe with data returns 1");
|
||||||
|
check((pfd.revents & POLLIN) != 0, "ppoll revents reports POLLIN");
|
||||||
|
ts.tv_sec = 0;
|
||||||
|
ts.tv_nsec = 0;
|
||||||
|
check(ppoll(&pfd, 1, &ts, NULL) == 1, "ppoll with a 0 timeout on unread data still returns 1");
|
||||||
|
check(read(fds[0], &c, 1) == 1 && c == 'y', "the ppoll byte reads back");
|
||||||
|
check(ppoll(&pfd, 1, &ts, NULL) == 0, "ppoll with a 0 timeout on the empty pipe returns 0");
|
||||||
|
check(close(fds[0]) == 0 && close(fds[1]) == 0, "close of both pipe ends returns 0 (ppoll)");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
/* 4. Negative paths: -1 assertions only, errno bracketed (see above). */
|
||||||
|
static void
|
||||||
|
negative_scenarios(void)
|
||||||
|
{
|
||||||
|
unsigned long saved;
|
||||||
|
struct pollfd one;
|
||||||
|
|
||||||
|
one.fd = 0;
|
||||||
|
one.events = POLLIN;
|
||||||
|
saved = tcb_slot1();
|
||||||
|
check(poll(bad_fds(), 1, 0) == -1, "poll with an invalid fds pointer returns -1");
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
saved = tcb_slot1();
|
||||||
|
check(poll(NULL, 1, 0) == -1, "poll(NULL, 1, 0) returns -1");
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
saved = tcb_slot1();
|
||||||
|
check(poll(&one, (nfds_t)1000000000, 0) == -1, "poll with an absurd nfds returns -1");
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
saved = tcb_slot1();
|
||||||
|
check(poll(bad_fds(), (nfds_t)1000000000, 0) == -1,
|
||||||
|
"poll with a big nfds and a bad pointer returns -1");
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Failure scenarios (-f): every assertion is on the return value only, and
|
||||||
|
* the process exits through raw SYS_exit_group because the library writes
|
||||||
|
* errno on these paths (host-TCB hazard).
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
failure_scenarios(void)
|
||||||
|
{
|
||||||
|
struct pollfd one;
|
||||||
|
int rc = 0;
|
||||||
|
|
||||||
|
one.fd = 0;
|
||||||
|
one.events = POLLIN;
|
||||||
|
if (poll(bad_fds(), 1, 0) != -1)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: poll with an invalid fds pointer did not return -1\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: poll with an invalid fds pointer -> -1\n");
|
||||||
|
}
|
||||||
|
if (poll(NULL, 1, 0) != -1)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: poll(NULL, 1, 0) did not return -1\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: poll(NULL, 1, 0) -> -1\n");
|
||||||
|
}
|
||||||
|
if (poll(&one, (nfds_t)1000000000, 0) != -1)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: poll with an absurd nfds did not return -1\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: poll with an absurd nfds -> -1\n");
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 */
|
||||||
|
}
|
||||||
|
|
||||||
|
fdset_scenarios();
|
||||||
|
poll_select_scenarios();
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
ppoll_scenarios();
|
||||||
|
#endif
|
||||||
|
negative_scenarios();
|
||||||
|
|
||||||
|
if (failures > 0)
|
||||||
|
{
|
||||||
|
say(2, "FAILED (");
|
||||||
|
say_dec(2, (unsigned long)failures);
|
||||||
|
say(2, " check(s))\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
say(1, "all poll/select tests passed\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,734 @@
|
|||||||
|
/*
|
||||||
|
* vlibc — formatted output test, integer conversions (todo 16, task A).
|
||||||
|
*
|
||||||
|
* Compares the snprintf/sprintf/dprintf/FILE-output of vfprintf.c against
|
||||||
|
* golden string literals, all of which were verified against the host glibc
|
||||||
|
* (a throwaway /tmp/probe.c) EXCEPT the spec'd divergence that %p of NULL is
|
||||||
|
* "0x0" (glibc prints "(nil)").
|
||||||
|
*
|
||||||
|
* Coverage: %d %i %u %o %x %X over 0, +/-1, INT_MIN/MAX, UINT_MAX,
|
||||||
|
* LONG_MIN/MAX (as %ld), LLONG_MIN/MAX (%lld), ULONG_MAX; %hhd/%hd
|
||||||
|
* truncation; %zu %zd %tu %td %ju %jd; the '#' prefix rules (including
|
||||||
|
* %#.0o of 0, %#x of 0, %#o of 0); the - + space 0 flags and their
|
||||||
|
* interactions with width and precision; %c %s with precision/width and the
|
||||||
|
* NULL "(null)" spelling; %p of a local (shape check) and of NULL ("0x0");
|
||||||
|
* %n; width and precision supplied via '*'; %% escaping; snprintf truncation
|
||||||
|
* (including n == 0 with a NULL buffer); sprintf/vsprintf; fprintf/vfprintf
|
||||||
|
* to a file; dprintf/vdprintf to a raw descriptor; printf/vprintf through a
|
||||||
|
* redirected stdout; and (level 2) asprintf/vasprintf.
|
||||||
|
*
|
||||||
|
* All diagnostics go through raw SYS_write; errno is never read and never
|
||||||
|
* deliberately written in the success paths exercised here, so no host TCB
|
||||||
|
* preservation is needed. Only vlibc headers are included (-Iinclude wins
|
||||||
|
* over the host's).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <float.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "../src/internal/syscall.h"
|
||||||
|
|
||||||
|
static int failures;
|
||||||
|
|
||||||
|
/* Write a NUL-terminated string to fd via the raw syscall layer. */
|
||||||
|
static 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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Byte-compare two buffers (the produced content need not be NUL-ended). */
|
||||||
|
static int
|
||||||
|
mem_eq(const unsigned char *a, const unsigned char *b, unsigned long n)
|
||||||
|
{
|
||||||
|
unsigned long i;
|
||||||
|
|
||||||
|
for (i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
if (a[i] != b[i])
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read a whole small file through raw syscalls into buf; returns size. */
|
||||||
|
static long
|
||||||
|
raw_read_all(const char *path, char *buf, unsigned long cap)
|
||||||
|
{
|
||||||
|
long fd = __syscall4(SYS_openat, -100, (long)path, 0x0, 0);
|
||||||
|
long got = -1;
|
||||||
|
long total = 0;
|
||||||
|
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
while (total < (long)cap)
|
||||||
|
{
|
||||||
|
got = __syscall3(SYS_read, fd, (long)(buf + total), (long)(cap - (unsigned long)total));
|
||||||
|
if (got <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
total += got;
|
||||||
|
}
|
||||||
|
__syscall1(SYS_close, fd);
|
||||||
|
return got < 0 ? -1 : total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Forwarding wrappers so the v* functions get exercised with real va_lists. */
|
||||||
|
static int
|
||||||
|
run_vprintf(const char *format, ...) // NOLINT(bugprone-easily-swappable-parameters)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
va_start(ap, format);
|
||||||
|
rc = vprintf(format, ap);
|
||||||
|
va_end(ap);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
run_vfprintf(FILE *f, const char *format, ...) // NOLINT(bugprone-easily-swappable-parameters)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
va_start(ap, format);
|
||||||
|
rc = vfprintf(f, format, ap);
|
||||||
|
va_end(ap);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
run_vsprintf(char *s, const char *format, ...) // NOLINT(bugprone-easily-swappable-parameters)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
va_start(ap, format);
|
||||||
|
rc = vsprintf(s, format, ap);
|
||||||
|
va_end(ap);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
run_vdprintf(int fd, const char *format, ...) // NOLINT(bugprone-easily-swappable-parameters)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
va_start(ap, format);
|
||||||
|
rc = vdprintf(fd, format, ap);
|
||||||
|
va_end(ap);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
static int
|
||||||
|
run_vasprintf(char **p, const char *format, ...) // NOLINT(bugprone-easily-swappable-parameters)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
va_start(ap, format);
|
||||||
|
rc = vasprintf(p, format, ap);
|
||||||
|
va_end(ap);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* A single snprintf result checked against a golden literal. */
|
||||||
|
static void
|
||||||
|
golden(const char *want, const char *format, ...) // NOLINT(bugprone-easily-swappable-parameters)
|
||||||
|
{
|
||||||
|
char out[64];
|
||||||
|
va_list ap;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
va_start(ap, format);
|
||||||
|
rc = vsnprintf(out, sizeof out, format, ap);
|
||||||
|
va_end(ap);
|
||||||
|
check(rc >= 0 && strcmp(out, want) == 0, want);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1. Plain integers: d/i/u/o/x/X over the extremes and the sign cases. */
|
||||||
|
static void
|
||||||
|
int_scenario(void)
|
||||||
|
{
|
||||||
|
char out[64];
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
golden("0", "%d", 0);
|
||||||
|
golden("1", "%d", 1);
|
||||||
|
golden("-1", "%d", -1);
|
||||||
|
golden("-2147483648", "%d", INT_MIN);
|
||||||
|
golden("2147483647", "%d", INT_MAX);
|
||||||
|
golden("-7", "%i", -7);
|
||||||
|
golden("4294967295", "%u", UINT_MAX);
|
||||||
|
golden("0", "%u", 0);
|
||||||
|
golden("10", "%o", 8);
|
||||||
|
golden("ff", "%x", 255);
|
||||||
|
golden("FF", "%X", 255);
|
||||||
|
golden("-9223372036854775808", "%ld", LONG_MIN);
|
||||||
|
golden("9223372036854775807", "%ld", LONG_MAX);
|
||||||
|
golden("-9223372036854775808", "%lld", LLONG_MIN);
|
||||||
|
golden("9223372036854775807", "%lld", LLONG_MAX);
|
||||||
|
golden("18446744073709551615", "%lu", ULONG_MAX);
|
||||||
|
rc = snprintf(out, sizeof out, "%d%d", 1, 2);
|
||||||
|
check(rc == 2 && strcmp(out, "12") == 0, "adjacent conversions concatenate");
|
||||||
|
rc = snprintf(out, sizeof out, "%s%d", "v", -7);
|
||||||
|
check(rc == 3 && strcmp(out, "v-7") == 0, "literal text between conversions");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. Length modifiers: hh/h truncation and the j/z/t widths. */
|
||||||
|
static void
|
||||||
|
length_scenario(void)
|
||||||
|
{
|
||||||
|
golden("44", "%hhd", 300);
|
||||||
|
golden("4464", "%hd", 70000);
|
||||||
|
golden("44", "%hhu", 300);
|
||||||
|
golden("-44", "%hhd", -300);
|
||||||
|
golden("4000000000", "%zu", (size_t)4000000000UL);
|
||||||
|
golden("-4000000000", "%zd", (ssize_t)-4000000000L);
|
||||||
|
golden("123456789", "%tu", (ptrdiff_t)123456789L);
|
||||||
|
golden("-987654321", "%td", (ptrdiff_t)-987654321L);
|
||||||
|
golden("18446744073709551615", "%ju", (uintmax_t)UINTMAX_MAX);
|
||||||
|
golden("-9223372036854775807", "%jd", (intmax_t)-9223372036854775807LL);
|
||||||
|
golden("ffffffffffffffff", "%lx", (unsigned long)ULONG_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. The '#' prefixes (octal, hex) and the zero value corner cases. */
|
||||||
|
static void
|
||||||
|
alt_scenario(void)
|
||||||
|
{
|
||||||
|
golden("0x1abc", "%#x", 0x1abc);
|
||||||
|
golden("0X1ABC", "%#X", 0x1abc);
|
||||||
|
golden("010", "%#o", 8);
|
||||||
|
golden("0", "%#.0o", 0);
|
||||||
|
golden("0", "%#x", 0);
|
||||||
|
golden("0", "%#o", 0);
|
||||||
|
golden("00000010", "%#08o", 8);
|
||||||
|
golden("010", "%#.3o", 8);
|
||||||
|
golden("", "%#.0x", 0);
|
||||||
|
golden(" 0", "%#5.0o", 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. Flags and their interactions with width and precision. */
|
||||||
|
static void
|
||||||
|
flag_scenario(void)
|
||||||
|
{
|
||||||
|
golden("42 ", "%-8d", 42);
|
||||||
|
golden("+42", "%+d", 42);
|
||||||
|
golden("-42", "%+d", -42);
|
||||||
|
golden(" 42", "% d", 42);
|
||||||
|
golden("-42", "% d", -42);
|
||||||
|
golden("00000042", "%08d", 42);
|
||||||
|
golden("42 ", "%-08d", 42);
|
||||||
|
golden("+0000042", "%+08d", 42);
|
||||||
|
golden(" 042", "%5.3d", 42);
|
||||||
|
golden("042 ", "%-5.3d", 42);
|
||||||
|
golden(" ", "%05.0d", 0);
|
||||||
|
golden(" 42", "%05.0d", 42);
|
||||||
|
golden(" 000", "%5.3d", 0);
|
||||||
|
golden(" 42", "%5d", 42);
|
||||||
|
golden("42 ", "%-5d", 42);
|
||||||
|
golden("+0042", "%+05d", 42);
|
||||||
|
golden(" 42", "%*d", 5, 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 5. Characters, strings, pointers, %n, and the literal percent. */
|
||||||
|
static void
|
||||||
|
charstr_scenario(void)
|
||||||
|
{
|
||||||
|
char out[64];
|
||||||
|
int marker;
|
||||||
|
int n;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
golden("A", "%c", 'A');
|
||||||
|
golden("hello", "%s", "hello");
|
||||||
|
golden("hel", "%.3s", "hello");
|
||||||
|
golden(" hel", "%5.3s", "hello");
|
||||||
|
golden("hel ", "%-5.3s", "hello");
|
||||||
|
golden("(null)", "%s", (char *)0);
|
||||||
|
golden("%", "%%");
|
||||||
|
golden("0x0", "%p", (void *)0);
|
||||||
|
|
||||||
|
n = -1;
|
||||||
|
rc = snprintf(out, sizeof out, "abc%n", &n);
|
||||||
|
check(rc == 3 && n == 3 && strcmp(out, "abc") == 0, "%n stores the count so far");
|
||||||
|
|
||||||
|
rc = snprintf(out, sizeof out, "%p", (void *)&marker);
|
||||||
|
if (rc > 2 && out[0] == '0' && out[1] == 'x')
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int hex = 1;
|
||||||
|
|
||||||
|
for (i = 2; out[i] != '\0'; i++)
|
||||||
|
{
|
||||||
|
if (!((out[i] >= '0' && out[i] <= '9') || (out[i] >= 'a' && out[i] <= 'f')))
|
||||||
|
{
|
||||||
|
hex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check(hex && i > 2, "%p of a local prints 0x + lowercase hex");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
check(0, "%p of a local prints 0x + lowercase hex");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 6. Width and precision supplied through '*'. */
|
||||||
|
static void
|
||||||
|
star_scenario(void)
|
||||||
|
{
|
||||||
|
golden("00042", "%.*d", 5, 42);
|
||||||
|
golden("042", "%.*d", 3, 42);
|
||||||
|
golden("42", "%.*d", -1, 42);
|
||||||
|
golden("hel", "%.*s", 3, "hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 7. snprintf truncation semantics. */
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wformat-truncation"
|
||||||
|
|
||||||
|
static void
|
||||||
|
trunc_scenario(void)
|
||||||
|
{
|
||||||
|
char b[8];
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = snprintf(b, 5, "%s", "hello world");
|
||||||
|
check(rc == 11 && strcmp(b, "hell") == 0,
|
||||||
|
"snprintf truncates at n-1 and returns the full length");
|
||||||
|
rc = snprintf((char *)0, 0, "%s", "hello world");
|
||||||
|
check(rc == 11, "snprintf with n == 0 writes nothing and returns the length");
|
||||||
|
rc = snprintf(b, 1, "%s", "hello world");
|
||||||
|
check(rc == 11 && b[0] == '\0', "snprintf with n == 1 writes only the NUL");
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
|
/* 8. sprintf/vsprintf (no bound). */
|
||||||
|
static void
|
||||||
|
sprintf_scenario(void)
|
||||||
|
{
|
||||||
|
char s[32];
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = sprintf(s, "%d-%s", 7, "x");
|
||||||
|
check(rc == 3 && strcmp(s, "7-x") == 0, "sprintf writes past the argument bound");
|
||||||
|
rc = run_vsprintf(s, "%d-%s", 7, "x");
|
||||||
|
check(rc == 3 && strcmp(s, "7-x") == 0, "vsprintf writes past the argument bound");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 9. dprintf/vdprintf to a raw descriptor. */
|
||||||
|
static void
|
||||||
|
dprintf_scenario(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-printf-dprintf.txt";
|
||||||
|
char buf[64];
|
||||||
|
long fd;
|
||||||
|
long got;
|
||||||
|
int r1;
|
||||||
|
int r2;
|
||||||
|
|
||||||
|
fd = __syscall4(SYS_openat, -100, (long)path, (long)(0x1 | 0x40 | 0x200), 0666);
|
||||||
|
check(fd >= 0, "dprintf openat succeeds");
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
r1 = (dprintf((int)fd, "%d %s", 9, "nine") == 6);
|
||||||
|
r2 = (run_vdprintf((int)fd, " %#x", 0xbeef) == 7);
|
||||||
|
__syscall1(SYS_close, fd);
|
||||||
|
check(r1, "dprintf returns the byte count");
|
||||||
|
check(r2, "vdprintf returns the byte count");
|
||||||
|
got = raw_read_all(path, buf, sizeof buf);
|
||||||
|
check(got == 13 &&
|
||||||
|
mem_eq((const unsigned char *)buf, (const unsigned char *)"9 nine 0xbeef", 13),
|
||||||
|
"dprintf content reached the file unbuffered");
|
||||||
|
check(remove(path) == 0, "remove deletes the dprintf file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 10. fprintf/vfprintf through a buffered FILE, read back raw. */
|
||||||
|
static void
|
||||||
|
file_scenario(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-printf-file.txt";
|
||||||
|
char buf[64];
|
||||||
|
FILE *f;
|
||||||
|
long got;
|
||||||
|
int r1;
|
||||||
|
int r2;
|
||||||
|
|
||||||
|
f = fopen(path, "w");
|
||||||
|
check(f != NULL, "fopen for fprintf succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
r1 = (fprintf(f, "%s %d", "abc", 123) == 7);
|
||||||
|
r2 = (run_vfprintf(f, " %x", 255) == 3);
|
||||||
|
check(r1, "fprintf returns the byte count");
|
||||||
|
check(r2, "vfprintf returns the byte count");
|
||||||
|
check(fclose(f) == 0, "fclose flushes the fprintf file");
|
||||||
|
got = raw_read_all(path, buf, sizeof buf);
|
||||||
|
check(got == 10 && mem_eq((const unsigned char *)buf, (const unsigned char *)"abc 123 ff", 10),
|
||||||
|
"fprintf/vfprintf content reached the file");
|
||||||
|
check(remove(path) == 0, "remove deletes the fprintf file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 11. printf/vprintf through a redirected stdout (the flush is explicit). */
|
||||||
|
static void
|
||||||
|
stdout_scenario(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-printf-stdout.txt";
|
||||||
|
char buf[64];
|
||||||
|
long saved;
|
||||||
|
long fd;
|
||||||
|
long got;
|
||||||
|
int r1;
|
||||||
|
int r2;
|
||||||
|
int r3;
|
||||||
|
|
||||||
|
saved = __syscall1(SYS_dup, 1);
|
||||||
|
check(saved >= 0, "dup(1) for the printf scenario");
|
||||||
|
if (saved < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fd = __syscall4(SYS_openat, -100, (long)path, (long)(0x1 | 0x40 | 0x200), 0666);
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
__syscall2(SYS_dup2, saved, 1);
|
||||||
|
__syscall1(SYS_close, saved);
|
||||||
|
check(0, "openat for the printf scenario");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
__syscall2(SYS_dup2, fd, 1);
|
||||||
|
__syscall1(SYS_close, fd);
|
||||||
|
/* No check() output while fd 1 names the file. */
|
||||||
|
r1 = (printf("%d-%s", 1, "x") == 3);
|
||||||
|
r2 = (run_vprintf("%c%d", 'q', 5) == 2);
|
||||||
|
r3 = (fflush(stdout) == 0);
|
||||||
|
__syscall2(SYS_dup2, saved, 1);
|
||||||
|
__syscall1(SYS_close, saved);
|
||||||
|
check(r1, "printf to stdout returns its byte count");
|
||||||
|
check(r2, "vprintf to stdout returns its byte count");
|
||||||
|
check(r3, "fflush(stdout) after the redirect");
|
||||||
|
got = raw_read_all(path, buf, sizeof buf);
|
||||||
|
check(got == 5 && mem_eq((const unsigned char *)buf, (const unsigned char *)"1-xq5", 5),
|
||||||
|
"printf/vprintf content reached the redirected stdout");
|
||||||
|
check(remove(path) == 0, "remove deletes the stdout file");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/* 12. Level 2: asprintf/vasprintf. */
|
||||||
|
static void
|
||||||
|
asprintf_scenario(void)
|
||||||
|
{
|
||||||
|
char *p = NULL;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = asprintf(&p, "%d-%s", 5, "x");
|
||||||
|
check(rc == 3 && p != NULL && strcmp(p, "5-x") == 0, "asprintf allocates and fills");
|
||||||
|
free(p);
|
||||||
|
p = NULL;
|
||||||
|
rc = run_vasprintf(&p, "%u", 42U);
|
||||||
|
check(rc == 2 && p != NULL && strcmp(p, "42") == 0, "vasprintf allocates and fills");
|
||||||
|
free(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
/* Bit-pattern constructors for the float scenarios (host arithmetic cannot
|
||||||
|
* name every subnormal or sign-of-NaN case portably). */
|
||||||
|
static double
|
||||||
|
mkdbl(unsigned long long bits)
|
||||||
|
{
|
||||||
|
double v;
|
||||||
|
|
||||||
|
memcpy(&v, &bits, sizeof v);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static long double
|
||||||
|
mkldbl(unsigned e15, unsigned neg, unsigned long long sig)
|
||||||
|
{
|
||||||
|
unsigned char raw[16];
|
||||||
|
long double v;
|
||||||
|
|
||||||
|
memset(raw, 0, sizeof raw);
|
||||||
|
memcpy(raw, &sig, 8);
|
||||||
|
raw[8] = (unsigned char)(e15 & 0xFF);
|
||||||
|
raw[9] = (unsigned char)((e15 >> 8) | (neg ? 0x80 : 0));
|
||||||
|
memcpy(&v, raw, sizeof raw);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 13. %a/%A hex floats, the long double L length, and the round-half-even
|
||||||
|
* hex-digit rounding (all goldens verified against host glibc). */
|
||||||
|
static void
|
||||||
|
hexfloat_scenario(void)
|
||||||
|
{
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Woverlength-strings"
|
||||||
|
static const char *const ldmax_dec =
|
||||||
|
"11897314953572317650212638530309702051690633222946242004403237338917370055229707"
|
||||||
|
"22616410290336528882853545697807495577314427443153670288434198125573853743678673"
|
||||||
|
"59320070697326320191591828296152436552951064679108661431179063216977883889613478"
|
||||||
|
"65606003991487534332114549111600886798451548665128523401497730376000091254793939"
|
||||||
|
"66223151383622417838542743917838138717805889487540575168226347659235576974805113"
|
||||||
|
"72564902088485522249479139937758502601177354918009979622602685950855888360815984"
|
||||||
|
"69002356451323465944763849398592764562845796617729304078066092291027150460853880"
|
||||||
|
"87959327781622986827547830768080040150694942303411728957777100335714010559775242"
|
||||||
|
"12405734700738625166011082837911962300846927720096515350020847447079244384854591"
|
||||||
|
"28867230006190851264721119513614675276335195629275979572502780029807959041931396"
|
||||||
|
"03021470997035276467445530922022679656280991498232083329641241038509239184734786"
|
||||||
|
"12192169721054348428704835340811304257300221642134891734717423480071488075100206"
|
||||||
|
"43905172342476560047217680964861079949434157034763206435586242074435044243805661"
|
||||||
|
"36017608837478165389027809576975977286860071487028287955567141404632615832623602"
|
||||||
|
"76289631617397848425448686060994827086796804807870251185893083854658422304090880"
|
||||||
|
"59962945945862019037660484467909260022254105307759010657606713472001258464069570"
|
||||||
|
"30257138960983757998926954553052368560758683179223113639519468850880771872104705"
|
||||||
|
"20395758748001314313144425494391994017575316933939236688185618912993172910425292"
|
||||||
|
"12368351599223220509980016771027840353601408292963981151228777681357060457893435"
|
||||||
|
"35451696539561254048846447169786893211671087229088082778350518228857646062218739"
|
||||||
|
"70285165508372099234948333443522898475123275372663606621390228126470623407535207"
|
||||||
|
"17240586650795182173034637826313533937067749019501978416904418247380631628285868"
|
||||||
|
"57741432581165364040218402724913393320949219498422442730427019873044536620350262"
|
||||||
|
"38695780468200360144729199712309553005720614186697485284685618651483271597448120"
|
||||||
|
"31219467516863793430961896151073300655524214851952017628585950910518394725028638"
|
||||||
|
"71632494167613804996319791441870254302706758495192008837915169401581740046711477"
|
||||||
|
"87720145964446117520405945350476472180797576111172084627363927960033967047003761"
|
||||||
|
"33745095531841500737964126050479232516613548412918842113408230154733047540670728"
|
||||||
|
"18763503617332908005951896325207071673904547777129682265206225651439919376804400"
|
||||||
|
"29238090311243791261477625596469422198137514696707944687035800439250765945161837"
|
||||||
|
"98118593920495440361149153107822510726914869798092409467721427270124043771874092"
|
||||||
|
"16756613634938900451232351668146089322400697993176017805338191849981933008410985"
|
||||||
|
"99393876029260139091141452600372028487213241195542428210183120421610446740462163"
|
||||||
|
"53369005836646065911562987647455250681450039329414041314954006776029510059622530"
|
||||||
|
"22823003631473824681059648442441324864573137437595096416168048024129351876204668"
|
||||||
|
"13563687753281467553879887177183651289394719533506188500326760735438867336800207"
|
||||||
|
"43878496570145760903498575712430451020387304948542567024793393228091105260415385"
|
||||||
|
"28994849203991091946129912491633289917998094380337879522093131466946149705939664"
|
||||||
|
"15237594928589096048991612194498998638483702248667224914892467841020618336462741"
|
||||||
|
"69695763076324802355879752452537370354338829608627534277400163334340550835370485"
|
||||||
|
"07374544819754722228975281083020898682633020285259923084168054539687911418297629"
|
||||||
|
"98896457648276528750456285492426516521775079951625966922911497778896235667095662"
|
||||||
|
"71384820181913483216879958636526376209782850700993372943967846398790249145142227"
|
||||||
|
"42527006363942327998483976739987154418554201562244154926653014515504685489258620"
|
||||||
|
"27608576183712976335876121538256512963353814166394951655600026415918655485005705"
|
||||||
|
"26114319529199188079545223946496276356301785808966922264062353828985358675959906"
|
||||||
|
"47008385687123810329591926494846250768992258419305480763620215089022149220528069"
|
||||||
|
"84201835084058693849381549890944546197789302911357651677540623227829831403347327"
|
||||||
|
"66039522316034228247175281818188443048809213219335508698733958612760736708666523"
|
||||||
|
"75555675803171490108477320096424318780070008797346032906278943553743564448851907"
|
||||||
|
"19161645514115576193939969076741515640282654366402676009508752394550734155613586"
|
||||||
|
"79330660317447209244465135323666476497354008519670407711036405381500734868917983"
|
||||||
|
"64049570606189535005089840913826869535090066783324472578712196604415284924840041"
|
||||||
|
"85093281190896363417573989716659600075948780061916409485433875852065711654107226"
|
||||||
|
"09962881501231443779440087493019447443307843889957018427100048083050121771235606"
|
||||||
|
"22895076269042856800047718893158089358515593863176652948089031267747029662545110"
|
||||||
|
"86154895839508779675546413794489596052797520987481383976257859210575628440175934"
|
||||||
|
"93241621483395653501891968113890918437957347032694063428900878058469403524534793"
|
||||||
|
"98080674273236297887100867175802531561302356064878709259865288416350972529537091"
|
||||||
|
"11431720488774740553905400942537542411931794417513706468964386151771884986701034"
|
||||||
|
"15325423859110896247108853858086888377772586485641459342621210866475884892600317"
|
||||||
|
"62345960769508849149662444156604419552086811989770240";
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
double pos_inf = mkdbl(0x7FF0000000000000ULL);
|
||||||
|
double neg_inf = mkdbl(0xFFF0000000000000ULL);
|
||||||
|
double pos_nan = mkdbl(0x7FF8000000000000ULL);
|
||||||
|
double neg_nan = mkdbl(0xFFF8000000000000ULL);
|
||||||
|
double maxsub = mkdbl(0x000FFFFFFFFFFFFFULL);
|
||||||
|
char out[6000];
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
golden("0x0p+0", "%a", 0.0);
|
||||||
|
golden("0x1p+0", "%a", 1.0);
|
||||||
|
golden("0x1.8p+0", "%a", 1.5);
|
||||||
|
golden("0x1p-1", "%a", 0.5);
|
||||||
|
golden("0x1.91eb851eb851fp+1", "%a", 3.14);
|
||||||
|
golden("0x1.fffffffffffffp+1023", "%a", DBL_MAX);
|
||||||
|
golden("0x2p+1023", "%.0a", DBL_MAX);
|
||||||
|
golden("0x2.0p+1023", "%.1a", DBL_MAX);
|
||||||
|
golden("0x0.0000000000001p-1022", "%a", DBL_TRUE_MIN);
|
||||||
|
golden("0x0.0p-1022", "%.1a", DBL_TRUE_MIN);
|
||||||
|
golden("0x0p-1022", "%.0a", DBL_TRUE_MIN);
|
||||||
|
golden("0x0.fffffffffffffp-1022", "%a", maxsub);
|
||||||
|
golden("0x1.0p-1022", "%.1a", maxsub);
|
||||||
|
golden("0x1p-1022", "%.0a", maxsub);
|
||||||
|
golden("0x0.0000p-1022", "%.4a", DBL_TRUE_MIN);
|
||||||
|
golden("0x1.81cd6e631f8a1p+13", "%a", 12345.6789);
|
||||||
|
golden("0x1.8000000000000p+0", "%.13a", 1.5);
|
||||||
|
golden("0x1.8000000000000000p+0", "%.16a", 1.5);
|
||||||
|
golden("0x2.0p+0", "%.1a", 0x1.fffffffffffffp+0);
|
||||||
|
golden("0x2p+0", "%.0a", 0x1.8p+0);
|
||||||
|
golden("0x1p+0", "%.0a", 0x1.4p+0);
|
||||||
|
golden("0x2p-1", "%.0a", 0x1.fffffffffffffp-1);
|
||||||
|
golden("0x1.000p+0", "%.3a", 1.0);
|
||||||
|
golden("0x1.p+0", "%#a", 1.0);
|
||||||
|
golden("0x2.p+0", "%#.0a", 1.5);
|
||||||
|
golden("0x0.p+0", "%#.0a", 0.0);
|
||||||
|
golden("0x0.p+0", "%#a", 0.0);
|
||||||
|
golden("0x0.000p+0", "%.3a", 0.0);
|
||||||
|
golden("0x0000000000001.8p+0", "%020a", 1.5);
|
||||||
|
golden("+0x000000000001.8p+0", "%+020a", 1.5);
|
||||||
|
golden("+0x1.8p+0", "%+a", 1.5);
|
||||||
|
golden(" 0x1.8p+0", "% a", 1.5);
|
||||||
|
golden("+0x1.8p+0 ", "%-+20a", 1.5);
|
||||||
|
golden("0x1.8p+0 ", "%-20a", 1.5);
|
||||||
|
golden("0x1.800p+0", "%.3a", 1.5);
|
||||||
|
golden("+0x1.800p+0", "%+08.3a", 1.5);
|
||||||
|
golden("0X1.8P+0", "%A", 1.5);
|
||||||
|
golden("0X1.91EB851EB851FP+1", "%A", 3.14);
|
||||||
|
golden("0X1.FFFFFFFFFFFFFP+1023", "%A", DBL_MAX);
|
||||||
|
golden("0X2P+0", "%.0A", 1.5);
|
||||||
|
golden("inf", "%a", pos_inf);
|
||||||
|
golden("-inf", "%a", neg_inf);
|
||||||
|
golden("nan", "%a", pos_nan);
|
||||||
|
golden("-nan", "%a", neg_nan);
|
||||||
|
golden("INF", "%A", pos_inf);
|
||||||
|
golden("NAN", "%A", pos_nan);
|
||||||
|
golden("inf", "%La", mkldbl(0x7FFF, 0, 0x8000000000000000ULL));
|
||||||
|
golden("-inf", "%La", mkldbl(0x7FFF, 1, 0x8000000000000000ULL));
|
||||||
|
golden("-nan", "%La", mkldbl(0x7FFF, 1, 0x0000000000000001ULL));
|
||||||
|
|
||||||
|
golden("0x8p-3", "%La", 1.0L);
|
||||||
|
golden("0xcp-3", "%La", 1.5L);
|
||||||
|
golden("0xcp-3", "%.0La", 0xcp-3L);
|
||||||
|
golden("0xc.0p-3", "%.1La", 0xcp-3L);
|
||||||
|
golden("0x8p-4", "%La", 0.5L);
|
||||||
|
golden("0xc.8f5c28f5c28f5c3p-2", "%La", 3.14L);
|
||||||
|
golden("0xf.fffffffffffffffp+16380", "%La", LDBL_MAX);
|
||||||
|
golden("0x1p+16384", "%.0La", LDBL_MAX);
|
||||||
|
golden("0x1.0p+16384", "%.1La", LDBL_MAX);
|
||||||
|
golden("0x8p-16385", "%La", LDBL_MIN);
|
||||||
|
golden("0x8.0p-16385", "%.1La", LDBL_MIN);
|
||||||
|
golden("0x0.000000000000001p-16385", "%La", LDBL_TRUE_MIN);
|
||||||
|
golden("0x0.0p-16385", "%.1La", LDBL_TRUE_MIN);
|
||||||
|
golden("0x0p-16385", "%.0La", LDBL_TRUE_MIN);
|
||||||
|
golden("0xc.p-3", "%#.0La", 1.5L);
|
||||||
|
golden("0x0.p+0", "%#.0La", 0.0L);
|
||||||
|
golden("-0xcp-3", "%La", -1.5L);
|
||||||
|
golden("-0x0p+0", "%.0La", -0.0L);
|
||||||
|
golden("0x0.0p+0", "%.1La", 0.0L);
|
||||||
|
golden("0xc.000p-3", "%.3La", 1.5L);
|
||||||
|
golden("0xc.000000000000000p-3", "%.15La", 1.5L);
|
||||||
|
golden("0x00000000000000cp-3", "%020La", 1.5L);
|
||||||
|
golden("+0x1p+16384", "%+.0La", LDBL_MAX);
|
||||||
|
golden("0x1p+4", "%.0La", 0xf.fffffffffffffffp+0L);
|
||||||
|
golden("0x1.0p+4", "%.1La", 0xf.fffffffffffffffp+0L);
|
||||||
|
golden("0XCP-3", "%LA", 1.5L);
|
||||||
|
golden("0XC.8F5C28F5C28F5C3P-2", "%LA", 3.14L);
|
||||||
|
golden("0xc.0e6b7318fc50481p+10", "%La", 12345.6789L);
|
||||||
|
golden("0xc.8p-3", "%.1La", 0xc.8p-3L);
|
||||||
|
golden("0xcp-3", "%.0La", 0xc.8p-3L);
|
||||||
|
golden("0xc.000000000000001p-3", "%.15La", 0xc.000000000000001p-3L);
|
||||||
|
|
||||||
|
golden("1.500000e+00", "%Le", 1.5L);
|
||||||
|
golden("1.500000", "%Lf", 1.5L);
|
||||||
|
golden("1.5", "%Lg", 1.5L);
|
||||||
|
golden("-0.000000e+00", "%Le", -0.0L);
|
||||||
|
golden("-0.000000", "%Lf", -0.0L);
|
||||||
|
golden("3.14159", "%Lg", 3.14159L);
|
||||||
|
golden("3.141590", "%Lf", 3.14159L);
|
||||||
|
golden("3.141590e+00", "%Le", 3.14159L);
|
||||||
|
golden("0.500000", "%Lf", 0.5L);
|
||||||
|
golden("1.000000", "%Lf", 1.0L);
|
||||||
|
golden("0.0001", "%Lg", 0.0001L);
|
||||||
|
golden("1.189731e+4932", "%Le", LDBL_MAX);
|
||||||
|
golden("0.000000", "%Lf", 0.0L);
|
||||||
|
golden("3.14159", "%.6Lg", 3.14159L);
|
||||||
|
golden("1.5", "%Lg", 1.5000001L);
|
||||||
|
|
||||||
|
rc = snprintf(out, sizeof out, "%.0Lf", LDBL_MAX);
|
||||||
|
check(rc == 4933 && strcmp(out, ldmax_dec) == 0,
|
||||||
|
"%.0Lf of LDBL_MAX prints all 4933 digits (no truncation)");
|
||||||
|
rc = snprintf(out, sizeof out, "%Lf", LDBL_MAX);
|
||||||
|
check(rc == 4940 && out[0] == '1' && strncmp(out, "11897314953572317650", 20) == 0 &&
|
||||||
|
out[4933] == '.' && out[4939] == '0',
|
||||||
|
"%Lf of LDBL_MAX fills the 4933-digit integer part");
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(void)
|
||||||
|
{
|
||||||
|
int_scenario();
|
||||||
|
length_scenario();
|
||||||
|
alt_scenario();
|
||||||
|
flag_scenario();
|
||||||
|
charstr_scenario();
|
||||||
|
star_scenario();
|
||||||
|
trunc_scenario();
|
||||||
|
sprintf_scenario();
|
||||||
|
dprintf_scenario();
|
||||||
|
file_scenario();
|
||||||
|
stdout_scenario();
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
asprintf_scenario();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
hexfloat_scenario();
|
||||||
|
if (failures > 0)
|
||||||
|
{
|
||||||
|
say(2, "FAILED (");
|
||||||
|
say_dec(2, (unsigned long)failures);
|
||||||
|
say(2, " check(s))\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
say(1, "all printf tests passed\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,779 @@
|
|||||||
|
/*
|
||||||
|
* vlibc — process control test (todo 20).
|
||||||
|
*
|
||||||
|
* Exercises fork/vfork/exec family/ids/session + system/popen end to
|
||||||
|
* end:
|
||||||
|
*
|
||||||
|
* 1. getpid/getppid/getuid/geteuid/getgid/getegid return live values.
|
||||||
|
* 2. fork: the child writes a marker and its pid through a pipe and
|
||||||
|
* exits 7 via the raw syscall; the parent sees 0 + the pid round
|
||||||
|
* trip and reaps a 7<<8 status via raw SYS_wait4.
|
||||||
|
* 3. fork + execve("/bin/true") exits 0.
|
||||||
|
* 4. fork + execve of a nonexistent binary: the child exits 127, the
|
||||||
|
* parent reaps 127<<8.
|
||||||
|
* 5. system("exit 3") returns 768 (WEXITSTATUS 3).
|
||||||
|
* 6. popen("echo hi","r") reads "hi\n" and pclose returns 0;
|
||||||
|
* popen("cat","w") writes through and pclose returns 0.
|
||||||
|
* 7. setuid(getuid()) etc. round trip to 0; getgroups counts and
|
||||||
|
* fills consistently.
|
||||||
|
* 8. (L2) session control in fork children: setsid/getpgrp/getsid and
|
||||||
|
* setpgid/setpgrp/getpgid; tcgetpgrp/tcsetpgrp fail with -1 on a
|
||||||
|
* pipe (ENOTTY, no errno read).
|
||||||
|
* 9. one fork+exec each for execl/execlp/execv/execvp/execle/fexecve.
|
||||||
|
* 10. PATH search: a helper script in a private directory is found via
|
||||||
|
* a custom PATH (exit 5 → 5<<8), an empty PATH entry means cwd,
|
||||||
|
* and execvp falls back to the default PATH when environ is NULL.
|
||||||
|
* 11. (L2) vfork: the child execs /bin/true, the parent reaps 0.
|
||||||
|
*
|
||||||
|
* sys/wait.h is todo 23 and does not exist: the test waits via raw
|
||||||
|
* SYS_wait4 in its own helper, and children exit via the raw syscalls.
|
||||||
|
*
|
||||||
|
* The negative paths make the LIBRARY write errno (syscall_ret), which
|
||||||
|
* under a host-linked binary targets glibc's private dtv slot at
|
||||||
|
* %fs:0+8. In the default mode each such call is bracketed with a
|
||||||
|
* save/restore of that slot (task 13 technique) — only vlibc/raw-syscall
|
||||||
|
* code runs between the write and the restore, so host state is intact
|
||||||
|
* when host code runs again. The test itself NEVER reads errno; every
|
||||||
|
* negative is asserted on the return value. The -f mode runs the
|
||||||
|
* failure scenarios and exits via raw SYS_exit_group (house pattern,
|
||||||
|
* tests/test_malloc.c).
|
||||||
|
*
|
||||||
|
* All diagnostics go through raw SYS_write (no host stdio): under
|
||||||
|
* -Iinclude the vlibc public headers shadow GCC's internal ones, so a
|
||||||
|
* host header would not compile. Only vlibc headers are included.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include "../include/stdio.h"
|
||||||
|
#include "../include/stdlib.h"
|
||||||
|
#include "../include/unistd.h"
|
||||||
|
|
||||||
|
#include "../src/internal/syscall.h"
|
||||||
|
|
||||||
|
/* Kernel-UAPI constants local to this test (fcntl.h is todo 21). */
|
||||||
|
#define T20_O_RDONLY 0x0
|
||||||
|
#define T20_O_WRONLY 0x1
|
||||||
|
#define T20_O_CREAT 0x40
|
||||||
|
#define T20_O_TRUNC 0x200
|
||||||
|
#define T20_AT_FDCWD (-100)
|
||||||
|
#define T20_EINTR 4
|
||||||
|
|
||||||
|
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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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).
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for pid via the raw syscall (sys/wait.h is todo 23) and return
|
||||||
|
* the raw wait status, or -1 when wait4 failed for a reason other than
|
||||||
|
* EINTR. */
|
||||||
|
static int
|
||||||
|
raw_wait(pid_t pid)
|
||||||
|
{
|
||||||
|
int status = 0;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
long r = __syscall4(SYS_wait4, pid, (long)&status, 0, 0);
|
||||||
|
|
||||||
|
if (r < 0 && -r == T20_EINTR)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (r < 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Run fn(argv) in a fork child (via a public exec entry point) and
|
||||||
|
* return the child's wait status; the child exits 127 through the raw
|
||||||
|
* syscall when the exec entry point returns. */
|
||||||
|
typedef int (*t20_exec_fn)(void);
|
||||||
|
|
||||||
|
static int
|
||||||
|
exec_one(const char *label, t20_exec_fn fn)
|
||||||
|
{
|
||||||
|
pid_t pid = fork();
|
||||||
|
int status;
|
||||||
|
|
||||||
|
if (pid < 0)
|
||||||
|
{
|
||||||
|
check(0, label);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
/* Child: exec (or raw-exit 127 on failure). */
|
||||||
|
(void)fn();
|
||||||
|
__syscall1(SYS_exit_group, 127);
|
||||||
|
}
|
||||||
|
status = raw_wait(pid);
|
||||||
|
check(status == 0, label);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The per-entry-point payloads. Each runs in the fork child. */
|
||||||
|
static char *av_true[] = {"true", 0};
|
||||||
|
static char *env_le[] = {"VLIBC_T20=le", 0};
|
||||||
|
static char *env_ve[] = {"VLIBC_T20=ve", 0};
|
||||||
|
|
||||||
|
static int
|
||||||
|
run_execve(void)
|
||||||
|
{
|
||||||
|
static char *envp_ok[] = {"VLIBC_T20=yes", 0};
|
||||||
|
|
||||||
|
return execve("/bin/true", av_true, envp_ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
run_fexecve(void)
|
||||||
|
{
|
||||||
|
int fd = open("/bin/true", T20_O_RDONLY);
|
||||||
|
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return fexecve(fd, av_true, environ);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1. Process ids. */
|
||||||
|
static void
|
||||||
|
id_scenarios(void)
|
||||||
|
{
|
||||||
|
check(getpid() > 0, "getpid returns a positive pid");
|
||||||
|
check(getppid() > 0, "getppid returns a positive pid");
|
||||||
|
check(getuid() == geteuid(), "getuid and geteuid agree");
|
||||||
|
check(getgid() == getegid(), "getgid and getegid agree");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. fork: marker + pid round trip through a pipe, child exits 7. */
|
||||||
|
static void
|
||||||
|
fork_scenario(void)
|
||||||
|
{
|
||||||
|
int fds[2] = {0};
|
||||||
|
unsigned char buf[5] = {0}; /* filled by SYS_read; the analyzer cannot model it */
|
||||||
|
pid_t pid;
|
||||||
|
int status;
|
||||||
|
|
||||||
|
if (syscall_ret(__syscall1(SYS_pipe, (long)fds)) < 0)
|
||||||
|
{
|
||||||
|
check(0, "fork scenario pipe");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pid = fork();
|
||||||
|
if (pid < 0)
|
||||||
|
{
|
||||||
|
check(0, "fork returns a pid");
|
||||||
|
check(0, "child writes the marker and its pid");
|
||||||
|
check(0, "parent reaps the child with status 7<<8");
|
||||||
|
(void)__syscall1(SYS_close, fds[0]);
|
||||||
|
(void)__syscall1(SYS_close, fds[1]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
int mypid = getpid();
|
||||||
|
|
||||||
|
/* Child: marker + pid, then exit 7 via the raw syscall. */
|
||||||
|
buf[0] = 'c';
|
||||||
|
buf[1] = (unsigned char)(mypid & 0xff);
|
||||||
|
buf[2] = (unsigned char)((mypid >> 8) & 0xff);
|
||||||
|
buf[3] = (unsigned char)((mypid >> 16) & 0xff);
|
||||||
|
buf[4] = (unsigned char)((mypid >> 24) & 0xff);
|
||||||
|
(void)__syscall3(SYS_write, fds[1], (long)buf, 5);
|
||||||
|
__syscall1(SYS_exit_group, 7);
|
||||||
|
}
|
||||||
|
check(pid > 0, "fork returns the child pid in the parent");
|
||||||
|
check(__syscall3(SYS_read, fds[0], (long)buf, 5) == 5, "parent reads the five marker bytes");
|
||||||
|
{
|
||||||
|
int child_pid = (int)((unsigned int)buf[1] | ((unsigned int)buf[2] << 8) |
|
||||||
|
((unsigned int)buf[3] << 16) | ((unsigned int)buf[4] << 24));
|
||||||
|
|
||||||
|
check(buf[0] == 'c' && child_pid == pid,
|
||||||
|
"the child's getpid matches the parent's fork return value");
|
||||||
|
}
|
||||||
|
(void)__syscall1(SYS_close, fds[0]);
|
||||||
|
(void)__syscall1(SYS_close, fds[1]);
|
||||||
|
status = raw_wait(pid);
|
||||||
|
check(status == 7 << 8, "the child exited 7 (status 7<<8 == 1792)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3+4. execve of a good and of a nonexistent binary. */
|
||||||
|
static void
|
||||||
|
exec_scenarios(void)
|
||||||
|
{
|
||||||
|
pid_t pid;
|
||||||
|
int status;
|
||||||
|
|
||||||
|
pid = fork();
|
||||||
|
if (pid < 0)
|
||||||
|
{
|
||||||
|
check(0, "execve /bin/true child exits 0");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
(void)run_execve();
|
||||||
|
__syscall1(SYS_exit_group, 127);
|
||||||
|
}
|
||||||
|
status = raw_wait(pid);
|
||||||
|
check(status == 0, "fork + execve(\"/bin/true\") exits 0");
|
||||||
|
|
||||||
|
pid = fork();
|
||||||
|
if (pid < 0)
|
||||||
|
{
|
||||||
|
check(0, "execve of a nonexistent binary gives 127<<8");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
static char *av_bad[] = {"vlibc-no-such-binary", 0};
|
||||||
|
|
||||||
|
(void)execve("/bin/nonexistent-vlibc-xyz", av_bad, environ);
|
||||||
|
__syscall1(SYS_exit_group, 127);
|
||||||
|
}
|
||||||
|
status = raw_wait(pid);
|
||||||
|
check(status == 127 << 8, "failed execve: the child exited 127 (status 127<<8)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 5. system. */
|
||||||
|
// NOLINTBEGIN(bugprone-command-processor)
|
||||||
|
static void
|
||||||
|
system_scenario(void)
|
||||||
|
{
|
||||||
|
check(system("exit 3") == 768, "system(\"exit 3\") returns 768 (WEXITSTATUS 3)");
|
||||||
|
}
|
||||||
|
// NOLINTEND(bugprone-command-processor)
|
||||||
|
|
||||||
|
/* 6. popen/pclose. */
|
||||||
|
// NOLINTBEGIN(bugprone-command-processor)
|
||||||
|
static void
|
||||||
|
popen_scenario(void)
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
char rbuf[8];
|
||||||
|
long n = 0;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
f = popen("echo hi", "r");
|
||||||
|
check(f != 0, "popen(\"echo hi\", \"r\") returns a stream");
|
||||||
|
if (f == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
n = (long)fread(rbuf, 1, sizeof(rbuf), f);
|
||||||
|
check(n == 3, "fread on the popen stream returns three bytes");
|
||||||
|
n = 0;
|
||||||
|
while (n < 3 && rbuf[n] == "hi\n"[n])
|
||||||
|
{
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
check(n == 3, "the stream reads \"hi\\n\"");
|
||||||
|
rc = pclose(f);
|
||||||
|
check(rc == 0, "pclose of the \"r\" stream returns 0");
|
||||||
|
|
||||||
|
f = popen("cat >/dev/null", "w");
|
||||||
|
check(f != 0, "popen(\"cat >/dev/null\", \"w\") returns a stream");
|
||||||
|
if (f == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fwrite("bye", 1, 3, f) == 3, "three bytes are written to the \"w\" stream");
|
||||||
|
rc = pclose(f);
|
||||||
|
check(rc == 0, "pclose of the \"w\" stream returns 0");
|
||||||
|
}
|
||||||
|
// NOLINTEND(bugprone-command-processor)
|
||||||
|
|
||||||
|
/* 7. uid/gid round trip + supplementary groups. */
|
||||||
|
static void
|
||||||
|
uid_scenarios(void)
|
||||||
|
{
|
||||||
|
gid_t gids[64];
|
||||||
|
int n;
|
||||||
|
int m;
|
||||||
|
|
||||||
|
check(setuid(getuid()) == 0, "setuid(getuid()) returns 0");
|
||||||
|
check(seteuid(geteuid()) == 0, "seteuid(geteuid()) returns 0");
|
||||||
|
check(setgid(getgid()) == 0, "setgid(getgid()) returns 0");
|
||||||
|
check(setegid(getegid()) == 0, "setegid(getegid()) returns 0");
|
||||||
|
n = getgroups(0, 0);
|
||||||
|
check(n >= 0, "getgroups(0, NULL) returns a non-negative count");
|
||||||
|
if (n >= 0)
|
||||||
|
{
|
||||||
|
m = getgroups(64, gids);
|
||||||
|
check(m == n, "getgroups(64, list) returns the same count");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/* 8. Session control in fork children (never in the parent: leaving the
|
||||||
|
* parent's process group would trigger SIGTTOU on terminal writes). */
|
||||||
|
static void
|
||||||
|
session_scenarios(void)
|
||||||
|
{
|
||||||
|
int fds[2] = {0};
|
||||||
|
pid_t pid;
|
||||||
|
int status;
|
||||||
|
unsigned long saved;
|
||||||
|
|
||||||
|
/* Child A: setsid makes it a session and group leader. */
|
||||||
|
pid = fork();
|
||||||
|
if (pid < 0)
|
||||||
|
{
|
||||||
|
check(0, "setsid child");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
pid_t sid = setsid();
|
||||||
|
|
||||||
|
if (sid != getpid())
|
||||||
|
{
|
||||||
|
__syscall1(SYS_exit_group, 1);
|
||||||
|
}
|
||||||
|
if (getpgrp() != getpid())
|
||||||
|
{
|
||||||
|
__syscall1(SYS_exit_group, 2);
|
||||||
|
}
|
||||||
|
if (getsid(0) != getpid())
|
||||||
|
{
|
||||||
|
__syscall1(SYS_exit_group, 3);
|
||||||
|
}
|
||||||
|
__syscall1(SYS_exit_group, 0);
|
||||||
|
}
|
||||||
|
status = raw_wait(pid);
|
||||||
|
check(status == 0, "child: setsid() == getpid(), getpgrp() == pid, getsid(0) == pid");
|
||||||
|
|
||||||
|
/* Child B: setpgid(0,0) + setpgrp() + getpgid. */
|
||||||
|
pid = fork();
|
||||||
|
if (pid < 0)
|
||||||
|
{
|
||||||
|
check(0, "setpgid child");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
if (setpgid(0, 0) != 0)
|
||||||
|
{
|
||||||
|
__syscall1(SYS_exit_group, 1);
|
||||||
|
}
|
||||||
|
if (setpgrp() != 0)
|
||||||
|
{
|
||||||
|
__syscall1(SYS_exit_group, 2);
|
||||||
|
}
|
||||||
|
if (getpgrp() != getpid())
|
||||||
|
{
|
||||||
|
__syscall1(SYS_exit_group, 3);
|
||||||
|
}
|
||||||
|
if (getpgid(0) != getpid())
|
||||||
|
{
|
||||||
|
__syscall1(SYS_exit_group, 4);
|
||||||
|
}
|
||||||
|
__syscall1(SYS_exit_group, 0);
|
||||||
|
}
|
||||||
|
status = raw_wait(pid);
|
||||||
|
check(status == 0, "child: setpgid(0,0), setpgrp(), getpgrp(), getpgid(0) all agree");
|
||||||
|
|
||||||
|
/* tcgetpgrp/tcsetpgrp on a pipe fail with -1 (ENOTTY). */
|
||||||
|
if (syscall_ret(__syscall1(SYS_pipe, (long)fds)) < 0)
|
||||||
|
{
|
||||||
|
check(0, "tcgetpgrp on a pipe");
|
||||||
|
check(0, "tcsetpgrp on a pipe");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
saved = tcb_slot1();
|
||||||
|
check(tcgetpgrp(fds[0]) == (pid_t)-1, "tcgetpgrp on a pipe returns -1");
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
saved = tcb_slot1();
|
||||||
|
check(tcsetpgrp(fds[0], 0) == -1, "tcsetpgrp on a pipe returns -1");
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
(void)__syscall1(SYS_close, fds[0]);
|
||||||
|
(void)__syscall1(SYS_close, fds[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 11. vfork: the child execs /bin/true while the parent is suspended. */
|
||||||
|
// NOLINTBEGIN(clang-analyzer-security.insecureAPI.vfork, clang-analyzer-unix.Vfork,
|
||||||
|
// bugprone-unsafe-functions)
|
||||||
|
static void
|
||||||
|
vfork_scenario(void)
|
||||||
|
{
|
||||||
|
pid_t pid = vfork();
|
||||||
|
int status;
|
||||||
|
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
/* Child: exec (the only sanctioned follow-up) or raw-exit. */
|
||||||
|
(void)execve("/bin/true", av_true, environ);
|
||||||
|
__syscall1(SYS_exit_group, 126);
|
||||||
|
}
|
||||||
|
check(pid >= 0, "vfork returns a pid in the parent (0 in the child)");
|
||||||
|
if (pid < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
status = raw_wait(pid);
|
||||||
|
check(status == 0, "vfork child exec'd /bin/true and exited 0");
|
||||||
|
}
|
||||||
|
// NOLINTEND(clang-analyzer-security.insecureAPI.vfork, clang-analyzer-unix.Vfork,
|
||||||
|
// bugprone-unsafe-functions)
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
/* 9. One fork+exec per entry point. */
|
||||||
|
static int
|
||||||
|
run_execv(void)
|
||||||
|
{
|
||||||
|
return execv("/bin/true", av_true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
run_execvp(void)
|
||||||
|
{
|
||||||
|
return execvp("true", av_true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
run_execl(void)
|
||||||
|
{
|
||||||
|
return execl("/bin/true", "true", (char *)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
run_execlp(void)
|
||||||
|
{
|
||||||
|
return execlp("true", "true", (char *)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
run_execle(void)
|
||||||
|
{
|
||||||
|
return execle("/bin/true", "true", (char *)0, env_le);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
exec_variants(void)
|
||||||
|
{
|
||||||
|
(void)exec_one("execv(\"/bin/true\")", run_execv);
|
||||||
|
(void)exec_one("execvp(\"true\") via the default PATH", run_execvp);
|
||||||
|
(void)exec_one("execl(\"/bin/true\")", run_execl);
|
||||||
|
(void)exec_one("execlp(\"true\") via the default PATH", run_execlp);
|
||||||
|
(void)exec_one("execle(\"/bin/true\") with a custom envp", run_execle);
|
||||||
|
(void)exec_one("fexecve of an open /bin/true", run_fexecve);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 10. PATH search with a private helper script. */
|
||||||
|
static void
|
||||||
|
path_search_scenario(void)
|
||||||
|
{
|
||||||
|
const char *dir = "/tmp/vlibc-t20-bin";
|
||||||
|
const char *script_path = "/tmp/vlibc-t20-bin/vlibc-t20-helper";
|
||||||
|
const char script[] = "#!/bin/sh\nexit 5\n";
|
||||||
|
static char *path_env[] = {"PATH=/tmp/vlibc-t20-bin", 0};
|
||||||
|
static char *path_env_cwd[] = {"PATH=:/tmp/vlibc-t20-bin", 0};
|
||||||
|
char **saved_environ;
|
||||||
|
unsigned long saved;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
(void)__syscall2(SYS_mkdir, (long)dir, 0755); /* EEXIST is fine */
|
||||||
|
fd = open(script_path, T20_O_WRONLY | T20_O_CREAT | T20_O_TRUNC, 0755);
|
||||||
|
check(fd >= 0, "the helper script is created");
|
||||||
|
if (fd >= 0)
|
||||||
|
{
|
||||||
|
check(write(fd, script, sizeof(script) - 1) == (ssize_t)(sizeof(script) - 1),
|
||||||
|
"the helper script body is written");
|
||||||
|
check(close(fd) == 0, "the helper script descriptor is closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
saved_environ = environ;
|
||||||
|
environ = path_env;
|
||||||
|
{
|
||||||
|
pid_t pid = fork();
|
||||||
|
int status;
|
||||||
|
|
||||||
|
if (pid < 0)
|
||||||
|
{
|
||||||
|
check(0, "execvp finds the helper via the custom PATH");
|
||||||
|
environ = saved_environ;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
static char *av[] = {"vlibc-t20-helper", 0};
|
||||||
|
|
||||||
|
(void)execvp("vlibc-t20-helper", av);
|
||||||
|
__syscall1(SYS_exit_group, 127);
|
||||||
|
}
|
||||||
|
status = raw_wait(pid);
|
||||||
|
check(status == 5 << 8, "execvp finds the helper via PATH and it exits 5");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
pid_t pid = fork();
|
||||||
|
int status;
|
||||||
|
|
||||||
|
if (pid < 0)
|
||||||
|
{
|
||||||
|
check(0, "execlp finds the helper via the custom PATH");
|
||||||
|
environ = saved_environ;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
(void)execlp("vlibc-t20-helper", "vlibc-t20-helper", (char *)0);
|
||||||
|
__syscall1(SYS_exit_group, 127);
|
||||||
|
}
|
||||||
|
status = raw_wait(pid);
|
||||||
|
check(status == 5 << 8, "execlp finds the helper via PATH and it exits 5");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* An empty PATH entry means the current directory (skipped here:
|
||||||
|
* the helper is not in cwd), and the search still reaches the
|
||||||
|
* second entry. */
|
||||||
|
environ = path_env_cwd;
|
||||||
|
{
|
||||||
|
pid_t pid = fork();
|
||||||
|
int status;
|
||||||
|
|
||||||
|
if (pid < 0)
|
||||||
|
{
|
||||||
|
check(0, "empty PATH entry is skipped");
|
||||||
|
environ = saved_environ;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
static char *av[] = {"vlibc-t20-helper", 0};
|
||||||
|
|
||||||
|
(void)execvp("vlibc-t20-helper", av);
|
||||||
|
__syscall1(SYS_exit_group, 127);
|
||||||
|
}
|
||||||
|
status = raw_wait(pid);
|
||||||
|
check(status == 5 << 8, "an empty PATH entry means cwd and the search continues");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* environ NULL -> the built-in default PATH finds /bin/true. */
|
||||||
|
environ = 0;
|
||||||
|
(void)exec_one("execvp(\"true\") with environ NULL (default PATH)", run_execvp);
|
||||||
|
|
||||||
|
/* Not found anywhere: -1 (errno written, bracketed). */
|
||||||
|
saved = tcb_slot1();
|
||||||
|
check(execvp("vlibc-no-such-helper-xyz", av_true) == -1,
|
||||||
|
"execvp of a missing helper returns -1");
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
|
||||||
|
environ = saved_environ;
|
||||||
|
(void)__syscall3(SYS_unlinkat, T20_AT_FDCWD, (long)script_path, 0);
|
||||||
|
(void)__syscall1(SYS_rmdir, (long)dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Failure scenarios (-f): every assertion is on the return value only,
|
||||||
|
* and the process exits through raw SYS_exit_group because the library
|
||||||
|
* writes errno on these paths (host-TCB hazard).
|
||||||
|
*/
|
||||||
|
// NOLINTBEGIN(bugprone-command-processor)
|
||||||
|
static int
|
||||||
|
failure_scenarios(void)
|
||||||
|
{
|
||||||
|
unsigned long saved;
|
||||||
|
int rc = 0;
|
||||||
|
|
||||||
|
saved = tcb_slot1();
|
||||||
|
if (execve("/bin/nonexistent-vlibc-xyz", av_true, env_ve) != -1)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: execve of a nonexistent binary did not return -1\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: execve of a nonexistent binary -> -1\n");
|
||||||
|
}
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
|
||||||
|
if (system(0) != 1)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: system(NULL) did not return 1\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: system(NULL) -> 1\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
saved = tcb_slot1();
|
||||||
|
if (popen("echo hi", "x") != 0)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: popen with an invalid mode did not return NULL\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: popen with an invalid mode -> NULL\n");
|
||||||
|
}
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
|
||||||
|
saved = tcb_slot1();
|
||||||
|
if (fexecve(-1, av_true, env_ve) != -1)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: fexecve(-1) did not return -1\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: fexecve(-1) -> -1\n");
|
||||||
|
}
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
|
||||||
|
saved = tcb_slot1();
|
||||||
|
if (execvp("", av_true) != -1)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: execvp(\"\") did not return -1\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: execvp(\"\") -> -1\n");
|
||||||
|
}
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
|
||||||
|
saved = tcb_slot1();
|
||||||
|
if (getgroups(-1, 0) != -1)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: getgroups(-1) did not return -1\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: getgroups(-1) -> -1\n");
|
||||||
|
}
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
{
|
||||||
|
gid_t one = 0;
|
||||||
|
|
||||||
|
saved = tcb_slot1();
|
||||||
|
if (setgroups(1, &one) != -1)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: setgroups without privilege did not return -1\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: setgroups without privilege -> -1\n");
|
||||||
|
}
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
// NOLINTEND(bugprone-command-processor)
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
int rc = failure_scenarios();
|
||||||
|
|
||||||
|
__syscall1(SYS_exit_group, rc);
|
||||||
|
return rc; /* not reached */
|
||||||
|
}
|
||||||
|
|
||||||
|
id_scenarios();
|
||||||
|
fork_scenario();
|
||||||
|
exec_scenarios();
|
||||||
|
system_scenario();
|
||||||
|
popen_scenario();
|
||||||
|
uid_scenarios();
|
||||||
|
exec_variants();
|
||||||
|
path_search_scenario();
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
session_scenarios();
|
||||||
|
vfork_scenario();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (failures > 0)
|
||||||
|
{
|
||||||
|
say(2, "FAILED (");
|
||||||
|
say_dec(2, (unsigned long)failures);
|
||||||
|
say(2, " check(s))\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
say(1, "all process tests passed\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,384 @@
|
|||||||
|
/*
|
||||||
|
* vlibc — stat family test (todo 22).
|
||||||
|
*
|
||||||
|
* Exercises sys/stat.h end to end:
|
||||||
|
*
|
||||||
|
* 1. stat("/dev/null") reports a character device with a non-zero rdev.
|
||||||
|
* 2. open+write a temp file, fstat shows S_ISREG with the written size;
|
||||||
|
* close then stat(path) agrees (and the inode matches).
|
||||||
|
* 3. umask(0) then mkdir/mkdirat 0755/0700 — stat shows S_ISDIR with the
|
||||||
|
* exact requested mode; the umask is restored afterwards.
|
||||||
|
* 4. A symlink created via raw SYS_symlink: lstat reports the link
|
||||||
|
* itself (S_ISLNK), stat follows it (S_ISREG); fstatat with
|
||||||
|
* AT_SYMLINK_NOFOLLOW does not follow.
|
||||||
|
* 5. chmod(file, 0600) / fchmod / fchmodat update the mode.
|
||||||
|
* 6. chown-family no-ops: fchown(fd, -1, -1) and fchownat(...,-1,-1,0)
|
||||||
|
* succeed without changing ownership; lchown on the symlink succeeds.
|
||||||
|
* 7. utimensat(AT_FDCWD, file, NULL, 0) and futimens(fd, NULL) set the
|
||||||
|
* timestamps to the current time and return 0.
|
||||||
|
* 8. mkfifo/mkfifoat create FIFOs (S_ISFIFO).
|
||||||
|
* 9. Level 2: mknod/mknodat create FIFOs via S_IFIFO (no privilege
|
||||||
|
* needed for a FIFO).
|
||||||
|
*
|
||||||
|
* The -f mode runs only the failure scenarios: stat/lstat/fstatat/fstat on
|
||||||
|
* nonexistent paths/fds, mkdir on an existing directory, mkfifo in a
|
||||||
|
* missing directory, chmod/fchmod/fchmodat/chown/lchown/fchown/fchownat/
|
||||||
|
* utimensat/futimens negatives, and (L2) mknod without a type bit
|
||||||
|
* (EINVAL). Only return values are asserted — errno is never read. The
|
||||||
|
* negative paths make the LIBRARY write errno (syscall_ret), which under
|
||||||
|
* a host-linked binary targets glibc's private dtv slot at %fs:0+8, so -f
|
||||||
|
* exits via raw SYS_exit_group (house pattern, tests/test_malloc.c).
|
||||||
|
*
|
||||||
|
* 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 <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "../src/internal/syscall.h"
|
||||||
|
|
||||||
|
/* Kernel-UAPI open/at flags, local to this test (include/fcntl.h is todo 21). */
|
||||||
|
#define T22_AT_FDCWD (-100)
|
||||||
|
#define T22_AT_SYMLINK_NOFOLLOW 0x100
|
||||||
|
#define T22_O_RDWR 0x2
|
||||||
|
#define T22_O_CREAT 0x40
|
||||||
|
#define T22_O_EXCL 0x80
|
||||||
|
|
||||||
|
/* Scratch paths in /tmp (test runs from an arbitrary cwd). */
|
||||||
|
#define T22_FILE "/tmp/vlibc_stat_t22_file"
|
||||||
|
#define T22_DIR "/tmp/vlibc_stat_t22_dir"
|
||||||
|
#define T22_DIR2 "/tmp/vlibc_stat_t22_dir2"
|
||||||
|
#define T22_LINK "/tmp/vlibc_stat_t22_link"
|
||||||
|
#define T22_LINK_TARGET "vlibc_stat_t22_file"
|
||||||
|
#define T22_FIFO "/tmp/vlibc_stat_t22_fifo"
|
||||||
|
#define T22_FIFO2 "/tmp/vlibc_stat_t22_fifo2"
|
||||||
|
#define T22_FIFO3 "/tmp/vlibc_stat_t22_fifo3"
|
||||||
|
#define T22_FIFO4 "/tmp/vlibc_stat_t22_fifo4"
|
||||||
|
#define T22_FDIR "/tmp/vlibc_stat_t22_fdir"
|
||||||
|
#define T22_MISSING "/nonexistent-vlibc-zzz"
|
||||||
|
|
||||||
|
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';
|
||||||
|
if (v == 0)
|
||||||
|
{
|
||||||
|
buf[--i] = '0';
|
||||||
|
}
|
||||||
|
while (v > 0 && i > 0)
|
||||||
|
{
|
||||||
|
buf[--i] = (char)('0' + v % 10);
|
||||||
|
v /= 10;
|
||||||
|
}
|
||||||
|
say(fd, &buf[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
check(int ok, const char *msg)
|
||||||
|
{
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
say(1, "PASS: ");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "FAIL: ");
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
|
say(1, msg);
|
||||||
|
say(1, "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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).
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1. /dev/null is a character device. */
|
||||||
|
static void
|
||||||
|
dev_null_scenario(void)
|
||||||
|
{
|
||||||
|
struct stat st = {0};
|
||||||
|
|
||||||
|
check(sizeof(struct stat) == 144, "sizeof(struct stat) is 144");
|
||||||
|
check(stat("/dev/null", &st) == 0, "stat(/dev/null) returns 0");
|
||||||
|
check(S_ISCHR(st.st_mode), "st_mode classifies /dev/null as S_ISCHR");
|
||||||
|
check(st.st_rdev != 0, "st_rdev of /dev/null is non-zero");
|
||||||
|
check(fstatat(T22_AT_FDCWD, "/dev/null", &st, 0) == 0,
|
||||||
|
"fstatat(AT_FDCWD, /dev/null) returns 0");
|
||||||
|
check(S_ISCHR(st.st_mode), "fstatat st_mode classifies /dev/null as S_ISCHR");
|
||||||
|
check(!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode) && !S_ISFIFO(st.st_mode),
|
||||||
|
"/dev/null is not reg/dir/fifo");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2 + 5 + 6 + 7: the temp file lifecycle. */
|
||||||
|
static void
|
||||||
|
file_scenario(void)
|
||||||
|
{
|
||||||
|
static const char payload[] = "hello from vlibc stat\n";
|
||||||
|
struct stat st = {0};
|
||||||
|
struct stat st2 = {0};
|
||||||
|
unsigned long saved;
|
||||||
|
ssize_t wrote;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
fd = open(T22_FILE, T22_O_RDWR | T22_O_CREAT | T22_O_EXCL, 0644);
|
||||||
|
check(fd >= 0, "open O_RDWR|O_CREAT|O_EXCL 0644 returns a descriptor");
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wrote = write(fd, payload, sizeof(payload) - 1);
|
||||||
|
check(wrote == (ssize_t)(sizeof(payload) - 1), "write of 22 bytes returns 22");
|
||||||
|
check(fstat(fd, &st) == 0, "fstat(fd) returns 0");
|
||||||
|
check(S_ISREG(st.st_mode), "fstat st_mode classifies the file as S_ISREG");
|
||||||
|
check(st.st_size == (off_t)(sizeof(payload) - 1), "fstat st_size equals the written bytes");
|
||||||
|
check((st.st_mode & 0777) == 0644, "fstat st_mode permission bits are 0644 (umask(0))");
|
||||||
|
check(st.st_nlink == 1, "fstat st_nlink is 1");
|
||||||
|
check(st.st_atime > 0 && st.st_mtime > 0 && st.st_ctime > 0,
|
||||||
|
"st_atime/st_mtime/st_ctime macros expose positive times");
|
||||||
|
check(stat(T22_FILE, &st2) == 0, "stat(path) returns 0");
|
||||||
|
check(st2.st_size == st.st_size, "stat(path) st_size matches fstat");
|
||||||
|
check(st2.st_ino == st.st_ino, "stat(path) st_ino matches fstat");
|
||||||
|
|
||||||
|
/* 5. chmod family. */
|
||||||
|
check(chmod(T22_FILE, 0600) == 0, "chmod(file, 0600) returns 0");
|
||||||
|
saved = tcb_slot1();
|
||||||
|
check(stat(T22_FILE, &st2) == 0, "stat after chmod returns 0");
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
check((st2.st_mode & 0777) == 0600, "mode is 0600 after chmod");
|
||||||
|
check(fchmod(fd, 0640) == 0, "fchmod(fd, 0640) returns 0");
|
||||||
|
check(fchmodat(T22_AT_FDCWD, T22_FILE, 0644, 0) == 0,
|
||||||
|
"fchmodat(AT_FDCWD, file, 0644) returns 0");
|
||||||
|
saved = tcb_slot1();
|
||||||
|
check(stat(T22_FILE, &st2) == 0, "stat after fchmodat returns 0");
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
check((st2.st_mode & 0777) == 0644, "mode is 0644 after fchmodat");
|
||||||
|
|
||||||
|
/* 6. chown-family no-ops (-1 = leave unchanged). */
|
||||||
|
check(fchown(fd, (uid_t)-1, (gid_t)-1) == 0, "fchown(fd, -1, -1) returns 0");
|
||||||
|
check(fchownat(T22_AT_FDCWD, T22_FILE, (uid_t)-1, (gid_t)-1, 0) == 0,
|
||||||
|
"fchownat(AT_FDCWD, file, -1, -1, 0) returns 0");
|
||||||
|
|
||||||
|
/* 7. utimensat/futimens with NULL times (set to current time). */
|
||||||
|
check(utimensat(T22_AT_FDCWD, T22_FILE, 0, 0) == 0,
|
||||||
|
"utimensat(AT_FDCWD, file, NULL, 0) returns 0");
|
||||||
|
check(futimens(fd, 0) == 0, "futimens(fd, NULL) returns 0");
|
||||||
|
|
||||||
|
check(close(fd) == 0, "close of the temp file returns 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. mkdir/mkdirat with a cleared umask. */
|
||||||
|
static void
|
||||||
|
mkdir_scenario(void)
|
||||||
|
{
|
||||||
|
struct stat st = {0};
|
||||||
|
mode_t old;
|
||||||
|
|
||||||
|
old = umask(0);
|
||||||
|
check(mkdir(T22_DIR, 0755) == 0, "mkdir(dir, 0755) returns 0");
|
||||||
|
check(mkdirat(T22_AT_FDCWD, T22_DIR2, 0700) == 0, "mkdirat(AT_FDCWD, dir2, 0700) returns 0");
|
||||||
|
umask(old);
|
||||||
|
check(stat(T22_DIR, &st) == 0, "stat(dir) returns 0");
|
||||||
|
check(S_ISDIR(st.st_mode), "st_mode classifies dir as S_ISDIR");
|
||||||
|
check((st.st_mode & 0777) == 0755, "dir mode is exactly 0755 (umask(0))");
|
||||||
|
check(stat(T22_DIR2, &st) == 0, "stat(dir2) returns 0");
|
||||||
|
check(S_ISDIR(st.st_mode) && (st.st_mode & 0777) == 0700, "dir2 mode is exactly 0700");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. symlink: lstat reports the link, stat follows. */
|
||||||
|
static void
|
||||||
|
symlink_scenario(void)
|
||||||
|
{
|
||||||
|
struct stat st = {0};
|
||||||
|
|
||||||
|
check(__syscall2(SYS_symlink, (long)T22_LINK_TARGET, (long)T22_LINK) == 0,
|
||||||
|
"raw SYS_symlink creates the link");
|
||||||
|
check(lstat(T22_LINK, &st) == 0, "lstat(link) returns 0");
|
||||||
|
check(S_ISLNK(st.st_mode), "lstat st_mode classifies the link as S_ISLNK");
|
||||||
|
check(!S_ISREG(st.st_mode), "lstat does not follow the link");
|
||||||
|
check(stat(T22_LINK, &st) == 0, "stat(link) returns 0");
|
||||||
|
check(S_ISREG(st.st_mode), "stat follows the link to S_ISREG");
|
||||||
|
check(fstatat(T22_AT_FDCWD, T22_LINK, &st, T22_AT_SYMLINK_NOFOLLOW) == 0,
|
||||||
|
"fstatat(link, AT_SYMLINK_NOFOLLOW) returns 0");
|
||||||
|
check(S_ISLNK(st.st_mode), "fstatat with AT_SYMLINK_NOFOLLOW reports S_ISLNK");
|
||||||
|
check(lchown(T22_LINK, (uid_t)-1, (gid_t)-1) == 0, "lchown(link, -1, -1) returns 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 8. mkfifo/mkfifoat create FIFOs. */
|
||||||
|
static void
|
||||||
|
mkfifo_scenario(void)
|
||||||
|
{
|
||||||
|
struct stat st = {0};
|
||||||
|
|
||||||
|
check(mkfifo(T22_FIFO, 0644) == 0, "mkfifo(fifo, 0644) returns 0");
|
||||||
|
check(mkfifoat(T22_AT_FDCWD, T22_FIFO2, 0600) == 0,
|
||||||
|
"mkfifoat(AT_FDCWD, fifo2, 0600) returns 0");
|
||||||
|
check(stat(T22_FIFO, &st) == 0, "stat(fifo) returns 0");
|
||||||
|
check(S_ISFIFO(st.st_mode), "st_mode classifies fifo as S_ISFIFO");
|
||||||
|
check(stat(T22_FIFO2, &st) == 0, "stat(fifo2) returns 0");
|
||||||
|
check(S_ISFIFO(st.st_mode), "st_mode classifies fifo2 as S_ISFIFO");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
/* 9. mknod/mknodat create FIFOs (no privilege needed for S_IFIFO). */
|
||||||
|
static void
|
||||||
|
mknod_scenario(void)
|
||||||
|
{
|
||||||
|
struct stat st = {0};
|
||||||
|
|
||||||
|
check(mknod(T22_FIFO3, S_IFIFO | 0640, 0) == 0, "mknod(fifo3, S_IFIFO|0640, 0) returns 0");
|
||||||
|
check(mknodat(T22_AT_FDCWD, T22_FIFO4, S_IFIFO | 0600, 0) == 0,
|
||||||
|
"mknodat(AT_FDCWD, fifo4, S_IFIFO|0600, 0) returns 0");
|
||||||
|
check(stat(T22_FIFO3, &st) == 0, "stat(fifo3) returns 0");
|
||||||
|
check(S_ISFIFO(st.st_mode) && (st.st_mode & 0777) == 0640,
|
||||||
|
"mknod fifo3 is S_ISFIFO with mode 0640");
|
||||||
|
check(stat(T22_FIFO4, &st) == 0, "stat(fifo4) returns 0");
|
||||||
|
check(S_ISFIFO(st.st_mode) && (st.st_mode & 0777) == 0600,
|
||||||
|
"mknodat fifo4 is S_ISFIFO with mode 0600");
|
||||||
|
}
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
/* Clean up every scratch path; failures here are reported, not fatal. */
|
||||||
|
static void
|
||||||
|
cleanup_scenario(void)
|
||||||
|
{
|
||||||
|
long r;
|
||||||
|
|
||||||
|
r = __syscall3(SYS_unlinkat, T22_AT_FDCWD, (long)T22_LINK, 0);
|
||||||
|
check(r == 0, "raw unlinkat removes the link");
|
||||||
|
r = __syscall3(SYS_unlinkat, T22_AT_FDCWD, (long)T22_FILE, 0);
|
||||||
|
check(r == 0, "raw unlinkat removes the temp file");
|
||||||
|
r = __syscall3(SYS_unlinkat, T22_AT_FDCWD, (long)T22_FIFO, 0);
|
||||||
|
check(r == 0, "raw unlinkat removes fifo");
|
||||||
|
r = __syscall3(SYS_unlinkat, T22_AT_FDCWD, (long)T22_FIFO2, 0);
|
||||||
|
check(r == 0, "raw unlinkat removes fifo2");
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
r = __syscall3(SYS_unlinkat, T22_AT_FDCWD, (long)T22_FIFO3, 0);
|
||||||
|
check(r == 0, "raw unlinkat removes fifo3");
|
||||||
|
r = __syscall3(SYS_unlinkat, T22_AT_FDCWD, (long)T22_FIFO4, 0);
|
||||||
|
check(r == 0, "raw unlinkat removes fifo4");
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
r = __syscall1(SYS_rmdir, (long)T22_DIR);
|
||||||
|
check(r == 0, "raw rmdir removes dir");
|
||||||
|
r = __syscall1(SYS_rmdir, (long)T22_DIR2);
|
||||||
|
check(r == 0, "raw rmdir removes dir2");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The failure scenarios. Exit via raw SYS_exit_group: the library's errno
|
||||||
|
* writes on these paths corrupt glibc's private dtv slot at %fs:0+8, so
|
||||||
|
* host cleanup must never run (house pattern). */
|
||||||
|
static void
|
||||||
|
failure_scenarios(void)
|
||||||
|
{
|
||||||
|
struct stat st = {0};
|
||||||
|
|
||||||
|
check(stat(T22_MISSING, &st) == -1, "stat(nonexistent) returns -1");
|
||||||
|
check(lstat(T22_MISSING, &st) == -1, "lstat(nonexistent) returns -1");
|
||||||
|
check(fstatat(T22_AT_FDCWD, T22_MISSING, &st, 0) == -1,
|
||||||
|
"fstatat(AT_FDCWD, nonexistent) returns -1");
|
||||||
|
check(fstat(-1, &st) == -1, "fstat(-1) returns -1");
|
||||||
|
check(mkdir(T22_FDIR, 0755) == 0, "mkdir(fdir) succeeds for the negative");
|
||||||
|
check(mkdir(T22_FDIR, 0755) == -1, "mkdir(existing dir) returns -1");
|
||||||
|
check(mkfifo("/nonexistent-vlibc-zzz/x", 0644) == -1, "mkfifo in missing dir returns -1");
|
||||||
|
check(chmod(T22_MISSING, 0600) == -1, "chmod(nonexistent) returns -1");
|
||||||
|
check(fchmod(-1, 0600) == -1, "fchmod(-1) returns -1");
|
||||||
|
check(fchmodat(T22_AT_FDCWD, T22_MISSING, 0600, 0) == -1, "fchmodat(nonexistent) returns -1");
|
||||||
|
check(chown(T22_MISSING, (uid_t)-1, (gid_t)-1) == -1, "chown(nonexistent) returns -1");
|
||||||
|
check(lchown(T22_MISSING, (uid_t)-1, (gid_t)-1) == -1, "lchown(nonexistent) returns -1");
|
||||||
|
check(fchown(-1, (uid_t)-1, (gid_t)-1) == -1, "fchown(-1) returns -1");
|
||||||
|
check(fchownat(T22_AT_FDCWD, T22_MISSING, (uid_t)-1, (gid_t)-1, 0) == -1,
|
||||||
|
"fchownat(nonexistent) returns -1");
|
||||||
|
check(utimensat(T22_AT_FDCWD, T22_MISSING, 0, 0) == -1, "utimensat(nonexistent) returns -1");
|
||||||
|
check(futimens(-1, 0) == -1, "futimens(-1) returns -1");
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
check(mknod(T22_FDIR, 0644, 0) == -1, "mknod without a type bit returns -1");
|
||||||
|
check(mknodat(T22_AT_FDCWD, T22_FDIR, 0644, 0) == -1, "mknodat without a type bit returns -1");
|
||||||
|
check(mknod("/nonexistent-vlibc-zzz/x", S_IFIFO | 0644, 0) == -1,
|
||||||
|
"mknod in missing dir returns -1");
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
__syscall1(SYS_rmdir, (long)T22_FDIR);
|
||||||
|
if (failures == 0)
|
||||||
|
{
|
||||||
|
say(1, "all stat failure scenarios passed\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "FAILURES: ");
|
||||||
|
say_dec(1, (unsigned long)failures);
|
||||||
|
say(1, "\n");
|
||||||
|
}
|
||||||
|
__syscall1(SYS_exit_group, failures == 0 ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'f' && argv[1][2] == '\0')
|
||||||
|
{
|
||||||
|
failure_scenarios();
|
||||||
|
}
|
||||||
|
|
||||||
|
dev_null_scenario();
|
||||||
|
file_scenario();
|
||||||
|
mkdir_scenario();
|
||||||
|
symlink_scenario();
|
||||||
|
mkfifo_scenario();
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
mknod_scenario();
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
cleanup_scenario();
|
||||||
|
|
||||||
|
if (failures == 0)
|
||||||
|
{
|
||||||
|
say(1, "all stat tests passed\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "FAILURES: ");
|
||||||
|
say_dec(1, (unsigned long)failures);
|
||||||
|
say(1, "\n");
|
||||||
|
}
|
||||||
|
return failures == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,802 @@
|
|||||||
|
/*
|
||||||
|
* vlibc — buffered stdio test (todo 15).
|
||||||
|
*
|
||||||
|
* Exercises the stdio FILE core end to end:
|
||||||
|
*
|
||||||
|
* 1. Round trip: fopen "w+b", fwrite a 1 MiB deterministic binary
|
||||||
|
* pattern, fflush, ftello == size, fseek(0, SEEK_SET), fread the
|
||||||
|
* pattern back, byte-identical. Then read past the end: fgetc ==
|
||||||
|
* EOF, feof set, ferror clear.
|
||||||
|
* 2. Text lines: fputs lines, fgets line boundaries (newline kept,
|
||||||
|
* NUL-terminated), the n-1 clamp, NULL + feof at end of file.
|
||||||
|
* 3. Seek: fseeko SEEK_SET/CUR/END positions, ftell/ftello after
|
||||||
|
* fgetc, fgetpos/fsetpos round trip, rewind.
|
||||||
|
* 4. ungetc: pushback of a read character yields the same character;
|
||||||
|
* ungetc(EOF) == EOF; a second pushback fails; fseek discards the
|
||||||
|
* pushed-back byte.
|
||||||
|
* 5. fflush mid-stream then continue; fflush(NULL) flushes an open
|
||||||
|
* file stream and a freopen'd stdout.
|
||||||
|
* 6. setvbuf modes: _IONBF (NULL buffer), _IOFBF (user buffer),
|
||||||
|
* _IOLBF flushes on newline without an explicit fflush; setbuf
|
||||||
|
* with NULL selects unbuffered.
|
||||||
|
* 7. fdopen over a raw descriptor; fileno returns it; fclose closes
|
||||||
|
* it (probed with raw fcntl).
|
||||||
|
* 8. freopen: re-open a different path on the same FILE; NULL path
|
||||||
|
* changes the mode only.
|
||||||
|
* 9. remove/rename lifecycle; tmpfile write+rewind+read.
|
||||||
|
* 10. Level 2: tmpnam/ctermid/setbuffer/setlinebuf/fopen64.
|
||||||
|
* 11. Read -> write mode switch: after a partial read the logical
|
||||||
|
* position survives the buffered-data discard, so an overwrite
|
||||||
|
* lands at the right offset.
|
||||||
|
* 12. fclose leak check: 200 open/write/close cycles leave no live
|
||||||
|
* allocations (probed with the allocator's heap walk).
|
||||||
|
*
|
||||||
|
* Failure mode (-f): fseek past EOF then fread returns 0 with feof set
|
||||||
|
* and no crash; fopen of a nonexistent path returns NULL; fdopen(-1)
|
||||||
|
* returns NULL; fputc on a read-only stream returns EOF with ferror;
|
||||||
|
* remove of a nonexistent path returns -1. The -f mode makes the
|
||||||
|
* library write errno in several places and then leaves through a raw
|
||||||
|
* SYS_exit_group before any host-libc cleanup runs (the
|
||||||
|
* tests/syscall_test.c discipline).
|
||||||
|
*
|
||||||
|
* The default mode keeps the stream error paths free of library errno
|
||||||
|
* writes, except one setvbuf EINVAL case which is bracketed with a
|
||||||
|
* save/restore of the host TCB slot 1 (the tests/test_env.c pattern).
|
||||||
|
*
|
||||||
|
* errno is never READ here. All diagnostics go through raw SYS_write;
|
||||||
|
* the only header included is vlibc's own <stdio.h> (host headers would
|
||||||
|
* pull GCC internals shadowed by -Iinclude).
|
||||||
|
*
|
||||||
|
* Not part of the library proper; compiled manually for this todo (the
|
||||||
|
* tests/ + make check wiring is owned by a later todo).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "../src/internal/syscall.h"
|
||||||
|
|
||||||
|
/* The allocator's heap-walk consistency probe (hidden; linked in via
|
||||||
|
* malloc.c). Returns the live block count, or (size_t)-1 on disagreement. */
|
||||||
|
extern size_t
|
||||||
|
__vlibc_malloc_check(void); // NOLINT(bugprone-reserved-identifier)
|
||||||
|
|
||||||
|
static int failures;
|
||||||
|
|
||||||
|
/* Write a NUL-terminated string to fd via the raw syscall layer. */
|
||||||
|
static 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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Byte-compare two buffers. noipa keeps -O2 from folding the check.
|
||||||
|
* From here through the scenarios the analyzer is waived: the stream
|
||||||
|
* checker cannot model vlibc's FILE semantics (deliberate reads at EOF,
|
||||||
|
* reads after a failed probe, buffers written by our own fread), and its
|
||||||
|
* raw-syscall modeling flags byte comparisons as reading garbage. */
|
||||||
|
// NOLINTBEGIN(clang-analyzer-unix.Stream, clang-analyzer-core.UndefinedBinaryOperatorResult,
|
||||||
|
// clang-analyzer-unix.StdCLibraryFunctions)
|
||||||
|
static __attribute__((noipa)) int
|
||||||
|
mem_eq(const unsigned char *a, const unsigned char *b, unsigned long n)
|
||||||
|
{
|
||||||
|
unsigned long i;
|
||||||
|
|
||||||
|
for (i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
if (a[i] != b[i])
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Byte-compare two NUL-terminated strings. */
|
||||||
|
static __attribute__((noipa)) int
|
||||||
|
str_eq(const char *a, const char *b)
|
||||||
|
{
|
||||||
|
if (a == NULL || b == NULL)
|
||||||
|
{
|
||||||
|
return a == b;
|
||||||
|
}
|
||||||
|
while (*a == *b && *a != '\0')
|
||||||
|
{
|
||||||
|
a++;
|
||||||
|
b++;
|
||||||
|
}
|
||||||
|
return *a == *b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* True when s starts with the given prefix (used by the L2 scenario). */
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
static __attribute__((noipa)) int
|
||||||
|
prefix_eq(const char *s, const char *prefix)
|
||||||
|
{
|
||||||
|
while (*prefix != '\0')
|
||||||
|
{
|
||||||
|
if (*s != *prefix)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
s++;
|
||||||
|
prefix++;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Save/restore the host libc's TCB slot 1 (%fs:0+8), see the banner. */
|
||||||
|
static unsigned long
|
||||||
|
tcb_slot1_save(void)
|
||||||
|
{
|
||||||
|
return *(unsigned long *)((char *)__builtin_thread_pointer() + 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
tcb_slot1_restore(unsigned long v)
|
||||||
|
{
|
||||||
|
*(unsigned long *)((char *)__builtin_thread_pointer() + 8) = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Deterministic pattern generator (64-bit LCG, like the library's). */
|
||||||
|
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
|
||||||
|
static void
|
||||||
|
fill_pattern(unsigned char *p, unsigned long n, unsigned long long seed)
|
||||||
|
{
|
||||||
|
unsigned long i;
|
||||||
|
unsigned long long s = seed;
|
||||||
|
|
||||||
|
for (i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
s = (s * 6364136223846793005ULL) + 1ULL;
|
||||||
|
p[i] = (unsigned char)(s >> 33);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// NOLINTEND(bugprone-easily-swappable-parameters)
|
||||||
|
|
||||||
|
/* Read a whole small file through raw syscalls into buf; returns size. */
|
||||||
|
static long
|
||||||
|
raw_read_all(const char *path, char *buf, unsigned long cap)
|
||||||
|
{
|
||||||
|
long fd = __syscall4(SYS_openat, -100, (long)path, 0x0, 0);
|
||||||
|
long got = -1;
|
||||||
|
long total = 0;
|
||||||
|
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
while (total < (long)cap)
|
||||||
|
{
|
||||||
|
got = __syscall3(SYS_read, fd, (long)(buf + total), (long)(cap - (unsigned long)total));
|
||||||
|
if (got <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
total += got;
|
||||||
|
}
|
||||||
|
__syscall1(SYS_close, fd);
|
||||||
|
return got < 0 ? -1 : total;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned char pattern[1 << 20];
|
||||||
|
static unsigned char back[1 << 20];
|
||||||
|
|
||||||
|
/* 1. Binary round trip. */
|
||||||
|
static void
|
||||||
|
roundtrip_scenario(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-stdio-roundtrip.bin";
|
||||||
|
FILE *f;
|
||||||
|
unsigned long w;
|
||||||
|
unsigned long r;
|
||||||
|
|
||||||
|
f = fopen(path, "w+b");
|
||||||
|
check(f != NULL, "fopen w+b succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fill_pattern(pattern, sizeof(pattern), 42);
|
||||||
|
w = (unsigned long)fwrite(pattern, 1, sizeof(pattern), f);
|
||||||
|
check(w == sizeof(pattern), "fwrite writes the full 1 MiB");
|
||||||
|
check(fflush(f) == 0, "fflush after fwrite returns 0");
|
||||||
|
check(ftello(f) == (off_t)sizeof(pattern), "ftello after the write == size");
|
||||||
|
check(fseek(f, 0, SEEK_SET) == 0, "fseek(0, SEEK_SET) returns 0");
|
||||||
|
r = (unsigned long)fread(back, 1, sizeof(back), f);
|
||||||
|
check(r == sizeof(back), "fread reads the full 1 MiB");
|
||||||
|
check(mem_eq(pattern, back, sizeof(pattern)), "1 MiB round trip is byte-identical");
|
||||||
|
check(feof(f) == 0, "feof is clear after reading exactly to the end");
|
||||||
|
check(fgetc(f) == EOF, "fgetc past the end returns EOF");
|
||||||
|
check(feof(f) != 0, "feof is set after reading past the end");
|
||||||
|
check(ferror(f) == 0, "ferror stays clear at EOF");
|
||||||
|
clearerr(f);
|
||||||
|
check(feof(f) == 0, "clearerr clears feof");
|
||||||
|
check(fclose(f) == 0, "fclose returns 0");
|
||||||
|
check(remove(path) == 0, "remove deletes the round-trip file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. Text lines. */
|
||||||
|
static void
|
||||||
|
text_scenario(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-stdio-text.txt";
|
||||||
|
FILE *f;
|
||||||
|
char line[128];
|
||||||
|
char small[4];
|
||||||
|
|
||||||
|
f = fopen(path, "w");
|
||||||
|
check(f != NULL, "text fopen w succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fputs("alpha\n", f) >= 0, "fputs line 1 returns non-negative");
|
||||||
|
check(fputs("beta gamma\n", f) >= 0, "fputs line 2 returns non-negative");
|
||||||
|
check(fputs("delta-no-newline", f) >= 0, "fputs final line without newline");
|
||||||
|
check(fclose(f) == 0, "fclose text writer returns 0");
|
||||||
|
|
||||||
|
f = fopen(path, "r");
|
||||||
|
check(f != NULL, "text fopen r succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fgets(line, (int)sizeof(line), f) == line, "fgets line 1 returns s");
|
||||||
|
check(str_eq(line, "alpha\n"), "fgets line 1 == \"alpha\\n\"");
|
||||||
|
check(fgets(line, (int)sizeof(line), f) == line, "fgets line 2 returns s");
|
||||||
|
check(str_eq(line, "beta gamma\n"), "fgets line 2 == \"beta gamma\\n\"");
|
||||||
|
check(fgets(line, (int)sizeof(line), f) == line, "fgets final line returns s");
|
||||||
|
check(str_eq(line, "delta-no-newline"), "fgets final line has no newline");
|
||||||
|
check(fgets(line, (int)sizeof(line), f) == NULL, "fgets at EOF returns NULL");
|
||||||
|
check(feof(f) != 0, "feof is set after the NULL fgets");
|
||||||
|
check(fclose(f) == 0, "fclose text reader returns 0");
|
||||||
|
|
||||||
|
f = fopen(path, "r");
|
||||||
|
check(fgets(small, (int)sizeof(small), f) == small, "fgets with n=4 returns s");
|
||||||
|
check(small[0] == 'a' && small[1] == 'l' && small[2] == 'p' && small[3] == '\0',
|
||||||
|
"fgets clamps to n-1 chars and NUL-terminates");
|
||||||
|
check(fclose(f) == 0, "fclose after the clamp check returns 0");
|
||||||
|
check(remove(path) == 0, "remove deletes the text file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. Seek and position queries. */
|
||||||
|
static void
|
||||||
|
seek_scenario(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-stdio-seek.bin";
|
||||||
|
FILE *f;
|
||||||
|
fpos_t saved;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
f = fopen(path, "w+");
|
||||||
|
check(f != NULL, "seek fopen w+ succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fputs("0123456789", f) >= 0, "seek setup writes digits");
|
||||||
|
check(fseeko(f, 3, SEEK_SET) == 0, "fseeko(3, SEEK_SET) returns 0");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == '3', "fgetc after SEEK_SET 3 == '3'");
|
||||||
|
check(ftello(f) == 4, "ftello after reading one char == 4");
|
||||||
|
check(fseek(f, 2, SEEK_CUR) == 0, "fseek(2, SEEK_CUR) returns 0");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == '6', "fgetc after SEEK_CUR 2 == '6'");
|
||||||
|
check(ftell(f) == 7, "ftell after the SEEK_CUR read == 7");
|
||||||
|
check(fseek(f, -2, SEEK_END) == 0, "fseek(-2, SEEK_END) returns 0");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == '8', "fgetc after SEEK_END -2 == '8'");
|
||||||
|
check(fgetpos(f, &saved) == 0, "fgetpos returns 0");
|
||||||
|
check((off_t)saved == 9, "fgetpos saved position == 9");
|
||||||
|
check(fseek(f, 0, SEEK_SET) == 0, "fseek(0, SEEK_SET) returns 0");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == '0', "fgetc at the start == '0'");
|
||||||
|
check(fsetpos(f, &saved) == 0, "fsetpos returns 0");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == '9', "fgetc after fsetpos == '9'");
|
||||||
|
rewind(f); // NOLINT: rewind has no error return; its effect is asserted below
|
||||||
|
check(ftello(f) == 0, "rewind positions at 0");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == '0', "fgetc after rewind == '0'");
|
||||||
|
check(fclose(f) == 0, "fclose seek stream returns 0");
|
||||||
|
check(remove(path) == 0, "remove deletes the seek file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. ungetc pushback. */
|
||||||
|
static void
|
||||||
|
ungetc_scenario(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-stdio-ungetc.txt";
|
||||||
|
FILE *f;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
f = fopen(path, "w+");
|
||||||
|
check(f != NULL, "ungetc fopen w+ succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fputs("hello", f) >= 0, "ungetc setup writes \"hello\"");
|
||||||
|
check(fseek(f, 0, SEEK_SET) == 0, "ungetc setup seeks back");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == 'h', "fgetc reads 'h'");
|
||||||
|
check(ungetc(c, f) == 'h', "ungetc('h') returns 'h'");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == 'h', "fgetc after ungetc reads 'h' again");
|
||||||
|
check(ungetc(EOF, f) == EOF, "ungetc(EOF) returns EOF");
|
||||||
|
check(ungetc('x', f) == 'x', "ungetc('x') returns 'x'");
|
||||||
|
check(ungetc('y', f) == EOF, "second pushback without a read fails");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == 'x', "the first pushed-back byte is still readable");
|
||||||
|
check(ungetc('z', f) == 'z', "ungetc('z') returns 'z'");
|
||||||
|
check(fseek(f, 0, SEEK_SET) == 0, "fseek after ungetc returns 0");
|
||||||
|
c = fgetc(f);
|
||||||
|
check(c == 'h', "fseek discards the pushed-back byte");
|
||||||
|
check(fclose(f) == 0, "fclose ungetc stream returns 0");
|
||||||
|
check(remove(path) == 0, "remove deletes the ungetc file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 5. fflush mid-stream and fflush(NULL). */
|
||||||
|
static void
|
||||||
|
flush_scenario(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-stdio-flush.txt";
|
||||||
|
const char outpath[] = "/tmp/vlibc-test-stdio-stdout.txt";
|
||||||
|
char buf[64];
|
||||||
|
FILE *f;
|
||||||
|
long got;
|
||||||
|
long saved_fd;
|
||||||
|
int r1;
|
||||||
|
int r2;
|
||||||
|
int r3;
|
||||||
|
int r4;
|
||||||
|
|
||||||
|
f = fopen(path, "w");
|
||||||
|
check(f != NULL, "flush fopen w succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fwrite("abc", 1, 3, f) == 3, "fwrite the first half");
|
||||||
|
check(fflush(f) == 0, "fflush mid-stream returns 0");
|
||||||
|
check(fwrite("def", 1, 3, f) == 3, "fwrite the second half");
|
||||||
|
|
||||||
|
saved_fd = __syscall1(SYS_dup, 1);
|
||||||
|
check(saved_fd >= 0, "dup(1) saves the stdout descriptor");
|
||||||
|
/* No check() output while fd 1 is the outpath file: the PASS lines
|
||||||
|
* would pollute the very file content the checks assert below. */
|
||||||
|
r1 = (freopen(outpath, "w", stdout) == stdout);
|
||||||
|
r2 = (fputs("STDOUT-MARKER", stdout) >= 0);
|
||||||
|
r3 = (fflush(NULL) == 0);
|
||||||
|
r4 = (fclose(f) == 0);
|
||||||
|
__syscall2(SYS_dup2, saved_fd, 1);
|
||||||
|
__syscall1(SYS_close, saved_fd);
|
||||||
|
check(r1, "freopen stdout to a file");
|
||||||
|
check(r2, "fputs to the freopen'd stdout");
|
||||||
|
check(r3, "fflush(NULL) returns 0");
|
||||||
|
check(r4, "fclose after fflush(NULL) returns 0");
|
||||||
|
|
||||||
|
got = raw_read_all(path, buf, sizeof(buf));
|
||||||
|
check(got == 6 && mem_eq((const unsigned char *)buf, (const unsigned char *)"abcdef", 6),
|
||||||
|
"fflush(NULL) left the whole file content");
|
||||||
|
got = raw_read_all(outpath, buf, sizeof(buf));
|
||||||
|
check(got == 13 &&
|
||||||
|
mem_eq((const unsigned char *)buf, (const unsigned char *)"STDOUT-MARKER", 13),
|
||||||
|
"fflush(NULL) flushed the freopen'd stdout");
|
||||||
|
|
||||||
|
check(remove(path) == 0, "remove deletes the flush file");
|
||||||
|
check(remove(outpath) == 0, "remove deletes the stdout file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 6. Buffering modes. */
|
||||||
|
static void
|
||||||
|
setvbuf_scenario(void)
|
||||||
|
{
|
||||||
|
const char path1[] = "/tmp/vlibc-test-stdio-setvbuf1.txt";
|
||||||
|
const char path2[] = "/tmp/vlibc-test-stdio-setvbuf2.txt";
|
||||||
|
const char path3[] = "/tmp/vlibc-test-stdio-setvbuf3.txt";
|
||||||
|
char buf[64];
|
||||||
|
static char userbuf[512];
|
||||||
|
FILE *f;
|
||||||
|
unsigned long saved;
|
||||||
|
long got;
|
||||||
|
|
||||||
|
f = fopen(path1, "w");
|
||||||
|
check(f != NULL, "setvbuf fopen w succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(setvbuf(f, NULL, _IONBF, 0) == 0, "setvbuf _IONBF NULL returns 0");
|
||||||
|
check(fputs("AB", f) >= 0, "fputs through the unbuffered stream");
|
||||||
|
got = raw_read_all(path1, buf, sizeof(buf));
|
||||||
|
check(got == 2 && buf[0] == 'A' && buf[1] == 'B',
|
||||||
|
"_IONBF data reaches the file without fflush");
|
||||||
|
check(setvbuf(f, NULL, _IOFBF, BUFSIZ) == 0, "setvbuf _IOFBF NULL returns 0");
|
||||||
|
check(fputs("CD", f) >= 0, "fputs through the fully buffered stream");
|
||||||
|
check(fflush(f) == 0, "fflush the fully buffered stream");
|
||||||
|
check(setvbuf(f, userbuf, _IOFBF, sizeof(userbuf)) == 0,
|
||||||
|
"setvbuf with a user buffer returns 0");
|
||||||
|
check(fputs("EF", f) >= 0, "fputs through the user buffer");
|
||||||
|
check(fflush(f) == 0, "fflush the user buffer");
|
||||||
|
got = raw_read_all(path1, buf, sizeof(buf));
|
||||||
|
check(got == 6 && mem_eq((const unsigned char *)buf, (const unsigned char *)"ABCDEF", 6),
|
||||||
|
"all three modes wrote sequential content");
|
||||||
|
check(fclose(f) == 0, "fclose setvbuf stream returns 0");
|
||||||
|
check(remove(path1) == 0, "remove deletes the setvbuf file");
|
||||||
|
|
||||||
|
f = fopen(path2, "w");
|
||||||
|
check(setvbuf(f, NULL, _IOLBF, 0) == 0, "setvbuf _IOLBF NULL returns 0");
|
||||||
|
check(fputs("line-one\n", f) >= 0, "fputs a newline-terminated line");
|
||||||
|
got = raw_read_all(path2, buf, sizeof(buf));
|
||||||
|
check(got == 9 && mem_eq((const unsigned char *)buf, (const unsigned char *)"line-one\n", 9),
|
||||||
|
"_IOLBF flushes on the newline without fflush");
|
||||||
|
check(fputs("line-two\n", f) >= 0, "fputs a second line");
|
||||||
|
check(fclose(f) == 0, "fclose line-buffered stream returns 0");
|
||||||
|
got = raw_read_all(path2, buf, sizeof(buf));
|
||||||
|
check(got == 18, "fclose flushed the second line");
|
||||||
|
check(remove(path2) == 0, "remove deletes the line-buffered file");
|
||||||
|
|
||||||
|
f = fopen(path3, "w");
|
||||||
|
setbuf(f, NULL); // NOLINT: setbuf has no error return; its effect is asserted below
|
||||||
|
check(fputs("SB", f) >= 0, "fputs through setbuf(NULL) stream");
|
||||||
|
got = raw_read_all(path3, buf, sizeof(buf));
|
||||||
|
check(got == 2 && buf[0] == 'S' && buf[1] == 'B',
|
||||||
|
"setbuf(NULL) data reaches the file without fflush");
|
||||||
|
check(fclose(f) == 0, "fclose setbuf stream returns 0");
|
||||||
|
check(remove(path3) == 0, "remove deletes the setbuf file");
|
||||||
|
|
||||||
|
saved = tcb_slot1_save();
|
||||||
|
check(setvbuf(f, NULL, 99, 0) == -1, "setvbuf with an invalid mode returns -1");
|
||||||
|
tcb_slot1_restore(saved);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 7. fdopen and fileno. */
|
||||||
|
static void
|
||||||
|
fdopen_scenario(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-stdio-fdopen.txt";
|
||||||
|
char line[64];
|
||||||
|
FILE *g;
|
||||||
|
long fd;
|
||||||
|
long rc;
|
||||||
|
|
||||||
|
fd = __syscall4(SYS_openat, -100, (long)path, (long)(0x2 | 0x40 | 0x200 /* RDWR|CREAT|TRUNC */),
|
||||||
|
0666);
|
||||||
|
check(fd >= 0, "raw openat for fdopen succeeds");
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
g = fdopen((int)fd, "w+");
|
||||||
|
check(g != NULL, "fdopen wraps the descriptor");
|
||||||
|
if (g == NULL)
|
||||||
|
{
|
||||||
|
__syscall1(SYS_close, fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fileno(g) == (int)fd, "fileno returns the wrapped descriptor");
|
||||||
|
check(fputs("via-fdopen", g) >= 0, "fputs through the fdopen'd stream");
|
||||||
|
rewind(g); // NOLINT: rewind has no error return; its effect is asserted below
|
||||||
|
check(fgets(line, (int)sizeof(line), g) == line, "fgets after rewind returns s");
|
||||||
|
check(str_eq(line, "via-fdopen"), "fgets reads back the fdopen'd content");
|
||||||
|
check(fclose(g) == 0, "fclose fdopen stream returns 0");
|
||||||
|
rc = __syscall3(SYS_fcntl, fd, 3 /* F_GETFL */, 0);
|
||||||
|
check(rc < 0, "fclose closed the underlying descriptor");
|
||||||
|
check(remove(path) == 0, "remove deletes the fdopen file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 8. freopen. */
|
||||||
|
static void
|
||||||
|
freopen_scenario(void)
|
||||||
|
{
|
||||||
|
const char path1[] = "/tmp/vlibc-test-stdio-freopen1.txt";
|
||||||
|
const char path2[] = "/tmp/vlibc-test-stdio-freopen2.txt";
|
||||||
|
char line[64];
|
||||||
|
FILE *f;
|
||||||
|
|
||||||
|
f = fopen(path1, "w");
|
||||||
|
check(f != NULL, "freopen setup fopen w succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fputs("one", f) >= 0, "freopen setup writes the first file");
|
||||||
|
check(fclose(f) == 0, "fclose the first file");
|
||||||
|
f = fopen(path1, "r");
|
||||||
|
check(f != NULL, "freopen setup fopen r succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(freopen(NULL, "r", f) == f, "freopen with NULL path changes the mode only");
|
||||||
|
check(fgets(line, (int)sizeof(line), f) == line, "fgets on the mode-changed stream");
|
||||||
|
check(str_eq(line, "one"), "the mode change kept the descriptor");
|
||||||
|
check(freopen(path2, "w", f) == f, "freopen rebinds to the second path");
|
||||||
|
check(fputs("two", f) >= 0, "fputs through the rebound stream");
|
||||||
|
check(fclose(f) == 0, "fclose the rebound stream");
|
||||||
|
check(fopen(path2, "r") != NULL, "the second path exists");
|
||||||
|
check(remove(path1) == 0, "remove deletes the first freopen file");
|
||||||
|
check(remove(path2) == 0, "remove deletes the second freopen file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 9. remove/rename and tmpfile. */
|
||||||
|
static void
|
||||||
|
name_scenario(void)
|
||||||
|
{
|
||||||
|
const char path1[] = "/tmp/vlibc-test-stdio-name1.txt";
|
||||||
|
const char path2[] = "/tmp/vlibc-test-stdio-name2.txt";
|
||||||
|
char line[64];
|
||||||
|
FILE *f;
|
||||||
|
FILE *t;
|
||||||
|
|
||||||
|
f = fopen(path1, "w");
|
||||||
|
check(f != NULL, "name fopen w succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fclose(f) == 0, "fclose the name file");
|
||||||
|
check(rename(path1, path2) == 0, "rename returns 0");
|
||||||
|
check(fopen(path1, "r") == NULL, "the old name is gone after rename");
|
||||||
|
check(fopen(path2, "r") != NULL, "the new name exists after rename");
|
||||||
|
check(remove(path2) == 0, "remove returns 0");
|
||||||
|
check(fopen(path2, "r") == NULL, "the removed file is gone");
|
||||||
|
|
||||||
|
t = tmpfile();
|
||||||
|
check(t != NULL, "tmpfile returns a stream");
|
||||||
|
if (t != NULL)
|
||||||
|
{
|
||||||
|
check(fputs("temp-data", t) >= 0, "fputs through the tmpfile stream");
|
||||||
|
rewind(t); // NOLINT: rewind has no error return; its effect is asserted below
|
||||||
|
check(fgets(line, (int)sizeof(line), t) == line, "fgets after rewind returns s");
|
||||||
|
check(str_eq(line, "temp-data"), "tmpfile content round-trips");
|
||||||
|
check(fclose(t) == 0, "fclose tmpfile returns 0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 11. Read -> write mode switch keeps the logical position. */
|
||||||
|
static void
|
||||||
|
modeswitch_scenario(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-stdio-modeswitch.bin";
|
||||||
|
char got[16];
|
||||||
|
FILE *f;
|
||||||
|
unsigned long r;
|
||||||
|
|
||||||
|
f = fopen(path, "w+b");
|
||||||
|
check(f != NULL, "modeswitch fopen w+b succeeds");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fwrite("abcdefgh", 1, 8, f) == 8, "modeswitch writes the 8-byte seed");
|
||||||
|
check(fflush(f) == 0, "modeswitch fflush returns 0");
|
||||||
|
check(fseek(f, 0, SEEK_SET) == 0, "modeswitch seeks back to the start");
|
||||||
|
check(fgetc(f) == 'a', "modeswitch reads 'a'");
|
||||||
|
check(fgetc(f) == 'b', "modeswitch reads 'b' (6 bytes left buffered)");
|
||||||
|
check(ftello(f) == 2, "modeswitch ftello == 2 after the two reads");
|
||||||
|
check(fputc('X', f) == 'X', "modeswitch fputc('X') switches back to writing");
|
||||||
|
check(ftello(f) == 3, "modeswitch ftello == 3 after the overwrite");
|
||||||
|
check(fseeko(f, 0, SEEK_CUR) == 0, "modeswitch fseeko(0, SEEK_CUR) returns 0");
|
||||||
|
check(ftello(f) == 3, "modeswitch ftello stays 3 after the no-op seek");
|
||||||
|
check(fclose(f) == 0, "modeswitch fclose returns 0");
|
||||||
|
|
||||||
|
f = fopen(path, "rb");
|
||||||
|
check(f != NULL, "modeswitch reopen rb succeeds");
|
||||||
|
if (f != NULL)
|
||||||
|
{
|
||||||
|
r = fread(got, 1, 8, f);
|
||||||
|
check(r == 8 && mem_eq((const unsigned char *)got, (const unsigned char *)"abXdefgh", 8),
|
||||||
|
"modeswitch: the overwrite landed at offset 2 (\"abXdefgh\")");
|
||||||
|
check(fclose(f) == 0, "modeswitch fclose after the verify returns 0");
|
||||||
|
}
|
||||||
|
check(remove(path) == 0, "remove deletes the modeswitch file");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 12. fclose releases the owned buffer (no leak across many cycles). */
|
||||||
|
static void
|
||||||
|
fclose_leak_scenario(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-stdio-leak.txt";
|
||||||
|
size_t before;
|
||||||
|
size_t after;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
before = __vlibc_malloc_check();
|
||||||
|
check(before != (size_t)-1, "allocator heap walk is consistent before the cycles");
|
||||||
|
if (before == (size_t)-1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (i = 0; i < 200; i++)
|
||||||
|
{
|
||||||
|
FILE *f = fopen(path, "w");
|
||||||
|
|
||||||
|
if (f == NULL || fputc('x', f) == EOF || fclose(f) != 0)
|
||||||
|
{
|
||||||
|
(void)fclose(f); /* safe when f == NULL */
|
||||||
|
say(2, "FAIL: leak-cycle fopen/fputc/fclose\n");
|
||||||
|
failures++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
after = __vlibc_malloc_check();
|
||||||
|
check(after == before,
|
||||||
|
"fclose frees the FILE struct and its owned buffer (no leak over 200 cycles)");
|
||||||
|
check(remove(path) == 0, "remove deletes the leak-cycle file");
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/* 10. Level 2 additions. */
|
||||||
|
static void
|
||||||
|
level2_scenario(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-stdio-l2.txt";
|
||||||
|
char tname[L_tmpnam];
|
||||||
|
char buf[64];
|
||||||
|
char *p;
|
||||||
|
FILE *f;
|
||||||
|
static char userbuf[128];
|
||||||
|
long got;
|
||||||
|
|
||||||
|
p = tmpnam(tname);
|
||||||
|
check(p == tname, "tmpnam returns its argument");
|
||||||
|
check(prefix_eq(tname, "/tmp/"), "tmpnam produces a /tmp name");
|
||||||
|
check(tmpnam(NULL) != NULL, "tmpnam with NULL uses a static buffer");
|
||||||
|
p = ctermid(NULL);
|
||||||
|
check(p != NULL && str_eq(p, "/dev/tty"), "ctermid returns \"/dev/tty\"");
|
||||||
|
|
||||||
|
f = fopen64(path, "w");
|
||||||
|
check(f != NULL, "fopen64 opens a stream");
|
||||||
|
if (f != NULL)
|
||||||
|
{
|
||||||
|
check(fputs("l2", f) >= 0, "fputs through the fopen64 stream");
|
||||||
|
check(fclose(f) == 0, "fclose the fopen64 stream");
|
||||||
|
got = raw_read_all(path, buf, sizeof(buf));
|
||||||
|
check(got == 2 && buf[0] == 'l' && buf[1] == '2', "fopen64 wrote the content");
|
||||||
|
check(remove(path) == 0, "remove deletes the fopen64 file");
|
||||||
|
}
|
||||||
|
|
||||||
|
f = fopen(path, "w");
|
||||||
|
setbuffer(f, userbuf, sizeof(userbuf));
|
||||||
|
check(fputs("setbuffer", f) >= 0, "fputs through the setbuffer stream");
|
||||||
|
check(fflush(f) == 0, "fflush the setbuffer stream");
|
||||||
|
setlinebuf(f);
|
||||||
|
check(fputs("line\n", f) >= 0, "fputs a line through the setlinebuf stream");
|
||||||
|
got = raw_read_all(path, buf, sizeof(buf));
|
||||||
|
check(got == 14 &&
|
||||||
|
mem_eq((const unsigned char *)buf, (const unsigned char *)"setbufferline\n", 14),
|
||||||
|
"setlinebuf flushed on the newline");
|
||||||
|
check(fclose(f) == 0, "fclose the setlinebuf stream");
|
||||||
|
check(remove(path) == 0, "remove deletes the setlinebuf file");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
/* Failure scenarios (-f). */
|
||||||
|
static int
|
||||||
|
failure_scenarios(void)
|
||||||
|
{
|
||||||
|
const char path[] = "/tmp/vlibc-test-stdio-fail.bin";
|
||||||
|
unsigned char small[8];
|
||||||
|
FILE *f;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
f = fopen(path, "w+b");
|
||||||
|
if (f == NULL)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: -f setup fopen failed\n");
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
check(fwrite("abc", 1, 3, f) == 3, "-f setup fwrite");
|
||||||
|
check(fseek(f, 100, SEEK_SET) == 0, "fseek past EOF succeeds");
|
||||||
|
check(ftello(f) == 100, "ftello after fseek past EOF == 100");
|
||||||
|
rc = (int)fread(small, 1, sizeof(small), f);
|
||||||
|
check(rc == 0, "fread past EOF returns 0 items");
|
||||||
|
check(feof(f) != 0, "feof is set after the past-EOF read");
|
||||||
|
check(ferror(f) == 0, "ferror stays clear: it was EOF, not an error");
|
||||||
|
check(fclose(f) == 0, "fclose the -f stream");
|
||||||
|
check(remove(path) == 0, "remove the -f file");
|
||||||
|
}
|
||||||
|
check(fopen("/tmp/vlibc-no-such-file-000", "r") == NULL,
|
||||||
|
"fopen of a nonexistent path returns NULL");
|
||||||
|
check(fdopen(-1, "r") == NULL, "fdopen(-1) returns NULL");
|
||||||
|
f = fopen(path, "r");
|
||||||
|
if (f != NULL)
|
||||||
|
{
|
||||||
|
check(fputc('x', f) == EOF, "fputc on a read-only stream returns EOF");
|
||||||
|
check(ferror(f) != 0, "ferror is set after the invalid fputc");
|
||||||
|
check(fclose(f) == 0, "fclose the read-only stream");
|
||||||
|
}
|
||||||
|
check(remove("/tmp/vlibc-no-such-file-000") == -1, "remove of a missing path returns -1");
|
||||||
|
return failures > 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
// NOLINTEND(clang-analyzer-unix.Stream, clang-analyzer-core.UndefinedBinaryOperatorResult,
|
||||||
|
// clang-analyzer-unix.StdCLibraryFunctions)
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* The failure scenarios make the library write errno several
|
||||||
|
* times; leave via the raw syscall so the host cleanup never
|
||||||
|
* runs after those writes (see the banner).
|
||||||
|
*/
|
||||||
|
int rc = failure_scenarios();
|
||||||
|
|
||||||
|
__syscall1(SYS_exit_group, rc);
|
||||||
|
return rc; /* not reached */
|
||||||
|
}
|
||||||
|
|
||||||
|
roundtrip_scenario();
|
||||||
|
text_scenario();
|
||||||
|
seek_scenario();
|
||||||
|
ungetc_scenario();
|
||||||
|
flush_scenario();
|
||||||
|
setvbuf_scenario();
|
||||||
|
fdopen_scenario();
|
||||||
|
freopen_scenario();
|
||||||
|
name_scenario();
|
||||||
|
modeswitch_scenario();
|
||||||
|
fclose_leak_scenario();
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
level2_scenario();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (failures > 0)
|
||||||
|
{
|
||||||
|
say(2, "FAILED (");
|
||||||
|
say_dec(2, (unsigned long)failures);
|
||||||
|
say(2, " check(s))\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
say(1, "all stdio tests passed\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,445 @@
|
|||||||
|
/*
|
||||||
|
* vlibc — unistd file I/O test (todo 19).
|
||||||
|
*
|
||||||
|
* Exercises the raw syscall wrappers end to end:
|
||||||
|
*
|
||||||
|
* 1. open(O_RDWR|O_CREAT|O_EXCL, 0600) + 4 KiB write + seek/read
|
||||||
|
* round-trip, then unlink via raw SYS_unlinkat.
|
||||||
|
* 2. pread/pwrite positional I/O: pread(fd, buf, 4, 0) reads the first
|
||||||
|
* four bytes after the file position advanced to the end, and the
|
||||||
|
* position is untouched by positional calls.
|
||||||
|
* 3. lseek SEEK_SET/CUR/END offsets, including lseek(SEEK_END) after
|
||||||
|
* ftruncate to 100.
|
||||||
|
* 4. pipe + write + read transports bytes.
|
||||||
|
* 5. dup2 duplicates a descriptor (a write through the dup is visible
|
||||||
|
* through the original); level-2 dup3 rejects same-fd with -1 and
|
||||||
|
* duplicates with O_CLOEXEC.
|
||||||
|
* 6. fsync/fdatasync on the temp file return 0.
|
||||||
|
* 7. sync() completes.
|
||||||
|
* 8. access/faccessat on the existing file return 0; on a nonexistent
|
||||||
|
* path return -1.
|
||||||
|
* 9. open flag validation: O_RDONLY on the existing file, O_CREAT|O_EXCL
|
||||||
|
* on the existing file → -1, O_TRUNC truncates.
|
||||||
|
*
|
||||||
|
* Level-2 gated section: dup3, pipe2, truncate, lseek64.
|
||||||
|
*
|
||||||
|
* The negative paths make the LIBRARY write errno (syscall_ret), which
|
||||||
|
* under a host-linked binary targets glibc's private dtv slot at %fs:0+8.
|
||||||
|
* In the default mode each such call is bracketed with a save/restore of
|
||||||
|
* that slot (task 13 technique) — only vlibc/raw-syscall code runs between
|
||||||
|
* the write and the restore, so host state is intact when host code runs
|
||||||
|
* again. The test itself NEVER reads errno; every negative is asserted on
|
||||||
|
* the return value. The -f mode runs the failure scenarios and exits via
|
||||||
|
* raw SYS_exit_group (house pattern, tests/test_malloc.c).
|
||||||
|
*
|
||||||
|
* 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/unistd.h"
|
||||||
|
|
||||||
|
#include "../src/internal/syscall.h"
|
||||||
|
|
||||||
|
/* Kernel-UAPI open flags, local to this test (include/fcntl.h is todo 21). */
|
||||||
|
#define T19_O_RDONLY 0x0
|
||||||
|
#define T19_O_WRONLY 0x1
|
||||||
|
#define T19_O_RDWR 0x2
|
||||||
|
#define T19_O_CREAT 0x40
|
||||||
|
#define T19_O_EXCL 0x80
|
||||||
|
#define T19_O_TRUNC 0x200
|
||||||
|
#define T19_O_CLOEXEC 0x80000
|
||||||
|
#define T19_AT_FDCWD (-100)
|
||||||
|
|
||||||
|
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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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).
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1+2+3: file round-trip, positional I/O, seeks, on one temp file. */
|
||||||
|
static int
|
||||||
|
file_scenarios(const char *path)
|
||||||
|
{
|
||||||
|
static unsigned char pattern[4096];
|
||||||
|
unsigned char rbuf[4096];
|
||||||
|
unsigned char small[4];
|
||||||
|
unsigned i;
|
||||||
|
int ok = 1;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
for (i = 0; i < sizeof(pattern); i++)
|
||||||
|
{
|
||||||
|
pattern[i] = (unsigned char)(i * 7 + 1);
|
||||||
|
}
|
||||||
|
fd = open(path, T19_O_RDWR | T19_O_CREAT | T19_O_EXCL, 0600);
|
||||||
|
check(fd >= 0, "open O_RDWR|O_CREAT|O_EXCL 0600 returns a descriptor");
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
check(write(fd, pattern, sizeof(pattern)) == (ssize_t)sizeof(pattern),
|
||||||
|
"write of 4096 bytes returns 4096");
|
||||||
|
check(lseek(fd, 0, SEEK_SET) == 0, "lseek SEEK_SET 0 returns 0");
|
||||||
|
check(read(fd, rbuf, sizeof(rbuf)) == (ssize_t)sizeof(rbuf), "read of 4096 bytes returns 4096");
|
||||||
|
for (i = 0; i < sizeof(pattern); i++)
|
||||||
|
{
|
||||||
|
if (rbuf[i] != pattern[i])
|
||||||
|
{
|
||||||
|
ok = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check(ok, "read-back matches the written 4096 bytes");
|
||||||
|
|
||||||
|
/* The file position is now at EOF (4096). */
|
||||||
|
check(pread(fd, small, 4, 0) == 4, "pread(fd, buf, 4, 0) returns 4");
|
||||||
|
check(small[0] == pattern[0] && small[1] == pattern[1] && small[2] == pattern[2] &&
|
||||||
|
small[3] == pattern[3],
|
||||||
|
"pread reads the first four bytes");
|
||||||
|
check(lseek(fd, 0, SEEK_CUR) == 4096, "pread leaves the position at 4096");
|
||||||
|
|
||||||
|
small[0] = 'W';
|
||||||
|
small[1] = 'X';
|
||||||
|
small[2] = 'Y';
|
||||||
|
small[3] = 'Z';
|
||||||
|
check(pwrite(fd, small, 4, 100) == 4, "pwrite(fd, buf, 4, 100) returns 4");
|
||||||
|
small[0] = 0;
|
||||||
|
small[1] = 0;
|
||||||
|
small[2] = 0;
|
||||||
|
small[3] = 0;
|
||||||
|
check(pread(fd, small, 4, 100) == 4, "pread(fd, buf, 4, 100) returns 4");
|
||||||
|
check(small[0] == 'W' && small[1] == 'X' && small[2] == 'Y' && small[3] == 'Z',
|
||||||
|
"pwrite/pread round-trip at offset 100");
|
||||||
|
check(lseek(fd, 0, SEEK_CUR) == 4096, "position still 4096 after positional I/O");
|
||||||
|
|
||||||
|
/* Scenario 3: ftruncate + seek offsets. */
|
||||||
|
check(ftruncate(fd, 100) == 0, "ftruncate(fd, 100) returns 0");
|
||||||
|
check(lseek(fd, 0, SEEK_END) == 100, "lseek SEEK_END after ftruncate returns 100");
|
||||||
|
check(lseek(fd, 10, SEEK_SET) == 10, "lseek SEEK_SET 10 returns 10");
|
||||||
|
check(lseek(fd, 5, SEEK_CUR) == 15, "lseek SEEK_CUR +5 from 10 returns 15");
|
||||||
|
check(lseek(fd, -7, SEEK_CUR) == 8, "lseek SEEK_CUR -7 from 15 returns 8");
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. pipe + write + read transports bytes. */
|
||||||
|
static void
|
||||||
|
pipe_scenario(void)
|
||||||
|
{
|
||||||
|
int fds[2];
|
||||||
|
char buf[8];
|
||||||
|
long n = 0;
|
||||||
|
|
||||||
|
check(pipe(fds) == 0, "pipe returns 0");
|
||||||
|
check(write(fds[1], "hello", 5) == 5, "write to the pipe write end returns 5");
|
||||||
|
check(read(fds[0], buf, sizeof(buf)) == 5, "read from the pipe read end returns 5");
|
||||||
|
while (n < 5 && buf[n] == "hello"[n])
|
||||||
|
{
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
check(n == 5, "pipe transports the five bytes intact");
|
||||||
|
check(close(fds[0]) == 0 && close(fds[1]) == 0, "close of both pipe ends returns 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 5. dup2 duplicates a descriptor sharing the file description. */
|
||||||
|
static void
|
||||||
|
dup_scenarios(int fd)
|
||||||
|
{
|
||||||
|
unsigned char b[1];
|
||||||
|
int d;
|
||||||
|
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
d = dup2(fd, 200);
|
||||||
|
check(d == 200, "dup2(fd, 200) returns 200");
|
||||||
|
if (d != 200)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(lseek(200, 0, SEEK_SET) == 0, "the dup shares the file position");
|
||||||
|
check(write(200, "Z", 1) == 1, "write through the dup returns 1");
|
||||||
|
check(pread(fd, b, 1, 0) == 1 && b[0] == 'Z', "write through dup visible on original");
|
||||||
|
check(dup2(200, 200) == 200, "dup2(x, x) returns x (no-op)");
|
||||||
|
check(close(200) == 0, "close(200) returns 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 6+7. fsync/fdatasync/sync on the temp file. */
|
||||||
|
static void
|
||||||
|
sync_scenarios(int fd)
|
||||||
|
{
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
check(fsync(fd) == 0, "fsync on the temp file returns 0");
|
||||||
|
check(fdatasync(fd) == 0, "fdatasync on the temp file returns 0");
|
||||||
|
sync();
|
||||||
|
check(1, "sync() completes");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 8. access/faccessat happy + negative (bracketed). */
|
||||||
|
static void
|
||||||
|
access_scenarios(const char *path)
|
||||||
|
{
|
||||||
|
unsigned long saved;
|
||||||
|
|
||||||
|
check(access(path, F_OK) == 0, "access on the existing file with F_OK returns 0");
|
||||||
|
check(access(path, R_OK) == 0, "access on the existing file with R_OK returns 0");
|
||||||
|
check(access(path, W_OK) == 0, "access on the existing file with W_OK returns 0");
|
||||||
|
check(faccessat(T19_AT_FDCWD, path, F_OK, 0) == 0,
|
||||||
|
"faccessat(AT_FDCWD, path, F_OK, 0) returns 0");
|
||||||
|
saved = tcb_slot1();
|
||||||
|
check(access("/nonexistent-vlibc-t19", F_OK) == -1, "access on a nonexistent path returns -1");
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
saved = tcb_slot1();
|
||||||
|
check(faccessat(T19_AT_FDCWD, "/nonexistent-vlibc-t19", F_OK, 0) == -1,
|
||||||
|
"faccessat on a nonexistent path returns -1");
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 9. open flag validation (O_TRUNC runs last: it resets the size). */
|
||||||
|
static void
|
||||||
|
open_flag_scenarios(const char *path)
|
||||||
|
{
|
||||||
|
unsigned long saved;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
fd = open(path, T19_O_RDONLY);
|
||||||
|
check(fd >= 0, "open with O_RDONLY on the existing file succeeds");
|
||||||
|
if (fd >= 0)
|
||||||
|
{
|
||||||
|
check(close(fd) == 0, "close of the O_RDONLY descriptor returns 0");
|
||||||
|
}
|
||||||
|
saved = tcb_slot1();
|
||||||
|
check(open(path, T19_O_CREAT | T19_O_EXCL, 0600) == -1,
|
||||||
|
"open O_CREAT|O_EXCL on the existing file returns -1");
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
fd = open(path, T19_O_WRONLY | T19_O_TRUNC);
|
||||||
|
check(fd >= 0, "open with O_WRONLY|O_TRUNC succeeds");
|
||||||
|
if (fd >= 0)
|
||||||
|
{
|
||||||
|
check(lseek(fd, 0, SEEK_END) == 0, "O_TRUNC leaves the size at 0");
|
||||||
|
check(close(fd) == 0, "close of the O_TRUNC descriptor returns 0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/* Level-2 gate proof: dup3, pipe2, truncate, lseek64. */
|
||||||
|
static void
|
||||||
|
level2_scenarios(int fd)
|
||||||
|
{
|
||||||
|
const char *path2 = "/tmp/vlibc-t19-l2";
|
||||||
|
unsigned long saved;
|
||||||
|
char buf[8];
|
||||||
|
int fds[2];
|
||||||
|
long n = 0;
|
||||||
|
int d;
|
||||||
|
int fd2;
|
||||||
|
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
saved = tcb_slot1();
|
||||||
|
check(dup3(fd, fd, 0) == -1, "dup3(fd, fd, 0) returns -1 (same-fd)");
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
d = dup3(fd, 201, T19_O_CLOEXEC);
|
||||||
|
check(d == 201, "dup3(fd, 201, O_CLOEXEC) returns 201");
|
||||||
|
if (d == 201)
|
||||||
|
{
|
||||||
|
check(close(201) == 0, "close of the dup3 descriptor returns 0");
|
||||||
|
}
|
||||||
|
check(pipe2(fds, T19_O_CLOEXEC) == 0, "pipe2 with O_CLOEXEC returns 0");
|
||||||
|
check(write(fds[1], "hi", 2) == 2, "write to the pipe2 write end returns 2");
|
||||||
|
check(read(fds[0], buf, sizeof(buf)) == 2, "read from the pipe2 read end returns 2");
|
||||||
|
while (n < 2 && buf[n] == "hi"[n])
|
||||||
|
{
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
check(n == 2, "pipe2 transports the two bytes intact");
|
||||||
|
check(close(fds[0]) == 0 && close(fds[1]) == 0, "close of both pipe2 ends returns 0");
|
||||||
|
|
||||||
|
fd2 = open(path2, T19_O_RDWR | T19_O_CREAT | T19_O_EXCL, 0600);
|
||||||
|
check(fd2 >= 0, "open creates the second temp file for truncate");
|
||||||
|
if (fd2 >= 0)
|
||||||
|
{
|
||||||
|
check(write(fd2, "abcdef", 6) == 6, "write six bytes to the second file");
|
||||||
|
check(truncate(path2, 42) == 0, "truncate(path, 42) returns 0");
|
||||||
|
check(lseek64(fd2, 0, SEEK_END) == 42, "lseek64 SEEK_END after truncate returns 42");
|
||||||
|
check(lseek64(fd2, 0, SEEK_SET) == 0, "lseek64 SEEK_SET 0 returns 0");
|
||||||
|
check(close(fd2) == 0, "close of the second temp file returns 0");
|
||||||
|
}
|
||||||
|
check(__syscall3(SYS_unlinkat, T19_AT_FDCWD, (long)path2, 0) == 0,
|
||||||
|
"unlink of the second temp file returns 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 the library writes
|
||||||
|
* errno on these paths (host-TCB hazard).
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
failure_scenarios(void)
|
||||||
|
{
|
||||||
|
const char *path = "/tmp/vlibc-t19-f";
|
||||||
|
int rc = 0;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
if (open("/nonexistent/vlibc/t19", T19_O_RDONLY) != -1)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: open on a nonexistent path did not return -1\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: open on a nonexistent path -> -1\n");
|
||||||
|
}
|
||||||
|
fd = open(path, T19_O_RDWR | T19_O_CREAT | T19_O_TRUNC, 0600);
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: -f setup open failed\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (close(fd) != 0)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: -f setup close failed\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
if (write(fd, "x", 1) != -1)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: write on a closed fd did not return -1\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: write on a closed fd -> -1\n");
|
||||||
|
}
|
||||||
|
if (lseek(fd, 0, SEEK_SET) != -1)
|
||||||
|
{
|
||||||
|
say(2, "FAIL: lseek on a closed fd did not return -1\n");
|
||||||
|
rc = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "PASS: lseek on a closed fd -> -1\n");
|
||||||
|
}
|
||||||
|
__syscall3(SYS_unlinkat, T19_AT_FDCWD, (long)path, 0);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
const char *path = "/tmp/vlibc-t19-XXXX";
|
||||||
|
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 = file_scenarios(path);
|
||||||
|
pipe_scenario();
|
||||||
|
dup_scenarios(fd);
|
||||||
|
sync_scenarios(fd);
|
||||||
|
access_scenarios(path);
|
||||||
|
open_flag_scenarios(path);
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
level2_scenarios(fd);
|
||||||
|
#endif
|
||||||
|
if (fd >= 0)
|
||||||
|
{
|
||||||
|
check(close(fd) == 0, "close of the main temp file returns 0");
|
||||||
|
}
|
||||||
|
check(__syscall3(SYS_unlinkat, T19_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 unistd file I/O tests passed\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,380 @@
|
|||||||
|
/*
|
||||||
|
* vlibc — wait family test (todo 23).
|
||||||
|
*
|
||||||
|
* Exercises wait/waitpid/waitid (+ L2 wait3/wait4) end to end against real
|
||||||
|
* children. Children are created with the RAW SYS_clone(SIGCHLD) syscall and
|
||||||
|
* exit with raw SYS_exit — deliberately NOT the fork() wrapper of the
|
||||||
|
* parallel todo 20, so this test stays independent of it.
|
||||||
|
*
|
||||||
|
* Default-mode scenarios (in run order):
|
||||||
|
*
|
||||||
|
* 1. waitpid(-1, &st, WNOHANG) with no children yet -> -1 (ECHILD).
|
||||||
|
* Runs FIRST: the test process must have no live children.
|
||||||
|
* 2. child exits 7 -> waitpid returns the child, WIFEXITED, WEXITSTATUS 7.
|
||||||
|
* 3. child exits 42 -> the QA happy path: WEXITSTATUS 42.
|
||||||
|
* 4. child killed via raw SYS_kill(SIGKILL) -> WIFSIGNALED, WTERMSIG 9,
|
||||||
|
* and WCOREDUMP false (SIGKILL never dumps core).
|
||||||
|
* 5. WNOHANG: child sleeps 100 ms via raw SYS_nanosleep -> waitpid(WNOHANG)
|
||||||
|
* returns 0 while it runs, then the blocking waitpid reaps it.
|
||||||
|
* 6. wait() == waitpid(-1): child exits 3 -> wait() reaps it, WEXITSTATUS 3.
|
||||||
|
* 7. waitid(P_PID, child, &info, WEXITED): returns 0, info.si_pid == child,
|
||||||
|
* si_signo == SIGCHLD, si_code == CLD_EXITED, si_status == 5.
|
||||||
|
*
|
||||||
|
* Level-2 gated section: wait4(child,...) and wait3(...) reaping exit 6/9.
|
||||||
|
*
|
||||||
|
* The negative path in scenario 1 makes the LIBRARY write errno
|
||||||
|
* (syscall_ret), which under a host-linked binary targets glibc's private
|
||||||
|
* dtv slot at %fs:0+8. In the default mode that call is bracketed with a
|
||||||
|
* save/restore of the slot (task 13 technique) — only vlibc/raw-syscall code
|
||||||
|
* runs between the write and the restore. The test itself NEVER reads
|
||||||
|
* errno; the negative is asserted on the return value. The -f mode runs
|
||||||
|
* the failure scenario alone and exits via raw SYS_exit_group (house
|
||||||
|
* pattern, tests/test_malloc.c).
|
||||||
|
*
|
||||||
|
* 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 "../include/sys/wait.h"
|
||||||
|
|
||||||
|
#include "../src/internal/syscall.h"
|
||||||
|
|
||||||
|
/* Kernel-UAPI signal numbers, local to this test (signal.h is todo 28). */
|
||||||
|
#define T23_SIGCHLD 17
|
||||||
|
#define T23_SIGKILL 9
|
||||||
|
|
||||||
|
/* Kernel timespec (time.h is a later todo). */
|
||||||
|
struct t23_timespec
|
||||||
|
{
|
||||||
|
long tv_sec;
|
||||||
|
long tv_nsec;
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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).
|
||||||
|
*/
|
||||||
|
static unsigned long
|
||||||
|
tcb_slot1(void)
|
||||||
|
{
|
||||||
|
return *(unsigned long *)((char *)__builtin_thread_pointer() + 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
tcb_slot1_set(unsigned long v)
|
||||||
|
{
|
||||||
|
*(unsigned long *)((char *)__builtin_thread_pointer() + 8) = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Terminate the current thread via the raw syscall (never returns). */
|
||||||
|
static void
|
||||||
|
child_exit(int code)
|
||||||
|
{
|
||||||
|
__syscall1(SYS_exit, code);
|
||||||
|
__builtin_unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fork-equivalent via raw clone: parent gets the child pid, child gets 0. */
|
||||||
|
static pid_t
|
||||||
|
spawn_child(void)
|
||||||
|
{
|
||||||
|
return (pid_t)__syscall5(SYS_clone, T23_SIGCHLD, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reap a child that has already been spawned (pid > 0 in the parent). */
|
||||||
|
static void
|
||||||
|
reap_exited(const char *what, pid_t pid, int want_status)
|
||||||
|
{
|
||||||
|
int st = -1;
|
||||||
|
pid_t r = waitpid(pid, &st, 0);
|
||||||
|
|
||||||
|
check(r == pid && WIFEXITED(st) && WEXITSTATUS(st) == want_status, what);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* waitpid(-1, &st, WNOHANG) with no children -> -1 (ECHILD). Must run before
|
||||||
|
* any child is created for the result to be deterministic.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
scenario_no_children(void)
|
||||||
|
{
|
||||||
|
int st = 0;
|
||||||
|
unsigned long saved = tcb_slot1();
|
||||||
|
|
||||||
|
check(waitpid(-1, &st, WNOHANG) == -1, "waitpid WNOHANG with no children -> -1");
|
||||||
|
tcb_slot1_set(saved);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Child exits 7 -> WIFEXITED && WEXITSTATUS == 7. */
|
||||||
|
static void
|
||||||
|
scenario_exit_7(void)
|
||||||
|
{
|
||||||
|
pid_t pid = spawn_child();
|
||||||
|
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
child_exit(7);
|
||||||
|
}
|
||||||
|
if (pid > 0)
|
||||||
|
{
|
||||||
|
reap_exited("waitpid reaps exit(7): WIFEXITED && WEXITSTATUS 7", pid, 7);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
check(0, "clone for exit(7) child");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* QA happy path: child exits 42 -> WEXITSTATUS == 42. */
|
||||||
|
static void
|
||||||
|
scenario_exit_42(void)
|
||||||
|
{
|
||||||
|
pid_t pid = spawn_child();
|
||||||
|
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
child_exit(42);
|
||||||
|
}
|
||||||
|
if (pid > 0)
|
||||||
|
{
|
||||||
|
reap_exited("waitpid reaps exit(42): WEXITSTATUS 42", pid, 42);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
check(0, "clone for exit(42) child");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Child killed by SIGKILL -> WIFSIGNALED && WTERMSIG == 9, no core dump. */
|
||||||
|
static void
|
||||||
|
scenario_killed(void)
|
||||||
|
{
|
||||||
|
int st = -1;
|
||||||
|
pid_t pid = spawn_child();
|
||||||
|
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
/* Stay alive until the parent's SIGKILL arrives (10 s is plenty). */
|
||||||
|
struct t23_timespec ts = {.tv_sec = 10, .tv_nsec = 0};
|
||||||
|
|
||||||
|
__syscall2(SYS_nanosleep, (long)&ts, 0);
|
||||||
|
child_exit(0);
|
||||||
|
}
|
||||||
|
if (pid > 0)
|
||||||
|
{
|
||||||
|
pid_t r;
|
||||||
|
|
||||||
|
__syscall2(SYS_kill, pid, T23_SIGKILL);
|
||||||
|
r = waitpid(pid, &st, 0);
|
||||||
|
check(r == pid && WIFSIGNALED(st) && WTERMSIG(st) == T23_SIGKILL && !WCOREDUMP(st),
|
||||||
|
"waitpid reaps SIGKILLed child: WIFSIGNALED && WTERMSIG 9");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
check(0, "clone for SIGKILL child");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* WNOHANG returns 0 while the child runs; the blocking waitpid then reaps. */
|
||||||
|
static void
|
||||||
|
scenario_wnohang(void)
|
||||||
|
{
|
||||||
|
int st = -1;
|
||||||
|
pid_t pid = spawn_child();
|
||||||
|
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
struct t23_timespec ts = {.tv_sec = 0, .tv_nsec = 100000000L}; /* 100 ms */
|
||||||
|
|
||||||
|
__syscall2(SYS_nanosleep, (long)&ts, 0);
|
||||||
|
child_exit(0);
|
||||||
|
}
|
||||||
|
if (pid > 0)
|
||||||
|
{
|
||||||
|
pid_t r;
|
||||||
|
|
||||||
|
/* The child cannot have exited yet: 100 ms of nanosleep remain. */
|
||||||
|
r = waitpid(pid, &st, WNOHANG);
|
||||||
|
check(r == 0, "waitpid WNOHANG returns 0 while child still runs");
|
||||||
|
|
||||||
|
r = waitpid(pid, &st, 0);
|
||||||
|
check(r == pid && WIFEXITED(st) && WEXITSTATUS(st) == 0,
|
||||||
|
"blocking waitpid reaps the child after WNOHANG");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
check(0, "clone for WNOHANG child");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* wait() == waitpid(-1): reaps any child. */
|
||||||
|
static void
|
||||||
|
scenario_wait_any(void)
|
||||||
|
{
|
||||||
|
int st = -1;
|
||||||
|
pid_t pid = spawn_child();
|
||||||
|
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
child_exit(3);
|
||||||
|
}
|
||||||
|
if (pid > 0)
|
||||||
|
{
|
||||||
|
pid_t r = wait(&st);
|
||||||
|
|
||||||
|
check(r == pid && WIFEXITED(st) && WEXITSTATUS(st) == 3,
|
||||||
|
"wait() reaps any child: WEXITSTATUS 3");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
check(0, "clone for wait() child");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* waitid(P_PID, ...) fills siginfo with the reaped child's details. */
|
||||||
|
static void
|
||||||
|
scenario_waitid(void)
|
||||||
|
{
|
||||||
|
pid_t pid = spawn_child();
|
||||||
|
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
child_exit(5);
|
||||||
|
}
|
||||||
|
if (pid > 0)
|
||||||
|
{
|
||||||
|
siginfo_t info = {0};
|
||||||
|
|
||||||
|
check(waitid(P_PID, (id_t)pid, &info, WEXITED) == 0, "waitid WEXITED returns 0");
|
||||||
|
check(info.si_pid == pid, "waitid info.si_pid == child");
|
||||||
|
check(info.si_signo == T23_SIGCHLD, "waitid info.si_signo == SIGCHLD");
|
||||||
|
check(info.si_code == CLD_EXITED, "waitid info.si_code == CLD_EXITED");
|
||||||
|
check(info.si_status == 5, "waitid info.si_status == exit status");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
check(0, "clone for waitid child");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
/* Level 2 (muslmimic): wait3/wait4 pass-through (NULL rusage is legal). */
|
||||||
|
static void
|
||||||
|
scenario_wait3_wait4(void)
|
||||||
|
{
|
||||||
|
int st = -1;
|
||||||
|
pid_t pid = spawn_child();
|
||||||
|
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
child_exit(6);
|
||||||
|
}
|
||||||
|
if (pid > 0)
|
||||||
|
{
|
||||||
|
pid_t r = wait4(pid, &st, 0, 0);
|
||||||
|
|
||||||
|
check(r == pid && WIFEXITED(st) && WEXITSTATUS(st) == 6,
|
||||||
|
"wait4 reaps the child: WEXITSTATUS 6");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
check(0, "clone for wait4 child");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pid = spawn_child();
|
||||||
|
if (pid == 0)
|
||||||
|
{
|
||||||
|
child_exit(9);
|
||||||
|
}
|
||||||
|
if (pid > 0)
|
||||||
|
{
|
||||||
|
pid_t r = wait3(&st, 0, 0);
|
||||||
|
|
||||||
|
check(r == pid && WIFEXITED(st) && WEXITSTATUS(st) == 9,
|
||||||
|
"wait3 reaps any child: WEXITSTATUS 9");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
check(0, "clone for wait3 child");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
/* The failure scenario, run alone under -f (writes errno; see top). */
|
||||||
|
static int
|
||||||
|
failure_scenarios(void)
|
||||||
|
{
|
||||||
|
scenario_no_children();
|
||||||
|
return failures;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* The failure scenario writes 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.
|
||||||
|
*/
|
||||||
|
int rc = failure_scenarios();
|
||||||
|
|
||||||
|
__syscall1(SYS_exit_group, rc);
|
||||||
|
return rc; /* not reached */
|
||||||
|
}
|
||||||
|
|
||||||
|
scenario_no_children(); /* first: the process must have no children */
|
||||||
|
scenario_exit_7();
|
||||||
|
scenario_exit_42();
|
||||||
|
scenario_killed();
|
||||||
|
scenario_wnohang();
|
||||||
|
scenario_wait_any();
|
||||||
|
scenario_waitid();
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
scenario_wait3_wait4();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return failures != 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user