VirtualBox

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

Last change on this file since 3074 was 3074, checked in by bird, 7 years ago

touch: OS/2 build fix

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette