mirror of
https://github.com/restic/restic.git
synced 2026-08-06 05:33:17 +00:00
committed by
Alexander Neumann
parent
b2d00b2a86
commit
366bf4eb0c
@@ -15,6 +15,7 @@ import (
|
||||
type dirEntry struct {
|
||||
path string
|
||||
fi os.FileInfo
|
||||
link uint64
|
||||
}
|
||||
|
||||
func walkDir(dir string) <-chan *dirEntry {
|
||||
@@ -36,6 +37,7 @@ func walkDir(dir string) <-chan *dirEntry {
|
||||
ch <- &dirEntry{
|
||||
path: name,
|
||||
fi: info,
|
||||
link: nlink(info),
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -4,7 +4,9 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
@@ -37,5 +39,37 @@ func (e *dirEntry) equals(other *dirEntry) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if stat.Nlink != stat2.Nlink {
|
||||
fmt.Fprintf(os.Stderr, "%v: Number of links do not match (%v != %v)\n", e.path, stat.Nlink, stat2.Nlink)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func nlink(info os.FileInfo) uint64 {
|
||||
stat, _ := info.Sys().(*syscall.Stat_t)
|
||||
return uint64(stat.Nlink)
|
||||
}
|
||||
|
||||
func inode(info os.FileInfo) uint64 {
|
||||
stat, _ := info.Sys().(*syscall.Stat_t)
|
||||
return uint64(stat.Ino)
|
||||
}
|
||||
|
||||
func createFileSetPerHardlink(dir string) map[uint64][]string {
|
||||
var stat syscall.Stat_t
|
||||
linkTests := make(map[uint64][]string)
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for _, f := range files {
|
||||
|
||||
if err := syscall.Stat(filepath.Join(dir, f.Name()), &stat); err != nil {
|
||||
return nil
|
||||
}
|
||||
linkTests[uint64(stat.Ino)] = append(linkTests[uint64(stat.Ino)], f.Name())
|
||||
}
|
||||
return linkTests
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
@@ -25,3 +26,24 @@ func (e *dirEntry) equals(other *dirEntry) bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func nlink(info os.FileInfo) uint64 {
|
||||
return 1
|
||||
}
|
||||
|
||||
func inode(info os.FileInfo) uint64 {
|
||||
return uint64(0)
|
||||
}
|
||||
|
||||
func createFileSetPerHardlink(dir string) map[uint64][]string {
|
||||
linkTests := make(map[uint64][]string)
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for i, f := range files {
|
||||
linkTests[uint64(i)] = append(linkTests[uint64(i)], f.Name())
|
||||
i++
|
||||
}
|
||||
return linkTests
|
||||
}
|
||||
|
||||
@@ -1011,3 +1011,100 @@ func TestPrune(t *testing.T) {
|
||||
testRunCheck(t, gopts)
|
||||
})
|
||||
}
|
||||
|
||||
func TestHardLink(t *testing.T) {
|
||||
// this test assumes a test set with a single directory containing hard linked files
|
||||
withTestEnvironment(t, func(env *testEnvironment, gopts GlobalOptions) {
|
||||
datafile := filepath.Join("testdata", "test.hl.tar.gz")
|
||||
fd, err := os.Open(datafile)
|
||||
if os.IsNotExist(errors.Cause(err)) {
|
||||
t.Skipf("unable to find data file %q, skipping", datafile)
|
||||
return
|
||||
}
|
||||
OK(t, err)
|
||||
OK(t, fd.Close())
|
||||
|
||||
testRunInit(t, gopts)
|
||||
|
||||
SetupTarTestFixture(t, env.testdata, datafile)
|
||||
|
||||
linkTests := createFileSetPerHardlink(env.testdata)
|
||||
|
||||
opts := BackupOptions{}
|
||||
|
||||
// first backup
|
||||
testRunBackup(t, []string{env.testdata}, opts, gopts)
|
||||
snapshotIDs := testRunList(t, "snapshots", gopts)
|
||||
Assert(t, len(snapshotIDs) == 1,
|
||||
"expected one snapshot, got %v", snapshotIDs)
|
||||
|
||||
testRunCheck(t, gopts)
|
||||
|
||||
// restore all backups and compare
|
||||
for i, snapshotID := range snapshotIDs {
|
||||
restoredir := filepath.Join(env.base, fmt.Sprintf("restore%d", i))
|
||||
t.Logf("restoring snapshot %v to %v", snapshotID.Str(), restoredir)
|
||||
testRunRestore(t, gopts, restoredir, snapshotIDs[0])
|
||||
Assert(t, directoriesEqualContents(env.testdata, filepath.Join(restoredir, "testdata")),
|
||||
"directories are not equal")
|
||||
|
||||
linkResults := createFileSetPerHardlink(filepath.Join(restoredir, "testdata"))
|
||||
Assert(t, linksEqual(linkTests, linkResults),
|
||||
"links are not equal")
|
||||
}
|
||||
|
||||
testRunCheck(t, gopts)
|
||||
})
|
||||
}
|
||||
|
||||
func linksEqual(source, dest map[uint64][]string) bool {
|
||||
for _, vs := range source {
|
||||
found := false
|
||||
for kd, vd := range dest {
|
||||
if linkEqual(vs, vd) {
|
||||
delete(dest, kd)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if len(dest) != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func linkEqual(source, dest []string) bool {
|
||||
// equal if sliced are equal without considering order
|
||||
if source == nil && dest == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if source == nil || dest == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(source) != len(dest) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range source {
|
||||
found := false
|
||||
for j := range dest {
|
||||
if source[i] == dest[j] {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
BIN
Binary file not shown.
Reference in New Issue
Block a user