time-util.c 37.6 KB
Newer Older
Dan Williams's avatar
Dan Williams committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/***
  This file is part of systemd.

  Copyright 2010 Lennart Poettering

  systemd is free software; you can redistribute it and/or modify it
  under the terms of the GNU Lesser General Public License as published by
  the Free Software Foundation; either version 2.1 of the License, or
  (at your option) any later version.

  systemd is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/

20 21 22
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
Dan Williams's avatar
Dan Williams committed
23
#include <string.h>
24 25
#include <sys/stat.h>
#include <sys/time.h>
Dan Williams's avatar
Dan Williams committed
26
#include <sys/timerfd.h>
27
#include <sys/timex.h>
28 29
#include <sys/types.h>
#include <unistd.h>
Dan Williams's avatar
Dan Williams committed
30

31 32 33 34
#include "alloc-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
35 36
#include "log.h"
#include "macro.h"
37
#include "parse-util.h"
38
#include "path-util.h"
39
#include "string-util.h"
Dan Williams's avatar
Dan Williams committed
40
#include "strv.h"
41
#include "time-util.h"
Dan Williams's avatar
Dan Williams committed
42

43 44 45 46 47
static clockid_t map_clock_id(clockid_t c) {

        /* Some more exotic archs (s390, ppc, …) lack the "ALARM" flavour of the clocks. Thus, clock_gettime() will
         * fail for them. Since they are essentially the same as their non-ALARM pendants (their only difference is
         * when timers are set on them), let's just map them accordingly. This way, we can get the correct time even on
48
         * those archs. */
49 50 51 52

        switch (c) {

        case CLOCK_BOOTTIME_ALARM:
53
                return CLOCK_BOOTTIME;
54 55 56 57 58 59 60 61 62

        case CLOCK_REALTIME_ALARM:
                return CLOCK_REALTIME;

        default:
                return c;
        }
}

Dan Williams's avatar
Dan Williams committed
63 64 65
usec_t now(clockid_t clock_id) {
        struct timespec ts;

66
        assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0);
Dan Williams's avatar
Dan Williams committed
67 68 69 70

        return timespec_load(&ts);
}

71 72 73
nsec_t now_nsec(clockid_t clock_id) {
        struct timespec ts;

74
        assert_se(clock_gettime(map_clock_id(clock_id), &ts) == 0);
75 76 77 78

        return timespec_load_nsec(&ts);
}

Dan Williams's avatar
Dan Williams committed
79 80 81 82 83 84 85 86 87
dual_timestamp* dual_timestamp_get(dual_timestamp *ts) {
        assert(ts);

        ts->realtime = now(CLOCK_REALTIME);
        ts->monotonic = now(CLOCK_MONOTONIC);

        return ts;
}

88 89 90 91 92 93 94 95 96 97
triple_timestamp* triple_timestamp_get(triple_timestamp *ts) {
        assert(ts);

        ts->realtime = now(CLOCK_REALTIME);
        ts->monotonic = now(CLOCK_MONOTONIC);
        ts->boottime = clock_boottime_supported() ? now(CLOCK_BOOTTIME) : USEC_INFINITY;

        return ts;
}

Dan Williams's avatar
Dan Williams committed
98 99 100 101 102 103 104 105 106 107 108 109
dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) {
        int64_t delta;
        assert(ts);

        if (u == USEC_INFINITY || u <= 0) {
                ts->realtime = ts->monotonic = u;
                return ts;
        }

        ts->realtime = u;

        delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u;
110
        ts->monotonic = usec_sub(now(CLOCK_MONOTONIC), delta);
Dan Williams's avatar
Dan Williams committed
111 112 113 114

        return ts;
}

115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u) {
        int64_t delta;

        assert(ts);

        if (u == USEC_INFINITY || u <= 0) {
                ts->realtime = ts->monotonic = ts->boottime = u;
                return ts;
        }

        ts->realtime = u;
        delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u;
        ts->monotonic = usec_sub(now(CLOCK_MONOTONIC), delta);
        ts->boottime = clock_boottime_supported() ? usec_sub(now(CLOCK_BOOTTIME), delta) : USEC_INFINITY;

        return ts;
}

