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