Blob Blame History Raw
From dc2074588d3e7b5a216cb8c0b82094157c3cf773 Mon Sep 17 00:00:00 2001
From: David King <dking@redhat.com>
Date: Mon, 25 Jun 2018 14:46:14 -0400
Subject: [PATCH] daemon: use HOME as the working directory

Session buses started as part of a systemd --user session are launched
with the current working directory being the home directory of the user.
Applications which are launched via dbus activation inherit the working
directory from the session bus dbus-daemon.

When dbus-launch is used to start dbus-daemon, as is commonly the case
with a session manager such as gnome-session, this leads to applications
having a default working directory of "/", which is undesirable (as an
example, the default directory in a GTK+ save dialog becomes "/").

As an improvement, make dbus-launch use the value of the environment
variable HOME, if it is set, as the current working directory.

Signed-off-by: David King <dking@redhat.com>
Bug: https://bugs.freedesktop.org/show_bug.cgi?id=106987
Bug-RedHat: https://bugzilla.redhat.com/show_bug.cgi?id=1470310
---
 bus/bus.c                     |  9 +++++++++
 dbus/dbus-sysdeps-util-unix.c |  8 +++++---
 dbus/dbus-sysdeps-util-win.c  |  2 ++
 dbus/dbus-sysdeps.h           |  1 +
 doc/dbus-launch.1.xml.in      |  4 ++++
 tools/dbus-launch.c           | 22 ++++++++++++++--------
 6 files changed, 35 insertions(+), 11 deletions(-)

