feat(systime): gettimeofday/itimer/utimes
This commit is contained in:
@@ -86,12 +86,20 @@ typedef struct
|
|||||||
typedef unsigned long sigset_t;
|
typedef unsigned long sigset_t;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Elapsed time in seconds and microseconds (see select()'s timeout). */
|
/*
|
||||||
|
* Elapsed time in seconds and microseconds (see select()'s timeout).
|
||||||
|
* Shared guard with <sys/time.h>, the canonical POSIX home of struct
|
||||||
|
* timeval: both headers define the same layout under
|
||||||
|
* VLIBC_TIMEVAL_DEFINED, so either may be included first at any level.
|
||||||
|
*/
|
||||||
|
#ifndef VLIBC_TIMEVAL_DEFINED
|
||||||
|
#define VLIBC_TIMEVAL_DEFINED
|
||||||
struct timeval
|
struct timeval
|
||||||
{
|
{
|
||||||
time_t tv_sec;
|
time_t tv_sec;
|
||||||
suseconds_t tv_usec;
|
suseconds_t tv_usec;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Wait for readiness on the descriptors marked in readfds, writefds and
|
* Wait for readiness on the descriptors marked in readfds, writefds and
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
#ifndef VLIBC_SYS_TIME_H
|
||||||
|
#define VLIBC_SYS_TIME_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vlibc — <sys/time.h>.
|
||||||
|
*
|
||||||
|
* Wall-clock access and per-process interval timers expressed in
|
||||||
|
* microseconds. Everything here is XSI or BSD legacy — POSIX.1-2008 base
|
||||||
|
* leaves only timerisset in this header — so the whole surface is gated at
|
||||||
|
* level 2:
|
||||||
|
*
|
||||||
|
* Level 2 (muslmimic): gettimeofday, settimeofday, getitimer,
|
||||||
|
* setitimer, utimes, futimes, lutimes,
|
||||||
|
* struct timeval, struct itimerval,
|
||||||
|
* struct timezone, the timer* macros.
|
||||||
|
*
|
||||||
|
* struct timeval is the same type <sys/select.h> uses for select()'s
|
||||||
|
* timeout; that header defines it under the shared guard below so the two
|
||||||
|
* headers agree and either may be included first. The layout is the kernel
|
||||||
|
* ABI on x86_64 (two longs), which is why gettimeofday/setitimer pass it
|
||||||
|
* through unmodified.
|
||||||
|
*
|
||||||
|
* ITIMER_REAL/ITIMER_VIRTUAL/ITIMER_PROF are kernel ABI values (the
|
||||||
|
* getitimer/setitimer `which` argument). None of these declarations carries
|
||||||
|
* an intent attribute: every function performs I/O with side effects and
|
||||||
|
* reports failures through errno, so const/pure would be unsound.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vlibc/features.h>
|
||||||
|
|
||||||
|
#include <sys/types.h> /* time_t, suseconds_t */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
/* Level 2 (muslmimic): XSI/BSD date-and-time interfaces. */
|
||||||
|
|
||||||
|
/* Interval-timer kinds (kernel ABI; the `which` argument). */
|
||||||
|
#define ITIMER_REAL 0 /* count down in real time */
|
||||||
|
#define ITIMER_VIRTUAL 1 /* count down in process virtual time */
|
||||||
|
#define ITIMER_PROF 2 /* count down in process virtual + system time */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Elapsed time in seconds and microseconds. Shared guard with
|
||||||
|
* <sys/select.h>, which needs struct timeval for select()'s timeout at
|
||||||
|
* level 1 and defines it under the same macro; either header may therefore
|
||||||
|
* be included first. tv_usec holds 0..999999.
|
||||||
|
*/
|
||||||
|
#ifndef VLIBC_TIMEVAL_DEFINED
|
||||||
|
#define VLIBC_TIMEVAL_DEFINED
|
||||||
|
struct timeval
|
||||||
|
{
|
||||||
|
time_t tv_sec; /* seconds */
|
||||||
|
suseconds_t tv_usec; /* microseconds */
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* An interval timer setting: it_value is the time to the next expiry,
|
||||||
|
* it_interval the period between expiries after the first (both zero for a
|
||||||
|
* one-shot or a disarmed timer). Kernel ABI layout (x86_64): two timevals.
|
||||||
|
*/
|
||||||
|
struct itimerval
|
||||||
|
{
|
||||||
|
struct timeval it_interval; /* interval between periodic expiries */
|
||||||
|
struct timeval it_value; /* time to the next expiry */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Historic timezone record for settimeofday(). POSIX.1-2008 removed struct
|
||||||
|
* timezone; it is kept for the legacy gettimeofday/settimeofday signatures,
|
||||||
|
* whose tz argument is ignored (see src/time/gettimeofday.c).
|
||||||
|
*/
|
||||||
|
struct timezone
|
||||||
|
{
|
||||||
|
int tz_minuteswest; /* minutes west of Greenwich */
|
||||||
|
int tz_dsttime; /* type of daylight-saving correction */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* True when the timer described by tp is armed. */
|
||||||
|
#define timerisset(tp) ((tp)->tv_sec != 0 || (tp)->tv_usec != 0)
|
||||||
|
|
||||||
|
/* Disarm the timer described by tp (zero both fields). */
|
||||||
|
#define timerclear(tp) ((tp)->tv_sec = (tp)->tv_usec = 0)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set *result to *a plus *b, normalizing the carry into tv_sec. The
|
||||||
|
* arguments may be evaluated more than once (classic BSD form).
|
||||||
|
*/
|
||||||
|
#define timeradd(a, b, result) \
|
||||||
|
do \
|
||||||
|
{ \
|
||||||
|
(result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \
|
||||||
|
(result)->tv_usec = (a)->tv_usec + (b)->tv_usec; \
|
||||||
|
if ((result)->tv_usec >= 1000000) \
|
||||||
|
{ \
|
||||||
|
(result)->tv_sec++; \
|
||||||
|
(result)->tv_usec -= 1000000; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set *result to *a minus *b, normalizing the borrow out of tv_sec. The
|
||||||
|
* arguments may be evaluated more than once (classic BSD form).
|
||||||
|
*/
|
||||||
|
#define timersub(a, b, result) \
|
||||||
|
do \
|
||||||
|
{ \
|
||||||
|
(result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
|
||||||
|
(result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
|
||||||
|
if ((result)->tv_usec < 0) \
|
||||||
|
{ \
|
||||||
|
(result)->tv_sec--; \
|
||||||
|
(result)->tv_usec += 1000000; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* True when *a op *b, compared first by tv_sec and, on equality, by
|
||||||
|
* tv_usec. op is a comparison operator (timercmp(x, y, <=)). The
|
||||||
|
* arguments may be evaluated more than once (classic BSD form).
|
||||||
|
*/
|
||||||
|
/* clang-format off */
|
||||||
|
#define timercmp(a, b, op) \
|
||||||
|
(((a)->tv_sec == (b)->tv_sec) ? ((a)->tv_usec op (b)->tv_usec) : ((a)->tv_sec op (b)->tv_sec))
|
||||||
|
/* clang-format on */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read the current wall-clock time into tv (seconds since the Epoch and
|
||||||
|
* microseconds 0..999999). tz is ignored for compatibility with the
|
||||||
|
* historic two-argument form and may be anything, including NULL; tv may
|
||||||
|
* also be NULL, in which case nothing is written. Return 0, or -1 with
|
||||||
|
* errno set when tv points outside the address space.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
gettimeofday(struct timeval *restrict tv, void *restrict tz);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the kernel's idea of the current time from tv. tz, when not NULL, is
|
||||||
|
* honored only together with tv; passing tz alone is unsupported (ENOTSUP).
|
||||||
|
* Requires privilege. Return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
settimeofday(const struct timeval *tv, const struct timezone *tz);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Store the current setting of interval timer which (ITIMER_REAL,
|
||||||
|
* ITIMER_VIRTUAL or ITIMER_PROF) through value. Return 0, or -1 with errno
|
||||||
|
* set for an unknown which or when value points outside the address space.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
getitimer(int which, struct itimerval *value);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Arm interval timer which from value (when value is not NULL) and store
|
||||||
|
* the previous setting through ovalue (when not NULL). A value whose
|
||||||
|
* it_value is zero disarms the timer. Return 0, or -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
setitimer(int which, const struct itimerval *restrict value, struct itimerval *restrict ovalue);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the access (times[0]) and modification (times[1]) timestamps of path.
|
||||||
|
* A NULL times array sets both to the current time. Return 0, or -1 with
|
||||||
|
* errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
utimes(const char *path, const struct timeval times[2]);
|
||||||
|
|
||||||
|
/* utimes() on the open file fd. */
|
||||||
|
int
|
||||||
|
futimes(int fd, const struct timeval times[2]);
|
||||||
|
|
||||||
|
/* utimes() on the symbolic link path itself, never following it. */
|
||||||
|
int
|
||||||
|
lutimes(const char *path, const struct timeval times[2]);
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VLIBC_SYS_TIME_H */
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getitimer: read the current setting of interval timer which (ITIMER_REAL,
|
||||||
|
* ITIMER_VIRTUAL or ITIMER_PROF), via SYS_getitimer. struct itimerval is
|
||||||
|
* layout-identical to the kernel's two-timeval structure on x86_64, so the
|
||||||
|
* buffer passes straight through and the kernel reports whether the timer
|
||||||
|
* is armed (it_value nonzero) and its period (it_interval). An unknown
|
||||||
|
* which or an invalid value pointer is rejected by the kernel (EINVAL /
|
||||||
|
* EFAULT), reported as -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
getitimer(int which, struct itimerval *value)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall2(SYS_getitimer, which, (long)value));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* gettimeofday: the current wall-clock time, via SYS_gettimeofday.
|
||||||
|
*
|
||||||
|
* The kernel writes the two-field struct timeval (tv_sec, tv_usec) that is
|
||||||
|
* layout-identical to the public struct timeval on x86_64, so the pointer
|
||||||
|
* passes straight through. The tz argument is deliberately ignored: the
|
||||||
|
* historic second parameter is obsolete, POSIX.1-2008 removed struct
|
||||||
|
* timezone, and no caller may rely on it being filled — NULL is passed to
|
||||||
|
* the kernel, which then leaves any timezone record untouched. tv may also
|
||||||
|
* be NULL, in which case the kernel writes nothing and 0 is returned.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
gettimeofday(struct timeval *restrict tv, void *restrict tz)
|
||||||
|
{
|
||||||
|
(void)tz;
|
||||||
|
return syscall_ret(__syscall2(SYS_gettimeofday, (long)tv, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* setitimer: arm (or disarm) interval timer which (ITIMER_REAL,
|
||||||
|
* ITIMER_VIRTUAL or ITIMER_PROF), via SYS_setitimer. struct itimerval is
|
||||||
|
* layout-identical to the kernel's two-timeval structure on x86_64, so both
|
||||||
|
* pointers pass straight through: value NULL leaves the timer unchanged,
|
||||||
|
* it_value zero disarms it, and the previous setting is stored through
|
||||||
|
* ovalue when it is not NULL. An unknown which or an invalid pointer is
|
||||||
|
* rejected by the kernel (EINVAL / EFAULT), reported as -1 with errno set.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
setitimer(int which, const struct itimerval *restrict value, struct itimerval *restrict ovalue)
|
||||||
|
{
|
||||||
|
return syscall_ret(__syscall3(SYS_setitimer, which, (long)value, (long)ovalue));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* settimeofday: set the kernel clock, via SYS_settimeofday.
|
||||||
|
*
|
||||||
|
* Requires privilege (the unprivileged caller gets EPERM from the kernel).
|
||||||
|
* The tz argument is honored only alongside tv; passing a timezone record
|
||||||
|
* while tv is NULL would ask the kernel to update its timezone state alone,
|
||||||
|
* a legacy use no caller needs, so it is refused up front with ENOTSUP (the
|
||||||
|
* Linux spelling of the value musl reports here; <errno.h> carries it as
|
||||||
|
* EOPNOTSUPP).
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
settimeofday(const struct timeval *tv, const struct timezone *tz)
|
||||||
|
{
|
||||||
|
if (tz != NULL && tv == NULL)
|
||||||
|
{
|
||||||
|
errno = EOPNOTSUPP;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return syscall_ret(__syscall2(SYS_settimeofday, (long)tv, (long)tz));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#include "../internal/syscall.h"
|
||||||
|
|
||||||
|
#if VLIBC_LEVEL_GE(2)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* utimes/futimes/lutimes: set file timestamps with microsecond precision,
|
||||||
|
* over SYS_utimensat (the dedicated SYS_utimes syscall is legacy and only
|
||||||
|
* accepts second+microsecond values through a translated struct; utimensat
|
||||||
|
* is the modern ABI and covers all three forms). The classic syscall family
|
||||||
|
* takes two struct timeval values, so the timeval pair is converted to the
|
||||||
|
* kernel timespec pair (tv_usec * 1000 -> tv_nsec) the way glibc's own
|
||||||
|
* wrapper does; a NULL times array is passed through unchanged and the
|
||||||
|
* kernel then sets both timestamps to the current time.
|
||||||
|
*
|
||||||
|
* The kernel AT_ flag values below come from <fcntl.h> (todo 21) and are
|
||||||
|
* transcribed locally with the house prefix so this translation unit stays
|
||||||
|
* self-contained.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define VLIBC_SYS_TIME_AT_FDCWD (-100) /* resolve path from cwd */
|
||||||
|
#define VLIBC_SYS_TIME_AT_SYMLINK_NOFOLLOW 0x100 /* operate on the link */
|
||||||
|
|
||||||
|
/* Convert one timeval into the kernel's timespec layout. */
|
||||||
|
static void
|
||||||
|
systime_to_timespec(const struct timeval *tv, struct timespec *ts)
|
||||||
|
{
|
||||||
|
ts->tv_sec = tv->tv_sec;
|
||||||
|
ts->tv_nsec = (long)tv->tv_usec * 1000L;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* utimes: set path's access (times[0]) and modification (times[1])
|
||||||
|
* timestamps; NULL times sets both to the current time.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
utimes(const char *path, const struct timeval times[2])
|
||||||
|
{
|
||||||
|
struct timespec kernel_times[2];
|
||||||
|
|
||||||
|
if (times != NULL)
|
||||||
|
{
|
||||||
|
systime_to_timespec(×[0], &kernel_times[0]);
|
||||||
|
systime_to_timespec(×[1], &kernel_times[1]);
|
||||||
|
return syscall_ret(
|
||||||
|
__syscall4(SYS_utimensat, VLIBC_SYS_TIME_AT_FDCWD, (long)path, (long)kernel_times, 0));
|
||||||
|
}
|
||||||
|
return syscall_ret(__syscall4(SYS_utimensat, VLIBC_SYS_TIME_AT_FDCWD, (long)path, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* futimes: utimes on the open file fd. On Linux, utimensat with a NULL path
|
||||||
|
* operates on the file referenced by the directory descriptor, which for a
|
||||||
|
* plain descriptor is the file itself.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
futimes(int fd, const struct timeval times[2])
|
||||||
|
{
|
||||||
|
struct timespec kernel_times[2];
|
||||||
|
|
||||||
|
if (times != NULL)
|
||||||
|
{
|
||||||
|
systime_to_timespec(×[0], &kernel_times[0]);
|
||||||
|
systime_to_timespec(×[1], &kernel_times[1]);
|
||||||
|
return syscall_ret(__syscall4(SYS_utimensat, fd, 0, (long)kernel_times, 0));
|
||||||
|
}
|
||||||
|
return syscall_ret(__syscall4(SYS_utimensat, fd, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* lutimes: utimes on the symbolic link path itself. AT_SYMLINK_NOFOLLOW
|
||||||
|
* makes utimensat update the link's own timestamps rather than its target's.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
lutimes(const char *path, const struct timeval times[2])
|
||||||
|
{
|
||||||
|
struct timespec kernel_times[2];
|
||||||
|
|
||||||
|
if (times != NULL)
|
||||||
|
{
|
||||||
|
systime_to_timespec(×[0], &kernel_times[0]);
|
||||||
|
systime_to_timespec(×[1], &kernel_times[1]);
|
||||||
|
return syscall_ret(__syscall4(SYS_utimensat, VLIBC_SYS_TIME_AT_FDCWD, (long)path,
|
||||||
|
(long)kernel_times, VLIBC_SYS_TIME_AT_SYMLINK_NOFOLLOW));
|
||||||
|
}
|
||||||
|
return syscall_ret(__syscall4(SYS_utimensat, VLIBC_SYS_TIME_AT_FDCWD, (long)path, 0,
|
||||||
|
VLIBC_SYS_TIME_AT_SYMLINK_NOFOLLOW));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
@@ -0,0 +1,373 @@
|
|||||||
|
/*
|
||||||
|
* vlibc — sys/time.h test (todo 27).
|
||||||
|
*
|
||||||
|
* Exercises the whole <sys/time.h> surface end to end:
|
||||||
|
*
|
||||||
|
* 1. gettimeofday: plausible post-2001 epoch with tv_usec in [0, 1e6);
|
||||||
|
* a second call reads a time no earlier than the first; the
|
||||||
|
* (tv, tz) = (NULL, NULL) form returns 0 without writing anything.
|
||||||
|
* 2. The timer* macros: timerclear/timerisset, timeradd/timersub carry
|
||||||
|
* and borrow, timercmp ordering.
|
||||||
|
* 3. ITIMER_REAL: arm a 20 ms one-shot, then poll getitimer (with raw
|
||||||
|
* 1 ms nanosleeps) until it reports a disarmed timer. SIGALRM is
|
||||||
|
* blocked first via raw rt_sigprocmask so the expiry cannot terminate
|
||||||
|
* the process; the timer still disarms on expiry. The elapsed time
|
||||||
|
* must be at least 5 ms (proving a real wait, not an instant cancel)
|
||||||
|
* and under 500 ms (proving the timer actually ran down).
|
||||||
|
* 4. utimes/futimes/lutimes on a scratch file + symlink: stat (todo 22)
|
||||||
|
* confirms the access and modification timestamps land with the exact
|
||||||
|
* second + microsecond-derived nanosecond values, and that lutimes
|
||||||
|
* changed the link's own timestamps, never the target's.
|
||||||
|
*
|
||||||
|
* The -f mode runs the failure shapes — setitimer/getitimer with an
|
||||||
|
* unknown `which`, settimeofday with tz alone (ENOTSUP), utimes/futimes/
|
||||||
|
* lutimes on nonexistent targets — 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_stat.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/time.h> is entirely level 2, so at
|
||||||
|
* level 1 this TU compiles to a no-op runner.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include <sys/time.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/at values, local to this test (include/fcntl.h). */
|
||||||
|
#define T27_AT_FDCWD (-100)
|
||||||
|
#define T27_O_RDWR 0x2
|
||||||
|
#define T27_O_CREAT 0x40
|
||||||
|
#define T27_O_EXCL 0x80
|
||||||
|
|
||||||
|
/* SIGALRM number and its bit in a one-word kernel sigset. */
|
||||||
|
#define T27_SIGALRM 14
|
||||||
|
#define T27_SIG_BLOCK 0
|
||||||
|
|
||||||
|
/* Scratch paths in /tmp (the test runs from an arbitrary cwd). */
|
||||||
|
#define T27_FILE "/tmp/vlibc_systime_t27_file"
|
||||||
|
#define T27_LINK "/tmp/vlibc_systime_t27_link"
|
||||||
|
#define T27_LINK_TARGET "vlibc_systime_t27_file"
|
||||||
|
#define T27_MISSING "/nonexistent-vlibc-t27-xyz"
|
||||||
|
|
||||||
|
static int failures;
|
||||||
|
|
||||||
|
/* Monotonic clock reading in milliseconds (raw SYS_clock_gettime). */
|
||||||
|
static long
|
||||||
|
mono_ms(void)
|
||||||
|
{
|
||||||
|
struct timespec ts;
|
||||||
|
|
||||||
|
if (__syscall2(SYS_clock_gettime, 1, (long)&ts) == 0)
|
||||||
|
{
|
||||||
|
return ts.tv_sec * 1000L + ts.tv_nsec / 1000000L;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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. gettimeofday: epoch sanity, tv_usec range, non-decreasing reads. */
|
||||||
|
static void
|
||||||
|
gettimeofday_scenario(void)
|
||||||
|
{
|
||||||
|
struct timeval a;
|
||||||
|
struct timeval b;
|
||||||
|
|
||||||
|
check(gettimeofday(&a, NULL) == 0, "gettimeofday(tv, NULL) returns 0");
|
||||||
|
check(a.tv_sec > 1000000000, "gettimeofday tv_sec is past 2001");
|
||||||
|
check(a.tv_usec >= 0 && a.tv_usec < 1000000, "gettimeofday tv_usec in [0, 1e6)");
|
||||||
|
check(gettimeofday(NULL, NULL) == 0, "gettimeofday(NULL, NULL) returns 0");
|
||||||
|
check(gettimeofday(&b, NULL) == 0, "second gettimeofday returns 0");
|
||||||
|
check(b.tv_sec > a.tv_sec || (b.tv_sec == a.tv_sec && b.tv_usec >= a.tv_usec),
|
||||||
|
"gettimeofday reads do not go backwards");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. The timer* arithmetic macros. */
|
||||||
|
static void
|
||||||
|
timer_macro_scenario(void)
|
||||||
|
{
|
||||||
|
struct timeval a = {1, 500000};
|
||||||
|
struct timeval b = {0, 900000};
|
||||||
|
struct timeval r;
|
||||||
|
|
||||||
|
timerclear(&r);
|
||||||
|
check(!timerisset(&r), "timerclear zeroes a timeval");
|
||||||
|
check(timerisset(&a), "timerisset true for an armed timeval");
|
||||||
|
|
||||||
|
timersub(&a, &b, &r);
|
||||||
|
check(r.tv_sec == 0 && r.tv_usec == 600000, "timersub borrows 1 s into 600000 us");
|
||||||
|
timeradd(&b, &r, &r);
|
||||||
|
check(r.tv_sec == 1 && r.tv_usec == 500000, "timeradd carries 1e6 us into tv_sec");
|
||||||
|
check(timercmp(&a, &r, ==), "timeradd/timersub round-trip");
|
||||||
|
|
||||||
|
check(timercmp(&a, &b, >), "timercmp(a, b, >) when a is later");
|
||||||
|
check(timercmp(&b, &a, <), "timercmp(b, a, <) when b is earlier");
|
||||||
|
check(!timercmp(&a, &b, ==), "timercmp(a, b, ==) false on unequal values");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. ITIMER_REAL: a 20 ms one-shot runs down and disarms. */
|
||||||
|
static void
|
||||||
|
itimer_scenario(void)
|
||||||
|
{
|
||||||
|
struct itimerval zero = {{0, 0}, {0, 0}};
|
||||||
|
struct itimerval it = {{0, 0}, {0, 20000}};
|
||||||
|
struct itimerval got;
|
||||||
|
struct timespec nap = {0, 1000000L};
|
||||||
|
unsigned long sigalrm_bit = 1UL << (T27_SIGALRM - 1);
|
||||||
|
long t0;
|
||||||
|
long elapsed;
|
||||||
|
int fired = 0;
|
||||||
|
int attempt;
|
||||||
|
|
||||||
|
check(__syscall4(SYS_rt_sigprocmask, T27_SIG_BLOCK, (long)&sigalrm_bit, 0, 8) == 0,
|
||||||
|
"raw rt_sigprocmask blocks SIGALRM");
|
||||||
|
|
||||||
|
for (attempt = 0; attempt < 5 && !fired; attempt++)
|
||||||
|
{
|
||||||
|
long r;
|
||||||
|
|
||||||
|
setitimer(ITIMER_REAL, &zero, NULL); /* disarm any leftover expiry */
|
||||||
|
t0 = mono_ms();
|
||||||
|
check(setitimer(ITIMER_REAL, &it, NULL) == 0, "setitimer(ITIMER_REAL, 20ms) returns 0");
|
||||||
|
r = getitimer(ITIMER_REAL, &got);
|
||||||
|
if (r != 0)
|
||||||
|
{
|
||||||
|
check(0, "getitimer(ITIMER_REAL) returns 0");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (got.it_value.tv_sec == 0 && got.it_value.tv_usec == 0)
|
||||||
|
{
|
||||||
|
continue; /* the whole interval elapsed before the first read */
|
||||||
|
}
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
long now;
|
||||||
|
|
||||||
|
if (getitimer(ITIMER_REAL, &got) != 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (got.it_value.tv_sec == 0 && got.it_value.tv_usec == 0)
|
||||||
|
{
|
||||||
|
fired = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
now = mono_ms();
|
||||||
|
if (now < 0 || now - t0 >= 500)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
__syscall2(SYS_nanosleep, (long)&nap, 0);
|
||||||
|
}
|
||||||
|
if (!fired)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
elapsed = mono_ms() - t0;
|
||||||
|
check(elapsed >= 5 && elapsed < 500,
|
||||||
|
"20ms ITIMER_REAL fired after a real (5..500 ms) wait");
|
||||||
|
}
|
||||||
|
check(fired, "ITIMER_REAL timer expired on its own");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. utimes/futimes/lutimes set the exact timestamps stat() reports. */
|
||||||
|
static void
|
||||||
|
utimes_scenario(void)
|
||||||
|
{
|
||||||
|
struct timeval tv[2];
|
||||||
|
struct timeval tv2[2];
|
||||||
|
struct timeval tv3[2];
|
||||||
|
struct stat st = {0};
|
||||||
|
struct stat lst = {0};
|
||||||
|
static const char payload[] = "vlibc systime\n";
|
||||||
|
long fd;
|
||||||
|
long wrote;
|
||||||
|
|
||||||
|
/* Drop leftovers from a crashed earlier run, then create the file. */
|
||||||
|
__syscall3(SYS_unlinkat, T27_AT_FDCWD, (long)T27_LINK, 0);
|
||||||
|
__syscall3(SYS_unlinkat, T27_AT_FDCWD, (long)T27_FILE, 0);
|
||||||
|
fd = __syscall4(SYS_openat, T27_AT_FDCWD, (long)T27_FILE, T27_O_RDWR | T27_O_CREAT | T27_O_EXCL,
|
||||||
|
0644);
|
||||||
|
check(fd >= 0, "raw openat O_CREAT|O_EXCL creates the scratch file");
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wrote = __syscall3(SYS_write, fd, (long)payload, (long)(sizeof(payload) - 1));
|
||||||
|
check(wrote == (long)(sizeof(payload) - 1), "raw write fills the scratch file");
|
||||||
|
check(__syscall1(SYS_close, fd) == 0, "raw close of the scratch file");
|
||||||
|
|
||||||
|
/* utimes: atime {1111111111, 222222} / mtime {1234567890, 333333}. */
|
||||||
|
tv[0].tv_sec = 1111111111;
|
||||||
|
tv[0].tv_usec = 222222;
|
||||||
|
tv[1].tv_sec = 1234567890;
|
||||||
|
tv[1].tv_usec = 333333;
|
||||||
|
check(utimes(T27_FILE, tv) == 0, "utimes(file, times) returns 0");
|
||||||
|
check(stat(T27_FILE, &st) == 0, "stat(file) after utimes returns 0");
|
||||||
|
check(st.st_atim.tv_sec == 1111111111 && st.st_atim.tv_nsec == 222222000,
|
||||||
|
"utimes set atime second + microsecond-derived nsec");
|
||||||
|
check(st.st_mtim.tv_sec == 1234567890 && st.st_mtim.tv_nsec == 333333000,
|
||||||
|
"utimes set mtime second + microsecond-derived nsec");
|
||||||
|
|
||||||
|
/* futimes on a fresh descriptor of the same file. */
|
||||||
|
fd = __syscall4(SYS_openat, T27_AT_FDCWD, (long)T27_FILE, T27_O_RDWR, 0);
|
||||||
|
check(fd >= 0, "raw openat reopens the file for futimes");
|
||||||
|
if (fd < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tv2[0].tv_sec = 1111111112;
|
||||||
|
tv2[0].tv_usec = 444444;
|
||||||
|
tv2[1].tv_sec = 1234567891;
|
||||||
|
tv2[1].tv_usec = 555555;
|
||||||
|
check(futimes((int)fd, tv2) == 0, "futimes(fd, times) returns 0");
|
||||||
|
check(stat(T27_FILE, &st) == 0, "stat(file) after futimes returns 0");
|
||||||
|
check(st.st_atim.tv_sec == 1111111112 && st.st_atim.tv_nsec == 444444000,
|
||||||
|
"futimes set atime through the descriptor");
|
||||||
|
check(st.st_mtim.tv_sec == 1234567891 && st.st_mtim.tv_nsec == 555555000,
|
||||||
|
"futimes set mtime through the descriptor");
|
||||||
|
check(__syscall1(SYS_close, fd) == 0, "raw close of the futimes descriptor");
|
||||||
|
|
||||||
|
/* lutimes on a symlink: the link's own times move, the target's do not. */
|
||||||
|
check(__syscall2(SYS_symlink, (long)T27_LINK_TARGET, (long)T27_LINK) == 0,
|
||||||
|
"raw SYS_symlink creates the link");
|
||||||
|
tv3[0].tv_sec = 1111111113;
|
||||||
|
tv3[0].tv_usec = 666666;
|
||||||
|
tv3[1].tv_sec = 1234567892;
|
||||||
|
tv3[1].tv_usec = 777777;
|
||||||
|
check(lutimes(T27_LINK, tv3) == 0, "lutimes(link, times) returns 0");
|
||||||
|
check(lstat(T27_LINK, &lst) == 0, "lstat(link) after lutimes returns 0");
|
||||||
|
check(lst.st_mtim.tv_sec == 1234567892 && lst.st_mtim.tv_nsec == 777777000,
|
||||||
|
"lutimes set the link's own mtime");
|
||||||
|
check(stat(T27_LINK, &st) == 0, "stat(link) follows to the file");
|
||||||
|
check(st.st_mtim.tv_sec == 1234567891 && st.st_mtim.tv_nsec == 555555000,
|
||||||
|
"lutimes left the target file's mtime untouched");
|
||||||
|
|
||||||
|
/* A NULL times array sets both timestamps to "now" (after 2001). */
|
||||||
|
check(utimes(T27_FILE, NULL) == 0, "utimes(file, NULL) returns 0");
|
||||||
|
check(stat(T27_FILE, &st) == 0, "stat(file) after utimes(NULL) returns 0");
|
||||||
|
check(st.st_mtim.tv_sec > 1000000000, "utimes(NULL) set mtime to the current time");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove the scratch paths. */
|
||||||
|
static void
|
||||||
|
cleanup_scenario(void)
|
||||||
|
{
|
||||||
|
__syscall3(SYS_unlinkat, T27_AT_FDCWD, (long)T27_LINK, 0);
|
||||||
|
__syscall3(SYS_unlinkat, T27_AT_FDCWD, (long)T27_FILE, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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_stat.c). */
|
||||||
|
static void
|
||||||
|
failure_scenarios(void)
|
||||||
|
{
|
||||||
|
struct itimerval it;
|
||||||
|
struct itimerval got;
|
||||||
|
struct timeval tv[2];
|
||||||
|
struct timezone tz;
|
||||||
|
|
||||||
|
it.it_interval.tv_sec = 0;
|
||||||
|
it.it_interval.tv_usec = 0;
|
||||||
|
it.it_value.tv_sec = 0;
|
||||||
|
it.it_value.tv_usec = 1000;
|
||||||
|
tv[0].tv_sec = 1;
|
||||||
|
tv[0].tv_usec = 0;
|
||||||
|
tv[1].tv_sec = 2;
|
||||||
|
tv[1].tv_usec = 0;
|
||||||
|
tz.tz_minuteswest = 0;
|
||||||
|
tz.tz_dsttime = 0;
|
||||||
|
|
||||||
|
check(setitimer(999, &it, NULL) == -1, "-f setitimer(999) returns -1");
|
||||||
|
check(getitimer(999, &got) == -1, "-f getitimer(999) returns -1");
|
||||||
|
check(settimeofday(NULL, &tz) == -1, "-f settimeofday(NULL, tz) returns -1");
|
||||||
|
check(utimes(T27_MISSING, tv) == -1, "-f utimes(nonexistent) returns -1");
|
||||||
|
check(futimes(-1, tv) == -1, "-f futimes(-1) returns -1");
|
||||||
|
check(lutimes(T27_MISSING, tv) == -1, "-f lutimes(nonexistent) returns -1");
|
||||||
|
|
||||||
|
if (failures == 0)
|
||||||
|
{
|
||||||
|
say(1, "all systime 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
gettimeofday_scenario();
|
||||||
|
timer_macro_scenario();
|
||||||
|
itimer_scenario();
|
||||||
|
utimes_scenario();
|
||||||
|
cleanup_scenario();
|
||||||
|
|
||||||
|
if (failures == 0)
|
||||||
|
{
|
||||||
|
say(1, "all systime tests passed\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
say(1, "FAILURES\n");
|
||||||
|
}
|
||||||
|
return failures == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else /* !VLIBC_LEVEL_GE(2) */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Level 1: every <sys/time.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/time.h> is level 2, not available here\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* VLIBC_LEVEL_GE(2) */
|
||||||
Reference in New Issue
Block a user