(WRITTEN_BY): Rename from AUTHORS.
[platform/upstream/coreutils.git] / src / touch.c
index 2db00d3..1cccc84 100644 (file)
@@ -1,5 +1,5 @@
 /* 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 ();
@@ -70,7 +73,7 @@ static int posix_date;
 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;
@@ -78,9 +81,16 @@ 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 */
@@ -116,9 +126,14 @@ touch (const char *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;
     }
 
@@ -130,15 +145,24 @@ touch (const char *file)
         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;
     }
 
@@ -150,7 +174,7 @@ touch (const char *file)
     }
   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
@@ -158,24 +182,45 @@ touch (const char *file)
 
       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;
     }
 
@@ -191,28 +236,33 @@ usage (int status)
   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);
 }
@@ -224,13 +274,15 @@ main (int argc, char **argv)
   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)
     {
@@ -249,9 +301,10 @@ main (int argc, char **argv)
 
        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;
 
@@ -269,24 +322,24 @@ main (int argc, char **argv)
 
        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);
        }
     }
 
@@ -297,21 +350,36 @@ main (int argc, char **argv)
       || (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++;
        }
@@ -321,13 +389,26 @@ main (int argc, char **argv)
       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)