From 2aa38be20b015777ec6570d1e2fd7e072f5f3be9 Mon Sep 17 00:00:00 2001 From: Anders Carlsson Date: Sat, 15 Feb 2003 17:18:13 +0000 Subject: [PATCH] 2003-02-15 Anders Carlsson * dbus/dbus-errors.c: (dbus_set_error): * dbus/dbus-errors.h: Add a few errors and make dbus_set_error void. * dbus/dbus-sysdeps.c: (_dbus_errno_to_string), (close_and_invalidate), (make_pipe), (write_err_and_exit), (read_ints), (do_exec), (_dbus_spawn_async): * dbus/dbus-sysdeps.h: Add _dbus_spawn_async. * test/spawn-test.c: (main): Test for _dbus_spawn_async. --- ChangeLog | 15 +++ dbus/dbus-errors.c | 18 ++-- dbus/dbus-errors.h | 6 +- dbus/dbus-sysdeps.c | 272 ++++++++++++++++++++++++++++++++++++++++++++++++++++ dbus/dbus-sysdeps.h | 5 + test/Makefile.am | 6 +- test/spawn-test.c | 34 +++++++ 7 files changed, 347 insertions(+), 9 deletions(-) create mode 100644 test/spawn-test.c diff --git a/ChangeLog b/ChangeLog index 24abc4e..6c5517b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,20 @@ 2003-02-15 Anders Carlsson + * dbus/dbus-errors.c: (dbus_set_error): + * dbus/dbus-errors.h: + Add a few errors and make dbus_set_error void. + + * dbus/dbus-sysdeps.c: + (_dbus_errno_to_string), (close_and_invalidate), (make_pipe), + (write_err_and_exit), (read_ints), (do_exec), (_dbus_spawn_async): + * dbus/dbus-sysdeps.h: + Add _dbus_spawn_async. + + * test/spawn-test.c: (main): + Test for _dbus_spawn_async. + +2003-02-15 Anders Carlsson + * dbus/dbus-internals.h: Fix build without tests. diff --git a/dbus/dbus-errors.c b/dbus/dbus-errors.c index a679e6c..dfc52fb 100644 --- a/dbus/dbus-errors.c +++ b/dbus/dbus-errors.c @@ -214,12 +214,14 @@ dbus_set_error_const (DBusError *error, * Assigns an error name and message to a DBusError. * Does nothing if error is #NULL. * + * If no memory can be allocated for the error message, + * an out-of-memory error message will be set instead. + * * @param error the error. * @param name the error name (not copied!!!) * @param format printf-style format string. - * @returns #TRUE on success. */ -dbus_bool_t +void dbus_set_error (DBusError *error, const char *name, const char *format, @@ -232,7 +234,7 @@ dbus_set_error (DBusError *error, char c; if (error == NULL) - return TRUE; + return; va_start (args, format); @@ -246,8 +248,12 @@ dbus_set_error (DBusError *error, vsprintf (message, format, args2); if (!message) - return FALSE; - + { + dbus_set_error_const (error, DBUS_ERROR_NO_MEMORY, + "Failed to allocate memory for error message."); + return; + } + va_end (args); dbus_error_init (error); @@ -256,8 +262,6 @@ dbus_set_error (DBusError *error, real->name = name; real->message = message; real->const_message = FALSE; - - return TRUE; } /** @} */ diff --git a/dbus/dbus-errors.h b/dbus/dbus-errors.h index 639c6a7..5cc7749 100644 --- a/dbus/dbus-errors.h +++ b/dbus/dbus-errors.h @@ -49,6 +49,10 @@ struct DBusError void *padding1; /**< placeholder */ }; +#define DBUS_ERROR_SPAWN_FORK_FAILED "org.freedesktop.DBus.Error.Spawn.ForkFailed" +#define DBUS_ERROR_SPAWN_FAILED "org.freedesktop.DBus.Error.Spawn.Failed" +#define DBUS_ERROR_NO_MEMORY "org.freedesktop.DBus.Error.NoMemory" + typedef enum { DBUS_RESULT_SUCCESS, /**< Operation was successful. */ @@ -75,7 +79,7 @@ typedef enum void dbus_error_init (DBusError *error); void dbus_error_free (DBusError *error); -dbus_bool_t dbus_set_error (DBusError *error, +void dbus_set_error (DBusError *error, const char *name, const char *message, ...); diff --git a/dbus/dbus-sysdeps.c b/dbus/dbus-sysdeps.c index 53eebf7..e1ae16c 100644 --- a/dbus/dbus-sysdeps.c +++ b/dbus/dbus-sysdeps.c @@ -39,6 +39,7 @@ #include #include #include +#include #ifdef HAVE_WRITEV #include @@ -1450,4 +1451,275 @@ _dbus_generate_random_bytes (DBusString *str, return FALSE; } +const char * +_dbus_errno_to_string (int errnum) +{ + return strerror (errnum); +} + +/* 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) +{ + 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 + 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; + + 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) +{ + execvp (argv[0], argv); + + /* Exec failed */ + write_err_and_exit (child_err_report_fd, + CHILD_EXEC_FAILED); + +} + +dbus_bool_t +_dbus_spawn_async (char **argv, + DBusError *error) +{ + int pid = -1, grandchild_pid; + int child_err_report_pipe[2] = { -1, -1 }; + int child_pid_report_pipe[2] = { -1, -1 }; + int status; + + printf ("spawning application: %s\n", argv[0]); + + if (!make_pipe (child_err_report_pipe, error)) + return FALSE; + + if (!make_pipe (child_pid_report_pipe, error)) + goto cleanup_and_fail; + + 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]); + close_and_invalidate (&child_pid_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) + { + /* report -1 as child PID */ + write (child_pid_report_pipe[1], &grandchild_pid, + sizeof(grandchild_pid)); + + 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); + } + else + { + write (child_pid_report_pipe[1], &grandchild_pid, sizeof(grandchild_pid)); + close_and_invalidate (&child_pid_report_pipe[1]); + + _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]); + close_and_invalidate (&child_pid_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]); + close_and_invalidate (&child_pid_report_pipe[0]); + close_and_invalidate (&child_pid_report_pipe[1]); + + return FALSE; +} + /** @} end of sysdeps */ diff --git a/dbus/dbus-sysdeps.h b/dbus/dbus-sysdeps.h index 76c943b..dca12ed 100644 --- a/dbus/dbus-sysdeps.h +++ b/dbus/dbus-sysdeps.h @@ -147,6 +147,11 @@ void _dbus_directory_close (DBusDirIter *iter); dbus_bool_t _dbus_generate_random_bytes (DBusString *str, int n_bytes); +const char *_dbus_errno_to_string (int errnum); +dbus_bool_t _dbus_spawn_async (char **argv, + DBusError *error); + + DBUS_END_DECLS; #endif /* DBUS_SYSDEPS_H */ diff --git a/test/Makefile.am b/test/Makefile.am index ad65782..604fd3f 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -2,7 +2,7 @@ INCLUDES=-I$(top_srcdir) $(DBUS_TEST_CFLAGS) if DBUS_BUILD_TESTS -TEST_BINARIES=echo-client echo-server unbase64 bus-test break-loader +TEST_BINARIES=echo-client echo-server unbase64 bus-test break-loader spawn-test else TEST_BINARIES= endif @@ -31,6 +31,9 @@ bus_test_SOURCES = \ break_loader_SOURCES= \ break-loader.c +spawn_test_SOURCES= \ + spawn-test.c + TEST_LIBS=$(DBUS_TEST_LIBS) $(top_builddir)/dbus/libdbus-convenience.la $(top_builddir)/dbus/libdbus-1.la echo_client_LDADD=$(TEST_LIBS) @@ -38,6 +41,7 @@ echo_server_LDADD=$(TEST_LIBS) unbase64_LDADD=$(TEST_LIBS) break_loader_LDADD= $(TEST_LIBS) bus_test_LDADD=$(TEST_LIBS) $(top_builddir)/bus/libdbus-daemon.la +spawn_test_LDADD=$(TEST_LIBS) dist-hook: \ DIRS="data data/valid-messages data/invalid-messages data/incomplete-messages data/auth" ; \ diff --git a/test/spawn-test.c b/test/spawn-test.c new file mode 100644 index 0000000..fda0309 --- /dev/null +++ b/test/spawn-test.c @@ -0,0 +1,34 @@ +#include + +#define DBUS_COMPILATION /* cheat and use dbus-sysdeps */ +#include +#undef DBUS_COMPILATION +#include + +int +main (int argc, char **argv) +{ + char **argv_copy; + int i; + DBusError error; + + if (argc < 2) + { + fprintf (stderr, "You need to specify a program to launch.\n"); + + return -1; + } + + argv_copy = dbus_new (char *, argc); + for (i = 0; i < argc - 1; i++) + argv_copy [i] = argv[i + 1]; + argv_copy[argc - 1] = NULL; + + if (!_dbus_spawn_async (argv_copy, &error)) + { + fprintf (stderr, "Could not launch application: \"%s\"\n", + error.message); + } + + return 0; +} -- 2.7.4