feat(statvfs): statvfs/fstatvfs (L2 XSI)
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
#ifndef VLIBC_SYS_STATVFS_H
|
||||
#define VLIBC_SYS_STATVFS_H
|
||||
|
||||
/*
|
||||
* vlibc — <sys/statvfs.h>.
|
||||
*
|
||||
* Filesystem statistics in an ABI-independent POSIX shape: block and file
|
||||
* counts as unsigned counts over a reported block size, plus the read-only
|
||||
* and setuid-disabling mount flags and the longest file name. This header
|
||||
* carries only the portable view; the Linux kernel's private <linux/statfs.h>
|
||||
* layout (signed longs, an int-pair f_fsid, a f_spare tail) never leaks
|
||||
* into it.
|
||||
*
|
||||
* Level 2 (muslmimic): the whole header (statvfs and fstatvfs are XSI
|
||||
* [CX] in POSIX.1-2008, not base; the base filesystem
|
||||
* queries are stat()/fstat() in <sys/stat.h>).
|
||||
*
|
||||
* f_flag holds ST_RDONLY and/or ST_NOSUID, and f_fsid is a best-effort
|
||||
* filesystem identifier (the kernel does not populate it for every
|
||||
* filesystem). The block counts are measured in f_frsize units (which is
|
||||
* f_bsize when the filesystem does not distinguish a transfer size).
|
||||
*
|
||||
* None of these declarations carries an intent attribute: both functions
|
||||
* perform I/O with side effects and report failures through errno, so
|
||||
* const/pure would be unsound.
|
||||
*/
|
||||
|
||||
#include <vlibc/features.h>
|
||||
|
||||
#include <sys/types.h> /* fsblkcnt_t, fsfilcnt_t */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
/* Level 2 (muslmimic): XSI. */
|
||||
|
||||
/*
|
||||
* Filesystem statistics. f_flag is a bitwise OR of ST_RDONLY and
|
||||
* ST_NOSUID; f_frsize is the fundamental block size the count fields are
|
||||
* expressed in. f_fsid is 0 when the filesystem provides no identifier.
|
||||
*/
|
||||
struct statvfs
|
||||
{
|
||||
unsigned long f_bsize; /* preferred I/O block size, bytes */
|
||||
unsigned long f_frsize; /* fundamental block size, bytes */
|
||||
fsblkcnt_t f_blocks; /* total blocks, in f_frsize units */
|
||||
fsblkcnt_t f_bfree; /* free blocks */
|
||||
fsblkcnt_t f_bavail; /* free blocks available to an unprivileged process */
|
||||
fsfilcnt_t f_files; /* total file serial numbers (inodes) */
|
||||
fsfilcnt_t f_ffree; /* free file serial numbers */
|
||||
fsfilcnt_t f_favail; /* free serial numbers available to an unprivileged process */
|
||||
unsigned long f_fsid; /* filesystem ID (best effort) */
|
||||
unsigned long f_flag; /* mount flags: ST_RDONLY | ST_NOSUID */
|
||||
unsigned long f_namemax; /* maximum file name length */
|
||||
};
|
||||
|
||||
/* f_flag values (also the kernel's f_flags bits on Linux). */
|
||||
#define ST_RDONLY 1 /* read-only filesystem */
|
||||
#define ST_NOSUID 2 /* set-user-ID/set-group-ID bits ignored on exec */
|
||||
|
||||
/*
|
||||
* Store statistics for the filesystem holding path into buf; return 0, or
|
||||
* -1 with errno set. XSI.
|
||||
*/
|
||||
int
|
||||
statvfs(const char *restrict path, struct statvfs *restrict buf);
|
||||
|
||||
/*
|
||||
* Like statvfs(), for the filesystem holding the open descriptor fildes;
|
||||
* return 0, or -1 with errno set. XSI.
|
||||
*/
|
||||
int
|
||||
fstatvfs(int fildes, struct statvfs *buf);
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* VLIBC_SYS_STATVFS_H */
|
||||
@@ -0,0 +1,88 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <sys/statvfs.h>
|
||||
|
||||
#include "../internal/syscall.h"
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
|
||||
/*
|
||||
* Linux struct statfs (kernel ABI, x86_64; linux/statfs.h). Layout and
|
||||
* sizes are LP64 facts: f_type..f_ffree are longs, f_fsid is an int pair,
|
||||
* then f_namelen/f_frsize/f_flags/f_spare[4] complete the 120 bytes the
|
||||
* kernel copies out (the same layout src/misc/pathconf.c pins for
|
||||
* pathconf()/fpathconf()). The signed kernel longs are non-negative on
|
||||
* every real filesystem, so they convert straight into the unsigned POSIX
|
||||
* counts below.
|
||||
*/
|
||||
struct vlibc_kstatfs
|
||||
{
|
||||
long f_type;
|
||||
long f_bsize;
|
||||
long f_blocks;
|
||||
long f_bfree;
|
||||
long f_bavail;
|
||||
long f_files;
|
||||
long f_ffree;
|
||||
int f_fsid[2];
|
||||
long f_namelen;
|
||||
long f_frsize;
|
||||
long f_flags;
|
||||
long f_spare[4];
|
||||
};
|
||||
|
||||
/* Map one kernel statfs record into the portable POSIX statvfs view. */
|
||||
static void
|
||||
statvfs_fill(struct statvfs *out, const struct vlibc_kstatfs *in)
|
||||
{
|
||||
out->f_bsize = (unsigned long)in->f_bsize;
|
||||
out->f_frsize = in->f_frsize != 0 ? (unsigned long)in->f_frsize : (unsigned long)in->f_bsize;
|
||||
out->f_blocks = (unsigned long)in->f_blocks;
|
||||
out->f_bfree = (unsigned long)in->f_bfree;
|
||||
out->f_bavail = (unsigned long)in->f_bavail;
|
||||
out->f_files = (unsigned long)in->f_files;
|
||||
out->f_ffree = (unsigned long)in->f_ffree;
|
||||
out->f_favail = (unsigned long)in->f_ffree;
|
||||
/* Reconstruct the kernel's 64-bit fsid (val[0] holds the low word on
|
||||
* the little-endian x86_64; the same packing glibc's statvfs reports). */
|
||||
out->f_fsid = ((unsigned long)(unsigned int)in->f_fsid[1] << 32) |
|
||||
(unsigned long)(unsigned int)in->f_fsid[0];
|
||||
out->f_flag = (unsigned long)(in->f_flags & (ST_RDONLY | ST_NOSUID));
|
||||
out->f_namemax = (unsigned long)in->f_namelen;
|
||||
}
|
||||
|
||||
/*
|
||||
* statvfs()/fstatvfs(): filesystem statistics in the portable POSIX shape,
|
||||
* over SYS_statfs/SYS_fstatfs (which share the same kernel struct and the
|
||||
* same mapping). The kernel returns 0 on success after filling all 120
|
||||
* bytes; failures come back as -errno and are translated by syscall_ret.
|
||||
*/
|
||||
int
|
||||
statvfs(const char *restrict path, struct statvfs *restrict buf)
|
||||
{
|
||||
struct vlibc_kstatfs fs;
|
||||
|
||||
if (syscall_ret(__syscall2(SYS_statfs, (long)path, (long)&fs)) != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
statvfs_fill(buf, &fs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
fstatvfs(int fildes, struct statvfs *buf)
|
||||
{
|
||||
struct vlibc_kstatfs fs;
|
||||
|
||||
if (syscall_ret(__syscall2(SYS_fstatfs, fildes, (long)&fs)) != 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
statvfs_fill(buf, &fs);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* vlibc — sys/statvfs.h test (todo 34).
|
||||
*
|
||||
* Exercises the whole <sys/statvfs.h> surface end to end:
|
||||
*
|
||||
* 1. statvfs("/") returns 0 with sane values: a positive block size, a
|
||||
* positive transfer size, a positive total block count whose free
|
||||
* and available sub-counts stay within it (unsigned compares — all
|
||||
* counts are fsblkcnt_t/fsfilcnt_t = unsigned long), and a positive
|
||||
* maximum name length.
|
||||
* 2. fstatvfs on a raw-open descriptor of "/" returns 0 and agrees
|
||||
* with statvfs("/") on the stable fields (block size, total blocks,
|
||||
* name length), since both name the same filesystem.
|
||||
*
|
||||
* The -f mode runs the failure shapes — statvfs on a nonexistent path and
|
||||
* fstatvfs on a closed descriptor (-1) — asserting return values only
|
||||
* (errno is never read: vlibc's errno slot collides with the host TCB). It
|
||||
* leaves via a raw SYS_exit_group so no host cleanup runs after the
|
||||
* library's errno writes (house pattern, tests/test_systime.c).
|
||||
*
|
||||
* No host libc headers are included (the -Iinclude path would shadow
|
||||
* GCC's internal headers); diagnostics go through raw SYS_write. The whole
|
||||
* body mirrors the header's gate: <sys/statvfs.h> is entirely level 2, so
|
||||
* at level 1 this TU compiles to a no-op runner.
|
||||
*/
|
||||
|
||||
#include <sys/statvfs.h>
|
||||
|
||||
#include "../src/internal/syscall.h"
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
#if VLIBC_LEVEL_GE(2)
|
||||
|
||||
/* Kernel-UAPI open values, local to this test (include/fcntl.h). */
|
||||
#define T34_AT_FDCWD (-100)
|
||||
#define T34_O_RDONLY 0
|
||||
|
||||
#define T34_MISSING "/nonexistent-vlibc-t34-xyz"
|
||||
|
||||
static int failures;
|
||||
|
||||
static void
|
||||
check(int ok, const char *msg)
|
||||
{
|
||||
if (ok)
|
||||
{
|
||||
say(1, "ok ");
|
||||
}
|
||||
else
|
||||
{
|
||||
say(1, "FAIL ");
|
||||
failures++;
|
||||
}
|
||||
say(1, msg);
|
||||
say(1, "\n");
|
||||
}
|
||||
|
||||
/* 1 + 2. statvfs("/") sanity, then fstatvfs on a raw descriptor of "/". */
|
||||
static void
|
||||
happy_scenarios(void)
|
||||
{
|
||||
struct statvfs s = {0};
|
||||
struct statvfs d = {0};
|
||||
long fd;
|
||||
|
||||
check(statvfs("/", &s) == 0, "statvfs(\"/\") returns 0");
|
||||
check(s.f_bsize > 0, "statvfs f_bsize > 0");
|
||||
check(s.f_frsize > 0, "statvfs f_frsize > 0");
|
||||
check(s.f_blocks > 0, "statvfs f_blocks > 0");
|
||||
check(s.f_bfree <= s.f_blocks, "statvfs f_bfree within f_blocks (unsigned)");
|
||||
check(s.f_bavail <= s.f_bfree, "statvfs f_bavail within f_bfree (unsigned)");
|
||||
check(s.f_namemax > 0, "statvfs f_namemax > 0");
|
||||
|
||||
fd = __syscall4(SYS_openat, T34_AT_FDCWD, (long)"/", T34_O_RDONLY, 0);
|
||||
check(fd >= 0, "raw openat(\"/\") opens the root directory");
|
||||
if (fd >= 0)
|
||||
{
|
||||
check(fstatvfs((int)fd, &d) == 0, "fstatvfs(fd of \"/\") returns 0");
|
||||
check(d.f_bsize == s.f_bsize, "fstatvfs f_bsize matches statvfs");
|
||||
check(d.f_blocks == s.f_blocks, "fstatvfs f_blocks matches statvfs");
|
||||
check(d.f_namemax == s.f_namemax, "fstatvfs f_namemax matches statvfs");
|
||||
check(__syscall1(SYS_close, fd) == 0, "raw close of the root descriptor");
|
||||
}
|
||||
|
||||
if (failures == 0)
|
||||
{
|
||||
say(1, "all statvfs tests passed\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
say(1, "FAILURES\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* The failure shapes; return values only, never errno. Exit via a 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, tests/test_systime.c). */
|
||||
static void
|
||||
failure_scenarios(void)
|
||||
{
|
||||
struct statvfs s = {0};
|
||||
|
||||
check(statvfs(T34_MISSING, &s) == -1, "-f statvfs(nonexistent) returns -1");
|
||||
check(fstatvfs(-1, &s) == -1, "-f fstatvfs(-1) returns -1");
|
||||
|
||||
if (failures == 0)
|
||||
{
|
||||
say(1, "all statvfs failure scenarios passed\n");
|
||||
}
|
||||
__syscall1(SYS_exit_group, failures == 0 ? 0 : 1);
|
||||
/* not reached */
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'f' && argv[1][2] == '\0')
|
||||
{
|
||||
failure_scenarios();
|
||||
}
|
||||
|
||||
happy_scenarios();
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
#else /* !VLIBC_LEVEL_GE(2) */
|
||||
|
||||
/*
|
||||
* Level 1: every <sys/statvfs.h> symbol is gated at level 2, so there is
|
||||
* nothing to run. Keep the TU compilable at any configured profile.
|
||||
*/
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
say(1, "SKIP: <sys/statvfs.h> is level 2, not available here\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
Reference in New Issue
Block a user