diff options
Diffstat (limited to 'tools/testing/selftests/timens/timens.c')
-rw-r--r-- | tools/testing/selftests/timens/timens.c | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/tools/testing/selftests/timens/timens.c b/tools/testing/selftests/timens/timens.c new file mode 100644 index 000000000..387220791 --- /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; +} |