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
38char *TCID = "leapsec_timer";
39int TST_TOTAL = 1;
40
41static inline int in_order(struct timespec a, struct timespec b);
42static void adjtimex_status(struct timex *tx, int status);
43static const char *strtime(const struct timespec *now);
44static void test_hrtimer_early_expiration(void);
45static void run_leapsec(void);
46static void setup(void);
47static void cleanup(void);
48
49int 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
66static 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
77static 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
107static 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
122static 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
147static 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
203static void setup(void)
204{
205	tst_require_root();
206	tst_sig(NOFORK, DEF_HANDLER, CLEANUP);
207	TEST_PAUSE;
208}
209
210static 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