1 /* $NetBSD: date.c,v 1.70 2024/09/17 15:25:39 kre Exp $ */ 2 3 /* 4 * Copyright (c) 1985, 1987, 1988, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #if HAVE_NBTOOL_CONFIG_H 33 #include "nbtool_config.h" 34 #endif 35 36 #include <sys/cdefs.h> 37 #ifndef lint 38 __COPYRIGHT( 39 "@(#) Copyright (c) 1985, 1987, 1988, 1993\ 40 The Regents of the University of California. All rights reserved."); 41 #endif /* not lint */ 42 43 #ifndef lint 44 #if 0 45 static char sccsid[] = "@(#)date.c 8.2 (Berkeley) 4/28/95"; 46 #else 47 __RCSID("$NetBSD: date.c,v 1.70 2024/09/17 15:25:39 kre Exp $"); 48 #endif 49 #endif /* not lint */ 50 51 #include <sys/param.h> 52 #include <sys/time.h> 53 54 #include <ctype.h> 55 #include <err.h> 56 #include <fcntl.h> 57 #include <errno.h> 58 #include <locale.h> 59 #include <stdio.h> 60 #include <stdlib.h> 61 #include <string.h> 62 #include <syslog.h> 63 #include <time.h> 64 #include <tzfile.h> 65 #include <unistd.h> 66 #include <util.h> 67 #if !HAVE_NBTOOL_CONFIG_H 68 #include <utmpx.h> 69 #endif 70 71 #include "extern.h" 72 73 static time_t tval; 74 static int Rflag, aflag, jflag, rflag, nflag; 75 76 __dead static void badcanotime(const char *, const char *, size_t); 77 static void setthetime(const char *); 78 __dead static void usage(void); 79 80 #if HAVE_NBTOOL_CONFIG_H 81 static int parse_iso_datetime(time_t *, const char *); 82 #else 83 static char *fmt; 84 #endif 85 86 #if !defined(isleap) 87 # define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) 88 #endif 89 90 int 91 main(int argc, char *argv[]) 92 { 93 char *buf; 94 size_t bufsiz; 95 const char *format; 96 int ch; 97 long long val; 98 struct tm *tm; 99 char *default_tz; 100 101 setprogname(argv[0]); 102 (void)setlocale(LC_ALL, ""); 103 104 default_tz = getenv("TZ"); 105 106 while ((ch = getopt(argc, argv, "ad:f:jnRr:Uuz:")) != -1) { 107 switch (ch) { 108 case 'a': /* adjust time slowly */ 109 aflag = 1; 110 nflag = 1; 111 break; 112 case 'd': 113 rflag = 1; 114 #ifdef HAVE_NBTOOL_CONFIG_H 115 if (parse_iso_datetime(&tval, optarg)) 116 break; 117 errx(EXIT_FAILURE, 118 "-d only supports ISO format in the tool version"); 119 break; 120 #else 121 errno = 0; 122 tval = parsedate(optarg, NULL, NULL); 123 if (tval == -1 && errno != 0) { 124 errx(EXIT_FAILURE, 125 "%s: Unrecognized date format", optarg); 126 } 127 break; 128 case 'f': 129 fmt = optarg; 130 break; 131 #endif 132 case 'j': /* don't set time */ 133 jflag = 1; 134 break; 135 case 'n': /* don't set network */ 136 nflag = 1; 137 break; 138 case 'R': /* RFC-5322 email format */ 139 Rflag = 1; 140 break; 141 case 'r': /* user specified seconds */ 142 if (optarg[0] == '\0') { 143 errx(EXIT_FAILURE, "<empty>: Invalid number"); 144 } 145 errno = 0; 146 val = strtoll(optarg, &buf, 0); 147 if (errno) { 148 err(EXIT_FAILURE, "%s", optarg); 149 } 150 if (optarg[0] == '\0' || *buf != '\0') { 151 errx(EXIT_FAILURE, 152 "%s: Invalid number", optarg); 153 } 154 rflag = 1; 155 tval = (time_t)val; 156 break; 157 case 'U': /* reset to default timezone */ 158 if (default_tz) 159 (void)setenv("TZ", default_tz, 1); 160 else 161 (void)unsetenv("TZ"); 162 break; 163 case 'u': /* do everything in UTC */ 164 (void)setenv("TZ", "UTC0", 1); 165 break; 166 case 'z': 167 if (optarg[0] == '\0') 168 (void)unsetenv("TZ"); 169 else 170 (void)setenv("TZ", optarg, 1); 171 break; 172 default: 173 usage(); 174 } 175 } 176 argc -= optind; 177 argv += optind; 178 179 if (!rflag && time(&tval) == -1) 180 err(EXIT_FAILURE, "time"); 181 182 183 /* allow the operands in any order */ 184 if (*argv && **argv == '+') { 185 format = *argv; 186 ++argv; 187 } else if (Rflag) { 188 (void)setlocale(LC_TIME, "C"); 189 format = "+%a, %-e %b %Y %H:%M:%S %z"; 190 } else 191 format = "+%a %b %e %H:%M:%S %Z %Y"; 192 193 if (*argv) { 194 setthetime(*argv); 195 ++argv; 196 #ifndef HAVE_NBTOOL_CONFIG_H 197 } else if (fmt) { 198 usage(); 199 #endif 200 } 201 202 if (*argv && **argv == '+') 203 format = *argv; 204 205 if ((buf = malloc(bufsiz = 1024)) == NULL) 206 goto bad; 207 208 if ((tm = localtime(&tval)) == NULL) 209 err(EXIT_FAILURE, "%lld: localtime", (long long)tval); 210 211 while (strftime(buf, bufsiz, format, tm) == 0) 212 if ((buf = realloc(buf, bufsiz <<= 1)) == NULL) 213 goto bad; 214 215 (void)printf("%s\n", buf + 1); 216 free(buf); 217 return 0; 218 bad: 219 err(EXIT_FAILURE, "Cannot allocate format buffer"); 220 } 221 222 static void 223 badcanotime(const char *msg, const char *val, size_t where) 224 { 225 warnx("%s in canonical time", msg); 226 warnx("%s", val); 227 warnx("%*s", (int)where + 1, "^"); 228 usage(); 229 } 230 231 #define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0')) 232 233 #if HAVE_NBTOOL_CONFIG_H 234 235 inline static int 236 digitstring(const char *s, int len) 237 { 238 while (--len > 0) { 239 if (!isdigit(*(unsigned char *)s)) 240 return 0; 241 s++; 242 } 243 return 1; 244 } 245 246 static int 247 parse_iso_datetime(time_t * res, const char * string) 248 { 249 struct tm tm; 250 time_t t; 251 252 memset(&tm, 0, sizeof tm); 253 254 if (!digitstring(string, 4)) 255 return 0; 256 tm.tm_year = ATOI2(string) * 100; 257 tm.tm_year += ATOI2(string); 258 tm.tm_year -= 1900; 259 260 if (*string == '-') 261 string++; 262 263 if (!digitstring(string, 2)) 264 return 0; 265 266 tm.tm_mon = ATOI2(string); 267 if (tm.tm_mon < 1 || tm.tm_mon > 12) 268 return 0; 269 tm.tm_mon--; 270 271 if (*string == '-') 272 string++; 273 274 if (!digitstring(string, 2)) 275 return 0; 276 277 tm.tm_mday = ATOI2(string); 278 if (tm.tm_mday < 1) 279 return 0; 280 switch (tm.tm_mon) { 281 case 0: case 2: case 4: case 6: case 7: case 9: case 11: 282 if (tm.tm_mday > 31) 283 return 0; 284 break; 285 case 3: case 5: case 8: case 10: 286 if (tm.tm_mday > 30) 287 return 0; 288 break; 289 case 1: 290 if (tm.tm_mday > 28 + isleap(tm.tm_year + 1900)) 291 return 0; 292 break; 293 default: 294 abort(); 295 } 296 297 do { 298 if (*string == '\0') 299 break; 300 if (*string == 'T' || *string == 't' || *string == ' ' || 301 *string == '-') 302 string++; 303 304 if (!digitstring(string, 2)) 305 return 0; 306 tm.tm_hour = ATOI2(string); 307 if (tm.tm_hour > 23) 308 return 0; 309 310 if (*string == '\0') 311 break; 312 if (*string == ':') 313 string++; 314 315 if (!digitstring(string, 2)) 316 return 0; 317 tm.tm_min = ATOI2(string); 318 if (tm.tm_min >= 60) 319 return 0; 320 321 if (*string == '\0') 322 break; 323 if (*string == ':') 324 string++; 325 326 if (!digitstring(string, 2)) 327 return 0; 328 tm.tm_sec = ATOI2(string); 329 if (tm.tm_sec >= 60) 330 return 0; 331 } while (0); 332 333 if (*string != '\0') 334 return 0; 335 336 tm.tm_isdst = -1; 337 tm.tm_wday = -1; 338 339 t = mktime(&tm); 340 if (tm.tm_wday == -1) 341 return 0; 342 343 *res = t; 344 return 1; 345 } 346 347 #endif /*NBTOOL*/ 348 349 static void 350 setthetime(const char *p) 351 { 352 struct timeval tv; 353 time_t new_time; 354 struct tm *lt; 355 const char *dot, *t, *op; 356 size_t len; 357 int yearset; 358 359 if ((lt = localtime(&tval)) == NULL) 360 err(EXIT_FAILURE, "%lld: localtime", (long long)tval); 361 362 lt->tm_isdst = -1; /* Divine correct DST */ 363 364 #ifndef HAVE_NBTOOL_CONFIG_H 365 if (fmt) { 366 t = strptime(p, fmt, lt); 367 if (t == NULL) { 368 warnx("Failed conversion of ``%s''" 369 " using format ``%s''\n", p, fmt); 370 } else if (*t != '\0') 371 warnx("Ignoring %zu extraneous" 372 " characters in date string (%s)", 373 strlen(t), t); 374 goto setit; 375 } 376 if (getenv("POSIXLY_CORRECT") != NULL) { 377 int yrdigs; 378 const char * const e = "Bad POSIX format date ``%s''"; 379 380 t = strptime(p, "%m%d%H%M", lt); 381 if (t == NULL) 382 errx(EXIT_FAILURE, e, p); 383 if (*t != '\0') { 384 yrdigs = strspn(t, "0123456789"); 385 if (yrdigs != 2 && yrdigs != 4) 386 errx(EXIT_FAILURE, e, p); 387 t = strptime(t, yrdigs == 2 ? "%y" : "%Y", lt); 388 if (t == NULL || *t != '\0') 389 errx(EXIT_FAILURE, e, p); 390 } 391 goto setit; 392 } 393 #endif 394 for (t = p, dot = NULL; *t; ++t) { 395 if (*t == '.') { 396 if (dot == NULL) { 397 dot = t; 398 } else { 399 badcanotime("Unexpected dot", p, t - p); 400 } 401 } else if (!isdigit((unsigned char)*t)) { 402 badcanotime("Expected digit", p, t - p); 403 } 404 } 405 406 407 if (dot != NULL) { /* .ss */ 408 len = strlen(dot); 409 if (len > 3) { 410 badcanotime("Unexpected digit after seconds field", 411 p, strlen(p) - 1); 412 } else if (len < 3) { 413 badcanotime("Expected digit in seconds field", 414 p, strlen(p)); 415 } 416 ++dot; 417 lt->tm_sec = ATOI2(dot); 418 if (lt->tm_sec > 61) 419 badcanotime("Seconds out of range", p, strlen(p) - 1); 420 } else { 421 len = 0; 422 lt->tm_sec = 0; 423 } 424 425 op = p; 426 yearset = 0; 427 switch (strlen(p) - len) { 428 case 12: /* cc */ 429 lt->tm_year = ATOI2(p) * 100 - TM_YEAR_BASE; 430 if (lt->tm_year < 0) 431 badcanotime("Year before 1900", op, p - op + 1); 432 yearset = 1; 433 /* FALLTHROUGH */ 434 case 10: /* yy */ 435 if (yearset) { 436 lt->tm_year += ATOI2(p); 437 } else { 438 yearset = ATOI2(p); 439 if (yearset < 69) 440 lt->tm_year = yearset + 2000 - TM_YEAR_BASE; 441 else 442 lt->tm_year = yearset + 1900 - TM_YEAR_BASE; 443 } 444 /* FALLTHROUGH */ 445 case 8: /* mm */ 446 lt->tm_mon = ATOI2(p); 447 if (lt->tm_mon > 12 || lt->tm_mon == 0) 448 badcanotime("Month out of range", op, p - op - 1); 449 --lt->tm_mon; /* time struct is 0 - 11 */ 450 /* FALLTHROUGH */ 451 case 6: /* dd */ 452 lt->tm_mday = ATOI2(p); 453 switch (lt->tm_mon) { 454 case 0: 455 case 2: 456 case 4: 457 case 6: 458 case 7: 459 case 9: 460 case 11: 461 if (lt->tm_mday > 31 || lt->tm_mday == 0) 462 badcanotime("Day out of range (max 31)", 463 op, p - op - 1); 464 break; 465 case 3: 466 case 5: 467 case 8: 468 case 10: 469 if (lt->tm_mday > 30 || lt->tm_mday == 0) 470 badcanotime("Day out of range (max 30)", 471 op, p - op - 1); 472 break; 473 case 1: 474 if (isleap(lt->tm_year + TM_YEAR_BASE)) { 475 if (lt->tm_mday > 29 || lt->tm_mday == 0) { 476 badcanotime("Day out of range " 477 "(max 29)", 478 op, p - op - 1); 479 } 480 } else { 481 if (lt->tm_mday > 28 || lt->tm_mday == 0) { 482 badcanotime("Day out of range " 483 "(max 28)", 484 op, p - op - 1); 485 } 486 } 487 break; 488 default: 489 /* 490 * If the month was given, it's already been 491 * checked. If a bad value came back from 492 * localtime, something's badly broken. 493 * (make this an assertion?) 494 */ 495 errx(EXIT_FAILURE, "localtime gave invalid month %d", 496 lt->tm_mon); 497 } 498 /* FALLTHROUGH */ 499 case 4: /* hh */ 500 lt->tm_hour = ATOI2(p); 501 if (lt->tm_hour > 23) 502 badcanotime("Hour out of range", op, p - op - 1); 503 /* FALLTHROUGH */ 504 case 2: /* mm */ 505 lt->tm_min = ATOI2(p); 506 if (lt->tm_min > 59) 507 badcanotime("Minute out of range", op, p - op - 1); 508 break; 509 case 0: /* was just .sss */ 510 if (len != 0) 511 break; 512 /* FALLTHROUGH */ 513 default: 514 if (strlen(p) - len > 12) { 515 badcanotime("Too many digits", p, 12); 516 } else { 517 badcanotime("Not enough digits", p, strlen(p) - len); 518 } 519 } 520 setit: 521 /* convert broken-down time to UTC clock time */ 522 if ((new_time = mktime(lt)) == -1) { 523 /* Can this actually happen? */ 524 err(EXIT_FAILURE, "mktime"); 525 } 526 527 /* if jflag is set, don't actually change the time, just return */ 528 if (jflag) { 529 tval = new_time; 530 return; 531 } 532 533 /* set the time */ 534 #ifndef HAVE_NBTOOL_CONFIG_H 535 struct utmpx utx; 536 memset(&utx, 0, sizeof(utx)); 537 utx.ut_type = OLD_TIME; 538 (void)gettimeofday(&utx.ut_tv, NULL); 539 pututxline(&utx); 540 541 if (nflag || netsettime(new_time)) { 542 logwtmp("|", "date", ""); 543 if (aflag) { 544 tv.tv_sec = new_time - tval; 545 tv.tv_usec = 0; 546 if (adjtime(&tv, NULL)) 547 err(EXIT_FAILURE, "adjtime"); 548 } else { 549 tval = new_time; 550 tv.tv_sec = tval; 551 tv.tv_usec = 0; 552 if (settimeofday(&tv, NULL)) 553 err(EXIT_FAILURE, "settimeofday"); 554 } 555 logwtmp("{", "date", ""); 556 } 557 utx.ut_type = NEW_TIME; 558 (void)gettimeofday(&utx.ut_tv, NULL); 559 pututxline(&utx); 560 561 if ((p = getlogin()) == NULL) 562 p = "???"; 563 syslog(LOG_AUTH | LOG_NOTICE, "date set by %s", p); 564 #else 565 errx(EXIT_FAILURE, "Can't set the time in the tools version"); 566 #endif 567 } 568 569 static void 570 usage(void) 571 { 572 (void)fprintf(stderr, 573 "Usage: %s [-ajnRUu] [-d date] [-r seconds] [-z zone] [+format]", 574 getprogname()); 575 (void)fprintf(stderr, "\n\t%*s[[[[[[CC]yy]mm]dd]HH]MM[.SS]]\n", 576 (int)strlen(getprogname()), ""); 577 (void)fprintf(stderr, 578 " %s [-ajnRu] -f input_format new_date [+format]\n", 579 getprogname()); 580 exit(EXIT_FAILURE); 581 /* NOTREACHED */ 582 } 583