diff --git a/glib/gspawn.c b/glib/gspawn.c index c37ac7c84..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 @@ -1819,7 +1821,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,25 +1836,58 @@ 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; + 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) + 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 * 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; } @@ -2099,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; @@ -2134,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; } 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..378c203c7 --- /dev/null +++ b/glib/tests/spawn-path-search-helper.c @@ -0,0 +1,171 @@ +/* + * 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; + gboolean unset_path_in_envp = 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", }, + { "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 }, + { 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 && 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; + + 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..f4278f3e0 --- /dev/null +++ b/glib/tests/spawn-path-search.c @@ -0,0 +1,454 @@ +/* + * 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); +} + +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) +{ + 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); + 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 (); +} 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; +}