commit aa04c0126a590fc9646d491151bcbfeed34ba693 Author: elly elly@leptoquark.net Date: Mon Jun 24 15:36:01 2013 -0400
Refactor event loop.
Refactor the event loop to be modular and testable. Also, add support for detecting corruption of the realtime clock, as can be caused by suspend/resume cycles without an rtc battery. The event loop is now driven by a tree of events, which are either sources (currently suspend/resume events, periodic events, and network route events) or composite events.
Signed-off-by: Elly Fong-Jones elly@leptoquark.net --- CHANGELOG | 2 + Makefile.am | 2 +- run-tests | 4 +- src/event-unittest.c | 48 ++++++++ src/event.c | 295 ++++++++++++++++++++++++++++++++++++++++++++++ src/event.h | 21 ++++ src/include.am | 10 +- src/tlsdate.h | 4 + src/tlsdated-unittest.c | 80 +++++++++++++ src/tlsdated.c | 300 +++++++++++++++++++++++++---------------------- src/util.c | 187 +++++++++++++++++++++++++++++ src/util.h | 19 +++ tests/common.sh | 2 +- 13 files changed, 831 insertions(+), 143 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG index e7c89f1..34879c5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,8 @@ Add DragonFly BSD 3.3 support Refactored subprocess watching. Added integration tests. Run with ./run-tests + Refactored event loop. + Added suspend/resume RTC corruption detection. 0.0.6 Mon 18 Feb, 2013 Ensure that tlsdate compiles with g++ by explicit casting rather than implicit casting by whatever compiler is compiling tlsdate. diff --git a/Makefile.am b/Makefile.am index 5c2c845..cd5879b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -19,7 +19,7 @@ if !TARGET_OSX # GNU style is "make check", this will make check and test work TESTS+= src/conf_unittest src/proxy-bio_unittest if TARGET_LINUX -TESTS+= src/tlsdated_unittest +TESTS+= src/tlsdated_unittest src/event_unittest endif test: check endif diff --git a/run-tests b/run-tests index 693fa59..e3b4e09 100755 --- a/run-tests +++ b/run-tests @@ -5,9 +5,9 @@ run_test() { if [ -r "$1"/tlsdated-flags ]; then flags=$(cat "$1"/tlsdated-flags | sed "s/@TESTDIR@/$1/g") elif [ -r "$1"/test.conf ]; then - flags="-w -p -r -l -s -f $1/test.conf" + flags="-w -p -r -l -s -f $1/test.conf -v" else - flags="-w -p -r -l -s -f test.conf" + flags="-w -p -r -l -s -f test.conf -v" fi # flags are deliberately unquoted here so that they'll be interpolated timeout 5 src/tlsdated $flags -- "$1"/subproc.sh <"$1"/input >"$1"/run-output \ diff --git a/src/event-unittest.c b/src/event-unittest.c new file mode 100644 index 0000000..a97f941 --- /dev/null +++ b/src/event-unittest.c @@ -0,0 +1,48 @@ +#include "src/event.h" +#include "src/test_harness.h" + +#include <sys/time.h> + +TEST(every) { + struct event *e = event_every(3); + time_t then = time(NULL); + time_t now; + EXPECT_EQ(1, event_wait(e)); + now = time(NULL); + EXPECT_GT(now, then + 2); + event_free(e); +} + +TEST(fdread) { + int fds[2]; + struct event *e; + char z = '0'; + + ASSERT_EQ(0, pipe(fds)); + e = event_fdread(fds[0]); + + write(fds[1], &z, 1); + EXPECT_EQ(1, event_wait(e)); + event_free(e); +} + +TEST(composite) { + struct event *e0 = event_every(2); + struct event *e1 = event_every(3); + struct event *ec = event_composite(); + time_t then = time(NULL); + time_t now; + + ASSERT_EQ(0, event_composite_add(ec, e0)); + ASSERT_EQ(0, event_composite_add(ec, e1)); + EXPECT_EQ(1, event_wait(ec)); + EXPECT_EQ(1, event_wait(ec)); + EXPECT_EQ(1, event_wait(ec)); + now = time(NULL); + EXPECT_EQ(now, then + 4); + event_free(ec); + event_free(e1); + event_free(e0); +} + +TEST_HARNESS_MAIN diff --git a/src/event.c b/src/event.c new file mode 100644 index 0000000..3b2fedb --- /dev/null +++ b/src/event.c @@ -0,0 +1,295 @@ +#include <assert.h> +#include <signal.h> +#include <stdlib.h> +#include <sys/select.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> + +#include "routeup.h" + +struct event { + const char *name; + int (*fd)(struct event *); + int (*wait)(struct event *); + void (*free)(struct event *); +}; + +struct event_fd { + struct event event; + int fd; + pid_t pid; +}; + +struct event_composite { + struct event event; + struct event **children; + size_t nchildren; +}; + +static int _subproc_fd(struct event *e) +{ + struct event_fd *efd = (struct event_fd *)e; + return efd->fd; +} + +static int _subproc_wait(struct event *e) +{ + struct event_fd *efd = (struct event_fd *)e; + char buf; + if (read(_subproc_fd(e), &buf, 1) == 1) + return 1; + /* fd is broken...? */ + close(efd->fd); + efd->fd = -1; + return -1; +} + +static const char kZero = '0'; + +static void _subproc_signal(int fd) +{ + ssize_t _unused = write(fd, &kZero, 1); + (void)_unused; +} + +static void _subproc_free(struct event *e) +{ + struct event_fd *efd = (struct event_fd *)e; + if (efd->pid > 0) { + kill(efd->pid, SIGKILL); + waitpid(efd->pid, NULL, 0); + } + close(efd->fd); + free(efd); +} + +static struct event *_event_subproc(void (*func)(void *, int), void *arg) +{ + struct event_fd *ev = malloc(sizeof *ev); + int fds[2]; + + if (!ev) + return NULL; + + ev->event.fd = _subproc_fd; + ev->event.wait = _subproc_wait; + ev->event.free = _subproc_free; + + if (pipe(fds)) + { + free(ev); + return NULL; + } + + ev->pid = fork(); + if (ev->pid < 0) + { + close(fds[0]); + close(fds[1]); + free(ev); + return NULL; + } + else if (ev->pid == 0) + { + close(fds[0]); + func(arg, fds[1]); + exit(0); + } + + close(fds[1]); + ev->fd = fds[0]; + + return &ev->event; +} + +static void _routeup(void *arg, int fd) +{ + struct routeup *rtup = arg; + while (!routeup_once(rtup, 0)) + _subproc_signal(fd); +} + +struct event *event_routeup() +{ + struct routeup *rtup = malloc(sizeof *rtup); + struct event *e; + if (!rtup) + return NULL; + if (!routeup_setup(rtup)) + { + free(rtup); + return NULL; + } + e = _event_subproc(_routeup, (void*)rtup); + /* free it in the parent only */ + routeup_teardown(rtup); + if (e) + e->name = "routeup"; + return e; +} + +static void _every(void *arg, int fd) +{ + int delay = *(int*)arg; + while (1) + { + sleep(delay); + _subproc_signal(fd); + } +} + +struct event *event_every(int seconds) +{ + struct event *e = _event_subproc(_every, (void *)&seconds); + e->name = "every"; + return e; +} + +static void _suspend(void *arg, int fd) +{ + while (1) + { + static const int kInterval = 60; + static const int kThreshold = 3; + time_t then = time(NULL); + time_t now; + sleep(kInterval); + now = time(NULL); + if (abs(now - then - kInterval) > kThreshold) + _subproc_signal(fd); + } +} + +struct event *event_suspend() +{ + struct event *e = _event_subproc(_suspend, NULL); + e->name = "suspend"; + return e; +} + +struct event *event_fdread(int fd) +{ + struct event_fd *efd = malloc(sizeof *efd); + efd->event.name = "fdread"; + efd->event.fd = _subproc_fd; + efd->event.wait = _subproc_wait; + efd->event.free = _subproc_free; + efd->fd = fd; + return &efd->event; +} + +int event_wait(struct event *e) +{ + assert(e->wait); + return e->wait(e); +} + +void event_free(struct event *e) +{ + assert(e->free); + return e->free(e); +} + +static int _composite_fd(struct event *e) +{ + return -1; +} + +static int _composite_wait(struct event *e) +{ + struct event_composite *ec = (struct event_composite *)e; + fd_set fds; + size_t i; + int n; + int maxfd = -1; + + FD_ZERO(&fds); + for (i = 0; i < ec->nchildren; i++) + { + struct event *child = ec->children[i]; + int fd = child->fd(child); + if (fd != -1) + FD_SET(child->fd(child), &fds); + if (fd > maxfd) + maxfd = fd; + } + + n = select(maxfd + 1, &fds, NULL, NULL, NULL); + if (n < 0) + return -1; + for (i = 0; i < ec->nchildren; i++) + { + struct event *child = ec->children[i]; + int fd = child->fd(child); + if (FD_ISSET(fd, &fds)) + { + /* won't block, but clears the event */ + int r = child->wait(child); + if (r) + /* either error or success, but not 'no event' */ + return r; + } + } + + return 0; +} + +static void _composite_free(struct event *e) +{ + struct event_composite *ec = (struct event_composite *)e; + free(ec->children); + free(ec); +} + +struct event *event_composite(void) +{ + struct event_composite *ec = malloc(sizeof *ec); + if (!ec) + return NULL; + ec->event.name = "composite"; + ec->event.fd = _composite_fd; + ec->event.wait = _composite_wait; + ec->event.free = _composite_free; + ec->children = NULL; + ec->nchildren = 0; + return &ec->event; +} + +int event_composite_add(struct event *comp, struct event *e) +{ + struct event_composite *ec = (struct event_composite *)comp; + size_t i; + size_t nsz; + struct event **nch; + for (i = 0; i < ec->nchildren; i++) + { + if (ec->children[i]) + continue; + ec->children[i] = e; + return 0; + } + nsz = ec->nchildren + 1; + nch = realloc(ec->children, nsz * sizeof(struct event *)); + if (!nch) + return 1; + ec->nchildren = nsz; + ec->children = nch; + ec->children[nsz - 1] = e; + return 0; +} + +int event_composite_del(struct event *comp, struct event *e) +{ + struct event_composite *ec = (struct event_composite *)comp; + size_t i; + for (i = 0; i < ec->nchildren; i++) + { + if (ec->children[i] != e) + continue; + ec->children[i] = NULL; + return 0; + } + return 1; +} diff --git a/src/event.h b/src/event.h new file mode 100644 index 0000000..ce4a535 --- /dev/null +++ b/src/event.h @@ -0,0 +1,21 @@ +/* event.h - event sources */ + +#ifndef EVENT_H +#define EVENT_H + +struct event; + +extern struct event *event_fdread(int fd); +extern struct event *event_routeup(void); +extern struct event *event_suspend(void); +extern struct event *event_every(int seconds); + +extern struct event *event_composite(void); +extern int event_composite_add(struct event *comp, struct event *e); +extern int event_composite_del(struct event *comp, struct event *e); + +extern int event_wait(struct event *e); + +extern void event_free(struct event *e); + +#endif /* !EVENT_H */ diff --git a/src/include.am b/src/include.am index f724f39..953bcd6 100644 --- a/src/include.am +++ b/src/include.am @@ -183,6 +183,7 @@ src_tlsdated_SOURCES+= src/common/android.c endif
src_tlsdated_SOURCES+= src/conf.c +src_tlsdated_SOURCES+= src/event.c src_tlsdated_SOURCES+= src/routeup.c src_tlsdated_SOURCES+= src/tlsdated.c src_tlsdated_SOURCES+= src/util.c @@ -192,7 +193,8 @@ src_tlsdate_dbus_announce_LDADD = @DBUS_LIBS@ src_tlsdate_dbus_announce_SOURCES = src/tlsdate-dbus-announce.c
src_tlsdated_unittest_LDADD = @SSL_LIBS@ -src_tlsdated_unittest_SOURCES = src/routeup.c +src_tlsdated_unittest_SOURCES = src/event.c +src_tlsdated_unittest_SOURCES+= src/routeup.c src_tlsdated_unittest_SOURCES+= src/tlsdated-unittest.c src_tlsdated_unittest_SOURCES+= src/util.c src_tlsdated_unittest_SOURCES+= src/tlsdated.c @@ -424,6 +426,12 @@ endif endif endif
+src_event_unittest_SOURCES = src/event.c +src_event_unittest_SOURCES+= src/event-unittest.c +src_event_unittest_SOURCES+= src/routeup.c +src_event_unittest_SOURCES+= src/util.c +check_PROGRAMS+= src/event_unittest + if !TARGET_OSX check_PROGRAMS+= src/test/proxy-override src/test/return-argc \ src/test/rotate src/test/sleep-wrap diff --git a/src/tlsdate.h b/src/tlsdate.h index 978396d..c1e64ac 100644 --- a/src/tlsdate.h +++ b/src/tlsdate.h @@ -11,6 +11,7 @@ #define TLSDATE_H
#include "src/configmake.h" +#include <limits.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> @@ -84,6 +85,9 @@ struct opts { int leap; };
+char timestamp_path[PATH_MAX]; + +void sync_hwclock (void *rtc_handle); int is_sane_time (time_t ts); int load_disk_timestamp (const char *path, time_t * t); void save_disk_timestamp (const char *path, time_t t); diff --git a/src/tlsdated-unittest.c b/src/tlsdated-unittest.c index 5baca13..a56e57e 100644 --- a/src/tlsdated-unittest.c +++ b/src/tlsdated-unittest.c @@ -14,6 +14,7 @@ #include <fcntl.h> #include <limits.h> #include <stdlib.h> +#include <sys/time.h> #include <unistd.h>
FIXTURE(tempdir) { @@ -195,6 +196,24 @@ TEST(proxy_override) { EXPECT_EQ(0, tlsdate(&opts, environ)); }
+FIXTURE(mock_platform) { + struct platform platform; + struct platform *old_platform; +}; + +FIXTURE_SETUP(mock_platform) { + self->old_platform = platform; + self->platform.rtc_open = NULL; + self->platform.rtc_write = NULL; + self->platform.rtc_read = NULL; + self->platform.rtc_close = NULL; + platform = &self->platform; +} + +FIXTURE_TEARDOWN(mock_platform) { + platform = self->old_platform; +} + TEST(tlsdate_args) { struct source s1 = { .next = NULL, @@ -213,4 +232,65 @@ TEST(tlsdate_args) { EXPECT_EQ(9, tlsdate(&opts, environ)); }
+/* + * The stuff below this line is ugly. For a lot of these mock functions, we want + * to smuggle some state (our success) back to the caller, but there's no angle + * for that, so we're basically stuck with some static variables to store + * expectations and successes/failures. This could also be done with nested + * functions, but only gcc supports them. + */ +static const time_t sync_hwclock_expected = 12345678; + +static int sync_hwclock_time_get(struct timeval *tv) { + tv->tv_sec = sync_hwclock_expected; + tv->tv_usec = 0; + return 0; +} + +static int sync_hwclock_rtc_write(void *handle, const struct timeval *tv) { + *(int *)handle = tv->tv_sec == sync_hwclock_expected; + return 0; +} + +TEST_F(mock_platform, sync_hwclock) { + int ok = 0; + void *fake_handle = (void *)&ok; + self->platform.time_get = sync_hwclock_time_get; + self->platform.rtc_write = sync_hwclock_rtc_write; + sync_hwclock(fake_handle); + ASSERT_EQ(ok, 1); +} + +static const time_t sync_and_save_expected = 12345678; + +static int sync_and_save_time_get(struct timeval *tv) { + tv->tv_sec = sync_and_save_expected; + tv->tv_usec = 0; + return 0; +} + +static int sync_and_save_rtc_write(void *handle, const struct timeval *tv) { + *(int *)handle += tv->tv_sec == sync_and_save_expected; + return 0; +} + +static int sync_and_save_file_write_ok = 0; + +static int sync_and_save_file_write(const char *path, void *buf, size_t sz) { + if (!strcmp(path, timestamp_path)) + sync_and_save_file_write_ok++; + return 0; +} + +TEST_F(mock_platform, sync_and_save) { + int nosave_ok = 0; + self->platform.time_get = sync_and_save_time_get; + self->platform.rtc_write = sync_and_save_rtc_write; + self->platform.file_write = sync_and_save_file_write; + sync_and_save(&sync_and_save_file_write_ok, 1); + ASSERT_EQ(sync_and_save_file_write_ok, 2); + sync_and_save(&nosave_ok, 0); + ASSERT_EQ(nosave_ok, 1); +} + TEST_HARNESS_MAIN diff --git a/src/tlsdated.c b/src/tlsdated.c index 7816223..0f6759b 100644 --- a/src/tlsdated.c +++ b/src/tlsdated.c @@ -38,12 +38,12 @@ #endif
#include "src/conf.h" +#include "src/event.h" #include "src/routeup.h" #include "src/util.h" #include "src/tlsdate.h"
const char *kCacheDir = DEFAULT_DAEMON_CACHEDIR; -const char *kTempSuffix = DEFAULT_DAEMON_TMPSUFFIX;
int is_sane_time (time_t ts) @@ -145,25 +145,15 @@ tlsdate (struct opts *opts, char *envp[]) int load_disk_timestamp (const char *path, time_t * t) { - int fd = open (path, O_RDONLY | O_NOFOLLOW); time_t tmpt; - if (fd < 0) - { - perror ("Can't open %s for reading", path); - return -1; - } - if (read (fd, &tmpt, sizeof (tmpt)) != sizeof (tmpt)) - { - perror ("Can't read seconds from %s", path); - close (fd); - return -1; - } - close (fd); - if (!is_sane_time (tmpt)) - { - perror ("Timevalue not sane: %lu", tmpt); - return -1; - } + if (platform->file_read(path, &tmpt, sizeof(tmpt))) { + info("can't load time file"); + return -1; + } + if (!is_sane_time(tmpt)) { + info("time %lu not sane", tmpt); + return -1; + } *t = tmpt; return 0; } @@ -172,34 +162,8 @@ load_disk_timestamp (const char *path, time_t * t) void save_disk_timestamp (const char *path, time_t t) { - char tmp[PATH_MAX]; - int fd; - - if (snprintf (tmp, sizeof (tmp), "%s%s", path, kTempSuffix) >= sizeof (tmp)) - { - pinfo ("Path %s too long to use", path); - exit (1); - } - - if ((fd = open (tmp, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, - S_IRUSR | S_IWUSR)) < 0) - { - pinfo ("open failed"); - return; - } - if (write (fd, &t, sizeof (t)) != sizeof (t)) - { - pinfo ("write failed"); - close (fd); - return; - } - if (close (fd)) - { - pinfo ("close failed"); - return; - } - if (rename (tmp, path)) - pinfo ("rename failed"); + if (platform->file_write(path, &t, sizeof(t))) + info("saving disk timestamp failed"); }
/* @@ -210,38 +174,17 @@ save_disk_timestamp (const char *path, time_t t) * this function except try later. */ void -sync_hwclock (int fd) +sync_hwclock (void *rtc_handle) { struct timeval tv; - struct tm *tm; - struct rtc_time rtctm; - - if (gettimeofday (&tv, NULL)) - { - pinfo ("gettimeofday() failed"); - return; - } - - tm = gmtime (&tv.tv_sec); - - /* these structs are identical, but separately defined */ - rtctm.tm_sec = tm->tm_sec; - rtctm.tm_min = tm->tm_min; - rtctm.tm_hour = tm->tm_hour; - rtctm.tm_mday = tm->tm_mday; - rtctm.tm_mon = tm->tm_mon; - rtctm.tm_year = tm->tm_year; - rtctm.tm_wday = tm->tm_wday; - rtctm.tm_yday = tm->tm_yday; - rtctm.tm_isdst = tm->tm_isdst; - - if (ioctl (fd, RTC_SET_TIME, &rtctm)) - { - pinfo ("ioctl(%d, RTC_SET_TIME, ...) failed", fd); - return; - } + if (platform->time_get(&tv)) + { + pinfo("gettimeofday() failed"); + return; + }
- info ("synced rtc to sysclock"); + if (platform->rtc_write(rtc_handle, &tv)) + info("rtc_write() failed"); }
/* @@ -274,14 +217,14 @@ wait_for_event (struct routeup *rtc, int should_netlink, int timeout) char timestamp_path[PATH_MAX];
void -sync_and_save (int hwclock_fd, int should_sync, int should_save) +sync_and_save (void *hwclock_handle, int should_save) { struct timeval tv; - if (should_sync) - sync_hwclock (hwclock_fd); + if (hwclock_handle) + sync_hwclock (hwclock_handle); if (should_save) { - if (gettimeofday (&tv, NULL)) + if (platform->time_get(&tv)) pfatal ("gettimeofday() failed"); save_disk_timestamp (timestamp_path, tv.tv_sec); } @@ -320,6 +263,12 @@ add_jitter (int base, int jitter) return base + (abs(n) % (2 * jitter)) - jitter; }
+int +calc_wait_time (const struct opts *opts) +{ + return add_jitter (opts->steady_state_interval, opts->jitter); +} + #ifdef TLSDATED_MAIN #ifdef HAVE_DBUS void @@ -348,6 +297,7 @@ void sigterm_handler (int _unused) { struct timeval tv; + platform->pgrp_kill(); if (gettimeofday (&tv, NULL)) /* can't use stdio or syslog inside a sig handler */ exit (2); @@ -356,6 +306,13 @@ sigterm_handler (int _unused) }
void +sigterm_handler_nosave (int _unused) +{ + platform->pgrp_kill(); + exit(0); +} + +void usage (const char *progn) { printf ("Usage: %s [flags...] [--] [tlsdate command...]\n", progn); @@ -608,15 +565,60 @@ check_conf(struct opts *opts) opts->jitter, opts->steady_state_interval); }
+struct tlsdated_state +{ + struct routeup rtc; + time_t last_success; + struct opts *opts; + char *envp[]; +}; + +static time_t now(void) +{ + struct timeval tv; + if (!platform->time_get(&tv)) + return 0; + return tv.tv_sec; +} + +int tlsdate_retry(struct opts *opts, char *envp[]) +{ + int backoff = opts->wait_between_tries; + int i; + + for (i = 0; i < opts->max_tries; i++) + { + if (!tlsdate(opts, envp)) + return 0; + if (backoff < 1) + fatal("backoff too small? %d", backoff); + sleep(backoff); + if (backoff < MAX_SANE_BACKOFF) + backoff *= 2; + } + return 1; +} + int API main (int argc, char *argv[], char *envp[]) { - struct routeup rtc; - int hwclock_fd = -1; + void *hwclock_handle = NULL; time_t last_success = 0; struct opts opts; - int wait_time = 0; struct timeval tv = { 0, 0 }; + int r; + + struct event *routeup = NULL; + struct event *suspend = NULL; + struct event *periodic = NULL; /* XXX */ + struct event *composite = event_composite(); + + if (platform->pgrp_enter()) + pfatal("pgrp_enter() failed"); + + suspend = event_suspend(); + + event_composite_add(composite, suspend);
set_conf_defaults(&opts); parse_argv(&opts, argc, argv); @@ -624,6 +626,9 @@ main (int argc, char *argv[], char *envp[]) load_conf(&opts); check_conf(&opts);
+ periodic = event_every(opts.steady_state_interval); + event_composite_add(composite, periodic); + info ("started up, loaded config file");
if (!opts.should_load_disk || load_disk_timestamp (timestamp_path, &tv.tv_sec)) @@ -636,49 +641,53 @@ main (int argc, char *argv[], char *envp[]) (char *) DEFAULT_PROXY);
/* grab a handle to /dev/rtc for sync_hwclock() */ - if (opts.should_sync_hwclock && (hwclock_fd = open (DEFAULT_RTC_DEVICE, O_RDONLY)) < 0) - { - pinfo ("can't open hwclock fd"); - opts.should_sync_hwclock = 0; - } + if (opts.should_sync_hwclock && !(hwclock_handle = platform->rtc_open())) + pinfo ("can't open hwclock fd"); + + if (opts.should_netlink) + routeup = event_routeup(); + else + routeup = event_fdread(STDIN_FILENO); + event_composite_add(composite, routeup);
- /* set up a netlink context if we need one */ - if (opts.should_netlink && routeup_setup (&rtc)) + if (!routeup) pfatal ("Can't open netlink socket");
if (!is_sane_time (time (NULL))) - { - /* - * If the time is before the build timestamp, we're probably on - * a system with a broken rtc. Try loading the timestamp off - * disk. - */ - tv.tv_sec = RECENT_COMPILE_DATE; - if (opts.should_load_disk && - load_disk_timestamp (timestamp_path, &tv.tv_sec)) - pinfo ("can't load disk timestamp"); - if (!opts.dry_run && settimeofday (&tv, NULL)) - pfatal ("settimeofday() failed"); - dbus_announce(); - /* - * don't save here - we either just loaded this time or used the - * default time, and neither of those are good to save - */ - sync_and_save (hwclock_fd, opts.should_sync_hwclock, 0); - } + { + /* + * If the time is before the build timestamp, we're probably on + * a system with a broken rtc. Try loading the timestamp off + * disk. + */ + tv.tv_sec = RECENT_COMPILE_DATE; + if (opts.should_load_disk + && load_disk_timestamp (timestamp_path, &tv.tv_sec)) + pinfo ("can't load disk timestamp"); + if (!opts.dry_run && settimeofday (&tv, NULL)) + pfatal ("settimeofday() failed"); + dbus_announce(); + /* + * don't save here - we either just loaded this time or used the + * default time, and neither of those are good to save + */ + sync_and_save (hwclock_handle, 0); + }
- /* register a signal handler to save time at shutdown */ if (opts.should_save_disk) signal (SIGTERM, sigterm_handler); + else + signal (SIGTERM, sigterm_handler_nosave);
/* * Try once right away. If we fail, wait for a route to appear, then try * for a while; repeat whenever another route appears. Try until we * succeed. */ - if (!tlsdate (&opts, envp)) { + if (!tlsdate (&opts, envp)) + { last_success = time (NULL); - sync_and_save (hwclock_fd, opts.should_sync_hwclock, opts.should_save_disk); + sync_and_save (hwclock_handle, opts.should_save_disk); dbus_announce(); }
@@ -688,36 +697,51 @@ main (int argc, char *argv[], char *envp[]) * this should handle cases like a VPN being brought up and down * periodically. */ - wait_time = add_jitter(opts.steady_state_interval, opts.jitter); - while (wait_for_event (&rtc, opts.should_netlink, wait_time) >= 0) + + /* + * The event loop. + * When we start up, we may have no time fix at all; we'll call this "active + * mode", where we are actively looking for opportunities to get a time fix. + * Once we get a time fix, we go into "passive mode", where we're looking to + * prevent clock drift or rtc corruption. The difference between these two + * modes is whether we're aggressive about checking for time after network + * routes come up. + * + * To do this, we create some event sources: + * e0 = event_routeup() + * e1 = event_suspend() + * e2 = event_every(interval) + * Then we create a couple of composite events: + * active = event_anyof(3, e0, e1, e2) + * Whenever our wait for an event returns, we start checking (i.e., running + * tlsdate with exponential backoff until it succeeds). If checking succeeds + * and we were in active state, we go to passive state; otherwise we return to + * our previous state. + */ + + while ((r = event_wait(composite))) + { + if (r < 0) { - /* - * If a route just came up, run tlsdate; if it - * succeeded, then we're good and can keep time locally - * from now on. - */ - int i; - int backoff = opts.wait_between_tries; - wait_time = add_jitter(opts.steady_state_interval, opts.jitter); - if (time (NULL) - last_success < opts.min_steady_state_interval) - continue; - for (i = 0; i < opts.max_tries && tlsdate (&opts, envp); ++i) { - if (backoff < 1) - fatal ("backoff too small? %d", backoff); - sleep (backoff); - if (backoff < MAX_SANE_BACKOFF) - backoff *= 2; - } - if (i != opts.max_tries) - { - last_success = time (NULL); - info ("tlsdate succeeded"); - sync_and_save (hwclock_fd, opts.should_sync_hwclock, opts.should_save_disk); - dbus_announce(); - } + info("event_wait() failed: %d", r); + continue; } + if (now() - last_success < opts.min_steady_state_interval) + { + info("too soon"); + continue; + } + if (!tlsdate_retry(&opts, envp)) + { + last_success = now(); + info("tlsdate succeeded"); + sync_and_save (hwclock_handle, opts.should_save_disk); + dbus_announce(); + } + }
info ("exiting"); + platform->pgrp_kill(); return 1; } #endif /* !TLSDATED_MAIN */ diff --git a/src/util.c b/src/util.c index 30a0f44..fe5c370 100644 --- a/src/util.c +++ b/src/util.c @@ -6,19 +6,29 @@ */
#include "config.h" +#include "tlsdate.h"
+#include <fcntl.h> #include <grp.h> +#include <limits.h> +#include <linux/rtc.h> #include <pwd.h> #include <signal.h> #include <stdarg.h> #include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.h> +#include <sys/time.h> #include <sys/types.h> #include <sys/wait.h> #include <syslog.h> +#include <time.h> #include <unistd.h>
#include "src/util.h"
+const char *kTempSuffix = DEFAULT_DAEMON_TMPSUFFIX; + /** helper function to print message and die */ void die (const char *fmt, ...) @@ -132,3 +142,180 @@ wait_with_timeout(int *status, int timeout_secs) *status = st; return exited; } + +struct rtc_handle +{ + int fd; +}; + +void *rtc_open() +{ + struct rtc_handle *h = malloc(sizeof *h); + h->fd = open(DEFAULT_RTC_DEVICE, O_RDONLY); + if (h->fd < 0) + { + pinfo("can't open rtc"); + free(h); + return NULL; + } + return h; +} + +int rtc_write(void *handle, const struct timeval *tv) +{ + struct tm tmr; + struct tm *tm; + struct rtc_time rtctm; + int fd = ((struct rtc_handle *)handle)->fd; + + tm = gmtime_r (&tv->tv_sec, &tmr); + + /* these structs are identical, but separately defined */ + rtctm.tm_sec = tm->tm_sec; + rtctm.tm_min = tm->tm_min; + rtctm.tm_hour = tm->tm_hour; + rtctm.tm_mday = tm->tm_mday; + rtctm.tm_mon = tm->tm_mon; + rtctm.tm_year = tm->tm_year; + rtctm.tm_wday = tm->tm_wday; + rtctm.tm_yday = tm->tm_yday; + rtctm.tm_isdst = tm->tm_isdst; + + if (ioctl (fd, RTC_SET_TIME, &rtctm)) + { + pinfo ("ioctl(%d, RTC_SET_TIME, ...) failed", fd); + return 1; + } + + info ("synced rtc to sysclock"); + return 0; +} + +int rtc_read(void *handle, struct timeval *tv) +{ + struct tm tm; + struct rtc_time rtctm; + int fd = ((struct rtc_handle *)handle)->fd; + + if (ioctl (fd, RTC_RD_TIME, &rtctm)) + { + pinfo ("ioctl(%d, RTC_RD_TIME, ...) failed", fd); + return 1; + } + + tm.tm_sec = rtctm.tm_sec; + tm.tm_min = rtctm.tm_min; + tm.tm_hour = rtctm.tm_hour; + tm.tm_mday = rtctm.tm_mday; + tm.tm_mon = rtctm.tm_mon; + tm.tm_year = rtctm.tm_year; + tm.tm_wday = rtctm.tm_wday; + tm.tm_yday = rtctm.tm_yday; + tm.tm_isdst = rtctm.tm_isdst; + + tv->tv_sec = mktime(&tm); + tv->tv_usec = 0; + + return 0; +} + +int rtc_close(void *handle) +{ + struct rtc_handle *h = handle; + close(h->fd); + free(h); + return 0; +} + +int file_write(const char *path, void *buf, size_t sz) +{ + char tmp[PATH_MAX]; + int oflags = O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC; + int perms = S_IRUSR | S_IWUSR; + int fd; + + if (snprintf(tmp, sizeof(tmp), path, kTempSuffix) >= sizeof(tmp)) + { + pinfo("path %s too long to use", path); + exit(1); + } + + if ((fd = open(tmp, oflags, perms)) < 0) + { + pinfo("open(%s) failed", tmp); + return 1; + } + + if (write(fd, buf, sz) != sz) + { + pinfo("write() failed"); + close(fd); + return 1; + } + + if (close(fd)) + { + pinfo("close() failed"); + return 1; + } + + if (rename(tmp, path)) + { + pinfo("rename() failed"); + return 1; + } + + return 0; +} + +int file_read(const char *path, void *buf, size_t sz) +{ + int fd = open(path, O_RDONLY | O_NOFOLLOW); + if (fd < 0) + { + pinfo("open(%s) failed", path); + return 1; + } + + if (read(fd, buf, sz) != sz) + { + pinfo("read() failed"); + close(fd); + return 1; + } + + return close(fd); +} + +int time_get(struct timeval *tv) +{ + return gettimeofday(tv, NULL); +} + +int pgrp_enter(void) +{ + return setpgid(0, 0); +} + +int pgrp_kill(void) +{ + pid_t grp = getpgrp(); + return kill(-grp, SIGKILL); +} + +static struct platform default_platform = { + .rtc_open = rtc_open, + .rtc_write = rtc_write, + .rtc_read = rtc_read, + .rtc_close = rtc_close, + + .file_write = file_write, + .file_read = file_read, + + .time_get = time_get, + + .pgrp_enter = pgrp_enter, + .pgrp_kill = pgrp_kill +}; + +struct platform *platform = &default_platform; diff --git a/src/util.h b/src/util.h index 2632933..4f63340 100644 --- a/src/util.h +++ b/src/util.h @@ -18,6 +18,8 @@
#define API __attribute__((visibility("default")))
+extern const char *kTempSuffix; + extern int verbose; void die (const char *fmt, ...); void verb (const char *fmt, ...); @@ -41,4 +43,21 @@ void drop_privs_to (const char *user, const char *group); * ETIMEDOUT. */ pid_t wait_with_timeout(int *status, int timeout_secs);
+struct platform { + void *(*rtc_open)(void); + int (*rtc_write)(void *handle, const struct timeval *tv); + int (*rtc_read)(void *handle, struct timeval *tv); + int (*rtc_close)(void *handle); + + int (*file_write)(const char *path, void *buf, size_t sz); + int (*file_read)(const char *path, void *buf, size_t sz); + + int (*time_get)(struct timeval *tv); + + int (*pgrp_enter)(void); + int (*pgrp_kill)(void); +}; + +extern struct platform *platform; + #endif /* !UTIL_H */ diff --git a/tests/common.sh b/tests/common.sh index 96ba238..4283c01 100644 --- a/tests/common.sh +++ b/tests/common.sh @@ -1,7 +1,7 @@ #!/bin/sh
kill_tlsdated() { - kill -9 $PPID + kill -TERM $PPID }
passed() {