summaryrefslogtreecommitdiff
path: root/dbus/dbus-spawn.c
diff options
context:
space:
mode:
Diffstat (limited to 'dbus/dbus-spawn.c')
-rw-r--r--dbus/dbus-spawn.c168
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))
{