1 /*- 2 * Copyright (c) 2025 Klara, Inc. 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7 #include <sys/stat.h> 8 #include <sys/wait.h> 9 10 #include <dlfcn.h> 11 #include <fcntl.h> 12 #include <limits.h> 13 #include <poll.h> 14 #include <stdarg.h> 15 #include <stdbool.h> 16 #include <stdio.h> 17 #include <stdlib.h> 18 #include <time.h> 19 #include <unistd.h> 20 21 #include <atf-c.h> 22 23 static const struct tzcase { 24 const char *tzfn; 25 const char *expect; 26 } tzcases[] = { 27 /* 28 * A handful of time zones and the expected result of 29 * strftime("%z (%Z)", tm) when that time zone is active 30 * and tm represents a date in the summer of 2025. 31 */ 32 { "America/Vancouver", "-0700 (PDT)" }, 33 { "America/New_York", "-0400 (EDT)" }, 34 { "Europe/London", "+0100 (BST)" }, 35 { "Europe/Paris", "+0200 (CEST)" }, 36 { "Asia/Kolkata", "+0530 (IST)" }, 37 { "Asia/Tokyo", "+0900 (JST)" }, 38 { "Australia/Canberra", "+1000 (AEST)" }, 39 { "UTC", "+0000 (UTC)" }, 40 { 0 }, 41 }; 42 43 static const time_t then = 1751328000; /* 2025-07-01 00:00:00 UTC */ 44 static const char *tz_change_interval_sym = "__tz_change_interval"; 45 static int *tz_change_interval_p; 46 static const int tz_change_interval = 3; 47 static int tz_change_timeout = 90; 48 49 static bool debugging; 50 51 static void 52 debug(const char *fmt, ...) 53 { 54 va_list ap; 55 56 if (debugging) { 57 va_start(ap, fmt); 58 vfprintf(stderr, fmt, ap); 59 va_end(ap); 60 fputc('\n', stderr); 61 } 62 } 63 64 static void 65 change_tz(const char *tzn) 66 { 67 static const char *zfn = "/usr/share/zoneinfo"; 68 static const char *tfn = "root/etc/.localtime"; 69 static const char *dfn = "root/etc/localtime"; 70 ssize_t clen; 71 int zfd, sfd, dfd; 72 73 ATF_REQUIRE((zfd = open(zfn, O_DIRECTORY | O_SEARCH)) >= 0); 74 ATF_REQUIRE((sfd = openat(zfd, tzn, O_RDONLY)) >= 0); 75 ATF_REQUIRE((dfd = open(tfn, O_CREAT | O_TRUNC | O_WRONLY)) >= 0); 76 do { 77 clen = copy_file_range(sfd, NULL, dfd, NULL, SSIZE_MAX, 0); 78 ATF_REQUIRE_MSG(clen != -1, "failed to copy %s/%s: %m", 79 zfn, tzn); 80 } while (clen > 0); 81 ATF_CHECK_EQ(0, close(dfd)); 82 ATF_CHECK_EQ(0, close(sfd)); 83 ATF_CHECK_EQ(0, close(zfd)); 84 ATF_REQUIRE_EQ(0, rename(tfn, dfn)); 85 debug("time zone %s installed", tzn); 86 } 87 88 /* 89 * Test time zone change detection. 90 * 91 * The parent creates a chroot containing only /etc/localtime, initially 92 * set to UTC. It then forks a child which enters the chroot, repeatedly 93 * checks the current time zone, and prints it to stdout if it changes 94 * (including once on startup). Meanwhile, the parent waits for output 95 * from the child. Every time it receives a line of text from the child, 96 * it checks that it is as expected, then changes /etc/localtime within 97 * the chroot to the next case in the list. Once it reaches the end of 98 * the list, it closes a pipe to notify the child, which terminates. 99 * 100 * Note that ATF and / or Kyua may have set the timezone before the test 101 * case starts (even unintentionally). Therefore, we start the test only 102 * after we've received and discarded the first report from the child, 103 * which should come almost immediately on startup. 104 */ 105 ATF_TC(detect_tz_changes); 106 ATF_TC_HEAD(detect_tz_changes, tc) 107 { 108 atf_tc_set_md_var(tc, "descr", "Test timezone change detection"); 109 atf_tc_set_md_var(tc, "require.user", "root"); 110 atf_tc_set_md_var(tc, "timeout", "600"); 111 } 112 ATF_TC_BODY(detect_tz_changes, tc) 113 { 114 char obuf[1024] = ""; 115 char ebuf[1024] = ""; 116 struct pollfd fds[3]; 117 int opd[2], epd[2], spd[2]; 118 time_t changed, now; 119 const struct tzcase *tzcase = NULL; 120 struct tm *tm; 121 size_t olen = 0, elen = 0; 122 ssize_t rlen; 123 long curoff = LONG_MIN; 124 pid_t pid; 125 int nfds, status; 126 127 /* speed up the test if possible */ 128 tz_change_interval_p = dlsym(RTLD_SELF, tz_change_interval_sym); 129 if (tz_change_interval_p != NULL && 130 *tz_change_interval_p > tz_change_interval) { 131 debug("reducing detection interval from %d to %d", 132 *tz_change_interval_p, tz_change_interval); 133 *tz_change_interval_p = tz_change_interval; 134 tz_change_timeout = tz_change_interval * 3; 135 } 136 /* prepare chroot */ 137 ATF_REQUIRE_EQ(0, mkdir("root", 0755)); 138 ATF_REQUIRE_EQ(0, mkdir("root/etc", 0755)); 139 change_tz("UTC"); 140 time(&changed); 141 /* output, error, sync pipes */ 142 if (pipe(opd) != 0 || pipe(epd) != 0 || pipe(spd) != 0) 143 atf_tc_fail("failed to pipe"); 144 /* fork child */ 145 if ((pid = fork()) < 0) 146 atf_tc_fail("failed to fork"); 147 if (pid == 0) { 148 /* child */ 149 dup2(opd[1], STDOUT_FILENO); 150 close(opd[0]); 151 close(opd[1]); 152 dup2(epd[1], STDERR_FILENO); 153 close(epd[0]); 154 close(epd[1]); 155 close(spd[0]); 156 unsetenv("TZ"); 157 ATF_REQUIRE_EQ(0, chroot("root")); 158 ATF_REQUIRE_EQ(0, chdir("/")); 159 fds[0].fd = spd[1]; 160 fds[0].events = POLLIN; 161 for (;;) { 162 ATF_REQUIRE(poll(fds, 1, 100) >= 0); 163 if (fds[0].revents & POLLHUP) { 164 /* parent closed sync pipe */ 165 _exit(0); 166 } 167 ATF_REQUIRE((tm = localtime(&then)) != NULL); 168 if (tm->tm_gmtoff == curoff) 169 continue; 170 olen = strftime(obuf, sizeof(obuf), "%z (%Z)", tm); 171 ATF_REQUIRE(olen > 0); 172 fprintf(stdout, "%s\n", obuf); 173 fflush(stdout); 174 curoff = tm->tm_gmtoff; 175 } 176 _exit(2); 177 } 178 /* parent */ 179 close(opd[1]); 180 close(epd[1]); 181 close(spd[1]); 182 /* receive output until child terminates */ 183 fds[0].fd = opd[0]; 184 fds[0].events = POLLIN; 185 fds[1].fd = epd[0]; 186 fds[1].events = POLLIN; 187 fds[2].fd = spd[0]; 188 fds[2].events = POLLIN; 189 nfds = 3; 190 for (;;) { 191 ATF_REQUIRE(poll(fds, 3, 1000) >= 0); 192 time(&now); 193 if (fds[0].revents & POLLIN && olen < sizeof(obuf)) { 194 rlen = read(opd[0], obuf + olen, sizeof(obuf) - olen); 195 ATF_REQUIRE(rlen >= 0); 196 olen += rlen; 197 } 198 if (olen > 0) { 199 ATF_REQUIRE_EQ('\n', obuf[olen - 1]); 200 obuf[--olen] = '\0'; 201 /* tzcase will be NULL at first */ 202 if (tzcase != NULL) { 203 debug("%s", obuf); 204 ATF_REQUIRE_STREQ(tzcase->expect, obuf); 205 debug("change to %s detected after %d s", 206 tzcase->tzfn, (int)(now - changed)); 207 if (tz_change_interval_p != NULL) { 208 ATF_CHECK((int)(now - changed) >= 209 *tz_change_interval_p - 1); 210 ATF_CHECK((int)(now - changed) <= 211 *tz_change_interval_p + 1); 212 } 213 } 214 olen = 0; 215 /* first / next test case */ 216 if (tzcase == NULL) 217 tzcase = tzcases; 218 else 219 tzcase++; 220 if (tzcase->tzfn == NULL) { 221 /* test is over */ 222 break; 223 } 224 change_tz(tzcase->tzfn); 225 changed = now; 226 } 227 if (fds[1].revents & POLLIN && elen < sizeof(ebuf)) { 228 rlen = read(epd[0], ebuf + elen, sizeof(ebuf) - elen); 229 ATF_REQUIRE(rlen >= 0); 230 elen += rlen; 231 } 232 if (elen > 0) { 233 ATF_REQUIRE_EQ(elen, fwrite(ebuf, 1, elen, stderr)); 234 elen = 0; 235 } 236 if (nfds > 2 && fds[2].revents & POLLHUP) { 237 /* child closed sync pipe */ 238 break; 239 } 240 /* 241 * The timeout for this test case is set to 10 minutes, 242 * because it can take that long to run with the default 243 * 61-second interval. However, each individual tzcase 244 * entry should not take much longer than the detection 245 * interval to test, so we can detect a problem long 246 * before Kyua terminates us. 247 */ 248 if ((now - changed) > tz_change_timeout) { 249 close(spd[0]); 250 if (tz_change_interval_p == NULL && 251 tzcase == tzcases) { 252 /* 253 * The most likely explanation in this 254 * case is that libc was built without 255 * time zone change detection. 256 */ 257 atf_tc_skip("time zone change detection " 258 "does not appear to be enabled"); 259 } 260 atf_tc_fail("timed out waiting for change to %s " 261 "to be detected", tzcase->tzfn); 262 } 263 } 264 close(opd[0]); 265 close(epd[0]); 266 close(spd[0]); /* this will wake up and terminate the child */ 267 if (olen > 0) 268 ATF_REQUIRE_EQ(olen, fwrite(obuf, 1, olen, stdout)); 269 if (elen > 0) 270 ATF_REQUIRE_EQ(elen, fwrite(ebuf, 1, elen, stderr)); 271 ATF_REQUIRE_EQ(pid, waitpid(pid, &status, 0)); 272 ATF_REQUIRE(WIFEXITED(status)); 273 ATF_REQUIRE_EQ(0, WEXITSTATUS(status)); 274 } 275 276 ATF_TC(tz_env); 277 ATF_TC_HEAD(tz_env, tc) 278 { 279 atf_tc_set_md_var(tc, "descr", "Test TZ environment variable"); 280 } 281 ATF_TC_BODY(tz_env, tc) 282 { 283 char buf[128]; 284 const struct tzcase *tzcase = NULL; 285 struct tm *tm; 286 size_t len; 287 288 for (tzcase = tzcases; tzcase->tzfn != NULL; tzcase++) { 289 setenv("TZ", tzcase->tzfn, 1); 290 ATF_REQUIRE((tm = localtime(&then)) != NULL); 291 len = strftime(buf, sizeof(buf), "%z (%Z)", tm); 292 ATF_REQUIRE(len > 0); 293 ATF_REQUIRE_STREQ(tzcase->expect, buf); 294 } 295 } 296 297 ATF_TP_ADD_TCS(tp) 298 { 299 debugging = !getenv("__RUNNING_INSIDE_ATF_RUN") && 300 isatty(STDERR_FILENO); 301 ATF_TP_ADD_TC(tp, detect_tz_changes); 302 ATF_TP_ADD_TC(tp, tz_env); 303 return (atf_no_error()); 304 } 305