2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-27 15:26:37 +00:00

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:
Michael Eischer 2024-06-01 22:21:16 +02:00
parent f19b69af25
commit d265ec64f2
2 changed files with 39 additions and 15 deletions

View File

@ -89,7 +89,18 @@ func createFile(path string, createSize int64, sparse bool) (*os.File, error) {
return nil, err 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 // close handle if we still have it
if f != nil { if f != nil {
if err := f.Close(); err != nil { if err := f.Close(); err != nil {

View File

@ -45,36 +45,46 @@ func TestCreateFile(t *testing.T) {
scenarios := []struct { scenarios := []struct {
name string name string
create func(t testing.TB, path string) create func(t testing.TB, path string)
check func(t testing.TB, path string)
err error err error
}{ }{
{ {
"file", name: "file",
func(t testing.TB, path string) { create: func(t testing.TB, path string) {
rtest.OK(t, os.WriteFile(path, []byte("test-test-test-data"), 0o400)) rtest.OK(t, os.WriteFile(path, []byte("test-test-test-data"), 0o400))
}, },
nil,
}, },
{ {
"empty dir", name: "empty dir",
func(t testing.TB, path string) { create: func(t testing.TB, path string) {
rtest.OK(t, os.Mkdir(path, 0o400)) rtest.OK(t, os.Mkdir(path, 0o400))
}, },
nil,
}, },
{ {
"symlink", name: "symlink",
func(t testing.TB, path string) { create: func(t testing.TB, path string) {
rtest.OK(t, os.Symlink("./something", path)) rtest.OK(t, os.Symlink("./something", path))
}, },
nil,
}, },
{ {
"filled dir", name: "filled dir",
func(t testing.TB, path string) { create: func(t testing.TB, path string) {
rtest.OK(t, os.Mkdir(path, 0o700)) rtest.OK(t, os.Mkdir(path, 0o700))
rtest.OK(t, os.WriteFile(filepath.Join(path, "file"), []byte("data"), 0o400)) 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 { for i, sc := range scenarios {
t.Run(sc.name, func(t *testing.T) { t.Run(sc.name, func(t *testing.T) {
for _, test := range tests { for j, test := range tests {
path := basepath + fmt.Sprintf("%v", i) path := basepath + fmt.Sprintf("%v%v", i, j)
sc.create(t, path) sc.create(t, path)
f, err := createFile(path, test.size, test.isSparse) f, err := createFile(path, test.size, test.isSparse)
if sc.err == nil { 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.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.Assert(t, fi.Size() <= test.size, "unexpected file size expected %v, got %v", test.size, fi.Size())
rtest.OK(t, f.Close()) rtest.OK(t, f.Close())
if sc.check != nil {
sc.check(t, path)
}
} else { } else {
rtest.Assert(t, errors.Is(err, sc.err), "unexpected error got %v expected %v", err, sc.err) rtest.Assert(t, errors.Is(err, sc.err), "unexpected error got %v expected %v", err, sc.err)
} }