506 lines
17 KiB
Diff
506 lines
17 KiB
Diff
commit 229f65f5f322609283c7104c80c8af6434dff628
|
|
Author: Florian Weimer <fweimer@redhat.com>
|
|
Date: Mon Feb 2 21:15:48 2026 +0100
|
|
|
|
support: Add support_spawn_wrap and related functionality
|
|
|
|
It allows us to write test cases in C that run tests with
|
|
dynamic linker wrapping.
|
|
|
|
The iconv test case was auto-generated. The posix_spawn usage
|
|
is mechanical, and the interface it tests is newly added in this
|
|
commit, so this should be acceptable.
|
|
|
|
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
|
|
|
|
Conflicts:
|
|
support/Makefile
|
|
(Remove duplicate entry for support_stack_alloc)
|
|
|
|
diff -Nrup a/support/Makefile b/support/Makefile
|
|
--- a/support/Makefile 2026-02-23 11:21:06.705964233 -0500
|
|
+++ b/support/Makefile 2026-02-23 11:46:54.560737380 -0500
|
|
@@ -90,7 +90,7 @@ libsupport-routines = \
|
|
support_shared_allocate \
|
|
support_small_stack_thread_attribute \
|
|
support_socket_so_timestamp_time64 \
|
|
- support_stack_alloc \
|
|
+ support_spawn_wrap \
|
|
support_stack_alloc \
|
|
support_stat_nanoseconds \
|
|
support_subprocess \
|
|
@@ -258,6 +258,11 @@ else
|
|
CFLAGS-support_paths.c += -DHARDCODED_PATHS_IN_TEST=false
|
|
endif
|
|
|
|
+CFLAGS-support_spawn_wrap.c += \
|
|
+ '-DRUN_PROGRAM_ENV=$(patsubst %, ELEMENT ("%"), $(run-program-env))' \
|
|
+ '-DRTLD_PREFIX=$(patsubst %, ELEMENT ("%"), $(rtld-prefix))' \
|
|
+ # CFLAGS-support_spawn_wrap.c
|
|
+
|
|
# In support_timespec_check_in_range we may be passed a very tight
|
|
# range for which we should produce a correct result for expected
|
|
# being withing the observed range. The code uses double internally
|
|
@@ -343,6 +348,7 @@ tests = \
|
|
tst-support_quote_string \
|
|
tst-support_readdir \
|
|
tst-support_record_failure \
|
|
+ tst-support_spawn_wrap \
|
|
tst-test_compare \
|
|
tst-test_compare_blob \
|
|
tst-test_compare_string \
|
|
diff -Nrup a/support/subprocess.h b/support/subprocess.h
|
|
--- a/support/subprocess.h 2021-08-01 21:33:43.000000000 -0400
|
|
+++ b/support/subprocess.h 2026-02-23 11:22:26.578465391 -0500
|
|
@@ -51,4 +51,36 @@ int support_process_wait (struct support
|
|
then with a SIGKILL. Return the status as for waitpid call. */
|
|
int support_process_terminate (struct support_subprocess *proc);
|
|
|
|
+/* Arguments to pass to posix_spawn and related functions to run a
|
|
+ process under the built glibc. This overrides the dynamic linker,
|
|
+ its search path, and other search paths, such as for locales. */
|
|
+struct support_spawn_wrapped
|
|
+{
|
|
+ const char *path;
|
|
+ char *const *argv;
|
|
+ char *const *envp;
|
|
+};
|
|
+
|
|
+enum support_spawn_wrap_flags
|
|
+ {
|
|
+ /* Always wrap the invocation, even if test binaries are linked
|
|
+ with overridden the default paths to point into the build tree
|
|
+ (--enable-hardcoded-path-in-tests). Can be used to run
|
|
+ non-test binaries. */
|
|
+ support_spawn_wrap_force = 1 << 0,
|
|
+ };
|
|
+
|
|
+/* Wrap the invocation for invoking testing. PATH is the program
|
|
+ path. If ARGV is null, no arguments are passed. If ENVP is null,
|
|
+ environ is used instead. The result must not be modified. It is a
|
|
+ deep copy of the inputs. */
|
|
+struct support_spawn_wrapped *support_spawn_wrap (const char *path,
|
|
+ char *const argv[],
|
|
+ char *const envp[],
|
|
+ enum
|
|
+ support_spawn_wrap_flags);
|
|
+
|
|
+/* Deallocate the result of support_spawn_wrap. */
|
|
+void support_spawn_wrapped_free (struct support_spawn_wrapped *);
|
|
+
|
|
#endif
|
|
diff -Nrup a/support/support_spawn_wrap.c b/support/support_spawn_wrap.c
|
|
--- a/support/support_spawn_wrap.c 1969-12-31 19:00:00.000000000 -0500
|
|
+++ b/support/support_spawn_wrap.c 2026-02-23 11:22:26.578713214 -0500
|
|
@@ -0,0 +1,171 @@
|
|
+/* Wrap a subprocess invocation with an ld.so invocation.
|
|
+ Copyright (C) 2026 Free Software Foundation, Inc.
|
|
+ This file is part of the GNU C Library.
|
|
+
|
|
+ The GNU C 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.
|
|
+
|
|
+ The GNU C 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 the GNU C Library; if not, see
|
|
+ <https://www.gnu.org/licenses/>. */
|
|
+
|
|
+#include <array_length.h>
|
|
+#include <stdbool.h>
|
|
+#include <stdlib.h>
|
|
+#include <string.h>
|
|
+#include <support/check.h>
|
|
+#include <support/subprocess.h>
|
|
+#include <support/support.h>
|
|
+#include <unistd.h>
|
|
+
|
|
+#define ELEMENT(s) s,
|
|
+static const char *const rtld_prefix[] = { RTLD_PREFIX "--argv0" };
|
|
+static const char *const run_program_env[] = { RUN_PROGRAM_ENV };
|
|
+#undef ELEMENT
|
|
+
|
|
+/* Return a newly allocated argument vector, with ld.so wrapping per
|
|
+ rtld_prefix applied if WRAP is true. */
|
|
+static char *const *
|
|
+rewrite_argv (const char *path, char *const argv[], bool wrap)
|
|
+{
|
|
+ char *const substitute[] = { (char *) path, NULL};
|
|
+ if (argv == NULL)
|
|
+ argv = substitute;
|
|
+ TEST_VERIFY (argv[0] != NULL);
|
|
+
|
|
+ size_t length;
|
|
+ for (length = 0; argv[length] != 0; ++length)
|
|
+ ;
|
|
+ /* Potential wrapping, injected path, and null terminator. */
|
|
+ length += array_length (rtld_prefix) + 1 + 1;
|
|
+
|
|
+ char **result = xcalloc (length, sizeof (result));
|
|
+
|
|
+ size_t inpos = 0;
|
|
+ size_t outpos = 0;
|
|
+ if (wrap)
|
|
+ {
|
|
+ for (size_t i = 0; i < array_length (rtld_prefix); ++i)
|
|
+ {
|
|
+ TEST_VERIFY (outpos < length);
|
|
+ result[outpos++] = xstrdup (rtld_prefix[i]);
|
|
+ }
|
|
+
|
|
+ /* --argv0 argument. */
|
|
+ TEST_VERIFY (outpos < length);
|
|
+ result[outpos++] = xstrdup (argv[0]);
|
|
+ inpos = 1;
|
|
+
|
|
+ /* Path to program as used by ld.so. */
|
|
+ TEST_VERIFY (outpos < length);
|
|
+ result[outpos++] = xstrdup (path);
|
|
+ }
|
|
+
|
|
+ for (; argv[inpos] != NULL; ++inpos)
|
|
+ {
|
|
+ TEST_VERIFY (outpos < length);
|
|
+ result[outpos++] = xstrdup (argv[inpos]);
|
|
+ }
|
|
+
|
|
+ TEST_VERIFY (outpos < length);
|
|
+ return result;
|
|
+
|
|
+}
|
|
+
|
|
+/* Return a newly allocated, rewritten environment, with the settings
|
|
+ from run_program_env. */
|
|
+static char *const *
|
|
+rewrite_env (char *const envp[])
|
|
+{
|
|
+ if (envp == NULL)
|
|
+ envp = environ;
|
|
+
|
|
+ size_t length;
|
|
+ for (length = 0; envp[length] != 0; ++length)
|
|
+ ;
|
|
+ length += array_length (run_program_env) + 1;
|
|
+
|
|
+ /* Set to true if an element of run_program_env is copied. This is
|
|
+ used to avoid adding it again. */
|
|
+ bool copied[array_length (run_program_env)] = { false, };
|
|
+
|
|
+ char **result = xcalloc (length, sizeof (result));
|
|
+ size_t outpos = 0;
|
|
+ for (size_t inpos = 0; envp[inpos] != NULL; ++inpos)
|
|
+ {
|
|
+ const char *to_copy = envp[inpos];
|
|
+ /* If there is no assignment operator, this environment string
|
|
+ cannot be overridden. */
|
|
+ const char *envp_assign = strchr (to_copy, '=');
|
|
+ if (envp_assign != NULL)
|
|
+ {
|
|
+ size_t length_with_assign = envp_assign - to_copy + 1;
|
|
+ for (size_t i = 0; i < array_length (run_program_env); ++i)
|
|
+ {
|
|
+ if (strncmp (to_copy, run_program_env[i], length_with_assign)
|
|
+ == 0 && !copied[i])
|
|
+ {
|
|
+ to_copy = run_program_env[i];
|
|
+ copied[i] = true;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ TEST_VERIFY (outpos < length);
|
|
+ result[outpos++] = xstrdup (to_copy);
|
|
+ }
|
|
+
|
|
+ for (size_t i = 0; i < array_length (run_program_env); ++i)
|
|
+ {
|
|
+ TEST_VERIFY (strchr (run_program_env[i], '=') != 0);
|
|
+ if (!copied[i])
|
|
+ {
|
|
+ TEST_VERIFY (outpos < length);
|
|
+ result[outpos++] = xstrdup (run_program_env[i]);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ TEST_VERIFY (outpos < length);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+struct support_spawn_wrapped *
|
|
+support_spawn_wrap (const char *path,
|
|
+ char *const argv[],
|
|
+ char *const envp[],
|
|
+ enum support_spawn_wrap_flags flags)
|
|
+{
|
|
+ if (flags != 0)
|
|
+ TEST_COMPARE (flags, support_spawn_wrap_force);
|
|
+ bool force = flags & support_spawn_wrap_force;
|
|
+ bool wrap = force || !support_hardcoded_paths_in_test;
|
|
+
|
|
+ struct support_spawn_wrapped *result = xmalloc (sizeof (*result));
|
|
+ if (wrap)
|
|
+ result->path = xstrdup (support_objdir_elf_ldso);
|
|
+ else
|
|
+ result->path = xstrdup (path);
|
|
+ result->argv = rewrite_argv (path, argv, wrap);
|
|
+ result->envp = rewrite_env (envp);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+void
|
|
+support_spawn_wrapped_free (struct support_spawn_wrapped *wrapped)
|
|
+{
|
|
+ free ((char *) wrapped->path);
|
|
+ for (size_t i = 0; wrapped->argv[i] != NULL; ++i)
|
|
+ free (wrapped->argv[i]);
|
|
+ free ((char **) wrapped->argv);
|
|
+ for (size_t i = 0; wrapped->envp[i] != NULL; ++i)
|
|
+ free (wrapped->envp[i]);
|
|
+ free ((char **) wrapped->envp);
|
|
+ free (wrapped);
|
|
+}
|
|
diff -Nrup a/support/tst-support_spawn_wrap.c b/support/tst-support_spawn_wrap.c
|
|
--- a/support/tst-support_spawn_wrap.c 1969-12-31 19:00:00.000000000 -0500
|
|
+++ b/support/tst-support_spawn_wrap.c 2026-02-23 11:22:26.578942243 -0500
|
|
@@ -0,0 +1,235 @@
|
|
+/* Tests for support_spawn_wrap.
|
|
+ Copyright (C) 2026 Free Software Foundation, Inc.
|
|
+ This file is part of the GNU C Library.
|
|
+
|
|
+ The GNU C 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.
|
|
+
|
|
+ The GNU C 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 the GNU C Library; if not, see
|
|
+ <https://www.gnu.org/licenses/>. */
|
|
+
|
|
+#include <getopt.h>
|
|
+#include <spawn.h>
|
|
+#include <stdlib.h>
|
|
+#include <string.h>
|
|
+#include <support/capture_subprocess.h>
|
|
+#include <support/check.h>
|
|
+#include <support/subprocess.h>
|
|
+#include <support/support.h>
|
|
+#include <support/xspawn.h>
|
|
+#include <support/xunistd.h>
|
|
+#include <sys/wait.h>
|
|
+#include <unistd.h>
|
|
+
|
|
+/* Return true if running via an explicit ld.so invocation. */
|
|
+static bool
|
|
+running_via_ldso (void)
|
|
+{
|
|
+ char *self = realpath ("/proc/self/exe", NULL);
|
|
+ if (self == NULL)
|
|
+ FAIL_UNSUPPORTED ("/proc/self/exe not available");
|
|
+ char *ldso = realpath (support_objdir_elf_ldso, NULL);
|
|
+ TEST_VERIFY_EXIT (ldso != NULL);
|
|
+ bool result = strcmp (self, ldso) == 0;
|
|
+ free (ldso);
|
|
+ free (self);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+static void
|
|
+test_subprocess (int argc, char **argv)
|
|
+{
|
|
+ {
|
|
+ char *argc_env = getenv ("argc");
|
|
+ if (argc_env != NULL)
|
|
+ {
|
|
+ char *argc_arg = xasprintf ("%d", argc);
|
|
+ TEST_COMPARE_STRING (argc_arg, argc_env);
|
|
+ free (argc_arg);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (argc >= 2 && strcmp (argv[1], "alpha") == 0)
|
|
+ {
|
|
+ TEST_COMPARE (argc, 4);
|
|
+ TEST_COMPARE_STRING (argv[0], "program");
|
|
+ TEST_COMPARE_STRING (argv[2], "beta");
|
|
+ TEST_COMPARE_STRING (argv[3], "gamma");
|
|
+ TEST_COMPARE_STRING (argv[4], NULL);
|
|
+ }
|
|
+ else if (argc >= 2 && strcmp (argv[1], "check-env") == 0)
|
|
+ printf ("%d %s\n", argc, getenv ("extra"));
|
|
+ else if (argc >= 2 && strcmp (argv[1], "check-ld.so") == 0)
|
|
+ TEST_VERIFY (running_via_ldso ());
|
|
+}
|
|
+
|
|
+/* The "recurse" environment variable and the --recurse option
|
|
+ indicate recursive invocation. */
|
|
+static int flag_recurse;
|
|
+#define CMDLINE_OPTIONS \
|
|
+ { "recurse", no_argument, &flag_recurse, 1 }, \
|
|
+ /* CMDLINE_OPTION */
|
|
+
|
|
+static void
|
|
+prepare (int argc, char **argv)
|
|
+{
|
|
+ if (getenv ("recurse") != NULL || flag_recurse)
|
|
+ {
|
|
+ test_subprocess (argc, argv);
|
|
+ support_record_failure_barrier ();
|
|
+ exit (0);
|
|
+ }
|
|
+}
|
|
+
|
|
+#define PREPARE prepare
|
|
+
|
|
+/* Test wrapping of a non-test program (iconv). This uses posix_spawn
|
|
+ directly, mainly for illustrative purposes. */
|
|
+void
|
|
+test_iconv (void)
|
|
+{
|
|
+ char *iconv_prog = xasprintf ("%s/iconv/iconv_prog", support_objdir_root);
|
|
+ char *argv[] = { (char *) "iconv", (char *) "-f", (char *) "UTF-8",
|
|
+ (char *) "-t", (char *) "ISO-8859-1", NULL };
|
|
+ struct support_spawn_wrapped *w
|
|
+ = support_spawn_wrap (iconv_prog, argv, NULL, support_spawn_wrap_force);
|
|
+
|
|
+ /* Set up pipes for stdin, stdout, and stderr. */
|
|
+ int stdin_pipe[2];
|
|
+ xpipe (stdin_pipe);
|
|
+ int stdout_pipe[2];
|
|
+ xpipe (stdout_pipe);
|
|
+ int stderr_pipe[2];
|
|
+ xpipe (stderr_pipe);
|
|
+
|
|
+ posix_spawn_file_actions_t fa;
|
|
+ posix_spawn_file_actions_init (&fa);
|
|
+ xposix_spawn_file_actions_adddup2 (&fa, stdin_pipe[0], STDIN_FILENO);
|
|
+ xposix_spawn_file_actions_addclose (&fa, stdin_pipe[0]);
|
|
+ xposix_spawn_file_actions_addclose (&fa, stdin_pipe[1]);
|
|
+ xposix_spawn_file_actions_adddup2 (&fa, stdout_pipe[1], STDOUT_FILENO);
|
|
+ xposix_spawn_file_actions_addclose (&fa, stdout_pipe[0]);
|
|
+ xposix_spawn_file_actions_addclose (&fa, stdout_pipe[1]);
|
|
+ xposix_spawn_file_actions_adddup2 (&fa, stderr_pipe[1], STDERR_FILENO);
|
|
+ xposix_spawn_file_actions_addclose (&fa, stderr_pipe[0]);
|
|
+ xposix_spawn_file_actions_addclose (&fa, stderr_pipe[1]);
|
|
+
|
|
+ pid_t pid = xposix_spawn (w->path, &fa, NULL, w->argv, w->envp);
|
|
+ posix_spawn_file_actions_destroy (&fa);
|
|
+
|
|
+ xclose (stdin_pipe[0]);
|
|
+ xclose (stdout_pipe[1]);
|
|
+ xclose (stderr_pipe[1]);
|
|
+
|
|
+ /* Write UTF-8 encoding of "äöü\n" to stdin. */
|
|
+ xwrite (stdin_pipe[1], "\xc3\xa4\xc3\xb6\xc3\xbc\n", 7);
|
|
+ xclose (stdin_pipe[1]);
|
|
+
|
|
+ /* Read the converted output from the pipe. */
|
|
+ char buf[16];
|
|
+ ssize_t ret = read (stdout_pipe[0], buf, sizeof (buf));
|
|
+ xclose (stdout_pipe[0]);
|
|
+
|
|
+ /* ISO-8859-1 encoding of "äöü\n". */
|
|
+ TEST_COMPARE_BLOB (buf, ret, "\xe4\xf6\xfc\n", 4);
|
|
+
|
|
+ int status;
|
|
+ xwaitpid (pid, &status, 0);
|
|
+ TEST_COMPARE (status, 0);
|
|
+
|
|
+ /* Check that nothing has been written to stderr. */
|
|
+ ret = read (stderr_pipe[0], buf, sizeof (buf));
|
|
+ TEST_COMPARE (ret, 0);
|
|
+ xclose (stderr_pipe[0]);
|
|
+
|
|
+ support_spawn_wrapped_free (w);
|
|
+ free (iconv_prog);
|
|
+}
|
|
+
|
|
+static int
|
|
+do_test (void)
|
|
+{
|
|
+ char *program = xasprintf ("%s/support/tst-support_spawn_wrap",
|
|
+ support_objdir_root);
|
|
+
|
|
+ {
|
|
+ char *env[] = { (char *) "recurse=", (char *) "argc=1", NULL };
|
|
+ struct support_spawn_wrapped *w
|
|
+ = support_spawn_wrap (program, NULL, env, 0);
|
|
+ struct support_capture_subprocess proc
|
|
+ = support_capture_subprogram (w->path, w->argv, w->envp);
|
|
+ support_capture_subprocess_check (&proc, "no arguments", 0, sc_allow_none);
|
|
+ support_capture_subprocess_free (&proc);
|
|
+ support_spawn_wrapped_free (w);
|
|
+ }
|
|
+
|
|
+ {
|
|
+ char *argv[] = { (char *) "program", (char *) "--recurse", NULL };
|
|
+ struct support_spawn_wrapped *w
|
|
+ = support_spawn_wrap (program, argv, NULL, 0);
|
|
+ struct support_capture_subprocess proc
|
|
+ = support_capture_subprogram (w->path, w->argv, w->envp);
|
|
+ support_capture_subprocess_check (&proc, "default envvironment", 0,
|
|
+ sc_allow_none);
|
|
+ support_capture_subprocess_free (&proc);
|
|
+ support_spawn_wrapped_free (w);
|
|
+ }
|
|
+
|
|
+ {
|
|
+ char *argv[] = { (char *) "program", (char *) "alpha", (char *) "beta",
|
|
+ (char *) "gamma", NULL };
|
|
+ char *env[] = { (char *) "recurse=", (char *) "argc=4", NULL };
|
|
+ struct support_spawn_wrapped *w
|
|
+ = support_spawn_wrap (program, argv, env, 0);
|
|
+ struct support_capture_subprocess proc
|
|
+ = support_capture_subprogram (w->path, w->argv, w->envp);
|
|
+ support_capture_subprocess_check (&proc, "3 arguments", 0, sc_allow_none);
|
|
+ support_capture_subprocess_free (&proc);
|
|
+ support_spawn_wrapped_free (w);
|
|
+ }
|
|
+
|
|
+ {
|
|
+ char *argv[] = { (char *) "program", (char *) "check-env", NULL };
|
|
+ char *env[] = { (char *) "recurse=", (char *) "argc=2",
|
|
+ (char *) "extra=17", NULL };
|
|
+ struct support_spawn_wrapped *w
|
|
+ = support_spawn_wrap (program, argv, env, 0);
|
|
+ struct support_capture_subprocess proc
|
|
+ = support_capture_subprogram (w->path, w->argv, w->envp);
|
|
+ TEST_COMPARE_STRING (proc.out.buffer, "2 17\n");
|
|
+ support_capture_subprocess_check (&proc, "check-env", 0, sc_allow_stdout);
|
|
+ support_capture_subprocess_free (&proc);
|
|
+ support_spawn_wrapped_free (w);
|
|
+ }
|
|
+
|
|
+ test_iconv ();
|
|
+
|
|
+ /* This may trigger EXIT_UNSUPPORTED, so run this before the tests
|
|
+ that rely on running_via_ldso. */
|
|
+ TEST_COMPARE (!running_via_ldso (), support_hardcoded_paths_in_test);
|
|
+
|
|
+ {
|
|
+ char *argv[] = { (char *) "program", (char *) "check-ld.so", NULL };
|
|
+ char *env[] = { (char *) "recurse=", NULL };
|
|
+ struct support_spawn_wrapped *w
|
|
+ = support_spawn_wrap (program, argv, env, support_spawn_wrap_force);
|
|
+ struct support_capture_subprocess proc
|
|
+ = support_capture_subprogram (w->path, w->argv, w->envp);
|
|
+ support_capture_subprocess_check (&proc, "check-ld.so", 0, sc_allow_none);
|
|
+ support_capture_subprocess_free (&proc);
|
|
+ support_spawn_wrapped_free (w);
|
|
+ }
|
|
+
|
|
+ free (program);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+#include <support/test-driver.c>
|