root/tools/testing/selftests/timens/timens.c
// 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;
}