1 /* touch -- change modification and access times of files
2    Copyright (C) 1987-2020 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 #include <assert.h>
25 
26 #include "system.h"
27 #include "argmatch.h"
28 #include "die.h"
29 #include "error.h"
30 #include "fd-reopen.h"
31 #include "parse-datetime.h"
32 #include "posixtm.h"
33 #include "posixver.h"
34 #include "quote.h"
35 #include "stat-time.h"
36 #include "utimens.h"
37 
38 /* The official name of this program (e.g., no 'g' prefix).  */
39 #define PROGRAM_NAME "touch"
40 
41 #define AUTHORS \
42   proper_name ("Paul Rubin"), \
43   proper_name ("Arnold Robbins"), \
44   proper_name ("Jim Kingdon"), \
45   proper_name ("David MacKenzie"), \
46   proper_name ("Randy Smith")
47 
48 /* Bitmasks for 'change_times'. */
49 #define CH_ATIME 1
50 #define CH_MTIME 2
51 
52 /* Which timestamps to change. */
53 static int change_times;
54 
55 /* (-c) If true, don't create if not already there.  */
56 static bool no_create;
57 
58 /* (-r) If true, use times from a reference file.  */
59 static bool use_ref;
60 
61 /* (-h) If true, change the times of an existing symlink, if possible.  */
62 static bool no_dereference;
63 
64 /* If true, the only thing we have to do is change both the
65    modification and access time to the current time, so we don't
66    have to own the file, just be able to read and write it.
67    On some systems, we can do this if we own the file, even though
68    we have neither read nor write access to it.  */
69 static bool amtime_now;
70 
71 /* New access and modification times to use when setting time.  */
72 static struct timespec newtime[2];
73 
74 /* File to use for -r. */
75 static char *ref_file;
76 
77 /* For long options that have no equivalent short option, use a
78    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
79 enum
80 {
81   TIME_OPTION = CHAR_MAX + 1
82 };
83 
84 static struct option const longopts[] =
85 {
86   {"time", required_argument, NULL, TIME_OPTION},
87   {"no-create", no_argument, NULL, 'c'},
88   {"date", required_argument, NULL, 'd'},
89   {"reference", required_argument, NULL, 'r'},
90   {"no-dereference", no_argument, NULL, 'h'},
91   {GETOPT_HELP_OPTION_DECL},
92   {GETOPT_VERSION_OPTION_DECL},
93   {NULL, 0, NULL, 0}
94 };
95 
96 /* Valid arguments to the '--time' option. */
97 static char const* const time_args[] =
98 {
99   "atime", "access", "use", "mtime", "modify", NULL
100 };
101 
102 /* The bits in 'change_times' that those arguments set. */
103 static int const time_masks[] =
104 {
105   CH_ATIME, CH_ATIME, CH_ATIME, CH_MTIME, CH_MTIME
106 };
107 
108 /* Store into *RESULT the result of interpreting FLEX_DATE as a date,
109    relative to NOW.  If NOW is null, use the current time.  */
110 
111 static void
get_reldate(struct timespec * result,char const * flex_date,struct timespec const * now)112 get_reldate (struct timespec *result,
113              char const *flex_date, struct timespec const *now)
114 {
115   if (! parse_datetime (result, flex_date, now))
116     die (EXIT_FAILURE, 0, _("invalid date format %s"), quote (flex_date));
117 }
118 
119 /* Update the time of file FILE according to the options given.
120    Return true if successful.  */
121 
122 static bool
touch(const char * file)123 touch (const char *file)
124 {
125   bool ok;
126   int fd = -1;
127   int open_errno = 0;
128   struct timespec const *t = newtime;
129 
130   if (STREQ (file, "-"))
131     fd = STDOUT_FILENO;
132   else if (! (no_create || no_dereference))
133     {
134       /* Try to open FILE, creating it if necessary.  */
135       fd = fd_reopen (STDIN_FILENO, file,
136                       O_WRONLY | O_CREAT | O_NONBLOCK | O_NOCTTY, MODE_RW_UGO);
137 
138       /* Don't save a copy of errno if it's EISDIR, since that would lead
139          touch to give a bogus diagnostic for e.g., 'touch /' (assuming
140          we don't own / or have write access to it).  On Solaris 5.6,
141          and probably other systems, it is EINVAL.  On SunOS4, it's EPERM.  */
142       if (fd == -1 && errno != EISDIR && errno != EINVAL && errno != EPERM)
143         open_errno = errno;
144     }
145 
146   if (change_times != (CH_ATIME | CH_MTIME))
147     {
148       /* We're setting only one of the time values.  */
149       if (change_times == CH_MTIME)
150         newtime[0].tv_nsec = UTIME_OMIT;
151       else
152         {
153           assert (change_times == CH_ATIME);
154           newtime[1].tv_nsec = UTIME_OMIT;
155         }
156     }
157 
158   if (amtime_now)
159     {
160       /* Pass NULL to futimens so it will not fail if we have
161          write access to the file, but don't own it.  */
162       t = NULL;
163     }
164 
165   ok = (fdutimensat (fd, AT_FDCWD, (fd == STDOUT_FILENO ? NULL : file), t,
166                      (no_dereference && fd == -1) ? AT_SYMLINK_NOFOLLOW : 0)
167         == 0);
168 
169   if (fd == STDIN_FILENO)
170     {
171       if (close (STDIN_FILENO) != 0)
172         {
173           error (0, errno, _("failed to close %s"), quoteaf (file));
174           return false;
175         }
176     }
177   else if (fd == STDOUT_FILENO)
178     {
179       /* Do not diagnose "touch -c - >&-".  */
180       if (!ok && errno == EBADF && no_create)
181         return true;
182     }
183 
184   if (!ok)
185     {
186       if (open_errno)
187         {
188           /* The wording of this diagnostic should cover at least two cases:
189              - the file does not exist, but the parent directory is unwritable
190              - the file exists, but it isn't writable
191              I think it's not worth trying to distinguish them.  */
192           error (0, open_errno, _("cannot touch %s"), quoteaf (file));
193         }
194       else
195         {
196           if (no_create && errno == ENOENT)
197             return true;
198           error (0, errno, _("setting times of %s"), quoteaf (file));
199         }
200       return false;
201     }
202 
203   return true;
204 }
205 
206 void
usage(int status)207 usage (int status)
208 {
209   if (status != EXIT_SUCCESS)
210     emit_try_help ();
211   else
212     {
213       printf (_("Usage: %s [OPTION]... FILE...\n"), program_name);
214       fputs (_("\
215 Update the access and modification times of each FILE to the current time.\n\
216 \n\
217 A FILE argument that does not exist is created empty, unless -c or -h\n\
218 is supplied.\n\
219 \n\
220 A FILE argument string of - is handled specially and causes touch to\n\
221 change the times of the file associated with standard output.\n\
222 "), stdout);
223 
224       emit_mandatory_arg_note ();
225 
226       fputs (_("\
227   -a                     change only the access time\n\
228   -c, --no-create        do not create any files\n\
229   -d, --date=STRING      parse STRING and use it instead of current time\n\
230   -f                     (ignored)\n\
231 "), stdout);
232       fputs (_("\
233   -h, --no-dereference   affect each symbolic link instead of any referenced\n\
234                          file (useful only on systems that can change the\n\
235                          timestamps of a symlink)\n\
236   -m                     change only the modification time\n\
237 "), stdout);
238       fputs (_("\
239   -r, --reference=FILE   use this file's times instead of current time\n\
240   -t STAMP               use [[CC]YY]MMDDhhmm[.ss] instead of current time\n\
241       --time=WORD        change the specified time:\n\
242                            WORD is access, atime, or use: equivalent to -a\n\
243                            WORD is modify or mtime: equivalent to -m\n\
244 "), stdout);
245       fputs (HELP_OPTION_DESCRIPTION, stdout);
246       fputs (VERSION_OPTION_DESCRIPTION, stdout);
247       fputs (_("\
248 \n\
249 Note that the -d and -t options accept different time-date formats.\n\
250 "), stdout);
251       emit_ancillary_info (PROGRAM_NAME);
252     }
253   exit (status);
254 }
255 
256 int
main(int argc,char ** argv)257 main (int argc, char **argv)
258 {
259   int c;
260   bool date_set = false;
261   bool ok = true;
262   char const *flex_date = NULL;
263 
264   initialize_main (&argc, &argv);
265   set_program_name (argv[0]);
266   setlocale (LC_ALL, "");
267   bindtextdomain (PACKAGE, LOCALEDIR);
268   textdomain (PACKAGE);
269 
270   atexit (close_stdout);
271 
272   change_times = 0;
273   no_create = use_ref = false;
274 
275   while ((c = getopt_long (argc, argv, "acd:fhmr:t:", longopts, NULL)) != -1)
276     {
277       switch (c)
278         {
279         case 'a':
280           change_times |= CH_ATIME;
281           break;
282 
283         case 'c':
284           no_create = true;
285           break;
286 
287         case 'd':
288           flex_date = optarg;
289           break;
290 
291         case 'f':
292           break;
293 
294         case 'h':
295           no_dereference = true;
296           break;
297 
298         case 'm':
299           change_times |= CH_MTIME;
300           break;
301 
302         case 'r':
303           use_ref = true;
304           ref_file = optarg;
305           break;
306 
307         case 't':
308           if (! posixtime (&newtime[0].tv_sec, optarg,
309                            PDS_LEADING_YEAR | PDS_CENTURY | PDS_SECONDS))
310             die (EXIT_FAILURE, 0, _("invalid date format %s"),
311                  quote (optarg));
312           newtime[0].tv_nsec = 0;
313           newtime[1] = newtime[0];
314           date_set = true;
315           break;
316 
317         case TIME_OPTION:	/* --time */
318           change_times |= XARGMATCH ("--time", optarg,
319                                      time_args, time_masks);
320           break;
321 
322         case_GETOPT_HELP_CHAR;
323 
324         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
325 
326         default:
327           usage (EXIT_FAILURE);
328         }
329     }
330 
331   if (change_times == 0)
332     change_times = CH_ATIME | CH_MTIME;
333 
334   if (date_set && (use_ref || flex_date))
335     {
336       error (0, 0, _("cannot specify times from more than one source"));
337       usage (EXIT_FAILURE);
338     }
339 
340   if (use_ref)
341     {
342       struct stat ref_stats;
343       /* Don't use (no_dereference?lstat:stat) (args), since stat
344          might be an object-like macro.  */
345       if (no_dereference ? lstat (ref_file, &ref_stats)
346           : stat (ref_file, &ref_stats))
347         die (EXIT_FAILURE, errno,
348              _("failed to get attributes of %s"), quoteaf (ref_file));
349       newtime[0] = get_stat_atime (&ref_stats);
350       newtime[1] = get_stat_mtime (&ref_stats);
351       date_set = true;
352       if (flex_date)
353         {
354           if (change_times & CH_ATIME)
355             get_reldate (&newtime[0], flex_date, &newtime[0]);
356           if (change_times & CH_MTIME)
357             get_reldate (&newtime[1], flex_date, &newtime[1]);
358         }
359     }
360   else
361     {
362       if (flex_date)
363         {
364           struct timespec now;
365           gettime (&now);
366           get_reldate (&newtime[0], flex_date, &now);
367           newtime[1] = newtime[0];
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               get_reldate (&notnow1, 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 NULL 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