-.\" $OpenBSD: touch.1,v 1.14 2007/08/06 19:16:06 sobrado Exp $
-.\" $NetBSD: touch.1,v 1.8 1995/08/31 22:10:05 jtc Exp $
+.\" $NetBSD: touch.1,v 1.25 2012/10/24 02:46:25 pgoyette Exp $
.\"
.\" Copyright (c) 1991, 1993
.\" The Regents of the University of California. All rights reserved.
.\"
.\" @(#)touch.1 8.3 (Berkeley) 4/28/95
.\"
-.Dd $Mdocdate: August 6 2007 $
+.Dd October 22, 2012
.Dt TOUCH 1
.Os
.Sh NAME
.Nm touch
.Nd change file access and modification times
.Sh SYNOPSIS
-.Nm touch
-.Op Fl acm
+.Nm
+.Op Fl acfhm
+.Op Fl d Ar human-datetime
+.Op Fl Fl date Ar human-datetime
.Op Fl r Ar file
-.Op Fl t Ar [[CC]YY]MMDDhhmm[.SS]
-.Ar
+.Op Fl Fl reference Ar file
+.Op Fl t Ar datetime
+.Ar file ...
.Sh DESCRIPTION
The
.Nm
-utility sets the modification and access times of files to the
+utility changes the access and modification times of files to the
current time of day.
If the file doesn't exist, it is created with default permissions.
.Pp
-The options are as follows:
-.Bl -tag -width Ds
+The following options are available:
+.Bl -tag -width "-d human-datetime"
.It Fl a
Change the access time of the file.
The modification time of the file is not changed unless the
.Nm
utility does not treat this as an error.
No error messages are displayed and the exit value is not affected.
+.It Fl d Ar human-datetime
+.It Fl Fl date Ar human-datetime
+Parse
+.Ar human-datetime
+using the human datetime parser
+.Xr parsedate 3 .
+.It Fl f
+This flag has no effect; it is accepted for compatibility reasons.
+.It Fl h
+If
+.Ar file
+is a symbolic link, access and/or modification time of the link is changed.
+This option implies
+.Fl c .
.It Fl m
Change the modification time of the file.
The access time of the file is not changed unless the
.Fl a
flag is also specified.
.It Fl r Ar file
-Use the access and modification times from the specified file
+.It Fl Fl reference Ar file
+Use the access and modifications times from
+.Ar file
instead of the current time of day.
-.It Fl t Ar [[CC]YY]MMDDhhmm[.SS]
+.It Fl t Ar datetime
Change the access and modification times to the specified time.
-The argument should be in the form
+The argument
+.Ar datetime
+should be in the form
.Dq [[CC]YY]MMDDhhmm[.SS]
where each pair of letters represents the following:
.Pp
.El
.Pp
The
-.Nm
-utility exits 0 on success or >0 if an error occurred.
-.Sh SEE ALSO
-.Xr utimes 2
-.Sh STANDARDS
-The
-.Nm
-utility is compliant with the
-.St -p1003.1-2004
-specification.
-.Pp
+.Fl d ,
+.Fl r ,
+and
+.Fl t
+options are mutually exclusive.
+If more than one of these options is present, the last one is used.
+.Sh EXIT STATUS
+.Ex -std
+.Sh COMPATIBILITY
The obsolescent form of
-.Nm touch ,
+.Nm ,
where a time format is specified as the first argument, is supported.
When no
-.Fl r
+.Fl d ,
+.Fl r ,
or
.Fl t
option is specified, there are at least two arguments, and the first
option.
If the
.Dq YY
-letter pair is in the range 69 to 99, the year is set from 1969 to 1999;
+letter pair is in the range 69 to 99, the year is set to 1969 to 1999,
otherwise, the year is set in the 21st century.
+.Sh SEE ALSO
+.Xr utimes 2
+.Sh STANDARDS
+The
+.Nm
+utility is expected to be a superset of the
+.St -p1003.2
+specification.
.Sh HISTORY
A
.Nm
utility appeared in
.At v7 .
+.Sh BUGS
+A symbolic link can't be a reference file of access and/or modification time.
-/* $OpenBSD: touch.c,v 1.17 2007/08/06 19:16:06 sobrado Exp $ */
-/* $NetBSD: touch.c,v 1.11 1995/08/31 22:10:06 jtc Exp $ */
+/* $NetBSD: touch.c,v 1.32 2012/10/22 21:51:58 christos Exp $ */
/*
* Copyright (c) 1993
* SUCH DAMAGE.
*/
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1993\
+ The Regents of the University of California. All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)touch.c 8.2 (Berkeley) 4/28/95";
+#endif
+__RCSID("$NetBSD: touch.c,v 1.32 2012/10/22 21:51:58 christos Exp $");
+#endif /* not lint */
+
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
-#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <locale.h>
#include <time.h>
-#include <utime.h>
#include <tzfile.h>
#include <unistd.h>
-
-void stime_arg1(char *, struct timeval *);
-void stime_arg2(char *, int, struct timeval *);
-void stime_file(char *, struct timeval *);
-__dead void usage(void);
-
-char * __progname;
+#include <util.h>
+#include <getopt.h>
+
+static void stime_arg0(char *, struct timeval *);
+static void stime_arg1(char *, struct timeval *);
+static void stime_arg2(char *, int, struct timeval *);
+static void stime_file(char *, struct timeval *);
+__dead static void usage(void);
+
+struct option touch_longopts[] = {
+ { "date", required_argument, 0,
+ 'd' },
+ { "reference", required_argument, 0,
+ 'r' },
+ { NULL, 0, 0,
+ 0 },
+};
int
main(int argc, char *argv[])
{
- struct stat sb;
- struct timeval tv[2];
- int aflag, cflag, mflag, ch, fd, len, rval, timeset;
- char *p;
+ struct stat sb;
+ struct timeval tv[2];
+ int aflag, cflag, hflag, mflag, ch, fd, len, rval, timeset;
+ char *p;
+ int (*change_file_times)(const char *, const struct timeval *);
+ int (*get_file_status)(const char *, struct stat *);
- (void)setlocale(LC_ALL, "");
- __progname = argv[0];
+ setlocale(LC_ALL, "");
- aflag = cflag = mflag = timeset = 0;
+ aflag = cflag = hflag = mflag = timeset = 0;
if (gettimeofday(&tv[0], NULL))
err(1, "gettimeofday");
- while ((ch = getopt(argc, argv, "acfmr:t:")) != -1)
- switch (ch) {
+ while ((ch = getopt_long(argc, argv, "acd:fhmr:t:", touch_longopts,
+ NULL)) != -1)
+ switch(ch) {
case 'a':
aflag = 1;
break;
case 'c':
cflag = 1;
break;
+ case 'd':
+ timeset = 1;
+ stime_arg0(optarg, tv);
+ break;
case 'f':
break;
+ case 'h':
+ hflag = 1;
+ break;
case 'm':
mflag = 1;
break;
timeset = 1;
stime_arg1(optarg, tv);
break;
+ case '?':
default:
usage();
}
if (aflag == 0 && mflag == 0)
aflag = mflag = 1;
+ if (hflag) {
+ cflag = 1; /* Don't create new file */
+ change_file_times = lutimes;
+ get_file_status = lstat;
+ } else {
+ change_file_times = utimes;
+ get_file_status = stat;
+ }
+
/*
* If no -r or -t flag, at least two operands, the first of which
* is an 8 or 10 digit number, use the obsolete time specification.
if (*argv == NULL)
usage();
- for (rval = 0; *argv; ++argv) {
+ for (rval = EXIT_SUCCESS; *argv; ++argv) {
/* See if the file exists. */
- if (stat(*argv, &sb)) {
+ if ((*get_file_status)(*argv, &sb)) {
if (!cflag) {
/* Create the file. */
fd = open(*argv,
O_WRONLY | O_CREAT, DEFFILEMODE);
if (fd == -1 || fstat(fd, &sb) || close(fd)) {
- rval = 1;
+ rval = EXIT_FAILURE;
warn("%s", *argv);
continue;
}
} else
continue;
}
-
if (!aflag)
TIMESPEC_TO_TIMEVAL(&tv[0], &sb.st_atimespec);
if (!mflag)
TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtimespec);
/* Try utimes(2). */
- if (!utimes(*argv, tv))
+ if (!(*change_file_times)(*argv, tv))
continue;
/* If the user specified a time, nothing else we can do. */
if (timeset) {
- rval = 1;
+ rval = EXIT_FAILURE;
warn("%s", *argv);
}
* The permission checks are different, too, in that the
* ability to write the file is sufficient. Take a shot.
*/
- if (!utimes(*argv, NULL))
+ if (!(*change_file_times)(*argv, NULL))
continue;
- rval = 1;
+ rval = EXIT_FAILURE;
warn("%s", *argv);
}
exit(rval);
#define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0'))
-void
+static void
+stime_arg0(char *arg, struct timeval *tvp)
+{
+ tvp[1].tv_sec = tvp[0].tv_sec = parsedate(arg, NULL, NULL);
+ if (tvp[0].tv_sec == -1)
+ errx(EXIT_FAILURE, "Could not parse `%s'", arg);
+ tvp[0].tv_usec = tvp[1].tv_usec = 0;
+}
+
+static void
stime_arg1(char *arg, struct timeval *tvp)
{
- struct tm *lt;
- time_t tmptime;
- int yearset;
- char *dot, *p;
+ struct tm *t;
+ time_t tmptime;
+ int yearset;
+ char *p;
/* Start with the current time. */
tmptime = tvp[0].tv_sec;
- if ((lt = localtime(&tmptime)) == NULL)
- err(1, "localtime");
+ if ((t = localtime(&tmptime)) == NULL)
+ err(EXIT_FAILURE, "localtime");
/* [[CC]YY]MMDDhhmm[.SS] */
- for (p = arg, dot = NULL; *p != '\0'; p++) {
- if (*p == '.' && dot == NULL)
- dot = p;
- else if (!isdigit((unsigned char)*p))
- goto terr;
- }
- if (dot == NULL)
- lt->tm_sec = 0; /* Seconds defaults to 0. */
+ if ((p = strchr(arg, '.')) == NULL)
+ t->tm_sec = 0; /* Seconds defaults to 0. */
else {
- *dot++ = '\0';
- if (strlen(dot) != 2)
- goto terr;
- lt->tm_sec = ATOI2(dot);
- if (lt->tm_sec > 61) /* Could be leap second. */
+ if (strlen(p + 1) != 2)
goto terr;
+ *p++ = '\0';
+ t->tm_sec = ATOI2(p);
}
-
+
yearset = 0;
switch (strlen(arg)) {
case 12: /* CCYYMMDDhhmm */
- lt->tm_year = ATOI2(arg) * 100 - TM_YEAR_BASE;
+ t->tm_year = ATOI2(arg) * 100 - TM_YEAR_BASE;
yearset = 1;
/* FALLTHROUGH */
case 10: /* YYMMDDhhmm */
if (yearset) {
- yearset = ATOI2(arg);
- lt->tm_year += yearset;
+ t->tm_year += ATOI2(arg);
} else {
yearset = ATOI2(arg);
- /* Preserve current century. */
- lt->tm_year = ((lt->tm_year / 100) * 100) + yearset;
+ if (yearset < 69)
+ t->tm_year = yearset + 2000 - TM_YEAR_BASE;
+ else
+ t->tm_year = yearset + 1900 - TM_YEAR_BASE;
}
/* FALLTHROUGH */
case 8: /* MMDDhhmm */
- lt->tm_mon = ATOI2(arg);
- if (lt->tm_mon > 12 || lt->tm_mon == 0)
- goto terr;
- --lt->tm_mon; /* Convert from 01-12 to 00-11 */
- lt->tm_mday = ATOI2(arg);
- if (lt->tm_mday > 31 || lt->tm_mday == 0)
- goto terr;
- lt->tm_hour = ATOI2(arg);
- if (lt->tm_hour > 23)
- goto terr;
- lt->tm_min = ATOI2(arg);
- if (lt->tm_min > 59)
- goto terr;
+ t->tm_mon = ATOI2(arg);
+ --t->tm_mon; /* Convert from 01-12 to 00-11 */
+ /* FALLTHROUGH */
+ case 6:
+ t->tm_mday = ATOI2(arg);
+ /* FALLTHROUGH */
+ case 4:
+ t->tm_hour = ATOI2(arg);
+ /* FALLTHROUGH */
+ case 2:
+ t->tm_min = ATOI2(arg);
break;
default:
goto terr;
}
- lt->tm_isdst = -1; /* Figure out DST. */
- tvp[0].tv_sec = tvp[1].tv_sec = mktime(lt);
+ t->tm_isdst = -1; /* Figure out DST. */
+ tvp[0].tv_sec = tvp[1].tv_sec = mktime(t);
if (tvp[0].tv_sec == -1)
-terr: errx(1,
+terr: errx(EXIT_FAILURE,
"out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]");
tvp[0].tv_usec = tvp[1].tv_usec = 0;
}
-void
+static void
stime_arg2(char *arg, int year, struct timeval *tvp)
{
- struct tm *lt;
- time_t tmptime;
+ struct tm *t;
+ time_t tmptime;
/* Start with the current time. */
tmptime = tvp[0].tv_sec;
- if ((lt = localtime(&tmptime)) == NULL)
- err(1, "localtime");
-
- lt->tm_mon = ATOI2(arg); /* MMDDhhmm[YY] */
- if (lt->tm_mon > 12 || lt->tm_mon == 0)
- goto terr;
- --lt->tm_mon; /* Convert from 01-12 to 00-11 */
- lt->tm_mday = ATOI2(arg);
- if (lt->tm_mday > 31 || lt->tm_mday == 0)
- goto terr;
- lt->tm_hour = ATOI2(arg);
- if (lt->tm_hour > 23)
- goto terr;
- lt->tm_min = ATOI2(arg);
- if (lt->tm_min > 59)
- goto terr;
+ if ((t = localtime(&tmptime)) == NULL)
+ err(EXIT_FAILURE, "localtime");
+
+ t->tm_mon = ATOI2(arg); /* MMDDhhmm[yy] */
+ --t->tm_mon; /* Convert from 01-12 to 00-11 */
+ t->tm_mday = ATOI2(arg);
+ t->tm_hour = ATOI2(arg);
+ t->tm_min = ATOI2(arg);
if (year) {
- year = ATOI2(arg); /* Preserve current century. */
- lt->tm_year = ((lt->tm_year / 100) * 100) + year;
+ year = ATOI2(arg);
+ if (year < 69)
+ t->tm_year = year + 2000 - TM_YEAR_BASE;
+ else
+ t->tm_year = year + 1900 - TM_YEAR_BASE;
}
- lt->tm_sec = 0;
+ t->tm_sec = 0;
- lt->tm_isdst = -1; /* Figure out DST. */
- tvp[0].tv_sec = tvp[1].tv_sec = mktime(lt);
+ t->tm_isdst = -1; /* Figure out DST. */
+ tvp[0].tv_sec = tvp[1].tv_sec = mktime(t);
if (tvp[0].tv_sec == -1)
-terr: errx(1,
- "out of range or illegal time specification: MMDDhhmm[YY]");
+ errx(EXIT_FAILURE,
+ "out of range or illegal time specification: MMDDhhmm[yy]");
tvp[0].tv_usec = tvp[1].tv_usec = 0;
}
-void
+static void
stime_file(char *fname, struct timeval *tvp)
{
- struct stat sb;
+ struct stat sb;
if (stat(fname, &sb))
err(1, "%s", fname);
- TIMESPEC_TO_TIMEVAL(tvp, &sb.st_atimespec);
- TIMESPEC_TO_TIMEVAL(tvp + 1, &sb.st_mtimespec);
+ TIMESPEC_TO_TIMEVAL(&tvp[0], &sb.st_atimespec);
+ TIMESPEC_TO_TIMEVAL(&tvp[1], &sb.st_mtimespec);
}
-__dead void
+static void
usage(void)
{
- extern char *__progname;
-
(void)fprintf(stderr,
- "usage: %s [-acm] [-r file] [-t [[CC]YY]MMDDhhmm[.SS]] file ...\n",
- __progname);
- exit(1);
+ "Usage: %s [-acfhm] [-d|--date datetime] [-r|--reference file] [-t time] file ...\n",
+ getprogname());
+ exit(EXIT_FAILURE);
}