chiark / gitweb /
core: properly initialize kernel timestamp
[elogind.git] / src / shared / time-util.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <time.h>
23 #include <string.h>
24
25 #include "util.h"
26 #include "time-util.h"
27
28 usec_t now(clockid_t clock_id) {
29         struct timespec ts;
30
31         assert_se(clock_gettime(clock_id, &ts) == 0);
32
33         return timespec_load(&ts);
34 }
35
36 dual_timestamp* dual_timestamp_get(dual_timestamp *ts) {
37         assert(ts);
38
39         ts->realtime = now(CLOCK_REALTIME);
40         ts->monotonic = now(CLOCK_MONOTONIC);
41
42         return ts;
43 }
44
45 dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) {
46         int64_t delta;
47         assert(ts);
48
49         if (u == (usec_t) -1) {
50                 ts->realtime = ts->monotonic = (usec_t) -1;
51                 return ts;
52         }
53
54         ts->realtime = u;
55
56         if (u == 0)
57                 ts->monotonic = 0;
58         else {
59                 delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u;
60
61                 ts->monotonic = now(CLOCK_MONOTONIC);
62
63                 if ((int64_t) ts->monotonic > delta)
64                         ts->monotonic -= delta;
65                 else
66                         ts->monotonic = 0;
67         }
68
69         return ts;
70 }
71
72 dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) {
73         int64_t delta;
74         assert(ts);
75
76         if (u == (usec_t) -1) {
77                 ts->realtime = ts->monotonic = (usec_t) -1;
78                 return ts;
79         }
80
81         ts->monotonic = u;
82         delta = (int64_t) now(CLOCK_MONOTONIC) - (int64_t) u;
83
84         ts->realtime = now(CLOCK_REALTIME);
85         if ((int64_t) ts->realtime > delta)
86                 ts->realtime -= delta;
87         else
88                 ts->realtime = 0;
89
90         return ts;
91 }
92
93 usec_t timespec_load(const struct timespec *ts) {
94         assert(ts);
95
96         if (ts->tv_sec == (time_t) -1 &&
97             ts->tv_nsec == (long) -1)
98                 return (usec_t) -1;
99
100         if ((usec_t) ts->tv_sec > (UINT64_MAX - (ts->tv_nsec / NSEC_PER_USEC)) / USEC_PER_SEC)
101                 return (usec_t) -1;
102
103         return
104                 (usec_t) ts->tv_sec * USEC_PER_SEC +
105                 (usec_t) ts->tv_nsec / NSEC_PER_USEC;
106 }
107
108 struct timespec *timespec_store(struct timespec *ts, usec_t u)  {
109         assert(ts);
110
111         if (u == (usec_t) -1) {
112                 ts->tv_sec = (time_t) -1;
113                 ts->tv_nsec = (long) -1;
114                 return ts;
115         }
116
117         ts->tv_sec = (time_t) (u / USEC_PER_SEC);
118         ts->tv_nsec = (long int) ((u % USEC_PER_SEC) * NSEC_PER_USEC);
119
120         return ts;
121 }
122
123 usec_t timeval_load(const struct timeval *tv) {
124         assert(tv);
125
126         if (tv->tv_sec == (time_t) -1 &&
127             tv->tv_usec == (suseconds_t) -1)
128                 return (usec_t) -1;
129
130         if ((usec_t) tv->tv_sec > (UINT64_MAX - tv->tv_usec) / USEC_PER_SEC)
131                 return (usec_t) -1;
132
133         return
134                 (usec_t) tv->tv_sec * USEC_PER_SEC +
135                 (usec_t) tv->tv_usec;
136 }
137
138 struct timeval *timeval_store(struct timeval *tv, usec_t u) {
139         assert(tv);
140
141         if (u == (usec_t) -1) {
142                 tv->tv_sec = (time_t) -1;
143                 tv->tv_usec = (suseconds_t) -1;
144                 return tv;
145         }
146
147         tv->tv_sec = (time_t) (u / USEC_PER_SEC);
148         tv->tv_usec = (suseconds_t) (u % USEC_PER_SEC);
149
150         return tv;
151 }
152
153 char *format_timestamp(char *buf, size_t l, usec_t t) {
154         struct tm tm;
155         time_t sec;
156
157         assert(buf);
158         assert(l > 0);
159
160         if (t <= 0)
161                 return NULL;
162
163         sec = (time_t) (t / USEC_PER_SEC);
164
165         if (strftime(buf, l, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)) <= 0)
166                 return NULL;
167
168         return buf;
169 }
170
171 char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
172         usec_t n, d;
173
174         n = now(CLOCK_REALTIME);
175
176         if (t <= 0 || t > n || t + USEC_PER_DAY*7 <= t)
177                 return NULL;
178
179         d = n - t;
180
181         if (d >= USEC_PER_YEAR)
182                 snprintf(buf, l, "%llu years %llu months ago",
183                          (unsigned long long) (d / USEC_PER_YEAR),
184                          (unsigned long long) ((d % USEC_PER_YEAR) / USEC_PER_MONTH));
185         else if (d >= USEC_PER_MONTH)
186                 snprintf(buf, l, "%llu months %llu days ago",
187                          (unsigned long long) (d / USEC_PER_MONTH),
188                          (unsigned long long) ((d % USEC_PER_MONTH) / USEC_PER_DAY));
189         else if (d >= USEC_PER_WEEK)
190                 snprintf(buf, l, "%llu weeks %llu days ago",
191                          (unsigned long long) (d / USEC_PER_WEEK),
192                          (unsigned long long) ((d % USEC_PER_WEEK) / USEC_PER_DAY));
193         else if (d >= 2*USEC_PER_DAY)
194                 snprintf(buf, l, "%llu days ago", (unsigned long long) (d / USEC_PER_DAY));
195         else if (d >= 25*USEC_PER_HOUR)
196                 snprintf(buf, l, "1 day %lluh ago",
197                          (unsigned long long) ((d - USEC_PER_DAY) / USEC_PER_HOUR));
198         else if (d >= 6*USEC_PER_HOUR)
199                 snprintf(buf, l, "%lluh ago",
200                          (unsigned long long) (d / USEC_PER_HOUR));
201         else if (d >= USEC_PER_HOUR)
202                 snprintf(buf, l, "%lluh %llumin ago",
203                          (unsigned long long) (d / USEC_PER_HOUR),
204                          (unsigned long long) ((d % USEC_PER_HOUR) / USEC_PER_MINUTE));
205         else if (d >= 5*USEC_PER_MINUTE)
206                 snprintf(buf, l, "%llumin ago",
207                          (unsigned long long) (d / USEC_PER_MINUTE));
208         else if (d >= USEC_PER_MINUTE)
209                 snprintf(buf, l, "%llumin %llus ago",
210                          (unsigned long long) (d / USEC_PER_MINUTE),
211                          (unsigned long long) ((d % USEC_PER_MINUTE) / USEC_PER_SEC));
212         else if (d >= USEC_PER_SEC)
213                 snprintf(buf, l, "%llus ago",
214                          (unsigned long long) (d / USEC_PER_SEC));
215         else if (d >= USEC_PER_MSEC)
216                 snprintf(buf, l, "%llums ago",
217                          (unsigned long long) (d / USEC_PER_MSEC));
218         else if (d > 0)
219                 snprintf(buf, l, "%lluus ago",
220                          (unsigned long long) d);
221         else
222                 snprintf(buf, l, "now");
223
224         buf[l-1] = 0;
225         return buf;
226 }
227
228 char *format_timespan(char *buf, size_t l, usec_t t) {
229         static const struct {
230                 const char *suffix;
231                 usec_t usec;
232         } table[] = {
233                 { "w", USEC_PER_WEEK },
234                 { "d", USEC_PER_DAY },
235                 { "h", USEC_PER_HOUR },
236                 { "min", USEC_PER_MINUTE },
237                 { "s", USEC_PER_SEC },
238                 { "ms", USEC_PER_MSEC },
239                 { "us", 1 },
240         };
241
242         unsigned i;
243         char *p = buf;
244
245         assert(buf);
246         assert(l > 0);
247
248         if (t == (usec_t) -1)
249                 return NULL;
250
251         if (t == 0) {
252                 snprintf(p, l, "0");
253                 p[l-1] = 0;
254                 return p;
255         }
256
257         /* The result of this function can be parsed with parse_usec */
258
259         for (i = 0; i < ELEMENTSOF(table); i++) {
260                 int k;
261                 size_t n;
262
263                 if (t < table[i].usec)
264                         continue;
265
266                 if (l <= 1)
267                         break;
268
269                 k = snprintf(p, l, "%s%llu%s", p > buf ? " " : "", (unsigned long long) (t / table[i].usec), table[i].suffix);
270                 n = MIN((size_t) k, l);
271
272                 l -= n;
273                 p += n;
274
275                 t %= table[i].usec;
276         }
277
278         *p = 0;
279
280         return buf;
281 }
282
283 void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) {
284
285         assert(f);
286         assert(name);
287         assert(t);
288
289         if (!dual_timestamp_is_set(t))
290                 return;
291
292         fprintf(f, "%s=%llu %llu\n",
293                 name,
294                 (unsigned long long) t->realtime,
295                 (unsigned long long) t->monotonic);
296 }
297
298 void dual_timestamp_deserialize(const char *value, dual_timestamp *t) {
299         unsigned long long a, b;
300
301         assert(value);
302         assert(t);
303
304         if (sscanf(value, "%lli %llu", &a, &b) != 2)
305                 log_debug("Failed to parse finish timestamp value %s", value);
306         else {
307                 t->realtime = a;
308                 t->monotonic = b;
309         }
310 }
311
312 int parse_timestamp(const char *t, usec_t *usec) {
313         static const struct {
314                 const char *name;
315                 const int nr;
316         } day_nr[] = {
317                 { "Sunday",    0 },
318                 { "Sun",       0 },
319                 { "Monday",    1 },
320                 { "Mon",       1 },
321                 { "Tuesday",   2 },
322                 { "Tue",       2 },
323                 { "Wednesday", 3 },
324                 { "Wed",       3 },
325                 { "Thursday",  4 },
326                 { "Thu",       4 },
327                 { "Friday",    5 },
328                 { "Fri",       5 },
329                 { "Saturday",  6 },
330                 { "Sat",       6 },
331         };
332
333         const char *k;
334         struct tm tm, copy;
335         time_t x;
336         usec_t plus = 0, minus = 0, ret;
337         int r, weekday = -1;
338         unsigned i;
339
340         /*
341          * Allowed syntaxes:
342          *
343          *   2012-09-22 16:34:22
344          *   2012-09-22 16:34     (seconds will be set to 0)
345          *   2012-09-22           (time will be set to 00:00:00)
346          *   16:34:22             (date will be set to today)
347          *   16:34                (date will be set to today, seconds to 0)
348          *   now
349          *   yesterday            (time is set to 00:00:00)
350          *   today                (time is set to 00:00:00)
351          *   tomorrow             (time is set to 00:00:00)
352          *   +5min
353          *   -5days
354          *
355          */
356
357         assert(t);
358         assert(usec);
359
360         x = time(NULL);
361         assert_se(localtime_r(&x, &tm));
362         tm.tm_isdst = -1;
363
364         if (streq(t, "now"))
365                 goto finish;
366
367         else if (streq(t, "today")) {
368                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
369                 goto finish;
370
371         } else if (streq(t, "yesterday")) {
372                 tm.tm_mday --;
373                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
374                 goto finish;
375
376         } else if (streq(t, "tomorrow")) {
377                 tm.tm_mday ++;
378                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
379                 goto finish;
380
381         } else if (t[0] == '+') {
382
383                 r = parse_usec(t+1, &plus);
384                 if (r < 0)
385                         return r;
386
387                 goto finish;
388         } else if (t[0] == '-') {
389
390                 r = parse_usec(t+1, &minus);
391                 if (r < 0)
392                         return r;
393
394                 goto finish;
395
396         } else if (endswith(t, " ago")) {
397                 _cleanup_free_ char *z;
398
399                 z = strndup(t, strlen(t) - 4);
400                 if (!z)
401                         return -ENOMEM;
402
403                 r = parse_usec(z, &minus);
404                 if (r < 0)
405                         return r;
406
407                 goto finish;
408         }
409
410         for (i = 0; i < ELEMENTSOF(day_nr); i++) {
411                 size_t skip;
412
413                 if (!startswith_no_case(t, day_nr[i].name))
414                         continue;
415
416                 skip = strlen(day_nr[i].name);
417                 if (t[skip] != ' ')
418                         continue;
419
420                 weekday = day_nr[i].nr;
421                 t += skip + 1;
422                 break;
423         }
424
425         copy = tm;
426         k = strptime(t, "%y-%m-%d %H:%M:%S", &tm);
427         if (k && *k == 0)
428                 goto finish;
429
430         tm = copy;
431         k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm);
432         if (k && *k == 0)
433                 goto finish;
434
435         tm = copy;
436         k = strptime(t, "%y-%m-%d %H:%M", &tm);
437         if (k && *k == 0) {
438                 tm.tm_sec = 0;
439                 goto finish;
440         }
441
442         tm = copy;
443         k = strptime(t, "%Y-%m-%d %H:%M", &tm);
444         if (k && *k == 0) {
445                 tm.tm_sec = 0;
446                 goto finish;
447         }
448
449         tm = copy;
450         k = strptime(t, "%y-%m-%d", &tm);
451         if (k && *k == 0) {
452                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
453                 goto finish;
454         }
455
456         tm = copy;
457         k = strptime(t, "%Y-%m-%d", &tm);
458         if (k && *k == 0) {
459                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
460                 goto finish;
461         }
462
463         tm = copy;
464         k = strptime(t, "%H:%M:%S", &tm);
465         if (k && *k == 0)
466                 goto finish;
467
468         tm = copy;
469         k = strptime(t, "%H:%M", &tm);
470         if (k && *k == 0) {
471                 tm.tm_sec = 0;
472                 goto finish;
473         }
474
475         return -EINVAL;
476
477 finish:
478         x = mktime(&tm);
479         if (x == (time_t) -1)
480                 return -EINVAL;
481
482         if (weekday >= 0 && tm.tm_wday != weekday)
483                 return -EINVAL;
484
485         ret = (usec_t) x * USEC_PER_SEC;
486
487         ret += plus;
488         if (ret > minus)
489                 ret -= minus;
490         else
491                 ret = 0;
492
493         *usec = ret;
494
495         return 0;
496 }
497
498 int parse_usec(const char *t, usec_t *usec) {
499         static const struct {
500                 const char *suffix;
501                 usec_t usec;
502         } table[] = {
503                 { "seconds", USEC_PER_SEC },
504                 { "second", USEC_PER_SEC },
505                 { "sec", USEC_PER_SEC },
506                 { "s", USEC_PER_SEC },
507                 { "minutes", USEC_PER_MINUTE },
508                 { "minute", USEC_PER_MINUTE },
509                 { "min", USEC_PER_MINUTE },
510                 { "months", USEC_PER_MONTH },
511                 { "month", USEC_PER_MONTH },
512                 { "msec", USEC_PER_MSEC },
513                 { "ms", USEC_PER_MSEC },
514                 { "m", USEC_PER_MINUTE },
515                 { "hours", USEC_PER_HOUR },
516                 { "hour", USEC_PER_HOUR },
517                 { "hr", USEC_PER_HOUR },
518                 { "h", USEC_PER_HOUR },
519                 { "days", USEC_PER_DAY },
520                 { "day", USEC_PER_DAY },
521                 { "d", USEC_PER_DAY },
522                 { "weeks", USEC_PER_WEEK },
523                 { "week", USEC_PER_WEEK },
524                 { "w", USEC_PER_WEEK },
525                 { "years", USEC_PER_YEAR },
526                 { "year", USEC_PER_YEAR },
527                 { "y", USEC_PER_YEAR },
528                 { "usec", 1ULL },
529                 { "us", 1ULL },
530                 { "", USEC_PER_SEC }, /* default is sec */
531         };
532
533         const char *p;
534         usec_t r = 0;
535
536         assert(t);
537         assert(usec);
538
539         p = t;
540         do {
541                 long long l;
542                 char *e;
543                 unsigned i;
544
545                 errno = 0;
546                 l = strtoll(p, &e, 10);
547
548                 if (errno != 0)
549                         return -errno;
550
551                 if (l < 0)
552                         return -ERANGE;
553
554                 if (e == p)
555                         return -EINVAL;
556
557                 e += strspn(e, WHITESPACE);
558
559                 for (i = 0; i < ELEMENTSOF(table); i++)
560                         if (startswith(e, table[i].suffix)) {
561                                 r += (usec_t) l * table[i].usec;
562                                 p = e + strlen(table[i].suffix);
563                                 break;
564                         }
565
566                 if (i >= ELEMENTSOF(table))
567                         return -EINVAL;
568
569         } while (*p != 0);
570
571         *usec = r;
572
573         return 0;
574 }
575
576 int parse_nsec(const char *t, nsec_t *nsec) {
577         static const struct {
578                 const char *suffix;
579                 nsec_t nsec;
580         } table[] = {
581                 { "seconds", NSEC_PER_SEC },
582                 { "second", NSEC_PER_SEC },
583                 { "sec", NSEC_PER_SEC },
584                 { "s", NSEC_PER_SEC },
585                 { "minutes", NSEC_PER_MINUTE },
586                 { "minute", NSEC_PER_MINUTE },
587                 { "min", NSEC_PER_MINUTE },
588                 { "months", NSEC_PER_MONTH },
589                 { "month", NSEC_PER_MONTH },
590                 { "msec", NSEC_PER_MSEC },
591                 { "ms", NSEC_PER_MSEC },
592                 { "m", NSEC_PER_MINUTE },
593                 { "hours", NSEC_PER_HOUR },
594                 { "hour", NSEC_PER_HOUR },
595                 { "hr", NSEC_PER_HOUR },
596                 { "h", NSEC_PER_HOUR },
597                 { "days", NSEC_PER_DAY },
598                 { "day", NSEC_PER_DAY },
599                 { "d", NSEC_PER_DAY },
600                 { "weeks", NSEC_PER_WEEK },
601                 { "week", NSEC_PER_WEEK },
602                 { "w", NSEC_PER_WEEK },
603                 { "years", NSEC_PER_YEAR },
604                 { "year", NSEC_PER_YEAR },
605                 { "y", NSEC_PER_YEAR },
606                 { "usec", NSEC_PER_USEC },
607                 { "us", NSEC_PER_USEC },
608                 { "nsec", 1ULL },
609                 { "ns", 1ULL },
610                 { "", 1ULL }, /* default is nsec */
611         };
612
613         const char *p;
614         nsec_t r = 0;
615
616         assert(t);
617         assert(nsec);
618
619         p = t;
620         do {
621                 long long l;
622                 char *e;
623                 unsigned i;
624
625                 errno = 0;
626                 l = strtoll(p, &e, 10);
627
628                 if (errno != 0)
629                         return -errno;
630
631                 if (l < 0)
632                         return -ERANGE;
633
634                 if (e == p)
635                         return -EINVAL;
636
637                 e += strspn(e, WHITESPACE);
638
639                 for (i = 0; i < ELEMENTSOF(table); i++)
640                         if (startswith(e, table[i].suffix)) {
641                                 r += (nsec_t) l * table[i].nsec;
642                                 p = e + strlen(table[i].suffix);
643                                 break;
644                         }
645
646                 if (i >= ELEMENTSOF(table))
647                         return -EINVAL;
648
649         } while (*p != 0);
650
651         *nsec = r;
652
653         return 0;
654 }