diff options
-rw-r--r-- | configure.ac | 2 | ||||
-rw-r--r-- | docs/reference/gio/gio-sections.txt | 1 | ||||
-rw-r--r-- | docs/reference/glib/glib-sections.txt | 1 | ||||
-rw-r--r-- | gio/Makefile.am | 4 | ||||
-rw-r--r-- | gio/gdesktopappinfo.c | 205 | ||||
-rw-r--r-- | gio/gdesktopappinfo.h | 14 | ||||
-rw-r--r-- | gio/gio-launch-desktop.c | 52 | ||||
-rw-r--r-- | gio/meson.build | 6 | ||||
-rw-r--r-- | gio/tests/Makefile.am | 4 | ||||
-rw-r--r-- | gio/tests/desktop-app-info.c | 41 | ||||
-rw-r--r-- | gio/tests/meson.build | 1 | ||||
-rw-r--r-- | glib/gspawn-win32.c | 163 | ||||
-rw-r--r-- | glib/gspawn.c | 540 | ||||
-rw-r--r-- | glib/gspawn.h | 13 | ||||
-rw-r--r-- | glib/tests/spawn-singlethread.c | 176 | ||||
-rw-r--r-- | meson.build | 5 |
16 files changed, 1042 insertions, 186 deletions
diff --git a/configure.ac b/configure.ac index afbf34edb..06fac48af 100644 --- a/configure.ac +++ b/configure.ac @@ -480,7 +480,7 @@ AM_CONDITIONAL(OS_WIN32_AND_DLL_COMPILATION, [test x$glib_native_win32 = xyes -a # Checks for library functions. AC_FUNC_ALLOCA AC_CHECK_FUNCS(mmap posix_memalign memalign valloc fsync pipe2 issetugid) -AC_CHECK_FUNCS(timegm gmtime_r) +AC_CHECK_FUNCS(timegm gmtime_r posix_spawn) AC_FUNC_STRERROR_R() AC_CHECK_SIZEOF(char) diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt index bca2e3670..0eb560716 100644 --- a/docs/reference/gio/gio-sections.txt +++ b/docs/reference/gio/gio-sections.txt @@ -1624,6 +1624,7 @@ g_desktop_app_info_get_boolean g_desktop_app_info_has_key GDesktopAppLaunchCallback g_desktop_app_info_launch_uris_as_manager +g_desktop_app_info_launch_uris_as_manager_with_fds <SUBSECTION> g_desktop_app_info_list_actions g_desktop_app_info_get_action_name diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index e1b165684..01da779e8 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -1233,6 +1233,7 @@ GSpawnError G_SPAWN_ERROR GSpawnFlags GSpawnChildSetupFunc +g_spawn_async_with_fds g_spawn_async_with_pipes g_spawn_async g_spawn_sync diff --git a/gio/Makefile.am b/gio/Makefile.am index 8a70bf98c..9dccb6829 100644 --- a/gio/Makefile.am +++ b/gio/Makefile.am @@ -819,7 +819,7 @@ gio.def: libgio-2.0.la gio-2.0.lib: libgio-2.0.la gio.def $(AM_V_GEN) lib.exe -machine:@LIB_EXE_MACHINE_FLAG@ -name:libgio-2.0-$(LT_CURRENT_MINUS_AGE).dll -def:$(builddir)/gio.def -out:$@ -bin_PROGRAMS = gio-querymodules glib-compile-schemas glib-compile-resources gsettings +bin_PROGRAMS = gio-querymodules glib-compile-schemas glib-compile-resources gsettings gio-launch-desktop glib_compile_resources_LDADD = libgio-2.0.la \ $(top_builddir)/gobject/libgobject-2.0.la \ @@ -840,6 +840,8 @@ gio_querymodules_LDADD = libgio-2.0.la \ $(top_builddir)/glib/libglib-2.0.la \ $(NULL) +gio_launch_desktop_SOURCES = gio-launch-desktop.c + gconstructor_as_data.h: $(top_srcdir)/glib/gconstructor.h data-to-c.py $(AM_V_GEN) $(srcdir)/data-to-c.py $(top_srcdir)/glib/gconstructor.h gconstructor_code $@ diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index a2aa760c7..617a096fb 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -155,6 +155,7 @@ static guint n_desktop_file_dirs; static const guint desktop_file_dir_user_config_index = 0; static guint desktop_file_dir_user_data_index; static GMutex desktop_file_dir_lock; +static const gchar *gio_launch_desktop_path = NULL; /* Monitor 'changed' signal handler {{{2 */ static void desktop_file_dir_reset (DesktopFileDir *dir); @@ -2562,41 +2563,6 @@ create_files_for_uris (GList *uris) return g_list_reverse (res); } -typedef struct -{ - GSpawnChildSetupFunc user_setup; - gpointer user_setup_data; - - char *pid_envvar; -} ChildSetupData; - -static void -child_setup (gpointer user_data) -{ - ChildSetupData *data = user_data; - - if (data->pid_envvar) - { - pid_t pid = getpid (); - char buf[20]; - int i; - - /* Write the pid into the space already reserved for it in the - * environment array. We can't use sprintf because it might - * malloc, so we do it by hand. It's simplest to write the pid - * out backwards first, then copy it over. - */ - for (i = 0; pid; i++, pid /= 10) - buf[i] = (pid % 10) + '0'; - for (i--; i >= 0; i--) - *(data->pid_envvar++) = buf[i]; - *data->pid_envvar = '\0'; - } - - if (data->user_setup) - data->user_setup (data->user_setup_data); -} - static void notify_desktop_launch (GDBusConnection *session_bus, GDesktopAppInfo *info, @@ -2675,6 +2641,9 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo *info, gpointer user_setup_data, GDesktopAppLaunchCallback pid_callback, gpointer pid_callback_data, + gint stdin_fd, + gint stdout_fd, + gint stderr_fd, GError **error) { gboolean completed = FALSE; @@ -2683,7 +2652,6 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo *info, char **argv, **envp; int argc; - ChildSetupData data; g_return_val_if_fail (info != NULL, FALSE); @@ -2705,6 +2673,8 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo *info, GList *launched_uris; GList *iter; char *sn_id = NULL; + char **wrapped_argv; + int i; old_uris = dup_uris; if (!expand_application_parameters (info, exec_line, &dup_uris, &argc, &argv, error)) @@ -2723,25 +2693,11 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo *info, goto out; } - data.user_setup = user_setup; - data.user_setup_data = user_setup_data; - if (info->filename) - { - envp = g_environ_setenv (envp, - "GIO_LAUNCHED_DESKTOP_FILE", - info->filename, - TRUE); - envp = g_environ_setenv (envp, - "GIO_LAUNCHED_DESKTOP_FILE_PID", - "XXXXXXXXXXXXXXXXXXXX", /* filled in child_setup */ - TRUE); - data.pid_envvar = (char *)g_environ_getenv (envp, "GIO_LAUNCHED_DESKTOP_FILE_PID"); - } - else - { - data.pid_envvar = NULL; - } + envp = g_environ_setenv (envp, + "GIO_LAUNCHED_DESKTOP_FILE", + info->filename, + TRUE); sn_id = NULL; if (launch_context) @@ -2760,14 +2716,40 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo *info, g_list_free_full (launched_files, g_object_unref); } - if (!g_spawn_async (info->path, - argv, - envp, - spawn_flags, - child_setup, - &data, - &pid, - error)) + if (g_once_init_enter (&gio_launch_desktop_path)) + { + const gchar *tmp; + + /* Allow test suite to specify path to gio-launch-desktop */ + tmp = g_getenv ("GIO_LAUNCH_DESKTOP"); + + /* Fall back on usual searching in $PATH */ + if (tmp == NULL) + tmp = "gio-launch-desktop"; + g_once_init_leave (&gio_launch_desktop_path, tmp); + } + + wrapped_argv = g_new (char *, argc + 2); + wrapped_argv[0] = g_strdup (gio_launch_desktop_path); + + for (i = 0; i < argc; i++) + wrapped_argv[i + 1] = g_steal_pointer (&argv[i]); + + wrapped_argv[i + 1] = NULL; + g_free (argv); + argv = NULL; + + if (!g_spawn_async_with_fds (info->path, + wrapped_argv, + envp, + spawn_flags, + user_setup, + user_setup_data, + &pid, + stdin_fd, + stdout_fd, + stderr_fd, + error)) { if (sn_id) g_app_launch_context_launch_failed (launch_context, sn_id); @@ -2805,8 +2787,8 @@ g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo *info, g_free (sn_id); g_list_free (launched_uris); - g_strfreev (argv); - argv = NULL; + g_strfreev (wrapped_argv); + wrapped_argv = NULL; } while (dup_uris != NULL); @@ -2940,6 +2922,9 @@ g_desktop_app_info_launch_uris_internal (GAppInfo *appinfo, gpointer user_setup_data, GDesktopAppLaunchCallback pid_callback, gpointer pid_callback_data, + gint stdin_fd, + gint stdout_fd, + gint stderr_fd, GError **error) { GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo); @@ -2953,7 +2938,8 @@ g_desktop_app_info_launch_uris_internal (GAppInfo *appinfo, else success = g_desktop_app_info_launch_uris_with_spawn (info, session_bus, info->exec, uris, launch_context, spawn_flags, user_setup, user_setup_data, - pid_callback, pid_callback_data, error); + pid_callback, pid_callback_data, + stdin_fd, stdout_fd, stderr_fd, error); if (session_bus != NULL) { @@ -2978,6 +2964,7 @@ g_desktop_app_info_launch_uris (GAppInfo *appinfo, launch_context, _SPAWN_FLAGS_DEFAULT, NULL, NULL, NULL, NULL, + -1, -1, -1, error); } @@ -3029,6 +3016,61 @@ g_desktop_app_info_launch (GAppInfo *appinfo, } /** + * g_desktop_app_info_launch_uris_as_manager_with_fds: + * @appinfo: a #GDesktopAppInfo + * @uris: (element-type utf8): List of URIs + * @launch_context: (nullable): a #GAppLaunchContext + * @spawn_flags: #GSpawnFlags, used for each process + * @user_setup: (scope async) (nullable): a #GSpawnChildSetupFunc, used once + * for each process. + * @user_setup_data: (closure user_setup) (nullable): User data for @user_setup + * @pid_callback: (scope call) (nullable): Callback for child processes + * @pid_callback_data: (closure pid_callback) (nullable): User data for @callback + * @stdin_fd: file descriptor to use for child's stdin, or -1 + * @stdout_fd: file descriptor to use for child's stdout, or -1 + * @stderr_fd: file descriptor to use for child's stderr, or -1 + * @error: return location for a #GError, or %NULL + * + * Equivalent to g_desktop_app_info_launch_uris_as_manager() but allows + * you to pass in file descriptors for the stdin, stdout and stderr streams + * of the launched process. + * + * If application launching occurs via some non-spawn mechanism (e.g. D-Bus + * activation) then @stdin_fd, @stdout_fd and @stderr_fd are ignored. + * + * Returns: %TRUE on successful launch, %FALSE otherwise. + * + * Since: 2.58 + */ +gboolean +g_desktop_app_info_launch_uris_as_manager_with_fds (GDesktopAppInfo *appinfo, + GList *uris, + GAppLaunchContext *launch_context, + GSpawnFlags spawn_flags, + GSpawnChildSetupFunc user_setup, + gpointer user_setup_data, + GDesktopAppLaunchCallback pid_callback, + gpointer pid_callback_data, + gint stdin_fd, + gint stdout_fd, + gint stderr_fd, + GError **error) +{ + return g_desktop_app_info_launch_uris_internal ((GAppInfo*)appinfo, + uris, + launch_context, + spawn_flags, + user_setup, + user_setup_data, + pid_callback, + pid_callback_data, + stdin_fd, + stdout_fd, + stderr_fd, + error); +} + +/** * g_desktop_app_info_launch_uris_as_manager: * @appinfo: a #GDesktopAppInfo * @uris: (element-type utf8): List of URIs @@ -3046,11 +3088,12 @@ g_desktop_app_info_launch (GAppInfo *appinfo, * launch applications. Ordinary applications should use * g_app_info_launch_uris(). * - * If the application is launched via traditional UNIX fork()/exec() - * then @spawn_flags, @user_setup and @user_setup_data are used for the - * call to g_spawn_async(). Additionally, @pid_callback (with - * @pid_callback_data) will be called to inform about the PID of the - * created process. + * If the application is launched via GSpawn, then @spawn_flags, @user_setup + * and @user_setup_data are used for the call to g_spawn_async(). + * Additionally, @pid_callback (with @pid_callback_data) will be called to + * inform about the PID of the created process. See g_spawn_async_with_pipes() + * for information on certain parameter conditions that can enable an + * optimized posix_spawn() codepath to be used. * * If application launching occurs via some other mechanism (eg: D-Bus * activation) then @spawn_flags, @user_setup, @user_setup_data, @@ -3069,15 +3112,16 @@ g_desktop_app_info_launch_uris_as_manager (GDesktopAppInfo *appinfo, gpointer pid_callback_data, GError **error) { - return g_desktop_app_info_launch_uris_internal ((GAppInfo*)appinfo, - uris, - launch_context, - spawn_flags, - user_setup, - user_setup_data, - pid_callback, - pid_callback_data, - error); + return g_desktop_app_info_launch_uris_as_manager_with_fds (appinfo, + uris, + launch_context, + spawn_flags, + user_setup, + user_setup_data, + pid_callback, + pid_callback_data, + -1, -1, -1, + error); } /* OnlyShowIn API support {{{2 */ @@ -4652,7 +4696,8 @@ g_desktop_app_info_launch_action (GDesktopAppInfo *info, if (exec_line) g_desktop_app_info_launch_uris_with_spawn (info, session_bus, exec_line, NULL, launch_context, - _SPAWN_FLAGS_DEFAULT, NULL, NULL, NULL, NULL, NULL); + _SPAWN_FLAGS_DEFAULT, NULL, NULL, NULL, NULL, + -1, -1, -1, NULL); } if (session_bus != NULL) diff --git a/gio/gdesktopappinfo.h b/gio/gdesktopappinfo.h index a2df3dd51..86a3caa30 100644 --- a/gio/gdesktopappinfo.h +++ b/gio/gdesktopappinfo.h @@ -169,6 +169,20 @@ gboolean g_desktop_app_info_launch_uris_as_manager (GDesktopAppInfo gpointer pid_callback_data, GError **error); +GLIB_AVAILABLE_IN_2_58 +gboolean g_desktop_app_info_launch_uris_as_manager_with_fds (GDesktopAppInfo *appinfo, + GList *uris, + GAppLaunchContext *launch_context, + GSpawnFlags spawn_flags, + GSpawnChildSetupFunc user_setup, + gpointer user_setup_data, + GDesktopAppLaunchCallback pid_callback, + gpointer pid_callback_data, + gint stdin_fd, + gint stdout_fd, + gint stderr_fd, + GError **error); + GLIB_AVAILABLE_IN_2_40 gchar *** g_desktop_app_info_search (const gchar *search_string); diff --git a/gio/gio-launch-desktop.c b/gio/gio-launch-desktop.c new file mode 100644 index 000000000..03845df28 --- /dev/null +++ b/gio/gio-launch-desktop.c @@ -0,0 +1,52 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright (C) 2018 Endless Mobile, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. + * + * Author: Daniel Drake <drake@endlessm.com> + */ + +/* + * gio-launch-desktop: GDesktopAppInfo helper + * Executable wrapper to set GIO_LAUNCHED_DESKTOP_FILE_PID + * There are complications when doing this in a fork()/exec() codepath, + * and it cannot otherwise be done with posix_spawn(). + * This wrapper is designed to be minimal and lightweight. + * It does not even link against glib. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <unistd.h> + +int +main (int argc, char *argv[]) +{ + pid_t pid = getpid (); + char buf[50]; + int r; + + if (argc < 2) + return -1; + + r = snprintf (buf, sizeof (buf), "GIO_LAUNCHED_DESKTOP_FILE_PID=%ld", (long) pid); + if (r >= sizeof (buf)) + return -1; + + putenv (buf); + + return execvp (argv[1], argv + 1); +} diff --git a/gio/meson.build b/gio/meson.build index f37911fac..a24a92e3d 100644 --- a/gio/meson.build +++ b/gio/meson.build @@ -411,6 +411,12 @@ if host_system != 'windows' contenttype_sources += files('gcontenttype.c') appinfo_sources += files('gdesktopappinfo.c') gio_unix_include_headers += files('gdesktopappinfo.h') + + executable('gio-launch-desktop', 'gio-launch-desktop.c', + install : true, + c_args : gio_c_args, + # intl.lib is not compatible with SAFESEH + link_args : noseh_link_args) endif subdir('xdgmime') diff --git a/gio/tests/Makefile.am b/gio/tests/Makefile.am index 15db2881c..f5231a96e 100644 --- a/gio/tests/Makefile.am +++ b/gio/tests/Makefile.am @@ -15,7 +15,9 @@ LDADD = \ AM_CPPFLAGS = $(gio_INCLUDES) $(GLIB_DEBUG_FLAGS) -I$(top_builddir)/gio -I$(top_srcdir)/gio DEFS = -DG_LOG_DOMAIN=\"GLib-GIO\" -DTEST_SERVICES=\""$(abs_top_builddir)/gio/tests/services"\" AM_CFLAGS = $(GLIB_WARN_CFLAGS) -AM_TESTS_ENVIRONMENT += GIO_MODULE_DIR= +AM_TESTS_ENVIRONMENT += \ + GIO_MODULE_DIR= \ + GIO_LAUNCH_DESKTOP="$(top_builddir)/gio/gio-launch-desktop" # ----------------------------------------------------------------------------- # Test programs buildable on all platforms diff --git a/gio/tests/desktop-app-info.c b/gio/tests/desktop-app-info.c index 669db5769..639b27e18 100644 --- a/gio/tests/desktop-app-info.c +++ b/gio/tests/desktop-app-info.c @@ -788,6 +788,46 @@ test_show_in (void) assert_shown ("gcr-prompter.desktop", TRUE, "KDE:GNOME-Classic"); } +/* Test g_desktop_app_info_launch_uris_as_manager() and + * g_desktop_app_info_launch_uris_as_manager_with_fds() + */ +static void +test_launch_as_manager (void) +{ + GDesktopAppInfo *appinfo; + GError *error = NULL; + gboolean retval; + const gchar *path; + + if (g_getenv ("DISPLAY") == NULL || g_getenv ("DISPLAY")[0] == '\0') + { + g_test_skip ("No DISPLAY. Skipping test."); + return; + } + + path = g_test_get_filename (G_TEST_DIST, "appinfo-test.desktop", NULL); + appinfo = g_desktop_app_info_new_from_filename (path); + g_assert_nonnull (appinfo); + + retval = g_desktop_app_info_launch_uris_as_manager (appinfo, NULL, NULL, 0, + NULL, NULL, + NULL, NULL, + &error); + g_assert_no_error (error); + g_assert_true (retval); + + retval = g_desktop_app_info_launch_uris_as_manager_with_fds (appinfo, + NULL, NULL, 0, + NULL, NULL, + NULL, NULL, + -1, -1, -1, + &error); + g_assert_no_error (error); + g_assert_true (retval); + + g_object_unref (appinfo); +} + int main (int argc, char *argv[]) @@ -816,6 +856,7 @@ main (int argc, g_test_add_func ("/desktop-app-info/search", test_search); g_test_add_func ("/desktop-app-info/implements", test_implements); g_test_add_func ("/desktop-app-info/show-in", test_show_in); + g_test_add_func ("/desktop-app-info/launch-as-manager", test_launch_as_manager); result = g_test_run (); diff --git a/gio/tests/meson.build b/gio/tests/meson.build index fd79bc01b..be007fa1d 100644 --- a/gio/tests/meson.build +++ b/gio/tests/meson.build @@ -75,6 +75,7 @@ test_env = [ 'G_TEST_SRCDIR=' + meson.current_source_dir(), 'G_TEST_BUILDDIR=' + meson.current_build_dir(), 'GIO_MODULE_DIR=', + 'GIO_LAUNCH_DESKTOP=' + meson.build_root() + '/gio/gio-launch-desktop', ] test_c_args = [ diff --git a/glib/gspawn-win32.c b/glib/gspawn-win32.c index 0f5b8d034..b0cf5ab7a 100644 --- a/glib/gspawn-win32.c +++ b/glib/gspawn-win32.c @@ -520,19 +520,19 @@ do_spawn_directly (gint *exit_status, } static gboolean -do_spawn_with_pipes (gint *exit_status, - gboolean do_return_handle, - const gchar *working_directory, - gchar **argv, - char **envp, - GSpawnFlags flags, - GSpawnChildSetupFunc child_setup, - GPid *child_handle, - gint *standard_input, - gint *standard_output, - gint *standard_error, - gint *err_report, - GError **error) +do_spawn_with_fds (gint *exit_status, + gboolean do_return_handle, + const gchar *working_directory, + gchar **argv, + char **envp, + GSpawnFlags flags, + GSpawnChildSetupFunc child_setup, + GPid *child_handle, + gint stdin_fd, + gint stdout_fd, + gint stderr_fd, + gint *err_report, + GError **error) { char **protected_argv; char args[ARG_COUNT][10]; @@ -541,9 +541,6 @@ do_spawn_with_pipes (gint *exit_status, gintptr rc = -1; int errsv; int argc; - int stdin_pipe[2] = { -1, -1 }; - int stdout_pipe[2] = { -1, -1 }; - int stderr_pipe[2] = { -1, -1 }; int child_err_report_pipe[2] = { -1, -1 }; int helper_sync_pipe[2] = { -1, -1 }; gintptr helper_report[2]; @@ -562,7 +559,7 @@ do_spawn_with_pipes (gint *exit_status, argc = protect_argv (argv, &protected_argv); - if (!standard_input && !standard_output && !standard_error && + if (stdin_fd == -1 && stdout_fd == -1 && stderr_fd == -1 && (flags & G_SPAWN_CHILD_INHERITS_STDIN) && !(flags & G_SPAWN_STDOUT_TO_DEV_NULL) && !(flags & G_SPAWN_STDERR_TO_DEV_NULL) && @@ -578,15 +575,6 @@ do_spawn_with_pipes (gint *exit_status, return retval; } - if (standard_input && !make_pipe (stdin_pipe, error)) - goto cleanup_and_fail; - - if (standard_output && !make_pipe (stdout_pipe, error)) - goto cleanup_and_fail; - - if (standard_error && !make_pipe (stderr_pipe, error)) - goto cleanup_and_fail; - if (!make_pipe (child_err_report_pipe, error)) goto cleanup_and_fail; @@ -639,9 +627,9 @@ do_spawn_with_pipes (gint *exit_status, */ helper_sync_pipe[1] = dup_noninherited (helper_sync_pipe[1], _O_WRONLY); - if (standard_input) + if (stdin_fd != -1) { - _g_sprintf (args[ARG_STDIN], "%d", stdin_pipe[0]); + _g_sprintf (args[ARG_STDIN], "%d", stdin_fd); new_argv[ARG_STDIN] = args[ARG_STDIN]; } else if (flags & G_SPAWN_CHILD_INHERITS_STDIN) @@ -655,9 +643,9 @@ do_spawn_with_pipes (gint *exit_status, new_argv[ARG_STDIN] = "z"; } - if (standard_output) + if (stdout_fd != -1) { - _g_sprintf (args[ARG_STDOUT], "%d", stdout_pipe[1]); + _g_sprintf (args[ARG_STDOUT], "%d", stdout_fd); new_argv[ARG_STDOUT] = args[ARG_STDOUT]; } else if (flags & G_SPAWN_STDOUT_TO_DEV_NULL) @@ -669,9 +657,9 @@ do_spawn_with_pipes (gint *exit_status, new_argv[ARG_STDOUT] = "-"; } - if (standard_error) + if (stdout_fd != -1) { - _g_sprintf (args[ARG_STDERR], "%d", stderr_pipe[1]); + _g_sprintf (args[ARG_STDERR], "%d", stderr_fd); new_argv[ARG_STDERR] = args[ARG_STDERR]; } else if (flags & G_SPAWN_STDERR_TO_DEV_NULL) @@ -770,9 +758,6 @@ do_spawn_with_pipes (gint *exit_status, */ close_and_invalidate (&child_err_report_pipe[1]); close_and_invalidate (&helper_sync_pipe[0]); - close_and_invalidate (&stdin_pipe[0]); - close_and_invalidate (&stdout_pipe[1]); - close_and_invalidate (&stderr_pipe[1]); g_strfreev (protected_argv); @@ -842,12 +827,6 @@ do_spawn_with_pipes (gint *exit_status, /* Success against all odds! return the information */ - if (standard_input) - *standard_input = stdin_pipe[1]; - if (standard_output) - *standard_output = stdout_pipe[0]; - if (standard_error) - *standard_error = stderr_pipe[0]; if (rc != -1) CloseHandle ((HANDLE) rc); @@ -865,6 +844,71 @@ do_spawn_with_pipes (gint *exit_status, close (helper_sync_pipe[0]); if (helper_sync_pipe[1] != -1) close (helper_sync_pipe[1]); + + return FALSE; +} + +static gboolean +do_spawn_with_pipes (gint *exit_status, + gboolean do_return_handle, + const gchar *working_directory, + gchar **argv, + char **envp, + GSpawnFlags flags, + GSpawnChildSetupFunc child_setup, + GPid *child_handle, + gint *standard_input, + gint *standard_output, + gint *standard_error, + gint *err_report, + GError **error) +{ + int stdin_pipe[2] = { -1, -1 }; + int stdout_pipe[2] = { -1, -1 }; + int stderr_pipe[2] = { -1, -1 }; + + if (standard_input && !make_pipe (stdin_pipe, error)) + goto cleanup_and_fail; + + if (standard_output && !make_pipe (stdout_pipe, error)) + goto cleanup_and_fail; + + if (standard_error && !make_pipe (stderr_pipe, error)) + goto cleanup_and_fail; + + if (!do_spawn_with_fds (exit_status, + do_return_handle, + working_directory, + argv, + envp, + flags, + child_setup, + child_handle, + stdin_pipe[0], + stdout_pipe[1], + stderr_pipe[1], + err_report, + error)) + goto cleanup_and_fail; + + /* Close the other process's ends of the pipes in this process, + * otherwise the reader will never get EOF. + */ + close_and_invalidate (&stdin_pipe[0]); + close_and_invalidate (&stdout_pipe[1]); + close_and_invalidate (&stderr_pipe[1]); + + if (standard_input) + *standard_input = stdin_pipe[1]; + if (standard_output) + *standard_output = stdout_pipe[0]; + if (standard_error) + *standard_error = stderr_pipe[0]; + + return TRUE; + + cleanup_and_fail: + if (stdin_pipe[0] != -1) close (stdin_pipe[0]); if (stdin_pipe[1] != -1) @@ -1161,6 +1205,43 @@ g_spawn_async_with_pipes (const gchar *working_directory, } gboolean +g_spawn_async_with_fds (const gchar *working_directory, + gchar **argv, + gchar **envp, + GSpawnFlags flags, + GSpawnChildSetupFunc child_setup, + gpointer user_data, + GPid *child_handle, + gint stdin_fd, + gint stdout_fd, + gint stderr_fd, + GError **error) +{ + g_return_val_if_fail (argv != NULL, FALSE); + g_return_val_if_fail (stdin_fd == -1 || + !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE); + g_return_val_if_fail (stderr_fd == -1 || + !(flags & G_SPAWN_STDERR_TO_DEV_NULL), FALSE); + /* can't inherit stdin if we have an input pipe. */ + g_return_val_if_fail (stdin_fd == -1 || + !(flags & G_SPAWN_CHILD_INHERITS_STDIN), FALSE); + + return do_spawn_with_fds (NULL, + (flags & G_SPAWN_DO_NOT_REAP_CHILD), + working_directory, + argv, + envp, + flags, + child_setup, + child_handle, + stdin_fd, + stdout_fd, + stderr_fd, + NULL, + error); +} + +gboolean g_spawn_command_line_sync (const gchar *command_line, gchar **standard_output, gchar **standard_error, diff --git a/glib/gspawn.c b/glib/gspawn.c index 5e90d4c5b..4fe60a584 100644 --- a/glib/gspawn.c +++ b/glib/gspawn.c @@ -30,6 +30,7 @@ #include <string.h> #include <stdlib.h> /* for fdwalk */ #include <dirent.h> +#include <spawn.h> #ifdef HAVE_SYS_SELECT_H #include <sys/select.h> @@ -54,6 +55,22 @@ #include "glibintl.h" #include "glib-unix.h" +/* posix_spawn() is assumed the fastest way to spawn, but glibc's + * implementation was buggy before glibc 2.24, so avoid it on old versions. + */ +#ifdef HAVE_POSIX_SPAWN +#ifdef __GLIBC__ + +#if __GLIBC_PREREQ(2,24) +#define POSIX_SPAWN_AVAILABLE +#endif + +#else /* !__GLIBC__ */ +/* Assume that all non-glibc posix_spawn implementations are fine. */ +#define POSIX_SPAWN_AVAILABLE +#endif /* __GLIBC__ */ +#endif /* HAVE_POSIX_SPAWN */ + /** * SECTION:spawn * @Short_description: process launching @@ -142,6 +159,27 @@ static gboolean fork_exec_with_pipes (gboolean intermediate_child, gint *standard_error, GError **error); +static gboolean fork_exec_with_fds (gboolean intermediate_child, + const gchar *working_directory, + gchar **argv, + gchar **envp, + gboolean close_descriptors, + gboolean search_path, + gboolean search_path_from_envp, + gboolean stdout_to_null, + gboolean stderr_to_null, + gboolean child_inherits_stdin, + gboolean file_and_argv_zero, + gboolean cloexec_pipes, + GSpawnChildSetupFunc child_setup, + gpointer user_data, + GPid *child_pid, + gint *child_close_fds, + gint stdin_fd, + gint stdout_fd, + gint stderr_fd, + GError **error); + G_DEFINE_QUARK (g-exec-error-quark, g_spawn_error) G_DEFINE_QUARK (g-spawn-exit-error-quark, g_spawn_exit_error) @@ -600,10 +638,11 @@ g_spawn_sync (const gchar *working_directory, * is equivalent to calling CloseHandle() on the process handle returned * in @child_pid). See g_child_watch_add(). * - * %G_SPAWN_LEAVE_DESCRIPTORS_OPEN means that the parent's open file - * descriptors will be inherited by the child; otherwise all descriptors - * except stdin/stdout/stderr will be closed before calling exec() in - * the child. %G_SPAWN_SEARCH_PATH means that @argv[0] need not be an + * Open UNIX file descriptors marked as `FD_CLOEXEC` will be automatically + * closed in the child process. %G_SPAWN_LEAVE_DESCRIPTORS_OPEN means that + * other open file descriptors will be inherited by the child; otherwise all + * descriptors except stdin/stdout/stderr will be closed before calling exec() + * in the child. %G_SPAWN_SEARCH_PATH means that @argv[0] need not be an * absolute path, it will be looked for in the `PATH` environment * variable. %G_SPAWN_SEARCH_PATH_FROM_ENVP means need not be an * absolute path, it will be looked for in the `PATH` variable from @@ -678,6 +717,21 @@ g_spawn_sync (const gchar *working_directory, * If @child_pid is not %NULL and an error does not occur then the returned * process reference must be closed using g_spawn_close_pid(). * + * On modern UNIX platforms, GLib can use an efficient process launching + * codepath driven internally by posix_spawn(). This has the advantage of + * avoiding the fork-time performance costs of cloning the parent process + * address space, and avoiding associated memory overcommit checks that are + * not relevant in the context of immediately executing a distinct process. + * This optimized codepath will be used provided that the following conditions + * are met: + * + * 1. %G_SPAWN_DO_NOT_REAP_CHILD is set + * 2. %G_SPAWN_LEAVE_DESCRIPTORS_OPEN is set + * 3. %G_SPAWN_SEARCH_PATH_FROM_ENVP is not set + * 4. @working_directory is %NULL + * 5. @child_setup is %NULL + * 6. The program is of a recognised binary format, or has a shebang. Otherwise, GLib will have to execute the program through the shell, which is not done using the optimized codepath. + * * If you are writing a GTK+ application, and the program you are spawning is a * graphical application too, then to ensure that the spawned program opens its * windows on the right screen, you may want to use #GdkAppLaunchContext, @@ -729,6 +783,87 @@ g_spawn_async_with_pipes (const gchar *working_directory, } /** + * g_spawn_async_with_fds: + * @working_directory: (type filename) (nullable): child's current working directory, or %NULL to inherit parent's, in the GLib file name encoding + * @argv: (array zero-terminated=1): child's argument vector, in the GLib file name encoding + * @envp: (array zero-terminated=1) (nullable): child's environment, or %NULL to inherit parent's, in the GLib file name encoding + * @flags: flags from #GSpawnFlags + * @child_setup: (scope async) (nullable): function to run in the child just before exec() + * @user_data: (closure): user data for @child_setup + * @child_pid: (out) (optional): return location for child process ID, or %NULL + * @stdin_fd: file descriptor to use for child's stdin, or -1 + * @stdout_fd: file descriptor to use for child's stdout, or -1 + * @stderr_fd: file descriptor to use for child's stderr, or -1 + * @error: return location for error + * + * Identical to g_spawn_async_with_pipes() but instead of + * creating pipes for the stdin/stdout/stderr, you can pass existing + * file descriptors into this function through the @stdin_fd, + * @stdout_fd and @stderr_fd parameters. The following @flags + * also have their behaviour slightly tweaked as a result: + * + * %G_SPAWN_STDOUT_TO_DEV_NULL means that the child's standard output + * will be discarded, instead of going to the same location as the parent's + * standard output. If you use this flag, @standard_output must be -1. + * %G_SPAWN_STDERR_TO_DEV_NULL means that the child's standard error + * will be discarded, instead of going to the same location as the parent's + * standard error. If you use this flag, @standard_error must be -1. + * %G_SPAWN_CHILD_INHERITS_STDIN means that the child will inherit the parent's + * standard input (by default, the child's standard input is attached to + * /dev/null). If you use this flag, @standard_input must be -1. + * + * It is valid to pass the same fd in multiple parameters (e.g. you can pass + * a single fd for both stdout and stderr). + * + * Returns: %TRUE on success, %FALSE if an error was set + * + * Since: 2.58 + */ +gboolean +g_spawn_async_with_fds (const gchar *working_directory, + gchar **argv, + gchar **envp, + GSpawnFlags flags, + GSpawnChildSetupFunc child_setup, + gpointer user_data, + GPid *child_pid, + gint stdin_fd, + gint stdout_fd, + gint stderr_fd, + GError **error) +{ + g_return_val_if_fail (argv != NULL, FALSE); + g_return_val_if_fail (stdout_fd == -1 || + !(flags & G_SPAWN_STDOUT_TO_DEV_NULL), FALSE); + g_return_val_if_fail (stderr_fd == -1 || + !(flags & G_SPAWN_STDERR_TO_DEV_NULL), FALSE); + /* can't inherit stdin if we have an input pipe. */ + g_return_val_if_fail (stdin_fd == -1 || + !(flags & G_SPAWN_CHILD_INHERITS_STDIN), FALSE); + + return fork_exec_with_fds (!(flags & G_SPAWN_DO_NOT_REAP_CHILD), + working_directory, + argv, + envp, + !(flags & G_SPAWN_LEAVE_DESCRIPTORS_OPEN), + (flags & G_SPAWN_SEARCH_PATH) != 0, + (flags & G_SPAWN_SEARCH_PATH_FROM_ENVP) != 0, + (flags & G_SPAWN_STDOUT_TO_DEV_NULL) != 0, + (flags & G_SPAWN_STDERR_TO_DEV_NULL) != 0, + (flags & G_SPAWN_CHILD_INHERITS_STDIN) != 0, + (flags & G_SPAWN_FILE_AND_ARGV_ZERO) != 0, + (flags & G_SPAWN_CLOEXEC_PIPES) != 0, + child_setup, + user_data, + child_pid, + NULL, + stdin_fd, + stdout_fd, + stderr_fd, + error); +} + +/** * g_spawn_command_line_sync: * @command_line: (type filename): a command line * @standard_output: (out) (array zero-terminated=1) (element-type guint8) (optional): return location for child output @@ -1118,13 +1253,12 @@ do_exec (gint child_err_report_fd, write_err_and_exit (child_err_report_fd, CHILD_DUP2_FAILED); - /* ignore this if it doesn't work */ - close_and_invalidate (&stdin_fd); + set_cloexec (GINT_TO_POINTER(0), stdin_fd); } else if (!child_inherits_stdin) { /* Keep process from blocking on a read of stdin */ - gint read_null = open ("/dev/null", O_RDONLY); + gint read_null = sane_open ("/dev/null", O_RDONLY); g_assert (read_null != -1); sane_dup2 (read_null, 0); close_and_invalidate (&read_null); @@ -1138,8 +1272,7 @@ do_exec (gint child_err_report_fd, write_err_and_exit (child_err_report_fd, CHILD_DUP2_FAILED); - /* ignore this if it doesn't work */ - close_and_invalidate (&stdout_fd); + set_cloexec (GINT_TO_POINTER(0), stdout_fd); } else if (stdout_to_null) { @@ -1157,8 +1290,7 @@ do_exec (gint child_err_report_fd, write_err_and_exit (child_err_report_fd, CHILD_DUP2_FAILED); - /* ignore this if it doesn't work */ - close_and_invalidate (&stderr_fd); + set_cloexec (GINT_TO_POINTER(0), stderr_fd); } else if (stderr_to_null) { @@ -1231,51 +1363,256 @@ read_ints (int fd, return TRUE; } +#ifdef POSIX_SPAWN_AVAILABLE static gboolean -fork_exec_with_pipes (gboolean intermediate_child, - const gchar *working_directory, - gchar **argv, - gchar **envp, - gboolean close_descriptors, - gboolean search_path, - gboolean search_path_from_envp, - gboolean stdout_to_null, - gboolean stderr_to_null, - gboolean child_inherits_stdin, - gboolean file_and_argv_zero, - gboolean cloexec_pipes, - GSpawnChildSetupFunc child_setup, - gpointer user_data, - GPid *child_pid, - gint *standard_input, - gint *standard_output, - gint *standard_error, - GError **error) +do_posix_spawn (gchar **argv, + gchar **envp, + gboolean search_path, + gboolean stdout_to_null, + gboolean stderr_to_null, + gboolean child_inherits_stdin, + gboolean file_and_argv_zero, + GPid *child_pid, + gint *child_close_fds, + gint stdin_fd, + gint stdout_fd, + gint stderr_fd) +{ + pid_t pid; + gchar **argv_pass; + posix_spawnattr_t attr; + posix_spawn_file_actions_t file_actions; + gint parent_close_fds[3]; + gint num_parent_close_fds = 0; + GSList *child_close = NULL; + GSList *elem; + sigset_t mask; + int i, r; + + if (*argv[0] == '\0') + { + /* We check the simple case first. */ + return ENOENT; + } + + r = posix_spawnattr_init (&attr); + if (r != 0) + return r; + + if (child_close_fds) + { + int i = -1; + while (child_close_fds[++i] != -1) + child_close = g_slist_prepend (child_close, + GINT_TO_POINTER (child_close_fds[i])); + } + + r = posix_spawnattr_setflags (&attr, POSIX_SPAWN_SETSIGDEF); + if (r != 0) + goto out_free_spawnattr; + + /* Reset some signal handlers that we may use */ + sigemptyset (&mask); + sigaddset (&mask, SIGCHLD); + sigaddset (&mask, SIGINT); + sigaddset (&mask, SIGTERM); + sigaddset (&mask, SIGHUP); + + r = posix_spawnattr_setsigdefault (&attr, &mask); + if (r != 0) + goto out_free_spawnattr; + + r = posix_spawn_file_actions_init (&file_actions); + if (r != 0) + goto out_free_spawnattr; + + /* Redirect pipes as required */ + + if (stdin_fd >= 0) + { + r = posix_spawn_file_actions_adddup2 (&file_actions, stdin_fd, 0); + if (r != 0) + goto out_close_fds; + + if (!g_slist_find (child_close, GINT_TO_POINTER (stdin_fd))) + child_close = g_slist_prepend (child_close, GINT_TO_POINTER (stdin_fd)); + } + else if (!child_inherits_stdin) + { + /* Keep process from blocking on a read of stdin */ + gint read_null = sane_open ("/dev/null", O_RDONLY | O_CLOEXEC); + g_assert (read_null != -1); + parent_close_fds[num_parent_close_fds++] = read_null; + + r = posix_spawn_file_actions_adddup2 (&file_actions, read_null, 0); + if (r != 0) + goto out_close_fds; + } + + if (stdout_fd >= 0) + { + r = posix_spawn_file_actions_adddup2 (&file_actions, stdout_fd, 1); + if (r != 0) + goto out_close_fds; + + if (!g_slist_find (child_close, GINT_TO_POINTER (stdout_fd))) + child_close = g_slist_prepend (child_close, GINT_TO_POINTER (stdout_fd)); + } + else if (stdout_to_null) + { + gint write_null = sane_open ("/dev/null", O_WRONLY | O_CLOEXEC); + g_assert (write_null != -1); + parent_close_fds[num_parent_close_fds++] = write_null; + + r = posix_spawn_file_actions_adddup2 (&file_actions, write_null, 1); + if (r != 0) + goto out_close_fds; + } + + if (stderr_fd >= 0) + { + r = posix_spawn_file_actions_adddup2 (&file_actions, stderr_fd, 2); + if (r != 0) + goto out_close_fds; + + if (!g_slist_find (child_close, GINT_TO_POINTER (stderr_fd))) + child_close = g_slist_prepend (child_close, GINT_TO_POINTER (stderr_fd)); + } + else if (stderr_to_null) + { + gint write_null = sane_open ("/dev/null", O_WRONLY | O_CLOEXEC); + g_assert (write_null != -1); + parent_close_fds[num_parent_close_fds++] = write_null; + + r = posix_spawn_file_actions_adddup2 (&file_actions, write_null, 2); + if (r != 0) + goto out_close_fds; + } + + /* Intentionally close the fds in the child as the last file action, + * having been careful not to add the same fd to this list twice. + * + * This is important to allow (e.g.) for the same fd to be passed as stdout + * and stderr (we must not close it before we have dupped it in both places, + * and we must not attempt to close it twice). + */ + for (elem = child_close; elem != NULL; elem = elem->next) + { + r = posix_spawn_file_actions_addclose (&file_actions, + GPOINTER_TO_INT (elem->data)); + if (r != 0) + goto out_close_fds; + } + + argv_pass = file_and_argv_zero ? argv + 1 : argv; + if (envp == NULL) + envp = environ; + + /* Don't search when it contains a slash. */ + if (!search_path || strchr (argv[0], '/') != NULL) + r = posix_spawn (&pid, argv[0], &file_actions, &attr, argv_pass, envp); + else + r = posix_spawnp (&pid, argv[0], &file_actions, &attr, argv_pass, envp); + + if (r == 0 && child_pid != NULL) + *child_pid = pid; + +out_close_fds: + for (i = 0; i < num_parent_close_fds; i++) + close_and_invalidate (&parent_close_fds [i]); + + posix_spawn_file_actions_destroy (&file_actions); +out_free_spawnattr: + posix_spawnattr_destroy (&attr); + g_slist_free (child_close); + + return r; +} +#endif /* POSIX_SPAWN_AVAILABLE */ + +static gboolean +fork_exec_with_fds (gboolean intermediate_child, + const gchar *working_directory, + gchar **argv, + gchar **envp, + gboolean close_descriptors, + gboolean search_path, + gboolean search_path_from_envp, + gboolean stdout_to_null, + gboolean stderr_to_null, + gboolean child_inherits_stdin, + gboolean file_and_argv_zero, + gboolean cloexec_pipes, + GSpawnChildSetupFunc child_setup, + gpointer user_data, + GPid *child_pid, + gint *child_close_fds, + gint stdin_fd, + gint stdout_fd, + gint stderr_fd, + GError **error) { GPid pid = -1; - gint stdin_pipe[2] = { -1, -1 }; - gint stdout_pipe[2] = { -1, -1 }; - gint stderr_pipe[2] = { -1, -1 }; gint child_err_report_pipe[2] = { -1, -1 }; gint child_pid_report_pipe[2] = { -1, -1 }; guint pipe_flags = cloexec_pipes ? FD_CLOEXEC : 0; gint status; - + +#ifdef POSIX_SPAWN_AVAILABLE + if (!intermediate_child && working_directory == NULL && !close_descriptors && + !search_path_from_envp && child_setup == NULL) + { + g_debug ("Launching with posix_spawn"); + status = do_posix_spawn (argv, + envp, + search_path, + stdout_to_null, + stderr_to_null, + child_inherits_stdin, + file_and_argv_zero, + child_pid, + child_close_fds, + stdin_fd, + stdout_fd, + stderr_fd); + if (status == 0) + return TRUE; + + if (status != ENOEXEC) + { + g_set_error (error, + G_SPAWN_ERROR, + G_SPAWN_ERROR_FAILED, + _("Failed to spawn child process \"%s\" (%s)"), + argv[0], + g_strerror (status)); + return FALSE; + } + + /* posix_spawn is not intended to support script execution. It does in + * some situations on some glibc versions, but that will be fixed. + * So if it fails with ENOEXEC, we fall through to the regular + * gspawn codepath so that script execution can be attempted, + * per standard gspawn behaviour. */ + g_debug ("posix_spawn failed (ENOEXEC), fall back to regular gspawn"); + } + else + { + g_debug ("posix_spawn avoided %s%s%s%s%s", + !intermediate_child ? "" : "(automatic reaping requested) ", + working_directory == NULL ? "" : "(workdir specified) ", + !close_descriptors ? "" : "(fd close requested) ", + !search_path_from_envp ? "" : "(using envp for search path) ", + child_setup == NULL ? "" : "(child_setup specified) "); + } +#endif /* POSIX_SPAWN_AVAILABLE */ + if (!g_unix_open_pipe (child_err_report_pipe, pipe_flags, error)) return FALSE; if (intermediate_child && !g_unix_open_pipe (child_pid_report_pipe, pipe_flags, error)) goto cleanup_and_fail; - if (standard_input && !g_unix_open_pipe (stdin_pipe, pipe_flags, error)) - goto cleanup_and_fail; - - if (standard_output && !g_unix_open_pipe (stdout_pipe, pipe_flags, error)) - goto cleanup_and_fail; - - if (standard_error && !g_unix_open_pipe (stderr_pipe, FD_CLOEXEC, error)) - goto cleanup_and_fail; - pid = fork (); if (pid < 0) @@ -1313,9 +1650,12 @@ fork_exec_with_pipes (gboolean intermediate_child, */ close_and_invalidate (&child_err_report_pipe[0]); close_and_invalidate (&child_pid_report_pipe[0]); - close_and_invalidate (&stdin_pipe[1]); - close_and_invalidate (&stdout_pipe[0]); - close_and_invalidate (&stderr_pipe[0]); + if (child_close_fds != NULL) + { + int i = -1; + while (child_close_fds[++i] != -1) + close_and_invalidate (&child_close_fds[i]); + } if (intermediate_child) { @@ -1341,9 +1681,9 @@ fork_exec_with_pipes (gboolean intermediate_child, { close_and_invalidate (&child_pid_report_pipe[1]); do_exec (child_err_report_pipe[1], - stdin_pipe[0], - stdout_pipe[1], - stderr_pipe[1], + stdin_fd, + stdout_fd, + stderr_fd, working_directory, argv, envp, @@ -1371,9 +1711,9 @@ fork_exec_with_pipes (gboolean intermediate_child, */ do_exec (child_err_report_pipe[1], - stdin_pipe[0], - stdout_pipe[1], - stderr_pipe[1], + stdin_fd, + stdout_fd, + stderr_fd, working_directory, argv, envp, @@ -1398,9 +1738,6 @@ fork_exec_with_pipes (gboolean intermediate_child, /* Close the uncared-about ends of the pipes */ close_and_invalidate (&child_err_report_pipe[1]); close_and_invalidate (&child_pid_report_pipe[1]); - close_and_invalidate (&stdin_pipe[0]); - close_and_invalidate (&stdout_pipe[1]); - close_and_invalidate (&stderr_pipe[1]); /* If we had an intermediate child, reap it */ if (intermediate_child) @@ -1513,13 +1850,6 @@ fork_exec_with_pipes (gboolean intermediate_child, if (child_pid) *child_pid = pid; - if (standard_input) - *standard_input = stdin_pipe[1]; - if (standard_output) - *standard_output = stdout_pipe[0]; - if (standard_error) - *standard_error = stderr_pipe[0]; - return TRUE; } @@ -1548,6 +1878,92 @@ fork_exec_with_pipes (gboolean intermediate_child, 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; +} + +static gboolean +fork_exec_with_pipes (gboolean intermediate_child, + const gchar *working_directory, + gchar **argv, + gchar **envp, + gboolean close_descriptors, + gboolean search_path, + gboolean search_path_from_envp, + gboolean stdout_to_null, + gboolean stderr_to_null, + gboolean child_inherits_stdin, + gboolean file_and_argv_zero, + gboolean cloexec_pipes, + GSpawnChildSetupFunc child_setup, + gpointer user_data, + GPid *child_pid, + gint *standard_input, + gint *standard_output, + gint *standard_error, + GError **error) +{ + guint pipe_flags = cloexec_pipes ? FD_CLOEXEC : 0; + gint stdin_pipe[2] = { -1, -1 }; + gint stdout_pipe[2] = { -1, -1 }; + gint stderr_pipe[2] = { -1, -1 }; + gint child_close_fds[4]; + gboolean ret; + + if (standard_input && !g_unix_open_pipe (stdin_pipe, pipe_flags, error)) + goto cleanup_and_fail; + + if (standard_output && !g_unix_open_pipe (stdout_pipe, pipe_flags, error)) + goto cleanup_and_fail; + + if (standard_error && !g_unix_open_pipe (stderr_pipe, FD_CLOEXEC, error)) + goto cleanup_and_fail; + + child_close_fds[0] = stdin_pipe[1]; + child_close_fds[1] = stdout_pipe[0]; + child_close_fds[2] = stderr_pipe[0]; + child_close_fds[3] = -1; + + ret = fork_exec_with_fds (intermediate_child, + working_directory, + argv, + envp, + close_descriptors, + search_path, + search_path_from_envp, + stdout_to_null, + stderr_to_null, + child_inherits_stdin, + file_and_argv_zero, + pipe_flags, + child_setup, + user_data, + child_pid, + child_close_fds, + stdin_pipe[0], + stdout_pipe[1], + stderr_pipe[1], + error); + if (!ret) + goto cleanup_and_fail; + + /* Close the uncared-about ends of the pipes */ + close_and_invalidate (&stdin_pipe[0]); + close_and_invalidate (&stdout_pipe[1]); + close_and_invalidate (&stderr_pipe[1]); + + if (standard_input) + *standard_input = stdin_pipe[1]; + + if (standard_output) + *standard_output = stdout_pipe[0]; + + if (standard_error) + *standard_error = stderr_pipe[0]; + + return TRUE; + +cleanup_and_fail: close_and_invalidate (&stdin_pipe[0]); close_and_invalidate (&stdin_pipe[1]); close_and_invalidate (&stdout_pipe[0]); diff --git a/glib/gspawn.h b/glib/gspawn.h index 055743ea2..d6b0be7d0 100644 --- a/glib/gspawn.h +++ b/glib/gspawn.h @@ -215,6 +215,19 @@ gboolean g_spawn_async_with_pipes (const gchar *working_directory, gint *standard_error, GError **error); +/* Lets you provide fds for stdin/stdout/stderr */ +GLIB_AVAILABLE_IN_2_58 +gboolean g_spawn_async_with_fds (const gchar *working_directory, + gchar **argv, + gchar **envp, + GSpawnFlags flags, + GSpawnChildSetupFunc child_setup, + gpointer user_data, + GPid *child_pid, + gint stdin_fd, + gint stdout_fd, + gint stderr_fd, + GError **error); /* If standard_output or standard_error are non-NULL, the full * standard output or error of the command will be placed there. diff --git a/glib/tests/spawn-singlethread.c b/glib/tests/spawn-singlethread.c index 6a07df736..212291b57 100644 --- a/glib/tests/spawn-singlethread.c +++ b/glib/tests/spawn-singlethread.c @@ -25,8 +25,14 @@ #include <glib.h> #include <string.h> +#include <fcntl.h> + +#ifdef G_OS_UNIX +#include <glib-unix.h> +#endif #ifdef G_OS_WIN32 +#include <io.h> #define LINEEND "\r\n" #else #define LINEEND "\n" @@ -156,6 +162,145 @@ test_spawn_async (void) g_free (arg); } +/* Windows close() causes failure through the Invalid Parameter Handler + * Routine if the file descriptor does not exist. + */ +static void +sane_close (int fd) +{ + if (fd >= 0) + close (fd); +} + +/* Test g_spawn_async_with_fds() with a variety of different inputs */ +static void +test_spawn_async_with_fds (void) +{ + int tnum = 1; + GPtrArray *argv; + char *arg; + int i; + + /* Each test has 3 variable parameters: stdin, stdout, stderr */ + enum fd_type { + NO_FD, /* don't pass a fd */ + PIPE, /* pass fd of new/unique pipe */ + STDOUT_PIPE, /* pass the same pipe as stdout */ + } tests[][3] = { + { NO_FD, NO_FD, NO_FD }, /* Test with no fds passed */ + { PIPE, PIPE, PIPE }, /* Test with unique fds passed */ + { NO_FD, PIPE, STDOUT_PIPE }, /* Test the same fd for stdout + stderr */ + }; + + arg = g_strdup_printf ("thread %d", tnum); + + argv = g_ptr_array_new (); + g_ptr_array_add (argv, echo_prog_path); + g_ptr_array_add (argv, arg); + g_ptr_array_add (argv, NULL); + + for (i = 0; i < G_N_ELEMENTS (tests); i++) + { + GError *error = NULL; + GPid pid; + GMainContext *context; + GMainLoop *loop; + GIOChannel *channel = NULL; + GSource *source; + SpawnAsyncMultithreadedData data; + enum fd_type *fd_info = tests[i]; + gint test_pipe[3][2]; + int j; + + for (j = 0; j < 3; j++) + { + switch (fd_info[j]) + { + case NO_FD: + test_pipe[j][0] = -1; + test_pipe[j][1] = -1; + break; + case PIPE: +#ifdef G_OS_UNIX + g_unix_open_pipe (test_pipe[j], FD_CLOEXEC, &error); + g_assert_no_error (error); +#else + g_assert_cmpint (_pipe (test_pipe[j], 4096, _O_BINARY), >=, 0); +#endif + break; + case STDOUT_PIPE: + g_assert_cmpint (j, ==, 2); /* only works for stderr */ + test_pipe[j][0] = test_pipe[1][0]; + test_pipe[j][1] = test_pipe[1][1]; + break; + default: + g_assert_not_reached (); + } + } + + context = g_main_context_new (); + loop = g_main_loop_new (context, TRUE); + + g_spawn_async_with_fds (NULL, (char**)argv->pdata, NULL, + G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, + test_pipe[0][0], test_pipe[1][1], test_pipe[2][1], + &error); + g_assert_no_error (error); + sane_close (test_pipe[0][0]); + sane_close (test_pipe[1][1]); + if (fd_info[2] != STDOUT_PIPE) + sane_close (test_pipe[2][1]); + + data.loop = loop; + data.stdout_done = FALSE; + data.child_exited = FALSE; + data.stdout_buf = g_string_new (0); + + source = g_child_watch_source_new (pid); + g_source_set_callback (source, (GSourceFunc)on_child_exited, &data, NULL); + g_source_attach (source, context); + g_source_unref (source); + + if (test_pipe[1][0] != -1) + { + channel = g_io_channel_unix_new (test_pipe[1][0]); + source = g_io_create_watch (channel, G_IO_IN | G_IO_HUP | G_IO_ERR); + g_source_set_callback (source, (GSourceFunc)on_child_stdout, + &data, NULL); + g_source_attach (source, context); + g_source_unref (source); + } + else + { + /* Don't check stdout data if we didn't pass a fd */ + data.stdout_done = TRUE; + } + + g_main_loop_run (loop); + + g_assert_true (data.child_exited); + + if (test_pipe[1][0] != -1) + { + /* Check for echo on stdout */ + g_assert_true (data.stdout_done); + g_assert_cmpstr (data.stdout_buf->str, ==, arg); + g_io_channel_unref (channel); + } + g_string_free (data.stdout_buf, TRUE); + + g_main_context_unref (context); + g_main_loop_unref (loop); + sane_close (test_pipe[0][1]); + sane_close (test_pipe[1][0]); + if (fd_info[2] != STDOUT_PIPE) + sane_close (test_pipe[2][0]); + } + + g_ptr_array_free (argv, TRUE); + g_free (arg); +} + static void test_spawn_sync (void) { @@ -181,6 +326,35 @@ test_spawn_sync (void) g_ptr_array_free (argv, TRUE); } +/* Like test_spawn_sync but uses spawn flags that trigger the optimized + * posix_spawn codepath. + */ +static void +test_posix_spawn (void) +{ + int tnum = 1; + GError *error = NULL; + GPtrArray *argv; + char *arg; + char *stdout_str; + int estatus; + GSpawnFlags flags = G_SPAWN_CLOEXEC_PIPES | G_SPAWN_LEAVE_DESCRIPTORS_OPEN; + + arg = g_strdup_printf ("thread %d", tnum); + + argv = g_ptr_array_new (); + g_ptr_array_add (argv, echo_prog_path); + g_ptr_array_add (argv, arg); + g_ptr_array_add (argv, NULL); + + g_spawn_sync (NULL, (char**)argv->pdata, NULL, flags, NULL, NULL, &stdout_str, NULL, &estatus, &error); + g_assert_no_error (error); + g_assert_cmpstr (arg, ==, stdout_str); + g_free (arg); + g_free (stdout_str); + g_ptr_array_free (argv, TRUE); +} + static void test_spawn_script (void) { @@ -251,8 +425,10 @@ main (int argc, g_test_add_func ("/gthread/spawn-single-sync", test_spawn_sync); g_test_add_func ("/gthread/spawn-single-async", test_spawn_async); + g_test_add_func ("/gthread/spawn-single-async-with-fds", test_spawn_async_with_fds); g_test_add_func ("/gthread/spawn-script", test_spawn_script); g_test_add_func ("/gthread/spawn/nonexistent", test_spawn_nonexistent); + g_test_add_func ("/gthread/spawn-posix-spawn", test_posix_spawn); ret = g_test_run(); diff --git a/meson.build b/meson.build index 6edfeafe4..ef17ba309 100644 --- a/meson.build +++ b/meson.build @@ -487,6 +487,11 @@ if cc.has_function('posix_memalign', prefix : '#include <stdlib.h>') glib_conf.set('HAVE_POSIX_MEMALIGN', 1) endif +# Check that posix_spawn() is usable; must use header +if cc.has_function('posix_spawn', prefix : '#include <spawn.h>') + glib_conf.set('HAVE_POSIX_SPAWN', 1) +endif + # Check whether strerror_r returns char * if have_func_strerror_r if cc.compiles('''#define _GNU_SOURCE |