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
date_relative(char const * flex_date,struct timespec now)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
touch(char const * file)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
usage(int status)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
main(int argc,char ** argv)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 }
439