1818package ldconfig
1919
2020import (
21+ "bufio"
22+ "flag"
2123 "fmt"
2224 "os"
2325 "os/exec"
2426 "path/filepath"
27+ "runtime"
2528 "strings"
2629
2730 "github.com/NVIDIA/nvidia-container-toolkit/internal/config"
@@ -37,53 +40,94 @@ const (
3740)
3841
3942type Ldconfig struct {
40- ldconfigPath string
41- inRoot string
43+ ldconfigPath string
44+ inRoot string
45+ isDebianLikeHost bool
46+ isDebianLikeContainer bool
47+ directories []string
4248}
4349
4450// NewRunner creates an exec.Cmd that can be used to run ldconfig.
4551func NewRunner (id string , ldconfigPath string , containerRoot string , additionalargs ... string ) (* exec.Cmd , error ) {
4652 args := []string {
4753 id ,
48- strings .TrimPrefix (config .NormalizeLDConfigPath ("@" + ldconfigPath ), "@" ),
49- containerRoot ,
54+ "--ldconfig-path" , strings .TrimPrefix (config .NormalizeLDConfigPath ("@" + ldconfigPath ), "@" ),
55+ "--container-root" , containerRoot ,
56+ }
57+ if isDebian () {
58+ args = append (args , "--is-debian-like-host" )
5059 }
5160 args = append (args , additionalargs ... )
5261
5362 return createReexecCommand (args )
5463}
5564
56- // New creates an Ldconfig struct that is used to perform operations on the
57- // ldcache and libraries in a particular root (e.g. a container).
58- func New (ldconfigPath string , inRoot string ) (* Ldconfig , error ) {
59- l := & Ldconfig {
60- ldconfigPath : ldconfigPath ,
61- inRoot : inRoot ,
65+ // NewFromArgs creates an Ldconfig struct from the args passed to the Cmd
66+ // above.
67+ // This struct is used to perform operations on the ldcache and libraries in a
68+ // particular root (e.g. a container).
69+ //
70+ // args[0] is the reexec initializer function name
71+ // The following flags are required:
72+ //
73+ // --ldconfig-path=LDCONFIG_PATH the path to ldconfig on the host
74+ // --container-root=CONTAINER_ROOT the path in which ldconfig must be run
75+ //
76+ // The following flags are optional:
77+ //
78+ // --is-debian-like-host Indicates that the host system is debian-based.
79+ //
80+ // The remaining args are folders where soname symlinks need to be created.
81+ func NewFromArgs (args ... string ) (* Ldconfig , error ) {
82+ if len (args ) < 1 {
83+ return nil , fmt .Errorf ("incorrect arguments: %v" , args )
6284 }
63- if ldconfigPath == "" {
85+ fs := flag .NewFlagSet (args [1 ], flag .ExitOnError )
86+ ldconfigPath := fs .String ("ldconfig-path" , "" , "the path to ldconfig on the host" )
87+ containerRoot := fs .String ("container-root" , "" , "the path in which ldconfig must be run" )
88+ isDebianLikeHost := fs .Bool ("is-debian-like-host" , false , "the hook is running from a Debian-like host" )
89+ if err := fs .Parse (args [1 :]); err != nil {
90+ return nil , err
91+ }
92+
93+ if * ldconfigPath == "" {
6494 return nil , fmt .Errorf ("an ldconfig path must be specified" )
6595 }
66- if inRoot == "" || inRoot == "/" {
96+ if * containerRoot == "" || * containerRoot == "/" {
6797 return nil , fmt .Errorf ("ldconfig must be run in the non-system root" )
6898 }
99+
100+ l := & Ldconfig {
101+ ldconfigPath : * ldconfigPath ,
102+ inRoot : * containerRoot ,
103+ isDebianLikeHost : * isDebianLikeHost ,
104+ isDebianLikeContainer : isDebian (),
105+ directories : fs .Args (),
106+ }
69107 return l , nil
70108}
71109
72- func (l * Ldconfig ) UpdateLDCache (directories ... string ) error {
110+ func (l * Ldconfig ) UpdateLDCache () error {
73111 ldconfigPath , err := l .prepareRoot ()
74112 if err != nil {
75113 return err
76114 }
77115
116+ // Explicitly specify using /etc/ld.so.conf since the host's ldconfig may
117+ // be configured to use a different config file by default.
118+ const topLevelLdsoconfFilePath = "/etc/ld.so.conf"
119+ filteredDirectories , err := l .filterDirectories (topLevelLdsoconfFilePath , l .directories ... )
120+ if err != nil {
121+ return err
122+ }
123+
78124 args := []string {
79125 filepath .Base (ldconfigPath ),
80- // Explicitly specify using /etc/ld.so.conf since the host's ldconfig may
81- // be configured to use a different config file by default.
82- "-f" , "/etc/ld.so.conf" ,
126+ "-f" , topLevelLdsoconfFilePath ,
83127 "-C" , "/etc/ld.so.cache" ,
84128 }
85129
86- if err := createLdsoconfdFile (ldsoconfdFilenamePattern , directories ... ); err != nil {
130+ if err := createLdsoconfdFile (ldsoconfdFilenamePattern , filteredDirectories ... ); err != nil {
87131 return fmt .Errorf ("failed to update ld.so.conf.d: %w" , err )
88132 }
89133
@@ -113,6 +157,22 @@ func (l *Ldconfig) prepareRoot() (string, error) {
113157 return ldconfigPath , nil
114158}
115159
160+ func (l * Ldconfig ) filterDirectories (configFilePath string , directories ... string ) ([]string , error ) {
161+ ldconfigDirs , err := l .getLdsoconfDirectories (configFilePath )
162+ if err != nil {
163+ return nil , err
164+ }
165+
166+ var filtered []string
167+ for _ , d := range directories {
168+ if _ , ok := ldconfigDirs [d ]; ok {
169+ continue
170+ }
171+ filtered = append (filtered , d )
172+ }
173+ return filtered , nil
174+ }
175+
116176// createLdsoconfdFile creates a file at /etc/ld.so.conf.d/.
117177// The file is created at /etc/ld.so.conf.d/{{ .pattern }} using `CreateTemp` and
118178// contains the specified directories on each line.
@@ -153,3 +213,121 @@ func createLdsoconfdFile(pattern string, dirs ...string) error {
153213
154214 return nil
155215}
216+
217+ // getLdsoconfDirectories returns a map of ldsoconf directories to the conf
218+ // files that refer to the directory.
219+ func (l * Ldconfig ) getLdsoconfDirectories (configFilePath string ) (map [string ]struct {}, error ) {
220+ ldconfigDirs := make (map [string ]struct {})
221+ for _ , d := range l .getSystemSerachPaths () {
222+ ldconfigDirs [d ] = struct {}{}
223+ }
224+
225+ processedConfFiles := make (map [string ]bool )
226+ ldsoconfFilenames := []string {configFilePath }
227+ for len (ldsoconfFilenames ) > 0 {
228+ ldsoconfFilename := ldsoconfFilenames [0 ]
229+ ldsoconfFilenames = ldsoconfFilenames [1 :]
230+ if processedConfFiles [ldsoconfFilename ] {
231+ continue
232+ }
233+ processedConfFiles [ldsoconfFilename ] = true
234+
235+ if len (ldsoconfFilename ) == 0 {
236+ continue
237+ }
238+ directories , includedFilenames , err := processLdsoconfFile (ldsoconfFilename )
239+ if err != nil {
240+ return nil , err
241+ }
242+ ldsoconfFilenames = append (ldsoconfFilenames , includedFilenames ... )
243+ for _ , d := range directories {
244+ ldconfigDirs [d ] = struct {}{}
245+ }
246+ }
247+ return ldconfigDirs , nil
248+ }
249+
250+ func (l * Ldconfig ) getSystemSerachPaths () []string {
251+ if l .isDebianLikeContainer {
252+ debianSystemSearchPaths ()
253+ }
254+ return nonDebianSystemSearchPaths ()
255+ }
256+
257+ // processLdsoconfFile extracts the list of directories and included configs
258+ // from the specified file.
259+ func processLdsoconfFile (ldsoconfFilename string ) ([]string , []string , error ) {
260+ ldsoconf , err := os .Open (ldsoconfFilename )
261+ if os .IsNotExist (err ) {
262+ return nil , nil , nil
263+ }
264+ if err != nil {
265+ return nil , nil , err
266+ }
267+ defer ldsoconf .Close ()
268+
269+ var directories []string
270+ var includedFilenames []string
271+ scanner := bufio .NewScanner (ldsoconf )
272+ for scanner .Scan () {
273+ line := strings .TrimSpace (scanner .Text ())
274+ switch {
275+ case strings .HasPrefix (line , "#" ) || len (line ) == 0 :
276+ continue
277+ case strings .HasPrefix (line , "include " ):
278+ include , err := filepath .Glob (strings .TrimPrefix (line , "include " ))
279+ if err != nil {
280+ // We ignore invalid includes.
281+ // TODO: How does ldconfig handle this?
282+ continue
283+ }
284+ includedFilenames = append (includedFilenames , include ... )
285+ default :
286+ directories = append (directories , line )
287+ }
288+ }
289+ return directories , includedFilenames , nil
290+ }
291+
292+ func isDebian () bool {
293+ info , err := os .Stat ("/etc/debian_version" )
294+ if err != nil {
295+ return false
296+ }
297+ return ! info .IsDir ()
298+ }
299+
300+ // nonDebianSystemSearchPaths returns the system search paths for non-Debian
301+ // systems.
302+ //
303+ // This list was taken from the output of:
304+ //
305+ // docker run --rm -ti redhat/ubi9 /usr/lib/ld-linux-aarch64.so.1 --help | grep -A6 "Shared library search path"
306+ func nonDebianSystemSearchPaths () []string {
307+ return []string {"/lib64" , "/usr/lib64" }
308+ }
309+
310+ // debianSystemSearchPaths returns the system search paths for Debian-like
311+ // systems.
312+ //
313+ // This list was taken from the output of:
314+ //
315+ // docker run --rm -ti ubuntu /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1 --help | grep -A6 "Shared library search path"
316+ func debianSystemSearchPaths () []string {
317+ var paths []string
318+ switch runtime .GOARCH {
319+ case "amd64" :
320+ paths = append (paths ,
321+ "/lib/x86_64-linux-gnu" ,
322+ "/usr/lib/x86_64-linux-gnu" ,
323+ )
324+ case "arm64" :
325+ paths = append (paths ,
326+ "/lib/aarch64-linux-gnu" ,
327+ "/usr/lib/aarch64-linux-gnu" ,
328+ )
329+ }
330+ paths = append (paths , "/lib" , "/usr/lib" )
331+
332+ return paths
333+ }
0 commit comments