From 21d309ed6ac1e57a3d1aa969697b875705e13ad0 Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Mon, 11 Sep 2023 14:04:03 -0500 Subject: [PATCH 1/5] Hardlinks are dereferenced in generated archives Signed-off-by: Juan Bustamante --- pkg/archive/archive.go | 18 +++++++++- pkg/archive/archive_test.go | 32 +++++++++++++++++ pkg/archive/archive_unix.go | 20 +++++++++++ pkg/archive/archive_windows.go | 13 +++++++ .../dir-to-tar-with-hardlink/original-file | 1 + pkg/buildpack/buildpack.go | 5 +++ pkg/buildpack/buildpack_tar_writer.go | 7 +--- pkg/buildpack/buildpack_test.go | 34 ++++++++++++++++++ .../buildpack-with-hardlink/bin/build | 1 + .../buildpack-with-hardlink/bin/detect | 0 .../buildpack-with-hardlink/buildpack.toml | 10 ++++++ .../buildpack-with-hardlink/original-file | 1 + testhelpers/tar_assertions.go | 36 +++++++++++++++++++ 13 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 pkg/archive/archive_unix.go create mode 100644 pkg/archive/archive_windows.go create mode 100644 pkg/archive/testdata/dir-to-tar-with-hardlink/original-file create mode 100644 pkg/buildpack/testdata/buildpack-with-hardlink/bin/build create mode 100644 pkg/buildpack/testdata/buildpack-with-hardlink/bin/detect create mode 100644 pkg/buildpack/testdata/buildpack-with-hardlink/buildpack.toml create mode 100644 pkg/buildpack/testdata/buildpack-with-hardlink/original-file diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go index 9affb3a3c..be47f6576 100644 --- a/pkg/archive/archive.go +++ b/pkg/archive/archive.go @@ -176,6 +176,7 @@ func WriteDirToTar(tw TarWriter, srcDir, basePath string, uid, gid int, mode int } } + hardLinkFiles := map[uint64]string{} return filepath.Walk(srcDir, func(file string, fi os.FileInfo, err error) error { var relPath string if fileFilter != nil { @@ -218,12 +219,27 @@ func WriteDirToTar(tw TarWriter, srcDir, basePath string, uid, gid int, mode int } header.Name = getHeaderNameFromBaseAndRelPath(basePath, relPath) + if hasHardlinks(fi) { + inode, err := getInodeFromStat(fi.Sys()) + if err != nil { + return err + } + + if previousPath, ok := hardLinkFiles[inode]; ok { + header.Typeflag = tar.TypeLink + header.Linkname = previousPath + header.Size = 0 + } else { + hardLinkFiles[inode] = header.Name + } + } + err = writeHeader(header, uid, gid, mode, normalizeModTime, tw) if err != nil { return err } - if hasRegularMode(fi) { + if hasRegularMode(fi) && header.Size > 0 { f, err := os.Open(filepath.Clean(file)) if err != nil { return err diff --git a/pkg/archive/archive_test.go b/pkg/archive/archive_test.go index 6eeaa5431..e144beb27 100644 --- a/pkg/archive/archive_test.go +++ b/pkg/archive/archive_test.go @@ -442,6 +442,38 @@ func testArchive(t *testing.T, when spec.G, it spec.S) { }) }) }) + + when("hard link files are present", func() { + it.Before(func() { + h.SkipIf(t, runtime.GOOS == "windows", "Skipping on windows") + src = filepath.Join("testdata", "dir-to-tar-with-hardlink") + // create a hard link + err := os.Link(filepath.Join(src, "original-file"), filepath.Join(src, "original-file-2")) + h.AssertNil(t, err) + }) + + it.After(func() { + os.RemoveAll(filepath.Join(src, "original-file-2")) + }) + + it("tar file file doesn't include duplicated data", func() { + outputFilename := filepath.Join(tmpDir, "file-with-hard-links.tar") + fh, err := os.Create(outputFilename) + h.AssertNil(t, err) + + tw := tar.NewWriter(fh) + err = archive.WriteDirToTar(tw, src, "/nested/dir", 1234, 2345, 0777, true, false, nil) + + h.AssertNil(t, err) + h.AssertNil(t, tw.Close()) + h.AssertNil(t, fh.Close()) + h.AssertOnTarEntries(t, outputFilename, + "/nested/dir/original-file", + "/nested/dir/original-file-2", + h.HardLinks(), + ) + }) + }) }) when("#WriteZipToTar", func() { diff --git a/pkg/archive/archive_unix.go b/pkg/archive/archive_unix.go new file mode 100644 index 000000000..5eaca5f29 --- /dev/null +++ b/pkg/archive/archive_unix.go @@ -0,0 +1,20 @@ +//go:build linux || darwin + +package archive + +import ( + "os" + "syscall" +) + +func hasHardlinks(fi os.FileInfo) bool { + return fi.Sys().(*syscall.Stat_t).Nlink > 1 +} + +func getInodeFromStat(stat interface{}) (inode uint64, err error) { + s, ok := stat.(*syscall.Stat_t) + if ok { + inode = s.Ino + } + return +} diff --git a/pkg/archive/archive_windows.go b/pkg/archive/archive_windows.go new file mode 100644 index 000000000..42c1b1f19 --- /dev/null +++ b/pkg/archive/archive_windows.go @@ -0,0 +1,13 @@ +package archive + +import ( + "os" +) + +func hasHardlinks(fi os.FileInfo) bool { + return false +} + +func getInodeFromStat(stat interface{}) (inode uint64, err error) { + return +} diff --git a/pkg/archive/testdata/dir-to-tar-with-hardlink/original-file b/pkg/archive/testdata/dir-to-tar-with-hardlink/original-file new file mode 100644 index 000000000..257cc5642 --- /dev/null +++ b/pkg/archive/testdata/dir-to-tar-with-hardlink/original-file @@ -0,0 +1 @@ +foo diff --git a/pkg/buildpack/buildpack.go b/pkg/buildpack/buildpack.go index 182a3c6d0..1a2b3bc49 100644 --- a/pkg/buildpack/buildpack.go +++ b/pkg/buildpack/buildpack.go @@ -227,6 +227,11 @@ func toDistTar(tw archive.TarWriter, descriptor Descriptor, blob Blob) error { header.Mode = calcFileMode(header) header.Name = path.Join(baseTarDir, header.Name) + + if header.Typeflag == tar.TypeLink { + header.Linkname = path.Clean(header.Linkname) + header.Linkname = path.Join(baseTarDir, header.Linkname) + } err = tw.WriteHeader(header) if err != nil { return errors.Wrapf(err, "failed to write header for '%s'", header.Name) diff --git a/pkg/buildpack/buildpack_tar_writer.go b/pkg/buildpack/buildpack_tar_writer.go index c38823cfb..c119b13ff 100644 --- a/pkg/buildpack/buildpack_tar_writer.go +++ b/pkg/buildpack/buildpack_tar_writer.go @@ -107,12 +107,7 @@ func (b *BuildModuleWriter) writeBuildModuleToTar(tw archive.TarWriter, module B return errors.Wrapf(err, "failed to write header for '%s'", header.Name) } - buf, err := io.ReadAll(tr) - if err != nil { - return errors.Wrapf(err, "failed to read contents of '%s'", header.Name) - } - - _, err = tw.Write(buf) + _, err = io.Copy(tw, tr) if err != nil { return errors.Wrapf(err, "failed to write contents to '%s'", header.Name) } diff --git a/pkg/buildpack/buildpack_test.go b/pkg/buildpack/buildpack_test.go index d2adef68f..ac20a48a8 100644 --- a/pkg/buildpack/buildpack_test.go +++ b/pkg/buildpack/buildpack_test.go @@ -17,6 +17,7 @@ import ( "github.com/sclevine/spec/report" "github.com/buildpacks/pack/pkg/archive" + "github.com/buildpacks/pack/pkg/blob" "github.com/buildpacks/pack/pkg/buildpack" "github.com/buildpacks/pack/pkg/dist" h "github.com/buildpacks/pack/testhelpers" @@ -511,6 +512,39 @@ version = "1.2.3" h.AssertNil(t, err) }) }) + + when("hardlink is present", func() { + var bpRootFolder string + + it.Before(func() { + h.SkipIf(t, runtime.GOOS == "windows", "Skipping on windows") + bpRootFolder = filepath.Join("testdata", "buildpack-with-hardlink") + // create a hard link + err := os.Link(filepath.Join(bpRootFolder, "original-file"), filepath.Join(bpRootFolder, "original-file-2")) + h.AssertNil(t, err) + }) + + it.After(func() { + os.RemoveAll(filepath.Join(bpRootFolder, "original-file-2")) + }) + + it("hardlink is preserved in the output tar file", func() { + bp, err := buildpack.FromBuildpackRootBlob( + blob.NewBlob(bpRootFolder), + archive.DefaultTarWriterFactory(), + ) + h.AssertNil(t, err) + + tarPath := writeBlobToFile(bp) + defer os.Remove(tarPath) + + h.AssertOnTarEntries(t, tarPath, + "/cnb/buildpacks/bp.one/1.2.3/original-file", + "/cnb/buildpacks/bp.one/1.2.3/original-file-2", + h.HardLinks(), + ) + }) + }) }) when("#Match", func() { diff --git a/pkg/buildpack/testdata/buildpack-with-hardlink/bin/build b/pkg/buildpack/testdata/buildpack-with-hardlink/bin/build new file mode 100644 index 000000000..c76df1a29 --- /dev/null +++ b/pkg/buildpack/testdata/buildpack-with-hardlink/bin/build @@ -0,0 +1 @@ +build-contents \ No newline at end of file diff --git a/pkg/buildpack/testdata/buildpack-with-hardlink/bin/detect b/pkg/buildpack/testdata/buildpack-with-hardlink/bin/detect new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/buildpack/testdata/buildpack-with-hardlink/buildpack.toml b/pkg/buildpack/testdata/buildpack-with-hardlink/buildpack.toml new file mode 100644 index 000000000..131cb045f --- /dev/null +++ b/pkg/buildpack/testdata/buildpack-with-hardlink/buildpack.toml @@ -0,0 +1,10 @@ +api = "0.3" + +[buildpack] +id = "bp.one" +version = "1.2.3" +homepage = "http://one.buildpack" + +[[stacks]] +id = "some.stack.id" +mixins = ["mixinX", "build:mixinY", "run:mixinZ"] diff --git a/pkg/buildpack/testdata/buildpack-with-hardlink/original-file b/pkg/buildpack/testdata/buildpack-with-hardlink/original-file new file mode 100644 index 000000000..257cc5642 --- /dev/null +++ b/pkg/buildpack/testdata/buildpack-with-hardlink/original-file @@ -0,0 +1 @@ +foo diff --git a/testhelpers/tar_assertions.go b/testhelpers/tar_assertions.go index 1de4442a4..6a936a1ec 100644 --- a/testhelpers/tar_assertions.go +++ b/testhelpers/tar_assertions.go @@ -20,6 +20,8 @@ var gzipMagicHeader = []byte{'\x1f', '\x8b'} type TarEntryAssertion func(t *testing.T, header *tar.Header, data []byte) +type TarEntriesAssertion func(t *testing.T, header1 *tar.Header, data1 []byte, header2 *tar.Header, data2 []byte) + func AssertOnTarEntry(t *testing.T, tarPath, entryPath string, assertFns ...TarEntryAssertion) { t.Helper() @@ -48,6 +50,27 @@ func AssertOnNestedTar(nestedEntryPath string, assertions ...TarEntryAssertion) } } +func AssertOnTarEntries(t *testing.T, tarPath string, entryPath1, entryPath2 string, assertFns ...TarEntriesAssertion) { + t.Helper() + + tarFile, err := os.Open(filepath.Clean(tarPath)) + AssertNil(t, err) + defer tarFile.Close() + + header1, data1, err := readTarFileEntry(tarFile, entryPath1) + AssertNil(t, err) + + _, err = tarFile.Seek(0, io.SeekStart) + AssertNil(t, err) + + header2, data2, err := readTarFileEntry(tarFile, entryPath2) + AssertNil(t, err) + + for _, fn := range assertFns { + fn(t, header1, data1, header2, data2) + } +} + func readTarFileEntry(reader io.Reader, entryPath string) (*tar.Header, []byte, error) { var ( gzipReader *gzip.Reader @@ -113,6 +136,19 @@ func SymlinksTo(expectedTarget string) TarEntryAssertion { } } +func HardLinks() TarEntriesAssertion { + return func(t *testing.T, header1 *tar.Header, _ []byte, header2 *tar.Header, _ []byte) { + t.Helper() + if header1.Typeflag != tar.TypeLink && header2.Typeflag != tar.TypeLink { + t.Fatalf("path '%s' and '%s' are not hardlinks, type flags are '%c' and '%c'", header1.Name, header2.Name, header1.Typeflag, header2.Typeflag) + } + + if header1.Linkname != header2.Name && header2.Linkname != header1.Name { + t.Fatalf("'%s' and '%s' are not the same file", header1.Name, header2.Name) + } + } +} + func HasOwnerAndGroup(expectedUID int, expectedGID int) TarEntryAssertion { return func(t *testing.T, header *tar.Header, _ []byte) { t.Helper() From 9c532299f660617cbe5a2da64a7bcba63a30c522 Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Wed, 13 Sep 2023 15:47:05 -0500 Subject: [PATCH 2/5] Apply suggestions from code review Co-authored-by: Natalie Arellano Signed-off-by: Juan Bustamante --- pkg/archive/archive.go | 4 ++-- pkg/buildpack/buildpack.go | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go index be47f6576..a58261502 100644 --- a/pkg/archive/archive.go +++ b/pkg/archive/archive.go @@ -225,9 +225,9 @@ func WriteDirToTar(tw TarWriter, srcDir, basePath string, uid, gid int, mode int return err } - if previousPath, ok := hardLinkFiles[inode]; ok { + if processedPath, ok := hardLinkFiles[inode]; ok { header.Typeflag = tar.TypeLink - header.Linkname = previousPath + header.Linkname = processedPath header.Size = 0 } else { hardLinkFiles[inode] = header.Name diff --git a/pkg/buildpack/buildpack.go b/pkg/buildpack/buildpack.go index 1a2b3bc49..e658c53e5 100644 --- a/pkg/buildpack/buildpack.go +++ b/pkg/buildpack/buildpack.go @@ -229,8 +229,7 @@ func toDistTar(tw archive.TarWriter, descriptor Descriptor, blob Blob) error { header.Name = path.Join(baseTarDir, header.Name) if header.Typeflag == tar.TypeLink { - header.Linkname = path.Clean(header.Linkname) - header.Linkname = path.Join(baseTarDir, header.Linkname) + header.Linkname = path.Join(baseTarDir, path.Clean(header.Linkname)) } err = tw.WriteHeader(header) if err != nil { From 5f5f2af3a0b0faa4785ff929d80afcb547a44143 Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Wed, 8 Nov 2023 17:04:03 -0500 Subject: [PATCH 3/5] Implementing the methods to detect hardlinks on windows Signed-off-by: Juan Bustamante --- pkg/archive/archive.go | 41 +++++++++++++++-------- pkg/archive/archive_test.go | 9 +++-- pkg/archive/archive_unix.go | 6 ++-- pkg/archive/archive_windows.go | 59 +++++++++++++++++++++++++++++++-- pkg/buildpack/buildpack_test.go | 3 +- 5 files changed, 95 insertions(+), 23 deletions(-) diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go index a58261502..5dd56330c 100644 --- a/pkg/archive/archive.go +++ b/pkg/archive/archive.go @@ -219,19 +219,8 @@ func WriteDirToTar(tw TarWriter, srcDir, basePath string, uid, gid int, mode int } header.Name = getHeaderNameFromBaseAndRelPath(basePath, relPath) - if hasHardlinks(fi) { - inode, err := getInodeFromStat(fi.Sys()) - if err != nil { - return err - } - - if processedPath, ok := hardLinkFiles[inode]; ok { - header.Typeflag = tar.TypeLink - header.Linkname = processedPath - header.Size = 0 - } else { - hardLinkFiles[inode] = header.Name - } + if err = processHardLinks(file, fi, hardLinkFiles, header); err != nil { + return err } err = writeHeader(header, uid, gid, mode, normalizeModTime, tw) @@ -255,6 +244,32 @@ func WriteDirToTar(tw TarWriter, srcDir, basePath string, uid, gid int, mode int }) } +func processHardLinks(file string, fi os.FileInfo, hardLinkFiles map[uint64]string, header *tar.Header) error { + var ( + err error + hardlinks bool + inode uint64 + ) + if hardlinks, err = hasHardlinks(fi, file); err != nil { + return err + } + if hardlinks { + inode, err = getInodeFromStat(fi.Sys(), file) + if err != nil { + return err + } + + if processedPath, ok := hardLinkFiles[inode]; ok { + header.Typeflag = tar.TypeLink + header.Linkname = processedPath + header.Size = 0 + } else { + hardLinkFiles[inode] = header.Name + } + } + return nil +} + // WriteZipToTar writes the contents of a zip file to a tar writer. func WriteZipToTar(tw TarWriter, srcZip, basePath string, uid, gid int, mode int64, normalizeModTime bool, fileFilter func(string) bool) error { zipReader, err := zip.OpenReader(srcZip) diff --git a/pkg/archive/archive_test.go b/pkg/archive/archive_test.go index e144beb27..8c91b2534 100644 --- a/pkg/archive/archive_test.go +++ b/pkg/archive/archive_test.go @@ -40,7 +40,10 @@ func testArchive(t *testing.T, when spec.G, it spec.S) { it.After(func() { if err := os.RemoveAll(tmpDir); err != nil { - t.Fatalf("failed to clean up tmp dir %s: %s", tmpDir, err) + if runtime.GOOS != "windows" { + // skip "The process cannot access the file because it is being used by another process" on windows + t.Fatalf("failed to clean up tmp dir %s: %s", tmpDir, err) + } } }) @@ -445,8 +448,10 @@ func testArchive(t *testing.T, when spec.G, it spec.S) { when("hard link files are present", func() { it.Before(func() { - h.SkipIf(t, runtime.GOOS == "windows", "Skipping on windows") src = filepath.Join("testdata", "dir-to-tar-with-hardlink") + if runtime.GOOS == "windows" { + src = filepath.Join(".", "testdata", "dir-to-tar-with-hardlink") + } // create a hard link err := os.Link(filepath.Join(src, "original-file"), filepath.Join(src, "original-file-2")) h.AssertNil(t, err) diff --git a/pkg/archive/archive_unix.go b/pkg/archive/archive_unix.go index 5eaca5f29..a8bd5d1b0 100644 --- a/pkg/archive/archive_unix.go +++ b/pkg/archive/archive_unix.go @@ -7,11 +7,11 @@ import ( "syscall" ) -func hasHardlinks(fi os.FileInfo) bool { - return fi.Sys().(*syscall.Stat_t).Nlink > 1 +func hasHardlinks(fi os.FileInfo, path string) (bool, error) { + return fi.Sys().(*syscall.Stat_t).Nlink > 1, nil } -func getInodeFromStat(stat interface{}) (inode uint64, err error) { +func getInodeFromStat(stat interface{}, path string) (inode uint64, err error) { s, ok := stat.(*syscall.Stat_t) if ok { inode = s.Ino diff --git a/pkg/archive/archive_windows.go b/pkg/archive/archive_windows.go index 42c1b1f19..e133e874f 100644 --- a/pkg/archive/archive_windows.go +++ b/pkg/archive/archive_windows.go @@ -1,13 +1,66 @@ +//go:build windows + package archive import ( "os" + "syscall" + + "golang.org/x/sys/windows" ) -func hasHardlinks(fi os.FileInfo) bool { - return false +func hasHardlinks(fi os.FileInfo, path string) (bool, error) { + var numberOfLinks uint32 + switch v := fi.Sys().(type) { + case *syscall.ByHandleFileInformation: + numberOfLinks = v.NumberOfLinks + default: + // We need an instance of a ByHandleFileInformation to read NumberOfLinks + info, err := open(path) + if err != nil { + return false, err + } + numberOfLinks = info.NumberOfLinks + } + return numberOfLinks > 1, nil } -func getInodeFromStat(stat interface{}) (inode uint64, err error) { +func getInodeFromStat(stat interface{}, path string) (inode uint64, err error) { + s, ok := stat.(*syscall.ByHandleFileInformation) + if ok { + inode = (uint64(s.FileIndexHigh) << 32) | uint64(s.FileIndexLow) + } else { + s, err = open(path) + if err == nil { + inode = (uint64(s.FileIndexHigh) << 32) | uint64(s.FileIndexLow) + } + } return } + +func open(path string) (*syscall.ByHandleFileInformation, error) { + fPath, err := syscall.UTF16PtrFromString(path) + if err != nil { + return nil, err + } + + handle, err := syscall.CreateFile( + fPath, + windows.FILE_READ_ATTRIBUTES, + syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, + nil, + syscall.OPEN_EXISTING, + syscall.FILE_FLAG_BACKUP_SEMANTICS, + 0) + if err != nil { + return nil, err + } + defer syscall.CloseHandle(handle) + + var info syscall.ByHandleFileInformation + err = syscall.GetFileInformationByHandle(handle, &info) + if err != nil { + return nil, err + } + return &info, nil +} diff --git a/pkg/buildpack/buildpack_test.go b/pkg/buildpack/buildpack_test.go index ac20a48a8..01f7f16d9 100644 --- a/pkg/buildpack/buildpack_test.go +++ b/pkg/buildpack/buildpack_test.go @@ -517,7 +517,6 @@ version = "1.2.3" var bpRootFolder string it.Before(func() { - h.SkipIf(t, runtime.GOOS == "windows", "Skipping on windows") bpRootFolder = filepath.Join("testdata", "buildpack-with-hardlink") // create a hard link err := os.Link(filepath.Join(bpRootFolder, "original-file"), filepath.Join(bpRootFolder, "original-file-2")) @@ -528,7 +527,7 @@ version = "1.2.3" os.RemoveAll(filepath.Join(bpRootFolder, "original-file-2")) }) - it("hardlink is preserved in the output tar file", func() { + it.Focus("hardlink is preserved in the output tar file", func() { bp, err := buildpack.FromBuildpackRootBlob( blob.NewBlob(bpRootFolder), archive.DefaultTarWriterFactory(), From 16e645de8aef311858a38effb27c7c55baf26b12 Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Wed, 15 Nov 2023 16:49:05 -0500 Subject: [PATCH 4/5] documenting new methods added Signed-off-by: Juan Bustamante --- pkg/archive/archive.go | 3 +++ pkg/archive/archive_test.go | 3 --- pkg/archive/archive_unix.go | 2 ++ pkg/archive/archive_windows.go | 6 ++++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go index 5dd56330c..980ed8e78 100644 --- a/pkg/archive/archive.go +++ b/pkg/archive/archive.go @@ -244,6 +244,9 @@ func WriteDirToTar(tw TarWriter, srcDir, basePath string, uid, gid int, mode int }) } +// processHardLinks determine if the given file has hard-links associated with it, the given hardLinkFiles map keeps track +// of any previous hard-link previously processed. In case the hard-link was already found, the header will be updated with +// the previous information otherwise the new hard-link found will be tracked into the map func processHardLinks(file string, fi os.FileInfo, hardLinkFiles map[uint64]string, header *tar.Header) error { var ( err error diff --git a/pkg/archive/archive_test.go b/pkg/archive/archive_test.go index 8c91b2534..c6dc2f7bd 100644 --- a/pkg/archive/archive_test.go +++ b/pkg/archive/archive_test.go @@ -449,9 +449,6 @@ func testArchive(t *testing.T, when spec.G, it spec.S) { when("hard link files are present", func() { it.Before(func() { src = filepath.Join("testdata", "dir-to-tar-with-hardlink") - if runtime.GOOS == "windows" { - src = filepath.Join(".", "testdata", "dir-to-tar-with-hardlink") - } // create a hard link err := os.Link(filepath.Join(src, "original-file"), filepath.Join(src, "original-file-2")) h.AssertNil(t, err) diff --git a/pkg/archive/archive_unix.go b/pkg/archive/archive_unix.go index a8bd5d1b0..b783b6e45 100644 --- a/pkg/archive/archive_unix.go +++ b/pkg/archive/archive_unix.go @@ -7,10 +7,12 @@ import ( "syscall" ) +// hasHardlinks check if the given files has a hard-link associated with it func hasHardlinks(fi os.FileInfo, path string) (bool, error) { return fi.Sys().(*syscall.Stat_t).Nlink > 1, nil } +// getInodeFromStat returns the inode (index node) value associated with the given file func getInodeFromStat(stat interface{}, path string) (inode uint64, err error) { s, ok := stat.(*syscall.Stat_t) if ok { diff --git a/pkg/archive/archive_windows.go b/pkg/archive/archive_windows.go index e133e874f..318936393 100644 --- a/pkg/archive/archive_windows.go +++ b/pkg/archive/archive_windows.go @@ -9,6 +9,7 @@ import ( "golang.org/x/sys/windows" ) +// hasHardlinks returns true if the given file has hard-links associated with it func hasHardlinks(fi os.FileInfo, path string) (bool, error) { var numberOfLinks uint32 switch v := fi.Sys().(type) { @@ -25,6 +26,7 @@ func hasHardlinks(fi os.FileInfo, path string) (bool, error) { return numberOfLinks > 1, nil } +// getInodeFromStat returns an equivalent representation of unix inode on windows based on FileIndexHigh and FileIndexLow values func getInodeFromStat(stat interface{}, path string) (inode uint64, err error) { s, ok := stat.(*syscall.ByHandleFileInformation) if ok { @@ -38,6 +40,7 @@ func getInodeFromStat(stat interface{}, path string) (inode uint64, err error) { return } +// open returns a ByHandleFileInformation object representation of the given file func open(path string) (*syscall.ByHandleFileInformation, error) { fPath, err := syscall.UTF16PtrFromString(path) if err != nil { @@ -58,8 +61,7 @@ func open(path string) (*syscall.ByHandleFileInformation, error) { defer syscall.CloseHandle(handle) var info syscall.ByHandleFileInformation - err = syscall.GetFileInformationByHandle(handle, &info) - if err != nil { + if err = syscall.GetFileInformationByHandle(handle, &info); err != nil { return nil, err } return &info, nil From 3c598f40bea073840ec3711bbce4d7f8f930c2da Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Wed, 20 Dec 2023 10:35:31 -0400 Subject: [PATCH 5/5] fixing some feedback from review Signed-off-by: Juan Bustamante --- pkg/archive/archive_test.go | 2 +- pkg/buildpack/buildpack_test.go | 4 ++-- testhelpers/tar_assertions.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/archive/archive_test.go b/pkg/archive/archive_test.go index c6dc2f7bd..3dbfb5810 100644 --- a/pkg/archive/archive_test.go +++ b/pkg/archive/archive_test.go @@ -472,7 +472,7 @@ func testArchive(t *testing.T, when spec.G, it spec.S) { h.AssertOnTarEntries(t, outputFilename, "/nested/dir/original-file", "/nested/dir/original-file-2", - h.HardLinks(), + h.AreEquivalentHardLinks(), ) }) }) diff --git a/pkg/buildpack/buildpack_test.go b/pkg/buildpack/buildpack_test.go index 01f7f16d9..4536a5e79 100644 --- a/pkg/buildpack/buildpack_test.go +++ b/pkg/buildpack/buildpack_test.go @@ -527,7 +527,7 @@ version = "1.2.3" os.RemoveAll(filepath.Join(bpRootFolder, "original-file-2")) }) - it.Focus("hardlink is preserved in the output tar file", func() { + it("hardlink is preserved in the output tar file", func() { bp, err := buildpack.FromBuildpackRootBlob( blob.NewBlob(bpRootFolder), archive.DefaultTarWriterFactory(), @@ -540,7 +540,7 @@ version = "1.2.3" h.AssertOnTarEntries(t, tarPath, "/cnb/buildpacks/bp.one/1.2.3/original-file", "/cnb/buildpacks/bp.one/1.2.3/original-file-2", - h.HardLinks(), + h.AreEquivalentHardLinks(), ) }) }) diff --git a/testhelpers/tar_assertions.go b/testhelpers/tar_assertions.go index 6a936a1ec..55b2834a4 100644 --- a/testhelpers/tar_assertions.go +++ b/testhelpers/tar_assertions.go @@ -136,7 +136,7 @@ func SymlinksTo(expectedTarget string) TarEntryAssertion { } } -func HardLinks() TarEntriesAssertion { +func AreEquivalentHardLinks() TarEntriesAssertion { return func(t *testing.T, header1 *tar.Header, _ []byte, header2 *tar.Header, _ []byte) { t.Helper() if header1.Typeflag != tar.TypeLink && header2.Typeflag != tar.TypeLink {