Dan Williams's avatar
Dan Williams committed
133 134 135 136 137 138 139 140 141 142 143
dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) {
        int64_t delta;
        assert(ts);

        if (u == USEC_INFINITY) {
                ts->realtime = ts->monotonic = USEC_INFINITY;
                return ts;
        }

        ts->monotonic = u;
        delta = (int64_t) now(CLOCK_MONOTONIC) - (int64_t) u;
144
        ts->realtime = usec_sub(now(CLOCK_REALTIME), delta);
Dan Williams's avatar
Dan Williams committed
145 146 147 148

        return ts;
}

149 150 151 152 153 154 155 156
dual_timestamp* dual_timestamp_from_boottime_or_monotonic(dual_timestamp *ts, usec_t u) {
        int64_t delta;

        if (u == USEC_INFINITY) {
                ts->realtime = ts->monotonic = USEC_INFINITY;
                return ts;
        }

157
        dual_timestamp_get(ts);
158
        delta = (int64_t) now(clock_boottime_or_monotonic()) - (int64_t) u;
159 160
        ts->realtime = usec_sub(ts->realtime, delta);
        ts->monotonic = usec_sub(ts->monotonic, delta);
161 162 163 164

        return ts;
}

165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock) {

        switch (clock) {

        case CLOCK_REALTIME:
        case CLOCK_REALTIME_ALARM:
                return ts->realtime;

        case CLOCK_MONOTONIC:
                return ts->monotonic;

        case CLOCK_BOOTTIME:
        case CLOCK_BOOTTIME_ALARM:
                return ts->boottime;

        default:
                return USEC_INFINITY;
        }
}

Dan Williams's avatar
Dan Williams committed
185 186 187
usec_t timespec_load(const struct timespec *ts) {
        assert(ts);

188
        if (ts->tv_sec == (time_t) -1 && ts->tv_nsec == (long) -1)
Dan Williams's avatar
Dan Williams committed
189 190 191 192 193 194 195 196 197 198
                return USEC_INFINITY;

        if ((usec_t) ts->tv_sec > (UINT64_MAX - (ts->tv_nsec / NSEC_PER_USEC)) / USEC_PER_SEC)
                return USEC_INFINITY;

        return
                (usec_t) ts->tv_sec * USEC_PER_SEC +
                (usec_t) ts->tv_nsec / NSEC_PER_USEC;
}

199
nsec_t timespec_load_nsec(const struct timespec *ts) {
200 201
        assert(ts);

202
        if (ts->tv_sec == (time_t) -1 && ts->tv_nsec == (long) -1)
203 204
                return NSEC_INFINITY;

205 206 207 208
        if ((nsec_t) ts->tv_sec >= (UINT64_MAX - ts->tv_nsec) / NSEC_PER_SEC)
                return NSEC_INFINITY;

        return (nsec_t) ts->tv_sec * NSEC_PER_SEC + (nsec_t) ts->tv_nsec;
209 210
}

Dan Williams's avatar
Dan Williams committed
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
struct timespec *timespec_store(struct timespec *ts, usec_t u)  {
        assert(ts);

        if (u == USEC_INFINITY) {
                ts->tv_sec = (time_t) -1;
                ts->tv_nsec = (long) -1;
                return ts;
        }

        ts->tv_sec = (time_t) (u / USEC_PER_SEC);
        ts->tv_nsec = (long int) ((u % USEC_PER_SEC) * NSEC_PER_USEC);

        return ts;
}

usec_t timeval_load(const struct timeval *tv) {
        assert(tv);

        if (tv->tv_sec == (time_t) -1 &&
            tv->tv_usec == (suseconds_t) -1)
                return USEC_INFINITY;

        if ((usec_t) tv->tv_sec > (UINT64_MAX - tv->tv_usec) / USEC_PER_SEC)
                return USEC_INFINITY;

        return
                (usec_t) tv->tv_sec * USEC_PER_SEC +
                (usec_t) tv->tv_usec;
}

struct timeval *timeval_store(struct timeval *tv, usec_t u) {
        assert(tv);

        if (u == USEC_INFINITY) {
                tv->tv_sec = (time_t) -1;
                tv->tv_usec = (suseconds_t) -1;
        } else {
                tv->tv_sec = (time_t) (u / USEC_PER_SEC);
                tv->tv_usec = (suseconds_t) (u % USEC_PER_SEC);
        }

        return tv;
}

255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
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",
        };

Dan Williams's avatar
Dan Williams committed
274 275
        struct tm tm;
        time_t sec;
276
        size_t n;
Dan Williams's avatar
Dan Williams committed
277 278 279

        assert(buf);