diff --git a/bus/bus.c b/bus/bus.c
index f788e677..da2b2c1f 100644
--- a/bus/bus.c
+++ b/bus/bus.c
@@ -870,63 +870,72 @@ bus_context_new (const DBusString *config_file,
 
   context->matchmaker = bus_matchmaker_new ();
   if (context->matchmaker == NULL)
     {
       BUS_SET_OOM (error);
       goto failed;
     }
 
   /* check user before we fork */
   if (context->user != NULL)
     {
       if (!_dbus_verify_daemon_user (context->user))
         {
           dbus_set_error (error, DBUS_ERROR_FAILED,
                           "Could not get UID and GID for username \"%s\"",
                           context->user);
           goto failed;
         }
     }
 
   /* Now become a daemon if appropriate and write out pid file in any case */
   {
     DBusString u;
 
     if (context->pidfile)
       _dbus_string_init_const (&u, context->pidfile);
 
     if (((flags & BUS_CONTEXT_FLAG_FORK_NEVER) == 0 && context->fork) ||
         (flags & BUS_CONTEXT_FLAG_FORK_ALWAYS))
       {
+        const char *working_dir = NULL;
+
         _dbus_verbose ("Forking and becoming daemon\n");
 
+        if (context->type != NULL && strcmp (context->type, "session") == 0)
+          working_dir = _dbus_getenv ("HOME");
+
+        if (working_dir == NULL)
+          working_dir = "/";
+
         if (!_dbus_become_daemon (context->pidfile ? &u : NULL,
+                                  working_dir,
                                   print_pid_pipe,
                                   error,
                                   context->keep_umask))
           {
             _DBUS_ASSERT_ERROR_IS_SET (error);
             goto failed;
           }
       }
     else
       {
         _dbus_verbose ("Fork not requested\n");
 
         /* Need to write PID file and to PID pipe for ourselves,
          * not for the child process. This is a no-op if the pidfile
          * is NULL and print_pid_pipe is NULL.
          */
         if (!_dbus_write_pid_to_file_and_pipe (context->pidfile ? &u : NULL,
                                                print_pid_pipe,
                                                _dbus_getpid (),
                                                error))
           {
             _DBUS_ASSERT_ERROR_IS_SET (error);
             goto failed;
           }
       }
   }
 
   if (print_pid_pipe && _dbus_pipe_is_valid (print_pid_pipe) &&
       !_dbus_pipe_is_stdout_or_stderr (print_pid_pipe))
     _dbus_pipe_close (print_pid_pipe, NULL);
diff --git a/dbus/dbus-sysdeps-util-unix.c b/dbus/dbus-sysdeps-util-unix.c
index 9b724cc9..30bb1441 100644
--- a/dbus/dbus-sysdeps-util-unix.c
+++ b/dbus/dbus-sysdeps-util-unix.c
@@ -49,82 +49,84 @@
 #include <sys/socket.h>
 #include <dirent.h>
 #include <sys/un.h>
 
 #ifdef HAVE_SYSLOG_H
 #include <syslog.h>
 #endif
 
 #ifdef HAVE_SYS_SYSLIMITS_H
 #include <sys/syslimits.h>
 #endif
 
 #ifdef HAVE_SYSTEMD
 #include <systemd/sd-daemon.h>
 #endif
 
 #ifndef O_BINARY
 #define O_BINARY 0
 #endif
 
 /**
  * @addtogroup DBusInternalsUtils
  * @{
  */
 
 
 /**
  * Does the chdir, fork, setsid, etc. to become a daemon process.
  *
  * @param pidfile #NULL, or pidfile to create
+ * @param working_dir directory to chdir to
  * @param print_pid_pipe pipe to print daemon's pid to, or -1 for none
  * @param error return location for errors
  * @param keep_umask #TRUE to keep the original umask
  * @returns #FALSE on failure
  */
 dbus_bool_t
 _dbus_become_daemon (const DBusString *pidfile,
+                     const char       *working_dir,
                      DBusPipe         *print_pid_pipe,
                      DBusError        *error,
                      dbus_bool_t       keep_umask)
 {
   const char *s;
   pid_t child_pid;
   int dev_null_fd;
 
   _dbus_verbose ("Becoming a daemon...\n");
 
-  _dbus_verbose ("chdir to /\n");
-  if (chdir ("/") < 0)
+  _dbus_verbose ("chdir to %s\n", working_dir);
+  if (chdir (working_dir) < 0)
     {
       dbus_set_error (error, DBUS_ERROR_FAILED,
-                      "Could not chdir() to root directory");
+                      "Could not chdir() to working directory (%s)", working_dir);
       return FALSE;
     }
 
   _dbus_verbose ("forking...\n");
   switch ((child_pid = fork ()))
     {
     case -1:
       _dbus_verbose ("fork failed\n");
       dbus_set_error (error, _dbus_error_from_errno (errno),
                       "Failed to fork daemon: %s", _dbus_strerror (errno));
       return FALSE;
       break;
 
     case 0:
       _dbus_verbose ("in child, closing std file descriptors\n");
 
       /* silently ignore failures here, if someone
        * doesn't have /dev/null we may as well try
        * to continue anyhow
        */
       
       dev_null_fd = open ("/dev/null", O_RDWR);
       if (dev_null_fd >= 0)
         {
           dup2 (dev_null_fd, 0);
           dup2 (dev_null_fd, 1);
           
           s = _dbus_getenv ("DBUS_DEBUG_OUTPUT");
           if (s == NULL || *s == '\0')
             dup2 (dev_null_fd, 2);
diff --git a/dbus/dbus-sysdeps-util-win.c b/dbus/dbus-sysdeps-util-win.c
index 3b754dbf..bfc1cb90 100644
--- a/dbus/dbus-sysdeps-util-win.c
+++ b/dbus/dbus-sysdeps-util-win.c
@@ -27,67 +27,69 @@
 #define STRSAFE_NO_DEPRECATE
 
 #include "dbus-sysdeps.h"
 #include "dbus-internals.h"
 #include "dbus-protocol.h"
 #include "dbus-string.h"
 #include "dbus-sysdeps.h"
 #include "dbus-sysdeps-win.h"
 #include "dbus-sockets-win.h"
 #include "dbus-memory.h"
 #include "dbus-pipe.h"
 
 #include <stdio.h>
 #include <stdlib.h>
 #if HAVE_ERRNO_H
 #include <errno.h>
 #endif
 #include <winsock2.h>   // WSA error codes
 
 #ifndef DBUS_WINCE
 #include <io.h>
 #include <lm.h>
 #include <sys/stat.h>
 #endif
 
 
 /**
  * Does the chdir, fork, setsid, etc. to become a daemon process.
  *
  * @param pidfile #NULL, or pidfile to create
+ * @param working_dir directory to chdir to
  * @param print_pid_pipe file descriptor to print daemon's pid to, or -1 for none
  * @param error return location for errors
  * @param keep_umask #TRUE to keep the original umask
  * @returns #FALSE on failure
  */
 dbus_bool_t
 _dbus_become_daemon (const DBusString *pidfile,
+                     const char       *working_dir,
                      DBusPipe         *print_pid_pipe,
                      DBusError        *error,
                      dbus_bool_t       keep_umask)
 {
   dbus_set_error (error, DBUS_ERROR_NOT_SUPPORTED,
                   "Cannot daemonize on Windows");
   return FALSE;
 }
 
 /**
  * Creates a file containing the process ID.
  *
  * @param filename the filename to write to
  * @param pid our process ID
  * @param error return location for errors
  * @returns #FALSE on failure
  */
 static dbus_bool_t
 _dbus_write_pid_file (const DBusString *filename,
                       unsigned long     pid,
                       DBusError        *error)
 {
   const char *cfilename;
   HANDLE hnd;
   char pidstr[20];
   int total;
   int bytes_to_write;
 
   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
 
diff --git a/dbus/dbus-sysdeps.h b/dbus/dbus-sysdeps.h
index 0ee45c97..e569b545 100644
--- a/dbus/dbus-sysdeps.h
+++ b/dbus/dbus-sysdeps.h
@@ -498,60 +498,61 @@ int _dbus_printf_string_upper_bound (const char *format,
                                      va_list args);
 
 
 /**
  * Portable struct with stat() results
  */
 typedef struct
 {
   unsigned long mode;  /**< File mode */
   unsigned long nlink; /**< Number of hard links */
   dbus_uid_t    uid;   /**< User owning file */
   dbus_gid_t    gid;   /**< Group owning file */
   unsigned long size;  /**< Size of file */
   unsigned long atime; /**< Access time */
   unsigned long mtime; /**< Modify time */
   unsigned long ctime; /**< Creation time */
 } DBusStat;
 
 dbus_bool_t _dbus_stat             (const DBusString *filename,
                                     DBusStat         *statbuf,
                                     DBusError        *error);
 DBUS_PRIVATE_EXPORT
 dbus_bool_t _dbus_socketpair (DBusSocket       *fd1,
                               DBusSocket       *fd2,
                               dbus_bool_t       blocking,
                               DBusError        *error);
 
 void        _dbus_print_backtrace  (void);
 
 dbus_bool_t _dbus_become_daemon   (const DBusString *pidfile,
+                                   const char       *working_dir,
                                    DBusPipe         *print_pid_pipe,
                                    DBusError        *error,
                                    dbus_bool_t       keep_umask);
 
 dbus_bool_t _dbus_verify_daemon_user    (const char *user);
 dbus_bool_t _dbus_change_to_daemon_user (const char *user,
                                          DBusError  *error);
 
 dbus_bool_t _dbus_write_pid_to_file_and_pipe (const DBusString *pidfile,
                                               DBusPipe         *print_pid_pipe,
                                               dbus_pid_t        pid_to_write,
                                               DBusError        *error);
 
 dbus_bool_t _dbus_command_for_pid (unsigned long  pid,
                                    DBusString    *str,
                                    int            max_len,
                                    DBusError     *error);
 
 /** A UNIX signal handler */
 typedef void (* DBusSignalHandler) (int sig);
 
 void _dbus_set_signal_handler (int               sig,
                                DBusSignalHandler handler);
 
 dbus_bool_t _dbus_user_at_console (const char *username,
                                    DBusError  *error);
 
 void _dbus_init_system_log (dbus_bool_t is_daemon);
 
 typedef enum {
diff --git a/doc/dbus-launch.1.xml.in b/doc/dbus-launch.1.xml.in
index 5135d9ca..606c65be 100644
--- a/doc/dbus-launch.1.xml.in
+++ b/doc/dbus-launch.1.xml.in
@@ -23,60 +23,64 @@
   <command>dbus-launch</command>
     <arg choice='opt'>--version </arg>
     <arg choice='opt'>--help </arg>
     <arg choice='opt'>--sh-syntax </arg>
     <arg choice='opt'>--csh-syntax </arg>
     <arg choice='opt'>--auto-syntax </arg>
     <arg choice='opt'>--binary-syntax </arg>
     <arg choice='opt'>--close-stderr </arg>
     <arg choice='opt'>--exit-with-session </arg>
     <arg choice='opt'>--autolaunch=<replaceable>MACHINEID</replaceable></arg>
     <arg choice='opt'>--config-file=<replaceable>FILENAME</replaceable></arg>
     <arg choice='opt'><replaceable>PROGRAM</replaceable></arg>
     <arg choice='opt' rep='repeat'><replaceable>ARGS</replaceable></arg>
     <sbr/>
 </cmdsynopsis>
 </refsynopsisdiv>
 
 
 <refsect1 id='description'><title>DESCRIPTION</title>
 <para>The <command>dbus-launch</command> command is used to start a session bus
 instance of <emphasis remap='I'>dbus-daemon</emphasis> from a shell script.
 It would normally be called from a user's login
 scripts. Unlike the daemon itself, <command>dbus-launch</command> exits, so
 backticks or the $() construct can be used to read information from
 <command>dbus-launch</command>.</para>
 
 <para>With no arguments, <command>dbus-launch</command> will launch a session bus
 instance and print the address and PID of that instance to standard
 output.</para>
 
+<para>If the environment variable HOME is set, it is used as the current
+working directory. Otherwise, the root directory (<filename>/</filename>) is
+used.</para>
+
 <para>You may specify a program to be run; in this case, <command>dbus-launch</command>
 will launch a session bus instance, set the appropriate environment
 variables so the specified program can find the bus, and then execute the
 specified program, with the specified arguments.  See below for
 examples.</para>
 
 <para>If you launch a program, <command>dbus-launch</command> will not print the
 information about the new bus to standard output.</para>
 
 <para>When <command>dbus-launch</command> prints bus information to standard output, by
 default it is in a simple key-value pairs format. However, you may
 request several alternate syntaxes using the --sh-syntax, --csh-syntax,
 --binary-syntax, or
 --auto-syntax options. Several of these cause <command>dbus-launch</command> to emit shell code
 to set up the environment.</para>
 
 <para>With the --auto-syntax option, <command>dbus-launch</command> looks at the value
 of the SHELL environment variable to determine which shell syntax
 should be used.  If SHELL ends in "csh", then csh-compatible code is
 emitted; otherwise Bourne shell code is emitted.  Instead of passing
 --auto-syntax, you may explicitly specify a particular one by using
 --sh-syntax for Bourne syntax, or --csh-syntax for csh syntax.
 In scripts, it's more robust to avoid --auto-syntax and you hopefully
 know which shell your script is written in.</para>
 
 
 <para>See <ulink url='http://www.freedesktop.org/software/dbus/'>http://www.freedesktop.org/software/dbus/</ulink> for more information
 about D-Bus. See also the man page for <emphasis remap='I'>dbus-daemon</emphasis>.</para>
 
 </refsect1>
diff --git a/tools/dbus-launch.c b/tools/dbus-launch.c
index 80e4a241..a956684c 100644
--- a/tools/dbus-launch.c
+++ b/tools/dbus-launch.c
@@ -592,71 +592,77 @@ kill_bus_when_session_ends (void)
                   /* This shouldn't happen I don't think; to avoid
                    * spinning on the fd forever we exit.
                    */
                   fprintf (stderr, "dbus-launch: error reading from stdin: %s\n",
                            strerror (errno));
                   kill_bus_and_exit (0);
                 }
             }
           else if (FD_ISSET (tty_fd, &err_set))
             {
               verbose ("TTY has error condition\n");
               
               kill_bus_and_exit (0);
             }
         }
     }
 }
 
 static void
 babysit (int   exit_with_session,
          pid_t child_pid,
          int   read_bus_pid_fd)  /* read pid from here */
 {
   int ret;
   int dev_null_fd;
   const char *s;
 
   verbose ("babysitting, exit_with_session = %d, child_pid = %ld, read_bus_pid_fd = %d\n",
            exit_with_session, (long) child_pid, read_bus_pid_fd);
   
-  /* We chdir ("/") since we are persistent and daemon-like, and fork
-   * again so dbus-launch can reap the parent.  However, we don't
-   * setsid() or close fd 0 because the idea is to remain attached
-   * to the tty and the X server in order to kill the message bus
-   * when the session ends.
+  /* We chdir () since we are persistent and daemon-like, either to $HOME
+   * to match the behaviour of a session bus started by systemd --user, or
+   * otherwise "/". We fork again so dbus-launch can reap the parent.
+   * However, we don't setsid() or close fd 0 because the idea is to
+   * remain attached to the tty and the X server in order to kill the
+   * message bus when the session ends.
    */
 
-  if (chdir ("/") < 0)
+  s = getenv ("HOME");
+
+  if (s == NULL || *s == '\0')
+    s = "/";
+
+  if (chdir (s) < 0)
     {
-      fprintf (stderr, "Could not change to root directory: %s\n",
-               strerror (errno));
+      fprintf (stderr, "Could not change to working directory \"%s\": %s\n",
+               s, strerror (errno));
       exit (1);
     }
 
   /* Close stdout/stderr so we don't block an "eval" or otherwise
    * lock up. stdout is still chaining through to dbus-launch
    * and in turn to the parent shell.
    */
   dev_null_fd = open ("/dev/null", O_RDWR);
   if (dev_null_fd >= 0)
     {
       if (!exit_with_session)
         dup2 (dev_null_fd, 0);
       dup2 (dev_null_fd, 1);
       s = getenv ("DBUS_DEBUG_OUTPUT");
       if (s == NULL || *s == '\0')
         dup2 (dev_null_fd, 2);
       close (dev_null_fd);
     }
   else
     {
       fprintf (stderr, "Failed to open /dev/null: %s\n",
                strerror (errno));
       /* continue, why not */
     }
   
   ret = fork ();
 
   if (ret < 0)
     {
       fprintf (stderr, "fork() failed in babysitter: %s\n",
-- 
2.17.1