From db79ec0094e70caa537c3339595003a3cd164297 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Wed, 11 Feb 2015 15:47:53 +0000 Subject: [PATCH 7/7] 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 --- configure.ac | 1 + doc/Makefile.am | 1 + doc/dbus-update-activation-environment.1.xml.in | 201 ++++++++++++ tools/Makefile.am | 8 + tools/dbus-update-activation-environment.c | 401 ++++++++++++++++++++++++ 5 files changed, 612 insertions(+) create mode 100644 doc/dbus-update-activation-environment.1.xml.in create mode 100644 tools/dbus-update-activation-environment.c diff --git a/configure.ac b/configure.ac index d122e42..82926cc 100644 --- a/configure.ac +++ b/configure.ac @@ -1810,6 +1810,7 @@ doc/dbus-monitor.1.xml doc/dbus-run-session.1.xml doc/dbus-send.1.xml doc/dbus-test-tool.1.xml +doc/dbus-update-activation-environment.1.xml doc/dbus-uuidgen.1.xml dbus-1.pc dbus-1-uninstalled.pc diff --git a/doc/Makefile.am b/doc/Makefile.am index 3879a61..3c1175e 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -8,6 +8,7 @@ man_pages = \ dbus-run-session.1 \ dbus-send.1 \ dbus-test-tool.1 \ + dbus-update-activation-environment.1 \ dbus-uuidgen.1 \ $(NULL) diff --git a/doc/dbus-update-activation-environment.1.xml.in b/doc/dbus-update-activation-environment.1.xml.in new file mode 100644 index 0000000..025d002 --- /dev/null +++ b/doc/dbus-update-activation-environment.1.xml.in @@ -0,0 +1,201 @@ + + + + + dbus-update-activation-environment + 1 + User Commands + D-Bus + @DBUS_VERSION@ + + + dbus-update-activation-environment + update environment used for D-Bus session services + + + + + dbus-update-activation-environment + --systemd + --verbose + + --all + VAR + VAR=VAL + + + + + + DESCRIPTION + dbus-update-activation-environment + updates the list of environment variables used by + dbus-daemon --session + when it activates session services without using + systemd. + + With the option, + if an instance of systemd --user is + available on D-Bus, it also updates the list of environment variables + used by systemd --user + when it activates user services, including D-Bus session services + for which dbus-daemon has been configured to + delegate activation to systemd. + This is very similar to the + command provided by + systemctl1). + + Variables that are special to dbus-daemon + or systemd may be set, but their values will + be overridden when a service is started. For instance, it is + not useful to add DBUS_SESSION_BUS_ADDRESS to + dbus-daemon's activation environment, + although it might still be useful to add it to + systemd's activation environment. + + + + OPTIONS + + + + + + Set all environment variables present in + the environment used by + dbus-update-activation-environment. + + + + + + + + Set environment variables for systemd user services as well as + for traditional D-Bus session services. + + + + + + + Output messages to standard error explaining what + dbus-update-activation-environment is doing. + + + + + VAR + + If VAR is present in the + environment of dbus-update-activation-environment, + set it to the same value for D-Bus services. Its value must be + UTF-8 (if not, it is skipped with a warning). If + VAR is not present + in the environment, this argument is silently ignored. + + + + + + VAR=VAL + + Set VAR to VAL, + which must be UTF-8. + + + + + + + + EXAMPLES + + dbus-update-activation-environment is + primarily designed to be used in Linux distributions' X11 session + startup scripts, in conjunction with the "user bus" design. + + + To propagate DISPLAY and XAUTHORITY + to dbus-daemon + and, if present, systemd, + and propagate DBUS_SESSION_BUS_ADDRESS to + systemd: + + dbus-update-activation-environment --systemd \ + DBUS_SESSION_BUS_ADDRESS DISPLAY XAUTHORITY + + + + To propagate all environment variables except + XDG_SEAT, XDG_SESSION_ID + and XDG_VTNR to dbus-daemon + (and, if present, systemd) for compatibility + with legacy X11 session startup scripts: + + # in a subshell so the variables remain set in the + # parent script + ( + unset XDG_SEAT + unset XDG_SESSION_ID + unset XDG_VTNR + + dbus-update-activation-environment --systemd --all + ) + + + + + + EXIT STATUS + + dbus-update-activation-environment + exits with status 0 on success, EX_USAGE (64) on invalid + command-line options, EX_OSERR (71) if unable to connect + to the session bus, or EX_UNAVAILABLE (69) if unable to + set the environment variables. Other nonzero exit codes might be + added in future versions. + + + + ENVIRONMENT + DBUS_SESSION_BUS_ADDRESS, + XDG_RUNTIME_DIR and/or DISPLAY + are used to find the address of the session bus. + + + + LIMITATIONS + + dbus-daemon does not provide a way to unset + environment variables after they have been set (although + systemd does), so + dbus-update-activation-environment does not + offer this functionality either. + + + + POSIX does not specify the encoding of non-ASCII environment variable + names or values and allows them to contain any non-zero byte, but + neither dbus-daemon nor systemd + supports environment variables with non-UTF-8 names or values. + Accordingly, dbus-update-activation-environment + assumes that any name or value that appears to be valid UTF-8 is + intended to be UTF-8, and ignores other names or values with a warning. + + + + + BUGS + Please send bug reports to the D-Bus bug tracker or mailing list. + See http://www.freedesktop.org/software/dbus/. + + + + SEE ALSO + dbus-daemon1, + systemd1, + the command of + systemctl1 + + diff --git a/tools/Makefile.am b/tools/Makefile.am index 294bbc6..9046282 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 @@ -95,6 +96,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 0000000..ba8f610 --- /dev/null +++ b/tools/dbus-update-activation-environment.c @@ -0,0 +1,401 @@ +/* + * 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 + +#include +#include +#include +#include +#ifdef HAVE_SYSEXITS_H +# include +#endif + +#include + +#ifdef DBUS_UNIX +# include +# include +# include +#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; + + 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) + return FALSE; + + free (path); + + return TRUE; +} +#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 + /* ignore, systemd is Linux-specific */ +#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); + return 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); + return 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. */ + 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); + return 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); + return 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); + } + } +#endif + + return 0; +} -- 2.1.4