diff --git a/internal/ldconfig/ldconfig.go b/internal/ldconfig/ldconfig.go index 6e166e3ef..7da70cb62 100644 --- a/internal/ldconfig/ldconfig.go +++ b/internal/ldconfig/ldconfig.go @@ -21,6 +21,7 @@ import ( "bufio" "flag" "fmt" + "io" "os" "os/exec" "path/filepath" @@ -168,6 +169,7 @@ func (l *Ldconfig) UpdateLDCache() error { return fmt.Errorf("failed to write %s drop-in: %w", ldsoconfdFilenamePattern, err) } + systemSearchPaths := l.getSystemSearchPaths() // In most cases, the hook will be executing a host ldconfig that may be configured widely // differently from what the container image expects. // The common case is Debian-like (e.g. Debian, Ubuntu) vs non-Debian-like (e.g. RHEL, Fedora). @@ -176,10 +178,15 @@ func (l *Ldconfig) UpdateLDCache() error { // paths to a drop-in conf file that is likely to be last in lexicographic order. Entries in the // top-level ld.so.conf file may be processed after this drop-in, but this hook does not modify // the top-level file if it exists. - if err := createLdsoconfdFile(defaultLdsoconfdDir, ldsoconfdSystemDirsFilenamePattern, l.getSystemSearchPaths()...); err != nil { + if err := createLdsoconfdFile(defaultLdsoconfdDir, ldsoconfdSystemDirsFilenamePattern, systemSearchPaths...); err != nil { return fmt.Errorf("failed to write %s drop-in: %w", ldsoconfdSystemDirsFilenamePattern, err) } + // Also output the folders to the alpine .path file as required. + if err := createAlpinePathFileIfRequired(append(filteredDirectories, systemSearchPaths...)...); err != nil { + return err + } + return SafeExec(ldconfigPath, args, nil) } @@ -246,23 +253,30 @@ func createLdsoconfdFile(ldsoconfdDir, pattern string, dirs ...string) error { _ = configFile.Close() }() + if err := outputListToFile(configFile, dirs...); err != nil { + return err + } + + // The created file needs to be world readable for the cases where the container is run as a non-root user. + if err := configFile.Chmod(0644); err != nil { + return fmt.Errorf("failed to chmod config file: %w", err) + } + + return nil +} + +func outputListToFile(w io.Writer, dirs ...string) error { added := make(map[string]bool) for _, dir := range dirs { if added[dir] { continue } - _, err := fmt.Fprintf(configFile, "%s\n", dir) + _, err := fmt.Fprintf(w, "%s\n", dir) if err != nil { return fmt.Errorf("failed to update config file: %w", err) } added[dir] = true } - - // The created file needs to be world readable for the cases where the container is run as a non-root user. - if err := configFile.Chmod(0644); err != nil { - return fmt.Errorf("failed to chmod config file: %w", err) - } - return nil } @@ -360,6 +374,38 @@ func processLdsoconfFile(ldsoconfFilename string) ([]string, []string, error) { return directories, includedFilenames, nil } +func createAlpinePathFileIfRequired(dirs ...string) error { + if !isAlpine() { + return nil + } + + var pathFileName string + switch runtime.GOARCH { + case "amd64": + pathFileName = "/etc/ld-musl-x86_64.path" + case "arm64": + pathFileName = "/etc/ld-musl-aarch64.path" + } + + pathFile, err := os.OpenFile(pathFileName, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644) + if err != nil { + return fmt.Errorf("could not open .path file: %w", err) + } + defer func() { + _ = pathFile.Close() + }() + + return outputListToFile(pathFile, dirs...) +} + +func isAlpine() bool { + info, err := os.Stat("/etc/alpine-release") + if err != nil { + return false + } + return !info.IsDir() +} + // isDebianLike returns true if a Debian-like distribution is detected. // Debian-like distributions include Debian and Ubuntu, whereas non-Debian-like // distributions include RHEL and Fedora. diff --git a/tests/e2e/nvidia-container-toolkit_test.go b/tests/e2e/nvidia-container-toolkit_test.go index 33181345e..da35cf6aa 100644 --- a/tests/e2e/nvidia-container-toolkit_test.go +++ b/tests/e2e/nvidia-container-toolkit_test.go @@ -30,6 +30,7 @@ import ( var _ = Describe("docker", Ordered, ContinueOnFailure, func() { var hostDriverVersion string var hostDriverMajor string + var hostOutput string // Install the NVIDIA Container Toolkit BeforeAll(func(ctx context.Context) { @@ -41,6 +42,9 @@ var _ = Describe("docker", Ordered, ContinueOnFailure, func() { hostDriverVersion = strings.TrimSpace(parts[1]) Expect(hostDriverVersion).ToNot(BeEmpty()) hostDriverMajor = strings.SplitN(hostDriverVersion, ".", 2)[0] + + hostOutput, _, err = runner.Run("nvidia-smi -L") + Expect(err).ToNot(HaveOccurred()) }) // GPUs are accessible in a container: Running nvidia-smi -L inside the @@ -48,13 +52,7 @@ var _ = Describe("docker", Ordered, ContinueOnFailure, func() { // container. This means that the following commands must all produce // the same output When("running nvidia-smi -L", Ordered, func() { - var hostOutput string - var err error - BeforeAll(func(ctx context.Context) { - hostOutput, _, err = runner.Run("nvidia-smi -L") - Expect(err).ToNot(HaveOccurred()) - _, _, err := runner.Run("docker pull ubuntu") Expect(err).ToNot(HaveOccurred()) }) @@ -589,4 +587,30 @@ EOF`) Expect(output).To(Equal(expectedOutput)) }) }) + + When("running an alpine container", Ordered, func() { + BeforeAll(func(ctx context.Context) { + _, _, err := runner.Run(`docker build -t alpinecompat \ + - <