1 | // SPDX-License-Identifier: GPL-2.0 |
2 | #define _GNU_SOURCE |
3 | #include <errno.h> |
4 | #include <fcntl.h> |
5 | #include <math.h> |
6 | #include <sched.h> |
7 | #include <stdio.h> |
8 | #include <stdbool.h> |
9 | #include <stdlib.h> |
10 | #include <sys/stat.h> |
11 | #include <sys/syscall.h> |
12 | #include <sys/types.h> |
13 | #include <time.h> |
14 | #include <unistd.h> |
15 | |
16 | #include "log.h" |
17 | #include "timens.h" |
18 | |
19 | /* |
20 | * Test shouldn't be run for a day, so add 10 days to child |
21 | * time and check parent's time to be in the same day. |
22 | */ |
23 | #define MAX_TEST_TIME_SEC (60*5) |
24 | #define DAY_IN_SEC (60*60*24) |
25 | #define TEN_DAYS_IN_SEC (10*DAY_IN_SEC) |
26 | |
27 | static int child_ns, parent_ns; |
28 | |
29 | static int switch_ns(int fd) |
30 | { |
31 | if (setns(fd, CLONE_NEWTIME)) |
32 | return pr_perror("setns()" ); |
33 | |
34 | return 0; |
35 | } |
36 | |
37 | static int init_namespaces(void) |
38 | { |
39 | char path[] = "/proc/self/ns/time_for_children" ; |
40 | struct stat st1, st2; |
41 | |
42 | parent_ns = open(path, O_RDONLY); |
43 | if (parent_ns <= 0) |
44 | return pr_perror("Unable to open %s" , path); |
45 | |
46 | if (fstat(parent_ns, &st1)) |
47 | return pr_perror("Unable to stat the parent timens" ); |
48 | |
49 | if (unshare_timens()) |
50 | return -1; |
51 | |
52 | child_ns = open(path, O_RDONLY); |
53 | if (child_ns <= 0) |
54 | return pr_perror("Unable to open %s" , path); |
55 | |
56 | if (fstat(child_ns, &st2)) |
57 | return pr_perror("Unable to stat the timens" ); |
58 | |
59 | if (st1.st_ino == st2.st_ino) |
60 | return pr_err("The same child_ns after CLONE_NEWTIME" ); |
61 | |
62 | if (_settime(CLOCK_BOOTTIME, TEN_DAYS_IN_SEC)) |
63 | return -1; |
64 | |
65 | return 0; |
66 | } |
67 | |
68 | static int read_proc_uptime(struct timespec *uptime) |
69 | { |
70 | unsigned long up_sec, up_nsec; |
71 | FILE *proc; |
72 | |
73 | proc = fopen("/proc/uptime" , "r" ); |
74 | if (proc == NULL) { |
75 | pr_perror("Unable to open /proc/uptime" ); |
76 | return -1; |
77 | } |
78 | |
79 | if (fscanf(proc, "%lu.%02lu" , &up_sec, &up_nsec) != 2) { |
80 | if (errno) { |
81 | pr_perror("fscanf" ); |
82 | return -errno; |
83 | } |
84 | pr_err("failed to parse /proc/uptime" ); |
85 | return -1; |
86 | } |
87 | fclose(proc); |
88 | |
89 | uptime->tv_sec = up_sec; |
90 | uptime->tv_nsec = up_nsec; |
91 | return 0; |
92 | } |
93 | |
94 | static int read_proc_stat_btime(unsigned long long *boottime_sec) |
95 | { |
96 | FILE *proc; |
97 | char line_buf[2048]; |
98 | |
99 | proc = fopen("/proc/stat" , "r" ); |
100 | if (proc == NULL) { |
101 | pr_perror("Unable to open /proc/stat" ); |
102 | return -1; |
103 | } |
104 | |
105 | while (fgets(line_buf, 2048, proc)) { |
106 | if (sscanf(line_buf, "btime %llu" , boottime_sec) != 1) |
107 | continue; |
108 | fclose(proc); |
109 | return 0; |
110 | } |
111 | if (errno) { |
112 | pr_perror("fscanf" ); |
113 | fclose(proc); |
114 | return -errno; |
115 | } |
116 | pr_err("failed to parse /proc/stat" ); |
117 | fclose(proc); |
118 | return -1; |
119 | } |
120 | |
121 | static int check_uptime(void) |
122 | { |
123 | struct timespec uptime_new, uptime_old; |
124 | time_t uptime_expected; |
125 | double prec = MAX_TEST_TIME_SEC; |
126 | |
127 | if (switch_ns(fd: parent_ns)) |
128 | return pr_err("switch_ns(%d)" , parent_ns); |
129 | |
130 | if (read_proc_uptime(uptime: &uptime_old)) |
131 | return 1; |
132 | |
133 | if (switch_ns(fd: child_ns)) |
134 | return pr_err("switch_ns(%d)" , child_ns); |
135 | |
136 | if (read_proc_uptime(uptime: &uptime_new)) |
137 | return 1; |
138 | |
139 | uptime_expected = uptime_old.tv_sec + TEN_DAYS_IN_SEC; |
140 | if (fabs(difftime(uptime_new.tv_sec, uptime_expected)) > prec) { |
141 | pr_fail("uptime in /proc/uptime: old %ld, new %ld [%ld]" , |
142 | uptime_old.tv_sec, uptime_new.tv_sec, |
143 | uptime_old.tv_sec + TEN_DAYS_IN_SEC); |
144 | return 1; |
145 | } |
146 | |
147 | ksft_test_result_pass(msg: "Passed for /proc/uptime\n" ); |
148 | return 0; |
149 | } |
150 | |
151 | static int check_stat_btime(void) |
152 | { |
153 | unsigned long long btime_new, btime_old; |
154 | unsigned long long btime_expected; |
155 | |
156 | if (switch_ns(fd: parent_ns)) |
157 | return pr_err("switch_ns(%d)" , parent_ns); |
158 | |
159 | if (read_proc_stat_btime(boottime_sec: &btime_old)) |
160 | return 1; |
161 | |
162 | if (switch_ns(fd: child_ns)) |
163 | return pr_err("switch_ns(%d)" , child_ns); |
164 | |
165 | if (read_proc_stat_btime(boottime_sec: &btime_new)) |
166 | return 1; |
167 | |
168 | btime_expected = btime_old - TEN_DAYS_IN_SEC; |
169 | if (btime_new != btime_expected) { |
170 | pr_fail("btime in /proc/stat: old %llu, new %llu [%llu]" , |
171 | btime_old, btime_new, btime_expected); |
172 | return 1; |
173 | } |
174 | |
175 | ksft_test_result_pass(msg: "Passed for /proc/stat btime\n" ); |
176 | return 0; |
177 | } |
178 | |
179 | int main(int argc, char *argv[]) |
180 | { |
181 | int ret = 0; |
182 | |
183 | nscheck(); |
184 | |
185 | ksft_set_plan(plan: 2); |
186 | |
187 | if (init_namespaces()) |
188 | return 1; |
189 | |
190 | ret |= check_uptime(); |
191 | ret |= check_stat_btime(); |
192 | |
193 | if (ret) |
194 | ksft_exit_fail(); |
195 | ksft_exit_pass(); |
196 | return ret; |
197 | } |
198 | |