date.c revision 1.68 1 /* $NetBSD: date.c,v 1.68 2024/09/17 11:30:11 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.68 2024/09/17 11:30:11 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
100 setprogname(argv[0]);
101 (void)setlocale(LC_ALL, "");
102
103 while ((ch = getopt(argc, argv, "ad:f:jnRr:u")) != -1) {
104 switch (ch) {
105 case 'a': /* adjust time slowly */
106 aflag = 1;
107 nflag = 1;
108 break;
109 case 'd':
110 rflag = 1;
111 #ifdef HAVE_NBTOOL_CONFIG_H
112 if (parse_iso_datetime(&tval, optarg))
113 break;
114 errx(EXIT_FAILURE,
115 "-d only supports ISO format in the tool version");
116 break;
117 #else
118 errno = 0;
119 tval = parsedate(optarg, NULL, NULL);
120 if (tval == -1 && errno != 0) {
121 errx(EXIT_FAILURE,
122 "%s: Unrecognized date format", optarg);
123 }
124 break;
125 case 'f':
126 fmt = optarg;
127 break;
128 #endif
129 case 'j': /* don't set time */
130 jflag = 1;
131 break;
132 case 'n': /* don't set network */
133 nflag = 1;
134 break;
135 case 'R': /* RFC-5322 email format */
136 Rflag = 1;
137 break;
138 case 'r': /* user specified seconds */
139 if (optarg[0] == '\0') {
140 errx(EXIT_FAILURE, "<empty>: Invalid number");
141 }
142 errno = 0;
143 val = strtoll(optarg, &buf, 0);
144 if (errno) {
145 err(EXIT_FAILURE, "%s", optarg);
146 }
147 if (optarg[0] == '\0' || *buf != '\0') {
148 errx(EXIT_FAILURE,
149 "%s: Invalid number", optarg);
150 }
151 rflag = 1;
152 tval = (time_t)val;
153 break;
154 case 'u': /* do everything in UTC */
155 (void)setenv("TZ", "UTC0", 1);
156 break;
157 default:
158 usage();
159 }
160 }
161 argc -= optind;
162 argv += optind;
163
164 if (!rflag && time(&tval) == -1)
165 err(EXIT_FAILURE, "time");
166
167
168 /* allow the operands in any order */
169 if (*argv && **argv == '+') {
170 format = *argv;
171 ++argv;
172 } else if (Rflag) {
173 (void)setlocale(LC_TIME, "C");
174 format = "+%a, %-e %b %Y %H:%M:%S %z";
175 } else
176 format = "+%a %b %e %H:%M:%S %Z %Y";
177
178 if (*argv) {
179 setthetime(*argv);
180 ++argv;
181 #ifndef HAVE_NBTOOL_CONFIG_H
182 } else if (fmt) {
183 usage();
184 #endif
185 }
186
187 if (*argv && **argv == '+')
188 format = *argv;
189
190 if ((buf = malloc(bufsiz = 1024)) == NULL)
191 goto bad;
192
193 if ((tm = localtime(&tval)) == NULL)
194 err(EXIT_FAILURE, "%lld: localtime", (long long)tval);
195
196 while (strftime(buf, bufsiz, format, tm) == 0)
197 if ((buf = realloc(buf, bufsiz <<= 1)) == NULL)
198 goto bad;
199
200 (void)printf("%s\n", buf + 1);
201 free(buf);
202 return 0;
203 bad:
204 err(EXIT_FAILURE, "Cannot allocate format buffer");
205 }
206
207 static void
208 badcanotime(const char *msg, const char *val, size_t where)
209 {
210 warnx("%s in canonical time", msg);
211 warnx("%s", val);
212 warnx("%*s", (int)where + 1, "^");
213 usage();
214 }
215
216 #define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0'))
217
218 #if HAVE_NBTOOL_CONFIG_H
219
220 inline static int
221 digitstring(const char *s, int len)
222 {
223 while (--len > 0) {
224 if (!isdigit(*(unsigned char *)s))
225 return 0;
226 s++;
227 }
228 return 1;
229 }
230
231 static int
232 parse_iso_datetime(time_t * res, const char * string)
233 {
234 struct tm tm;
235 time_t t;
236
237 memset(&tm, 0, sizeof tm);
238
239 if (!digitstring(string, 4))
240 return 0;
241 tm.tm_year = ATOI2(string) * 100;
242 tm.tm_year += ATOI2(string);
243 tm.tm_year -= 1900;
244
245 if (*string == '-')
246 string++;
247
248 if (!digitstring(string, 2))
249 return 0;
250
251 tm.tm_mon = ATOI2(string);
252 if (tm.tm_mon < 1 || tm.tm_mon > 12)
253 return 0;
254 tm.tm_mon--;
255
256 if (*string == '-')
257 string++;
258
259 if (!digitstring(string, 2))
260 return 0;
261
262 tm.tm_mday = ATOI2(string);
263 if (tm.tm_mday < 1)
264 return 0;
265 switch (tm.tm_mon) {
266 case 0: case 2: case 4: case 6: case 7: case 9: case 11:
267 if (tm.tm_mday > 31)
268 return 0;
269 break;
270 case 3: case 5: case 8: case 10:
271 if (tm.tm_mday > 30)
272 return 0;
273 break;
274 case 1:
275 if (tm.tm_mday > 28 + isleap(tm.tm_year + 1900))
276 return 0;
277 break;
278 default:
279 abort();
280 }
281
282 do {
283 if (*string == '\0')
284 break;
285 if (*string == 'T' || *string == 't' || *string == ' ' ||
286 *string == '-')
287 string++;
288
289 if (!digitstring(string, 2))
290 return 0;
291 tm.tm_hour = ATOI2(string);
292 if (tm.tm_hour > 23)
293 return 0;
294
295 if (*string == '\0')
296 break;
297 if (*string == ':')
298 string++;
299
300 if (!digitstring(string, 2))
301 return 0;
302 tm.tm_min = ATOI2(string);
303 if (tm.tm_min >= 60)
304 return 0;
305
306 if (*string == '\0')
307 break;
308 if (*string == ':')
309 string++;
310
311 if (!digitstring(string, 2))
312 return 0;
313 tm.tm_sec = ATOI2(string);
314 if (tm.tm_sec >= 60)
315 return 0;
316 } while (0);
317
318 if (*string != '\0')
319 return 0;
320
321 tm.tm_isdst = -1;
322 tm.tm_wday = -1;
323
324 t = mktime(&tm);
325 if (tm.tm_wday == -1)
326 return 0;
327
328 *res = t;
329 return 1;
330 }
331
332 #endif /*NBTOOL*/
333
334 static void
335 setthetime(const char *p)
336 {
337 struct timeval tv;
338 time_t new_time;
339 struct tm *lt;
340 const char *dot, *t, *op;
341 size_t len;
342 int yearset;
343
344 if ((lt = localtime(&tval)) == NULL)
345 err(EXIT_FAILURE, "%lld: localtime", (long long)tval);
346
347 lt->tm_isdst = -1; /* Divine correct DST */
348
349 #ifndef HAVE_NBTOOL_CONFIG_H
350 if (fmt) {
351 t = strptime(p, fmt, lt);
352 if (t == NULL) {
353 warnx("Failed conversion of ``%s''"
354 " using format ``%s''\n", p, fmt);
355 } else if (*t != '\0')
356 warnx("Ignoring %zu extraneous"
357 " characters in date string (%s)",
358 strlen(t), t);
359 goto setit;
360 }
361 #endif
362 for (t = p, dot = NULL; *t; ++t) {
363 if (*t == '.') {
364 if (dot == NULL) {
365 dot = t;
366 } else {
367 badcanotime("Unexpected dot", p, t - p);
368 }
369 } else if (!isdigit((unsigned char)*t)) {
370 badcanotime("Expected digit", p, t - p);
371 }
372 }
373
374
375 if (dot != NULL) { /* .ss */
376 len = strlen(dot);
377 if (len > 3) {
378 badcanotime("Unexpected digit after seconds field",
379 p, strlen(p) - 1);
380 } else if (len < 3) {
381 badcanotime("Expected digit in seconds field",
382 p, strlen(p));
383 }
384 ++dot;
385 lt->tm_sec = ATOI2(dot);
386 if (lt->tm_sec > 61)
387 badcanotime("Seconds out of range", p, strlen(p) - 1);
388 } else {
389 len = 0;
390 lt->tm_sec = 0;
391 }
392
393 op = p;
394 yearset = 0;
395 switch (strlen(p) - len) {
396 case 12: /* cc */
397 lt->tm_year = ATOI2(p) * 100 - TM_YEAR_BASE;
398 if (lt->tm_year < 0)
399 badcanotime("Year before 1900", op, p - op + 1);
400 yearset = 1;
401 /* FALLTHROUGH */
402 case 10: /* yy */
403 if (yearset) {
404 lt->tm_year += ATOI2(p);
405 } else {
406 yearset = ATOI2(p);
407 if (yearset < 69)
408 lt->tm_year = yearset + 2000 - TM_YEAR_BASE;
409 else
410 lt->tm_year = yearset + 1900 - TM_YEAR_BASE;
411 }
412 /* FALLTHROUGH */
413 case 8: /* mm */
414 lt->tm_mon = ATOI2(p);
415 if (lt->tm_mon > 12 || lt->tm_mon == 0)
416 badcanotime("Month out of range", op, p - op - 1);
417 --lt->tm_mon; /* time struct is 0 - 11 */
418 /* FALLTHROUGH */
419 case 6: /* dd */
420 lt->tm_mday = ATOI2(p);
421 switch (lt->tm_mon) {
422 case 0:
423 case 2:
424 case 4:
425 case 6:
426 case 7:
427 case 9:
428 case 11:
429 if (lt->tm_mday > 31 || lt->tm_mday == 0)
430 badcanotime("Day out of range (max 31)",
431 op, p - op - 1);
432 break;
433 case 3:
434 case 5:
435 case 8:
436 case 10:
437 if (lt->tm_mday > 30 || lt->tm_mday == 0)
438 badcanotime("Day out of range (max 30)",
439 op, p - op - 1);
440 break;
441 case 1:
442 if (isleap(lt->tm_year + TM_YEAR_BASE)) {
443 if (lt->tm_mday > 29 || lt->tm_mday == 0) {
444 badcanotime("Day out of range "
445 "(max 29)",
446 op, p - op - 1);
447 }
448 } else {
449 if (lt->tm_mday > 28 || lt->tm_mday == 0) {
450 badcanotime("Day out of range "
451 "(max 28)",
452 op, p - op - 1);
453 }
454 }
455 break;
456 default:
457 /*
458 * If the month was given, it's already been
459 * checked. If a bad value came back from
460 * localtime, something's badly broken.
461 * (make this an assertion?)
462 */
463 errx(EXIT_FAILURE, "localtime gave invalid month %d",
464 lt->tm_mon);
465 }
466 /* FALLTHROUGH */
467 case 4: /* hh */
468 lt->tm_hour = ATOI2(p);
469 if (lt->tm_hour > 23)
470 badcanotime("Hour out of range", op, p - op - 1);
471 /* FALLTHROUGH */
472 case 2: /* mm */
473 lt->tm_min = ATOI2(p);
474 if (lt->tm_min > 59)
475 badcanotime("Minute out of range", op, p - op - 1);
476 break;
477 case 0: /* was just .sss */
478 if (len != 0)
479 break;
480 /* FALLTHROUGH */
481 default:
482 if (strlen(p) - len > 12) {
483 badcanotime("Too many digits", p, 12);
484 } else {
485 badcanotime("Not enough digits", p, strlen(p) - len);
486 }
487 }
488 setit:
489 /* convert broken-down time to UTC clock time */
490 if ((new_time = mktime(lt)) == -1) {
491 /* Can this actually happen? */
492 err(EXIT_FAILURE, "mktime");
493 }
494
495 /* if jflag is set, don't actually change the time, just return */
496 if (jflag) {
497 tval = new_time;
498 return;
499 }
500
501 /* set the time */
502 #ifndef HAVE_NBTOOL_CONFIG_H
503 struct utmpx utx;
504 memset(&utx, 0, sizeof(utx));
505 utx.ut_type = OLD_TIME;
506 (void)gettimeofday(&utx.ut_tv, NULL);
507 pututxline(&utx);
508
509 if (nflag || netsettime(new_time)) {
510 logwtmp("|", "date", "");
511 if (aflag) {
512 tv.tv_sec = new_time - tval;
513 tv.tv_usec = 0;
514 if (adjtime(&tv, NULL))
515 err(EXIT_FAILURE, "adjtime");
516 } else {
517 tval = new_time;
518 tv.tv_sec = tval;
519 tv.tv_usec = 0;
520 if (settimeofday(&tv, NULL))
521 err(EXIT_FAILURE, "settimeofday");
522 }
523 logwtmp("{", "date", "");
524 }
525 utx.ut_type = NEW_TIME;
526 (void)gettimeofday(&utx.ut_tv, NULL);
527 pututxline(&utx);
528
529 if ((p = getlogin()) == NULL)
530 p = "???";
531 syslog(LOG_AUTH | LOG_NOTICE, "date set by %s", p);
532 #else
533 errx(EXIT_FAILURE, "Can't set the time in the tools version");
534 #endif
535 }
536
537 static void
538 usage(void)
539 {
540 (void)fprintf(stderr,
541 "Usage: %s [-ajnRu] [-d date] [-r seconds] [+format]",
542 getprogname());
543 (void)fprintf(stderr, " [[[[[[CC]yy]mm]dd]HH]MM[.SS]]\n");
544 (void)fprintf(stderr,
545 " %s [-ajnRu] -f input_format new_date [+format]\n",
546 getprogname());
547 exit(EXIT_FAILURE);
548 /* NOTREACHED */
549 }
550