diff options
Diffstat (limited to 'dbus/dbus-spawn.c')
-rw-r--r-- | dbus/dbus-spawn.c | 168 |
1 files changed, 138 insertions, 30 deletions
diff --git a/dbus/dbus-spawn.c b/dbus/dbus-spawn.c index ef00801c..b95cad6e 100644 --- a/dbus/dbus-spawn.c +++ b/dbus/dbus-spawn.c @@ -38,6 +38,12 @@ #ifdef HAVE_ERRNO_H #include <errno.h> #endif +#ifdef HAVE_SYSTEMD +#ifdef HAVE_SYSLOG_H +#include <syslog.h> +#endif +#include <systemd/sd-journal.h> +#endif extern char **environ; @@ -174,6 +180,48 @@ read_pid (int fd, * and the grandchild. The grandchild is our spawned process. The intermediate * child is a babysitter process; it keeps track of when the grandchild * exits/crashes, and reaps the grandchild. + * + * We automatically reap the babysitter process, killing it if necessary, + * when the DBusBabysitter's refcount goes to zero. + * + * Processes: + * + * main process + * | fork() A + * \- babysitter + * | fork () B + * \- grandchild --> exec --> spawned process + * + * IPC: + * child_err_report_pipe + * /-----------<---------<--------------\ + * | ^ + * v | + * main process babysitter grandchild + * ^ ^ + * v v + * \-------<->-------/ + * babysitter_pipe + * + * child_err_report_pipe is genuinely a pipe. + * The READ_END (also called error_pipe_from_child) is used in the main + * process. The WRITE_END (also called child_err_report_fd) is used in + * the grandchild process. + * + * On failure, the grandchild process sends CHILD_EXEC_FAILED + errno. + * On success, the pipe just closes (because it's close-on-exec) without + * sending any bytes. + * + * babysitter_pipe is mis-named: it's really a bidirectional socketpair. + * The [0] end (also called socket_to_babysitter) is used in the main + * process, the [1] end (also called parent_pipe) is used in the babysitter. + * + * If the fork() labelled B in the diagram above fails, the babysitter sends + * CHILD_FORK_FAILED + errno. + * On success, the babysitter sends CHILD_PID + the grandchild's pid. + * On SIGCHLD, the babysitter sends CHILD_EXITED + the exit status. + * The main process doesn't explicitly send anything, but when it exits, + * the babysitter gets POLLHUP or POLLERR. */ /* Messages from children to parents */ @@ -192,7 +240,8 @@ struct DBusBabysitter { int refcount; /**< Reference count */ - char *executable; /**< executable name to use in error messages */ + char *log_name; /**< the name under which to log messages about this + process being spawned */ int socket_to_babysitter; /**< Connection to the babysitter process */ int error_pipe_from_child; /**< Connection to the process that does the exec() */ @@ -308,15 +357,18 @@ _dbus_babysitter_unref (DBusBabysitter *sitter) if (ret == 0) kill (sitter->sitter_pid, SIGKILL); - again: if (ret == 0) - ret = waitpid (sitter->sitter_pid, &status, 0); + { + do + { + ret = waitpid (sitter->sitter_pid, &status, 0); + } + while (_DBUS_UNLIKELY (ret < 0 && errno == EINTR)); + } if (ret < 0) { - if (errno == EINTR) - goto again; - else if (errno == ECHILD) + if (errno == ECHILD) _dbus_warn ("Babysitter process not available to be reaped; should not happen\n"); else _dbus_warn ("Unexpected error %d in waitpid() for babysitter: %s\n", @@ -343,7 +395,7 @@ _dbus_babysitter_unref (DBusBabysitter *sitter) if (sitter->watches) _dbus_watch_list_free (sitter->watches); - dbus_free (sitter->executable); + dbus_free (sitter->log_name); dbus_free (sitter); } @@ -698,34 +750,34 @@ _dbus_babysitter_set_child_exit_error (DBusBabysitter *sitter, { dbus_set_error (error, DBUS_ERROR_SPAWN_EXEC_FAILED, "Failed to execute program %s: %s", - sitter->executable, _dbus_strerror (sitter->errnum)); + sitter->log_name, _dbus_strerror (sitter->errnum)); } else if (sitter->have_fork_errnum) { dbus_set_error (error, DBUS_ERROR_NO_MEMORY, "Failed to fork a new process %s: %s", - sitter->executable, _dbus_strerror (sitter->errnum)); + sitter->log_name, _dbus_strerror (sitter->errnum)); } else if (sitter->have_child_status) { if (WIFEXITED (sitter->status)) dbus_set_error (error, DBUS_ERROR_SPAWN_CHILD_EXITED, "Process %s exited with status %d", - sitter->executable, WEXITSTATUS (sitter->status)); + sitter->log_name, WEXITSTATUS (sitter->status)); else if (WIFSIGNALED (sitter->status)) dbus_set_error (error, DBUS_ERROR_SPAWN_CHILD_SIGNALED, "Process %s received signal %d", - sitter->executable, WTERMSIG (sitter->status)); + sitter->log_name, WTERMSIG (sitter->status)); else dbus_set_error (error, DBUS_ERROR_FAILED, "Process %s exited abnormally", - sitter->executable); + sitter->log_name); } else { dbus_set_error (error, DBUS_ERROR_FAILED, "Process %s exited, reason unknown", - sitter->executable); + sitter->log_name); } } @@ -807,9 +859,14 @@ handle_watch (DBusWatch *watch, #define WRITE_END 1 -/* Avoids a danger in threaded situations (calling close() - * on a file descriptor twice, and another thread has - * re-opened it since the first close) +/* Avoids a danger in re-entrant situations (calling close() + * on a file descriptor twice, and another module has + * re-opened it since the first close). + * + * This previously claimed to be relevant for threaded situations, but by + * trivial inspection, it is not thread-safe. It doesn't actually + * matter, since this module is only used in the -util variant of the + * library, which is only used in single-threaded situations. */ static int close_and_invalidate (int *fd) @@ -936,7 +993,7 @@ do_exec (int child_err_report_fd, DBusSpawnChildSetupFunc child_setup, void *user_data) { -#ifdef DBUS_BUILD_TESTS +#ifdef DBUS_ENABLE_EMBEDDED_TESTS int i, max_open; #endif @@ -947,7 +1004,7 @@ do_exec (int child_err_report_fd, if (child_setup) (* child_setup) (user_data); -#ifdef DBUS_BUILD_TESTS +#ifdef DBUS_ENABLE_EMBEDDED_TESTS max_open = sysconf (_SC_OPEN_MAX); for (i = 3; i < max_open; i++) @@ -1115,8 +1172,7 @@ babysit (pid_t grandchild_pid, } /** - * Spawns a new process. The executable name and argv[0] - * are the same, both are provided in argv[0]. The child_setup + * Spawns a new process. The child_setup * function is passed the given user_data and is run in the child * just before calling exec(). * @@ -1126,8 +1182,9 @@ babysit (pid_t grandchild_pid, * If sitter_p is #NULL, no babysitter is kept. * * @param sitter_p return location for babysitter or #NULL + * @param log_name the name under which to log messages about this process being spawned * @param argv the executable and arguments - * @param env the environment (not used on unix yet) + * @param env the environment, or #NULL to copy the parent's * @param child_setup function to call in child pre-exec() * @param user_data user data for setup function * @param error error object to be filled in if function fails @@ -1135,6 +1192,7 @@ babysit (pid_t grandchild_pid, */ dbus_bool_t _dbus_spawn_async_with_babysitter (DBusBabysitter **sitter_p, + const char *log_name, char **argv, char **env, DBusSpawnChildSetupFunc child_setup, @@ -1145,8 +1203,13 @@ _dbus_spawn_async_with_babysitter (DBusBabysitter **sitter_p, int child_err_report_pipe[2] = { -1, -1 }; int babysitter_pipe[2] = { -1, -1 }; pid_t pid; +#ifdef HAVE_SYSTEMD + int fd_out = -1; + int fd_err = -1; +#endif _DBUS_ASSERT_ERROR_IS_CLEAR (error); + _dbus_assert (argv[0] != NULL); if (sitter_p != NULL) *sitter_p = NULL; @@ -1160,8 +1223,17 @@ _dbus_spawn_async_with_babysitter (DBusBabysitter **sitter_p, return FALSE; } - sitter->executable = _dbus_strdup (argv[0]); - if (sitter->executable == NULL) + sitter->log_name = _dbus_strdup (log_name); + if (sitter->log_name == NULL && log_name != NULL) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto cleanup_and_fail; + } + + if (sitter->log_name == NULL) + sitter->log_name = _dbus_strdup (argv[0]); + + if (sitter->log_name == NULL) { dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); goto cleanup_and_fail; @@ -1221,7 +1293,16 @@ _dbus_spawn_async_with_babysitter (DBusBabysitter **sitter_p, } _DBUS_ASSERT_ERROR_IS_CLEAR (error); - + +#ifdef HAVE_SYSTEMD + /* This may fail, but it's not critical. + * In particular, if we were compiled with journald support but are now + * running on a non-systemd system, this is going to fail, so we + * have to cope gracefully. */ + fd_out = sd_journal_stream_fd (sitter->log_name, LOG_INFO, FALSE); + fd_err = sd_journal_stream_fd (sitter->log_name, LOG_WARNING, FALSE); +#endif + pid = fork (); if (pid < 0) @@ -1256,7 +1337,21 @@ _dbus_spawn_async_with_babysitter (DBusBabysitter **sitter_p, _dbus_assert_not_reached ("Got to code after write_err_and_exit()"); } else if (grandchild_pid == 0) - { + { + /* Go back to ignoring SIGPIPE, since it's evil + */ + signal (SIGPIPE, SIG_IGN); + + close_and_invalidate (&babysitter_pipe[1]); +#ifdef HAVE_SYSTEMD + /* log to systemd journal if possible */ + if (fd_out >= 0) + dup2 (fd_out, STDOUT_FILENO); + if (fd_err >= 0) + dup2 (fd_err, STDERR_FILENO); + close_and_invalidate (&fd_out); + close_and_invalidate (&fd_err); +#endif do_exec (child_err_report_pipe[WRITE_END], argv, env, @@ -1265,6 +1360,11 @@ _dbus_spawn_async_with_babysitter (DBusBabysitter **sitter_p, } else { + close_and_invalidate (&child_err_report_pipe[WRITE_END]); +#ifdef HAVE_SYSTEMD + close_and_invalidate (&fd_out); + close_and_invalidate (&fd_err); +#endif babysit (grandchild_pid, babysitter_pipe[1]); _dbus_assert_not_reached ("Got to code after babysit()"); } @@ -1274,6 +1374,10 @@ _dbus_spawn_async_with_babysitter (DBusBabysitter **sitter_p, /* Close the uncared-about ends of the pipes */ close_and_invalidate (&child_err_report_pipe[WRITE_END]); close_and_invalidate (&babysitter_pipe[1]); +#ifdef HAVE_SYSTEMD + close_and_invalidate (&fd_out); + close_and_invalidate (&fd_err); +#endif sitter->socket_to_babysitter = babysitter_pipe[0]; babysitter_pipe[0] = -1; @@ -1303,6 +1407,10 @@ _dbus_spawn_async_with_babysitter (DBusBabysitter **sitter_p, close_and_invalidate (&child_err_report_pipe[WRITE_END]); close_and_invalidate (&babysitter_pipe[0]); close_and_invalidate (&babysitter_pipe[1]); +#ifdef HAVE_SYSTEMD + close_and_invalidate (&fd_out); + close_and_invalidate (&fd_err); +#endif if (sitter != NULL) _dbus_babysitter_unref (sitter); @@ -1321,7 +1429,7 @@ _dbus_babysitter_set_result_function (DBusBabysitter *sitter, /** @} */ -#ifdef DBUS_BUILD_TESTS +#ifdef DBUS_ENABLE_EMBEDDED_TESTS static char * get_test_exec (const char *exe, @@ -1364,7 +1472,7 @@ check_spawn_nonexistent (void *data) /*** Test launching nonexistent binary */ argv[0] = "/this/does/not/exist/32542sdgafgafdg"; - if (_dbus_spawn_async_with_babysitter (&sitter, argv, + if (_dbus_spawn_async_with_babysitter (&sitter, "spawn_nonexistent", argv, NULL, NULL, NULL, &error)) { @@ -1413,7 +1521,7 @@ check_spawn_segfault (void *data) return TRUE; } - if (_dbus_spawn_async_with_babysitter (&sitter, argv, + if (_dbus_spawn_async_with_babysitter (&sitter, "spawn_segfault", argv, NULL, NULL, NULL, &error)) { @@ -1464,7 +1572,7 @@ check_spawn_exit (void *data) return TRUE; } - if (_dbus_spawn_async_with_babysitter (&sitter, argv, + if (_dbus_spawn_async_with_babysitter (&sitter, "spawn_exit", argv, NULL, NULL, NULL, &error)) { @@ -1515,7 +1623,7 @@ check_spawn_and_kill (void *data) return TRUE; } - if (_dbus_spawn_async_with_babysitter (&sitter, argv, + if (_dbus_spawn_async_with_babysitter (&sitter, "spawn_and_kill", argv, NULL, NULL, NULL, &error)) { |