From 13bbf71ead2452a7d6e4317c148c5d452beacac5 Mon Sep 17 00:00:00 2001 From: Evan Lezar <elezar@nvidia.com> Date: Fri, 28 Feb 2025 14:13:30 +0200 Subject: [PATCH] Move ContainerRoot type to oci package Thsi change moves the ContainerRoot type to the oci package and updates state.GetContainerRootDirPath to return a variable of type ContainerRoot. This enabled better reuse between hooks. Signed-off-by: Evan Lezar <elezar@nvidia.com> --- cmd/nvidia-cdi-hook/chmod/chmod.go | 5 +- .../create-symlinks/create-symlinks.go | 4 +- cmd/nvidia-cdi-hook/cudacompat/cudacompat.go | 6 +- .../update-ldcache/container-root.go | 46 ----------- .../update-ldcache/update-ldcache.go | 18 ++--- internal/oci/container-root.go | 77 +++++++++++++++++++ internal/oci/state.go | 14 ++-- 7 files changed, 101 insertions(+), 69 deletions(-) delete mode 100644 cmd/nvidia-cdi-hook/update-ldcache/container-root.go create mode 100644 internal/oci/container-root.go diff --git a/cmd/nvidia-cdi-hook/chmod/chmod.go b/cmd/nvidia-cdi-hook/chmod/chmod.go index 9a4ee656..7253b44d 100644 --- a/cmd/nvidia-cdi-hook/chmod/chmod.go +++ b/cmd/nvidia-cdi-hook/chmod/chmod.go @@ -113,7 +113,7 @@ func (m command) run(c *cli.Context, cfg *config) error { return fmt.Errorf("failed to load container state: %v", err) } - containerRoot, err := s.GetContainerRoot() + containerRoot, err := s.GetContainerRootDirPath() if err != nil { return fmt.Errorf("failed to determined container root: %v", err) } @@ -121,7 +121,7 @@ func (m command) run(c *cli.Context, cfg *config) error { return fmt.Errorf("empty container root detected") } - paths := m.getPaths(containerRoot, cfg.paths.Value(), cfg.mode) + paths := m.getPaths(string(containerRoot), cfg.paths.Value(), cfg.mode) if len(paths) == 0 { m.logger.Debugf("No paths specified; exiting") return nil @@ -140,6 +140,7 @@ func (m command) run(c *cli.Context, cfg *config) error { } // getPaths updates the specified paths relative to the root. +// TODO(elezar): This function should be updated to make use of the oci.ContainerRoot type. func (m command) getPaths(root string, paths []string, desiredMode fs.FileMode) []string { var pathsInRoot []string for _, f := range paths { diff --git a/cmd/nvidia-cdi-hook/create-symlinks/create-symlinks.go b/cmd/nvidia-cdi-hook/create-symlinks/create-symlinks.go index 28b0626e..001c3a91 100644 --- a/cmd/nvidia-cdi-hook/create-symlinks/create-symlinks.go +++ b/cmd/nvidia-cdi-hook/create-symlinks/create-symlinks.go @@ -84,7 +84,7 @@ func (m command) run(c *cli.Context, cfg *config) error { return fmt.Errorf("failed to load container state: %v", err) } - containerRoot, err := s.GetContainerRoot() + containerRoot, err := s.GetContainerRootDirPath() if err != nil { return fmt.Errorf("failed to determined container root: %v", err) } @@ -100,7 +100,7 @@ func (m command) run(c *cli.Context, cfg *config) error { return fmt.Errorf("invalid symlink specification %v", l) } - err := m.createLink(containerRoot, parts[0], parts[1]) + err := m.createLink(string(containerRoot), parts[0], parts[1]) if err != nil { return fmt.Errorf("failed to create link %v: %w", parts, err) } diff --git a/cmd/nvidia-cdi-hook/cudacompat/cudacompat.go b/cmd/nvidia-cdi-hook/cudacompat/cudacompat.go index 0cecd6c1..be1bb642 100644 --- a/cmd/nvidia-cdi-hook/cudacompat/cudacompat.go +++ b/cmd/nvidia-cdi-hook/cudacompat/cudacompat.go @@ -103,12 +103,12 @@ func (m command) run(_ *cli.Context, cfg *options) error { return fmt.Errorf("failed to load container state: %w", err) } - containerRootDir, err := s.GetContainerRoot() + containerRootDirPath, err := s.GetContainerRootDirPath() if err != nil { return fmt.Errorf("failed to determined container root: %w", err) } - containerForwardCompatDir, err := m.getContainerForwardCompatDir(containerRoot(containerRootDir), cfg.hostDriverVersion) + containerForwardCompatDir, err := m.getContainerForwardCompatDir(containerRoot(containerRootDirPath), cfg.hostDriverVersion) if err != nil { return fmt.Errorf("failed to get container forward compat directory: %w", err) } @@ -116,7 +116,7 @@ func (m command) run(_ *cli.Context, cfg *options) error { return nil } - return m.createLdsoconfdFile(containerRoot(containerRootDir), cudaCompatLdsoconfdFilenamePattern, containerForwardCompatDir) + return m.createLdsoconfdFile(containerRoot(containerRootDirPath), cudaCompatLdsoconfdFilenamePattern, containerForwardCompatDir) } func (m command) getContainerForwardCompatDir(containerRoot containerRoot, hostDriverVersion string) (string, error) { diff --git a/cmd/nvidia-cdi-hook/update-ldcache/container-root.go b/cmd/nvidia-cdi-hook/update-ldcache/container-root.go deleted file mode 100644 index 71a49469..00000000 --- a/cmd/nvidia-cdi-hook/update-ldcache/container-root.go +++ /dev/null @@ -1,46 +0,0 @@ -/** -# 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 e35da8ae..c39d8cba 100644 --- a/cmd/nvidia-cdi-hook/update-ldcache/update-ldcache.go +++ b/cmd/nvidia-cdi-hook/update-ldcache/update-ldcache.go @@ -108,8 +108,8 @@ func (m command) run(c *cli.Context, cfg *options) error { return fmt.Errorf("failed to load container state: %v", err) } - containerRootDir, err := s.GetContainerRoot() - if err != nil || containerRootDir == "" || containerRootDir == "/" { + containerRootDirPath, err := s.GetContainerRootDirPath() + if err != nil || containerRootDirPath == "" || containerRootDirPath == "/" { return fmt.Errorf("failed to determined container root: %v", err) } @@ -117,7 +117,7 @@ func (m command) run(c *cli.Context, cfg *options) error { args := []string{ filepath.Base(ldconfigPath), // Run ldconfig in the container root directory on the host. - "-r", containerRootDir, + "-r", string(containerRootDirPath), // Explicitly specify using /etc/ld.so.conf since the host's ldconfig may // be configured to use a different config file by default. // Note that since we apply the `-r {{ .containerRootDir }}` argument, /etc/ld.so.conf is @@ -125,9 +125,7 @@ func (m command) run(c *cli.Context, cfg *options) error { "-f", "/etc/ld.so.conf", } - containerRoot := containerRoot(containerRootDir) - - if containerRoot.hasPath("/etc/ld.so.cache") { + if containerRootDirPath.HasPath("/etc/ld.so.cache") { args = append(args, "-C", "/etc/ld.so.cache") } else { m.logger.Debugf("No ld.so.cache found, skipping update") @@ -135,8 +133,8 @@ func (m command) run(c *cli.Context, cfg *options) error { } folders := cfg.folders.Value() - if containerRoot.hasPath("/etc/ld.so.conf.d") { - err := m.createLdsoconfdFile(containerRoot, ldsoconfdFilenamePattern, folders...) + if containerRootDirPath.HasPath("/etc/ld.so.conf.d") { + err := m.createLdsoconfdFile(containerRootDirPath, ldsoconfdFilenamePattern, folders...) if err != nil { return fmt.Errorf("failed to update ld.so.conf.d: %v", err) } @@ -157,13 +155,13 @@ func (m command) resolveLDConfigPath(path string) string { // 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 { +func (m command) createLdsoconfdFile(in oci.ContainerRoot, pattern string, dirs ...string) error { if len(dirs) == 0 { m.logger.Debugf("No directories to add to /etc/ld.so.conf") return nil } - ldsoconfdDir, err := in.resolve("/etc/ld.so.conf.d") + ldsoconfdDir, err := in.Resolve("/etc/ld.so.conf.d") if err != nil { return err } diff --git a/internal/oci/container-root.go b/internal/oci/container-root.go new file mode 100644 index 00000000..13703ff5 --- /dev/null +++ b/internal/oci/container-root.go @@ -0,0 +1,77 @@ +/** +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# 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 oci + +import ( + "os" + "path/filepath" + + "github.com/moby/sys/symlink" +) + +// A ContainerRoot represents the root directory of a container's filesystem. +type ContainerRoot string + +// GlobFiles matches the specified pattern in the container root. +// The files that match must be regular files. Symlinks and directories are ignored. +func (r ContainerRoot) GlobFiles(pattern string) ([]string, error) { + patternPath, err := r.Resolve(pattern) + if err != nil { + return nil, err + } + matches, err := filepath.Glob(patternPath) + if err != nil { + return nil, err + } + var files []string + for _, match := range matches { + info, err := os.Lstat(match) + if err != nil { + return nil, err + } + // Ignore symlinks. + if info.Mode()&os.ModeSymlink != 0 { + continue + } + // Ignore directories. + if info.IsDir() { + continue + } + files = append(files, match) + } + return files, nil +} + +// 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/internal/oci/state.go b/internal/oci/state.go index 2bb4e6e5..1e760959 100644 --- a/internal/oci/state.go +++ b/internal/oci/state.go @@ -72,9 +72,11 @@ func (s *State) LoadSpec() (*specs.Spec, error) { return spec, nil } -// GetContainerRoot returns the root for the container from the associated spec. If the spec is not yet loaded, it is -// loaded and cached. -func (s *State) GetContainerRoot() (string, error) { +// GetContainerRootDirPath returns the root for the container from the associated spec. +// If the spec is not yet loaded, it is loaded and cached. +// The container root directory is the absolute path to the directory containing the root +// of the container filesystem on the host. +func (s *State) GetContainerRootDirPath() (ContainerRoot, error) { spec, err := s.LoadSpec() if err != nil { return "", err @@ -85,9 +87,9 @@ func (s *State) GetContainerRoot() (string, error) { containerRoot = spec.Root.Path } - if filepath.IsAbs(containerRoot) { - return containerRoot, nil + if !filepath.IsAbs(containerRoot) { + containerRoot = filepath.Join(s.Bundle, containerRoot) } - return filepath.Join(s.Bundle, containerRoot), nil + return ContainerRoot(containerRoot), nil }