Compare commits

...
11 Commits
84 changed files with 15160 additions and 0 deletions
+179
View File
@@ -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
View File
@@ -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 */
+99
View File
@@ -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
View File
@@ -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 */
+13
View File
@@ -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. */
+120
View File
@@ -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 */
+294
View File
@@ -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 */
+207
View File
@@ -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 */
+379
View File
@@ -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 */
+22
View File
@@ -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) */
+25
View File
@@ -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;
}
+57
View File
@@ -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 */
+19
View File
@@ -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;
}
+47
View File
@@ -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;
}
+37
View File
@@ -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;
}
+78
View File
@@ -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;
}
}
+46
View File
@@ -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) */
+29
View File
@@ -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;
}
+124
View File
@@ -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) */
+31
View File
@@ -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;
}
}
+22
View File
@@ -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;
}
+21
View File
@@ -0,0 +1,21 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <fcntl.h>
#include "../internal/syscall.h"
/*
* creat ≡ open(path, O_WRONLY | O_CREAT | O_TRUNC, mode) (POSIX). Done as
* a direct SYS_openat rather than a call to open(): the flag set is fixed,
* the mode is always supplied, and this keeps src/fcntl self-contained
* with no inter-object dependency on src/unistd. The kernel applies the
* process umask to mode.
*/
int
creat(const char *path, mode_t mode)
{
return syscall_ret(
__syscall4(SYS_openat, AT_FDCWD, (long)path, O_WRONLY | O_CREAT | O_TRUNC, mode));
}
+55
View File
@@ -0,0 +1,55 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdarg.h>
#include <fcntl.h>
#include "../internal/syscall.h"
/*
* fcntl over SYS_fcntl (72). The third argument is read from the varargs
* only for the commands POSIX defines one for (F_DUPFD, F_DUPFD_CLOEXEC,
* F_SETFD, F_SETFL, the lock commands, and the owner/signal commands);
* every other command passes 0, which the kernel ignores. Pointers (the
* struct flock * of F_GETLK/F_SETLK/F_SETLKW/F_OFD_*) ride the varargs
* slot as a long — on x86_64 long and void * share one GPR slot, so the
* read is ABI-exact.
*
* F_DUPFD_CLOEXEC passes straight through: the x86_64 kernel has supported
* it as a single native operation since 2.6.24, so no F_DUPFD + F_SETFD
* fallback is needed. F_SETFL needs no O_LARGEFILE massaging on x86_64
* (that is a 32-bit compat concern only).
*/
int
fcntl(int fildes, int cmd, ...)
{
long arg = 0;
va_list ap;
switch (cmd)
{
case F_DUPFD:
case F_DUPFD_CLOEXEC:
case F_SETFD:
case F_SETFL:
case F_GETLK:
case F_SETLK:
case F_SETLKW:
case F_SETOWN:
case F_SETSIG:
case F_GETOWN_EX:
case F_SETOWN_EX:
case F_OFD_GETLK:
case F_OFD_SETLK:
case F_OFD_SETLKW:
va_start(ap, cmd);
arg = va_arg(ap, long);
va_end(ap);
break;
default:
break;
}
return syscall_ret(__syscall3(SYS_fcntl, fildes, cmd, arg));
}
+70
View File
@@ -0,0 +1,70 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include "../internal/syscall.h"
#if VLIBC_LEVEL_GE(2)
/*
* lockf (XSI) over the fcntl record-lock commands: the kernel has no lockf
* syscall, so every cmd maps to a struct flock operation on the range
* [current offset, current offset + len) — the POSIX definition. len 0
* means through EOF, which the kernel's l_len 0 convention already
* expresses.
*
* The region start uses l_whence = SEEK_CUR with l_start = 0: the kernel
* resolves the current file offset at syscall time, atomically with the
* lock operation, so no lseek round-trip (and no TOCTOU window) is needed.
*
* F_TEST probes with F_GETLK using a read lock (the probe type that
* conflicts with any exclusive lock): if the kernel reports no lock, or
* the reported holder is this process itself (a different descriptor of
* ours may hold it — POSIX says our own process never conflicts with
* itself), the region is lockable. The pid is read via raw SYS_getpid;
* the process wrappers are todo 20's and not a dependency of this file.
*/
int
lockf(int fd, int cmd, off_t len)
{
struct flock lk;
lk.l_type = F_WRLCK;
lk.l_whence = SEEK_CUR;
lk.l_start = 0;
lk.l_len = len;
switch (cmd)
{
case F_TEST:
lk.l_type = F_RDLCK;
if (fcntl(fd, F_GETLK, &lk) == -1)
{
return -1;
}
if (lk.l_type == F_UNLCK || lk.l_pid == (pid_t)__syscall0(SYS_getpid))
{
return 0;
}
errno = EACCES;
return -1;
case F_ULOCK:
lk.l_type = F_UNLCK;
return fcntl(fd, F_SETLK, &lk);
case F_LOCK:
return fcntl(fd, F_SETLKW, &lk);
case F_TLOCK:
return fcntl(fd, F_SETLK, &lk);
default:
errno = EINVAL;
return -1;
}
}
#endif /* VLIBC_LEVEL_GE(2) */
+24
View File
@@ -0,0 +1,24 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <fcntl.h>
#include "../internal/syscall.h"
/*
* posix_fadvise over SYS_fadvise64 (221). POSIX return convention, unlike
* the rest of the family: an error NUMBER directly — 0 on success, else
* the positive errno value (EBADF, ESPIPE, EINVAL, ...) — and errno is
* left untouched. The raw syscall result is therefore mapped without the
* syscall_ret() translation (which would write errno): a negative return
* is negated, 0 stays 0. On x86_64 the syscall takes the offset as one
* 64-bit value (fd, offset, len, advice) — no lo/hi split.
*/
int
posix_fadvise(int fd, off_t offset, off_t len, int advice)
{
long r = __syscall4(SYS_fadvise64, fd, offset, len, advice);
return r < 0 ? (int)-r : 0;
}
+24
View File
@@ -0,0 +1,24 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <fcntl.h>
#include "../internal/syscall.h"
/*
* posix_fallocate over SYS_fallocate (285). The kernel signature is
* (fd, mode, offset, len); mode is 0 — POSIX exposes no FALLOC_FL_* flags,
* so the plain allocate-and-grow operation is all there is (the kernel
* rejects any other mode with EOPNOTSUPP/EINVAL, which is exactly what
* POSIX wants). Same error-number return convention as posix_fadvise:
* 0 on success, the positive errno value on failure, errno untouched —
* so the raw result is negated directly, no syscall_ret().
*/
int
posix_fallocate(int fd, off_t offset, off_t len)
{
long r = __syscall4(SYS_fallocate, fd, 0, offset, len);
return r < 0 ? (int)-r : 0;
}
+111
View File
@@ -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();
}
}
}
+67
View File
@@ -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 */
+50
View File
@@ -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)
+50
View File
@@ -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)
+47
View File
@@ -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)
+19
View File
@@ -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);
}
+33
View File
@@ -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));
}
+104
View File
@@ -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)
+77
View File
@@ -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) */
+34
View File
@@ -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) */
+27
View File
@@ -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));
}
+109
View File
@@ -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) */
+145
View File
@@ -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;
}
+70
View File
@@ -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));
}
+59
View File
@@ -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) */
+32
View File
@@ -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));
}
+24
View File
@@ -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) */
+27
View File
@@ -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));
}
+38
View File
@@ -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));
}
+32
View File
@@ -0,0 +1,32 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/stat.h>
#include "../internal/syscall.h"
#include "stat_impl.h"
/*
* chmod/chmodat over SYS_fchmodat (the *at form is the modern kernel ABI)
* and fchmod over SYS_fchmod. chmod() passes AT_FDCWD and no flags; the
* kernel silently ignores the file-type bits of mode and applies only the
* permission + special bits.
*/
int
fchmodat(int fd, const char *path, mode_t mode, int flag)
{
return syscall_ret(__syscall4(SYS_fchmodat, fd, (long)path, (long)mode, flag));
}
int
chmod(const char *path, mode_t mode)
{
return fchmodat(VLIBC_STAT_AT_FDCWD, path, mode, 0);
}
int
fchmod(int fd, mode_t mode)
{
return syscall_ret(__syscall2(SYS_fchmod, fd, (long)mode));
}
+40
View File
@@ -0,0 +1,40 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/stat.h>
#include "../internal/syscall.h"
#include "stat_impl.h"
/*
* chown family over SYS_fchownat (the *at form is the modern kernel ABI)
* and fchown over SYS_fchown. A uid/gid of (uid_t)-1 (or (gid_t)-1) means
* "leave unchanged" — the kernel checks for the all-ones value, so the
* wrapper passes the arguments through as-is. chown() passes AT_FDCWD and
* no flags; lchown() adds AT_SYMLINK_NOFOLLOW (its whole point: the link
* itself is chowned, not the target).
*/
int
fchownat(int fd, const char *path, uid_t owner, gid_t group, int flag)
{
return syscall_ret(__syscall5(SYS_fchownat, fd, (long)path, (long)owner, (long)group, flag));
}
int
chown(const char *path, uid_t owner, gid_t group)
{
return fchownat(VLIBC_STAT_AT_FDCWD, path, owner, group, 0);
}
int
lchown(const char *path, uid_t owner, gid_t group)
{
return fchownat(VLIBC_STAT_AT_FDCWD, path, owner, group, VLIBC_STAT_AT_SYMLINK_NOFOLLOW);
}
int
fchown(int fd, uid_t owner, gid_t group)
{
return syscall_ret(__syscall3(SYS_fchown, fd, (long)owner, (long)group));
}
+39
View File
@@ -0,0 +1,39 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/stat.h>
#include "../internal/syscall.h"
#include "stat_impl.h"
/*
* The stat family over SYS_newfstatat (the modern *at form; SYS_stat/
* SYS_lstat exist only for compatibility) and SYS_fstat for the
* descriptor-based form. stat()/lstat() are newfstatat on AT_FDCWD with
* flag 0 / AT_SYMLINK_NOFOLLOW; the kernel fills the full 144-byte
* struct stat pinned by the static assertions in <sys/stat.h>.
*/
int
fstatat(int fd, const char *path, struct stat *buf, int flag)
{
return syscall_ret(__syscall4(SYS_newfstatat, fd, (long)path, (long)buf, flag));
}
int
stat(const char *path, struct stat *buf)
{
return fstatat(VLIBC_STAT_AT_FDCWD, path, buf, 0);
}
int
lstat(const char *path, struct stat *buf)
{
return fstatat(VLIBC_STAT_AT_FDCWD, path, buf, VLIBC_STAT_AT_SYMLINK_NOFOLLOW);
}
int
fstat(int fd, struct stat *buf)
{
return syscall_ret(__syscall2(SYS_fstat, fd, (long)buf));
}
+24
View File
@@ -0,0 +1,24 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/stat.h>
#include "../internal/syscall.h"
/*
* mkdir over SYS_mkdir and mkdirat over SYS_mkdirat (the *at form takes
* the directory fd, so mkdir() cannot simply delegate to it — SYS_mkdir
* is the direct ABI). The kernel applies the process umask to mode.
*/
int
mkdirat(int fd, const char *path, mode_t mode)
{
return syscall_ret(__syscall3(SYS_mkdirat, fd, (long)path, (long)mode));
}
int
mkdir(const char *path, mode_t mode)
{
return syscall_ret(__syscall2(SYS_mkdir, (long)path, (long)mode));
}
+25
View File
@@ -0,0 +1,25 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/stat.h>
#include "../internal/syscall.h"
#include "stat_impl.h"
/*
* mkfifo/mkfifoat over SYS_mknodat with the type bits fixed to S_IFIFO:
* creating a FIFO is mknodat(..., mode | S_IFIFO, dev = 0). POSIX defines
* mkfifo with no device argument, so the dev slot is always 0 here.
*/
int
mkfifoat(int fd, const char *path, mode_t mode)
{
return syscall_ret(__syscall4(SYS_mknodat, fd, (long)path, (long)(mode | S_IFIFO), 0));
}
int
mkfifo(const char *path, mode_t mode)
{
return mkfifoat(VLIBC_STAT_AT_FDCWD, path, mode);
}
+31
View File
@@ -0,0 +1,31 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/stat.h>
#include "../internal/syscall.h"
#include "stat_impl.h"
#if VLIBC_LEVEL_GE(2)
/*
* mknod/mknodat over SYS_mknodat (XSI, level 2). The mode argument must
* carry a file-type bit (S_IFIFO, S_IFCHR, S_IFBLK, S_IFREG, ...); dev is
* the raw device id for the S_IFCHR/S_IFBLK cases and is otherwise
* ignored by the kernel. Creating device nodes requires privilege
* (CAP_MKNOD); creating a FIFO or a regular file does not.
*/
int
mknodat(int fd, const char *path, mode_t mode, dev_t dev)
{
return syscall_ret(__syscall4(SYS_mknodat, fd, (long)path, (long)mode, (long)dev));
}
int
mknod(const char *path, mode_t mode, dev_t dev)
{
return mknodat(VLIBC_STAT_AT_FDCWD, path, mode, dev);
}
#endif /* VLIBC_LEVEL_GE(2) */
+22
View File
@@ -0,0 +1,22 @@
#ifndef VLIBC_STAT_INTERNAL_H
#define VLIBC_STAT_INTERNAL_H
/*
* vlibc — internal constants for the stat wrappers (temporary).
*
* include/fcntl.h is owned by todo 21; until it lands, the *at wrappers
* need AT_FDCWD and AT_SYMLINK_NOFOLLOW locally. These names carry the
* VLIBC_STAT_ prefix so they cannot collide with the real constants todo
* 21 will publish. The values are kernel UAPI facts
* (asm-generic/fcntl.h), transcribed, not invented:
*
* AT_FDCWD -100, AT_SYMLINK_NOFOLLOW 0x100.
*/
/* *at syscall base directory (kernel UAPI). */
#define VLIBC_STAT_AT_FDCWD (-100)
/* *at flag: operate on the link itself, not its target (kernel UAPI). */
#define VLIBC_STAT_AT_SYMLINK_NOFOLLOW 0x100
#endif /* VLIBC_STAT_INTERNAL_H */
+18
View File
@@ -0,0 +1,18 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/stat.h>
#include "../internal/syscall.h"
/*
* umask over SYS_umask. The kernel always returns the previous mask (even
* through the syscall_ret error translation, which never fires here —
* umask cannot fail), so no special handling is needed.
*/
mode_t
umask(mode_t cmask)
{
return (mode_t)syscall_ret(__syscall1(SYS_umask, (long)cmask));
}
+26
View File
@@ -0,0 +1,26 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/stat.h>
#include "../internal/syscall.h"
/*
* utimensat/futimens over SYS_utimensat. A NULL times array is passed
* through unchanged: the kernel sets both timestamps to the current time
* (the POSIX contract). futimens() is utimensat on the descriptor itself
* — Linux applies utimensat(fd, NULL, times, 0) to the file referenced by
* fd when the path is NULL, which is exactly futimens(fd, times).
*/
int
utimensat(int fd, const char *path, const struct timespec times[2], int flag)
{
return syscall_ret(__syscall4(SYS_utimensat, fd, (long)path, (long)times, flag));
}
int
futimens(int fd, const struct timespec times[2])
{
return syscall_ret(__syscall4(SYS_utimensat, fd, 0, (long)times, 0));
}
+503
View File
@@ -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;
}
+142
View File
@@ -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);
}
+178
View File
@@ -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);
}
+201
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+150
View File
@@ -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 */
+2151
View File
File diff suppressed because it is too large Load Diff
+842
View File
@@ -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;
}
+25
View File
@@ -0,0 +1,25 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
#include "unistd_impl.h"
/*
* access/faccessat over SYS_faccessat (the *at form is the modern kernel
* ABI; SYS_faccessat2 is deliberately not used — the classic syscall
* covers the POSIX amode set). access() passes AT_FDCWD and no flags.
*/
int
access(const char *path, int amode)
{
return syscall_ret(__syscall4(SYS_faccessat, VLIBC_UNISTD_AT_FDCWD, (long)path, amode, 0));
}
int
faccessat(int fd, const char *path, int amode, int flag)
{
return syscall_ret(__syscall4(SYS_faccessat, fd, (long)path, amode, flag));
}
+17
View File
@@ -0,0 +1,17 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
/*
* close: unbuffered pass-through. There is nothing to flush — vlibc I/O is
* unbuffered at this layer, and the stdio layer owns its own buffers.
*/
int
close(int fildes)
{
return syscall_ret(__syscall1(SYS_close, fildes));
}
+38
View File
@@ -0,0 +1,38 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
/*
* dup/dup2: descriptor duplication. The copies share the file description
* (position, status flags, locks) with the original. On x86_64 SYS_dup2 is
* a two-argument syscall.
*/
int
dup(int fildes)
{
return syscall_ret(__syscall1(SYS_dup, fildes));
}
int
dup2(int fildes, int fildes2)
{
return syscall_ret(__syscall2(SYS_dup2, fildes, fildes2));
}
#if VLIBC_LEVEL_GE(2)
/*
* dup3: dup2 with descriptor flags (O_CLOEXEC) applied atomically.
* Linux-specific; the kernel rejects fildes == fildes2 with EINVAL.
*/
int
dup3(int fildes, int fildes2, int flags)
{
return syscall_ret(__syscall3(SYS_dup3, fildes, fildes2, flags));
}
#endif /* VLIBC_LEVEL_GE(2) */
+23
View File
@@ -0,0 +1,23 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
/*
* fsync/fdatasync: flush a descriptor's dirty data (and for fsync, the
* metadata) to stable storage.
*/
int
fsync(int fildes)
{
return syscall_ret(__syscall1(SYS_fsync, fildes));
}
int
fdatasync(int fildes)
{
return syscall_ret(__syscall1(SYS_fdatasync, fildes));
}
+44
View File
@@ -0,0 +1,44 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <unistd.h>
#include "../internal/syscall.h"
/*
* lseek returns a full-width off_t: on x86_64 the offset is a 64-bit long
* that can legitimately exceed INT_MAX, so the value must not be routed
* through syscall_ret() (which narrows to int). The error translation is
* inlined here with the same semantics: errno set from -r and (off_t)-1 on
* error, errno untouched on success.
*/
off_t
lseek(int fildes, off_t offset, int whence)
{
long r = __syscall3(SYS_lseek, fildes, offset, whence);
if (r < 0 && r > -4096)
{
errno = (int)-r;
return (off_t)-1;
}
return (off_t)r;
}
#if VLIBC_LEVEL_GE(2)
/*
* lseek64: glibc LFS alias. On x86_64 the LFS and non-LFS off_t are
* identical (both 64-bit) and SYS_lseek is the single ABI, so this is a
* plain delegation. Provided for source compatibility only.
*/
off_t
lseek64(int fildes, off_t offset, int whence)
{
return lseek(fildes, offset, whence);
}
#endif /* VLIBC_LEVEL_GE(2) */
+51
View File
@@ -0,0 +1,51 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdarg.h>
#include <unistd.h>
#include "../internal/syscall.h"
#include "unistd_impl.h"
/*
* open/openat over SYS_openat (the *at syscall family is the modern kernel
* ABI; SYS_open exists only for compatibility and is not used). The
* optional fourth mode argument is read from the varargs only when oflag
* contains O_CREAT or O_TMPFILE — otherwise the caller supplied no mode
* and 0 is passed, which the kernel ignores. mode_t promotes to unsigned
* int in the varargs list.
*/
int
open(const char *path, int oflag, ...)
{
unsigned mode = 0;
if ((oflag & VLIBC_UNISTD_O_CREAT) != 0 || (oflag & VLIBC_UNISTD_O_TMPFILE) != 0)
{
va_list ap;
va_start(ap, oflag);
mode = va_arg(ap, unsigned int);
va_end(ap);
}
return syscall_ret(
__syscall4(SYS_openat, VLIBC_UNISTD_AT_FDCWD, (long)path, oflag, (long)mode));
}
int
openat(int fd, const char *path, int oflag, ...)
{
unsigned mode = 0;
if ((oflag & VLIBC_UNISTD_O_CREAT) != 0 || (oflag & VLIBC_UNISTD_O_TMPFILE) != 0)
{
va_list ap;
va_start(ap, oflag);
mode = va_arg(ap, unsigned int);
va_end(ap);
}
return syscall_ret(__syscall4(SYS_openat, fd, (long)path, oflag, (long)mode));
}
+31
View File
@@ -0,0 +1,31 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
/*
* pipe: plain POSIX pipe via SYS_pipe. Deliberately does NOT set
* O_CLOEXEC — pipe2 is the flag-taking variant (level 2).
*/
int
pipe(int fildes[2])
{
return syscall_ret(__syscall1(SYS_pipe, (long)fildes));
}
#if VLIBC_LEVEL_GE(2)
/*
* pipe2: pipe with descriptor flags (O_CLOEXEC) applied atomically.
* Linux-specific.
*/
int
pipe2(int fildes[2], int flags)
{
return syscall_ret(__syscall2(SYS_pipe2, (long)fildes, flags));
}
#endif /* VLIBC_LEVEL_GE(2) */
+25
View File
@@ -0,0 +1,25 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
/*
* pread/pwrite: positional I/O over SYS_pread64/SYS_pwrite64. On x86_64
* the offset is a single 64-bit register argument (off_t == long), so
* there is no lo/hi split. Positional I/O never touches the file position
* — the kernel handles that.
*/
ssize_t
pread(int fildes, void *buf, size_t nbyte, off_t offset)
{
return syscall_ret(__syscall4(SYS_pread64, fildes, (long)buf, (long)nbyte, offset));
}
ssize_t
pwrite(int fildes, const void *buf, size_t nbyte, off_t offset)
{
return syscall_ret(__syscall4(SYS_pwrite64, fildes, (long)buf, (long)nbyte, offset));
}
+25
View File
@@ -0,0 +1,25 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
/*
* read/write: unbuffered pass-through to the kernel. The raw result goes
* through syscall_ret(), which returns the byte count on success and -1
* with errno set on error; errno is untouched on success. No buffering, no
* argument inspection.
*/
ssize_t
read(int fildes, void *buf, size_t nbyte)
{
return syscall_ret(__syscall3(SYS_read, fildes, (long)buf, (long)nbyte));
}
ssize_t
write(int fildes, const void *buf, size_t nbyte)
{
return syscall_ret(__syscall3(SYS_write, fildes, (long)buf, (long)nbyte));
}
+18
View File
@@ -0,0 +1,18 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
/*
* sync: flush all filesystem caches to stable storage. The kernel's SYS_sync
* always returns 0 and the POSIX interface is void, so the raw result is
* deliberately not inspected (there is no failure to report).
*/
void
sync(void)
{
__syscall0(SYS_sync);
}
+30
View File
@@ -0,0 +1,30 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include "../internal/syscall.h"
/*
* ftruncate: truncate an open descriptor; POSIX base form.
*/
int
ftruncate(int fildes, off_t length)
{
return syscall_ret(__syscall2(SYS_ftruncate, fildes, length));
}
#if VLIBC_LEVEL_GE(2)
/*
* truncate: truncate by path name; XSI, so it is gated at level 2
* (ftruncate is the POSIX base form).
*/
int
truncate(const char *path, off_t length)
{
return syscall_ret(__syscall2(SYS_truncate, (long)path, length));
}
#endif /* VLIBC_LEVEL_GE(2) */
+33
View File
@@ -0,0 +1,33 @@
#ifndef VLIBC_UNISTD_INTERNAL_H
#define VLIBC_UNISTD_INTERNAL_H
/*
* vlibc — internal constants for the unistd wrappers (temporary).
*
* include/fcntl.h is owned by todo 21; until it lands, the open/openat and
* access wrappers need the O_* flag bits and AT_FDCWD locally. These names
* carry the VLIBC_UNISTD_ prefix so they cannot collide with the real
* constants todo 21 will publish. The values are kernel UAPI facts
* (asm-generic/fcntl.h), transcribed, not invented:
*
* O_RDONLY 0x0, O_WRONLY 0x1, O_RDWR 0x2, O_CREAT 0x40, O_EXCL 0x80,
* O_TRUNC 0x200, O_APPEND 0x400, O_CLOEXEC 0x80000,
* O_TMPFILE 0x410000 (__O_TMPFILE 0x400000 | O_DIRECTORY 0x10000),
* AT_FDCWD -100.
*/
/* fcntl open-flag bits (kernel UAPI; see the header comment above). */
#define VLIBC_UNISTD_O_RDONLY 0x0
#define VLIBC_UNISTD_O_WRONLY 0x1
#define VLIBC_UNISTD_O_RDWR 0x2
#define VLIBC_UNISTD_O_CREAT 0x40
#define VLIBC_UNISTD_O_EXCL 0x80
#define VLIBC_UNISTD_O_TRUNC 0x200
#define VLIBC_UNISTD_O_APPEND 0x400
#define VLIBC_UNISTD_O_CLOEXEC 0x80000
#define VLIBC_UNISTD_O_TMPFILE 0x410000
/* *at syscall base directory (kernel UAPI). */
#define VLIBC_UNISTD_AT_FDCWD (-100)
#endif /* VLIBC_UNISTD_INTERNAL_H */
+423
View File
@@ -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;
}
+457
View File
@@ -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;
}
+611
View File
@@ -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;
}
+331
View File
@@ -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;
}
+734
View File
@@ -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;
}
+779
View File
@@ -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;
}
+384
View File
@@ -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;
}
+802
View File
@@ -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;
}
+445
View File
@@ -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;
}
+380
View File
@@ -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;
}