/* SPDX-License-Identifier: LGPL-2.1+ */
#include <errno.h>
-#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <unistd.h>
#include "sd-bus.h"
#include "bus-error.h"
#include "bus-util.h"
#include "clock-util.h"
+#include "conf-files.h"
#include "def.h"
+#include "fd-util.h"
#include "fileio-label.h"
#include "fileio.h"
#include "fs-util.h"
#define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n"
#define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n"
+#define UNIT_LIST_DIRS (const char* const*) CONF_PATHS_STRV("systemd/ntp-units.d")
+
typedef struct UnitStatusInfo {
char *name;
char *load_state;
LIST_HEAD(UnitStatusInfo, units);
} Context;
+#define log_unit_full(unit, level, error, ...) \
+ ({ \
+ const UnitStatusInfo *_u = (unit); \
+ log_object_internal(level, error, PROJECT_FILE, __LINE__, __func__, \
+ "UNIT=", _u->name, NULL, NULL, ##__VA_ARGS__); \
+ })
+
+#define log_unit_debug(unit, ...) log_unit_full(unit, LOG_DEBUG, 0, ##__VA_ARGS__)
+#define log_unit_info(unit, ...) log_unit_full(unit, LOG_INFO, 0, ##__VA_ARGS__)
+#define log_unit_notice(unit, ...) log_unit_full(unit, LOG_NOTICE, 0, ##__VA_ARGS__)
+#define log_unit_warning(unit, ...) log_unit_full(unit, LOG_WARNING, 0, ##__VA_ARGS__)
+#define log_unit_error(unit, ...) log_unit_full(unit, LOG_ERR, 0, ##__VA_ARGS__)
+
+#define log_unit_debug_errno(unit, error, ...) log_unit_full(unit, LOG_DEBUG, error, ##__VA_ARGS__)
+#define log_unit_info_errno(unit, error, ...) log_unit_full(unit, LOG_INFO, error, ##__VA_ARGS__)
+#define log_unit_notice_errno(unit, error, ...) log_unit_full(unit, LOG_NOTICE, error, ##__VA_ARGS__)
+#define log_unit_warning_errno(unit, error, ...) log_unit_full(unit, LOG_WARNING, error, ##__VA_ARGS__)
+#define log_unit_error_errno(unit, error, ...) log_unit_full(unit, LOG_ERR, error, ##__VA_ARGS__)
+
static void unit_status_info_clear(UnitStatusInfo *p) {
assert(p);
}
}
-static int context_add_ntp_service(Context *c, const char *s) {
+static int context_add_ntp_service(Context *c, const char *s, const char *source) {
UnitStatusInfo *u;
if (!unit_name_is_valid(s, UNIT_NAME_PLAIN))
}
LIST_APPEND(units, c->units, u);
+ log_unit_debug(u, "added from %s.", source);
return 0;
}
-static int context_parse_ntp_services(Context *c) {
+static int context_parse_ntp_services_from_environment(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");
-
+ if (!env)
return 0;
- }
+
+ log_debug("Using list of ntp services from environment variable $SYSTEMD_TIMEDATED_NTP_SERVICES=%s.", env);
for (p = env;;) {
_cleanup_free_ char *word = NULL;
break;
}
- r = context_add_ntp_service(c, word);
+ r = context_add_ntp_service(c, word, "$SYSTEMD_TIMEDATED_NTP_SERVICES");
if (r < 0)
log_warning_errno(r, "Failed to add NTP service \"%s\", ignoring: %m", word);
}
- return 0;
+ return 1;
}
-static int context_ntp_service_is_active(Context *c) {
- UnitStatusInfo *info;
- int count = 0;
+static int context_parse_ntp_services_from_disk(Context *c) {
+ _cleanup_strv_free_ char **files = NULL;
+ char **f;
+ int r;
- assert(c);
+ r = conf_files_list_strv(&files, ".list", NULL, CONF_FILES_FILTER_MASKED, UNIT_LIST_DIRS);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enumerate .list files: %m");
- /* Call context_update_ntp_status() to update UnitStatusInfo before calling this. */
+ STRV_FOREACH(f, files) {
+ _cleanup_fclose_ FILE *file = NULL;
- LIST_FOREACH(units, info, c->units)
- count += !STRPTR_IN_SET(info->active_state, "inactive", "failed");
+ log_debug("Reading file '%s'", *f);
- return count;
+ r = fopen_unlocked(*f, "re", &file);
+ if (r < 0) {
+ log_error_errno(r, "Failed to open %s, ignoring: %m", *f);
+ continue;
+ }
+
+ for (;;) {
+ _cleanup_free_ char *line = NULL;
+ const char *word;
+
+ r = read_line(file, LINE_MAX, &line);
+ if (r < 0) {
+ log_error_errno(r, "Failed to read %s, ignoring: %m", *f);
+ continue;
+ }
+ if (r == 0)
+ break;
+
+ word = strstrip(line);
+ if (isempty(word) || startswith("#", word))
+ continue;
+
+ r = context_add_ntp_service(c, word, *f);
+ if (r < 0)
+ log_warning_errno(r, "Failed to add NTP service \"%s\", ignoring: %m", word);
+ }
+ }
+
+ return 1;
}
-static int context_ntp_service_is_enabled(Context *c) {
+static int context_parse_ntp_services(Context *c) {
+ int r;
+
+ r = context_parse_ntp_services_from_environment(c);
+ if (r != 0)
+ return r;
+
+ return context_parse_ntp_services_from_disk(c);
+}
+
+static int context_ntp_service_is_active(Context *c) {
UnitStatusInfo *info;
int count = 0;
/* 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, "masked", "masked-runtime", "disabled", "bad");
+ count += !STRPTR_IN_SET(info->active_state, "inactive", "failed");
return count;
}
static int context_write_data_timezone(Context *c) {
_cleanup_free_ char *p = NULL;
- int r = 0;
+ const char *source;
assert(c);
- if (isempty(c->zone)) {
- if (unlink("/etc/localtime") < 0 && errno != ENOENT)
- r = -errno;
+ /* No timezone is very similar to UTC. Hence in either of these cases link the UTC file in. Except if
+ * it isn't installed, in which case we remove the symlink altogether. Since glibc defaults to an
+ * internal version of UTC in that case behaviour is mostly equivalent. We still prefer creating the
+ * symlink though, since things are more self explanatory then. */
- return r;
- }
+ if (isempty(c->zone) || streq(c->zone, "UTC")) {
- p = strappend("../usr/share/zoneinfo/", c->zone);
- if (!p)
- return log_oom();
+ if (access("/usr/share/zoneinfo/UTC", F_OK) < 0) {
- r = symlink_atomic(p, "/etc/localtime");
- if (r < 0)
- return r;
+ if (unlink("/etc/localtime") < 0 && errno != ENOENT)
+ return -errno;
- return 0;
+ return 0;
+ }
+
+ source = "../usr/share/zoneinfo/UTC";
+ } else {
+ p = path_join("../usr/share/zoneinfo", c->zone);
+ if (!p)
+ return -ENOMEM;
+
+ source = p;
+ }
+
+ return symlink_atomic(source, "/etc/localtime");
}
static int context_write_data_local_rtc(Context *c) {
- int r;
_cleanup_free_ char *s = NULL, *w = NULL;
+ int r;
assert(c);
NULL,
u);
if (r < 0)
- return log_error_errno(r, "Failed to get properties: %s", bus_error_message(&error, r));
+ return log_unit_error_errno(u, r, "Failed to get properties: %s", bus_error_message(&error, r));
}
return 0;
if (n == 0) {
c->slot_job_removed = sd_bus_slot_unref(c->slot_job_removed);
- (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(sd_bus_message_get_bus(m),
+ "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP",
+ NULL);
}
return 0;
"ss",
u->name,
"replace");
+ log_unit_full(u, r < 0 ? LOG_WARNING : LOG_DEBUG, r,
+ "%s unit: %m", start ? "Starting" : "Stopping");
if (r < 0)
return r;
/* Call context_update_ntp_status() to update UnitStatusInfo before calling this. */
- if (streq(u->unit_file_state, "enabled") == enable)
+ if (streq(u->unit_file_state, "enabled") == enable) {
+ log_unit_debug(u, "already %sd.", enable_disable(enable));
return 0;
+ }
+
+ log_unit_info(u, "%s unit.", enable ? "Enabling" : "Disabling");
if (enable)
r = sd_bus_call_method(
void *userdata,
sd_bus_error *error) {
- struct tm tm;
- usec_t t;
+ struct tm tm = {};
+ usec_t t = 0;
int r;
- zero(tm);
r = clock_get_hwclock(&tm);
- if (r == -EBUSY) {
+ if (r == -EBUSY)
log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp.");
- t = 0;
- } else if (r == -ENOENT) {
+ else if (r == -ENOENT)
log_debug("/dev/rtc not found.");
- t = 0; /* no RTC found */
- } else if (r < 0)
+ else if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %m");
else
t = (usec_t) timegm(&tm) * USEC_PER_SEC;
return r;
if (!timezone_is_valid(z, LOG_DEBUG))
- return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z);
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid or not installed time zone '%s'", z);
if (streq_ptr(z, c->zone))
return sd_bus_reply_method_return(m, NULL);
"DAYLIGHT=%i", daylight,
LOG_MESSAGE("Changed time zone to '%s' (%s).", c->zone, tzname[daylight]));
- (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL);
+ (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m),
+ "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone",
+ NULL);
return sd_bus_reply_method_return(m, NULL);
}
/* 1. Write new configuration file */
r = context_write_data_local_rtc(c);
if (r < 0) {
- log_error_errno(r, "Failed to set RTC to local/UTC: %m");
- return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %m");
+ log_error_errno(r, "Failed to set RTC to %s: %m", lrtc ? "local" : "UTC");
+ return sd_bus_error_set_errnof(error, r, "Failed to set RTC to %s: %m", lrtc ? "local" : "UTC");
}
/* 2. Tell the kernel our timezone */
log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC");
- (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL);
+ (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m),
+ "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC",
+ NULL);
return sd_bus_reply_method_return(m, NULL);
}
sd_bus *bus = sd_bus_message_get_bus(m);
Context *c = userdata;
UnitStatusInfo *u;
+ const UnitStatusInfo *selected = NULL;
int enable, interactive, q, r;
assert(m);
return r;
}
- if (!enable)
+ if (enable)
LIST_FOREACH(units, u, c->units) {
- if (!streq(u->load_state, "loaded"))
- continue;
+ bool enable_this_one = !selected;
- 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);
+ r = unit_enable_or_disable(u, bus, error, enable_this_one);
if (r < 0)
- continue;
+ /* If enablement failed, don't start this unit. */
+ enable_this_one = false;
- r = unit_start_or_stop(u, bus, error, enable);
- break;
+ r = unit_start_or_stop(u, bus, error, enable_this_one);
+ if (r < 0)
+ log_unit_warning_errno(u, r, "Failed to %s %sd NTP unit, ignoring: %m",
+ enable_this_one ? "start" : "stop",
+ enable_disable(enable_this_one));
+ if (enable_this_one)
+ selected = u;
}
-
else
LIST_FOREACH(units, u, c->units) {
- if (!streq(u->load_state, "loaded") ||
- !streq(u->unit_file_state, "enabled"))
+ if (!streq(u->load_state, "loaded"))
continue;
- r = unit_start_or_stop(u, bus, error, enable);
- break;
+ q = unit_enable_or_disable(u, bus, error, false);
+ if (q < 0)
+ r = q;
+
+ q = unit_start_or_stop(u, bus, error, false);
+ if (q < 0)
+ r = q;
}
if (r < 0)
return r;
+ if (enable && !selected)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No NTP service found to enable.");
if (slot)
c->slot_job_removed = TAKE_PTR(slot);
- log_info("Set NTP to %sd", enable_disable(enable));
+ if (selected)
+ log_info("Set NTP to enabled (%s).", selected->name);
+ else
+ log_info("Set NTP to disabled.");
return sd_bus_reply_method_return(m, NULL);
}