source: trunk/src/kmk/kmkbuiltin/touch.c@ 3059

Last change on this file since 3059 was 3059, checked in by bird, 8 years ago

kmk: Initial kmk_touch implementation.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 31.3 KB
Line 
1/* $Id: touch.c 3059 2017-09-21 13:34:15Z bird $ */
2/** @file
3 * kmk_touch - Simple touch implementation.
4 */
5
6/*
7 * Copyright (c) 2017 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
8 *
9 * This file is part of kBuild.
10 *
11 * kBuild is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * kBuild is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with kBuild. If not, see <http://www.gnu.org/licenses/>
23 *
24 */
25
26/*******************************************************************************
27* Header Files *
28*******************************************************************************/
29#include "make.h"
30#include <assert.h>
31#include <stdarg.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <errno.h>
36#include <fcntl.h>
37#if defined(_MSC_VER)
38# include <ctype.h>
39# include <io.h>
40#else
41# include <unistd.h>
42#endif
43
44#include <k/kDefs.h>
45#include <k/kTypes.h>
46#include "err.h"
47#include "kbuild_version.h"
48#include "kmkbuiltin.h"
49
50/*********************************************************************************************************************************
51* Defined Constants And Macros *
52*********************************************************************************************************************************/
53/** The program name to use in message. */
54#ifdef KMK
55# define TOUCH_NAME "kmk_builtin_touch"
56#else
57# define TOUCH_NAME "kmk_touch"
58#endif
59/** Converts a two digit decimal field to a number. */
60#define TWO_CHARS_TO_INT(chHigh, chLow) ( ((unsigned)(chHigh) - (unsigned)'0') * 10 + ((unsigned)(chLow) - (unsigned)'0') )
61/** Checks an alleged digit. */
62#define IS_DIGIT(chDigit, uMax) ( ((unsigned)(chDigit) - (unsigned)'0') <= (unsigned)(uMax) )
63
64
65/*********************************************************************************************************************************
66* Structures and Typedefs *
67*********************************************************************************************************************************/
68typedef enum KMKTOUCHTARGET
69{
70 kTouchAccessAndModify,
71 kTouchAccessOnly,
72 kTouchModifyOnly
73} KMKTOUCHTARGET;
74
75typedef enum KMKTOUCHACTION
76{
77 kTouchActionCurrent,
78 kTouchActionSet,
79 kTouchActionAdjust
80} KMKTOUCHACTION;
81
82
83typedef struct KMKTOUCHOPTS
84{
85 /** What timestamps to modify on the files. */
86 KMKTOUCHTARGET enmWhatToTouch;
87 /** How to update the time. */
88 KMKTOUCHACTION enmAction;
89 /** Whether to create files (K_TRUE) or ignore missing (K_FALSE). */
90 KBOOL fCreate;
91 /** Whether to dereference files. */
92 KBOOL fDereference;
93 /** The new access time value. */
94 struct timeval NewATime;
95 /** The new modified time value. */
96 struct timeval NewMTime;
97
98 /** Number of files. */
99 int cFiles;
100 /** The specified files. */
101 const char **papszFiles;
102} KMKTOUCHOPTS;
103typedef KMKTOUCHOPTS *PKMKTOUCHOPTS;
104
105
106static int touch_error(const char *pszMsg, ...)
107{
108 va_list va;
109 fputs(TOUCH_NAME ": error: ", stderr);
110 va_start(va, pszMsg);
111 vfprintf(stderr, pszMsg, va);
112 va_end(va);
113 fputc('\n', stderr);
114 return 1;
115}
116
117static int touch_syntax(const char *pszMsg, ...)
118{
119 va_list va;
120 fputs(TOUCH_NAME ": syntax error: ", stderr);
121 va_start(va, pszMsg);
122 vfprintf(stderr, pszMsg, va);
123 va_end(va);
124 fputc('\n', stderr);
125 return 2;
126}
127
128static int touch_usage(void)
129{
130 fputs("Usage: " TOUCH_NAME " [options] [MMDDhhmm[YY]] <file1> [.. [fileN]]\n"
131 "\n"
132 "Options:\n"
133 " -A [-][[hh]mm]SS, --adjust=[-][[hh]mm]SS\n"
134 " Adjust timestamps by given delta.\n"
135 " -a, --time=atime, --time=access\n"
136 " Only change the accessed timestamp.\n"
137 " -c, --no-create\n"
138 " Ignore missing files and don't create them. (default create empty file)\n"
139 " -d YYYY-MM-DDThh:mm:SS[.frac][tz], --date=YYYY-MM-DDThh:mm:SS[.frac][tz]\n"
140 " Set the timestamps to the given one.\n"
141 " -f\n"
142 " Ignored for compatbility reasons.\n"
143 " -h, --no-dereference\n"
144 " Don't follow links, touch links. (Not applicable to -r.)\n"
145 " -m, --time=mtime, --time=modify\n"
146 " Only changed the modified timestamp.\n"
147 " -r <file>, --reference=<file>\n"
148 " Take the timestamps from <file>.\n"
149 " -t [[CC]YY]MMDDhhmm[.SS]\n"
150 " Set the timestamps to the given one.\n"
151 "\n"
152 "Note. For compatibility reasons the first file can be taken to be a 8 or 10\n"
153 " character long timestamp if it matches the given pattern and none of\n"
154 " the -A, -d, --date, -r, --reference, or -t options are given. So, use\n"
155 " absolute or relative paths when specifying more than one file.\n"
156 , stdout);
157 return 0;
158}
159
160
161/**
162 * Parses adjustment value: [-][[hh]mm]SS
163 */
164static int touch_parse_adjust(const char *pszValue, int *piAdjustValue)
165{
166 const char * const pszInValue = pszValue;
167 size_t cchValue = strlen(pszValue);
168 KBOOL fNegative = K_FALSE;
169
170 /* Deal with negativity */
171 if (pszValue[0] == '-')
172 {
173 fNegative = K_TRUE;
174 pszValue++;
175 cchValue--;
176 }
177
178 /* Validate and convert. */
179 *piAdjustValue = 0;
180 switch (cchValue)
181 {
182 case 6:
183 if ( !IS_DIGIT(pszValue[0], 9)
184 || !IS_DIGIT(pszValue[0], 9))
185 return touch_syntax("Malformed hour part of -A value: %s", pszInValue);
186 *piAdjustValue = TWO_CHARS_TO_INT(pszValue[0], pszValue[1]) * 60 * 60;
187 /* fall thru */
188 case 4:
189 if ( !IS_DIGIT(pszValue[cchValue - 4], 9) /* don't bother limit to 60 minutes */
190 || !IS_DIGIT(pszValue[cchValue - 3], 9))
191 return touch_syntax("Malformed minute part of -A value: %s", pszInValue);
192 *piAdjustValue += TWO_CHARS_TO_INT(pszValue[cchValue - 4], pszValue[cchValue - 3]) * 60;
193 /* fall thru */
194 case 2:
195 if ( !IS_DIGIT(pszValue[cchValue - 2], 9) /* don't bother limit to 60 seconds */
196 || !IS_DIGIT(pszValue[cchValue - 1], 9))
197 return touch_syntax("Malformed second part of -A value: %s", pszInValue);
198 *piAdjustValue += TWO_CHARS_TO_INT(pszValue[cchValue - 2], pszValue[cchValue - 1]);
199 break;
200
201 default:
202 return touch_syntax("Invalid -A value (length): %s", pszInValue);
203 }
204
205 /* Apply negativity. */
206 if (fNegative)
207 *piAdjustValue = -*piAdjustValue;
208
209 return 0;
210}
211
212
213/**
214 * Parse -d timestamp: YYYY-MM-DDThh:mm:SS[.frac][tz]
215 */
216static int touch_parse_d_ts(const char *pszTs, struct timeval *pDst)
217{
218 const char * const pszTsIn = pszTs;
219 KBOOL fIsZulu = K_FALSE;
220 struct tm ExpTime;
221
222 /*
223 * Validate and parse the timestamp into the tm structure.
224 */
225 memset(&ExpTime, 0, sizeof(ExpTime));
226
227 /* year */
228 if ( !IS_DIGIT(pszTs[0], 9)
229 || !IS_DIGIT(pszTs[1], 9)
230 || !IS_DIGIT(pszTs[2], 9)
231 || !IS_DIGIT(pszTs[3], 9)
232 || pszTs[4] != '-')
233 return touch_error("Malformed timestamp '%s' given to -d: expected to start with 4 digit year followed by a dash",
234 pszTsIn);
235 ExpTime.tm_year = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]) * 100
236 + TWO_CHARS_TO_INT(pszTs[2], pszTs[3])
237 - 1900;
238 pszTs += 5;
239
240 /* month */
241 if ( !IS_DIGIT(pszTs[0], 1)
242 || !IS_DIGIT(pszTs[1], 9)
243 || pszTs[2] != '-')
244 return touch_error("Malformed timestamp '%s' given to -d: expected to two digit month at position 6 followed by a dash",
245 pszTsIn);
246 ExpTime.tm_mon = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]);
247 pszTs += 3;
248
249 /* day */
250 if ( !IS_DIGIT(pszTs[0], 3)
251 || !IS_DIGIT(pszTs[1], 9)
252 || (pszTs[2] != 'T' && pszTs[2] != 't' && pszTs[2] != ' ') )
253 return touch_error("Malformed timestamp '%s' given to -d: expected to two digit day of month at position 9 followed by 'T' or space",
254 pszTsIn);
255 ExpTime.tm_mday = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]);
256 pszTs += 3;
257
258 /* hour */
259 if ( !IS_DIGIT(pszTs[0], 2)
260 || !IS_DIGIT(pszTs[1], 9)
261 || pszTs[2] != ':')
262 return touch_error("Malformed timestamp '%s' given to -d: expected to two digit hour at position 12 followed by colon",
263 pszTsIn);
264 ExpTime.tm_hour = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]);
265 pszTs += 3;
266
267 /* minute */
268 if ( !IS_DIGIT(pszTs[0], 5)
269 || !IS_DIGIT(pszTs[1], 9)
270 || pszTs[2] != ':')
271 return touch_error("Malformed timestamp '%s' given to -d: expected to two digit minute at position 15 followed by colon",
272 pszTsIn);
273 ExpTime.tm_min = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]);
274 pszTs += 3;
275
276 /* seconds */
277 if ( !IS_DIGIT(pszTs[0], 5)
278 || !IS_DIGIT(pszTs[1], 9))
279 return touch_error("Malformed timestamp '%s' given to -d: expected to two digit seconds at position 12", pszTsIn);
280 ExpTime.tm_sec = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]);
281 pszTs += 2;
282
283 /* fraction */
284 pDst->tv_usec = 0;
285 if (*pszTs == '.' || *pszTs == ',')
286 {
287 int iFactor;
288
289 pszTs++;
290 if (!IS_DIGIT(*pszTs, 9))
291 return touch_error("Malformed timestamp '%s' given to -d: empty fraction", pszTsIn);
292
293 iFactor = 100000;
294 do
295 {
296 pDst->tv_usec += ((unsigned)*pszTs - (unsigned)'0') * iFactor;
297 iFactor /= 10;
298 pszTs++;
299 } while (IS_DIGIT(*pszTs, 9));
300 }
301
302 /* zulu time indicator */
303 if (*pszTs == 'Z' || *pszTs == 'z')
304 {
305 fIsZulu = K_TRUE;
306 pszTs++;
307 if (*pszTs != '\0')
308 return touch_error("Malformed timestamp '%s' given to -d: Unexpected character(s) after zulu time indicator at end of timestamp",
309 pszTsIn);
310 }
311 else if (*pszTs != '\0')
312 return touch_error("Malformed timestamp '%s' given to -d: expected to 'Z' (zulu) or nothing at end of timestamp", pszTsIn);
313
314 /*
315 * Convert to UTC seconds using either timegm or mktime.
316 */
317 ExpTime.tm_isdst = fIsZulu ? 0 : -1;
318 ExpTime.tm_yday = -1;
319 ExpTime.tm_wday = -1;
320 pDst->tv_sec = fIsZulu ? timegm(&ExpTime) : mktime(&ExpTime);
321 if (pDst->tv_sec != -1)
322 return 0;
323 return touch_error("%s failed on '%s': %s", fIsZulu ? "timegm" : "mktime", pszTs, strerror(errno));
324}
325
326
327/**
328 * Parse -t timestamp: [[CC]YY]MMDDhhmm[.SS]
329 */
330static int touch_parse_ts(const char *pszTs, struct timeval *pDst)
331{
332 size_t const cchTs = strlen(pszTs);
333 size_t cchTsNoSec;
334 struct tm ExpTime;
335 struct tm *pExpTime;
336 struct timeval Now;
337 int rc;
338
339 /*
340 * Do some input validations first.
341 */
342 if ((cchTs & 1) && pszTs[cchTs - 3] != '.')
343 return touch_error("Invalid timestamp given to -t: %s", pszTs);
344 switch (cchTs)
345 {
346 case 8: /* MMDDhhmm */
347 case 8 + 2: /* YYMMDDhhmm */
348 case 8 + 2 + 2: /* CCYYMMDDhhmm */
349 cchTsNoSec = cchTs;
350 break;
351 case 8 + 3: /* MMDDhhmm.SS */
352 case 8 + 3 + 2: /* YYMMDDhhmm.SS */
353 case 8 + 3 + 2 + 2: /* CCYYMMDDhhmm.SS */
354 if (pszTs[cchTs - 3] != '.')
355 return touch_error("Invalid timestamp (-t) '%s': missing dot for seconds part", pszTs);
356 if ( !IS_DIGIT(pszTs[cchTs - 2], 5)
357 || !IS_DIGIT(pszTs[cchTs - 1], 9))
358 return touch_error("Invalid timestamp (-t) '%s': malformed seconds part", pszTs);
359 cchTsNoSec = cchTs - 3;
360 break;
361 default:
362 return touch_error("Invalid timestamp (-t) '%s': wrong length (%d)", pszTs, (int)cchTs);
363 }
364
365 switch (cchTsNoSec)
366 {
367 case 8 + 2 + 2: /* CCYYMMDDhhmm */
368 if ( !IS_DIGIT(pszTs[cchTsNoSec - 12], 9)
369 || !IS_DIGIT(pszTs[cchTsNoSec - 11], 9))
370 return touch_error("Invalid timestamp (-t) '%s': malformed CC part", pszTs);
371 /* fall thru */
372 case 8 + 2: /* YYMMDDhhmm */
373 if ( !IS_DIGIT(pszTs[cchTsNoSec - 10], 9)
374 || !IS_DIGIT(pszTs[cchTsNoSec - 9], 9))
375 return touch_error("Invalid timestamp (-t) '%s': malformed YY part", pszTs);
376 /* fall thru */
377 case 8: /* MMDDhhmm */
378 if ( !IS_DIGIT(pszTs[cchTsNoSec - 8], 1)
379 || !IS_DIGIT(pszTs[cchTsNoSec - 7], 9) )
380 return touch_error("Invalid timestamp (-t) '%s': malformed month part", pszTs);
381 if ( !IS_DIGIT(pszTs[cchTsNoSec - 6], 3)
382 || !IS_DIGIT(pszTs[cchTsNoSec - 5], 9) )
383 return touch_error("Invalid timestamp (-t) '%s': malformed day part", pszTs);
384 if ( !IS_DIGIT(pszTs[cchTsNoSec - 4], 2)
385 || !IS_DIGIT(pszTs[cchTsNoSec - 3], 9) )
386 return touch_error("Invalid timestamp (-t) '%s': malformed hour part", pszTs);
387 if ( !IS_DIGIT(pszTs[cchTsNoSec - 2], 5)
388 || !IS_DIGIT(pszTs[cchTsNoSec - 1], 9) )
389 return touch_error("Invalid timestamp (-t) '%s': malformed minute part", pszTs);
390 break;
391 }
392
393 /*
394 * Get the current time and explode it.
395 */
396 rc = gettimeofday(&Now, NULL);
397 if (rc != 0)
398 return touch_error("gettimeofday failed: %s", strerror(errno));
399
400 pExpTime = localtime_r(&Now.tv_sec, &ExpTime);
401 if (pExpTime == NULL)
402 return touch_error("localtime_r failed: %s", strerror(errno));
403
404 /*
405 * Do the decoding.
406 */
407 if (cchTs & 1)
408 pExpTime->tm_sec = TWO_CHARS_TO_INT(pszTs[cchTs - 2], pszTs[cchTs - 1]);
409 else
410 pExpTime->tm_sec = 0;
411
412 if (cchTsNoSec == 8 + 2 + 2) /* CCYY */
413 pExpTime->tm_year = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]) * 100
414 + TWO_CHARS_TO_INT(pszTs[2], pszTs[3])
415 - 1900;
416 else if (cchTsNoSec == 8 + 2) /* YY */
417 {
418 pExpTime->tm_year = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]);
419 if (pExpTime->tm_year < 69)
420 pExpTime->tm_year += 100;
421 }
422
423 pExpTime->tm_mon = TWO_CHARS_TO_INT(pszTs[cchTsNoSec - 8], pszTs[cchTsNoSec - 7]);
424 pExpTime->tm_mday = TWO_CHARS_TO_INT(pszTs[cchTsNoSec - 6], pszTs[cchTsNoSec - 5]);
425 pExpTime->tm_hour = TWO_CHARS_TO_INT(pszTs[cchTsNoSec - 4], pszTs[cchTsNoSec - 3]);
426 pExpTime->tm_min = TWO_CHARS_TO_INT(pszTs[cchTsNoSec - 2], pszTs[cchTsNoSec - 1]);
427
428 /*
429 * Use mktime to convert to UTC seconds.
430 */
431 pExpTime->tm_isdst = -1;
432 pExpTime->tm_yday = -1;
433 pExpTime->tm_wday = -1;
434 pDst->tv_usec = 0;
435 pDst->tv_sec = mktime(pExpTime);
436 if (pDst->tv_sec != -1)
437 return 0;
438 return touch_error("mktime failed on '%s': %s", pszTs, strerror(errno));
439}
440
441
442/**
443 * Check for old timestamp: MMDDhhmm[YY]
444 */
445static int touch_parse_old_ts(const char *pszOldTs, time_t Now, struct timeval *pDst)
446{
447 /*
448 * Check if this is a valid timestamp.
449 */
450 size_t const cchOldTs = strlen(pszOldTs);
451 if ( ( cchOldTs == 8
452 || cchOldTs == 10)
453 && IS_DIGIT(pszOldTs[0], 1)
454 && IS_DIGIT(pszOldTs[1], 9)
455 && IS_DIGIT(pszOldTs[2], 3)
456 && IS_DIGIT(pszOldTs[3], 9)
457 && IS_DIGIT(pszOldTs[4], 2)
458 && IS_DIGIT(pszOldTs[5], 9)
459 && IS_DIGIT(pszOldTs[6], 5)
460 && IS_DIGIT(pszOldTs[7], 9)
461 && ( cchOldTs == 8
462 || ( IS_DIGIT(pszOldTs[8], 9)
463 && IS_DIGIT(pszOldTs[9], 9) )) )
464 {
465 /*
466 * Explode the current time as local.
467 */
468 struct tm ExpTime;
469 struct tm *pExpTime;
470 pExpTime = localtime_r(&Now, &ExpTime);
471 if (pExpTime == NULL)
472 return touch_error("localtime_r failed: %s", strerror(errno));
473
474 /*
475 * Decode the bits we've got.
476 */
477 pExpTime->tm_mon = TWO_CHARS_TO_INT(pszOldTs[0], pszOldTs[1]) - 1;
478 pExpTime->tm_mday = TWO_CHARS_TO_INT(pszOldTs[2], pszOldTs[3]);
479 pExpTime->tm_hour = TWO_CHARS_TO_INT(pszOldTs[4], pszOldTs[5]);
480 pExpTime->tm_min = TWO_CHARS_TO_INT(pszOldTs[6], pszOldTs[7]);
481 if (cchOldTs == 10)
482 {
483 pExpTime->tm_year = TWO_CHARS_TO_INT(pszOldTs[8], pszOldTs[9]);
484 if (pExpTime->tm_year <= 38) /* up to 2038, 32-bit time_t style logic. */
485 pExpTime->tm_year += 100;
486 }
487
488 /*
489 * Use mktime to convert to UTC seconds.
490 */
491 pExpTime->tm_isdst = -1;
492 pExpTime->tm_yday = -1;
493 pExpTime->tm_wday = -1;
494 pDst->tv_usec = 0;
495 pDst->tv_sec = mktime(pExpTime);
496 if (pDst->tv_sec != -1)
497 return 0;
498 return touch_error("mktime failed on '%s': %s", pszOldTs, strerror(errno));
499 }
500
501 /* No valid timestamp present. */
502 return -1;
503}
504
505
506/**
507 * Parses the arguments into pOpts.
508 *
509 * @returns exit code.
510 * @param pOpts Options structure to return the parsed info in.
511 * Caller initalizes this with defaults.
512 * @param cArgs The number of arguments.
513 * @param papszArgs The arguments.
514 * @param pfExit Indicates whether to exit or to start processing
515 * files.
516 */
517static int touch_parse_args(PKMKTOUCHOPTS pOpts, int cArgs, char **papszArgs, KBOOL *pfExit)
518{
519 int iAdjustValue = 0;
520 int iArg;
521 int rc;
522
523 *pfExit = K_TRUE;
524 /*
525 * Parse arguments, skipping all files (GNU style).
526 */
527 for (iArg = 1; iArg < cArgs; iArg++)
528 {
529 const char *pszArg = papszArgs[iArg];
530 if (*pszArg == '-')
531 {
532 const char *pszLongValue = NULL;
533 char ch = *++pszArg;
534 pszArg++;
535
536 /*
537 * Deal with long options first, preferably translating them into short ones.
538 */
539 if (ch == '-')
540 {
541 const char *pszLong = pszArg;
542 ch = *pszArg++;
543 if (!ch)
544 {
545 while (++iArg < cArgs)
546 pOpts->papszFiles[pOpts->cFiles++] = papszArgs[iArg];
547 break; /* '--' */
548 }
549
550 /* Translate long options. */
551 pszArg = "";
552 if (strcmp(pszLong, "adjust") == 0)
553 ch = 'A';
554 else if (strncmp(pszLong, "adjust=", sizeof("adjust=") - 1) == 0)
555 {
556 ch = 'A';
557 pszLongValue = pszArg = pszLong + sizeof("adjust=") - 1;
558 }
559 else if (strcmp(pszLong, "no-create") == 0)
560 ch = 'c';
561 else if (strcmp(pszLong, "date") == 0)
562 ch = 'd';
563 else if (strncmp(pszLong, "date=", sizeof("date=") - 1) == 0)
564 {
565 ch = 'd';
566 pszLongValue = pszArg = pszLong + sizeof("date=") - 1;
567 }
568 else if (strcmp(pszLong, "no-dereference") == 0)
569 ch = 'h';
570 else if (strcmp(pszLong, "reference") == 0)
571 ch = 'r';
572 else if (strncmp(pszLong, "reference=", sizeof("reference=") - 1) == 0)
573 {
574 ch = 'r';
575 pszLongValue = pszArg = pszLong + sizeof("reference=") - 1;
576 }
577 else if (strcmp(pszLong, "time") == 0)
578 ch = 'T';
579 else if (strncmp(pszLong, "time=", sizeof("time=") - 1) == 0)
580 {
581 ch = 'T';
582 pszLongValue = pszArg = pszLong + sizeof("time=") - 1;
583 }
584 else if (strcmp(pszLong, "help") == 0)
585 return touch_usage();
586 else if (strcmp(pszLong, "version") == 0)
587 return kbuild_version(papszArgs[0]);
588 else
589 return touch_syntax("Unknown option: --%s", pszLong);
590 }
591
592 /*
593 * Process short options.
594 */
595 do
596 {
597 /* Some options takes a value. */
598 const char *pszValue;
599 switch (ch)
600 {
601 case 'A':
602 case 'd':
603 case 'r':
604 case 't':
605 case 'T':
606 if (*pszArg || pszLongValue)
607 {
608 pszValue = pszArg;
609 pszArg = "";
610 }
611 else if (iArg + 1 < cArgs)
612 pszValue = papszArgs[++iArg];
613 else
614 return touch_syntax("Option -%c requires a value", ch);
615 break;
616
617 default:
618 pszValue = NULL;
619 break;
620 }
621
622 switch (ch)
623 {
624 /* -A [-][[HH]MM]SS */
625 case 'A':
626 rc = touch_parse_adjust(pszValue, &iAdjustValue);
627 if (rc != 0)
628 return rc;
629 if (pOpts->enmAction != kTouchActionSet)
630 {
631 pOpts->enmAction = kTouchActionAdjust;
632 pOpts->NewATime.tv_sec = iAdjustValue;
633 pOpts->NewATime.tv_usec = 0;
634 pOpts->NewMTime = pOpts->NewATime;
635 }
636 /* else: applied after parsing everything. */
637 break;
638
639 case 'a':
640 pOpts->enmWhatToTouch = kTouchAccessOnly;
641 break;
642
643 case 'c':
644 pOpts->fCreate = K_FALSE;
645 break;
646
647 case 'd':
648 rc = touch_parse_d_ts(pszValue, &pOpts->NewATime);
649 if (rc != 0)
650 return rc;
651 pOpts->enmAction = kTouchActionSet;
652 pOpts->NewMTime = pOpts->NewATime;
653 break;
654
655 case 'f':
656 /* some historical thing, ignored. */
657 break;
658
659 case 'h':
660 pOpts->fDereference = K_FALSE;
661 break;
662
663 case 'm':
664 pOpts->enmWhatToTouch = kTouchModifyOnly;
665 break;
666
667 case 'r':
668 {
669 struct stat St;
670 if (stat(pszValue, &St) != 0)
671 return touch_error("Failed to stat '%s' (-r option): %s", pszValue, strerror(errno));
672
673 pOpts->enmAction = kTouchActionSet;
674 pOpts->NewATime.tv_sec = St.st_atime;
675 pOpts->NewMTime.tv_sec = St.st_mtime;
676#if FILE_TIMESTAMP_HI_RES
677 pOpts->NewATime.tv_usec = St.st_atim.tv_nsec / 1000;
678 pOpts->NewMTime.tv_usec = St.st_mtim.tv_nsec / 1000;
679#else
680 pOpts->NewATime.tv_usec = 0;
681 pOpts->NewMTime.tv_usec = 0;
682#endif
683 break;
684 }
685
686 case 't':
687 rc = touch_parse_ts(pszValue, &pOpts->NewATime);
688 if (rc != 0)
689 return rc;
690 pOpts->enmAction = kTouchActionSet;
691 pOpts->NewMTime = pOpts->NewATime;
692 break;
693
694 case 'T':
695 if ( strcmp(pszValue, "atime") == 0
696 || strcmp(pszValue, "access") == 0)
697 pOpts->enmWhatToTouch = kTouchAccessOnly;
698 else if ( strcmp(pszValue, "mtime") == 0
699 || strcmp(pszValue, "modify") == 0)
700 pOpts->enmWhatToTouch = kTouchModifyOnly;
701 else
702 return touch_syntax("Unknown --time value: %s", pszValue);
703 break;
704
705 case 'V':
706 return kbuild_version(papszArgs[0]);
707
708 default:
709 return touch_syntax("Unknown option: -%c (%c%s)", ch, ch, pszArg);
710 }
711
712 } while ((ch = *pszArg++) != '\0');
713 }
714 else
715 pOpts->papszFiles[pOpts->cFiles++] = papszArgs[iArg];
716 }
717
718 /*
719 * Allow adjusting specified timestamps too like BSD does.
720 */
721 if ( pOpts->enmAction == kTouchActionSet
722 && iAdjustValue != 0)
723 {
724 if ( pOpts->enmWhatToTouch == kTouchAccessAndModify
725 || pOpts->enmWhatToTouch == kTouchAccessOnly)
726 pOpts->NewATime.tv_sec += iAdjustValue;
727 if ( pOpts->enmWhatToTouch == kTouchAccessAndModify
728 || pOpts->enmWhatToTouch == kTouchModifyOnly)
729 pOpts->NewMTime.tv_sec += iAdjustValue;
730 }
731
732 /*
733 * Check for old timestamp: MMDDhhmm[YY]
734 */
735 if ( pOpts->enmAction == kTouchActionCurrent
736 && pOpts->cFiles >= 2)
737 {
738 struct timeval OldTs;
739 rc = touch_parse_old_ts(pOpts->papszFiles[0], pOpts->NewATime.tv_sec, &OldTs);
740 if (rc == 0)
741 {
742 int iFile;
743
744 pOpts->NewATime = OldTs;
745 pOpts->NewMTime = OldTs;
746 pOpts->enmAction = kTouchActionSet;
747
748 for (iFile = 1; iFile < pOpts->cFiles; iFile++)
749 pOpts->papszFiles[iFile - 1] = pOpts->papszFiles[iFile];
750 pOpts->cFiles--;
751 }
752 else if (rc > 0)
753 return rc;
754 }
755
756 /*
757 * Check that we've found at least one file argument.
758 */
759 if (pOpts->cFiles > 0)
760 {
761 *pfExit = K_FALSE;
762 return 0;
763 }
764 return touch_syntax("No file specified");
765}
766
767
768/**
769 * Touches one file.
770 *
771 * @returns exit code.
772 * @param pOpts The options.
773 * @param pszFile The file to touch.
774 */
775static int touch_process_file(PKMKTOUCHOPTS pOpts, const char *pszFile)
776{
777 int fd;
778 int rc;
779 struct stat St;
780 struct timeval aTimes[2];
781
782 /*
783 * Create the file if it doesn't exists. If the --no-create/-c option is
784 * in effect, we silently skip the file if it doesn't already exist.
785 */
786 if (pOpts->fDereference)
787 rc = stat(pszFile, &St);
788 else
789 rc = lstat(pszFile, &St);
790 if (rc != 0)
791 {
792 if (errno != ENOENT)
793 return touch_error("Failed to stat '%s': %s", pszFile, strerror(errno));
794
795 if (!pOpts->fCreate)
796 return 0;
797 fd = open(pszFile, O_WRONLY | O_CREAT, 0666);
798 if (fd == -1)
799 return touch_error("Failed to create '%s': %s", pszFile, strerror(errno));
800
801 /* If we're not setting the current time, we may need value stat info
802 on the file, so get it thru the file descriptor before closing it. */
803 if (pOpts->enmAction == kTouchActionCurrent)
804 rc = 0;
805 else
806 rc = fstat(fd, &St);
807 if (close(fd) != 0)
808 return touch_error("Failed to close '%s' after creation: %s", pszFile, strerror(errno));
809 if (rc != 0)
810 return touch_error("Failed to fstat '%s' after creation: %s", pszFile, strerror(errno));
811
812 /* We're done now if we're setting the current time. */
813 if (pOpts->enmAction == kTouchActionCurrent)
814 return 0;
815 }
816
817 /*
818 * Create aTimes and call utimes/lutimes.
819 */
820 aTimes[0].tv_sec = St.st_atime;
821 aTimes[1].tv_sec = St.st_mtime;
822#if FILE_TIMESTAMP_HI_RES
823 aTimes[0].tv_usec = St.st_atim.tv_nsec / 1000;
824 aTimes[1].tv_usec = St.st_mtim.tv_nsec / 1000;
825#else
826 aTimes[0].tv_usec = 0;
827 aTimes[1].tv_usec = 0;
828#endif
829 if ( pOpts->enmWhatToTouch == kTouchAccessAndModify
830 || pOpts->enmWhatToTouch == kTouchAccessOnly)
831 {
832 if ( pOpts->enmAction == kTouchActionCurrent
833 || pOpts->enmAction == kTouchActionSet)
834 aTimes[0] = pOpts->NewATime;
835 else
836 aTimes[0].tv_sec += pOpts->NewATime.tv_sec;
837 }
838 if ( pOpts->enmWhatToTouch == kTouchAccessAndModify
839 || pOpts->enmWhatToTouch == kTouchModifyOnly)
840 {
841 if ( pOpts->enmAction == kTouchActionCurrent
842 || pOpts->enmAction == kTouchActionSet)
843 aTimes[1] = pOpts->NewMTime;
844 else
845 aTimes[1].tv_sec += pOpts->NewMTime.tv_sec;
846 }
847
848 /*
849 * Try set the times. If we're setting current time, fall back on calling
850 * [l]utimes with a NULL timeval vector since that has slightly different
851 * permissions checks. (Note that we don't do that by default because it
852 * may do more than what we want (st_ctime).)
853 */
854 if (pOpts->fDereference)
855 rc = utimes(pszFile, aTimes);
856 else
857 rc = lutimes(pszFile, aTimes);
858 if (rc != 0)
859 {
860 if (pOpts->enmAction == kTouchActionCurrent)
861 {
862 if (pOpts->fDereference)
863 rc = utimes(pszFile, NULL);
864 else
865 rc = lutimes(pszFile, NULL);
866 }
867 if (rc != 0)
868 rc = touch_error("%stimes failed on '%s': %s", pOpts->fDereference ? "" : "l", pszFile, strerror(errno));
869 }
870
871 return rc;
872}
873
874
875/**
876 * The function that does almost everything here... ugly.
877 */
878#ifdef KMK
879int kmk_builtin_touch(int argc, char **argv, char **envp)
880#else
881int main(int argc, char **argv, char **envp)
882#endif
883{
884 int rc;
885 KMKTOUCHOPTS Opts;
886 K_NOREF(envp);
887
888 /*
889 * Initialize options with defaults and parse them.
890 */
891 Opts.enmWhatToTouch = kTouchAccessAndModify;
892 Opts.enmAction = kTouchActionCurrent;
893 Opts.fCreate = K_TRUE;
894 Opts.fDereference = K_TRUE;
895 Opts.cFiles = 0;
896 Opts.papszFiles = (const char **)calloc(argc, sizeof(const char *));
897 if (Opts.papszFiles)
898 {
899 rc = gettimeofday(&Opts.NewATime, NULL);
900 if (rc == 0)
901 {
902 KBOOL fExit;
903 Opts.NewMTime = Opts.NewATime;
904
905 rc = touch_parse_args(&Opts, argc, argv, &fExit);
906 if (rc == 0 && !fExit)
907 {
908 /*
909 * Process the files.
910 */
911 int iFile;
912 for (iFile = 0; iFile < Opts.cFiles; iFile++)
913 {
914 int rc2 = touch_process_file(&Opts, Opts.papszFiles[iFile]);
915 if (rc2 != 0 && rc == 0)
916 rc = rc2;
917 }
918 }
919 }
920 else
921 rc = touch_error("gettimeofday failed: %s", strerror(errno));
922 free(Opts.papszFiles);
923 }
924 else
925 rc = touch_error("calloc failed");
926 return rc;
927}
928
Note: See TracBrowser for help on using the repository browser.