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