X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fshared%2Ftime-util.c;h=1c36c577c4af36c9255554831161c8e65cb26171;hp=0c6deb66f4242367192a8da54a9894be7c4e4b0c;hb=01c94c5d0aff09b4c0e429d483c8eeba40017071;hpb=8333c77edf8fd1654cd96f3f6ee0f078dd64b58b diff --git a/src/shared/time-util.c b/src/shared/time-util.c index 0c6deb66f..1c36c577c 100644 --- a/src/shared/time-util.c +++ b/src/shared/time-util.c @@ -21,9 +21,12 @@ #include #include +#include +#include #include "util.h" #include "time-util.h" +#include "strv.h" usec_t now(clockid_t clock_id) { struct timespec ts; @@ -46,25 +49,20 @@ dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) { int64_t delta; assert(ts); - if (u == (usec_t) -1) { - ts->realtime = ts->monotonic = (usec_t) -1; + if (u == USEC_INFINITY || u <= 0) { + ts->realtime = ts->monotonic = u; return ts; } ts->realtime = u; - if (u == 0) - ts->monotonic = 0; - else { - delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u; - - ts->monotonic = now(CLOCK_MONOTONIC); + delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u; + ts->monotonic = now(CLOCK_MONOTONIC); - if ((int64_t) ts->monotonic > delta) - ts->monotonic -= delta; - else - ts->monotonic = 0; - } + if ((int64_t) ts->monotonic > delta) + ts->monotonic -= delta; + else + ts->monotonic = 0; return ts; } @@ -73,8 +71,8 @@ dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) { int64_t delta; assert(ts); - if (u == (usec_t) -1) { - ts->realtime = ts->monotonic = (usec_t) -1; + if (u == USEC_INFINITY) { + ts->realtime = ts->monotonic = USEC_INFINITY; return ts; } @@ -95,10 +93,10 @@ usec_t timespec_load(const struct timespec *ts) { if (ts->tv_sec == (time_t) -1 && ts->tv_nsec == (long) -1) - return (usec_t) -1; + return USEC_INFINITY; if ((usec_t) ts->tv_sec > (UINT64_MAX - (ts->tv_nsec / NSEC_PER_USEC)) / USEC_PER_SEC) - return (usec_t) -1; + return USEC_INFINITY; return (usec_t) ts->tv_sec * USEC_PER_SEC + @@ -108,7 +106,7 @@ usec_t timespec_load(const struct timespec *ts) { struct timespec *timespec_store(struct timespec *ts, usec_t u) { assert(ts); - if (u == (usec_t) -1) { + if (u == USEC_INFINITY) { ts->tv_sec = (time_t) -1; ts->tv_nsec = (long) -1; return ts; @@ -125,10 +123,10 @@ usec_t timeval_load(const struct timeval *tv) { if (tv->tv_sec == (time_t) -1 && tv->tv_usec == (suseconds_t) -1) - return (usec_t) -1; + return USEC_INFINITY; if ((usec_t) tv->tv_sec > (UINT64_MAX - tv->tv_usec) / USEC_PER_SEC) - return (usec_t) -1; + return USEC_INFINITY; return (usec_t) tv->tv_sec * USEC_PER_SEC + @@ -138,86 +136,136 @@ usec_t timeval_load(const struct timeval *tv) { struct timeval *timeval_store(struct timeval *tv, usec_t u) { assert(tv); - if (u == (usec_t) -1) { + if (u == USEC_INFINITY) { tv->tv_sec = (time_t) -1; tv->tv_usec = (suseconds_t) -1; - return tv; + } else { + tv->tv_sec = (time_t) (u / USEC_PER_SEC); + tv->tv_usec = (suseconds_t) (u % USEC_PER_SEC); } - tv->tv_sec = (time_t) (u / USEC_PER_SEC); - tv->tv_usec = (suseconds_t) (u % USEC_PER_SEC); - return tv; } +static char *format_timestamp_internal(char *buf, size_t l, usec_t t, bool utc) { + struct tm tm; + time_t sec; + + assert(buf); + assert(l > 0); + + if (t <= 0 || t == USEC_INFINITY) + return NULL; + + sec = (time_t) (t / USEC_PER_SEC); + + if (utc) + gmtime_r(&sec, &tm); + else + localtime_r(&sec, &tm); + if (strftime(buf, l, "%a %Y-%m-%d %H:%M:%S %Z", &tm) <= 0) + return NULL; + + return buf; +} + char *format_timestamp(char *buf, size_t l, usec_t t) { + return format_timestamp_internal(buf, l, t, false); +} + +char *format_timestamp_utc(char *buf, size_t l, usec_t t) { + return format_timestamp_internal(buf, l, t, true); +} + +static char *format_timestamp_internal_us(char *buf, size_t l, usec_t t, bool utc) { struct tm tm; time_t sec; assert(buf); assert(l > 0); - if (t <= 0) + if (t <= 0 || t == USEC_INFINITY) return NULL; sec = (time_t) (t / USEC_PER_SEC); + if (utc) + gmtime_r(&sec, &tm); + else + localtime_r(&sec, &tm); - if (strftime(buf, l, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) <= 0) + if (strftime(buf, l, "%a %Y-%m-%d %H:%M:%S", &tm) <= 0) + return NULL; + 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; return buf; } +char *format_timestamp_us(char *buf, size_t l, usec_t t) { + return format_timestamp_internal_us(buf, l, t, false); +} + +char *format_timestamp_us_utc(char *buf, size_t l, usec_t t) { + return format_timestamp_internal_us(buf, l, t, true); +} + char *format_timestamp_relative(char *buf, size_t l, usec_t t) { + const char *s; usec_t n, d; - n = now(CLOCK_REALTIME); - - if (t <= 0 || t > n || t + USEC_PER_DAY*7 <= t) + if (t <= 0 || t == USEC_INFINITY) return NULL; - d = n - t; + n = now(CLOCK_REALTIME); + if (n > t) { + d = n - t; + s = "ago"; + } else { + d = t - n; + s = "left"; + } if (d >= USEC_PER_YEAR) - snprintf(buf, l, "%llu years %llu months ago", - (unsigned long long) (d / USEC_PER_YEAR), - (unsigned long long) ((d % USEC_PER_YEAR) / USEC_PER_MONTH)); + snprintf(buf, l, USEC_FMT " years " USEC_FMT " months %s", + d / USEC_PER_YEAR, + (d % USEC_PER_YEAR) / USEC_PER_MONTH, s); else if (d >= USEC_PER_MONTH) - snprintf(buf, l, "%llu months %llu days ago", - (unsigned long long) (d / USEC_PER_MONTH), - (unsigned long long) ((d % USEC_PER_MONTH) / USEC_PER_DAY)); + snprintf(buf, l, USEC_FMT " months " USEC_FMT " days %s", + d / USEC_PER_MONTH, + (d % USEC_PER_MONTH) / USEC_PER_DAY, s); else if (d >= USEC_PER_WEEK) - snprintf(buf, l, "%llu weeks %llu days ago", - (unsigned long long) (d / USEC_PER_WEEK), - (unsigned long long) ((d % USEC_PER_WEEK) / USEC_PER_DAY)); + snprintf(buf, l, USEC_FMT " weeks " USEC_FMT " days %s", + d / USEC_PER_WEEK, + (d % USEC_PER_WEEK) / USEC_PER_DAY, s); else if (d >= 2*USEC_PER_DAY) - snprintf(buf, l, "%llu days ago", (unsigned long long) (d / USEC_PER_DAY)); + snprintf(buf, l, USEC_FMT " days %s", d / USEC_PER_DAY, s); else if (d >= 25*USEC_PER_HOUR) - snprintf(buf, l, "1 day %lluh ago", - (unsigned long long) ((d - USEC_PER_DAY) / USEC_PER_HOUR)); + snprintf(buf, l, "1 day " USEC_FMT "h %s", + (d - USEC_PER_DAY) / USEC_PER_HOUR, s); else if (d >= 6*USEC_PER_HOUR) - snprintf(buf, l, "%lluh ago", - (unsigned long long) (d / USEC_PER_HOUR)); + snprintf(buf, l, USEC_FMT "h %s", + d / USEC_PER_HOUR, s); else if (d >= USEC_PER_HOUR) - snprintf(buf, l, "%lluh %llumin ago", - (unsigned long long) (d / USEC_PER_HOUR), - (unsigned long long) ((d % USEC_PER_HOUR) / USEC_PER_MINUTE)); + snprintf(buf, l, USEC_FMT "h " USEC_FMT "min %s", + d / USEC_PER_HOUR, + (d % USEC_PER_HOUR) / USEC_PER_MINUTE, s); else if (d >= 5*USEC_PER_MINUTE) - snprintf(buf, l, "%llumin ago", - (unsigned long long) (d / USEC_PER_MINUTE)); + snprintf(buf, l, USEC_FMT "min %s", + d / USEC_PER_MINUTE, s); else if (d >= USEC_PER_MINUTE) - snprintf(buf, l, "%llumin %llus ago", - (unsigned long long) (d / USEC_PER_MINUTE), - (unsigned long long) ((d % USEC_PER_MINUTE) / USEC_PER_SEC)); + snprintf(buf, l, USEC_FMT "min " USEC_FMT "s %s", + d / USEC_PER_MINUTE, + (d % USEC_PER_MINUTE) / USEC_PER_SEC, s); else if (d >= USEC_PER_SEC) - snprintf(buf, l, "%llus ago", - (unsigned long long) (d / USEC_PER_SEC)); + snprintf(buf, l, USEC_FMT "s %s", + d / USEC_PER_SEC, s); else if (d >= USEC_PER_MSEC) - snprintf(buf, l, "%llums ago", - (unsigned long long) (d / USEC_PER_MSEC)); + snprintf(buf, l, USEC_FMT "ms %s", + d / USEC_PER_MSEC, s); else if (d > 0) - snprintf(buf, l, "%lluus ago", - (unsigned long long) d); + snprintf(buf, l, USEC_FMT"us %s", + d, s); else snprintf(buf, l, "now"); @@ -225,7 +273,7 @@ char *format_timestamp_relative(char *buf, size_t l, usec_t t) { return buf; } -char *format_timespan(char *buf, size_t l, usec_t t) { +char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) { static const struct { const char *suffix; usec_t usec; @@ -243,24 +291,36 @@ char *format_timespan(char *buf, size_t l, usec_t t) { unsigned i; char *p = buf; + bool something = false; assert(buf); assert(l > 0); - if (t == (usec_t) -1) - return NULL; + if (t == USEC_INFINITY) { + strncpy(p, "infinity", l-1); + p[l-1] = 0; + return p; + } - if (t == 0) { - snprintf(p, l, "0"); + if (t <= 0) { + strncpy(p, "0", l-1); p[l-1] = 0; return p; } - /* The result of this function can be parsed with parse_usec */ + /* The result of this function can be parsed with parse_sec */ for (i = 0; i < ELEMENTSOF(table); i++) { - int k; + int k = 0; size_t n; + bool done = false; + usec_t a, b; + + if (t <= 0) + break; + + if (t < accuracy && something) + break; if (t < table[i].usec) continue; @@ -268,13 +328,54 @@ char *format_timespan(char *buf, size_t l, usec_t t) { if (l <= 1) break; - k = snprintf(p, l, "%s%llu%s", p > buf ? " " : "", (unsigned long long) (t / table[i].usec), table[i].suffix); + a = t / table[i].usec; + b = t % table[i].usec; + + /* Let's see if we should shows this in dot notation */ + if (t < USEC_PER_MINUTE && b > 0) { + usec_t cc; + int j; + + j = 0; + for (cc = table[i].usec; cc > 1; cc /= 10) + j++; + + for (cc = accuracy; cc > 1; cc /= 10) { + b /= 10; + j--; + } + + if (j > 0) { + k = snprintf(p, l, + "%s"USEC_FMT".%0*llu%s", + p > buf ? " " : "", + a, + j, + (unsigned long long) b, + table[i].suffix); + + t = 0; + done = true; + } + } + + /* No? Then let's show it normally */ + if (!done) { + k = snprintf(p, l, + "%s"USEC_FMT"%s", + p > buf ? " " : "", + a, + table[i].suffix); + + t = b; + } + n = MIN((size_t) k, l); l -= n; p += n; - t %= table[i].usec; + something = true; } *p = 0; @@ -291,10 +392,10 @@ void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) { if (!dual_timestamp_is_set(t)) return; - fprintf(f, "%s=%llu %llu\n", + fprintf(f, "%s="USEC_FMT" "USEC_FMT"\n", name, - (unsigned long long) t->realtime, - (unsigned long long) t->monotonic); + t->realtime, + t->monotonic); } void dual_timestamp_deserialize(const char *value, dual_timestamp *t) { @@ -303,7 +404,7 @@ void dual_timestamp_deserialize(const char *value, dual_timestamp *t) { assert(value); assert(t); - if (sscanf(value, "%lli %llu", &a, &b) != 2) + if (sscanf(value, "%llu %llu", &a, &b) != 2) log_debug("Failed to parse finish timestamp value %s", value); else { t->realtime = a; @@ -353,6 +454,7 @@ int parse_timestamp(const char *t, usec_t *usec) { * tomorrow (time is set to 00:00:00) * +5min * -5days + * @2147483647 (seconds since epoch) * */ @@ -381,28 +483,42 @@ int parse_timestamp(const char *t, usec_t *usec) { goto finish; } else if (t[0] == '+') { - - r = parse_usec(t+1, &plus); + r = parse_sec(t+1, &plus); if (r < 0) return r; goto finish; - } else if (t[0] == '-') { - r = parse_usec(t+1, &minus); + } else if (t[0] == '-') { + r = parse_sec(t+1, &minus); if (r < 0) return r; goto finish; - } else if (endswith(t, " ago")) { + } else if (t[0] == '@') + return parse_sec(t + 1, usec); + + else if (endswith(t, " ago")) { + _cleanup_free_ char *z; + + z = strndup(t, strlen(t) - 4); + if (!z) + return -ENOMEM; + + r = parse_sec(z, &minus); + if (r < 0) + return r; + + goto finish; + } else if (endswith(t, " left")) { _cleanup_free_ char *z; z = strndup(t, strlen(t) - 4); if (!z) return -ENOMEM; - r = parse_usec(z, &minus); + r = parse_sec(z, &plus); if (r < 0) return r; @@ -497,7 +613,7 @@ finish: return 0; } -int parse_usec(const char *t, usec_t *usec) { +int parse_sec(const char *t, usec_t *usec) { static const struct { const char *suffix; usec_t usec; @@ -532,17 +648,39 @@ int parse_usec(const char *t, usec_t *usec) { { "", USEC_PER_SEC }, /* default is sec */ }; - const char *p; + const char *p, *s; usec_t r = 0; + bool something = false; assert(t); assert(usec); p = t; - do { - long long l; + + p += strspn(p, WHITESPACE); + s = startswith(p, "infinity"); + if (s) { + s += strspn(s, WHITESPACE); + if (*s != 0) + return -EINVAL; + + *usec = USEC_INFINITY; + return 0; + } + + for (;;) { + long long l, z = 0; char *e; - unsigned i; + unsigned i, n = 0; + + p += strspn(p, WHITESPACE); + + if (*p == 0) { + if (!something) + return -EINVAL; + + break; + } errno = 0; l = strtoll(p, &e, 10); @@ -553,22 +691,45 @@ int parse_usec(const char *t, usec_t *usec) { if (l < 0) return -ERANGE; - if (e == p) + if (*e == '.') { + char *b = e + 1; + + errno = 0; + z = strtoll(b, &e, 10); + if (errno > 0) + return -errno; + + if (z < 0) + return -ERANGE; + + if (e == b) + return -EINVAL; + + n = e - b; + + } else if (e == p) return -EINVAL; e += strspn(e, WHITESPACE); for (i = 0; i < ELEMENTSOF(table); i++) if (startswith(e, table[i].suffix)) { - r += (usec_t) l * table[i].usec; + usec_t k = (usec_t) z * table[i].usec; + + for (; n > 0; n--) + k /= 10; + + r += (usec_t) l * table[i].usec + k; p = e + strlen(table[i].suffix); + + something = true; break; } if (i >= ELEMENTSOF(table)) return -EINVAL; - } while (*p != 0); + } *usec = r; @@ -612,17 +773,39 @@ int parse_nsec(const char *t, nsec_t *nsec) { { "", 1ULL }, /* default is nsec */ }; - const char *p; + const char *p, *s; nsec_t r = 0; + bool something = false; assert(t); assert(nsec); p = t; - do { - long long l; + + p += strspn(p, WHITESPACE); + s = startswith(p, "infinity"); + if (s) { + s += strspn(s, WHITESPACE); + if (*s != 0) + return -EINVAL; + + *nsec = NSEC_INFINITY; + return 0; + } + + for (;;) { + long long l, z = 0; char *e; - unsigned i; + unsigned i, n = 0; + + p += strspn(p, WHITESPACE); + + if (*p == 0) { + if (!something) + return -EINVAL; + + break; + } errno = 0; l = strtoll(p, &e, 10); @@ -633,24 +816,179 @@ int parse_nsec(const char *t, nsec_t *nsec) { if (l < 0) return -ERANGE; - if (e == p) + if (*e == '.') { + char *b = e + 1; + + errno = 0; + z = strtoll(b, &e, 10); + if (errno > 0) + return -errno; + + if (z < 0) + return -ERANGE; + + if (e == b) + return -EINVAL; + + n = e - b; + + } else if (e == p) return -EINVAL; e += strspn(e, WHITESPACE); for (i = 0; i < ELEMENTSOF(table); i++) if (startswith(e, table[i].suffix)) { - r += (nsec_t) l * table[i].nsec; + nsec_t k = (nsec_t) z * table[i].nsec; + + for (; n > 0; n--) + k /= 10; + + r += (nsec_t) l * table[i].nsec + k; p = e + strlen(table[i].suffix); + + something = true; break; } if (i >= ELEMENTSOF(table)) return -EINVAL; - } while (*p != 0); + } *nsec = r; return 0; } + +bool ntp_synced(void) { + struct timex txc = {}; + + if (adjtimex(&txc) < 0) + return false; + + if (txc.status & STA_UNSYNC) + return false; + + return true; +} + +int get_timezones(char ***ret) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_strv_free_ char **zones = NULL; + size_t n_zones = 0, n_allocated = 0; + + assert(ret); + + zones = strv_new("UTC", NULL); + if (!zones) + return -ENOMEM; + + n_allocated = 2; + n_zones = 1; + + f = fopen("/usr/share/zoneinfo/zone.tab", "re"); + if (f) { + char l[LINE_MAX]; + + FOREACH_LINE(l, f, return -errno) { + char *p, *w; + size_t k; + + p = strstrip(l); + + if (isempty(p) || *p == '#') + continue; + + /* Skip over country code */ + p += strcspn(p, WHITESPACE); + p += strspn(p, WHITESPACE); + + /* Skip over coordinates */ + p += strcspn(p, WHITESPACE); + p += strspn(p, WHITESPACE); + + /* Found timezone name */ + k = strcspn(p, WHITESPACE); + if (k <= 0) + continue; + + w = strndup(p, k); + if (!w) + return -ENOMEM; + + if (!GREEDY_REALLOC(zones, n_allocated, n_zones + 2)) { + free(w); + return -ENOMEM; + } + + zones[n_zones++] = w; + zones[n_zones] = NULL; + } + + strv_sort(zones); + + } else if (errno != ENOENT) + return -errno; + + *ret = zones; + zones = NULL; + + return 0; +} + +bool timezone_is_valid(const char *name) { + bool slash = false; + const char *p, *t; + struct stat st; + + if (!name || *name == 0 || *name == '/') + return false; + + for (p = name; *p; p++) { + if (!(*p >= '0' && *p <= '9') && + !(*p >= 'a' && *p <= 'z') && + !(*p >= 'A' && *p <= 'Z') && + !(*p == '-' || *p == '_' || *p == '+' || *p == '/')) + return false; + + if (*p == '/') { + + if (slash) + return false; + + slash = true; + } else + slash = false; + } + + if (slash) + return false; + + t = strjoina("/usr/share/zoneinfo/", name); + if (stat(t, &st) < 0) + return false; + + if (!S_ISREG(st.st_mode)) + return false; + + return true; +} + +clockid_t clock_boottime_or_monotonic(void) { + static clockid_t clock = -1; + int fd; + + if (clock != -1) + return clock; + + fd = timerfd_create(CLOCK_BOOTTIME, TFD_NONBLOCK|TFD_CLOEXEC); + if (fd < 0) + clock = CLOCK_MONOTONIC; + else { + safe_close(fd); + clock = CLOCK_BOOTTIME; + } + + return clock; +}