VirtualBox

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

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

kmk_touch: Drop nsec stat timestamp kludge.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use