@@ -46,6 +46,9 @@ const (
4646 // ld.so.conf file, but some may not. And some container images may not have a top-level
4747 // ld.so.conf file at all.
4848 defaultLdsoconfdDir = "/etc/ld.so.conf.d"
49+ // ldsoconfdSystemDirsFilenamePattern specifies the filename pattern for the drop-in conf file
50+ // that includes the expected system directories for the container.
51+ ldsoconfdSystemDirsFilenamePattern = "zz-nvcr-*.conf"
4952)
5053
5154type Ldconfig struct {
@@ -131,7 +134,7 @@ func (l *Ldconfig) UpdateLDCache() error {
131134
132135 // Explicitly specify using /etc/ld.so.conf since the host's ldconfig may
133136 // be configured to use a different config file by default.
134- filteredDirectories , ldconfigDirs , err := l .filterDirectories (defaultTopLevelLdsoconfFilePath , l .directories ... )
137+ filteredDirectories , err := l .filterDirectories (defaultTopLevelLdsoconfFilePath , l .directories ... )
135138 if err != nil {
136139 return err
137140 }
@@ -141,25 +144,23 @@ func (l *Ldconfig) UpdateLDCache() error {
141144 "-f" , defaultTopLevelLdsoconfFilePath ,
142145 "-C" , "/etc/ld.so.cache" ,
143146 }
144- // If we are running in a non-debian container on a debian host we also
145- // need to add the system directories for non-debian hosts to the list of
146- // folders processed by ldconfig.
147- // We only do this if they are not already tracked, since the folders on
148- // on the command line have a higher priority than folders in ld.so.conf.
149- if l .isDebianLikeHost && ! l .isDebianLikeContainer {
150- for _ , systemSearchPath := range l .getSystemSearchPaths () {
151- if _ , ok := ldconfigDirs [systemSearchPath ]; ok {
152- continue
153- }
154- args = append (args , "/lib64" , "/usr/lib64" )
155- }
156- }
157147
158148 if err := ensureLdsoconfFile (defaultTopLevelLdsoconfFilePath , defaultLdsoconfdDir ); err != nil {
159149 return fmt .Errorf ("failed to ensure ld.so.conf file: %w" , err )
160150 }
161151 if err := createLdsoconfdFile (defaultLdsoconfdDir , ldsoconfdFilenamePattern , filteredDirectories ... ); err != nil {
162- return fmt .Errorf ("failed to create ld.so.conf.d drop-in file: %w" , err )
152+ return fmt .Errorf ("failed to write %s drop-in: %w" , ldsoconfdFilenamePattern , err )
153+ }
154+
155+ // In most cases, the hook will be executing a host ldconfig that may be configured widely
156+ // differently from what the container image expects. The common case is Debian vs non-Debian.
157+ // But there are also hosts that configure ldconfig to search in a glibc prefix
158+ // (e.g. /usr/lib/glibc). To avoid all these cases, write the container's expected system search
159+ // paths to a drop-in conf file that is likely to be last in lexicographic order. Entries in the
160+ // top-level ld.so.conf file may be processed after this drop-in, but this hook does not modify
161+ // the top-level file if it exists.
162+ if err := createLdsoconfdFile (defaultLdsoconfdDir , ldsoconfdSystemDirsFilenamePattern , l .getSystemSearchPaths ()... ); err != nil {
163+ return fmt .Errorf ("failed to write %s drop-in: %w" , ldsoconfdSystemDirsFilenamePattern , err )
163164 }
164165
165166 return SafeExec (ldconfigPath , args , nil )
@@ -194,10 +195,10 @@ func (l *Ldconfig) prepareRoot() (string, error) {
194195 return ldconfigPath , nil
195196}
196197
197- func (l * Ldconfig ) filterDirectories (configFilePath string , directories ... string ) ([]string , map [ string ] struct {}, error ) {
198+ func (l * Ldconfig ) filterDirectories (configFilePath string , directories ... string ) ([]string , error ) {
198199 ldconfigDirs , err := l .getLdsoconfDirectories (configFilePath )
199200 if err != nil {
200- return nil , nil , err
201+ return nil , err
201202 }
202203
203204 var filtered []string
@@ -208,7 +209,7 @@ func (l *Ldconfig) filterDirectories(configFilePath string, directories ...strin
208209 filtered = append (filtered , d )
209210 ldconfigDirs [d ] = struct {}{}
210211 }
211- return filtered , ldconfigDirs , nil
212+ return filtered , nil
212213}
213214
214215// createLdsoconfdFile creates a ld.so.conf.d drop-in file with the specified directories on each
@@ -350,22 +351,61 @@ func isDebian() bool {
350351 return ! info .IsDir ()
351352}
352353
353- // nonDebianSystemSearchPaths returns the system search paths for non-Debian
354- // systems.
354+ // nonDebianSystemSearchPaths returns the system search paths for non-Debian systems.
355+ //
356+ // glibc ldconfig's calls `add_system_dir` with `SLIBDIR` and `LIBDIR` (if they are not equal). On
357+ // aarch64 and x86_64, `add_system_dir` is a macro that scans the provided path. If the path ends
358+ // with "/lib64" (or "/libx32", x86_64 only), it strips those suffixes. Then it registers the
359+ // resulting path. Then if the path ends with "/lib", it registers "path"+"64" (and "path"+"x32",
360+ // x86_64 only).
361+ //
362+ // By default, "LIBDIR" is "/usr/lib" and "SLIBDIR" is "/lib". Note that on modern distributions,
363+ // "/lib" is usually a symlink to "/usr/lib" and "/lib64" to "/usr/lib64". ldconfig resolves
364+ // symlinks and skips duplicate directory entries.
355365//
356- // This list was taken from the output of:
366+ // To get the list of system paths, you can invoke the dynamic linker with `--list-diagnostics` and
367+ // look for "path.system_dirs". For example
368+ // `docker run --rm -ti fedora:latest /lib64/ld-linux-x86-64.so.2 --list-diagnostics | grep path.system_dirs`.
357369//
358- // docker run --rm -ti redhat/ubi9 /usr/lib/ld-linux-aarch64.so.1 --help | grep -A6 "Shared library search path"
370+ // On most distributions, including Fedora and derivatives, this yields the following
371+ // ldconfig system search paths.
372+ //
373+ // TODO: Add other architectures that have custom `add_system_dir` macros (e.g. riscv)
374+ // TODO: Replace with executing the container's dynamlic linker with `--list-diagnostics`?
359375func nonDebianSystemSearchPaths () []string {
360- return []string {"/lib64" , "/usr/lib64" }
376+ var paths []string
377+ paths = append (paths , "/lib" , "/usr/lib" )
378+ switch runtime .GOARCH {
379+ case "amd64" :
380+ paths = append (paths ,
381+ "/lib/lib64" ,
382+ "/usr/lib64" ,
383+ "/libx32" ,
384+ "/usr/libx32" ,
385+ )
386+ case "arm64" :
387+ paths = append (paths ,
388+ "/lib/lib64" ,
389+ "/usr/lib64" ,
390+ )
391+ }
392+ return paths
361393}
362394
363- // debianSystemSearchPaths returns the system search paths for Debian-like
364- // systems.
395+ // debianSystemSearchPaths returns the system search paths for Debian-like systems.
365396//
366- // This list was taken from the output of:
397+ // Debian (and derivatives) apply their multi-arch patch to glibc, which modifies ldconfig to
398+ // use the same set of system paths as the dynamic linker. These paths are going to include the
399+ // multi-arch directory _and_ by default "/lib" and "/usr/lib" for compatibility.
367400//
368- // docker run --rm -ti ubuntu /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1 --help | grep -A6 "Shared library search path"
401+ // To get the list of system paths, you can invoke the dynamic linker with `--list-diagnostics` and
402+ // look for "path.system_dirs". For example
403+ // `docker run --rm -ti ubuntu:latest /lib64/ld-linux-x86-64.so.2 --list-diagnostics | grep path.system_dirs`.
404+ //
405+ // This yields the following ldconfig system search paths.
406+ //
407+ // TODO: Add other architectures that have custom `add_system_dir` macros (e.g. riscv)
408+ // TODO: Replace with executing the container's dynamlic linker with `--list-diagnostics`?
369409func debianSystemSearchPaths () []string {
370410 var paths []string
371411 switch runtime .GOARCH {
@@ -381,6 +421,5 @@ func debianSystemSearchPaths() []string {
381421 )
382422 }
383423 paths = append (paths , "/lib" , "/usr/lib" )
384-
385424 return paths
386425}
0 commit comments