1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2004 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 /* 27 * Copyright 2012 Nexenta Systems, Inc. All rights reserved. 28 */ 29 /* 30 * Copyright (c) 2017, Joyent, Inc. 31 */ 32 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 33 /* All Rights Reserved */ 34 35 /* 36 * University Copyright- Copyright (c) 1982, 1986, 1988 37 * The Regents of the University of California 38 * All Rights Reserved 39 * 40 * University Acknowledgment- Portions of this document are derived from 41 * software developed by the University of California, Berkeley, and its 42 * contributors. 43 */ 44 45 /* 46 * date - with format capabilities and international flair 47 */ 48 49 #include <locale.h> 50 #include <fcntl.h> 51 #include <langinfo.h> 52 #include <stdio.h> 53 #include <stdlib.h> 54 #include <string.h> 55 #include <time.h> 56 #include <unistd.h> 57 #include <sys/time.h> 58 #include <sys/types.h> 59 #include <sys/stat.h> 60 #include <ctype.h> 61 #include <errno.h> 62 #include <utmpx.h> 63 #include <tzfile.h> 64 65 #define year_size(A) ((isleap(A)) ? 366 : 365) 66 static char buf[BUFSIZ]; 67 static time_t clock_val; 68 static short month_size[12] = 69 { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; 70 static struct utmpx wtmpx[2] = { 71 {"", "", OTIME_MSG, 0, OLD_TIME, 0, 0, 0}, 72 {"", "", NTIME_MSG, 0, NEW_TIME, 0, 0, 0} 73 }; 74 static char *usage = 75 "usage:\tdate [-u] mmddHHMM[[cc]yy][.SS]\n" 76 "\tdate [-Ru] [-r seconds | filename] [+format]\n" 77 "\tdate -a [-]sss[.fff]\n"; 78 static int uflag = 0; 79 static int Rflag = 0; 80 static int rflag = 0; 81 82 static int get_adj(char *, struct timeval *); 83 static int setdate(struct tm *, char *); 84 static void fmt_extensions(char *, size_t, 85 const char *, const struct timespec *); 86 87 int 88 main(int argc, char **argv) 89 { 90 struct tm *tp, tm; 91 struct timeval tv; 92 char *fmt, *eptr; 93 char fmtbuf[BUFSIZ]; 94 int c, aflag = 0, illflag = 0; 95 struct timespec ts; 96 97 (void) setlocale(LC_ALL, ""); 98 99 #if !defined(TEXT_DOMAIN) 100 #define TEXT_DOMAIN "SYS_TEST" 101 #endif 102 (void) textdomain(TEXT_DOMAIN); 103 104 while ((c = getopt(argc, argv, "a:uRr:")) != EOF) 105 switch (c) { 106 case 'a': 107 aflag++; 108 if (get_adj(optarg, &tv) < 0) { 109 (void) fprintf(stderr, 110 gettext("date: invalid argument -- %s\n"), 111 optarg); 112 illflag++; 113 } 114 break; 115 case 'u': 116 uflag++; 117 break; 118 case 'R': 119 Rflag++; 120 break; 121 case 'r': 122 123 /* 124 * BSD originally used -r to specify a unix time. GNU 125 * used -r to specify a reference to a file. Now, like 126 * some BSDs we attempt to parse the time. If we can, 127 * then we use that, otherwise we fall back and treat it 128 * like GNU. 129 */ 130 rflag++; 131 errno = 0; 132 ts.tv_sec = strtol(optarg, &eptr, 0); 133 if (errno == EINVAL || *eptr != '\0') { 134 struct stat st; 135 if (stat(optarg, &st) == 0) { 136 ts.tv_sec = st.st_mtime; 137 } else { 138 (void) fprintf(stderr, 139 gettext("date: failed to get stat " 140 "information about %s: %s\n"), 141 optarg, strerror(errno)); 142 exit(1); 143 } 144 } else if (errno != 0) { 145 (void) fprintf(stderr, 146 gettext("date: failed to parse -r " 147 "argument: %s\n"), optarg); 148 exit(1); 149 } 150 break; 151 default: 152 illflag++; 153 } 154 155 argc -= optind; 156 argv = &argv[optind]; 157 158 /* -a is mutually exclusive with -u, -R, and -r */ 159 if (uflag && aflag) 160 illflag++; 161 if (Rflag && aflag) 162 illflag++; 163 if (rflag && aflag) 164 illflag++; 165 166 if (illflag) { 167 (void) fprintf(stderr, gettext(usage)); 168 exit(1); 169 } 170 171 if (rflag == 0) { 172 if (clock_gettime(CLOCK_REALTIME, &ts) != 0) { 173 perror(gettext("date: Failed to obtain system time")); 174 exit(1); 175 } 176 } 177 clock_val = ts.tv_sec; 178 179 if (aflag) { 180 if (adjtime(&tv, 0) < 0) { 181 perror(gettext("date: Failed to adjust date")); 182 exit(1); 183 } 184 exit(0); 185 } 186 187 if (argc > 0) { 188 if (*argv[0] == '+') 189 fmt = &argv[0][1]; 190 else { 191 if (setdate(localtime(&clock_val), argv[0])) { 192 (void) fprintf(stderr, gettext(usage)); 193 exit(1); 194 } 195 fmt = nl_langinfo(_DATE_FMT); 196 } 197 } else if (Rflag) { 198 fmt = "%a, %d %h %Y %H:%M:%S %z"; 199 } else 200 fmt = nl_langinfo(_DATE_FMT); 201 202 fmt_extensions(fmtbuf, sizeof (fmtbuf), fmt, &ts); 203 204 if (uflag) { 205 (void) putenv("TZ=GMT0"); 206 tzset(); 207 tp = gmtime(&clock_val); 208 } else 209 tp = localtime(&clock_val); 210 (void) memcpy(&tm, tp, sizeof (struct tm)); 211 (void) strftime(buf, BUFSIZ, fmtbuf, &tm); 212 213 (void) puts(buf); 214 215 return (0); 216 } 217 218 int 219 setdate(struct tm *current_date, char *date) 220 { 221 int i; 222 int mm; 223 int hh; 224 int min; 225 int sec = 0; 226 char *secptr; 227 int yy; 228 int dd = 0; 229 int minidx = 6; 230 int len; 231 int dd_check; 232 233 /* Parse date string */ 234 if ((secptr = strchr(date, '.')) != NULL && strlen(&secptr[1]) == 2 && 235 isdigit(secptr[1]) && isdigit(secptr[2]) && 236 (sec = atoi(&secptr[1])) >= 0 && sec < 60) 237 secptr[0] = '\0'; /* eat decimal point only on success */ 238 239 len = strlen(date); 240 241 for (i = 0; i < len; i++) { 242 if (!isdigit(date[i])) { 243 (void) fprintf(stderr, 244 gettext("date: bad conversion\n")); 245 exit(1); 246 } 247 } 248 switch (strlen(date)) { 249 case 12: 250 yy = atoi(&date[8]); 251 date[8] = '\0'; 252 break; 253 case 10: 254 /* 255 * The YY format has the following representation: 256 * 00-68 = 2000 thru 2068 257 * 69-99 = 1969 thru 1999 258 */ 259 if (atoi(&date[8]) <= 68) { 260 yy = 1900 + (atoi(&date[8]) + 100); 261 } else { 262 yy = 1900 + atoi(&date[8]); 263 } 264 date[8] = '\0'; 265 break; 266 case 8: 267 yy = 1900 + current_date->tm_year; 268 break; 269 case 4: 270 yy = 1900 + current_date->tm_year; 271 mm = current_date->tm_mon + 1; /* tm_mon goes from 1 to 11 */ 272 dd = current_date->tm_mday; 273 minidx = 2; 274 break; 275 default: 276 (void) fprintf(stderr, gettext("date: bad conversion\n")); 277 return (1); 278 } 279 280 min = atoi(&date[minidx]); 281 date[minidx] = '\0'; 282 hh = atoi(&date[minidx-2]); 283 date[minidx-2] = '\0'; 284 285 if (!dd) { 286 /* 287 * if dd is 0 (not between 1 and 31), then 288 * read the value supplied by the user. 289 */ 290 dd = atoi(&date[2]); 291 date[2] = '\0'; 292 mm = atoi(&date[0]); 293 } 294 295 if (hh == 24) 296 hh = 0, dd++; 297 298 /* Validate date elements */ 299 dd_check = 0; 300 if (mm >= 1 && mm <= 12) { 301 dd_check = month_size[mm - 1]; /* get days in this month */ 302 if (mm == 2 && isleap(yy)) /* adjust for leap year */ 303 dd_check++; 304 } 305 if (!((mm >= 1 && mm <= 12) && (dd >= 1 && dd <= dd_check) && 306 (hh >= 0 && hh <= 23) && (min >= 0 && min <= 59))) { 307 (void) fprintf(stderr, gettext("date: bad conversion\n")); 308 return (1); 309 } 310 311 /* Build date and time number */ 312 for (clock_val = 0, i = 1970; i < yy; i++) 313 clock_val += year_size(i); 314 /* Adjust for leap year */ 315 if (isleap(yy) && mm >= 3) 316 clock_val += 1; 317 /* Adjust for different month lengths */ 318 while (--mm) 319 clock_val += (time_t)month_size[mm - 1]; 320 /* Load up the rest */ 321 clock_val += (time_t)(dd - 1); 322 clock_val *= 24; 323 clock_val += (time_t)hh; 324 clock_val *= 60; 325 clock_val += (time_t)min; 326 clock_val *= 60; 327 clock_val += sec; 328 329 if (!uflag) { 330 /* convert to GMT assuming standard time */ 331 /* correction is made in localtime(3C) */ 332 333 /* 334 * call localtime to set up "timezone" variable applicable 335 * for clock_val time, to support Olson timezones which 336 * can allow timezone rules to change. 337 */ 338 (void) localtime(&clock_val); 339 340 clock_val += (time_t)timezone; 341 342 /* correct if daylight savings time in effect */ 343 344 if (localtime(&clock_val)->tm_isdst) 345 clock_val = clock_val - (time_t)(timezone - altzone); 346 } 347 348 (void) time(&wtmpx[0].ut_xtime); 349 if (stime(&clock_val) < 0) { 350 perror("date"); 351 return (1); 352 } 353 #if defined(i386) 354 /* correct the kernel's "gmt_lag" and the PC's RTC */ 355 (void) system("/usr/sbin/rtc -c > /dev/null 2>&1"); 356 #endif 357 (void) time(&wtmpx[1].ut_xtime); 358 (void) pututxline(&wtmpx[0]); 359 (void) pututxline(&wtmpx[1]); 360 (void) updwtmpx(WTMPX_FILE, &wtmpx[0]); 361 (void) updwtmpx(WTMPX_FILE, &wtmpx[1]); 362 return (0); 363 } 364 365 int 366 get_adj(char *cp, struct timeval *tp) 367 { 368 register int mult; 369 int sign; 370 371 /* arg must be [-]sss[.fff] */ 372 373 tp->tv_sec = tp->tv_usec = 0; 374 if (*cp == '-') { 375 sign = -1; 376 cp++; 377 } else { 378 sign = 1; 379 } 380 381 while (*cp >= '0' && *cp <= '9') { 382 tp->tv_sec *= 10; 383 tp->tv_sec += *cp++ - '0'; 384 } 385 if (*cp == '.') { 386 cp++; 387 mult = 100000; 388 while (*cp >= '0' && *cp <= '9') { 389 tp->tv_usec += (*cp++ - '0') * mult; 390 mult /= 10; 391 } 392 } 393 /* 394 * if there's anything left in the string, 395 * the input was invalid. 396 */ 397 if (*cp) { 398 return (-1); 399 } else { 400 tp->tv_sec *= sign; 401 tp->tv_usec *= sign; 402 return (0); 403 } 404 } 405 406 /* 407 * Extensions that cannot be interpreted by strftime are interpreted here. 408 */ 409 void 410 fmt_extensions(char *fmtbuf, size_t len, 411 const char *fmt, const struct timespec *tsp) 412 { 413 const char *p; 414 char *q; 415 416 for (p = fmt, q = fmtbuf; *p != '\0' && q < fmtbuf + len; ++p) { 417 if (*p == '%') { 418 switch (*(p + 1)) { 419 case 'N': 420 ++p; 421 q += snprintf(q, len - (q - fmtbuf), 422 "%09lu", tsp->tv_nsec); 423 continue; 424 } 425 } 426 *q++ = *p; 427 } 428 429 if (q < fmtbuf + len) 430 *q = '\0'; 431 else 432 fmtbuf[len - 1] = '\0'; 433 } 434