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 (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
28/*	  All Rights Reserved  	*/
29
30/*	Copyright (c) 1987, 1988 Microsoft Corporation	*/
31/*	  All Rights Reserved	*/
32
33#include <sys/types.h>
34#include <sys/stat.h>
35#include <stdio.h>
36#include <string.h>
37#include <strings.h>
38#include <stdlib.h>
39#include <ctype.h>
40#include <libgen.h>
41#include <fcntl.h>
42#include <pwd.h>
43#include <time.h>
44#include <unistd.h>
45#include <locale.h>
46#include <sys/time.h>
47#include <errno.h>
48
49#define	BADTIME	"bad time specification"
50
51static char	*myname;
52
53static int isnumber(char *);
54static int atoi_for2(char *);
55static void usage(const int);
56static void touchabort(const char *);
57static void parse_datetime(char *, timespec_t *);
58static void parse_time(char *, timespec_t *);
59static void parse_timespec(char *, timespec_t *);
60
61int
62main(int argc, char *argv[])
63{
64	int c;
65
66	int		aflag	= 0;
67	int		cflag	= 0;
68	int		rflag	= 0;
69	int		mflag	= 0;
70	int		tflag	= 0;
71	int		stflag	= 0;
72	int		status	= 0;
73	int		usecurrenttime = 1;
74	int		timespecified;
75	int		optc;
76	int		fd = -1;
77	mode_t		cmode =
78	    (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
79	struct stat	stbuf;
80	struct stat	prstbuf;
81	timespec_t	times[2];
82	timespec_t	*tsp;
83
84	(void) setlocale(LC_ALL, "");
85#if !defined(TEXT_DOMAIN)
86#define	TEXT_DOMAIN "SYS_TEST"
87#endif
88	(void) textdomain(TEXT_DOMAIN);
89
90	myname = basename(argv[0]);
91	if (strcmp(myname, "settime") == 0) {
92		cflag++;
93		stflag++;
94		while ((optc = getopt(argc, argv, "f:")) != EOF) {
95			switch (optc) {
96			case 'f':
97				rflag++;
98				usecurrenttime = 0;
99				if (stat(optarg, &prstbuf) == -1) {
100					(void) fprintf(stderr, "%s: ", myname);
101					perror(optarg);
102					return (2);
103				}
104				break;
105			case '?':
106				usage(stflag);
107				break;
108			}
109		}
110	} else {
111		while ((optc = getopt(argc, argv, "acfmr:d:t:")) != EOF) {
112			switch (optc) {
113			case 'a':
114				aflag++;
115				break;
116			case 'c':
117				cflag++;
118				break;
119			case 'f':	/* silently ignore for UCB compat */
120				break;
121			case 'm':
122				mflag++;
123				break;
124			case 'r':	/* same as settime's -f option */
125				rflag++;
126				usecurrenttime = 0;
127				if (stat(optarg, &prstbuf) == -1) {
128					(void) fprintf(stderr, "%s: ", myname);
129					perror(optarg);
130					return (2);
131				}
132				break;
133			case 'd':
134				tflag++;
135				usecurrenttime = 0;
136				parse_datetime(optarg, &prstbuf.st_mtim);
137				prstbuf.st_atim = prstbuf.st_mtim;
138				break;
139			case 't':
140				tflag++;
141				usecurrenttime = 0;
142				parse_time(optarg, &prstbuf.st_mtim);
143				prstbuf.st_atim = prstbuf.st_mtim;
144				break;
145			case '?':
146				usage(stflag);
147				break;
148			}
149		}
150	}
151
152	argc -= optind;
153	argv += optind;
154
155	if ((argc < 1) || (rflag + tflag > 1))
156		usage(stflag);
157
158	if ((aflag == 0) && (mflag == 0)) {
159		aflag = 1;
160		mflag = 1;
161	}
162	if ((aflag && !mflag) || (mflag && !aflag))
163		usecurrenttime = 0;
164
165	/*
166	 * If -r, -t or -d has been specified,
167	 * use the specified time.
168	 */
169	timespecified = (rflag | tflag);
170
171	if (timespecified == 0 && argc >= 2 && isnumber(*argv) &&
172	    (strlen(*argv) == 8 || strlen(*argv) == 10)) {
173		/*
174		 * time is specified as an operand; use it.
175		 */
176		parse_timespec(*argv++, &prstbuf.st_mtim);
177		prstbuf.st_atim = prstbuf.st_mtim;
178		usecurrenttime = 0;
179		timespecified = 1;
180		argc--;
181	}
182
183	for (c = 0; c < argc; c++) {
184		if (stat(argv[c], &stbuf)) {
185			/*
186			 * If stat failed for reasons other than EOVERFLOW or
187			 * ENOENT, the file should not be created, since this
188			 * can clobber the contents of an existing file.
189			 */
190			if (errno == EOVERFLOW) {
191				/*
192				 * Since we have EOVERFLOW,
193				 * we know the file exists.
194				 */
195				/* EMPTY */;
196			} else if (errno != ENOENT) {
197				(void) fprintf(stderr,
198				    gettext("%s: cannot stat %s: %s\n"),
199				    myname, argv[c], strerror(errno));
200				status++;
201				continue;
202			} else if (cflag) {
203				continue;
204			} else if ((fd = creat(argv[c], cmode)) < 0) {
205				(void) fprintf(stderr,
206				    gettext("%s: cannot create %s: %s\n"),
207				    myname, argv[c], strerror(errno));
208				status++;
209				continue;
210			}
211		}
212
213		if (usecurrenttime) {
214			tsp = NULL;
215		} else {
216			if (mflag == 0) {
217				/* Keep the mtime of the file */
218				times[1].tv_nsec = UTIME_OMIT;
219			} else if (timespecified) {
220				/* Set the specified time */
221				times[1] = prstbuf.st_mtim;
222			} else {
223				/* Otherwise, use the current time */
224				times[1].tv_nsec = UTIME_NOW;
225			}
226
227			if (aflag == 0) {
228				/* Keep the atime of the file */
229				times[0].tv_nsec = UTIME_OMIT;
230			} else if (timespecified) {
231				/* Set the specified time */
232				times[0] = prstbuf.st_atim;
233			} else {
234				/* Otherwise, use the current time */
235				times[0].tv_nsec = UTIME_NOW;
236			}
237
238			tsp = times;
239		}
240
241		if ((fd >= 0 && futimens(fd, tsp) != 0) ||
242		    (fd < 0 && utimensat(AT_FDCWD, argv[c], tsp, 0) != 0)) {
243			(void) fprintf(stderr,
244			    gettext("%s: cannot change times on %s: %s\n"),
245			    myname, argv[c], strerror(errno));
246			status++;
247		}
248		if (fd >= 0) {
249			(void) close(fd);
250			fd = -1;
251		}
252	}
253	return (status);
254}
255
256static int
257isnumber(char *s)
258{
259	int c;
260
261	while ((c = *s++) != '\0')
262		if (!isdigit(c))
263			return (0);
264	return (1);
265}
266
267static void
268parse_datetime(char *t, timespec_t *ts)
269{
270	char		date[64];
271	char		*year;
272	char		*month;
273	char		*day;
274	char		*hour;
275	char		*minute;
276	char		*second;
277	char		*fraction;
278	int		utc = 0;
279	char		*p;
280	time_t		when;
281	int		nanoseconds;
282	struct tm	tm;
283
284	/*
285	 * The date string has the format (defined by the touch(1) spec):
286	 *	YYYY-MM-DDThh:mm:SS[.frac][tz]
287	 *	YYYY-MM-DDThh:mm:SS[,frac][tz]
288	 * T is either the literal 'T' or is a space character.
289	 * tz is either empty (local time) or the literal 'Z' (UTC).
290	 * All other fields are strings of digits.
291	 */
292
293	/*
294	 * Make a copy of the date string so it can be tokenized.
295	 */
296	if (strlcpy(date, t, sizeof (date)) >= sizeof (date))
297		touchabort(BADTIME);
298
299	/* deal with the optional trailing 'Z' first */
300	p = date + strlen(date) - 1;
301	if (*p == 'Z') {
302		utc = 1;
303		*p = '\0';
304	}
305
306	/* break out the component tokens */
307	p = date;
308	year = strsep(&p, "-");
309	month = strsep(&p, "-");
310	day = strsep(&p, "T ");
311	hour = strsep(&p, ":");
312	minute = strsep(&p, ":");
313	second = strsep(&p, ".,");
314	fraction = p;
315
316	/* verify the component tokens */
317	if (year == NULL || strlen(year) < 4 || !isnumber(year) ||
318	    month == NULL || strlen(month) != 2 || !isnumber(month) ||
319	    day == NULL || strlen(day) != 2 || !isnumber(day) ||
320	    hour == NULL || strlen(hour) != 2 || !isnumber(hour) ||
321	    minute == NULL || strlen(minute) != 2 || !isnumber(minute) ||
322	    second == NULL || strlen(second) != 2 || !isnumber(second) ||
323	    (fraction != NULL && (*fraction == '\0' || !isnumber(fraction))))
324		touchabort(BADTIME);
325
326	(void) memset(&tm, 0, sizeof (struct tm));
327
328	tm.tm_year = atoi(year) - 1900;
329	tm.tm_mon = atoi(month) - 1;
330	tm.tm_mday = atoi(day);
331	tm.tm_hour = atoi(hour);
332	tm.tm_min = atoi(minute);
333	tm.tm_sec = atoi(second);
334	if (utc) {
335		(void) setenv("TZ", "GMT0", 1);
336		tzset();
337	}
338
339	errno = 0;
340	if ((when = mktime(&tm)) == -1 && errno != 0)
341		touchabort(BADTIME);
342	if (tm.tm_isdst)
343		when -= (timezone - altzone);
344
345	if (fraction == NULL) {
346		nanoseconds = 0;
347	} else {
348		/* truncate beyond 9 digits (nanoseconds) */
349		if (strlen(fraction) > 9)
350			fraction[9] = '\0';
351		nanoseconds = atoi(fraction);
352
353		switch (strlen(fraction)) {
354		case 1:
355			nanoseconds *= 100000000;
356			break;
357		case 2:
358			nanoseconds *= 10000000;
359			break;
360		case 3:
361			nanoseconds *= 1000000;
362			break;
363		case 4:
364			nanoseconds *= 100000;
365			break;
366		case 5:
367			nanoseconds *= 10000;
368			break;
369		case 6:
370			nanoseconds *= 1000;
371			break;
372		case 7:
373			nanoseconds *= 100;
374			break;
375		case 8:
376			nanoseconds *= 10;
377			break;
378		case 9:
379			break;
380		}
381	}
382
383	ts->tv_sec = when;
384	ts->tv_nsec = nanoseconds;
385}
386
387static void
388parse_time(char *t, timespec_t *ts)
389{
390	int		century = 0;
391	int		seconds = 0;
392	char		*p;
393	time_t		when;
394	struct tm	tm;
395
396	/*
397	 * time in the following format (defined by the touch(1) spec):
398	 *	[[CC]YY]MMDDhhmm[.SS]
399	 */
400	if ((p = strchr(t, '.')) != NULL) {
401		if (strchr(p+1, '.') != NULL)
402			touchabort(BADTIME);
403		seconds = atoi_for2(p+1);
404		*p = '\0';
405	}
406
407	(void) memset(&tm, 0, sizeof (struct tm));
408	when = time(0);
409	tm.tm_year = localtime(&when)->tm_year;
410
411	switch (strlen(t)) {
412		case 12:	/* CCYYMMDDhhmm */
413			century = atoi_for2(t);
414			t += 2;
415			/* FALLTHROUGH */
416		case 10:	/* YYMMDDhhmm */
417			tm.tm_year = atoi_for2(t);
418			t += 2;
419			if (century == 0) {
420				if (tm.tm_year < 69)
421					tm.tm_year += 100;
422			} else
423				tm.tm_year += (century - 19) * 100;
424			/* FALLTHROUGH */
425		case 8:		/* MMDDhhmm */
426			tm.tm_mon = atoi_for2(t) - 1;
427			t += 2;
428			tm.tm_mday = atoi_for2(t);
429			t += 2;
430			tm.tm_hour = atoi_for2(t);
431			t += 2;
432			tm.tm_min = atoi_for2(t);
433			tm.tm_sec = seconds;
434			break;
435		default:
436			touchabort(BADTIME);
437	}
438
439	if ((when = mktime(&tm)) == -1)
440		touchabort(BADTIME);
441	if (tm.tm_isdst)
442		when -= (timezone-altzone);
443
444	ts->tv_sec = when;
445	ts->tv_nsec = 0;
446}
447
448static void
449parse_timespec(char *t, timespec_t *ts)
450{
451	time_t		when;
452	struct tm	tm;
453
454	/*
455	 * time in the following format (defined by the touch(1) spec):
456	 *	MMDDhhmm[yy]
457	 */
458
459	(void) memset(&tm, 0, sizeof (struct tm));
460	when = time(0);
461	tm.tm_year = localtime(&when)->tm_year;
462
463	switch (strlen(t)) {
464		case 10:	/* MMDDhhmmyy */
465			tm.tm_year = atoi_for2(t+8);
466			if (tm.tm_year < 69)
467				tm.tm_year += 100;
468			/* FALLTHROUGH */
469		case 8:		/* MMDDhhmm */
470			tm.tm_mon = atoi_for2(t) - 1;
471			t += 2;
472			tm.tm_mday = atoi_for2(t);
473			t += 2;
474			tm.tm_hour = atoi_for2(t);
475			t += 2;
476			tm.tm_min = atoi_for2(t);
477			break;
478		default:
479			touchabort(BADTIME);
480	}
481
482	if ((when = mktime(&tm)) == -1)
483		touchabort(BADTIME);
484	if (tm.tm_isdst)
485		when -= (timezone - altzone);
486
487	ts->tv_sec = when;
488	ts->tv_nsec = 0;
489}
490
491static int
492atoi_for2(char *p)
493{
494	int value;
495
496	value = (*p - '0') * 10 + *(p+1) - '0';
497	if ((value < 0) || (value > 99))
498		touchabort(BADTIME);
499	return (value);
500}
501
502static void
503touchabort(const char *message)
504{
505	(void) fprintf(stderr, "%s: %s\n", myname, gettext(message));
506	exit(1);
507}
508
509static void
510usage(const int settime)
511{
512	if (settime) {
513		(void) fprintf(stderr, gettext(
514		    "usage: %s [-f file] [mmddhhmm[yy]] file...\n"), myname);
515		exit(2);
516	}
517	(void) fprintf(stderr, gettext(
518	    "usage: %s [-acm] [-r ref_file] file...\n"
519	    "       %s [-acm] [-t [[CC]YY]MMDDhhmm[.SS]] file...\n"
520	    "       %s [-acm] [-d YYYY-MM-DDThh:mm:SS[.frac][Z]] file...\n"
521	    "       %s [-acm] [-d YYYY-MM-DDThh:mm:SS[,frac][Z]] file...\n"
522	    "       %s [-acm] [MMDDhhmm[yy]] file...\n"),
523	    myname, myname, myname, myname, myname);
524	exit(2);
525}
526