xref: /illumos-gate/usr/src/cmd/touch/touch.c (revision 46b592853d0f4f11781b6b0a7533f267c6aee132)
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 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 		    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 
256 static int
257 isnumber(char *s)
258 {
259 	int c;
260 
261 	while ((c = *s++) != '\0')
262 		if (!isdigit(c))
263 			return (0);
264 	return (1);
265 }
266 
267 static void
268 parse_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 
387 static void
388 parse_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 
448 static void
449 parse_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 
491 static int
492 atoi_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 
502 static void
503 touchabort(const char *message)
504 {
505 	(void) fprintf(stderr, "%s: %s\n", myname, gettext(message));
506 	exit(1);
507 }
508 
509 static void
510 usage(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