diff --git a/man/launcher.1.md b/man/launcher.1.md index f6c292ed6f94944c93a1e902dffb77960c037490..d2afb561a2d7107a4bc04d12a39868f2857de1df 100644 --- a/man/launcher.1.md +++ b/man/launcher.1.md @@ -47,6 +47,11 @@ D-Bus session bus, and executes arbitrary commands as subprocesses. : Connect to the well-known D-Bus session bus, request the given name and listen for commands there. +**--exit-on-readable** *FD* +: Exit when file descriptor *FD* (typically 0, for **stdin**) becomes + readable, meaning either data is available or end-of-file has been + reached. + **--replace** : When used with **--bus-name**, allow other **pressure-vessel-launcher** processes to take over the bus name, @@ -196,19 +201,29 @@ Listen on the session bus, and run two commands, and exit: kill -TERM "$launcher_pid" wait "$launcher_pid" -Listen on a socket in a temporary directory: +Listen on a socket in a temporary directory, and use `--exit-with-fd`. +Production code would be more likely to invoke **pressure-vessel-launch** +from C, C++ or Python code rather than a shell script, and use pipes +instead of fifos. tmpdir="$(mktemp -d)" - mkfifo "${tmpdir}/fifo" + mkfifo "${tmpdir}/exit-fifo" + mkfifo "${tmpdir}/ready-fifo" pressure-vessel-wrap \ --filesystem="${tmpdir}" \ ... \ -- \ pressure-vessel-launcher \ - --socket="${tmpdir}/launcher" > "${tmpdir}/fifo" & + --exit-on-readable=0 \ + --socket="${tmpdir}/launcher" \ + < "${tmpdir}/exit-fifo" \ + > "${tmpdir}/ready-fifo" \ + & launcher_pid="$!" + sleep infinity > "${tmpdir}/exit-fifo" & + sleep_pid="$!" # Wait for EOF so we know the socket is available - cat "${tmpdir}/fifo" > /dev/null + cat "${tmpdir}/ready-fifo" > /dev/null pressure-vessel-launch \ --socket="${tmpdir}/launcher" \ @@ -218,9 +233,17 @@ Listen on a socket in a temporary directory: --socket="${tmpdir}/launcher" \ -- \ id - - kill -TERM "$launcher_pid" - wait "$launcher_pid" + pressure-vessel-launch \ + --socket="${tmpdir}/launcher" \ + -- \ + sleep infinity & + launch_sleep_pid="$!" + + # Make pressure-vessel-launcher's stdin become readable, which + # will make it exit due to --exit-on-readable + kill "$sleep_pid" + wait "$launcher_pid" || echo "launcher exit status: $?" + wait "$launch_sleep_pid" || echo "launched process exit status: $?" rm -fr "$tmpdir" <!-- vim:set sw=4 sts=4 et: --> diff --git a/src/launcher.c b/src/launcher.c index 2a98a3ebcb7eff1903c3b2c70d82124277bcea9f..baf0c663773fa035f13b7dae179859322ee208ac 100644 --- a/src/launcher.c +++ b/src/launcher.c @@ -853,7 +853,103 @@ connect_to_signals (void) return g_unix_fd_add (sfd, G_IO_IN, signal_handler, NULL); } +/* + * If @fd is `stdin`, make `stdin` point to /dev/null and return a + * new fd that is a duplicate of the original `stdin`, so that the + * `stdin` inherited by child processes will not collide with the fd + * we are using for some other purpose. + */ +static int +avoid_stdin (int fd, + GError **error) +{ + g_return_val_if_fail (fd >= 0, FALSE); + + if (fd == STDIN_FILENO) + { + glnx_autofd int old_stdin = -1; + glnx_autofd int new_stdin = -1; + int fd_flags; + + old_stdin = dup (STDIN_FILENO); + + if (old_stdin < 0) + { + glnx_throw_errno_prefix (error, + "Unable to duplicate standard input"); + return -1; + } + + fd_flags = fcntl (old_stdin, F_GETFD); + + if (fd_flags < 0 || + fcntl (old_stdin, F_SETFD, fd_flags | FD_CLOEXEC) != 0) + { + glnx_throw_errno_prefix (error, "Unable to set flags on fd %d", + old_stdin); + return -1; + } + + new_stdin = open ("/dev/null", O_RDONLY | O_CLOEXEC); + + if (new_stdin < 0) + { + glnx_throw_errno_prefix (error, "Unable to open /dev/null"); + return -1; + } + + if (dup2 (new_stdin, STDIN_FILENO) != STDIN_FILENO) + { + glnx_throw_errno_prefix (error, + "Unable to make stdin point to /dev/null"); + return -1; + } + + fd = glnx_steal_fd (&old_stdin); + } + + return fd; +} + +static gboolean +exit_on_readable_cb (int fd, + GIOCondition condition, + gpointer user_data) +{ + guint *id_p = user_data; + + terminate_children (SIGTERM); + g_main_loop_quit (main_loop); + *id_p = 0; + return G_SOURCE_REMOVE; +} + +static gboolean +set_up_exit_on_readable (int fd, + guint *id_p, + GError **error) +{ + g_return_val_if_fail (fd >= 0, FALSE); + g_return_val_if_fail (id_p != NULL, FALSE); + g_return_val_if_fail (*id_p == 0, FALSE); + + if (fd == STDOUT_FILENO || fd == STDERR_FILENO) + { + return glnx_throw (error, + "--exit-on-readable fd cannot be stdout or stderr"); + } + + fd = avoid_stdin (fd, error); + + if (fd < 0) + return FALSE; + + *id_p = g_unix_fd_add (fd, G_IO_IN|G_IO_ERR|G_IO_HUP, exit_on_readable_cb, id_p); + return TRUE; +} + static gchar *opt_bus_name = NULL; +static gint opt_exit_on_readable_fd = -1; static gboolean opt_replace = FALSE; static gchar *opt_socket = NULL; static gchar *opt_socket_directory = NULL; @@ -866,6 +962,11 @@ static GOptionEntry options[] = G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &opt_bus_name, "Use this well-known name on the D-Bus session bus.", "NAME" }, + { "exit-on-readable", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_INT, &opt_exit_on_readable_fd, + "Exit when data is available for reading or when end-of-file is " + "reached on this fd, usually 0 for stdin.", + "FD" }, { "replace", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_replace, "Replace a previous instance with the same bus name. " @@ -907,6 +1008,7 @@ main (int argc, g_autoptr(AutoDBusServer) server = NULL; g_autoptr(GOptionContext) context = NULL; guint signals_id = 0; + guint exit_on_readable_id = 0; g_autoptr(GError) local_error = NULL; GError **error = &local_error; gsize i; @@ -957,6 +1059,16 @@ main (int argc, goto out; } + if (opt_exit_on_readable_fd >= 0) + { + if (!set_up_exit_on_readable (opt_exit_on_readable_fd, + &exit_on_readable_id, error)) + { + ret = EX_OSERR; + goto out; + } + } + /* We have to block the signals we want to forward before we start any * other thread, and in particular the GDBus worker thread, because * the signal mask is per-thread. We need all threads to have the same @@ -1154,6 +1266,9 @@ out: if (local_error != NULL) g_warning ("%s", local_error->message); + if (exit_on_readable_id > 0) + g_source_remove (exit_on_readable_id); + if (signals_id > 0) g_source_remove (signals_id);