11590Srgrimes/*
21590Srgrimes * Copyright (c) 1993
31590Srgrimes *	The Regents of the University of California.  All rights reserved.
41590Srgrimes *
51590Srgrimes * Redistribution and use in source and binary forms, with or without
61590Srgrimes * modification, are permitted provided that the following conditions
71590Srgrimes * are met:
81590Srgrimes * 1. Redistributions of source code must retain the above copyright
91590Srgrimes *    notice, this list of conditions and the following disclaimer.
101590Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
111590Srgrimes *    notice, this list of conditions and the following disclaimer in the
121590Srgrimes *    documentation and/or other materials provided with the distribution.
131590Srgrimes * 4. Neither the name of the University nor the names of its contributors
141590Srgrimes *    may be used to endorse or promote products derived from this software
151590Srgrimes *    without specific prior written permission.
161590Srgrimes *
171590Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
181590Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
191590Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
201590Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
211590Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
221590Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
231590Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
241590Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
251590Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
261590Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
271590Srgrimes * SUCH DAMAGE.
281590Srgrimes */
291590Srgrimes
3087706Smarkm#include <sys/cdefs.h>
3187706Smarkm
3287706Smarkm__FBSDID("$FreeBSD$");
3387706Smarkm
341590Srgrimes#ifndef lint
3577247Skrisstatic const char copyright[] =
361590Srgrimes"@(#) Copyright (c) 1993\n\
371590Srgrimes	The Regents of the University of California.  All rights reserved.\n";
3887706Smarkm#endif
391590Srgrimes
401590Srgrimes#ifndef lint
4177247Skrisstatic const char sccsid[] = "@(#)touch.c	8.1 (Berkeley) 6/6/93";
4277247Skris#endif
431590Srgrimes
441590Srgrimes#include <sys/types.h>
451590Srgrimes#include <sys/stat.h>
461590Srgrimes#include <sys/time.h>
471590Srgrimes
48236852Sjilles#include <ctype.h>
491590Srgrimes#include <err.h>
501590Srgrimes#include <errno.h>
511590Srgrimes#include <fcntl.h>
52168571Sgrog#include <libgen.h>
531590Srgrimes#include <stdio.h>
541590Srgrimes#include <stdlib.h>
551590Srgrimes#include <string.h>
561590Srgrimes#include <time.h>
571590Srgrimes#include <unistd.h>
581590Srgrimes
59249805Seadlerstatic void	stime_arg1(const char *, struct timeval *);
60249805Seadlerstatic void	stime_arg2(const char *, int, struct timeval *);
61249805Seadlerstatic void	stime_darg(const char *, struct timeval *);
62249805Seadlerstatic void	stime_file(const char *, struct timeval *);
63249805Seadlerstatic int	timeoffset(const char *);
64249806Seadlerstatic void	usage(const char *);
651590Srgrimes
661590Srgrimesint
67102944Sdwmalonemain(int argc, char *argv[])
681590Srgrimes{
691590Srgrimes	struct stat sb;
701590Srgrimes	struct timeval tv[2];
7183826Sobrien	int (*stat_f)(const char *, struct stat *);
7283826Sobrien	int (*utimes_f)(const char *, const struct timeval *);
73230979Sjh	int Aflag, aflag, cflag, mflag, ch, fd, len, rval, timeset;
741590Srgrimes	char *p;
75168525Sgrog	char *myname;
761590Srgrimes
77168571Sgrog	myname = basename(argv[0]);
78230979Sjh	Aflag = aflag = cflag = mflag = timeset = 0;
7983826Sobrien	stat_f = stat;
8083826Sobrien	utimes_f = utimes;
81249805Seadler	if (gettimeofday(&tv[0], NULL) == -1)
821590Srgrimes		err(1, "gettimeofday");
831590Srgrimes
84236852Sjilles	while ((ch = getopt(argc, argv, "A:acd:fhmr:t:")) != -1)
851590Srgrimes		switch(ch) {
86168525Sgrog		case 'A':
87168525Sgrog			Aflag = timeoffset(optarg);
88168525Sgrog			break;
891590Srgrimes		case 'a':
901590Srgrimes			aflag = 1;
911590Srgrimes			break;
921590Srgrimes		case 'c':
931590Srgrimes			cflag = 1;
941590Srgrimes			break;
95236852Sjilles		case 'd':
96236852Sjilles			timeset = 1;
97236852Sjilles			stime_darg(optarg, tv);
98236852Sjilles			break;
991590Srgrimes		case 'f':
100230979Sjh			/* No-op for compatibility. */
1011590Srgrimes			break;
10283826Sobrien		case 'h':
10383826Sobrien			cflag = 1;
10483826Sobrien			stat_f = lstat;
10583826Sobrien			utimes_f = lutimes;
10683826Sobrien			break;
1071590Srgrimes		case 'm':
1081590Srgrimes			mflag = 1;
1091590Srgrimes			break;
1101590Srgrimes		case 'r':
1111590Srgrimes			timeset = 1;
1121590Srgrimes			stime_file(optarg, tv);
1131590Srgrimes			break;
1141590Srgrimes		case 't':
1151590Srgrimes			timeset = 1;
1161590Srgrimes			stime_arg1(optarg, tv);
1171590Srgrimes			break;
1181590Srgrimes		default:
119168525Sgrog			usage(myname);
1201590Srgrimes		}
1211590Srgrimes	argc -= optind;
1221590Srgrimes	argv += optind;
1231590Srgrimes
124168571Sgrog	if (aflag == 0 && mflag == 0)
1251590Srgrimes		aflag = mflag = 1;
1261590Srgrimes
127168571Sgrog	if (timeset) {
128168571Sgrog		if (Aflag) {
129168571Sgrog			/*
130168571Sgrog			 * We're setting the time to an offset from a specified
131168571Sgrog			 * time.  God knows why, but it means that we can set
132168571Sgrog			 * that time once and for all here.
133168571Sgrog			 */
134168571Sgrog			if (aflag)
135168571Sgrog				tv[0].tv_sec += Aflag;
136168571Sgrog			if (mflag)
137168571Sgrog				tv[1].tv_sec += Aflag;
138168571Sgrog			Aflag = 0;		/* done our job */
1391590Srgrimes		}
140168571Sgrog	} else {
141168571Sgrog		/*
142168571Sgrog		 * If no -r or -t flag, at least two operands, the first of
143168571Sgrog		 * which is an 8 or 10 digit number, use the obsolete time
144168571Sgrog		 * specification, otherwise use the current time.
145168571Sgrog		 */
146168571Sgrog		if (argc > 1) {
147168571Sgrog			strtol(argv[0], &p, 10);
148168571Sgrog			len = p - argv[0];
149168571Sgrog			if (*p == '\0' && (len == 8 || len == 10)) {
150168571Sgrog				timeset = 1;
151168571Sgrog				stime_arg2(*argv++, len == 10, tv);
152168571Sgrog			}
153168571Sgrog		}
154168571Sgrog		/* Both times default to the same. */
155168571Sgrog		tv[1] = tv[0];
1561590Srgrimes	}
1571590Srgrimes
1581590Srgrimes	if (*argv == NULL)
159168525Sgrog		usage(myname);
1601590Srgrimes
161168571Sgrog	if (Aflag)
162168571Sgrog		cflag = 1;
163168571Sgrog
1641590Srgrimes	for (rval = 0; *argv; ++argv) {
1651590Srgrimes		/* See if the file exists. */
16683826Sobrien		if (stat_f(*argv, &sb) != 0) {
167198175Sjh			if (errno != ENOENT) {
168198175Sjh				rval = 1;
169198175Sjh				warn("%s", *argv);
170198175Sjh				continue;
171198175Sjh			}
1721590Srgrimes			if (!cflag) {
1731590Srgrimes				/* Create the file. */
1741590Srgrimes				fd = open(*argv,
1751590Srgrimes				    O_WRONLY | O_CREAT, DEFFILEMODE);
1761590Srgrimes				if (fd == -1 || fstat(fd, &sb) || close(fd)) {
1771590Srgrimes					rval = 1;
1781590Srgrimes					warn("%s", *argv);
1791590Srgrimes					continue;
1801590Srgrimes				}
1811590Srgrimes
1821590Srgrimes				/* If using the current time, we're done. */
1831590Srgrimes				if (!timeset)
1841590Srgrimes					continue;
1851590Srgrimes			} else
1861590Srgrimes				continue;
18748566Sbillf		}
1881590Srgrimes
1891590Srgrimes		if (!aflag)
190205793Sed			TIMESPEC_TO_TIMEVAL(&tv[0], &sb.st_atim);
1911590Srgrimes		if (!mflag)
192205793Sed			TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtim);
193168571Sgrog
194168571Sgrog		/*
195168571Sgrog		 * We're adjusting the times based on the file times, not a
196168571Sgrog		 * specified time (that gets handled above).
197168571Sgrog		 */
198168525Sgrog		if (Aflag) {
199168571Sgrog			if (aflag) {
200205793Sed				TIMESPEC_TO_TIMEVAL(&tv[0], &sb.st_atim);
201168571Sgrog				tv[0].tv_sec += Aflag;
202168571Sgrog			}
203168571Sgrog			if (mflag) {
204205793Sed				TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtim);
205168571Sgrog				tv[1].tv_sec += Aflag;
206168571Sgrog			}
207168525Sgrog		}
2081590Srgrimes
2091590Srgrimes		/* Try utimes(2). */
21083826Sobrien		if (!utimes_f(*argv, tv))
2111590Srgrimes			continue;
2121590Srgrimes
2131590Srgrimes		/* If the user specified a time, nothing else we can do. */
214198175Sjh		if (timeset || Aflag) {
2151590Srgrimes			rval = 1;
2161590Srgrimes			warn("%s", *argv);
217155082Sache			continue;
2181590Srgrimes		}
2191590Srgrimes
2201590Srgrimes		/*
2211590Srgrimes		 * System V and POSIX 1003.1 require that a NULL argument
2221590Srgrimes		 * set the access/modification times to the current time.
2231590Srgrimes		 * The permission checks are different, too, in that the
2241590Srgrimes		 * ability to write the file is sufficient.  Take a shot.
2251590Srgrimes		 */
22683826Sobrien		 if (!utimes_f(*argv, NULL))
2271590Srgrimes			continue;
2281590Srgrimes
229230979Sjh		rval = 1;
230230979Sjh		warn("%s", *argv);
2311590Srgrimes	}
2321590Srgrimes	exit(rval);
2331590Srgrimes}
2341590Srgrimes
2351590Srgrimes#define	ATOI2(ar)	((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2;
2361590Srgrimes
237249805Seadlerstatic void
238249805Seadlerstime_arg1(const char *arg, struct timeval *tvp)
2391590Srgrimes{
24037259Sbde	time_t now;
2411590Srgrimes	struct tm *t;
2421590Srgrimes	int yearset;
2431590Srgrimes	char *p;
2441590Srgrimes					/* Start with the current time. */
24537259Sbde	now = tvp[0].tv_sec;
24637259Sbde	if ((t = localtime(&now)) == NULL)
2471590Srgrimes		err(1, "localtime");
2481590Srgrimes					/* [[CC]YY]MMDDhhmm[.SS] */
2491590Srgrimes	if ((p = strchr(arg, '.')) == NULL)
2501590Srgrimes		t->tm_sec = 0;		/* Seconds defaults to 0. */
2511590Srgrimes	else {
2521590Srgrimes		if (strlen(p + 1) != 2)
2531590Srgrimes			goto terr;
2541590Srgrimes		*p++ = '\0';
2551590Srgrimes		t->tm_sec = ATOI2(p);
2561590Srgrimes	}
2578874Srgrimes
2581590Srgrimes	yearset = 0;
2591590Srgrimes	switch(strlen(arg)) {
2601590Srgrimes	case 12:			/* CCYYMMDDhhmm */
2611590Srgrimes		t->tm_year = ATOI2(arg);
2629446Sjoerg		t->tm_year *= 100;
2631590Srgrimes		yearset = 1;
264102412Scharnier		/* FALLTHROUGH */
2651590Srgrimes	case 10:			/* YYMMDDhhmm */
2661590Srgrimes		if (yearset) {
2671590Srgrimes			yearset = ATOI2(arg);
2681590Srgrimes			t->tm_year += yearset;
2691590Srgrimes		} else {
2701590Srgrimes			yearset = ATOI2(arg);
2711590Srgrimes			if (yearset < 69)
2721590Srgrimes				t->tm_year = yearset + 2000;
2731590Srgrimes			else
2741590Srgrimes				t->tm_year = yearset + 1900;
2751590Srgrimes		}
2761590Srgrimes		t->tm_year -= 1900;	/* Convert to UNIX time. */
2771590Srgrimes		/* FALLTHROUGH */
2781590Srgrimes	case 8:				/* MMDDhhmm */
2791590Srgrimes		t->tm_mon = ATOI2(arg);
2801590Srgrimes		--t->tm_mon;		/* Convert from 01-12 to 00-11 */
2811590Srgrimes		t->tm_mday = ATOI2(arg);
2821590Srgrimes		t->tm_hour = ATOI2(arg);
2831590Srgrimes		t->tm_min = ATOI2(arg);
2841590Srgrimes		break;
2851590Srgrimes	default:
2861590Srgrimes		goto terr;
2871590Srgrimes	}
2881590Srgrimes
2891590Srgrimes	t->tm_isdst = -1;		/* Figure out DST. */
2901590Srgrimes	tvp[0].tv_sec = tvp[1].tv_sec = mktime(t);
2911590Srgrimes	if (tvp[0].tv_sec == -1)
292249805Seadler		goto terr;
2931590Srgrimes
2941590Srgrimes	tvp[0].tv_usec = tvp[1].tv_usec = 0;
295249805Seadler	return;
296249805Seadler
297249805Seadlerterr:
298249805Seadler	errx(1, "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]");
2991590Srgrimes}
3001590Srgrimes
301249805Seadlerstatic void
302249805Seadlerstime_arg2(const char *arg, int year, struct timeval *tvp)
3031590Srgrimes{
30437259Sbde	time_t now;
3051590Srgrimes	struct tm *t;
3061590Srgrimes					/* Start with the current time. */
30737259Sbde	now = tvp[0].tv_sec;
30837259Sbde	if ((t = localtime(&now)) == NULL)
3091590Srgrimes		err(1, "localtime");
3101590Srgrimes
3111590Srgrimes	t->tm_mon = ATOI2(arg);		/* MMDDhhmm[yy] */
3121590Srgrimes	--t->tm_mon;			/* Convert from 01-12 to 00-11 */
3131590Srgrimes	t->tm_mday = ATOI2(arg);
3141590Srgrimes	t->tm_hour = ATOI2(arg);
3151590Srgrimes	t->tm_min = ATOI2(arg);
31642307Sdanny	if (year) {
3171590Srgrimes		t->tm_year = ATOI2(arg);
31842310Sdanny		if (t->tm_year < 39)	/* support 2000-2038 not 1902-1969 */
31942307Sdanny			t->tm_year += 100;
32042307Sdanny	}
3211590Srgrimes
3221590Srgrimes	t->tm_isdst = -1;		/* Figure out DST. */
3231590Srgrimes	tvp[0].tv_sec = tvp[1].tv_sec = mktime(t);
3241590Srgrimes	if (tvp[0].tv_sec == -1)
3251590Srgrimes		errx(1,
3261590Srgrimes	"out of range or illegal time specification: MMDDhhmm[yy]");
3271590Srgrimes
3281590Srgrimes	tvp[0].tv_usec = tvp[1].tv_usec = 0;
3291590Srgrimes}
3301590Srgrimes
331249805Seadlerstatic void
332249805Seadlerstime_darg(const char *arg, struct timeval *tvp)
333236852Sjilles{
334236852Sjilles	struct tm t = { .tm_sec = 0 };
335236852Sjilles	const char *fmt, *colon;
336236852Sjilles	char *p;
337236852Sjilles	int val, isutc = 0;
338236852Sjilles
339236852Sjilles	tvp[0].tv_usec = 0;
340236852Sjilles	t.tm_isdst = -1;
341236852Sjilles	colon = strchr(arg, ':');
342236852Sjilles	if (colon == NULL || strchr(colon + 1, ':') == NULL)
343236852Sjilles		goto bad;
344236852Sjilles	fmt = strchr(arg, 'T') != NULL ? "%Y-%m-%dT%H:%M:%S" :
345236852Sjilles	    "%Y-%m-%d %H:%M:%S";
346236852Sjilles	p = strptime(arg, fmt, &t);
347236852Sjilles	if (p == NULL)
348236852Sjilles		goto bad;
349236852Sjilles	/* POSIX: must have at least one digit after dot */
350236852Sjilles	if ((*p == '.' || *p == ',') && isdigit((unsigned char)p[1])) {
351236852Sjilles		p++;
352236852Sjilles		val = 100000;
353236852Sjilles		while (isdigit((unsigned char)*p)) {
354236852Sjilles			tvp[0].tv_usec += val * (*p - '0');
355236852Sjilles			p++;
356236852Sjilles			val /= 10;
357236852Sjilles		}
358236852Sjilles	}
359236852Sjilles	if (*p == 'Z') {
360236852Sjilles		isutc = 1;
361236852Sjilles		p++;
362236852Sjilles	}
363236852Sjilles	if (*p != '\0')
364236852Sjilles		goto bad;
365236852Sjilles
366236852Sjilles	tvp[0].tv_sec = isutc ? timegm(&t) : mktime(&t);
367236852Sjilles
368236852Sjilles	tvp[1] = tvp[0];
369236852Sjilles	return;
370236852Sjilles
371236852Sjillesbad:
372236852Sjilles	errx(1, "out of range or illegal time specification: YYYY-MM-DDThh:mm:SS[.frac][tz]");
373236852Sjilles}
374236852Sjilles
375168525Sgrog/* Calculate a time offset in seconds, given an arg of the format [-]HHMMSS. */
376168525Sgrogint
377249805Seadlertimeoffset(const char *arg)
378168525Sgrog{
379168525Sgrog	int offset;
380168525Sgrog	int isneg;
381168525Sgrog
382168525Sgrog	offset = 0;
383168525Sgrog	isneg = *arg == '-';
384168525Sgrog	if (isneg)
385168525Sgrog		arg++;
386168525Sgrog	switch (strlen(arg)) {
387168525Sgrog	default:				/* invalid */
388168525Sgrog		errx(1, "Invalid offset spec, must be [-][[HH]MM]SS");
389168525Sgrog
390168525Sgrog	case 6:					/* HHMMSS */
391168525Sgrog		offset = ATOI2(arg);
392168525Sgrog		/* FALLTHROUGH */
393168525Sgrog	case 4:					/* MMSS */
394168525Sgrog		offset = offset * 60 + ATOI2(arg);
395168525Sgrog		/* FALLTHROUGH */
396168525Sgrog	case 2:					/* SS */
397168525Sgrog		offset = offset * 60 + ATOI2(arg);
398168525Sgrog	}
399168525Sgrog	if (isneg)
400168525Sgrog		return (-offset);
401168525Sgrog	else
402168525Sgrog		return (offset);
403168525Sgrog}
404168525Sgrog
405249805Seadlerstatic void
406249805Seadlerstime_file(const char *fname, struct timeval *tvp)
4071590Srgrimes{
4081590Srgrimes	struct stat sb;
4091590Srgrimes
4101590Srgrimes	if (stat(fname, &sb))
4111590Srgrimes		err(1, "%s", fname);
412205793Sed	TIMESPEC_TO_TIMEVAL(tvp, &sb.st_atim);
413205793Sed	TIMESPEC_TO_TIMEVAL(tvp + 1, &sb.st_mtim);
4141590Srgrimes}
4151590Srgrimes
416249805Seadlerstatic void
417249806Seadlerusage(const char *myname)
4181590Srgrimes{
419236852Sjilles	fprintf(stderr, "usage: %s [-A [-][[hh]mm]SS] [-achm] [-r file] "
420236852Sjilles		"[-t [[CC]YY]MMDDhhmm[.SS]]\n"
421236852Sjilles		"       [-d YYYY-MM-DDThh:mm:SS[.frac][tz]] "
422236852Sjilles		"file ...\n", myname);
4231590Srgrimes	exit(1);
4241590Srgrimes}
425