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

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

kmk,lib: ported kmk_touch to windows (nt).

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