summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip Withnall <philip@tecnocode.co.uk>2018-06-21 17:10:43 +0000
committerPhilip Withnall <philip@tecnocode.co.uk>2018-06-21 17:10:43 +0000
commitca98ce42804c5bb70e15bd58da76b9d237564296 (patch)
treec99e17c256ae9256cb3d63792dada5e068923dc5
parent67001014957496b64a325abf9dc05550f8b018a1 (diff)
parent2b560457a0867639ceab8d04490e0ef902470799 (diff)
downloadglib-ca98ce42804c5bb70e15bd58da76b9d237564296.tar.gz
Merge branch 'master' into 'master'
Use posix_spawn for optimized process launching See merge request GNOME/glib!95
-rw-r--r--configure.ac2
-rw-r--r--docs/reference/gio/gio-sections.txt1
-rw-r--r--docs/reference/glib/glib-sections.txt1
-rw-r--r--gio/Makefile.am4
-rw-r--r--gio/gdesktopappinfo.c205
-rw-r--r--gio/gdesktopappinfo.h14
-rw-r--r--gio/gio-launch-desktop.c52
-rw-r--r--gio/meson.build6
-rw-r--r--gio/tests/Makefile.am4
-rw-r--r--gio/tests/desktop-app-info.c41
-rw-r--r--gio/tests/meson.build1
-rw-r--r--glib/gspawn-win32.c163
-rw-r--r--glib/gspawn.c540
-rw-r--r--glib/gspawn.h13
-rw-r--r--glib/tests/spawn-singlethread.c176
-rw-r--r--meson.build5
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