diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
commit | ace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch) | |
tree | b2d64bc10158fdd5497876388cd68142ca374ed3 /tools/testing/selftests/timens | |
parent | Initial commit. (diff) | |
download | linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip |
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/testing/selftests/timens')
-rw-r--r-- | tools/testing/selftests/timens/.gitignore | 11 | ||||
-rw-r--r-- | tools/testing/selftests/timens/Makefile | 7 | ||||
-rw-r--r-- | tools/testing/selftests/timens/clock_nanosleep.c | 149 | ||||
-rw-r--r-- | tools/testing/selftests/timens/config | 1 | ||||
-rw-r--r-- | tools/testing/selftests/timens/exec.c | 93 | ||||
-rw-r--r-- | tools/testing/selftests/timens/futex.c | 110 | ||||
-rw-r--r-- | tools/testing/selftests/timens/gettime_perf.c | 103 | ||||
-rw-r--r-- | tools/testing/selftests/timens/log.h | 26 | ||||
-rw-r--r-- | tools/testing/selftests/timens/procfs.c | 197 | ||||
-rw-r--r-- | tools/testing/selftests/timens/timens.c | 187 | ||||
-rw-r--r-- | tools/testing/selftests/timens/timens.h | 111 | ||||
-rw-r--r-- | tools/testing/selftests/timens/timer.c | 126 | ||||
-rw-r--r-- | tools/testing/selftests/timens/timerfd.c | 133 | ||||
-rw-r--r-- | tools/testing/selftests/timens/vfork_exec.c | 139 |
14 files changed, 1393 insertions, 0 deletions
diff --git a/tools/testing/selftests/timens/.gitignore b/tools/testing/selftests/timens/.gitignore new file mode 100644 index 0000000000..cae8dca0fb --- /dev/null +++ b/tools/testing/selftests/timens/.gitignore @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +clock_nanosleep +exec +futex +gettime_perf +gettime_perf_cold +procfs +timens +timer +timerfd +vfork_exec diff --git a/tools/testing/selftests/timens/Makefile b/tools/testing/selftests/timens/Makefile new file mode 100644 index 0000000000..f0d51d4d2c --- /dev/null +++ b/tools/testing/selftests/timens/Makefile @@ -0,0 +1,7 @@ +TEST_GEN_PROGS := timens timerfd timer clock_nanosleep procfs exec futex vfork_exec +TEST_GEN_PROGS_EXTENDED := gettime_perf + +CFLAGS := -Wall -Werror -pthread +LDLIBS := -lrt -ldl + +include ../lib.mk diff --git a/tools/testing/selftests/timens/clock_nanosleep.c b/tools/testing/selftests/timens/clock_nanosleep.c new file mode 100644 index 0000000000..72d41b955f --- /dev/null +++ b/tools/testing/selftests/timens/clock_nanosleep.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include <sched.h> + +#include <sys/timerfd.h> +#include <sys/syscall.h> +#include <time.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <pthread.h> +#include <signal.h> +#include <string.h> + +#include "log.h" +#include "timens.h" + +void test_sig(int sig) +{ + if (sig == SIGUSR2) + pthread_exit(NULL); +} + +struct thread_args { + struct timespec *now, *rem; + pthread_mutex_t *lock; + int clockid; + int abs; +}; + +void *call_nanosleep(void *_args) +{ + struct thread_args *args = _args; + + clock_nanosleep(args->clockid, args->abs ? TIMER_ABSTIME : 0, args->now, args->rem); + pthread_mutex_unlock(args->lock); + return NULL; +} + +int run_test(int clockid, int abs) +{ + struct timespec now = {}, rem; + struct thread_args args = { .now = &now, .rem = &rem, .clockid = clockid}; + struct timespec start; + pthread_mutex_t lock; + pthread_t thread; + int j, ok, ret; + + signal(SIGUSR1, test_sig); + signal(SIGUSR2, test_sig); + + pthread_mutex_init(&lock, NULL); + pthread_mutex_lock(&lock); + + if (clock_gettime(clockid, &start) == -1) { + if (errno == EINVAL && check_skip(clockid)) + return 0; + return pr_perror("clock_gettime"); + } + + + if (abs) { + now.tv_sec = start.tv_sec; + now.tv_nsec = start.tv_nsec; + } + + now.tv_sec += 3600; + args.abs = abs; + args.lock = &lock; + ret = pthread_create(&thread, NULL, call_nanosleep, &args); + if (ret != 0) { + pr_err("Unable to create a thread: %s", strerror(ret)); + return 1; + } + + /* Wait when the thread will call clock_nanosleep(). */ + ok = 0; + for (j = 0; j < 8; j++) { + /* The maximum timeout is about 5 seconds. */ + usleep(10000 << j); + + /* Try to interrupt clock_nanosleep(). */ + pthread_kill(thread, SIGUSR1); + + usleep(10000 << j); + /* Check whether clock_nanosleep() has been interrupted or not. */ + if (pthread_mutex_trylock(&lock) == 0) { + /**/ + ok = 1; + break; + } + } + if (!ok) + pthread_kill(thread, SIGUSR2); + pthread_join(thread, NULL); + pthread_mutex_destroy(&lock); + + if (!ok) { + ksft_test_result_pass("clockid: %d abs:%d timeout\n", clockid, abs); + return 1; + } + + if (rem.tv_sec < 3300 || rem.tv_sec > 3900) { + pr_fail("clockid: %d abs: %d remain: %ld\n", + clockid, abs, rem.tv_sec); + return 1; + } + ksft_test_result_pass("clockid: %d abs:%d\n", clockid, abs); + + return 0; +} + +int main(int argc, char *argv[]) +{ + int ret, nsfd; + + nscheck(); + + ksft_set_plan(4); + + check_supported_timers(); + + if (unshare_timens()) + return 1; + + if (_settime(CLOCK_MONOTONIC, 7 * 24 * 3600)) + return 1; + if (_settime(CLOCK_BOOTTIME, 9 * 24 * 3600)) + return 1; + + nsfd = open("/proc/self/ns/time_for_children", O_RDONLY); + if (nsfd < 0) + return pr_perror("Unable to open timens_for_children"); + + if (setns(nsfd, CLONE_NEWTIME)) + return pr_perror("Unable to set timens"); + + ret = 0; + ret |= run_test(CLOCK_MONOTONIC, 0); + ret |= run_test(CLOCK_MONOTONIC, 1); + ret |= run_test(CLOCK_BOOTTIME_ALARM, 0); + ret |= run_test(CLOCK_BOOTTIME_ALARM, 1); + + if (ret) + ksft_exit_fail(); + ksft_exit_pass(); + return ret; +} diff --git a/tools/testing/selftests/timens/config b/tools/testing/selftests/timens/config new file mode 100644 index 0000000000..4480620f6f --- /dev/null +++ b/tools/testing/selftests/timens/config @@ -0,0 +1 @@ +CONFIG_TIME_NS=y diff --git a/tools/testing/selftests/timens/exec.c b/tools/testing/selftests/timens/exec.c new file mode 100644 index 0000000000..e40dc5be2f --- /dev/null +++ b/tools/testing/selftests/timens/exec.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include <errno.h> +#include <fcntl.h> +#include <sched.h> +#include <stdio.h> +#include <stdbool.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> +#include <string.h> + +#include "log.h" +#include "timens.h" + +#define OFFSET (36000) + +int main(int argc, char *argv[]) +{ + struct timespec now, tst; + int status, i; + pid_t pid; + + if (argc > 1) { + if (sscanf(argv[1], "%ld", &now.tv_sec) != 1) + return pr_perror("sscanf"); + + for (i = 0; i < 2; i++) { + _gettime(CLOCK_MONOTONIC, &tst, i); + if (abs(tst.tv_sec - now.tv_sec) > 5) + return pr_fail("%ld %ld\n", now.tv_sec, tst.tv_sec); + } + return 0; + } + + nscheck(); + + ksft_set_plan(1); + + clock_gettime(CLOCK_MONOTONIC, &now); + + if (unshare_timens()) + return 1; + + if (_settime(CLOCK_MONOTONIC, OFFSET)) + return 1; + + for (i = 0; i < 2; i++) { + _gettime(CLOCK_MONOTONIC, &tst, i); + if (abs(tst.tv_sec - now.tv_sec) > 5) + return pr_fail("%ld %ld\n", + now.tv_sec, tst.tv_sec); + } + + if (argc > 1) + return 0; + + pid = fork(); + if (pid < 0) + return pr_perror("fork"); + + if (pid == 0) { + char now_str[64]; + char *cargv[] = {"exec", now_str, NULL}; + char *cenv[] = {NULL}; + + /* Check that a child process is in the new timens. */ + for (i = 0; i < 2; i++) { + _gettime(CLOCK_MONOTONIC, &tst, i); + if (abs(tst.tv_sec - now.tv_sec - OFFSET) > 5) + return pr_fail("%ld %ld\n", + now.tv_sec + OFFSET, tst.tv_sec); + } + + /* Check for proper vvar offsets after execve. */ + snprintf(now_str, sizeof(now_str), "%ld", now.tv_sec + OFFSET); + execve("/proc/self/exe", cargv, cenv); + return pr_perror("execve"); + } + + if (waitpid(pid, &status, 0) != pid) + return pr_perror("waitpid"); + + if (status) + ksft_exit_fail(); + + ksft_test_result_pass("exec\n"); + ksft_exit_pass(); + return 0; +} diff --git a/tools/testing/selftests/timens/futex.c b/tools/testing/selftests/timens/futex.c new file mode 100644 index 0000000000..6b2b9264e8 --- /dev/null +++ b/tools/testing/selftests/timens/futex.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include <sched.h> + +#include <linux/unistd.h> +#include <linux/futex.h> +#include <stdio.h> +#include <string.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> + +#include "log.h" +#include "timens.h" + +#define NSEC_PER_SEC 1000000000ULL + +static int run_test(int clockid) +{ + int futex_op = FUTEX_WAIT_BITSET; + struct timespec timeout, end; + int val = 0; + + if (clockid == CLOCK_REALTIME) + futex_op |= FUTEX_CLOCK_REALTIME; + + clock_gettime(clockid, &timeout); + timeout.tv_nsec += NSEC_PER_SEC / 10; // 100ms + if (timeout.tv_nsec > NSEC_PER_SEC) { + timeout.tv_sec++; + timeout.tv_nsec -= NSEC_PER_SEC; + } + + if (syscall(__NR_futex, &val, futex_op, 0, + &timeout, 0, FUTEX_BITSET_MATCH_ANY) >= 0) { + ksft_test_result_fail("futex didn't return ETIMEDOUT\n"); + return 1; + } + + if (errno != ETIMEDOUT) { + ksft_test_result_fail("futex didn't return ETIMEDOUT: %s\n", + strerror(errno)); + return 1; + } + + clock_gettime(clockid, &end); + + if (end.tv_sec < timeout.tv_sec || + (end.tv_sec == timeout.tv_sec && end.tv_nsec < timeout.tv_nsec)) { + ksft_test_result_fail("futex slept less than 100ms\n"); + return 1; + } + + + ksft_test_result_pass("futex with the %d clockid\n", clockid); + + return 0; +} + +int main(int argc, char *argv[]) +{ + int status, len, fd; + char buf[4096]; + pid_t pid; + struct timespec mtime_now; + + nscheck(); + + ksft_set_plan(2); + + clock_gettime(CLOCK_MONOTONIC, &mtime_now); + + if (unshare_timens()) + return 1; + + len = snprintf(buf, sizeof(buf), "%d %d 0", + CLOCK_MONOTONIC, 70 * 24 * 3600); + fd = open("/proc/self/timens_offsets", O_WRONLY); + if (fd < 0) + return pr_perror("/proc/self/timens_offsets"); + + if (write(fd, buf, len) != len) + return pr_perror("/proc/self/timens_offsets"); + + close(fd); + + pid = fork(); + if (pid < 0) + return pr_perror("Unable to fork"); + if (pid == 0) { + int ret = 0; + + ret |= run_test(CLOCK_REALTIME); + ret |= run_test(CLOCK_MONOTONIC); + if (ret) + ksft_exit_fail(); + ksft_exit_pass(); + return 0; + } + + if (waitpid(pid, &status, 0) != pid) + return pr_perror("Unable to wait the child process"); + + if (WIFEXITED(status)) + return WEXITSTATUS(status); + + return 1; +} diff --git a/tools/testing/selftests/timens/gettime_perf.c b/tools/testing/selftests/timens/gettime_perf.c new file mode 100644 index 0000000000..6b13dc2777 --- /dev/null +++ b/tools/testing/selftests/timens/gettime_perf.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <fcntl.h> +#include <sched.h> +#include <time.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/syscall.h> +#include <dlfcn.h> + +#include "log.h" +#include "timens.h" + +typedef int (*vgettime_t)(clockid_t, struct timespec *); + +vgettime_t vdso_clock_gettime; + +static void fill_function_pointers(void) +{ + void *vdso = dlopen("linux-vdso.so.1", + RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); + if (!vdso) + vdso = dlopen("linux-gate.so.1", + RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); + if (!vdso) + vdso = dlopen("linux-vdso32.so.1", + RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); + if (!vdso) + vdso = dlopen("linux-vdso64.so.1", + RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD); + if (!vdso) { + pr_err("[WARN]\tfailed to find vDSO\n"); + return; + } + + vdso_clock_gettime = (vgettime_t)dlsym(vdso, "__vdso_clock_gettime"); + if (!vdso_clock_gettime) + vdso_clock_gettime = (vgettime_t)dlsym(vdso, "__kernel_clock_gettime"); + if (!vdso_clock_gettime) + pr_err("Warning: failed to find clock_gettime in vDSO\n"); + +} + +static void test(clock_t clockid, char *clockstr, bool in_ns) +{ + struct timespec tp, start; + long i = 0; + const int timeout = 3; + + vdso_clock_gettime(clockid, &start); + tp = start; + for (tp = start; start.tv_sec + timeout > tp.tv_sec || + (start.tv_sec + timeout == tp.tv_sec && + start.tv_nsec > tp.tv_nsec); i++) { + vdso_clock_gettime(clockid, &tp); + } + + ksft_test_result_pass("%s:\tclock: %10s\tcycles:\t%10ld\n", + in_ns ? "ns" : "host", clockstr, i); +} + +int main(int argc, char *argv[]) +{ + time_t offset = 10; + int nsfd; + + ksft_set_plan(8); + + fill_function_pointers(); + + test(CLOCK_MONOTONIC, "monotonic", false); + test(CLOCK_MONOTONIC_COARSE, "monotonic-coarse", false); + test(CLOCK_MONOTONIC_RAW, "monotonic-raw", false); + test(CLOCK_BOOTTIME, "boottime", false); + + nscheck(); + + if (unshare_timens()) + return 1; + + nsfd = open("/proc/self/ns/time_for_children", O_RDONLY); + if (nsfd < 0) + return pr_perror("Can't open a time namespace"); + + if (_settime(CLOCK_MONOTONIC, offset)) + return 1; + if (_settime(CLOCK_BOOTTIME, offset)) + return 1; + + if (setns(nsfd, CLONE_NEWTIME)) + return pr_perror("setns"); + + test(CLOCK_MONOTONIC, "monotonic", true); + test(CLOCK_MONOTONIC_COARSE, "monotonic-coarse", true); + test(CLOCK_MONOTONIC_RAW, "monotonic-raw", true); + test(CLOCK_BOOTTIME, "boottime", true); + + ksft_exit_pass(); + return 0; +} diff --git a/tools/testing/selftests/timens/log.h b/tools/testing/selftests/timens/log.h new file mode 100644 index 0000000000..db64df2a84 --- /dev/null +++ b/tools/testing/selftests/timens/log.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __SELFTEST_TIMENS_LOG_H__ +#define __SELFTEST_TIMENS_LOG_H__ + +#define pr_msg(fmt, lvl, ...) \ + ksft_print_msg("[%s] (%s:%d)\t" fmt "\n", \ + lvl, __FILE__, __LINE__, ##__VA_ARGS__) + +#define pr_p(func, fmt, ...) func(fmt ": %m", ##__VA_ARGS__) + +#define pr_err(fmt, ...) \ + ({ \ + ksft_test_result_error(fmt "\n", ##__VA_ARGS__); \ + -1; \ + }) + +#define pr_fail(fmt, ...) \ + ({ \ + ksft_test_result_fail(fmt, ##__VA_ARGS__); \ + -1; \ + }) + +#define pr_perror(fmt, ...) pr_p(pr_err, fmt, ##__VA_ARGS__) + +#endif diff --git a/tools/testing/selftests/timens/procfs.c b/tools/testing/selftests/timens/procfs.c new file mode 100644 index 0000000000..1833ca97eb --- /dev/null +++ b/tools/testing/selftests/timens/procfs.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include <errno.h> +#include <fcntl.h> +#include <math.h> +#include <sched.h> +#include <stdio.h> +#include <stdbool.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#include "log.h" +#include "timens.h" + +/* + * Test shouldn't be run for a day, so add 10 days to child + * time and check parent's time to be in the same day. + */ +#define MAX_TEST_TIME_SEC (60*5) +#define DAY_IN_SEC (60*60*24) +#define TEN_DAYS_IN_SEC (10*DAY_IN_SEC) + +static int child_ns, parent_ns; + +static int switch_ns(int fd) +{ + if (setns(fd, CLONE_NEWTIME)) + return pr_perror("setns()"); + + return 0; +} + +static int init_namespaces(void) +{ + char path[] = "/proc/self/ns/time_for_children"; + struct stat st1, st2; + + parent_ns = open(path, O_RDONLY); + if (parent_ns <= 0) + return pr_perror("Unable to open %s", path); + + if (fstat(parent_ns, &st1)) + return pr_perror("Unable to stat the parent timens"); + + if (unshare_timens()) + return -1; + + child_ns = open(path, O_RDONLY); + if (child_ns <= 0) + return pr_perror("Unable to open %s", path); + + if (fstat(child_ns, &st2)) + return pr_perror("Unable to stat the timens"); + + if (st1.st_ino == st2.st_ino) + return pr_err("The same child_ns after CLONE_NEWTIME"); + + if (_settime(CLOCK_BOOTTIME, TEN_DAYS_IN_SEC)) + return -1; + + return 0; +} + +static int read_proc_uptime(struct timespec *uptime) +{ + unsigned long up_sec, up_nsec; + FILE *proc; + + proc = fopen("/proc/uptime", "r"); + if (proc == NULL) { + pr_perror("Unable to open /proc/uptime"); + return -1; + } + + if (fscanf(proc, "%lu.%02lu", &up_sec, &up_nsec) != 2) { + if (errno) { + pr_perror("fscanf"); + return -errno; + } + pr_err("failed to parse /proc/uptime"); + return -1; + } + fclose(proc); + + uptime->tv_sec = up_sec; + uptime->tv_nsec = up_nsec; + return 0; +} + +static int read_proc_stat_btime(unsigned long long *boottime_sec) +{ + FILE *proc; + char line_buf[2048]; + + proc = fopen("/proc/stat", "r"); + if (proc == NULL) { + pr_perror("Unable to open /proc/stat"); + return -1; + } + + while (fgets(line_buf, 2048, proc)) { + if (sscanf(line_buf, "btime %llu", boottime_sec) != 1) + continue; + fclose(proc); + return 0; + } + if (errno) { + pr_perror("fscanf"); + fclose(proc); + return -errno; + } + pr_err("failed to parse /proc/stat"); + fclose(proc); + return -1; +} + +static int check_uptime(void) +{ + struct timespec uptime_new, uptime_old; + time_t uptime_expected; + double prec = MAX_TEST_TIME_SEC; + + if (switch_ns(parent_ns)) + return pr_err("switch_ns(%d)", parent_ns); + + if (read_proc_uptime(&uptime_old)) + return 1; + + if (switch_ns(child_ns)) + return pr_err("switch_ns(%d)", child_ns); + + if (read_proc_uptime(&uptime_new)) + return 1; + + uptime_expected = uptime_old.tv_sec + TEN_DAYS_IN_SEC; + if (fabs(difftime(uptime_new.tv_sec, uptime_expected)) > prec) { + pr_fail("uptime in /proc/uptime: old %ld, new %ld [%ld]", + uptime_old.tv_sec, uptime_new.tv_sec, + uptime_old.tv_sec + TEN_DAYS_IN_SEC); + return 1; + } + + ksft_test_result_pass("Passed for /proc/uptime\n"); + return 0; +} + +static int check_stat_btime(void) +{ + unsigned long long btime_new, btime_old; + unsigned long long btime_expected; + + if (switch_ns(parent_ns)) + return pr_err("switch_ns(%d)", parent_ns); + + if (read_proc_stat_btime(&btime_old)) + return 1; + + if (switch_ns(child_ns)) + return pr_err("switch_ns(%d)", child_ns); + + if (read_proc_stat_btime(&btime_new)) + return 1; + + btime_expected = btime_old - TEN_DAYS_IN_SEC; + if (btime_new != btime_expected) { + pr_fail("btime in /proc/stat: old %llu, new %llu [%llu]", + btime_old, btime_new, btime_expected); + return 1; + } + + ksft_test_result_pass("Passed for /proc/stat btime\n"); + return 0; +} + +int main(int argc, char *argv[]) +{ + int ret = 0; + + nscheck(); + + ksft_set_plan(2); + + if (init_namespaces()) + return 1; + + ret |= check_uptime(); + ret |= check_stat_btime(); + + if (ret) + ksft_exit_fail(); + ksft_exit_pass(); + return ret; +} diff --git a/tools/testing/selftests/timens/timens.c b/tools/testing/selftests/timens/timens.c new file mode 100644 index 0000000000..387220791a --- /dev/null +++ b/tools/testing/selftests/timens/timens.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include <errno.h> +#include <fcntl.h> +#include <sched.h> +#include <stdio.h> +#include <stdbool.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> +#include <string.h> + +#include "log.h" +#include "timens.h" + +/* + * Test shouldn't be run for a day, so add 10 days to child + * time and check parent's time to be in the same day. + */ +#define DAY_IN_SEC (60*60*24) +#define TEN_DAYS_IN_SEC (10*DAY_IN_SEC) + +struct test_clock { + clockid_t id; + char *name; + /* + * off_id is -1 if a clock has own offset, or it contains an index + * which contains a right offset of this clock. + */ + int off_id; + time_t offset; +}; + +#define ct(clock, off_id) { clock, #clock, off_id } +static struct test_clock clocks[] = { + ct(CLOCK_BOOTTIME, -1), + ct(CLOCK_BOOTTIME_ALARM, 1), + ct(CLOCK_MONOTONIC, -1), + ct(CLOCK_MONOTONIC_COARSE, 1), + ct(CLOCK_MONOTONIC_RAW, 1), +}; +#undef ct + +static int child_ns, parent_ns = -1; + +static int switch_ns(int fd) +{ + if (setns(fd, CLONE_NEWTIME)) { + pr_perror("setns()"); + return -1; + } + + return 0; +} + +static int init_namespaces(void) +{ + char path[] = "/proc/self/ns/time_for_children"; + struct stat st1, st2; + + if (parent_ns == -1) { + parent_ns = open(path, O_RDONLY); + if (parent_ns <= 0) + return pr_perror("Unable to open %s", path); + } + + if (fstat(parent_ns, &st1)) + return pr_perror("Unable to stat the parent timens"); + + if (unshare_timens()) + return -1; + + child_ns = open(path, O_RDONLY); + if (child_ns <= 0) + return pr_perror("Unable to open %s", path); + + if (fstat(child_ns, &st2)) + return pr_perror("Unable to stat the timens"); + + if (st1.st_ino == st2.st_ino) + return pr_perror("The same child_ns after CLONE_NEWTIME"); + + return 0; +} + +static int test_gettime(clockid_t clock_index, bool raw_syscall, time_t offset) +{ + struct timespec child_ts_new, parent_ts_old, cur_ts; + char *entry = raw_syscall ? "syscall" : "vdso"; + double precision = 0.0; + + if (check_skip(clocks[clock_index].id)) + return 0; + + switch (clocks[clock_index].id) { + case CLOCK_MONOTONIC_COARSE: + case CLOCK_MONOTONIC_RAW: + precision = -2.0; + break; + } + + if (switch_ns(parent_ns)) + return pr_err("switch_ns(%d)", child_ns); + + if (_gettime(clocks[clock_index].id, &parent_ts_old, raw_syscall)) + return -1; + + child_ts_new.tv_nsec = parent_ts_old.tv_nsec; + child_ts_new.tv_sec = parent_ts_old.tv_sec + offset; + + if (switch_ns(child_ns)) + return pr_err("switch_ns(%d)", child_ns); + + if (_gettime(clocks[clock_index].id, &cur_ts, raw_syscall)) + return -1; + + if (difftime(cur_ts.tv_sec, child_ts_new.tv_sec) < precision) { + ksft_test_result_fail( + "Child's %s (%s) time has not changed: %lu -> %lu [%lu]\n", + clocks[clock_index].name, entry, parent_ts_old.tv_sec, + child_ts_new.tv_sec, cur_ts.tv_sec); + return -1; + } + + if (switch_ns(parent_ns)) + return pr_err("switch_ns(%d)", parent_ns); + + if (_gettime(clocks[clock_index].id, &cur_ts, raw_syscall)) + return -1; + + if (difftime(cur_ts.tv_sec, parent_ts_old.tv_sec) > DAY_IN_SEC) { + ksft_test_result_fail( + "Parent's %s (%s) time has changed: %lu -> %lu [%lu]\n", + clocks[clock_index].name, entry, parent_ts_old.tv_sec, + child_ts_new.tv_sec, cur_ts.tv_sec); + /* Let's play nice and put it closer to original */ + clock_settime(clocks[clock_index].id, &cur_ts); + return -1; + } + + ksft_test_result_pass("Passed for %s (%s)\n", + clocks[clock_index].name, entry); + return 0; +} + +int main(int argc, char *argv[]) +{ + unsigned int i; + time_t offset; + int ret = 0; + + nscheck(); + + check_supported_timers(); + + ksft_set_plan(ARRAY_SIZE(clocks) * 2); + + if (init_namespaces()) + return 1; + + /* Offsets have to be set before tasks enter the namespace. */ + for (i = 0; i < ARRAY_SIZE(clocks); i++) { + if (clocks[i].off_id != -1) + continue; + offset = TEN_DAYS_IN_SEC + i * 1000; + clocks[i].offset = offset; + if (_settime(clocks[i].id, offset)) + return 1; + } + + for (i = 0; i < ARRAY_SIZE(clocks); i++) { + if (clocks[i].off_id != -1) + offset = clocks[clocks[i].off_id].offset; + else + offset = clocks[i].offset; + ret |= test_gettime(i, true, offset); + ret |= test_gettime(i, false, offset); + } + + if (ret) + ksft_exit_fail(); + + ksft_exit_pass(); + return !!ret; +} diff --git a/tools/testing/selftests/timens/timens.h b/tools/testing/selftests/timens/timens.h new file mode 100644 index 0000000000..d4fc52d471 --- /dev/null +++ b/tools/testing/selftests/timens/timens.h @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __TIMENS_H__ +#define __TIMENS_H__ + +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdbool.h> + +#include "../kselftest.h" + +#ifndef CLONE_NEWTIME +# define CLONE_NEWTIME 0x00000080 +#endif + +static int config_posix_timers = true; +static int config_alarm_timers = true; + +static inline void check_supported_timers(void) +{ + struct timespec ts; + + if (timer_create(-1, 0, 0) == -1 && errno == ENOSYS) + config_posix_timers = false; + + if (clock_gettime(CLOCK_BOOTTIME_ALARM, &ts) == -1 && errno == EINVAL) + config_alarm_timers = false; +} + +static inline bool check_skip(int clockid) +{ + if (!config_alarm_timers && clockid == CLOCK_BOOTTIME_ALARM) { + ksft_test_result_skip("CLOCK_BOOTTIME_ALARM isn't supported\n"); + return true; + } + + if (config_posix_timers) + return false; + + switch (clockid) { + /* Only these clocks are supported without CONFIG_POSIX_TIMERS. */ + case CLOCK_BOOTTIME: + case CLOCK_MONOTONIC: + case CLOCK_REALTIME: + return false; + default: + ksft_test_result_skip("Posix Clocks & timers are not supported\n"); + return true; + } + + return false; +} + +static inline int unshare_timens(void) +{ + if (unshare(CLONE_NEWTIME)) { + if (errno == EPERM) + ksft_exit_skip("need to run as root\n"); + return pr_perror("Can't unshare() timens"); + } + return 0; +} + +static inline int _settime(clockid_t clk_id, time_t offset) +{ + int fd, len; + char buf[4096]; + + if (clk_id == CLOCK_MONOTONIC_COARSE || clk_id == CLOCK_MONOTONIC_RAW) + clk_id = CLOCK_MONOTONIC; + + len = snprintf(buf, sizeof(buf), "%d %ld 0", clk_id, offset); + + fd = open("/proc/self/timens_offsets", O_WRONLY); + if (fd < 0) + return pr_perror("/proc/self/timens_offsets"); + + if (write(fd, buf, len) != len) + return pr_perror("/proc/self/timens_offsets"); + + close(fd); + + return 0; +} + +static inline int _gettime(clockid_t clk_id, struct timespec *res, bool raw_syscall) +{ + int err; + + if (!raw_syscall) { + if (clock_gettime(clk_id, res)) { + pr_perror("clock_gettime(%d)", (int)clk_id); + return -1; + } + return 0; + } + + err = syscall(SYS_clock_gettime, clk_id, res); + if (err) + pr_perror("syscall(SYS_clock_gettime(%d))", (int)clk_id); + + return err; +} + +static inline void nscheck(void) +{ + if (access("/proc/self/ns/time", F_OK) < 0) + ksft_exit_skip("Time namespaces are not supported\n"); +} + +#endif diff --git a/tools/testing/selftests/timens/timer.c b/tools/testing/selftests/timens/timer.c new file mode 100644 index 0000000000..5e7f0051bd --- /dev/null +++ b/tools/testing/selftests/timens/timer.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include <sched.h> + +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <signal.h> + +#include "log.h" +#include "timens.h" + +int run_test(int clockid, struct timespec now) +{ + struct itimerspec new_value; + long long elapsed; + timer_t fd; + int i; + + if (check_skip(clockid)) + return 0; + + for (i = 0; i < 2; i++) { + struct sigevent sevp = {.sigev_notify = SIGEV_NONE}; + int flags = 0; + + new_value.it_value.tv_sec = 3600; + new_value.it_value.tv_nsec = 0; + new_value.it_interval.tv_sec = 1; + new_value.it_interval.tv_nsec = 0; + + if (i == 1) { + new_value.it_value.tv_sec += now.tv_sec; + new_value.it_value.tv_nsec += now.tv_nsec; + } + + if (timer_create(clockid, &sevp, &fd) == -1) { + if (errno == ENOSYS) { + ksft_test_result_skip("Posix Clocks & timers are supported\n"); + return 0; + } + return pr_perror("timerfd_create"); + } + + if (i == 1) + flags |= TIMER_ABSTIME; + if (timer_settime(fd, flags, &new_value, NULL) == -1) + return pr_perror("timerfd_settime"); + + if (timer_gettime(fd, &new_value) == -1) + return pr_perror("timerfd_gettime"); + + elapsed = new_value.it_value.tv_sec; + if (abs(elapsed - 3600) > 60) { + ksft_test_result_fail("clockid: %d elapsed: %lld\n", + clockid, elapsed); + return 1; + } + } + + ksft_test_result_pass("clockid=%d\n", clockid); + + return 0; +} + +int main(int argc, char *argv[]) +{ + int ret, status, len, fd; + char buf[4096]; + pid_t pid; + struct timespec btime_now, mtime_now; + + nscheck(); + + check_supported_timers(); + + ksft_set_plan(3); + + clock_gettime(CLOCK_MONOTONIC, &mtime_now); + clock_gettime(CLOCK_BOOTTIME, &btime_now); + + if (unshare_timens()) + return 1; + + len = snprintf(buf, sizeof(buf), "%d %d 0\n%d %d 0", + CLOCK_MONOTONIC, 70 * 24 * 3600, + CLOCK_BOOTTIME, 9 * 24 * 3600); + fd = open("/proc/self/timens_offsets", O_WRONLY); + if (fd < 0) + return pr_perror("/proc/self/timens_offsets"); + + if (write(fd, buf, len) != len) + return pr_perror("/proc/self/timens_offsets"); + + close(fd); + mtime_now.tv_sec += 70 * 24 * 3600; + btime_now.tv_sec += 9 * 24 * 3600; + + pid = fork(); + if (pid < 0) + return pr_perror("Unable to fork"); + if (pid == 0) { + ret = 0; + ret |= run_test(CLOCK_BOOTTIME, btime_now); + ret |= run_test(CLOCK_MONOTONIC, mtime_now); + ret |= run_test(CLOCK_BOOTTIME_ALARM, btime_now); + + if (ret) + ksft_exit_fail(); + ksft_exit_pass(); + return ret; + } + + if (waitpid(pid, &status, 0) != pid) + return pr_perror("Unable to wait the child process"); + + if (WIFEXITED(status)) + return WEXITSTATUS(status); + + return 1; +} diff --git a/tools/testing/selftests/timens/timerfd.c b/tools/testing/selftests/timens/timerfd.c new file mode 100644 index 0000000000..9edd43d6b2 --- /dev/null +++ b/tools/testing/selftests/timens/timerfd.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include <sched.h> + +#include <sys/timerfd.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> + +#include "log.h" +#include "timens.h" + +static int tclock_gettime(clock_t clockid, struct timespec *now) +{ + if (clockid == CLOCK_BOOTTIME_ALARM) + clockid = CLOCK_BOOTTIME; + return clock_gettime(clockid, now); +} + +int run_test(int clockid, struct timespec now) +{ + struct itimerspec new_value; + long long elapsed; + int fd, i; + + if (check_skip(clockid)) + return 0; + + if (tclock_gettime(clockid, &now)) + return pr_perror("clock_gettime(%d)", clockid); + + for (i = 0; i < 2; i++) { + int flags = 0; + + new_value.it_value.tv_sec = 3600; + new_value.it_value.tv_nsec = 0; + new_value.it_interval.tv_sec = 1; + new_value.it_interval.tv_nsec = 0; + + if (i == 1) { + new_value.it_value.tv_sec += now.tv_sec; + new_value.it_value.tv_nsec += now.tv_nsec; + } + + fd = timerfd_create(clockid, 0); + if (fd == -1) + return pr_perror("timerfd_create(%d)", clockid); + + if (i == 1) + flags |= TFD_TIMER_ABSTIME; + + if (timerfd_settime(fd, flags, &new_value, NULL)) + return pr_perror("timerfd_settime(%d)", clockid); + + if (timerfd_gettime(fd, &new_value)) + return pr_perror("timerfd_gettime(%d)", clockid); + + elapsed = new_value.it_value.tv_sec; + if (abs(elapsed - 3600) > 60) { + ksft_test_result_fail("clockid: %d elapsed: %lld\n", + clockid, elapsed); + return 1; + } + + close(fd); + } + + ksft_test_result_pass("clockid=%d\n", clockid); + + return 0; +} + +int main(int argc, char *argv[]) +{ + int ret, status, len, fd; + char buf[4096]; + pid_t pid; + struct timespec btime_now, mtime_now; + + nscheck(); + + check_supported_timers(); + + ksft_set_plan(3); + + clock_gettime(CLOCK_MONOTONIC, &mtime_now); + clock_gettime(CLOCK_BOOTTIME, &btime_now); + + if (unshare_timens()) + return 1; + + len = snprintf(buf, sizeof(buf), "%d %d 0\n%d %d 0", + CLOCK_MONOTONIC, 70 * 24 * 3600, + CLOCK_BOOTTIME, 9 * 24 * 3600); + fd = open("/proc/self/timens_offsets", O_WRONLY); + if (fd < 0) + return pr_perror("/proc/self/timens_offsets"); + + if (write(fd, buf, len) != len) + return pr_perror("/proc/self/timens_offsets"); + + close(fd); + mtime_now.tv_sec += 70 * 24 * 3600; + btime_now.tv_sec += 9 * 24 * 3600; + + pid = fork(); + if (pid < 0) + return pr_perror("Unable to fork"); + if (pid == 0) { + ret = 0; + ret |= run_test(CLOCK_BOOTTIME, btime_now); + ret |= run_test(CLOCK_MONOTONIC, mtime_now); + ret |= run_test(CLOCK_BOOTTIME_ALARM, btime_now); + + if (ret) + ksft_exit_fail(); + ksft_exit_pass(); + return ret; + } + + if (waitpid(pid, &status, 0) != pid) + return pr_perror("Unable to wait the child process"); + + if (WIFEXITED(status)) + return WEXITSTATUS(status); + + return 1; +} diff --git a/tools/testing/selftests/timens/vfork_exec.c b/tools/testing/selftests/timens/vfork_exec.c new file mode 100644 index 0000000000..beb7614941 --- /dev/null +++ b/tools/testing/selftests/timens/vfork_exec.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include <errno.h> +#include <fcntl.h> +#include <sched.h> +#include <stdio.h> +#include <stdbool.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> +#include <string.h> +#include <pthread.h> + +#include "log.h" +#include "timens.h" + +#define OFFSET (36000) + +struct thread_args { + char *tst_name; + struct timespec *now; +}; + +static void *tcheck(void *_args) +{ + struct thread_args *args = _args; + struct timespec *now = args->now, tst; + int i; + + for (i = 0; i < 2; i++) { + _gettime(CLOCK_MONOTONIC, &tst, i); + if (abs(tst.tv_sec - now->tv_sec) > 5) { + pr_fail("%s: in-thread: unexpected value: %ld (%ld)\n", + args->tst_name, tst.tv_sec, now->tv_sec); + return (void *)1UL; + } + } + return NULL; +} + +static int check_in_thread(char *tst_name, struct timespec *now) +{ + struct thread_args args = { + .tst_name = tst_name, + .now = now, + }; + pthread_t th; + void *retval; + + if (pthread_create(&th, NULL, tcheck, &args)) + return pr_perror("thread"); + if (pthread_join(th, &retval)) + return pr_perror("pthread_join"); + return !(retval == NULL); +} + +static int check(char *tst_name, struct timespec *now) +{ + struct timespec tst; + int i; + + for (i = 0; i < 2; i++) { + _gettime(CLOCK_MONOTONIC, &tst, i); + if (abs(tst.tv_sec - now->tv_sec) > 5) + return pr_fail("%s: unexpected value: %ld (%ld)\n", + tst_name, tst.tv_sec, now->tv_sec); + } + if (check_in_thread(tst_name, now)) + return 1; + ksft_test_result_pass("%s\n", tst_name); + return 0; +} + +int main(int argc, char *argv[]) +{ + struct timespec now; + int status; + pid_t pid; + + if (argc > 1) { + char *endptr; + + ksft_cnt.ksft_pass = 1; + now.tv_sec = strtoul(argv[1], &endptr, 0); + if (*endptr != 0) + return pr_perror("strtoul"); + + return check("child after exec", &now); + } + + nscheck(); + + ksft_set_plan(4); + + clock_gettime(CLOCK_MONOTONIC, &now); + + if (unshare_timens()) + return 1; + + if (_settime(CLOCK_MONOTONIC, OFFSET)) + return 1; + + if (check("parent before vfork", &now)) + return 1; + + pid = vfork(); + if (pid < 0) + return pr_perror("fork"); + + if (pid == 0) { + char now_str[64]; + char *cargv[] = {"exec", now_str, NULL}; + char *cenv[] = {NULL}; + + /* Check for proper vvar offsets after execve. */ + snprintf(now_str, sizeof(now_str), "%ld", now.tv_sec + OFFSET); + execve("/proc/self/exe", cargv, cenv); + pr_perror("execve"); + _exit(1); + } + + if (waitpid(pid, &status, 0) != pid) + return pr_perror("waitpid"); + + if (status) + ksft_exit_fail(); + ksft_inc_pass_cnt(); + ksft_test_result_pass("wait for child\n"); + + /* Check that we are still in the source timens. */ + if (check("parent after vfork", &now)) + return 1; + + ksft_exit_pass(); + return 0; +} |