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