From 29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:21:29 +0200 Subject: Adding upstream version 1:0.1.9998svn3589+dfsg. Signed-off-by: Daniel Baumann --- src/kmk/kmkbuiltin/touch.c | 952 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 952 insertions(+) create mode 100644 src/kmk/kmkbuiltin/touch.c (limited to 'src/kmk/kmkbuiltin/touch.c') diff --git a/src/kmk/kmkbuiltin/touch.c b/src/kmk/kmkbuiltin/touch.c new file mode 100644 index 0000000..046b0d3 --- /dev/null +++ b/src/kmk/kmkbuiltin/touch.c @@ -0,0 +1,952 @@ +/* $Id: touch.c 3282 2019-01-05 00:57:52Z bird $ */ +/** @file + * kmk_touch - Simple touch implementation. + */ + +/* + * Copyright (c) 2017 knut st. osmundsen + * + * This file is part of kBuild. + * + * kBuild is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * kBuild is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with kBuild. If not, see + * + */ + +/******************************************************************************* +* Header Files * +*******************************************************************************/ +#include "makeint.h" +#include +#include +#include +#include +#include +#include +#include +#if defined(_MSC_VER) +# include +# include +# include +#else +# include +#endif + +#include +#include +#include "err.h" +#include "kbuild_version.h" +#include "kmkbuiltin.h" + +#ifdef _MSC_VER +# include "nt/ntstat.h" +# undef FILE_TIMESTAMP_HI_RES +# define FILE_TIMESTAMP_HI_RES 1 +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The program name to use in message. */ +#ifdef KMK +# define TOUCH_NAME "kmk_builtin_touch" +#else +# define TOUCH_NAME "kmk_touch" +#endif +/** Converts a two digit decimal field to a number. */ +#define TWO_CHARS_TO_INT(chHigh, chLow) ( ((unsigned)(chHigh) - (unsigned)'0') * 10 + ((unsigned)(chLow) - (unsigned)'0') ) +/** Checks an alleged digit. */ +#define IS_DIGIT(chDigit, uMax) ( ((unsigned)(chDigit) - (unsigned)'0') <= (unsigned)(uMax) ) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef enum KMKTOUCHTARGET +{ + kTouchAccessAndModify, + kTouchAccessOnly, + kTouchModifyOnly +} KMKTOUCHTARGET; + +typedef enum KMKTOUCHACTION +{ + kTouchActionCurrent, + kTouchActionSet, + kTouchActionAdjust +} KMKTOUCHACTION; + + +typedef struct KMKTOUCHOPTS +{ + /** Command execution context. */ + PKMKBUILTINCTX pCtx; + /** What timestamps to modify on the files. */ + KMKTOUCHTARGET enmWhatToTouch; + /** How to update the time. */ + KMKTOUCHACTION enmAction; + /** Whether to create files (K_TRUE) or ignore missing (K_FALSE). */ + KBOOL fCreate; + /** Whether to dereference files. */ + KBOOL fDereference; + /** The new access time value. */ + struct timeval NewATime; + /** The new modified time value. */ + struct timeval NewMTime; + + /** Number of files. */ + int cFiles; + /** The specified files. */ + char **papszFiles; +} KMKTOUCHOPTS; +typedef KMKTOUCHOPTS *PKMKTOUCHOPTS; + + +static int touch_usage(void) +{ + fputs("Usage: " TOUCH_NAME " [options] [MMDDhhmm[YY]] [.. [fileN]]\n" + "\n" + "Options:\n" + " -A [-][[hh]mm]SS, --adjust=[-][[hh]mm]SS\n" + " Adjust timestamps by given delta.\n" + " -a, --time=atime, --time=access\n" + " Only change the accessed timestamp.\n" + " -c, --no-create\n" + " Ignore missing files and don't create them. (default create empty file)\n" + " -d YYYY-MM-DDThh:mm:SS[.frac][tz], --date=YYYY-MM-DDThh:mm:SS[.frac][tz]\n" + " Set the timestamps to the given one.\n" + " -f\n" + " Ignored for compatbility reasons.\n" + " -h, --no-dereference\n" + " Don't follow links, touch links. (Not applicable to -r.)\n" + " -m, --time=mtime, --time=modify\n" + " Only changed the modified timestamp.\n" + " -r , --reference=\n" + " Take the timestamps from .\n" + " -t [[CC]YY]MMDDhhmm[.SS]\n" + " Set the timestamps to the given one.\n" + "\n" + "Note. For compatibility reasons the first file can be taken to be a 8 or 10\n" + " character long timestamp if it matches the given pattern and none of\n" + " the -A, -d, --date, -r, --reference, or -t options are given. So, use\n" + " absolute or relative paths when specifying more than one file.\n" + , stdout); + return 0; +} + + +#if K_OS == K_OS_SOLARIS +/** + * Solaris doesn't have lutimes because System V doesn't believe in stuff like file modes on symbolic links. + */ +static int lutimes(const char *pszFile, struct timeval aTimes[2]) +{ + struct stat Stat; + if (stat(pszFile, &Stat) != -1) + { + if (!S_ISLNK(Stat.st_mode)) + return utimes(pszFile, aTimes); + return 0; + } + return -1; +} +#endif + + +/** + * Parses adjustment value: [-][[hh]mm]SS + */ +static int touch_parse_adjust(PKMKBUILTINCTX pCtx, const char *pszValue, int *piAdjustValue) +{ + const char * const pszInValue = pszValue; + size_t cchValue = strlen(pszValue); + KBOOL fNegative = K_FALSE; + + /* Deal with negativity */ + if (pszValue[0] == '-') + { + fNegative = K_TRUE; + pszValue++; + cchValue--; + } + + /* Validate and convert. */ + *piAdjustValue = 0; + switch (cchValue) + { + case 6: + if ( !IS_DIGIT(pszValue[0], 9) + || !IS_DIGIT(pszValue[0], 9)) + return errx(pCtx, 2, "Malformed hour part of -A value: %s", pszInValue); + *piAdjustValue = TWO_CHARS_TO_INT(pszValue[0], pszValue[1]) * 60 * 60; + /* fall thru */ + case 4: + if ( !IS_DIGIT(pszValue[cchValue - 4], 9) /* don't bother limit to 60 minutes */ + || !IS_DIGIT(pszValue[cchValue - 3], 9)) + return errx(pCtx, 2, "Malformed minute part of -A value: %s", pszInValue); + *piAdjustValue += TWO_CHARS_TO_INT(pszValue[cchValue - 4], pszValue[cchValue - 3]) * 60; + /* fall thru */ + case 2: + if ( !IS_DIGIT(pszValue[cchValue - 2], 9) /* don't bother limit to 60 seconds */ + || !IS_DIGIT(pszValue[cchValue - 1], 9)) + return errx(pCtx, 2, "Malformed second part of -A value: %s", pszInValue); + *piAdjustValue += TWO_CHARS_TO_INT(pszValue[cchValue - 2], pszValue[cchValue - 1]); + break; + + default: + return errx(pCtx, 2, "Invalid -A value (length): %s", pszInValue); + } + + /* Apply negativity. */ + if (fNegative) + *piAdjustValue = -*piAdjustValue; + + return 0; +} + + +/** + * Parse -d timestamp: YYYY-MM-DDThh:mm:SS[.frac][tz] + */ +static int touch_parse_d_ts(PKMKBUILTINCTX pCtx, const char *pszTs, struct timeval *pDst) +{ + const char * const pszTsIn = pszTs; + struct tm ExpTime; + + /* + * Validate and parse the timestamp into the tm structure. + */ + memset(&ExpTime, 0, sizeof(ExpTime)); + + /* year */ + if ( !IS_DIGIT(pszTs[0], 9) + || !IS_DIGIT(pszTs[1], 9) + || !IS_DIGIT(pszTs[2], 9) + || !IS_DIGIT(pszTs[3], 9) + || pszTs[4] != '-') + return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: expected to start with 4 digit year followed by a dash", + pszTsIn); + ExpTime.tm_year = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]) * 100 + + TWO_CHARS_TO_INT(pszTs[2], pszTs[3]) + - 1900; + pszTs += 5; + + /* month */ + if ( !IS_DIGIT(pszTs[0], 1) + || !IS_DIGIT(pszTs[1], 9) + || pszTs[2] != '-') + return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: expected to two digit month at position 6 followed by a dash", + pszTsIn); + ExpTime.tm_mon = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]) - 1; + pszTs += 3; + + /* day */ + if ( !IS_DIGIT(pszTs[0], 3) + || !IS_DIGIT(pszTs[1], 9) + || (pszTs[2] != 'T' && pszTs[2] != 't' && pszTs[2] != ' ') ) + 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", + pszTsIn); + ExpTime.tm_mday = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]); + pszTs += 3; + + /* hour */ + if ( !IS_DIGIT(pszTs[0], 2) + || !IS_DIGIT(pszTs[1], 9) + || pszTs[2] != ':') + return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: expected to two digit hour at position 12 followed by colon", + pszTsIn); + ExpTime.tm_hour = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]); + pszTs += 3; + + /* minute */ + if ( !IS_DIGIT(pszTs[0], 5) + || !IS_DIGIT(pszTs[1], 9) + || pszTs[2] != ':') + return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: expected to two digit minute at position 15 followed by colon", + pszTsIn); + ExpTime.tm_min = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]); + pszTs += 3; + + /* seconds */ + if ( !IS_DIGIT(pszTs[0], 5) + || !IS_DIGIT(pszTs[1], 9)) + return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: expected to two digit seconds at position 12", pszTsIn); + ExpTime.tm_sec = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]); + pszTs += 2; + + /* fraction */ + pDst->tv_usec = 0; + if (*pszTs == '.' || *pszTs == ',') + { + int iFactor; + + pszTs++; + if (!IS_DIGIT(*pszTs, 9)) + return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: empty fraction", pszTsIn); + + iFactor = 100000; + do + { + pDst->tv_usec += ((unsigned)*pszTs - (unsigned)'0') * iFactor; + iFactor /= 10; + pszTs++; + } while (IS_DIGIT(*pszTs, 9)); + } + + /* zulu time indicator */ + ExpTime.tm_isdst = -1; + if (*pszTs == 'Z' || *pszTs == 'z') + { + ExpTime.tm_isdst = 0; + pszTs++; + if (*pszTs != '\0') + return errx(pCtx, 2, + "Malformed timestamp '%s' given to -d: Unexpected character(s) after zulu time indicator at end of timestamp", + pszTsIn); + } + else if (*pszTs != '\0') + return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: expected to 'Z' (zulu) or nothing at end of timestamp", pszTsIn); + + /* + * Convert to UTC seconds using either timegm or mktime. + */ + ExpTime.tm_yday = -1; + ExpTime.tm_wday = -1; + if (ExpTime.tm_isdst == 0) + { +#if K_OS == K_OS_SOLARIS || K_OS == K_OS_OS2 + pDst->tv_sec = mktime(&ExpTime) - timezone; /* best we can do for now */ +#else + pDst->tv_sec = timegm(&ExpTime); +#endif + if (pDst->tv_sec == -1) + return errx(pCtx, 1, "timegm failed on '%s': %s", pszTs, strerror(errno)); + } + else + { + pDst->tv_sec = mktime(&ExpTime); + if (pDst->tv_sec == -1) + return errx(pCtx, 1, "mktime failed on '%s': %s", pszTs, strerror(errno)); + } + return 0; +} + + +/** + * Parse -t timestamp: [[CC]YY]MMDDhhmm[.SS] + */ +static int touch_parse_ts(PKMKBUILTINCTX pCtx, const char *pszTs, struct timeval *pDst) +{ + size_t const cchTs = strlen(pszTs); + size_t cchTsNoSec; + struct tm ExpTime; + struct tm *pExpTime; + struct timeval Now; + int rc; + + /* + * Do some input validations first. + */ + if ((cchTs & 1) && pszTs[cchTs - 3] != '.') + return errx(pCtx, 2, "Invalid timestamp given to -t: %s", pszTs); + switch (cchTs) + { + case 8: /* MMDDhhmm */ + case 8 + 2: /* YYMMDDhhmm */ + case 8 + 2 + 2: /* CCYYMMDDhhmm */ + cchTsNoSec = cchTs; + break; + case 8 + 3: /* MMDDhhmm.SS */ + case 8 + 3 + 2: /* YYMMDDhhmm.SS */ + case 8 + 3 + 2 + 2: /* CCYYMMDDhhmm.SS */ + if (pszTs[cchTs - 3] != '.') + return errx(pCtx, 2, "Invalid timestamp (-t) '%s': missing dot for seconds part", pszTs); + if ( !IS_DIGIT(pszTs[cchTs - 2], 5) + || !IS_DIGIT(pszTs[cchTs - 1], 9)) + return errx(pCtx, 2, "Invalid timestamp (-t) '%s': malformed seconds part", pszTs); + cchTsNoSec = cchTs - 3; + break; + default: + return errx(pCtx, 1, "Invalid timestamp (-t) '%s': wrong length (%d)", pszTs, (int)cchTs); + } + + switch (cchTsNoSec) + { + case 8 + 2 + 2: /* CCYYMMDDhhmm */ + if ( !IS_DIGIT(pszTs[cchTsNoSec - 12], 9) + || !IS_DIGIT(pszTs[cchTsNoSec - 11], 9)) + return errx(pCtx, 2, "Invalid timestamp (-t) '%s': malformed CC part", pszTs); + /* fall thru */ + case 8 + 2: /* YYMMDDhhmm */ + if ( !IS_DIGIT(pszTs[cchTsNoSec - 10], 9) + || !IS_DIGIT(pszTs[cchTsNoSec - 9], 9)) + return errx(pCtx, 2, "Invalid timestamp (-t) '%s': malformed YY part", pszTs); + /* fall thru */ + case 8: /* MMDDhhmm */ + if ( !IS_DIGIT(pszTs[cchTsNoSec - 8], 1) + || !IS_DIGIT(pszTs[cchTsNoSec - 7], 9) ) + return errx(pCtx, 2, "Invalid timestamp (-t) '%s': malformed month part", pszTs); + if ( !IS_DIGIT(pszTs[cchTsNoSec - 6], 3) + || !IS_DIGIT(pszTs[cchTsNoSec - 5], 9) ) + return errx(pCtx, 2, "Invalid timestamp (-t) '%s': malformed day part", pszTs); + if ( !IS_DIGIT(pszTs[cchTsNoSec - 4], 2) + || !IS_DIGIT(pszTs[cchTsNoSec - 3], 9) ) + return errx(pCtx, 2, "Invalid timestamp (-t) '%s': malformed hour part", pszTs); + if ( !IS_DIGIT(pszTs[cchTsNoSec - 2], 5) + || !IS_DIGIT(pszTs[cchTsNoSec - 1], 9) ) + return errx(pCtx, 2, "Invalid timestamp (-t) '%s': malformed minute part", pszTs); + break; + } + + /* + * Get the current time and explode it. + */ + rc = gettimeofday(&Now, NULL); + if (rc != 0) + return errx(pCtx, 1, "gettimeofday failed: %s", strerror(errno)); + + pExpTime = localtime_r(&Now.tv_sec, &ExpTime); + if (pExpTime == NULL) + return errx(pCtx, 1, "localtime_r failed: %s", strerror(errno)); + + /* + * Do the decoding. + */ + if (cchTs & 1) + pExpTime->tm_sec = TWO_CHARS_TO_INT(pszTs[cchTs - 2], pszTs[cchTs - 1]); + else + pExpTime->tm_sec = 0; + + if (cchTsNoSec == 8 + 2 + 2) /* CCYY */ + pExpTime->tm_year = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]) * 100 + + TWO_CHARS_TO_INT(pszTs[2], pszTs[3]) + - 1900; + else if (cchTsNoSec == 8 + 2) /* YY */ + { + pExpTime->tm_year = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]); + if (pExpTime->tm_year < 69) + pExpTime->tm_year += 100; + } + + pExpTime->tm_mon = TWO_CHARS_TO_INT(pszTs[cchTsNoSec - 8], pszTs[cchTsNoSec - 7]) - 1; + pExpTime->tm_mday = TWO_CHARS_TO_INT(pszTs[cchTsNoSec - 6], pszTs[cchTsNoSec - 5]); + pExpTime->tm_hour = TWO_CHARS_TO_INT(pszTs[cchTsNoSec - 4], pszTs[cchTsNoSec - 3]); + pExpTime->tm_min = TWO_CHARS_TO_INT(pszTs[cchTsNoSec - 2], pszTs[cchTsNoSec - 1]); + + /* + * Use mktime to convert to UTC seconds. + */ + pExpTime->tm_isdst = -1; + pExpTime->tm_yday = -1; + pExpTime->tm_wday = -1; + pDst->tv_usec = 0; + pDst->tv_sec = mktime(pExpTime); + if (pDst->tv_sec != -1) + return 0; + return errx(pCtx, 1, "mktime failed on '%s': %s", pszTs, strerror(errno)); +} + + +/** + * Check for old timestamp: MMDDhhmm[YY] + */ +static int touch_parse_old_ts(PKMKBUILTINCTX pCtx, const char *pszOldTs, time_t Now, struct timeval *pDst) +{ + /* + * Check if this is a valid timestamp. + */ + size_t const cchOldTs = strlen(pszOldTs); + if ( ( cchOldTs == 8 + || cchOldTs == 10) + && IS_DIGIT(pszOldTs[0], 1) + && IS_DIGIT(pszOldTs[1], 9) + && IS_DIGIT(pszOldTs[2], 3) + && IS_DIGIT(pszOldTs[3], 9) + && IS_DIGIT(pszOldTs[4], 2) + && IS_DIGIT(pszOldTs[5], 9) + && IS_DIGIT(pszOldTs[6], 5) + && IS_DIGIT(pszOldTs[7], 9) + && ( cchOldTs == 8 + || ( IS_DIGIT(pszOldTs[8], 9) + && IS_DIGIT(pszOldTs[9], 9) )) ) + { + /* + * Explode the current time as local. + */ + struct tm ExpTime; + struct tm *pExpTime; + pExpTime = localtime_r(&Now, &ExpTime); + if (pExpTime == NULL) + return errx(pCtx, 1, "localtime_r failed: %s", strerror(errno)); + + /* + * Decode the bits we've got. + */ + pExpTime->tm_mon = TWO_CHARS_TO_INT(pszOldTs[0], pszOldTs[1]) - 1; + pExpTime->tm_mday = TWO_CHARS_TO_INT(pszOldTs[2], pszOldTs[3]); + pExpTime->tm_hour = TWO_CHARS_TO_INT(pszOldTs[4], pszOldTs[5]); + pExpTime->tm_min = TWO_CHARS_TO_INT(pszOldTs[6], pszOldTs[7]); + if (cchOldTs == 10) + { + pExpTime->tm_year = TWO_CHARS_TO_INT(pszOldTs[8], pszOldTs[9]); + if (pExpTime->tm_year <= 38) /* up to 2038, 32-bit time_t style logic. */ + pExpTime->tm_year += 100; + } + + /* + * Use mktime to convert to UTC seconds. + */ + pExpTime->tm_isdst = -1; + pExpTime->tm_yday = -1; + pExpTime->tm_wday = -1; + pDst->tv_usec = 0; + pDst->tv_sec = mktime(pExpTime); + if (pDst->tv_sec != -1) + return 0; + return errx(pCtx, 1, "mktime failed on '%s': %s", pszOldTs, strerror(errno)); + } + + /* No valid timestamp present. */ + return -1; +} + + +/** + * Parses the arguments into pThis. + * + * @returns exit code. + * @param pThis Options structure to return the parsed info in. + * Caller initalizes this with defaults. + * @param cArgs The number of arguments. + * @param papszArgs The arguments. + * @param pfExit Indicates whether to exit or to start processing + * files. + */ +static int touch_parse_args(PKMKTOUCHOPTS pThis, int cArgs, char **papszArgs, KBOOL *pfExit) +{ + int iAdjustValue = 0; + int iArg; + int rc; + + *pfExit = K_TRUE; + /* + * Parse arguments, skipping all files (GNU style). + */ + for (iArg = 1; iArg < cArgs; iArg++) + { + const char *pszArg = papszArgs[iArg]; + if (*pszArg == '-') + { + const char *pszLongValue = NULL; + char ch = *++pszArg; + pszArg++; + + /* + * Deal with long options first, preferably translating them into short ones. + */ + if (ch == '-') + { + const char *pszLong = pszArg; + ch = *pszArg++; + if (!ch) + { + while (++iArg < cArgs) + pThis->papszFiles[pThis->cFiles++] = papszArgs[iArg]; + break; /* '--' */ + } + + /* Translate long options. */ + pszArg = ""; + if (strcmp(pszLong, "adjust") == 0) + ch = 'A'; + else if (strncmp(pszLong, "adjust=", sizeof("adjust=") - 1) == 0) + { + ch = 'A'; + pszLongValue = pszArg = pszLong + sizeof("adjust=") - 1; + } + else if (strcmp(pszLong, "no-create") == 0) + ch = 'c'; + else if (strcmp(pszLong, "date") == 0) + ch = 'd'; + else if (strncmp(pszLong, "date=", sizeof("date=") - 1) == 0) + { + ch = 'd'; + pszLongValue = pszArg = pszLong + sizeof("date=") - 1; + } + else if (strcmp(pszLong, "no-dereference") == 0) + ch = 'h'; + else if (strcmp(pszLong, "reference") == 0) + ch = 'r'; + else if (strncmp(pszLong, "reference=", sizeof("reference=") - 1) == 0) + { + ch = 'r'; + pszLongValue = pszArg = pszLong + sizeof("reference=") - 1; + } + else if (strcmp(pszLong, "time") == 0) + ch = 'T'; + else if (strncmp(pszLong, "time=", sizeof("time=") - 1) == 0) + { + ch = 'T'; + pszLongValue = pszArg = pszLong + sizeof("time=") - 1; + } + else if (strcmp(pszLong, "help") == 0) + return touch_usage(); + else if (strcmp(pszLong, "version") == 0) + return kbuild_version(papszArgs[0]); + else + return errx(pThis->pCtx, 2, "Unknown option: --%s", pszLong); + } + + /* + * Process short options. + */ + do + { + /* Some options takes a value. */ + const char *pszValue; + switch (ch) + { + case 'A': + case 'd': + case 'r': + case 't': + case 'T': + if (*pszArg || pszLongValue) + { + pszValue = pszArg; + pszArg = ""; + } + else if (iArg + 1 < cArgs) + pszValue = papszArgs[++iArg]; + else + return errx(pThis->pCtx, 2, "Option -%c requires a value", ch); + break; + + default: + pszValue = NULL; + break; + } + + switch (ch) + { + /* -A [-][[HH]MM]SS */ + case 'A': + rc = touch_parse_adjust(pThis->pCtx, pszValue, &iAdjustValue); + if (rc != 0) + return rc; + if (pThis->enmAction != kTouchActionSet) + { + pThis->enmAction = kTouchActionAdjust; + pThis->NewATime.tv_sec = iAdjustValue; + pThis->NewATime.tv_usec = 0; + pThis->NewMTime = pThis->NewATime; + } + /* else: applied after parsing everything. */ + break; + + case 'a': + pThis->enmWhatToTouch = kTouchAccessOnly; + break; + + case 'c': + pThis->fCreate = K_FALSE; + break; + + case 'd': + rc = touch_parse_d_ts(pThis->pCtx, pszValue, &pThis->NewATime); + if (rc != 0) + return rc; + pThis->enmAction = kTouchActionSet; + pThis->NewMTime = pThis->NewATime; + break; + + case 'f': + /* some historical thing, ignored. */ + break; + + case 'h': + pThis->fDereference = K_FALSE; + break; + + case 'm': + pThis->enmWhatToTouch = kTouchModifyOnly; + break; + + case 'r': + { + struct stat St; + if (stat(pszValue, &St) != 0) + return errx(pThis->pCtx, 1, "Failed to stat '%s' (-r option): %s", pszValue, strerror(errno)); + + pThis->enmAction = kTouchActionSet; + pThis->NewATime.tv_sec = St.st_atime; + pThis->NewMTime.tv_sec = St.st_mtime; +#if FILE_TIMESTAMP_HI_RES + pThis->NewATime.tv_usec = St.ST_ATIM_NSEC / 1000; + pThis->NewMTime.tv_usec = St.ST_MTIM_NSEC / 1000; +#else + pThis->NewATime.tv_usec = 0; + pThis->NewMTime.tv_usec = 0; +#endif + break; + } + + case 't': + rc = touch_parse_ts(pThis->pCtx, pszValue, &pThis->NewATime); + if (rc != 0) + return rc; + pThis->enmAction = kTouchActionSet; + pThis->NewMTime = pThis->NewATime; + break; + + case 'T': + if ( strcmp(pszValue, "atime") == 0 + || strcmp(pszValue, "access") == 0) + pThis->enmWhatToTouch = kTouchAccessOnly; + else if ( strcmp(pszValue, "mtime") == 0 + || strcmp(pszValue, "modify") == 0) + pThis->enmWhatToTouch = kTouchModifyOnly; + else + return errx(pThis->pCtx, 2, "Unknown --time value: %s", pszValue); + break; + + case 'V': + return kbuild_version(papszArgs[0]); + + default: + return errx(pThis->pCtx, 2, "Unknown option: -%c (%c%s)", ch, ch, pszArg); + } + + } while ((ch = *pszArg++) != '\0'); + } + else + pThis->papszFiles[pThis->cFiles++] = papszArgs[iArg]; + } + + /* + * Allow adjusting specified timestamps too like BSD does. + */ + if ( pThis->enmAction == kTouchActionSet + && iAdjustValue != 0) + { + if ( pThis->enmWhatToTouch == kTouchAccessAndModify + || pThis->enmWhatToTouch == kTouchAccessOnly) + pThis->NewATime.tv_sec += iAdjustValue; + if ( pThis->enmWhatToTouch == kTouchAccessAndModify + || pThis->enmWhatToTouch == kTouchModifyOnly) + pThis->NewMTime.tv_sec += iAdjustValue; + } + + /* + * Check for old timestamp: MMDDhhmm[YY] + */ + if ( pThis->enmAction == kTouchActionCurrent + && pThis->cFiles >= 2) + { + struct timeval OldTs; + rc = touch_parse_old_ts(pThis->pCtx, pThis->papszFiles[0], pThis->NewATime.tv_sec, &OldTs); + if (rc == 0) + { + int iFile; + + pThis->NewATime = OldTs; + pThis->NewMTime = OldTs; + pThis->enmAction = kTouchActionSet; + + for (iFile = 1; iFile < pThis->cFiles; iFile++) + pThis->papszFiles[iFile - 1] = pThis->papszFiles[iFile]; + pThis->cFiles--; + } + else if (rc > 0) + return rc; + } + + /* + * Check that we've found at least one file argument. + */ + if (pThis->cFiles > 0) + { + *pfExit = K_FALSE; + return 0; + } + return errx(pThis->pCtx, 2, "No file specified"); +} + + +/** + * Touches one file. + * + * @returns exit code. + * @param pThis The options. + * @param pszFile The file to touch. + */ +static int touch_process_file(PKMKTOUCHOPTS pThis, const char *pszFile) +{ + int fd; + int rc; + struct stat St; + struct timeval aTimes[2]; + + /* + * Create the file if it doesn't exists. If the --no-create/-c option is + * in effect, we silently skip the file if it doesn't already exist. + */ + if (pThis->fDereference) + rc = stat(pszFile, &St); + else + rc = lstat(pszFile, &St); + if (rc != 0) + { + if (errno != ENOENT) + return errx(pThis->pCtx, 1, "Failed to stat '%s': %s", pszFile, strerror(errno)); + + if (!pThis->fCreate) + return 0; + fd = open(pszFile, O_WRONLY | O_CREAT | KMK_OPEN_NO_INHERIT, 0666); + if (fd == -1) + return errx(pThis->pCtx, 1, "Failed to create '%s': %s", pszFile, strerror(errno)); + + /* If we're not setting the current time, we may need value stat info + on the file, so get it thru the file descriptor before closing it. */ + if (pThis->enmAction == kTouchActionCurrent) + rc = 0; + else + rc = fstat(fd, &St); + if (close(fd) != 0) + return errx(pThis->pCtx, 1, "Failed to close '%s' after creation: %s", pszFile, strerror(errno)); + if (rc != 0) + return errx(pThis->pCtx, 1, "Failed to fstat '%s' after creation: %s", pszFile, strerror(errno)); + + /* We're done now if we're setting the current time. */ + if (pThis->enmAction == kTouchActionCurrent) + return 0; + } + + /* + * Create aTimes and call utimes/lutimes. + */ + aTimes[0].tv_sec = St.st_atime; + aTimes[1].tv_sec = St.st_mtime; +#if FILE_TIMESTAMP_HI_RES + aTimes[0].tv_usec = St.ST_ATIM_NSEC / 1000; + aTimes[1].tv_usec = St.ST_MTIM_NSEC / 1000; +#else + aTimes[0].tv_usec = 0; + aTimes[1].tv_usec = 0; +#endif + if ( pThis->enmWhatToTouch == kTouchAccessAndModify + || pThis->enmWhatToTouch == kTouchAccessOnly) + { + if ( pThis->enmAction == kTouchActionCurrent + || pThis->enmAction == kTouchActionSet) + aTimes[0] = pThis->NewATime; + else + aTimes[0].tv_sec += pThis->NewATime.tv_sec; + } + if ( pThis->enmWhatToTouch == kTouchAccessAndModify + || pThis->enmWhatToTouch == kTouchModifyOnly) + { + if ( pThis->enmAction == kTouchActionCurrent + || pThis->enmAction == kTouchActionSet) + aTimes[1] = pThis->NewMTime; + else + aTimes[1].tv_sec += pThis->NewMTime.tv_sec; + } + + /* + * Try set the times. If we're setting current time, fall back on calling + * [l]utimes with a NULL timeval vector since that has slightly different + * permissions checks. (Note that we don't do that by default because it + * may do more than what we want (st_ctime).) + */ + if (pThis->fDereference) + rc = utimes(pszFile, aTimes); + else + rc = lutimes(pszFile, aTimes); + if (rc != 0) + { + if (pThis->enmAction == kTouchActionCurrent) + { + if (pThis->fDereference) + rc = utimes(pszFile, NULL); + else + rc = lutimes(pszFile, NULL); + } + if (rc != 0) + rc = errx(pThis->pCtx, 1, "%stimes failed on '%s': %s", pThis->fDereference ? "" : "l", pszFile, strerror(errno)); + } + + return rc; +} + + +/** + * Actual main function for the touch command. + */ +int kmk_builtin_touch(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx) +{ + int rc; + KMKTOUCHOPTS This; + K_NOREF(envp); + + /* + * Initialize options with defaults and parse them. + */ + This.pCtx = pCtx; + This.enmWhatToTouch = kTouchAccessAndModify; + This.enmAction = kTouchActionCurrent; + This.fCreate = K_TRUE; + This.fDereference = K_TRUE; + This.cFiles = 0; + This.papszFiles = (char **)calloc(argc, sizeof(char *)); + if (This.papszFiles) + { + rc = gettimeofday(&This.NewATime, NULL); + if (rc == 0) + { + KBOOL fExit; + This.NewMTime = This.NewATime; + + rc = touch_parse_args(&This, argc, argv, &fExit); + if (rc == 0 && !fExit) + { + /* + * Process the files. + */ + int iFile; + for (iFile = 0; iFile < This.cFiles; iFile++) + { + int rc2 = touch_process_file(&This, This.papszFiles[iFile]); + if (rc2 != 0 && rc == 0) + rc = rc2; + } + } + } + else + rc = errx(pCtx, 2, "gettimeofday failed: %s", strerror(errno)); + free(This.papszFiles); + } + else + rc = errx(pCtx, 2, "calloc failed"); + return rc; +} + +#ifdef KMK_BUILTIN_STANDALONE +int main(int argc, char **argv, char **envp) +{ + KMKBUILTINCTX Ctx = { "kmk_touch", NULL }; + return kmk_builtin_touch(argc, argv, envp, &Ctx); +} +#endif + -- cgit v1.2.3