diff options
author | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2012-02-07 17:45:16 +0000 |
---|---|---|
committer | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2013-06-05 17:55:23 +0100 |
commit | f691c24dd1bff1c6cd38fe3b5f2f2831e175464c (patch) | |
tree | a9b651847356bdce0dd90c611112ff4ca4c11bcb | |
parent | 108ea348d844ec138588690ad5f17b87839ce1f4 (diff) | |
download | dbus-f691c24dd1bff1c6cd38fe3b5f2f2831e175464c.tar.gz |
Add dbus-run-session
Bug: https://bugs.freedesktop.org/show_bug.cgi?id=39196
Reviewed-by: Colin Walters <walters@verbum.org>
-rw-r--r-- | doc/Makefile.am | 2 | ||||
-rw-r--r-- | doc/dbus-run-session.1 | 100 | ||||
-rw-r--r-- | tools/Makefile.am | 4 | ||||
-rw-r--r-- | tools/dbus-run-session.c | 477 |
4 files changed, 583 insertions, 0 deletions
diff --git a/doc/Makefile.am b/doc/Makefile.am index 35725696..0c6b6fdf 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -15,6 +15,7 @@ man1_MANS = \ dbus-daemon.1 \ dbus-launch.1 \ dbus-monitor.1 \ + dbus-run-session.1 \ dbus-send.1 \ dbus-uuidgen.1 endif @@ -24,6 +25,7 @@ MAN_HTML_FILES = \ dbus-daemon.1.html \ dbus-launch.1.html \ dbus-monitor.1.html \ + dbus-run-session.1.html \ dbus-send.1.html \ dbus-uuidgen.1.html diff --git a/doc/dbus-run-session.1 b/doc/dbus-run-session.1 new file mode 100644 index 00000000..8b270ebe --- /dev/null +++ b/doc/dbus-run-session.1 @@ -0,0 +1,100 @@ +.TH dbus\-run\-session 1 +.SH NAME +dbus\-run\-session \- start a process as a new D-Bus session +.SH SYNOPSIS +.B dbus\-run\-session +.RB [ \-\-config\-file +.IR FILENAME ] +.RB [ \-\-dbus\-daemon +.IR BINARY ] +.RB [ \-\- ] +.IR PROGRAM " [" ARGUMENTS ...] +.P +.B dbus\-run\-session \-\-help +.P +.B dbus\-run\-session \-\-version +.SH DESCRIPTION +.B dbus\-run\-session +is used to start a session bus instance of +.B dbus\-daemon +from a shell script, and start a specified program in that session. The +.B dbus\-daemon +will run for as long as the program does, after which it will terminate. +.P +One use is to run a shell with its own +.B dbus\-daemon +in a text\(hymode or SSH session, and have the +.B dbus\-daemon +terminate automatically on leaving the sub\(hyshell, like this: +.P + dbus\-run\-session \-\- bash +.P +or to replace the login shell altogether, by combining \fBdbus\-run\-session\fR +with the \fBexec\fR builtin: +.P + exec dbus\-run\-session \-\- bash +.P +Another use is to run regression tests and similar things in an isolated +D-Bus session, to avoid either interfering with the "real" D-Bus session +or relying on there already being a D-Bus session active, for instance: +.P + dbus\-run\-session \-\- make check +.P +or (in +.BR automake (1)): +.P +.nf + TESTS_ENVIRONMENT = MY_DEBUG=all dbus\-run\-session \-\- +.fi +.P +.SH OPTIONS +.TP +\fB\-\-config\-file=\fIFILENAME\fR, \fB\-\-config\-file\fR \fIFILENAME\fR +Pass +.BI \-\-config-file= FILENAME +to the bus daemon, instead of passing it the +.B \-\-session +argument. See +.BR dbus-daemon (1). +.TP +\fB\-\-dbus\-daemon=\fIBINARY\fR, \fB\-\-dbus\-daemon\fR \fIBINARY\fR +Run \fIBINARY\fR as \fBdbus\-daemon\fR(1), instead of searching the \fBPATH\fR +in the usual way for an executable called \fBdbus\-daemon\fR. +.TP +.B \-\-help +Print usage information and exit. +.TP +.B \-\-version +Print the version of dbus\-run\-session and exit. +.SH EXIT STATUS +.B dbus\-run\-session +exits with the exit status of +.IR PROGRAM , +0 if the +.BR \-\-help " or " \-\-version +options were used, 127 on an error within +.B dbus\-run\-session +itself, or +.RI 128+ n +if the +.I PROGRAM +was killed by signal +.IR n . +.SH ENVIRONMENT +.B PATH +is searched to find +.IR PROGRAM , +and (if the \-\-dbus\-daemon option is not used or its argument does not +contain a +.BR / " character) to find " dbus\-daemon . +.P +The session bus' address is made available to +.I PROGRAM +in the environment variable +.BR DBUS_SESSION_BUS_ADDRESS . +.SH BUGS +Please send bug reports to the D\-Bus mailing list or bug tracker, +see http://www.freedesktop.org/software/dbus/ +.SH SEE ALSO +.BR dbus\-daemon (1), +.BR dbus\-launch (1) diff --git a/tools/Makefile.am b/tools/Makefile.am index cfd54b8b..464a8050 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -20,6 +20,7 @@ bin_PROGRAMS = \ if DBUS_UNIX bin_PROGRAMS += \ dbus-cleanup-sockets \ + dbus-run-session \ dbus-uuidgen \ $(NULL) endif @@ -43,6 +44,9 @@ dbus_launch_SOURCES= \ dbus-launch.c \ dbus-launch-x11.c \ dbus-launch.h + +dbus_run_session_SOURCES = \ + dbus-run-session.c endif dbus_cleanup_sockets_SOURCES= \ diff --git a/tools/dbus-run-session.c b/tools/dbus-run-session.c new file mode 100644 index 00000000..4f0b32b5 --- /dev/null +++ b/tools/dbus-run-session.c @@ -0,0 +1,477 @@ +/* -*- 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 + * + * 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> +#include <unistd.h> + +#include <sys/types.h> +#include <sys/wait.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) +{ + 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) +{ + printf ("%s %s\n" + "Copyright (C) 2003-2006 Red Hat, Inc.\n" + "Copyright (C) 2006 Thiago Macieira\n" + "Copyright © 2011-2012 Nokia Corporation\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); +} + +static void +oom (void) +{ + fprintf (stderr, "%s: out of memory\n", me); + exit (1); +} + +static void * +xmalloc (size_t bytes) +{ + void *ret; + + if (bytes == 0) + bytes = 1; + + ret = malloc (bytes); + + if (ret == NULL) + oom (); + + return ret; +} + +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]); + + 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) +{ + execvp (argv[prog_arg], argv + prog_arg); + + fprintf (stderr, "%s: failed to exec '%s': %s\n", me, argv[prog_arg], + strerror (errno)); + exit (1); +} + +int +main (int argc, char **argv) +{ + int prog_arg = 0; + int bus_address_pipe[2] = { 0, 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; + pid_t bus_pid; + pid_t app_pid; + char *envvar; + + 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"; + + if (pipe (bus_address_pipe) < 0) + { + fprintf (stderr, "%s: failed to create pipe: %s\n", me, strerror (errno)); + return 127; + } + + 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; + } + + close (bus_address_pipe[PIPE_READ_END]); + + envvar = xmalloc (strlen ("DBUS_SESSION_BUS_ADDRESS=") + + strlen (bus_address) + 1); + strcpy (envvar, "DBUS_SESSION_BUS_ADDRESS="); + strcat (envvar, bus_address); + putenv (envvar); + + 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; +} |