xref: /freebsd/usr.bin/touch/touch.c (revision aa64588d28258aef88cc33b8043112e8856948d0)
1 /*
2  * Copyright (c) 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *	This product includes software developed by the University of
16  *	California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 
34 #include <sys/cdefs.h>
35 
36 __FBSDID("$FreeBSD$");
37 
38 #ifndef lint
39 static const char copyright[] =
40 "@(#) Copyright (c) 1993\n\
41 	The Regents of the University of California.  All rights reserved.\n";
42 #endif
43 
44 #ifndef lint
45 static const char sccsid[] = "@(#)touch.c	8.1 (Berkeley) 6/6/93";
46 #endif
47 
48 #include <sys/types.h>
49 #include <sys/stat.h>
50 #include <sys/time.h>
51 
52 #include <err.h>
53 #include <errno.h>
54 #include <fcntl.h>
55 #include <libgen.h>
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <time.h>
60 #include <unistd.h>
61 
62 int	rw(char *, struct stat *, int);
63 void	stime_arg1(char *, struct timeval *);
64 void	stime_arg2(char *, int, struct timeval *);
65 void	stime_file(char *, struct timeval *);
66 int	timeoffset(char *);
67 void	usage(char *);
68 
69 int
70 main(int argc, char *argv[])
71 {
72 	struct stat sb;
73 	struct timeval tv[2];
74 	int (*stat_f)(const char *, struct stat *);
75 	int (*utimes_f)(const char *, const struct timeval *);
76 	int Aflag, aflag, cflag, fflag, mflag, ch, fd, len, rval, timeset;
77 	char *p;
78 	char *myname;
79 
80 	myname = basename(argv[0]);
81 	Aflag = aflag = cflag = fflag = mflag = timeset = 0;
82 	stat_f = stat;
83 	utimes_f = utimes;
84 	if (gettimeofday(&tv[0], NULL))
85 		err(1, "gettimeofday");
86 
87 	while ((ch = getopt(argc, argv, "A:acfhmr:t:")) != -1)
88 		switch(ch) {
89 		case 'A':
90 			Aflag = timeoffset(optarg);
91 			break;
92 		case 'a':
93 			aflag = 1;
94 			break;
95 		case 'c':
96 			cflag = 1;
97 			break;
98 		case 'f':
99 			fflag = 1;
100 			break;
101 		case 'h':
102 			cflag = 1;
103 			stat_f = lstat;
104 			utimes_f = lutimes;
105 			break;
106 		case 'm':
107 			mflag = 1;
108 			break;
109 		case 'r':
110 			timeset = 1;
111 			stime_file(optarg, tv);
112 			break;
113 		case 't':
114 			timeset = 1;
115 			stime_arg1(optarg, tv);
116 			break;
117 		case '?':
118 		default:
119 			usage(myname);
120 		}
121 	argc -= optind;
122 	argv += optind;
123 
124 	if (aflag == 0 && mflag == 0)
125 		aflag = mflag = 1;
126 
127 	if (timeset) {
128 		if (Aflag) {
129 			/*
130 			 * We're setting the time to an offset from a specified
131 			 * time.  God knows why, but it means that we can set
132 			 * that time once and for all here.
133 			 */
134 			if (aflag)
135 				tv[0].tv_sec += Aflag;
136 			if (mflag)
137 				tv[1].tv_sec += Aflag;
138 			Aflag = 0;		/* done our job */
139 		}
140 	} else {
141 		/*
142 		 * If no -r or -t flag, at least two operands, the first of
143 		 * which is an 8 or 10 digit number, use the obsolete time
144 		 * specification, otherwise use the current time.
145 		 */
146 		if (argc > 1) {
147 			strtol(argv[0], &p, 10);
148 			len = p - argv[0];
149 			if (*p == '\0' && (len == 8 || len == 10)) {
150 				timeset = 1;
151 				stime_arg2(*argv++, len == 10, tv);
152 			}
153 		}
154 		/* Both times default to the same. */
155 		tv[1] = tv[0];
156 	}
157 
158 	if (*argv == NULL)
159 		usage(myname);
160 
161 	if (Aflag)
162 		cflag = 1;
163 
164 	for (rval = 0; *argv; ++argv) {
165 		/* See if the file exists. */
166 		if (stat_f(*argv, &sb) != 0) {
167 			if (errno != ENOENT) {
168 				rval = 1;
169 				warn("%s", *argv);
170 				continue;
171 			}
172 			if (!cflag) {
173 				/* Create the file. */
174 				fd = open(*argv,
175 				    O_WRONLY | O_CREAT, DEFFILEMODE);
176 				if (fd == -1 || fstat(fd, &sb) || close(fd)) {
177 					rval = 1;
178 					warn("%s", *argv);
179 					continue;
180 				}
181 
182 				/* If using the current time, we're done. */
183 				if (!timeset)
184 					continue;
185 			} else
186 				continue;
187 		}
188 
189 		if (!aflag)
190 			TIMESPEC_TO_TIMEVAL(&tv[0], &sb.st_atim);
191 		if (!mflag)
192 			TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtim);
193 
194 		/*
195 		 * We're adjusting the times based on the file times, not a
196 		 * specified time (that gets handled above).
197 		 */
198 		if (Aflag) {
199 			if (aflag) {
200 				TIMESPEC_TO_TIMEVAL(&tv[0], &sb.st_atim);
201 				tv[0].tv_sec += Aflag;
202 			}
203 			if (mflag) {
204 				TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtim);
205 				tv[1].tv_sec += Aflag;
206 			}
207 		}
208 
209 		/* Try utimes(2). */
210 		if (!utimes_f(*argv, tv))
211 			continue;
212 
213 		/* If the user specified a time, nothing else we can do. */
214 		if (timeset || Aflag) {
215 			rval = 1;
216 			warn("%s", *argv);
217 			continue;
218 		}
219 
220 		/*
221 		 * System V and POSIX 1003.1 require that a NULL argument
222 		 * set the access/modification times to the current time.
223 		 * The permission checks are different, too, in that the
224 		 * ability to write the file is sufficient.  Take a shot.
225 		 */
226 		 if (!utimes_f(*argv, NULL))
227 			continue;
228 
229 		/* Try reading/writing. */
230 		if (!S_ISLNK(sb.st_mode) && !S_ISDIR(sb.st_mode)) {
231 			if (rw(*argv, &sb, fflag))
232 				rval = 1;
233 		} else {
234 			rval = 1;
235 			warn("%s", *argv);
236 		}
237 	}
238 	exit(rval);
239 }
240 
241 #define	ATOI2(ar)	((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2;
242 
243 void
244 stime_arg1(char *arg, struct timeval *tvp)
245 {
246 	time_t now;
247 	struct tm *t;
248 	int yearset;
249 	char *p;
250 					/* Start with the current time. */
251 	now = tvp[0].tv_sec;
252 	if ((t = localtime(&now)) == NULL)
253 		err(1, "localtime");
254 					/* [[CC]YY]MMDDhhmm[.SS] */
255 	if ((p = strchr(arg, '.')) == NULL)
256 		t->tm_sec = 0;		/* Seconds defaults to 0. */
257 	else {
258 		if (strlen(p + 1) != 2)
259 			goto terr;
260 		*p++ = '\0';
261 		t->tm_sec = ATOI2(p);
262 	}
263 
264 	yearset = 0;
265 	switch(strlen(arg)) {
266 	case 12:			/* CCYYMMDDhhmm */
267 		t->tm_year = ATOI2(arg);
268 		t->tm_year *= 100;
269 		yearset = 1;
270 		/* FALLTHROUGH */
271 	case 10:			/* YYMMDDhhmm */
272 		if (yearset) {
273 			yearset = ATOI2(arg);
274 			t->tm_year += yearset;
275 		} else {
276 			yearset = ATOI2(arg);
277 			if (yearset < 69)
278 				t->tm_year = yearset + 2000;
279 			else
280 				t->tm_year = yearset + 1900;
281 		}
282 		t->tm_year -= 1900;	/* Convert to UNIX time. */
283 		/* FALLTHROUGH */
284 	case 8:				/* MMDDhhmm */
285 		t->tm_mon = ATOI2(arg);
286 		--t->tm_mon;		/* Convert from 01-12 to 00-11 */
287 		t->tm_mday = ATOI2(arg);
288 		t->tm_hour = ATOI2(arg);
289 		t->tm_min = ATOI2(arg);
290 		break;
291 	default:
292 		goto terr;
293 	}
294 
295 	t->tm_isdst = -1;		/* Figure out DST. */
296 	tvp[0].tv_sec = tvp[1].tv_sec = mktime(t);
297 	if (tvp[0].tv_sec == -1)
298 terr:		errx(1,
299 	"out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]");
300 
301 	tvp[0].tv_usec = tvp[1].tv_usec = 0;
302 }
303 
304 void
305 stime_arg2(char *arg, int year, struct timeval *tvp)
306 {
307 	time_t now;
308 	struct tm *t;
309 					/* Start with the current time. */
310 	now = tvp[0].tv_sec;
311 	if ((t = localtime(&now)) == NULL)
312 		err(1, "localtime");
313 
314 	t->tm_mon = ATOI2(arg);		/* MMDDhhmm[yy] */
315 	--t->tm_mon;			/* Convert from 01-12 to 00-11 */
316 	t->tm_mday = ATOI2(arg);
317 	t->tm_hour = ATOI2(arg);
318 	t->tm_min = ATOI2(arg);
319 	if (year) {
320 		t->tm_year = ATOI2(arg);
321 		if (t->tm_year < 39)	/* support 2000-2038 not 1902-1969 */
322 			t->tm_year += 100;
323 	}
324 
325 	t->tm_isdst = -1;		/* Figure out DST. */
326 	tvp[0].tv_sec = tvp[1].tv_sec = mktime(t);
327 	if (tvp[0].tv_sec == -1)
328 		errx(1,
329 	"out of range or illegal time specification: MMDDhhmm[yy]");
330 
331 	tvp[0].tv_usec = tvp[1].tv_usec = 0;
332 }
333 
334 /* Calculate a time offset in seconds, given an arg of the format [-]HHMMSS. */
335 int
336 timeoffset(char *arg)
337 {
338 	int offset;
339 	int isneg;
340 
341 	offset = 0;
342 	isneg = *arg == '-';
343 	if (isneg)
344 		arg++;
345 	switch (strlen(arg)) {
346 	default:				/* invalid */
347 		errx(1, "Invalid offset spec, must be [-][[HH]MM]SS");
348 
349 	case 6:					/* HHMMSS */
350 		offset = ATOI2(arg);
351 		/* FALLTHROUGH */
352 	case 4:					/* MMSS */
353 		offset = offset * 60 + ATOI2(arg);
354 		/* FALLTHROUGH */
355 	case 2:					/* SS */
356 		offset = offset * 60 + ATOI2(arg);
357 	}
358 	if (isneg)
359 		return (-offset);
360 	else
361 		return (offset);
362 }
363 
364 void
365 stime_file(char *fname, struct timeval *tvp)
366 {
367 	struct stat sb;
368 
369 	if (stat(fname, &sb))
370 		err(1, "%s", fname);
371 	TIMESPEC_TO_TIMEVAL(tvp, &sb.st_atim);
372 	TIMESPEC_TO_TIMEVAL(tvp + 1, &sb.st_mtim);
373 }
374 
375 int
376 rw(char *fname, struct stat *sbp, int force)
377 {
378 	int fd, needed_chmod, rval;
379 	u_char byte;
380 
381 	/* Try regular files. */
382 	if (!S_ISREG(sbp->st_mode)) {
383 		warnx("%s: %s", fname, strerror(EFTYPE));
384 		return (1);
385 	}
386 
387 	needed_chmod = rval = 0;
388 	if ((fd = open(fname, O_RDWR, 0)) == -1) {
389 		if (!force || chmod(fname, DEFFILEMODE))
390 			goto err;
391 		if ((fd = open(fname, O_RDWR, 0)) == -1)
392 			goto err;
393 		needed_chmod = 1;
394 	}
395 
396 	if (sbp->st_size != 0) {
397 		if (read(fd, &byte, sizeof(byte)) != sizeof(byte))
398 			goto err;
399 		if (lseek(fd, (off_t)0, SEEK_SET) == -1)
400 			goto err;
401 		if (write(fd, &byte, sizeof(byte)) != sizeof(byte))
402 			goto err;
403 	} else {
404 		if (write(fd, &byte, sizeof(byte)) != sizeof(byte)) {
405 err:			rval = 1;
406 			warn("%s", fname);
407 		} else if (ftruncate(fd, (off_t)0)) {
408 			rval = 1;
409 			warn("%s: file modified", fname);
410 		}
411 	}
412 
413 	if (close(fd) && rval != 1) {
414 		rval = 1;
415 		warn("%s", fname);
416 	}
417 	if (needed_chmod && chmod(fname, sbp->st_mode) && rval != 1) {
418 		rval = 1;
419 		warn("%s: permissions modified", fname);
420 	}
421 	return (rval);
422 }
423 
424 void
425 usage(char *myname)
426 {
427 	fprintf(stderr, "usage:\n" "%s [-A [-][[hh]mm]SS] [-acfhm] [-r file] "
428 		"[-t [[CC]YY]MMDDhhmm[.SS]] file ...\n", myname);
429 	exit(1);
430 }
431