From 95f862fdd82a99e30bbf2c43009ec9a51e416804 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 13 Feb 2020 16:30:05 -0500 Subject: parse: Handle 1969-12-31T23:59:59Z mktime() returns -1 on error, but also for one second before the epoch. Compare the input against localtime(-1) to distinguish those cases. --- parse.c | 27 ++++++++++++++------------- tests.sh | 6 ++++++ tests/test_newermt_epoch_minus_one.out | 5 +++++ util.c | 27 ++++++++++++++++++++++++--- util.h | 20 +++++++++++++++++--- 5 files changed, 66 insertions(+), 19 deletions(-) create mode 100644 tests/test_newermt_epoch_minus_one.out diff --git a/parse.c b/parse.c index 6ffe8e7..ada0ec2 100644 --- a/parse.c +++ b/parse.c @@ -1129,9 +1129,9 @@ static struct expr *parse_daystart(struct parser_state *state, int arg1, int arg tm.tm_min = 0; tm.tm_sec = 0; - time_t time = mktime(&tm); - if (time == -1) { - perror("mktime()"); + time_t time; + if (xmktime(&tm, &time) != 0) { + perror("xmktime()"); return NULL; } @@ -1753,23 +1753,20 @@ static int parse_timestamp(const struct parser_state *state, struct expr *expr) end: if (local) { - expr->reftime.tv_sec = mktime(&tm); - if (expr->reftime.tv_sec == -1) { - perror("mktime()"); - return -1; + if (xmktime(&tm, &expr->reftime.tv_sec) != 0) { + goto error; } } else { - expr->reftime.tv_sec = xtimegm(&tm); - if (expr->reftime.tv_sec == -1) { - perror("xtimegm()"); - return -1; + if (xtimegm(&tm, &expr->reftime.tv_sec) != 0) { + goto error; } int offset = 60*tz_hour + tz_min; if (tz_negative) { - offset = -offset; + expr->reftime.tv_sec -= offset; + } else { + expr->reftime.tv_sec += offset; } - expr->reftime.tv_sec += offset; } expr->reftime.tv_nsec = 0; @@ -1809,6 +1806,10 @@ 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; } /** diff --git a/tests.sh b/tests.sh index 3893251..d1c5413 100755 --- a/tests.sh +++ b/tests.sh @@ -343,6 +343,7 @@ bsd_tests=( test_newerma test_newermt + test_newermt_epoch_minus_one test_nogroup test_nogroup_ulimit @@ -492,6 +493,7 @@ gnu_tests=( test_newerma test_newermt + test_newermt_epoch_minus_one test_nogroup test_nogroup_ulimit @@ -1364,6 +1366,10 @@ function test_newermt() { bfs_diff times -newermt 1991-12-14T00:01 } +function test_newermt_epoch_minus_one() { + bfs_diff times -newermt 1969-12-31T23:59:59Z +} + function test_size() { bfs_diff basic -type f -size 0 } diff --git a/tests/test_newermt_epoch_minus_one.out b/tests/test_newermt_epoch_minus_one.out new file mode 100644 index 0000000..f7f63b0 --- /dev/null +++ b/tests/test_newermt_epoch_minus_one.out @@ -0,0 +1,5 @@ +times +times/a +times/b +times/c +times/l diff --git a/util.c b/util.c index 88f90aa..6af7d27 100644 --- a/util.c +++ b/util.c @@ -185,9 +185,30 @@ int xgmtime(const time_t *timep, struct tm *result) { } } -time_t xtimegm(struct tm *tm) { +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 - time_t ret = -1; + int ret = -1; int error; char *old_tz = getenv("TZ"); @@ -204,7 +225,7 @@ time_t xtimegm(struct tm *tm) { goto fail; } - ret = mktime(tm); + ret = xmktime(tm, timep); error = errno; if (old_tz) { diff --git a/util.h b/util.h index ef546d4..ae12703 100644 --- a/util.h +++ b/util.h @@ -181,15 +181,29 @@ int xlocaltime(const time_t *timep, struct tm *result); */ 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 tm + * @param[in,out] tm * The struct tm to convert. + * @param[out] timep + * Where to store the result. * @return - * The converted time on success, or -1 on failure. + * 0 on success, -1 on failure. */ -time_t xtimegm(struct tm *tm); +int xtimegm(struct tm *tm, time_t *timep); /** * Format a mode like ls -l (e.g. -rw-r--r--). -- cgit v1.2.3