• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Regression test for hrtimer early expiration during and after leap seconds
3  *
4  * A bug in the hrtimer subsystem caused all TIMER_ABSTIME CLOCK_REALTIME
5  * timers to expire one second early during leap second.
6  * See http://lwn.net/Articles/504658/.
7  *
8  * This is a regression test for the bug.
9  *
10  * Lingzhu Xiang <lxiang@redhat.com> Copyright (c) Red Hat, Inc., 2012.
11  *
12  * This program is free software; you can redistribute it and/or modify it
13  * under the terms of version 2 of the GNU General Public License as
14  * published by the Free Software Foundation.
15  *
16  * This program is distributed in the hope that it would be useful, but
17  * WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19  *
20  * You should have received a copy of the GNU General Public License along
21  * with this program; if not, write the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23  *
24  */
25 
26 #include <sys/types.h>
27 #include <sys/time.h>
28 #include <sys/timex.h>
29 #include <errno.h>
30 #include <stdlib.h>
31 #include <time.h>
32 #include "test.h"
33 #include "common_timers.h"
34 
35 #define SECONDS_BEFORE_LEAP 2
36 #define SECONDS_AFTER_LEAP 2
37 
38 char *TCID = "leapsec_timer";
39 int TST_TOTAL = 1;
40 
41 static inline int in_order(struct timespec a, struct timespec b);
42 static void adjtimex_status(struct timex *tx, int status);
43 static const char *strtime(const struct timespec *now);
44 static void test_hrtimer_early_expiration(void);
45 static void run_leapsec(void);
46 static void setup(void);
47 static void cleanup(void);
48 
main(int argc,char ** argv)49 int main(int argc, char **argv)
50 {
51 	int lc;
52 
53 	tst_parse_opts(argc, argv, NULL, NULL);
54 
55 	setup();
56 
57 	for (lc = 0; TEST_LOOPING(lc); lc++) {
58 		tst_count = 0;
59 		run_leapsec();
60 	}
61 
62 	cleanup();
63 	tst_exit();
64 }
65 
in_order(struct timespec a,struct timespec b)66 static inline int in_order(struct timespec a, struct timespec b)
67 {
68 	if (a.tv_sec < b.tv_sec)
69 		return 1;
70 	if (a.tv_sec > b.tv_sec)
71 		return 0;
72 	if (a.tv_nsec > b.tv_nsec)
73 		return 0;
74 	return 1;
75 }
76 
adjtimex_status(struct timex * tx,int status)77 static void adjtimex_status(struct timex *tx, int status)
78 {
79 	const char *const msgs[6] = {
80 		"clock synchronized",
81 		"insert leap second",
82 		"delete leap second",
83 		"leap second in progress",
84 		"leap second has occurred",
85 		"clock not synchronized",
86 	};
87 	int r;
88 	struct timespec now;
89 
90 	tx->modes = ADJ_STATUS;
91 	tx->status = status;
92 	r = adjtimex(tx);
93 	now.tv_sec = tx->time.tv_sec;
94 	now.tv_nsec = tx->time.tv_usec * 1000;
95 
96 	if ((tx->status & status) != status)
97 		tst_brkm(TBROK, cleanup, "adjtimex status %d not set", status);
98 	else if (r < 0)
99 		tst_brkm(TBROK | TERRNO, cleanup, "adjtimex");
100 	else if (r < 6)
101 		tst_resm(TINFO, "%s adjtimex: %s", strtime(&now), msgs[r]);
102 	else
103 		tst_resm(TINFO, "%s adjtimex: clock state %d",
104 			 strtime(&now), r);
105 }
106 
strtime(const struct timespec * now)107 static const char *strtime(const struct timespec *now)
108 {
109 	static char fmt[256], buf[256];
110 
111 	if (snprintf(fmt, sizeof(fmt), "%%F %%T.%09ld %%z", now->tv_nsec) < 0) {
112 		buf[0] = '\0';
113 		return buf;
114 	}
115 	if (!strftime(buf, sizeof(buf), fmt, localtime(&now->tv_sec))) {
116 		buf[0] = '\0';
117 		return buf;
118 	}
119 	return buf;
120 }
121 
test_hrtimer_early_expiration(void)122 static void test_hrtimer_early_expiration(void)
123 {
124 	struct timespec now, target;
125 	int r, fail;
126 
127 	clock_gettime(CLOCK_REALTIME, &now);
128 	tst_resm(TINFO, "now is     %s", strtime(&now));
129 
130 	target = now;
131 	target.tv_sec++;
132 	tst_resm(TINFO, "sleep till %s", strtime(&target));
133 	r = clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &target, NULL);
134 	if (r < 0) {
135 		tst_resm(TINFO | TERRNO, "clock_nanosleep");
136 		return;
137 	}
138 
139 	clock_gettime(CLOCK_REALTIME, &now);
140 	tst_resm(TINFO, "now is     %s", strtime(&now));
141 
142 	fail = !in_order(target, now);
143 	tst_resm(fail ? TFAIL : TINFO, "hrtimer early expiration is %s.",
144 		 fail ? "detected" : "not detected");
145 }
146 
run_leapsec(void)147 static void run_leapsec(void)
148 {
149 	const struct timespec sleeptime = { 0, NSEC_PER_SEC / 2 };
150 	struct timespec now, leap, start;
151 	struct timex tx;
152 
153 	clock_gettime(CLOCK_REALTIME, &now);
154 	start = now;
155 	tst_resm(TINFO, "test start at %s", strtime(&now));
156 
157 	test_hrtimer_early_expiration();
158 
159 	/* calculate the next leap second */
160 	now.tv_sec += 86400 - now.tv_sec % 86400;
161 	now.tv_nsec = 0;
162 	leap = now;
163 	tst_resm(TINFO, "scheduling leap second %s", strtime(&leap));
164 
165 	/* start before the leap second */
166 	now.tv_sec -= SECONDS_BEFORE_LEAP;
167 	if (clock_settime(CLOCK_REALTIME, &now) < 0)
168 		tst_brkm(TBROK | TERRNO, cleanup, "clock_settime");
169 	tst_resm(TINFO, "setting time to        %s", strtime(&now));
170 
171 	/* reset NTP time state */
172 	adjtimex_status(&tx, STA_PLL);
173 	adjtimex_status(&tx, 0);
174 
175 	/* set the leap second insert flag */
176 	adjtimex_status(&tx, STA_INS);
177 
178 	/* reliably sleep till after the leap second */
179 	while (tx.time.tv_sec < leap.tv_sec + SECONDS_AFTER_LEAP) {
180 		adjtimex_status(&tx, tx.status);
181 		clock_nanosleep(CLOCK_MONOTONIC, 0, &sleeptime, NULL);
182 	}
183 
184 	test_hrtimer_early_expiration();
185 
186 	adjtimex_status(&tx, STA_PLL);
187 	adjtimex_status(&tx, 0);
188 
189 	/* recover from timer expiring state and restore time */
190 	clock_gettime(CLOCK_REALTIME, &now);
191 	start.tv_sec += now.tv_sec - (leap.tv_sec - SECONDS_BEFORE_LEAP);
192 	start.tv_nsec += now.tv_nsec;
193 	start.tv_sec += start.tv_nsec / NSEC_PER_SEC;
194 	start.tv_nsec = start.tv_nsec % NSEC_PER_SEC;
195 	tst_resm(TINFO, "restoring time to %s", strtime(&start));
196 	/* calls clock_was_set() in kernel to revert inconsistency */
197 	if (clock_settime(CLOCK_REALTIME, &start) < 0)
198 		tst_brkm(TBROK | TERRNO, cleanup, "clock_settime");
199 
200 	test_hrtimer_early_expiration();
201 }
202 
setup(void)203 static void setup(void)
204 {
205 	tst_require_root();
206 	tst_sig(NOFORK, DEF_HANDLER, CLEANUP);
207 	TEST_PAUSE;
208 }
209 
cleanup(void)210 static void cleanup(void)
211 {
212 	struct timespec now;
213 	clock_gettime(CLOCK_REALTIME, &now);
214 	/* Calls clock_was_set() in kernel to revert inconsistency.
215 	 * The only possible EPERM doesn't matter here. */
216 	clock_settime(CLOCK_REALTIME, &now);
217 }
218