summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--parse.c180
-rwxr-xr-xtests.sh6
-rw-r--r--tests/test_newermt.out3
-rw-r--r--util.c54
-rw-r--r--util.h21
5 files changed, 261 insertions, 3 deletions
diff --git a/parse.c b/parse.c
index 9f9eabe..373f7c2 100644
--- a/parse.c
+++ b/parse.c
@@ -619,6 +619,10 @@ static const char *parse_int(const struct parser_state *state, const char *str,
case IF_LONG_LONG:
*(long long *)result = value;
break;
+
+ default:
+ assert(false);
+ break;
}
return endptr;
@@ -1615,7 +1619,7 @@ static struct expr *parse_lname(struct parser_state *state, int casefold, int ar
return parse_fnmatch(state, expr, casefold);
}
-/** Get the bfs_stat_field for X/Y in -newerXY */
+/** Get the bfs_stat_field for X/Y in -newerXY. */
static enum bfs_stat_field parse_newerxy_field(char c) {
switch (c) {
case 'a':
@@ -1631,6 +1635,175 @@ static enum bfs_stat_field parse_newerxy_field(char c) {
}
}
+/** Parse some digits from an explicit reference time. */
+static int parse_reftime_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 time. */
+static int parse_reftime(const struct parser_state *state, struct expr *expr) {
+ const char *str = expr->sdata;
+ struct tm tm = {0};
+
+ int tz_hour = 0;
+ int tz_min = 0;
+ bool tz_negative = false;
+ bool local = true;
+
+ // YYYY
+ if (parse_reftime_part(state, &str, 4, &tm.tm_year, -1900) != 0) {
+ goto invalid;
+ }
+
+ // MM
+ if (*str == '-') {
+ ++str;
+ }
+ if (parse_reftime_part(state, &str, 2, &tm.tm_mon, -1) != 0) {
+ goto invalid;
+ }
+
+ // DD
+ if (*str == '-') {
+ ++str;
+ }
+ if (parse_reftime_part(state, &str, 2, &tm.tm_mday, 0) != 0) {
+ goto invalid;
+ }
+
+ if (!*str) {
+ goto end;
+ } else if (*str == 'T') {
+ ++str;
+ }
+
+ // HH
+ if (parse_reftime_part(state, &str, 2, &tm.tm_hour, 0) != 0) {
+ goto invalid;
+ }
+
+ // MM
+ if (!*str) {
+ goto end;
+ } else if (*str == ':') {
+ ++str;
+ }
+ if (parse_reftime_part(state, &str, 2, &tm.tm_min, 0) != 0) {
+ goto invalid;
+ }
+
+ // SS
+ if (!*str) {
+ goto end;
+ } else if (*str == ':') {
+ ++str;
+ }
+ if (parse_reftime_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_reftime_part(state, &str, 2, &tz_hour, 0) != 0) {
+ goto invalid;
+ }
+
+ // MM
+ if (!*str) {
+ goto end;
+ } else if (*str == ':') {
+ ++str;
+ }
+ if (parse_reftime_part(state, &str, 2, &tz_min, 0) != 0) {
+ goto invalid;
+ }
+ } else {
+ goto invalid;
+ }
+
+ if (*str) {
+ goto invalid;
+ }
+
+end:
+ if (local) {
+ expr->reftime.tv_sec = mktime(&tm);
+ if (expr->reftime.tv_sec == -1) {
+ perror("mktime()");
+ return -1;
+ }
+ } else {
+ expr->reftime.tv_sec = xtimegm(&tm);
+ if (expr->reftime.tv_sec == -1) {
+ perror("xtimegm()");
+ return -1;
+ }
+
+ int offset = 60*tz_hour + tz_min;
+ if (tz_negative) {
+ offset = -offset;
+ }
+ expr->reftime.tv_sec += offset;
+ }
+
+ expr->reftime.tv_nsec = 0;
+ return 0;
+
+invalid:
+ parse_error(state, "%s %s: Invalid date/time.\n\n", expr->argv[0], expr->argv[1]);
+ fprintf(stderr, "Supported date/time formats are ISO 8601-like, e.g.\n\n");
+
+ if (xlocaltime(&state->now.tv_sec, &tm) != 0) {
+ perror("xlocaltime()");
+ return -1;
+ }
+
+ int year = tm.tm_year + 1900;
+ int month = tm.tm_mon + 1;
+ fprintf(stderr, " - %04d-%02d-%02d\n", year, month, tm.tm_mday);
+ fprintf(stderr, " - %04d-%02d-%02dT%02d:%02d:%02d\n", year, month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
+
+ tz_hour = -timezone/3600;
+ tz_min = (labs(timezone)/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);
+
+ if (xgmtime(&state->now.tv_sec, &tm) != 0) {
+ perror("xgmtime()");
+ return -1;
+ }
+
+ year = tm.tm_year + 1900;
+ month = tm.tm_mon + 1;
+ 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;
+}
+
/**
* Parse -newerXY.
*/
@@ -1653,8 +1826,9 @@ static struct expr *parse_newerxy(struct parser_state *state, int arg1, int arg2
}
if (arg[7] == 't') {
- parse_error(state, "%s: Explicit reference times ('t') are not supported.\n", arg);
- goto fail;
+ if (parse_reftime(state, expr) != 0) {
+ goto fail;
+ }
} else {
enum bfs_stat_field field = parse_newerxy_field(arg[7]);
if (!field) {
diff --git a/tests.sh b/tests.sh
index dac6f1b..fb73e4d 100755
--- a/tests.sh
+++ b/tests.sh
@@ -337,6 +337,7 @@ bsd_tests=(
test_H_newer
test_newerma
+ test_newermt
test_nogroup
test_nogroup_ulimit
@@ -485,6 +486,7 @@ gnu_tests=(
test_H_newer
test_newerma
+ test_newermt
test_nogroup
test_nogroup_ulimit
@@ -1349,6 +1351,10 @@ function test_newerma() {
bfs_diff times -newerma times/a
}
+function test_newermt() {
+ bfs_diff times -newermt 1991-12-14T00:01
+}
+
function test_size() {
bfs_diff basic -type f -size 0
}
diff --git a/tests/test_newermt.out b/tests/test_newermt.out
new file mode 100644
index 0000000..650e550
--- /dev/null
+++ b/tests/test_newermt.out
@@ -0,0 +1,3 @@
+times
+times/c
+times/l
diff --git a/util.c b/util.c
index 691402c..88f90aa 100644
--- a/util.c
+++ b/util.c
@@ -174,6 +174,60 @@ int xlocaltime(const time_t *timep, struct tm *result) {
}
}
+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;
+ }
+}
+
+time_t xtimegm(struct tm *tm) {
+ // Some man pages for timegm() recommend this as a portable approach
+ time_t 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 = mktime(tm);
+ 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 e7b844e..ef546d4 100644
--- a/util.h
+++ b/util.h
@@ -171,6 +171,27 @@ char *xregerror(int err, const regex_t *regex);
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);
+
+/**
+ * A portable timegm(), the inverse of gmtime().
+ *
+ * @param tm
+ * The struct tm to convert.
+ * @return
+ * The converted time on success, or -1 on failure.
+ */
+time_t xtimegm(struct tm *tm);
+
+/**
* Format a mode like ls -l (e.g. -rw-r--r--).
*
* @param mode