diff options
author | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2015-02-11 15:47:53 +0000 |
---|---|---|
committer | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2015-02-24 11:15:52 +0000 |
commit | 2a6cefbc3bd3ed9392603da6a76b49c0dcba7e0d (patch) | |
tree | df99c280c45338a93752ba68c8cf90d3126e823a /tools | |
parent | 263aca37ecf5f977f68d87b54f2fb30584725781 (diff) | |
download | dbus-2a6cefbc3bd3ed9392603da6a76b49c0dcba7e0d.tar.gz |
Add dbus-update-activation-environment tool
If OS builders (distributions) have chosen to use the per-user bus,
this provides two possible modes of operation for compatibility with
existing X session startup hooks.
A legacy-free system can just upload DISPLAY, XAUTHORITY and possibly
DBUS_SESSION_BUS_ADDRESS into dbus-daemon's and systemd's activation
environments, similar to
http://cgit.freedesktop.org/systemd/systemd/tree/xorg/50-systemd-user.sh
installed by systemd (but unlike systemctl,
dbus-update-activation-environment works for traditional
D-Bus-activated services, not just for systemd services).
A system where compatibility is required for environment variables
exported by snippets in /etc/X11/xinit/xinitrc.d (in Red Hat derivatives,
Gentoo, etc.) or /etc/X11/Xsession.d (Debian derivatives) can upload
the entire environment of the X session, minus some selected environment
variables which are specific to a login session (notably XDG_SESSION_ID).
In Debian, I plan to put the former in a new dbus-user-session package
that enables a user-session-centric mode of operation for D-Bus,
and the latter in the existing dbus-x11 package, with the intention that
dbus-x11 eventually becomes a tool for change-averse setups or goes
away entirely.
Bug: https://bugs.freedesktop.org/show_bug.cgi?id=61301
Reviewed-by: Philip Withnall <philip.withnall@collabora.co.uk>
Diffstat (limited to 'tools')
-rw-r--r-- | tools/Makefile.am | 8 | ||||
-rw-r--r-- | tools/dbus-update-activation-environment.c | 417 |
2 files changed, 425 insertions, 0 deletions
diff --git a/tools/Makefile.am b/tools/Makefile.am index 80025b82..68a59707 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -16,6 +16,7 @@ bin_PROGRAMS = \ dbus-monitor \ dbus-send \ dbus-test-tool \ + dbus-update-activation-environment \ $(NULL) if DBUS_UNIX @@ -98,6 +99,13 @@ dbus_test_tool_SOURCES = \ $(NULL) dbus_test_tool_LDADD = $(top_builddir)/dbus/libdbus-1.la +dbus_update_activation_environment_SOURCES = \ + dbus-update-activation-environment.c \ + tool-common.c \ + tool-common.h \ + $(NULL) +dbus_update_activation_environment_LDADD = $(top_builddir)/dbus/libdbus-1.la + EXTRA_DIST = run-with-tmp-session-bus.sh strtoll.c strtoull.c CLEANFILES = \ run-with-tmp-session-bus.conf diff --git a/tools/dbus-update-activation-environment.c b/tools/dbus-update-activation-environment.c new file mode 100644 index 00000000..56d3b20d --- /dev/null +++ b/tools/dbus-update-activation-environment.c @@ -0,0 +1,417 @@ +/* + * dbus-update-activation-environment - update D-Bus, and optionally + * systemd, activation environment + * + * Copyright © 2014-2015 Collabora Ltd. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <config.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_SYSEXITS_H +# include <sysexits.h> +#endif + +#include <dbus/dbus.h> + +#ifdef DBUS_UNIX +# include <unistd.h> +# include <sys/stat.h> +# include <sys/types.h> +#endif + +#include "tool-common.h" + +#define PROGNAME "dbus-update-activation-environment" + +#ifndef EX_USAGE +# define EX_USAGE 64 +#endif + +#ifndef EX_UNAVAILABLE +# define EX_UNAVAILABLE 69 +#endif + +#ifndef EX_OSERR +# define EX_OSERR 71 +#endif + +/* apparently this is the portable way to get the entire environment... */ +extern char **environ; + +/* we don't really have anything useful to say about the stage at which we + * failed */ +#define oom() tool_oom ("updating environment") + +static dbus_bool_t verbose = FALSE; + +static void say (const char *format, ...) _DBUS_GNUC_PRINTF (1, 2); + +static void +say (const char *format, + ...) +{ + va_list ap; + + if (!verbose) + return; + + fprintf (stderr, "%s: ", PROGNAME); + va_start (ap, format); + vfprintf (stderr, format, ap); + fputc ('\n', stderr); + va_end (ap); +} + +#ifdef __linux__ +static dbus_bool_t +systemd_user_running (void) +{ + char *xdg_runtime_dir = getenv ("XDG_RUNTIME_DIR"); + char *path; + struct stat buf; + dbus_bool_t ret = FALSE; + + if (xdg_runtime_dir == NULL) + return FALSE; + + /* Assume that XDG_RUNTIME_DIR/systemd exists if and only if + * "systemd --user" is running. It's OK to use asprintf() here + * because we know we're on Linux. */ + if (asprintf (&path, "%s/systemd", xdg_runtime_dir) < 0) + oom (); + + if (stat (path, &buf) == 0) + ret = TRUE; + + free (path); + return ret; +} +#endif + +int +main (int argc, char **argv) +{ + DBusConnection *conn; + DBusMessage *msg; + DBusMessage *reply; + DBusError error = DBUS_ERROR_INIT; + DBusMessageIter msg_iter; + DBusMessageIter array_iter; + int i; + int first_non_option = argc; + dbus_bool_t all = FALSE; +#ifdef __linux__ + DBusMessage *sd_msg = NULL; + DBusMessageIter sd_msg_iter; + DBusMessageIter sd_array_iter; + dbus_bool_t systemd = FALSE; +#endif + + for (i = 1; i < argc; i++) + { + if (argv[i][0] != '-') + { + first_non_option = i; + break; + } + else if (strcmp (argv[i], "--") == 0) + { + first_non_option = i + 1; + break; + } + else if (strcmp (argv[i], "--all") == 0) + { + all = TRUE; + } + else if (strcmp (argv[i], "--systemd") == 0) + { +#ifdef __linux__ + systemd = TRUE; +#else + say ("not on Linux, ignoring --systemd argument"); +#endif + } + else if (strcmp (argv[i], "--verbose") == 0) + { + verbose = TRUE; + } + else + { + fprintf (stderr, + "%1$s: update environment variables that will be set for D-Bus\n" + " session services\n" + "\n" + "%1$s [options] VAR[=VAL] [VAR2[=VAL2] ...]\n" + " Add specified variables to D-Bus activation environment.\n" + " If omitted, values are taken from current environment;\n" + " variables not found in the environment are ignored.\n" + "%1$s --all\n" + " Add entire current environment to D-Bus activation\n" + " environment.\n" + "\n" + "Options:\n" + "\n" + "--all\n" + " Upload all environment variables.\n" + "--systemd\n" + " Also update the 'systemd --user' environment\n" + " if possible.\n" + "--verbose\n" + " Talk about it.\n" + , + PROGNAME); + exit (EX_USAGE); + } + } + + if (all && first_non_option < argc) + { + fprintf (stderr, "%s: error: --all cannot be used with VAR or " + "VAR=VAL arguments\n", PROGNAME); + exit (EX_USAGE); + } + + conn = dbus_bus_get (DBUS_BUS_SESSION, &error); + + if (conn == NULL) + { + fprintf (stderr, + "%s: error: unable to connect to D-Bus: %s\n", PROGNAME, + error.message); + exit (EX_OSERR); + } + + msg = dbus_message_new_method_call (DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, "UpdateActivationEnvironment"); + + if (msg == NULL) + oom (); + + dbus_message_iter_init_append (msg, &msg_iter); + + if (!dbus_message_iter_open_container (&msg_iter, DBUS_TYPE_ARRAY, + "{ss}", &array_iter)) + oom (); + +#ifdef __linux__ + if (systemd) + { + if (!systemd_user_running ()) + { + /* This is only best-effort. */ + say ("systemd --user not found, ignoring --systemd argument"); + systemd = FALSE; + } + } + + if (systemd) + { + sd_msg = dbus_message_new_method_call ("org.freedesktop.systemd1", + "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", + "SetEnvironment"); + + if (sd_msg == NULL) + oom (); + + dbus_message_iter_init_append (sd_msg, &sd_msg_iter); + + if (!dbus_message_iter_open_container (&sd_msg_iter, DBUS_TYPE_ARRAY, + "s", &sd_array_iter)) + oom (); + } +#endif + + for (i = all ? 0 : first_non_option; + all ? environ[i] != NULL : i < argc; + i++) + { + const char *var; + char *copy; + char *eq; + const char *val; + DBusMessageIter pair_iter; + + if (all) + var = environ[i]; + else + var = argv[i]; + + copy = strdup (var); + + if (copy == NULL) + oom (); + + if (!dbus_validate_utf8 (var, NULL)) + { + /* var is either of the form VAR or VAR=VAL */ + fprintf (stderr, + "%s: warning: environment variable not UTF-8: %s\n", + PROGNAME, var); + goto next; + } + + eq = strchr (copy, '='); + + if (eq == NULL) + { + if (all) + { + /* items in the environment block should be of the form + * VAR=VAL */ + fprintf (stderr, + "%s: warning: environment variable without '=': %s\n", + PROGNAME, var); + goto next; + } + else + { + /* items on the command-line may be of the form VAR + * in which case we infer the value from the environment */ + val = getenv (var); + + if (val == NULL) + { + /* nothing to be done here */ + goto next; + } + + if (!dbus_validate_utf8 (val, NULL)) + { + fprintf (stderr, + "%s: warning: environment variable not UTF-8: %s=%s\n", + PROGNAME, var, val); + goto next; + } + } + } + else + { + /* split VAR=VAL into VAR and VAL */ + *eq = '\0'; + val = eq + 1; + } + +#ifdef __linux__ + if (systemd) + { + char *combined; + + /* recombine if necessary */ + if (asprintf (&combined, "%s=%s", copy, val) < 0) + oom (); + + if (!dbus_message_iter_append_basic (&sd_array_iter, + DBUS_TYPE_STRING, &combined)) + oom (); + + free (combined); + } +#endif + + if (!dbus_message_iter_open_container (&array_iter, + DBUS_TYPE_DICT_ENTRY, NULL, &pair_iter)) + oom (); + + say ("setting %s=%s", copy, val); + + if (!dbus_message_iter_append_basic (&pair_iter, DBUS_TYPE_STRING, + ©)) + oom (); + + if (!dbus_message_iter_append_basic (&pair_iter, DBUS_TYPE_STRING, + &val)) + oom (); + + if (!dbus_message_iter_close_container (&array_iter, &pair_iter)) + oom (); + +next: + free (copy); + } + + if (!dbus_message_iter_close_container (&msg_iter, &array_iter)) + oom (); + +#ifdef __linux__ + if (systemd && + !dbus_message_iter_close_container (&sd_msg_iter, &sd_array_iter)) + oom (); +#endif + + reply = dbus_connection_send_with_reply_and_block (conn, msg, -1, &error); + + if (reply == NULL) + { + fprintf (stderr, + "%s: error sending to dbus-daemon: %s: %s\n", + PROGNAME, error.name, error.message); + exit (EX_UNAVAILABLE); + } + + if (!dbus_message_get_args (msg, &error, DBUS_TYPE_INVALID)) + { + fprintf (stderr, + "%s: error from dbus-daemon: %s: %s\n", + PROGNAME, error.name, error.message); + exit (EX_UNAVAILABLE); + } + + dbus_message_unref (reply); + +#ifdef __linux__ + if (systemd) + { + reply = dbus_connection_send_with_reply_and_block (conn, sd_msg, -1, + &error); + + /* non-fatal, the main purpose of this thing is to communicate + * with dbus-daemon */ + if (reply == NULL) + { + fprintf (stderr, + "%s: warning: error sending to systemd: %s: %s\n", + PROGNAME, error.name, error.message); + } + else if (!dbus_message_get_args (msg, &error, DBUS_TYPE_INVALID)) + { + fprintf (stderr, + "%s: warning: error from systemd: %s: %s\n", + PROGNAME, error.name, error.message); + } + + if (reply != NULL) + dbus_message_unref (reply); + + dbus_message_unref (sd_msg); + dbus_error_free (&error); + } +#endif + + dbus_message_unref (msg); + dbus_connection_unref (conn); + return 0; +} |