280 281 282 283 284 285 286 287
        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. */
Dan Williams's avatar
Dan Williams committed
288
        if (t <= 0 || t == USEC_INFINITY)
289 290 291 292 293 294 295
                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))
Dan Williams's avatar
Dan Williams committed
296 297
                return NULL;

298 299 300
        /* Start with the week day */
        assert((size_t) tm.tm_wday < ELEMENTSOF(weekdays));
        memcpy(buf, weekdays[tm.tm_wday], 4);
Dan Williams's avatar
Dan Williams committed
301

302 303 304
        /* Add the main components */
        if (strftime(buf + 3, l - 3, " %Y-%m-%d %H:%M:%S", &tm) <= 0)
                return NULL; /* Doesn't fit */
305

306
        /* Append the microseconds part, if that's requested */
307
        if (us) {
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
                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);
                }
344
        }
Dan Williams's avatar
Dan Williams committed
345 346 347 348 349

        return buf;
}

char *format_timestamp(char *buf, size_t l, usec_t t) {
350
        return format_timestamp_internal(buf, l, t, false, false);
Dan Williams's avatar
Dan Williams committed
351 352 353
}

char *format_timestamp_utc(char *buf, size_t l, usec_t t) {
354
        return format_timestamp_internal(buf, l, t, true, false);
Dan Williams's avatar
Dan Williams committed
355 356 357
}

char *format_timestamp_us(char *buf, size_t l, usec_t t) {
358
        return format_timestamp_internal(buf, l, t, false, true);
Dan Williams's avatar
Dan Williams committed
359 360 361
}

char *format_timestamp_us_utc(char *buf, size_t l, usec_t t) {
362
        return format_timestamp_internal(buf, l, t, true, true);
Dan Williams's avatar
Dan Williams committed
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432
}

char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
        const char *s;
        usec_t n, d;

        if (t <= 0 || t == USEC_INFINITY)
                return NULL;

        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, 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, 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, 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, USEC_FMT " days %s", d / USEC_PER_DAY, s);
        else if (d >= 25*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, USEC_FMT "h %s",
                         d / USEC_PER_HOUR, s);
        else if (d >= USEC_PER_HOUR)
                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, USEC_FMT "min %s",
                         d / USEC_PER_MINUTE, s);
        else if (d >= USEC_PER_MINUTE)
                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, USEC_FMT "s %s",
                         d / USEC_PER_SEC, s);
        else if (d >= USEC_PER_MSEC)
                snprintf(buf, l, USEC_FMT "ms %s",
                         d / USEC_PER_MSEC, s);
        else if (d > 0)
                snprintf(buf, l, USEC_FMT"us %s",
                         d, s);
        else
                snprintf(buf, l, "now");

        buf[l-1] = 0;
        return buf;
}

char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
        static const struct {
                const char *suffix;
                usec_t usec;
        } table[] = {
433 434 435 436 437 438 439 440 441
                { "y",     USEC_PER_YEAR   },
                { "month", USEC_PER_MONTH  },
                { "w",     USEC_PER_WEEK   },
                { "d",     USEC_PER_DAY    },
                { "h",     USEC_PER_HOUR   },
                { "min",   USEC_PER_MINUTE },
                { "s",     USEC_PER_SEC    },
                { "ms",    USEC_PER_MSEC   },
                { "us",    1               },
Dan Williams's avatar
Dan Williams committed
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
        };

        unsigned i;
        char *p = buf;
        bool something = false;

        assert(buf);
        assert(l > 0);

        if (t == USEC_INFINITY) {
                strncpy(p, "infinity", l-1);
                p[l-1] = 0;
                return p;
        }

        if (t <= 0) {
                strncpy(p, "0", l-1);
                p[l-1] = 0;
                return p;
        }

        /* The result of this function can be parsed with parse_sec */

        for (i = 0; i < ELEMENTSOF(table); i++) {
                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;

                if (l <= 1)
                        break;

                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;

                something = true;
        }

        *p = 0;

        return buf;
}

void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) {

        assert(f);
        assert(name);
        assert(t);

        if (!dual_timestamp_is_set(t))
                return;

        fprintf(f, "%s="USEC_FMT" "USEC_FMT"\n",
                name,
                t->realtime,
                t->monotonic);
}

