forked from TrueCloudLab/restic
Merge pull request #5024 from MichaelEischer/move-node-to-fs
Cleanup FS package
This commit is contained in:
commit
d8be8f1e06
81 changed files with 474 additions and 782 deletions
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
13
internal/backend/cache/cache.go
vendored
13
internal/backend/cache/cache.go
vendored
|
@ -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
|
||||
|
|
21
internal/backend/cache/file.go
vendored
21
internal/backend/cache/file.go
vendored
|
@ -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
|
||||
}
|
||||
|
|
3
internal/backend/cache/file_test.go
vendored
3
internal/backend/cache/file_test.go
vendored
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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 += "/"
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
@ -124,7 +123,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{
|
||||
{
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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,32 +27,12 @@ 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 {
|
||||
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.
|
||||
|
@ -74,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,
|
||||
|
@ -88,15 +50,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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
|
@ -74,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
|
||||
|
@ -104,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, 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 {
|
||||
|
@ -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:
|
||||
|
|
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -229,22 +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) 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)
|
||||
}
|
||||
|
||||
func (f fakeFile) Read(_ []byte) (int, error) {
|
||||
return 0, pathError("read", f.name, os.ErrInvalid)
|
||||
}
|
||||
|
@ -279,13 +267,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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -29,10 +29,7 @@ type File interface {
|
|||
io.Reader
|
||||
io.Closer
|
||||
|
||||
Fd() uintptr
|
||||
Readdirnames(n int) ([]string, error)
|
||||
Readdir(int) ([]os.FileInfo, error)
|
||||
Seek(int64, int) (int64, error)
|
||||
Stat() (os.FileInfo, error)
|
||||
Name() string
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
|
@ -25,7 +24,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,32 +32,31 @@ 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 {
|
||||
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 "file":
|
||||
node.Size = uint64(stat.size())
|
||||
node.Links = uint64(stat.nlink())
|
||||
case "dir":
|
||||
case "symlink":
|
||||
case restic.NodeTypeFile:
|
||||
node.Size = uint64(stat.Size)
|
||||
node.Links = stat.Links
|
||||
case restic.NodeTypeDir:
|
||||
case restic.NodeTypeSymlink:
|
||||
var err error
|
||||
node.LinkTarget, err = Readlink(path)
|
||||
node.Links = uint64(stat.nlink())
|
||||
node.LinkTarget, err = os.Readlink(fixpath(path))
|
||||
node.Links = stat.Links
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
case "dev":
|
||||
node.Device = uint64(stat.rdev())
|
||||
node.Links = uint64(stat.nlink())
|
||||
case "chardev":
|
||||
node.Device = uint64(stat.rdev())
|
||||
node.Links = uint64(stat.nlink())
|
||||
case "fifo":
|
||||
case "socket":
|
||||
case restic.NodeTypeDev:
|
||||
node.Device = stat.Device
|
||||
node.Links = stat.Links
|
||||
case restic.NodeTypeCharDev:
|
||||
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{}
|
||||
|
@ -178,31 +167,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)
|
||||
|
@ -212,7 +201,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 +223,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)
|
||||
}
|
||||
|
||||
|
@ -295,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,8 +294,8 @@ 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 err := Chmod(path, node.Mode); err != nil {
|
||||
if node.Type != restic.NodeTypeSymlink {
|
||||
if err := chmod(path, node.Mode); err != nil {
|
||||
if firsterr == nil {
|
||||
firsterr = errors.WithStack(err)
|
||||
}
|
||||
|
@ -316,13 +305,13 @@ 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()),
|
||||
}
|
||||
|
||||
if node.Type == "symlink" {
|
||||
if node.Type == restic.NodeTypeSymlink {
|
||||
return nodeRestoreSymlinkTimestamps(path, utimes)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -35,17 +23,12 @@ 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
@ -30,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 }
|
||||
|
|
|
@ -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
|
||||
|
@ -25,17 +20,12 @@ 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -25,17 +20,12 @@ 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
@ -42,7 +45,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)
|
||||
}
|
||||
|
||||
|
@ -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,23 +117,17 @@ 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)
|
||||
}
|
||||
|
||||
switch node.Type {
|
||||
case "file", "symlink":
|
||||
checkFile(t, s, node)
|
||||
case "dev", "chardev":
|
||||
checkFile(t, s, node)
|
||||
checkDevice(t, s, node)
|
||||
case restic.NodeTypeFile, restic.NodeTypeSymlink:
|
||||
checkFile(t, fi, node)
|
||||
case restic.NodeTypeDev, restic.NodeTypeCharDev:
|
||||
checkFile(t, fi, node)
|
||||
checkDevice(t, fi, node)
|
||||
default:
|
||||
t.Fatalf("invalid node type %q", node.Type)
|
||||
}
|
||||
|
|
|
@ -3,10 +3,8 @@ package fs
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
@ -18,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")
|
||||
|
@ -58,7 +45,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
|
||||
}
|
||||
|
@ -85,9 +72,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 +86,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 +94,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 +126,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, 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 +137,7 @@ func restoreExtendedAttributes(nodeType, path string, eas []ExtendedAttribute) (
|
|||
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,50 +152,16 @@ func restoreExtendedAttributes(nodeType, path string, eas []ExtendedAttribute) (
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -230,7 +183,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))
|
||||
}
|
||||
}
|
||||
|
@ -240,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
|
||||
|
@ -296,7 +249,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 +277,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)
|
||||
}
|
||||
|
@ -365,7 +318,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.
|
||||
|
@ -386,20 +339,23 @@ 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 {
|
||||
return false, err
|
||||
}
|
||||
if sd, err = GetSecurityDescriptor(path); err != nil {
|
||||
if sd, err = getSecurityDescriptor(path); err != nil {
|
||||
return allowExtended, err
|
||||
}
|
||||
}
|
||||
|
||||
winFI := stat.Sys().(*syscall.Win32FileAttributeData)
|
||||
|
||||
// Add Windows attributes
|
||||
node.GenericAttributes, err = WindowsAttrsToGenericAttributes(WindowsAttributes{
|
||||
CreationTime: getCreationTime(fi, path),
|
||||
FileAttributes: &stat.FileAttributes,
|
||||
node.GenericAttributes, err = restic.WindowsAttrsToGenericAttributes(restic.WindowsAttributes{
|
||||
CreationTime: &winFI.CreationTime,
|
||||
FileAttributes: &winFI.FileAttributes,
|
||||
SecurityDescriptor: sd,
|
||||
})
|
||||
return allowExtended, err
|
||||
|
@ -422,7 +378,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 +387,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 +403,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)
|
||||
|
@ -494,25 +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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,20 +23,20 @@ import (
|
|||
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))
|
||||
for i, sd := range testFileSDs {
|
||||
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))
|
||||
for i, sd := range testDirSDs {
|
||||
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))
|
||||
// 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)
|
||||
|
@ -47,16 +47,16 @@ func testRestoreSecurityDescriptor(t *testing.T, sd string, tempDir, fileType, f
|
|||
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 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,
|
||||
|
@ -68,7 +68,7 @@ func getNode(name string, fileType string, genericAttributes map[restic.GenericA
|
|||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -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, 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,12 +108,12 @@ 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{
|
||||
{
|
||||
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"),
|
||||
|
@ -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,12 +141,12 @@ 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{
|
||||
{
|
||||
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"),
|
||||
|
@ -158,13 +158,13 @@ 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{
|
||||
{
|
||||
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"),
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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 "
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
26
internal/restic/node_windows.go
Normal file
26
internal/restic/node_windows.go
Normal file
|
@ -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)
|
||||
}
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue