date.c revision 1.70 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