]> git.ipfire.org Git - thirdparty/dbus.git/commitdiff
Add dbus-update-activation-environment tool
authorSimon McVittie <simon.mcvittie@collabora.co.uk>
Wed, 11 Feb 2015 15:47:53 +0000 (15:47 +0000)
committerSimon McVittie <simon.mcvittie@collabora.co.uk>
Tue, 24 Feb 2015 11:15:52 +0000 (11:15 +0000)
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>
cmake/doc/CMakeLists.txt
cmake/tools/CMakeLists.txt
configure.ac
doc/Makefile.am
doc/dbus-update-activation-environment.1.xml.in [new file with mode: 0644]
tools/Makefile.am
tools/dbus-update-activation-environment.c [new file with mode: 0644]

index 2bb720bd52e91f2d9b543bb21485ab0fcf8c9426..f21e84e7af92f58de4ca831f1768084edad73d8b 100644 (file)
@@ -138,6 +138,7 @@ configure_file(${CMAKE_SOURCE_DIR}/../doc/dbus-launch.1.xml.in ${CMAKE_BINARY_DI
 configure_file(${CMAKE_SOURCE_DIR}/../doc/dbus-monitor.1.xml.in ${CMAKE_BINARY_DIR}/doc/dbus-monitor.1.xml)
 configure_file(${CMAKE_SOURCE_DIR}/../doc/dbus-send.1.xml.in ${CMAKE_BINARY_DIR}/doc/dbus-send.1.xml)
 configure_file(${CMAKE_SOURCE_DIR}/../doc/dbus-test-tool.1.xml.in ${CMAKE_BINARY_DIR}/doc/dbus-test-tool.1.xml)
+configure_file(${CMAKE_SOURCE_DIR}/../doc/dbus-update-activation-environment.1.xml.in ${CMAKE_BINARY_DIR}/doc/dbus-update-activation-environment.1.xml)
 configure_file(${CMAKE_SOURCE_DIR}/../doc/dbus-uuidgen.1.xml.in ${CMAKE_BINARY_DIR}/doc/dbus-uuidgen.1.xml)
 DOCBOOK(${CMAKE_BINARY_DIR}/doc/dbus-cleanup-sockets.1.xml html-nochunks)
 DOCBOOK(${CMAKE_BINARY_DIR}/doc/dbus-daemon.1.xml html-nochunks)
@@ -146,6 +147,7 @@ DOCBOOK(${CMAKE_BINARY_DIR}/doc/dbus-monitor.1.xml html-nochunks)
 DOCBOOK(${CMAKE_BINARY_DIR}/doc/dbus-send.1.xml html-nochunks)
 DOCBOOK(${CMAKE_BINARY_DIR}/doc/dbus-test-tool.1.xml html-nochunks)
 DOCBOOK(${CMAKE_BINARY_DIR}/doc/dbus-uuidgen.1.xml html-nochunks)
+DOCBOOK(${CMAKE_BINARY_DIR}/doc/dbus-update-activation-environment.1.xml html-nochunks)
 if (UNIX)
   DOCBOOK(${CMAKE_BINARY_DIR}/doc/dbus-daemon.1.xml man)
   DOCBOOK(${CMAKE_BINARY_DIR}/doc/dbus-monitor.1.xml man)
@@ -154,6 +156,7 @@ if (UNIX)
   DOCBOOK(${CMAKE_BINARY_DIR}/doc/dbus-launch.1.xml man)
   DOCBOOK(${CMAKE_BINARY_DIR}/doc/dbus-uuidgen.1.xml man)
   DOCBOOK(${CMAKE_BINARY_DIR}/doc/dbus-cleanup-sockets.1.xml man)
+  DOCBOOK(${CMAKE_BINARY_DIR}/doc/dbus-update-activation-environment.1.xml man)
 endif()
 #
 # handle html index file
index 525dc0576e3906625fe17ccff829b88b87f4ce16..fd0617d10baab3c566c18e6847776397e9f62b19 100644 (file)
@@ -25,6 +25,12 @@ set (dbus_test_tool_SOURCES
        ../../tools/test-tool.h
 )
 
+set (dbus_update_activation_environment_SOURCES
+       ../../tools/dbus-update-activation-environment.c
+       ../../tools/tool-common.c
+       ../../tools/tool-common.h
+)
+
 if (WIN32)
 set (dbus_launch_SOURCES
        ../../tools/dbus-launch-win.c
@@ -54,6 +60,10 @@ add_executable(dbus-test-tool ${dbus_test_tool_SOURCES})
 target_link_libraries(dbus-test-tool ${DBUS_LIBRARIES})
 install_targets(/bin dbus-test-tool )
 
+add_executable(dbus-update-activation-environment ${dbus_update_activation_environment_SOURCES})
+target_link_libraries(dbus-update-activation-environment ${DBUS_LIBRARIES})
+install_targets(/bin dbus-update-activation-environment )
+
 add_executable(dbus-launch ${dbus_launch_SOURCES})
 target_link_libraries(dbus-launch )
 if (DBUS_BUILD_X11)
index 303bc344389b37d964366a2c49bc6bc74e950d06..bea630a05a70f34abdfb623ec178542b79d3bfa6 100644 (file)
@@ -1861,6 +1861,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
index f875dc2517663cdf745defbdff70b752fa306a6c..8bc85c53b8ee640f16e8fa4554d19bc7328ab811 100644 (file)
@@ -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 (file)
index 0000000..8a495df
--- /dev/null
@@ -0,0 +1,213 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
+<refentry id="dbus-update-activation-environment.1">
+  <refentryinfo>
+    <copyright>
+      <year>2015</year>
+      <holder>Collabora Ltd.</holder>
+    </copyright>
+    <legalnotice>
+      <para>This man page is distributed under the same terms as
+        dbus-update-activation-environment (MIT/X11). There is NO WARRANTY,
+        to the extent permitted by law.</para>
+    </legalnotice>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>dbus-update-activation-environment</refentrytitle>
+    <manvolnum>1</manvolnum>
+    <refmiscinfo class="manual">User Commands</refmiscinfo>
+    <refmiscinfo class="source">D-Bus</refmiscinfo>
+    <refmiscinfo class="version">@DBUS_VERSION@</refmiscinfo>
+  </refmeta>
+  <refnamediv>
+    <refname>dbus-update-activation-environment</refname>
+    <refpurpose>update environment used for D-Bus session services</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv id="synopsis">
+    <cmdsynopsis>
+      <command>dbus-update-activation-environment</command>
+      <arg choice="opt">--systemd</arg>
+      <arg choice="opt">--verbose</arg>
+      <group choice="plain">
+        <arg choice="plain">--all</arg>
+        <arg choice="plain" rep="repeat"><replaceable>VAR</replaceable></arg>
+        <arg choice="plain" rep="repeat"><replaceable>VAR</replaceable>=<replaceable>VAL</replaceable></arg>
+      </group>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1 id="description">
+    <title>DESCRIPTION</title>
+    <para><command>dbus-update-activation-environment</command>
+      updates the list of environment variables used by
+      <command>dbus-daemon --session</command>
+      when it activates session services without using
+      <command>systemd</command>.</para>
+
+    <para>With the <option>--systemd</option> option,
+      if an instance of <command>systemd --user</command> is
+      available on D-Bus, it also updates the list of environment variables
+      used by <command>systemd --user</command>
+      when it activates user services, including D-Bus session services
+      for which <command>dbus-daemon</command> has been configured to
+      delegate activation to <command>systemd</command>.
+      This is very similar to the <option>import-environment</option>
+      command provided by
+      <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>).</para>
+
+    <para>Variables that are special to <command>dbus-daemon</command>
+      or <command>systemd</command> may be set, but their values will
+      be overridden when a service is started. For instance, it is
+      not useful to add <envar>DBUS_SESSION_BUS_ADDRESS</envar> to
+      <command>dbus-daemon</command>'s activation environment,
+      although it might still be useful to add it to
+      <command>systemd</command>'s activation environment.</para>
+  </refsect1>
+
+  <refsect1 id="options">
+    <title>OPTIONS</title>
+    <variablelist remap="TP">
+
+      <varlistentry>
+        <term><option>--all</option></term>
+        <listitem>
+          <para>Set all environment variables present in
+            the environment used by
+            <command>dbus-update-activation-environment</command>.
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--systemd</option></term>
+        <listitem>
+          <para>Set environment variables for systemd user services as well as
+            for traditional D-Bus session services.</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--verbose</option></term>
+        <listitem>
+          <para>Output messages to standard error explaining what
+            dbus-update-activation-environment is doing.</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><replaceable>VAR</replaceable></term>
+        <listitem>
+          <para>If <replaceable>VAR</replaceable> is present in the
+            environment of <command>dbus-update-activation-environment</command>,
+            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
+            <replaceable>VAR</replaceable> is not present
+            in the environment, this argument is silently ignored.
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><replaceable>VAR</replaceable>=<replaceable>VAL</replaceable></term>
+        <listitem>
+          <para>Set <replaceable>VAR</replaceable> to <replaceable>VAL</replaceable>,
+            which must be UTF-8.</para>
+        </listitem>
+      </varlistentry>
+
+    </variablelist>
+  </refsect1>
+
+  <refsect1 id="examples">
+    <title>EXAMPLES</title>
+    <para>
+      <command>dbus-update-activation-environment</command> is
+      primarily designed to be used in Linux distributions' X11 session
+      startup scripts, in conjunction with the "user bus" design.
+    </para>
+
+    <para>To propagate <envar>DISPLAY</envar> and <envar>XAUTHORITY</envar>
+      to <command>dbus-daemon</command>
+      and, if present, <command>systemd</command>,
+      and propagate <envar>DBUS_SESSION_BUS_ADDRESS</envar> to
+      <command>systemd</command>:
+      <programlisting language="sh">
+        dbus-update-activation-environment --systemd \
+            DBUS_SESSION_BUS_ADDRESS DISPLAY XAUTHORITY
+      </programlisting>
+    </para>
+
+    <para>To propagate all environment variables except
+      <envar>XDG_SEAT</envar>, <envar>XDG_SESSION_ID</envar>
+      and <envar>XDG_VTNR</envar> to <command>dbus-daemon</command>
+      (and, if present, <command>systemd</command>) for compatibility
+      with legacy X11 session startup scripts:
+      <programlisting language="sh">
+        # 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
+        )
+      </programlisting>
+    </para>
+  </refsect1>
+
+  <refsect1 id="exit_status">
+    <title>EXIT STATUS</title>
+    <para>
+      <command>dbus-update-activation-environment</command>
+      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.</para>
+  </refsect1>
+
+  <refsect1 id="environment">
+    <title>ENVIRONMENT</title>
+      <para><envar>DBUS_SESSION_BUS_ADDRESS</envar>,
+        <envar>XDG_RUNTIME_DIR</envar> and/or <envar>DISPLAY</envar>
+        are used to find the address of the session bus.</para>
+  </refsect1>
+
+  <refsect1 id="limitations">
+    <title>LIMITATIONS</title>
+    <para>
+      <command>dbus-daemon</command> does not provide a way to unset
+      environment variables after they have been set (although
+      <command>systemd</command> does), so
+      <command>dbus-update-activation-environment</command> does not
+      offer this functionality either.
+    </para>
+
+    <para>
+      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 <command>dbus-daemon</command> nor <command>systemd</command>
+      supports environment variables with non-UTF-8 names or values.
+      Accordingly, <command>dbus-update-activation-environment</command>
+      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.
+    </para>
+  </refsect1>
+
+  <refsect1 id="bugs">
+    <title>BUGS</title>
+      <para>Please send bug reports to the D-Bus bug tracker or mailing list.
+        See <ulink url="http://www.freedesktop.org/software/dbus/">http://www.freedesktop.org/software/dbus/</ulink>.</para>
+  </refsect1>
+
+  <refsect1 id="see_also">
+    <title>SEE ALSO</title>
+    <para><citerefentry><refentrytitle>dbus-daemon</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+      the <option>import-environment</option> command of
+      <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry></para>
+  </refsect1>
+</refentry>
index 80025b82bb098ab3f93e3f0a9958d84d59b63e32..68a597076f8887466a40facb294e42e165d64b94 100644 (file)
@@ -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 (file)
index 0000000..56d3b20
--- /dev/null
@@ -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,
+              &copy))
+        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;
+}