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