/* 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 "error.h"
#include "getdate.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 AUTHORS \
- "Paul Rubin, Arnold Robbins, Jim Kingdon, David MacKenzie, and Randy Smith"
+#define WRITTEN_BY \
+_("Written by Paul Rubin, Arnold Robbins, Jim Kingdon, David MacKenzie, and Randy Smith.")
#ifndef STDC_HEADERS
time_t time ();
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;
+/* 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'}, /* FIXME: phase out --file */
if (! no_create)
{
/* Try to open FILE, creating it if necessary. */
- fd = open (file, O_WRONLY | O_CREAT,
+ fd = open (file, O_WRONLY | O_CREAT | O_NONBLOCK | O_NOCTTY,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
- if (fd == -1)
+
+ /* 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;
}
or FILE is inaccessible or a directory, so we have to use stat. */
if (fd != -1 ? fstat (fd, &sbuf) : stat (file, &sbuf))
{
- error (0, open_errno ? open_errno : errno, "%s", file);
- close (fd);
+ if (open_errno)
+ error (0, open_errno, _("creating %s"), quote (file));
+ else
+ {
+ if (no_create && errno == ENOENT)
+ return 0;
+ error (0, errno, _("failed to get attributes of %s"),
+ quote (file));
+ }
+ if (fd != -1)
+ close (fd);
return 1;
}
}
if (fd != -1 && close (fd) < 0)
{
- error (0, errno, "%s", file);
+ error (0, errno, _("creating %s"), quote (file));
return 1;
}
}
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, open_errno ? open_errno : 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;
}
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, --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 set time given by WORD: access atime use (same as -a)\n\
modify mtime (same as -m)\n\
- --help display this help and exit\n\
- --version output version information and exit\n\
+"), stdout);
+ fputs (HELP_OPTION_DESCRIPTION, stdout);
+ fputs (VERSION_OPTION_DESCRIPTION, stdout);
+ fputs (_("\
\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 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 */
+ case TIME_OPTION: /* --time */
change_times |= XARGMATCH ("--time", optarg,
time_args, time_masks);
break;
case_GETOPT_HELP_CHAR;
- case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
+ case_GETOPT_VERSION_CHAR (PROGRAM_NAME, WRITTEN_BY);
default:
- usage (1);
+ usage (EXIT_FAILURE);
}
}
|| (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)