From 0d50c6bbe6be4ccc145452e8c8d2a8f853073870 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Wed, 27 Jan 2021 10:53:22 +0000 Subject: [PATCH 1/4] spawn: Don't set a search path if we don't want to search PATH MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit do_exec() and g_execute() rely on being passed a NULL search path if we intend to avoid searching the PATH, but since the refactoring in commit 62ce66d4, this was never done. This resulted in some spawn calls searching the PATH when it was not intended. Spawn calls that go through the posix_spawn fast-path were unaffected. The deprecated gtester utility, as used in GTK 3, relies on the ability to run an executable from the current working directory by omitting the G_SPAWN_SEARCH_PATH flag. This *mostly* worked, because our fallback PATH ends with ".". However, if an executable of the same name existed in /usr/bin or /bin, it would run that instead of the intended test: in particular, GTK 3's build-time tests failed if ImageMagick happens to be installed, because gtester would accidentally run display(1) instead of testsuite/gdk/display. Fixes: 62ce66d4 "gspawn: Don’t use getenv() in async-signal-safe context" Bug-Debian: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=977961 --- glib/gspawn.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/glib/gspawn.c b/glib/gspawn.c index c37ac7c84..0b9473022 100644 --- a/glib/gspawn.c +++ b/glib/gspawn.c @@ -1819,7 +1819,7 @@ fork_exec_with_fds (gboolean intermediate_child, if (search_path && chosen_search_path == NULL) chosen_search_path = g_getenv ("PATH"); - if (chosen_search_path == NULL) + if ((search_path || search_path_from_envp) && chosen_search_path == NULL) { /* There is no 'PATH' in the environment. The default * * search path in libc is the current directory followed by @@ -1834,14 +1834,27 @@ fork_exec_with_fds (gboolean intermediate_child, chosen_search_path = "/bin:/usr/bin:."; } + if (search_path || search_path_from_envp) + g_assert (chosen_search_path != NULL); + else + g_assert (chosen_search_path == NULL); + /* Allocate a buffer which the fork()ed child can use to assemble potential * paths for the binary to exec(), combining the argv[0] and elements from * the chosen_search_path. This can’t be done in the child because malloc() * (or alloca()) are not async-signal-safe (see `man 7 signal-safety`). * * Add 2 for the nul terminator and a leading `/`. */ - search_path_buffer_len = strlen (chosen_search_path) + strlen (argv[0]) + 2; - search_path_buffer = g_malloc (search_path_buffer_len); + if (chosen_search_path != NULL) + { + search_path_buffer_len = strlen (chosen_search_path) + strlen (argv[0]) + 2; + search_path_buffer = g_malloc (search_path_buffer_len); + } + + if (search_path || search_path_from_envp) + g_assert (search_path_buffer != NULL); + else + g_assert (search_path_buffer == NULL); /* And allocate a buffer which is 2 elements longer than @argv, so that if * script_execute() has to be called later on, it can build a wrapper argv From 136a9d7ffd5b46e5c65fcc40ed3ca4dd9a0de5d1 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Thu, 28 Jan 2021 18:16:36 +0000 Subject: [PATCH 2/4] Add test coverage for G_SPAWN_SEARCH_PATH For manual test coverage that would reproduce the bug fixed in !1902, copy /bin/true (or any other harmless executable) to /usr/bin/spawn-test-helper. Signed-off-by: Simon McVittie --- glib/tests/meson.build | 17 ++ glib/tests/path-test-subdir/meson.build | 6 + .../path-test-subdir/spawn-test-helper.c | 7 + glib/tests/spawn-path-search-helper.c | 156 +++++++++++ glib/tests/spawn-path-search.c | 254 ++++++++++++++++++ glib/tests/spawn-test-helper.c | 7 + 6 files changed, 447 insertions(+) create mode 100644 glib/tests/path-test-subdir/meson.build create mode 100644 glib/tests/path-test-subdir/spawn-test-helper.c create mode 100644 glib/tests/spawn-path-search-helper.c create mode 100644 glib/tests/spawn-path-search.c create mode 100644 glib/tests/spawn-test-helper.c diff --git a/glib/tests/meson.build b/glib/tests/meson.build index 6eb23e8a7..d35bd16fd 100644 --- a/glib/tests/meson.build +++ b/glib/tests/meson.build @@ -89,6 +89,7 @@ glib_tests = { 'slist' : {}, 'sort' : {}, 'spawn-multithreaded' : {}, + 'spawn-path-search' : {}, 'spawn-singlethread' : {}, 'strfuncs' : {}, 'string' : {}, @@ -235,6 +236,20 @@ foreach test_name, extra_args : glib_tests test(test_name, exe, env : test_env, timeout : timeout, suite : suite) endforeach +executable('spawn-path-search-helper', 'spawn-path-search-helper.c', + c_args : test_cargs, + dependencies : test_deps, + install_dir: installed_tests_execdir, + install: installed_tests_enabled, +) + +executable('spawn-test-helper', 'spawn-test-helper.c', + c_args : test_cargs, + dependencies : test_deps, + install_dir: installed_tests_execdir, + install: installed_tests_enabled, +) + # test-spawn-echo helper binary required by the spawn tests above executable('test-spawn-echo', 'test-spawn-echo.c', c_args : test_cargs, @@ -266,3 +281,5 @@ if not meson.is_cross_build() and host_system != 'windows' ) endif endif + +subdir('path-test-subdir') diff --git a/glib/tests/path-test-subdir/meson.build b/glib/tests/path-test-subdir/meson.build new file mode 100644 index 000000000..351254cd8 --- /dev/null +++ b/glib/tests/path-test-subdir/meson.build @@ -0,0 +1,6 @@ +executable('spawn-test-helper', 'spawn-test-helper.c', + c_args : test_cargs, + dependencies : test_deps, + install_dir: join_paths(installed_tests_execdir, 'path-test-subdir'), + install: installed_tests_enabled, +) diff --git a/glib/tests/path-test-subdir/spawn-test-helper.c b/glib/tests/path-test-subdir/spawn-test-helper.c new file mode 100644 index 000000000..f9f2cee84 --- /dev/null +++ b/glib/tests/path-test-subdir/spawn-test-helper.c @@ -0,0 +1,7 @@ +#include + +int main (void) +{ + fprintf (stderr, "this is spawn-test-helper from path-test-subdir\n"); + return 5; +} diff --git a/glib/tests/spawn-path-search-helper.c b/glib/tests/spawn-path-search-helper.c new file mode 100644 index 000000000..b417c7896 --- /dev/null +++ b/glib/tests/spawn-path-search-helper.c @@ -0,0 +1,156 @@ +/* + * Copyright 2021 Collabora Ltd. + * + * 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 + * . + */ + +#include +#include + +#include + +#ifdef G_OS_UNIX +#include +#include +#endif + +static void +child_setup (gpointer user_data) +{ +} + +typedef struct +{ + int wait_status; + gboolean done; +} ChildStatus; + +static ChildStatus child_status = { -1, FALSE }; + +static void +child_watch_cb (GPid pid, + gint status, + gpointer user_data) +{ + child_status.wait_status = status; + child_status.done = TRUE; +} + +int +main (int argc, + char **argv) +{ + gboolean search_path = FALSE; + gboolean search_path_from_envp = FALSE; + gboolean slow_path = FALSE; + gchar *chdir_child = NULL; + gchar *set_path_in_envp = NULL; + gchar **envp = NULL; + GOptionEntry entries[] = + { + { "chdir-child", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &chdir_child, + "Run PROGRAM in this working directory", NULL }, + { "search-path", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &search_path, + "Search PATH for PROGRAM", NULL }, + { "search-path-from-envp", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &search_path_from_envp, + "Search PATH from specified environment", NULL }, + { "set-path-in-envp", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &set_path_in_envp, + "Set PATH in specified environment to this value", "PATH", }, + { "slow-path", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &slow_path, + "Use a child-setup function to avoid the posix_spawn fast path", NULL }, + { NULL } + }; + GError *error = NULL; + int ret = 1; + GSpawnFlags spawn_flags = G_SPAWN_DO_NOT_REAP_CHILD; + GPid pid; + GOptionContext *context = NULL; + + context = g_option_context_new ("PROGRAM [ARGS...]"); + g_option_context_add_main_entries (context, entries, NULL); + + if (!g_option_context_parse (context, &argc, &argv, &error)) + { + ret = 2; + goto out; + } + + if (argc < 2) + { + g_set_error (&error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, + "Usage: %s [OPTIONS] PROGRAM [ARGS...]", argv[0]); + ret = 2; + goto out; + } + + envp = g_get_environ (); + + if (set_path_in_envp != NULL) + envp = g_environ_setenv (envp, "PATH", set_path_in_envp, TRUE); + + if (search_path) + spawn_flags |= G_SPAWN_SEARCH_PATH; + + if (search_path_from_envp) + spawn_flags |= G_SPAWN_SEARCH_PATH_FROM_ENVP; + + if (!g_spawn_async_with_pipes (chdir_child, + &argv[1], + envp, + spawn_flags, + slow_path ? child_setup : NULL, + NULL, /* user_data */ + &pid, + NULL, /* stdin */ + NULL, /* stdout */ + NULL, /* stderr */ + &error)) + { + ret = 1; + goto out; + } + + g_child_watch_add (pid, child_watch_cb, NULL); + + while (!child_status.done) + g_main_context_iteration (NULL, TRUE); + + g_spawn_close_pid (pid); + +#ifdef G_OS_UNIX + if (WIFEXITED (child_status.wait_status)) + ret = WEXITSTATUS (child_status.wait_status); + else + ret = 1; +#else + ret = child_status.wait_status; +#endif + +out: + if (error != NULL) + fprintf (stderr, "%s\n", error->message); + + g_free (set_path_in_envp); + g_free (chdir_child); + g_clear_error (&error); + g_strfreev (envp); + g_option_context_free (context); + return ret; +} diff --git a/glib/tests/spawn-path-search.c b/glib/tests/spawn-path-search.c new file mode 100644 index 000000000..9fbfd478e --- /dev/null +++ b/glib/tests/spawn-path-search.c @@ -0,0 +1,254 @@ +/* + * Copyright 2021 Collabora Ltd. + * + * 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 + * . + */ + +#include + +#ifdef G_OS_UNIX +#include +#include +#endif + +static void +test_do_not_search (void) +{ + GPtrArray *argv = g_ptr_array_new_with_free_func (g_free); + gchar *here = g_test_build_filename (G_TEST_BUILT, ".", NULL); + gchar *subdir = g_test_build_filename (G_TEST_BUILT, "path-test-subdir", NULL); + gchar **envp = g_get_environ (); + gchar *out = NULL; + gchar *err = NULL; + GError *error = NULL; + int wait_status = -1; + + g_test_summary ("Without G_SPAWN_SEARCH_PATH, spawn-test-helper " + "means ./spawn-test-helper."); + + envp = g_environ_setenv (envp, "PATH", subdir, TRUE); + + g_ptr_array_add (argv, + g_test_build_filename (G_TEST_BUILT, "spawn-path-search-helper", NULL)); + g_ptr_array_add (argv, g_strdup ("--")); + g_ptr_array_add (argv, g_strdup ("spawn-test-helper")); + g_ptr_array_add (argv, NULL); + + g_spawn_sync (here, + (char **) argv->pdata, + envp, + G_SPAWN_DEFAULT, + NULL, /* child setup */ + NULL, /* user data */ + &out, + &err, + &wait_status, + &error); + g_assert_no_error (error); + + g_test_message ("%s", out); + g_test_message ("%s", err); + g_assert_nonnull (strstr (err, "this is spawn-test-helper from glib/tests")); + +#ifdef G_OS_UNIX + g_assert_true (WIFEXITED (wait_status)); + g_assert_cmpint (WEXITSTATUS (wait_status), ==, 0); +#endif + + g_strfreev (envp); + g_free (here); + g_free (subdir); + g_free (out); + g_free (err); + g_ptr_array_unref (argv); +} + +static void +test_search_path (void) +{ + GPtrArray *argv = g_ptr_array_new_with_free_func (g_free); + gchar *here = g_test_build_filename (G_TEST_BUILT, ".", NULL); + gchar *subdir = g_test_build_filename (G_TEST_BUILT, "path-test-subdir", NULL); + gchar **envp = g_get_environ (); + gchar *out = NULL; + gchar *err = NULL; + GError *error = NULL; + int wait_status = -1; + + g_test_summary ("With G_SPAWN_SEARCH_PATH, spawn-test-helper " + "means $PATH/spawn-test-helper."); + + envp = g_environ_setenv (envp, "PATH", subdir, TRUE); + + g_ptr_array_add (argv, + g_test_build_filename (G_TEST_BUILT, "spawn-path-search-helper", NULL)); + g_ptr_array_add (argv, g_strdup ("--search-path")); + g_ptr_array_add (argv, g_strdup ("--")); + g_ptr_array_add (argv, g_strdup ("spawn-test-helper")); + g_ptr_array_add (argv, NULL); + + g_spawn_sync (here, + (char **) argv->pdata, + envp, + G_SPAWN_DEFAULT, + NULL, /* child setup */ + NULL, /* user data */ + &out, + &err, + &wait_status, + &error); + g_assert_no_error (error); + + g_test_message ("%s", out); + g_test_message ("%s", err); + g_assert_nonnull (strstr (err, "this is spawn-test-helper from path-test-subdir")); + +#ifdef G_OS_UNIX + g_assert_true (WIFEXITED (wait_status)); + g_assert_cmpint (WEXITSTATUS (wait_status), ==, 5); +#endif + + g_strfreev (envp); + g_free (here); + g_free (subdir); + g_free (out); + g_free (err); + g_ptr_array_unref (argv); +} + +static void +test_search_path_from_envp (void) +{ + GPtrArray *argv = g_ptr_array_new_with_free_func (g_free); + gchar *here = g_test_build_filename (G_TEST_BUILT, ".", NULL); + gchar *subdir = g_test_build_filename (G_TEST_BUILT, "path-test-subdir", NULL); + gchar **envp = g_get_environ (); + gchar *out = NULL; + gchar *err = NULL; + GError *error = NULL; + int wait_status = -1; + + g_test_summary ("With G_SPAWN_SEARCH_PATH_FROM_ENVP, spawn-test-helper " + "means $PATH/spawn-test-helper with $PATH from envp."); + + envp = g_environ_setenv (envp, "PATH", here, TRUE); + + g_ptr_array_add (argv, + g_test_build_filename (G_TEST_BUILT, "spawn-path-search-helper", NULL)); + g_ptr_array_add (argv, g_strdup ("--search-path-from-envp")); + g_ptr_array_add (argv, g_strdup ("--set-path-in-envp")); + g_ptr_array_add (argv, g_strdup (subdir)); + g_ptr_array_add (argv, g_strdup ("--")); + g_ptr_array_add (argv, g_strdup ("spawn-test-helper")); + g_ptr_array_add (argv, NULL); + + g_spawn_sync (here, + (char **) argv->pdata, + envp, + G_SPAWN_DEFAULT, + NULL, /* child setup */ + NULL, /* user data */ + &out, + &err, + &wait_status, + &error); + g_assert_no_error (error); + + g_test_message ("%s", out); + g_test_message ("%s", err); + g_assert_nonnull (strstr (err, "this is spawn-test-helper from path-test-subdir")); + +#ifdef G_OS_UNIX + g_assert_true (WIFEXITED (wait_status)); + g_assert_cmpint (WEXITSTATUS (wait_status), ==, 5); +#endif + + g_strfreev (envp); + g_free (here); + g_free (subdir); + g_free (out); + g_free (err); + g_ptr_array_unref (argv); +} + +static void +test_search_path_ambiguous (void) +{ + GPtrArray *argv = g_ptr_array_new_with_free_func (g_free); + gchar *here = g_test_build_filename (G_TEST_BUILT, ".", NULL); + gchar *subdir = g_test_build_filename (G_TEST_BUILT, "path-test-subdir", NULL); + gchar **envp = g_get_environ (); + gchar *out = NULL; + gchar *err = NULL; + GError *error = NULL; + int wait_status = -1; + + g_test_summary ("With G_SPAWN_SEARCH_PATH and G_SPAWN_SEARCH_PATH_FROM_ENVP, " + "the latter wins."); + + envp = g_environ_setenv (envp, "PATH", here, TRUE); + + g_ptr_array_add (argv, + g_test_build_filename (G_TEST_BUILT, "spawn-path-search-helper", NULL)); + g_ptr_array_add (argv, g_strdup ("--search-path")); + g_ptr_array_add (argv, g_strdup ("--search-path-from-envp")); + g_ptr_array_add (argv, g_strdup ("--set-path-in-envp")); + g_ptr_array_add (argv, g_strdup (subdir)); + g_ptr_array_add (argv, g_strdup ("--")); + g_ptr_array_add (argv, g_strdup ("spawn-test-helper")); + g_ptr_array_add (argv, NULL); + + g_spawn_sync (here, + (char **) argv->pdata, + envp, + G_SPAWN_DEFAULT, + NULL, /* child setup */ + NULL, /* user data */ + &out, + &err, + &wait_status, + &error); + g_assert_no_error (error); + + g_test_message ("%s", out); + g_test_message ("%s", err); + g_assert_nonnull (strstr (err, "this is spawn-test-helper from path-test-subdir")); + +#ifdef G_OS_UNIX + g_assert_true (WIFEXITED (wait_status)); + g_assert_cmpint (WEXITSTATUS (wait_status), ==, 5); +#endif + + g_strfreev (envp); + g_free (here); + g_free (subdir); + g_free (out); + g_free (err); + g_ptr_array_unref (argv); +} + +int +main (int argc, + char **argv) +{ + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/spawn/do-not-search", test_do_not_search); + g_test_add_func ("/spawn/search-path", test_search_path); + g_test_add_func ("/spawn/search-path-from-envp", test_search_path_from_envp); + g_test_add_func ("/spawn/search-path-ambiguous", test_search_path_ambiguous); + + return g_test_run (); +} diff --git a/glib/tests/spawn-test-helper.c b/glib/tests/spawn-test-helper.c new file mode 100644 index 000000000..301f3f31c --- /dev/null +++ b/glib/tests/spawn-test-helper.c @@ -0,0 +1,7 @@ +#include + +int main (void) +{ + fprintf (stderr, "this is spawn-test-helper from glib/tests\n"); + return 0; +} From 8a3c3b8cd9496b74e0d284d4f4cad01f1bbe07d3 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Fri, 29 Jan 2021 12:26:00 +0100 Subject: [PATCH 3/4] spawn: prefer allocating buffers on stack for small sizes to avoid valgrind leaks We preallocate buffers that are used after forked. That is because malloc()/free() are not async-signal-safe and must not be used between fork() and exec(). However, for the child process that exits without fork, valgrind wrongly reports these buffers as leaked. That can be suppressed with "--child-silent-after-fork=yes", but it is cumbersome. Work around by trying to allocate the buffers on the stack. At least in the common cases where the pointers are small enough so that we can reasonably do that. If the buffers happen to be large, we still allocate them on the heap and the problem still happens. Maybe we could have also allocated them as thread_local, but currently glib doesn't use that. [smcv: Cosmetic adjustments to address review comments from pwithnall] --- glib/gspawn.c | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/glib/gspawn.c b/glib/gspawn.c index 0b9473022..c8e0ca806 100644 --- a/glib/gspawn.c +++ b/glib/gspawn.c @@ -1753,8 +1753,10 @@ fork_exec_with_fds (gboolean intermediate_child, gint status; const gchar *chosen_search_path; gchar *search_path_buffer = NULL; + gchar *search_path_buffer_heap = NULL; gsize search_path_buffer_len = 0; gchar **argv_buffer = NULL; + gchar **argv_buffer_heap = NULL; gsize argv_buffer_len = 0; #ifdef POSIX_SPAWN_AVAILABLE @@ -1848,7 +1850,17 @@ fork_exec_with_fds (gboolean intermediate_child, if (chosen_search_path != NULL) { search_path_buffer_len = strlen (chosen_search_path) + strlen (argv[0]) + 2; - search_path_buffer = g_malloc (search_path_buffer_len); + if (search_path_buffer_len < 4000) + { + /* Prefer small stack allocations to avoid valgrind leak warnings + * in forked child. The 4000B cutoff is arbitrary. */ + search_path_buffer = g_alloca (search_path_buffer_len); + } + else + { + search_path_buffer_heap = g_malloc (search_path_buffer_len); + search_path_buffer = search_path_buffer_heap; + } } if (search_path || search_path_from_envp) @@ -1860,12 +1872,22 @@ fork_exec_with_fds (gboolean intermediate_child, * script_execute() has to be called later on, it can build a wrapper argv * array in this buffer. */ argv_buffer_len = g_strv_length (argv) + 2; - argv_buffer = g_new (gchar *, argv_buffer_len); + if (argv_buffer_len < 4000 / sizeof (gchar *)) + { + /* Prefer small stack allocations to avoid valgrind leak warnings + * in forked child. The 4000B cutoff is arbitrary. */ + argv_buffer = g_newa (gchar *, argv_buffer_len); + } + else + { + argv_buffer_heap = g_new (gchar *, argv_buffer_len); + argv_buffer = argv_buffer_heap; + } if (!g_unix_open_pipe (child_err_report_pipe, pipe_flags, error)) { - g_free (search_path_buffer); - g_free (argv_buffer); + g_free (search_path_buffer_heap); + g_free (argv_buffer_heap); return FALSE; } @@ -2112,8 +2134,8 @@ fork_exec_with_fds (gboolean intermediate_child, close_and_invalidate (&child_err_report_pipe[0]); close_and_invalidate (&child_pid_report_pipe[0]); - g_free (search_path_buffer); - g_free (argv_buffer); + g_free (search_path_buffer_heap); + g_free (argv_buffer_heap); if (child_pid) *child_pid = pid; @@ -2147,8 +2169,8 @@ fork_exec_with_fds (gboolean intermediate_child, close_and_invalidate (&child_pid_report_pipe[0]); close_and_invalidate (&child_pid_report_pipe[1]); - g_free (search_path_buffer); - g_free (argv_buffer); + g_free (search_path_buffer_heap); + g_free (argv_buffer_heap); return FALSE; } From 9eb4fe3c00a84190431f0e71143eafc48235d643 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Sun, 31 Jan 2021 14:01:06 +0000 Subject: [PATCH 4/4] Expand test coverage for G_SPAWN_SEARCH_PATH Signed-off-by: Simon McVittie --- glib/tests/spawn-path-search-helper.c | 15 ++ glib/tests/spawn-path-search.c | 200 ++++++++++++++++++++++++++ 2 files changed, 215 insertions(+) diff --git a/glib/tests/spawn-path-search-helper.c b/glib/tests/spawn-path-search-helper.c index b417c7896..378c203c7 100644 --- a/glib/tests/spawn-path-search-helper.c +++ b/glib/tests/spawn-path-search-helper.c @@ -55,6 +55,7 @@ main (int argc, gboolean search_path = FALSE; gboolean search_path_from_envp = FALSE; gboolean slow_path = FALSE; + gboolean unset_path_in_envp = FALSE; gchar *chdir_child = NULL; gchar *set_path_in_envp = NULL; gchar **envp = NULL; @@ -72,6 +73,9 @@ main (int argc, { "set-path-in-envp", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &set_path_in_envp, "Set PATH in specified environment to this value", "PATH", }, + { "unset-path-in-envp", '\0', + G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &unset_path_in_envp, + "Unset PATH in specified environment", NULL }, { "slow-path", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &slow_path, "Use a child-setup function to avoid the posix_spawn fast path", NULL }, @@ -102,9 +106,20 @@ main (int argc, envp = g_get_environ (); + if (set_path_in_envp != NULL && unset_path_in_envp) + { + g_set_error (&error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, + "Cannot both set PATH and unset it"); + ret = 2; + goto out; + } + if (set_path_in_envp != NULL) envp = g_environ_setenv (envp, "PATH", set_path_in_envp, TRUE); + if (unset_path_in_envp) + envp = g_environ_unsetenv (envp, "PATH"); + if (search_path) spawn_flags |= G_SPAWN_SEARCH_PATH; diff --git a/glib/tests/spawn-path-search.c b/glib/tests/spawn-path-search.c index 9fbfd478e..f4278f3e0 100644 --- a/glib/tests/spawn-path-search.c +++ b/glib/tests/spawn-path-search.c @@ -239,6 +239,200 @@ test_search_path_ambiguous (void) g_ptr_array_unref (argv); } +static void +test_search_path_fallback_in_environ (void) +{ + GPtrArray *argv = g_ptr_array_new_with_free_func (g_free); + gchar *here = g_test_build_filename (G_TEST_BUILT, ".", NULL); + gchar *subdir = g_test_build_filename (G_TEST_BUILT, "path-test-subdir", NULL); + gchar **envp = g_get_environ (); + gchar *out = NULL; + gchar *err = NULL; + GError *error = NULL; + int wait_status = -1; + + g_test_summary ("With G_SPAWN_SEARCH_PATH but no PATH, a fallback is used."); + /* We can't make a meaningful assertion about what the fallback *is*, + * but we can assert that it *includes* the current working directory. */ + + if (g_file_test ("/usr/bin/spawn-test-helper", G_FILE_TEST_IS_EXECUTABLE) || + g_file_test ("/bin/spawn-test-helper", G_FILE_TEST_IS_EXECUTABLE)) + { + g_test_skip ("Not testing fallback with unknown spawn-test-helper " + "executable in /usr/bin:/bin"); + return; + } + + envp = g_environ_unsetenv (envp, "PATH"); + + g_ptr_array_add (argv, + g_test_build_filename (G_TEST_BUILT, "spawn-path-search-helper", NULL)); + g_ptr_array_add (argv, g_strdup ("--search-path")); + g_ptr_array_add (argv, g_strdup ("--set-path-in-envp")); + g_ptr_array_add (argv, g_strdup (subdir)); + g_ptr_array_add (argv, g_strdup ("--")); + g_ptr_array_add (argv, g_strdup ("spawn-test-helper")); + g_ptr_array_add (argv, NULL); + + g_spawn_sync (here, + (char **) argv->pdata, + envp, + G_SPAWN_DEFAULT, + NULL, /* child setup */ + NULL, /* user data */ + &out, + &err, + &wait_status, + &error); + g_assert_no_error (error); + + g_test_message ("%s", out); + g_test_message ("%s", err); + g_assert_nonnull (strstr (err, "this is spawn-test-helper from glib/tests")); + +#ifdef G_OS_UNIX + g_assert_true (WIFEXITED (wait_status)); + g_assert_cmpint (WEXITSTATUS (wait_status), ==, 0); +#endif + + g_strfreev (envp); + g_free (here); + g_free (subdir); + g_free (out); + g_free (err); + g_ptr_array_unref (argv); +} + +static void +test_search_path_fallback_in_envp (void) +{ + GPtrArray *argv = g_ptr_array_new_with_free_func (g_free); + gchar *here = g_test_build_filename (G_TEST_BUILT, ".", NULL); + gchar *subdir = g_test_build_filename (G_TEST_BUILT, "path-test-subdir", NULL); + gchar **envp = g_get_environ (); + gchar *out = NULL; + gchar *err = NULL; + GError *error = NULL; + int wait_status = -1; + + g_test_summary ("With G_SPAWN_SEARCH_PATH_FROM_ENVP but no PATH, a fallback is used."); + /* We can't make a meaningful assertion about what the fallback *is*, + * but we can assert that it *includes* the current working directory. */ + + if (g_file_test ("/usr/bin/spawn-test-helper", G_FILE_TEST_IS_EXECUTABLE) || + g_file_test ("/bin/spawn-test-helper", G_FILE_TEST_IS_EXECUTABLE)) + { + g_test_skip ("Not testing fallback with unknown spawn-test-helper " + "executable in /usr/bin:/bin"); + return; + } + + envp = g_environ_setenv (envp, "PATH", subdir, TRUE); + + g_ptr_array_add (argv, + g_test_build_filename (G_TEST_BUILT, "spawn-path-search-helper", NULL)); + g_ptr_array_add (argv, g_strdup ("--search-path-from-envp")); + g_ptr_array_add (argv, g_strdup ("--unset-path-in-envp")); + g_ptr_array_add (argv, g_strdup ("--")); + g_ptr_array_add (argv, g_strdup ("spawn-test-helper")); + g_ptr_array_add (argv, NULL); + + g_spawn_sync (here, + (char **) argv->pdata, + envp, + G_SPAWN_DEFAULT, + NULL, /* child setup */ + NULL, /* user data */ + &out, + &err, + &wait_status, + &error); + g_assert_no_error (error); + + g_test_message ("%s", out); + g_test_message ("%s", err); + g_assert_nonnull (strstr (err, "this is spawn-test-helper from glib/tests")); + +#ifdef G_OS_UNIX + g_assert_true (WIFEXITED (wait_status)); + g_assert_cmpint (WEXITSTATUS (wait_status), ==, 0); +#endif + + g_strfreev (envp); + g_free (here); + g_free (subdir); + g_free (out); + g_free (err); + g_ptr_array_unref (argv); +} + +static void +test_search_path_heap_allocation (void) +{ + GPtrArray *argv = g_ptr_array_new_with_free_func (g_free); + /* Must be longer than the arbitrary 4000 byte limit for stack allocation + * in gspawn.c */ + char placeholder[4096]; + gchar *here = g_test_build_filename (G_TEST_BUILT, ".", NULL); + gchar *subdir = g_test_build_filename (G_TEST_BUILT, "path-test-subdir", NULL); + gchar *long_dir = NULL; + gchar *long_path = NULL; + gchar **envp = g_get_environ (); + gchar *out = NULL; + gchar *err = NULL; + GError *error = NULL; + int wait_status = -1; + gsize i; + + memset (placeholder, '_', sizeof (placeholder)); + /* Force search_path_buffer to be heap-allocated */ + long_dir = g_test_build_filename (G_TEST_BUILT, "path-test-subdir", placeholder, NULL); + long_path = g_strjoin (G_SEARCHPATH_SEPARATOR_S, subdir, long_dir, NULL); + envp = g_environ_setenv (envp, "PATH", long_path, TRUE); + + g_ptr_array_add (argv, + g_test_build_filename (G_TEST_BUILT, "spawn-path-search-helper", NULL)); + g_ptr_array_add (argv, g_strdup ("--search-path")); + g_ptr_array_add (argv, g_strdup ("--")); + g_ptr_array_add (argv, g_strdup ("spawn-test-helper")); + + /* Add enough arguments to make argv longer than the arbitrary 4000 byte + * limit for stack allocation in gspawn.c. + * This assumes sizeof (char *) >= 4. */ + for (i = 0; i < 1001; i++) + g_ptr_array_add (argv, g_strdup ("_")); + + g_ptr_array_add (argv, NULL); + + g_spawn_sync (here, + (char **) argv->pdata, + envp, + G_SPAWN_DEFAULT, + NULL, /* child setup */ + NULL, /* user data */ + &out, + &err, + &wait_status, + &error); + g_assert_no_error (error); + + g_test_message ("%s", out); + g_test_message ("%s", err); + g_assert_nonnull (strstr (err, "this is spawn-test-helper from path-test-subdir")); + +#ifdef G_OS_UNIX + g_assert_true (WIFEXITED (wait_status)); + g_assert_cmpint (WEXITSTATUS (wait_status), ==, 5); +#endif + + g_strfreev (envp); + g_free (here); + g_free (subdir); + g_free (out); + g_free (err); + g_ptr_array_unref (argv); +} + int main (int argc, char **argv) @@ -249,6 +443,12 @@ main (int argc, g_test_add_func ("/spawn/search-path", test_search_path); g_test_add_func ("/spawn/search-path-from-envp", test_search_path_from_envp); g_test_add_func ("/spawn/search-path-ambiguous", test_search_path_ambiguous); + g_test_add_func ("/spawn/search-path-heap-allocation", + test_search_path_heap_allocation); + g_test_add_func ("/spawn/search-path-fallback-in-environ", + test_search_path_fallback_in_environ); + g_test_add_func ("/spawn/search-path-fallback-in-envp", + test_search_path_fallback_in_envp); return g_test_run (); }