553
int dual_timestamp_deserialize(const char *value, dual_timestamp *t) {
Dan Williams's avatar
Dan Williams committed
554 555 556 557 558
        unsigned long long a, b;

        assert(value);
        assert(t);

559
        if (sscanf(value, "%llu %llu", &a, &b) != 2) {
560
                log_debug("Failed to parse dual timestamp value \"%s\": %m", value);
561
                return -EINVAL;
Dan Williams's avatar
Dan Williams committed
562
        }
563 564 565 566 567

        t->realtime = a;
        t->monotonic = b;

        return 0;
Dan Williams's avatar
Dan Williams committed
568 569
}

570 571 572 573 574 575 576 577 578 579 580 581
int timestamp_deserialize(const char *value, usec_t *timestamp) {
        int r;

        assert(value);

        r = safe_atou64(value, timestamp);
        if (r < 0)
                return log_debug_errno(r, "Failed to parse timestamp value \"%s\": %m", value);

        return r;
}

Dan Williams's avatar
Dan Williams committed
582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602
int parse_timestamp(const char *t, usec_t *usec) {
        static const struct {
                const char *name;
                const int nr;
        } day_nr[] = {
                { "Sunday",    0 },
                { "Sun",       0 },
                { "Monday",    1 },
                { "Mon",       1 },
                { "Tuesday",   2 },
                { "Tue",       2 },
                { "Wednesday", 3 },
                { "Wed",       3 },
                { "Thursday",  4 },
                { "Thu",       4 },
                { "Friday",    5 },
                { "Fri",       5 },
                { "Saturday",  6 },
                { "Sat",       6 },
        };

603
        const char *k, *utc, *tzn = NULL;
Dan Williams's avatar
Dan Williams committed
604 605
        struct tm tm, copy;
        time_t x;
606
        usec_t x_usec, plus = 0, minus = 0, ret;
607
        int r, weekday = -1, dst = -1;
Dan Williams's avatar
Dan Williams committed
608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630
        unsigned i;

        /*
         * Allowed syntaxes:
         *
         *   2012-09-22 16:34:22
         *   2012-09-22 16:34     (seconds will be set to 0)
         *   2012-09-22           (time will be set to 00:00:00)
         *   16:34:22             (date will be set to today)
         *   16:34                (date will be set to today, seconds to 0)
         *   now
         *   yesterday            (time is set to 00:00:00)
         *   today                (time is set to 00:00:00)
         *   tomorrow             (time is set to 00:00:00)
         *   +5min
         *   -5days
         *   @2147483647          (seconds since epoch)
         *
         */

        assert(t);
        assert(usec);

631 632
        if (t[0] == '@')
                return parse_sec(t + 1, usec);
Dan Williams's avatar
Dan Williams committed
633

634
        ret = now(CLOCK_REALTIME);
Dan Williams's avatar
Dan Williams committed
635

636
        if (streq(t, "now"))
Dan Williams's avatar
Dan Williams committed
637 638
                goto finish;

639
        else if (t[0] == '+') {
Dan Williams's avatar
Dan Williams committed
640 641 642 643 644 645 646 647 648 649 650 651 652
                r = parse_sec(t+1, &plus);
                if (r < 0)
                        return r;

                goto finish;

        } else if (t[0] == '-') {
                r = parse_sec(t+1, &minus);
                if (r < 0)
                        return r;

                goto finish;

653 654
        } else if ((k = endswith(t, " ago"))) {
                t = strndupa(t, k - t);
Dan Williams's avatar
Dan Williams committed
655

656
                r = parse_sec(t, &minus);
Dan Williams's avatar
Dan Williams committed
657 658 659 660 661
                if (r < 0)
                        return r;

                goto finish;

662 663
        } else if ((k = endswith(t, " left"))) {
                t = strndupa(t, k - t);
Dan Williams's avatar
Dan Williams committed
664

665
                r = parse_sec(t, &plus);
Dan Williams's avatar
Dan Williams committed
666 667 668 669 670 671
                if (r < 0)
                        return r;

                goto finish;
        }

672
        /* See if the timestamp is suffixed with UTC */
673 674 675
        utc = endswith_no_case(t, " UTC");
        if (utc)
                t = strndupa(t, utc - t);
676 677 678 679 680 681 682 683 684 685 686
        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.  */
687

688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712
                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;
                }

                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);
713 714
        x_usec = 0;

715 716 717 718 719 720
        if (!localtime_or_gmtime_r(&x, &tm, utc))
                return -EINVAL;

        tm.tm_isdst = dst;
        if (tzn)
                tm.tm_zone = tzn;
