Re: [PATCH v7 1/1] ldconfig: add --install option

Adhemerval Zanella Netto <[email protected]>
Newsgroups gmane.comp.lib.glibc.alpha
Organization Linaro
Message-ID <[email protected]>

On 30/06/26 18:28, DJ Delorie wrote:
> 
> Add --install option, which copies a pre-built ld.so.cache into place,
> honoring the cache and root options and defaults.  This gives the user
> a canonical "correct" way to install a pre-built cache without risk
> of a program trying to load a partially-written file.
> 
> Co-authored-by: Adhemerval Zanella Netto <[email protected]>

This version looks good to me, thanks.

Reviewed-by: Adhemerval Zanella  <[email protected]>

> 
> --- >8 ---
> 
> Changes since v6:
>   - split install_cache_file into separate function
>   - preserve errno across unlink/close
>   - don't use errno from 
> 
> Changes since v5:
>   - Fixed style issues
>   - Make counters ssize_t
>   - Fix write count accumulation logic
>   - additional error checks
>   
> Changes since v4:
>   - Added testcase
>   - Fix open mode
> 
> Changes since v3:
>   - initialize w because some compilers can't tell initialization
>     isn't needed.
> 
> Changes since v2:
>   - removed "rename if possible" option to avoid removing source file.
>     We always copy now.
> ---
>  NEWS                        |   2 +
>  elf/Makefile                |   6 ++
>  elf/ldconfig.c              | 162 +++++++++++++++++++++++++++++++++++-
>  elf/tst-ldconfig-install.sh | 125 ++++++++++++++++++++++++++++
>  4 files changed, 293 insertions(+), 2 deletions(-)
>  create mode 100644 elf/tst-ldconfig-install.sh
> 
> diff --git a/NEWS b/NEWS
> index f9d90c5194..e30b7a5f76 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -37,6 +37,8 @@ Major new features:
>    status via prctl syscall.  This prevents disabling or corrupting GCS
>    shadow stack during runtime.
>  
> +* Pre-built ld.so.cache files can be installed with ldconfig.
> +
>  Deprecated and removed features, and other changes affecting compatibility:
>  
>  * Although malloc and related functions currently return pointers
> diff --git a/elf/Makefile b/elf/Makefile
> index 789c504da9..3479450191 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -718,6 +718,7 @@ tests-special += $(tests-execstack-special-$(have-z-execstack))
>  ifeq ($(run-built-tests),yes)
>  tests-special += \
>    $(objpfx)tst-ldconfig-X.out \
> +  $(objpfx)tst-ldconfig-install.out \
>    $(objpfx)tst-ldconfig-p.out \
>    $(objpfx)tst-ldconfig-soname.out \
>    $(objpfx)tst-rtld-help.out \
> @@ -2833,6 +2834,11 @@ $(objpfx)tst-ldconfig-X.out : tst-ldconfig-X.sh $(objpfx)ldconfig
>  		 '$(run-program-env)' > $@; \
>  	$(evaluate-test)
>  
> +$(objpfx)tst-ldconfig-install.out : tst-ldconfig-install.sh $(objpfx)ldconfig
> +	$(SHELL) $< '$(common-objpfx)' '$(test-wrapper-env)' \
> +		'$(run-program-env)' > $@; \
> +	$(evaluate-test)
> +
>  $(objpfx)tst-ldconfig-p.out : tst-ldconfig-p.sh $(objpfx)ldconfig
>  	$(SHELL) $< '$(common-objpfx)' '$(sysconfdir)' '$(test-wrapper-env)' \
>  		 '$(run-program-env)' > $@; \
> diff --git a/elf/ldconfig.c b/elf/ldconfig.c
> index 1ea55400f3..a39f3ecfcd 100644
> --- a/elf/ldconfig.c
> +++ b/elf/ldconfig.c
> @@ -109,6 +109,9 @@ static int opt_manual_link;
>  /* Should we ignore an old auxiliary cache file?  */
>  static int opt_ignore_aux_cache;
>  
> +/* Install a pre-existing cache file instead of generating a new one.  */
> +static int opt_install;
> +
>  /* Cache file to use.  */
>  static char *cache_file;
>  
> @@ -141,6 +144,7 @@ static const struct argp_option options[] =
>    { NULL, 'l', NULL, 0, N_("Manually link individual libraries."), 0},
>    { "format", 'c', N_("FORMAT"), 0, N_("Format to use: new (default), old, or compat"), 0},
>    { "ignore-aux-cache", 'i', NULL, 0, N_("Ignore auxiliary cache file"), 0},
> +  { "install", 'I', NULL, 0, N_("Install pre-existing cache file"), 0},
>    { NULL, 0, NULL, 0, NULL, 0 }
>  };
>  
> @@ -209,6 +213,9 @@ parse_opt (int key, char *arg, struct argp_state *state)
>        else if (strcmp (arg, "new") == 0)
>  	opt_format = opt_format_new;
>        break;
> +    case 'I':
> +      opt_install = 1;
> +      break;
>      default:
>        return ARGP_ERR_UNKNOWN;
>      }
> @@ -1041,6 +1048,155 @@ search_dirs (void)
>      }
>  }
>  
> +static void
> +install_cache_file (const char *source_arg)
> +{
> +  int e;
> +
> +  if (source_arg == NULL)
> +    error (EXIT_FAILURE, 0, _("Missing source file name"));
> +
> +  const char *source = (opt_chroot
> +			? chroot_canon (opt_chroot, source_arg)
> +			: source_arg);
> +  if (source == NULL)
> +    error (EXIT_FAILURE, errno, _("Can't find %s"), source_arg);
> +
> +  int src_fd = open (source, O_RDONLY);
> +  if (src_fd < 0)
> +    error (EXIT_FAILURE, errno, _("Can't open %s"), source);
> +
> +  char *dest = xmalloc (strlen (cache_file) + 1 + 1);
> +
> +  /* This matches the temp file created by cache.c, and should be
> +     on the same filesystem as the cache file.  */
> +  sprintf (dest, "%s~", cache_file);
> +  int dest_fd;
> +
> +  struct stat st;
> +  if (fstat (src_fd, &st) < 0)
> +    error (EXIT_FAILURE, errno, _("Can't stat %s"), source);
> +
> +  char buf[512];
> +  ssize_t r, w = 0, sz = 0;
> +  char *bp = buf;
> +
> +  /* Read the first part of the file and verify it looks
> +     reasonable.  */
> +  while (sz < sizeof (buf)
> +	 && (r = read (src_fd, bp, sizeof (buf) - sz)) > 0)
> +    {
> +      sz += r;
> +      bp += r;
> +    }
> +  if (r < 0)
> +    error (EXIT_FAILURE, errno, _("Error reading file %s"), source);
> +
> +  if (! ((sz >= sizeof (CACHEMAGIC)
> +	  && memcmp (buf, CACHEMAGIC,
> +		     sizeof (CACHEMAGIC) - 1) == 0)
> +	 || (sz >= sizeof (CACHEMAGIC_NEW)
> +	     && memcmp (buf, CACHEMAGIC_NEW,
> +			sizeof (CACHEMAGIC_NEW) - 1) == 0)))
> +    {
> +      error (EXIT_FAILURE, 0,
> +	     _("File %s does not look like an ld.so.cache file"),
> +	     source);
> +    }
> +
> +  /* Now write that first part out.  */
> +  dest_fd = open (dest, O_CREAT|O_WRONLY|O_TRUNC|O_NOFOLLOW,
> +		  S_IRUSR|S_IWUSR);
> +  if (dest_fd < 0)
> +    error (EXIT_FAILURE, errno, _("Can't create %s"), dest);
> +
> +  r = sz;
> +  bp = buf;
> +  while (r > 0 && (w = write (dest_fd, bp, r)) > 0)
> +    {
> +      r -= w;
> +      bp += w;
> +    }
> +  if (w < 0)
> +    {
> +      e = errno;
> +      unlink (dest);
> +      close (dest_fd);
> +      error (EXIT_FAILURE, e, _("Error writing file %s"), dest);
> +    }
> +
> +  /* At this point, sz contains the number of bytes copied so far.
> +     Copy the rest of the file.  */
> +  while ((r = read (src_fd, buf, sizeof(buf))) > 0)
> +    {
> +      bp = buf;
> +      while (r > 0 && (w = write (dest_fd, bp, r)) > 0)
> +	{
> +	  bp += w;
> +	  r -= w;
> +	  sz += w;
> +	}
> +      if (w <= 0)
> +	break;
> +    }
> +  if (r < 0)
> +    {
> +      e = errno;
> +      unlink (dest);
> +      close (dest_fd);
> +      error (EXIT_FAILURE, e, _("Error reading file %s"), source);
> +    }
> +  if (w < 0)
> +    {
> +      e = errno;
> +      unlink (dest);
> +      close (dest_fd);
> +      error (EXIT_FAILURE, e, _("Error writing file %s"), dest);
> +    }
> +
> +  close (src_fd);
> +
> +  /* Make sure we copied it all.  */
> +  if (sz < st.st_size)
> +    {
> +      unlink (dest);
> +      close (dest_fd);
> +      error (EXIT_FAILURE, 0, _("Unable to copy file %s to %s"),
> +	     source, dest);
> +    }
> +
> +  /* Make sure user can always read the cache file */
> +  if (fchmod (dest_fd, S_IROTH|S_IRGRP|S_IRUSR|S_IWUSR))
> +    {
> +      e = errno;
> +      unlink (dest);
> +      close (dest_fd);
> +      error (EXIT_FAILURE, e,
> +	     _("Changing access rights of %s to %#o failed"), dest,
> +	     S_IROTH|S_IRGRP|S_IRUSR|S_IWUSR);
> +    }
> +
> +  if (fsync (dest_fd) != 0)
> +    {
> +      e = errno;
> +      unlink (dest);
> +      close (dest_fd);
> +      error (EXIT_FAILURE, e, _("Writing to %s failed"), dest);
> +    }
> +
> +  if (rename (dest, cache_file) < 0)
> +    {
> +      e = errno;
> +      unlink (dest);
> +      close (dest_fd);
> +      error (EXIT_FAILURE, e, _("Can't rename %s to %s"),
> +	     dest, cache_file);
> +    }
> +
> +  if (close (dest_fd) != 0)
> +    error (EXIT_FAILURE, errno, _("Writing to %s failed"), dest);
> +  exit (0);
> +}
>  
>  int
>  main (int argc, char **argv)
> @@ -1060,8 +1216,8 @@ main (int argc, char **argv)
>    argp_parse (&argp, argc, argv, 0, &remaining, NULL);
>  
>    /* Remaining arguments are additional directories if opt_manual_link
> -     is not set.  */
> -  if (remaining != argc && !opt_manual_link)
> +     and opt_install are not set.  */
> +  if (remaining != argc && !opt_manual_link && !opt_install)
>      {
>        int i;
>        for (i = remaining; i < argc; ++i)
> @@ -1157,6 +1313,8 @@ main (int argc, char **argv)
>        exit (0);
>      }
>  
> +  if (opt_install)
> +    install_cache_file (argv[remaining]);
>  
>    if (opt_build_cache)
>      init_cache ();
> diff --git a/elf/tst-ldconfig-install.sh b/elf/tst-ldconfig-install.sh
> new file mode 100644
> index 0000000000..550281c7ab
> --- /dev/null
> +++ b/elf/tst-ldconfig-install.sh
> @@ -0,0 +1,125 @@
> +#!/bin/sh
> +# Test that ldconfig --install installs a pre-built cache file.
> +# 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/>.
> +
> +set -e
> +
> +common_objpfx=$1
> +test_wrapper_env=$2
> +run_program_env=$3
> +
> +testroot="${common_objpfx}elf/tst-ldconfig-install-directory"
> +cleanup () {
> +    rm -rf "$testroot"
> +}
> +trap cleanup 0
> +
> +rm -rf "$testroot"
> +mkdir -p "$testroot/etc"
> +
> +ldconfig="${common_objpfx}elf/ldconfig"
> +run_ldconfig () {
> +    ${test_wrapper_env} ${run_program_env} "$ldconfig" "$@"
> +}
> +
> +errors=0
> +fail () {
> +    echo "error: $1"
> +    errors=1
> +}
> +
> +# Build a pre-built cache to install.
> +source="$testroot/prebuilt-ld.so.cache"
> +mkdir -p "$testroot/lib"
> +run_ldconfig -X -f /dev/null -C "$source" "$testroot/lib"
> +test -r "$source" || fail "ldconfig did not create the pre-built cache"
> +
> +# Pad the source past the 512-byte internal copy buffer so that the multi-block
> +# copy path is exercised, while leaving the cache magic at the start intact.
> +dd if=/dev/zero bs=1024 count=4 >> "$source" 2>/dev/null
> +
> +dest="$testroot/etc/ld.so.cache"
> +temp="$dest~"
> +
> +run_ldconfig --install -f /dev/null -C "$dest" "$source"
> +
> +# The destination must exist and be byte-identical to the source.
> +if test -r "$dest"; then
> +  if cmp -s "$source" "$dest"; then
> +    echo "info: installed cache matches the source"
> +  else
> +    fail "installed cache differs from the source"
> +  fi
> +else
> +  fail "destination cache file was not created"
> +fi
> +
> +# The temporary file used during the atomic rename must not be left behind.
> +if test -e "$temp"; then
> +  fail "temporary file $temp was left behind"
> +fi
> +
> +# The installed cache must be world-readable (0644).
> +if test -r "$dest"; then
> +  mode=$(ls -l "$dest" | cut -c1-10)
> +  case "$mode" in
> +    (-rw-r--r--) echo "info: installed cache has expected permissions" ;;
> +    (*) fail "installed cache has unexpected permissions: $mode" ;;
> +  esac
> +fi
> +
> +# A second install over an existing cache must also succeed.
> +run_ldconfig --install -f /dev/null -C "$dest" "$source"
> +if cmp -s "$source" "$dest"; then
> +  echo "info: re-install over an existing cache works"
> +else
> +  fail "re-install produced a different cache"
> +fi
> +
> +# Error case: a source file that is not a cache must be rejected, and no
> +# destination must be produced.
> +rm -f "$dest"
> +notcache="$testroot/not-a-cache"
> +echo "this is not an ld.so.cache file" > "$notcache"
> +if run_ldconfig --install -f /dev/null -C "$dest" "$notcache" 2>"$testroot/err"; then
> +  fail "ldconfig accepted a file that is not a cache"
> +else
> +  if grep -q "does not look like an ld.so.cache file" "$testroot/err"; then
> +    echo "info: non-cache source correctly rejected"
> +  else
> +    fail "unexpected error message for non-cache source"
> +    cat "$testroot/err"
> +  fi
> +fi
> +test -e "$dest" && fail "destination created from an invalid source"
> +
> +# Error case: a missing source argument must be diagnosed.
> +if run_ldconfig --install -f /dev/null -C "$dest" 2>"$testroot/err"; then
> +  fail "ldconfig accepted --install without a source file"
> +else
> +  grep -q "Missing source file name" "$testroot/err" \
> +    || fail "unexpected error message for missing source"
> +fi
> +
> +# Error case: a nonexistent source must be diagnosed.
> +if run_ldconfig --install -f /dev/null -C "$dest" \
> +       "$testroot/does-not-exist" 2>"$testroot/err"; then
> +  fail "ldconfig accepted a nonexistent source file"
> +fi
> +
> +exit $errors
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.