unofficial mirror of guix-patches@gnu.org 
 help / color / mirror / code / Atom feed
* [bug#44899] [PATCH 0/3] Using 'ld.so.cache' to speed up application startup
@ 2020-11-27  8:33 Ludovic Courtès
  2020-11-27  9:05 ` [bug#44899] [PATCH 1/3] gnu: glibc: Load ${ORIGIN}/../etc/ld.so.cache when available Ludovic Courtès
  2020-11-28 10:24 ` [bug#44899] [PATCH v2 0/4] Using 'ld.so.cache' to speed up application startup Ludovic Courtès
  0 siblings, 2 replies; 11+ messages in thread
From: Ludovic Courtès @ 2020-11-27  8:33 UTC (permalink / raw)
  To: 44899; +Cc: Ludovic Courtès

Hello Guix!

The other day on IRC Ricardo had the brilliant idea of using the
ld.so cache to avoid the “stat storm” stemming from our long RUNPATHs,
and thus to speed up application startup.  As an example, Guile has
9 entries in its RUNPATH and Inkscape has 44 entries.

The first patch changes the loader (1) to look for the cache in
$ORIGIN/../etc/ld.so.cache, and (2) to look for the cache before
looking at RUNPATH entries.

The second patch adds a build phase that creates ‘etc/ld.so.cache’.
It passes ‘ldconfig’ a config file that contains the union of all
the RUNPATH entries of all the executables found in the output at
hand.  (It cannot be done in a profile hook because $ORIGIN is
determined by looking at /proc/self/exe, which is the canonical file
name in the store.)

You can see it in action with LD_DEBUG=libs:

--8<---------------cut here---------------start------------->8---
$ LD_DEBUG=libs /gnu/store/3dfv892jq8081xnsl9gfylbgv1fdicvd-guile-3.0.4/bin/guile --version
     11150:     find library=libguile-3.0.so.1 [0]; searching
     11150:      search cache=/gnu/store/3dfv892jq8081xnsl9gfylbgv1fdicvd-guile-3.0.4/bin/../etc/ld.so.cache
     11150:       trying file=/gnu/store/3dfv892jq8081xnsl9gfylbgv1fdicvd-guile-3.0.4/lib/libguile-3.0.so.1
     11150:
     11150:     find library=libgc.so.1 [0]; searching
     11150:      search cache=/gnu/store/3dfv892jq8081xnsl9gfylbgv1fdicvd-guile-3.0.4/bin/../etc/ld.so.cache
     11150:       trying file=/gnu/store/hy88vf2ynlica0wj0ppi0d3b11gi2b2h-libgc-8.0.4/lib/libgc.so.1

[...]
--8<---------------cut here---------------end--------------->8---

Here’s the after/before for Guile:

--8<---------------cut here---------------start------------->8---
$ strace -c /gnu/store/3dfv892jq8081xnsl9gfylbgv1fdicvd-guile-3.0.4/bin/guile --version

[...]

  2.70    0.000259           5        46         6 openat
  1.87    0.000180           1       130        88 stat
  1.66    0.000159           4        36           rt_sigprocmask
  0.74    0.000071           1        40           close
  0.64    0.000061           3        18           fstat

[...]

100.00    0.009604                   600       105 total
$ strace -c guile --version

[...]

 13.82    0.000723           4       165       114 openat
 13.46    0.000704           3       190       144 stat

[...]

  1.43    0.000075           2        29           fstat

[...]

100.00    0.005232                   773       268 total
--8<---------------cut here---------------end--------------->8---

Erroneous syscalls are divided by 2.5; total syscalls reduced by 22%.

For Bash:

--8<---------------cut here---------------start------------->8---
$ strace -c /gnu/store/qs33sf58502v1wx77va092y14sbspv4f-bash-minimal-5.0.16/bin/bash --version
GNU bash, version 5.0.16(1)-release (x86_64-unknown-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
  0.00    0.000000           0         4           read
  0.00    0.000000           0         6           write
  0.00    0.000000           0         5           close
  0.00    0.000000           0         5           fstat
  0.00    0.000000           0        12           mmap
  0.00    0.000000           0         5           mprotect
  0.00    0.000000           0         1           munmap
  0.00    0.000000           0         3           brk
  0.00    0.000000           0         1           rt_sigprocmask
  0.00    0.000000           0         1         1 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0         1           readlink
  0.00    0.000000           0         1           getuid
  0.00    0.000000           0         1           getgid
  0.00    0.000000           0         1           geteuid
  0.00    0.000000           0         1           getegid
  0.00    0.000000           0         1           arch_prctl
  0.00    0.000000           0         9         4 openat
------ ----------- ----------- --------- --------- ----------------
100.00    0.000000                    59         5 total
$ strace -c /gnu/store/fvhj74pghapbjvsvj27skvkra1by1965-bash-minimal-5.0.16/bin/bash --version
GNU bash, version 5.0.16(1)-release (x86_64-unknown-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 34.15    0.000420          12        35        16 openat
 18.29    0.000225           8        27           mmap
  8.13    0.000100          25         4           brk
  7.72    0.000095           6        14        14 stat
  7.24    0.000089          89         1         1 access
  6.18    0.000076           4        19           fstat
  5.45    0.000067           3        19           close
  4.47    0.000055           2        20           read
  3.82    0.000047           9         5           mprotect
  2.36    0.000029           4         6           write
  0.65    0.000008           8         1           execve
  0.41    0.000005           5         1           arch_prctl
  0.33    0.000004           4         1           getuid
  0.24    0.000003           3         1           rt_sigprocmask
  0.24    0.000003           3         1           getegid
  0.16    0.000002           2         1           getgid
  0.16    0.000002           2         1           geteuid
------ ----------- ----------- --------- --------- ----------------
100.00    0.001230                   157        31 total
--8<---------------cut here---------------end--------------->8---

Erroneous syscalls are divided by 6; total syscalls divided by 2.7.

As always, this is probably not that big a deal on warm-cache SSD,
but it probably makes a difference on a cold cache, on spinning
disks, and on network file systems.

* Possible improvements

The hard-coded ‘../etc/ld.so.cache’ means that it can only be used
with first-level sub-directories like bin/ and sbin/; it won’t be
used for libexec/guix/guile, for instance, which is a bummer.
Perhaps we should compute the ‘ld.so.cache’ file name “lexically”
instead.

We should also think hard about ways users could be tricked into
loading a malicious ‘ld.so.cache’.  That’s also another reason why
“lexical dot-dot” would be safer: we could ensure that only
pre-computed ‘ld.so.cache’ that live in the store are ever loaded.

The ‘ld.so.conf’ file passed to ‘ldconfig’ should ideally contains
the RUNPATH entries _recursively_, such that even indirect
dependencies can be found in cache.

Thoughts?

Ludo’.

Ludovic Courtès (3):
  gnu: glibc: Load ${ORIGIN}/../etc/ld.so.cache when available.
  gremlin: Fix typo in docstring.
  build-system/gnu: Add 'make-dynamic-linker-cache' phase.

 gnu/local.mk                              |   1 +
 gnu/packages/base.scm                     |  11 +-
 gnu/packages/patches/glibc-dl-cache.patch | 122 ++++++++++++++++++++++
 guix/build-system/gnu.scm                 |   4 +
 guix/build/gnu-build-system.scm           |  73 +++++++++++++
 guix/build/gremlin.scm                    |   2 +-
 6 files changed, 202 insertions(+), 11 deletions(-)
 create mode 100644 gnu/packages/patches/glibc-dl-cache.patch

-- 
2.29.2





^ permalink raw reply	[flat|nested] 11+ messages in thread

* [bug#44899] [PATCH 1/3] gnu: glibc: Load ${ORIGIN}/../etc/ld.so.cache when available.
  2020-11-27  8:33 [bug#44899] [PATCH 0/3] Using 'ld.so.cache' to speed up application startup Ludovic Courtès
@ 2020-11-27  9:05 ` Ludovic Courtès
  2020-11-27  9:05   ` [bug#44899] [PATCH 2/3] gremlin: Fix typo in docstring Ludovic Courtès
  2020-11-27  9:05   ` [bug#44899] [PATCH 3/3] build-system/gnu: Add 'make-dynamic-linker-cache' phase Ludovic Courtès
  2020-11-28 10:24 ` [bug#44899] [PATCH v2 0/4] Using 'ld.so.cache' to speed up application startup Ludovic Courtès
  1 sibling, 2 replies; 11+ messages in thread
From: Ludovic Courtès @ 2020-11-27  9:05 UTC (permalink / raw)
  To: 44899; +Cc: Ludovic Courtès

* gnu/packages/patches/glibc-dl-cache.patch: New file.
* gnu/local.mk (dist_patch_DATA): Add it.
* gnu/packages/base.scm (glibc)[source]: Remove 'snippet' and 'modules'.
---
 gnu/local.mk                              |   1 +
 gnu/packages/base.scm                     |  11 +-
 gnu/packages/patches/glibc-dl-cache.patch | 122 ++++++++++++++++++++++
 3 files changed, 124 insertions(+), 10 deletions(-)
 create mode 100644 gnu/packages/patches/glibc-dl-cache.patch

diff --git a/gnu/local.mk b/gnu/local.mk
index f9fed30a3f..82c3c608c6 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -1051,6 +1051,7 @@ dist_patch_DATA =						\
   %D%/packages/patches/glibc-bootstrap-system-2.2.5.patch 	\
   %D%/packages/patches/glibc-bootstrap-system-2.16.0.patch 	\
   %D%/packages/patches/glibc-bootstrap-system.patch		\
+  %D%/packages/patches/glibc-dl-cache.patch			\
   %D%/packages/patches/glibc-hidden-visibility-ldconfig.patch	\
   %D%/packages/patches/glibc-hurd-clock_gettime_monotonic.patch	\
   %D%/packages/patches/glibc-hurd-clock_t_centiseconds.patch	\
diff --git a/gnu/packages/base.scm b/gnu/packages/base.scm
index bd352319a1..f8f4ae37fd 100644
--- a/gnu/packages/base.scm
+++ b/gnu/packages/base.scm
@@ -675,17 +675,8 @@ the store.")
             (sha256
              (base32
               "0di848ibffrnwq7g2dvgqrnn4xqhj3h96csn69q4da51ymafl9qn"))
-            (snippet
-             ;; Disable 'ldconfig' and /etc/ld.so.cache.  The latter is
-             ;; required on LFS distros to avoid loading the distro's libc.so
-             ;; instead of ours.
-             '(begin
-                (substitute* "sysdeps/unix/sysv/linux/configure"
-                  (("use_ldconfig=yes")
-                   "use_ldconfig=no"))
-                #t))
-            (modules '((guix build utils)))
             (patches (search-patches "glibc-ldd-x86_64.patch"
+                                     "glibc-dl-cache.patch"
                                      "glibc-hidden-visibility-ldconfig.patch"
                                      "glibc-versioned-locpath.patch"
                                      "glibc-allow-kernel-2.6.32.patch"
diff --git a/gnu/packages/patches/glibc-dl-cache.patch b/gnu/packages/patches/glibc-dl-cache.patch
new file mode 100644
index 0000000000..9d578bb3d9
--- /dev/null
+++ b/gnu/packages/patches/glibc-dl-cache.patch
@@ -0,0 +1,122 @@
+Read the shared library cache relative to $ORIGIN instead of reading
+from /etc/ld.so.cache.  Also arrange so that this cache takes
+precedence over RUNPATH.
+
+diff --git a/elf/dl-cache.c b/elf/dl-cache.c
+index 93d185e788..6a2989bd4c 100644
+--- a/elf/dl-cache.c
++++ b/elf/dl-cache.c
+@@ -171,6 +171,33 @@ _dl_cache_libcmp (const char *p1, const char *p2)
+   return *p1 - *p2;
+ }
+ 
++/* Special value representing the lack of an ld.so cache.  */
++static const char ld_so_cache_lacking[] = "/ld.so cache is lacking";
++
++/* Return the per-application ld.so cache, relative to $ORIGIN, or NULL if
++   that fails for some reason.  Do not return the system-wide LD_SO_CACHE
++   since on a foreign distro it would contain invalid information.  */
++static const char *
++ld_so_cache (void)
++{
++  static const char *loader_cache;
++
++  if (loader_cache == NULL)
++    {
++      const char suffix[] = "/../etc/ld.so.cache";
++      const char *origin = _dl_get_origin ();
++
++      /* Note: We can't use 'malloc' because it can be interposed.  */
++      char *cache = alloca (strlen (origin) + sizeof suffix);
++
++      strcpy (cache, origin);
++      strcat (cache, suffix);
++
++      loader_cache = __strdup (cache) ?: ld_so_cache_lacking;
++    }
++
++  return loader_cache;
++}
+ 
+ /* Look up NAME in ld.so.cache and return the file name stored there, or null
+    if none is found.  The cache is loaded if it was not already.  If loading
+@@ -190,12 +217,15 @@ _dl_load_cache_lookup (const char *name)
+ 
+   /* Print a message if the loading of libs is traced.  */
+   if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
+-    _dl_debug_printf (" search cache=%s\n", LD_SO_CACHE);
++    _dl_debug_printf (" search cache=%s\n", ld_so_cache ());
++
++  if (__glibc_unlikely (ld_so_cache () == ld_so_cache_lacking))
++    return NULL;
+ 
+   if (cache == NULL)
+     {
+       /* Read the contents of the file.  */
+-      void *file = _dl_sysdep_read_whole_file (LD_SO_CACHE, &cachesize,
++      void *file = _dl_sysdep_read_whole_file (ld_so_cache (), &cachesize,
+ 					       PROT_READ);
+ 
+       /* We can handle three different cache file formats here:
+diff --git a/elf/dl-load.c b/elf/dl-load.c
+index f3201e7c14..a69aec3428 100644
+--- a/elf/dl-load.c
++++ b/elf/dl-load.c
+@@ -2152,28 +2152,6 @@ _dl_map_object (struct link_map *loader, const char *name,
+ 			loader ?: GL(dl_ns)[LM_ID_BASE]._ns_loaded,
+ 			LA_SER_LIBPATH, &found_other_class);
+ 
+-      /* Look at the RUNPATH information for this binary.  */
+-      if (fd == -1 && loader != NULL
+-	  && cache_rpath (loader, &loader->l_runpath_dirs,
+-			  DT_RUNPATH, "RUNPATH"))
+-	fd = open_path (name, namelen, mode,
+-			&loader->l_runpath_dirs, &realname, &fb, loader,
+-			LA_SER_RUNPATH, &found_other_class);
+-
+-      if (fd == -1)
+-        {
+-          realname = _dl_sysdep_open_object (name, namelen, &fd);
+-          if (realname != NULL)
+-            {
+-              fd = open_verify (realname, fd,
+-                                &fb, loader ?: GL(dl_ns)[nsid]._ns_loaded,
+-                                LA_SER_CONFIG, mode, &found_other_class,
+-                                false);
+-              if (fd == -1)
+-                free (realname);
+-            }
+-        }
+-
+ #ifdef USE_LDCONFIG
+       if (fd == -1
+ 	  && (__glibc_likely ((mode & __RTLD_SECURE) == 0)
+@@ -2232,6 +2210,28 @@ _dl_map_object (struct link_map *loader, const char *name,
+ 	}
+ #endif
+ 
++      /* Look at the RUNPATH information for this binary.  */
++      if (fd == -1 && loader != NULL
++	  && cache_rpath (loader, &loader->l_runpath_dirs,
++			  DT_RUNPATH, "RUNPATH"))
++	fd = open_path (name, namelen, mode,
++			&loader->l_runpath_dirs, &realname, &fb, loader,
++			LA_SER_RUNPATH, &found_other_class);
++
++      if (fd == -1)
++        {
++          realname = _dl_sysdep_open_object (name, namelen, &fd);
++          if (realname != NULL)
++            {
++              fd = open_verify (realname, fd,
++                                &fb, loader ?: GL(dl_ns)[nsid]._ns_loaded,
++                                LA_SER_CONFIG, mode, &found_other_class,
++                                false);
++              if (fd == -1)
++                free (realname);
++            }
++        }
++
+       /* Finally, try the default path.  */
+       if (fd == -1
+ 	  && ((l = loader ?: GL(dl_ns)[nsid]._ns_loaded) == NULL
-- 
2.29.2





^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [bug#44899] [PATCH 2/3] gremlin: Fix typo in docstring.
  2020-11-27  9:05 ` [bug#44899] [PATCH 1/3] gnu: glibc: Load ${ORIGIN}/../etc/ld.so.cache when available Ludovic Courtès
@ 2020-11-27  9:05   ` Ludovic Courtès
  2020-11-27  9:05   ` [bug#44899] [PATCH 3/3] build-system/gnu: Add 'make-dynamic-linker-cache' phase Ludovic Courtès
  1 sibling, 0 replies; 11+ messages in thread
From: Ludovic Courtès @ 2020-11-27  9:05 UTC (permalink / raw)
  To: 44899; +Cc: Ludovic Courtès

* guix/build/gremlin.scm (file-runpath): Fix typo.
---
 guix/build/gremlin.scm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/guix/build/gremlin.scm b/guix/build/gremlin.scm
index 6857e47b99..44604827a9 100644
--- a/guix/build/gremlin.scm
+++ b/guix/build/gremlin.scm
@@ -250,7 +250,7 @@ info."
       (elf-dynamic-info (parse-elf (get-bytevector-all port))))))
 
 (define (file-runpath file)
-  "Return the DT_RUNPATH dynamic entry of FILE as a list of string, or #f if
+  "Return the DT_RUNPATH dynamic entry of FILE as a list of strings, or #f if
 FILE lacks dynamic info."
   (and=> (file-dynamic-info file) elf-dynamic-info-runpath))
 
-- 
2.29.2





^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [bug#44899] [PATCH 3/3] build-system/gnu: Add 'make-dynamic-linker-cache' phase.
  2020-11-27  9:05 ` [bug#44899] [PATCH 1/3] gnu: glibc: Load ${ORIGIN}/../etc/ld.so.cache when available Ludovic Courtès
  2020-11-27  9:05   ` [bug#44899] [PATCH 2/3] gremlin: Fix typo in docstring Ludovic Courtès
@ 2020-11-27  9:05   ` Ludovic Courtès
  1 sibling, 0 replies; 11+ messages in thread
From: Ludovic Courtès @ 2020-11-27  9:05 UTC (permalink / raw)
  To: 44899; +Cc: Ludovic Courtès

* guix/build/gnu-build-system.scm (make-dynamic-linker-cache): New
procedure.
(%standard-phases): Add it.
* guix/build-system/gnu.scm (gnu-build, gnu-cross-build): Add
 #:make-dynamic-linker-cache? and honor it.
---
 guix/build-system/gnu.scm       |  4 ++
 guix/build/gnu-build-system.scm | 73 +++++++++++++++++++++++++++++++++
 2 files changed, 77 insertions(+)

diff --git a/guix/build-system/gnu.scm b/guix/build-system/gnu.scm
index 2c23197e77..d6c4dc9bbc 100644
--- a/guix/build-system/gnu.scm
+++ b/guix/build-system/gnu.scm
@@ -342,6 +342,7 @@ standard packages used as implicit inputs of the GNU build system."
                     (strip-directories ''("lib" "lib64" "libexec"
                                           "bin" "sbin"))
                     (validate-runpath? #t)
+                    (make-dynamic-linker-cache? #t)
                     (license-file-regexp %license-file-regexp)
                     (phases '%standard-phases)
                     (locale "en_US.utf8")
@@ -410,6 +411,7 @@ packages that must not be referenced."
                   #:patch-shebangs? ,patch-shebangs?
                   #:strip-binaries? ,strip-binaries?
                   #:validate-runpath? ,validate-runpath?
+                  #:make-dynamic-linker-cache? ,make-dynamic-linker-cache?
                   #:license-file-regexp ,license-file-regexp
                   #:strip-flags ,strip-flags
                   #:strip-directories ,strip-directories)))
@@ -497,6 +499,7 @@ is one of `host' or `target'."
                           (strip-directories ''("lib" "lib64" "libexec"
                                                 "bin" "sbin"))
                           (validate-runpath? #t)
+                          (make-dynamic-linker-cache? #t)
                           (license-file-regexp %license-file-regexp)
                           (phases '%standard-phases)
                           (locale "en_US.utf8")
@@ -577,6 +580,7 @@ platform."
                     #:patch-shebangs? ,patch-shebangs?
                     #:strip-binaries? ,strip-binaries?
                     #:validate-runpath? ,validate-runpath?
+                    #:make-dynamic-linker-cache? ,make-dynamic-linker-cache?
                     #:license-file-regexp ,license-file-regexp
                     #:strip-flags ,strip-flags
                     #:strip-directories ,strip-directories))))
diff --git a/guix/build/gnu-build-system.scm b/guix/build/gnu-build-system.scm
index 8fa11f4ea9..194cddc047 100644
--- a/guix/build/gnu-build-system.scm
+++ b/guix/build/gnu-build-system.scm
@@ -712,6 +712,78 @@ which cannot be found~%"
                                          (which binary) rest)))))))))
             outputs))
 
+(define* (make-dynamic-linker-cache #:key outputs
+                                    (make-dynamic-linker-cache? #t)
+                                    #:allow-other-keys)
+  "Create a dynamic linker cache under 'etc/ld.so.cache' in each of the
+OUTPUTS.  This reduces application startup time by avoiding the 'stat' storm
+that traversing all the RUNPATH entries entails."
+  (define (make-cache-for-output directory)
+    (define bin-directories
+      (filter-map (lambda (sub-directory)
+                    (let ((directory (string-append directory "/"
+                                                    sub-directory)))
+                      (and (directory-exists? directory)
+                           directory)))
+                  '("bin" "sbin")))
+
+    (define programs
+      ;; Programs that can benefit from the ld.so cache.  These programs must
+      ;; be in a directory such that:
+      ;;
+      ;;   (string-append (dirname PROGRAM) "../etc/ld.so.cache")
+      ;;
+      ;; potentially exists since that's what ld.so will look for.  Thus,
+      ;; something like 'libexec/foo/PROGRAM' is not a valid candidate.
+      (append-map (lambda (directory)
+                    (if (directory-exists? directory)
+                        (filter-map (lambda (file)
+                                      (let ((file (string-append
+                                                   directory "/" file)))
+                                        (and (executable-file? file)
+                                             (not (file-is-directory? file))
+                                             (elf-file? file)
+                                             file)))
+                                    (scandir directory))
+                        '()))
+                  bin-directories))
+
+    (define runpaths
+      ;; The union of RUNPATH entries.
+      (delete-duplicates
+       (append-map (lambda (program)
+                     (or (file-runpath program) '()))
+                   programs)))
+
+    (define cache-file
+      (string-append directory "/etc/ld.so.cache"))
+
+    (unless (null? runpaths)
+      (mkdir-p (dirname cache-file))
+      (guard (c ((invoke-error? c)
+                 ;; Do not treat 'ldconfig' failure as an error.
+                 (format (current-error-port)
+                         "warning: 'ldconfig' failed:~%")
+                 (report-invoke-error c (current-error-port))))
+        ;; Create a config file to tell 'ldconfig' where to look for the
+        ;; libraries that PROGRAMS need.
+        (call-with-output-file "/tmp/ld.so.conf"
+          (lambda (port)
+            (for-each (lambda (directory)
+                        (display directory port)
+                        (newline port))
+                      runpaths)))
+
+        (invoke "ldconfig" "-f" "/tmp/ld.so.conf" "-C" cache-file)
+        (format #t "created '~a' from ~a library search path entries~%"
+                cache-file (length runpaths)))))
+
+  (if make-dynamic-linker-cache?
+      (match outputs
+        (((_ . directories) ...)
+         (for-each make-cache-for-output directories)))
+      (format #t "ld.so cache not built~%")))
+
 (define %license-file-regexp
   ;; Regexp matching license files.
   "^(COPYING.*|LICEN[CS]E.*|[Ll]icen[cs]e.*|Copy[Rr]ight(\\.(txt|md))?)$")
@@ -791,6 +863,7 @@ which cannot be found~%"
             validate-documentation-location
             delete-info-dir-file
             patch-dot-desktop-files
+            make-dynamic-linker-cache
             install-license-files
             reset-gzip-timestamps
             compress-documentation)))
-- 
2.29.2





^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [bug#44899] [PATCH v2 0/4] Using 'ld.so.cache' to speed up application startup
  2020-11-27  8:33 [bug#44899] [PATCH 0/3] Using 'ld.so.cache' to speed up application startup Ludovic Courtès
  2020-11-27  9:05 ` [bug#44899] [PATCH 1/3] gnu: glibc: Load ${ORIGIN}/../etc/ld.so.cache when available Ludovic Courtès
@ 2020-11-28 10:24 ` Ludovic Courtès
  2020-11-28 10:24   ` [bug#44899] [PATCH v2 1/4] gremlin: Fix typo in docstring Ludovic Courtès
                     ` (5 more replies)
  1 sibling, 6 replies; 11+ messages in thread
From: Ludovic Courtès @ 2020-11-28 10:24 UTC (permalink / raw)
  To: 44899; +Cc: Ludovic Courtès

Hi!

This new version addresses the shortcomings I mentioned earlier and
other issues reported on IRC:

  • ld.so no longer uses “../etc/ld.so.cache”.  Instead, it (1) ensures
    $ORIGIN is in the store, (2) extracts the store file name, and
    (3) appends “/etc/ld.so.cache”.  IOW, the ‘ld.so.cache’ is always
    resolved relative to the store directory $ORIGIN belongs to, not
    relative to $ORIGIN itself.

    Thinking about it, it’s a direct translation of the ld.so.cache model
    from FHS to the functional model where a system-wide /etc/ld.so.cache
    makes no sense.

  • ‘make-dynamic-linker-cache’ now creates an ‘ld.so.conf’ that contains
    all the dependencies, recursively, as would be returned by ‘ldd’.
    The new ‘file-needed/recursive’ procedure returns the list of shared
    objects depended on like ‘ldd’.

  • ‘make-dynamic-linker-cache’ creates ‘ld.so.conf’ in $TMPDIR rather
    than hard-code /tmp, so as to be friendlier to ‘--disable-chroot’
    builds (as is currently used on GNU/Hurd).

I’m rather happy and confident with that version.  :-)

Feedback welcome!

Ludo’.

Ludovic Courtès (4):
  gremlin: Fix typo in docstring.
  gremlin: Add 'file-needed/recursive'.
  gnu: glibc: Load 'etc/ld.so.cache' in $ORIGIN's store item when
    available.
  build-system/gnu: Add 'make-dynamic-linker-cache' phase.

 gnu/local.mk                              |   1 +
 gnu/packages/base.scm                     |  16 +--
 gnu/packages/patches/glibc-dl-cache.patch | 140 ++++++++++++++++++++++
 guix/build-system/gnu.scm                 |   4 +
 guix/build/gnu-build-system.scm           |  68 +++++++++++
 guix/build/gremlin.scm                    |  43 ++++++-
 tests/gremlin.scm                         |  36 ++++++
 7 files changed, 297 insertions(+), 11 deletions(-)
 create mode 100644 gnu/packages/patches/glibc-dl-cache.patch

-- 
2.29.2





^ permalink raw reply	[flat|nested] 11+ messages in thread

* [bug#44899] [PATCH v2 1/4] gremlin: Fix typo in docstring.
  2020-11-28 10:24 ` [bug#44899] [PATCH v2 0/4] Using 'ld.so.cache' to speed up application startup Ludovic Courtès
@ 2020-11-28 10:24   ` Ludovic Courtès
  2020-11-28 10:24   ` [bug#44899] [PATCH v2 2/4] gremlin: Add 'file-needed/recursive' Ludovic Courtès
                     ` (4 subsequent siblings)
  5 siblings, 0 replies; 11+ messages in thread
From: Ludovic Courtès @ 2020-11-28 10:24 UTC (permalink / raw)
  To: 44899; +Cc: Ludovic Courtès

* guix/build/gremlin.scm (file-runpath): Fix typo.
---
 guix/build/gremlin.scm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/guix/build/gremlin.scm b/guix/build/gremlin.scm
index 6857e47b99..44604827a9 100644
--- a/guix/build/gremlin.scm
+++ b/guix/build/gremlin.scm
@@ -250,7 +250,7 @@ info."
       (elf-dynamic-info (parse-elf (get-bytevector-all port))))))
 
 (define (file-runpath file)
-  "Return the DT_RUNPATH dynamic entry of FILE as a list of string, or #f if
+  "Return the DT_RUNPATH dynamic entry of FILE as a list of strings, or #f if
 FILE lacks dynamic info."
   (and=> (file-dynamic-info file) elf-dynamic-info-runpath))
 
-- 
2.29.2





^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [bug#44899] [PATCH v2 2/4] gremlin: Add 'file-needed/recursive'.
  2020-11-28 10:24 ` [bug#44899] [PATCH v2 0/4] Using 'ld.so.cache' to speed up application startup Ludovic Courtès
  2020-11-28 10:24   ` [bug#44899] [PATCH v2 1/4] gremlin: Fix typo in docstring Ludovic Courtès
@ 2020-11-28 10:24   ` Ludovic Courtès
  2020-11-28 10:24   ` [bug#44899] [PATCH v2 3/4] gnu: glibc: Load 'etc/ld.so.cache' in $ORIGIN's store item when available Ludovic Courtès
                     ` (3 subsequent siblings)
  5 siblings, 0 replies; 11+ messages in thread
From: Ludovic Courtès @ 2020-11-28 10:24 UTC (permalink / raw)
  To: 44899; +Cc: Ludovic Courtès

* guix/build/gremlin.scm (file-needed/recursive): New procedure.
* tests/gremlin.scm ("file-needed/recursive"): New test.
---
 guix/build/gremlin.scm | 41 +++++++++++++++++++++++++++++++++++++++++
 tests/gremlin.scm      | 36 ++++++++++++++++++++++++++++++++++++
 2 files changed, 77 insertions(+)

diff --git a/guix/build/gremlin.scm b/guix/build/gremlin.scm
index 44604827a9..d56984b85e 100644
--- a/guix/build/gremlin.scm
+++ b/guix/build/gremlin.scm
@@ -44,6 +44,7 @@
             file-dynamic-info
             file-runpath
             file-needed
+            file-needed/recursive
 
             missing-runpath-error?
             missing-runpath-error-file
@@ -259,6 +260,46 @@ FILE lacks dynamic info."
 dynamic info."
   (and=> (file-dynamic-info file) elf-dynamic-info-needed))
 
+(define (file-needed/recursive file)
+  "Return two values: the list of absolute .so file names FILE depends on,
+recursively, and the list of .so file names that could not be found.  File
+names are resolved by searching the RUNPATH of the file that NEEDs them.
+
+This is similar to the info returned by the 'ldd' command."
+  (let loop ((files  (list file))
+             (result '())
+             (not-found '()))
+    (match files
+      (()
+       (values (reverse result)
+               (reverse (delete-duplicates not-found))))
+      ((file . rest)
+       (match (file-dynamic-info (pk 'file file))
+         (#f
+          (loop rest result not-found))
+         (info
+          (let ((runpath (elf-dynamic-info-runpath info))
+                (needed  (elf-dynamic-info-needed info)))
+            (if (and runpath needed)
+                (let* ((runpath  (map (cute expand-origin <> (dirname file))
+                                      runpath))
+                       (resolved (map (cut search-path runpath <>)
+                                      needed))
+                       (failed   (filter-map (lambda (needed resolved)
+                                               (and (not resolved)
+                                                    (not (libc-library? needed))
+                                                    needed))
+                                             needed resolved))
+                       (needed   (remove (lambda (value)
+                                           (or (not value)
+                                               ;; XXX: quadratic
+                                               (member value result)))
+                                         resolved)))
+                  (loop (append rest needed)
+                        (append needed result)
+                        (append failed not-found)))
+                (loop rest result not-found)))))))))
+
 (define %libc-libraries
   ;; List of libraries as of glibc 2.21 (there are more but those are
   ;; typically mean to be LD_PRELOADed and thus do not appear as NEEDED.)
diff --git a/tests/gremlin.scm b/tests/gremlin.scm
index f191adb8b3..9ddac14265 100644
--- a/tests/gremlin.scm
+++ b/tests/gremlin.scm
@@ -27,6 +27,8 @@
   #:use-module (srfi srfi-64)
   #:use-module (rnrs io ports)
   #:use-module (ice-9 popen)
+  #:use-module (ice-9 rdelim)
+  #:use-module (ice-9 regex)
   #:use-module (ice-9 match))
 
 (define %guile-executable
@@ -58,6 +60,40 @@
                        (string-take lib (string-contains lib ".so")))
                      (elf-dynamic-info-needed dyninfo))))))
 
+(unless (and %guile-executable (not (getenv "LD_LIBRARY_PATH"))
+             (file-needed %guile-executable))     ;statically linked?
+  (test-skip 1))
+(test-assert "file-needed/recursive"
+  (let* ((needed (file-needed/recursive %guile-executable))
+         (pipe   (dynamic-wind
+                   (lambda ()
+                     ;; Tell ld.so to list loaded objects, like 'ldd' does.
+                     (setenv "LD_TRACE_LOADED_OBJECTS" "yup"))
+                   (lambda ()
+                     (open-pipe* OPEN_READ %guile-executable))
+                   (lambda ()
+                     (unsetenv "LD_TRACE_LOADED_OBJECTS")))))
+    (define ldd-rx
+      (make-regexp "^[[:blank:]]+([[:graph:]]+ => )?([[:graph:]]+) .*$"))
+
+    (define (read-ldd-output port)
+      ;; Read from PORT output in GNU ldd format.
+      (let loop ((result '()))
+        (match (read-line port)
+          ((? eof-object?)
+           (reverse result))
+          ((= (cut regexp-exec ldd-rx <>) m)
+           (if m
+               (loop (cons (match:substring m 2) result))
+               (loop result))))))
+
+    (define ground-truth
+      (remove (cut string-prefix? "linux-vdso.so" <>)
+              (read-ldd-output pipe)))
+
+    (and (zero? (close-pipe pipe))
+         (lset= string=? (pk 'truth ground-truth) (pk 'needed needed)))))
+
 (test-equal "expand-origin"
   '("OOO/../lib"
     "OOO"
-- 
2.29.2





^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [bug#44899] [PATCH v2 3/4] gnu: glibc: Load 'etc/ld.so.cache' in $ORIGIN's store item when available.
  2020-11-28 10:24 ` [bug#44899] [PATCH v2 0/4] Using 'ld.so.cache' to speed up application startup Ludovic Courtès
  2020-11-28 10:24   ` [bug#44899] [PATCH v2 1/4] gremlin: Fix typo in docstring Ludovic Courtès
  2020-11-28 10:24   ` [bug#44899] [PATCH v2 2/4] gremlin: Add 'file-needed/recursive' Ludovic Courtès
@ 2020-11-28 10:24   ` Ludovic Courtès
  2020-11-28 10:24   ` [bug#44899] [PATCH v2 3/4] gnu: glibc: Load ${ORIGIN}/../etc/ld.so.cache " Ludovic Courtès
                     ` (2 subsequent siblings)
  5 siblings, 0 replies; 11+ messages in thread
From: Ludovic Courtès @ 2020-11-28 10:24 UTC (permalink / raw)
  To: 44899; +Cc: Ludovic Courtès

* gnu/packages/patches/glibc-dl-cache.patch: New file.
* gnu/local.mk (dist_patch_DATA): Add it.
* gnu/packages/base.scm (glibc)[source]: Remove 'snippet' and 'modules'.
[arguments]: In 'pre-configure' phase, substitute @STORE_DIRECTORY@ in
'elf/dl-cache.c'.
---
 gnu/local.mk                              |   1 +
 gnu/packages/base.scm                     |  16 +--
 gnu/packages/patches/glibc-dl-cache.patch | 140 ++++++++++++++++++++++
 3 files changed, 147 insertions(+), 10 deletions(-)
 create mode 100644 gnu/packages/patches/glibc-dl-cache.patch

diff --git a/gnu/local.mk b/gnu/local.mk
index f9fed30a3f..82c3c608c6 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -1051,6 +1051,7 @@ dist_patch_DATA =						\
   %D%/packages/patches/glibc-bootstrap-system-2.2.5.patch 	\
   %D%/packages/patches/glibc-bootstrap-system-2.16.0.patch 	\
   %D%/packages/patches/glibc-bootstrap-system.patch		\
+  %D%/packages/patches/glibc-dl-cache.patch			\
   %D%/packages/patches/glibc-hidden-visibility-ldconfig.patch	\
   %D%/packages/patches/glibc-hurd-clock_gettime_monotonic.patch	\
   %D%/packages/patches/glibc-hurd-clock_t_centiseconds.patch	\
diff --git a/gnu/packages/base.scm b/gnu/packages/base.scm
index bd352319a1..ad4415f226 100644
--- a/gnu/packages/base.scm
+++ b/gnu/packages/base.scm
@@ -675,17 +675,8 @@ the store.")
             (sha256
              (base32
               "0di848ibffrnwq7g2dvgqrnn4xqhj3h96csn69q4da51ymafl9qn"))
-            (snippet
-             ;; Disable 'ldconfig' and /etc/ld.so.cache.  The latter is
-             ;; required on LFS distros to avoid loading the distro's libc.so
-             ;; instead of ours.
-             '(begin
-                (substitute* "sysdeps/unix/sysv/linux/configure"
-                  (("use_ldconfig=yes")
-                   "use_ldconfig=no"))
-                #t))
-            (modules '((guix build utils)))
             (patches (search-patches "glibc-ldd-x86_64.patch"
+                                     "glibc-dl-cache.patch"
                                      "glibc-hidden-visibility-ldconfig.patch"
                                      "glibc-versioned-locpath.patch"
                                      "glibc-allow-kernel-2.6.32.patch"
@@ -800,6 +791,11 @@ the store.")
                         ;; 4.7.1.
                         ((" -lgcc_s") ""))
 
+                      ;; Tell the ld.so cache code where the store is.
+                      (substitute* "elf/dl-cache.c"
+                        (("@STORE_DIRECTORY@")
+                         (string-append "\"" (%store-directory) "\"")))
+
                       ;; Have `system' use that Bash.
                       (substitute* "sysdeps/posix/system.c"
                         (("#define[[:blank:]]+SHELL_PATH.*$")
diff --git a/gnu/packages/patches/glibc-dl-cache.patch b/gnu/packages/patches/glibc-dl-cache.patch
new file mode 100644
index 0000000000..0f23b12add
--- /dev/null
+++ b/gnu/packages/patches/glibc-dl-cache.patch
@@ -0,0 +1,140 @@
+Read the shared library cache relative to $ORIGIN instead of reading
+from /etc/ld.so.cache.  Also arrange so that this cache takes
+precedence over RUNPATH.
+
+diff --git a/elf/dl-cache.c b/elf/dl-cache.c
+index 93d185e788..e0760a1f40 100644
+--- a/elf/dl-cache.c
++++ b/elf/dl-cache.c
+@@ -171,6 +171,51 @@ _dl_cache_libcmp (const char *p1, const char *p2)
+   return *p1 - *p2;
+ }
+ 
++/* Special value representing the lack of an ld.so cache.  */
++static const char ld_so_cache_lacking[] = "/ld.so cache is lacking";
++
++/* Return the per-application ld.so cache, relative to $ORIGIN, or NULL if
++   that fails for some reason.  Do not return the system-wide LD_SO_CACHE
++   since on a foreign distro it would contain invalid information.  */
++static const char *
++ld_so_cache (void)
++{
++  static const char *loader_cache;
++
++  if (loader_cache == NULL)
++    {
++      static const char store[] = "/gnu/store";
++      const char *origin = _dl_get_origin ();
++
++      /* Check whether ORIGIN is something like "/gnu/store/…-foo/bin".  */
++      if (strncmp (store, origin, strlen (store)) == 0
++	  && origin[sizeof store - 1] == '/')
++	{
++	  char *store_item_end = strchr (origin + sizeof store, '/');
++
++	  if (store_item_end != NULL)
++	    {
++	      static const char suffix[] = "/etc/ld.so.cache";
++	      size_t store_item_len = store_item_end - origin;
++
++	      /* Note: We can't use 'malloc' because it can be interposed.
++		 Likewise, 'strncpy' is not available.  */
++	      char *cache = alloca (strlen (origin) + sizeof suffix);
++
++	      strcpy (cache, origin);
++	      strcpy (cache + store_item_len, suffix);
++
++	      loader_cache = __strdup (cache) ?: ld_so_cache_lacking;
++	    }
++	  else
++	    loader_cache = ld_so_cache_lacking;
++	}
++      else
++	loader_cache = ld_so_cache_lacking;
++    }
++
++  return loader_cache;
++}
+ 
+ /* Look up NAME in ld.so.cache and return the file name stored there, or null
+    if none is found.  The cache is loaded if it was not already.  If loading
+@@ -190,12 +235,15 @@ _dl_load_cache_lookup (const char *name)
+ 
+   /* Print a message if the loading of libs is traced.  */
+   if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
+-    _dl_debug_printf (" search cache=%s\n", LD_SO_CACHE);
++    _dl_debug_printf (" search cache=%s\n", ld_so_cache ());
++
++  if (__glibc_unlikely (ld_so_cache () == ld_so_cache_lacking))
++    return NULL;
+ 
+   if (cache == NULL)
+     {
+       /* Read the contents of the file.  */
+-      void *file = _dl_sysdep_read_whole_file (LD_SO_CACHE, &cachesize,
++      void *file = _dl_sysdep_read_whole_file (ld_so_cache (), &cachesize,
+ 					       PROT_READ);
+ 
+       /* We can handle three different cache file formats here:
+diff --git a/elf/dl-load.c b/elf/dl-load.c
+index f3201e7c14..a69aec3428 100644
+--- a/elf/dl-load.c
++++ b/elf/dl-load.c
+@@ -2152,28 +2152,6 @@ _dl_map_object (struct link_map *loader, const char *name,
+ 			loader ?: GL(dl_ns)[LM_ID_BASE]._ns_loaded,
+ 			LA_SER_LIBPATH, &found_other_class);
+ 
+-      /* Look at the RUNPATH information for this binary.  */
+-      if (fd == -1 && loader != NULL
+-	  && cache_rpath (loader, &loader->l_runpath_dirs,
+-			  DT_RUNPATH, "RUNPATH"))
+-	fd = open_path (name, namelen, mode,
+-			&loader->l_runpath_dirs, &realname, &fb, loader,
+-			LA_SER_RUNPATH, &found_other_class);
+-
+-      if (fd == -1)
+-        {
+-          realname = _dl_sysdep_open_object (name, namelen, &fd);
+-          if (realname != NULL)
+-            {
+-              fd = open_verify (realname, fd,
+-                                &fb, loader ?: GL(dl_ns)[nsid]._ns_loaded,
+-                                LA_SER_CONFIG, mode, &found_other_class,
+-                                false);
+-              if (fd == -1)
+-                free (realname);
+-            }
+-        }
+-
+ #ifdef USE_LDCONFIG
+       if (fd == -1
+ 	  && (__glibc_likely ((mode & __RTLD_SECURE) == 0)
+@@ -2232,6 +2210,28 @@ _dl_map_object (struct link_map *loader, const char *name,
+ 	}
+ #endif
+ 
++      /* Look at the RUNPATH information for this binary.  */
++      if (fd == -1 && loader != NULL
++	  && cache_rpath (loader, &loader->l_runpath_dirs,
++			  DT_RUNPATH, "RUNPATH"))
++	fd = open_path (name, namelen, mode,
++			&loader->l_runpath_dirs, &realname, &fb, loader,
++			LA_SER_RUNPATH, &found_other_class);
++
++      if (fd == -1)
++        {
++          realname = _dl_sysdep_open_object (name, namelen, &fd);
++          if (realname != NULL)
++            {
++              fd = open_verify (realname, fd,
++                                &fb, loader ?: GL(dl_ns)[nsid]._ns_loaded,
++                                LA_SER_CONFIG, mode, &found_other_class,
++                                false);
++              if (fd == -1)
++                free (realname);
++            }
++        }
++
+       /* Finally, try the default path.  */
+       if (fd == -1
+ 	  && ((l = loader ?: GL(dl_ns)[nsid]._ns_loaded) == NULL
-- 
2.29.2





^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [bug#44899] [PATCH v2 3/4] gnu: glibc: Load ${ORIGIN}/../etc/ld.so.cache when available.
  2020-11-28 10:24 ` [bug#44899] [PATCH v2 0/4] Using 'ld.so.cache' to speed up application startup Ludovic Courtès
                     ` (2 preceding siblings ...)
  2020-11-28 10:24   ` [bug#44899] [PATCH v2 3/4] gnu: glibc: Load 'etc/ld.so.cache' in $ORIGIN's store item when available Ludovic Courtès
@ 2020-11-28 10:24   ` Ludovic Courtès
  2020-11-28 10:24   ` [bug#44899] [PATCH v2 4/4] build-system/gnu: Add 'make-dynamic-linker-cache' phase Ludovic Courtès
  2020-12-01 20:45   ` bug#44899: [PATCH v2 0/4] Using 'ld.so.cache' to speed up application startup Ludovic Courtès
  5 siblings, 0 replies; 11+ messages in thread
From: Ludovic Courtès @ 2020-11-28 10:24 UTC (permalink / raw)
  To: 44899; +Cc: Ludovic Courtès

* gnu/packages/patches/glibc-dl-cache.patch: New file.
* gnu/local.mk (dist_patch_DATA): Add it.
* gnu/packages/base.scm (glibc)[source]: Remove 'snippet' and 'modules'.
[arguments]: In 'pre-configure' phase, substitute @STORE_DIRECTORY@ in
'elf/dl-cache.c'.
---
 gnu/local.mk                              |   1 +
 gnu/packages/base.scm                     |  16 +--
 gnu/packages/patches/glibc-dl-cache.patch | 140 ++++++++++++++++++++++
 3 files changed, 147 insertions(+), 10 deletions(-)
 create mode 100644 gnu/packages/patches/glibc-dl-cache.patch

diff --git a/gnu/local.mk b/gnu/local.mk
index f9fed30a3f..82c3c608c6 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -1051,6 +1051,7 @@ dist_patch_DATA =						\
   %D%/packages/patches/glibc-bootstrap-system-2.2.5.patch 	\
   %D%/packages/patches/glibc-bootstrap-system-2.16.0.patch 	\
   %D%/packages/patches/glibc-bootstrap-system.patch		\
+  %D%/packages/patches/glibc-dl-cache.patch			\
   %D%/packages/patches/glibc-hidden-visibility-ldconfig.patch	\
   %D%/packages/patches/glibc-hurd-clock_gettime_monotonic.patch	\
   %D%/packages/patches/glibc-hurd-clock_t_centiseconds.patch	\
diff --git a/gnu/packages/base.scm b/gnu/packages/base.scm
index bd352319a1..ad4415f226 100644
--- a/gnu/packages/base.scm
+++ b/gnu/packages/base.scm
@@ -675,17 +675,8 @@ the store.")
             (sha256
              (base32
               "0di848ibffrnwq7g2dvgqrnn4xqhj3h96csn69q4da51ymafl9qn"))
-            (snippet
-             ;; Disable 'ldconfig' and /etc/ld.so.cache.  The latter is
-             ;; required on LFS distros to avoid loading the distro's libc.so
-             ;; instead of ours.
-             '(begin
-                (substitute* "sysdeps/unix/sysv/linux/configure"
-                  (("use_ldconfig=yes")
-                   "use_ldconfig=no"))
-                #t))
-            (modules '((guix build utils)))
             (patches (search-patches "glibc-ldd-x86_64.patch"
+                                     "glibc-dl-cache.patch"
                                      "glibc-hidden-visibility-ldconfig.patch"
                                      "glibc-versioned-locpath.patch"
                                      "glibc-allow-kernel-2.6.32.patch"
@@ -800,6 +791,11 @@ the store.")
                         ;; 4.7.1.
                         ((" -lgcc_s") ""))
 
+                      ;; Tell the ld.so cache code where the store is.
+                      (substitute* "elf/dl-cache.c"
+                        (("@STORE_DIRECTORY@")
+                         (string-append "\"" (%store-directory) "\"")))
+
                       ;; Have `system' use that Bash.
                       (substitute* "sysdeps/posix/system.c"
                         (("#define[[:blank:]]+SHELL_PATH.*$")
diff --git a/gnu/packages/patches/glibc-dl-cache.patch b/gnu/packages/patches/glibc-dl-cache.patch
new file mode 100644
index 0000000000..0f23b12add
--- /dev/null
+++ b/gnu/packages/patches/glibc-dl-cache.patch
@@ -0,0 +1,140 @@
+Read the shared library cache relative to $ORIGIN instead of reading
+from /etc/ld.so.cache.  Also arrange so that this cache takes
+precedence over RUNPATH.
+
+diff --git a/elf/dl-cache.c b/elf/dl-cache.c
+index 93d185e788..e0760a1f40 100644
+--- a/elf/dl-cache.c
++++ b/elf/dl-cache.c
+@@ -171,6 +171,51 @@ _dl_cache_libcmp (const char *p1, const char *p2)
+   return *p1 - *p2;
+ }
+ 
++/* Special value representing the lack of an ld.so cache.  */
++static const char ld_so_cache_lacking[] = "/ld.so cache is lacking";
++
++/* Return the per-application ld.so cache, relative to $ORIGIN, or NULL if
++   that fails for some reason.  Do not return the system-wide LD_SO_CACHE
++   since on a foreign distro it would contain invalid information.  */
++static const char *
++ld_so_cache (void)
++{
++  static const char *loader_cache;
++
++  if (loader_cache == NULL)
++    {
++      static const char store[] = "/gnu/store";
++      const char *origin = _dl_get_origin ();
++
++      /* Check whether ORIGIN is something like "/gnu/store/…-foo/bin".  */
++      if (strncmp (store, origin, strlen (store)) == 0
++	  && origin[sizeof store - 1] == '/')
++	{
++	  char *store_item_end = strchr (origin + sizeof store, '/');
++
++	  if (store_item_end != NULL)
++	    {
++	      static const char suffix[] = "/etc/ld.so.cache";
++	      size_t store_item_len = store_item_end - origin;
++
++	      /* Note: We can't use 'malloc' because it can be interposed.
++		 Likewise, 'strncpy' is not available.  */
++	      char *cache = alloca (strlen (origin) + sizeof suffix);
++
++	      strcpy (cache, origin);
++	      strcpy (cache + store_item_len, suffix);
++
++	      loader_cache = __strdup (cache) ?: ld_so_cache_lacking;
++	    }
++	  else
++	    loader_cache = ld_so_cache_lacking;
++	}
++      else
++	loader_cache = ld_so_cache_lacking;
++    }
++
++  return loader_cache;
++}
+ 
+ /* Look up NAME in ld.so.cache and return the file name stored there, or null
+    if none is found.  The cache is loaded if it was not already.  If loading
+@@ -190,12 +235,15 @@ _dl_load_cache_lookup (const char *name)
+ 
+   /* Print a message if the loading of libs is traced.  */
+   if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS))
+-    _dl_debug_printf (" search cache=%s\n", LD_SO_CACHE);
++    _dl_debug_printf (" search cache=%s\n", ld_so_cache ());
++
++  if (__glibc_unlikely (ld_so_cache () == ld_so_cache_lacking))
++    return NULL;
+ 
+   if (cache == NULL)
+     {
+       /* Read the contents of the file.  */
+-      void *file = _dl_sysdep_read_whole_file (LD_SO_CACHE, &cachesize,
++      void *file = _dl_sysdep_read_whole_file (ld_so_cache (), &cachesize,
+ 					       PROT_READ);
+ 
+       /* We can handle three different cache file formats here:
+diff --git a/elf/dl-load.c b/elf/dl-load.c
+index f3201e7c14..a69aec3428 100644
+--- a/elf/dl-load.c
++++ b/elf/dl-load.c
+@@ -2152,28 +2152,6 @@ _dl_map_object (struct link_map *loader, const char *name,
+ 			loader ?: GL(dl_ns)[LM_ID_BASE]._ns_loaded,
+ 			LA_SER_LIBPATH, &found_other_class);
+ 
+-      /* Look at the RUNPATH information for this binary.  */
+-      if (fd == -1 && loader != NULL
+-	  && cache_rpath (loader, &loader->l_runpath_dirs,
+-			  DT_RUNPATH, "RUNPATH"))
+-	fd = open_path (name, namelen, mode,
+-			&loader->l_runpath_dirs, &realname, &fb, loader,
+-			LA_SER_RUNPATH, &found_other_class);
+-
+-      if (fd == -1)
+-        {
+-          realname = _dl_sysdep_open_object (name, namelen, &fd);
+-          if (realname != NULL)
+-            {
+-              fd = open_verify (realname, fd,
+-                                &fb, loader ?: GL(dl_ns)[nsid]._ns_loaded,
+-                                LA_SER_CONFIG, mode, &found_other_class,
+-                                false);
+-              if (fd == -1)
+-                free (realname);
+-            }
+-        }
+-
+ #ifdef USE_LDCONFIG
+       if (fd == -1
+ 	  && (__glibc_likely ((mode & __RTLD_SECURE) == 0)
+@@ -2232,6 +2210,28 @@ _dl_map_object (struct link_map *loader, const char *name,
+ 	}
+ #endif
+ 
++      /* Look at the RUNPATH information for this binary.  */
++      if (fd == -1 && loader != NULL
++	  && cache_rpath (loader, &loader->l_runpath_dirs,
++			  DT_RUNPATH, "RUNPATH"))
++	fd = open_path (name, namelen, mode,
++			&loader->l_runpath_dirs, &realname, &fb, loader,
++			LA_SER_RUNPATH, &found_other_class);
++
++      if (fd == -1)
++        {
++          realname = _dl_sysdep_open_object (name, namelen, &fd);
++          if (realname != NULL)
++            {
++              fd = open_verify (realname, fd,
++                                &fb, loader ?: GL(dl_ns)[nsid]._ns_loaded,
++                                LA_SER_CONFIG, mode, &found_other_class,
++                                false);
++              if (fd == -1)
++                free (realname);
++            }
++        }
++
+       /* Finally, try the default path.  */
+       if (fd == -1
+ 	  && ((l = loader ?: GL(dl_ns)[nsid]._ns_loaded) == NULL
-- 
2.29.2





^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [bug#44899] [PATCH v2 4/4] build-system/gnu: Add 'make-dynamic-linker-cache' phase.
  2020-11-28 10:24 ` [bug#44899] [PATCH v2 0/4] Using 'ld.so.cache' to speed up application startup Ludovic Courtès
                     ` (3 preceding siblings ...)
  2020-11-28 10:24   ` [bug#44899] [PATCH v2 3/4] gnu: glibc: Load ${ORIGIN}/../etc/ld.so.cache " Ludovic Courtès
@ 2020-11-28 10:24   ` Ludovic Courtès
  2020-12-01 20:45   ` bug#44899: [PATCH v2 0/4] Using 'ld.so.cache' to speed up application startup Ludovic Courtès
  5 siblings, 0 replies; 11+ messages in thread
From: Ludovic Courtès @ 2020-11-28 10:24 UTC (permalink / raw)
  To: 44899; +Cc: Ludovic Courtès

* guix/build/gnu-build-system.scm (make-dynamic-linker-cache): New
procedure.
(%standard-phases): Add it.
* guix/build-system/gnu.scm (gnu-build, gnu-cross-build): Add
 #:make-dynamic-linker-cache? and honor it.
---
 guix/build-system/gnu.scm       |  4 ++
 guix/build/gnu-build-system.scm | 68 +++++++++++++++++++++++++++++++++
 2 files changed, 72 insertions(+)

diff --git a/guix/build-system/gnu.scm b/guix/build-system/gnu.scm
index 2c23197e77..d6c4dc9bbc 100644
--- a/guix/build-system/gnu.scm
+++ b/guix/build-system/gnu.scm
@@ -342,6 +342,7 @@ standard packages used as implicit inputs of the GNU build system."
                     (strip-directories ''("lib" "lib64" "libexec"
                                           "bin" "sbin"))
                     (validate-runpath? #t)
+                    (make-dynamic-linker-cache? #t)
                     (license-file-regexp %license-file-regexp)
                     (phases '%standard-phases)
                     (locale "en_US.utf8")
@@ -410,6 +411,7 @@ packages that must not be referenced."
                   #:patch-shebangs? ,patch-shebangs?
                   #:strip-binaries? ,strip-binaries?
                   #:validate-runpath? ,validate-runpath?
+                  #:make-dynamic-linker-cache? ,make-dynamic-linker-cache?
                   #:license-file-regexp ,license-file-regexp
                   #:strip-flags ,strip-flags
                   #:strip-directories ,strip-directories)))
@@ -497,6 +499,7 @@ is one of `host' or `target'."
                           (strip-directories ''("lib" "lib64" "libexec"
                                                 "bin" "sbin"))
                           (validate-runpath? #t)
+                          (make-dynamic-linker-cache? #t)
                           (license-file-regexp %license-file-regexp)
                           (phases '%standard-phases)
                           (locale "en_US.utf8")
@@ -577,6 +580,7 @@ platform."
                     #:patch-shebangs? ,patch-shebangs?
                     #:strip-binaries? ,strip-binaries?
                     #:validate-runpath? ,validate-runpath?
+                    #:make-dynamic-linker-cache? ,make-dynamic-linker-cache?
                     #:license-file-regexp ,license-file-regexp
                     #:strip-flags ,strip-flags
                     #:strip-directories ,strip-directories))))
diff --git a/guix/build/gnu-build-system.scm b/guix/build/gnu-build-system.scm
index 8fa11f4ea9..5f08b9d6ac 100644
--- a/guix/build/gnu-build-system.scm
+++ b/guix/build/gnu-build-system.scm
@@ -712,6 +712,73 @@ which cannot be found~%"
                                          (which binary) rest)))))))))
             outputs))
 
+(define* (make-dynamic-linker-cache #:key outputs
+                                    (make-dynamic-linker-cache? #t)
+                                    #:allow-other-keys)
+  "Create a dynamic linker cache under 'etc/ld.so.cache' in each of the
+OUTPUTS.  This reduces application startup time by avoiding the 'stat' storm
+that traversing all the RUNPATH entries entails."
+  (define (make-cache-for-output directory)
+    (define bin-directories
+      (filter-map (lambda (sub-directory)
+                    (let ((directory (string-append directory "/"
+                                                    sub-directory)))
+                      (and (directory-exists? directory)
+                           directory)))
+                  '("bin" "sbin" "libexec")))
+
+    (define programs
+      ;; Programs that can benefit from the ld.so cache.
+      (append-map (lambda (directory)
+                    (if (directory-exists? directory)
+                        (find-files directory
+                                    (lambda (file stat)
+                                      (and (executable-file? file)
+                                           (elf-file? file))))
+                        '()))
+                  bin-directories))
+
+    (define library-path
+      ;; Directories containing libraries that PROGRAMS depend on,
+      ;; recursively.
+      (delete-duplicates
+       (append-map (lambda (program)
+                     (map dirname (file-needed/recursive program)))
+                   programs)))
+
+    (define cache-file
+      (string-append directory "/etc/ld.so.cache"))
+
+    (define ld.so.conf
+      (string-append (or (getenv "TMPDIR") "/tmp")
+                     "/ld.so.conf"))
+
+    (unless (null? library-path)
+      (mkdir-p (dirname cache-file))
+      (guard (c ((invoke-error? c)
+                 ;; Do not treat 'ldconfig' failure as an error.
+                 (format (current-error-port)
+                         "warning: 'ldconfig' failed:~%")
+                 (report-invoke-error c (current-error-port))))
+        ;; Create a config file to tell 'ldconfig' where to look for the
+        ;; libraries that PROGRAMS need.
+        (call-with-output-file ld.so.conf
+          (lambda (port)
+            (for-each (lambda (directory)
+                        (display directory port)
+                        (newline port))
+                      library-path)))
+
+        (invoke "ldconfig" "-f" ld.so.conf "-C" cache-file)
+        (format #t "created '~a' from ~a library search path entries~%"
+                cache-file (length library-path)))))
+
+  (if make-dynamic-linker-cache?
+      (match outputs
+        (((_ . directories) ...)
+         (for-each make-cache-for-output directories)))
+      (format #t "ld.so cache not built~%")))
+
 (define %license-file-regexp
   ;; Regexp matching license files.
   "^(COPYING.*|LICEN[CS]E.*|[Ll]icen[cs]e.*|Copy[Rr]ight(\\.(txt|md))?)$")
@@ -791,6 +858,7 @@ which cannot be found~%"
             validate-documentation-location
             delete-info-dir-file
             patch-dot-desktop-files
+            make-dynamic-linker-cache
             install-license-files
             reset-gzip-timestamps
             compress-documentation)))
-- 
2.29.2





^ permalink raw reply related	[flat|nested] 11+ messages in thread

* bug#44899: [PATCH v2 0/4] Using 'ld.so.cache' to speed up application startup
  2020-11-28 10:24 ` [bug#44899] [PATCH v2 0/4] Using 'ld.so.cache' to speed up application startup Ludovic Courtès
                     ` (4 preceding siblings ...)
  2020-11-28 10:24   ` [bug#44899] [PATCH v2 4/4] build-system/gnu: Add 'make-dynamic-linker-cache' phase Ludovic Courtès
@ 2020-12-01 20:45   ` Ludovic Courtès
  5 siblings, 0 replies; 11+ messages in thread
From: Ludovic Courtès @ 2020-12-01 20:45 UTC (permalink / raw)
  To: 44899-done

Ludovic Courtès <ludo@gnu.org> skribis:

>   gremlin: Fix typo in docstring.
>   gremlin: Add 'file-needed/recursive'.
>   gnu: glibc: Load 'etc/ld.so.cache' in $ORIGIN's store item when
>     available.
>   build-system/gnu: Add 'make-dynamic-linker-cache' phase.

Pushed as f85efa86e7690d9181946351631e02b1c20958c9, sans hard-coded
/gnu/store in the patch as reported by Ricardo on IRC and debugging
leftover in gremlin.scm.

Enjoy… and report any issues you may find!  :-)

Ludo’.




^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2020-12-01 20:46 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-11-27  8:33 [bug#44899] [PATCH 0/3] Using 'ld.so.cache' to speed up application startup Ludovic Courtès
2020-11-27  9:05 ` [bug#44899] [PATCH 1/3] gnu: glibc: Load ${ORIGIN}/../etc/ld.so.cache when available Ludovic Courtès
2020-11-27  9:05   ` [bug#44899] [PATCH 2/3] gremlin: Fix typo in docstring Ludovic Courtès
2020-11-27  9:05   ` [bug#44899] [PATCH 3/3] build-system/gnu: Add 'make-dynamic-linker-cache' phase Ludovic Courtès
2020-11-28 10:24 ` [bug#44899] [PATCH v2 0/4] Using 'ld.so.cache' to speed up application startup Ludovic Courtès
2020-11-28 10:24   ` [bug#44899] [PATCH v2 1/4] gremlin: Fix typo in docstring Ludovic Courtès
2020-11-28 10:24   ` [bug#44899] [PATCH v2 2/4] gremlin: Add 'file-needed/recursive' Ludovic Courtès
2020-11-28 10:24   ` [bug#44899] [PATCH v2 3/4] gnu: glibc: Load 'etc/ld.so.cache' in $ORIGIN's store item when available Ludovic Courtès
2020-11-28 10:24   ` [bug#44899] [PATCH v2 3/4] gnu: glibc: Load ${ORIGIN}/../etc/ld.so.cache " Ludovic Courtès
2020-11-28 10:24   ` [bug#44899] [PATCH v2 4/4] build-system/gnu: Add 'make-dynamic-linker-cache' phase Ludovic Courtès
2020-12-01 20:45   ` bug#44899: [PATCH v2 0/4] Using 'ld.so.cache' to speed up application startup Ludovic Courtès

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/guix.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).