721 722 723 724 725 726

        if (streq(t, "today")) {
                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
                goto from_tm;

        } else if (streq(t, "yesterday")) {
727
                tm.tm_mday--;
728 729 730 731
                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
                goto from_tm;

        } else if (streq(t, "tomorrow")) {
732
                tm.tm_mday++;
733 734 735 736
                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
                goto from_tm;
        }

Dan Williams's avatar
Dan Williams committed
737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753
        for (i = 0; i < ELEMENTSOF(day_nr); i++) {
                size_t skip;

                if (!startswith_no_case(t, day_nr[i].name))
                        continue;

                skip = strlen(day_nr[i].name);
                if (t[skip] != ' ')
                        continue;

                weekday = day_nr[i].nr;
                t += skip + 1;
                break;
        }

        copy = tm;
        k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
754 755 756 757 758 759
        if (k) {
                if (*k == '.')
                        goto parse_usec;
                else if (*k == 0)
                        goto from_tm;
        }
Dan Williams's avatar
Dan Williams committed
760 761 762

        tm = copy;
        k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
763 764 765 766 767 768
        if (k) {
                if (*k == '.')
                        goto parse_usec;
                else if (*k == 0)
                        goto from_tm;
        }
Dan Williams's avatar
Dan Williams committed
769 770 771 772 773

        tm = copy;
        k = strptime(t, "%y-%m-%d %H:%M", &tm);
        if (k && *k == 0) {
                tm.tm_sec = 0;
774
                goto from_tm;
Dan Williams's avatar
Dan Williams committed
775 776 777 778 779 780
        }

        tm = copy;
        k = strptime(t, "%Y-%m-%d %H:%M", &tm);
        if (k && *k == 0) {
                tm.tm_sec = 0;
781
                goto from_tm;
Dan Williams's avatar
Dan Williams committed
782 783 784 785 786 787
        }

        tm = copy;
        k = strptime(t, "%y-%m-%d", &tm);
        if (k && *k == 0) {
                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
788
                goto from_tm;
Dan Williams's avatar
Dan Williams committed
789 790 791 792 793 794
        }

        tm = copy;
        k = strptime(t, "%Y-%m-%d", &tm);
        if (k && *k == 0) {
                tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
795
                goto from_tm;
Dan Williams's avatar
Dan Williams committed
796 797 798 799
        }

        tm = copy;
        k = strptime(t, "%H:%M:%S", &tm);
800 801 802 803 804 805
        if (k) {
                if (*k == '.')
                        goto parse_usec;
                else if (*k == 0)
                        goto from_tm;
        }
Dan Williams's avatar
Dan Williams committed
806 807 808 809 810

        tm = copy;
        k = strptime(t, "%H:%M", &tm);
        if (k && *k == 0) {
                tm.tm_sec = 0;
811
                goto from_tm;
Dan Williams's avatar
Dan Williams committed
812 813 814 815
        }

        return -EINVAL;

816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832
parse_usec:
        {
                unsigned add;

                k++;
                r = parse_fractional_part_u(&k, 6, &add);
                if (r < 0)
                        return -EINVAL;

                if (*k)
                        return -EINVAL;

                x_usec = add;
        }

from_tm:
        x = mktime_or_timegm(&tm, utc);
Dan Williams's avatar
Dan Williams committed
833 834 835 836 837 838
        if (x == (time_t) -1)
                return -EINVAL;

        if (weekday >= 0 && tm.tm_wday != weekday)
                return -EINVAL;

839
        ret = (usec_t) x * USEC_PER_SEC + x_usec;
Dan Williams's avatar
Dan Williams committed
840

841
finish:
Dan Williams's avatar
Dan Williams committed
842 843 844 845 846 847 848 849 850 851 852
        ret += plus;
        if (ret > minus)
                ret -= minus;
        else
                ret = 0;

        *usec = ret;

        return 0;
}

