+/* SPDX-License-Identifier: LGPL-2.1+ */
/***
- 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/>.
***/
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/timerfd.h>
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
+//#include "io-util.h"
#include "log.h"
#include "macro.h"
#include "parse-util.h"
#include "path-util.h"
+//#include "process-util.h"
+//#include "stat-util.h"
#include "string-util.h"
#include "strv.h"
#include "time-util.h"
return NULL; /* Timestamp is unset */
/* Let's not format times with years > 9999 */
- if (t > USEC_TIMESTAMP_FORMATTABLE_MAX)
- return NULL;
+ if (t > USEC_TIMESTAMP_FORMATTABLE_MAX) {
+ assert(l >= strlen("--- XXXX-XX-XX XX:XX:XX") + 1);
+ strcpy(buf, "--- XXXX-XX-XX XX:XX:XX");
+ return buf;
+ }
sec = (time_t) (t / USEC_PER_SEC); /* Round down */
{ "us", 1 },
};
- unsigned i;
+ size_t i;
char *p = buf;
bool something = false;
/* Let's see if we should shows this in dot notation */
if (t < USEC_PER_MINUTE && b > 0) {
usec_t cc;
- int j;
+ signed char j;
j = 0;
for (cc = table[i].usec; cc > 1; cc /= 10)
time_t x;
usec_t x_usec, plus = 0, minus = 0, ret;
int r, weekday = -1, dst = -1;
- unsigned i;
+ size_t i;
- /*
- * Allowed syntaxes:
+ /* Allowed syntaxes:
*
* 2012-09-22 16:34:22
* 2012-09-22 16:34 (seconds will be set to 0)
* +5min
* -5days
* @2147483647 (seconds since epoch)
- *
*/
assert(t);
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. */
+ * 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 (!localtime_or_gmtime_r(&x, &tm, utc))
return -EINVAL;
- if (!with_tz) {
- tm.tm_isdst = dst;
- if (tzn)
- tm.tm_zone = tzn;
- }
+ tm.tm_isdst = dst;
+ if (!with_tz && tzn)
+ tm.tm_zone = tzn;
if (streq(t, "today")) {
tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
}
from_tm:
- x = mktime_or_timegm(&tm, utc);
- if (x < 0)
+ if (weekday >= 0 && tm.tm_wday != weekday)
return -EINVAL;
- if (weekday >= 0 && tm.tm_wday != weekday)
+ x = mktime_or_timegm(&tm, utc);
+ if (x < 0)
return -EINVAL;
ret = (usec_t) x * USEC_PER_SEC + x_usec;
return 0;
}
-#endif // 0
typedef struct ParseTimestampResult {
usec_t usec;
} ParseTimestampResult;
int parse_timestamp(const char *t, usec_t *usec) {
- char *last_space, *timezone = NULL;
+ char *last_space, *tz = NULL;
ParseTimestampResult *shared, tmp;
int r;
- pid_t pid;
last_space = strrchr(t, ' ');
+ if (last_space != NULL && timezone_is_valid(last_space + 1, LOG_DEBUG))
+ tz = last_space + 1;
- if (last_space != NULL) {
- if (timezone_is_valid(last_space + 1)) {
- timezone = last_space + 1;
- }
- }
-
- if (timezone == NULL || endswith_no_case(t, " UTC"))
+ if (!tz || endswith_no_case(t, " UTC"))
return parse_timestamp_impl(t, usec, false);
- t = strndupa(t, last_space - t);
-
shared = mmap(NULL, sizeof *shared, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
if (shared == MAP_FAILED)
return negative_errno();
- pid = fork();
-
- if (pid == -1) {
- int fork_errno = errno;
+ r = safe_fork("(sd-timestamp)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_WAIT, NULL);
+ if (r < 0) {
(void) munmap(shared, sizeof *shared);
- return -fork_errno;
+ return r;
}
+ if (r == 0) {
+ bool with_tz = true;
- if (pid == 0) {
- if (setenv("TZ", timezone, 1) != 0) {
+ if (setenv("TZ", tz, 1) != 0) {
shared->return_value = negative_errno();
_exit(EXIT_FAILURE);
}
tzset();
- shared->return_value = parse_timestamp_impl(t, &shared->usec, true);
+ /* If there is a timezone that matches the tzname fields, leave the parsing to the implementation.
+ * Otherwise just cut it off. */
+ with_tz = !STR_IN_SET(tz, tzname[0], tzname[1]);
- _exit(EXIT_SUCCESS);
- }
+ /* Cut off the timezone if we dont need it. */
+ if (with_tz)
+ t = strndupa(t, last_space - t);
- r = wait_for_terminate(pid, NULL);
- if (r < 0) {
- (void) munmap(shared, sizeof *shared);
- return r;
+ shared->return_value = parse_timestamp_impl(t, &shared->usec, with_tz);
+
+ _exit(EXIT_SUCCESS);
}
tmp = *shared;
return tmp.return_value;
}
+#endif // 0
static char* extract_multiplier(char *p, usec_t *multiplier) {
static const struct {
{ "us", 1ULL },
{ "µs", 1ULL },
};
- unsigned i;
+ size_t i;
for (i = 0; i < ELEMENTSOF(table); i++) {
char *e;
#if 0 /// UNNEEDED by elogind
int parse_sec_fix_0(const char *t, usec_t *usec) {
+ assert(t);
+ assert(usec);
+
t += strspn(t, WHITESPACE);
+
if (streq(t, "0")) {
*usec = USEC_INFINITY;
return 0;
for (;;) {
long long l, z = 0;
+ size_t n = 0, i;
char *e;
- unsigned i, n = 0;
p += strspn(p, WHITESPACE);
} else if (errno != ENOENT)
return -errno;
- *ret = zones;
- zones = NULL;
+ *ret = TAKE_PTR(zones);
return 0;
}
-bool timezone_is_valid(const char *name) {
+bool timezone_is_valid(const char *name, int log_level) {
bool slash = false;
const char *p, *t;
- struct stat st;
+ _cleanup_close_ int fd = -1;
+ char buf[4];
+ int r;
if (isempty(name))
return false;
if (!(*p >= '0' && *p <= '9') &&
!(*p >= 'a' && *p <= 'z') &&
!(*p >= 'A' && *p <= 'Z') &&
- !(*p == '-' || *p == '_' || *p == '+' || *p == '/'))
+ !IN_SET(*p, '-', '_', '+', '/'))
return false;
if (*p == '/') {
if (slash)
return false;
+ if (p - name >= PATH_MAX)
+ return false;
+
t = strjoina("/usr/share/zoneinfo/", name);
- if (stat(t, &st) < 0)
+
+ fd = open(t, O_RDONLY|O_CLOEXEC);
+ if (fd < 0) {
+ log_full_errno(log_level, errno, "Failed to open timezone file '%s': %m", t);
return false;
+ }
- if (!S_ISREG(st.st_mode))
+ r = fd_verify_regular(fd);
+ if (r < 0) {
+ log_full_errno(log_level, r, "Timezone file '%s' is not a regular file: %m", t);
return false;
+ }
+
+ r = loop_read_exact(fd, buf, 4, false);
+ if (r < 0) {
+ log_full_errno(log_level, r, "Failed to read from timezone file '%s': %m", t);
+ return false;
+ }
+
+ /* Magic from tzfile(5) */
+ if (memcmp(buf, "TZif", 4) != 0) {
+ log_full(log_level, "Timezone file '%s' has wrong magic bytes", t);
+ return false;
+ }
return true;
}
}
#endif // 0
+#if 1 /// let's add a diagnostic push to silence -Wimplicit-fallthrough to elogind
+# if defined(__GNUC__) && (__GNUC__ > 6)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
+# endif // __GNUC__
+#endif // 1
bool clock_supported(clockid_t clock) {
struct timespec ts;
if (!clock_boottime_supported())
return false;
- /* fall through */
-
+ _fallthrough_;
default:
/* For everything else, check properly */
return clock_gettime(clock, &ts) >= 0;
}
}
+#if 1 /// end diagnostic push in elogind
+# ifdef __GNUC__
+# pragma GCC diagnostic pop
+# endif // __GNUC__
+#endif // 1
#if 0 /// UNNEEDED by elogind
int get_timezone(char **tz) {
if (!e)
return -EINVAL;
- if (!timezone_is_valid(e))
+ if (!timezone_is_valid(e, LOG_DEBUG))
return -EINVAL;
z = strdup(e);
return usec_sub_unsigned(b, usec_sub_unsigned(a, x));
}
#endif // 0
+
+bool in_utc_timezone(void) {
+ tzset();
+
+ return timezone == 0 && daylight == 0;
+}
+
+int time_change_fd(void) {
+
+ /* We only care for the cancellation event, hence we set the timeout to the latest possible value. */
+ static const struct itimerspec its = {
+ .it_value.tv_sec = TIME_T_MAX,
+ };
+
+ _cleanup_close_ int fd;
+
+ assert_cc(sizeof(time_t) == sizeof(TIME_T_MAX));
+
+ /* Uses TFD_TIMER_CANCEL_ON_SET to get notifications whenever CLOCK_REALTIME makes a jump relative to
+ * CLOCK_MONOTONIC. */
+
+ fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC);
+ if (fd < 0)
+ return -errno;
+
+ if (timerfd_settime(fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) < 0)
+ return -errno;
+
+ return TAKE_FD(fd);
+}