From df01c98cc7e83f3336e501fcf2eeee52c95464fb Mon Sep 17 00:00:00 2001 From: Havoc Pennington Date: Sun, 4 May 2003 08:54:24 +0000 Subject: 2003-05-04 Havoc Pennington * tools/dbus-launch.c: implement * bus/main.c (main), bus/bus.c (bus_context_new): implement --print-pid and --fork --- tools/Makefile.am | 3 +- tools/dbus-launch.1 | 13 +- tools/dbus-launch.c | 672 +++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 679 insertions(+), 9 deletions(-) (limited to 'tools') diff --git a/tools/Makefile.am b/tools/Makefile.am index c33caae7..60b9ddab 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -19,7 +19,8 @@ dbus_launch_SOURCES= \ dbus_send_LDADD= $(top_builddir)/dbus/libdbus-1.la dbus_monitor_LDADD= $(top_builddir)/glib/libdbus-glib-1.la -dbus_launch_LDADD= $(top_builddir)/dbus/libdbus-1.la +## dbus-launch doesn't link to anything +dbus_launch_LDADD= $(DBUS_X_LIBS) man_MANS = dbus-send.1 dbus-monitor.1 dbus-launch.1 EXTRA_DIST = $(man_MANS) diff --git a/tools/dbus-launch.1 b/tools/dbus-launch.1 index 9342423d..c0fb03f6 100644 --- a/tools/dbus-launch.1 +++ b/tools/dbus-launch.1 @@ -24,14 +24,19 @@ about D-BUS. See also the man page for \fIdbus-daemon-1\fP. .PP Here is an example of how to use \fIdbus-launch\fP with an -sh-compatible shell: +sh-compatible shell to start the per-session bus daemon: .nf - VARIABLES=`dbus-launch` - eval $VARIABLES - echo "D-BUS per-session daemon address is: $DBUS_SESSION_BUS_ADDRESS" + ## test for an existing bus daemon, just to be safe + if test -z "$DBUS_SESSION_BUS_ADDRESS" ; then + ## if not found, launch a new one + eval `dbus-launch --exit-with-session` + echo "D-BUS per-session daemon address is: $DBUS_SESSION_BUS_ADDRESS" + export DBUS_SESSION_BUS_ADDRESS + fi .fi +You might run something like that in your login scripts. .SH OPTIONS The following options are supported: diff --git a/tools/dbus-launch.c b/tools/dbus-launch.c index b21c7e9d..f0b9e589 100644 --- a/tools/dbus-launch.c +++ b/tools/dbus-launch.c @@ -21,7 +21,6 @@ * */ #include -#include #include #include #include @@ -30,10 +29,53 @@ #include #include #include +#include +#include #ifdef DBUS_BUILD_X11 #include #endif +#ifndef TRUE +#define TRUE (1) +#endif + +#ifndef FALSE +#define FALSE (0) +#endif + +#undef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +static void +verbose (const char *format, + ...) +{ + va_list args; + static int verbose = TRUE; + static int verbose_initted = FALSE; + + /* things are written a bit oddly here so that + * in the non-verbose case we just have the one + * conditional and return immediately. + */ + if (!verbose) + return; + + if (!verbose_initted) + { + verbose = getenv ("DBUS_VERBOSE") != NULL; + verbose_initted = TRUE; + if (!verbose) + return; + } + + fprintf (stderr, "%lu: ", (unsigned long) getpid ()); + + va_start (args, format); + vfprintf (stderr, format, args); + va_end (args); +} + static void usage (void) { @@ -52,13 +94,462 @@ version (void) exit (0); } +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 (TRUE) + { + size_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 ReadStatus +read_pid (int fd, + pid_t *buf) +{ + size_t bytes = 0; + ReadStatus retval; + + retval = READ_STATUS_OK; + + while (TRUE) + { + size_t chunk; + size_t to_read; + + again: + to_read = sizeof (pid_t) - bytes; + + if (to_read == 0) + break; + + chunk = read (fd, + ((char*)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; + } + + return retval; +} + +static void +do_write (int fd, const void *buf, size_t count) +{ + size_t bytes_written; + int ret; + + bytes_written = 0; + + again: + + ret = write (fd, ((const char*)buf) + bytes_written, count - bytes_written); + + if (ret < 0) + { + if (errno == EINTR) + goto again; + else + { + fprintf (stderr, "Failed to write data to pipe!\n"); + exit (1); /* give up, we suck */ + } + } + else + bytes_written += ret; + + if (bytes_written < count) + goto again; +} + +static void +write_pid (int fd, + pid_t pid) +{ + do_write (fd, &pid, sizeof (pid)); +} + +static int +do_waitpid (pid_t pid) +{ + int ret; + + again: + ret = waitpid (pid, NULL, 0); + + if (ret < 0 && + errno == EINTR) + goto again; + + return ret; +} + +static pid_t bus_pid_to_kill = -1; + +static void +kill_bus_and_exit (void) +{ + verbose ("Killing message bus and exiting babysitter\n"); + + /* in case these point to any NFS mounts, get rid of them immediately */ + close (0); + close (1); + close (2); + + kill (bus_pid_to_kill, SIGTERM); + sleep (3); + kill (bus_pid_to_kill, SIGKILL); + + exit (0); +} + +#ifdef DBUS_BUILD_X11 +static int +x_io_error_handler (Display *xdisplay) +{ + verbose ("X IO error\n"); + kill_bus_and_exit (); +} +#endif + +static int got_sighup = FALSE; + +static void +signal_handler (int sig) +{ + switch (sig) + { + case SIGHUP: + got_sighup = TRUE; + break; + } +} + +static void +kill_bus_when_session_ends (void) +{ + int tty_fd; + int x_fd; + fd_set read_set; + fd_set err_set; + int ret; + struct sigaction act; + sigset_t empty_mask; +#ifdef DBUS_BUILD_X11 + Display *xdisplay; +#endif + + /* install SIGHUP handler */ + got_sighup = FALSE; + sigemptyset (&empty_mask); + act.sa_handler = signal_handler; + act.sa_mask = empty_mask; + act.sa_flags = 0; + sigaction (SIGHUP, &act, 0); + +#ifdef DBUS_BUILD_X11 + xdisplay = XOpenDisplay (NULL); + if (xdisplay != NULL) + { + verbose ("Successfully opened X display\n"); + x_fd = ConnectionNumber (xdisplay); + XSetIOErrorHandler (x_io_error_handler); + } + else + x_fd = -1; +#else + verbose ("Compiled without X11 support\n"); + x_fd = -1; +#endif + + if (isatty (0)) + tty_fd = 0; + else + tty_fd = -1; + + if (tty_fd >= 0) + verbose ("stdin isatty(), monitoring it\n"); + else + verbose ("stdin was not a TTY, not monitoring it\n"); + + if (tty_fd < 0 && x_fd < 0) + { + fprintf (stderr, "No terminal on standard input and no X display; cannot attach message bus to session lifetime\n"); + exit (1); + } + + while (TRUE) + { + FD_ZERO (&read_set); + FD_ZERO (&err_set); + + if (tty_fd >= 0) + { + FD_SET (tty_fd, &read_set); + FD_SET (tty_fd, &err_set); + } + + if (x_fd >= 0) + { + FD_SET (x_fd, &read_set); + FD_SET (x_fd, &err_set); + } + + ret = select (MAX (tty_fd, x_fd) + 1, + &read_set, NULL, &err_set, NULL); + + if (got_sighup) + { + verbose ("Got SIGHUP, exiting\n"); + kill_bus_and_exit (); + } + +#ifdef DBUS_BUILD_X11 + /* Dump events on the floor, and let + * IO error handler run if we lose + * the X connection + */ + if (x_fd >= 0) + verbose ("X fd condition reading = %d error = %d\n", + FD_ISSET (x_fd, &read_set), + FD_ISSET (x_fd, &err_set)); + + if (xdisplay != NULL) + { + while (XPending (xdisplay)) + { + XEvent ignored; + XNextEvent (xdisplay, &ignored); + } + } +#endif + + if (tty_fd >= 0) + { + if (FD_ISSET (tty_fd, &read_set)) + { + int bytes_read; + char discard[512]; + + verbose ("TTY ready for reading\n"); + + bytes_read = read (tty_fd, discard, sizeof (discard)); + + verbose ("Read %d bytes from TTY errno = %d\n", + bytes_read, errno); + + if (bytes_read == 0) + kill_bus_and_exit (); /* EOF */ + else if (bytes_read < 0 && errno != EINTR) + { + /* 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 (); + } + } + else if (FD_ISSET (tty_fd, &err_set)) + { + verbose ("TTY has error condition\n"); + + kill_bus_and_exit (); + } + } + } +} + +static void +babysit (int exit_with_session, + pid_t child_pid, + int read_bus_pid_fd, /* read pid from here */ + int write_bus_pid_fd) /* forward pid to here */ +{ + int ret; +#define MAX_PID_LEN 64 + char buf[MAX_PID_LEN]; + long val; + char *end; + + /* 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,1,2 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) + { + fprintf (stderr, "Could not change to root directory: %s\n", + strerror (errno)); + exit (1); + } + + ret = fork (); + + if (ret < 0) + { + fprintf (stderr, "fork() failed in babysitter: %s\n", + strerror (errno)); + exit (1); + } + + if (ret > 0) + { + /* Parent reaps pre-fork part of bus daemon, then exits and is + * reaped so the babysitter isn't a zombie + */ + + verbose ("=== Babysitter's intermediate parent continues again\n"); + + if (do_waitpid (child_pid) < 0) + { + /* shouldn't happen */ + fprintf (stderr, "Failed waitpid() waiting for bus daemon's parent\n"); + exit (1); + } + + verbose ("Babysitter's intermediate parent exiting\n"); + + exit (0); + } + + /* Child continues */ + verbose ("=== Babysitter process created\n"); + + verbose ("Reading PID from daemon\n"); + /* Now read data */ + switch (read_line (read_bus_pid_fd, buf, MAX_PID_LEN)) + { + case READ_STATUS_OK: + break; + case READ_STATUS_EOF: + fprintf (stderr, "EOF reading PID from bus daemon\n"); + exit (1); + break; + case READ_STATUS_ERROR: + fprintf (stderr, "Error reading PID from bus daemon: %s\n", + strerror (errno)); + exit (1); + break; + } + + end = NULL; + val = strtol (buf, &end, 0); + if (buf == end || end == NULL) + { + fprintf (stderr, "Failed to parse bus PID \"%s\": %s\n", + buf, strerror (errno)); + exit (1); + } + + bus_pid_to_kill = val; + + verbose ("Got PID %ld from daemon\n", + (long) bus_pid_to_kill); + + /* Write data to launcher */ + write_pid (write_bus_pid_fd, bus_pid_to_kill); + close (write_bus_pid_fd); + + if (exit_with_session) + { + /* Bus is now started and launcher has needed info; + * we connect to X display and tty and wait to + * kill bus if requested. + */ + + kill_bus_when_session_ends (); + } + + verbose ("Babysitter exiting\n"); + + exit (0); +} + +#define READ_END 0 +#define WRITE_END 1 + int main (int argc, char **argv) { const char *prev_arg; - dbus_bool_t exit_with_session; + int exit_with_session; int i; - + int ret; + int bus_pid_to_launcher_pipe[2]; + int bus_pid_to_babysitter_pipe[2]; + int bus_address_to_launcher_pipe[2]; + exit_with_session = FALSE; prev_arg = NULL; @@ -82,8 +573,181 @@ main (int argc, char **argv) ++i; } + + verbose ("--exit-with-session provided\n"); + + if (pipe (bus_pid_to_launcher_pipe) < 0 || + pipe (bus_address_to_launcher_pipe) < 0) + { + fprintf (stderr, + "Failed to create pipe: %s\n", + strerror (errno)); + exit (1); + } + + bus_pid_to_babysitter_pipe[READ_END] = -1; + bus_pid_to_babysitter_pipe[WRITE_END] = -1; - + ret = fork (); + if (ret < 0) + { + fprintf (stderr, "Failed to fork: %s\n", + strerror (errno)); + exit (1); + } + + if (ret == 0) + { + /* Child */ +#define MAX_FD_LEN 64 + char write_pid_fd_as_string[MAX_FD_LEN]; + char write_address_fd_as_string[MAX_FD_LEN]; + + verbose ("=== Babysitter's intermediate parent created\n"); + + /* Fork once more to create babysitter */ + + if (pipe (bus_pid_to_babysitter_pipe) < 0) + { + fprintf (stderr, + "Failed to create pipe: %s\n", + strerror (errno)); + exit (1); + } + + ret = fork (); + if (ret < 0) + { + fprintf (stderr, "Failed to fork: %s\n", + strerror (errno)); + exit (1); + } + + if (ret > 0) + { + /* In babysitter */ + verbose ("=== Babysitter's intermediate parent continues\n"); + + close (bus_pid_to_launcher_pipe[READ_END]); + close (bus_address_to_launcher_pipe[READ_END]); + close (bus_address_to_launcher_pipe[WRITE_END]); + close (bus_pid_to_babysitter_pipe[WRITE_END]); + + /* babysit() will fork *again* + * and will also reap the pre-forked bus + * daemon + */ + babysit (exit_with_session, ret, + bus_pid_to_babysitter_pipe[READ_END], + bus_pid_to_launcher_pipe[WRITE_END]); + exit (0); + } + + verbose ("=== Bus exec process created\n"); + + /* Now we are the bus process (well, almost; + * dbus-daemon-1 itself forks again) + */ + close (bus_pid_to_launcher_pipe[READ_END]); + close (bus_address_to_launcher_pipe[READ_END]); + close (bus_pid_to_babysitter_pipe[READ_END]); + close (bus_pid_to_launcher_pipe[WRITE_END]); + + sprintf (write_pid_fd_as_string, + "%d", bus_pid_to_babysitter_pipe[WRITE_END]); + + sprintf (write_address_fd_as_string, + "%d", bus_address_to_launcher_pipe[WRITE_END]); + + verbose ("Calling exec()\n"); + + execlp ("dbus-daemon-1", + "dbus-daemon-1", + "--fork", + "--session", + "--print-pid", write_pid_fd_as_string, + "--print-address", write_address_fd_as_string, + NULL); + + fprintf (stderr, + "Failed to execute message bus daemon: %s\n", + strerror (errno)); + exit (1); + } + else + { + /* Parent */ +#define MAX_ADDR_LEN 512 + pid_t bus_pid; + char bus_address[MAX_ADDR_LEN]; + + verbose ("=== Parent dbus-launch continues\n"); + + close (bus_pid_to_launcher_pipe[WRITE_END]); + close (bus_address_to_launcher_pipe[WRITE_END]); + + verbose ("Waiting for babysitter's intermediate parent\n"); + + /* Immediately reap parent of babysitter + * (which was created just for us to reap) + */ + if (do_waitpid (ret) < 0) + { + fprintf (stderr, "Failed to waitpid() for babysitter intermediate process: %s\n", + strerror (errno)); + exit (1); + } + + verbose ("Reading address from bus\n"); + + /* Read the pipe data, print, and exit */ + switch (read_line (bus_address_to_launcher_pipe[READ_END], + bus_address, MAX_ADDR_LEN)) + { + case READ_STATUS_OK: + break; + case READ_STATUS_EOF: + fprintf (stderr, "EOF in dbus-launch reading address from bus daemon\n"); + exit (1); + break; + case READ_STATUS_ERROR: + fprintf (stderr, "Error in dbus-launch reading address from bus daemon: %s\n", + strerror (errno)); + exit (1); + break; + } + + close (bus_address_to_launcher_pipe[READ_END]); + + verbose ("Reading PID from babysitter\n"); + + switch (read_pid (bus_pid_to_launcher_pipe[READ_END], &bus_pid)) + { + case READ_STATUS_OK: + break; + case READ_STATUS_EOF: + fprintf (stderr, "EOF in dbus-launch reading address from bus daemon\n"); + exit (1); + break; + case READ_STATUS_ERROR: + fprintf (stderr, "Error in dbus-launch reading address from bus daemon: %s\n", + strerror (errno)); + exit (1); + break; + } + + close (bus_pid_to_launcher_pipe[READ_END]); + + printf ("DBUS_SESSION_BUS_ADDRESS='%s'\n", + bus_address); + + printf ("DBUS_SESSION_BUS_PID=%ld\n", + (long) bus_pid); + + verbose ("dbus-launch exiting\n"); + + exit (0); + } return 0; } -- cgit