date.c revision 1.65
1/* $NetBSD: date.c,v 1.65 2023/05/31 17:56:54 kim 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 45static char sccsid[] = "@(#)date.c 8.2 (Berkeley) 4/28/95"; 46#else 47__RCSID("$NetBSD: date.c,v 1.65 2023/05/31 17:56:54 kim 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 73static time_t tval; 74static int Rflag, aflag, jflag, rflag, nflag; 75static char *fmt; 76 77__dead static void badcanotime(const char *, const char *, size_t); 78static void setthetime(const char *); 79__dead static void usage(void); 80 81int 82main(int argc, char *argv[]) 83{ 84 char *buf; 85 size_t bufsiz; 86 const char *format; 87 int ch; 88 long long val; 89 struct tm *tm; 90 91 setprogname(argv[0]); 92 (void)setlocale(LC_ALL, ""); 93 94 while ((ch = getopt(argc, argv, "ad:f:jnRr:u")) != -1) { 95 switch (ch) { 96 case 'a': /* adjust time slowly */ 97 aflag = 1; 98 nflag = 1; 99 break; 100 case 'd': 101#ifndef HAVE_NBTOOL_CONFIG_H 102 rflag = 1; 103 tval = parsedate(optarg, NULL, NULL); 104 if (tval == -1) { 105 errx(EXIT_FAILURE, 106 "%s: Unrecognized date format", optarg); 107 } 108 break; 109#else 110 errx(EXIT_FAILURE, 111 "-d not supported in the tool version"); 112#endif 113 case 'f': 114 fmt = optarg; 115 break; 116 case 'j': /* don't set time */ 117 jflag = 1; 118 break; 119 case 'n': /* don't set network */ 120 nflag = 1; 121 break; 122 case 'R': /* RFC-5322 email format */ 123 Rflag = 1; 124 break; 125 case 'r': /* user specified seconds */ 126 if (optarg[0] == '\0') { 127 errx(EXIT_FAILURE, "<empty>: Invalid number"); 128 } 129 errno = 0; 130 val = strtoll(optarg, &buf, 0); 131 if (errno) { 132 err(EXIT_FAILURE, "%s", optarg); 133 } 134 if (optarg[0] == '\0' || *buf != '\0') { 135 errx(EXIT_FAILURE, 136 "%s: Invalid number", optarg); 137 } 138 rflag = 1; 139 tval = (time_t)val; 140 break; 141 case 'u': /* do everything in UTC */ 142 (void)setenv("TZ", "UTC0", 1); 143 break; 144 default: 145 usage(); 146 } 147 } 148 argc -= optind; 149 argv += optind; 150 151 if (!rflag && time(&tval) == -1) 152 err(EXIT_FAILURE, "time"); 153 154 155 /* allow the operands in any order */ 156 if (*argv && **argv == '+') { 157 format = *argv; 158 ++argv; 159 } else if (Rflag) { 160 (void)setlocale(LC_TIME, "C"); 161 format = "+%a, %-e %b %Y %H:%M:%S %z"; 162 } else 163 format = "+%a %b %e %H:%M:%S %Z %Y"; 164 165 if (*argv) { 166 setthetime(*argv); 167 ++argv; 168 } else if (fmt) 169 usage(); 170 171 if (*argv && **argv == '+') 172 format = *argv; 173 174 if ((buf = malloc(bufsiz = 1024)) == NULL) 175 goto bad; 176 177 if ((tm = localtime(&tval)) == NULL) 178 err(EXIT_FAILURE, "%lld: localtime", (long long)tval); 179 180 while (strftime(buf, bufsiz, format, tm) == 0) 181 if ((buf = realloc(buf, bufsiz <<= 1)) == NULL) 182 goto bad; 183 184 (void)printf("%s\n", buf + 1); 185 free(buf); 186 return 0; 187bad: 188 err(EXIT_FAILURE, "Cannot allocate format buffer"); 189} 190 191static void 192badcanotime(const char *msg, const char *val, size_t where) 193{ 194 warnx("%s in canonical time", msg); 195 warnx("%s", val); 196 warnx("%*s", (int)where + 1, "^"); 197 usage(); 198} 199 200#define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0')) 201 202static void 203setthetime(const char *p) 204{ 205 struct timeval tv; 206 time_t new_time; 207 struct tm *lt; 208 const char *dot, *t, *op; 209 size_t len; 210 int yearset; 211 212 if ((lt = localtime(&tval)) == NULL) 213 err(EXIT_FAILURE, "%lld: localtime", (long long)tval); 214 215 lt->tm_isdst = -1; /* Divine correct DST */ 216 217 if (fmt) { 218 t = strptime(p, fmt, lt); 219 if (t == NULL) { 220 warnx("Failed conversion of ``%s''" 221 " using format ``%s''\n", p, fmt); 222 } else if (*t != '\0') 223 warnx("Ignoring %zu extraneous" 224 " characters in date string (%s)", 225 strlen(t), t); 226 goto setit; 227 } 228 for (t = p, dot = NULL; *t; ++t) { 229 if (*t == '.') { 230 if (dot == NULL) { 231 dot = t; 232 } else { 233 badcanotime("Unexpected dot", p, t - p); 234 } 235 } else if (!isdigit((unsigned char)*t)) { 236 badcanotime("Expected digit", p, t - p); 237 } 238 } 239 240 241 if (dot != NULL) { /* .ss */ 242 len = strlen(dot); 243 if (len > 3) { 244 badcanotime("Unexpected digit after seconds field", 245 p, strlen(p) - 1); 246 } else if (len < 3) { 247 badcanotime("Expected digit in seconds field", 248 p, strlen(p)); 249 } 250 ++dot; 251 lt->tm_sec = ATOI2(dot); 252 if (lt->tm_sec > 61) 253 badcanotime("Seconds out of range", p, strlen(p) - 1); 254 } else { 255 len = 0; 256 lt->tm_sec = 0; 257 } 258 259 op = p; 260 yearset = 0; 261 switch (strlen(p) - len) { 262 case 12: /* cc */ 263 lt->tm_year = ATOI2(p) * 100 - TM_YEAR_BASE; 264 if (lt->tm_year < 0) 265 badcanotime("Year before 1900", op, p - op + 1); 266 yearset = 1; 267 /* FALLTHROUGH */ 268 case 10: /* yy */ 269 if (yearset) { 270 lt->tm_year += ATOI2(p); 271 } else { 272 yearset = ATOI2(p); 273 if (yearset < 69) 274 lt->tm_year = yearset + 2000 - TM_YEAR_BASE; 275 else 276 lt->tm_year = yearset + 1900 - TM_YEAR_BASE; 277 } 278 /* FALLTHROUGH */ 279 case 8: /* mm */ 280 lt->tm_mon = ATOI2(p); 281 if (lt->tm_mon > 12 || lt->tm_mon == 0) 282 badcanotime("Month out of range", op, p - op - 1); 283 --lt->tm_mon; /* time struct is 0 - 11 */ 284 /* FALLTHROUGH */ 285 case 6: /* dd */ 286 lt->tm_mday = ATOI2(p); 287 switch (lt->tm_mon) { 288 case 0: 289 case 2: 290 case 4: 291 case 6: 292 case 7: 293 case 9: 294 case 11: 295 if (lt->tm_mday > 31 || lt->tm_mday == 0) 296 badcanotime("Day out of range (max 31)", 297 op, p - op - 1); 298 break; 299 case 3: 300 case 5: 301 case 8: 302 case 10: 303 if (lt->tm_mday > 30 || lt->tm_mday == 0) 304 badcanotime("Day out of range (max 30)", 305 op, p - op - 1); 306 break; 307 case 1: 308 if (isleap(lt->tm_year + TM_YEAR_BASE)) { 309 if (lt->tm_mday > 29 || lt->tm_mday == 0) { 310 badcanotime("Day out of range " 311 "(max 29)", 312 op, p - op - 1); 313 } 314 } else { 315 if (lt->tm_mday > 28 || lt->tm_mday == 0) { 316 badcanotime("Day out of range " 317 "(max 28)", 318 op, p - op - 1); 319 } 320 } 321 break; 322 default: 323 /* 324 * If the month was given, it's already been 325 * checked. If a bad value came back from 326 * localtime, something's badly broken. 327 * (make this an assertion?) 328 */ 329 errx(EXIT_FAILURE, "localtime gave invalid month %d", 330 lt->tm_mon); 331 } 332 /* FALLTHROUGH */ 333 case 4: /* hh */ 334 lt->tm_hour = ATOI2(p); 335 if (lt->tm_hour > 23) 336 badcanotime("Hour out of range", op, p - op - 1); 337 /* FALLTHROUGH */ 338 case 2: /* mm */ 339 lt->tm_min = ATOI2(p); 340 if (lt->tm_min > 59) 341 badcanotime("Minute out of range", op, p - op - 1); 342 break; 343 case 0: /* was just .sss */ 344 if (len != 0) 345 break; 346 /* FALLTHROUGH */ 347 default: 348 if (strlen(p) - len > 12) { 349 badcanotime("Too many digits", p, 12); 350 } else { 351 badcanotime("Not enough digits", p, strlen(p) - len); 352 } 353 } 354setit: 355 /* convert broken-down time to UTC clock time */ 356 if ((new_time = mktime(lt)) == -1) { 357 /* Can this actually happen? */ 358 err(EXIT_FAILURE, "mktime"); 359 } 360 361 /* if jflag is set, don't actually change the time, just return */ 362 if (jflag) { 363 tval = new_time; 364 return; 365 } 366 367 /* set the time */ 368#ifndef HAVE_NBTOOL_CONFIG_H 369 struct utmpx utx; 370 memset(&utx, 0, sizeof(utx)); 371 utx.ut_type = OLD_TIME; 372 (void)gettimeofday(&utx.ut_tv, NULL); 373 pututxline(&utx); 374 375 if (nflag || netsettime(new_time)) { 376 logwtmp("|", "date", ""); 377 if (aflag) { 378 tv.tv_sec = new_time - tval; 379 tv.tv_usec = 0; 380 if (adjtime(&tv, NULL)) 381 err(EXIT_FAILURE, "adjtime"); 382 } else { 383 tval = new_time; 384 tv.tv_sec = tval; 385 tv.tv_usec = 0; 386 if (settimeofday(&tv, NULL)) 387 err(EXIT_FAILURE, "settimeofday"); 388 } 389 logwtmp("{", "date", ""); 390 } 391 utx.ut_type = NEW_TIME; 392 (void)gettimeofday(&utx.ut_tv, NULL); 393 pututxline(&utx); 394 395 if ((p = getlogin()) == NULL) 396 p = "???"; 397 syslog(LOG_AUTH | LOG_NOTICE, "date set by %s", p); 398#else 399 errx(EXIT_FAILURE, "Can't set the time in the tools version"); 400#endif 401} 402 403static void 404usage(void) 405{ 406 (void)fprintf(stderr, 407 "Usage: %s [-ajnRu] [-d date] [-r seconds] [+format]", 408 getprogname()); 409 (void)fprintf(stderr, " [[[[[[CC]yy]mm]dd]HH]MM[.SS]]\n"); 410 (void)fprintf(stderr, 411 " %s [-ajnRu] -f input_format new_date [+format]\n", 412 getprogname()); 413 exit(EXIT_FAILURE); 414 /* NOTREACHED */ 415} 416