diff --git a/cmd/nvidia-cdi-hook/create-symlinks/create-symlinks.go b/cmd/nvidia-cdi-hook/create-symlinks/create-symlinks.go index dd9b5710..df93cfaf 100644 --- a/cmd/nvidia-cdi-hook/create-symlinks/create-symlinks.go +++ b/cmd/nvidia-cdi-hook/create-symlinks/create-symlinks.go @@ -17,6 +17,7 @@ package symlinks import ( + "errors" "fmt" "os" "path/filepath" @@ -25,6 +26,7 @@ import ( "github.com/urfave/cli/v2" "github.com/NVIDIA/nvidia-container-toolkit/internal/logger" + "github.com/NVIDIA/nvidia-container-toolkit/internal/lookup/symlinks" "github.com/NVIDIA/nvidia-container-toolkit/internal/oci" ) @@ -121,8 +123,17 @@ func (m command) run(c *cli.Context, cfg *config) error { func (m command) createLink(containerRoot string, targetPath string, link string) error { linkPath := filepath.Join(containerRoot, link) + exists, err := doesLinkExist(targetPath, linkPath) + if err != nil { + return fmt.Errorf("failed to check if link exists: %w", err) + } + if exists { + m.logger.Debugf("Link %s already exists", linkPath) + return nil + } + m.logger.Infof("Symlinking %v to %v", linkPath, targetPath) - err := os.MkdirAll(filepath.Dir(linkPath), 0755) + err = os.MkdirAll(filepath.Dir(linkPath), 0755) if err != nil { return fmt.Errorf("failed to create directory: %v", err) } @@ -133,3 +144,19 @@ func (m command) createLink(containerRoot string, targetPath string, link string return nil } + +// doesLinkExist returns true if link exists and points to target. +// An error is returned if link exists but points to a different target. +func doesLinkExist(target string, link string) (bool, error) { + currentTarget, err := symlinks.Resolve(link) + if errors.Is(err, os.ErrNotExist) { + return false, nil + } + if err != nil { + return false, fmt.Errorf("failed to resolve existing symlink %s: %w", link, err) + } + if currentTarget == target { + return true, nil + } + return true, fmt.Errorf("unexpected link target: %s", currentTarget) +} diff --git a/cmd/nvidia-cdi-hook/create-symlinks/create-symlinks_test.go b/cmd/nvidia-cdi-hook/create-symlinks/create-symlinks_test.go index 7ce481cf..7a832759 100644 --- a/cmd/nvidia-cdi-hook/create-symlinks/create-symlinks_test.go +++ b/cmd/nvidia-cdi-hook/create-symlinks/create-symlinks_test.go @@ -12,6 +12,36 @@ import ( "github.com/NVIDIA/nvidia-container-toolkit/internal/lookup/symlinks" ) +func TestDoesLinkExist(t *testing.T) { + tmpDir := t.TempDir() + + require.NoError( + t, + makeFs(tmpDir, + dirOrLink{path: "/a/b/c", target: "d"}, + dirOrLink{path: "/a/b/e", target: "/a/b/f"}, + ), + ) + + exists, err := doesLinkExist("d", filepath.Join(tmpDir, "/a/b/c")) + require.NoError(t, err) + require.True(t, exists) + + exists, err = doesLinkExist("/a/b/f", filepath.Join(tmpDir, "/a/b/e")) + require.NoError(t, err) + require.True(t, exists) + + _, err = doesLinkExist("different-target", filepath.Join(tmpDir, "/a/b/c")) + require.Error(t, err) + + _, err = doesLinkExist("/a/b/d", filepath.Join(tmpDir, "/a/b/c")) + require.Error(t, err) + + exists, err = doesLinkExist("foo", filepath.Join(tmpDir, "/a/b/does-not-exist")) + require.NoError(t, err) + require.False(t, exists) +} + func TestCreateLinkRelativePath(t *testing.T) { tmpDir := t.TempDir() hostRoot := filepath.Join(tmpDir, "/host-root/") @@ -56,12 +86,28 @@ func TestCreateLinkAlreadyExists(t *testing.T) { // nvidia-cdi-hook create-symlinks --link libfoo.so.1::/lib/libfoo.so err := getTestCommand().createLink(containerRoot, "libfoo.so.1", "/lib/libfoo.so") - require.Error(t, err) + require.NoError(t, err) target, err := symlinks.Resolve(filepath.Join(containerRoot, "lib/libfoo.so")) require.NoError(t, err) require.Equal(t, "libfoo.so.1", target) } +func TestCreateLinkAlreadyExistsDifferentTarget(t *testing.T) { + tmpDir := t.TempDir() + hostRoot := filepath.Join(tmpDir, "/host-root/") + containerRoot := filepath.Join(tmpDir, "/container-root") + + require.NoError(t, makeFs(hostRoot)) + require.NoError(t, makeFs(containerRoot, dirOrLink{path: "/lib/libfoo.so", target: "different-target"})) + + // nvidia-cdi-hook create-symlinks --link libfoo.so.1::/lib/libfoo.so + err := getTestCommand().createLink(containerRoot, "libfoo.so.1", "/lib/libfoo.so") + require.Error(t, err) + target, err := symlinks.Resolve(filepath.Join(containerRoot, "lib/libfoo.so")) + require.NoError(t, err) + require.Equal(t, "different-target", target) +} + type dirOrLink struct { path string target string