Change `exit (0)' to `exit (EXIT_SUCCESS)',
[platform/upstream/coreutils.git] / src / touch.c
1 /* touch -- change modification and access times of files
2    Copyright (C) 87, 1989-1991, 1995-2002 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 "posixver.h"
32 #include "quote.h"
33 #include "safe-read.h"
34
35 /* The official name of this program (e.g., no `g' prefix).  */
36 #define PROGRAM_NAME "touch"
37
38 #define AUTHORS \
39   "Paul Rubin, Arnold Robbins, Jim Kingdon, David MacKenzie, and Randy Smith"
40
41 #ifndef STDC_HEADERS
42 time_t time ();
43 #endif
44
45 /* Bitmasks for `change_times'. */
46 #define CH_ATIME 1
47 #define CH_MTIME 2
48
49 #if !defined O_NDELAY
50 # define O_NDELAY 0
51 #endif
52
53 #if !defined O_NONBLOCK
54 # define O_NONBLOCK O_NDELAY
55 #endif
56
57 #if !defined O_NOCTTY
58 # define O_NOCTTY 0
59 #endif
60
61 #if !defined EISDIR
62 # define EISDIR 0
63 #endif
64
65 /* The name by which this program was run. */
66 char *program_name;
67
68 /* Which timestamps to change. */
69 static int change_times;
70
71 /* (-c) If nonzero, don't create if not already there. */
72 static int no_create;
73
74 /* (-d) If nonzero, date supplied on command line in get_date formats. */
75 static int flexible_date;
76
77 /* (-r) If nonzero, use times from a reference file. */
78 static int use_ref;
79
80 /* (-t) If nonzero, date supplied on command line in POSIX format. */
81 static int posix_date;
82
83 /* If nonzero, the only thing we have to do is change both the
84    modification and access time to the current time, so we don't
85    have to own the file, just be able to read and write it.
86    On some systems, we can do this if we own the file, even though
87    we have neither read nor write access to it.  */
88 static int amtime_now;
89
90 /* New time to use when setting time. */
91 static time_t newtime;
92
93 /* File to use for -r. */
94 static char *ref_file;
95
96 /* Info about the reference file. */
97 static struct stat ref_stats;
98
99 /* For long options that have no equivalent short option, use a
100    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
101 enum
102 {
103   TIME_OPTION = CHAR_MAX + 1
104 };
105
106 static struct option const longopts[] =
107 {
108   {"time", required_argument, 0, TIME_OPTION},
109   {"no-create", no_argument, 0, 'c'},
110   {"date", required_argument, 0, 'd'},
111   {"file", required_argument, 0, 'r'}, /* FIXME: phase out --file */
112   {"reference", required_argument, 0, 'r'},
113   {GETOPT_HELP_OPTION_DECL},
114   {GETOPT_VERSION_OPTION_DECL},
115   {0, 0, 0, 0}
116 };
117
118 /* Valid arguments to the `--time' option. */
119 static char const* const time_args[] =
120 {
121   "atime", "access", "use", "mtime", "modify", 0
122 };
123
124 /* The bits in `change_times' that those arguments set. */
125 static int const time_masks[] =
126 {
127   CH_ATIME, CH_ATIME, CH_ATIME, CH_MTIME, CH_MTIME
128 };
129
130 /* Update the time of file FILE according to the options given.
131    Return 0 if successful, 1 if an error occurs. */
132
133 static int
134 touch (const char *file)
135 {
136   int status;
137   struct stat sbuf;
138   int fd = -1;
139   int open_errno = 0;
140
141   if (! no_create)
142     {
143       /* Try to open FILE, creating it if necessary.  */
144       fd = open (file, O_WRONLY | O_CREAT | O_NONBLOCK | O_NOCTTY,
145                  S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
146
147       /* Don't save a copy of errno if it's EISDIR, since that would lead
148          touch to give a bogus diagnostic for e.g., `touch /' (assuming
149          we don't own / or have write access to it).  On Solaris 5.6,
150          and probably other systems, it is EINVAL.  On SunOS4, it's EPERM.  */
151       if (fd == -1 && errno != EISDIR && errno != EINVAL && errno != EPERM)
152         open_errno = errno;
153     }
154
155   if (! amtime_now)
156     {
157       /* We're setting only one of the time values.  stat the target to get
158          the other one.  If we have the file descriptor already, use fstat.
159          Otherwise, either we're in no-create mode (and hence didn't call open)
160          or FILE is inaccessible or a directory, so we have to use stat.  */
161       if (fd != -1 ? fstat (fd, &sbuf) : stat (file, &sbuf))
162         {
163           if (open_errno)
164             error (0, open_errno, _("creating %s"), quote (file));
165           else
166             {
167               if (no_create && errno == ENOENT)
168                 return 0;
169               error (0, errno, _("failed to get attributes of %s"),
170                      quote (file));
171             }
172           close (fd);
173           return 1;
174         }
175     }
176
177   if (fd != -1 && close (fd) < 0)
178     {
179       error (0, errno, _("creating %s"), quote (file));
180       return 1;
181     }
182
183   if (amtime_now)
184     {
185       /* Pass NULL to utime so it will not fail if we just have
186          write access to the file, but don't own it.  */
187       status = utime (file, NULL);
188     }
189   else
190     {
191       struct utimbuf utb;
192
193       /* There's currently no interface to set file timestamps with
194          better than 1-second resolution, so discard any fractional
195          part of the source timestamp.  */
196
197       if (use_ref)
198         {
199           utb.actime = ref_stats.st_atime;
200           utb.modtime = ref_stats.st_mtime;
201         }
202       else
203         utb.actime = utb.modtime = newtime;
204
205       if (!(change_times & CH_ATIME))
206         utb.actime = sbuf.st_atime;
207
208       if (!(change_times & CH_MTIME))
209         utb.modtime = sbuf.st_mtime;
210
211       status = utime (file, &utb);
212     }
213
214   if (status)
215     {
216       if (open_errno)
217         error (0, open_errno, _("creating %s"), quote (file));
218       else
219         {
220           if (no_create && errno == ENOENT)
221             return 0;
222           error (0, errno, _("setting times of %s"), quote (file));
223         }
224       return 1;
225     }
226
227   return 0;
228 }
229
230 void
231 usage (int status)
232 {
233   if (status != 0)
234     fprintf (stderr, _("Try `%s --help' for more information.\n"),
235              program_name);
236   else
237     {
238       printf (_("Usage: %s [OPTION]... FILE...\n"), program_name);
239       fputs (_("\
240 Update the access and modification times of each FILE to the current time.\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            set time given by WORD: access atime use (same as -a)\n\
257                            modify mtime (same as -m)\n\
258 "), stdout);
259       fputs (HELP_OPTION_DESCRIPTION, stdout);
260       fputs (VERSION_OPTION_DESCRIPTION, stdout);
261       fputs (_("\
262 \n\
263 Note that the -d and -t options accept different time-date formats.\n\
264 "), stdout);
265       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
266     }
267   exit (status);
268 }
269
270 int
271 main (int argc, char **argv)
272 {
273   int c;
274   int date_set = 0;
275   int err = 0;
276
277   program_name = argv[0];
278   setlocale (LC_ALL, "");
279   bindtextdomain (PACKAGE, LOCALEDIR);
280   textdomain (PACKAGE);
281
282   atexit (close_stdout);
283
284   change_times = no_create = use_ref = posix_date = flexible_date = 0;
285
286   while ((c = getopt_long (argc, argv, "acd:fmr:t:", longopts, NULL)) != -1)
287     {
288       switch (c)
289         {
290         case 0:
291           break;
292
293         case 'a':
294           change_times |= CH_ATIME;
295           break;
296
297         case 'c':
298           no_create++;
299           break;
300
301         case 'd':
302           flexible_date++;
303           newtime = get_date (optarg, NULL);
304           if (newtime == (time_t) -1)
305             error (EXIT_FAILURE, 0, _("invalid date format %s"), quote (optarg));
306           date_set++;
307           break;
308
309         case 'f':
310           break;
311
312         case 'm':
313           change_times |= CH_MTIME;
314           break;
315
316         case 'r':
317           use_ref++;
318           ref_file = optarg;
319           break;
320
321         case 't':
322           posix_date++;
323           if (! posixtime (&newtime, optarg,
324                            PDS_LEADING_YEAR | PDS_CENTURY | PDS_SECONDS))
325             error (EXIT_FAILURE, 0, _("invalid date format %s"), quote (optarg));
326           date_set++;
327           break;
328
329         case TIME_OPTION:       /* --time */
330           change_times |= XARGMATCH ("--time", optarg,
331                                      time_args, time_masks);
332           break;
333
334         case_GETOPT_HELP_CHAR;
335
336         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
337
338         default:
339           usage (EXIT_FAILURE);
340         }
341     }
342
343   if (change_times == 0)
344     change_times = CH_ATIME | CH_MTIME;
345
346   if ((use_ref && (posix_date || flexible_date))
347       || (posix_date && flexible_date))
348     {
349       error (0, 0, _("cannot specify times from more than one source"));
350       usage (EXIT_FAILURE);
351     }
352
353   if (use_ref)
354     {
355       if (stat (ref_file, &ref_stats))
356         error (EXIT_FAILURE, errno,
357                _("failed to get attributes of %s"), quote (ref_file));
358       date_set++;
359     }
360
361   /* The obsolete `MMDDhhmm[YY]' form is valid IFF there are
362      two or more non-option arguments.  */
363   if (!date_set && 2 <= argc - optind && !STREQ (argv[optind - 1], "--")
364       && posix2_version () < 200112)
365     {
366       if (posixtime (&newtime, argv[optind], PDS_TRAILING_YEAR))
367         {
368           if (! getenv ("POSIXLY_CORRECT"))
369             {
370               struct tm const *tm = localtime (&newtime);
371               error (0, 0,
372                      _("warning: `touch %s' is obsolete; use `touch -t %04d%02d%02d%02d%02d.%02d'"),
373                      argv[optind],
374                      tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
375                      tm->tm_hour, tm->tm_min, tm->tm_sec);
376             }
377
378           optind++;
379           date_set++;
380         }
381     }
382   if (!date_set)
383     {
384       if ((change_times & (CH_ATIME | CH_MTIME)) == (CH_ATIME | CH_MTIME))
385         amtime_now = 1;
386       else
387         time (&newtime);
388     }
389
390   if (optind == argc)
391     {
392       error (0, 0, _("file arguments missing"));
393       usage (EXIT_FAILURE);
394     }
395
396   for (; optind < argc; ++optind)
397     err += touch (argv[optind]);
398
399   exit (err != 0);
400 }