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: src/usr.bin/touch/touch.c,v 1.25 2010/03/28 13:16:08 ed Exp $");
37
38#ifndef lint
39static 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
45static 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
62int	rw(char *, struct stat *, int);
63void	stime_arg1(char *, struct timeval *);
64void	stime_arg2(char *, int, struct timeval *);
65void	stime_file(char *, struct timeval *);
66int	timeoffset(char *);
67void	usage(char *);
68
69int
70main(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_atimespec);
191		if (!mflag)
192			TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtimespec);
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_atimespec);
201				tv[0].tv_sec += Aflag;
202			}
203			if (mflag) {
204				TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtimespec);
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
243void
244stime_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)
298terr:		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
304void
305stime_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. */
335int
336timeoffset(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
364void
365stime_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_atimespec);
372	TIMESPEC_TO_TIMEVAL(tvp + 1, &sb.st_mtimespec);
373}
374
375int
376rw(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)) {
405err:			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
424void
425usage(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