From ca1e5e10b64c0ee56037b450a3d300345b8fb679 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Tue, 9 Jul 2024 19:51:44 +0200 Subject: [PATCH 01/12] add proper constants for node type --- cmd/restic/cmd_diff.go | 30 ++++++++-------- cmd/restic/cmd_dump.go | 8 ++--- cmd/restic/cmd_find.go | 6 ++-- cmd/restic/cmd_ls.go | 10 +++--- cmd/restic/cmd_ls_test.go | 16 ++++----- cmd/restic/cmd_recover.go | 4 +-- cmd/restic/cmd_repair_snapshots.go | 2 +- cmd/restic/cmd_stats.go | 2 +- cmd/restic/format.go | 14 ++++---- cmd/restic/format_test.go | 2 +- internal/archiver/archiver.go | 10 +++--- internal/archiver/archiver_test.go | 2 +- internal/archiver/file_saver.go | 2 +- internal/archiver/testing.go | 8 ++--- internal/checker/checker.go | 6 ++-- internal/checker/checker_test.go | 6 ++-- internal/dump/common.go | 19 ++-------- internal/dump/tar.go | 6 ++-- internal/dump/tar_test.go | 2 +- internal/dump/zip.go | 4 +-- internal/fs/file_windows.go | 7 ++-- internal/fs/node.go | 54 ++++++++++++++--------------- internal/fs/node_test.go | 28 +++++++-------- internal/fs/node_unix_test.go | 6 ++-- internal/fs/node_windows.go | 4 +-- internal/fs/node_windows_test.go | 32 ++++++++--------- internal/fs/node_xattr_all_test.go | 4 +-- internal/fuse/dir.go | 18 +++++----- internal/fuse/fuse_test.go | 14 ++++---- internal/fuse/inode.go | 2 +- internal/restic/find.go | 2 +- internal/restic/node.go | 30 +++++++++++----- internal/restic/testing.go | 4 +-- internal/restic/tree.go | 4 +-- internal/restorer/restorer.go | 14 ++++---- internal/restorer/restorer_test.go | 6 ++-- internal/ui/backup/progress.go | 4 +-- internal/ui/backup/progress_test.go | 4 +-- internal/walker/rewriter.go | 4 +-- internal/walker/rewriter_test.go | 4 +-- internal/walker/walker.go | 4 +-- internal/walker/walker_test.go | 4 +-- 42 files changed, 206 insertions(+), 206 deletions(-) diff --git a/cmd/restic/cmd_diff.go b/cmd/restic/cmd_diff.go index 594e387e8..d1067b5ec 100644 --- a/cmd/restic/cmd_diff.go +++ b/cmd/restic/cmd_diff.go @@ -108,9 +108,9 @@ func (s *DiffStat) Add(node *restic.Node) { } switch node.Type { - case "file": + case restic.NodeTypeFile: s.Files++ - case "dir": + case restic.NodeTypeDir: s.Dirs++ default: s.Others++ @@ -124,7 +124,7 @@ func addBlobs(bs restic.BlobSet, node *restic.Node) { } switch node.Type { - case "file": + case restic.NodeTypeFile: for _, blob := range node.Content { h := restic.BlobHandle{ ID: blob, @@ -132,7 +132,7 @@ func addBlobs(bs restic.BlobSet, node *restic.Node) { } bs.Insert(h) } - case "dir": + case restic.NodeTypeDir: h := restic.BlobHandle{ ID: *node.Subtree, Type: restic.TreeBlob, @@ -184,14 +184,14 @@ func (c *Comparer) printDir(ctx context.Context, mode string, stats *DiffStat, b } name := path.Join(prefix, node.Name) - if node.Type == "dir" { + if node.Type == restic.NodeTypeDir { name += "/" } c.printChange(NewChange(name, mode)) stats.Add(node) addBlobs(blobs, node) - if node.Type == "dir" { + if node.Type == restic.NodeTypeDir { err := c.printDir(ctx, mode, stats, blobs, name, *node.Subtree) if err != nil && err != context.Canceled { Warnf("error: %v\n", err) @@ -216,7 +216,7 @@ func (c *Comparer) collectDir(ctx context.Context, blobs restic.BlobSet, id rest addBlobs(blobs, node) - if node.Type == "dir" { + if node.Type == restic.NodeTypeDir { err := c.collectDir(ctx, blobs, *node.Subtree) if err != nil && err != context.Canceled { Warnf("error: %v\n", err) @@ -284,12 +284,12 @@ func (c *Comparer) diffTree(ctx context.Context, stats *DiffStatsContainer, pref mod += "T" } - if node2.Type == "dir" { + if node2.Type == restic.NodeTypeDir { name += "/" } - if node1.Type == "file" && - node2.Type == "file" && + if node1.Type == restic.NodeTypeFile && + node2.Type == restic.NodeTypeFile && !reflect.DeepEqual(node1.Content, node2.Content) { mod += "M" stats.ChangedFiles++ @@ -311,7 +311,7 @@ func (c *Comparer) diffTree(ctx context.Context, stats *DiffStatsContainer, pref c.printChange(NewChange(name, mod)) } - if node1.Type == "dir" && node2.Type == "dir" { + if node1.Type == restic.NodeTypeDir && node2.Type == restic.NodeTypeDir { var err error if (*node1.Subtree).Equal(*node2.Subtree) { err = c.collectDir(ctx, stats.BlobsCommon, *node1.Subtree) @@ -324,13 +324,13 @@ func (c *Comparer) diffTree(ctx context.Context, stats *DiffStatsContainer, pref } case t1 && !t2: prefix := path.Join(prefix, name) - if node1.Type == "dir" { + if node1.Type == restic.NodeTypeDir { prefix += "/" } c.printChange(NewChange(prefix, "-")) stats.Removed.Add(node1) - if node1.Type == "dir" { + if node1.Type == restic.NodeTypeDir { err := c.printDir(ctx, "-", &stats.Removed, stats.BlobsBefore, prefix, *node1.Subtree) if err != nil && err != context.Canceled { Warnf("error: %v\n", err) @@ -338,13 +338,13 @@ func (c *Comparer) diffTree(ctx context.Context, stats *DiffStatsContainer, pref } case !t1 && t2: prefix := path.Join(prefix, name) - if node2.Type == "dir" { + if node2.Type == restic.NodeTypeDir { prefix += "/" } c.printChange(NewChange(prefix, "+")) stats.Added.Add(node2) - if node2.Type == "dir" { + if node2.Type == restic.NodeTypeDir { err := c.printDir(ctx, "+", &stats.Added, stats.BlobsAfter, prefix, *node2.Subtree) if err != nil && err != context.Canceled { Warnf("error: %v\n", err) diff --git a/cmd/restic/cmd_dump.go b/cmd/restic/cmd_dump.go index 7d6652e17..6b7f8d012 100644 --- a/cmd/restic/cmd_dump.go +++ b/cmd/restic/cmd_dump.go @@ -95,15 +95,15 @@ func printFromTree(ctx context.Context, tree *restic.Tree, repo restic.BlobLoade // first item it finds and dump that according to the switch case below. if node.Name == pathComponents[0] { switch { - case l == 1 && dump.IsFile(node): + case l == 1 && node.Type == restic.NodeTypeFile: return d.WriteNode(ctx, node) - case l > 1 && dump.IsDir(node): + case l > 1 && node.Type == restic.NodeTypeDir: subtree, err := restic.LoadTree(ctx, repo, *node.Subtree) if err != nil { return errors.Wrapf(err, "cannot load subtree for %q", item) } return printFromTree(ctx, subtree, repo, item, pathComponents[1:], d, canWriteArchiveFunc) - case dump.IsDir(node): + case node.Type == restic.NodeTypeDir: if err := canWriteArchiveFunc(); err != nil { return err } @@ -114,7 +114,7 @@ func printFromTree(ctx context.Context, tree *restic.Tree, repo restic.BlobLoade return d.DumpTree(ctx, subtree, item) case l > 1: return fmt.Errorf("%q should be a dir, but is a %q", item, node.Type) - case !dump.IsFile(node): + case node.Type != restic.NodeTypeFile: return fmt.Errorf("%q should be a file, but is a %q", item, node.Type) } } diff --git a/cmd/restic/cmd_find.go b/cmd/restic/cmd_find.go index cb5c0e5e0..92e88203f 100644 --- a/cmd/restic/cmd_find.go +++ b/cmd/restic/cmd_find.go @@ -298,7 +298,7 @@ func (f *Finder) findInSnapshot(ctx context.Context, sn *restic.Snapshot) error } var errIfNoMatch error - if node.Type == "dir" { + if node.Type == restic.NodeTypeDir { var childMayMatch bool for _, pat := range f.pat.pattern { mayMatch, err := filter.ChildMatch(pat, normalizedNodepath) @@ -357,7 +357,7 @@ func (f *Finder) findIDs(ctx context.Context, sn *restic.Snapshot) error { return nil } - if node.Type == "dir" && f.treeIDs != nil { + if node.Type == restic.NodeTypeDir && f.treeIDs != nil { treeID := node.Subtree found := false if _, ok := f.treeIDs[treeID.Str()]; ok { @@ -377,7 +377,7 @@ func (f *Finder) findIDs(ctx context.Context, sn *restic.Snapshot) error { } } - if node.Type == "file" && f.blobIDs != nil { + if node.Type == restic.NodeTypeFile && f.blobIDs != nil { for _, id := range node.Content { if ctx.Err() != nil { return ctx.Err() diff --git a/cmd/restic/cmd_ls.go b/cmd/restic/cmd_ls.go index 69e278103..2213d8e7a 100644 --- a/cmd/restic/cmd_ls.go +++ b/cmd/restic/cmd_ls.go @@ -137,7 +137,7 @@ func lsNodeJSON(enc *json.Encoder, path string, node *restic.Node) error { size uint64 // Target for Size pointer. }{ Name: node.Name, - Type: node.Type, + Type: string(node.Type), Path: path, UID: node.UID, GID: node.GID, @@ -153,7 +153,7 @@ func lsNodeJSON(enc *json.Encoder, path string, node *restic.Node) error { } // Always print size for regular files, even when empty, // but never for other types. - if node.Type == "file" { + if node.Type == restic.NodeTypeFile { n.Size = &n.size } @@ -208,7 +208,7 @@ func lsNcduNode(_ string, node *restic.Node) ([]byte, error) { Dev: node.DeviceID, Ino: node.Inode, NLink: node.Links, - NotReg: node.Type != "dir" && node.Type != "file", + NotReg: node.Type != restic.NodeTypeDir && node.Type != restic.NodeTypeFile, UID: node.UID, GID: node.GID, Mode: uint16(node.Mode & os.ModePerm), @@ -238,7 +238,7 @@ func (p *ncduLsPrinter) Node(path string, node *restic.Node, _ bool) { Warnf("JSON encode failed: %v\n", err) } - if node.Type == "dir" { + if node.Type == restic.NodeTypeDir { fmt.Fprintf(p.out, ",\n%s[\n%s%s", strings.Repeat(" ", p.depth), strings.Repeat(" ", p.depth+1), string(out)) p.depth++ } else { @@ -409,7 +409,7 @@ func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []stri // otherwise, signal the walker to not walk recursively into any // subdirs - if node.Type == "dir" { + if node.Type == restic.NodeTypeDir { // immediately generate leaveDir if the directory is skipped if printedDir { printer.LeaveDir(nodepath) diff --git a/cmd/restic/cmd_ls_test.go b/cmd/restic/cmd_ls_test.go index a1fcd479b..b8b074242 100644 --- a/cmd/restic/cmd_ls_test.go +++ b/cmd/restic/cmd_ls_test.go @@ -23,7 +23,7 @@ var lsTestNodes = []lsTestNode{ path: "/bar/baz", Node: restic.Node{ Name: "baz", - Type: "file", + Type: restic.NodeTypeFile, Size: 12345, UID: 10000000, GID: 20000000, @@ -39,7 +39,7 @@ var lsTestNodes = []lsTestNode{ path: "/foo/empty", Node: restic.Node{ Name: "empty", - Type: "file", + Type: restic.NodeTypeFile, Size: 0, UID: 1001, GID: 1001, @@ -56,7 +56,7 @@ var lsTestNodes = []lsTestNode{ path: "/foo/link", Node: restic.Node{ Name: "link", - Type: "symlink", + Type: restic.NodeTypeSymlink, Mode: os.ModeSymlink | 0777, LinkTarget: "not printed", }, @@ -66,7 +66,7 @@ var lsTestNodes = []lsTestNode{ path: "/some/directory", Node: restic.Node{ Name: "directory", - Type: "dir", + Type: restic.NodeTypeDir, Mode: os.ModeDir | 0755, ModTime: time.Date(2020, 1, 2, 3, 4, 5, 0, time.UTC), AccessTime: time.Date(2021, 2, 3, 4, 5, 6, 7, time.UTC), @@ -79,7 +79,7 @@ var lsTestNodes = []lsTestNode{ path: "/some/sticky", Node: restic.Node{ Name: "sticky", - Type: "dir", + Type: restic.NodeTypeDir, Mode: os.ModeDir | 0755 | os.ModeSetuid | os.ModeSetgid | os.ModeSticky, }, }, @@ -139,19 +139,19 @@ func TestLsNcdu(t *testing.T) { Paths: []string{"/example"}, }) printer.Node("/directory", &restic.Node{ - Type: "dir", + Type: restic.NodeTypeDir, Name: "directory", ModTime: modTime, }, false) printer.Node("/directory/data", &restic.Node{ - Type: "file", + Type: restic.NodeTypeFile, Name: "data", Size: 42, ModTime: modTime, }, false) printer.LeaveDir("/directory") printer.Node("/file", &restic.Node{ - Type: "file", + Type: restic.NodeTypeFile, Name: "file", Size: 12345, ModTime: modTime, diff --git a/cmd/restic/cmd_recover.go b/cmd/restic/cmd_recover.go index a6ef59cc2..133f77978 100644 --- a/cmd/restic/cmd_recover.go +++ b/cmd/restic/cmd_recover.go @@ -88,7 +88,7 @@ func runRecover(ctx context.Context, gopts GlobalOptions) error { } for _, node := range tree.Nodes { - if node.Type == "dir" && node.Subtree != nil { + if node.Type == restic.NodeTypeDir && node.Subtree != nil { trees[*node.Subtree] = true } } @@ -128,7 +128,7 @@ func runRecover(ctx context.Context, gopts GlobalOptions) error { for id := range roots { var subtreeID = id node := restic.Node{ - Type: "dir", + Type: restic.NodeTypeDir, Name: id.Str(), Mode: 0755, Subtree: &subtreeID, diff --git a/cmd/restic/cmd_repair_snapshots.go b/cmd/restic/cmd_repair_snapshots.go index 385854312..46ba52dd4 100644 --- a/cmd/restic/cmd_repair_snapshots.go +++ b/cmd/restic/cmd_repair_snapshots.go @@ -92,7 +92,7 @@ func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOpt // - files whose contents are not fully available (-> file will be modified) rewriter := walker.NewTreeRewriter(walker.RewriteOpts{ RewriteNode: func(node *restic.Node, path string) *restic.Node { - if node.Type != "file" { + if node.Type != restic.NodeTypeFile { return node } diff --git a/cmd/restic/cmd_stats.go b/cmd/restic/cmd_stats.go index dd51175d3..e0b60a29e 100644 --- a/cmd/restic/cmd_stats.go +++ b/cmd/restic/cmd_stats.go @@ -276,7 +276,7 @@ func statsWalkTree(repo restic.Loader, opts StatsOptions, stats *statsContainer, // will still be restored stats.TotalFileCount++ - if node.Links == 1 || node.Type == "dir" { + if node.Links == 1 || node.Type == restic.NodeTypeDir { stats.TotalSize += node.Size } else { // if hardlinks are present only count each deviceID+inode once diff --git a/cmd/restic/format.go b/cmd/restic/format.go index 063cd4e71..e7d178b4e 100644 --- a/cmd/restic/format.go +++ b/cmd/restic/format.go @@ -24,20 +24,20 @@ func formatNode(path string, n *restic.Node, long bool, human bool) string { } switch n.Type { - case "file": + case restic.NodeTypeFile: mode = 0 - case "dir": + case restic.NodeTypeDir: mode = os.ModeDir - case "symlink": + case restic.NodeTypeSymlink: mode = os.ModeSymlink target = fmt.Sprintf(" -> %v", n.LinkTarget) - case "dev": + case restic.NodeTypeDev: mode = os.ModeDevice - case "chardev": + case restic.NodeTypeCharDev: mode = os.ModeDevice | os.ModeCharDevice - case "fifo": + case restic.NodeTypeFifo: mode = os.ModeNamedPipe - case "socket": + case restic.NodeTypeSocket: mode = os.ModeSocket } diff --git a/cmd/restic/format_test.go b/cmd/restic/format_test.go index 689bd27a5..e232a200b 100644 --- a/cmd/restic/format_test.go +++ b/cmd/restic/format_test.go @@ -19,7 +19,7 @@ func TestFormatNode(t *testing.T) { testPath := "/test/path" node := restic.Node{ Name: "baz", - Type: "file", + Type: restic.NodeTypeFile, Size: 14680064, UID: 1000, GID: 2000, diff --git a/internal/archiver/archiver.go b/internal/archiver/archiver.go index a783f6c7f..0a0c18d28 100644 --- a/internal/archiver/archiver.go +++ b/internal/archiver/archiver.go @@ -232,7 +232,7 @@ func (arch *Archiver) trackItem(item string, previous, current *restic.Node, s I } switch current.Type { - case "dir": + case restic.NodeTypeDir: switch { case previous == nil: arch.summary.Dirs.New++ @@ -242,7 +242,7 @@ func (arch *Archiver) trackItem(item string, previous, current *restic.Node, s I arch.summary.Dirs.Changed++ } - case "file": + case restic.NodeTypeFile: switch { case previous == nil: arch.summary.Files.New++ @@ -261,7 +261,7 @@ func (arch *Archiver) nodeFromFileInfo(snPath, filename string, fi os.FileInfo, node.AccessTime = node.ModTime } if feature.Flag.Enabled(feature.DeviceIDForHardlinks) { - if node.Links == 1 || node.Type == "dir" { + if node.Links == 1 || node.Type == restic.NodeTypeDir { // the DeviceID is only necessary for hardlinked files // when using subvolumes or snapshots their deviceIDs tend to change which causes // restic to upload new tree blobs @@ -280,7 +280,7 @@ func (arch *Archiver) nodeFromFileInfo(snPath, filename string, fi os.FileInfo, // loadSubtree tries to load the subtree referenced by node. In case of an error, nil is returned. // If there is no node to load, then nil is returned without an error. func (arch *Archiver) loadSubtree(ctx context.Context, node *restic.Node) (*restic.Tree, error) { - if node == nil || node.Type != "dir" || node.Subtree == nil { + if node == nil || node.Type != restic.NodeTypeDir || node.Subtree == nil { return nil, nil } @@ -583,7 +583,7 @@ func fileChanged(fs fs.FS, fi os.FileInfo, node *restic.Node, ignoreFlags uint) switch { case node == nil: return true - case node.Type != "file": + case node.Type != restic.NodeTypeFile: // We're only called for regular files, so this is a type change. return true case uint64(fi.Size()) != node.Size: diff --git a/internal/archiver/archiver_test.go b/internal/archiver/archiver_test.go index 962fd5481..04d0b876b 100644 --- a/internal/archiver/archiver_test.go +++ b/internal/archiver/archiver_test.go @@ -730,7 +730,7 @@ func TestFilChangedSpecialCases(t *testing.T) { t.Run("type-change", func(t *testing.T) { fi := lstat(t, filename) node := nodeFromFI(t, filename, fi) - node.Type = "symlink" + node.Type = "restic.NodeTypeSymlink" if !fileChanged(&fs.Local{}, fi, node, 0) { t.Fatal("node with changed type detected as unchanged") } diff --git a/internal/archiver/file_saver.go b/internal/archiver/file_saver.go index fa19cab86..b9d07434a 100644 --- a/internal/archiver/file_saver.go +++ b/internal/archiver/file_saver.go @@ -163,7 +163,7 @@ func (s *fileSaver) saveFile(ctx context.Context, chnker *chunker.Chunker, snPat return } - if node.Type != "file" { + if node.Type != restic.NodeTypeFile { _ = f.Close() completeError(errors.Errorf("node type %q is wrong", node.Type)) return diff --git a/internal/archiver/testing.go b/internal/archiver/testing.go index 8bd854904..97bada644 100644 --- a/internal/archiver/testing.go +++ b/internal/archiver/testing.go @@ -289,7 +289,7 @@ func TestEnsureTree(ctx context.Context, t testing.TB, prefix string, repo resti switch e := entry.(type) { case TestDir: - if node.Type != "dir" { + if node.Type != restic.NodeTypeDir { t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "dir") return } @@ -301,13 +301,13 @@ func TestEnsureTree(ctx context.Context, t testing.TB, prefix string, repo resti TestEnsureTree(ctx, t, path.Join(prefix, node.Name), repo, *node.Subtree, e) case TestFile: - if node.Type != "file" { + if node.Type != restic.NodeTypeFile { t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "file") } TestEnsureFileContent(ctx, t, repo, nodePrefix, node, e) case TestSymlink: - if node.Type != "symlink" { - t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "file") + if node.Type != restic.NodeTypeSymlink { + t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "symlink") } if e.Target != node.LinkTarget { diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 76bb15f63..12020891a 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -344,7 +344,7 @@ func (c *Checker) checkTree(id restic.ID, tree *restic.Tree) (errs []error) { for _, node := range tree.Nodes { switch node.Type { - case "file": + case restic.NodeTypeFile: if node.Content == nil { errs = append(errs, &Error{TreeID: id, Err: errors.Errorf("file %q has nil blob list", node.Name)}) } @@ -380,7 +380,7 @@ func (c *Checker) checkTree(id restic.ID, tree *restic.Tree) (errs []error) { c.blobRefs.Unlock() } - case "dir": + case restic.NodeTypeDir: if node.Subtree == nil { errs = append(errs, &Error{TreeID: id, Err: errors.Errorf("dir node %q has no subtree", node.Name)}) continue @@ -391,7 +391,7 @@ func (c *Checker) checkTree(id restic.ID, tree *restic.Tree) (errs []error) { continue } - case "symlink", "socket", "chardev", "dev", "fifo": + case restic.NodeTypeSymlink, restic.NodeTypeSocket, restic.NodeTypeCharDev, restic.NodeTypeDev, restic.NodeTypeFifo: // nothing to check default: diff --git a/internal/checker/checker_test.go b/internal/checker/checker_test.go index 5eaf550ba..0e2125bba 100644 --- a/internal/checker/checker_test.go +++ b/internal/checker/checker_test.go @@ -482,7 +482,7 @@ func TestCheckerBlobTypeConfusion(t *testing.T) { damagedNode := &restic.Node{ Name: "damaged", - Type: "file", + Type: restic.NodeTypeFile, Mode: 0644, Size: 42, Content: restic.IDs{restic.TestParseID("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")}, @@ -507,14 +507,14 @@ func TestCheckerBlobTypeConfusion(t *testing.T) { malNode := &restic.Node{ Name: "aaaaa", - Type: "file", + Type: restic.NodeTypeFile, Mode: 0644, Size: uint64(len(buf)), Content: restic.IDs{id}, } dirNode := &restic.Node{ Name: "bbbbb", - Type: "dir", + Type: restic.NodeTypeDir, Mode: 0755, Subtree: &id, } diff --git a/internal/dump/common.go b/internal/dump/common.go index 62145ba9c..4bc404fe0 100644 --- a/internal/dump/common.go +++ b/internal/dump/common.go @@ -67,7 +67,7 @@ func sendNodes(ctx context.Context, repo restic.BlobLoader, root *restic.Node, c } // If this is no directory we are finished - if !IsDir(root) { + if root.Type != restic.NodeTypeDir { return nil } @@ -81,7 +81,7 @@ func sendNodes(ctx context.Context, repo restic.BlobLoader, root *restic.Node, c node.Path = path.Join(root.Path, nodepath) - if !IsFile(node) && !IsDir(node) && !IsLink(node) { + if node.Type != restic.NodeTypeFile && node.Type != restic.NodeTypeDir && node.Type != restic.NodeTypeSymlink { return nil } @@ -176,18 +176,3 @@ func (d *Dumper) writeNode(ctx context.Context, w io.Writer, node *restic.Node) return wg.Wait() } - -// IsDir checks if the given node is a directory. -func IsDir(node *restic.Node) bool { - return node.Type == "dir" -} - -// IsLink checks if the given node as a link. -func IsLink(node *restic.Node) bool { - return node.Type == "symlink" -} - -// IsFile checks if the given node is a file. -func IsFile(node *restic.Node) bool { - return node.Type == "file" -} diff --git a/internal/dump/tar.go b/internal/dump/tar.go index e8f34deb1..c5933d4f8 100644 --- a/internal/dump/tar.go +++ b/internal/dump/tar.go @@ -79,16 +79,16 @@ func (d *Dumper) dumpNodeTar(ctx context.Context, node *restic.Node, w *tar.Writ header.Mode |= cISVTX } - if IsFile(node) { + if node.Type == restic.NodeTypeFile { header.Typeflag = tar.TypeReg } - if IsLink(node) { + if node.Type == restic.NodeTypeSymlink { header.Typeflag = tar.TypeSymlink header.Linkname = node.LinkTarget } - if IsDir(node) { + if node.Type == restic.NodeTypeDir { header.Typeflag = tar.TypeDir header.Name += "/" } diff --git a/internal/dump/tar_test.go b/internal/dump/tar_test.go index 3556e6aeb..0c97eae35 100644 --- a/internal/dump/tar_test.go +++ b/internal/dump/tar_test.go @@ -124,7 +124,7 @@ func TestFieldTooLong(t *testing.T) { node := restic.Node{ Name: "file_with_xattr", Path: "/file_with_xattr", - Type: "file", + Type: restic.NodeTypeFile, Mode: 0644, ExtendedAttributes: []restic.ExtendedAttribute{ { diff --git a/internal/dump/zip.go b/internal/dump/zip.go index e5ef5c95b..d32475770 100644 --- a/internal/dump/zip.go +++ b/internal/dump/zip.go @@ -40,7 +40,7 @@ func (d *Dumper) dumpNodeZip(ctx context.Context, node *restic.Node, zw *zip.Wri } header.SetMode(node.Mode) - if IsDir(node) { + if node.Type == restic.NodeTypeDir { header.Name += "/" } @@ -49,7 +49,7 @@ func (d *Dumper) dumpNodeZip(ctx context.Context, node *restic.Node, zw *zip.Wri return errors.Wrap(err, "ZipHeader") } - if IsLink(node) { + if node.Type == restic.NodeTypeSymlink { if _, err = w.Write([]byte(node.LinkTarget)); err != nil { return errors.Wrap(err, "Write") } diff --git a/internal/fs/file_windows.go b/internal/fs/file_windows.go index 50c7e9938..7a6d79f25 100644 --- a/internal/fs/file_windows.go +++ b/internal/fs/file_windows.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "github.com/restic/restic/internal/restic" "golang.org/x/sys/windows" ) @@ -105,7 +106,7 @@ func ClearAttribute(path string, attribute uint32) error { } // OpenHandleForEA return a file handle for file or dir for setting/getting EAs -func OpenHandleForEA(nodeType, path string, writeAccess bool) (handle windows.Handle, err error) { +func OpenHandleForEA(nodeType restic.NodeType, path string, writeAccess bool) (handle windows.Handle, err error) { path = fixpath(path) fileAccess := windows.FILE_READ_EA if writeAccess { @@ -113,10 +114,10 @@ func OpenHandleForEA(nodeType, path string, writeAccess bool) (handle windows.Ha } switch nodeType { - case "file": + case restic.NodeTypeFile: utf16Path := windows.StringToUTF16Ptr(path) handle, err = windows.CreateFile(utf16Path, uint32(fileAccess), 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, 0) - case "dir": + case restic.NodeTypeDir: utf16Path := windows.StringToUTF16Ptr(path) handle, err = windows.CreateFile(utf16Path, uint32(fileAccess), 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_BACKUP_SEMANTICS, 0) default: diff --git a/internal/fs/node.go b/internal/fs/node.go index 9bd507ba5..e29011cc2 100644 --- a/internal/fs/node.go +++ b/internal/fs/node.go @@ -25,7 +25,7 @@ func NodeFromFileInfo(path string, fi os.FileInfo, ignoreXattrListError bool) (* } node.Type = nodeTypeFromFileInfo(fi) - if node.Type == "file" { + if node.Type == restic.NodeTypeFile { node.Size = uint64(fi.Size()) } @@ -33,27 +33,27 @@ func NodeFromFileInfo(path string, fi os.FileInfo, ignoreXattrListError bool) (* return node, err } -func nodeTypeFromFileInfo(fi os.FileInfo) string { +func nodeTypeFromFileInfo(fi os.FileInfo) restic.NodeType { switch fi.Mode() & os.ModeType { case 0: - return "file" + return restic.NodeTypeFile case os.ModeDir: - return "dir" + return restic.NodeTypeDir case os.ModeSymlink: - return "symlink" + return restic.NodeTypeSymlink case os.ModeDevice | os.ModeCharDevice: - return "chardev" + return restic.NodeTypeCharDev case os.ModeDevice: - return "dev" + return restic.NodeTypeDev case os.ModeNamedPipe: - return "fifo" + return restic.NodeTypeFifo case os.ModeSocket: - return "socket" + return restic.NodeTypeSocket case os.ModeIrregular: - return "irregular" + return restic.NodeTypeIrregular } - return "" + return restic.NodeTypeInvalid } func nodeFillExtra(node *restic.Node, path string, fi os.FileInfo, ignoreXattrListError bool) error { @@ -74,25 +74,25 @@ func nodeFillExtra(node *restic.Node, path string, fi os.FileInfo, ignoreXattrLi nodeFillUser(node, stat) switch node.Type { - case "file": + case restic.NodeTypeFile: node.Size = uint64(stat.size()) node.Links = uint64(stat.nlink()) - case "dir": - case "symlink": + case restic.NodeTypeDir: + case restic.NodeTypeSymlink: var err error node.LinkTarget, err = Readlink(path) node.Links = uint64(stat.nlink()) if err != nil { return errors.WithStack(err) } - case "dev": + case restic.NodeTypeDev: node.Device = uint64(stat.rdev()) node.Links = uint64(stat.nlink()) - case "chardev": + case restic.NodeTypeCharDev: node.Device = uint64(stat.rdev()) node.Links = uint64(stat.nlink()) - case "fifo": - case "socket": + case restic.NodeTypeFifo: + case restic.NodeTypeSocket: default: return errors.Errorf("unsupported file type %q", node.Type) } @@ -178,31 +178,31 @@ func NodeCreateAt(node *restic.Node, path string) error { debug.Log("create node %v at %v", node.Name, path) switch node.Type { - case "dir": + case restic.NodeTypeDir: if err := nodeCreateDirAt(node, path); err != nil { return err } - case "file": + case restic.NodeTypeFile: if err := nodeCreateFileAt(path); err != nil { return err } - case "symlink": + case restic.NodeTypeSymlink: if err := nodeCreateSymlinkAt(node, path); err != nil { return err } - case "dev": + case restic.NodeTypeDev: if err := nodeCreateDevAt(node, path); err != nil { return err } - case "chardev": + case restic.NodeTypeCharDev: if err := nodeCreateCharDevAt(node, path); err != nil { return err } - case "fifo": + case restic.NodeTypeFifo: if err := nodeCreateFifoAt(path); err != nil { return err } - case "socket": + case restic.NodeTypeSocket: return nil default: return errors.Errorf("filetype %q not implemented", node.Type) @@ -305,7 +305,7 @@ func nodeRestoreMetadata(node *restic.Node, path string, warn func(msg string)) // Moving RestoreTimestamps and restoreExtendedAttributes calls above as for readonly files in windows // calling Chmod below will no longer allow any modifications to be made on the file and the // calls above would fail. - if node.Type != "symlink" { + if node.Type != restic.NodeTypeSymlink { if err := Chmod(path, node.Mode); err != nil { if firsterr == nil { firsterr = errors.WithStack(err) @@ -322,7 +322,7 @@ func NodeRestoreTimestamps(node *restic.Node, path string) error { syscall.NsecToTimespec(node.ModTime.UnixNano()), } - if node.Type == "symlink" { + if node.Type == restic.NodeTypeSymlink { return nodeRestoreSymlinkTimestamps(path, utimes) } diff --git a/internal/fs/node_test.go b/internal/fs/node_test.go index e7f608352..2623513a8 100644 --- a/internal/fs/node_test.go +++ b/internal/fs/node_test.go @@ -79,7 +79,7 @@ func parseTime(s string) time.Time { var nodeTests = []restic.Node{ { Name: "testFile", - Type: "file", + Type: restic.NodeTypeFile, Content: restic.IDs{}, UID: uint32(os.Getuid()), GID: uint32(os.Getgid()), @@ -90,7 +90,7 @@ var nodeTests = []restic.Node{ }, { Name: "testSuidFile", - Type: "file", + Type: restic.NodeTypeFile, Content: restic.IDs{}, UID: uint32(os.Getuid()), GID: uint32(os.Getgid()), @@ -101,7 +101,7 @@ var nodeTests = []restic.Node{ }, { Name: "testSuidFile2", - Type: "file", + Type: restic.NodeTypeFile, Content: restic.IDs{}, UID: uint32(os.Getuid()), GID: uint32(os.Getgid()), @@ -112,7 +112,7 @@ var nodeTests = []restic.Node{ }, { Name: "testSticky", - Type: "file", + Type: restic.NodeTypeFile, Content: restic.IDs{}, UID: uint32(os.Getuid()), GID: uint32(os.Getgid()), @@ -123,7 +123,7 @@ var nodeTests = []restic.Node{ }, { Name: "testDir", - Type: "dir", + Type: restic.NodeTypeDir, Subtree: nil, UID: uint32(os.Getuid()), GID: uint32(os.Getgid()), @@ -134,7 +134,7 @@ var nodeTests = []restic.Node{ }, { Name: "testSymlink", - Type: "symlink", + Type: restic.NodeTypeSymlink, LinkTarget: "invalid", UID: uint32(os.Getuid()), GID: uint32(os.Getgid()), @@ -148,7 +148,7 @@ var nodeTests = []restic.Node{ // metadata, so we can test if CreateAt works with pre-existing files. { Name: "testFile", - Type: "file", + Type: restic.NodeTypeFile, Content: restic.IDs{}, UID: uint32(os.Getuid()), GID: uint32(os.Getgid()), @@ -159,7 +159,7 @@ var nodeTests = []restic.Node{ }, { Name: "testDir", - Type: "dir", + Type: restic.NodeTypeDir, Subtree: nil, UID: uint32(os.Getuid()), GID: uint32(os.Getgid()), @@ -170,7 +170,7 @@ var nodeTests = []restic.Node{ }, { Name: "testXattrFile", - Type: "file", + Type: restic.NodeTypeFile, Content: restic.IDs{}, UID: uint32(os.Getuid()), GID: uint32(os.Getgid()), @@ -184,7 +184,7 @@ var nodeTests = []restic.Node{ }, { Name: "testXattrDir", - Type: "dir", + Type: restic.NodeTypeDir, Subtree: nil, UID: uint32(os.Getuid()), GID: uint32(os.Getgid()), @@ -198,7 +198,7 @@ var nodeTests = []restic.Node{ }, { Name: "testXattrFileMacOSResourceFork", - Type: "file", + Type: restic.NodeTypeFile, Content: restic.IDs{}, UID: uint32(os.Getuid()), GID: uint32(os.Getgid()), @@ -268,7 +268,7 @@ func TestNodeRestoreAt(t *testing.T) { "%v: UID doesn't match (%v != %v)", test.Type, test.UID, n2.UID) rtest.Assert(t, test.GID == n2.GID, "%v: GID doesn't match (%v != %v)", test.Type, test.GID, n2.GID) - if test.Type != "symlink" { + if test.Type != restic.NodeTypeSymlink { // On OpenBSD only root can set sticky bit (see sticky(8)). if runtime.GOOS != "openbsd" && runtime.GOOS != "netbsd" && runtime.GOOS != "solaris" && test.Name == "testSticky" { rtest.Assert(t, test.Mode == n2.Mode, @@ -288,11 +288,11 @@ func TestNodeRestoreAt(t *testing.T) { } } -func AssertFsTimeEqual(t *testing.T, label string, nodeType string, t1 time.Time, t2 time.Time) { +func AssertFsTimeEqual(t *testing.T, label string, nodeType restic.NodeType, t1 time.Time, t2 time.Time) { var equal bool // Go currently doesn't support setting timestamps of symbolic links on darwin and bsd - if nodeType == "symlink" { + if nodeType == restic.NodeTypeSymlink { switch runtime.GOOS { case "darwin", "freebsd", "openbsd", "netbsd", "solaris": return diff --git a/internal/fs/node_unix_test.go b/internal/fs/node_unix_test.go index b505357f2..3658c8356 100644 --- a/internal/fs/node_unix_test.go +++ b/internal/fs/node_unix_test.go @@ -42,7 +42,7 @@ func checkFile(t testing.TB, stat *syscall.Stat_t, node *restic.Node) { t.Errorf("Dev does not match, want %v, got %v", stat.Dev, node.DeviceID) } - if node.Size != uint64(stat.Size) && node.Type != "symlink" { + if node.Size != uint64(stat.Size) && node.Type != restic.NodeTypeSymlink { t.Errorf("Size does not match, want %v, got %v", stat.Size, node.Size) } @@ -135,9 +135,9 @@ func TestNodeFromFileInfo(t *testing.T) { } switch node.Type { - case "file", "symlink": + case restic.NodeTypeFile, restic.NodeTypeSymlink: checkFile(t, s, node) - case "dev", "chardev": + case restic.NodeTypeDev, restic.NodeTypeCharDev: checkFile(t, s, node) checkDevice(t, s, node) default: diff --git a/internal/fs/node_windows.go b/internal/fs/node_windows.go index 90fa3462c..f2a2001e9 100644 --- a/internal/fs/node_windows.go +++ b/internal/fs/node_windows.go @@ -139,7 +139,7 @@ func closeFileHandle(fileHandle windows.Handle, path string) { // restoreExtendedAttributes handles restore of the Windows Extended Attributes to the specified path. // The Windows API requires setting of all the Extended Attributes in one call. -func restoreExtendedAttributes(nodeType, path string, eas []ExtendedAttribute) (err error) { +func restoreExtendedAttributes(nodeType restic.NodeType, path string, eas []ExtendedAttribute) (err error) { var fileHandle windows.Handle if fileHandle, err = OpenHandleForEA(nodeType, path, true); fileHandle == 0 { return nil @@ -386,7 +386,7 @@ func nodeFillGenericAttributes(node *restic.Node, path string, fi os.FileInfo, s } var sd *[]byte - if node.Type == "file" || node.Type == "dir" { + if node.Type == restic.NodeTypeFile || node.Type == restic.NodeTypeDir { // Check EA support and get security descriptor for file/dir only allowExtended, err = checkAndStoreEASupport(path) if err != nil { diff --git a/internal/fs/node_windows_test.go b/internal/fs/node_windows_test.go index 046c1984c..729ce2adb 100644 --- a/internal/fs/node_windows_test.go +++ b/internal/fs/node_windows_test.go @@ -24,14 +24,14 @@ func TestRestoreSecurityDescriptors(t *testing.T) { t.Parallel() tempDir := t.TempDir() for i, sd := range TestFileSDs { - testRestoreSecurityDescriptor(t, sd, tempDir, "file", fmt.Sprintf("testfile%d", i)) + testRestoreSecurityDescriptor(t, sd, tempDir, restic.NodeTypeFile, fmt.Sprintf("testfile%d", i)) } for i, sd := range TestDirSDs { - testRestoreSecurityDescriptor(t, sd, tempDir, "dir", fmt.Sprintf("testdir%d", i)) + testRestoreSecurityDescriptor(t, sd, tempDir, restic.NodeTypeDir, fmt.Sprintf("testdir%d", i)) } } -func testRestoreSecurityDescriptor(t *testing.T, sd string, tempDir, fileType, fileName string) { +func testRestoreSecurityDescriptor(t *testing.T, sd string, tempDir string, fileType restic.NodeType, fileName string) { // Decode the encoded string SD to get the security descriptor input in bytes. sdInputBytes, err := base64.StdEncoding.DecodeString(sd) test.OK(t, errors.Wrapf(err, "Error decoding SD for: %s", fileName)) @@ -56,7 +56,7 @@ func testRestoreSecurityDescriptor(t *testing.T, sd string, tempDir, fileType, f CompareSecurityDescriptors(t, testPath, *sdByteFromRestoredNode, *sdBytesFromRestoredPath) } -func getNode(name string, fileType string, genericAttributes map[restic.GenericAttributeType]json.RawMessage) restic.Node { +func getNode(name string, fileType restic.NodeType, genericAttributes map[restic.GenericAttributeType]json.RawMessage) restic.Node { return restic.Node{ Name: name, Type: fileType, @@ -113,7 +113,7 @@ func TestRestoreFileAttributes(t *testing.T) { expectedNodes := []restic.Node{ { Name: fmt.Sprintf("testfile%d", i), - Type: "file", + Type: restic.NodeTypeFile, Mode: 0655, ModTime: parseTime("2005-05-14 21:07:03.111"), AccessTime: parseTime("2005-05-14 21:07:04.222"), @@ -146,7 +146,7 @@ func TestRestoreFileAttributes(t *testing.T) { expectedNodes := []restic.Node{ { Name: fmt.Sprintf("testdirectory%d", i), - Type: "dir", + Type: restic.NodeTypeDir, Mode: 0755, ModTime: parseTime("2005-05-14 21:07:03.111"), AccessTime: parseTime("2005-05-14 21:07:04.222"), @@ -164,7 +164,7 @@ func runGenericAttributesTest(t *testing.T, tempDir string, genericAttributeName expectedNodes := []restic.Node{ { Name: "testfile", - Type: "file", + Type: restic.NodeTypeFile, Mode: 0644, ModTime: parseTime("2005-05-14 21:07:03.111"), AccessTime: parseTime("2005-05-14 21:07:04.222"), @@ -173,7 +173,7 @@ func runGenericAttributesTest(t *testing.T, tempDir string, genericAttributeName }, { Name: "testdirectory", - Type: "dir", + Type: restic.NodeTypeDir, Mode: 0755, ModTime: parseTime("2005-05-14 21:07:03.111"), AccessTime: parseTime("2005-05-14 21:07:04.222"), @@ -200,12 +200,12 @@ func restoreAndGetNode(t *testing.T, tempDir string, testNode *restic.Node, warn err := os.MkdirAll(filepath.Dir(testPath), testNode.Mode) test.OK(t, errors.Wrapf(err, "Failed to create parent directories for: %s", testPath)) - if testNode.Type == "file" { + if testNode.Type == restic.NodeTypeFile { testFile, err := os.Create(testPath) test.OK(t, errors.Wrapf(err, "Failed to create test file: %s", testPath)) testFile.Close() - } else if testNode.Type == "dir" { + } else if testNode.Type == restic.NodeTypeDir { err := os.Mkdir(testPath, testNode.Mode) test.OK(t, errors.Wrapf(err, "Failed to create test directory: %s", testPath)) @@ -242,7 +242,7 @@ func TestNewGenericAttributeType(t *testing.T) { expectedNodes := []restic.Node{ { Name: "testfile", - Type: "file", + Type: restic.NodeTypeFile, Mode: 0644, ModTime: parseTime("2005-05-14 21:07:03.111"), AccessTime: parseTime("2005-05-14 21:07:04.222"), @@ -251,7 +251,7 @@ func TestNewGenericAttributeType(t *testing.T) { }, { Name: "testdirectory", - Type: "dir", + Type: restic.NodeTypeDir, Mode: 0755, ModTime: parseTime("2005-05-14 21:07:03.111"), AccessTime: parseTime("2005-05-14 21:07:04.222"), @@ -274,7 +274,7 @@ func TestRestoreExtendedAttributes(t *testing.T) { expectedNodes := []restic.Node{ { Name: "testfile", - Type: "file", + Type: restic.NodeTypeFile, Mode: 0644, ModTime: parseTime("2005-05-14 21:07:03.111"), AccessTime: parseTime("2005-05-14 21:07:04.222"), @@ -285,7 +285,7 @@ func TestRestoreExtendedAttributes(t *testing.T) { }, { Name: "testdirectory", - Type: "dir", + Type: restic.NodeTypeDir, Mode: 0755, ModTime: parseTime("2005-05-14 21:07:03.111"), AccessTime: parseTime("2005-05-14 21:07:04.222"), @@ -301,9 +301,9 @@ func TestRestoreExtendedAttributes(t *testing.T) { var handle windows.Handle var err error utf16Path := windows.StringToUTF16Ptr(testPath) - if node.Type == "file" { + if node.Type == restic.NodeTypeFile { handle, err = windows.CreateFile(utf16Path, windows.FILE_READ_EA, 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, 0) - } else if node.Type == "dir" { + } else if node.Type == restic.NodeTypeDir { handle, err = windows.CreateFile(utf16Path, windows.FILE_READ_EA, 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_BACKUP_SEMANTICS, 0) } test.OK(t, errors.Wrapf(err, "Error opening file/directory for: %s", testPath)) diff --git a/internal/fs/node_xattr_all_test.go b/internal/fs/node_xattr_all_test.go index 39670d6e1..690302f70 100644 --- a/internal/fs/node_xattr_all_test.go +++ b/internal/fs/node_xattr_all_test.go @@ -23,13 +23,13 @@ func setAndVerifyXattr(t *testing.T, file string, attrs []restic.ExtendedAttribu } node := &restic.Node{ - Type: "file", + Type: restic.NodeTypeFile, ExtendedAttributes: attrs, } rtest.OK(t, nodeRestoreExtendedAttributes(node, file)) nodeActual := &restic.Node{ - Type: "file", + Type: restic.NodeTypeFile, } rtest.OK(t, nodeFillExtendedAttributes(nodeActual, file, false)) diff --git a/internal/fuse/dir.go b/internal/fuse/dir.go index fd030295b..62298cf24 100644 --- a/internal/fuse/dir.go +++ b/internal/fuse/dir.go @@ -59,7 +59,7 @@ func unwrapCtxCanceled(err error) error { // replaceSpecialNodes replaces nodes with name "." and "/" by their contents. // Otherwise, the node is returned. func replaceSpecialNodes(ctx context.Context, repo restic.BlobLoader, node *restic.Node) ([]*restic.Node, error) { - if node.Type != "dir" || node.Subtree == nil { + if node.Type != restic.NodeTypeDir || node.Subtree == nil { return []*restic.Node{node}, nil } @@ -147,7 +147,7 @@ func (d *dir) calcNumberOfLinks() uint32 { // of directories contained by d count := uint32(2) for _, node := range d.items { - if node.Type == "dir" { + if node.Type == restic.NodeTypeDir { count++ } } @@ -182,11 +182,11 @@ func (d *dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { name := cleanupNodeName(node.Name) var typ fuse.DirentType switch node.Type { - case "dir": + case restic.NodeTypeDir: typ = fuse.DT_Dir - case "file": + case restic.NodeTypeFile: typ = fuse.DT_File - case "symlink": + case restic.NodeTypeSymlink: typ = fuse.DT_Link } @@ -215,13 +215,13 @@ func (d *dir) Lookup(ctx context.Context, name string) (fs.Node, error) { } inode := inodeFromNode(d.inode, node) switch node.Type { - case "dir": + case restic.NodeTypeDir: return newDir(d.root, inode, d.inode, node) - case "file": + case restic.NodeTypeFile: return newFile(d.root, inode, node) - case "symlink": + case restic.NodeTypeSymlink: return newLink(d.root, inode, node) - case "dev", "chardev", "fifo", "socket": + case restic.NodeTypeDev, restic.NodeTypeCharDev, restic.NodeTypeFifo, restic.NodeTypeSocket: return newOther(d.root, inode, node) default: debug.Log(" node %v has unknown type %v", name, node.Type) diff --git a/internal/fuse/fuse_test.go b/internal/fuse/fuse_test.go index aebcb1272..bbdfe6b3f 100644 --- a/internal/fuse/fuse_test.go +++ b/internal/fuse/fuse_test.go @@ -249,7 +249,7 @@ func TestBlocks(t *testing.T) { } func TestInodeFromNode(t *testing.T) { - node := &restic.Node{Name: "foo.txt", Type: "chardev", Links: 2} + node := &restic.Node{Name: "foo.txt", Type: restic.NodeTypeCharDev, Links: 2} ino1 := inodeFromNode(1, node) ino2 := inodeFromNode(2, node) rtest.Assert(t, ino1 == ino2, "inodes %d, %d of hard links differ", ino1, ino2) @@ -261,9 +261,9 @@ func TestInodeFromNode(t *testing.T) { // Regression test: in a path a/b/b, the grandchild should not get the // same inode as the grandparent. - a := &restic.Node{Name: "a", Type: "dir", Links: 2} - ab := &restic.Node{Name: "b", Type: "dir", Links: 2} - abb := &restic.Node{Name: "b", Type: "dir", Links: 2} + a := &restic.Node{Name: "a", Type: restic.NodeTypeDir, Links: 2} + ab := &restic.Node{Name: "b", Type: restic.NodeTypeDir, Links: 2} + abb := &restic.Node{Name: "b", Type: restic.NodeTypeDir, Links: 2} inoA := inodeFromNode(1, a) inoAb := inodeFromNode(inoA, ab) inoAbb := inodeFromNode(inoAb, abb) @@ -272,7 +272,7 @@ func TestInodeFromNode(t *testing.T) { } func TestLink(t *testing.T) { - node := &restic.Node{Name: "foo.txt", Type: "symlink", Links: 1, LinkTarget: "dst", ExtendedAttributes: []restic.ExtendedAttribute{ + node := &restic.Node{Name: "foo.txt", Type: restic.NodeTypeSymlink, Links: 1, LinkTarget: "dst", ExtendedAttributes: []restic.ExtendedAttribute{ {Name: "foo", Value: []byte("bar")}, }} @@ -305,11 +305,11 @@ func BenchmarkInode(b *testing.B) { }{ { name: "no_hard_links", - node: restic.Node{Name: "a somewhat long-ish filename.svg.bz2", Type: "fifo"}, + node: restic.Node{Name: "a somewhat long-ish filename.svg.bz2", Type: restic.NodeTypeFifo}, }, { name: "hard_link", - node: restic.Node{Name: "some other filename", Type: "file", Links: 2}, + node: restic.Node{Name: "some other filename", Type: restic.NodeTypeFile, Links: 2}, }, } { b.Run(sub.name, func(b *testing.B) { diff --git a/internal/fuse/inode.go b/internal/fuse/inode.go index 5e2ece4ac..88d5b8bb8 100644 --- a/internal/fuse/inode.go +++ b/internal/fuse/inode.go @@ -25,7 +25,7 @@ func inodeFromName(parent uint64, name string) uint64 { // inodeFromNode generates an inode number for a file within a snapshot. func inodeFromNode(parent uint64, node *restic.Node) (inode uint64) { - if node.Links > 1 && node.Type != "dir" { + if node.Links > 1 && node.Type != restic.NodeTypeDir { // If node has hard links, give them all the same inode, // irrespective of the parent. var buf [16]byte diff --git a/internal/restic/find.go b/internal/restic/find.go index d7b032bf8..f9b4e8bdf 100644 --- a/internal/restic/find.go +++ b/internal/restic/find.go @@ -46,7 +46,7 @@ func FindUsedBlobs(ctx context.Context, repo Loader, treeIDs IDs, blobs FindBlob lock.Lock() for _, node := range tree.Nodes { switch node.Type { - case "file": + case NodeTypeFile: for _, blob := range node.Content { blobs.Insert(BlobHandle{ID: blob, Type: DataBlob}) } diff --git a/internal/restic/node.go b/internal/restic/node.go index 8bf97e59c..c572996a5 100644 --- a/internal/restic/node.go +++ b/internal/restic/node.go @@ -67,10 +67,24 @@ func storeGenericAttributeType(attributeTypes ...GenericAttributeType) { } } +type NodeType string + +var ( + NodeTypeFile = NodeType("file") + NodeTypeDir = NodeType("dir") + NodeTypeSymlink = NodeType("symlink") + NodeTypeDev = NodeType("dev") + NodeTypeCharDev = NodeType("chardev") + NodeTypeFifo = NodeType("fifo") + NodeTypeSocket = NodeType("socket") + NodeTypeIrregular = NodeType("irregular") + NodeTypeInvalid = NodeType("") +) + // Node is a file, directory or other item in a backup. type Node struct { Name string `json:"name"` - Type string `json:"type"` + Type NodeType `json:"type"` Mode os.FileMode `json:"mode,omitempty"` ModTime time.Time `json:"mtime,omitempty"` AccessTime time.Time `json:"atime,omitempty"` @@ -110,19 +124,19 @@ func (n Nodes) Swap(i, j int) { n[i], n[j] = n[j], n[i] } func (node Node) String() string { var mode os.FileMode switch node.Type { - case "file": + case NodeTypeFile: mode = 0 - case "dir": + case NodeTypeDir: mode = os.ModeDir - case "symlink": + case NodeTypeSymlink: mode = os.ModeSymlink - case "dev": + case NodeTypeDev: mode = os.ModeDevice - case "chardev": + case NodeTypeCharDev: mode = os.ModeDevice | os.ModeCharDevice - case "fifo": + case NodeTypeFifo: mode = os.ModeNamedPipe - case "socket": + case NodeTypeSocket: mode = os.ModeSocket } diff --git a/internal/restic/testing.go b/internal/restic/testing.go index 8f86a7b2c..3e0563430 100644 --- a/internal/restic/testing.go +++ b/internal/restic/testing.go @@ -81,7 +81,7 @@ func (fs *fakeFileSystem) saveTree(ctx context.Context, seed int64, depth int) I node := &Node{ Name: fmt.Sprintf("dir-%v", treeSeed), - Type: "dir", + Type: NodeTypeDir, Mode: 0755, Subtree: &id, } @@ -95,7 +95,7 @@ func (fs *fakeFileSystem) saveTree(ctx context.Context, seed int64, depth int) I node := &Node{ Name: fmt.Sprintf("file-%v", fileSeed), - Type: "file", + Type: NodeTypeFile, Mode: 0644, Size: uint64(fileSize), } diff --git a/internal/restic/tree.go b/internal/restic/tree.go index 3c3e3ab56..c4125653b 100644 --- a/internal/restic/tree.go +++ b/internal/restic/tree.go @@ -96,7 +96,7 @@ func (t *Tree) Sort() { // Subtrees returns a slice of all subtree IDs of the tree. func (t *Tree) Subtrees() (trees IDs) { for _, node := range t.Nodes { - if node.Type == "dir" && node.Subtree != nil { + if node.Type == NodeTypeDir && node.Subtree != nil { trees = append(trees, *node.Subtree) } } @@ -208,7 +208,7 @@ func FindTreeDirectory(ctx context.Context, repo BlobLoader, id *ID, dir string) if node == nil { return nil, fmt.Errorf("path %s: not found", subfolder) } - if node.Type != "dir" || node.Subtree == nil { + if node.Type != NodeTypeDir || node.Subtree == nil { return nil, fmt.Errorf("path %s: not a directory", subfolder) } id = node.Subtree diff --git a/internal/restorer/restorer.go b/internal/restorer/restorer.go index 26b6f3474..f28cd0ba3 100644 --- a/internal/restorer/restorer.go +++ b/internal/restorer/restorer.go @@ -202,18 +202,18 @@ func (res *Restorer) traverseTreeInner(ctx context.Context, target, location str } // sockets cannot be restored - if node.Type == "socket" { + if node.Type == restic.NodeTypeSocket { continue } - selectedForRestore, childMayBeSelected := res.SelectFilter(nodeLocation, node.Type == "dir") + selectedForRestore, childMayBeSelected := res.SelectFilter(nodeLocation, node.Type == restic.NodeTypeDir) debug.Log("SelectFilter returned %v %v for %q", selectedForRestore, childMayBeSelected, nodeLocation) if selectedForRestore { hasRestored = true } - if node.Type == "dir" { + if node.Type == restic.NodeTypeDir { if node.Subtree == nil { return nil, hasRestored, errors.Errorf("Dir without subtree in tree %v", treeID.Str()) } @@ -377,7 +377,7 @@ func (res *Restorer) RestoreTo(ctx context.Context, dst string) (uint64, error) return err } - if node.Type != "file" { + if node.Type != restic.NodeTypeFile { res.opts.Progress.AddFile(0) return nil } @@ -433,7 +433,7 @@ func (res *Restorer) RestoreTo(ctx context.Context, dst string) (uint64, error) err = res.traverseTree(ctx, dst, *res.sn.Tree, treeVisitor{ visitNode: func(node *restic.Node, target, location string) error { debug.Log("second pass, visitNode: restore node %q", location) - if node.Type != "file" { + if node.Type != restic.NodeTypeFile { _, err := res.withOverwriteCheck(ctx, node, target, location, false, nil, func(_ bool, _ *fileState) error { return res.restoreNodeTo(node, target, location) }) @@ -547,7 +547,7 @@ func (res *Restorer) withOverwriteCheck(ctx context.Context, node *restic.Node, var matches *fileState updateMetadataOnly := false - if node.Type == "file" && !isHardlink { + if node.Type == restic.NodeTypeFile && !isHardlink { // if a file fails to verify, then matches is nil which results in restoring from scratch matches, buf, _ = res.verifyFile(ctx, target, node, false, res.opts.Overwrite == OverwriteIfChanged, buf) // skip files that are already correct completely @@ -616,7 +616,7 @@ func (res *Restorer) VerifyFiles(ctx context.Context, dst string, countRestoredF err := res.traverseTree(ctx, dst, *res.sn.Tree, treeVisitor{ visitNode: func(node *restic.Node, target, location string) error { - if node.Type != "file" { + if node.Type != restic.NodeTypeFile { return nil } if metadataOnly, ok := res.hasRestoredFile(location); !ok || metadataOnly { diff --git a/internal/restorer/restorer_test.go b/internal/restorer/restorer_test.go index 7d4895068..8844435ca 100644 --- a/internal/restorer/restorer_test.go +++ b/internal/restorer/restorer_test.go @@ -108,7 +108,7 @@ func saveDir(t testing.TB, repo restic.BlobSaver, nodes map[string]Node, inode u mode = 0644 } err := tree.Insert(&restic.Node{ - Type: "file", + Type: restic.NodeTypeFile, Mode: mode, ModTime: node.ModTime, Name: name, @@ -123,7 +123,7 @@ func saveDir(t testing.TB, repo restic.BlobSaver, nodes map[string]Node, inode u rtest.OK(t, err) case Symlink: err := tree.Insert(&restic.Node{ - Type: "symlink", + Type: restic.NodeTypeSymlink, Mode: os.ModeSymlink | 0o777, ModTime: node.ModTime, Name: name, @@ -143,7 +143,7 @@ func saveDir(t testing.TB, repo restic.BlobSaver, nodes map[string]Node, inode u } err := tree.Insert(&restic.Node{ - Type: "dir", + Type: restic.NodeTypeDir, Mode: mode, ModTime: node.ModTime, Name: name, diff --git a/internal/ui/backup/progress.go b/internal/ui/backup/progress.go index 1d494bf14..24640d71f 100644 --- a/internal/ui/backup/progress.go +++ b/internal/ui/backup/progress.go @@ -124,7 +124,7 @@ func (p *Progress) CompleteItem(item string, previous, current *restic.Node, s a } switch current.Type { - case "dir": + case restic.NodeTypeDir: p.mu.Lock() p.addProcessed(Counter{Dirs: 1}) p.mu.Unlock() @@ -138,7 +138,7 @@ func (p *Progress) CompleteItem(item string, previous, current *restic.Node, s a p.printer.CompleteItem("dir modified", item, s, d) } - case "file": + case restic.NodeTypeFile: p.mu.Lock() p.addProcessed(Counter{Files: 1}) delete(p.currentFiles, item) diff --git a/internal/ui/backup/progress_test.go b/internal/ui/backup/progress_test.go index 6b242a0f3..512fbab26 100644 --- a/internal/ui/backup/progress_test.go +++ b/internal/ui/backup/progress_test.go @@ -55,10 +55,10 @@ func TestProgress(t *testing.T) { prog.CompleteBlob(1024) // "dir unchanged" - node := restic.Node{Type: "dir"} + node := restic.Node{Type: restic.NodeTypeDir} prog.CompleteItem("foo", &node, &node, archiver.ItemStats{}, 0) // "file new" - node.Type = "file" + node.Type = restic.NodeTypeFile prog.CompleteItem("foo", nil, &node, archiver.ItemStats{}, 0) time.Sleep(10 * time.Millisecond) diff --git a/internal/walker/rewriter.go b/internal/walker/rewriter.go index 7e984ae25..968ef44f3 100644 --- a/internal/walker/rewriter.go +++ b/internal/walker/rewriter.go @@ -65,7 +65,7 @@ func NewSnapshotSizeRewriter(rewriteNode NodeRewriteFunc) (*TreeRewriter, QueryR t := NewTreeRewriter(RewriteOpts{ RewriteNode: func(node *restic.Node, path string) *restic.Node { node = rewriteNode(node, path) - if node != nil && node.Type == "file" { + if node != nil && node.Type == restic.NodeTypeFile { count++ size += node.Size } @@ -126,7 +126,7 @@ func (t *TreeRewriter) RewriteTree(ctx context.Context, repo BlobLoadSaver, node continue } - if node.Type != "dir" { + if node.Type != restic.NodeTypeDir { err = tb.AddNode(node) if err != nil { return restic.ID{}, err diff --git a/internal/walker/rewriter_test.go b/internal/walker/rewriter_test.go index f05e50f9b..58dd25cd0 100644 --- a/internal/walker/rewriter_test.go +++ b/internal/walker/rewriter_test.go @@ -110,7 +110,7 @@ func checkIncreaseNodeSize(increase uint64) checkRewriteFunc { return func(t testing.TB) (rewriter *TreeRewriter, final func(testing.TB)) { rewriter = NewTreeRewriter(RewriteOpts{ RewriteNode: func(node *restic.Node, path string) *restic.Node { - if node.Type == "file" { + if node.Type == restic.NodeTypeFile { node.Size += increase } return node @@ -329,7 +329,7 @@ func TestSnapshotSizeQuery(t *testing.T) { if path == "/bar" { return nil } - if node.Type == "file" { + if node.Type == restic.NodeTypeFile { node.Size += 21 } return node diff --git a/internal/walker/walker.go b/internal/walker/walker.go index 788ece1cf..8acfed2f2 100644 --- a/internal/walker/walker.go +++ b/internal/walker/walker.go @@ -63,11 +63,11 @@ func walk(ctx context.Context, repo restic.BlobLoader, prefix string, parentTree p := path.Join(prefix, node.Name) - if node.Type == "" { + if node.Type == restic.NodeTypeInvalid { return errors.Errorf("node type is empty for node %q", node.Name) } - if node.Type != "dir" { + if node.Type != restic.NodeTypeDir { err := visitor.ProcessNode(parentTreeID, p, node, nil) if err != nil { if err == ErrSkipNode { diff --git a/internal/walker/walker_test.go b/internal/walker/walker_test.go index 75f80e57f..fa377bb8f 100644 --- a/internal/walker/walker_test.go +++ b/internal/walker/walker_test.go @@ -38,7 +38,7 @@ func buildTreeMap(tree TestTree, m TreeMap) restic.ID { case TestFile: err := tb.AddNode(&restic.Node{ Name: name, - Type: "file", + Type: restic.NodeTypeFile, Size: elem.Size, }) if err != nil { @@ -49,7 +49,7 @@ func buildTreeMap(tree TestTree, m TreeMap) restic.ID { err := tb.AddNode(&restic.Node{ Name: name, Subtree: &id, - Type: "dir", + Type: restic.NodeTypeDir, }) if err != nil { panic(err) From 24f4e780f1019b49fd9f0c6644d3925a1e754056 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 21 Jul 2024 15:22:21 +0200 Subject: [PATCH 02/12] backend: consistently use os package for filesystem access The go std library should be good enough to manage the files in the backend and cache folders. --- cmd/restic/cmd_cache.go | 3 +-- cmd/restic/cmd_check.go | 3 +-- cmd/restic/global.go | 3 +-- internal/backend/cache/cache.go | 13 ++++++------- internal/backend/cache/file.go | 21 ++++++++++----------- internal/backend/cache/file_test.go | 3 +-- internal/backend/local/local.go | 24 ++++++++++++------------ internal/backend/local/local_unix.go | 4 +--- internal/fs/file.go | 9 --------- 9 files changed, 33 insertions(+), 50 deletions(-) diff --git a/cmd/restic/cmd_cache.go b/cmd/restic/cmd_cache.go index e54c73451..cd970b699 100644 --- a/cmd/restic/cmd_cache.go +++ b/cmd/restic/cmd_cache.go @@ -10,7 +10,6 @@ import ( "github.com/restic/restic/internal/backend/cache" "github.com/restic/restic/internal/errors" - "github.com/restic/restic/internal/fs" "github.com/restic/restic/internal/ui" "github.com/restic/restic/internal/ui/table" "github.com/spf13/cobra" @@ -89,7 +88,7 @@ func runCache(opts CacheOptions, gopts GlobalOptions, args []string) error { for _, item := range oldDirs { dir := filepath.Join(cachedir, item.Name()) - err = fs.RemoveAll(dir) + err = os.RemoveAll(dir) if err != nil { Warnf("unable to remove %v: %v\n", dir, err) } diff --git a/cmd/restic/cmd_check.go b/cmd/restic/cmd_check.go index fc460e39e..8788b0caf 100644 --- a/cmd/restic/cmd_check.go +++ b/cmd/restic/cmd_check.go @@ -14,7 +14,6 @@ import ( "github.com/restic/restic/internal/backend/cache" "github.com/restic/restic/internal/checker" "github.com/restic/restic/internal/errors" - "github.com/restic/restic/internal/fs" "github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/ui" @@ -202,7 +201,7 @@ func prepareCheckCache(opts CheckOptions, gopts *GlobalOptions, printer progress printer.P("using temporary cache in %v\n", tempdir) cleanup = func() { - err := fs.RemoveAll(tempdir) + err := os.RemoveAll(tempdir) if err != nil { printer.E("error removing temporary cache directory: %v\n", err) } diff --git a/cmd/restic/global.go b/cmd/restic/global.go index 375b57f98..5fb37f9f2 100644 --- a/cmd/restic/global.go +++ b/cmd/restic/global.go @@ -29,7 +29,6 @@ import ( "github.com/restic/restic/internal/backend/sftp" "github.com/restic/restic/internal/backend/swift" "github.com/restic/restic/internal/debug" - "github.com/restic/restic/internal/fs" "github.com/restic/restic/internal/options" "github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/restic" @@ -548,7 +547,7 @@ func OpenRepository(ctx context.Context, opts GlobalOptions) (*repository.Reposi } for _, item := range oldCacheDirs { dir := filepath.Join(c.Base, item.Name()) - err = fs.RemoveAll(dir) + err = os.RemoveAll(dir) if err != nil { Warnf("unable to remove %v: %v\n", dir, err) } diff --git a/internal/backend/cache/cache.go b/internal/backend/cache/cache.go index a55b51c70..2893df501 100644 --- a/internal/backend/cache/cache.go +++ b/internal/backend/cache/cache.go @@ -12,7 +12,6 @@ import ( "github.com/pkg/errors" "github.com/restic/restic/internal/backend" "github.com/restic/restic/internal/debug" - "github.com/restic/restic/internal/fs" "github.com/restic/restic/internal/restic" ) @@ -54,7 +53,7 @@ const cachedirTagSignature = "Signature: 8a477f597d28d172789f06886806bc55\n" func writeCachedirTag(dir string) error { tagfile := filepath.Join(dir, "CACHEDIR.TAG") - f, err := fs.OpenFile(tagfile, os.O_CREATE|os.O_EXCL|os.O_WRONLY, fileMode) + f, err := os.OpenFile(tagfile, os.O_CREATE|os.O_EXCL|os.O_WRONLY, fileMode) if err != nil { if errors.Is(err, os.ErrExist) { return nil @@ -85,7 +84,7 @@ func New(id string, basedir string) (c *Cache, err error) { } } - err = fs.MkdirAll(basedir, dirMode) + err = os.MkdirAll(basedir, dirMode) if err != nil { return nil, errors.WithStack(err) } @@ -113,7 +112,7 @@ func New(id string, basedir string) (c *Cache, err error) { case errors.Is(err, os.ErrNotExist): // Create the repo cache dir. The parent exists, so Mkdir suffices. - err := fs.Mkdir(cachedir, dirMode) + err := os.Mkdir(cachedir, dirMode) switch { case err == nil: created = true @@ -134,7 +133,7 @@ func New(id string, basedir string) (c *Cache, err error) { } for _, p := range cacheLayoutPaths { - if err = fs.MkdirAll(filepath.Join(cachedir, p), dirMode); err != nil { + if err = os.MkdirAll(filepath.Join(cachedir, p), dirMode); err != nil { return nil, errors.WithStack(err) } } @@ -152,7 +151,7 @@ func New(id string, basedir string) (c *Cache, err error) { // directory d to the current time. func updateTimestamp(d string) error { t := time.Now() - return fs.Chtimes(d, t, t) + return os.Chtimes(d, t, t) } // MaxCacheAge is the default age (30 days) after which cache directories are considered old. @@ -165,7 +164,7 @@ func validCacheDirName(s string) bool { // listCacheDirs returns the list of cache directories. func listCacheDirs(basedir string) ([]os.FileInfo, error) { - f, err := fs.Open(basedir) + f, err := os.Open(basedir) if err != nil { if errors.Is(err, os.ErrNotExist) { err = nil diff --git a/internal/backend/cache/file.go b/internal/backend/cache/file.go index 12f5f23c5..41fd0b49b 100644 --- a/internal/backend/cache/file.go +++ b/internal/backend/cache/file.go @@ -12,7 +12,6 @@ import ( "github.com/restic/restic/internal/backend/util" "github.com/restic/restic/internal/crypto" "github.com/restic/restic/internal/debug" - "github.com/restic/restic/internal/fs" "github.com/restic/restic/internal/restic" ) @@ -44,7 +43,7 @@ func (c *Cache) load(h backend.Handle, length int, offset int64) (io.ReadCloser, return nil, false, errors.New("cannot be cached") } - f, err := fs.Open(c.filename(h)) + f, err := os.Open(c.filename(h)) if err != nil { return nil, false, errors.WithStack(err) } @@ -91,7 +90,7 @@ func (c *Cache) save(h backend.Handle, rd io.Reader) error { finalname := c.filename(h) dir := filepath.Dir(finalname) - err := fs.Mkdir(dir, 0700) + err := os.Mkdir(dir, 0700) if err != nil && !errors.Is(err, os.ErrExist) { return err } @@ -106,26 +105,26 @@ func (c *Cache) save(h backend.Handle, rd io.Reader) error { n, err := io.Copy(f, rd) if err != nil { _ = f.Close() - _ = fs.Remove(f.Name()) + _ = os.Remove(f.Name()) return errors.Wrap(err, "Copy") } if n <= int64(crypto.CiphertextLength(0)) { _ = f.Close() - _ = fs.Remove(f.Name()) + _ = os.Remove(f.Name()) debug.Log("trying to cache truncated file %v, removing", h) return nil } // Close, then rename. Windows doesn't like the reverse order. if err = f.Close(); err != nil { - _ = fs.Remove(f.Name()) + _ = os.Remove(f.Name()) return errors.WithStack(err) } - err = fs.Rename(f.Name(), finalname) + err = os.Rename(f.Name(), finalname) if err != nil { - _ = fs.Remove(f.Name()) + _ = os.Remove(f.Name()) } if runtime.GOOS == "windows" && errors.Is(err, os.ErrPermission) { // On Windows, renaming over an existing file is ok @@ -162,7 +161,7 @@ func (c *Cache) remove(h backend.Handle) (bool, error) { return false, nil } - err := fs.Remove(c.filename(h)) + err := os.Remove(c.filename(h)) removed := err == nil if errors.Is(err, os.ErrNotExist) { err = nil @@ -189,7 +188,7 @@ func (c *Cache) Clear(t restic.FileType, valid restic.IDSet) error { } // ignore ErrNotExist to gracefully handle multiple processes running Clear() concurrently - if err = fs.Remove(c.filename(backend.Handle{Type: t, Name: id.String()})); err != nil && !errors.Is(err, os.ErrNotExist) { + if err = os.Remove(c.filename(backend.Handle{Type: t, Name: id.String()})); err != nil && !errors.Is(err, os.ErrNotExist) { return err } } @@ -236,6 +235,6 @@ func (c *Cache) Has(h backend.Handle) bool { return false } - _, err := fs.Stat(c.filename(h)) + _, err := os.Stat(c.filename(h)) return err == nil } diff --git a/internal/backend/cache/file_test.go b/internal/backend/cache/file_test.go index ed2cd295a..942f71f91 100644 --- a/internal/backend/cache/file_test.go +++ b/internal/backend/cache/file_test.go @@ -12,7 +12,6 @@ import ( "github.com/restic/restic/internal/backend" "github.com/restic/restic/internal/errors" - "github.com/restic/restic/internal/fs" "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" @@ -278,7 +277,7 @@ func TestFileSaveConcurrent(t *testing.T) { func TestFileSaveAfterDamage(t *testing.T) { c := TestNewCache(t) - rtest.OK(t, fs.RemoveAll(c.path)) + rtest.OK(t, os.RemoveAll(c.path)) // save a few bytes of data in the cache data := rtest.Random(123456789, 42) diff --git a/internal/backend/local/local.go b/internal/backend/local/local.go index 8985ef4c4..ee87ae5d6 100644 --- a/internal/backend/local/local.go +++ b/internal/backend/local/local.go @@ -40,7 +40,7 @@ func NewFactory() location.Factory { func open(cfg Config) (*Local, error) { l := layout.NewDefaultLayout(cfg.Path, filepath.Join) - fi, err := fs.Stat(l.Filename(backend.Handle{Type: backend.ConfigFile})) + fi, err := os.Stat(l.Filename(backend.Handle{Type: backend.ConfigFile})) m := util.DeriveModesFromFileInfo(fi, err) debug.Log("using (%03O file, %03O dir) permissions", m.File, m.Dir) @@ -68,14 +68,14 @@ func Create(_ context.Context, cfg Config) (*Local, error) { } // test if config file already exists - _, err = fs.Lstat(be.Filename(backend.Handle{Type: backend.ConfigFile})) + _, err = os.Lstat(be.Filename(backend.Handle{Type: backend.ConfigFile})) if err == nil { return nil, errors.New("config file already exists") } // create paths for data and refs for _, d := range be.Paths() { - err := fs.MkdirAll(d, be.Modes.Dir) + err := os.MkdirAll(d, be.Modes.Dir) if err != nil { return nil, errors.WithStack(err) } @@ -127,7 +127,7 @@ func (b *Local) Save(_ context.Context, h backend.Handle, rd backend.RewindReade debug.Log("error %v: creating dir", err) // error is caused by a missing directory, try to create it - mkdirErr := fs.MkdirAll(dir, b.Modes.Dir) + mkdirErr := os.MkdirAll(dir, b.Modes.Dir) if mkdirErr != nil { debug.Log("error creating dir %v: %v", dir, mkdirErr) } else { @@ -147,7 +147,7 @@ func (b *Local) Save(_ context.Context, h backend.Handle, rd backend.RewindReade // temporary's name and no other goroutine will get the same data to // Save, so the temporary name should never be reused by another // goroutine. - _ = fs.Remove(f.Name()) + _ = os.Remove(f.Name()) } }(f) @@ -211,7 +211,7 @@ func (b *Local) Load(ctx context.Context, h backend.Handle, length int, offset i } func (b *Local) openReader(_ context.Context, h backend.Handle, length int, offset int64) (io.ReadCloser, error) { - f, err := fs.Open(b.Filename(h)) + f, err := os.Open(b.Filename(h)) if err != nil { return nil, err } @@ -245,7 +245,7 @@ func (b *Local) openReader(_ context.Context, h backend.Handle, length int, offs // Stat returns information about a blob. func (b *Local) Stat(_ context.Context, h backend.Handle) (backend.FileInfo, error) { - fi, err := fs.Stat(b.Filename(h)) + fi, err := os.Stat(b.Filename(h)) if err != nil { return backend.FileInfo{}, errors.WithStack(err) } @@ -258,12 +258,12 @@ func (b *Local) Remove(_ context.Context, h backend.Handle) error { fn := b.Filename(h) // reset read-only flag - err := fs.Chmod(fn, 0666) + err := os.Chmod(fn, 0666) if err != nil && !os.IsPermission(err) { return errors.WithStack(err) } - return fs.Remove(fn) + return os.Remove(fn) } // List runs fn for each file in the backend which has the type t. When an @@ -289,7 +289,7 @@ func (b *Local) List(ctx context.Context, t backend.FileType, fn func(backend.Fi // Also, visitDirs assumes it sees a directory full of directories, while // visitFiles wants a directory full or regular files. func visitDirs(ctx context.Context, dir string, fn func(backend.FileInfo) error) error { - d, err := fs.Open(dir) + d, err := os.Open(dir) if err != nil { return err } @@ -316,7 +316,7 @@ func visitDirs(ctx context.Context, dir string, fn func(backend.FileInfo) error) } func visitFiles(ctx context.Context, dir string, fn func(backend.FileInfo) error, ignoreNotADirectory bool) error { - d, err := fs.Open(dir) + d, err := os.Open(dir) if err != nil { return err } @@ -362,7 +362,7 @@ func visitFiles(ctx context.Context, dir string, fn func(backend.FileInfo) error // Delete removes the repository and all files. func (b *Local) Delete(_ context.Context) error { - return fs.RemoveAll(b.Path) + return os.RemoveAll(b.Path) } // Close closes all open files. diff --git a/internal/backend/local/local_unix.go b/internal/backend/local/local_unix.go index e3256ed7a..e52587456 100644 --- a/internal/backend/local/local_unix.go +++ b/internal/backend/local/local_unix.go @@ -8,8 +8,6 @@ import ( "os" "runtime" "syscall" - - "github.com/restic/restic/internal/fs" ) // fsyncDir flushes changes to the directory dir. @@ -45,5 +43,5 @@ func isMacENOTTY(err error) bool { // set file to readonly func setFileReadonly(f string, mode os.FileMode) error { - return fs.Chmod(f, mode&^0222) + return os.Chmod(f, mode&^0222) } diff --git a/internal/fs/file.go b/internal/fs/file.go index 356b466c3..b727df79c 100644 --- a/internal/fs/file.go +++ b/internal/fs/file.go @@ -88,15 +88,6 @@ func OpenFile(name string, flag int, perm os.FileMode) (*os.File, error) { return os.OpenFile(fixpath(name), flag, perm) } -// Chtimes changes the access and modification times of the named file, -// similar to the Unix utime() or utimes() functions. -// -// The underlying filesystem may truncate or round the values to a less -// precise time unit. If there is an error, it will be of type *PathError. -func Chtimes(name string, atime time.Time, mtime time.Time) error { - return os.Chtimes(fixpath(name), atime, mtime) -} - // IsAccessDenied checks if the error is due to permission error. func IsAccessDenied(err error) bool { return os.IsPermission(err) From 65a71573836a2f1677472fca514beeaf76fe7cba Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 21 Jul 2024 15:56:03 +0200 Subject: [PATCH 03/12] mount: use os instead of fs package --- cmd/restic/cmd_mount.go | 3 +-- internal/fs/file.go | 6 ------ 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 2f57a6d1f..b8a66dc90 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -15,7 +15,6 @@ import ( "github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/restic" - resticfs "github.com/restic/restic/internal/fs" "github.com/restic/restic/internal/fuse" systemFuse "github.com/anacrolix/fuse" @@ -122,7 +121,7 @@ func runMount(ctx context.Context, opts MountOptions, gopts GlobalOptions, args // Check the existence of the mount point at the earliest stage to // prevent unnecessary computations while opening the repository. - if _, err := resticfs.Stat(mountpoint); errors.Is(err, os.ErrNotExist) { + if _, err := os.Stat(mountpoint); errors.Is(err, os.ErrNotExist) { Verbosef("Mountpoint %s doesn't exist\n", mountpoint) return err } diff --git a/internal/fs/file.go b/internal/fs/file.go index b727df79c..c1ebae081 100644 --- a/internal/fs/file.go +++ b/internal/fs/file.go @@ -60,12 +60,6 @@ func Link(oldname, newname string) error { return os.Link(fixpath(oldname), fixpath(newname)) } -// Stat returns a FileInfo structure describing the named file. -// If there is an error, it will be of type *PathError. -func Stat(name string) (os.FileInfo, error) { - return os.Stat(fixpath(name)) -} - // Lstat returns the FileInfo structure describing the named file. // If the file is a symbolic link, the returned FileInfo // describes the symbolic link. Lstat makes no attempt to follow the link. From cc7f99125a7cc25b0da073c29f787c8ba1be30c9 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 21 Jul 2024 15:58:41 +0200 Subject: [PATCH 04/12] minimize usage of internal/fs in tests --- internal/archiver/archiver_test.go | 4 ++-- internal/archiver/testing.go | 10 +++++----- internal/archiver/testing_test.go | 12 +++++------ internal/dump/tar_test.go | 3 +-- internal/dump/zip_test.go | 4 +--- internal/fs/file.go | 32 ------------------------------ internal/fs/node.go | 6 +++--- internal/fs/node_linux.go | 3 ++- internal/fs/node_windows.go | 2 +- internal/fs/setflags_linux_test.go | 2 +- internal/restorer/restorer_test.go | 2 +- 11 files changed, 23 insertions(+), 57 deletions(-) diff --git a/internal/archiver/archiver_test.go b/internal/archiver/archiver_test.go index 04d0b876b..b95947a2e 100644 --- a/internal/archiver/archiver_test.go +++ b/internal/archiver/archiver_test.go @@ -846,7 +846,7 @@ func TestArchiverSaveDir(t *testing.T) { back := rtest.Chdir(t, chdir) defer back() - fi, err := fs.Lstat(test.target) + fi, err := os.Lstat(test.target) if err != nil { t.Fatal(err) } @@ -920,7 +920,7 @@ func TestArchiverSaveDirIncremental(t *testing.T) { arch.runWorkers(ctx, wg) arch.summary = &Summary{} - fi, err := fs.Lstat(tempdir) + fi, err := os.Lstat(tempdir) if err != nil { t.Fatal(err) } diff --git a/internal/archiver/testing.go b/internal/archiver/testing.go index 97bada644..e555a70d6 100644 --- a/internal/archiver/testing.go +++ b/internal/archiver/testing.go @@ -95,17 +95,17 @@ func TestCreateFiles(t testing.TB, target string, dir TestDir) { t.Fatal(err) } case TestSymlink: - err := fs.Symlink(filepath.FromSlash(it.Target), targetPath) + err := os.Symlink(filepath.FromSlash(it.Target), targetPath) if err != nil { t.Fatal(err) } case TestHardlink: - err := fs.Link(filepath.Join(target, filepath.FromSlash(it.Target)), targetPath) + err := os.Link(filepath.Join(target, filepath.FromSlash(it.Target)), targetPath) if err != nil { t.Fatal(err) } case TestDir: - err := fs.Mkdir(targetPath, 0755) + err := os.Mkdir(targetPath, 0755) if err != nil { t.Fatal(err) } @@ -157,7 +157,7 @@ func TestEnsureFiles(t testing.TB, target string, dir TestDir) { // first, test that all items are there TestWalkFiles(t, target, dir, func(path string, item interface{}) error { - fi, err := fs.Lstat(path) + fi, err := os.Lstat(path) if err != nil { return err } @@ -188,7 +188,7 @@ func TestEnsureFiles(t testing.TB, target string, dir TestDir) { return nil } - target, err := fs.Readlink(path) + target, err := os.Readlink(path) if err != nil { return err } diff --git a/internal/archiver/testing_test.go b/internal/archiver/testing_test.go index bb4b63a82..a217abe25 100644 --- a/internal/archiver/testing_test.go +++ b/internal/archiver/testing_test.go @@ -54,7 +54,7 @@ func (t *MockT) Errorf(msg string, args ...interface{}) { func createFilesAt(t testing.TB, targetdir string, files map[string]interface{}) { for name, item := range files { target := filepath.Join(targetdir, filepath.FromSlash(name)) - err := fs.MkdirAll(filepath.Dir(target), 0700) + err := os.MkdirAll(filepath.Dir(target), 0700) if err != nil { t.Fatal(err) } @@ -66,7 +66,7 @@ func createFilesAt(t testing.TB, targetdir string, files map[string]interface{}) t.Fatal(err) } case TestSymlink: - err := fs.Symlink(filepath.FromSlash(it.Target), target) + err := os.Symlink(filepath.FromSlash(it.Target), target) if err != nil { t.Fatal(err) } @@ -105,7 +105,7 @@ func TestTestCreateFiles(t *testing.T) { t.Run("", func(t *testing.T) { tempdir := filepath.Join(tempdir, fmt.Sprintf("test-%d", i)) - err := fs.MkdirAll(tempdir, 0700) + err := os.MkdirAll(tempdir, 0700) if err != nil { t.Fatal(err) } @@ -114,7 +114,7 @@ func TestTestCreateFiles(t *testing.T) { for name, item := range test.files { targetPath := filepath.Join(tempdir, filepath.FromSlash(name)) - fi, err := fs.Lstat(targetPath) + fi, err := os.Lstat(targetPath) if err != nil { t.Error(err) continue @@ -142,7 +142,7 @@ func TestTestCreateFiles(t *testing.T) { continue } - target, err := fs.Readlink(targetPath) + target, err := os.Readlink(targetPath) if err != nil { t.Error(err) continue @@ -455,7 +455,7 @@ func TestTestEnsureSnapshot(t *testing.T) { tempdir := rtest.TempDir(t) targetDir := filepath.Join(tempdir, "target") - err := fs.Mkdir(targetDir, 0700) + err := os.Mkdir(targetDir, 0700) if err != nil { t.Fatal(err) } diff --git a/internal/dump/tar_test.go b/internal/dump/tar_test.go index 0c97eae35..cb3cb08c4 100644 --- a/internal/dump/tar_test.go +++ b/internal/dump/tar_test.go @@ -13,7 +13,6 @@ import ( "testing" "time" - "github.com/restic/restic/internal/fs" "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" ) @@ -83,7 +82,7 @@ func checkTar(t *testing.T, testDir string, srcTar *bytes.Buffer) error { return fmt.Errorf("foldernames must end with separator got %v", hdr.Name) } case tar.TypeSymlink: - target, err := fs.Readlink(matchPath) + target, err := os.Readlink(matchPath) if err != nil { return err } diff --git a/internal/dump/zip_test.go b/internal/dump/zip_test.go index 0c304d3da..6f5f60f54 100644 --- a/internal/dump/zip_test.go +++ b/internal/dump/zip_test.go @@ -9,8 +9,6 @@ import ( "strings" "testing" "time" - - "github.com/restic/restic/internal/fs" ) func TestWriteZip(t *testing.T) { @@ -91,7 +89,7 @@ func checkZip(t *testing.T, testDir string, srcZip *bytes.Buffer) error { return fmt.Errorf("foldernames must end with separator got %v", f.Name) } case f.Mode()&os.ModeSymlink != 0: - target, err := fs.Readlink(matchPath) + target, err := os.Readlink(matchPath) if err != nil { return err } diff --git a/internal/fs/file.go b/internal/fs/file.go index c1ebae081..8d60ed159 100644 --- a/internal/fs/file.go +++ b/internal/fs/file.go @@ -3,15 +3,8 @@ package fs import ( "fmt" "os" - "time" ) -// Mkdir creates a new directory with the specified name and permission bits. -// If there is an error, it will be of type *PathError. -func Mkdir(name string, perm os.FileMode) error { - return os.Mkdir(fixpath(name), perm) -} - // MkdirAll creates a directory named path, along with any necessary parents, // and returns nil, or else returns an error. The permission bits perm are used // for all directories that MkdirAll creates. If path is already a directory, @@ -20,12 +13,6 @@ func MkdirAll(path string, perm os.FileMode) error { return os.MkdirAll(fixpath(path), perm) } -// Readlink returns the destination of the named symbolic link. -// If there is an error, it will be of type *PathError. -func Readlink(name string) (string, error) { - return os.Readlink(fixpath(name)) -} - // Remove removes the named file or directory. // If there is an error, it will be of type *PathError. func Remove(name string) error { @@ -40,20 +27,6 @@ func RemoveAll(path string) error { return os.RemoveAll(fixpath(path)) } -// Rename renames (moves) oldpath to newpath. -// If newpath already exists, Rename replaces it. -// OS-specific restrictions may apply when oldpath and newpath are in different directories. -// If there is an error, it will be of type *LinkError. -func Rename(oldpath, newpath string) error { - return os.Rename(fixpath(oldpath), fixpath(newpath)) -} - -// Symlink creates newname as a symbolic link to oldname. -// If there is an error, it will be of type *LinkError. -func Symlink(oldname, newname string) error { - return os.Symlink(oldname, fixpath(newname)) -} - // Link creates newname as a hard link to oldname. // If there is an error, it will be of type *LinkError. func Link(oldname, newname string) error { @@ -68,11 +41,6 @@ func Lstat(name string) (os.FileInfo, error) { return os.Lstat(fixpath(name)) } -// Open opens a file for reading. -func Open(name string) (File, error) { - return os.Open(fixpath(name)) -} - // OpenFile is the generalized open call; most users will use Open // or Create instead. It opens the named file with specified flag // (O_RDONLY etc.) and perm, (0666 etc.) if applicable. If successful, diff --git a/internal/fs/node.go b/internal/fs/node.go index e29011cc2..3945c426e 100644 --- a/internal/fs/node.go +++ b/internal/fs/node.go @@ -80,7 +80,7 @@ func nodeFillExtra(node *restic.Node, path string, fi os.FileInfo, ignoreXattrLi case restic.NodeTypeDir: case restic.NodeTypeSymlink: var err error - node.LinkTarget, err = Readlink(path) + node.LinkTarget, err = os.Readlink(fixpath(path)) node.Links = uint64(stat.nlink()) if err != nil { return errors.WithStack(err) @@ -212,7 +212,7 @@ func NodeCreateAt(node *restic.Node, path string) error { } func nodeCreateDirAt(node *restic.Node, path string) error { - err := Mkdir(path, node.Mode) + err := os.Mkdir(fixpath(path), node.Mode) if err != nil && !os.IsExist(err) { return errors.WithStack(err) } @@ -234,7 +234,7 @@ func nodeCreateFileAt(path string) error { } func nodeCreateSymlinkAt(node *restic.Node, path string) error { - if err := Symlink(node.LinkTarget, path); err != nil { + if err := os.Symlink(node.LinkTarget, fixpath(path)); err != nil { return errors.WithStack(err) } diff --git a/internal/fs/node_linux.go b/internal/fs/node_linux.go index 1cb4ee1ae..4cb9bdeaa 100644 --- a/internal/fs/node_linux.go +++ b/internal/fs/node_linux.go @@ -1,6 +1,7 @@ package fs import ( + "os" "path/filepath" "syscall" @@ -10,7 +11,7 @@ import ( ) func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error { - dir, err := Open(filepath.Dir(path)) + dir, err := os.Open(fixpath(filepath.Dir(path))) if err != nil { return errors.WithStack(err) } diff --git a/internal/fs/node_windows.go b/internal/fs/node_windows.go index f2a2001e9..d904c37fe 100644 --- a/internal/fs/node_windows.go +++ b/internal/fs/node_windows.go @@ -58,7 +58,7 @@ func lchown(_ string, _ int, _ int) (err error) { // restoreSymlinkTimestamps restores timestamps for symlinks func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error { // tweaked version of UtimesNano from go/src/syscall/syscall_windows.go - pathp, e := syscall.UTF16PtrFromString(path) + pathp, e := syscall.UTF16PtrFromString(fixpath(path)) if e != nil { return e } diff --git a/internal/fs/setflags_linux_test.go b/internal/fs/setflags_linux_test.go index b561a1009..8fe14a5a6 100644 --- a/internal/fs/setflags_linux_test.go +++ b/internal/fs/setflags_linux_test.go @@ -19,7 +19,7 @@ func TestNoatime(t *testing.T) { defer func() { _ = f.Close() - err = Remove(f.Name()) + err = os.Remove(f.Name()) if err != nil { t.Fatal(err) } diff --git a/internal/restorer/restorer_test.go b/internal/restorer/restorer_test.go index 8844435ca..e0306ce01 100644 --- a/internal/restorer/restorer_test.go +++ b/internal/restorer/restorer_test.go @@ -1223,7 +1223,7 @@ func TestRestorerOverwriteSpecial(t *testing.T) { } } for filename, target := range links { - link, err := fs.Readlink(filepath.Join(tempdir, filepath.FromSlash(filename))) + link, err := os.Readlink(filepath.Join(tempdir, filepath.FromSlash(filename))) rtest.OK(t, err) rtest.Equals(t, link, target, "wrong symlink target") } From cf051e777a7c2bd443171a9e8c35ce55161fdcab Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 21 Jul 2024 16:47:13 +0200 Subject: [PATCH 05/12] fs: remove Readdir method from File interface --- internal/fs/fs_reader.go | 11 ---- internal/fs/fs_reader_test.go | 95 ----------------------------------- internal/fs/interface.go | 1 - 3 files changed, 107 deletions(-) diff --git a/internal/fs/fs_reader.go b/internal/fs/fs_reader.go index 84a79168e..9dd5ff6eb 100644 --- a/internal/fs/fs_reader.go +++ b/internal/fs/fs_reader.go @@ -237,10 +237,6 @@ func (f fakeFile) Readdirnames(_ int) ([]string, error) { return nil, pathError("readdirnames", f.name, os.ErrInvalid) } -func (f fakeFile) Readdir(_ int) ([]os.FileInfo, error) { - return nil, pathError("readdir", f.name, os.ErrInvalid) -} - func (f fakeFile) Seek(int64, int) (int64, error) { return 0, pathError("seek", f.name, os.ErrInvalid) } @@ -279,13 +275,6 @@ func (d fakeDir) Readdirnames(n int) ([]string, error) { return names, nil } -func (d fakeDir) Readdir(n int) ([]os.FileInfo, error) { - if n > 0 { - return nil, pathError("readdir", d.name, errors.New("not implemented")) - } - return d.entries, nil -} - // fakeFileInfo implements the bare minimum of os.FileInfo. type fakeFileInfo struct { name string diff --git a/internal/fs/fs_reader_test.go b/internal/fs/fs_reader_test.go index 9fa67b5ac..442912fe3 100644 --- a/internal/fs/fs_reader_test.go +++ b/internal/fs/fs_reader_test.go @@ -60,77 +60,6 @@ func verifyDirectoryContents(t testing.TB, fs FS, dir string, want []string) { } } -type fiSlice []os.FileInfo - -func (s fiSlice) Len() int { - return len(s) -} - -func (s fiSlice) Less(i, j int) bool { - return s[i].Name() < s[j].Name() -} - -func (s fiSlice) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} - -func verifyDirectoryContentsFI(t testing.TB, fs FS, dir string, want []os.FileInfo) { - f, err := fs.OpenFile(dir, os.O_RDONLY, 0) - if err != nil { - t.Fatal(err) - } - - entries, err := f.Readdir(-1) - if err != nil { - t.Fatal(err) - } - - err = f.Close() - if err != nil { - t.Fatal(err) - } - - sort.Sort(fiSlice(want)) - sort.Sort(fiSlice(entries)) - - if len(want) != len(entries) { - t.Errorf("wrong number of entries returned, want %d, got %d", len(want), len(entries)) - } - max := len(want) - if len(entries) < max { - max = len(entries) - } - - for i := 0; i < max; i++ { - fi1 := want[i] - fi2 := entries[i] - - if fi1.Name() != fi2.Name() { - t.Errorf("entry %d: wrong value for Name: want %q, got %q", i, fi1.Name(), fi2.Name()) - } - - if fi1.IsDir() != fi2.IsDir() { - t.Errorf("entry %d: wrong value for IsDir: want %v, got %v", i, fi1.IsDir(), fi2.IsDir()) - } - - if fi1.Mode() != fi2.Mode() { - t.Errorf("entry %d: wrong value for Mode: want %v, got %v", i, fi1.Mode(), fi2.Mode()) - } - - if fi1.ModTime() != fi2.ModTime() { - t.Errorf("entry %d: wrong value for ModTime: want %v, got %v", i, fi1.ModTime(), fi2.ModTime()) - } - - if fi1.Size() != fi2.Size() { - t.Errorf("entry %d: wrong value for Size: want %v, got %v", i, fi1.Size(), fi2.Size()) - } - - if fi1.Sys() != fi2.Sys() { - t.Errorf("entry %d: wrong value for Sys: want %v, got %v", i, fi1.Sys(), fi2.Sys()) - } - } -} - func checkFileInfo(t testing.TB, fi os.FileInfo, filename string, modtime time.Time, mode os.FileMode, isdir bool) { if fi.IsDir() != isdir { t.Errorf("IsDir returned %t, want %t", fi.IsDir(), isdir) @@ -174,30 +103,6 @@ func TestFSReader(t *testing.T) { verifyDirectoryContents(t, fs, ".", []string{filename}) }, }, - { - name: "Readdir-slash", - f: func(t *testing.T, fs FS) { - fi := fakeFileInfo{ - mode: 0644, - modtime: now, - name: filename, - size: int64(len(data)), - } - verifyDirectoryContentsFI(t, fs, "/", []os.FileInfo{fi}) - }, - }, - { - name: "Readdir-current", - f: func(t *testing.T, fs FS) { - fi := fakeFileInfo{ - mode: 0644, - modtime: now, - name: filename, - size: int64(len(data)), - } - verifyDirectoryContentsFI(t, fs, ".", []os.FileInfo{fi}) - }, - }, { name: "file/OpenFile", f: func(t *testing.T, fs FS) { diff --git a/internal/fs/interface.go b/internal/fs/interface.go index bc6aab44a..7707abd1d 100644 --- a/internal/fs/interface.go +++ b/internal/fs/interface.go @@ -31,7 +31,6 @@ type File interface { Fd() uintptr Readdirnames(n int) ([]string, error) - Readdir(int) ([]os.FileInfo, error) Seek(int64, int) (int64, error) Stat() (os.FileInfo, error) Name() string From 6d3a5260d358cd89e8727fd279a94dd8d553ff35 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 21 Jul 2024 16:00:47 +0200 Subject: [PATCH 06/12] fs: unexport a several windows functions --- internal/fs/ea_windows.go | 36 ++++++++++----------- internal/fs/ea_windows_test.go | 32 +++++++++---------- internal/fs/file_unix.go | 4 +-- internal/fs/file_windows.go | 16 +++++----- internal/fs/fs_local_vss.go | 4 +-- internal/fs/fs_local_vss_test.go | 7 ++-- internal/fs/node.go | 2 +- internal/fs/node_windows.go | 36 ++++++++++----------- internal/fs/node_windows_test.go | 18 +++++------ internal/fs/sd_windows.go | 44 +++++++++++++------------- internal/fs/sd_windows_test.go | 10 +++--- internal/fs/sd_windows_test_helpers.go | 18 +++++------ internal/fs/vss.go | 4 +-- internal/fs/vss_windows.go | 5 +-- 14 files changed, 119 insertions(+), 117 deletions(-) diff --git a/internal/fs/ea_windows.go b/internal/fs/ea_windows.go index bf7b02fd4..6bfe20209 100644 --- a/internal/fs/ea_windows.go +++ b/internal/fs/ea_windows.go @@ -56,14 +56,14 @@ var ( errEaValueTooLarge = errors.New("extended attribute value too large") ) -// ExtendedAttribute represents a single Windows EA. -type ExtendedAttribute struct { +// extendedAttribute represents a single Windows EA. +type extendedAttribute struct { Name string Value []byte Flags uint8 } -func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) { +func parseEa(b []byte) (ea extendedAttribute, nb []byte, err error) { var info fileFullEaInformation err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info) if err != nil { @@ -90,9 +90,9 @@ func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) { return ea, nb, err } -// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION +// decodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION // buffer retrieved from BackupRead, ZwQueryEaFile, etc. -func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) { +func decodeExtendedAttributes(b []byte) (eas []extendedAttribute, err error) { for len(b) != 0 { ea, nb, err := parseEa(b) if err != nil { @@ -105,7 +105,7 @@ func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) { return eas, err } -func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error { +func writeEa(buf *bytes.Buffer, ea *extendedAttribute, last bool) error { if int(uint8(len(ea.Name))) != len(ea.Name) { return errEaNameTooLarge } @@ -153,9 +153,9 @@ func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error { return nil } -// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION +// encodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION // buffer for use with BackupWrite, ZwSetEaFile, etc. -func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) { +func encodeExtendedAttributes(eas []extendedAttribute) ([]byte, error) { var buf bytes.Buffer for i := range eas { last := false @@ -217,11 +217,11 @@ const ( STATUS_NO_EAS_ON_FILE = -1073741742 ) -// GetFileEA retrieves the extended attributes for the file represented by `handle`. The +// fgetEA retrieves the extended attributes for the file represented by `handle`. The // `handle` must have been opened with file access flag FILE_READ_EA (0x8). // The extended file attribute names in windows are case-insensitive and when fetching // the attributes the names are generally returned in UPPER case. -func GetFileEA(handle windows.Handle) ([]ExtendedAttribute, error) { +func fgetEA(handle windows.Handle) ([]extendedAttribute, error) { // default buffer size to start with bufLen := 1024 buf := make([]byte, bufLen) @@ -246,13 +246,13 @@ func GetFileEA(handle windows.Handle) ([]ExtendedAttribute, error) { } break } - return DecodeExtendedAttributes(buf) + return decodeExtendedAttributes(buf) } -// SetFileEA sets the extended attributes for the file represented by `handle`. The +// fsetEA sets the extended attributes for the file represented by `handle`. The // handle must have been opened with the file access flag FILE_WRITE_EA(0x10). -func SetFileEA(handle windows.Handle, attrs []ExtendedAttribute) error { - encodedEA, err := EncodeExtendedAttributes(attrs) +func fsetEA(handle windows.Handle, attrs []extendedAttribute) error { + encodedEA, err := encodeExtendedAttributes(attrs) if err != nil { return fmt.Errorf("failed to encoded extended attributes: %w", err) } @@ -285,8 +285,8 @@ func setFileEA(handle windows.Handle, iosb *ioStatusBlock, buf *uint8, bufLen ui return } -// PathSupportsExtendedAttributes returns true if the path supports extended attributes. -func PathSupportsExtendedAttributes(path string) (supported bool, err error) { +// pathSupportsExtendedAttributes returns true if the path supports extended attributes. +func pathSupportsExtendedAttributes(path string) (supported bool, err error) { var fileSystemFlags uint32 utf16Path, err := windows.UTF16PtrFromString(path) if err != nil { @@ -300,8 +300,8 @@ func PathSupportsExtendedAttributes(path string) (supported bool, err error) { return supported, nil } -// GetVolumePathName returns the volume path name for the given path. -func GetVolumePathName(path string) (volumeName string, err error) { +// getVolumePathName returns the volume path name for the given path. +func getVolumePathName(path string) (volumeName string, err error) { utf16Path, err := windows.UTF16PtrFromString(path) if err != nil { return "", err diff --git a/internal/fs/ea_windows_test.go b/internal/fs/ea_windows_test.go index e474a3735..64bc7f7b6 100644 --- a/internal/fs/ea_windows_test.go +++ b/internal/fs/ea_windows_test.go @@ -46,7 +46,7 @@ import ( // under MIT license. var ( - testEas = []ExtendedAttribute{ + testEas = []extendedAttribute{ {Name: "foo", Value: []byte("bar")}, {Name: "fizz", Value: []byte("buzz")}, } @@ -58,14 +58,14 @@ var ( ) func TestRoundTripEas(t *testing.T) { - b, err := EncodeExtendedAttributes(testEas) + b, err := encodeExtendedAttributes(testEas) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(testEasEncoded, b) { t.Fatalf("Encoded mismatch %v %v", testEasEncoded, b) } - eas, err := DecodeExtendedAttributes(b) + eas, err := decodeExtendedAttributes(b) if err != nil { t.Fatal(err) } @@ -75,7 +75,7 @@ func TestRoundTripEas(t *testing.T) { } func TestEasDontNeedPaddingAtEnd(t *testing.T) { - eas, err := DecodeExtendedAttributes(testEasNotPadded) + eas, err := decodeExtendedAttributes(testEasNotPadded) if err != nil { t.Fatal(err) } @@ -85,21 +85,21 @@ func TestEasDontNeedPaddingAtEnd(t *testing.T) { } func TestTruncatedEasFailCorrectly(t *testing.T) { - _, err := DecodeExtendedAttributes(testEasTruncated) + _, err := decodeExtendedAttributes(testEasTruncated) if err == nil { t.Fatal("expected error") } } func TestNilEasEncodeAndDecodeAsNil(t *testing.T) { - b, err := EncodeExtendedAttributes(nil) + b, err := encodeExtendedAttributes(nil) if err != nil { t.Fatal(err) } if len(b) != 0 { t.Fatal("expected empty") } - eas, err := DecodeExtendedAttributes(nil) + eas, err := decodeExtendedAttributes(nil) if err != nil { t.Fatal(err) } @@ -178,8 +178,8 @@ func setupTestFolder(t *testing.T) string { return testfolderPath } -func generateTestEAs(t *testing.T, nAttrs int, path string) []ExtendedAttribute { - testEAs := make([]ExtendedAttribute, nAttrs) +func generateTestEAs(t *testing.T, nAttrs int, path string) []extendedAttribute { + testEAs := make([]extendedAttribute, nAttrs) for i := 0; i < nAttrs; i++ { testEAs[i].Name = fmt.Sprintf("TESTEA%d", i+1) testEAs[i].Value = make([]byte, getRandomInt()) @@ -231,12 +231,12 @@ func cleanupTestFile(t *testing.T, path string) { } } -func testSetGetEA(t *testing.T, path string, handle windows.Handle, testEAs []ExtendedAttribute) { - if err := SetFileEA(handle, testEAs); err != nil { +func testSetGetEA(t *testing.T, path string, handle windows.Handle, testEAs []extendedAttribute) { + if err := fsetEA(handle, testEAs); err != nil { t.Fatalf("set EA for path %s failed: %s", path, err) } - readEAs, err := GetFileEA(handle) + readEAs, err := fgetEA(handle) if err != nil { t.Fatalf("get EA for path %s failed: %s", path, err) } @@ -262,7 +262,7 @@ func TestPathSupportsExtendedAttributes(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - supported, err := PathSupportsExtendedAttributes(tc.path) + supported, err := pathSupportsExtendedAttributes(tc.path) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -273,7 +273,7 @@ func TestPathSupportsExtendedAttributes(t *testing.T) { } // Test with an invalid path - _, err := PathSupportsExtendedAttributes("Z:\\NonExistentPath-UAS664da5s4dyu56das45f5as") + _, err := pathSupportsExtendedAttributes("Z:\\NonExistentPath-UAS664da5s4dyu56das45f5as") if err == nil { t.Error("Expected an error for non-existent path, but got nil") } @@ -305,7 +305,7 @@ func TestGetVolumePathName(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - volumeName, err := GetVolumePathName(tc.path) + volumeName, err := getVolumePathName(tc.path) if err != nil { t.Fatalf("Unexpected error: %v", err) } @@ -316,7 +316,7 @@ func TestGetVolumePathName(t *testing.T) { } // Test with an invalid path - _, err := GetVolumePathName("Z:\\NonExistentPath") + _, err := getVolumePathName("Z:\\NonExistentPath") if err == nil { t.Error("Expected an error for non-existent path, but got nil") } diff --git a/internal/fs/file_unix.go b/internal/fs/file_unix.go index b562d15b1..4e7765c30 100644 --- a/internal/fs/file_unix.go +++ b/internal/fs/file_unix.go @@ -37,8 +37,8 @@ func isNotSupported(err error) bool { return false } -// Chmod changes the mode of the named file to mode. -func Chmod(name string, mode os.FileMode) error { +// chmod changes the mode of the named file to mode. +func chmod(name string, mode os.FileMode) error { err := os.Chmod(fixpath(name), mode) // ignore the error if the FS does not support setting this mode (e.g. CIFS with gvfs on Linux) diff --git a/internal/fs/file_windows.go b/internal/fs/file_windows.go index 7a6d79f25..3d011f719 100644 --- a/internal/fs/file_windows.go +++ b/internal/fs/file_windows.go @@ -75,17 +75,17 @@ func TempFile(dir, prefix string) (f *os.File, err error) { } // Chmod changes the mode of the named file to mode. -func Chmod(name string, mode os.FileMode) error { +func chmod(name string, mode os.FileMode) error { return os.Chmod(fixpath(name), mode) } -// ClearSystem removes the system attribute from the file. -func ClearSystem(path string) error { - return ClearAttribute(path, windows.FILE_ATTRIBUTE_SYSTEM) +// clearSystem removes the system attribute from the file. +func clearSystem(path string) error { + return clearAttribute(path, windows.FILE_ATTRIBUTE_SYSTEM) } -// ClearAttribute removes the specified attribute from the file. -func ClearAttribute(path string, attribute uint32) error { +// clearAttribute removes the specified attribute from the file. +func clearAttribute(path string, attribute uint32) error { ptr, err := windows.UTF16PtrFromString(fixpath(path)) if err != nil { return err @@ -105,8 +105,8 @@ func ClearAttribute(path string, attribute uint32) error { return nil } -// OpenHandleForEA return a file handle for file or dir for setting/getting EAs -func OpenHandleForEA(nodeType restic.NodeType, path string, writeAccess bool) (handle windows.Handle, err error) { +// openHandleForEA return a file handle for file or dir for setting/getting EAs +func openHandleForEA(nodeType restic.NodeType, path string, writeAccess bool) (handle windows.Handle, err error) { path = fixpath(path) fileAccess := windows.FILE_READ_EA if writeAccess { diff --git a/internal/fs/fs_local_vss.go b/internal/fs/fs_local_vss.go index 46b40d013..27abcf9ea 100644 --- a/internal/fs/fs_local_vss.go +++ b/internal/fs/fs_local_vss.go @@ -79,7 +79,7 @@ func parseMountPoints(list string, msgError ErrorHandler) (volumes map[string]st return } for _, s := range strings.Split(list, ";") { - if v, err := GetVolumeNameForVolumeMountPoint(s); err != nil { + if v, err := getVolumeNameForVolumeMountPoint(s); err != nil { msgError(s, errors.Errorf("failed to parse vss.exclude-volumes [%s]: %s", s, err)) } else { if volumes == nil { @@ -146,7 +146,7 @@ func (fs *LocalVss) isMountPointIncluded(mountPoint string) bool { return true } - volume, err := GetVolumeNameForVolumeMountPoint(mountPoint) + volume, err := getVolumeNameForVolumeMountPoint(mountPoint) if err != nil { fs.msgError(mountPoint, errors.Errorf("failed to get volume from mount point [%s]: %s", mountPoint, err)) return true diff --git a/internal/fs/fs_local_vss_test.go b/internal/fs/fs_local_vss_test.go index 60262c873..a59882381 100644 --- a/internal/fs/fs_local_vss_test.go +++ b/internal/fs/fs_local_vss_test.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package fs @@ -120,10 +121,10 @@ func TestVSSConfig(t *testing.T) { func TestParseMountPoints(t *testing.T) { volumeMatch := regexp.MustCompile(`^\\\\\?\\Volume\{[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}\}\\$`) - // It's not a good idea to test functions based on GetVolumeNameForVolumeMountPoint by calling - // GetVolumeNameForVolumeMountPoint itself, but we have restricted test environment: + // It's not a good idea to test functions based on getVolumeNameForVolumeMountPoint by calling + // getVolumeNameForVolumeMountPoint itself, but we have restricted test environment: // cannot manage volumes and can only be sure that the mount point C:\ exists - sysVolume, err := GetVolumeNameForVolumeMountPoint("C:") + sysVolume, err := getVolumeNameForVolumeMountPoint("C:") if err != nil { t.Fatal(err) } diff --git a/internal/fs/node.go b/internal/fs/node.go index 3945c426e..2dfe54172 100644 --- a/internal/fs/node.go +++ b/internal/fs/node.go @@ -306,7 +306,7 @@ func nodeRestoreMetadata(node *restic.Node, path string, warn func(msg string)) // calling Chmod below will no longer allow any modifications to be made on the file and the // calls above would fail. if node.Type != restic.NodeTypeSymlink { - if err := Chmod(path, node.Mode); err != nil { + if err := chmod(path, node.Mode); err != nil { if firsterr == nil { firsterr = errors.WithStack(err) } diff --git a/internal/fs/node_windows.go b/internal/fs/node_windows.go index d904c37fe..d529e3503 100644 --- a/internal/fs/node_windows.go +++ b/internal/fs/node_windows.go @@ -85,9 +85,9 @@ func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error func nodeRestoreExtendedAttributes(node *restic.Node, path string) (err error) { count := len(node.ExtendedAttributes) if count > 0 { - eas := make([]ExtendedAttribute, count) + eas := make([]extendedAttribute, count) for i, attr := range node.ExtendedAttributes { - eas[i] = ExtendedAttribute{Name: attr.Name, Value: attr.Value} + eas[i] = extendedAttribute{Name: attr.Name, Value: attr.Value} } if errExt := restoreExtendedAttributes(node.Type, path, eas); errExt != nil { return errExt @@ -99,7 +99,7 @@ func nodeRestoreExtendedAttributes(node *restic.Node, path string) (err error) { // fill extended attributes in the node. This also includes the Generic attributes for windows. func nodeFillExtendedAttributes(node *restic.Node, path string, _ bool) (err error) { var fileHandle windows.Handle - if fileHandle, err = OpenHandleForEA(node.Type, path, false); fileHandle == 0 { + if fileHandle, err = openHandleForEA(node.Type, path, false); fileHandle == 0 { return nil } if err != nil { @@ -107,8 +107,8 @@ func nodeFillExtendedAttributes(node *restic.Node, path string, _ bool) (err err } defer closeFileHandle(fileHandle, path) // Replaced inline defer with named function call //Get the windows Extended Attributes using the file handle - var extAtts []ExtendedAttribute - extAtts, err = GetFileEA(fileHandle) + var extAtts []extendedAttribute + extAtts, err = fgetEA(fileHandle) debug.Log("fillExtendedAttributes(%v) %v", path, extAtts) if err != nil { return errors.Errorf("get EA failed for path %v, with: %v", path, err) @@ -139,9 +139,9 @@ func closeFileHandle(fileHandle windows.Handle, path string) { // restoreExtendedAttributes handles restore of the Windows Extended Attributes to the specified path. // The Windows API requires setting of all the Extended Attributes in one call. -func restoreExtendedAttributes(nodeType restic.NodeType, path string, eas []ExtendedAttribute) (err error) { +func restoreExtendedAttributes(nodeType restic.NodeType, path string, eas []extendedAttribute) (err error) { var fileHandle windows.Handle - if fileHandle, err = OpenHandleForEA(nodeType, path, true); fileHandle == 0 { + if fileHandle, err = openHandleForEA(nodeType, path, true); fileHandle == 0 { return nil } if err != nil { @@ -150,7 +150,7 @@ func restoreExtendedAttributes(nodeType restic.NodeType, path string, eas []Exte defer closeFileHandle(fileHandle, path) // Replaced inline defer with named function call // clear old unexpected xattrs by setting them to an empty value - oldEAs, err := GetFileEA(fileHandle) + oldEAs, err := fgetEA(fileHandle) if err != nil { return err } @@ -165,11 +165,11 @@ func restoreExtendedAttributes(nodeType restic.NodeType, path string, eas []Exte } if !found { - eas = append(eas, ExtendedAttribute{Name: oldEA.Name, Value: nil}) + eas = append(eas, extendedAttribute{Name: oldEA.Name, Value: nil}) } } - if err = SetFileEA(fileHandle, eas); err != nil { + if err = fsetEA(fileHandle, eas); err != nil { return errors.Errorf("set EA failed for path %v, with: %v", path, err) } return nil @@ -230,7 +230,7 @@ func nodeRestoreGenericAttributes(node *restic.Node, path string, warn func(msg } } if windowsAttributes.SecurityDescriptor != nil { - if err := SetSecurityDescriptor(path, windowsAttributes.SecurityDescriptor); err != nil { + if err := setSecurityDescriptor(path, windowsAttributes.SecurityDescriptor); err != nil { errs = append(errs, fmt.Errorf("error restoring security descriptor for: %s : %v", path, err)) } } @@ -296,7 +296,7 @@ func fixEncryptionAttribute(path string, attrs *uint32, pathPointer *uint16) (er if err != nil { return fmt.Errorf("failed to encrypt file: failed to reset permissions: %s : %v", path, err) } - err = ClearSystem(path) + err = clearSystem(path) if err != nil { return fmt.Errorf("failed to encrypt file: failed to clear system flag: %s : %v", path, err) } @@ -324,7 +324,7 @@ func fixEncryptionAttribute(path string, attrs *uint32, pathPointer *uint16) (er if err != nil { return fmt.Errorf("failed to encrypt file: failed to reset permissions: %s : %v", path, err) } - err = ClearSystem(path) + err = clearSystem(path) if err != nil { return fmt.Errorf("failed to decrypt file: failed to clear system flag: %s : %v", path, err) } @@ -392,7 +392,7 @@ func nodeFillGenericAttributes(node *restic.Node, path string, fi os.FileInfo, s if err != nil { return false, err } - if sd, err = GetSecurityDescriptor(path); err != nil { + if sd, err = getSecurityDescriptor(path); err != nil { return allowExtended, err } } @@ -422,7 +422,7 @@ func checkAndStoreEASupport(path string) (isEASupportedVolume bool, err error) { return eaSupportedValue.(bool), nil } // If not found, check if EA is supported with manually prepared volume name - isEASupportedVolume, err = PathSupportsExtendedAttributes(volumeName + `\`) + isEASupportedVolume, err = pathSupportsExtendedAttributes(volumeName + `\`) // If the prepared volume name is not valid, we will fetch the actual volume name next. if err != nil && !errors.Is(err, windows.DNS_ERROR_INVALID_NAME) { debug.Log("Error checking if extended attributes are supported for prepared volume name %s: %v", volumeName, err) @@ -431,8 +431,8 @@ func checkAndStoreEASupport(path string) (isEASupportedVolume bool, err error) { return false, nil } } - // If an entry is not found, get the actual volume name using the GetVolumePathName function - volumeNameActual, err := GetVolumePathName(path) + // If an entry is not found, get the actual volume name + volumeNameActual, err := getVolumePathName(path) if err != nil { debug.Log("Error getting actual volume name %s for path %s: %v", volumeName, path, err) // There can be multiple errors like path does not exist, bad network path, etc. @@ -447,7 +447,7 @@ func checkAndStoreEASupport(path string) (isEASupportedVolume bool, err error) { return eaSupportedValue.(bool), nil } // If the actual volume name is different and is not in the map, again check if the new volume supports extended attributes with the actual volume name - isEASupportedVolume, err = PathSupportsExtendedAttributes(volumeNameActual + `\`) + isEASupportedVolume, err = pathSupportsExtendedAttributes(volumeNameActual + `\`) // Debug log for cases where the prepared volume name is not valid if err != nil { debug.Log("Error checking if extended attributes are supported for actual volume name %s: %v", volumeNameActual, err) diff --git a/internal/fs/node_windows_test.go b/internal/fs/node_windows_test.go index 729ce2adb..618364879 100644 --- a/internal/fs/node_windows_test.go +++ b/internal/fs/node_windows_test.go @@ -23,10 +23,10 @@ import ( func TestRestoreSecurityDescriptors(t *testing.T) { t.Parallel() tempDir := t.TempDir() - for i, sd := range TestFileSDs { + for i, sd := range testFileSDs { testRestoreSecurityDescriptor(t, sd, tempDir, restic.NodeTypeFile, fmt.Sprintf("testfile%d", i)) } - for i, sd := range TestDirSDs { + for i, sd := range testDirSDs { testRestoreSecurityDescriptor(t, sd, tempDir, restic.NodeTypeDir, fmt.Sprintf("testdir%d", i)) } } @@ -47,13 +47,13 @@ func testRestoreSecurityDescriptor(t *testing.T, sd string, tempDir string, file sdByteFromRestoredNode := getWindowsAttr(t, testPath, node).SecurityDescriptor // Get the security descriptor for the test path after the restore. - sdBytesFromRestoredPath, err := GetSecurityDescriptor(testPath) + sdBytesFromRestoredPath, err := getSecurityDescriptor(testPath) test.OK(t, errors.Wrapf(err, "Error while getting the security descriptor for: %s", testPath)) // Compare the input SD and the SD got from the restored file. - CompareSecurityDescriptors(t, testPath, sdInputBytes, *sdBytesFromRestoredPath) + compareSecurityDescriptors(t, testPath, sdInputBytes, *sdBytesFromRestoredPath) // Compare the SD got from node constructed from the restored file info and the SD got directly from the restored file. - CompareSecurityDescriptors(t, testPath, *sdByteFromRestoredNode, *sdBytesFromRestoredPath) + compareSecurityDescriptors(t, testPath, *sdByteFromRestoredNode, *sdBytesFromRestoredPath) } func getNode(name string, fileType restic.NodeType, genericAttributes map[restic.GenericAttributeType]json.RawMessage) restic.Node { @@ -312,12 +312,12 @@ func TestRestoreExtendedAttributes(t *testing.T) { test.OK(t, errors.Wrapf(err, "Error closing file for: %s", testPath)) }() - extAttr, err := GetFileEA(handle) + extAttr, err := fgetEA(handle) test.OK(t, errors.Wrapf(err, "Error getting extended attributes for: %s", testPath)) test.Equals(t, len(node.ExtendedAttributes), len(extAttr)) for _, expectedExtAttr := range node.ExtendedAttributes { - var foundExtAttr *ExtendedAttribute + var foundExtAttr *extendedAttribute for _, ea := range extAttr { if strings.EqualFold(ea.Name, expectedExtAttr.Name) { foundExtAttr = &ea @@ -491,13 +491,13 @@ func TestPrepareVolumeName(t *testing.T) { test.Equals(t, tc.expectedVolume, volume) if tc.isRealPath { - isEASupportedVolume, err := PathSupportsExtendedAttributes(volume + `\`) + isEASupportedVolume, err := pathSupportsExtendedAttributes(volume + `\`) // If the prepared volume name is not valid, we will next fetch the actual volume name. test.OK(t, err) test.Equals(t, tc.expectedEASupported, isEASupportedVolume) - actualVolume, err := GetVolumePathName(tc.path) + actualVolume, err := getVolumePathName(tc.path) test.OK(t, err) test.Equals(t, tc.expectedVolume, actualVolume) } diff --git a/internal/fs/sd_windows.go b/internal/fs/sd_windows.go index 0004f1809..c68926f24 100644 --- a/internal/fs/sd_windows.go +++ b/internal/fs/sd_windows.go @@ -19,14 +19,14 @@ var ( onceBackup sync.Once onceRestore sync.Once - // SeBackupPrivilege allows the application to bypass file and directory ACLs to back up files and directories. - SeBackupPrivilege = "SeBackupPrivilege" - // SeRestorePrivilege allows the application to bypass file and directory ACLs to restore files and directories. - SeRestorePrivilege = "SeRestorePrivilege" - // SeSecurityPrivilege allows read and write access to all SACLs. - SeSecurityPrivilege = "SeSecurityPrivilege" - // SeTakeOwnershipPrivilege allows the application to take ownership of files and directories, regardless of the permissions set on them. - SeTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege" + // seBackupPrivilege allows the application to bypass file and directory ACLs to back up files and directories. + seBackupPrivilege = "SeBackupPrivilege" + // seRestorePrivilege allows the application to bypass file and directory ACLs to restore files and directories. + seRestorePrivilege = "SeRestorePrivilege" + // seSecurityPrivilege allows read and write access to all SACLs. + seSecurityPrivilege = "SeSecurityPrivilege" + // seTakeOwnershipPrivilege allows the application to take ownership of files and directories, regardless of the permissions set on them. + seTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege" lowerPrivileges atomic.Bool ) @@ -40,10 +40,10 @@ var lowBackupSecurityFlags windows.SECURITY_INFORMATION = windows.OWNER_SECURITY // Flags for restore without admin permissions. If there are no admin permissions, only the DACL from the SD can be restored and owner and group will be set based on the current user. var lowRestoreSecurityFlags windows.SECURITY_INFORMATION = windows.DACL_SECURITY_INFORMATION | windows.ATTRIBUTE_SECURITY_INFORMATION | windows.PROTECTED_DACL_SECURITY_INFORMATION -// GetSecurityDescriptor takes the path of the file and returns the SecurityDescriptor for the file. +// getSecurityDescriptor takes the path of the file and returns the SecurityDescriptor for the file. // This needs admin permissions or SeBackupPrivilege for getting the full SD. // If there are no admin permissions, only the current user's owner, group and DACL will be got. -func GetSecurityDescriptor(filePath string) (securityDescriptor *[]byte, err error) { +func getSecurityDescriptor(filePath string) (securityDescriptor *[]byte, err error) { onceBackup.Do(enableBackupPrivilege) var sd *windows.SECURITY_DESCRIPTOR @@ -59,7 +59,7 @@ func GetSecurityDescriptor(filePath string) (securityDescriptor *[]byte, err err if !useLowerPrivileges && isHandlePrivilegeNotHeldError(err) { // If ERROR_PRIVILEGE_NOT_HELD is encountered, fallback to backups/restores using lower non-admin privileges. lowerPrivileges.Store(true) - return GetSecurityDescriptor(filePath) + return getSecurityDescriptor(filePath) } else if errors.Is(err, windows.ERROR_NOT_SUPPORTED) { return nil, nil } else { @@ -74,15 +74,15 @@ func GetSecurityDescriptor(filePath string) (securityDescriptor *[]byte, err err return &sdBytes, nil } -// SetSecurityDescriptor sets the SecurityDescriptor for the file at the specified path. +// setSecurityDescriptor sets the SecurityDescriptor for the file at the specified path. // This needs admin permissions or SeRestorePrivilege, SeSecurityPrivilege and SeTakeOwnershipPrivilege // for setting the full SD. // If there are no admin permissions/required privileges, only the DACL from the SD can be set and // owner and group will be set based on the current user. -func SetSecurityDescriptor(filePath string, securityDescriptor *[]byte) error { +func setSecurityDescriptor(filePath string, securityDescriptor *[]byte) error { onceRestore.Do(enableRestorePrivilege) // Set the security descriptor on the file - sd, err := SecurityDescriptorBytesToStruct(*securityDescriptor) + sd, err := securityDescriptorBytesToStruct(*securityDescriptor) if err != nil { return fmt.Errorf("error converting bytes to security descriptor: %w", err) } @@ -120,7 +120,7 @@ func SetSecurityDescriptor(filePath string, securityDescriptor *[]byte) error { if !useLowerPrivileges && isHandlePrivilegeNotHeldError(err) { // If ERROR_PRIVILEGE_NOT_HELD is encountered, fallback to backups/restores using lower non-admin privileges. lowerPrivileges.Store(true) - return SetSecurityDescriptor(filePath, securityDescriptor) + return setSecurityDescriptor(filePath, securityDescriptor) } else { return fmt.Errorf("set named security info failed with: %w", err) } @@ -150,7 +150,7 @@ func setNamedSecurityInfoLow(filePath string, dacl *windows.ACL) error { // enableBackupPrivilege enables privilege for backing up security descriptors func enableBackupPrivilege() { - err := enableProcessPrivileges([]string{SeBackupPrivilege}) + err := enableProcessPrivileges([]string{seBackupPrivilege}) if err != nil { debug.Log("error enabling backup privilege: %v", err) } @@ -158,7 +158,7 @@ func enableBackupPrivilege() { // enableBackupPrivilege enables privilege for restoring security descriptors func enableRestorePrivilege() { - err := enableProcessPrivileges([]string{SeRestorePrivilege, SeSecurityPrivilege, SeTakeOwnershipPrivilege}) + err := enableProcessPrivileges([]string{seRestorePrivilege, seSecurityPrivilege, seTakeOwnershipPrivilege}) if err != nil { debug.Log("error enabling restore/security privilege: %v", err) } @@ -174,9 +174,9 @@ func isHandlePrivilegeNotHeldError(err error) bool { return false } -// SecurityDescriptorBytesToStruct converts the security descriptor bytes representation +// securityDescriptorBytesToStruct converts the security descriptor bytes representation // into a pointer to windows SECURITY_DESCRIPTOR. -func SecurityDescriptorBytesToStruct(sd []byte) (*windows.SECURITY_DESCRIPTOR, error) { +func securityDescriptorBytesToStruct(sd []byte) (*windows.SECURITY_DESCRIPTOR, error) { if l := int(unsafe.Sizeof(windows.SECURITY_DESCRIPTOR{})); len(sd) < l { return nil, fmt.Errorf("securityDescriptor (%d) smaller than expected (%d): %w", len(sd), l, windows.ERROR_INCORRECT_SIZE) } @@ -245,13 +245,13 @@ var ( privNameMutex sync.Mutex ) -// PrivilegeError represents an error enabling privileges. -type PrivilegeError struct { +// privilegeError represents an error enabling privileges. +type privilegeError struct { privileges []uint64 } // Error returns the string message for the error. -func (e *PrivilegeError) Error() string { +func (e *privilegeError) Error() string { s := "Could not enable privilege " if len(e.privileges) > 1 { s = "Could not enable privileges " diff --git a/internal/fs/sd_windows_test.go b/internal/fs/sd_windows_test.go index e78241ed3..c31b19b8b 100644 --- a/internal/fs/sd_windows_test.go +++ b/internal/fs/sd_windows_test.go @@ -28,7 +28,7 @@ func TestSetGetFileSecurityDescriptors(t *testing.T) { } }() - testSecurityDescriptors(t, TestFileSDs, testfilePath) + testSecurityDescriptors(t, testFileSDs, testfilePath) } func TestSetGetFolderSecurityDescriptors(t *testing.T) { @@ -40,7 +40,7 @@ func TestSetGetFolderSecurityDescriptors(t *testing.T) { t.Fatalf("failed to create temporary file: %s", err) } - testSecurityDescriptors(t, TestDirSDs, testfolderPath) + testSecurityDescriptors(t, testDirSDs, testfolderPath) } func testSecurityDescriptors(t *testing.T, testSDs []string, testPath string) { @@ -48,13 +48,13 @@ func testSecurityDescriptors(t *testing.T, testSDs []string, testPath string) { sdInputBytes, err := base64.StdEncoding.DecodeString(testSD) test.OK(t, errors.Wrapf(err, "Error decoding SD: %s", testPath)) - err = SetSecurityDescriptor(testPath, &sdInputBytes) + err = setSecurityDescriptor(testPath, &sdInputBytes) test.OK(t, errors.Wrapf(err, "Error setting file security descriptor for: %s", testPath)) var sdOutputBytes *[]byte - sdOutputBytes, err = GetSecurityDescriptor(testPath) + sdOutputBytes, err = getSecurityDescriptor(testPath) test.OK(t, errors.Wrapf(err, "Error getting file security descriptor for: %s", testPath)) - CompareSecurityDescriptors(t, testPath, sdInputBytes, *sdOutputBytes) + compareSecurityDescriptors(t, testPath, sdInputBytes, *sdOutputBytes) } } diff --git a/internal/fs/sd_windows_test_helpers.go b/internal/fs/sd_windows_test_helpers.go index 8b3be5fd7..0e888884a 100644 --- a/internal/fs/sd_windows_test_helpers.go +++ b/internal/fs/sd_windows_test_helpers.go @@ -13,18 +13,18 @@ import ( ) var ( - TestFileSDs = []string{"AQAUvBQAAAAwAAAAAAAAAEwAAAABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvqAwAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIAfAAEAAAAAAAkAKkAEgABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvtAwAAABAUAP8BHwABAQAAAAAABRIAAAAAEBgA/wEfAAECAAAAAAAFIAAAACACAAAAECQA/wEfAAEFAAAAAAAFFQAAAIifWK5WoILqzL0mq+oDAAA=", + testFileSDs = []string{"AQAUvBQAAAAwAAAAAAAAAEwAAAABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvqAwAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIAfAAEAAAAAAAkAKkAEgABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvtAwAAABAUAP8BHwABAQAAAAAABRIAAAAAEBgA/wEfAAECAAAAAAAFIAAAACACAAAAECQA/wEfAAEFAAAAAAAFFQAAAIifWK5WoILqzL0mq+oDAAA=", "AQAUvBQAAAAwAAAAAAAAAEwAAAABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvqAwAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIAyAAHAAAAAAAUAKkAEgABAQAAAAAABQcAAAAAABQAiQASAAEBAAAAAAAFBwAAAAAAJACpABIAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSar7QMAAAAAJAC/ARMAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSar6gMAAAAAFAD/AR8AAQEAAAAAAAUSAAAAAAAYAP8BHwABAgAAAAAABSAAAAAgAgAAAAAkAP8BHwABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvqAwAA", "AQAUvBQAAAAwAAAA7AAAAEwAAAABBQAAAAAABRUAAAAvr7t03PyHGk2FokNHCAAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIAoAAFAAAAAAAkAP8BHwABBQAAAAAABRUAAAAvr7t03PyHGk2FokNHCAAAAAAkAKkAEgABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvtAwAAABAUAP8BHwABAQAAAAAABRIAAAAAEBgA/wEfAAECAAAAAAAFIAAAACACAAAAECQA/wEfAAEFAAAAAAAFFQAAAIifWK5WoILqzL0mq+oDAAACAHQAAwAAAAKAJAC/AQIAAQUAAAAAAAUVAAAAL6+7dNz8hxpNhaJDtgQAAALAJAC/AQMAAQUAAAAAAAUVAAAAL6+7dNz8hxpNhaJDPgkAAAJAJAD/AQ8AAQUAAAAAAAUVAAAAL6+7dNz8hxpNhaJDtQQAAA==", } - TestDirSDs = []string{"AQAUvBQAAAAwAAAAAAAAAEwAAAABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvqAwAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIAfAAEAAAAAAAkAKkAEgABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvtAwAAABMUAP8BHwABAQAAAAAABRIAAAAAExgA/wEfAAECAAAAAAAFIAAAACACAAAAEyQA/wEfAAEFAAAAAAAFFQAAAIifWK5WoILqzL0mq+oDAAA=", + testDirSDs = []string{"AQAUvBQAAAAwAAAAAAAAAEwAAAABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvqAwAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIAfAAEAAAAAAAkAKkAEgABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvtAwAAABMUAP8BHwABAQAAAAAABRIAAAAAExgA/wEfAAECAAAAAAAFIAAAACACAAAAEyQA/wEfAAEFAAAAAAAFFQAAAIifWK5WoILqzL0mq+oDAAA=", "AQAUvBQAAAAwAAAAAAAAAEwAAAABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvqAwAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIA3AAIAAAAAAIUAKkAEgABAQAAAAAABQcAAAAAAxQAiQASAAEBAAAAAAAFBwAAAAAAJACpABIAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSar7QMAAAAAJAC/ARMAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSar6gMAAAALFAC/ARMAAQEAAAAAAAMAAAAAABMUAP8BHwABAQAAAAAABRIAAAAAExgA/wEfAAECAAAAAAAFIAAAACACAAAAEyQA/wEfAAEFAAAAAAAFFQAAAIifWK5WoILqzL0mq+oDAAA=", "AQAUvBQAAAAwAAAA7AAAAEwAAAABBQAAAAAABRUAAAAvr7t03PyHGk2FokNHCAAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIAoAAFAAAAAAAkAP8BHwABBQAAAAAABRUAAAAvr7t03PyHGk2FokNHCAAAAAAkAKkAEgABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvtAwAAABMUAP8BHwABAQAAAAAABRIAAAAAExgA/wEfAAECAAAAAAAFIAAAACACAAAAEyQA/wEfAAEFAAAAAAAFFQAAAIifWK5WoILqzL0mq+oDAAACAHQAAwAAAAKAJAC/AQIAAQUAAAAAAAUVAAAAL6+7dNz8hxpNhaJDtgQAAALAJAC/AQMAAQUAAAAAAAUVAAAAL6+7dNz8hxpNhaJDPgkAAAJAJAD/AQ8AAQUAAAAAAAUVAAAAL6+7dNz8hxpNhaJDtQQAAA==", } ) -// IsAdmin checks if current user is an administrator. -func IsAdmin() (isAdmin bool, err error) { +// isAdmin checks if current user is an administrator. +func isAdmin() (isAdmin bool, err error) { var sid *windows.SID err = windows.AllocateAndInitializeSid(&windows.SECURITY_NT_AUTHORITY, 2, windows.SECURITY_BUILTIN_DOMAIN_RID, windows.DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &sid) @@ -40,15 +40,15 @@ func IsAdmin() (isAdmin bool, err error) { return member, nil } -// CompareSecurityDescriptors runs tests for comparing 2 security descriptors in []byte format. -func CompareSecurityDescriptors(t *testing.T, testPath string, sdInputBytes, sdOutputBytes []byte) { - sdInput, err := SecurityDescriptorBytesToStruct(sdInputBytes) +// compareSecurityDescriptors runs tests for comparing 2 security descriptors in []byte format. +func compareSecurityDescriptors(t *testing.T, testPath string, sdInputBytes, sdOutputBytes []byte) { + sdInput, err := securityDescriptorBytesToStruct(sdInputBytes) test.OK(t, errors.Wrapf(err, "Error converting SD to struct for: %s", testPath)) - sdOutput, err := SecurityDescriptorBytesToStruct(sdOutputBytes) + sdOutput, err := securityDescriptorBytesToStruct(sdOutputBytes) test.OK(t, errors.Wrapf(err, "Error converting SD to struct for: %s", testPath)) - isAdmin, err := IsAdmin() + isAdmin, err := isAdmin() test.OK(t, errors.Wrapf(err, "Error checking if user is admin: %s", testPath)) var ownerExpected *windows.SID diff --git a/internal/fs/vss.go b/internal/fs/vss.go index 8bfffab71..3215c9aa3 100644 --- a/internal/fs/vss.go +++ b/internal/fs/vss.go @@ -33,9 +33,9 @@ func HasSufficientPrivilegesForVSS() error { return errors.New("VSS snapshots are only supported on windows") } -// GetVolumeNameForVolumeMountPoint add trailing backslash to input parameter +// getVolumeNameForVolumeMountPoint add trailing backslash to input parameter // and calls the equivalent windows api. -func GetVolumeNameForVolumeMountPoint(mountPoint string) (string, error) { +func getVolumeNameForVolumeMountPoint(mountPoint string) (string, error) { return mountPoint, nil } diff --git a/internal/fs/vss_windows.go b/internal/fs/vss_windows.go index 0b51b00f3..bb168c553 100644 --- a/internal/fs/vss_windows.go +++ b/internal/fs/vss_windows.go @@ -22,6 +22,7 @@ import ( type HRESULT uint // HRESULT constant values necessary for using VSS api. +// //nolint:golint const ( S_OK HRESULT = 0x00000000 @@ -830,9 +831,9 @@ func HasSufficientPrivilegesForVSS() error { return err } -// GetVolumeNameForVolumeMountPoint add trailing backslash to input parameter +// getVolumeNameForVolumeMountPoint add trailing backslash to input parameter // and calls the equivalent windows api. -func GetVolumeNameForVolumeMountPoint(mountPoint string) (string, error) { +func getVolumeNameForVolumeMountPoint(mountPoint string) (string, error) { if mountPoint != "" && mountPoint[len(mountPoint)-1] != filepath.Separator { mountPoint += string(filepath.Separator) } From f0329bb4e6f1cad84a09758f43151264071581e9 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sat, 24 Aug 2024 23:43:45 +0200 Subject: [PATCH 07/12] fs: replace statT with ExtendedFileInfo --- internal/fs/node.go | 47 ++++++++++--------------- internal/fs/node_aix.go | 14 +------- internal/fs/node_darwin.go | 4 --- internal/fs/node_freebsd.go | 4 --- internal/fs/node_linux.go | 4 --- internal/fs/node_netbsd.go | 7 +--- internal/fs/node_openbsd.go | 7 +--- internal/fs/node_solaris.go | 4 --- internal/fs/node_unix.go | 19 ---------- internal/fs/node_unix_test.go | 46 +++++++++---------------- internal/fs/node_windows.go | 59 ++++---------------------------- internal/fs/node_windows_test.go | 6 ++-- internal/fs/node_xattr.go | 2 +- internal/fs/stat_windows.go | 3 +- 14 files changed, 50 insertions(+), 176 deletions(-) diff --git a/internal/fs/node.go b/internal/fs/node.go index 2dfe54172..a8aa3c408 100644 --- a/internal/fs/node.go +++ b/internal/fs/node.go @@ -6,7 +6,6 @@ import ( "strconv" "sync" "syscall" - "time" "github.com/restic/restic/internal/debug" "github.com/restic/restic/internal/errors" @@ -57,8 +56,7 @@ func nodeTypeFromFileInfo(fi os.FileInfo) restic.NodeType { } func nodeFillExtra(node *restic.Node, path string, fi os.FileInfo, ignoreXattrListError bool) error { - stat, ok := toStatT(fi.Sys()) - if !ok { + if fi.Sys() == nil { // fill minimal info with current values for uid, gid node.UID = uint32(os.Getuid()) node.GID = uint32(os.Getgid()) @@ -66,38 +64,43 @@ func nodeFillExtra(node *restic.Node, path string, fi os.FileInfo, ignoreXattrLi return nil } - node.Inode = uint64(stat.ino()) - node.DeviceID = uint64(stat.dev()) + stat := ExtendedStat(fi) - nodeFillTimes(node, stat) + node.Inode = stat.Inode + node.DeviceID = stat.DeviceID + node.ChangeTime = stat.ChangeTime + node.AccessTime = stat.AccessTime - nodeFillUser(node, stat) + node.UID = stat.UID + node.GID = stat.GID + node.User = lookupUsername(stat.UID) + node.Group = lookupGroup(stat.GID) switch node.Type { case restic.NodeTypeFile: - node.Size = uint64(stat.size()) - node.Links = uint64(stat.nlink()) + node.Size = uint64(stat.Size) + node.Links = stat.Links case restic.NodeTypeDir: case restic.NodeTypeSymlink: var err error node.LinkTarget, err = os.Readlink(fixpath(path)) - node.Links = uint64(stat.nlink()) + node.Links = stat.Links if err != nil { return errors.WithStack(err) } case restic.NodeTypeDev: - node.Device = uint64(stat.rdev()) - node.Links = uint64(stat.nlink()) + node.Device = stat.Device + node.Links = stat.Links case restic.NodeTypeCharDev: - node.Device = uint64(stat.rdev()) - node.Links = uint64(stat.nlink()) + node.Device = stat.Device + node.Links = stat.Links case restic.NodeTypeFifo: case restic.NodeTypeSocket: default: return errors.Errorf("unsupported file type %q", node.Type) } - allowExtended, err := nodeFillGenericAttributes(node, path, fi, stat) + allowExtended, err := nodeFillGenericAttributes(node, path, &stat) if allowExtended { // Skip processing ExtendedAttributes if allowExtended is false. err = errors.CombineErrors(err, nodeFillExtendedAttributes(node, path, ignoreXattrListError)) @@ -105,20 +108,6 @@ func nodeFillExtra(node *restic.Node, path string, fi os.FileInfo, ignoreXattrLi return err } -func nodeFillTimes(node *restic.Node, stat *statT) { - ctim := stat.ctim() - atim := stat.atim() - node.ChangeTime = time.Unix(ctim.Unix()) - node.AccessTime = time.Unix(atim.Unix()) -} - -func nodeFillUser(node *restic.Node, stat *statT) { - uid, gid := stat.uid(), stat.gid() - node.UID, node.GID = uid, gid - node.User = lookupUsername(uid) - node.Group = lookupGroup(gid) -} - var ( uidLookupCache = make(map[uint32]string) uidLookupCacheMutex = sync.RWMutex{} diff --git a/internal/fs/node_aix.go b/internal/fs/node_aix.go index 123985c2d..2967c6db7 100644 --- a/internal/fs/node_aix.go +++ b/internal/fs/node_aix.go @@ -4,7 +4,6 @@ package fs import ( - "os" "syscall" "github.com/restic/restic/internal/restic" @@ -14,17 +13,6 @@ func nodeRestoreSymlinkTimestamps(_ string, _ [2]syscall.Timespec) error { return nil } -// AIX has a funny timespec type in syscall, with 32-bit nanoseconds. -// golang.org/x/sys/unix handles this cleanly, but we're stuck with syscall -// because os.Stat returns a syscall type in its os.FileInfo.Sys(). -func toTimespec(t syscall.StTimespec_t) syscall.Timespec { - return syscall.Timespec{Sec: t.Sec, Nsec: int64(t.Nsec)} -} - -func (s statT) atim() syscall.Timespec { return toTimespec(s.Atim) } -func (s statT) mtim() syscall.Timespec { return toTimespec(s.Mtim) } -func (s statT) ctim() syscall.Timespec { return toTimespec(s.Ctim) } - // nodeRestoreExtendedAttributes is a no-op on AIX. func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error { return nil @@ -46,6 +34,6 @@ func nodeRestoreGenericAttributes(node *restic.Node, _ string, warn func(msg str } // nodeFillGenericAttributes is a no-op on AIX. -func nodeFillGenericAttributes(_ *restic.Node, _ string, _ os.FileInfo, _ *statT) (allowExtended bool, err error) { +func nodeFillGenericAttributes(_ *restic.Node, _ string, _ *ExtendedFileInfo) (allowExtended bool, err error) { return true, nil } diff --git a/internal/fs/node_darwin.go b/internal/fs/node_darwin.go index 1ca7ce480..f4c843498 100644 --- a/internal/fs/node_darwin.go +++ b/internal/fs/node_darwin.go @@ -5,7 +5,3 @@ import "syscall" func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error { return nil } - -func (s statT) atim() syscall.Timespec { return s.Atimespec } -func (s statT) mtim() syscall.Timespec { return s.Mtimespec } -func (s statT) ctim() syscall.Timespec { return s.Ctimespec } diff --git a/internal/fs/node_freebsd.go b/internal/fs/node_freebsd.go index 8796358b0..1b2f2fc7e 100644 --- a/internal/fs/node_freebsd.go +++ b/internal/fs/node_freebsd.go @@ -12,7 +12,3 @@ func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error func mknod(path string, mode uint32, dev uint64) (err error) { return syscall.Mknod(path, mode, dev) } - -func (s statT) atim() syscall.Timespec { return s.Atimespec } -func (s statT) mtim() syscall.Timespec { return s.Mtimespec } -func (s statT) ctim() syscall.Timespec { return s.Ctimespec } diff --git a/internal/fs/node_linux.go b/internal/fs/node_linux.go index 4cb9bdeaa..91ef4f907 100644 --- a/internal/fs/node_linux.go +++ b/internal/fs/node_linux.go @@ -31,7 +31,3 @@ func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error return dir.Close() } - -func (s statT) atim() syscall.Timespec { return s.Atim } -func (s statT) mtim() syscall.Timespec { return s.Mtim } -func (s statT) ctim() syscall.Timespec { return s.Ctim } diff --git a/internal/fs/node_netbsd.go b/internal/fs/node_netbsd.go index 996125851..00e6c92ca 100644 --- a/internal/fs/node_netbsd.go +++ b/internal/fs/node_netbsd.go @@ -1,7 +1,6 @@ package fs import ( - "os" "syscall" "github.com/restic/restic/internal/restic" @@ -11,10 +10,6 @@ func nodeRestoreSymlinkTimestamps(_ string, _ [2]syscall.Timespec) error { return nil } -func (s statT) atim() syscall.Timespec { return s.Atimespec } -func (s statT) mtim() syscall.Timespec { return s.Mtimespec } -func (s statT) ctim() syscall.Timespec { return s.Ctimespec } - // nodeRestoreExtendedAttributes is a no-op on netbsd. func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error { return nil @@ -36,6 +31,6 @@ func nodeRestoreGenericAttributes(node *restic.Node, _ string, warn func(msg str } // nodeFillGenericAttributes is a no-op on netbsd. -func nodeFillGenericAttributes(_ *restic.Node, _ string, _ os.FileInfo, _ *statT) (allowExtended bool, err error) { +func nodeFillGenericAttributes(_ *restic.Node, _ string, _ *ExtendedFileInfo) (allowExtended bool, err error) { return true, nil } diff --git a/internal/fs/node_openbsd.go b/internal/fs/node_openbsd.go index 62eb78618..590c603a3 100644 --- a/internal/fs/node_openbsd.go +++ b/internal/fs/node_openbsd.go @@ -1,7 +1,6 @@ package fs import ( - "os" "syscall" "github.com/restic/restic/internal/restic" @@ -11,10 +10,6 @@ func nodeRestoreSymlinkTimestamps(_ string, _ [2]syscall.Timespec) error { return nil } -func (s statT) atim() syscall.Timespec { return s.Atim } -func (s statT) mtim() syscall.Timespec { return s.Mtim } -func (s statT) ctim() syscall.Timespec { return s.Ctim } - // nodeRestoreExtendedAttributes is a no-op on openbsd. func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error { return nil @@ -36,6 +31,6 @@ func nodeRestoreGenericAttributes(node *restic.Node, _ string, warn func(msg str } // fillGenericAttributes is a no-op on openbsd. -func nodeFillGenericAttributes(_ *restic.Node, _ string, _ os.FileInfo, _ *statT) (allowExtended bool, err error) { +func nodeFillGenericAttributes(_ *restic.Node, _ string, _ *ExtendedFileInfo) (allowExtended bool, err error) { return true, nil } diff --git a/internal/fs/node_solaris.go b/internal/fs/node_solaris.go index 3f025b334..f4c843498 100644 --- a/internal/fs/node_solaris.go +++ b/internal/fs/node_solaris.go @@ -5,7 +5,3 @@ import "syscall" func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error { return nil } - -func (s statT) atim() syscall.Timespec { return s.Atim } -func (s statT) mtim() syscall.Timespec { return s.Mtim } -func (s statT) ctim() syscall.Timespec { return s.Ctim } diff --git a/internal/fs/node_unix.go b/internal/fs/node_unix.go index fb247ac99..5f08f3623 100644 --- a/internal/fs/node_unix.go +++ b/internal/fs/node_unix.go @@ -5,27 +5,8 @@ package fs import ( "os" - "syscall" ) func lchown(name string, uid, gid int) error { return os.Lchown(name, uid, gid) } - -type statT syscall.Stat_t - -func toStatT(i interface{}) (*statT, bool) { - s, ok := i.(*syscall.Stat_t) - if ok && s != nil { - return (*statT)(s), true - } - return nil, false -} - -func (s statT) dev() uint64 { return uint64(s.Dev) } -func (s statT) ino() uint64 { return uint64(s.Ino) } -func (s statT) nlink() uint64 { return uint64(s.Nlink) } -func (s statT) uid() uint32 { return uint32(s.Uid) } -func (s statT) gid() uint32 { return uint32(s.Gid) } -func (s statT) rdev() uint64 { return uint64(s.Rdev) } -func (s statT) size() int64 { return int64(s.Size) } diff --git a/internal/fs/node_unix_test.go b/internal/fs/node_unix_test.go index 3658c8356..4d01b6cc5 100644 --- a/internal/fs/node_unix_test.go +++ b/internal/fs/node_unix_test.go @@ -4,12 +4,12 @@ package fs import ( + "io/fs" "os" "path/filepath" "runtime" "syscall" "testing" - "time" "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" @@ -28,8 +28,11 @@ func stat(t testing.TB, filename string) (fi os.FileInfo, ok bool) { return fi, true } -func checkFile(t testing.TB, stat *syscall.Stat_t, node *restic.Node) { +func checkFile(t testing.TB, fi fs.FileInfo, node *restic.Node) { t.Helper() + + stat := fi.Sys().(*syscall.Stat_t) + if uint32(node.Mode.Perm()) != uint32(stat.Mode&0777) { t.Errorf("Mode does not match, want %v, got %v", stat.Mode&0777, node.Mode) } @@ -59,29 +62,20 @@ func checkFile(t testing.TB, stat *syscall.Stat_t, node *restic.Node) { } // use the os dependent function to compare the timestamps - s, ok := toStatT(stat) - if !ok { - return + s := ExtendedStat(fi) + if node.ModTime != s.ModTime { + t.Errorf("ModTime does not match, want %v, got %v", s.ModTime, node.ModTime) } - - mtime := s.mtim() - if node.ModTime != time.Unix(mtime.Unix()) { - t.Errorf("ModTime does not match, want %v, got %v", time.Unix(mtime.Unix()), node.ModTime) + if node.ChangeTime != s.ChangeTime { + t.Errorf("ChangeTime does not match, want %v, got %v", s.ChangeTime, node.ChangeTime) } - - ctime := s.ctim() - if node.ChangeTime != time.Unix(ctime.Unix()) { - t.Errorf("ChangeTime does not match, want %v, got %v", time.Unix(ctime.Unix()), node.ChangeTime) + if node.AccessTime != s.AccessTime { + t.Errorf("AccessTime does not match, want %v, got %v", s.AccessTime, node.AccessTime) } - - atime := s.atim() - if node.AccessTime != time.Unix(atime.Unix()) { - t.Errorf("AccessTime does not match, want %v, got %v", time.Unix(atime.Unix()), node.AccessTime) - } - } -func checkDevice(t testing.TB, stat *syscall.Stat_t, node *restic.Node) { +func checkDevice(t testing.TB, fi fs.FileInfo, node *restic.Node) { + stat := fi.Sys().(*syscall.Stat_t) if node.Device != uint64(stat.Rdev) { t.Errorf("Rdev does not match, want %v, got %v", stat.Rdev, node.Device) } @@ -123,12 +117,6 @@ func TestNodeFromFileInfo(t *testing.T) { return } - s, ok := fi.Sys().(*syscall.Stat_t) - if !ok { - t.Skipf("fi type is %T, not stat_t", fi.Sys()) - return - } - node, err := NodeFromFileInfo(test.filename, fi, false) if err != nil { t.Fatal(err) @@ -136,10 +124,10 @@ func TestNodeFromFileInfo(t *testing.T) { switch node.Type { case restic.NodeTypeFile, restic.NodeTypeSymlink: - checkFile(t, s, node) + checkFile(t, fi, node) case restic.NodeTypeDev, restic.NodeTypeCharDev: - checkFile(t, s, node) - checkDevice(t, s, node) + checkFile(t, fi, node) + checkDevice(t, fi, node) default: t.Fatalf("invalid node type %q", node.Type) } diff --git a/internal/fs/node_windows.go b/internal/fs/node_windows.go index d529e3503..0a9064fc5 100644 --- a/internal/fs/node_windows.go +++ b/internal/fs/node_windows.go @@ -3,7 +3,6 @@ package fs import ( "encoding/json" "fmt" - "os" "path/filepath" "reflect" "runtime" @@ -175,40 +174,6 @@ func restoreExtendedAttributes(nodeType restic.NodeType, path string, eas []exte return nil } -type statT syscall.Win32FileAttributeData - -func toStatT(i interface{}) (*statT, bool) { - s, ok := i.(*syscall.Win32FileAttributeData) - if ok && s != nil { - return (*statT)(s), true - } - return nil, false -} - -func (s statT) dev() uint64 { return 0 } -func (s statT) ino() uint64 { return 0 } -func (s statT) nlink() uint64 { return 0 } -func (s statT) uid() uint32 { return 0 } -func (s statT) gid() uint32 { return 0 } -func (s statT) rdev() uint64 { return 0 } - -func (s statT) size() int64 { - return int64(s.FileSizeLow) | (int64(s.FileSizeHigh) << 32) -} - -func (s statT) atim() syscall.Timespec { - return syscall.NsecToTimespec(s.LastAccessTime.Nanoseconds()) -} - -func (s statT) mtim() syscall.Timespec { - return syscall.NsecToTimespec(s.LastWriteTime.Nanoseconds()) -} - -func (s statT) ctim() syscall.Timespec { - // Windows does not have the concept of a "change time" in the sense Unix uses it, so we're using the LastWriteTime here. - return s.mtim() -} - // restoreGenericAttributes restores generic attributes for Windows func nodeRestoreGenericAttributes(node *restic.Node, path string, warn func(msg string)) (err error) { if len(node.GenericAttributes) == 0 { @@ -365,7 +330,7 @@ func decryptFile(pathPointer *uint16) error { // Created time and Security Descriptors. // It also checks if the volume supports extended attributes and stores the result in a map // so that it does not have to be checked again for subsequent calls for paths in the same volume. -func nodeFillGenericAttributes(node *restic.Node, path string, fi os.FileInfo, stat *statT) (allowExtended bool, err error) { +func nodeFillGenericAttributes(node *restic.Node, path string, stat *ExtendedFileInfo) (allowExtended bool, err error) { if strings.Contains(filepath.Base(path), ":") { // Do not process for Alternate Data Streams in Windows // Also do not allow processing of extended attributes for ADS. @@ -396,10 +361,13 @@ func nodeFillGenericAttributes(node *restic.Node, path string, fi os.FileInfo, s return allowExtended, err } } + + winFI := stat.Sys().(*syscall.Win32FileAttributeData) + // Add Windows attributes node.GenericAttributes, err = WindowsAttrsToGenericAttributes(WindowsAttributes{ - CreationTime: getCreationTime(fi, path), - FileAttributes: &stat.FileAttributes, + CreationTime: &winFI.CreationTime, + FileAttributes: &winFI.FileAttributes, SecurityDescriptor: sd, }) return allowExtended, err @@ -501,18 +469,3 @@ func WindowsAttrsToGenericAttributes(windowsAttributes WindowsAttributes) (attrs windowsAttributesValue := reflect.ValueOf(windowsAttributes) return restic.OSAttrsToGenericAttributes(reflect.TypeOf(windowsAttributes), &windowsAttributesValue, runtime.GOOS) } - -// getCreationTime gets the value for the WindowsAttribute CreationTime in a windows specific time format. -// The value is a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC) -// split into two 32-bit parts: the low-order DWORD and the high-order DWORD for efficiency and interoperability. -// The low-order DWORD represents the number of 100-nanosecond intervals elapsed since January 1, 1601, modulo -// 2^32. The high-order DWORD represents the number of times the low-order DWORD has overflowed. -func getCreationTime(fi os.FileInfo, path string) (creationTimeAttribute *syscall.Filetime) { - attrib, success := fi.Sys().(*syscall.Win32FileAttributeData) - if success && attrib != nil { - return &attrib.CreationTime - } else { - debug.Log("Could not get create time for path: %s", path) - return nil - } -} diff --git a/internal/fs/node_windows_test.go b/internal/fs/node_windows_test.go index 618364879..218e729ce 100644 --- a/internal/fs/node_windows_test.go +++ b/internal/fs/node_windows_test.go @@ -80,10 +80,10 @@ func TestRestoreCreationTime(t *testing.T) { path := t.TempDir() fi, err := os.Lstat(path) test.OK(t, errors.Wrapf(err, "Could not Lstat for path: %s", path)) - creationTimeAttribute := getCreationTime(fi, path) - test.OK(t, errors.Wrapf(err, "Could not get creation time for path: %s", path)) + attr := fi.Sys().(*syscall.Win32FileAttributeData) + creationTimeAttribute := attr.CreationTime //Using the temp dir creation time as the test creation time for the test file and folder - runGenericAttributesTest(t, path, restic.TypeCreationTime, WindowsAttributes{CreationTime: creationTimeAttribute}, false) + runGenericAttributesTest(t, path, restic.TypeCreationTime, WindowsAttributes{CreationTime: &creationTimeAttribute}, false) } func TestRestoreFileAttributes(t *testing.T) { diff --git a/internal/fs/node_xattr.go b/internal/fs/node_xattr.go index 55376ba58..1781452f7 100644 --- a/internal/fs/node_xattr.go +++ b/internal/fs/node_xattr.go @@ -71,7 +71,7 @@ func nodeRestoreGenericAttributes(node *restic.Node, _ string, warn func(msg str } // nodeFillGenericAttributes is a no-op. -func nodeFillGenericAttributes(_ *restic.Node, _ string, _ os.FileInfo, _ *statT) (allowExtended bool, err error) { +func nodeFillGenericAttributes(_ *restic.Node, _ string, _ *ExtendedFileInfo) (allowExtended bool, err error) { return true, nil } diff --git a/internal/fs/stat_windows.go b/internal/fs/stat_windows.go index ee678d92a..57f330fb5 100644 --- a/internal/fs/stat_windows.go +++ b/internal/fs/stat_windows.go @@ -19,7 +19,7 @@ func extendedStat(fi os.FileInfo) ExtendedFileInfo { extFI := ExtendedFileInfo{ FileInfo: fi, - Size: int64(s.FileSizeLow) + int64(s.FileSizeHigh)<<32, + Size: int64(s.FileSizeLow) | (int64(s.FileSizeHigh) << 32), } atime := syscall.NsecToTimespec(s.LastAccessTime.Nanoseconds()) @@ -28,6 +28,7 @@ func extendedStat(fi os.FileInfo) ExtendedFileInfo { mtime := syscall.NsecToTimespec(s.LastWriteTime.Nanoseconds()) extFI.ModTime = time.Unix(mtime.Unix()) + // Windows does not have the concept of a "change time" in the sense Unix uses it, so we're using the LastWriteTime here. extFI.ChangeTime = extFI.ModTime return extFI From 6c16733dfd54a821948866983ec5e2503d832afc Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Tue, 27 Aug 2024 15:15:27 +0200 Subject: [PATCH 08/12] fs: remove unused methods from File interface --- internal/fs/fs_reader.go | 8 -------- internal/fs/interface.go | 2 -- 2 files changed, 10 deletions(-) diff --git a/internal/fs/fs_reader.go b/internal/fs/fs_reader.go index 9dd5ff6eb..490a6b68d 100644 --- a/internal/fs/fs_reader.go +++ b/internal/fs/fs_reader.go @@ -229,18 +229,10 @@ type fakeFile struct { // ensure that fakeFile implements File var _ File = fakeFile{} -func (f fakeFile) Fd() uintptr { - return 0 -} - func (f fakeFile) Readdirnames(_ int) ([]string, error) { return nil, pathError("readdirnames", f.name, os.ErrInvalid) } -func (f fakeFile) Seek(int64, int) (int64, error) { - return 0, pathError("seek", f.name, os.ErrInvalid) -} - func (f fakeFile) Read(_ []byte) (int, error) { return 0, pathError("read", f.name, os.ErrInvalid) } diff --git a/internal/fs/interface.go b/internal/fs/interface.go index 7707abd1d..dcd16a0b3 100644 --- a/internal/fs/interface.go +++ b/internal/fs/interface.go @@ -29,9 +29,7 @@ type File interface { io.Reader io.Closer - Fd() uintptr Readdirnames(n int) ([]string, error) - Seek(int64, int) (int64, error) Stat() (os.FileInfo, error) Name() string } From 2aa1e2615b7c2f201ca30812f17b883a00b00725 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Tue, 27 Aug 2024 15:34:39 +0200 Subject: [PATCH 09/12] fs: fix comments --- internal/fs/fs_local_vss.go | 4 ++-- internal/fs/fs_reader_command.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/fs/fs_local_vss.go b/internal/fs/fs_local_vss.go index 27abcf9ea..908e744ee 100644 --- a/internal/fs/fs_local_vss.go +++ b/internal/fs/fs_local_vss.go @@ -130,12 +130,12 @@ func (fs *LocalVss) OpenFile(name string, flag int, perm os.FileMode) (File, err return os.OpenFile(fs.snapshotPath(name), flag, perm) } -// Stat wraps the Open method of the underlying file system. +// Stat wraps the Stat method of the underlying file system. func (fs *LocalVss) Stat(name string) (os.FileInfo, error) { return os.Stat(fs.snapshotPath(name)) } -// Lstat wraps the Open method of the underlying file system. +// Lstat wraps the Lstat method of the underlying file system. func (fs *LocalVss) Lstat(name string) (os.FileInfo, error) { return os.Lstat(fs.snapshotPath(name)) } diff --git a/internal/fs/fs_reader_command.go b/internal/fs/fs_reader_command.go index 6d061f641..2fa4375dd 100644 --- a/internal/fs/fs_reader_command.go +++ b/internal/fs/fs_reader_command.go @@ -10,7 +10,7 @@ import ( "github.com/restic/restic/internal/errors" ) -// CommandReader wrap a command such that its standard output can be read using +// CommandReader wraps a command such that its standard output can be read using // a io.ReadCloser. Close() waits for the command to terminate, reporting // any error back to the caller. type CommandReader struct { From ebdd946ac152c53977d5157714d832eb56146d63 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Tue, 27 Aug 2024 15:34:53 +0200 Subject: [PATCH 10/12] fs: unexport nodeRestoreTimestamps --- internal/fs/node.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/fs/node.go b/internal/fs/node.go index a8aa3c408..280e290c2 100644 --- a/internal/fs/node.go +++ b/internal/fs/node.go @@ -284,7 +284,7 @@ func nodeRestoreMetadata(node *restic.Node, path string, warn func(msg string)) } } - if err := NodeRestoreTimestamps(node, path); err != nil { + if err := nodeRestoreTimestamps(node, path); err != nil { debug.Log("error restoring timestamps for %v: %v", path, err) if firsterr == nil { firsterr = err @@ -305,7 +305,7 @@ func nodeRestoreMetadata(node *restic.Node, path string, warn func(msg string)) return firsterr } -func NodeRestoreTimestamps(node *restic.Node, path string) error { +func nodeRestoreTimestamps(node *restic.Node, path string) error { var utimes = [...]syscall.Timespec{ syscall.NsecToTimespec(node.AccessTime.UnixNano()), syscall.NsecToTimespec(node.ModTime.UnixNano()), From e2bce1b9eefd1ed9cdc77e727c5c245045ba49bb Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Tue, 27 Aug 2024 16:00:29 +0200 Subject: [PATCH 11/12] fs: move WindowsAttributes definition back to restic package --- internal/fs/node_windows.go | 23 ++----------------- internal/fs/node_windows_test.go | 22 +++++++++--------- internal/restic/node_windows.go | 26 ++++++++++++++++++++++ internal/restorer/restorer_windows_test.go | 3 +-- 4 files changed, 40 insertions(+), 34 deletions(-) create mode 100644 internal/restic/node_windows.go diff --git a/internal/fs/node_windows.go b/internal/fs/node_windows.go index 0a9064fc5..9d46143cc 100644 --- a/internal/fs/node_windows.go +++ b/internal/fs/node_windows.go @@ -5,7 +5,6 @@ import ( "fmt" "path/filepath" "reflect" - "runtime" "strings" "sync" "syscall" @@ -17,17 +16,6 @@ import ( "golang.org/x/sys/windows" ) -// WindowsAttributes are the genericAttributes for Windows OS -type WindowsAttributes struct { - // CreationTime is used for storing creation time for windows files. - CreationTime *syscall.Filetime `generic:"creation_time"` - // FileAttributes is used for storing file attributes for windows files. - FileAttributes *uint32 `generic:"file_attributes"` - // SecurityDescriptor is used for storing security descriptors which includes - // owner, group, discretionary access control list (DACL), system access control list (SACL) - SecurityDescriptor *[]byte `generic:"security_descriptor"` -} - var ( modAdvapi32 = syscall.NewLazyDLL("advapi32.dll") procEncryptFile = modAdvapi32.NewProc("EncryptFileW") @@ -205,7 +193,7 @@ func nodeRestoreGenericAttributes(node *restic.Node, path string, warn func(msg } // genericAttributesToWindowsAttrs converts the generic attributes map to a WindowsAttributes and also returns a string of unknown attributes that it could not convert. -func genericAttributesToWindowsAttrs(attrs map[restic.GenericAttributeType]json.RawMessage) (windowsAttributes WindowsAttributes, unknownAttribs []restic.GenericAttributeType, err error) { +func genericAttributesToWindowsAttrs(attrs map[restic.GenericAttributeType]json.RawMessage) (windowsAttributes restic.WindowsAttributes, unknownAttribs []restic.GenericAttributeType, err error) { waValue := reflect.ValueOf(&windowsAttributes).Elem() unknownAttribs, err = restic.GenericAttributesToOSAttrs(attrs, reflect.TypeOf(windowsAttributes), &waValue, "windows") return windowsAttributes, unknownAttribs, err @@ -365,7 +353,7 @@ func nodeFillGenericAttributes(node *restic.Node, path string, stat *ExtendedFil winFI := stat.Sys().(*syscall.Win32FileAttributeData) // Add Windows attributes - node.GenericAttributes, err = WindowsAttrsToGenericAttributes(WindowsAttributes{ + node.GenericAttributes, err = restic.WindowsAttrsToGenericAttributes(restic.WindowsAttributes{ CreationTime: &winFI.CreationTime, FileAttributes: &winFI.FileAttributes, SecurityDescriptor: sd, @@ -462,10 +450,3 @@ func prepareVolumeName(path string) (volumeName string, err error) { } return volumeName, nil } - -// windowsAttrsToGenericAttributes converts the WindowsAttributes to a generic attributes map using reflection -func WindowsAttrsToGenericAttributes(windowsAttributes WindowsAttributes) (attrs map[restic.GenericAttributeType]json.RawMessage, err error) { - // Get the value of the WindowsAttributes - windowsAttributesValue := reflect.ValueOf(windowsAttributes) - return restic.OSAttrsToGenericAttributes(reflect.TypeOf(windowsAttributes), &windowsAttributesValue, runtime.GOOS) -} diff --git a/internal/fs/node_windows_test.go b/internal/fs/node_windows_test.go index 218e729ce..83ad72d53 100644 --- a/internal/fs/node_windows_test.go +++ b/internal/fs/node_windows_test.go @@ -36,7 +36,7 @@ func testRestoreSecurityDescriptor(t *testing.T, sd string, tempDir string, file sdInputBytes, err := base64.StdEncoding.DecodeString(sd) test.OK(t, errors.Wrapf(err, "Error decoding SD for: %s", fileName)) // Wrap the security descriptor bytes in windows attributes and convert to generic attributes. - genericAttributes, err := WindowsAttrsToGenericAttributes(WindowsAttributes{CreationTime: nil, FileAttributes: nil, SecurityDescriptor: &sdInputBytes}) + genericAttributes, err := restic.WindowsAttrsToGenericAttributes(restic.WindowsAttributes{CreationTime: nil, FileAttributes: nil, SecurityDescriptor: &sdInputBytes}) test.OK(t, errors.Wrapf(err, "Error constructing windows attributes for: %s", fileName)) // Construct a Node with the generic attributes. expectedNode := getNode(fileName, fileType, genericAttributes) @@ -68,7 +68,7 @@ func getNode(name string, fileType restic.NodeType, genericAttributes map[restic } } -func getWindowsAttr(t *testing.T, testPath string, node *restic.Node) WindowsAttributes { +func getWindowsAttr(t *testing.T, testPath string, node *restic.Node) restic.WindowsAttributes { windowsAttributes, unknownAttribs, err := genericAttributesToWindowsAttrs(node.GenericAttributes) test.OK(t, errors.Wrapf(err, "Error getting windows attr from generic attr: %s", testPath)) test.Assert(t, len(unknownAttribs) == 0, "Unknown attribs found: %s for: %s", unknownAttribs, testPath) @@ -83,7 +83,7 @@ func TestRestoreCreationTime(t *testing.T) { attr := fi.Sys().(*syscall.Win32FileAttributeData) creationTimeAttribute := attr.CreationTime //Using the temp dir creation time as the test creation time for the test file and folder - runGenericAttributesTest(t, path, restic.TypeCreationTime, WindowsAttributes{CreationTime: &creationTimeAttribute}, false) + runGenericAttributesTest(t, path, restic.TypeCreationTime, restic.WindowsAttributes{CreationTime: &creationTimeAttribute}, false) } func TestRestoreFileAttributes(t *testing.T) { @@ -95,7 +95,7 @@ func TestRestoreFileAttributes(t *testing.T) { system := uint32(syscall.FILE_ATTRIBUTE_SYSTEM) archive := uint32(syscall.FILE_ATTRIBUTE_ARCHIVE) encrypted := uint32(windows.FILE_ATTRIBUTE_ENCRYPTED) - fileAttributes := []WindowsAttributes{ + fileAttributes := []restic.WindowsAttributes{ //normal {FileAttributes: &normal}, //hidden @@ -108,7 +108,7 @@ func TestRestoreFileAttributes(t *testing.T) { {FileAttributes: &encrypted}, } for i, fileAttr := range fileAttributes { - genericAttrs, err := WindowsAttrsToGenericAttributes(fileAttr) + genericAttrs, err := restic.WindowsAttrsToGenericAttributes(fileAttr) test.OK(t, err) expectedNodes := []restic.Node{ { @@ -128,7 +128,7 @@ func TestRestoreFileAttributes(t *testing.T) { system = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY | windows.FILE_ATTRIBUTE_SYSTEM) archive = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY | windows.FILE_ATTRIBUTE_ARCHIVE) encrypted = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY | windows.FILE_ATTRIBUTE_ENCRYPTED) - folderAttributes := []WindowsAttributes{ + folderAttributes := []restic.WindowsAttributes{ //normal {FileAttributes: &normal}, //hidden @@ -141,7 +141,7 @@ func TestRestoreFileAttributes(t *testing.T) { {FileAttributes: &encrypted}, } for i, folderAttr := range folderAttributes { - genericAttrs, err := WindowsAttrsToGenericAttributes(folderAttr) + genericAttrs, err := restic.WindowsAttrsToGenericAttributes(folderAttr) test.OK(t, err) expectedNodes := []restic.Node{ { @@ -158,8 +158,8 @@ func TestRestoreFileAttributes(t *testing.T) { } } -func runGenericAttributesTest(t *testing.T, tempDir string, genericAttributeName restic.GenericAttributeType, genericAttributeExpected WindowsAttributes, warningExpected bool) { - genericAttributes, err := WindowsAttrsToGenericAttributes(genericAttributeExpected) +func runGenericAttributesTest(t *testing.T, tempDir string, genericAttributeName restic.GenericAttributeType, genericAttributeExpected restic.WindowsAttributes, warningExpected bool) { + genericAttributes, err := restic.WindowsAttrsToGenericAttributes(genericAttributeExpected) test.OK(t, err) expectedNodes := []restic.Node{ { @@ -183,12 +183,12 @@ func runGenericAttributesTest(t *testing.T, tempDir string, genericAttributeName } runGenericAttributesTestForNodes(t, expectedNodes, tempDir, genericAttributeName, genericAttributeExpected, warningExpected) } -func runGenericAttributesTestForNodes(t *testing.T, expectedNodes []restic.Node, tempDir string, genericAttr restic.GenericAttributeType, genericAttributeExpected WindowsAttributes, warningExpected bool) { +func runGenericAttributesTestForNodes(t *testing.T, expectedNodes []restic.Node, tempDir string, genericAttr restic.GenericAttributeType, genericAttributeExpected restic.WindowsAttributes, warningExpected bool) { for _, testNode := range expectedNodes { testPath, node := restoreAndGetNode(t, tempDir, &testNode, warningExpected) rawMessage := node.GenericAttributes[genericAttr] - genericAttrsExpected, err := WindowsAttrsToGenericAttributes(genericAttributeExpected) + genericAttrsExpected, err := restic.WindowsAttrsToGenericAttributes(genericAttributeExpected) test.OK(t, err) rawMessageExpected := genericAttrsExpected[genericAttr] test.Equals(t, rawMessageExpected, rawMessage, "Generic attribute: %s got from NodeFromFileInfo not equal for path: %s", string(genericAttr), testPath) diff --git a/internal/restic/node_windows.go b/internal/restic/node_windows.go new file mode 100644 index 000000000..7df426665 --- /dev/null +++ b/internal/restic/node_windows.go @@ -0,0 +1,26 @@ +package restic + +import ( + "encoding/json" + "reflect" + "runtime" + "syscall" +) + +// WindowsAttributes are the genericAttributes for Windows OS +type WindowsAttributes struct { + // CreationTime is used for storing creation time for windows files. + CreationTime *syscall.Filetime `generic:"creation_time"` + // FileAttributes is used for storing file attributes for windows files. + FileAttributes *uint32 `generic:"file_attributes"` + // SecurityDescriptor is used for storing security descriptors which includes + // owner, group, discretionary access control list (DACL), system access control list (SACL) + SecurityDescriptor *[]byte `generic:"security_descriptor"` +} + +// windowsAttrsToGenericAttributes converts the WindowsAttributes to a generic attributes map using reflection +func WindowsAttrsToGenericAttributes(windowsAttributes WindowsAttributes) (attrs map[GenericAttributeType]json.RawMessage, err error) { + // Get the value of the WindowsAttributes + windowsAttributesValue := reflect.ValueOf(windowsAttributes) + return OSAttrsToGenericAttributes(reflect.TypeOf(windowsAttributes), &windowsAttributesValue, runtime.GOOS) +} diff --git a/internal/restorer/restorer_windows_test.go b/internal/restorer/restorer_windows_test.go index 9fcdfc48d..4764bed2d 100644 --- a/internal/restorer/restorer_windows_test.go +++ b/internal/restorer/restorer_windows_test.go @@ -16,7 +16,6 @@ import ( "unsafe" "github.com/restic/restic/internal/errors" - "github.com/restic/restic/internal/fs" "github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/restic" "github.com/restic/restic/internal/test" @@ -264,7 +263,7 @@ func setup(t *testing.T, nodesMap map[string]Node) *Restorer { //If the node is a directory add FILE_ATTRIBUTE_DIRECTORY to attributes fileattr |= windows.FILE_ATTRIBUTE_DIRECTORY } - attrs, err := fs.WindowsAttrsToGenericAttributes(fs.WindowsAttributes{FileAttributes: &fileattr}) + attrs, err := restic.WindowsAttrsToGenericAttributes(restic.WindowsAttributes{FileAttributes: &fileattr}) test.OK(t, err) return attrs } From b91ef3f1ff617549129437d1162e0f43f9628454 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Wed, 28 Aug 2024 11:01:27 +0200 Subject: [PATCH 12/12] fs: remove dead code --- internal/fs/node_aix.go | 5 ----- internal/fs/node_netbsd.go | 5 ----- internal/fs/node_openbsd.go | 5 ----- 3 files changed, 15 deletions(-) diff --git a/internal/fs/node_aix.go b/internal/fs/node_aix.go index 2967c6db7..463ed1c33 100644 --- a/internal/fs/node_aix.go +++ b/internal/fs/node_aix.go @@ -23,11 +23,6 @@ func nodeFillExtendedAttributes(_ *restic.Node, _ string, _ bool) error { return nil } -// isListxattrPermissionError is a no-op on AIX. -func isListxattrPermissionError(_ error) bool { - return false -} - // nodeRestoreGenericAttributes is no-op on AIX. func nodeRestoreGenericAttributes(node *restic.Node, _ string, warn func(msg string)) error { return restic.HandleAllUnknownGenericAttributesFound(node.GenericAttributes, warn) diff --git a/internal/fs/node_netbsd.go b/internal/fs/node_netbsd.go index 00e6c92ca..182050da0 100644 --- a/internal/fs/node_netbsd.go +++ b/internal/fs/node_netbsd.go @@ -20,11 +20,6 @@ func nodeFillExtendedAttributes(_ *restic.Node, _ string, _ bool) error { return nil } -// isListxattrPermissionError is a no-op on netbsd. -func isListxattrPermissionError(_ error) bool { - return false -} - // nodeRestoreGenericAttributes is no-op on netbsd. func nodeRestoreGenericAttributes(node *restic.Node, _ string, warn func(msg string)) error { return restic.HandleAllUnknownGenericAttributesFound(node.GenericAttributes, warn) diff --git a/internal/fs/node_openbsd.go b/internal/fs/node_openbsd.go index 590c603a3..2a7a410dd 100644 --- a/internal/fs/node_openbsd.go +++ b/internal/fs/node_openbsd.go @@ -20,11 +20,6 @@ func nodeFillExtendedAttributes(_ *restic.Node, _ string, _ bool) error { return nil } -// isListxattrPermissionError is a no-op on openbsd. -func isListxattrPermissionError(_ error) bool { - return false -} - // nodeRestoreGenericAttributes is no-op on openbsd. func nodeRestoreGenericAttributes(node *restic.Node, _ string, warn func(msg string)) error { return restic.HandleAllUnknownGenericAttributesFound(node.GenericAttributes, warn)