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