xref: /illumos-gate/usr/src/cmd/touch/touch.c (revision 5400c85432c0a689ecefb8c19b8f9861809ca093)
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 
51 static char	*myname;
52 
53 static int isnumber(char *);
54 static int atoi_for2(char *);
55 static void usage(const int);
56 static void touchabort(const char *);
57 static void parse_datetime(char *, timespec_t *);
58 static void parse_time(char *, timespec_t *);
59 static void parse_timespec(char *, timespec_t *);
60 
61 int
62 main(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 reason other than ENOENT, the file
187 			 * should not be created, since this can clobber
188 			 * the contents of an existing file.
189 			 */
190 			if (errno != ENOENT) {
191 				(void) fprintf(stderr,
192 				    gettext("%s: cannot stat %s: %s\n"),
193 				    myname, argv[c], strerror(errno));
194 				status++;
195 				continue;
196 			} else if (cflag) {
197 				continue;
198 			} else if ((fd = creat(argv[c], cmode)) < 0) {
199 				(void) fprintf(stderr,
200 				    gettext("%s: cannot create %s: %s\n"),
201 				    myname, argv[c], strerror(errno));
202 				status++;
203 				continue;
204 			}
205 		}
206 
207 		if (usecurrenttime) {
208 			tsp = NULL;
209 		} else {
210 			if (mflag == 0) {
211 				/* Keep the mtime of the file */
212 				times[1].tv_nsec = UTIME_OMIT;
213 			} else if (timespecified) {
214 				/* Set the specified time */
215 				times[1] = prstbuf.st_mtim;
216 			} else {
217 				/* Otherwise, use the current time */
218 				times[1].tv_nsec = UTIME_NOW;
219 			}
220 
221 			if (aflag == 0) {
222 				/* Keep the atime of the file */
223 				times[0].tv_nsec = UTIME_OMIT;
224 			} else if (timespecified) {
225 				/* Set the specified time */
226 				times[0] = prstbuf.st_atim;
227 			} else {
228 				/* Otherwise, use the current time */
229 				times[0].tv_nsec = UTIME_NOW;
230 			}
231 
232 			tsp = times;
233 		}
234 
235 		if ((fd >= 0 && futimens(fd, tsp) != 0) ||
236 		    (fd < 0 && utimensat(AT_FDCWD, argv[c], tsp, 0) != 0)) {
237 			(void) fprintf(stderr,
238 			    gettext("%s: cannot change times on %s: %s\n"),
239 			    myname, argv[c], strerror(errno));
240 			status++;
241 		}
242 		if (fd >= 0) {
243 			(void) close(fd);
244 			fd = -1;
245 		}
246 	}
247 	return (status);
248 }
249 
250 static int
251 isnumber(char *s)
252 {
253 	int c;
254 
255 	while ((c = *s++) != '\0')
256 		if (!isdigit(c))
257 			return (0);
258 	return (1);
259 }
260 
261 static void
262 parse_datetime(char *t, timespec_t *ts)
263 {
264 	char		date[64];
265 	char		*year;
266 	char		*month;
267 	char		*day;
268 	char		*hour;
269 	char		*minute;
270 	char		*second;
271 	char		*fraction;
272 	int		utc = 0;
273 	char		*p;
274 	time_t		when;
275 	int		nanoseconds;
276 	struct tm	tm;
277 
278 	/*
279 	 * The date string has the format (defined by the touch(1) spec):
280 	 *	YYYY-MM-DDThh:mm:SS[.frac][tz]
281 	 *	YYYY-MM-DDThh:mm:SS[,frac][tz]
282 	 * T is either the literal 'T' or is a space character.
283 	 * tz is either empty (local time) or the literal 'Z' (UTC).
284 	 * All other fields are strings of digits.
285 	 */
286 
287 	/*
288 	 * Make a copy of the date string so it can be tokenized.
289 	 */
290 	if (strlcpy(date, t, sizeof (date)) >= sizeof (date))
291 		touchabort(BADTIME);
292 
293 	/* deal with the optional trailing 'Z' first */
294 	p = date + strlen(date) - 1;
295 	if (*p == 'Z') {
296 		utc = 1;
297 		*p = '\0';
298 	}
299 
300 	/* break out the component tokens */
301 	p = date;
302 	year = strsep(&p, "-");
303 	month = strsep(&p, "-");
304 	day = strsep(&p, "T ");
305 	hour = strsep(&p, ":");
306 	minute = strsep(&p, ":");
307 	second = strsep(&p, ".,");
308 	fraction = p;
309 
310 	/* verify the component tokens */
311 	if (year == NULL || strlen(year) < 4 || !isnumber(year) ||
312 	    month == NULL || strlen(month) != 2 || !isnumber(month) ||
313 	    day == NULL || strlen(day) != 2 || !isnumber(day) ||
314 	    hour == NULL || strlen(hour) != 2 || !isnumber(hour) ||
315 	    minute == NULL || strlen(minute) != 2 || !isnumber(minute) ||
316 	    second == NULL || strlen(second) != 2 || !isnumber(second) ||
317 	    (fraction != NULL && (*fraction == '\0' || !isnumber(fraction))))
318 		touchabort(BADTIME);
319 
320 	(void) memset(&tm, 0, sizeof (struct tm));
321 
322 	tm.tm_year = atoi(year) - 1900;
323 	tm.tm_mon = atoi(month) - 1;
324 	tm.tm_mday = atoi(day);
325 	tm.tm_hour = atoi(hour);
326 	tm.tm_min = atoi(minute);
327 	tm.tm_sec = atoi(second);
328 	if (utc) {
329 		(void) setenv("TZ", "GMT0", 1);
330 		tzset();
331 	}
332 
333 	errno = 0;
334 	if ((when = mktime(&tm)) == -1 && errno != 0)
335 		touchabort(BADTIME);
336 	if (tm.tm_isdst)
337 		when -= (timezone - altzone);
338 
339 	if (fraction == NULL) {
340 		nanoseconds = 0;
341 	} else {
342 		/* truncate beyond 9 digits (nanoseconds) */
343 		if (strlen(fraction) > 9)
344 			fraction[9] = '\0';
345 		nanoseconds = atoi(fraction);
346 
347 		switch (strlen(fraction)) {
348 		case 1:
349 			nanoseconds *= 100000000;
350 			break;
351 		case 2:
352 			nanoseconds *= 10000000;
353 			break;
354 		case 3:
355 			nanoseconds *= 1000000;
356 			break;
357 		case 4:
358 			nanoseconds *= 100000;
359 			break;
360 		case 5:
361 			nanoseconds *= 10000;
362 			break;
363 		case 6:
364 			nanoseconds *= 1000;
365 			break;
366 		case 7:
367 			nanoseconds *= 100;
368 			break;
369 		case 8:
370 			nanoseconds *= 10;
371 			break;
372 		case 9:
373 			break;
374 		}
375 	}
376 
377 	ts->tv_sec = when;
378 	ts->tv_nsec = nanoseconds;
379 }
380 
381 static void
382 parse_time(char *t, timespec_t *ts)
383 {
384 	int		century = 0;
385 	int		seconds = 0;
386 	char		*p;
387 	time_t		when;
388 	struct tm	tm;
389 
390 	/*
391 	 * time in the following format (defined by the touch(1) spec):
392 	 *	[[CC]YY]MMDDhhmm[.SS]
393 	 */
394 	if ((p = strchr(t, '.')) != NULL) {
395 		if (strchr(p+1, '.') != NULL)
396 			touchabort(BADTIME);
397 		seconds = atoi_for2(p+1);
398 		*p = '\0';
399 	}
400 
401 	(void) memset(&tm, 0, sizeof (struct tm));
402 	when = time(0);
403 	tm.tm_year = localtime(&when)->tm_year;
404 
405 	switch (strlen(t)) {
406 		case 12:	/* CCYYMMDDhhmm */
407 			century = atoi_for2(t);
408 			t += 2;
409 			/* FALLTHROUGH */
410 		case 10:	/* YYMMDDhhmm */
411 			tm.tm_year = atoi_for2(t);
412 			t += 2;
413 			if (century == 0) {
414 				if (tm.tm_year < 69)
415 					tm.tm_year += 100;
416 			} else
417 				tm.tm_year += (century - 19) * 100;
418 			/* FALLTHROUGH */
419 		case 8:		/* MMDDhhmm */
420 			tm.tm_mon = atoi_for2(t) - 1;
421 			t += 2;
422 			tm.tm_mday = atoi_for2(t);
423 			t += 2;
424 			tm.tm_hour = atoi_for2(t);
425 			t += 2;
426 			tm.tm_min = atoi_for2(t);
427 			tm.tm_sec = seconds;
428 			break;
429 		default:
430 			touchabort(BADTIME);
431 	}
432 
433 	if ((when = mktime(&tm)) == -1)
434 		touchabort(BADTIME);
435 	if (tm.tm_isdst)
436 		when -= (timezone-altzone);
437 
438 	ts->tv_sec = when;
439 	ts->tv_nsec = 0;
440 }
441 
442 static void
443 parse_timespec(char *t, timespec_t *ts)
444 {
445 	time_t		when;
446 	struct tm	tm;
447 
448 	/*
449 	 * time in the following format (defined by the touch(1) spec):
450 	 *	MMDDhhmm[yy]
451 	 */
452 
453 	(void) memset(&tm, 0, sizeof (struct tm));
454 	when = time(0);
455 	tm.tm_year = localtime(&when)->tm_year;
456 
457 	switch (strlen(t)) {
458 		case 10:	/* MMDDhhmmyy */
459 			tm.tm_year = atoi_for2(t+8);
460 			if (tm.tm_year < 69)
461 				tm.tm_year += 100;
462 			/* FALLTHROUGH */
463 		case 8:		/* MMDDhhmm */
464 			tm.tm_mon = atoi_for2(t) - 1;
465 			t += 2;
466 			tm.tm_mday = atoi_for2(t);
467 			t += 2;
468 			tm.tm_hour = atoi_for2(t);
469 			t += 2;
470 			tm.tm_min = atoi_for2(t);
471 			break;
472 		default:
473 			touchabort(BADTIME);
474 	}
475 
476 	if ((when = mktime(&tm)) == -1)
477 		touchabort(BADTIME);
478 	if (tm.tm_isdst)
479 		when -= (timezone - altzone);
480 
481 	ts->tv_sec = when;
482 	ts->tv_nsec = 0;
483 }
484 
485 static int
486 atoi_for2(char *p)
487 {
488 	int value;
489 
490 	value = (*p - '0') * 10 + *(p+1) - '0';
491 	if ((value < 0) || (value > 99))
492 		touchabort(BADTIME);
493 	return (value);
494 }
495 
496 static void
497 touchabort(const char *message)
498 {
499 	(void) fprintf(stderr, "%s: %s\n", myname, gettext(message));
500 	exit(1);
501 }
502 
503 static void
504 usage(const int settime)
505 {
506 	if (settime) {
507 		(void) fprintf(stderr, gettext(
508 		    "usage: %s [-f file] [mmddhhmm[yy]] file...\n"), myname);
509 		exit(2);
510 	}
511 	(void) fprintf(stderr, gettext(
512 	    "usage: %s [-acm] [-r ref_file] file...\n"
513 	    "       %s [-acm] [-t [[CC]YY]MMDDhhmm[.SS]] file...\n"
514 	    "       %s [-acm] [-d YYYY-MM-DDThh:mm:SS[.frac][Z]] file...\n"
515 	    "       %s [-acm] [-d YYYY-MM-DDThh:mm:SS[,frac][Z]] file...\n"
516 	    "       %s [-acm] [MMDDhhmm[yy]] file...\n"),
517 	    myname, myname, myname, myname, myname);
518 	exit(2);
519 }
520