feat(resource): rlimit/rusage/priority
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* vlibc — sys/resource.h test (todo 31).
|
||||
*
|
||||
* Exercises the whole <sys/resource.h> surface end to end:
|
||||
*
|
||||
* 1. getrlimit(RLIMIT_NOFILE): returns 0 with a finite soft limit in
|
||||
* (0, max] — the kernel's own NOFILE ceiling, so cur is never
|
||||
* RLIM_INFINITY. getrlimit(RLIMIT_STACK) is the QA happy shape and
|
||||
* must also report a positive soft limit.
|
||||
* 2. setrlimit round-trip: re-installing the just-read RLIMIT_NOFILE
|
||||
* pair must succeed without privilege (identical values never raise
|
||||
* the hard limit), and a following getrlimit must reflect it.
|
||||
* 3. getrlimit64/setrlimit64 parity: the LFS aliases return the same
|
||||
* values and accept the same writes as the base functions.
|
||||
* 4. getrusage(RUSAGE_SELF) and RUSAGE_THREAD: ru_utime is a sane
|
||||
* timeval and ru_maxrss is non-negative.
|
||||
* 5. getpriority(PRIO_PROCESS, 0) lands in PRIO_MIN..PRIO_MAX, and
|
||||
* setpriority to the same value round-trips (equal values are never
|
||||
* a denied raise).
|
||||
* 6. nice(0) returns the current nice value: no change, no error.
|
||||
* 7. Constant sanity: RLIM_INFINITY is ~0UL, RLIMIT_NLIMITS is 16 and
|
||||
* PRIO_MIN/PRIO_MAX bound the range [-20, 19].
|
||||
*
|
||||
* The -f mode runs the failure shapes — setrlimit/getrusage/getpriority/
|
||||
* setpriority with a bogus resource kind or `which` — 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/resource.h> is entirely level 2, so
|
||||
* at level 1 this TU compiles to a no-op runner.
|
||||
*
|
||||
* Standalone build (list the src/resource/ files individually, or the
|
||||
* shell glob would trip -Wcomment in this header block):
|
||||
* gcc -Iinclude -DVLIBC_LEVEL=2 -std=c23 -Wall -Wextra -pedantic -O2 \
|
||||
* -o /tmp/t31 tests/test_resource.c \
|
||||
* src/resource/getrlimit.c src/resource/setrlimit.c \
|
||||
* src/resource/getrusage.c src/resource/getpriority.c \
|
||||
* src/resource/setpriority.c src/resource/nice.c \
|
||||
* src/internal/errno.c src/internal/syscall_ret.c
|
||||
*/
|
||||
|
||||
#include <sys/resource.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)
|
||||
|
||||
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. getrlimit reads sane, finite NOFILE and STACK soft limits. */
|
||||
static void
|
||||
rlimit_read_scenario(void)
|
||||
{
|
||||
struct rlimit rl;
|
||||
|
||||
check(getrlimit(RLIMIT_NOFILE, &rl) == 0, "getrlimit(RLIMIT_NOFILE) returns 0");
|
||||
check(rl.rlim_cur > 0, "getrlimit NOFILE rlim_cur is positive");
|
||||
check(rl.rlim_max > 0 && rl.rlim_max >= rl.rlim_cur,
|
||||
"getrlimit NOFILE rlim_max is positive and at least rlim_cur");
|
||||
|
||||
check(getrlimit(RLIMIT_STACK, &rl) == 0, "getrlimit(RLIMIT_STACK) returns 0");
|
||||
check(rl.rlim_cur > 0, "getrlimit STACK rlim_cur is positive");
|
||||
}
|
||||
|
||||
/* 2. Re-installing the same limit pair succeeds and round-trips. */
|
||||
static void
|
||||
setrlimit_roundtrip_scenario(void)
|
||||
{
|
||||
struct rlimit before;
|
||||
struct rlimit after;
|
||||
|
||||
if (getrlimit(RLIMIT_NOFILE, &before) != 0)
|
||||
{
|
||||
check(0, "getrlimit(RLIMIT_NOFILE) for the round-trip returns 0");
|
||||
return;
|
||||
}
|
||||
check(setrlimit(RLIMIT_NOFILE, &before) == 0, "setrlimit of identical NOFILE pair returns 0");
|
||||
check(getrlimit(RLIMIT_NOFILE, &after) == 0, "getrlimit after setrlimit returns 0");
|
||||
check(after.rlim_cur == before.rlim_cur && after.rlim_max == before.rlim_max,
|
||||
"getrlimit reflects the re-installed NOFILE pair");
|
||||
}
|
||||
|
||||
/* 3. The LFS aliases read and write exactly like the base functions. */
|
||||
static void
|
||||
rlimit64_alias_scenario(void)
|
||||
{
|
||||
struct rlimit a;
|
||||
struct rlimit b;
|
||||
|
||||
check(getrlimit64(RLIMIT_NOFILE, &a) == 0, "getrlimit64(RLIMIT_NOFILE) returns 0");
|
||||
check(getrlimit(RLIMIT_NOFILE, &b) == 0, "getrlimit(RLIMIT_NOFILE) returns 0");
|
||||
check(a.rlim_cur == b.rlim_cur && a.rlim_max == b.rlim_max,
|
||||
"getrlimit64 agrees with getrlimit");
|
||||
check(setrlimit64(RLIMIT_NOFILE, &a) == 0, "setrlimit64 of identical pair returns 0");
|
||||
check(getrlimit(RLIMIT_NOFILE, &b) == 0, "getrlimit after setrlimit64 returns 0");
|
||||
check(b.rlim_cur == a.rlim_cur && b.rlim_max == a.rlim_max,
|
||||
"setrlimit64 write is visible through getrlimit");
|
||||
}
|
||||
|
||||
/* 4. getrusage reports sane accounting for the process and the thread. */
|
||||
static void
|
||||
getrusage_scenario(void)
|
||||
{
|
||||
struct rusage ru;
|
||||
|
||||
check(getrusage(RUSAGE_SELF, &ru) == 0, "getrusage(RUSAGE_SELF) returns 0");
|
||||
check(ru.ru_utime.tv_sec >= 0, "getrusage ru_utime.tv_sec is non-negative");
|
||||
check(ru.ru_utime.tv_usec >= 0 && ru.ru_utime.tv_usec < 1000000,
|
||||
"getrusage ru_utime.tv_usec is in [0, 1e6)");
|
||||
check(ru.ru_maxrss >= 0, "getrusage ru_maxrss is non-negative");
|
||||
|
||||
check(getrusage(RUSAGE_THREAD, &ru) == 0, "getrusage(RUSAGE_THREAD) returns 0");
|
||||
check(ru.ru_utime.tv_sec >= 0, "getrusage THREAD ru_utime.tv_sec is non-negative");
|
||||
}
|
||||
|
||||
/* 5. getpriority/setpriority round-trip on the current process. */
|
||||
static void
|
||||
priority_roundtrip_scenario(void)
|
||||
{
|
||||
int p;
|
||||
|
||||
p = getpriority(PRIO_PROCESS, 0);
|
||||
check(p >= PRIO_MIN && p <= PRIO_MAX,
|
||||
"getpriority(PRIO_PROCESS, 0) is in [PRIO_MIN, PRIO_MAX]");
|
||||
check(setpriority(PRIO_PROCESS, 0, p) == 0, "setpriority to the current value returns 0");
|
||||
check(getpriority(PRIO_PROCESS, 0) == p, "getpriority round-trips through setpriority");
|
||||
}
|
||||
|
||||
/* 6. nice(0) reports the current nice value without changing it. */
|
||||
static void
|
||||
nice_scenario(void)
|
||||
{
|
||||
int cur;
|
||||
int n;
|
||||
|
||||
cur = getpriority(PRIO_PROCESS, 0);
|
||||
check(cur >= PRIO_MIN && cur <= PRIO_MAX, "nice baseline is in [PRIO_MIN, PRIO_MAX]");
|
||||
n = nice(0);
|
||||
check(n == cur, "nice(0) returns the current nice value");
|
||||
check(getpriority(PRIO_PROCESS, 0) == cur, "nice(0) left the nice value unchanged");
|
||||
}
|
||||
|
||||
/* 7. Constant sanity. */
|
||||
static void
|
||||
constant_scenario(void)
|
||||
{
|
||||
check(RLIM_INFINITY == ~0UL, "RLIM_INFINITY is ~0UL");
|
||||
check(RLIMIT_NLIMITS == 16, "RLIMIT_NLIMITS is 16");
|
||||
check(PRIO_MIN == -20 && PRIO_MAX == 19, "PRIO_MIN..PRIO_MAX is -20..19");
|
||||
check(RUSAGE_SELF == 0 && RUSAGE_CHILDREN == -1 && RUSAGE_THREAD == 1,
|
||||
"RUSAGE_SELF/CHILDREN/THREAD are 0/-1/1");
|
||||
}
|
||||
|
||||
/* 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 rlimit rl;
|
||||
struct rusage ru;
|
||||
|
||||
rl.rlim_cur = 0;
|
||||
rl.rlim_max = 0;
|
||||
|
||||
check(setrlimit(9999, &rl) == -1, "-f setrlimit(9999) returns -1");
|
||||
check(getrlimit(9999, &rl) == -1, "-f getrlimit(9999) returns -1");
|
||||
check(getrusage(999, &ru) == -1, "-f getrusage(999) returns -1");
|
||||
check(getpriority(999, 0) == -1, "-f getpriority(999, 0) returns -1");
|
||||
check(setpriority(999, 0, 0) == -1, "-f setpriority(999, 0, 0) returns -1");
|
||||
|
||||
if (failures == 0)
|
||||
{
|
||||
say(1, "all resource 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();
|
||||
}
|
||||
|
||||
rlimit_read_scenario();
|
||||
setrlimit_roundtrip_scenario();
|
||||
rlimit64_alias_scenario();
|
||||
getrusage_scenario();
|
||||
priority_roundtrip_scenario();
|
||||
nice_scenario();
|
||||
constant_scenario();
|
||||
|
||||
if (failures == 0)
|
||||
{
|
||||
say(1, "all resource tests passed\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
say(1, "FAILURES\n");
|
||||
}
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
#else /* !VLIBC_LEVEL_GE(2) */
|
||||
|
||||
/*
|
||||
* Level 1: every <sys/resource.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/resource.h> is level 2, not available here\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* VLIBC_LEVEL_GE(2) */
|
||||
Reference in New Issue
Block a user