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..10ffd6d94 100644 --- a/internal/ldconfig/ldconfig.go +++ b/internal/ldconfig/ldconfig.go @@ -18,6 +18,7 @@ package ldconfig import ( + "flag" "fmt" "os" "os/exec" @@ -37,39 +38,72 @@ const ( ) type Ldconfig struct { - ldconfigPath string - inRoot string + ldconfigPath string + inRoot string + isDebianLikeHost bool + 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, + } + if isDebian() { + args = append(args, "--is-debian-like-host") } 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 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 { + 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") + 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 } - 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, + isDebianLikeHost: *isDebianLikeHost, + 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 @@ -82,14 +116,28 @@ func (l *Ldconfig) UpdateLDCache(directories ...string) 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, directories...); err != nil { + if err := createLdsoconfdFile(ldsoconfdFilenamePattern, l.directories...); err != nil { return fmt.Errorf("failed to update ld.so.conf.d: %w", err) } 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)) + }) + }) })