func BenchmarkNodeFromFileInfo(t *testing.B) { tempfile, err := ioutil.TempFile("", "restic-test-temp-") if err != nil { t.Fatal(err) } fi, err := tempfile.Stat() if err != nil { t.Fatal(err) } path := tempfile.Name() t.ResetTimer() for i := 0; i < t.N; i++ { _, err := restic.NodeFromFileInfo(path, fi) if err != nil { t.Fatal(err) } } OK(t, tempfile.Close()) RemoveAll(t, tempfile.Name()) }
func TestNodeComparison(t *testing.T) { fi, err := os.Lstat("tree_test.go") OK(t, err) node, err := restic.NodeFromFileInfo("foo", fi) OK(t, err) n2 := *node Assert(t, node.Equals(n2), "nodes aren't equal") n2.Size-- Assert(t, !node.Equals(n2), "nodes are equal") }
func TestNodeRestoreAt(t *testing.T) { tempdir, err := ioutil.TempDir(TestTempDir, "restic-test-") OK(t, err) defer func() { if TestCleanupTempDirs { RemoveAll(t, tempdir) } else { t.Logf("leaving tempdir at %v", tempdir) } }() for _, test := range nodeTests { nodePath := filepath.Join(tempdir, test.Name) OK(t, test.CreateAt(nodePath, nil)) if test.Type == "symlink" && runtime.GOOS == "windows" { continue } if test.Type == "dir" { OK(t, test.RestoreTimestamps(nodePath)) } fi, err := os.Lstat(nodePath) OK(t, err) n2, err := restic.NodeFromFileInfo(nodePath, fi) OK(t, err) Assert(t, test.Name == n2.Name, "%v: name doesn't match (%v != %v)", test.Type, test.Name, n2.Name) Assert(t, test.Type == n2.Type, "%v: type doesn't match (%v != %v)", test.Type, test.Type, n2.Type) Assert(t, test.Size == n2.Size, "%v: size doesn't match (%v != %v)", test.Size, test.Size, n2.Size) if runtime.GOOS != "windows" { Assert(t, test.UID == n2.UID, "%v: UID doesn't match (%v != %v)", test.Type, test.UID, n2.UID) Assert(t, test.GID == n2.GID, "%v: GID doesn't match (%v != %v)", test.Type, test.GID, n2.GID) if test.Type != "symlink" { Assert(t, test.Mode == n2.Mode, "%v: mode doesn't match (0%o != 0%o)", test.Type, test.Mode, n2.Mode) } } AssertFsTimeEqual(t, "AccessTime", test.Type, test.AccessTime, n2.AccessTime) AssertFsTimeEqual(t, "ModTime", test.Type, test.ModTime, n2.ModTime) } }
func (arch *Archiver) reloadFileIfChanged(node *restic.Node, file fs.File) (*restic.Node, error) { fi, err := file.Stat() if err != nil { return nil, errors.Wrap(err, "restic.Stat") } if fi.ModTime() == node.ModTime { return node, nil } arch.Warn(node.Path, fi, errors.New("file has changed")) node, err = restic.NodeFromFileInfo(node.Path, fi) if err != nil { debug.Log("restic.NodeFromFileInfo returned error for %v: %v", node.Path, err) return nil, err } return node, nil }
func (arch *Archiver) dirWorker(wg *sync.WaitGroup, p *restic.Progress, done <-chan struct{}, dirCh <-chan pipe.Dir) { debug.Log("start") defer func() { debug.Log("done") wg.Done() }() for { select { case dir, ok := <-dirCh: if !ok { // channel is closed return } debug.Log("save dir %v (%d entries), error %v\n", dir.Path(), len(dir.Entries), dir.Error()) // ignore dir nodes with errors if dir.Error() != nil { fmt.Fprintf(os.Stderr, "error walking dir %v: %v\n", dir.Path(), dir.Error()) dir.Result() <- nil p.Report(restic.Stat{Errors: 1}) continue } tree := restic.NewTree() // wait for all content for _, ch := range dir.Entries { debug.Log("receiving result from %v", ch) res := <-ch // if we get a nil pointer here, an error has happened while // processing this entry. Ignore it for now. if res == nil { debug.Log("got nil result?") continue } // else insert node node := res.(*restic.Node) tree.Insert(node) if node.Type == "dir" { debug.Log("got tree node for %s: %v", node.Path, node.Subtree) if node.Subtree.IsNull() { panic("invalid null subtree restic.ID") } } } node := &restic.Node{} if dir.Path() != "" && dir.Info() != nil { n, err := restic.NodeFromFileInfo(dir.Path(), dir.Info()) if err != nil { n.Error = err.Error() dir.Result() <- n continue } node = n } if err := dir.Error(); err != nil { node.Error = err.Error() } id, err := arch.SaveTreeJSON(tree) if err != nil { panic(err) } debug.Log("save tree for %s: %v", dir.Path(), id.Str()) if id.IsNull() { panic("invalid null subtree restic.ID return from SaveTreeJSON()") } node.Subtree = &id debug.Log("sending result to %v", dir.Result()) dir.Result() <- node if dir.Path() != "" { p.Report(restic.Stat{Dirs: 1}) } case <-done: // pipeline was cancelled return } } }
func (arch *Archiver) fileWorker(wg *sync.WaitGroup, p *restic.Progress, done <-chan struct{}, entCh <-chan pipe.Entry) { defer func() { debug.Log("done") wg.Done() }() for { select { case e, ok := <-entCh: if !ok { // channel is closed return } debug.Log("got job %v", e) // check for errors if e.Error() != nil { debug.Log("job %v has errors: %v", e.Path(), e.Error()) // TODO: integrate error reporting fmt.Fprintf(os.Stderr, "error for %v: %v\n", e.Path(), e.Error()) // ignore this file e.Result() <- nil p.Report(restic.Stat{Errors: 1}) continue } node, err := restic.NodeFromFileInfo(e.Fullpath(), e.Info()) if err != nil { // TODO: integrate error reporting debug.Log("restic.NodeFromFileInfo returned error for %v: %v", node.Path, err) e.Result() <- nil p.Report(restic.Stat{Errors: 1}) continue } // try to use old node, if present if e.Node != nil { debug.Log(" %v use old data", e.Path()) oldNode := e.Node.(*restic.Node) // check if all content is still available in the repository contentMissing := false for _, blob := range oldNode.Content { if !arch.repo.Index().Has(blob, restic.DataBlob) { debug.Log(" %v not using old data, %v is missing", e.Path(), blob.Str()) contentMissing = true break } } if !contentMissing { node.Content = oldNode.Content debug.Log(" %v content is complete", e.Path()) } } else { debug.Log(" %v no old data", e.Path()) } // otherwise read file normally if node.Type == "file" && len(node.Content) == 0 { debug.Log(" read and save %v, content: %v", e.Path(), node.Content) err = arch.SaveFile(p, node) if err != nil { // TODO: integrate error reporting fmt.Fprintf(os.Stderr, "error for %v: %v\n", node.Path, err) // ignore this file e.Result() <- nil p.Report(restic.Stat{Errors: 1}) continue } } else { // report old data size p.Report(restic.Stat{Bytes: node.Size}) } debug.Log(" processed %v, %d blobs", e.Path(), len(node.Content)) e.Result() <- node p.Report(restic.Stat{Files: 1}) case <-done: // pipeline was cancelled return } } }