(usage): Remove misleading sentence in --help output.
[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 Note that the three time-date formats recognized for the -d and -t options\n\
282 and for the obsolescent argument are all different.\n\
283 "));
284       puts (_("\nReport bugs to <bug-fileutils@gnu.org>."));
285       close_stdout ();
286     }
287   exit (status);
288 }
289
290 int
291 main (int argc, char **argv)
292 {
293   int c;
294   int date_set = 0;
295   int err = 0;
296
297   program_name = argv[0];
298   setlocale (LC_ALL, "");
299   bindtextdomain (PACKAGE, LOCALEDIR);
300   textdomain (PACKAGE);
301
302   change_times = no_create = use_ref = posix_date = flexible_date = 0;
303   newtime = (time_t) -1;
304
305   while ((c = getopt_long (argc, argv, "acd:fmr:t:", longopts, NULL)) != -1)
306     {
307       switch (c)
308         {
309         case 0:
310           break;
311
312         case 'a':
313           change_times |= CH_ATIME;
314           break;
315
316         case 'c':
317           no_create++;
318           break;
319
320         case 'd':
321           flexible_date++;
322           newtime = get_date (optarg, NULL);
323           if (newtime == (time_t) -1)
324             error (1, 0, _("invalid date format `%s'"), optarg);
325           date_set++;
326           break;
327
328         case 'f':
329           break;
330
331         case 'm':
332           change_times |= CH_MTIME;
333           break;
334
335         case 'r':
336           use_ref++;
337           ref_file = optarg;
338           break;
339
340         case 't':
341           posix_date++;
342           newtime = posixtime (optarg,
343                                PDS_LEADING_YEAR | PDS_CENTURY | PDS_SECONDS);
344           if (newtime == (time_t) -1)
345             error (1, 0, _("invalid date format `%s'"), optarg);
346           date_set++;
347           break;
348
349         case CHAR_MAX + 1:      /* --time */
350           change_times |= XARGMATCH ("--time", optarg,
351                                      time_args, time_masks);
352           break;
353
354         case_GETOPT_HELP_CHAR;
355
356         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
357
358         default:
359           usage (1);
360         }
361     }
362
363   if (change_times == 0)
364     change_times = CH_ATIME | CH_MTIME;
365
366   if ((use_ref && (posix_date || flexible_date))
367       || (posix_date && flexible_date))
368     {
369       error (0, 0, _("cannot specify times from more than one source"));
370       usage (1);
371     }
372
373   if (use_ref)
374     {
375       if (stat (ref_file, &ref_stats))
376         error (1, errno, "%s", ref_file);
377       date_set++;
378     }
379
380   if (!date_set && optind < argc && !STREQ (argv[optind - 1], "--"))
381     {
382       newtime = posixtime (argv[optind], PDS_TRAILING_YEAR);
383       if (newtime != (time_t) -1)
384         {
385           optind++;
386           date_set++;
387         }
388     }
389   if (!date_set)
390     {
391       if ((change_times & (CH_ATIME | CH_MTIME)) == (CH_ATIME | CH_MTIME))
392         amtime_now = 1;
393       else
394         time (&newtime);
395     }
396
397   if (optind == argc)
398     {
399       error (0, 0, _("file arguments missing"));
400       usage (1);
401     }
402
403   for (; optind < argc; ++optind)
404     err += touch (argv[optind]);
405
406   exit (err != 0);
407 }