There was a bug where all files treated as hardlinks in resulting tar archive, now it was hopefully fixed
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"backer/internal/log"
|
"backer/internal/log"
|
||||||
)
|
)
|
||||||
@@ -149,6 +150,7 @@ func writeFilesToTar(ctx context.Context, filepaths []string, tw *tar.Writer, cl
|
|||||||
// If linkTarget is set, ensure the header is marked as a link.
|
// If linkTarget is set, ensure the header is marked as a link.
|
||||||
if linkTarget != "" {
|
if linkTarget != "" {
|
||||||
header.Typeflag = tar.TypeLink
|
header.Typeflag = tar.TypeLink
|
||||||
|
header.Linkname = linkTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
header.Format = tar.FormatGNU
|
header.Format = tar.FormatGNU
|
||||||
@@ -164,8 +166,8 @@ func writeFilesToTar(ctx context.Context, filepaths []string, tw *tar.Writer, cl
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only stream file contents for regular files.
|
// Only stream file contents for regular files that are not hard links.
|
||||||
if mode.IsRegular() {
|
if mode.IsRegular() && linkTarget == "" {
|
||||||
f, err := os.Open(fpath)
|
f, err := os.Open(fpath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Skipping %s: open failed: %v", fpath, err)
|
log.Warnf("Skipping %s: open failed: %v", fpath, err)
|
||||||
@@ -195,15 +197,12 @@ func getInode(fi os.FileInfo) uint64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use type assertion to get inode - works on Unix, returns 0 on Windows.
|
// Use reflection to get Dev and Ino fields from the syscall.Stat_t struct.
|
||||||
type inodeGetter interface {
|
st := fi.Sys()
|
||||||
Dev() uint64
|
// Try type assertion for the struct with fields.
|
||||||
Ino() uint64
|
switch v := st.(type) {
|
||||||
}
|
case *syscall.Stat_t:
|
||||||
|
return (v.Dev << 32) | v.Ino
|
||||||
if ig, ok := fi.Sys().(inodeGetter); ok {
|
|
||||||
// Combine device and inode for uniqueness across file systems.
|
|
||||||
return (ig.Dev() << 32) | ig.Ino()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -247,6 +247,59 @@ func TestCreateTarGzStreamHardLink(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCreateTarGzStreamHardLinkFromTestData tests hard link handling with test data.
|
||||||
|
func TestCreateTarGzStreamHardLinkFromTestData(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("Hard link detection not supported on Windows (inodes unavailable)")
|
||||||
|
}
|
||||||
|
|
||||||
|
testDataPath := filepath.Join("../../test_data", "test1", "hardlinks")
|
||||||
|
filepaths, err := GetFilesFromDirectories(context.Background(), []string{testDataPath})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get files from directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
reader := CreateTarGzStream(ctx, filepaths)
|
||||||
|
defer reader.Close()
|
||||||
|
|
||||||
|
gzReader, err := gzip.NewReader(reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to create gzip reader: %v", err)
|
||||||
|
}
|
||||||
|
defer gzReader.Close()
|
||||||
|
|
||||||
|
tarReader := tar.NewReader(gzReader)
|
||||||
|
|
||||||
|
entries := 0
|
||||||
|
linkCount := 0
|
||||||
|
|
||||||
|
for {
|
||||||
|
header, err := tarReader.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to read tar entry: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entries++
|
||||||
|
t.Logf("Entry %d: name=%s typeflag=%c linkname=%s", entries, header.Name, header.Typeflag, header.Linkname)
|
||||||
|
|
||||||
|
if header.Typeflag == tar.TypeLink {
|
||||||
|
linkCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if entries != 4 {
|
||||||
|
t.Errorf("Expected 4 entries (dir + 1 original + 2 hardlinks), got %d", entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
if linkCount != 2 {
|
||||||
|
t.Errorf("Expected 2 hard link entries, got %d", linkCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestCreateTarGzStreamCancellation tests context cancellation.
|
// TestCreateTarGzStreamCancellation tests context cancellation.
|
||||||
func TestCreateTarGzStreamCancellation(t *testing.T) {
|
func TestCreateTarGzStreamCancellation(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
|
|||||||
1
test_data/test1/hardlinks/hardlink1.txt
Normal file
1
test_data/test1/hardlinks/hardlink1.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
shared content for hardlinks
|
||||||
1
test_data/test1/hardlinks/hardlink2.txt
Normal file
1
test_data/test1/hardlinks/hardlink2.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
shared content for hardlinks
|
||||||
1
test_data/test1/hardlinks/original.txt
Normal file
1
test_data/test1/hardlinks/original.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
shared content for hardlinks
|
||||||
Reference in New Issue
Block a user