Browse Source

cmd/nardump: support symlinks, add basic test

Signed-off-by: phanirithvij <[email protected]>
phanirithvij 11 months ago
parent
commit
ad2b075d4f
2 changed files with 73 additions and 5 deletions
  1. 21 5
      cmd/nardump/nardump.go
  2. 52 0
      cmd/nardump/nardump_test.go

+ 21 - 5
cmd/nardump/nardump.go

@@ -100,14 +100,13 @@ func (nw *narWriter) writeDir(dirPath string) error {
 		sub := path.Join(dirPath, ent.Name())
 		var err error
 		switch {
-		case mode.IsRegular():
-			err = nw.writeRegular(sub)
 		case mode.IsDir():
 			err = nw.writeDir(sub)
+		case mode.IsRegular():
+			err = nw.writeRegular(sub)
+		case mode&os.ModeSymlink != 0:
+			err = nw.writeSymlink(sub)
 		default:
-			// TODO(bradfitz): symlink, but requires fighting io/fs a bit
-			// to get at Readlink or the osFS via fs. But for now
-			// we don't need symlinks because they're not in Go's archive.
 			return fmt.Errorf("unsupported file type %v at %q", sub, mode)
 		}
 		if err != nil {
@@ -143,6 +142,23 @@ func (nw *narWriter) writeRegular(path string) error {
 	return nil
 }
 
+func (nw *narWriter) writeSymlink(path string) error {
+	nw.str("(")
+	nw.str("type")
+	nw.str("symlink")
+	nw.str("target")
+	// broken symlinks are valid in a nar
+	// given we do os.chdir(dir) and os.dirfs(".") above
+	// readlink now resolves relative links even if they are broken
+	link, err := os.Readlink(path)
+	if err != nil {
+		return err
+	}
+	nw.str(link)
+	nw.str(")")
+	return nil
+}
+
 func (nw *narWriter) str(s string) {
 	if err := writeString(nw.w, s); err != nil {
 		panic(writeNARError{err})

+ 52 - 0
cmd/nardump/nardump_test.go

@@ -0,0 +1,52 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package main
+
+import (
+	"crypto/sha256"
+	"fmt"
+	"os"
+	"runtime"
+	"testing"
+)
+
+// setupTmpdir sets up a known golden layout, covering all allowed file/folder types in a nar
+func setupTmpdir(t *testing.T) string {
+	tmpdir := t.TempDir()
+	pwd, _ := os.Getwd()
+	os.Chdir(tmpdir)
+	defer os.Chdir(pwd)
+	os.MkdirAll("sub/dir", 0755)
+	os.Symlink("brokenfile", "brokenlink")
+	os.Symlink("sub/dir", "dirl")
+	os.Symlink("/abs/nonexistentdir", "dirb")
+	os.Create("sub/dir/file1")
+	f, _ := os.Create("file2m")
+	_ = f.Truncate(2 * 1024 * 1024)
+	f.Close()
+	os.Symlink("../file2m", "sub/goodlink")
+	return tmpdir
+}
+
+func TestWriteNar(t *testing.T) {
+	if runtime.GOOS == "windows" {
+		// Skip test on Windows as the Nix package manager is not supported on this platform
+		t.Skip("nix package manager is not available on Windows")
+	}
+	dir := setupTmpdir(t)
+	t.Run("nar", func(t *testing.T) {
+		// obtained via `nix-store --dump /tmp/... | sha256sum` of the above test dir
+		expected := "727613a36f41030e93a4abf2649c3ec64a2757ccff364e3f6f7d544eb976e442"
+		h := sha256.New()
+		os.Chdir(dir)
+		err := writeNAR(h, os.DirFS("."))
+		if err != nil {
+			t.Fatal(err)
+		}
+		hash := fmt.Sprintf("%x", h.Sum(nil))
+		if expected != hash {
+			t.Fatal("sha256sum of nar not matched", hash, expected)
+		}
+	})
+}