From b598826ff2d591eb15c16e003af01b338c9f2fbb Mon Sep 17 00:00:00 2001 From: Evan Lezar Date: Tue, 25 Feb 2025 17:11:59 +0200 Subject: [PATCH] [no-relnote] Move root to separate file Signed-off-by: Evan Lezar --- .../update-ldcache/container-root.go | 46 +++++++++++ .../update-ldcache/update-ldcache.go | 77 ++++++++++--------- 2 files changed, 86 insertions(+), 37 deletions(-) create mode 100644 cmd/nvidia-cdi-hook/update-ldcache/container-root.go diff --git a/cmd/nvidia-cdi-hook/update-ldcache/container-root.go b/cmd/nvidia-cdi-hook/update-ldcache/container-root.go new file mode 100644 index 00000000..71a49469 --- /dev/null +++ b/cmd/nvidia-cdi-hook/update-ldcache/container-root.go @@ -0,0 +1,46 @@ +/** +# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +**/ + +package ldcache + +import ( + "os" + "path/filepath" + + "github.com/moby/sys/symlink" +) + +// A containerRoot represents the root filesystem of a container. +type containerRoot string + +// hasPath checks whether the specified path exists in the root. +func (r containerRoot) hasPath(path string) bool { + resolved, err := r.resolve(path) + if err != nil { + return false + } + if _, err := os.Stat(resolved); err != nil && os.IsNotExist(err) { + return false + } + return true +} + +// resolve returns the absolute path including root path. +// Symlinks are resolved, but are guaranteed to resolve in the root. +func (r containerRoot) resolve(path string) (string, error) { + absolute := filepath.Clean(filepath.Join(string(r), path)) + return symlink.FollowSymlinkInScope(absolute, string(r)) +} diff --git a/cmd/nvidia-cdi-hook/update-ldcache/update-ldcache.go b/cmd/nvidia-cdi-hook/update-ldcache/update-ldcache.go index 7128aa6b..34bd788b 100644 --- a/cmd/nvidia-cdi-hook/update-ldcache/update-ldcache.go +++ b/cmd/nvidia-cdi-hook/update-ldcache/update-ldcache.go @@ -31,6 +31,15 @@ import ( "github.com/NVIDIA/nvidia-container-toolkit/internal/oci" ) +const ( + // ldsoconfdFilenamePattern specifies the pattern for the filename + // in ld.so.conf.d that includes references to the specified directories. + // The 00-nvcr prefix is chosen to ensure that these libraries have a + // higher precedence than other libraries on the system, but lower than + // the 00-cuda-compat that is included in some containers. + ldsoconfdFilenamePattern = "00-nvcr-*.conf" +) + type command struct { logger logger.Interface } @@ -100,18 +109,20 @@ func (m command) run(c *cli.Context, cfg *options) error { return fmt.Errorf("failed to load container state: %v", err) } - containerRoot, err := s.GetContainerRoot() + containerRootDir, err := s.GetContainerRoot() if err != nil { return fmt.Errorf("failed to determined container root: %v", err) } ldconfigPath := m.resolveLDConfigPath(cfg.ldconfigPath) args := []string{filepath.Base(ldconfigPath)} - if containerRoot != "" { - args = append(args, "-r", containerRoot) + if containerRootDir != "" { + args = append(args, "-r", containerRootDir) } - if root(containerRoot).hasPath("/etc/ld.so.cache") { + containerRoot := containerRoot(containerRootDir) + + if containerRoot.hasPath("/etc/ld.so.cache") { args = append(args, "-C", "/etc/ld.so.cache") } else { m.logger.Debugf("No ld.so.cache found, skipping update") @@ -119,8 +130,8 @@ func (m command) run(c *cli.Context, cfg *options) error { } folders := cfg.folders.Value() - if root(containerRoot).hasPath("/etc/ld.so.conf.d") { - err := m.createConfig(containerRoot, folders) + if containerRoot.hasPath("/etc/ld.so.conf.d") { + err := m.createLdsoconfdFile(containerRoot, ldsoconfdFilenamePattern, folders...) if err != nil { return fmt.Errorf("failed to update ld.so.conf.d: %v", err) } @@ -136,16 +147,6 @@ func (m command) run(c *cli.Context, cfg *options) error { return syscall.Exec(ldconfigPath, args, nil) } -type root string - -func (r root) hasPath(path string) bool { - _, err := os.Stat(filepath.Join(string(r), path)) - if err != nil && os.IsNotExist(err) { - return false - } - return true -} - // resolveLDConfigPath determines the LDConfig path to use for the system. // On systems such as Ubuntu where `/sbin/ldconfig` is a wrapper around // /sbin/ldconfig.real, the latter is returned. @@ -153,44 +154,46 @@ func (m command) resolveLDConfigPath(path string) string { return strings.TrimPrefix(config.NormalizeLDConfigPath("@"+path), "@") } -// createConfig creates (or updates) /etc/ld.so.conf.d/00-nvcr-.conf in the container -// to include the required paths. -// Note that the 00-nvcr prefix is chosen to ensure that these libraries have -// a higher precedence than other libraries on the system but are applied AFTER -// 00-cuda-compat.conf. -func (m command) createConfig(root string, folders []string) error { - if len(folders) == 0 { - m.logger.Debugf("No folders to add to /etc/ld.so.conf") +// createLdsoconfdFile creates a file at /etc/ld.so.conf.d/ in the specified root. +// The file is created at /etc/ld.so.conf.d/{{ .pattern }} using `CreateTemp` and +// contains the specified directories on each line. +func (m command) createLdsoconfdFile(in containerRoot, pattern string, dirs ...string) error { + if len(dirs) == 0 { + m.logger.Debugf("No directories to add to /etc/ld.so.conf") return nil } - if err := os.MkdirAll(filepath.Join(root, "/etc/ld.so.conf.d"), 0755); err != nil { - return fmt.Errorf("failed to create ld.so.conf.d: %v", err) + ldsoconfdDir, err := in.resolve("/etc/ld.so.conf.d") + if err != nil { + return err + } + if err := os.MkdirAll(ldsoconfdDir, 0755); err != nil { + return fmt.Errorf("failed to create ld.so.conf.d: %w", err) } - configFile, err := os.CreateTemp(filepath.Join(root, "/etc/ld.so.conf.d"), "00-nvcr-*.conf") + configFile, err := os.CreateTemp(ldsoconfdDir, pattern) if err != nil { - return fmt.Errorf("failed to create config file: %v", err) + return fmt.Errorf("failed to create config file: %w", err) } defer configFile.Close() - m.logger.Debugf("Adding folders %v to %v", folders, configFile.Name()) + m.logger.Debugf("Adding directories %v to %v", dirs, configFile.Name()) - configured := make(map[string]bool) - for _, folder := range folders { - if configured[folder] { + added := make(map[string]bool) + for _, dir := range dirs { + if added[dir] { continue } - _, err = configFile.WriteString(fmt.Sprintf("%s\n", folder)) + _, err = configFile.WriteString(fmt.Sprintf("%s\n", dir)) if err != nil { - return fmt.Errorf("failed to update ld.so.conf.d: %v", err) + return fmt.Errorf("failed to update config file: %w", err) } - configured[folder] = true + 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 := os.Chmod(configFile.Name(), 0644); err != nil { - return fmt.Errorf("failed to chmod config file: %v", err) + if err := configFile.Chmod(0644); err != nil { + return fmt.Errorf("failed to chmod config file: %w", err) } return nil