From e8f9aff31796430fec56561b3507630c50aa5e34 Mon Sep 17 00:00:00 2001 From: Brad Lugo Date: Thu, 4 Apr 2024 16:34:10 -0700 Subject: [PATCH] tarfs: follow hardlinks in `ReadFile` Signed-off-by: Brad Lugo --- pkg/tarfs/tarfs.go | 19 ++++++++-- pkg/tarfs/tarfs_test.go | 77 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 3 deletions(-) diff --git a/pkg/tarfs/tarfs.go b/pkg/tarfs/tarfs.go index 0b72609c9..3813fbb63 100644 --- a/pkg/tarfs/tarfs.go +++ b/pkg/tarfs/tarfs.go @@ -457,10 +457,23 @@ func (f *FS) ReadFile(name string) ([]byte, error) { if err != nil { return nil, err } - if i.h.FileInfo().Mode().Type()&fs.ModeSymlink != 0 { + + dataSize := i.h.Size + typ := i.h.FileInfo().Mode().Type() + var r *tar.Reader + switch { + case typ.IsRegular() && i.h.Typeflag != tar.TypeLink: + r = tar.NewReader(io.NewSectionReader(f.r, i.off, i.sz)) + case typ.IsRegular() && i.h.Typeflag == tar.TypeLink || typ&fs.ModeSymlink != 0: // is hardlink or symlink return f.ReadFile(i.h.Linkname) + default: + // Pretend all other kinds of files don't exist. + return nil, &fs.PathError{ + Op: op, + Path: name, + Err: fs.ErrExist, + } } - r := tar.NewReader(io.NewSectionReader(f.r, i.off, i.sz)) if _, err := r.Next(); err != nil { return nil, &fs.PathError{ Op: op, @@ -468,7 +481,7 @@ func (f *FS) ReadFile(name string) ([]byte, error) { Err: err, } } - ret := make([]byte, i.h.Size) + ret := make([]byte, dataSize) if _, err := io.ReadFull(r, ret); err != nil { return nil, &fs.PathError{ Op: op, diff --git a/pkg/tarfs/tarfs_test.go b/pkg/tarfs/tarfs_test.go index 52a453b4d..a463d2c53 100644 --- a/pkg/tarfs/tarfs_test.go +++ b/pkg/tarfs/tarfs_test.go @@ -460,6 +460,83 @@ func TestSymlinks(t *testing.T) { })) } +func TestReadFile(t *testing.T) { + type tarfsFile struct { + Header tar.Header + Data []byte + } + + tmp := t.TempDir() + setupAndRun := func(openErr bool, tarfsFiles []tarfsFile, chk func(*testing.T, *FS)) { + t.Helper() + // This is a perfect candidate for using test.GenerateFixture, but + // creates an import cycle. + f, err := os.Create(filepath.Join(tmp, filepath.Base(t.Name()))) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + tw := tar.NewWriter(f) + for _, tarfsFile := range tarfsFiles { + h := tarfsFile.Header + h.Size = int64(len(tarfsFile.Data)) + h.Format = tar.FormatGNU + if err := tw.WriteHeader(&h); err != nil { + t.Error(err) + } + _, err := tw.Write(tarfsFile.Data) + if err != nil { + t.Error(err) + } + } + if err := tw.Close(); err != nil { + t.Error(err) + } + + sys, err := New(f) + t.Log(err) + if (err != nil) != openErr { + t.Fail() + } + + if chk != nil { + chk(t, sys) + } + } + + t.Run("Hardlink", func(t *testing.T) { + originalData := []byte(`Hello, World!`) + abData := make([]byte, len(originalData)) + copy(abData, originalData) + + setupAndRun(false, []tarfsFile{ + { + Header: tar.Header{Name: `a/`}, + }, + { + Header: tar.Header{Name: `a/b`}, + Data: abData, + }, + { + Header: tar.Header{ + Name: `a/c`, + Typeflag: tar.TypeLink, + Linkname: `a/b`, + }, + }, + }, func(t *testing.T, sys *FS) { + acFile, err := sys.ReadFile("a/c") + if err != nil { + t.Errorf("error while opening file: %v", err) + } + if !bytes.Equal(originalData, acFile) { + t.Errorf("unexpected \"%s\", got \"%s\"", originalData, acFile) + } + }) + }) +} + func TestKnownLayers(t *testing.T) { ents, err := os.ReadDir(`testdata/known`) if err != nil {