feat(stat): stat/mkdir/chmod and mode macros
This commit is contained in:
@@ -0,0 +1,294 @@
|
|||||||
|
#ifndef VLIBC_SYS_STAT_H
|
||||||
|
#define VLIBC_SYS_STAT_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — <sys/stat.h>.
|
||||||
|
*
|
||||||
|
* File status: the x86_64 Linux struct stat layout and the S_* mode-bit
|
||||||
|
* macros, plus the stat/mkdir/chmod/utimensat/chown wrapper families
|
||||||
|
* (POSIX.1-2008 base). Every function is an unbuffered pass-through to the
|
||||||
|
* kernel: failures are reported as -1 with errno set by the syscall layer.
|
||||||
|
*
|
||||||
|
* Level 1 (onlyposix): stat, fstat, lstat, fstatat, mkdir, mkdirat,
|
||||||
|
* mkfifo, mkfifoat, chmod, fchmod, fchmodat, umask,
|
||||||
|
* utimensat, futimens, chown, fchown, lchown,
|
||||||
|
* fchownat (Issue 7 moved lchown from XSI to base).
|
||||||
|
* Level 2 (muslmimic): mknod, mknodat (XSI).
|
||||||
|
*
|
||||||
|
* struct stat matches the x86_64 kernel layout exactly (144 bytes), pinned
|
||||||
|
* by static assertions — the same layout src/stdio/stdio.c transcribed
|
||||||
|
* privately as stdio_stat for its fstat-based isatty(). The
|
||||||
|
* st_atime/st_mtime/st_ctime spellings are glibc-style macros onto
|
||||||
|
* st_atim.tv_sec &c for POSIX source compatibility.
|
||||||
|
*
|
||||||
|
* The AT_* flag constants (AT_FDCWD, AT_SYMLINK_NOFOLLOW, ...) belong to
|
||||||
|
* <fcntl.h> (todo 21) and are deliberately not defined here; the flag
|
||||||
|
* arguments below are plain int and take their values from that header.
|
||||||
|
*
|
||||||
|
* None of these declarations carry an intent attribute: every function
|
||||||
|
* performs I/O with side effects and reports failures through errno, so
|
||||||
|
* const/pure would be unsound.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vlibc/features.h>
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Provisional struct timespec definition. The canonical home of struct
|
||||||
|
* timespec is <time.h> (todo 41); until it lands, sys/stat.h needs the
|
||||||
|
* definition for the st_*tim members and utimensat/futimens. Todo 41 must
|
||||||
|
* move (or reconcile) this definition — it currently exists nowhere else
|
||||||
|
* (include/threads.h only forward-declares it).
|
||||||
|
*/
|
||||||
|
struct timespec
|
||||||
|
{
|
||||||
|
time_t tv_sec; /* seconds */
|
||||||
|
long tv_nsec; /* nanoseconds (0..999999999) */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File status, x86_64 Linux kernel layout. Field widths are LP64: the
|
||||||
|
* comment column gives the byte offset of each member. The order and the
|
||||||
|
* two padding areas are kernel-ABI facts, not style choices.
|
||||||
|
*/
|
||||||
|
struct stat
|
||||||
|
{
|
||||||
|
dev_t st_dev; /* 0: device containing the file */
|
||||||
|
ino_t st_ino; /* 8: inode number */
|
||||||
|
nlink_t st_nlink; /* 16: hard link count */
|
||||||
|
mode_t st_mode; /* 24: file type + permissions */
|
||||||
|
uid_t st_uid; /* 28: owner user id */
|
||||||
|
gid_t st_gid; /* 32: owner group id */
|
||||||
|
unsigned int st_pad0; /* 36: kernel padding (int __pad0) */
|
||||||
|
dev_t st_rdev; /* 40: device id (if device file) */
|
||||||
|
off_t st_size; /* 48: size in bytes */
|
||||||
|
blksize_t st_blksize; /* 56: preferred I/O block size */
|
||||||
|
blkcnt_t st_blocks; /* 64: 512-byte blocks allocated */
|
||||||
|
struct timespec st_atim; /* 72: last access time */
|
||||||
|
struct timespec st_mtim; /* 88: last modification time */
|
||||||
|
struct timespec st_ctim; /* 104: last status change time */
|
||||||
|
long st_unused[3]; /* 120: kernel padding (__unused) */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Pin the kernel layout: sizeof and the two offsets stdio consumes. */
|
||||||
|
_Static_assert(sizeof(struct stat) == 144, "struct stat must match the x86_64 kernel layout");
|
||||||
|
_Static_assert(offsetof(struct stat, st_mode) == 24, "st_mode must sit at offset 24");
|
||||||
|
_Static_assert(offsetof(struct stat, st_size) == 48, "st_size must sit at offset 48");
|
||||||
|
|
||||||
|
/* File type bits (st_mode & S_IFMT). */
|
||||||
|
#define S_IFMT 0170000 /* type-of-file mask */
|
||||||
|
#define S_IFSOCK 0140000 /* socket */
|
||||||
|
#define S_IFLNK 0120000 /* symbolic link */
|
||||||
|
#define S_IFREG 0100000 /* regular file */
|
||||||
|
#define S_IFBLK 0060000 /* block device */
|
||||||
|
#define S_IFDIR 0040000 /* directory */
|
||||||
|
#define S_IFCHR 0020000 /* character device */
|
||||||
|
#define S_IFIFO 0010000 /* FIFO (named pipe) */
|
||||||
|
|
||||||
|
/* Special permission bits. */
|
||||||
|
#define S_ISUID 04000 /* set-user-id on execution */
|
||||||
|
#define S_ISGID 02000 /* set-group-id on execution */
|
||||||
|
#define S_ISVTX 01000 /* sticky bit (restricted deletion on dirs) */
|
||||||
|
|
||||||
|
/* Owner permission bits. */
|
||||||
|
#define S_IRWXU 0700 /* read, write, execute/search by owner */
|
||||||
|
#define S_IRUSR 0400 /* read permission, owner */
|
||||||
|
#define S_IWUSR 0200 /* write permission, owner */
|
||||||
|
#define S_IXUSR 0100 /* execute/search permission, owner */
|
||||||
|
|
||||||
|
/* Group permission bits. */
|
||||||
|
#define S_IRWXG 070 /* read, write, execute/search by group */
|
||||||
|
#define S_IRGRP 040 /* read permission, group */
|
||||||
|
#define S_IWGRP 020 /* write permission, group */
|
||||||
|
#define S_IXGRP 010 /* execute/search permission, group */
|
||||||
|
|
||||||
|
/* Others permission bits. */
|
||||||
|
#define S_IRWXO 07 /* read, write, execute/search by others */
|
||||||
|
#define S_IROTH 04 /* read permission, others */
|
||||||
|
#define S_IWOTH 02 /* write permission, others */
|
||||||
|
#define S_IXOTH 01 /* execute/search permission, others */
|
||||||
|
|
||||||
|
/* File type predicates over the type bits. */
|
||||||
|
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
|
||||||
|
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
|
||||||
|
#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
|
||||||
|
#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
|
||||||
|
#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
|
||||||
|
#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
|
||||||
|
#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
|
||||||
|
|
||||||
|
/* POSIX compatibility spellings of the struct timespec members. */
|
||||||
|
#define st_atime st_atim.tv_sec
|
||||||
|
#define st_mtime st_mtim.tv_sec
|
||||||
|
#define st_ctime st_ctim.tv_sec
|
||||||
|
|
||||||
|
/* Level 1 (POSIX base). */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Store the status of the file named by path into buf. Follows symbolic
|
||||||
|
* links; return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
stat(const char *path, struct stat *buf);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Store the status of the file descriptor fd into buf; return 0, or -1
|
||||||
|
* with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fstat(int fd, struct stat *buf);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like stat(), but a symbolic link is reported itself, not its target;
|
||||||
|
* return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
lstat(const char *path, struct stat *buf);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like stat(), but path is relative to the directory named by fd (use
|
||||||
|
* AT_FDCWD from <fcntl.h> for the current working directory); flag may
|
||||||
|
* hold AT_SYMLINK_NOFOLLOW to report the link itself. Return 0, or -1
|
||||||
|
* with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fstatat(int fd, const char *path, struct stat *buf, int flag);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the directory named by path with the access mode mode (masked by
|
||||||
|
* the process umask); return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
mkdir(const char *path, mode_t mode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like mkdir(), but path is relative to the directory named by fd (use
|
||||||
|
* AT_FDCWD for the current working directory); return 0, or -1 with errno
|
||||||
|
* set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
mkdirat(int fd, const char *path, mode_t mode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create the FIFO (named pipe) named by path with mode; return 0, or -1
|
||||||
|
* with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
mkfifo(const char *path, mode_t mode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like mkfifo(), but path is relative to the directory named by fd (use
|
||||||
|
* AT_FDCWD for the current working directory); return 0, or -1 with errno
|
||||||
|
* set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
mkfifoat(int fd, const char *path, mode_t mode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change the access mode of the file named by path to mode; return 0, or
|
||||||
|
* -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
chmod(const char *path, mode_t mode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change the access mode of the file descriptor fd to mode; return 0, or
|
||||||
|
* -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fchmod(int fd, mode_t mode);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like chmod(), but path is relative to the directory named by fd (use
|
||||||
|
* AT_FDCWD for the current working directory); flag may hold
|
||||||
|
* AT_SYMLINK_NOFOLLOW. Return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fchmodat(int fd, const char *path, mode_t mode, int flag);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the process file-mode creation mask to cmask and return the previous
|
||||||
|
* mask. Never fails.
|
||||||
|
*/
|
||||||
|
mode_t
|
||||||
|
umask(mode_t cmask);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the access and modification times of the file named by path
|
||||||
|
* (relative to fd, or AT_FDCWD) to times[0] (access) and times[1]
|
||||||
|
* (modification). A NULL times sets both to the current time; a tv_nsec
|
||||||
|
* of UTIME_NOW/UTIME_OMIT (from <time.h>) selects per-member behavior.
|
||||||
|
* Return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
utimensat(int fd, const char *path, const struct timespec times[2], int flag);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like utimensat() on the file descriptor fd (the path is implicit);
|
||||||
|
* return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
futimens(int fd, const struct timespec times[2]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change the owner and group of the file named by path to owner/group
|
||||||
|
* (a value of (uid_t)-1 leaves the current one unchanged). Follows
|
||||||
|
* symbolic links; return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
chown(const char *path, uid_t owner, gid_t group);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change the owner and group of the file descriptor fd; return 0, or -1
|
||||||
|
* with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fchown(int fd, uid_t owner, gid_t group);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like chown(), but a symbolic link is changed itself, not its target;
|
||||||
|
* return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
lchown(const char *path, uid_t owner, gid_t group);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like chown(), but path is relative to the directory named by fd (use
|
||||||
|
* AT_FDCWD for the current working directory); flag may hold
|
||||||
|
* AT_SYMLINK_NOFOLLOW. Return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fchownat(int fd, const char *path, uid_t owner, gid_t group, int flag);
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
/* Level 2 (muslmimic): XSI. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a special file named by path with mode (a file-type bit like
|
||||||
|
* S_IFIFO or S_IFCHR must be set) and device id dev; return 0, or -1 with
|
||||||
|
* errno set. Creating device nodes requires privilege. XSI.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
mknod(const char *path, mode_t mode, dev_t dev);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like mknod(), but path is relative to the directory named by fd (use
|
||||||
|
* AT_FDCWD for the current working directory); return 0, or -1 with errno
|
||||||
|
* set. XSI.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
mknodat(int fd, const char *path, mode_t mode, dev_t dev);
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VLIBC_SYS_STAT_H */
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
#include "stat_impl.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* chmod/chmodat over SYS_fchmodat (the *at form is the modern kernel ABI)
|
||||||
|
* and fchmod over SYS_fchmod. chmod() passes AT_FDCWD and no flags; the
|
||||||
|
* kernel silently ignores the file-type bits of mode and applies only the
|
||||||
|
* permission + special bits.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fchmodat(int fd, const char *path, mode_t mode, int flag)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall4(SYS_fchmodat, fd, (long)path, (long)mode, flag));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
chmod(const char *path, mode_t mode)
|
||||||
|
{
|
||||||
|
return fchmodat(VLIBC_STAT_AT_FDCWD, path, mode, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
fchmod(int fd, mode_t mode)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall2(SYS_fchmod, fd, (long)mode));
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
#include "stat_impl.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* chown family over SYS_fchownat (the *at form is the modern kernel ABI)
|
||||||
|
* and fchown over SYS_fchown. A uid/gid of (uid_t)-1 (or (gid_t)-1) means
|
||||||
|
* "leave unchanged" — the kernel checks for the all-ones value, so the
|
||||||
|
* wrapper passes the arguments through as-is. chown() passes AT_FDCWD and
|
||||||
|
* no flags; lchown() adds AT_SYMLINK_NOFOLLOW (its whole point: the link
|
||||||
|
* itself is chowned, not the target).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fchownat(int fd, const char *path, uid_t owner, gid_t group, int flag)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall5(SYS_fchownat, fd, (long)path, (long)owner, (long)group, flag));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
chown(const char *path, uid_t owner, gid_t group)
|
||||||
|
{
|
||||||
|
return fchownat(VLIBC_STAT_AT_FDCWD, path, owner, group, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
lchown(const char *path, uid_t owner, gid_t group)
|
||||||
|
{
|
||||||
|
return fchownat(VLIBC_STAT_AT_FDCWD, path, owner, group, VLIBC_STAT_AT_SYMLINK_NOFOLLOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
fchown(int fd, uid_t owner, gid_t group)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall3(SYS_fchown, fd, (long)owner, (long)group));
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
#include "stat_impl.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The stat family over SYS_newfstatat (the modern *at form; SYS_stat/
|
||||||
|
* SYS_lstat exist only for compatibility) and SYS_fstat for the
|
||||||
|
* descriptor-based form. stat()/lstat() are newfstatat on AT_FDCWD with
|
||||||
|
* flag 0 / AT_SYMLINK_NOFOLLOW; the kernel fills the full 144-byte
|
||||||
|
* struct stat pinned by the static assertions in <sys/stat.h>.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
fstatat(int fd, const char *path, struct stat *buf, int flag)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall4(SYS_newfstatat, fd, (long)path, (long)buf, flag));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
stat(const char *path, struct stat *buf)
|
||||||
|
{
|
||||||
|
return fstatat(VLIBC_STAT_AT_FDCWD, path, buf, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
lstat(const char *path, struct stat *buf)
|
||||||
|
{
|
||||||
|
return fstatat(VLIBC_STAT_AT_FDCWD, path, buf, VLIBC_STAT_AT_SYMLINK_NOFOLLOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
fstat(int fd, struct stat *buf)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall2(SYS_fstat, fd, (long)buf));
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* mkdir over SYS_mkdir and mkdirat over SYS_mkdirat (the *at form takes
|
||||||
|
* the directory fd, so mkdir() cannot simply delegate to it — SYS_mkdir
|
||||||
|
* is the direct ABI). The kernel applies the process umask to mode.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
mkdirat(int fd, const char *path, mode_t mode)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall3(SYS_mkdirat, fd, (long)path, (long)mode));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mkdir(const char *path, mode_t mode)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall2(SYS_mkdir, (long)path, (long)mode));
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
#include "stat_impl.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* mkfifo/mkfifoat over SYS_mknodat with the type bits fixed to S_IFIFO:
|
||||||
|
* creating a FIFO is mknodat(..., mode | S_IFIFO, dev = 0). POSIX defines
|
||||||
|
* mkfifo with no device argument, so the dev slot is always 0 here.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
mkfifoat(int fd, const char *path, mode_t mode)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall4(SYS_mknodat, fd, (long)path, (long)(mode | S_IFIFO), 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mkfifo(const char *path, mode_t mode)
|
||||||
|
{
|
||||||
|
return mkfifoat(VLIBC_STAT_AT_FDCWD, path, mode);
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
#include "stat_impl.h"
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* mknod/mknodat over SYS_mknodat (XSI, level 2). The mode argument must
|
||||||
|
* carry a file-type bit (S_IFIFO, S_IFCHR, S_IFBLK, S_IFREG, ...); dev is
|
||||||
|
* the raw device id for the S_IFCHR/S_IFBLK cases and is otherwise
|
||||||
|
* ignored by the kernel. Creating device nodes requires privilege
|
||||||
|
* (CAP_MKNOD); creating a FIFO or a regular file does not.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
mknodat(int fd, const char *path, mode_t mode, dev_t dev)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall4(SYS_mknodat, fd, (long)path, (long)mode, (long)dev));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mknod(const char *path, mode_t mode, dev_t dev)
|
||||||
|
{
|
||||||
|
return mknodat(VLIBC_STAT_AT_FDCWD, path, mode, dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#ifndef VLIBC_STAT_INTERNAL_H
|
||||||
|
#define VLIBC_STAT_INTERNAL_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — internal constants for the stat wrappers (temporary).
|
||||||
|
*
|
||||||
|
* include/fcntl.h is owned by todo 21; until it lands, the *at wrappers
|
||||||
|
* need AT_FDCWD and AT_SYMLINK_NOFOLLOW locally. These names carry the
|
||||||
|
* VLIBC_STAT_ prefix so they cannot collide with the real constants todo
|
||||||
|
* 21 will publish. The values are kernel UAPI facts
|
||||||
|
* (asm-generic/fcntl.h), transcribed, not invented:
|
||||||
|
*
|
||||||
|
* AT_FDCWD -100, AT_SYMLINK_NOFOLLOW 0x100.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* *at syscall base directory (kernel UAPI). */
|
||||||
|
#define VLIBC_STAT_AT_FDCWD (-100)
|
||||||
|
|
||||||
|
/* *at flag: operate on the link itself, not its target (kernel UAPI). */
|
||||||
|
#define VLIBC_STAT_AT_SYMLINK_NOFOLLOW 0x100
|
||||||
|
|
||||||
|
#endif /* VLIBC_STAT_INTERNAL_H */
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* umask over SYS_umask. The kernel always returns the previous mask (even
|
||||||
|
* through the syscall_ret error translation, which never fires here —
|
||||||
|
* umask cannot fail), so no special handling is needed.
|
||||||
|
*/
|
||||||
|
mode_t
|
||||||
|
umask(mode_t cmask)
|
||||||
|
{
|
||||||
|
return (mode_t)syscall_ret(__syscall1(SYS_umask, (long)cmask));
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* utimensat/futimens over SYS_utimensat. A NULL times array is passed
|
||||||
|
* through unchanged: the kernel sets both timestamps to the current time
|
||||||
|
* (the POSIX contract). futimens() is utimensat on the descriptor itself
|
||||||
|
* — Linux applies utimensat(fd, NULL, times, 0) to the file referenced by
|
||||||
|
* fd when the path is NULL, which is exactly futimens(fd, times).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
utimensat(int fd, const char *path, const struct timespec times[2], int flag)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall4(SYS_utimensat, fd, (long)path, (long)times, flag));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
futimens(int fd, const struct timespec times[2])
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall4(SYS_utimensat, fd, 0, (long)times, 0));
|
||||||
|
}
|
||||||
@@ -0,0 +1,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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user