[v7,1/1] ldconfig: add --install option
Checks
| Context |
Check |
Description |
| redhat-pt-bot/TryBot-apply_patch |
success
|
Patch applied to master at the time it was sent
|
| redhat-pt-bot/TryBot-32bit |
success
|
Build for i686
|
| linaro-tcwg-bot/tcwg_glibc_build--master-arm |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 |
success
|
Test passed
|
Commit Message
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 <adhemerval.zanella@linaro.org>
--- >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
Comments
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 <adhemerval.zanella@linaro.org>
This version looks good to me, thanks.
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
>
> --- >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
Adhemerval Zanella Netto <adhemerval.zanella@linaro.org> writes:
> 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 <adhemerval.zanella@linaro.org>
>
> This version looks good to me, thanks.
>
> Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Thanks!
I'm assuming this is a post-2.44 patch, yes?
On 07/07/26 16:05, DJ Delorie wrote:
> Adhemerval Zanella Netto <adhemerval.zanella@linaro.org> writes:
>> 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 <adhemerval.zanella@linaro.org>
>>
>> This version looks good to me, thanks.
>>
>> Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
>
> Thanks!
>
> I'm assuming this is a post-2.44 patch, yes?
>
It should be doable for 2.44 if Andreas is OK with this.
Am Mittwoch, 8. Juli 2026, 04:31:23 Japanische Normalzeit schrieb Adhemerval Zanella Netto:
>
> On 07/07/26 16:05, DJ Delorie wrote:
> > Adhemerval Zanella Netto <adhemerval.zanella@linaro.org> writes:
> >> 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 <adhemerval.zanella@linaro.org>
> >>
> >> This version looks good to me, thanks.
> >>
> >> Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
> >
> > Thanks!
> >
> > I'm assuming this is a post-2.44 patch, yes?
> >
>
> It should be doable for 2.44 if Andreas is OK with this.
>
It's small, well-separated, and doesnt affect existing operation. So yeah, I
dont see any problems.
Mostly I don't fully understand the use case yet, but that may be a problem
behind *my* keyboard and is irrelevant for the release...
Is the source file supposed to come from the live system (since it needs to
know all installed libraries)? or from some other magic source?
* Andreas K. Huettel:
> Am Mittwoch, 8. Juli 2026, 04:31:23 Japanische Normalzeit schrieb Adhemerval Zanella Netto:
>>
>> On 07/07/26 16:05, DJ Delorie wrote:
>> > Adhemerval Zanella Netto <adhemerval.zanella@linaro.org> writes:
>> >> 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 <adhemerval.zanella@linaro.org>
>> >>
>> >> This version looks good to me, thanks.
>> >>
>> >> Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org>
>> >
>> > Thanks!
>> >
>> > I'm assuming this is a post-2.44 patch, yes?
>> >
>>
>> It should be doable for 2.44 if Andreas is OK with this.
>>
>
> It's small, well-separated, and doesnt affect existing operation. So yeah, I
> dont see any problems.
>
> Mostly I don't fully understand the use case yet, but that may be a problem
> behind *my* keyboard and is irrelevant for the release...
> Is the source file supposed to come from the live system (since it needs to
> know all installed libraries)? or from some other magic source?
I think this needs a NEWS entry. The dynamic linker keeps
/etc/ld.so.cache mapped continuously, so if you use cp to copy the
cache, it's more likely that it will lead to crashes because the size of
the mapping does not match the file contents.
Using ldconfig --install avoids that because doesn't overwrite the file
in place. Using rsync (without --inplace) or GNU install would be fine,
too.
Thanks,
Florian
Florian Weimer <fweimer@redhat.com> writes:
>> It's small, well-separated, and doesnt affect existing operation. So yeah, I
>> dont see any problems.
>>
>> Mostly I don't fully understand the use case yet, but that may be a problem
>> behind *my* keyboard and is irrelevant for the release...
>> Is the source file supposed to come from the live system (since it needs to
>> know all installed libraries)? or from some other magic source?
>
> I think this needs a NEWS entry. The dynamic linker keeps
> /etc/ld.so.cache mapped continuously, so if you use cp to copy the
> cache, it's more likely that it will lead to crashes because the size of
> the mapping does not match the file contents.
>
> Using ldconfig --install avoids that because doesn't overwrite the file
> in place. Using rsync (without --inplace) or GNU install would be fine,
> too.
Andreas,
Does this answer your concerns? Is it still OK to push, or do you now
request changes or a NEWS entry?
Am Dienstag, 14. Juli 2026, 03:01:42 Japanische Normalzeit schrieb DJ Delorie:
> Florian Weimer <fweimer@redhat.com> writes:
> >> It's small, well-separated, and doesnt affect existing operation. So yeah, I
> >> dont see any problems.
> >>
> >> Mostly I don't fully understand the use case yet, but that may be a problem
> >> behind *my* keyboard and is irrelevant for the release...
> >> Is the source file supposed to come from the live system (since it needs to
> >> know all installed libraries)? or from some other magic source?
> >
> > I think this needs a NEWS entry. The dynamic linker keeps
> > /etc/ld.so.cache mapped continuously, so if you use cp to copy the
> > cache, it's more likely that it will lead to crashes because the size of
> > the mapping does not match the file contents.
> >
> > Using ldconfig --install avoids that because doesn't overwrite the file
> > in place. Using rsync (without --inplace) or GNU install would be fine,
> > too.
>
> Andreas,
>
> Does this answer your concerns? Is it still OK to push, or do you now
> request changes or a NEWS entry?
I'm fine with it as it is, please push it.
NEWS is nice but optional, maybe subsequently per extra commit.
>
>
"Andreas K. Huettel" <dilfridge@gentoo.org> writes:
> I'm fine with it as it is, please push it.
Pushed!
@@ -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
@@ -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)' > $@; \
@@ -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 ();
new file mode 100644
@@ -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