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