/* -*- mode: C; c-file-style: "gnu" -*- */ /* dbus-launch.c dbus-launch utility * * Copyright (C) 2003, 2006 Red Hat, Inc. * Copyright (C) 2006 Thiago Macieira <thiago@kde.org> * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include "dbus-launch.h" #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <signal.h> #include <sys/wait.h> #include <errno.h> #include <stdio.h> #include <string.h> #include <signal.h> #include <stdarg.h> #include <sys/select.h> #include <time.h> #ifdef DBUS_BUILD_X11 #include <X11/Xlib.h> extern Display *xdisplay; #endif static char* machine_uuid = NULL; const char* get_machine_uuid (void) { return machine_uuid; } static void save_machine_uuid (const char *uuid_arg) { if (strlen (uuid_arg) != 32) { fprintf (stderr, "machine ID '%s' looks like it's the wrong length, should be 32 hex digits", uuid_arg); exit (1); } machine_uuid = xstrdup (uuid_arg); } 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 (int ecode) { fprintf (stderr, "dbus-launch [--version] [--help] [--sh-syntax] [--csh-syntax] [--auto-syntax] [--exit-with-session]\n"); exit (ecode); } static void version (void) { printf ("D-Bus Message Bus Launcher %s\n" "Copyright (C) 2003 Red Hat, Inc.\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", VERSION); exit (0); } char * xstrdup (const char *str) { int len; char *copy; if (str == NULL) return NULL; len = strlen (str); copy = malloc (len + 1); if (copy == NULL) return NULL; memcpy (copy, str, len + 1); return copy; } 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) { 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 ReadStatus read_pid (int fd, pid_t *buf) { size_t bytes = 0; ReadStatus retval; retval = READ_STATUS_OK; while (TRUE) { ssize_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! %s\n", strerror (errno)); 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; void kill_bus_and_exit (int exitcode) { 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 (exitcode); } static void print_variables (const char *bus_address, pid_t bus_pid, long bus_wid, int c_shell_syntax, int bourne_shell_syntax, int binary_syntax) { if (binary_syntax) { write (1, bus_address, strlen (bus_address) + 1); write (1, &bus_pid, sizeof bus_pid); write (1, &bus_wid, sizeof bus_wid); return; } else if (c_shell_syntax) { printf ("setenv DBUS_SESSION_BUS_ADDRESS '%s';\n", bus_address); printf ("set DBUS_SESSION_BUS_PID=%ld;\n", (long) bus_pid); if (bus_wid) printf ("set DBUS_SESSION_BUS_WINDOWID=%ld;\n", (long) bus_wid); fflush (stdout); } else if (bourne_shell_syntax) { printf ("DBUS_SESSION_BUS_ADDRESS='%s';\n", bus_address); if (bourne_shell_syntax) printf ("export DBUS_SESSION_BUS_ADDRESS;\n"); printf ("DBUS_SESSION_BUS_PID=%ld;\n", (long) bus_pid); if (bus_wid) printf ("DBUS_SESSION_BUS_WINDOWID=%ld;\n", (long) bus_wid); fflush (stdout); } else { printf ("DBUS_SESSION_BUS_ADDRESS=%s\n", bus_address); printf ("DBUS_SESSION_BUS_PID=%ld\n", (long) bus_pid); if (bus_wid) printf ("DBUS_SESSION_BUS_WINDOWID=%ld\n", (long) bus_wid); fflush (stdout); } } static int got_sighup = FALSE; static void signal_handler (int sig) { switch (sig) { case SIGHUP: case SIGTERM: 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; struct sigaction act; sigset_t empty_mask; /* 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, NULL); sigaction (SIGTERM, &act, NULL); #ifdef DBUS_BUILD_X11 x11_init(); if (xdisplay != NULL) { x_fd = ConnectionNumber (xdisplay); } else x_fd = -1; #else 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); } 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 (0); } #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)); x11_handle_event (); #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 (0); /* 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 (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. */ if (chdir ("/") < 0) { fprintf (stderr, "Could not change to root directory: %s\n", 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); } 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", 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 bus\n"); switch (read_pid (read_bus_pid_fd, &bus_pid_to_kill)) { case READ_STATUS_OK: break; case READ_STATUS_EOF: fprintf (stderr, "EOF in dbus-launch reading PID from bus daemon\n"); exit (1); break; case READ_STATUS_ERROR: fprintf (stderr, "Error in dbus-launch reading PID from bus daemon: %s\n", strerror (errno)); exit (1); break; } verbose ("Got PID %ld from daemon\n", (long) bus_pid_to_kill); 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; const char *shname; const char *runprog = NULL; int remaining_args = 0; int exit_with_session; int binary_syntax = FALSE; int c_shell_syntax = FALSE; int bourne_shell_syntax = FALSE; int auto_shell_syntax = FALSE; int autolaunch = FALSE; int requires_arg = FALSE; 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]; char *config_file; exit_with_session = FALSE; config_file = NULL; prev_arg = NULL; i = 1; while (i < argc) { const char *arg = argv[i]; if (strcmp (arg, "--help") == 0 || strcmp (arg, "-h") == 0 || strcmp (arg, "-?") == 0) usage (0); else if (strcmp (arg, "--auto-syntax") == 0) auto_shell_syntax = TRUE; else if (strcmp (arg, "-c") == 0 || strcmp (arg, "--csh-syntax") == 0) c_shell_syntax = TRUE; else if (strcmp (arg, "-s") == 0 || strcmp (arg, "--sh-syntax") == 0) bourne_shell_syntax = TRUE; else if (strcmp (arg, "--binary-syntax") == 0) binary_syntax = TRUE; else if (strcmp (arg, "--version") == 0) version (); else if (strcmp (arg, "--exit-with-session") == 0) exit_with_session = TRUE; else if (strstr (arg, "--autolaunch=") == arg) { const char *s; if (autolaunch) { fprintf (stderr, "--autolaunch given twice\n"); exit (1); } autolaunch = TRUE; s = strchr (arg, '='); ++s; save_machine_uuid (s); } else if (prev_arg && strcmp (prev_arg, "--autolaunch") == 0) { if (autolaunch) { fprintf (stderr, "--autolaunch given twice\n"); exit (1); } autolaunch = TRUE; save_machine_uuid (arg); requires_arg = FALSE; } else if (strcmp (arg, "--autolaunch") == 0) requires_arg = TRUE; else if (strstr (arg, "--config-file=") == arg) { const char *file; if (config_file != NULL) { fprintf (stderr, "--config-file given twice\n"); exit (1); } file = strchr (arg, '='); ++file; config_file = xstrdup (file); } else if (prev_arg && strcmp (prev_arg, "--config-file") == 0) { if (config_file != NULL) { fprintf (stderr, "--config-file given twice\n"); exit (1); } config_file = xstrdup (arg); requires_arg = FALSE; } else if (strcmp (arg, "--config-file") == 0) requires_arg = TRUE; else if (arg[0] == '-') { if (strcmp (arg, "--") != 0) { fprintf (stderr, "Option `%s' is unknown.\n", arg); exit (1); } else { runprog = argv[i+1]; remaining_args = i+2; break; } } else { runprog = arg; remaining_args = i+1; break; } prev_arg = arg; ++i; } if (requires_arg) { fprintf (stderr, "Option `%s' requires an argument.\n", prev_arg); exit (1); } if (auto_shell_syntax) { if ((shname = getenv ("SHELL")) != NULL) { if (!strncmp (shname + strlen (shname) - 3, "csh", 3)) c_shell_syntax = TRUE; else bourne_shell_syntax = TRUE; } else bourne_shell_syntax = TRUE; } if (exit_with_session) verbose ("--exit-with-session enabled\n"); if (autolaunch) { #ifndef DBUS_BUILD_X11 fprintf (stderr, "Autolaunch requested, but X11 support not compiled in.\n" "Cannot continue.\n"); exit (1); #else char *address; pid_t pid; long wid; if (get_machine_uuid () == NULL) { fprintf (stderr, "Machine UUID not provided as arg to --autolaunch\n"); exit (1); } /* FIXME right now autolaunch always does print_variables(), but it should really * exec the child program instead if a child program was specified. For now * we just exit if this conflict arises. */ if (runprog) { fprintf (stderr, "Currently --autolaunch does not support running a program\n"); exit (1); } verbose ("Autolaunch enabled (using X11).\n"); if (!exit_with_session) { verbose ("--exit-with-session automatically enabled\n"); exit_with_session = TRUE; } if (!x11_init ()) { fprintf (stderr, "Autolaunch error: X11 initialization failed.\n"); exit (1); } if (!x11_get_address (&address, &pid, &wid)) { fprintf (stderr, "Autolaunch error: X11 communication error.\n"); exit (1); } if (address != NULL) { verbose ("dbus-daemon is already running. Returning existing parameters.\n"); print_variables (address, pid, wid, c_shell_syntax, bourne_shell_syntax, binary_syntax); exit (0); } #endif } if (pipe (bus_pid_to_launcher_pipe) < 0 || pipe (bus_address_to_launcher_pipe) < 0 || 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) { /* 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 */ 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_pid_to_launcher_pipe[WRITE_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]); exit (0); } verbose ("=== Bus exec process created\n"); /* Now we are the bus process (well, almost; * dbus-daemon 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_babysitter_pipe[WRITE_END]); sprintf (write_pid_fd_as_string, "%d", bus_pid_to_launcher_pipe[WRITE_END]); sprintf (write_address_fd_as_string, "%d", bus_address_to_launcher_pipe[WRITE_END]); verbose ("Calling exec()\n"); #ifdef DBUS_BUILD_TESTS /* exec from testdir */ if (getenv("DBUS_USE_TEST_BINARY") != NULL) { execl (TEST_BUS_BINARY, TEST_BUS_BINARY, "--fork", "--print-pid", write_pid_fd_as_string, "--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, "Failed to execute test message bus daemon %s: %s. Will try again with the system path.\n", TEST_BUS_BINARY, strerror (errno)); } #endif /* DBUS_BUILD_TESTS */ execl (DBUS_DAEMONDIR"/dbus-daemon", DBUS_DAEMONDIR"/dbus-daemon", "--fork", "--print-pid", write_pid_fd_as_string, "--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, "Failed to execute message bus daemon %s: %s. Will try again without full path.\n", DBUS_DAEMONDIR"/dbus-daemon", strerror (errno)); /* * If it failed, try running without full PATH. Note this is needed * because the build process builds the run-with-tmp-session-bus.conf * file and the dbus-daemon will not be in the install location during * build time. */ execlp ("dbus-daemon", "dbus-daemon", "--fork", "--print-pid", write_pid_fd_as_string, "--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, "Failed to execute message bus daemon: %s\n", strerror (errno)); exit (1); } else { /* Parent */ #define MAX_PID_LEN 64 pid_t bus_pid; char bus_address[MAX_ADDR_LEN]; char buf[MAX_PID_LEN]; char *end; long wid = 0; long val; int ret2; verbose ("=== Parent dbus-launch continues\n"); close (bus_pid_to_launcher_pipe[WRITE_END]); close (bus_address_to_launcher_pipe[WRITE_END]); close (bus_pid_to_babysitter_pipe[READ_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 daemon\n"); /* Now read data */ switch (read_line (bus_pid_to_launcher_pipe[READ_END], 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 = val; close (bus_pid_to_launcher_pipe[READ_END]); #ifdef DBUS_BUILD_X11 /* FIXME the runprog == NULL is broken - we need to launch the runprog with the existing bus, * instead of just doing print_variables() if there's an existing bus. */ if (xdisplay != NULL && runprog == NULL) { ret2 = x11_save_address (bus_address, bus_pid, &wid); if (ret2 == 0) { /* another window got added. Return its address */ char *address; pid_t pid; long wid; if (x11_get_address (&address, &pid, &wid) && address != NULL) { verbose ("dbus-daemon is already running. Returning existing parameters.\n"); print_variables (address, pid, wid, c_shell_syntax, bourne_shell_syntax, binary_syntax); free (address); bus_pid_to_kill = bus_pid; kill_bus_and_exit (0); } /* if failed, fall through */ } if (ret2 <= 0) { fprintf (stderr, "Error saving bus information.\n"); bus_pid_to_kill = bus_pid; kill_bus_and_exit (1); } } #endif /* Forward the pid to the babysitter */ write_pid (bus_pid_to_babysitter_pipe[WRITE_END], bus_pid); close (bus_pid_to_babysitter_pipe[WRITE_END]); if (runprog) { char *envvar; char **args; envvar = malloc (strlen ("DBUS_SESSION_BUS_ADDRESS=") + strlen (bus_address) + 1); args = malloc (sizeof (char *) * ((argc-remaining_args)+2)); if (envvar == NULL || args == NULL) goto oom; args[0] = xstrdup (runprog); if (!args[0]) goto oom; for (i = 1; i <= (argc-remaining_args); i++) { size_t len = strlen (argv[remaining_args+i-1])+1; args[i] = malloc (len); if (!args[i]) goto oom; strncpy (args[i], argv[remaining_args+i-1], len); } args[i] = NULL; strcpy (envvar, "DBUS_SESSION_BUS_ADDRESS="); strcat (envvar, bus_address); putenv (envvar); execvp (runprog, args); fprintf (stderr, "Couldn't exec %s: %s\n", runprog, strerror (errno)); exit (1); } else { print_variables (bus_address, bus_pid, wid, c_shell_syntax, bourne_shell_syntax, binary_syntax); } verbose ("dbus-launch exiting\n"); fflush (stdout); fflush (stderr); close (1); close (2); exit (0); } return 0; oom: fprintf (stderr, "Out of memory!"); exit (1); }