853
static char* extract_multiplier(char *p, usec_t *multiplier) {
Dan Williams's avatar
Dan Williams committed
854 855 856 857
        static const struct {
                const char *suffix;
                usec_t usec;
        } table[] = {
858 859 860 861
                { "seconds", USEC_PER_SEC    },
                { "second",  USEC_PER_SEC    },
                { "sec",     USEC_PER_SEC    },
                { "s",       USEC_PER_SEC    },
Dan Williams's avatar
Dan Williams committed
862
                { "minutes", USEC_PER_MINUTE },
863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885
                { "minute",  USEC_PER_MINUTE },
                { "min",     USEC_PER_MINUTE },
                { "months",  USEC_PER_MONTH  },
                { "month",   USEC_PER_MONTH  },
                { "M",       USEC_PER_MONTH  },
                { "msec",    USEC_PER_MSEC   },
                { "ms",      USEC_PER_MSEC   },
                { "m",       USEC_PER_MINUTE },
                { "hours",   USEC_PER_HOUR   },
                { "hour",    USEC_PER_HOUR   },
                { "hr",      USEC_PER_HOUR   },
                { "h",       USEC_PER_HOUR   },
                { "days",    USEC_PER_DAY    },
                { "day",     USEC_PER_DAY    },
                { "d",       USEC_PER_DAY    },
                { "weeks",   USEC_PER_WEEK   },
                { "week",    USEC_PER_WEEK   },
                { "w",       USEC_PER_WEEK   },
                { "years",   USEC_PER_YEAR   },
                { "year",    USEC_PER_YEAR   },
                { "y",       USEC_PER_YEAR   },
                { "usec",    1ULL            },
                { "us",      1ULL            },
Dan Williams's avatar
Dan Williams committed
886
        };
887 888 889 890
        unsigned i;

        for (i = 0; i < ELEMENTSOF(table); i++) {
                char *e;
Dan Williams's avatar
Dan Williams committed
891

892 893 894 895 896 897 898 899 900 901 902
                e = startswith(p, table[i].suffix);
                if (e) {
                        *multiplier = table[i].usec;
                        return e;
                }
        }

        return p;
}

int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
Dan Williams's avatar
Dan Williams committed
903 904 905 906 907 908
        const char *p, *s;
        usec_t r = 0;
        bool something = false;

        assert(t);
        assert(usec);
909
        assert(default_unit > 0);
Dan Williams's avatar
Dan Williams committed
910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926

        p = t;

        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;
927 928
                unsigned n = 0;
                usec_t multiplier = default_unit, k;
Dan Williams's avatar
Dan Williams committed
929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965

                p += strspn(p, WHITESPACE);

                if (*p == 0) {
                        if (!something)
                                return -EINVAL;

                        break;
                }

                errno = 0;
                l = strtoll(p, &e, 10);
                if (errno > 0)
                        return -errno;
                if (l < 0)
                        return -ERANGE;

                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);
966
                p = extract_multiplier(e, &multiplier);
967 968

                something = true;
Dan Williams's avatar
Dan Williams committed
969

970 971 972 973 974 975
                k = (usec_t) z * multiplier;

                for (; n > 0; n--)
                        k /= 10;

                r += (usec_t) l * multiplier + k;
Dan Williams's avatar
Dan Williams committed
976 977 978 979 980 981 982
        }

        *usec = r;

        return 0;
}

983 984 985 986
int parse_sec(const char *t, usec_t *usec) {
        return parse_time(t, usec, USEC_PER_SEC);
}

Dan Williams's avatar
Dan Williams committed
987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036
int parse_nsec(const char *t, nsec_t *nsec) {
        static const struct {
                const char *suffix;
                nsec_t nsec;
        } table[] = {
                { "seconds", NSEC_PER_SEC },
                { "second", NSEC_PER_SEC },
                { "sec", NSEC_PER_SEC },
                { "s", NSEC_PER_SEC },
                { "minutes", NSEC_PER_MINUTE },
                { "minute", NSEC_PER_MINUTE },
                { "min", NSEC_PER_MINUTE },
                { "months", NSEC_PER_MONTH },
                { "month", NSEC_PER_MONTH },
                { "msec", NSEC_PER_MSEC },
                { "ms", NSEC_PER_MSEC },
                { "m", NSEC_PER_MINUTE },
                { "hours", NSEC_PER_HOUR },
                { "hour", NSEC_PER_HOUR },
                { "hr", NSEC_PER_HOUR },
                { "h", NSEC_PER_HOUR },
                { "days", NSEC_PER_DAY },
                { "day", NSEC_PER_DAY },
                { "d", NSEC_PER_DAY },
                { "weeks", NSEC_PER_WEEK },
                { "week", NSEC_PER_WEEK },
                { "w", NSEC_PER_WEEK },
                { "years", NSEC_PER_YEAR },
                { "year", NSEC_PER_YEAR },
                { "y", NSEC_PER_YEAR },
                { "usec", NSEC_PER_USEC },
                { "us", NSEC_PER_USEC },
                { "nsec", 1ULL },
                { "ns", 1ULL },
                { "", 1ULL }, /* default is nsec */
        };

        const char *p, *s;
        nsec_t r = 0;
        bool something = false;

        assert(t);
        assert(nsec);

        p = t;

        p += strspn(p, WHITESPACE);
        s = startswith(p, "infinity");
        if (s) {
                s += strspn(s, WHITESPACE);
Thomas Haller's avatar
Thomas Haller committed
1037
                if (*s != 0)
Dan Williams's avatar
Dan Williams committed
1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192
                        return -EINVAL;

                *nsec = NSEC_INFINITY;
                return 0;
        }

        for (;;) {
                long long l, z = 0;
                char *e;
                unsigned i, n = 0;

                p += strspn(p, WHITESPACE);

                if (*p == 0) {
                        if (!something)
                                return -EINVAL;

                        break;
                }

                errno = 0;
                l = strtoll(p, &e, 10);

                if (errno > 0)
                        return -errno;

                if (l < 0)
                        return -ERANGE;

                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)) {
                                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;

        }

        *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;

