/* touch -- change modification and access times of files
- Copyright (C) 87, 1989-1991, 1995-1999 Free Software Foundation, Inc.
+ Copyright (C) 87, 1989-1991, 1995-2003 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
#include <sys/types.h>
#include "system.h"
-#include "closeout.h"
-#include "error.h"
#include "argmatch.h"
+#include "error.h"
#include "getdate.h"
-#include "safe-read.h"
#include "posixtm.h"
+#include "posixver.h"
+#include "quote.h"
+#include "safe-read.h"
+#include "utimens.h"
+
+/* The official name of this program (e.g., no `g' prefix). */
+#define PROGRAM_NAME "touch"
+
+#define WRITTEN_BY \
+_("Written by Paul Rubin, Arnold Robbins, Jim Kingdon, David MacKenzie, and Randy Smith.")
#ifndef STDC_HEADERS
time_t time ();
#endif
-int full_write ();
-
/* Bitmasks for `change_times'. */
#define CH_ATIME 1
#define CH_MTIME 2
/* If nonzero, the only thing we have to do is change both the
modification and access time to the current time, so we don't
- have to own the file, just be able to read and write it. */
+ have to own the file, just be able to read and write it.
+ On some systems, we can do this if we own the file, even though
+ we have neither read nor write access to it. */
static int amtime_now;
/* New time to use when setting time. */
-static time_t newtime;
+static struct timespec newtime;
/* File to use for -r. */
static char *ref_file;
/* Info about the reference file. */
static struct stat ref_stats;
-/* If nonzero, display usage information and exit. */
-static int show_help;
-
-/* If nonzero, print the version on standard output and exit. */
-static int show_version;
+/* For long options that have no equivalent short option, use a
+ non-character as a pseudo short option, starting with CHAR_MAX + 1. */
+enum
+{
+ TIME_OPTION = CHAR_MAX + 1
+};
static struct option const longopts[] =
{
- {"time", required_argument, 0, CHAR_MAX + 1},
+ {"time", required_argument, 0, TIME_OPTION},
{"no-create", no_argument, 0, 'c'},
{"date", required_argument, 0, 'd'},
- {"file", required_argument, 0, 'r'},
+ {"file", required_argument, 0, 'r'}, /* FIXME: phase out --file */
{"reference", required_argument, 0, 'r'},
- {"help", no_argument, &show_help, 1},
- {"version", no_argument, &show_version, 1},
+ {GETOPT_HELP_OPTION_DECL},
+ {GETOPT_VERSION_OPTION_DECL},
{0, 0, 0, 0}
};
Return 0 if successful, 1 if an error occurs. */
static int
-touch (char *file)
+touch (const char *file)
{
int status;
struct stat sbuf;
- int fd;
+ int fd = -1;
+ int open_errno = 0;
- if (stat (file, &sbuf))
+ if (! no_create)
{
- if (errno != ENOENT)
- {
- error (0, errno, "%s", file);
- return 1;
- }
- if (no_create)
- return 0;
- fd = creat (file, 0666);
- if (fd == -1)
- {
- error (0, errno, "%s", file);
- return 1;
- }
- if (amtime_now)
+ /* Try to open FILE, creating it if necessary. */
+ fd = open (file, O_WRONLY | O_CREAT | O_NONBLOCK | O_NOCTTY,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+
+ /* Don't save a copy of errno if it's EISDIR, since that would lead
+ touch to give a bogus diagnostic for e.g., `touch /' (assuming
+ we don't own / or have write access to it). On Solaris 5.6,
+ and probably other systems, it is EINVAL. On SunOS4, it's EPERM. */
+ if (fd == -1 && errno != EISDIR && errno != EINVAL && errno != EPERM)
+ open_errno = errno;
+ }
+
+ if (! amtime_now)
+ {
+ /* We're setting only one of the time values. stat the target to get
+ the other one. If we have the file descriptor already, use fstat.
+ Otherwise, either we're in no-create mode (and hence didn't call open)
+ or FILE is inaccessible or a directory, so we have to use stat. */
+ if (fd != -1 ? fstat (fd, &sbuf) : stat (file, &sbuf))
{
- if (close (fd) < 0)
+ if (open_errno)
+ error (0, open_errno, _("creating %s"), quote (file));
+ else
{
- error (0, errno, "%s", file);
- return 1;
+ if (no_create && errno == ENOENT)
+ return 0;
+ error (0, errno, _("failed to get attributes of %s"),
+ quote (file));
}
- return 0; /* We've done all we have to. */
- }
- if (fstat (fd, &sbuf))
- {
- error (0, errno, "%s", file);
- close (fd);
- return 1;
- }
- if (close (fd) < 0)
- {
- error (0, errno, "%s", file);
+ if (fd != -1)
+ close (fd);
return 1;
}
}
+ if (fd != -1 && close (fd) < 0)
+ {
+ error (0, errno, _("creating %s"), quote (file));
+ return 1;
+ }
+
if (amtime_now)
{
/* Pass NULL to utime so it will not fail if we just have
}
else
{
- struct utimbuf utb;
+ struct timespec timespec[2];
/* There's currently no interface to set file timestamps with
better than 1-second resolution, so discard any fractional
if (use_ref)
{
- utb.actime = ref_stats.st_atime;
- utb.modtime = ref_stats.st_mtime;
+ timespec[0].tv_sec = ref_stats.st_atime;
+ timespec[0].tv_nsec = TIMESPEC_NS (ref_stats.st_atim);
+ timespec[1].tv_sec = ref_stats.st_mtime;
+ timespec[1].tv_nsec = TIMESPEC_NS (ref_stats.st_mtim);
}
else
- utb.actime = utb.modtime = newtime;
+ timespec[0] = timespec[1] = newtime;
if (!(change_times & CH_ATIME))
- utb.actime = sbuf.st_atime;
+ {
+ timespec[0].tv_sec = sbuf.st_atime;
+ timespec[0].tv_nsec = TIMESPEC_NS (sbuf.st_atim);
+ }
if (!(change_times & CH_MTIME))
- utb.modtime = sbuf.st_mtime;
+ {
+ timespec[1].tv_sec = sbuf.st_mtime;
+ timespec[1].tv_nsec = TIMESPEC_NS (sbuf.st_mtim);
+ }
- status = utime (file, &utb);
+ status = utimens (file, timespec);
}
if (status)
{
- error (0, errno, "%s", file);
+ if (open_errno)
+ {
+ /* The wording of this diagnostic should cover at least two cases:
+ - the file does not exist, but the parent directory is unwritable
+ - the file exists, but it isn't writable
+ I think it's not worth trying to distinguish them. */
+ error (0, open_errno, _("cannot touch %s"), quote (file));
+ }
+ else
+ {
+ if (no_create && errno == ENOENT)
+ return 0;
+ error (0, errno, _("setting times of %s"), quote (file));
+ }
return 1;
}
return 0;
}
-static void
+void
usage (int status)
{
if (status != 0)
else
{
printf (_("Usage: %s [OPTION]... FILE...\n"), program_name);
- printf (_(" or : %s [-acm] MMDDhhmm[YY] FILE... (obsolescent)\n"),
- program_name);
- printf (_("\
+ fputs (_("\
Update the access and modification times of each FILE to the current time.\n\
\n\
+"), stdout);
+ fputs (_("\
+Mandatory arguments to long options are mandatory for short options too.\n\
+"), stdout);
+ fputs (_("\
-a change only the access time\n\
- -c do not create any files\n\
+ -c, --no-create do not create any files\n\
-d, --date=STRING parse STRING and use it instead of current time\n\
-f (ignored)\n\
-m change only the modification time\n\
+"), stdout);
+ fputs (_("\
-r, --reference=FILE use this file's times instead of current time\n\
-t STAMP use [[CC]YY]MMDDhhmm[.ss] instead of current time\n\
- --time=WORD access -a, atime -a, mtime -m, modify -m, use -a\n\
- --help display this help and exit\n\
- --version output version information and exit\n\
+ --time=WORD set time given by WORD: access atime use (same as -a)\n\
+ modify mtime (same as -m)\n\
+"), stdout);
+ fputs (HELP_OPTION_DESCRIPTION, stdout);
+ fputs (VERSION_OPTION_DESCRIPTION, stdout);
+ fputs (_("\
\n\
-STAMP may be used without -t if none of -drt, nor --, are used.\n\
-Note that the three time-date formats recognized for the -d and -t options\n\
-and for the obsolescent argument are all different.\n\
-"));
- puts (_("\nReport bugs to <bug-fileutils@gnu.org>."));
- close_stdout ();
+Note that the -d and -t options accept different time-date formats.\n\
+"), stdout);
+ printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
}
exit (status);
}
int
main (int argc, char **argv)
{
- int c, i;
+ int c;
int date_set = 0;
int err = 0;
+ initialize_main (&argc, &argv);
program_name = argv[0];
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
+ atexit (close_stdout);
+
change_times = no_create = use_ref = posix_date = flexible_date = 0;
- newtime = (time_t) -1;
while ((c = getopt_long (argc, argv, "acd:fmr:t:", longopts, NULL)) != -1)
{
case 'd':
flexible_date++;
- newtime = get_date (optarg, NULL);
- if (newtime == (time_t) -1)
- error (1, 0, _("invalid date format `%s'"), optarg);
+ newtime.tv_sec = get_date (optarg, NULL);
+ newtime.tv_nsec = 0; /* FIXME: get_date should set this. */
+ if (newtime.tv_sec == (time_t) -1)
+ error (EXIT_FAILURE, 0, _("invalid date format %s"), quote (optarg));
date_set++;
break;
case 't':
posix_date++;
- newtime = posixtime (optarg,
- PDS_LEADING_YEAR | PDS_CENTURY | PDS_SECONDS);
- if (newtime == (time_t) -1)
- error (1, 0, _("invalid date format `%s'"), optarg);
+ if (! posixtime (&newtime.tv_sec, optarg,
+ PDS_LEADING_YEAR | PDS_CENTURY | PDS_SECONDS))
+ error (EXIT_FAILURE, 0, _("invalid date format %s"), quote (optarg));
+ newtime.tv_nsec = 0;
date_set++;
break;
- case CHAR_MAX + 1: /* --time */
- {
- int mask;
- XARGMATCH (&mask, "--time", optarg, time_args, time_masks,
- usage (1));
- change_times |= mask;
- }
+ case TIME_OPTION: /* --time */
+ change_times |= XARGMATCH ("--time", optarg,
+ time_args, time_masks);
break;
+ case_GETOPT_HELP_CHAR;
+
+ case_GETOPT_VERSION_CHAR (PROGRAM_NAME, WRITTEN_BY);
+
default:
- usage (1);
+ usage (EXIT_FAILURE);
}
}
- if (show_version)
- {
- printf ("touch (%s) %s\n", GNU_PACKAGE, VERSION);
- close_stdout ();
- exit (0);
- }
-
- if (show_help)
- usage (0);
-
if (change_times == 0)
change_times = CH_ATIME | CH_MTIME;
|| (posix_date && flexible_date))
{
error (0, 0, _("cannot specify times from more than one source"));
- usage (1);
+ usage (EXIT_FAILURE);
}
if (use_ref)
{
if (stat (ref_file, &ref_stats))
- error (1, errno, "%s", ref_file);
+ error (EXIT_FAILURE, errno,
+ _("failed to get attributes of %s"), quote (ref_file));
date_set++;
}
- if (!date_set && optind < argc && !STREQ (argv[optind - 1], "--"))
+ /* The obsolete `MMDDhhmm[YY]' form is valid IFF there are
+ two or more non-option arguments. */
+ if (!date_set && 2 <= argc - optind && !STREQ (argv[optind - 1], "--")
+ && posix2_version () < 200112)
{
- newtime = posixtime (argv[optind], PDS_TRAILING_YEAR);
- if (newtime != (time_t) -1)
+ if (posixtime (&newtime.tv_sec, argv[optind], PDS_TRAILING_YEAR))
{
+ newtime.tv_nsec = 0;
+ if (! getenv ("POSIXLY_CORRECT"))
+ {
+ struct tm const *tm = localtime (&newtime.tv_sec);
+ error (0, 0,
+ _("warning: `touch %s' is obsolete; use\
+ `touch -t %04d%02d%02d%02d%02d.%02d'"),
+ argv[optind],
+ tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec);
+ }
+
optind++;
date_set++;
}
if ((change_times & (CH_ATIME | CH_MTIME)) == (CH_ATIME | CH_MTIME))
amtime_now = 1;
else
- time (&newtime);
+ {
+ /* Get time of day, but only to microsecond resolution,
+ since 'utimes' currently supports only microsecond
+ resolution at best. It would be cleaner here to invoke
+ gettime, but then we would have to link in more shared
+ libraries on platforms like Solaris, and we'd rather not
+ have 'touch' depend on libraries that it doesn't
+ need. */
+ struct timeval timeval;
+ if (gettimeofday (&timeval, NULL) != 0)
+ error (EXIT_FAILURE, errno, _("cannot get time of day"));
+ newtime.tv_sec = timeval.tv_sec;
+ newtime.tv_nsec = timeval.tv_usec * 1000;
+ }
}
if (optind == argc)
{
error (0, 0, _("file arguments missing"));
- usage (1);
+ usage (EXIT_FAILURE);
}
for (; optind < argc; ++optind)