(usage): Clarify description of --time=WORD.
[platform/upstream/coreutils.git] / src / touch.c
1 /* touch -- change modification and access times of files
2    Copyright (C) 87, 1989-1991, 1995-1999 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 2, or (at your option)
7    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, write to the Free Software Foundation,
16    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
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 "getdate.h"
30 #include "posixtm.h"
31 #include "safe-read.h"
32
33 /* The official name of this program (e.g., no `g' prefix).  */
34 #define PROGRAM_NAME "touch"
35
36 #define AUTHORS \
37   "Paul Rubin, Arnold Robbins, Jim Kingdon, David MacKenzie, and Randy Smith"
38
39 #ifndef STDC_HEADERS
40 time_t time ();
41 #endif
42
43 int full_write ();
44
45 /* Bitmasks for `change_times'. */
46 #define CH_ATIME 1
47 #define CH_MTIME 2
48
49 /* The name by which this program was run. */
50 char *program_name;
51
52 /* Which timestamps to change. */
53 static int change_times;
54
55 /* (-c) If nonzero, don't create if not already there. */
56 static int no_create;
57
58 /* (-d) If nonzero, date supplied on command line in get_date formats. */
59 static int flexible_date;
60
61 /* (-r) If nonzero, use times from a reference file. */
62 static int use_ref;
63
64 /* (-t) If nonzero, date supplied on command line in POSIX format. */
65 static int posix_date;
66
67 /* If nonzero, the only thing we have to do is change both the
68    modification and access time to the current time, so we don't
69    have to own the file, just be able to read and write it.  */
70 static int amtime_now;
71
72 /* New time to use when setting time. */
73 static time_t newtime;
74
75 /* File to use for -r. */
76 static char *ref_file;
77
78 /* Info about the reference file. */
79 static struct stat ref_stats;
80
81 static struct option const longopts[] =
82 {
83   {"time", required_argument, 0, CHAR_MAX + 1},
84   {"no-create", no_argument, 0, 'c'},
85   {"date", required_argument, 0, 'd'},
86   {"file", required_argument, 0, 'r'}, /* FIXME: phase out --file */
87   {"reference", required_argument, 0, 'r'},
88   {GETOPT_HELP_OPTION_DECL},
89   {GETOPT_VERSION_OPTION_DECL},
90   {0, 0, 0, 0}
91 };
92
93 /* Valid arguments to the `--time' option. */
94 static char const* const time_args[] =
95 {
96   "atime", "access", "use", "mtime", "modify", 0
97 };
98
99 /* The bits in `change_times' that those arguments set. */
100 static int const time_masks[] =
101 {
102   CH_ATIME, CH_ATIME, CH_ATIME, CH_MTIME, CH_MTIME
103 };
104
105 /* Open FILE, possibly creating it.  Set *FILE_CREATED to nonzero if the
106    open creates it, or to zero if the open call opened an existing file.
107    Return the result of the open call.  Be careful to avoid race conditions.  */
108
109 static int
110 open_maybe_create (const char *file, int *file_created)
111 {
112   int fd;
113
114   *file_created = 0;
115   while (1)
116     {
117       /* First, see if we can create a new FILE.  */
118       fd = open (file, O_WRONLY | O_CREAT | O_EXCL,
119                  S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
120       if (fd != -1)
121         *file_created = 1;
122
123       /* If the open succeeded or if it failed for any reason other
124          than the existence of FILE, then we're done.  Some systems
125          (solaris) set errno to EINVAL when FILE is a directory.  */
126       if (fd != -1 || (errno != EEXIST && errno != EINVAL))
127         break;
128
129       /* The first open failed because FILE already exists.
130          Now try to open it for writing.  */
131       fd = open (file, O_WRONLY);
132
133       /* If the open succeeded or if it failed for any reason other
134          than the absence of FILE, then we're done.  */
135       /* The 2nd open can fail if FILE was unlinked between the two
136          open calls.  When that happens, just iterate.  */
137       if (fd != -1 || errno != ENOENT)
138         break;
139     }
140
141   return fd;
142 }
143
144 /* Update the time of file FILE according to the options given.
145    Return 0 if successful, 1 if an error occurs. */
146
147 static int
148 touch (const char *file)
149 {
150   int status;
151   struct stat sbuf;
152   int fd;
153   int file_created;
154
155   if (no_create)
156     {
157       /* Try to open an existing FILE.  */
158       fd = open (file, O_WRONLY);
159       if (fd == -1 && errno == ENOENT)
160         {
161           /* FILE doesn't exist.  So we're done.  */
162           return 0;
163         }
164       file_created = 0;
165     }
166   else
167     {
168       /* Try to open FILE, creating it if necessary.  */
169       fd = open_maybe_create (file, &file_created);
170     }
171
172   /* Don't fail if the open failed because FILE is a directory.  */
173   if (fd == -1
174       /* As usual, using E* macros like this is risky...
175          So heads up.  */
176       && errno != EISDIR)
177     {
178       error (0, errno, "%s", file);
179       return 1;
180     }
181
182   if (amtime_now)
183     {
184       if (file_created)
185         {
186           if (close (fd) < 0)
187             {
188               error (0, errno, "%s", file);
189               return 1;
190             }
191
192           /* We've just created the file with the current times.  */
193           return 0;
194         }
195     }
196   else
197     {
198       /* We're setting only one of the time values.  stat the target to get
199          the other one.  If we have the file descriptor already, use fstat,
200          otherwise, FILE is a directory, so we have to use stat.  */
201       if (fd == -1 ? stat (file, &sbuf) : fstat (fd, &sbuf))
202         {
203           error (0, errno, "%s", file);
204           close (fd);
205           return 1;
206         }
207     }
208
209   if (fd != -1 && close (fd) < 0)
210     {
211       error (0, errno, "%s", file);
212       return 1;
213     }
214
215   if (amtime_now)
216     {
217       /* Pass NULL to utime so it will not fail if we just have
218          write access to the file, but don't own it.  */
219       status = utime (file, NULL);
220     }
221   else
222     {
223       struct utimbuf utb;
224
225       /* There's currently no interface to set file timestamps with
226          better than 1-second resolution, so discard any fractional
227          part of the source timestamp.  */
228
229       if (use_ref)
230         {
231           utb.actime = ref_stats.st_atime;
232           utb.modtime = ref_stats.st_mtime;
233         }
234       else
235         utb.actime = utb.modtime = newtime;
236
237       if (!(change_times & CH_ATIME))
238         utb.actime = sbuf.st_atime;
239
240       if (!(change_times & CH_MTIME))
241         utb.modtime = sbuf.st_mtime;
242
243       status = utime (file, &utb);
244     }
245
246   if (status)
247     {
248       error (0, errno, "%s", file);
249       return 1;
250     }
251
252   return 0;
253 }
254
255 void
256 usage (int status)
257 {
258   if (status != 0)
259     fprintf (stderr, _("Try `%s --help' for more information.\n"),
260              program_name);
261   else
262     {
263       printf (_("Usage: %s [OPTION]... FILE...\n"), program_name);
264       printf (_("  or : %s [-acm] MMDDhhmm[YY] FILE... (obsolescent)\n"),
265               program_name);
266       printf (_("\
267 Update the access and modification times of each FILE to the current time.\n\
268 \n\
269   -a                     change only the access time\n\
270   -c, --no-create        do not create any files\n\
271   -d, --date=STRING      parse STRING and use it instead of current time\n\
272   -f                     (ignored)\n\
273   -m                     change only the modification time\n\
274   -r, --reference=FILE   use this file's times instead of current time\n\
275   -t STAMP               use [[CC]YY]MMDDhhmm[.ss] instead of current time\n\
276   --time=WORD            set time given by WORD: access atime use (same as -a)\n\
277                            modify mtime (same as -m)\n\
278       --help             display this help and exit\n\
279       --version          output version information and exit\n\
280 \n\
281 STAMP may be used without -t if none of -drt, nor --, are used.\n\
282 Note that the three time-date formats recognized for the -d and -t options\n\
283 and for the obsolescent argument are all different.\n\
284 "));
285       puts (_("\nReport bugs to <bug-fileutils@gnu.org>."));
286       close_stdout ();
287     }
288   exit (status);
289 }
290
291 int
292 main (int argc, char **argv)
293 {
294   int c;
295   int date_set = 0;
296   int err = 0;
297
298   program_name = argv[0];
299   setlocale (LC_ALL, "");
300   bindtextdomain (PACKAGE, LOCALEDIR);
301   textdomain (PACKAGE);
302
303   change_times = no_create = use_ref = posix_date = flexible_date = 0;
304   newtime = (time_t) -1;
305
306   while ((c = getopt_long (argc, argv, "acd:fmr:t:", longopts, NULL)) != -1)
307     {
308       switch (c)
309         {
310         case 0:
311           break;
312
313         case 'a':
314           change_times |= CH_ATIME;
315           break;
316
317         case 'c':
318           no_create++;
319           break;
320
321         case 'd':
322           flexible_date++;
323           newtime = get_date (optarg, NULL);
324           if (newtime == (time_t) -1)
325             error (1, 0, _("invalid date format `%s'"), optarg);
326           date_set++;
327           break;
328
329         case 'f':
330           break;
331
332         case 'm':
333           change_times |= CH_MTIME;
334           break;
335
336         case 'r':
337           use_ref++;
338           ref_file = optarg;
339           break;
340
341         case 't':
342           posix_date++;
343           newtime = posixtime (optarg,
344                                PDS_LEADING_YEAR | PDS_CENTURY | PDS_SECONDS);
345           if (newtime == (time_t) -1)
346             error (1, 0, _("invalid date format `%s'"), optarg);
347           date_set++;
348           break;
349
350         case CHAR_MAX + 1:      /* --time */
351           change_times |= XARGMATCH ("--time", optarg,
352                                      time_args, time_masks);
353           break;
354
355         case_GETOPT_HELP_CHAR;
356
357         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
358
359         default:
360           usage (1);
361         }
362     }
363
364   if (change_times == 0)
365     change_times = CH_ATIME | CH_MTIME;
366
367   if ((use_ref && (posix_date || flexible_date))
368       || (posix_date && flexible_date))
369     {
370       error (0, 0, _("cannot specify times from more than one source"));
371       usage (1);
372     }
373
374   if (use_ref)
375     {
376       if (stat (ref_file, &ref_stats))
377         error (1, errno, "%s", ref_file);
378       date_set++;
379     }
380
381   if (!date_set && optind < argc && !STREQ (argv[optind - 1], "--"))
382     {
383       newtime = posixtime (argv[optind], PDS_TRAILING_YEAR);
384       if (newtime != (time_t) -1)
385         {
386           optind++;
387           date_set++;
388         }
389     }
390   if (!date_set)
391     {
392       if ((change_times & (CH_ATIME | CH_MTIME)) == (CH_ATIME | CH_MTIME))
393         amtime_now = 1;
394       else
395         time (&newtime);
396     }
397
398   if (optind == argc)
399     {
400       error (0, 0, _("file arguments missing"));
401       usage (1);
402     }
403
404   for (; optind < argc; ++optind)
405     err += touch (argv[optind]);
406
407   exit (err != 0);
408 }