1193 1194 1195 1196
        if (isempty(name))
                return false;

        if (name[0] == '/')
Dan Williams's avatar
Dan Williams committed
1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218
                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;

Thomas Haller's avatar
Thomas Haller committed
1219
        t = strjoina("/usr/share/zoneinfo/", name);
Dan Williams's avatar
Dan Williams committed
1220 1221 1222 1223 1224 1225 1226 1227 1228
        if (stat(t, &st) < 0)
                return false;

        if (!S_ISREG(st.st_mode))
                return false;

        return true;
}

1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243
bool clock_boottime_supported(void) {
        static int supported = -1;

        /* Note that this checks whether CLOCK_BOOTTIME is available in general as well as available for timerfds()! */

        if (supported < 0) {
                int fd;

                fd = timerfd_create(CLOCK_BOOTTIME, TFD_NONBLOCK|TFD_CLOEXEC);
                if (fd < 0)
                        supported = false;
                else {
                        safe_close(fd);
                        supported = true;
                }
Dan Williams's avatar
Dan Williams committed
1244 1245
        }

1246 1247 1248 1249 1250 1251 1252 1253
        return supported;
}

clockid_t clock_boottime_or_monotonic(void) {
        if (clock_boottime_supported())
                return CLOCK_BOOTTIME;
        else
                return CLOCK_MONOTONIC;
Dan Williams's avatar
Dan Williams committed
1254
}
1255

1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279
bool clock_supported(clockid_t clock) {
        struct timespec ts;

        switch (clock) {

        case CLOCK_MONOTONIC:
        case CLOCK_REALTIME:
                return true;

        case CLOCK_BOOTTIME:
                return clock_boottime_supported();

        case CLOCK_BOOTTIME_ALARM:
                if (!clock_boottime_supported())
                        return false;

                /* fall through, after checking the cached value for CLOCK_BOOTTIME. */

        default:
                /* For everything else, check properly */
                return clock_gettime(clock, &ts) >= 0;
        }
}

1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305
int get_timezone(char **tz) {
        _cleanup_free_ char *t = NULL;
        const char *e;
        char *z;
        int r;

        r = readlink_malloc("/etc/localtime", &t);
        if (r < 0)
                return r; /* returns EINVAL if not a symlink */

        e = path_startswith(t, "/usr/share/zoneinfo/");
        if (!e)
                e = path_startswith(t, "../usr/share/zoneinfo/");
        if (!e)
                return -EINVAL;

        if (!timezone_is_valid(e))
                return -EINVAL;

        z = strdup(e);
        if (!z)
                return -ENOMEM;

        *tz = z;
        return 0;
}
1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327

time_t mktime_or_timegm(struct tm *tm, bool utc) {
        return utc ? timegm(tm) : mktime(tm);
}

struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc) {
        return utc ? gmtime_r(t, tm) : localtime_r(t, tm);
}

unsigned long usec_to_jiffies(usec_t u) {
        static thread_local unsigned long hz = 0;
        long r;

        if (hz == 0) {
                r = sysconf(_SC_CLK_TCK);

                assert(r > 0);
                hz = (unsigned long) r;
        }

        return DIV_ROUND_UP(u , USEC_PER_SEC / hz);
}