From 3fe923de88919e95baf7ccfa4c77bc1a5053b288 Mon Sep 17 00:00:00 2001 From: Evan Lezar Date: Wed, 2 Jul 2025 14:11:19 +0200 Subject: [PATCH 1/2] [no-relnote] Refactor Ldconfig construction Signed-off-by: Evan Lezar --- .../update-ldcache/update-ldcache.go | 18 +------ internal/ldconfig/ldconfig.go | 48 ++++++++++++++----- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/cmd/nvidia-cdi-hook/update-ldcache/update-ldcache.go b/cmd/nvidia-cdi-hook/update-ldcache/update-ldcache.go index 136d28188..f7f86f850 100644 --- a/cmd/nvidia-cdi-hook/update-ldcache/update-ldcache.go +++ b/cmd/nvidia-cdi-hook/update-ldcache/update-ldcache.go @@ -140,25 +140,11 @@ func updateLdCacheHandler() { // It is invoked from a reexec'd handler and provides namespace isolation for // the operations performed by this hook. At the point where this is invoked, // we are in a new mount namespace that is cloned from the parent. -// -// args[0] is the reexec initializer function name -// args[1] is the path of the ldconfig binary on the host -// args[2] is the container root directory -// The remaining args are folders where soname symlinks need to be created. func updateLdCache(args []string) error { - if len(args) < 3 { - return fmt.Errorf("incorrect arguments: %v", args) - } - hostLdconfigPath := args[1] - containerRootDirPath := args[2] - - ldconfig, err := ldconfig.New( - hostLdconfigPath, - containerRootDirPath, - ) + ldconfig, err := ldconfig.NewFromArgs(args...) if err != nil { return fmt.Errorf("failed to construct ldconfig runner: %w", err) } - return ldconfig.UpdateLDCache(args[3:]...) + return ldconfig.UpdateLDCache() } diff --git a/internal/ldconfig/ldconfig.go b/internal/ldconfig/ldconfig.go index ba707792f..0bd8be944 100644 --- a/internal/ldconfig/ldconfig.go +++ b/internal/ldconfig/ldconfig.go @@ -18,6 +18,7 @@ package ldconfig import ( + "flag" "fmt" "os" "os/exec" @@ -39,37 +40,60 @@ const ( type Ldconfig struct { ldconfigPath string inRoot string + directories []string } // NewRunner creates an exec.Cmd that can be used to run ldconfig. func NewRunner(id string, ldconfigPath string, containerRoot string, additionalargs ...string) (*exec.Cmd, error) { args := []string{ id, - strings.TrimPrefix(config.NormalizeLDConfigPath("@"+ldconfigPath), "@"), - containerRoot, + "--ldconfig-path", strings.TrimPrefix(config.NormalizeLDConfigPath("@"+ldconfigPath), "@"), + "--container-root", containerRoot, } args = append(args, additionalargs...) return createReexecCommand(args) } -// New creates an Ldconfig struct that is used to perform operations on the -// ldcache and libraries in a particular root (e.g. a container). -func New(ldconfigPath string, inRoot string) (*Ldconfig, error) { - l := &Ldconfig{ - ldconfigPath: ldconfigPath, - inRoot: inRoot, +// NewFromArgs creates an Ldconfig struct from the args passed to the Cmd +// above. +// This struct is used to perform operations on the ldcache and libraries in a +// particular root (e.g. a container). +// +// args[0] is the reexec initializer function name +// The following flags are required: +// +// --ldconfig-path=LDCONFIG_PATH the path to ldconfig on the host +// --container-root=CONTAINER_ROOT the path in which ldconfig must be run +// +// The remaining args are folders where soname symlinks need to be created. +func NewFromArgs(args ...string) (*Ldconfig, error) { + if len(args) < 1 { + return nil, fmt.Errorf("incorrect arguments: %v", args) + } + fs := flag.NewFlagSet(args[1], flag.ExitOnError) + ldconfigPath := fs.String("ldconfig-path", "", "the path to ldconfig on the host") + containerRoot := fs.String("container-root", "", "the path in which ldconfig must be run") + if err := fs.Parse(args[1:]); err != nil { + return nil, err } - if ldconfigPath == "" { + + if *ldconfigPath == "" { return nil, fmt.Errorf("an ldconfig path must be specified") } - if inRoot == "" || inRoot == "/" { + if *containerRoot == "" || *containerRoot == "/" { return nil, fmt.Errorf("ldconfig must be run in the non-system root") } + + l := &Ldconfig{ + ldconfigPath: *ldconfigPath, + inRoot: *containerRoot, + directories: fs.Args(), + } return l, nil } -func (l *Ldconfig) UpdateLDCache(directories ...string) error { +func (l *Ldconfig) UpdateLDCache() error { ldconfigPath, err := l.prepareRoot() if err != nil { return err @@ -83,7 +107,7 @@ func (l *Ldconfig) UpdateLDCache(directories ...string) error { "-C", "/etc/ld.so.cache", } - if err := createLdsoconfdFile(ldsoconfdFilenamePattern, directories...); err != nil { + if err := createLdsoconfdFile(ldsoconfdFilenamePattern, l.directories...); err != nil { return fmt.Errorf("failed to update ld.so.conf.d: %w", err) } From a854d3dfa7af2d36b99999c204c53e7e8f2fa055 Mon Sep 17 00:00:00 2001 From: Evan Lezar Date: Tue, 11 Nov 2025 11:18:38 +0100 Subject: [PATCH 2/2] Fix update of ldcache on non-debian containers This change ensures that the ldcache in a non-debian container includes libraries at /lib64 and /usr/lib64 when running on debian host. Signed-off-by: Evan Lezar --- internal/ldconfig/ldconfig.go | 36 ++++++++++++++++++---- tests/e2e/nvidia-container-toolkit_test.go | 19 ++++++++++++ 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/internal/ldconfig/ldconfig.go b/internal/ldconfig/ldconfig.go index 0bd8be944..10ffd6d94 100644 --- a/internal/ldconfig/ldconfig.go +++ b/internal/ldconfig/ldconfig.go @@ -38,9 +38,10 @@ const ( ) type Ldconfig struct { - ldconfigPath string - inRoot string - directories []string + ldconfigPath string + inRoot string + isDebianLikeHost bool + directories []string } // NewRunner creates an exec.Cmd that can be used to run ldconfig. @@ -50,6 +51,9 @@ func NewRunner(id string, ldconfigPath string, containerRoot string, additionala "--ldconfig-path", strings.TrimPrefix(config.NormalizeLDConfigPath("@"+ldconfigPath), "@"), "--container-root", containerRoot, } + if isDebian() { + args = append(args, "--is-debian-like-host") + } args = append(args, additionalargs...) return createReexecCommand(args) @@ -66,6 +70,10 @@ func NewRunner(id string, ldconfigPath string, containerRoot string, additionala // --ldconfig-path=LDCONFIG_PATH the path to ldconfig on the host // --container-root=CONTAINER_ROOT the path in which ldconfig must be run // +// The following flags are optional: +// +// --is-debian-like-host Indicates that the host system is debian-based. +// // The remaining args are folders where soname symlinks need to be created. func NewFromArgs(args ...string) (*Ldconfig, error) { if len(args) < 1 { @@ -74,6 +82,7 @@ func NewFromArgs(args ...string) (*Ldconfig, error) { fs := flag.NewFlagSet(args[1], flag.ExitOnError) ldconfigPath := fs.String("ldconfig-path", "", "the path to ldconfig on the host") containerRoot := fs.String("container-root", "", "the path in which ldconfig must be run") + isDebianLikeHost := fs.Bool("is-debian-like-host", false, "the hook is running from a Debian-like host") if err := fs.Parse(args[1:]); err != nil { return nil, err } @@ -86,9 +95,10 @@ func NewFromArgs(args ...string) (*Ldconfig, error) { } l := &Ldconfig{ - ldconfigPath: *ldconfigPath, - inRoot: *containerRoot, - directories: fs.Args(), + ldconfigPath: *ldconfigPath, + inRoot: *containerRoot, + isDebianLikeHost: *isDebianLikeHost, + directories: fs.Args(), } return l, nil } @@ -106,6 +116,12 @@ func (l *Ldconfig) UpdateLDCache() error { "-f", "/etc/ld.so.conf", "-C", "/etc/ld.so.cache", } + // If we are running in a non-debian container on a debian host we also + // need to add the system directories for non-debian hosts to the list of + // folders processed by ldconfig. + if l.isDebianLikeHost && !isDebian() { + args = append(args, "/lib64", "/usr/lib64") + } if err := createLdsoconfdFile(ldsoconfdFilenamePattern, l.directories...); err != nil { return fmt.Errorf("failed to update ld.so.conf.d: %w", err) @@ -114,6 +130,14 @@ func (l *Ldconfig) UpdateLDCache() error { return SafeExec(ldconfigPath, args, nil) } +func isDebian() bool { + info, err := os.Stat("/etc/debian_version") + if err != nil { + return false + } + return !info.IsDir() +} + func (l *Ldconfig) prepareRoot() (string, error) { // To prevent leaking the parent proc filesystem, we create a new proc mount // in the specified root. diff --git a/tests/e2e/nvidia-container-toolkit_test.go b/tests/e2e/nvidia-container-toolkit_test.go index b2487c436..b2a4feb34 100644 --- a/tests/e2e/nvidia-container-toolkit_test.go +++ b/tests/e2e/nvidia-container-toolkit_test.go @@ -533,4 +533,23 @@ EOF`) Expect(err).ToNot(HaveOccurred()) }) }) + + When("running a container a ubi9 container", Ordered, func() { + var ( + expectedOutput string + ) + BeforeAll(func(ctx context.Context) { + _, _, err := runner.Run(`docker pull redhat/ubi9`) + Expect(err).ToNot(HaveOccurred()) + + expectedOutput, _, err = runner.Run(`docker run --rm --runtime=runc redhat/ubi9 bash -c "ldconfig -p | grep libc.so."`) + Expect(err).ToNot(HaveOccurred()) + }) + + It("should include the system libraries when using the nvidia-container-runtime", func(ctx context.Context) { + output, _, err := runner.Run(`docker run --rm --runtime=nvidia -e NVIDIA_VISIBLE_DEVICES=all redhat/ubi9 bash -c "ldconfig -p | grep libc.so."`) + Expect(err).ToNot(HaveOccurred()) + Expect(output).To(Equal(expectedOutput)) + }) + }) })