Tizen 2.0 Release
[external/tizen-coreutils.git] / src / touch.c
1 /* touch -- change modification and access times of files
2    Copyright (C) 87, 1989-1991, 1995-2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 "fd-reopen.h"
30 #include "getdate.h"
31 #include "posixtm.h"
32 #include "posixver.h"
33 #include "quote.h"
34 #include "safe-read.h"
35 #include "stat-time.h"
36 #include "utimens.h"
37
38 /* The official name of this program (e.g., no `g' prefix).  */
39 #define PROGRAM_NAME "touch"
40
41 #define AUTHORS \
42 "Paul Rubin", "Arnold Robbins, Jim Kingdon, David MacKenzie", "Randy Smith"
43
44 /* Bitmasks for `change_times'. */
45 #define CH_ATIME 1
46 #define CH_MTIME 2
47
48 /* The name by which this program was run. */
49 char *program_name;
50
51 /* Which timestamps to change. */
52 static int change_times;
53
54 /* (-c) If true, don't create if not already there.  */
55 static bool no_create;
56
57 /* (-r) If true, use times from a reference file.  */
58 static bool use_ref;
59
60 /* If true, the only thing we have to do is change both the
61    modification and access time to the current time, so we don't
62    have to own the file, just be able to read and write it.
63    On some systems, we can do this if we own the file, even though
64    we have neither read nor write access to it.  */
65 static bool amtime_now;
66
67 /* New access and modification times to use when setting time.  */
68 static struct timespec newtime[2];
69
70 /* File to use for -r. */
71 static char *ref_file;
72
73 /* For long options that have no equivalent short option, use a
74    non-character as a pseudo short option, starting with CHAR_MAX + 1.  */
75 enum
76 {
77   TIME_OPTION = CHAR_MAX + 1
78 };
79
80 static struct option const longopts[] =
81 {
82   {"time", required_argument, NULL, TIME_OPTION},
83   {"no-create", no_argument, NULL, 'c'},
84   {"date", required_argument, NULL, 'd'},
85   {"file", required_argument, NULL, 'r'}, /* FIXME: remove --file in 2006 */
86   {"reference", required_argument, NULL, 'r'},
87   {GETOPT_HELP_OPTION_DECL},
88   {GETOPT_VERSION_OPTION_DECL},
89   {NULL, 0, NULL, 0}
90 };
91
92 /* Valid arguments to the `--time' option. */
93 static char const* const time_args[] =
94 {
95   "atime", "access", "use", "mtime", "modify", NULL
96 };
97
98 /* The bits in `change_times' that those arguments set. */
99 static int const time_masks[] =
100 {
101   CH_ATIME, CH_ATIME, CH_ATIME, CH_MTIME, CH_MTIME
102 };
103
104 /* Store into *RESULT the result of interpreting FLEX_DATE as a date,
105    relative to NOW.  If NOW is null, use the current time.  */
106
107 static void
108 get_reldate (struct timespec *result,
109              char const *flex_date, struct timespec const *now)
110 {
111   if (! get_date (result, flex_date, now))
112     error (EXIT_FAILURE, 0, _("invalid date format %s"), quote (flex_date));
113 }
114
115 /* Update the time of file FILE according to the options given.
116    Return true if successful.  */
117
118 static bool
119 touch (const char *file)
120 {
121   bool ok;
122   struct stat sbuf;
123   int fd = -1;
124   int open_errno = 0;
125   struct timespec timespec[2];
126   struct timespec const *t;
127
128   if (STREQ (file, "-"))
129     fd = STDOUT_FILENO;
130   else if (! no_create)
131     {
132       /* Try to open FILE, creating it if necessary.  */
133       fd = fd_reopen (STDIN_FILENO, file,
134                       O_WRONLY | O_CREAT | O_NONBLOCK | O_NOCTTY,
135                       S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
136
137       /* Don't save a copy of errno if it's EISDIR, since that would lead
138          touch to give a bogus diagnostic for e.g., `touch /' (assuming
139          we don't own / or have write access to it).  On Solaris 5.6,
140          and probably other systems, it is EINVAL.  On SunOS4, it's EPERM.  */
141       if (fd == -1 && errno != EISDIR && errno != EINVAL && errno != EPERM)
142         open_errno = errno;
143     }
144
145   if (change_times != (CH_ATIME | CH_MTIME))
146     {
147       /* We're setting only one of the time values.  stat the target to get
148          the other one.  If we have the file descriptor already, use fstat.
149          Otherwise, either we're in no-create mode (and hence didn't call open)
150          or FILE is inaccessible or a directory, so we have to use stat.  */
151       if (fd != -1 ? fstat (fd, &sbuf) : stat (file, &sbuf))
152         {
153           if (open_errno)
154             error (0, open_errno, _("creating %s"), quote (file));
155           else
156             {
157               if (no_create && (errno == ENOENT || errno == EBADF))
158                 return true;
159               error (0, errno, _("failed to get attributes of %s"),
160                      quote (file));
161             }
162           if (fd == STDIN_FILENO)
163             close (fd);
164           return false;
165         }
166     }
167
168   if (amtime_now)
169     {
170       /* Pass NULL to futimens so it will not fail if we have
171          write access to the file, but don't own it.  */
172       t = NULL;
173     }
174   else
175     {
176       timespec[0] = (change_times & CH_ATIME
177                      ? newtime[0]
178                      : get_stat_atime (&sbuf));
179       timespec[1] = (change_times & CH_MTIME
180                      ? newtime[1]
181                      : get_stat_mtime (&sbuf));
182       t = timespec;
183     }
184
185   ok = (futimens (fd, (fd == STDOUT_FILENO ? NULL : file), t) == 0);
186
187   if (fd == STDIN_FILENO)
188     {
189       if (close (STDIN_FILENO) != 0)
190         {
191           error (0, errno, _("closing %s"), quote (file));
192           return false;
193         }
194     }
195   else if (fd == STDOUT_FILENO)
196     {
197       /* Do not diagnose "touch -c - >&-".  */
198       if (!ok && errno == EBADF && no_create
199           && change_times == (CH_ATIME | CH_MTIME))
200         return true;
201     }
202
203   if (!ok)
204     {
205       if (open_errno)
206         {
207           /* The wording of this diagnostic should cover at least two cases:
208              - the file does not exist, but the parent directory is unwritable
209              - the file exists, but it isn't writable
210              I think it's not worth trying to distinguish them.  */
211           error (0, open_errno, _("cannot touch %s"), quote (file));
212         }
213       else
214         {
215           if (no_create && errno == ENOENT)
216             return true;
217           error (0, errno, _("setting times of %s"), quote (file));
218         }
219       return false;
220     }
221
222   return true;
223 }
224
225 void
226 usage (int status)
227 {
228   if (status != EXIT_SUCCESS)
229     fprintf (stderr, _("Try `%s --help' for more information.\n"),
230              program_name);
231   else
232     {
233       printf (_("Usage: %s [OPTION]... FILE...\n"), program_name);
234       fputs (_("\
235 Update the access and modification times of each FILE to the current time.\n\
236 \n\
237 "), stdout);
238       fputs (_("\
239 Mandatory arguments to long options are mandatory for short options too.\n\
240 "), stdout);
241       fputs (_("\
242   -a                     change only the access time\n\
243   -c, --no-create        do not create any files\n\
244   -d, --date=STRING      parse STRING and use it instead of current time\n\
245   -f                     (ignored)\n\
246   -m                     change only the modification time\n\
247 "), stdout);
248       fputs (_("\
249   -r, --reference=FILE   use this file's times instead of current time\n\
250   -t STAMP               use [[CC]YY]MMDDhhmm[.ss] instead of current time\n\
251   --time=WORD            change the specified time:\n\
252                            WORD is access, atime, or use: equivalent to -a\n\
253                            WORD is modify or mtime: equivalent to -m\n\
254 "), stdout);
255       fputs (HELP_OPTION_DESCRIPTION, stdout);
256       fputs (VERSION_OPTION_DESCRIPTION, stdout);
257       fputs (_("\
258 \n\
259 Note that the -d and -t options accept different time-date formats.\n\
260 \n\
261 If a FILE is -, touch standard output.\n\
262 "), stdout);
263       printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
264     }
265   exit (status);
266 }
267
268 int
269 main (int argc, char **argv)
270 {
271   int c;
272   bool date_set = false;
273   bool ok = true;
274   char const *flex_date = NULL;
275
276   initialize_main (&argc, &argv);
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 = 0;
285   no_create = use_ref = false;
286
287   while ((c = getopt_long (argc, argv, "acd:fmr:t:", longopts, NULL)) != -1)
288     {
289       switch (c)
290         {
291         case 'a':
292           change_times |= CH_ATIME;
293           break;
294
295         case 'c':
296           no_create = true;
297           break;
298
299         case 'd':
300           flex_date = optarg;
301           break;
302
303         case 'f':
304           break;
305
306         case 'm':
307           change_times |= CH_MTIME;
308           break;
309
310         case 'r':
311           use_ref = true;
312           ref_file = optarg;
313           break;
314
315         case 't':
316           if (! posixtime (&newtime[0].tv_sec, optarg,
317                            PDS_LEADING_YEAR | PDS_CENTURY | PDS_SECONDS))
318             error (EXIT_FAILURE, 0, _("invalid date format %s"),
319                    quote (optarg));
320           newtime[0].tv_nsec = 0;
321           newtime[1] = newtime[0];
322           date_set = true;
323           break;
324
325         case TIME_OPTION:       /* --time */
326           change_times |= XARGMATCH ("--time", optarg,
327                                      time_args, time_masks);
328           break;
329
330         case_GETOPT_HELP_CHAR;
331
332         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
333
334         default:
335           usage (EXIT_FAILURE);
336         }
337     }
338
339   if (change_times == 0)
340     change_times = CH_ATIME | CH_MTIME;
341
342   if (date_set && (use_ref || flex_date))
343     {
344       error (0, 0, _("cannot specify times from more than one source"));
345       usage (EXIT_FAILURE);
346     }
347
348   if (use_ref)
349     {
350       struct stat ref_stats;
351       if (stat (ref_file, &ref_stats))
352         error (EXIT_FAILURE, errno,
353                _("failed to get attributes of %s"), quote (ref_file));
354       newtime[0] = get_stat_atime (&ref_stats);
355       newtime[1] = get_stat_mtime (&ref_stats);
356       date_set = true;
357       if (flex_date)
358         {
359           if (change_times & CH_ATIME)
360             get_reldate (&newtime[0], flex_date, &newtime[0]);
361           if (change_times & CH_MTIME)
362             get_reldate (&newtime[1], flex_date, &newtime[1]);
363         }
364     }
365   else
366     {
367       if (flex_date)
368         {
369           get_reldate (&newtime[0], flex_date, NULL);
370           newtime[1] = newtime[0];
371           date_set = true;
372         }
373     }
374
375   /* The obsolete `MMDDhhmm[YY]' form is valid IFF there are
376      two or more non-option arguments.  */
377   if (!date_set && 2 <= argc - optind && posix2_version () < 200112
378       && posixtime (&newtime[0].tv_sec, argv[optind],
379                     PDS_TRAILING_YEAR | PDS_PRE_2000))
380     {
381       newtime[0].tv_nsec = 0;
382       newtime[1] = newtime[0];
383       date_set = true;
384
385       if (! getenv ("POSIXLY_CORRECT"))
386         {
387           struct tm const *tm = localtime (&newtime[0].tv_sec);
388           error (0, 0,
389                  _("warning: `touch %s' is obsolete; use "
390                    "`touch -t %04ld%02d%02d%02d%02d.%02d'"),
391                  argv[optind],
392                  tm->tm_year + 1900L, tm->tm_mon + 1, tm->tm_mday,
393                  tm->tm_hour, tm->tm_min, tm->tm_sec);
394         }
395
396       optind++;
397     }
398
399   if (!date_set)
400     {
401       if (change_times == (CH_ATIME | CH_MTIME))
402         amtime_now = true;
403       else
404         {
405           gettime (&newtime[0]);
406           newtime[1] = newtime[0];
407         }
408     }
409
410   if (optind == argc)
411     {
412       error (0, 0, _("missing file operand"));
413       usage (EXIT_FAILURE);
414     }
415
416   for (; optind < argc; ++optind)
417     ok &= touch (argv[optind]);
418
419   exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
420 }