(touch): Only do the fstat if we need to.
[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.  */
125       if (fd != -1 || errno != EEXIST)
126         break;
127
128       /* The first open failed because FILE already exists.
129          Now try to open it for writing.  */
130       fd = open (file, O_WRONLY);
131
132       /* If the open succeeded or if it failed for any reason other
133          than the absence of FILE, then we're done.  */
134       /* The 2nd open can fail if FILE was unlinked between the two
135          open calls.  When that happens, just iterate.  */
136       if (fd != -1 || errno != ENOENT)
137         break;
138     }
139
140   return fd;
141 }
142
143 /* Update the time of file FILE according to the options given.
144    Return 0 if successful, 1 if an error occurs. */
145
146 static int
147 touch (const char *file)
148 {
149   int status;
150   struct stat sbuf;
151   int fd;
152   int file_created;
153
154   if (no_create)
155     {
156       /* Try to open an existing FILE.  */
157       fd = open (file, O_WRONLY);
158       if (fd == -1 && errno == ENOENT)
159         {
160           /* FILE doesn't exist.  So we're done.  */
161           return 0;
162         }
163       file_created = 0;
164     }
165   else
166     {
167       /* Try to open FILE, creating it if necessary.  */
168       fd = open_maybe_create (file, &file_created);
169     }
170
171   /* Don't fail if the open failed because FILE is a directory.  */
172   if (fd == -1
173       /* As usual, using E* macros like this is risky...
174          So heads up.  */
175       && errno != EISDIR)
176     {
177       error (0, errno, "%s", file);
178       return 1;
179     }
180
181   if (amtime_now)
182     {
183       if (file_created)
184         {
185           if (close (fd) < 0)
186             {
187               error (0, errno, "%s", file);
188               return 1;
189             }
190
191           /* We've just created the file with the current times.  */
192           return 0;
193         }
194     }
195   else
196     {
197       /* We're setting only one of the time values.  stat the target to get
198          the other one.  If we have the file descriptor already, use fstat,
199          otherwise, FILE is a directory, so we have to use stat.  */
200       if (fd == -1 ? stat (file, &sbuf) : fstat (fd, &sbuf))
201         {
202           error (0, errno, "%s", file);
203           close (fd);
204           return 1;
205         }
206     }
207
208   if (fd != -1 && close (fd) < 0)
209     {
210       error (0, errno, "%s", file);
211       return 1;
212     }
213
214   if (amtime_now)
215     {
216       /* Pass NULL to utime so it will not fail if we just have
217          write access to the file, but don't own it.  */
218       status = utime (file, NULL);
219     }
220   else
221     {
222       struct utimbuf utb;
223
224       /* There's currently no interface to set file timestamps with
225          better than 1-second resolution, so discard any fractional
226          part of the source timestamp.  */
227
228       if (use_ref)
229         {
230           utb.actime = ref_stats.st_atime;
231           utb.modtime = ref_stats.st_mtime;
232         }
233       else
234         utb.actime = utb.modtime = newtime;
235
236       if (!(change_times & CH_ATIME))
237         utb.actime = sbuf.st_atime;
238
239       if (!(change_times & CH_MTIME))
240         utb.modtime = sbuf.st_mtime;
241
242       status = utime (file, &utb);
243     }
244
245   if (status)
246     {
247       error (0, errno, "%s", file);
248       return 1;
249     }
250
251   return 0;
252 }
253
254 void
255 usage (int status)
256 {
257   if (status != 0)
258     fprintf (stderr, _("Try `%s --help' for more information.\n"),
259              program_name);
260   else
261     {
262       printf (_("Usage: %s [OPTION]... FILE...\n"), program_name);
263       printf (_("  or : %s [-acm] MMDDhhmm[YY] FILE... (obsolescent)\n"),
264               program_name);
265       printf (_("\
266 Update the access and modification times of each FILE to the current time.\n\
267 \n\
268   -a                     change only the access time\n\
269   -c, --no-create        do not create any files\n\
270   -d, --date=STRING      parse STRING and use it instead of current time\n\
271   -f                     (ignored)\n\
272   -m                     change only the modification time\n\
273   -r, --reference=FILE   use this file's times instead of current time\n\
274   -t STAMP               use [[CC]YY]MMDDhhmm[.ss] instead of current time\n\
275       --time=WORD        access -a, atime -a, mtime -m, modify -m, use -a\n\
276       --help             display this help and exit\n\
277       --version          output version information and exit\n\
278 \n\
279 STAMP may be used without -t if none of -drt, nor --, are used.\n\
280 Note that the three time-date formats recognized for the -d and -t options\n\
281 and for the obsolescent argument are all different.\n\
282 "));
283       puts (_("\nReport bugs to <bug-fileutils@gnu.org>."));
284       close_stdout ();
285     }
286   exit (status);
287 }
288
289 int
290 main (int argc, char **argv)
291 {
292   int c;
293   int date_set = 0;
294   int err = 0;
295
296   program_name = argv[0];
297   setlocale (LC_ALL, "");
298   bindtextdomain (PACKAGE, LOCALEDIR);
299   textdomain (PACKAGE);
300
301   change_times = no_create = use_ref = posix_date = flexible_date = 0;
302   newtime = (time_t) -1;
303
304   while ((c = getopt_long (argc, argv, "acd:fmr:t:", longopts, NULL)) != -1)
305     {
306       switch (c)
307         {
308         case 0:
309           break;
310
311         case 'a':
312           change_times |= CH_ATIME;
313           break;
314
315         case 'c':
316           no_create++;
317           break;
318
319         case 'd':
320           flexible_date++;
321           newtime = get_date (optarg, NULL);
322           if (newtime == (time_t) -1)
323             error (1, 0, _("invalid date format `%s'"), optarg);
324           date_set++;
325           break;
326
327         case 'f':
328           break;
329
330         case 'm':
331           change_times |= CH_MTIME;
332           break;
333
334         case 'r':
335           use_ref++;
336           ref_file = optarg;
337           break;
338
339         case 't':
340           posix_date++;
341           newtime = posixtime (optarg,
342                                PDS_LEADING_YEAR | PDS_CENTURY | PDS_SECONDS);
343           if (newtime == (time_t) -1)
344             error (1, 0, _("invalid date format `%s'"), optarg);
345           date_set++;
346           break;
347
348         case CHAR_MAX + 1:      /* --time */
349           change_times |= XARGMATCH ("--time", optarg,
350                                      time_args, time_masks);
351           break;
352
353         case_GETOPT_HELP_CHAR;
354
355         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
356
357         default:
358           usage (1);
359         }
360     }
361
362   if (change_times == 0)
363     change_times = CH_ATIME | CH_MTIME;
364
365   if ((use_ref && (posix_date || flexible_date))
366       || (posix_date && flexible_date))
367     {
368       error (0, 0, _("cannot specify times from more than one source"));
369       usage (1);
370     }
371
372   if (use_ref)
373     {
374       if (stat (ref_file, &ref_stats))
375         error (1, errno, "%s", ref_file);
376       date_set++;
377     }
378
379   if (!date_set && optind < argc && !STREQ (argv[optind - 1], "--"))
380     {
381       newtime = posixtime (argv[optind], PDS_TRAILING_YEAR);
382       if (newtime != (time_t) -1)
383         {
384           optind++;
385           date_set++;
386         }
387     }
388   if (!date_set)
389     {
390       if ((change_times & (CH_ATIME | CH_MTIME)) == (CH_ATIME | CH_MTIME))
391         amtime_now = 1;
392       else
393         time (&newtime);
394     }
395
396   if (optind == argc)
397     {
398       error (0, 0, _("file arguments missing"));
399       usage (1);
400     }
401
402   for (; optind < argc; ++optind)
403     err += touch (argv[optind]);
404
405   exit (err != 0);
406 }