| // 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; | |
| ksft_print_header(); | |
| 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; | |
| } |