@@ -37,6 +37,12 @@ const (
3737 // higher precedence than other libraries on the system, but lower than
3838 // the 00-cuda-compat that is included in some containers.
3939 ldsoconfdFilenamePattern = "00-nvcr-*.conf"
40+ // ldsoconfdSystemDirsFilenamePattern specifies the filename pattern for the drop-in conf file
41+ // that includes the expected system directories for the container.
42+ // This is chosen to have a high likelihood of being lexographically last in
43+ // in the list of config files, since system search paths should be
44+ // considered last.
45+ ldsoconfdSystemDirsFilenamePattern = "zz-nvcr-*.conf"
4046)
4147
4248type Ldconfig struct {
@@ -123,7 +129,7 @@ func (l *Ldconfig) UpdateLDCache() error {
123129 // Explicitly specify using /etc/ld.so.conf since the host's ldconfig may
124130 // be configured to use a different config file by default.
125131 const topLevelLdsoconfFilePath = "/etc/ld.so.conf"
126- filteredDirectories , ldconfigDirs , err := l .filterDirectories (topLevelLdsoconfFilePath , l .directories ... )
132+ filteredDirectories , err := l .filterDirectories (topLevelLdsoconfFilePath , l .directories ... )
127133 if err != nil {
128134 return err
129135 }
@@ -133,22 +139,22 @@ func (l *Ldconfig) UpdateLDCache() error {
133139 "-f" , topLevelLdsoconfFilePath ,
134140 "-C" , "/etc/ld.so.cache" ,
135141 }
136- // If we are running in a non-debian container on a debian host we also
137- // need to add the system directories for non-debian hosts to the list of
138- // folders processed by ldconfig.
139- // We only do this if they are not already tracked, since the folders on
140- // on the command line have a higher priority than folders in ld.so.conf.
141- if l .isDebianLikeHost && ! l .isDebianLikeContainer {
142- for _ , systemSearchPath := range l .getSystemSearchPaths () {
143- if _ , ok := ldconfigDirs [systemSearchPath ]; ok {
144- continue
145- }
146- args = append (args , systemSearchPath )
147- }
148- }
149142
150143 if err := createLdsoconfdFile (ldsoconfdFilenamePattern , filteredDirectories ... ); err != nil {
151- return fmt .Errorf ("failed to update ld.so.conf.d: %w" , err )
144+ return fmt .Errorf ("failed to write %s drop-in: %w" , ldsoconfdFilenamePattern , err )
145+ }
146+
147+ if l .isDebianLikeHost != l .isDebianLikeContainer {
148+ // In most cases, the hook will be executing a host ldconfig that may be configured widely
149+ // differently from what the container image expects. The common case is Debian vs non-Debian.
150+ // But there are also hosts that configure ldconfig to search in a glibc prefix
151+ // (e.g. /usr/lib/glibc). To avoid all these cases, write the container's expected system search
152+ // paths to a drop-in conf file that is likely to be last in lexicographic order. Entries in the
153+ // top-level ld.so.conf file may be processed after this drop-in, but this hook does not modify
154+ // the top-level file if it exists.
155+ if err := createLdsoconfdFile (ldsoconfdSystemDirsFilenamePattern , l .getSystemSearchPaths ()... ); err != nil {
156+ return fmt .Errorf ("failed to write %s drop-in: %w" , ldsoconfdSystemDirsFilenamePattern , err )
157+ }
152158 }
153159
154160 return SafeExec (ldconfigPath , args , nil )
@@ -183,10 +189,10 @@ func (l *Ldconfig) prepareRoot() (string, error) {
183189 return ldconfigPath , nil
184190}
185191
186- func (l * Ldconfig ) filterDirectories (configFilePath string , directories ... string ) ([]string , map [ string ] struct {}, error ) {
192+ func (l * Ldconfig ) filterDirectories (configFilePath string , directories ... string ) ([]string , error ) {
187193 ldconfigDirs , err := l .getLdsoconfDirectories (configFilePath )
188194 if err != nil {
189- return nil , nil , err
195+ return nil , err
190196 }
191197
192198 var filtered []string
@@ -197,7 +203,7 @@ func (l *Ldconfig) filterDirectories(configFilePath string, directories ...strin
197203 filtered = append (filtered , d )
198204 ldconfigDirs [d ] = struct {}{}
199205 }
200- return filtered , ldconfigDirs , nil
206+ return filtered , nil
201207}
202208
203209// createLdsoconfdFile creates a file at /etc/ld.so.conf.d/.
@@ -324,22 +330,61 @@ func isDebian() bool {
324330 return ! info .IsDir ()
325331}
326332
327- // nonDebianSystemSearchPaths returns the system search paths for non-Debian
328- // systems.
333+ // nonDebianSystemSearchPaths returns the system search paths for non-Debian systems.
334+ //
335+ // glibc ldconfig's calls `add_system_dir` with `SLIBDIR` and `LIBDIR` (if they are not equal). On
336+ // aarch64 and x86_64, `add_system_dir` is a macro that scans the provided path. If the path ends
337+ // with "/lib64" (or "/libx32", x86_64 only), it strips those suffixes. Then it registers the
338+ // resulting path. Then if the path ends with "/lib", it registers "path"+"64" (and "path"+"x32",
339+ // x86_64 only).
329340//
330- // This list was taken from the output of:
341+ // By default, "LIBDIR" is "/usr/lib" and "SLIBDIR" is "/lib". Note that on modern distributions,
342+ // "/lib" is usually a symlink to "/usr/lib" and "/lib64" to "/usr/lib64". ldconfig resolves
343+ // symlinks and skips duplicate directory entries.
331344//
332- // docker run --rm -ti redhat/ubi9 /usr/lib/ld-linux-aarch64.so.1 --help | grep -A6 "Shared library search path"
345+ // To get the list of system paths, you can invoke the dynamic linker with `--list-diagnostics` and
346+ // look for "path.system_dirs". For example
347+ // `docker run --rm -ti fedora:latest /lib64/ld-linux-x86-64.so.2 --list-diagnostics | grep path.system_dirs`.
348+ //
349+ // On most distributions, including Fedora and derivatives, this yields the following
350+ // ldconfig system search paths.
351+ //
352+ // TODO: Add other architectures that have custom `add_system_dir` macros (e.g. riscv)
353+ // TODO: Replace with executing the container's dynamlic linker with `--list-diagnostics`?
333354func nonDebianSystemSearchPaths () []string {
334- return []string {"/lib64" , "/usr/lib64" }
355+ var paths []string
356+ paths = append (paths , "/lib" , "/usr/lib" )
357+ switch runtime .GOARCH {
358+ case "amd64" :
359+ paths = append (paths ,
360+ "/lib/lib64" ,
361+ "/usr/lib64" ,
362+ "/libx32" ,
363+ "/usr/libx32" ,
364+ )
365+ case "arm64" :
366+ paths = append (paths ,
367+ "/lib/lib64" ,
368+ "/usr/lib64" ,
369+ )
370+ }
371+ return paths
335372}
336373
337- // debianSystemSearchPaths returns the system search paths for Debian-like
338- // systems.
374+ // debianSystemSearchPaths returns the system search paths for Debian-like systems.
339375//
340- // This list was taken from the output of:
376+ // Debian (and derivatives) apply their multi-arch patch to glibc, which modifies ldconfig to
377+ // use the same set of system paths as the dynamic linker. These paths are going to include the
378+ // multi-arch directory _and_ by default "/lib" and "/usr/lib" for compatibility.
341379//
342- // docker run --rm -ti ubuntu /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1 --help | grep -A6 "Shared library search path"
380+ // To get the list of system paths, you can invoke the dynamic linker with `--list-diagnostics` and
381+ // look for "path.system_dirs". For example
382+ // `docker run --rm -ti ubuntu:latest /lib64/ld-linux-x86-64.so.2 --list-diagnostics | grep path.system_dirs`.
383+ //
384+ // This yields the following ldconfig system search paths.
385+ //
386+ // TODO: Add other architectures that have custom `add_system_dir` macros (e.g. riscv)
387+ // TODO: Replace with executing the container's dynamlic linker with `--list-diagnostics`?
343388func debianSystemSearchPaths () []string {
344389 var paths []string
345390 switch runtime .GOARCH {
@@ -355,6 +400,5 @@ func debianSystemSearchPaths() []string {
355400 )
356401 }
357402 paths = append (paths , "/lib" , "/usr/lib" )
358-
359403 return paths
360404}
0 commit comments