@@ -37,6 +37,9 @@ 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+ ldsoconfdSystemDirsFilenamePattern = "99-nvcr-*.conf"
4043)
4144
4245type Ldconfig struct {
@@ -123,7 +126,7 @@ func (l *Ldconfig) UpdateLDCache() error {
123126 // Explicitly specify using /etc/ld.so.conf since the host's ldconfig may
124127 // be configured to use a different config file by default.
125128 const topLevelLdsoconfFilePath = "/etc/ld.so.conf"
126- filteredDirectories , ldconfigDirs , err := l .filterDirectories (topLevelLdsoconfFilePath , l .directories ... )
129+ filteredDirectories , err := l .filterDirectories (topLevelLdsoconfFilePath , l .directories ... )
127130 if err != nil {
128131 return err
129132 }
@@ -133,22 +136,20 @@ func (l *Ldconfig) UpdateLDCache() error {
133136 "-f" , topLevelLdsoconfFilePath ,
134137 "-C" , "/etc/ld.so.cache" ,
135138 }
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 , "/lib64" , "/usr/lib64" )
147- }
148- }
149139
150140 if err := createLdsoconfdFile (ldsoconfdFilenamePattern , filteredDirectories ... ); err != nil {
151- return fmt .Errorf ("failed to update ld.so.conf.d: %w" , err )
141+ return fmt .Errorf ("failed to write %s drop-in: %w" , ldsoconfdFilenamePattern , err )
142+ }
143+
144+ // In most cases, the hook will be executing a host ldconfig that may be configured widely
145+ // differently from what the container image expects. The common case is Debian vs non-Debian.
146+ // But there are also hosts that configure ldconfig to search in a glibc prefix
147+ // (e.g. /usr/lib/glibc). To avoid all these cases, write the container's expected system search
148+ // paths to a drop-in conf file that is likely to be last in lexicographic order. Entries in the
149+ // top-level ld.so.conf file may be processed after this drop-in, but this hook does not modify
150+ // the top-level file if it exists.
151+ if err := createLdsoconfdFile (ldsoconfdSystemDirsFilenamePattern , l .getSystemSearchPaths ()... ); err != nil {
152+ return fmt .Errorf ("failed to write %s drop-in: %w" , ldsoconfdSystemDirsFilenamePattern , err )
152153 }
153154
154155 return SafeExec (ldconfigPath , args , nil )
@@ -183,10 +184,10 @@ func (l *Ldconfig) prepareRoot() (string, error) {
183184 return ldconfigPath , nil
184185}
185186
186- func (l * Ldconfig ) filterDirectories (configFilePath string , directories ... string ) ([]string , map [ string ] struct {}, error ) {
187+ func (l * Ldconfig ) filterDirectories (configFilePath string , directories ... string ) ([]string , error ) {
187188 ldconfigDirs , err := l .getLdsoconfDirectories (configFilePath )
188189 if err != nil {
189- return nil , nil , err
190+ return nil , err
190191 }
191192
192193 var filtered []string
@@ -197,7 +198,7 @@ func (l *Ldconfig) filterDirectories(configFilePath string, directories ...strin
197198 filtered = append (filtered , d )
198199 ldconfigDirs [d ] = struct {}{}
199200 }
200- return filtered , ldconfigDirs , nil
201+ return filtered , nil
201202}
202203
203204// createLdsoconfdFile creates a file at /etc/ld.so.conf.d/.
@@ -241,6 +242,24 @@ func createLdsoconfdFile(pattern string, dirs ...string) error {
241242 return nil
242243}
243244
245+ func appendSystemSearchPathsToLdsoconf (configFilePath string , dirs ... string ) error {
246+ if len (dirs ) == 0 {
247+ return nil
248+ }
249+ configFile , err := os .OpenFile (configFilePath , os .O_APPEND | os .O_WRONLY , 0644 )
250+ if err != nil {
251+ return fmt .Errorf ("failed to open config file: %w" , err )
252+ }
253+ defer configFile .Close ()
254+ for _ , dir := range dirs {
255+ _ , err = fmt .Fprintf (configFile , "%s\n " , dir )
256+ if err != nil {
257+ return fmt .Errorf ("failed to update config file: %w" , err )
258+ }
259+ }
260+ return nil
261+ }
262+
244263// getLdsoconfDirectories returns a map of ldsoconf directories to the conf
245264// files that refer to the directory.
246265func (l * Ldconfig ) getLdsoconfDirectories (configFilePath string ) (map [string ]struct {}, error ) {
@@ -324,22 +343,61 @@ func isDebian() bool {
324343 return ! info .IsDir ()
325344}
326345
327- // nonDebianSystemSearchPaths returns the system search paths for non-Debian
328- // systems.
346+ // nonDebianSystemSearchPaths returns the system search paths for non-Debian systems.
347+ //
348+ // glibc ldconfig's calls `add_system_dir` with `SLIBDIR` and `LIBDIR` (if they are not equal). On
349+ // aarch64 and x86_64, `add_system_dir` is a macro that scans the provided path. If the path ends
350+ // with "/lib64" (or "/libx32", x86_64 only), it strips those suffixes. Then it registers the
351+ // resulting path. Then if the path ends with "/lib", it registers "path"+"64" (and "path"+"x32",
352+ // x86_64 only).
353+ //
354+ // By default, "LIBDIR" is "/usr/lib" and "SLIBDIR" is "/lib". Note that on modern distributions,
355+ // "/lib" is usually a symlink to "/usr/lib" and "/lib64" to "/usr/lib64". ldconfig resolves
356+ // symlinks and skips duplicate directory entries.
357+ //
358+ // To get the list of system paths, you can invoke the dynamic linker with `--list-diagnostics` and
359+ // look for "path.system_dirs". For example
360+ // `docker run --rm -ti fedora:latest /lib64/ld-linux-x86-64.so.2 --list-diagnostics | grep path.system_dirs`.
329361//
330- // This list was taken from the output of:
362+ // On most distributions, including Fedora and derivatives, this yields the following
363+ // ldconfig system search paths.
331364//
332- // docker run --rm -ti redhat/ubi9 /usr/lib/ld-linux-aarch64.so.1 --help | grep -A6 "Shared library search path"
365+ // TODO: Add other architectures that have custom `add_system_dir` macros (e.g. riscv)
366+ // TODO: Replace with executing the container's dynamlic linker with `--list-diagnostics`?
333367func nonDebianSystemSearchPaths () []string {
334- return []string {"/lib64" , "/usr/lib64" }
368+ var paths []string
369+ paths = append (paths , "/lib" , "/usr/lib" )
370+ switch runtime .GOARCH {
371+ case "amd64" :
372+ paths = append (paths ,
373+ "/lib/lib64" ,
374+ "/usr/lib64" ,
375+ "/libx32" ,
376+ "/usr/libx32" ,
377+ )
378+ case "arm64" :
379+ paths = append (paths ,
380+ "/lib/lib64" ,
381+ "/usr/lib64" ,
382+ )
383+ }
384+ return paths
335385}
336386
337- // debianSystemSearchPaths returns the system search paths for Debian-like
338- // systems.
387+ // debianSystemSearchPaths returns the system search paths for Debian-like systems.
388+ //
389+ // Debian (and derivatives) apply their multi-arch patch to glibc, which modifies ldconfig to
390+ // use the same set of system paths as the dynamic linker. These paths are going to include the
391+ // multi-arch directory _and_ by default "/lib" and "/usr/lib" for compatibility.
339392//
340- // This list was taken from the output of:
393+ // To get the list of system paths, you can invoke the dynamic linker with `--list-diagnostics` and
394+ // look for "path.system_dirs". For example
395+ // `docker run --rm -ti ubuntu:latest /lib64/ld-linux-x86-64.so.2 --list-diagnostics | grep path.system_dirs`.
341396//
342- // docker run --rm -ti ubuntu /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1 --help | grep -A6 "Shared library search path"
397+ // This yields the following ldconfig system search paths.
398+ //
399+ // TODO: Add other architectures that have custom `add_system_dir` macros (e.g. riscv)
400+ // TODO: Replace with executing the container's dynamlic linker with `--list-diagnostics`?
343401func debianSystemSearchPaths () []string {
344402 var paths []string
345403 switch runtime .GOARCH {
@@ -355,6 +413,5 @@ func debianSystemSearchPaths() []string {
355413 )
356414 }
357415 paths = append (paths , "/lib" , "/usr/lib" )
358-
359416 return paths
360417}
0 commit comments