]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
timedated: support multiple NTP services
authorYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 17 Apr 2018 05:09:13 +0000 (14:09 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 17 Apr 2018 05:09:13 +0000 (14:09 +0900)
Support for alternative NTP services was dropped by b72ddf0f4f552dd53d6404b6ddbc9f17d02b8e12.
This makes timedated re-support alternative NTP services.

Closes #8402. Also, fixes #1329.

src/timedate/timedated.c

index b53bb5e49c7cb2166f10bfef874591ea0833e2ba..f479b0ab7f3e39b5be02cb4288d126a66a1cf9d5 100644 (file)
 #include "def.h"
 #include "fileio-label.h"
 #include "fs-util.h"
+#include "hashmap.h"
+#include "list.h"
 #include "path-util.h"
 #include "selinux-util.h"
+#include "string-util.h"
 #include "strv.h"
+#include "unit-def.h"
+#include "unit-name.h"
 #include "user-util.h"
 #include "util.h"
 
 #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
 #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
 
+typedef struct UnitStatusInfo {
+        char *name;
+        char *load_state;
+        char *unit_file_state;
+        char *active_state;
+
+        LIST_FIELDS(struct UnitStatusInfo, units);
+} UnitStatusInfo;
+
 typedef struct Context {
         char *zone;
         bool local_rtc;
-        bool can_ntp;
-        bool use_ntp;
         Hashmap *polkit_registry;
+
+        LIST_HEAD(UnitStatusInfo, units);
 } Context;
 
+static void unit_status_info_clear(UnitStatusInfo *p) {
+        assert(p);
+
+        p->load_state = mfree(p->load_state);
+        p->unit_file_state = mfree(p->unit_file_state);
+        p->active_state = mfree(p->active_state);
+}
+
+static void unit_status_info_free(UnitStatusInfo *p) {
+        assert(p);
+
+        unit_status_info_clear(p);
+        free(p->name);
+        free(p);
+}
+
 static void context_free(Context *c) {
+        UnitStatusInfo *p;
+
         assert(c);
 
         free(c->zone);
         bus_verify_polkit_async_registry_free(c->polkit_registry);
+
+        while ((p = c->units)) {
+                LIST_REMOVE(units, c->units, p);
+                unit_status_info_free(p);
+        }
+}
+
+static int context_add_ntp_service(Context *c, const char *s) {
+        _cleanup_free_ char *name = NULL;
+        UnitStatusInfo *u;
+
+        if (!unit_name_is_valid(s, UNIT_NAME_PLAIN))
+                return -EINVAL;
+
+        /* Do not add this if it is already listed */
+        LIST_FOREACH(units, u, c->units)
+                if (streq(u->name, s))
+                        return 0;
+
+        u = new0(UnitStatusInfo, 1);
+        if (!u)
+                return -ENOMEM;
+
+        u->name = strdup(s);
+        if (!u->name) {
+                free(u);
+                return -ENOMEM;
+        }
+
+        LIST_APPEND(units, c->units, u);
+
+        return 0;
+}
+
+static int context_parse_ntp_services(Context *c) {
+        const char *env, *p;
+        int r;
+
+        assert(c);
+
+        env = getenv("SYSTEMD_TIMEDATED_NTP_SERVICES");
+        if (!env) {
+                r = context_add_ntp_service(c, "systemd-timesyncd.service");
+                if (r < 0)
+                        log_warning_errno(r, "Failed to add NTP service \"systemd-timesyncd.service\", ignoring: %m");
+
+                return 0;
+        }
+
+        for (p = env;;) {
+                _cleanup_free_ char *word = NULL;
+
+                r = extract_first_word(&p, &word, ":", 0);
+                if (r == 0)
+                        break;
+                if (r == -ENOMEM)
+                        return log_oom();
+                if (r < 0) {
+                        log_error("Invalid syntax, ignoring: %s", env);
+                        break;
+                }
+
+                r = context_add_ntp_service(c, word);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to add NTP service \"%s\", ignoring: %m", word);
+        }
+
+        return 0;
+}
+
+static int context_ntp_service_is_active(Context *c) {
+        UnitStatusInfo *info;
+        int count = 0;
+
+        assert(c);
+
+        /* Call context_update_ntp_status() to update UnitStatusInfo before calling this. */
+
+        LIST_FOREACH(units, info, c->units)
+                count += streq_ptr(info->active_state, "active");
+
+        return count;
+}
+
+static int context_ntp_service_is_enabled(Context *c) {
+        UnitStatusInfo *info;
+        int count = 0;
+
+        assert(c);
+
+        /* Call context_update_ntp_status() to update UnitStatusInfo before calling this. */
+
+        LIST_FOREACH(units, info, c->units)
+                count += STRPTR_IN_SET(info->unit_file_state, "enabled", "enabled-runtime");
+
+        return count;
+}
+
+static int context_ntp_service_exists(Context *c) {
+        UnitStatusInfo *info;
+        int count = 0;
+
+        assert(c);
+
+        /* Call context_update_ntp_status() to update UnitStatusInfo before calling this. */
+
+        LIST_FOREACH(units, info, c->units)
+                count += streq_ptr(info->load_state, "loaded");
+
+        return count;
 }
 
 static int context_read_data(Context *c) {
@@ -158,81 +300,95 @@ static int context_write_data_local_rtc(Context *c) {
         return write_string_file_atomic_label("/etc/adjtime", w);
 }
 
-static int context_read_ntp(Context *c, sd_bus *bus) {
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
-        const char *s;
+static int context_update_ntp_status(Context *c, sd_bus *bus, sd_bus_message *m) {
+        static const struct bus_properties_map map[] = {
+                { "LoadState",     "s", NULL, offsetof(UnitStatusInfo, load_state)      },
+                { "ActiveState",   "s", NULL, offsetof(UnitStatusInfo, active_state)    },
+                { "UnitFileState", "s", NULL, offsetof(UnitStatusInfo, unit_file_state) },
+                {}
+        };
+        static sd_bus_message *_m = NULL;
+        UnitStatusInfo *u;
         int r;
 
         assert(c);
         assert(bus);
 
-        r = sd_bus_call_method(
-                        bus,
-                        "org.freedesktop.systemd1",
-                        "/org/freedesktop/systemd1",
-                        "org.freedesktop.systemd1.Manager",
-                        "GetUnitFileState",
-                        &error,
-                        &reply,
-                        "s",
-                        "systemd-timesyncd.service");
+        /* Suppress multiple call of context_update_ntp_status() within single DBus transaction. */
+        if (m && m == _m)
+                return 0;
 
-        if (r < 0) {
-                if (sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
-                    sd_bus_error_has_name(&error, "org.freedesktop.systemd1.LoadFailed") ||
-                    sd_bus_error_has_name(&error, "org.freedesktop.systemd1.NoSuchUnit"))
-                        return 0;
+        _m = m;
 
-                return r;
-        }
+        LIST_FOREACH(units, u, c->units) {
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+                _cleanup_free_ char *path = NULL;
 
-        r = sd_bus_message_read(reply, "s", &s);
-        if (r < 0)
-                return r;
+                unit_status_info_clear(u);
 
-        c->can_ntp = true;
-        c->use_ntp = STR_IN_SET(s, "enabled", "enabled-runtime");
+                path = unit_dbus_path_from_name(u->name);
+                if (!path)
+                        return -ENOMEM;
+
+                r = bus_map_all_properties(
+                                bus,
+                                "org.freedesktop.systemd1",
+                                path,
+                                map,
+                                BUS_MAP_STRDUP,
+                                &error,
+                                NULL,
+                                u);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to get properties: %s", bus_error_message(&error, r));
+        }
 
         return 0;
 }
 
-static int context_start_ntp(sd_bus *bus, sd_bus_error *error, bool enabled) {
+static int unit_start_or_stop(UnitStatusInfo *u, sd_bus *bus, sd_bus_error *error, bool start) {
         int r;
 
+        assert(u);
         assert(bus);
         assert(error);
 
+        /* Call context_update_ntp_status() to update UnitStatusInfo before calling this. */
+
+        if (streq(u->active_state, "active") == start)
+                return 0;
+
         r = sd_bus_call_method(
                 bus,
                 "org.freedesktop.systemd1",
                 "/org/freedesktop/systemd1",
                 "org.freedesktop.systemd1.Manager",
-                enabled ? "StartUnit" : "StopUnit",
+                start ? "StartUnit" : "StopUnit",
                 error,
                 NULL,
                 "ss",
-                "systemd-timesyncd.service",
+                u->name,
                 "replace");
-        if (r < 0) {
-                if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) ||
-                    sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") ||
-                    sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit"))
-                        return sd_bus_error_set(error, BUS_ERROR_NO_NTP_SUPPORT, "NTP not supported");
-
+        if (r < 0)
                 return r;
-        }
 
         return 0;
 }
 
-static int context_enable_ntp(sd_bus *bus, sd_bus_error *error, bool enabled) {
+static int unit_enable_or_disable(UnitStatusInfo *u, sd_bus *bus, sd_bus_error *error, bool enable) {
         int r;
 
+        assert(u);
         assert(bus);
         assert(error);
 
-        if (enabled)
+        /* Call context_update_ntp_status() to update UnitStatusInfo before calling this. */
+
+        if (streq(u->unit_file_state, "enabled") == enable)
+                return 0;
+
+        if (enable)
                 r = sd_bus_call_method(
                                 bus,
                                 "org.freedesktop.systemd1",
@@ -242,7 +398,7 @@ static int context_enable_ntp(sd_bus *bus, sd_bus_error *error, bool enabled) {
                                 error,
                                 NULL,
                                 "asbb", 1,
-                                "systemd-timesyncd.service",
+                                u->name,
                                 false, true);
         else
                 r = sd_bus_call_method(
@@ -254,15 +410,10 @@ static int context_enable_ntp(sd_bus *bus, sd_bus_error *error, bool enabled) {
                                 error,
                                 NULL,
                                 "asb", 1,
-                                "systemd-timesyncd.service",
+                                u->name,
                                 false);
-
-        if (r < 0) {
-                if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND))
-                        return sd_bus_error_set(error, BUS_ERROR_NO_NTP_SUPPORT, "NTP not supported");
-
+        if (r < 0)
                 return r;
-        }
 
         r = sd_bus_call_method(
                         bus,
@@ -273,9 +424,8 @@ static int context_enable_ntp(sd_bus *bus, sd_bus_error *error, bool enabled) {
                         error,
                         NULL,
                         NULL);
-        if (r < 0)
-                return r;
-
+         if (r < 0)
+                 return r;
         return 0;
 }
 
@@ -332,6 +482,56 @@ static int property_get_ntp_sync(
         return sd_bus_message_append(reply, "b", ntp_synced());
 }
 
+static int property_get_can_ntp(
+                sd_bus *bus,
+                const char *path,
+                const char *interface,
+                const char *property,
+                sd_bus_message *reply,
+                void *userdata,
+                sd_bus_error *error) {
+
+        Context *c = userdata;
+        int r;
+
+        assert(c);
+        assert(bus);
+        assert(property);
+        assert(reply);
+        assert(error);
+
+        r = context_update_ntp_status(c, bus, reply);
+        if (r < 0)
+                return r;
+
+        return sd_bus_message_append(reply, "b", context_ntp_service_exists(c) > 0);
+}
+
+static int property_get_ntp(
+                sd_bus *bus,
+                const char *path,
+                const char *interface,
+                const char *property,
+                sd_bus_message *reply,
+                void *userdata,
+                sd_bus_error *error) {
+
+        Context *c = userdata;
+        int r;
+
+        assert(c);
+        assert(bus);
+        assert(property);
+        assert(reply);
+        assert(error);
+
+        r = context_update_ntp_status(c, bus, reply);
+        if (r < 0)
+                return r;
+
+        return sd_bus_message_append(reply, "b", context_ntp_service_is_active(c) > 0);
+}
+
 static int method_set_timezone(sd_bus_message *m, void *userdata, sd_bus_error *error) {
         Context *c = userdata;
         const char *z;
@@ -495,18 +695,22 @@ static int method_set_local_rtc(sd_bus_message *m, void *userdata, sd_bus_error
 }
 
 static int method_set_time(sd_bus_message *m, void *userdata, sd_bus_error *error) {
-        int relative, interactive;
+        sd_bus *bus = sd_bus_message_get_bus(m);
+        int relative, interactive, r;
         Context *c = userdata;
         int64_t utc;
         struct timespec ts;
         usec_t start;
         struct tm* tm;
-        int r;
 
         assert(m);
         assert(c);
 
-        if (c->use_ntp)
+        r = context_update_ntp_status(c, bus, m);
+        if (r < 0)
+                return sd_bus_error_set_errnof(error, r, "Failed to update context: %m");
+
+        if (context_ntp_service_is_active(c) > 0)
                 return sd_bus_error_set(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Automatic time synchronization is enabled");
 
         /* this only gets used if dbus does not provide a timestamp */
@@ -581,19 +785,25 @@ static int method_set_time(sd_bus_message *m, void *userdata, sd_bus_error *erro
 }
 
 static int method_set_ntp(sd_bus_message *m, void *userdata, sd_bus_error *error) {
-        int enabled, interactive;
+        sd_bus *bus = sd_bus_message_get_bus(m);
         Context *c = userdata;
-        int r;
+        UnitStatusInfo *u;
+        int enable, interactive, q, r;
 
         assert(m);
+        assert(bus);
         assert(c);
 
-        r = sd_bus_message_read(m, "bb", &enabled, &interactive);
+        r = sd_bus_message_read(m, "bb", &enable, &interactive);
         if (r < 0)
                 return r;
 
-        if ((bool)enabled == c->use_ntp)
-                return sd_bus_reply_method_return(m, NULL);
+        r = context_update_ntp_status(c, bus, m);
+        if (r < 0)
+                return r;
+
+        if (context_ntp_service_exists(c) <= 0)
+                return sd_bus_error_set(error, BUS_ERROR_NO_NTP_SUPPORT, "NTP not supported");
 
         r = bus_verify_polkit_async(
                         m,
@@ -609,18 +819,49 @@ static int method_set_ntp(sd_bus_message *m, void *userdata, sd_bus_error *error
         if (r == 0)
                 return 1;
 
-        r = context_enable_ntp(sd_bus_message_get_bus(m), error, enabled);
-        if (r < 0)
-                return r;
+        if (!enable)
+                LIST_FOREACH(units, u, c->units) {
+                        if (!streq(u->load_state, "loaded"))
+                                continue;
+
+                        q = unit_enable_or_disable(u, bus, error, enable);
+                        if (q < 0)
+                                r = q;
+
+                        q = unit_start_or_stop(u, bus, error, enable);
+                        if (q < 0)
+                                r = q;
+                }
+
+        else if (context_ntp_service_is_enabled(c) <= 0)
+                LIST_FOREACH(units, u, c->units) {
+                        if (!streq(u->load_state, "loaded"))
+                                continue;
+
+                        r = unit_enable_or_disable(u, bus, error, enable);
+                        if (r < 0)
+                                continue;
+
+                        r = unit_start_or_stop(u, bus, error, enable);
+                        break;
+                }
+
+        else if (context_ntp_service_is_active(c) <= 0)
+                LIST_FOREACH(units, u, c->units) {
+                        if (!streq(u->load_state, "loaded") ||
+                            !streq(u->unit_file_state, "enabled"))
+                                continue;
+
+                        r = unit_start_or_stop(u, bus, error, enable);
+                        break;
+                }
 
-        r = context_start_ntp(sd_bus_message_get_bus(m), error, enabled);
         if (r < 0)
                 return r;
 
-        c->use_ntp = enabled;
-        log_info("Set NTP to %sd", enable_disable(enabled));
+        log_info("Set NTP to %sd", enable_disable(enable));
 
-        (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
+        (void) sd_bus_emit_properties_changed(bus, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL);
 
         return sd_bus_reply_method_return(m, NULL);
 }
@@ -629,8 +870,8 @@ static const sd_bus_vtable timedate_vtable[] = {
         SD_BUS_VTABLE_START(0),
         SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
         SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
-        SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool, offsetof(Context, can_ntp), 0),
-        SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+        SD_BUS_PROPERTY("CanNTP", "b", property_get_can_ntp, 0, 0),
+        SD_BUS_PROPERTY("NTP", "b", property_get_ntp, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
         SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0),
         SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0),
         SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0),
@@ -708,11 +949,9 @@ int main(int argc, char *argv[]) {
                 goto finish;
         }
 
-        r = context_read_ntp(&context, bus);
-        if (r < 0) {
-                log_error_errno(r, "Failed to determine whether NTP is enabled: %m");
+        r = context_parse_ntp_services(&context);
+        if (r < 0)
                 goto finish;
-        }
 
         r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL);
         if (r < 0) {