summaryrefslogtreecommitdiffstats
path: root/dbus/dbus-spawn.c
diff options
context:
space:
mode:
authorHavoc Pennington <hp@redhat.com>2003-04-04 01:55:28 +0000
committerHavoc Pennington <hp@redhat.com>2003-04-04 01:55:28 +0000
commit45d1479fad0fb55f1775c394e696643dad3e8e4d (patch)
tree3697b7dc26dde5a2aa25cab5a3d719d917d9a76b /dbus/dbus-spawn.c
parent1b08036103a70159e7a67b2349306710edcd6654 (diff)
2003-04-03 Havoc Pennington <hp@pobox.com>
* dbus/dbus-spawn.c: Move dbus-spawn into a separate file in preparation for modifying it, dbus-sysdeps is getting a bit unmanageable.
Diffstat (limited to 'dbus/dbus-spawn.c')
-rw-r--r--dbus/dbus-spawn.c339
1 files changed, 339 insertions, 0 deletions
diff --git a/dbus/dbus-spawn.c b/dbus/dbus-spawn.c
new file mode 100644
index 00000000..27fb6e6c
--- /dev/null
+++ b/dbus/dbus-spawn.c
@@ -0,0 +1,339 @@
+/* -*- mode: C; c-file-style: "gnu" -*- */
+/* dbus-spawn.c Wrapper around fork/exec
+ *
+ * Copyright (C) 2002, 2003 Red Hat, Inc.
+ * Copyright (C) 2003 CodeFactory AB
+ *
+ * Licensed under the Academic Free License version 1.2
+ *
+ * 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-spawn.h"
+#include "dbus-sysdeps.h"
+#include "dbus-internals.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <errno.h>
+
+/**
+ * @addtogroup DBusInternalsUtils
+ * @{
+ */
+
+/* Avoids a danger in threaded situations (calling close()
+ * on a file descriptor twice, and another thread has
+ * re-opened it since the first close)
+ */
+static int
+close_and_invalidate (int *fd)
+{
+ int ret;
+
+ if (*fd < 0)
+ return -1;
+ else
+ {
+ ret = close (*fd);
+ *fd = -1;
+ }
+
+ return ret;
+}
+
+static dbus_bool_t
+make_pipe (int p[2],
+ DBusError *error)
+{
+ _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+
+ if (pipe (p) < 0)
+ {
+ dbus_set_error (error,
+ DBUS_ERROR_SPAWN_FAILED,
+ "Failed to create pipe for communicating with child process (%s)",
+ _dbus_errno_to_string (errno));
+ return FALSE;
+ }
+ else
+ {
+ _dbus_fd_set_close_on_exec (p[0]);
+ _dbus_fd_set_close_on_exec (p[1]);
+ return TRUE;
+ }
+}
+
+enum
+{
+ CHILD_CHDIR_FAILED,
+ CHILD_EXEC_FAILED,
+ CHILD_DUP2_FAILED,
+ CHILD_FORK_FAILED
+};
+
+static void
+write_err_and_exit (int fd, int msg)
+{
+ int en = errno;
+
+ write (fd, &msg, sizeof(msg));
+ write (fd, &en, sizeof(en));
+
+ _exit (1);
+}
+
+static dbus_bool_t
+read_ints (int fd,
+ int *buf,
+ int n_ints_in_buf,
+ int *n_ints_read,
+ DBusError *error)
+{
+ size_t bytes = 0;
+
+ _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+
+ while (TRUE)
+ {
+ size_t chunk;
+
+ if (bytes >= sizeof(int)*2)
+ break; /* give up, who knows what happened, should not be
+ * possible.
+ */
+
+ again:
+ chunk = read (fd,
+ ((char*)buf) + bytes,
+ sizeof(int) * n_ints_in_buf - bytes);
+ if (chunk < 0 && errno == EINTR)
+ goto again;
+
+ if (chunk < 0)
+ {
+ /* Some weird shit happened, bail out */
+
+ dbus_set_error (error,
+ DBUS_ERROR_SPAWN_FAILED,
+ "Failed to read from child pipe (%s)",
+ _dbus_errno_to_string (errno));
+
+ return FALSE;
+ }
+ else if (chunk == 0)
+ break; /* EOF */
+ else /* chunk > 0 */
+ bytes += chunk;
+ }
+
+ *n_ints_read = (int)(bytes / sizeof(int));
+
+ return TRUE;
+}
+
+static void
+do_exec (int child_err_report_fd,
+ char **argv,
+ DBusSpawnChildSetupFunc child_setup,
+ void *user_data)
+{
+#ifdef DBUS_BUILD_TESTS
+ int i, max_open;
+#endif
+
+ if (child_setup)
+ (* child_setup) (user_data);
+
+#ifdef DBUS_BUILD_TESTS
+ max_open = sysconf (_SC_OPEN_MAX);
+
+ for (i = 3; i < max_open; i++)
+ {
+ int retval;
+
+ retval = fcntl (i, F_GETFD);
+
+ if (retval != -1 && !(retval & FD_CLOEXEC))
+ _dbus_warn ("Fd %d did not have the close-on-exec flag set!\n", i);
+ }
+#endif
+
+ execv (argv[0], argv);
+
+ /* Exec failed */
+ write_err_and_exit (child_err_report_fd,
+ CHILD_EXEC_FAILED);
+
+}
+
+/**
+ * Spawns a new process. The executable name and argv[0]
+ * are the same, both are provided in argv[0]. The child_setup
+ * function is passed the given user_data and is run in the child
+ * just before calling exec().
+ *
+ * @todo this code should be reviewed/double-checked as it's fairly
+ * complex and no one has reviewed it yet.
+ *
+ * @param argv the executable and arguments
+ * @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
+ * @returns #TRUE on success, #FALSE if error is filled in
+ */
+dbus_bool_t
+_dbus_spawn_async (char **argv,
+ DBusSpawnChildSetupFunc child_setup,
+ void *user_data,
+ DBusError *error)
+{
+ int pid = -1, grandchild_pid;
+ int child_err_report_pipe[2] = { -1, -1 };
+ int status;
+
+ _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+
+ if (!make_pipe (child_err_report_pipe, error))
+ return FALSE;
+
+ pid = fork ();
+
+ if (pid < 0)
+ {
+ dbus_set_error (error,
+ DBUS_ERROR_SPAWN_FORK_FAILED,
+ "Failed to fork (%s)",
+ _dbus_errno_to_string (errno));
+ return FALSE;
+ }
+ else if (pid == 0)
+ {
+ /* Immediate child. */
+
+ /* Be sure we crash if the parent exits
+ * and we write to the err_report_pipe
+ */
+ signal (SIGPIPE, SIG_DFL);
+
+ /* Close the parent's end of the pipes;
+ * not needed in the close_descriptors case,
+ * though
+ */
+ close_and_invalidate (&child_err_report_pipe[0]);
+
+ /* We need to fork an intermediate child that launches the
+ * final child. The purpose of the intermediate child
+ * is to exit, so we can waitpid() it immediately.
+ * Then the grandchild will not become a zombie.
+ */
+ grandchild_pid = fork ();
+
+ if (grandchild_pid < 0)
+ {
+ write_err_and_exit (child_err_report_pipe[1],
+ CHILD_FORK_FAILED);
+ }
+ else if (grandchild_pid == 0)
+ {
+ do_exec (child_err_report_pipe[1],
+ argv,
+ child_setup, user_data);
+ }
+ else
+ {
+ _exit (0);
+ }
+ }
+ else
+ {
+ /* Parent */
+
+ int buf[2];
+ int n_ints = 0;
+
+ /* Close the uncared-about ends of the pipes */
+ close_and_invalidate (&child_err_report_pipe[1]);
+
+ wait_again:
+ if (waitpid (pid, &status, 0) < 0)
+ {
+ if (errno == EINTR)
+ goto wait_again;
+ else if (errno == ECHILD)
+ ; /* do nothing, child already reaped */
+ else
+ _dbus_warn ("waitpid() should not fail in "
+ "'_dbus_spawn_async'");
+ }
+
+ if (!read_ints (child_err_report_pipe[0],
+ buf, 2, &n_ints,
+ error))
+ goto cleanup_and_fail;
+
+ if (n_ints >= 2)
+ {
+ /* Error from the child. */
+ switch (buf[0])
+ {
+ default:
+ dbus_set_error (error,
+ DBUS_ERROR_SPAWN_FAILED,
+ "Unknown error executing child process \"%s\"",
+ argv[0]);
+ break;
+ }
+
+ goto cleanup_and_fail;
+ }
+
+
+ /* Success against all odds! return the information */
+ close_and_invalidate (&child_err_report_pipe[0]);
+
+ return TRUE;
+ }
+
+ cleanup_and_fail:
+
+ /* There was an error from the Child, reap the child to avoid it being
+ a zombie.
+ */
+ if (pid > 0)
+ {
+ wait_failed:
+ if (waitpid (pid, NULL, 0) < 0)
+ {
+ if (errno == EINTR)
+ goto wait_failed;
+ else if (errno == ECHILD)
+ ; /* do nothing, child already reaped */
+ else
+ _dbus_warn ("waitpid() should not fail in "
+ "'_dbus_spawn_async'");
+ }
+ }
+
+ close_and_invalidate (&child_err_report_pipe[0]);
+ close_and_invalidate (&child_err_report_pipe[1]);
+
+ return FALSE;
+}
+
+
+/** @} */