mirror of
https://gitlab.freedesktop.org/dbus/dbus.git
synced 2025-12-22 17:10:17 +01:00
After the merge request !22 was created, this bug was fixed in !23, the associated branch was used for local tests, but the fix was not transferred to !22. After merging !22 into the master branch and rebasing !23 to the master, this fix was lost. Reviewed-by: Simon McVittie <smcv@collabora.com>
647 lines
17 KiB
C
647 lines
17 KiB
C
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
|
/* dbus-run-session.c - run a child process in its own session
|
|
*
|
|
* Copyright © 2003-2006 Red Hat, Inc.
|
|
* Copyright © 2006 Thiago Macieira <thiago@kde.org>
|
|
* Copyright © 2011-2012 Nokia Corporation
|
|
* Copyright © 2018 Ralf Habacker
|
|
*
|
|
* Licensed under the Academic Free License version 2.1
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#ifndef _MSC_VER
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include <sys/types.h>
|
|
#ifdef DBUS_UNIX
|
|
#include <sys/wait.h>
|
|
#include <signal.h>
|
|
#include <dbus/dbus-sysdeps-unix.h>
|
|
#else
|
|
#include <dbus/dbus-internals.h>
|
|
#include <dbus/dbus-sysdeps-win.h>
|
|
#endif
|
|
#include "dbus/dbus.h"
|
|
#include "dbus/dbus-internals.h"
|
|
|
|
#define MAX_ADDR_LEN 512
|
|
#define PIPE_READ_END 0
|
|
#define PIPE_WRITE_END 1
|
|
|
|
/* PROCESSES
|
|
*
|
|
* If you are in a shell and run "dbus-run-session myapp", here is what
|
|
* happens (compare and contrast with dbus-launch):
|
|
*
|
|
* shell
|
|
* \- dbus-run-session myapp
|
|
* \- dbus-daemon --nofork --print-address --session
|
|
* \- myapp
|
|
*
|
|
* All processes are long-running.
|
|
*
|
|
* When myapp exits, dbus-run-session kills dbus-daemon and terminates.
|
|
*
|
|
* If dbus-daemon exits, dbus-run-session warns and continues to run.
|
|
*
|
|
* PIPES
|
|
*
|
|
* dbus-daemon --print-address -> bus_address_pipe -> d-r-s
|
|
*/
|
|
|
|
static const char me[] = "dbus-run-session";
|
|
|
|
static void usage (int ecode) _DBUS_GNUC_NORETURN;
|
|
|
|
static void
|
|
usage (int ecode)
|
|
{
|
|
fprintf (stderr,
|
|
"%s [OPTIONS] [--] PROGRAM [ARGUMENTS]\n"
|
|
"%s --version\n"
|
|
"%s --help\n"
|
|
"\n"
|
|
"Options:\n"
|
|
"--dbus-daemon=BINARY run BINARY instead of dbus-daemon\n"
|
|
"--config-file=FILENAME pass to dbus-daemon instead of --session\n"
|
|
"\n",
|
|
me, me, me);
|
|
exit (ecode);
|
|
}
|
|
|
|
static void version (void) _DBUS_GNUC_NORETURN;
|
|
|
|
static void
|
|
version (void)
|
|
{
|
|
printf ("%s %s\n"
|
|
"Copyright (C) 2003-2006 Red Hat, Inc.\n"
|
|
"Copyright (C) 2006 Thiago Macieira\n"
|
|
"Copyright © 2011-2012 Nokia Corporation\n"
|
|
"Copyright © 2018 Ralf Habacker\n"
|
|
"\n"
|
|
"This is free software; see the source for copying conditions.\n"
|
|
"There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
|
|
me, VERSION);
|
|
exit (0);
|
|
}
|
|
|
|
#ifndef DBUS_WIN
|
|
static void oom (void) _DBUS_GNUC_NORETURN;
|
|
|
|
static void
|
|
oom (void)
|
|
{
|
|
fprintf (stderr, "%s: out of memory\n", me);
|
|
exit (1);
|
|
}
|
|
|
|
typedef enum
|
|
{
|
|
READ_STATUS_OK, /**< Read succeeded */
|
|
READ_STATUS_ERROR, /**< Some kind of error */
|
|
READ_STATUS_EOF /**< EOF returned */
|
|
} ReadStatus;
|
|
|
|
static ReadStatus
|
|
read_line (int fd,
|
|
char *buf,
|
|
size_t maxlen)
|
|
{
|
|
size_t bytes = 0;
|
|
ReadStatus retval;
|
|
|
|
memset (buf, '\0', maxlen);
|
|
maxlen -= 1; /* ensure nul term */
|
|
|
|
retval = READ_STATUS_OK;
|
|
|
|
while (1)
|
|
{
|
|
ssize_t chunk;
|
|
size_t to_read;
|
|
|
|
again:
|
|
to_read = maxlen - bytes;
|
|
|
|
if (to_read == 0)
|
|
break;
|
|
|
|
chunk = read (fd,
|
|
buf + bytes,
|
|
to_read);
|
|
if (chunk < 0 && errno == EINTR)
|
|
goto again;
|
|
|
|
if (chunk < 0)
|
|
{
|
|
retval = READ_STATUS_ERROR;
|
|
break;
|
|
}
|
|
else if (chunk == 0)
|
|
{
|
|
retval = READ_STATUS_EOF;
|
|
break; /* EOF */
|
|
}
|
|
else /* chunk > 0 */
|
|
bytes += chunk;
|
|
}
|
|
|
|
if (retval == READ_STATUS_EOF &&
|
|
bytes > 0)
|
|
retval = READ_STATUS_OK;
|
|
|
|
/* whack newline */
|
|
if (retval != READ_STATUS_ERROR &&
|
|
bytes > 0 &&
|
|
buf[bytes-1] == '\n')
|
|
buf[bytes-1] = '\0';
|
|
|
|
return retval;
|
|
}
|
|
|
|
static void
|
|
exec_dbus_daemon (const char *dbus_daemon,
|
|
int bus_address_pipe[2],
|
|
const char *config_file)
|
|
{
|
|
/* Child process, which execs dbus-daemon or dies trying */
|
|
#define MAX_FD_LEN 64
|
|
char write_address_fd_as_string[MAX_FD_LEN];
|
|
|
|
close (bus_address_pipe[PIPE_READ_END]);
|
|
|
|
/* Set all fds >= 3 close-on-execute, except for the one that can't be.
|
|
* We don't want dbus-daemon to inherit random fds we might have
|
|
* inherited from our caller. (Note that we *do* let the wrapped process
|
|
* inherit them in exec_app(), in an attempt to be as close as possible
|
|
* to being a transparent wrapper.) */
|
|
_dbus_fd_set_all_close_on_exec ();
|
|
_dbus_fd_clear_close_on_exec (bus_address_pipe[PIPE_WRITE_END]);
|
|
|
|
sprintf (write_address_fd_as_string, "%d", bus_address_pipe[PIPE_WRITE_END]);
|
|
|
|
execlp (dbus_daemon,
|
|
dbus_daemon,
|
|
"--nofork",
|
|
"--print-address", write_address_fd_as_string,
|
|
config_file ? "--config-file" : "--session",
|
|
config_file, /* has to be last in this varargs list */
|
|
NULL);
|
|
|
|
fprintf (stderr, "%s: failed to execute message bus daemon '%s': %s\n",
|
|
me, dbus_daemon, strerror (errno));
|
|
}
|
|
|
|
static void exec_app (int prog_arg, char **argv) _DBUS_GNUC_NORETURN;
|
|
|
|
static void
|
|
exec_app (int prog_arg, char **argv)
|
|
{
|
|
execvp (argv[prog_arg], argv + prog_arg);
|
|
|
|
fprintf (stderr, "%s: failed to exec '%s': %s\n", me, argv[prog_arg],
|
|
strerror (errno));
|
|
exit (1);
|
|
}
|
|
|
|
static int
|
|
run_session (const char *dbus_daemon,
|
|
const char *config_file,
|
|
char *bus_address,
|
|
char **argv,
|
|
int prog_arg)
|
|
{
|
|
pid_t bus_pid;
|
|
pid_t app_pid;
|
|
int bus_address_pipe[2] = { 0, 0 };
|
|
|
|
if (pipe (bus_address_pipe) < 0)
|
|
{
|
|
fprintf (stderr, "%s: failed to create pipe: %s\n", me, strerror (errno));
|
|
return 127;
|
|
}
|
|
|
|
/* Make sure our output buffers aren't redundantly printed by both the
|
|
* parent and the child */
|
|
fflush (stdout);
|
|
fflush (stderr);
|
|
|
|
bus_pid = fork ();
|
|
|
|
if (bus_pid < 0)
|
|
{
|
|
fprintf (stderr, "%s: failed to fork: %s\n", me, strerror (errno));
|
|
return 127;
|
|
}
|
|
|
|
if (bus_pid == 0)
|
|
{
|
|
/* child */
|
|
exec_dbus_daemon (dbus_daemon, bus_address_pipe, config_file);
|
|
/* not reached */
|
|
return 127;
|
|
}
|
|
|
|
close (bus_address_pipe[PIPE_WRITE_END]);
|
|
|
|
switch (read_line (bus_address_pipe[PIPE_READ_END], bus_address, MAX_ADDR_LEN))
|
|
{
|
|
case READ_STATUS_OK:
|
|
break;
|
|
|
|
case READ_STATUS_EOF:
|
|
fprintf (stderr, "%s: EOF reading address from bus daemon\n", me);
|
|
return 127;
|
|
break;
|
|
|
|
case READ_STATUS_ERROR:
|
|
fprintf (stderr, "%s: error reading address from bus daemon: %s\n",
|
|
me, strerror (errno));
|
|
return 127;
|
|
break;
|
|
|
|
default:
|
|
_dbus_assert_not_reached ("invalid read result");
|
|
}
|
|
|
|
close (bus_address_pipe[PIPE_READ_END]);
|
|
|
|
if (!dbus_setenv ("DBUS_SESSION_BUS_ADDRESS", bus_address) ||
|
|
!dbus_setenv ("DBUS_SESSION_BUS_PID", NULL) ||
|
|
!dbus_setenv ("DBUS_SESSION_BUS_WINDOWID", NULL) ||
|
|
!dbus_setenv ("DBUS_STARTER_ADDRESS", NULL) ||
|
|
!dbus_setenv ("DBUS_STARTER_BUS_TYPE", NULL))
|
|
oom ();
|
|
|
|
fflush (stdout);
|
|
fflush (stderr);
|
|
|
|
app_pid = fork ();
|
|
|
|
if (app_pid < 0)
|
|
{
|
|
fprintf (stderr, "%s: failed to fork: %s\n", me, strerror (errno));
|
|
return 127;
|
|
}
|
|
|
|
if (app_pid == 0)
|
|
{
|
|
/* child */
|
|
exec_app (prog_arg, argv);
|
|
/* not reached */
|
|
return 127;
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
int child_status;
|
|
pid_t child_pid = waitpid (-1, &child_status, 0);
|
|
|
|
if (child_pid == (pid_t) -1)
|
|
{
|
|
int errsv = errno;
|
|
|
|
if (errsv == EINTR)
|
|
continue;
|
|
|
|
/* shouldn't happen: the only other documented errors are ECHILD,
|
|
* which shouldn't happen because we terminate when all our children
|
|
* have died, and EINVAL, which would indicate programming error */
|
|
fprintf (stderr, "%s: waitpid() failed: %s\n", me, strerror (errsv));
|
|
return 127;
|
|
}
|
|
else if (child_pid == bus_pid)
|
|
{
|
|
/* no need to kill it, now */
|
|
bus_pid = 0;
|
|
|
|
if (WIFEXITED (child_status))
|
|
fprintf (stderr, "%s: dbus-daemon exited with code %d\n",
|
|
me, WEXITSTATUS (child_status));
|
|
else if (WIFSIGNALED (child_status))
|
|
fprintf (stderr, "%s: dbus-daemon terminated by signal %d\n",
|
|
me, WTERMSIG (child_status));
|
|
else
|
|
fprintf (stderr, "%s: dbus-daemon died or something\n", me);
|
|
}
|
|
else if (child_pid == app_pid)
|
|
{
|
|
if (bus_pid != 0)
|
|
kill (bus_pid, SIGTERM);
|
|
|
|
if (WIFEXITED (child_status))
|
|
return WEXITSTATUS (child_status);
|
|
|
|
/* if it died from a signal, behave like sh(1) */
|
|
if (WIFSIGNALED (child_status))
|
|
return 128 + WTERMSIG (child_status);
|
|
|
|
/* I give up (this should never be reached) */
|
|
fprintf (stderr, "%s: child process died or something\n", me);
|
|
return 127;
|
|
}
|
|
else
|
|
{
|
|
fprintf (stderr, "%s: ignoring unknown child process %ld\n", me,
|
|
(long) child_pid);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
static int
|
|
run_session (const char *dbus_daemon,
|
|
const char *config_file,
|
|
char *bus_address,
|
|
char **argv,
|
|
int prog_arg)
|
|
{
|
|
char *dbus_daemon_argv[4];
|
|
int ret = 127;
|
|
HANDLE server_handle = NULL;
|
|
HANDLE app_handle = NULL;
|
|
DWORD exit_code;
|
|
DBusString argv_strings[4];
|
|
DBusString address;
|
|
char **env = NULL;
|
|
DBusHashTable *env_table = NULL;
|
|
long sec,usec;
|
|
dbus_bool_t result = TRUE;
|
|
char *key = NULL;
|
|
char *value = NULL;
|
|
|
|
if (!_dbus_string_init (&argv_strings[0]))
|
|
result = FALSE;
|
|
if (!_dbus_string_init (&argv_strings[1]))
|
|
result = FALSE;
|
|
if (!_dbus_string_init (&argv_strings[2]))
|
|
result = FALSE;
|
|
if (!_dbus_string_init (&address))
|
|
result = FALSE;
|
|
if (!result)
|
|
goto out;
|
|
|
|
/* run dbus daemon */
|
|
_dbus_get_real_time (&sec, &usec);
|
|
/* On Windows it's difficult to make use of --print-address to
|
|
* convert a listenable address into a connectable address, so instead
|
|
* we tell the temporary dbus-daemon to use the Windows autolaunch
|
|
* mechanism, with a unique scope that is shared by this dbus-daemon,
|
|
* the app process that defines its lifetime, and any other child
|
|
* processes they might have. */
|
|
_dbus_string_append_printf (&address, "autolaunch:scope=dbus-tmp-session-%ld%ld-" DBUS_PID_FORMAT, sec, usec, _dbus_getpid ());
|
|
_dbus_string_append_printf (&argv_strings[0], "%s", dbus_daemon);
|
|
if (config_file != NULL)
|
|
_dbus_string_append_printf (&argv_strings[1], "--config-file=%s", config_file);
|
|
else
|
|
_dbus_string_append_printf (&argv_strings[1], "--session");
|
|
_dbus_string_append_printf (&argv_strings[2], "--address=%s", _dbus_string_get_const_data (&address));
|
|
dbus_daemon_argv[0] = _dbus_string_get_data (&argv_strings[0]);
|
|
dbus_daemon_argv[1] = _dbus_string_get_data (&argv_strings[1]);
|
|
dbus_daemon_argv[2] = _dbus_string_get_data (&argv_strings[2]);
|
|
dbus_daemon_argv[3] = NULL;
|
|
|
|
server_handle = _dbus_spawn_program (dbus_daemon, dbus_daemon_argv, NULL);
|
|
if (!server_handle)
|
|
{
|
|
_dbus_win_stderr_win_error (me, "Could not start dbus daemon", GetLastError ());
|
|
goto out;
|
|
}
|
|
|
|
/* run app */
|
|
env = _dbus_get_environment ();
|
|
env_table = _dbus_hash_table_new (DBUS_HASH_STRING,
|
|
dbus_free,
|
|
dbus_free);
|
|
if (!_dbus_hash_table_from_array (env_table, env, '='))
|
|
{
|
|
goto out;
|
|
}
|
|
|
|
/* replace DBUS_SESSION_BUS_ADDRESS in environment */
|
|
if (!_dbus_string_steal_data (&address, &value))
|
|
goto out;
|
|
|
|
key = _dbus_strdup ("DBUS_SESSION_BUS_ADDRESS");
|
|
|
|
if (key == NULL)
|
|
goto out;
|
|
|
|
if (_dbus_hash_table_insert_string (env_table, key, value))
|
|
{
|
|
/* env_table took ownership, do not free separately */
|
|
key = NULL;
|
|
value = NULL;
|
|
}
|
|
else
|
|
{
|
|
/* we still own key and value, the cleanup code will free them */
|
|
goto out;
|
|
}
|
|
|
|
_dbus_hash_table_remove_string (env_table, "DBUS_STARTER_ADDRESS");
|
|
_dbus_hash_table_remove_string (env_table, "DBUS_STARTER_BUS_TYPE");
|
|
_dbus_hash_table_remove_string (env_table, "DBUS_SESSION_BUS_PID");
|
|
_dbus_hash_table_remove_string (env_table, "DBUS_SESSION_BUS_WINDOWID");
|
|
|
|
dbus_free_string_array (env);
|
|
env = _dbus_hash_table_to_array (env_table, '=');
|
|
if (!env)
|
|
goto out;
|
|
|
|
app_handle = _dbus_spawn_program (argv[prog_arg], argv + prog_arg, env);
|
|
if (!app_handle)
|
|
{
|
|
_dbus_win_stderr_win_error (me, "unable to start child process", GetLastError ());
|
|
goto out;
|
|
}
|
|
|
|
WaitForSingleObject (app_handle, INFINITE);
|
|
if (!GetExitCodeProcess (app_handle, &exit_code))
|
|
{
|
|
_dbus_win_stderr_win_error (me, "could not fetch exit code", GetLastError ());
|
|
goto out;
|
|
}
|
|
ret = exit_code;
|
|
|
|
out:
|
|
TerminateProcess (server_handle, 0);
|
|
if (server_handle != NULL)
|
|
CloseHandle (server_handle);
|
|
if (app_handle != NULL)
|
|
CloseHandle (app_handle);
|
|
_dbus_string_free (&argv_strings[0]);
|
|
_dbus_string_free (&argv_strings[1]);
|
|
_dbus_string_free (&argv_strings[2]);
|
|
_dbus_string_free (&address);
|
|
dbus_free_string_array (env);
|
|
if (env_table != NULL)
|
|
_dbus_hash_table_unref (env_table);
|
|
dbus_free (key);
|
|
dbus_free (value);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
int prog_arg = 0;
|
|
const char *config_file = NULL;
|
|
const char *dbus_daemon = NULL;
|
|
char bus_address[MAX_ADDR_LEN] = { 0 };
|
|
const char *prev_arg = NULL;
|
|
int i = 1;
|
|
int requires_arg = 0;
|
|
|
|
while (i < argc)
|
|
{
|
|
const char *arg = argv[i];
|
|
|
|
if (requires_arg)
|
|
{
|
|
const char **arg_dest;
|
|
|
|
assert (prev_arg != NULL);
|
|
|
|
if (strcmp (prev_arg, "--config-file") == 0)
|
|
{
|
|
arg_dest = &config_file;
|
|
}
|
|
else if (strcmp (prev_arg, "--dbus-daemon") == 0)
|
|
{
|
|
arg_dest = &dbus_daemon;
|
|
}
|
|
else
|
|
{
|
|
/* shouldn't happen */
|
|
fprintf (stderr, "%s: internal error: %s not fully implemented\n",
|
|
me, prev_arg);
|
|
return 127;
|
|
}
|
|
|
|
if (*arg_dest != NULL)
|
|
{
|
|
fprintf (stderr, "%s: %s given twice\n", me, prev_arg);
|
|
return 127;
|
|
}
|
|
|
|
*arg_dest = arg;
|
|
requires_arg = 0;
|
|
prev_arg = arg;
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
if (strcmp (arg, "--help") == 0 ||
|
|
strcmp (arg, "-h") == 0 ||
|
|
strcmp (arg, "-?") == 0)
|
|
{
|
|
usage (0);
|
|
}
|
|
else if (strcmp (arg, "--version") == 0)
|
|
{
|
|
version ();
|
|
}
|
|
else if (strstr (arg, "--config-file=") == arg)
|
|
{
|
|
const char *file;
|
|
|
|
if (config_file != NULL)
|
|
{
|
|
fprintf (stderr, "%s: --config-file given twice\n", me);
|
|
return 127;
|
|
}
|
|
|
|
file = strchr (arg, '=');
|
|
++file;
|
|
|
|
config_file = file;
|
|
}
|
|
else if (strstr (arg, "--dbus-daemon=") == arg)
|
|
{
|
|
const char *file;
|
|
|
|
if (dbus_daemon != NULL)
|
|
{
|
|
fprintf (stderr, "%s: --dbus-daemon given twice\n", me);
|
|
return 127;
|
|
}
|
|
|
|
file = strchr (arg, '=');
|
|
++file;
|
|
|
|
dbus_daemon = file;
|
|
}
|
|
else if (strcmp (arg, "--config-file") == 0 ||
|
|
strcmp (arg, "--dbus-daemon") == 0)
|
|
{
|
|
requires_arg = 1;
|
|
}
|
|
else if (arg[0] == '-')
|
|
{
|
|
if (strcmp (arg, "--") != 0)
|
|
{
|
|
fprintf (stderr, "%s: option '%s' is unknown\n", me, arg);
|
|
return 127;
|
|
}
|
|
else
|
|
{
|
|
prog_arg = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
prog_arg = i;
|
|
break;
|
|
}
|
|
|
|
prev_arg = arg;
|
|
++i;
|
|
}
|
|
|
|
/* "dbus-run-session" and "dbus-run-session ... --" are not allowed:
|
|
* there must be something to run */
|
|
if (prog_arg < 1 || prog_arg >= argc)
|
|
{
|
|
fprintf (stderr, "%s: a non-option argument is required\n", me);
|
|
return 127;
|
|
}
|
|
|
|
if (requires_arg)
|
|
{
|
|
fprintf (stderr, "%s: option '%s' requires an argument\n", me, prev_arg);
|
|
return 127;
|
|
}
|
|
|
|
if (dbus_daemon == NULL)
|
|
dbus_daemon = "dbus-daemon";
|
|
|
|
return run_session (dbus_daemon, config_file, bus_address, argv, prog_arg);
|
|
}
|