2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-29 16:23:59 +00:00

Add test case for inconsistent timestamps and permissions restoration

Reproduce from https://github.com/restic/restic/issues/1212
This commit is contained in:
kitone 2020-08-28 23:29:33 +02:00 committed by Michael Eischer
parent 5fd3dbccb7
commit 295ddb9e57

View File

@ -6,6 +6,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -23,14 +24,17 @@ type Snapshot struct {
} }
type File struct { type File struct {
Data string Data string
Links uint64 Links uint64
Inode uint64 Inode uint64
Mode os.FileMode
ModTime time.Time
} }
type Dir struct { type Dir struct {
Nodes map[string]Node Nodes map[string]Node
Mode os.FileMode Mode os.FileMode
ModTime time.Time
} }
func saveFile(t testing.TB, repo restic.Repository, node File) restic.ID { func saveFile(t testing.TB, repo restic.Repository, node File) restic.ID {
@ -66,9 +70,14 @@ func saveDir(t testing.TB, repo restic.Repository, nodes map[string]Node, inode
if len(n.(File).Data) > 0 { if len(n.(File).Data) > 0 {
fc = append(fc, saveFile(t, repo, node)) fc = append(fc, saveFile(t, repo, node))
} }
mode := node.Mode
if mode == 0 {
mode = 0644
}
tree.Insert(&restic.Node{ tree.Insert(&restic.Node{
Type: "file", Type: "file",
Mode: 0644, Mode: mode,
ModTime: node.ModTime,
Name: name, Name: name,
UID: uint32(os.Getuid()), UID: uint32(os.Getuid()),
GID: uint32(os.Getgid()), GID: uint32(os.Getgid()),
@ -88,6 +97,7 @@ func saveDir(t testing.TB, repo restic.Repository, nodes map[string]Node, inode
tree.Insert(&restic.Node{ tree.Insert(&restic.Node{
Type: "dir", Type: "dir",
Mode: mode, Mode: mode,
ModTime: node.ModTime,
Name: name, Name: name,
UID: uint32(os.Getuid()), UID: uint32(os.Getuid()),
GID: uint32(os.Getgid()), GID: uint32(os.Getgid()),
@ -655,6 +665,7 @@ func TestRestorerTraverseTree(t *testing.T) {
}, },
Visitor: checkVisitOrder([]TreeVisit{ Visitor: checkVisitOrder([]TreeVisit{
{"visitNode", "/dir/otherfile"}, {"visitNode", "/dir/otherfile"},
{"leaveDir", "/dir"},
}), }),
}, },
} }
@ -688,3 +699,108 @@ func TestRestorerTraverseTree(t *testing.T) {
}) })
} }
} }
func normalizeFileMode(mode os.FileMode) os.FileMode {
if runtime.GOOS == "windows" {
if mode.IsDir() {
return 0555 | os.ModeDir
}
return os.FileMode(0444)
}
return mode
}
func checkConsistentInfo(t testing.TB, file string, fi os.FileInfo, modtime time.Time, mode os.FileMode) {
if fi.Mode() != mode {
t.Errorf("checking %q, Mode() returned wrong value, want 0%o, got 0%o", file, mode, fi.Mode())
}
if !fi.ModTime().Equal(modtime) {
t.Errorf("checking %s, ModTime() returned wrong value, want %v, got %v", file, modtime, fi.ModTime())
}
}
// test inspired from test case https://github.com/restic/restic/issues/1212
func TestRestorerConsistentTimestampsAndPermissions(t *testing.T) {
timeForTest := time.Date(2019, time.January, 9, 1, 46, 40, 0, time.UTC)
repo, cleanup := repository.TestRepository(t)
defer cleanup()
_, id := saveSnapshot(t, repo, Snapshot{
Nodes: map[string]Node{
"dir": Dir{
Mode: normalizeFileMode(0750 | os.ModeDir),
ModTime: timeForTest,
Nodes: map[string]Node{
"file1": File{
Mode: normalizeFileMode(os.FileMode(0700)),
ModTime: timeForTest,
Data: "content: file\n",
},
"anotherfile": File{
Data: "content: file\n",
},
"subdir": Dir{
Mode: normalizeFileMode(0700 | os.ModeDir),
ModTime: timeForTest,
Nodes: map[string]Node{
"file2": File{
Mode: normalizeFileMode(os.FileMode(0666)),
ModTime: timeForTest,
Links: 2,
Inode: 1,
},
},
},
},
},
},
})
res, err := NewRestorer(repo, id)
rtest.OK(t, err)
res.SelectFilter = func(item string, dstpath string, node *restic.Node) (selectedForRestore bool, childMayBeSelected bool) {
switch filepath.ToSlash(item) {
case "/dir":
childMayBeSelected = true
case "/dir/file1":
selectedForRestore = true
childMayBeSelected = false
case "/dir/subdir":
selectedForRestore = true
childMayBeSelected = true
case "/dir/subdir/file2":
selectedForRestore = true
childMayBeSelected = false
}
return selectedForRestore, childMayBeSelected
}
tempdir, cleanup := rtest.TempDir(t)
defer cleanup()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
err = res.RestoreTo(ctx, tempdir)
rtest.OK(t, err)
var testPatterns = []struct {
path string
modtime time.Time
mode os.FileMode
}{
{"dir", timeForTest, normalizeFileMode(0750 | os.ModeDir)},
{filepath.Join("dir", "file1"), timeForTest, normalizeFileMode(os.FileMode(0700))},
{filepath.Join("dir", "subdir"), timeForTest, normalizeFileMode(0700 | os.ModeDir)},
{filepath.Join("dir", "subdir", "file2"), timeForTest, normalizeFileMode(os.FileMode(0666))},
}
for _, test := range testPatterns {
f, err := os.Stat(filepath.Join(tempdir, test.path))
rtest.OK(t, err)
checkConsistentInfo(t, test.path, f, test.modtime, test.mode)
}
}