VirtualBox

source: kBuild/trunk/src/kmk/kmkbuiltin/touch.c@ 3248

Last change on this file since 3248 was 3248, checked in by bird, 5 years ago

kmkbuiltin: Darwin build fixes

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

© 2023 Oracle
ContactPrivacy policyTerms of Use