LCOV - code coverage report
Current view: top level - src - touch.c (source / functions) Hit Total Coverage
Test: coreutils.info Lines: 119 153 77.8 %
Date: 2018-01-30 Functions: 4 4 100.0 %

          Line data    Source code
       1             : /* touch -- change modification and access times of files
       2             :    Copyright (C) 87, 1989-1991, 1995-2005, 2007-2008
       3             :    Free Software Foundation, Inc.
       4             : 
       5             :    This program is free software: you can redistribute it and/or modify
       6             :    it under the terms of the GNU General Public License as published by
       7             :    the Free Software Foundation, either version 3 of the License, or
       8             :    (at your option) any later version.
       9             : 
      10             :    This program is distributed in the hope that it will be useful,
      11             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13             :    GNU General Public License for more details.
      14             : 
      15             :    You should have received a copy of the GNU General Public License
      16             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
      17             : 
      18             : /* Written by Paul Rubin, Arnold Robbins, Jim Kingdon, David MacKenzie,
      19             :    and Randy Smith. */
      20             : 
      21             : #include <config.h>
      22             : #include <stdio.h>
      23             : #include <getopt.h>
      24             : #include <sys/types.h>
      25             : 
      26             : #include "system.h"
      27             : #include "argmatch.h"
      28             : #include "error.h"
      29             : #include "fd-reopen.h"
      30             : #include "getdate.h"
      31             : #include "posixtm.h"
      32             : #include "posixver.h"
      33             : #include "quote.h"
      34             : #include "stat-time.h"
      35             : #include "utimens.h"
      36             : 
      37             : /* The official name of this program (e.g., no `g' prefix).  */
      38             : #define PROGRAM_NAME "touch"
      39             : 
      40             : #define AUTHORS \
      41             : "Paul Rubin", "Arnold Robbins, Jim Kingdon, David MacKenzie", "Randy Smith"
      42             : 
      43             : /* Bitmasks for `change_times'. */
      44             : #define CH_ATIME 1
      45             : #define CH_MTIME 2
      46             : 
      47             : /* The name by which this program was run. */
      48             : char *program_name;
      49             : 
      50             : /* Which timestamps to change. */
      51             : static int change_times;
      52             : 
      53             : /* (-c) If true, don't create if not already there.  */
      54             : static bool no_create;
      55             : 
      56             : /* (-r) If true, use times from a reference file.  */
      57             : static bool use_ref;
      58             : 
      59             : /* If true, the only thing we have to do is change both the
      60             :    modification and access time to the current time, so we don't
      61             :    have to own the file, just be able to read and write it.
      62             :    On some systems, we can do this if we own the file, even though
      63             :    we have neither read nor write access to it.  */
      64             : static bool amtime_now;
      65             : 
      66             : /* New access and modification times to use when setting time.  */
      67             : static struct timespec newtime[2];
      68             : 
      69             : /* File to use for -r. */
      70             : static char *ref_file;
      71             : 
      72             : /* For long options that have no equivalent short option, use a
      73             :    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
      74             : enum
      75             : {
      76             :   TIME_OPTION = CHAR_MAX + 1
      77             : };
      78             : 
      79             : static struct option const longopts[] =
      80             : {
      81             :   {"time", required_argument, NULL, TIME_OPTION},
      82             :   {"no-create", no_argument, NULL, 'c'},
      83             :   {"date", required_argument, NULL, 'd'},
      84             :   {"file", required_argument, NULL, 'r'}, /* FIXME: remove --file in 2006 */
      85             :   {"reference", required_argument, NULL, 'r'},
      86             :   {GETOPT_HELP_OPTION_DECL},
      87             :   {GETOPT_VERSION_OPTION_DECL},
      88             :   {NULL, 0, NULL, 0}
      89             : };
      90             : 
      91             : /* Valid arguments to the `--time' option. */
      92             : static char const* const time_args[] =
      93             : {
      94             :   "atime", "access", "use", "mtime", "modify", NULL
      95             : };
      96             : 
      97             : /* The bits in `change_times' that those arguments set. */
      98             : static int const time_masks[] =
      99             : {
     100             :   CH_ATIME, CH_ATIME, CH_ATIME, CH_MTIME, CH_MTIME
     101             : };
     102             : 
     103             : /* Store into *RESULT the result of interpreting FLEX_DATE as a date,
     104             :    relative to NOW.  If NOW is null, use the current time.  */
     105             : 
     106             : static void
     107          60 : get_reldate (struct timespec *result,
     108             :              char const *flex_date, struct timespec const *now)
     109             : {
     110          60 :   if (! get_date (result, flex_date, now))
     111          45 :     error (EXIT_FAILURE, 0, _("invalid date format %s"), quote (flex_date));
     112          15 : }
     113             : 
     114             : /* Update the time of file FILE according to the options given.
     115             :    Return true if successful.  */
     116             : 
     117             : static bool
     118          59 : touch (const char *file)
     119             : {
     120             :   bool ok;
     121             :   struct stat sbuf;
     122          59 :   int fd = -1;
     123          59 :   int open_errno = 0;
     124             :   struct timespec timespec[2];
     125             :   struct timespec const *t;
     126             : 
     127          59 :   if (STREQ (file, "-"))
     128          38 :     fd = STDOUT_FILENO;
     129          21 :   else if (! no_create)
     130             :     {
     131             :       /* Try to open FILE, creating it if necessary.  */
     132          19 :       fd = fd_reopen (STDIN_FILENO, file,
     133             :                       O_WRONLY | O_CREAT | O_NONBLOCK | O_NOCTTY,
     134             :                       S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
     135             : 
     136             :       /* Don't save a copy of errno if it's EISDIR, since that would lead
     137             :          touch to give a bogus diagnostic for e.g., `touch /' (assuming
     138             :          we don't own / or have write access to it).  On Solaris 5.6,
     139             :          and probably other systems, it is EINVAL.  On SunOS4, it's EPERM.  */
     140          19 :       if (fd == -1 && errno != EISDIR && errno != EINVAL && errno != EPERM)
     141          11 :         open_errno = errno;
     142             :     }
     143             : 
     144          59 :   if (change_times != (CH_ATIME | CH_MTIME))
     145             :     {
     146             :       /* We're setting only one of the time values.  stat the target to get
     147             :          the other one.  If we have the file descriptor already, use fstat.
     148             :          Otherwise, either we're in no-create mode (and hence didn't call open)
     149             :          or FILE is inaccessible or a directory, so we have to use stat.  */
     150          20 :       if (fd != -1 ? fstat (fd, &sbuf) : stat (file, &sbuf))
     151             :         {
     152           4 :           if (open_errno)
     153           2 :             error (0, open_errno, _("creating %s"), quote (file));
     154             :           else
     155             :             {
     156           2 :               if (no_create && (errno == ENOENT || errno == EBADF))
     157           1 :                 return true;
     158           1 :               error (0, errno, _("failed to get attributes of %s"),
     159             :                      quote (file));
     160             :             }
     161           3 :           if (fd == STDIN_FILENO)
     162           0 :             close (fd);
     163           3 :           return false;
     164             :         }
     165             :     }
     166             : 
     167          55 :   if (amtime_now)
     168             :     {
     169             :       /* Pass NULL to futimens so it will not fail if we have
     170             :          write access to the file, but don't own it.  */
     171          29 :       t = NULL;
     172             :     }
     173             :   else
     174             :     {
     175          26 :       timespec[0] = (change_times & CH_ATIME
     176             :                      ? newtime[0]
     177             :                      : get_stat_atime (&sbuf));
     178          26 :       timespec[1] = (change_times & CH_MTIME
     179             :                      ? newtime[1]
     180             :                      : get_stat_mtime (&sbuf));
     181          26 :       t = timespec;
     182             :     }
     183             : 
     184          55 :   ok = (gl_futimens (fd, (fd == STDOUT_FILENO ? NULL : file), t) == 0);
     185             : 
     186          55 :   if (fd == STDIN_FILENO)
     187             :     {
     188           3 :       if (close (STDIN_FILENO) != 0)
     189             :         {
     190           0 :           error (0, errno, _("closing %s"), quote (file));
     191           0 :           return false;
     192             :         }
     193             :     }
     194          52 :   else if (fd == STDOUT_FILENO)
     195             :     {
     196             :       /* Do not diagnose "touch -c - >&-".  */
     197          38 :       if (!ok && errno == EBADF && no_create
     198           0 :           && change_times == (CH_ATIME | CH_MTIME))
     199           0 :         return true;
     200             :     }
     201             : 
     202          55 :   if (!ok)
     203             :     {
     204          11 :       if (open_errno)
     205             :         {
     206             :           /* The wording of this diagnostic should cover at least two cases:
     207             :              - the file does not exist, but the parent directory is unwritable
     208             :              - the file exists, but it isn't writable
     209             :              I think it's not worth trying to distinguish them.  */
     210           6 :           error (0, open_errno, _("cannot touch %s"), quote (file));
     211             :         }
     212             :       else
     213             :         {
     214           5 :           if (no_create && errno == ENOENT)
     215           1 :             return true;
     216           4 :           error (0, errno, _("setting times of %s"), quote (file));
     217             :         }
     218          10 :       return false;
     219             :     }
     220             : 
     221          44 :   return true;
     222             : }
     223             : 
     224             : void
     225          25 : usage (int status)
     226             : {
     227          25 :   if (status != EXIT_SUCCESS)
     228          24 :     fprintf (stderr, _("Try `%s --help' for more information.\n"),
     229             :              program_name);
     230             :   else
     231             :     {
     232           1 :       printf (_("Usage: %s [OPTION]... FILE...\n"), program_name);
     233           1 :       fputs (_("\
     234             : Update the access and modification times of each FILE to the current time.\n\
     235             : \n\
     236             : A FILE argument that does not exist is created empty.\n\
     237             : \n\
     238             : A FILE argument string of - is handled specially and causes touch to\n\
     239             : change the times of the file associated with standard output.\n\
     240             : \n\
     241             : "), stdout);
     242           1 :       fputs (_("\
     243             : Mandatory arguments to long options are mandatory for short options too.\n\
     244             : "), stdout);
     245           1 :       fputs (_("\
     246             :   -a                     change only the access time\n\
     247             :   -c, --no-create        do not create any files\n\
     248             :   -d, --date=STRING      parse STRING and use it instead of current time\n\
     249             :   -f                     (ignored)\n\
     250             :   -m                     change only the modification time\n\
     251             : "), stdout);
     252           1 :       fputs (_("\
     253             :   -r, --reference=FILE   use this file's times instead of current time\n\
     254             :   -t STAMP               use [[CC]YY]MMDDhhmm[.ss] instead of current time\n\
     255             :   --time=WORD            change the specified time:\n\
     256             :                            WORD is access, atime, or use: equivalent to -a\n\
     257             :                            WORD is modify or mtime: equivalent to -m\n\
     258             : "), stdout);
     259           1 :       fputs (HELP_OPTION_DESCRIPTION, stdout);
     260           1 :       fputs (VERSION_OPTION_DESCRIPTION, stdout);
     261           1 :       fputs (_("\
     262             : \n\
     263             : Note that the -d and -t options accept different time-date formats.\n\
     264             : "), stdout);
     265           1 :       emit_bug_reporting_address ();
     266             :     }
     267          25 :   exit (status);
     268             : }
     269             : 
     270             : int
     271         110 : main (int argc, char **argv)
     272             : {
     273             :   int c;
     274         110 :   bool date_set = false;
     275         110 :   bool ok = true;
     276         110 :   char const *flex_date = NULL;
     277             : 
     278             :   initialize_main (&argc, &argv);
     279         110 :   program_name = argv[0];
     280         110 :   setlocale (LC_ALL, "");
     281             :   bindtextdomain (PACKAGE, LOCALEDIR);
     282             :   textdomain (PACKAGE);
     283             : 
     284         110 :   atexit (close_stdout);
     285             : 
     286         110 :   change_times = 0;
     287         110 :   no_create = use_ref = false;
     288             : 
     289         309 :   while ((c = getopt_long (argc, argv, "acd:fmr:t:", longopts, NULL)) != -1)
     290             :     {
     291         105 :       switch (c)
     292             :         {
     293          19 :         case 'a':
     294          19 :           change_times |= CH_ATIME;
     295          19 :           break;
     296             : 
     297           4 :         case 'c':
     298           4 :           no_create = true;
     299           4 :           break;
     300             : 
     301          60 :         case 'd':
     302          60 :           flex_date = optarg;
     303          60 :           break;
     304             : 
     305           0 :         case 'f':
     306           0 :           break;
     307             : 
     308           2 :         case 'm':
     309           2 :           change_times |= CH_MTIME;
     310           2 :           break;
     311             : 
     312           2 :         case 'r':
     313           2 :           use_ref = true;
     314           2 :           ref_file = optarg;
     315           2 :           break;
     316             : 
     317           2 :         case 't':
     318           2 :           if (! posixtime (&newtime[0].tv_sec, optarg,
     319             :                            PDS_LEADING_YEAR | PDS_CENTURY | PDS_SECONDS))
     320           2 :             error (EXIT_FAILURE, 0, _("invalid date format %s"),
     321             :                    quote (optarg));
     322           0 :           newtime[0].tv_nsec = 0;
     323           0 :           newtime[1] = newtime[0];
     324           0 :           date_set = true;
     325           0 :           break;
     326             : 
     327           6 :         case TIME_OPTION:       /* --time */
     328           6 :           change_times |= XARGMATCH ("--time", optarg,
     329             :                                      time_args, time_masks);
     330           2 :           break;
     331             : 
     332           1 :         case_GETOPT_HELP_CHAR;
     333             : 
     334           1 :         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
     335             : 
     336           8 :         default:
     337           8 :           usage (EXIT_FAILURE);
     338             :         }
     339             :     }
     340             : 
     341          94 :   if (change_times == 0)
     342          73 :     change_times = CH_ATIME | CH_MTIME;
     343             : 
     344          94 :   if (date_set && (use_ref || flex_date))
     345             :     {
     346           0 :       error (0, 0, _("cannot specify times from more than one source"));
     347           0 :       usage (EXIT_FAILURE);
     348             :     }
     349             : 
     350          94 :   if (use_ref)
     351             :     {
     352             :       struct stat ref_stats;
     353           2 :       if (stat (ref_file, &ref_stats))
     354           1 :         error (EXIT_FAILURE, errno,
     355             :                _("failed to get attributes of %s"), quote (ref_file));
     356           1 :       newtime[0] = get_stat_atime (&ref_stats);
     357           1 :       newtime[1] = get_stat_mtime (&ref_stats);
     358           1 :       date_set = true;
     359           1 :       if (flex_date)
     360             :         {
     361           0 :           if (change_times & CH_ATIME)
     362           0 :             get_reldate (&newtime[0], flex_date, &newtime[0]);
     363           0 :           if (change_times & CH_MTIME)
     364           0 :             get_reldate (&newtime[1], flex_date, &newtime[1]);
     365             :         }
     366             :     }
     367             :   else
     368             :     {
     369          92 :       if (flex_date)
     370             :         {
     371             :           struct timespec now;
     372          60 :           gettime (&now);
     373          60 :           get_reldate (&newtime[0], flex_date, &now);
     374          15 :           newtime[1] = newtime[0];
     375          15 :           date_set = true;
     376             : 
     377             :           /* If neither -a nor -m is specified, treat "-d now" as if
     378             :              it were absent; this lets "touch" succeed more often in
     379             :              the presence of restrictive permissions.  */
     380          15 :           if (change_times == (CH_ATIME | CH_MTIME)
     381          13 :               && newtime[0].tv_sec == now.tv_sec
     382           0 :               && newtime[0].tv_nsec == now.tv_nsec)
     383             :             {
     384             :               /* Check that it really was "-d now", and not a time
     385             :                  stamp that just happens to be the current time.  */
     386             :               struct timespec notnow, notnow1;
     387           0 :               notnow.tv_sec = now.tv_sec ^ 1;
     388           0 :               notnow.tv_nsec = now.tv_nsec;
     389           0 :               get_reldate (&notnow1, flex_date, &notnow);
     390           0 :               if (notnow1.tv_sec == notnow.tv_sec
     391           0 :                   && notnow1.tv_nsec == notnow.tv_nsec)
     392           0 :                 date_set = false;
     393             :             }
     394             :         }
     395             :     }
     396             : 
     397             :   /* The obsolete `MMDDhhmm[YY]' form is valid IFF there are
     398             :      two or more non-option arguments.  */
     399          48 :   if (!date_set && 2 <= argc - optind && posix2_version () < 200112
     400           0 :       && posixtime (&newtime[0].tv_sec, argv[optind],
     401             :                     PDS_TRAILING_YEAR | PDS_PRE_2000))
     402             :     {
     403           0 :       newtime[0].tv_nsec = 0;
     404           0 :       newtime[1] = newtime[0];
     405           0 :       date_set = true;
     406             : 
     407           0 :       if (! getenv ("POSIXLY_CORRECT"))
     408             :         {
     409           0 :           struct tm const *tm = localtime (&newtime[0].tv_sec);
     410           0 :           error (0, 0,
     411             :                  _("warning: `touch %s' is obsolete; use "
     412             :                    "`touch -t %04ld%02d%02d%02d%02d.%02d'"),
     413           0 :                  argv[optind],
     414           0 :                  tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
     415             :                  tm->tm_hour, tm->tm_min, tm->tm_sec);
     416             :         }
     417             : 
     418           0 :       optind++;
     419             :     }
     420             : 
     421          48 :   if (!date_set)
     422             :     {
     423          32 :       if (change_times == (CH_ATIME | CH_MTIME))
     424          16 :         amtime_now = true;
     425             :       else
     426             :         {
     427          16 :           gettime (&newtime[0]);
     428          16 :           newtime[1] = newtime[0];
     429             :         }
     430             :     }
     431             : 
     432          48 :   if (optind == argc)
     433             :     {
     434          12 :       error (0, 0, _("missing file operand"));
     435          12 :       usage (EXIT_FAILURE);
     436             :     }
     437             : 
     438          95 :   for (; optind < argc; ++optind)
     439          59 :     ok &= touch (argv[optind]);
     440             : 
     441          36 :   exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
     442             : }

Generated by: LCOV version 1.10