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)
66static 	char	buf[BUFSIZ];
67static	time_t	clock_val;
68static  short	month_size[12] =
69	{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
70static  struct  utmpx wtmpx[2] = {
71	{"", "", OTIME_MSG, 0, OLD_TIME, 0, 0, 0},
72	{"", "", NTIME_MSG, 0, NEW_TIME, 0, 0, 0}
73	};
74static 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";
78static int uflag = 0;
79static int Rflag = 0;
80static int rflag = 0;
81
82static int get_adj(char *, struct timeval *);
83static int setdate(struct tm *, char *);
84static void fmt_extensions(char *, size_t,
85    const char *, const struct timespec *);
86
87int
88main(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
218int
219setdate(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
365int
366get_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 */
409void
410fmt_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