From 1ca8b4d9e81bfe7bed51fa6b858d59b721143054 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 13 Feb 2020 17:08:23 -0500 Subject: time: Split out time-related functions from util --- Makefile | 1 + eval.c | 1 + main.c | 1 + parse.c | 159 ++++------------------------------------ printf.c | 1 + time.c | 247 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ time.h | 86 ++++++++++++++++++++++ util.c | 86 ---------------------- util.h | 47 ------------ 9 files changed, 351 insertions(+), 278 deletions(-) create mode 100644 time.c create mode 100644 time.h diff --git a/Makefile b/Makefile index f25fd12..d6183ab 100644 --- a/Makefile +++ b/Makefile @@ -89,6 +89,7 @@ bfs: \ printf.o \ spawn.o \ stat.o \ + time.o \ trie.o \ typo.o \ util.o diff --git a/eval.c b/eval.c index ab885fc..23c7b1b 100644 --- a/eval.c +++ b/eval.c @@ -30,6 +30,7 @@ #include "mtab.h" #include "printf.h" #include "stat.h" +#include "time.h" #include "trie.h" #include "util.h" #include diff --git a/main.c b/main.c index abab187..b8d7bae 100644 --- a/main.c +++ b/main.c @@ -42,6 +42,7 @@ * - mtab.[ch] (parses the system's mount table) * - spawn.[ch] (spawns processes) * - stat.[ch] (wraps stat(), or statx() on Linux) + * - time.[ch] (date/time handling utilities) * - trie.[ch] (a trie set/map implementation) * - typo.[ch] (fuzzy matching for typos) * - util.[ch] (everything else) diff --git a/parse.c b/parse.c index ada0ec2..509d835 100644 --- a/parse.c +++ b/parse.c @@ -34,6 +34,7 @@ #include "printf.h" #include "spawn.h" #include "stat.h" +#include "time.h" #include "typo.h" #include "util.h" #include @@ -1635,147 +1636,19 @@ static enum bfs_stat_field parse_newerxy_field(char c) { } } -/** Parse some digits from an explicit reference time. */ -static int parse_timestamp_part(const struct parser_state *state, const char **str, size_t n, int *result, int delta) { - char buf[n + 1]; - for (size_t i = 0; i < n; ++i, ++*str) { - char c = **str; - if (c < '0' || c > '9') { - return -1; - } - buf[i] = c; - } - buf[n] = '\0'; - - if (!parse_int(state, buf, result, IF_INT | IF_QUIET)) { - return -1; - } - - *result += delta; - return 0; -} - /** Parse an explicit reference timestamp for -newerXt and -*since. */ -static int parse_timestamp(const struct parser_state *state, struct expr *expr) { - const char *str = expr->sdata; - struct tm tm = { - .tm_isdst = -1, - }; - - int tz_hour = 0; - int tz_min = 0; - bool tz_negative = false; - bool local = true; - - // YYYY - if (parse_timestamp_part(state, &str, 4, &tm.tm_year, -1900) != 0) { - goto invalid; - } - - // MM - if (*str == '-') { - ++str; - } - if (parse_timestamp_part(state, &str, 2, &tm.tm_mon, -1) != 0) { - goto invalid; - } - - // DD - if (*str == '-') { - ++str; - } - if (parse_timestamp_part(state, &str, 2, &tm.tm_mday, 0) != 0) { - goto invalid; - } - - if (!*str) { - goto end; - } else if (*str == 'T') { - ++str; - } - - // hh - if (parse_timestamp_part(state, &str, 2, &tm.tm_hour, 0) != 0) { - goto invalid; - } - - // mm - if (!*str) { - goto end; - } else if (*str == ':') { - ++str; - } - if (parse_timestamp_part(state, &str, 2, &tm.tm_min, 0) != 0) { - goto invalid; - } - - // ss - if (!*str) { - goto end; - } else if (*str == ':') { - ++str; - } - if (parse_timestamp_part(state, &str, 2, &tm.tm_sec, 0) != 0) { - goto invalid; - } - - if (!*str) { - goto end; - } else if (*str == 'Z') { - local = false; - ++str; - } else if (*str == '+' || *str == '-') { - local = false; - tz_negative = *str == '-'; - ++str; - - // hh - if (parse_timestamp_part(state, &str, 2, &tz_hour, 0) != 0) { - goto invalid; - } - - // mm - if (!*str) { - goto end; - } else if (*str == ':') { - ++str; - } - if (parse_timestamp_part(state, &str, 2, &tz_min, 0) != 0) { - goto invalid; - } - } else { - goto invalid; - } - - if (*str) { - goto invalid; - } - -end: - if (local) { - if (xmktime(&tm, &expr->reftime.tv_sec) != 0) { - goto error; - } - } else { - if (xtimegm(&tm, &expr->reftime.tv_sec) != 0) { - goto error; - } - - int offset = 60*tz_hour + tz_min; - if (tz_negative) { - expr->reftime.tv_sec -= offset; - } else { - expr->reftime.tv_sec += offset; - } +static int parse_reftime(const struct parser_state *state, struct expr *expr) { + if (parse_timestamp(expr->sdata, &expr->reftime) == 0) { + return 0; + } else if (errno != EINVAL) { + parse_error(state, "%s %s: %m.\n", expr->argv[0], expr->argv[1]); + return -1; } - expr->reftime.tv_nsec = 0; - return 0; - -invalid: parse_error(state, "%s %s: Invalid timestamp.\n\n", expr->argv[0], expr->argv[1]); fprintf(stderr, "Supported timestamp formats are ISO 8601-like, e.g.\n\n"); + struct tm tm; if (xlocaltime(&state->now.tv_sec, &tm) != 0) { perror("xlocaltime()"); return -1; @@ -1787,12 +1660,12 @@ invalid: fprintf(stderr, " - %04d-%02d-%02dT%02d:%02d:%02d\n", year, month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); #if __FreeBSD__ - tz_hour = tm.tm_gmtoff/3600; - tz_min = (labs(tm.tm_gmtoff)/60)%60; + int gmtoff = tm.tm_gmtoff; #else - tz_hour = -timezone/3600; - tz_min = (labs(timezone)/60)%60; + int gmtoff = -timezone; #endif + int tz_hour = gmtoff/3600; + int tz_min = (labs(gmtoff)/60)%60; fprintf(stderr, " - %04d-%02d-%02dT%02d:%02d:%02d%+03d:%02d\n", year, month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tz_hour, tz_min); @@ -1806,10 +1679,6 @@ invalid: fprintf(stderr, " - %04d-%02d-%02dT%02d:%02d:%02dZ\n", year, month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); return -1; - -error: - parse_error(state, "%s %s: Error parsing timestamp: %m.\n", expr->argv[0], expr->argv[1]); - return -1; } /** @@ -1834,7 +1703,7 @@ static struct expr *parse_newerxy(struct parser_state *state, int arg1, int arg2 } if (arg[7] == 't') { - if (parse_timestamp(state, expr) != 0) { + if (parse_reftime(state, expr) != 0) { goto fail; } } else { @@ -2408,7 +2277,7 @@ static struct expr *parse_since(struct parser_state *state, int field, int arg2) return NULL; } - if (parse_timestamp(state, expr) != 0) { + if (parse_reftime(state, expr) != 0) { goto fail; } diff --git a/printf.c b/printf.c index 80f54c9..4e7af18 100644 --- a/printf.c +++ b/printf.c @@ -22,6 +22,7 @@ #include "expr.h" #include "mtab.h" #include "stat.h" +#include "time.h" #include "util.h" #include #include diff --git a/time.c b/time.c new file mode 100644 index 0000000..3575abd --- /dev/null +++ b/time.c @@ -0,0 +1,247 @@ +/**************************************************************************** + * bfs * + * Copyright (C) 2020 Tavian Barnes * + * * + * Permission to use, copy, modify, and/or distribute this software for any * + * purpose with or without fee is hereby granted. * + * * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * + ****************************************************************************/ + +#include "time.h" +#include +#include +#include +#include +#include + +int xlocaltime(const time_t *timep, struct tm *result) { + // Should be called before localtime_r() according to POSIX.1-2004 + tzset(); + + if (localtime_r(timep, result)) { + return 0; + } else { + return -1; + } +} + +int xgmtime(const time_t *timep, struct tm *result) { + // Should be called before gmtime_r() according to POSIX.1-2004 + tzset(); + + if (gmtime_r(timep, result)) { + return 0; + } else { + return -1; + } +} + +int xmktime(struct tm *tm, time_t *timep) { + *timep = mktime(tm); + + if (*timep == -1) { + int error = errno; + + struct tm tmp; + if (xlocaltime(timep, &tmp) != 0) { + return -1; + } + + if (tm->tm_year != tmp.tm_year || tm->tm_yday != tmp.tm_yday + || tm->tm_hour != tmp.tm_hour || tm->tm_min != tmp.tm_min || tm->tm_sec != tmp.tm_sec) { + errno = error; + return -1; + } + } + + return 0; +} + +int xtimegm(struct tm *tm, time_t *timep) { + // Some man pages for timegm() recommend this as a portable approach + int ret = -1; + int error; + + char *old_tz = getenv("TZ"); + if (old_tz) { + old_tz = strdup(old_tz); + if (!old_tz) { + error = errno; + goto fail; + } + } + + if (setenv("TZ", "UTC0", true) != 0) { + error = errno; + goto fail; + } + + ret = xmktime(tm, timep); + error = errno; + + if (old_tz) { + if (setenv("TZ", old_tz, true) != 0) { + ret = -1; + error = errno; + goto fail; + } + } else { + if (unsetenv("TZ") != 0) { + ret = -1; + error = errno; + goto fail; + } + } + + tzset(); +fail: + free(old_tz); + errno = error; + return ret; +} + +/** Parse some digits from a timestamp. */ +static int parse_timestamp_part(const char **str, size_t n, int *result) { + char buf[n + 1]; + for (size_t i = 0; i < n; ++i, ++*str) { + char c = **str; + if (c < '0' || c > '9') { + return -1; + } + buf[i] = c; + } + buf[n] = '\0'; + + *result = atoi(buf); + return 0; +} + +int parse_timestamp(const char *str, struct timespec *result) { + struct tm tm = { + .tm_isdst = -1, + }; + + int tz_hour = 0; + int tz_min = 0; + bool tz_negative = false; + bool local = true; + + // YYYY + if (parse_timestamp_part(&str, 4, &tm.tm_year) != 0) { + goto invalid; + } + tm.tm_year -= 1900; + + // MM + if (*str == '-') { + ++str; + } + if (parse_timestamp_part(&str, 2, &tm.tm_mon) != 0) { + goto invalid; + } + tm.tm_mon -= 1; + + // DD + if (*str == '-') { + ++str; + } + if (parse_timestamp_part(&str, 2, &tm.tm_mday) != 0) { + goto invalid; + } + + if (!*str) { + goto end; + } else if (*str == 'T') { + ++str; + } + + // hh + if (parse_timestamp_part(&str, 2, &tm.tm_hour) != 0) { + goto invalid; + } + + // mm + if (!*str) { + goto end; + } else if (*str == ':') { + ++str; + } + if (parse_timestamp_part(&str, 2, &tm.tm_min) != 0) { + goto invalid; + } + + // ss + if (!*str) { + goto end; + } else if (*str == ':') { + ++str; + } + if (parse_timestamp_part(&str, 2, &tm.tm_sec) != 0) { + goto invalid; + } + + if (!*str) { + goto end; + } else if (*str == 'Z') { + local = false; + ++str; + } else if (*str == '+' || *str == '-') { + local = false; + tz_negative = *str == '-'; + ++str; + + // hh + if (parse_timestamp_part(&str, 2, &tz_hour) != 0) { + goto invalid; + } + + // mm + if (!*str) { + goto end; + } else if (*str == ':') { + ++str; + } + if (parse_timestamp_part(&str, 2, &tz_min) != 0) { + goto invalid; + } + } else { + goto invalid; + } + + if (*str) { + goto invalid; + } + +end: + if (local) { + if (xmktime(&tm, &result->tv_sec) != 0) { + goto error; + } + } else { + if (xtimegm(&tm, &result->tv_sec) != 0) { + goto error; + } + + int offset = 60*tz_hour + tz_min; + if (tz_negative) { + result->tv_sec -= offset; + } else { + result->tv_sec += offset; + } + } + + result->tv_nsec = 0; + return 0; + +invalid: + errno = EINVAL; +error: + return -1; +} diff --git a/time.h b/time.h new file mode 100644 index 0000000..0f9adb4 --- /dev/null +++ b/time.h @@ -0,0 +1,86 @@ +/**************************************************************************** + * bfs * + * Copyright (C) 2020 Tavian Barnes * + * * + * Permission to use, copy, modify, and/or distribute this software for any * + * purpose with or without fee is hereby granted. * + * * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * + ****************************************************************************/ + +/** + * Date/time handling. + */ + +#ifndef BFS_TIME_H +#define BFS_TIME_H + +#include + +/** + * localtime_r() wrapper that calls tzset() first. + * + * @param[in] timep + * The time_t to convert. + * @param[out] result + * Buffer to hold the result. + * @return + * 0 on success, -1 on failure. + */ +int xlocaltime(const time_t *timep, struct tm *result); + +/** + * gmtime_r() wrapper that calls tzset() first. + * + * @param[in] timep + * The time_t to convert. + * @param[out] result + * Buffer to hold the result. + * @return + * 0 on success, -1 on failure. + */ +int xgmtime(const time_t *timep, struct tm *result); + +/** + * mktime() wrapper that reports errors more reliably. + * + * @param[in,out] tm + * The struct tm to convert. + * @param[out] timep + * A pointer to the result. + * @return + * 0 on success, -1 on failure. + */ +int xmktime(struct tm *tm, time_t *timep); + +/** + * A portable timegm(), the inverse of gmtime(). + * + * @param[in,out] tm + * The struct tm to convert. + * @param[out] timep + * A pointer to the result. + * @return + * 0 on success, -1 on failure. + */ +int xtimegm(struct tm *tm, time_t *timep); + +/** + * Parse an ISO 8601-style timestamp. + * + * @param[in] str + * The string to parse. + * @param[out] result + * A pointer to the result. + * @return + * 0 on success, -1 on failure. + */ +int parse_timestamp(const char *str, struct timespec *result); + +#endif // BFS_TIME_H diff --git a/util.c b/util.c index 6af7d27..ec019ce 100644 --- a/util.c +++ b/util.c @@ -163,92 +163,6 @@ char *xregerror(int err, const regex_t *regex) { return str; } -int xlocaltime(const time_t *timep, struct tm *result) { - // Should be called before localtime_r() according to POSIX.1-2004 - tzset(); - - if (localtime_r(timep, result)) { - return 0; - } else { - return -1; - } -} - -int xgmtime(const time_t *timep, struct tm *result) { - // Should be called before gmtime_r() according to POSIX.1-2004 - tzset(); - - if (gmtime_r(timep, result)) { - return 0; - } else { - return -1; - } -} - -int xmktime(struct tm *tm, time_t *timep) { - *timep = mktime(tm); - - if (*timep == -1) { - int error = errno; - - struct tm tmp; - if (xlocaltime(timep, &tmp) != 0) { - return -1; - } - - if (tm->tm_year != tmp.tm_year || tm->tm_yday != tmp.tm_yday - || tm->tm_hour != tmp.tm_hour || tm->tm_min != tmp.tm_min || tm->tm_sec != tmp.tm_sec) { - errno = error; - return -1; - } - } - - return 0; -} - -int xtimegm(struct tm *tm, time_t *timep) { - // Some man pages for timegm() recommend this as a portable approach - int ret = -1; - int error; - - char *old_tz = getenv("TZ"); - if (old_tz) { - old_tz = strdup(old_tz); - if (!old_tz) { - error = errno; - goto fail; - } - } - - if (setenv("TZ", "UTC0", true) != 0) { - error = errno; - goto fail; - } - - ret = xmktime(tm, timep); - error = errno; - - if (old_tz) { - if (setenv("TZ", old_tz, true) != 0) { - ret = -1; - error = errno; - goto fail; - } - } else { - if (unsetenv("TZ") != 0) { - ret = -1; - error = errno; - goto fail; - } - } - - tzset(); -fail: - free(old_tz); - errno = error; - return ret; -} - void format_mode(mode_t mode, char str[11]) { strcpy(str, "----------"); diff --git a/util.h b/util.h index ae12703..6e7c1f2 100644 --- a/util.h +++ b/util.h @@ -27,7 +27,6 @@ #include #include #include -#include // Some portability concerns @@ -159,52 +158,6 @@ int pipe_cloexec(int pipefd[2]); */ char *xregerror(int err, const regex_t *regex); -/** - * localtime_r() wrapper that calls tzset() first. - * - * @param timep - * The time_t to convert. - * @param result - * Buffer to hold the result. - * @return 0 on success, -1 on failure. - */ -int xlocaltime(const time_t *timep, struct tm *result); - -/** - * gmtime_r() wrapper that calls tzset() first. - * - * @param timep - * The time_t to convert. - * @param result - * Buffer to hold the result. - * @return 0 on success, -1 on failure. - */ -int xgmtime(const time_t *timep, struct tm *result); - -/** - * mktime() wrapper that reports errors more reliably. - * - * @param[in,out] tm - * The struct tm to convert. - * @param[out] timep - * Where to store the result. - * @return - * 0 on success, -1 on failure. - */ -int xmktime(struct tm *tm, time_t *timep); - -/** - * A portable timegm(), the inverse of gmtime(). - * - * @param[in,out] tm - * The struct tm to convert. - * @param[out] timep - * Where to store the result. - * @return - * 0 on success, -1 on failure. - */ -int xtimegm(struct tm *tm, time_t *timep); - /** * Format a mode like ls -l (e.g. -rw-r--r--). * -- cgit v1.2.3