diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/basic/calendarspec.c | 43 | ||||
-rw-r--r-- | src/basic/calendarspec.h | 1 | ||||
-rw-r--r-- | src/basic/time-util.c | 146 | ||||
-rw-r--r-- | src/basic/time-util.h | 4 | ||||
-rw-r--r-- | src/shared/logs-show.c | 183 | ||||
-rw-r--r-- | src/shared/output-mode.c | 1 | ||||
-rw-r--r-- | src/shared/output-mode.h | 1 | ||||
-rw-r--r-- | src/systemctl/systemctl.c | 2 | ||||
-rw-r--r-- | src/test/test-calendarspec.c | 23 | ||||
-rw-r--r-- | src/test/test-time.c | 44 |
10 files changed, 350 insertions, 98 deletions
diff --git a/src/basic/calendarspec.c b/src/basic/calendarspec.c index e4cfab364e..fda293fcb9 100644 --- a/src/basic/calendarspec.c +++ b/src/basic/calendarspec.c @@ -302,6 +302,17 @@ int calendar_spec_to_string(const CalendarSpec *c, char **p) { if (c->utc) fputs(" UTC", f); + else if (IN_SET(c->dst, 0, 1)) { + + /* If daylight saving is explicitly on or off, let's show the used timezone. */ + + tzset(); + + if (!isempty(tzname[c->dst])) { + fputc(' ', f); + fputs(tzname[c->dst], f); + } + } r = fflush_and_check(f); if (r < 0) { @@ -747,9 +758,9 @@ fail: } int calendar_spec_from_string(const char *p, CalendarSpec **spec) { + const char *utc; CalendarSpec *c; int r; - const char *utc; assert(p); assert(spec); @@ -760,11 +771,39 @@ int calendar_spec_from_string(const char *p, CalendarSpec **spec) { c = new0(CalendarSpec, 1); if (!c) return -ENOMEM; + c->dst = -1; utc = endswith_no_case(p, " UTC"); if (utc) { c->utc = true; p = strndupa(p, utc - p); + } else { + const char *e = NULL; + int j; + + tzset(); + + /* Check if the local timezone was specified? */ + for (j = 0; j <= 1; j++) { + if (isempty(tzname[j])) + continue; + + e = endswith_no_case(p, tzname[j]); + if(!e) + continue; + if (e == p) + continue; + if (e[-1] != ' ') + continue; + + break; + } + + /* Found one of the two timezones specified? */ + if (IN_SET(j, 0, 1)) { + p = strndupa(p, e - p - 1); + c->dst = j; + } } if (strcaseeq(p, "minutely")) { @@ -1017,7 +1056,7 @@ static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { for (;;) { /* Normalize the current date */ (void) mktime_or_timegm(&c, spec->utc); - c.tm_isdst = -1; + c.tm_isdst = spec->dst; c.tm_year += 1900; r = find_matching_component(spec->year, &c.tm_year); diff --git a/src/basic/calendarspec.h b/src/basic/calendarspec.h index f6472c1244..c6087228fd 100644 --- a/src/basic/calendarspec.h +++ b/src/basic/calendarspec.h @@ -37,6 +37,7 @@ typedef struct CalendarComponent { typedef struct CalendarSpec { int weekdays_bits; bool utc; + int dst; CalendarComponent *year; CalendarComponent *month; diff --git a/src/basic/time-util.c b/src/basic/time-util.c index 24e681bf85..0ef1f6393e 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -254,32 +254,95 @@ struct timeval *timeval_store(struct timeval *tv, usec_t u) { return tv; } -static char *format_timestamp_internal(char *buf, size_t l, usec_t t, - bool utc, bool us) { +static char *format_timestamp_internal( + char *buf, + size_t l, + usec_t t, + bool utc, + bool us) { + + /* The weekdays in non-localized (English) form. We use this instead of the localized form, so that our + * generated timestamps may be parsed with parse_timestamp(), and always read the same. */ + static const char * const weekdays[] = { + [0] = "Sun", + [1] = "Mon", + [2] = "Tue", + [3] = "Wed", + [4] = "Thu", + [5] = "Fri", + [6] = "Sat", + }; + struct tm tm; time_t sec; - int k; + size_t n; assert(buf); - assert(l > 0); + if (l < + 3 + /* week day */ + 1 + 10 + /* space and date */ + 1 + 8 + /* space and time */ + (us ? 1 + 6 : 0) + /* "." and microsecond part */ + 1 + 1 + /* space and shortest possible zone */ + 1) + return NULL; /* Not enough space even for the shortest form. */ if (t <= 0 || t == USEC_INFINITY) + return NULL; /* Timestamp is unset */ + + sec = (time_t) (t / USEC_PER_SEC); /* Round down */ + if ((usec_t) sec != (t / USEC_PER_SEC)) + return NULL; /* overflow? */ + + if (!localtime_or_gmtime_r(&sec, &tm, utc)) return NULL; - sec = (time_t) (t / USEC_PER_SEC); - localtime_or_gmtime_r(&sec, &tm, utc); + /* Start with the week day */ + assert((size_t) tm.tm_wday < ELEMENTSOF(weekdays)); + memcpy(buf, weekdays[tm.tm_wday], 4); - if (us) - k = strftime(buf, l, "%a %Y-%m-%d %H:%M:%S", &tm); - else - k = strftime(buf, l, "%a %Y-%m-%d %H:%M:%S %Z", &tm); + /* Add the main components */ + if (strftime(buf + 3, l - 3, " %Y-%m-%d %H:%M:%S", &tm) <= 0) + return NULL; /* Doesn't fit */ - if (k <= 0) - return NULL; + /* Append the microseconds part, if that's requested */ if (us) { - snprintf(buf + strlen(buf), l - strlen(buf), ".%06llu", (unsigned long long) (t % USEC_PER_SEC)); - if (strftime(buf + strlen(buf), l - strlen(buf), " %Z", &tm) <= 0) - return NULL; + n = strlen(buf); + if (n + 8 > l) + return NULL; /* Microseconds part doesn't fit. */ + + sprintf(buf + n, ".%06llu", (unsigned long long) (t % USEC_PER_SEC)); + } + + /* Append the timezone */ + n = strlen(buf); + if (utc) { + /* If this is UTC then let's explicitly use the "UTC" string here, because gmtime_r() normally uses the + * obsolete "GMT" instead. */ + if (n + 5 > l) + return NULL; /* "UTC" doesn't fit. */ + + strcpy(buf + n, " UTC"); + + } else if (!isempty(tm.tm_zone)) { + size_t tn; + + /* An explicit timezone is specified, let's use it, if it fits */ + tn = strlen(tm.tm_zone); + if (n + 1 + tn + 1 > l) { + /* The full time zone does not fit in. Yuck. */ + + if (n + 1 + _POSIX_TZNAME_MAX + 1 > l) + return NULL; /* Not even enough space for the POSIX minimum (of 6)? In that case, complain that it doesn't fit */ + + /* So the time zone doesn't fit in fully, but the caller passed enough space for the POSIX + * minimum time zone length. In this case suppress the timezone entirely, in order not to dump + * an overly long, hard to read string on the user. This should be safe, because the user will + * assume the local timezone anyway if none is shown. And so does parse_timestamp(). */ + } else { + buf[n++] = ' '; + strcpy(buf + n, tm.tm_zone); + } } return buf; @@ -539,12 +602,11 @@ int parse_timestamp(const char *t, usec_t *usec) { { "Sat", 6 }, }; - const char *k; - const char *utc; + const char *k, *utc, *tzn = NULL; struct tm tm, copy; time_t x; usec_t x_usec, plus = 0, minus = 0, ret; - int r, weekday = -1; + int r, weekday = -1, dst = -1; unsigned i; /* @@ -609,15 +671,55 @@ int parse_timestamp(const char *t, usec_t *usec) { goto finish; } + /* See if the timestamp is suffixed with UTC */ utc = endswith_no_case(t, " UTC"); if (utc) t = strndupa(t, utc - t); + else { + const char *e = NULL; + int j; + + tzset(); + + /* See if the timestamp is suffixed by either the DST or non-DST local timezone. Note that we only + * support the local timezones here, nothing else. Not because we wouldn't want to, but simply because + * there are no nice APIs available to cover this. By accepting the local time zone strings, we make + * sure that all timestamps written by format_timestamp() can be parsed correctly, even though we don't + * support arbitrary timezone specifications. */ + + for (j = 0; j <= 1; j++) { + + if (isempty(tzname[j])) + continue; + + e = endswith_no_case(t, tzname[j]); + if (!e) + continue; + if (e == t) + continue; + if (e[-1] != ' ') + continue; + + break; + } - x = ret / USEC_PER_SEC; + if (IN_SET(j, 0, 1)) { + /* Found one of the two timezones specified. */ + t = strndupa(t, e - t - 1); + dst = j; + tzn = tzname[j]; + } + } + + x = (time_t) (ret / USEC_PER_SEC); x_usec = 0; - assert_se(localtime_or_gmtime_r(&x, &tm, utc)); - tm.tm_isdst = -1; + if (!localtime_or_gmtime_r(&x, &tm, utc)) + return -EINVAL; + + tm.tm_isdst = dst; + if (tzn) + tm.tm_zone = tzn; if (streq(t, "today")) { tm.tm_sec = tm.tm_min = tm.tm_hour = 0; @@ -634,7 +736,6 @@ int parse_timestamp(const char *t, usec_t *usec) { goto from_tm; } - for (i = 0; i < ELEMENTSOF(day_nr); i++) { size_t skip; @@ -727,7 +828,6 @@ parse_usec: return -EINVAL; x_usec = add; - } from_tm: diff --git a/src/basic/time-util.h b/src/basic/time-util.h index 1b058f0e49..99be5ce6ee 100644 --- a/src/basic/time-util.h +++ b/src/basic/time-util.h @@ -68,7 +68,9 @@ typedef struct triple_timestamp { #define USEC_PER_YEAR ((usec_t) (31557600ULL*USEC_PER_SEC)) #define NSEC_PER_YEAR ((nsec_t) (31557600ULL*NSEC_PER_SEC)) -#define FORMAT_TIMESTAMP_MAX ((4*4+1)+11+9+4+1) /* weekdays can be unicode */ +/* We assume a maximum timezone length of 6. TZNAME_MAX is not defined on Linux, but glibc internally initializes this + * to 6. Let's rely on that. */ +#define FORMAT_TIMESTAMP_MAX (3+1+10+1+8+1+6+1+6+1) #define FORMAT_TIMESTAMP_WIDTH 28 /* when outputting, assume this width */ #define FORMAT_TIMESTAMP_RELATIVE_MAX 256 #define FORMAT_TIMESPAN_MAX 64 diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index d04728f505..f9d9c4ed62 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -45,6 +45,7 @@ #include "parse-util.h" #include "process-util.h" #include "sparse-endian.h" +#include "stdio-util.h" #include "string-table.h" #include "string-util.h" #include "terminal-util.h" @@ -206,6 +207,108 @@ static bool print_multiline(FILE *f, unsigned prefix, unsigned n_columns, Output return ellipsized; } +static int output_timestamp_monotonic(FILE *f, sd_journal *j, const char *monotonic) { + sd_id128_t boot_id; + uint64_t t; + int r; + + assert(f); + assert(j); + + r = -ENXIO; + if (monotonic) + r = safe_atou64(monotonic, &t); + if (r < 0) + r = sd_journal_get_monotonic_usec(j, &t, &boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get monotonic timestamp: %m"); + + fprintf(f, "[%5llu.%06llu]", + (unsigned long long) (t / USEC_PER_SEC), + (unsigned long long) (t % USEC_PER_SEC)); + + return 1 + 5 + 1 + 6 + 1; +} + +static int output_timestamp_realtime(FILE *f, sd_journal *j, OutputMode mode, OutputFlags flags, const char *realtime) { + char buf[MAX(FORMAT_TIMESTAMP_MAX, 64)]; + struct tm *(*gettime_r)(const time_t *, struct tm *); + struct tm tm; + uint64_t x; + time_t t; + int r; + + assert(f); + assert(j); + + r = -ENXIO; + if (realtime) + r = safe_atou64(realtime, &x); + if (r < 0) + r = sd_journal_get_realtime_usec(j, &x); + if (r < 0) + return log_error_errno(r, "Failed to get realtime timestamp: %m"); + + if (mode == OUTPUT_SHORT_FULL) { + const char *k; + + if (flags & OUTPUT_UTC) + k = format_timestamp_utc(buf, sizeof(buf), x); + else + k = format_timestamp(buf, sizeof(buf), x); + if (!k) { + log_error("Failed to format timestamp."); + return -EINVAL; + } + + } else { + gettime_r = (flags & OUTPUT_UTC) ? gmtime_r : localtime_r; + t = (time_t) (x / USEC_PER_SEC); + + switch (mode) { + + case OUTPUT_SHORT_UNIX: + xsprintf(buf, "%10llu.%06llu", (unsigned long long) t, (unsigned long long) (x % USEC_PER_SEC)); + break; + + case OUTPUT_SHORT_ISO: + if (strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", gettime_r(&t, &tm)) <= 0) { + log_error("Failed for format ISO time"); + return -EINVAL; + } + break; + + case OUTPUT_SHORT: + case OUTPUT_SHORT_PRECISE: + + if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm)) <= 0) { + log_error("Failed to format syslog time"); + return -EINVAL; + } + + if (mode == OUTPUT_SHORT_PRECISE) { + size_t k; + + assert(sizeof(buf) > strlen(buf)); + k = sizeof(buf) - strlen(buf); + + r = snprintf(buf + strlen(buf), k, ".%06llu", (unsigned long long) (x % USEC_PER_SEC)); + if (r <= 0 || (size_t) r >= k) { /* too long? */ + log_error("Failed to format precise time"); + return -EINVAL; + } + } + break; + + default: + assert_not_reached("Unknown time format"); + } + } + + fputs(buf, f); + return (int) strlen(buf); +} + static int output_short( FILE *f, sd_journal *j, @@ -305,78 +408,15 @@ static int output_short( if (priority_len == 1 && *priority >= '0' && *priority <= '7') p = *priority - '0'; - if (mode == OUTPUT_SHORT_MONOTONIC) { - uint64_t t; - sd_id128_t boot_id; - - r = -ENOENT; - - if (monotonic) - r = safe_atou64(monotonic, &t); - - if (r < 0) - r = sd_journal_get_monotonic_usec(j, &t, &boot_id); - - if (r < 0) - return log_error_errno(r, "Failed to get monotonic timestamp: %m"); - - fprintf(f, "[%5llu.%06llu]", - (unsigned long long) (t / USEC_PER_SEC), - (unsigned long long) (t % USEC_PER_SEC)); - - n += 1 + 5 + 1 + 6 + 1; - - } else { - char buf[64]; - uint64_t x; - time_t t; - struct tm tm; - struct tm *(*gettime_r)(const time_t *, struct tm *); - - r = -ENOENT; - gettime_r = (flags & OUTPUT_UTC) ? gmtime_r : localtime_r; - - if (realtime) - r = safe_atou64(realtime, &x); - - if (r < 0) - r = sd_journal_get_realtime_usec(j, &x); - - if (r < 0) - return log_error_errno(r, "Failed to get realtime timestamp: %m"); - - t = (time_t) (x / USEC_PER_SEC); - - switch (mode) { - - case OUTPUT_SHORT_UNIX: - r = snprintf(buf, sizeof(buf), "%10llu.%06llu", (unsigned long long) t, (unsigned long long) (x % USEC_PER_SEC)); - break; - - case OUTPUT_SHORT_ISO: - r = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S%z", gettime_r(&t, &tm)); - break; - - case OUTPUT_SHORT_PRECISE: - r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm)); - if (r > 0) - snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), ".%06llu", (unsigned long long) (x % USEC_PER_SEC)); - break; - - default: - r = strftime(buf, sizeof(buf), "%b %d %H:%M:%S", gettime_r(&t, &tm)); - } - - if (r <= 0) { - log_error("Failed to format time."); - return -EINVAL; - } - - fputs(buf, f); - n += strlen(buf); - } + if (mode == OUTPUT_SHORT_MONOTONIC) + r = output_timestamp_monotonic(f, j, monotonic); + else + r = output_timestamp_realtime(f, j, mode, flags, realtime); + if (r < 0) + return r; + n += r; - if (hostname && (flags & OUTPUT_NO_HOSTNAME)) { + if (flags & OUTPUT_NO_HOSTNAME) { /* Suppress display of the hostname if this is requested. */ hostname = NULL; hostname_len = 0; @@ -910,6 +950,7 @@ static int (*output_funcs[_OUTPUT_MODE_MAX])( [OUTPUT_SHORT_PRECISE] = output_short, [OUTPUT_SHORT_MONOTONIC] = output_short, [OUTPUT_SHORT_UNIX] = output_short, + [OUTPUT_SHORT_FULL] = output_short, [OUTPUT_VERBOSE] = output_verbose, [OUTPUT_EXPORT] = output_export, [OUTPUT_JSON] = output_json, diff --git a/src/shared/output-mode.c b/src/shared/output-mode.c index bec53ee0ae..67d8208ad2 100644 --- a/src/shared/output-mode.c +++ b/src/shared/output-mode.c @@ -22,6 +22,7 @@ static const char *const output_mode_table[_OUTPUT_MODE_MAX] = { [OUTPUT_SHORT] = "short", + [OUTPUT_SHORT_FULL] = "short-full", [OUTPUT_SHORT_ISO] = "short-iso", [OUTPUT_SHORT_PRECISE] = "short-precise", [OUTPUT_SHORT_MONOTONIC] = "short-monotonic", diff --git a/src/shared/output-mode.h b/src/shared/output-mode.h index f37189e57f..ff29dafcb5 100644 --- a/src/shared/output-mode.h +++ b/src/shared/output-mode.h @@ -23,6 +23,7 @@ typedef enum OutputMode { OUTPUT_SHORT, + OUTPUT_SHORT_FULL, OUTPUT_SHORT_ISO, OUTPUT_SHORT_PRECISE, OUTPUT_SHORT_MONOTONIC, diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 782824ff38..4fa7adfc41 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -4767,7 +4767,7 @@ static int show_one( else if (streq(verb, "status")) { print_status_info(bus, &info, ellipsized); - if (info.active_state && STR_IN_SET(info.active_state, "inactive", "failed")) + if (info.active_state && !STR_IN_SET(info.active_state, "active", "reloading")) r = EXIT_PROGRAM_NOT_RUNNING; else r = EXIT_PROGRAM_RUNNING_OR_SERVICE_OK; diff --git a/src/test/test-calendarspec.c b/src/test/test-calendarspec.c index 4a2b93de59..57d9da4855 100644 --- a/src/test/test-calendarspec.c +++ b/src/test/test-calendarspec.c @@ -88,6 +88,27 @@ static void test_next(const char *input, const char *new_tz, usec_t after, usec_ tzset(); } +static void test_timestamp(void) { + char buf[FORMAT_TIMESTAMP_MAX]; + _cleanup_free_ char *t = NULL; + CalendarSpec *c; + usec_t x, y; + + /* Ensure that a timestamp is also a valid calendar specification. Convert forth and back */ + + x = now(CLOCK_REALTIME); + + assert_se(format_timestamp_us(buf, sizeof(buf), x)); + printf("%s\n", buf); + assert_se(calendar_spec_from_string(buf, &c) >= 0); + assert_se(calendar_spec_to_string(c, &t) >= 0); + calendar_spec_free(c); + printf("%s\n", t); + + assert_se(parse_timestamp(t, &y) >= 0); + assert_se(y == x); +} + int main(int argc, char* argv[]) { CalendarSpec *c; @@ -155,5 +176,7 @@ int main(int argc, char* argv[]) { assert_se(calendar_spec_from_string("00:00:00/0.00000001", &c) < 0); assert_se(calendar_spec_from_string("00:00:00.0..00.9", &c) < 0); + test_timestamp(); + return 0; } diff --git a/src/test/test-time.c b/src/test/test-time.c index ee7d55c5ab..7078a0374d 100644 --- a/src/test/test-time.c +++ b/src/test/test-time.c @@ -19,6 +19,7 @@ #include "strv.h" #include "time-util.h" +#include "random-util.h" static void test_parse_sec(void) { usec_t u; @@ -201,6 +202,48 @@ static void test_usec_sub(void) { assert_se(usec_sub(USEC_INFINITY, 5) == USEC_INFINITY); } +static void test_format_timestamp(void) { + unsigned i; + + for (i = 0; i < 100; i++) { + char buf[MAX(FORMAT_TIMESTAMP_MAX, FORMAT_TIMESPAN_MAX)]; + usec_t x, y; + + random_bytes(&x, sizeof(x)); + x = x % (2147483600 * USEC_PER_SEC) + 1; + + assert_se(format_timestamp(buf, sizeof(buf), x)); + log_info("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC); + + assert_se(format_timestamp_utc(buf, sizeof(buf), x)); + log_info("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC); + + assert_se(format_timestamp_us(buf, sizeof(buf), x)); + log_info("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(x == y); + + assert_se(format_timestamp_us_utc(buf, sizeof(buf), x)); + log_info("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + assert_se(x == y); + + assert_se(format_timestamp_relative(buf, sizeof(buf), x)); + log_info("%s", buf); + assert_se(parse_timestamp(buf, &y) >= 0); + + /* The two calls above will run with a slightly different local time. Make sure we are in the same + * range however, but give enough leeway that this is unlikely to explode. And of course, + * format_timestamp_relative() scales the accuracy with the distance from the current time up to one + * month, cover for that too. */ + assert_se(y > x ? y - x : x - y <= USEC_PER_MONTH + USEC_PER_DAY); + } +} + int main(int argc, char *argv[]) { uintmax_t x; @@ -214,6 +257,7 @@ int main(int argc, char *argv[]) { test_get_timezones(); test_usec_add(); test_usec_sub(); + test_format_timestamp(); /* Ensure time_t is signed */ assert_cc((time_t) -1 < (time_t) 1); |