Home | History | Annotate | Download | only in leapsec
      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 (at) 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 
     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 
     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 
     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 
    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 
    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 
    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 
    203 static void setup(void)
    204 {
    205 	tst_require_root();
    206 	tst_sig(NOFORK, DEF_HANDLER, CLEANUP);
    207 	TEST_PAUSE;
    208 }
    209 
    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