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