[PATCH v2] support: Add support_spawn_wrap and related functionality

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Mon Feb 2 20:09:34 GMT 2026



On 27/01/26 13:26, Florian Weimer wrote:
> 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.

LGTM, thanks.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> 
> ---
> v2: Address Adhemerval's review comments.
> 
>  support/Makefile                 |   7 ++
>  support/subprocess.h             |  32 ++++++
>  support/support_spawn_wrap.c     | 171 ++++++++++++++++++++++++++++
>  support/tst-support_spawn_wrap.c | 235 +++++++++++++++++++++++++++++++++++++++
>  4 files changed, 445 insertions(+)
> 
> diff --git a/support/Makefile b/support/Makefile
> index 3a948c7f7e..bcdbe12cfe 100644
> --- a/support/Makefile
> +++ b/support/Makefile
> @@ -91,6 +91,7 @@ libsupport-routines = \
>    support_shared_allocate \
>    support_small_stack_thread_attribute \
>    support_socket_so_timestamp_time64 \
> +  support_spawn_wrap \
>    support_stack_alloc \
>    support_stat_nanoseconds \
>    support_subprocess \
> @@ -259,6 +260,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
> +
>  # Build with exception handling and asynchronous unwind table support.
>  CFLAGS-.oS += -fexceptions -fasynchronous-unwind-tables
>  
> @@ -352,6 +358,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 --git a/support/subprocess.h b/support/subprocess.h
> index 99025d99a4..b72ebe090e 100644
> --- a/support/subprocess.h
> +++ b/support/subprocess.h
> @@ -52,4 +52,36 @@ int support_process_wait (struct support_subprocess *proc);
>     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 --git a/support/support_spawn_wrap.c b/support/support_spawn_wrap.c
> new file mode 100644
> index 0000000000..e61e12f486
> --- /dev/null
> +++ b/support/support_spawn_wrap.c
> @@ -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 --git a/support/tst-support_spawn_wrap.c b/support/tst-support_spawn_wrap.c
> new file mode 100644
> index 0000000000..d0bb8a4194
> --- /dev/null
> +++ b/support/tst-support_spawn_wrap.c
> @@ -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>
> 
> base-commit: 39d9fa9e313308afa44b2b4843f8442e0ae1c067
> 



More information about the Libc-alpha mailing list