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