xref: /illumos-gate/usr/src/cmd/touch/touch.c (revision 92a0208178405fef708b0283ffcaa02fbc3468ff)
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  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
27 /*	  All Rights Reserved  	*/
28 
29 /*	Copyright (c) 1987, 1988 Microsoft Corporation	*/
30 /*	  All Rights Reserved	*/
31 
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <stdio.h>
35 #include <string.h>
36 #include <strings.h>
37 #include <stdlib.h>
38 #include <ctype.h>
39 #include <libgen.h>
40 #include <fcntl.h>
41 #include <pwd.h>
42 #include <time.h>
43 #include <unistd.h>
44 #include <locale.h>
45 #include <sys/time.h>
46 #include <errno.h>
47 
48 #define	BADTIME	"bad time specification"
49 
50 static char	*myname;
51 
52 static int isnumber(char *);
53 static int atoi_for2(char *);
54 static void usage(const int);
55 static void touchabort(const char *);
56 static void parse_time(char *, timestruc_t *);
57 static void parse_datetime(char *, timestruc_t *);
58 static void timestruc_to_timeval(timestruc_t *, struct timeval *);
59 
60 int
61 main(int argc, char *argv[])
62 {
63 	int c;
64 
65 	int		aflag	= 0;
66 	int		cflag	= 0;
67 	int		rflag	= 0;
68 	int		mflag	= 0;
69 	int		tflag	= 0;
70 	int		stflag	= 0;
71 	int		status	= 0;
72 	int		usecurrenttime = 1;
73 	int		timespecified;
74 	int		optc;
75 	int		fd;
76 	struct stat	stbuf;
77 	struct stat	prstbuf;
78 	struct timeval	times[2];
79 
80 	(void) setlocale(LC_ALL, "");
81 #if !defined(TEXT_DOMAIN)
82 #define	TEXT_DOMAIN "SYS_TEST"
83 #endif
84 	(void) textdomain(TEXT_DOMAIN);
85 
86 	myname = basename(argv[0]);
87 	if (strcmp(myname, "settime") == NULL) {
88 		cflag++;
89 		stflag++;
90 		while ((optc = getopt(argc, argv, "f:")) != EOF)
91 			switch (optc) {
92 			case 'f':
93 				rflag++;
94 				usecurrenttime = 0;
95 				if (stat(optarg, &prstbuf) == -1) {
96 					(void) fprintf(stderr, "%s: ", myname);
97 					perror(optarg);
98 					return (2);
99 				}
100 				break;
101 			case '?':
102 				usage(stflag);
103 				break;
104 			};
105 	} else
106 		while ((optc = getopt(argc, argv, "acfmr:t:")) != EOF)
107 			switch (optc) {
108 			case 'a':
109 				aflag++;
110 				usecurrenttime = 0;
111 				break;
112 			case 'c':
113 				cflag++;
114 				break;
115 			case 'f':	/* silently ignore for UCB compat */
116 				break;
117 			case 'm':
118 				mflag++;
119 				usecurrenttime = 0;
120 				break;
121 			case 'r':	/* same as settime's -f option */
122 				rflag++;
123 				usecurrenttime = 0;
124 				if (stat(optarg, &prstbuf) == -1) {
125 					(void) fprintf(stderr, "%s: ", myname);
126 					perror(optarg);
127 					return (2);
128 				}
129 				break;
130 			case 't':
131 				tflag++;
132 				usecurrenttime = 0;
133 				parse_time(optarg, &prstbuf.st_mtim);
134 				prstbuf.st_atim = prstbuf.st_mtim;
135 				break;
136 			case '?':
137 				usage(stflag);
138 				break;
139 			}
140 
141 	argc -= optind;
142 	argv += optind;
143 
144 	if ((argc < 1) || (rflag + tflag > 1))
145 		usage(stflag);
146 
147 	if ((aflag == 0) && (mflag == 0)) {
148 		aflag = 1;
149 		mflag = 1;
150 	}
151 
152 	/*
153 	 * If either -r or -t has been specified,
154 	 * use the specified time.
155 	 */
156 	timespecified = rflag || tflag;
157 
158 	if (timespecified == 0) {
159 		if (argc >= 2 && isnumber(*argv) && (strlen(*argv) == 8 ||
160 		    strlen(*argv) == 10)) {
161 			/*
162 			 * time is specified as an operand.
163 			 * use it.
164 			 */
165 			parse_datetime(*argv++, &prstbuf.st_mtim);
166 			prstbuf.st_atim = prstbuf.st_mtim;
167 			usecurrenttime = 0;
168 			timespecified = 1;
169 			argc--;
170 		} else {
171 			/*
172 			 * no time information is specified.
173 			 * use the current time.
174 			 */
175 			(void) gettimeofday(times, NULL);
176 			times[1] = times[0];
177 		}
178 	}
179 	for (c = 0; c < argc; c++) {
180 		if (stat(argv[c], &stbuf)) {
181 			/*
182 			 * If stat failed for reasons other than EOVERFLOW or
183 			 * ENOENT, the file should not be created, since this
184 			 * can clobber the contents of an existing file.
185 			 */
186 			if (errno == EOVERFLOW) {
187 				if (aflag == 0 || mflag == 0) {
188 					(void) fprintf(stderr,
189 					    gettext("%s: %s: current timestamps"
190 					    " unavailable:\n%s"),
191 					    myname, argv[c], aflag > 0 ?
192 					    gettext("consider trying again"
193 					    " without -a option\n") :
194 					    gettext("consider trying again"
195 					    " without -m option\n"));
196 					status++;
197 					continue;
198 				}
199 				/*
200 				 * Since we have EOVERFLOW, we know the file
201 				 * exists. Since both atime and mtime are being
202 				 * changed to known values, we don't care that
203 				 * st_atime and st_mtime from the file aren't
204 				 * available.
205 				 */
206 			} else if (errno != ENOENT) {
207 				(void) fprintf(stderr,
208 				    gettext("%s: cannot stat %s: %s\n"),
209 				    myname, argv[c], strerror(errno));
210 				status++;
211 				continue;
212 			} else if (cflag) {
213 				continue;
214 			} else if ((fd = creat(argv[c],
215 			    (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)))
216 			    < 0) {
217 				(void) fprintf(stderr,
218 				    gettext("%s: cannot create %s: %s\n"),
219 				    myname, argv[c], strerror(errno));
220 				status++;
221 				continue;
222 			} else {
223 				(void) close(fd);
224 				if (stat(argv[c], &stbuf)) {
225 					(void) fprintf(stderr,
226 					    gettext("%s: cannot stat %s: %s\n"),
227 					    myname, argv[c], strerror(errno));
228 					status++;
229 					continue;
230 				}
231 			}
232 		}
233 
234 		if (mflag == 0) {
235 			/* Keep the mtime of the file */
236 			timestruc_to_timeval(&stbuf.st_mtim, times + 1);
237 		} else {
238 			if (timespecified) {
239 				/* Set the specified time */
240 				timestruc_to_timeval(&prstbuf.st_mtim,
241 				    times + 1);
242 			}
243 			/* Otherwise, use the current time by gettimeofday */
244 		}
245 
246 		if (aflag == 0) {
247 			/* Keep the atime of the file */
248 			timestruc_to_timeval(&stbuf.st_atim, times);
249 		} else {
250 			if (timespecified) {
251 				/* Set the specified time */
252 				timestruc_to_timeval(&prstbuf.st_atim, times);
253 			}
254 			/* Otherwise, use the current time by gettimeofday */
255 		}
256 
257 		if (utimes(argv[c], (usecurrenttime) ? NULL : times)) {
258 			(void) fprintf(stderr,
259 			    gettext("%s: cannot change times on %s: %s\n"),
260 			    myname, argv[c], strerror(errno));
261 			status++;
262 			continue;
263 		}
264 	}
265 	return (status);
266 }
267 
268 static int
269 isnumber(char *s)
270 {
271 	int c;
272 
273 	while ((c = *s++) != '\0')
274 		if (!isdigit(c))
275 			return (0);
276 	return (1);
277 }
278 
279 
280 static void
281 parse_time(char *t, timestruc_t *ts)
282 {
283 	int		century = 0;
284 	int		seconds = 0;
285 	char		*p;
286 	time_t		when;
287 	struct tm	tm;
288 
289 	/*
290 	 * time in the following format (defined by the touch(1) spec):
291 	 *	[[CC]YY]MMDDhhmm[.SS]
292 	 */
293 	if ((p = strchr(t, '.')) != NULL) {
294 		if (strchr(p+1, '.') != NULL)
295 			touchabort(BADTIME);
296 		seconds = atoi_for2(p+1);
297 		*p = '\0';
298 	}
299 
300 	(void) memset(&tm, 0, sizeof (struct tm));
301 	when = time(0);
302 	tm.tm_year = localtime(&when)->tm_year;
303 
304 	switch (strlen(t)) {
305 		case 12:	/* CCYYMMDDhhmm */
306 			century = atoi_for2(t);
307 			t += 2;
308 			/* FALLTHROUGH */
309 		case 10:	/* YYMMDDhhmm */
310 			tm.tm_year = atoi_for2(t);
311 			t += 2;
312 			if (century == 0) {
313 				if (tm.tm_year < 69)
314 					tm.tm_year += 100;
315 			} else
316 				tm.tm_year += (century - 19) * 100;
317 			/* FALLTHROUGH */
318 		case 8:		/* MMDDhhmm */
319 			tm.tm_mon = atoi_for2(t) - 1;
320 			t += 2;
321 			tm.tm_mday = atoi_for2(t);
322 			t += 2;
323 			tm.tm_hour = atoi_for2(t);
324 			t += 2;
325 			tm.tm_min = atoi_for2(t);
326 			tm.tm_sec = seconds;
327 			break;
328 		default:
329 			touchabort(BADTIME);
330 	}
331 
332 	if ((when = mktime(&tm)) == -1)
333 		touchabort(BADTIME);
334 	if (tm.tm_isdst)
335 		when -= (timezone-altzone);
336 
337 	ts->tv_sec = when;
338 	ts->tv_nsec = 0;
339 }
340 
341 static void
342 parse_datetime(char *t, timestruc_t *ts)
343 {
344 	time_t		when;
345 	struct tm	tm;
346 
347 	/*
348 	 * time in the following format (defined by the touch(1) spec):
349 	 *	MMDDhhmm[yy]
350 	 */
351 
352 	(void) memset(&tm, 0, sizeof (struct tm));
353 	when = time(0);
354 	tm.tm_year = localtime(&when)->tm_year;
355 
356 	switch (strlen(t)) {
357 		case 10:	/* MMDDhhmmyy */
358 			tm.tm_year = atoi_for2(t+8);
359 			if (tm.tm_year < 69)
360 				tm.tm_year += 100;
361 			/* FALLTHROUGH */
362 		case 8:		/* MMDDhhmm */
363 			tm.tm_mon = atoi_for2(t) - 1;
364 			t += 2;
365 			tm.tm_mday = atoi_for2(t);
366 			t += 2;
367 			tm.tm_hour = atoi_for2(t);
368 			t += 2;
369 			tm.tm_min = atoi_for2(t);
370 			break;
371 		default:
372 			touchabort(BADTIME);
373 	}
374 
375 	if ((when = mktime(&tm)) == -1)
376 		touchabort(BADTIME);
377 	if (tm.tm_isdst)
378 		when -= (timezone - altzone);
379 
380 	ts->tv_sec = when;
381 	ts->tv_nsec = 0;
382 }
383 
384 static int
385 atoi_for2(char *p)
386 {
387 	int value;
388 
389 	value = (*p - '0') * 10 + *(p+1) - '0';
390 	if ((value < 0) || (value > 99))
391 		touchabort(BADTIME);
392 	return (value);
393 }
394 
395 static void
396 touchabort(const char *message)
397 {
398 	(void) fprintf(stderr, "%s: %s\n", myname, gettext(message));
399 	exit(1);
400 }
401 
402 static void
403 usage(const int settime)
404 {
405 	if (settime)
406 		(void) fprintf(stderr, gettext(
407 		    "usage: %s [-f file] [mmddhhmm[yy]] file...\n"), myname);
408 	else
409 		(void) fprintf(stderr, gettext(
410 		    "usage: %s [-acm] [-r ref_file] file...\n"
411 		    "       %s [-acm] [MMDDhhmm[yy]] file...\n"
412 		    "       %s [-acm] [-t [[CC]YY]MMDDhhmm[.SS]] file...\n"),
413 		    myname, myname, myname);
414 	exit(2);
415 }
416 
417 /*
418  * nanoseconds are rounded off to microseconds by flooring.
419  */
420 static void
421 timestruc_to_timeval(timestruc_t *ts, struct timeval *tv)
422 {
423 	tv->tv_sec = ts->tv_sec;
424 	tv->tv_usec = ts->tv_nsec / 1000;
425 }
426