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
 }