If we are running under systemd, the ProtectSystem key in our .service
file prevents us from writing to /etc/localtime. Instead, set the
timezone by using org.freedesktop.timedate1 over D-Bus, if it is
available. If it is not available, fall back to /etc/localtime.
Signed-off-by: Philip Withnall <philip.withnall(a)collabora.co.uk>
---
src/timezone.c | 213 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 210 insertions(+), 3 deletions(-)
diff --git a/src/timezone.c b/src/timezone.c
index e346b11..c74bfdd 100644
--- a/src/timezone.c
+++ b/src/timezone.c
@@ -38,11 +38,46 @@
#include <glib.h>
#include "connman.h"
+#include "gdbus.h"
#define ETC_LOCALTIME "/etc/localtime"
#define ETC_SYSCONFIG_CLOCK "/etc/sysconfig/clock"
#define USR_SHARE_ZONEINFO "/usr/share/zoneinfo"
+/* See
https://www.freedesktop.org/wiki/Software/systemd/timedated/ for
+ * reference. */
+#define TIMEDATED_SERVICE "org.freedesktop.timedate1"
+#define TIMEDATED_INTERFACE TIMEDATED_SERVICE
+#define TIMEDATED_PATH "/org/freedesktop/timedate1"
+
+static GDBusClient *timedated_client = NULL;
+static GDBusProxy *timedated_proxy = NULL;
+static gchar *timedated_timezone = NULL;
+
+static void timedate_property_changed(GDBusProxy *proxy, const char *name,
+ DBusMessageIter *iter, void *user_data)
+{
+ DBG("Property %s", name);
+
+ if (g_str_equal(name, "Timezone")) {
+ const char *str;
+
+ if (!iter) {
+ g_dbus_proxy_refresh_property(proxy, name);
+ return;
+ }
+
+ if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING)
+ return;
+
+ dbus_message_iter_get_basic(iter, &str);
+ g_free(timedated_timezone);
+ timedated_timezone = g_strdup(str);
+
+ DBG("Timezone set to %s", timedated_timezone);
+ }
+}
+
static char *read_key_file(const char *pathname, const char *key)
{
struct stat st;
@@ -226,7 +261,36 @@ static char *find_origin(void *src_map, struct stat *src_st,
return NULL;
}
-char *__connman_timezone_lookup(void)
+/* Query the timezone from org.freedesktop.timedate1. */
+static char *__connman_timezone_lookup_dbus(void)
+{
+ DBusMessageIter iter;
+ const char *str;
+
+ DBG("");
+
+ /* If timedated_timezone is set, we have been notified of the timezone
+ * previously. */
+ if (timedated_proxy && timedated_timezone)
+ return timedated_timezone;
+
+ /* Not connected to the D-Bus service? */
+ if (!timedated_proxy)
+ return NULL;
+
+ /* Query D-Bus and update the cache. */
+ if (!g_dbus_proxy_get_property(timedated_proxy, "Timezone", &iter))
+ return NULL;
+ dbus_message_iter_get_basic(&iter, &str);
+
+ g_free(timedated_timezone);
+ timedated_timezone = g_strdup(str);
+
+ return g_strdup(timedated_timezone);
+}
+
+/* Look up the timezone from /etc/sysconfig/clock or /etc/localtime. */
+static char *__connman_timezone_lookup_filesystem(void)
{
struct stat st;
void *map;
@@ -284,6 +348,20 @@ done:
return zone;
}
+char *__connman_timezone_lookup(void)
+{
+ char *timezone;
+
+ DBG("");
+
+ /* Try D-Bus first; then fall back to the filesystem. */
+ timezone = __connman_timezone_lookup_dbus();
+ if (timezone)
+ return timezone;
+
+ return __connman_timezone_lookup_filesystem();
+}
+
static int write_file(void *src_map, struct stat *src_st, const char *pathname)
{
struct stat st;
@@ -311,7 +389,53 @@ static int write_file(void *src_map, struct stat *src_st, const char
*pathname)
return 0;
}
-int __connman_timezone_change(const char *zone)
+static void timezone_change_dbus_cb(DBusMessage *message, void *user_data)
+{
+ const char *zone = user_data;
+
+ if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_ERROR) {
+ const char *dbus_error = dbus_message_get_error_name(message);
+
+ DBG("zone %s %s", zone, dbus_error);
+ } else {
+ DBG("zone %s success", zone);
+ }
+}
+
+static void timezone_change_dbus_append(DBusMessageIter *iter,
+ void *user_data)
+{
+ const char *zone = user_data;
+ dbus_bool_t user_interaction = FALSE;
+
+ /* The second parameter is user_interaction — whether polkit should ask
+ * for credentials interactively if necessary. We do not want that.
+ *
+ * The polkit action controlling this is:
+ * org.freedesktop.timedate1.set-timezone. */
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &zone);
+ dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &user_interaction);
+}
+
+/* Set the timezone using org.freedesktop.timedate1. */
+static int __connman_timezone_change_dbus(const char *zone)
+{
+ /* Are we connected to the D-Bus service? */
+ if (!timedated_proxy)
+ return -ENOENT;
+
+ DBG("zone %s", zone);
+
+ if (!g_dbus_proxy_method_call(timedated_proxy, "SetTimezone",
+ timezone_change_dbus_append, timezone_change_dbus_cb,
+ g_strdup(zone), g_free))
+ return -EIO;
+
+ return -EINPROGRESS;
+}
+
+/* Set the timezone by overwriting /etc/localtime. */
+static int __connman_timezone_change_filesystem(const char *zone)
{
struct stat st;
char *map, pathname[PATH_MAX];
@@ -345,6 +469,23 @@ int __connman_timezone_change(const char *zone)
return err;
}
+int __connman_timezone_change(const char *zone)
+{
+ int err;
+
+ DBG("");
+
+ /* Try D-Bus first; then fall back to the filesystem. This order is
+ * important, as if we are running under systemd, /etc will be mounted
+ * read-only (due to the ProtectSystem key in our .service file), and
+ * hence only org.freedesktop.timedate1 can be used. */
+ err = __connman_timezone_change_dbus(zone);
+ if (err >= 0 || err == -EINPROGRESS)
+ return 0;
+
+ return __connman_timezone_change_filesystem(zone);
+}
+
static guint inotify_watch = 0;
static gboolean inotify_data(GIOChannel *channel, GIOCondition cond,
@@ -402,7 +543,45 @@ static gboolean inotify_data(GIOChannel *channel, GIOCondition cond,
return TRUE;
}
-int __connman_timezone_init(void)
+static int __connman_timezone_init_dbus(void)
+{
+ DBusConnection *connection;
+ int err = -EIO;
+
+ DBG("");
+
+ /* Try connecting to org.freedesktop.timedate1 first. If that fails,
+ * try /etc/localtime as a fallback. */
+ connection = connman_dbus_get_connection();
+
+ timedated_client = g_dbus_client_new(connection, TIMEDATED_SERVICE,
+ TIMEDATED_PATH);
+ if (!timedated_client)
+ goto error;
+
+ timedated_proxy = g_dbus_proxy_new(timedated_client, TIMEDATED_PATH,
+ TIMEDATED_INTERFACE);
+ if (!timedated_proxy)
+ goto error;
+
+ g_dbus_proxy_set_property_watch(timedated_proxy,
+ timedate_property_changed, NULL);
+
+ dbus_connection_unref(connection);
+
+ return 0;
+error:
+ if (timedated_client) {
+ g_dbus_client_unref(timedated_client);
+ timedated_client = NULL;
+ }
+
+ dbus_connection_unref(connection);
+
+ return err;
+}
+
+static int __connman_timezone_init_filesystem(void)
{
GIOChannel *channel;
char *dirname;
@@ -443,6 +622,21 @@ int __connman_timezone_init(void)
return 0;
}
+int __connman_timezone_init(void)
+{
+ int err;
+
+ DBG("");
+
+ /* Try connecting to org.freedesktop.timedate1 first. If that fails,
+ * try /etc/localtime as a fallback. */
+ err = __connman_timezone_init_dbus();
+ if (err >= 0)
+ return err;
+
+ return __connman_timezone_init_filesystem();
+}
+
void __connman_timezone_cleanup(void)
{
DBG("");
@@ -451,4 +645,17 @@ void __connman_timezone_cleanup(void)
g_source_remove(inotify_watch);
inotify_watch = 0;
}
+
+ if (timedated_proxy) {
+ g_dbus_proxy_unref(timedated_proxy);
+ timedated_proxy = NULL;
+ }
+
+ if (timedated_client) {
+ g_dbus_client_unref(timedated_client);
+ timedated_client = NULL;
+ }
+
+ g_free(timedated_timezone);
+ timedated_timezone = NULL;
}
--
2.7.4