forked from TrueCloudLab/restic
restore: correctly handle existing hardlinks
With hardlinks there's no efficient way to detect which files are linked with each other. Thus, just start from scratch when restore has to modify a hardlinked file.
This commit is contained in:
parent
f19b69af25
commit
d265ec64f2
2 changed files with 39 additions and 15 deletions
|
@ -89,7 +89,18 @@ func createFile(path string, createSize int64, sparse bool) (*os.File, error) {
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
if f == nil || !fi.Mode().IsRegular() {
|
||||
|
||||
mustReplace := f == nil || !fi.Mode().IsRegular()
|
||||
if !mustReplace {
|
||||
ex := fs.ExtendedStat(fi)
|
||||
if ex.Links > 1 {
|
||||
// there is no efficient way to find out which other files might be linked to this file
|
||||
// thus nuke the existing file and start with a fresh one
|
||||
mustReplace = true
|
||||
}
|
||||
}
|
||||
|
||||
if mustReplace {
|
||||
// close handle if we still have it
|
||||
if f != nil {
|
||||
if err := f.Close(); err != nil {
|
||||
|
|
|
@ -45,36 +45,46 @@ func TestCreateFile(t *testing.T) {
|
|||
scenarios := []struct {
|
||||
name string
|
||||
create func(t testing.TB, path string)
|
||||
check func(t testing.TB, path string)
|
||||
err error
|
||||
}{
|
||||
{
|
||||
"file",
|
||||
func(t testing.TB, path string) {
|
||||
name: "file",
|
||||
create: func(t testing.TB, path string) {
|
||||
rtest.OK(t, os.WriteFile(path, []byte("test-test-test-data"), 0o400))
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"empty dir",
|
||||
func(t testing.TB, path string) {
|
||||
name: "empty dir",
|
||||
create: func(t testing.TB, path string) {
|
||||
rtest.OK(t, os.Mkdir(path, 0o400))
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"symlink",
|
||||
func(t testing.TB, path string) {
|
||||
name: "symlink",
|
||||
create: func(t testing.TB, path string) {
|
||||
rtest.OK(t, os.Symlink("./something", path))
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"filled dir",
|
||||
func(t testing.TB, path string) {
|
||||
name: "filled dir",
|
||||
create: func(t testing.TB, path string) {
|
||||
rtest.OK(t, os.Mkdir(path, 0o700))
|
||||
rtest.OK(t, os.WriteFile(filepath.Join(path, "file"), []byte("data"), 0o400))
|
||||
},
|
||||
syscall.ENOTEMPTY,
|
||||
err: syscall.ENOTEMPTY,
|
||||
},
|
||||
{
|
||||
name: "hardlinks",
|
||||
create: func(t testing.TB, path string) {
|
||||
rtest.OK(t, os.WriteFile(path, []byte("test-test-test-data"), 0o400))
|
||||
rtest.OK(t, os.Link(path, path+"h"))
|
||||
},
|
||||
check: func(t testing.TB, path string) {
|
||||
data, err := os.ReadFile(path + "h")
|
||||
rtest.OK(t, err)
|
||||
rtest.Equals(t, "test-test-test-data", string(data), "unexpected content change")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -92,8 +102,8 @@ func TestCreateFile(t *testing.T) {
|
|||
|
||||
for i, sc := range scenarios {
|
||||
t.Run(sc.name, func(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
path := basepath + fmt.Sprintf("%v", i)
|
||||
for j, test := range tests {
|
||||
path := basepath + fmt.Sprintf("%v%v", i, j)
|
||||
sc.create(t, path)
|
||||
f, err := createFile(path, test.size, test.isSparse)
|
||||
if sc.err == nil {
|
||||
|
@ -103,6 +113,9 @@ func TestCreateFile(t *testing.T) {
|
|||
rtest.Assert(t, fi.Mode().IsRegular(), "wrong filetype %v", fi.Mode())
|
||||
rtest.Assert(t, fi.Size() <= test.size, "unexpected file size expected %v, got %v", test.size, fi.Size())
|
||||
rtest.OK(t, f.Close())
|
||||
if sc.check != nil {
|
||||
sc.check(t, path)
|
||||
}
|
||||
} else {
|
||||
rtest.Assert(t, errors.Is(err, sc.err), "unexpected error got %v expected %v", err, sc.err)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue