(WRITTEN_BY): Rename from AUTHORS.
[platform/upstream/coreutils.git] / src / touch.c
index 3b01ab3..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 ();
 #endif
 
-int full_write ();
-
 /* Bitmasks for `change_times'. */
 #define CH_ATIME 1
 #define CH_MTIME 2
@@ -66,11 +67,13 @@ static int posix_date;
 
 /* 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;
@@ -78,12 +81,19 @@ 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'},
+  {"file", required_argument, 0, 'r'}, /* FIXME: phase out --file */
   {"reference", required_argument, 0, 'r'},
   {GETOPT_HELP_OPTION_DECL},
   {GETOPT_VERSION_OPTION_DECL},
@@ -110,45 +120,52 @@ 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
@@ -157,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
@@ -165,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, 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;
     }
 
@@ -198,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                     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);
 }
@@ -231,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)
     {
@@ -256,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;
 
@@ -276,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);
        }
     }
 
@@ -304,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++;
        }
@@ -328,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)