mirror of
https://github.com/octoleo/restic.git
synced 2024-12-25 04:05:58 +00:00
Merge pull request #5024 from MichaelEischer/move-node-to-fs
Cleanup FS package
This commit is contained in:
commit
d8be8f1e06
@ -10,7 +10,6 @@ import (
|
|||||||
|
|
||||||
"github.com/restic/restic/internal/backend/cache"
|
"github.com/restic/restic/internal/backend/cache"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/fs"
|
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/restic/restic/internal/ui/table"
|
"github.com/restic/restic/internal/ui/table"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -89,7 +88,7 @@ func runCache(opts CacheOptions, gopts GlobalOptions, args []string) error {
|
|||||||
|
|
||||||
for _, item := range oldDirs {
|
for _, item := range oldDirs {
|
||||||
dir := filepath.Join(cachedir, item.Name())
|
dir := filepath.Join(cachedir, item.Name())
|
||||||
err = fs.RemoveAll(dir)
|
err = os.RemoveAll(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Warnf("unable to remove %v: %v\n", dir, err)
|
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/backend/cache"
|
||||||
"github.com/restic/restic/internal/checker"
|
"github.com/restic/restic/internal/checker"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/fs"
|
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"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)
|
printer.P("using temporary cache in %v\n", tempdir)
|
||||||
|
|
||||||
cleanup = func() {
|
cleanup = func() {
|
||||||
err := fs.RemoveAll(tempdir)
|
err := os.RemoveAll(tempdir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printer.E("error removing temporary cache directory: %v\n", err)
|
printer.E("error removing temporary cache directory: %v\n", err)
|
||||||
}
|
}
|
||||||
|
@ -108,9 +108,9 @@ func (s *DiffStat) Add(node *restic.Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch node.Type {
|
switch node.Type {
|
||||||
case "file":
|
case restic.NodeTypeFile:
|
||||||
s.Files++
|
s.Files++
|
||||||
case "dir":
|
case restic.NodeTypeDir:
|
||||||
s.Dirs++
|
s.Dirs++
|
||||||
default:
|
default:
|
||||||
s.Others++
|
s.Others++
|
||||||
@ -124,7 +124,7 @@ func addBlobs(bs restic.BlobSet, node *restic.Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch node.Type {
|
switch node.Type {
|
||||||
case "file":
|
case restic.NodeTypeFile:
|
||||||
for _, blob := range node.Content {
|
for _, blob := range node.Content {
|
||||||
h := restic.BlobHandle{
|
h := restic.BlobHandle{
|
||||||
ID: blob,
|
ID: blob,
|
||||||
@ -132,7 +132,7 @@ func addBlobs(bs restic.BlobSet, node *restic.Node) {
|
|||||||
}
|
}
|
||||||
bs.Insert(h)
|
bs.Insert(h)
|
||||||
}
|
}
|
||||||
case "dir":
|
case restic.NodeTypeDir:
|
||||||
h := restic.BlobHandle{
|
h := restic.BlobHandle{
|
||||||
ID: *node.Subtree,
|
ID: *node.Subtree,
|
||||||
Type: restic.TreeBlob,
|
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)
|
name := path.Join(prefix, node.Name)
|
||||||
if node.Type == "dir" {
|
if node.Type == restic.NodeTypeDir {
|
||||||
name += "/"
|
name += "/"
|
||||||
}
|
}
|
||||||
c.printChange(NewChange(name, mode))
|
c.printChange(NewChange(name, mode))
|
||||||
stats.Add(node)
|
stats.Add(node)
|
||||||
addBlobs(blobs, node)
|
addBlobs(blobs, node)
|
||||||
|
|
||||||
if node.Type == "dir" {
|
if node.Type == restic.NodeTypeDir {
|
||||||
err := c.printDir(ctx, mode, stats, blobs, name, *node.Subtree)
|
err := c.printDir(ctx, mode, stats, blobs, name, *node.Subtree)
|
||||||
if err != nil && err != context.Canceled {
|
if err != nil && err != context.Canceled {
|
||||||
Warnf("error: %v\n", err)
|
Warnf("error: %v\n", err)
|
||||||
@ -216,7 +216,7 @@ func (c *Comparer) collectDir(ctx context.Context, blobs restic.BlobSet, id rest
|
|||||||
|
|
||||||
addBlobs(blobs, node)
|
addBlobs(blobs, node)
|
||||||
|
|
||||||
if node.Type == "dir" {
|
if node.Type == restic.NodeTypeDir {
|
||||||
err := c.collectDir(ctx, blobs, *node.Subtree)
|
err := c.collectDir(ctx, blobs, *node.Subtree)
|
||||||
if err != nil && err != context.Canceled {
|
if err != nil && err != context.Canceled {
|
||||||
Warnf("error: %v\n", err)
|
Warnf("error: %v\n", err)
|
||||||
@ -284,12 +284,12 @@ func (c *Comparer) diffTree(ctx context.Context, stats *DiffStatsContainer, pref
|
|||||||
mod += "T"
|
mod += "T"
|
||||||
}
|
}
|
||||||
|
|
||||||
if node2.Type == "dir" {
|
if node2.Type == restic.NodeTypeDir {
|
||||||
name += "/"
|
name += "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
if node1.Type == "file" &&
|
if node1.Type == restic.NodeTypeFile &&
|
||||||
node2.Type == "file" &&
|
node2.Type == restic.NodeTypeFile &&
|
||||||
!reflect.DeepEqual(node1.Content, node2.Content) {
|
!reflect.DeepEqual(node1.Content, node2.Content) {
|
||||||
mod += "M"
|
mod += "M"
|
||||||
stats.ChangedFiles++
|
stats.ChangedFiles++
|
||||||
@ -311,7 +311,7 @@ func (c *Comparer) diffTree(ctx context.Context, stats *DiffStatsContainer, pref
|
|||||||
c.printChange(NewChange(name, mod))
|
c.printChange(NewChange(name, mod))
|
||||||
}
|
}
|
||||||
|
|
||||||
if node1.Type == "dir" && node2.Type == "dir" {
|
if node1.Type == restic.NodeTypeDir && node2.Type == restic.NodeTypeDir {
|
||||||
var err error
|
var err error
|
||||||
if (*node1.Subtree).Equal(*node2.Subtree) {
|
if (*node1.Subtree).Equal(*node2.Subtree) {
|
||||||
err = c.collectDir(ctx, stats.BlobsCommon, *node1.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:
|
case t1 && !t2:
|
||||||
prefix := path.Join(prefix, name)
|
prefix := path.Join(prefix, name)
|
||||||
if node1.Type == "dir" {
|
if node1.Type == restic.NodeTypeDir {
|
||||||
prefix += "/"
|
prefix += "/"
|
||||||
}
|
}
|
||||||
c.printChange(NewChange(prefix, "-"))
|
c.printChange(NewChange(prefix, "-"))
|
||||||
stats.Removed.Add(node1)
|
stats.Removed.Add(node1)
|
||||||
|
|
||||||
if node1.Type == "dir" {
|
if node1.Type == restic.NodeTypeDir {
|
||||||
err := c.printDir(ctx, "-", &stats.Removed, stats.BlobsBefore, prefix, *node1.Subtree)
|
err := c.printDir(ctx, "-", &stats.Removed, stats.BlobsBefore, prefix, *node1.Subtree)
|
||||||
if err != nil && err != context.Canceled {
|
if err != nil && err != context.Canceled {
|
||||||
Warnf("error: %v\n", err)
|
Warnf("error: %v\n", err)
|
||||||
@ -338,13 +338,13 @@ func (c *Comparer) diffTree(ctx context.Context, stats *DiffStatsContainer, pref
|
|||||||
}
|
}
|
||||||
case !t1 && t2:
|
case !t1 && t2:
|
||||||
prefix := path.Join(prefix, name)
|
prefix := path.Join(prefix, name)
|
||||||
if node2.Type == "dir" {
|
if node2.Type == restic.NodeTypeDir {
|
||||||
prefix += "/"
|
prefix += "/"
|
||||||
}
|
}
|
||||||
c.printChange(NewChange(prefix, "+"))
|
c.printChange(NewChange(prefix, "+"))
|
||||||
stats.Added.Add(node2)
|
stats.Added.Add(node2)
|
||||||
|
|
||||||
if node2.Type == "dir" {
|
if node2.Type == restic.NodeTypeDir {
|
||||||
err := c.printDir(ctx, "+", &stats.Added, stats.BlobsAfter, prefix, *node2.Subtree)
|
err := c.printDir(ctx, "+", &stats.Added, stats.BlobsAfter, prefix, *node2.Subtree)
|
||||||
if err != nil && err != context.Canceled {
|
if err != nil && err != context.Canceled {
|
||||||
Warnf("error: %v\n", err)
|
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.
|
// first item it finds and dump that according to the switch case below.
|
||||||
if node.Name == pathComponents[0] {
|
if node.Name == pathComponents[0] {
|
||||||
switch {
|
switch {
|
||||||
case l == 1 && dump.IsFile(node):
|
case l == 1 && node.Type == restic.NodeTypeFile:
|
||||||
return d.WriteNode(ctx, node)
|
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)
|
subtree, err := restic.LoadTree(ctx, repo, *node.Subtree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "cannot load subtree for %q", item)
|
return errors.Wrapf(err, "cannot load subtree for %q", item)
|
||||||
}
|
}
|
||||||
return printFromTree(ctx, subtree, repo, item, pathComponents[1:], d, canWriteArchiveFunc)
|
return printFromTree(ctx, subtree, repo, item, pathComponents[1:], d, canWriteArchiveFunc)
|
||||||
case dump.IsDir(node):
|
case node.Type == restic.NodeTypeDir:
|
||||||
if err := canWriteArchiveFunc(); err != nil {
|
if err := canWriteArchiveFunc(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -114,7 +114,7 @@ func printFromTree(ctx context.Context, tree *restic.Tree, repo restic.BlobLoade
|
|||||||
return d.DumpTree(ctx, subtree, item)
|
return d.DumpTree(ctx, subtree, item)
|
||||||
case l > 1:
|
case l > 1:
|
||||||
return fmt.Errorf("%q should be a dir, but is a %q", item, node.Type)
|
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)
|
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
|
var errIfNoMatch error
|
||||||
if node.Type == "dir" {
|
if node.Type == restic.NodeTypeDir {
|
||||||
var childMayMatch bool
|
var childMayMatch bool
|
||||||
for _, pat := range f.pat.pattern {
|
for _, pat := range f.pat.pattern {
|
||||||
mayMatch, err := filter.ChildMatch(pat, normalizedNodepath)
|
mayMatch, err := filter.ChildMatch(pat, normalizedNodepath)
|
||||||
@ -357,7 +357,7 @@ func (f *Finder) findIDs(ctx context.Context, sn *restic.Snapshot) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if node.Type == "dir" && f.treeIDs != nil {
|
if node.Type == restic.NodeTypeDir && f.treeIDs != nil {
|
||||||
treeID := node.Subtree
|
treeID := node.Subtree
|
||||||
found := false
|
found := false
|
||||||
if _, ok := f.treeIDs[treeID.Str()]; ok {
|
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 {
|
for _, id := range node.Content {
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
|
@ -137,7 +137,7 @@ func lsNodeJSON(enc *json.Encoder, path string, node *restic.Node) error {
|
|||||||
size uint64 // Target for Size pointer.
|
size uint64 // Target for Size pointer.
|
||||||
}{
|
}{
|
||||||
Name: node.Name,
|
Name: node.Name,
|
||||||
Type: node.Type,
|
Type: string(node.Type),
|
||||||
Path: path,
|
Path: path,
|
||||||
UID: node.UID,
|
UID: node.UID,
|
||||||
GID: node.GID,
|
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,
|
// Always print size for regular files, even when empty,
|
||||||
// but never for other types.
|
// but never for other types.
|
||||||
if node.Type == "file" {
|
if node.Type == restic.NodeTypeFile {
|
||||||
n.Size = &n.size
|
n.Size = &n.size
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,7 +208,7 @@ func lsNcduNode(_ string, node *restic.Node) ([]byte, error) {
|
|||||||
Dev: node.DeviceID,
|
Dev: node.DeviceID,
|
||||||
Ino: node.Inode,
|
Ino: node.Inode,
|
||||||
NLink: node.Links,
|
NLink: node.Links,
|
||||||
NotReg: node.Type != "dir" && node.Type != "file",
|
NotReg: node.Type != restic.NodeTypeDir && node.Type != restic.NodeTypeFile,
|
||||||
UID: node.UID,
|
UID: node.UID,
|
||||||
GID: node.GID,
|
GID: node.GID,
|
||||||
Mode: uint16(node.Mode & os.ModePerm),
|
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)
|
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))
|
fmt.Fprintf(p.out, ",\n%s[\n%s%s", strings.Repeat(" ", p.depth), strings.Repeat(" ", p.depth+1), string(out))
|
||||||
p.depth++
|
p.depth++
|
||||||
} else {
|
} 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
|
// otherwise, signal the walker to not walk recursively into any
|
||||||
// subdirs
|
// subdirs
|
||||||
if node.Type == "dir" {
|
if node.Type == restic.NodeTypeDir {
|
||||||
// immediately generate leaveDir if the directory is skipped
|
// immediately generate leaveDir if the directory is skipped
|
||||||
if printedDir {
|
if printedDir {
|
||||||
printer.LeaveDir(nodepath)
|
printer.LeaveDir(nodepath)
|
||||||
|
@ -23,7 +23,7 @@ var lsTestNodes = []lsTestNode{
|
|||||||
path: "/bar/baz",
|
path: "/bar/baz",
|
||||||
Node: restic.Node{
|
Node: restic.Node{
|
||||||
Name: "baz",
|
Name: "baz",
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
Size: 12345,
|
Size: 12345,
|
||||||
UID: 10000000,
|
UID: 10000000,
|
||||||
GID: 20000000,
|
GID: 20000000,
|
||||||
@ -39,7 +39,7 @@ var lsTestNodes = []lsTestNode{
|
|||||||
path: "/foo/empty",
|
path: "/foo/empty",
|
||||||
Node: restic.Node{
|
Node: restic.Node{
|
||||||
Name: "empty",
|
Name: "empty",
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
Size: 0,
|
Size: 0,
|
||||||
UID: 1001,
|
UID: 1001,
|
||||||
GID: 1001,
|
GID: 1001,
|
||||||
@ -56,7 +56,7 @@ var lsTestNodes = []lsTestNode{
|
|||||||
path: "/foo/link",
|
path: "/foo/link",
|
||||||
Node: restic.Node{
|
Node: restic.Node{
|
||||||
Name: "link",
|
Name: "link",
|
||||||
Type: "symlink",
|
Type: restic.NodeTypeSymlink,
|
||||||
Mode: os.ModeSymlink | 0777,
|
Mode: os.ModeSymlink | 0777,
|
||||||
LinkTarget: "not printed",
|
LinkTarget: "not printed",
|
||||||
},
|
},
|
||||||
@ -66,7 +66,7 @@ var lsTestNodes = []lsTestNode{
|
|||||||
path: "/some/directory",
|
path: "/some/directory",
|
||||||
Node: restic.Node{
|
Node: restic.Node{
|
||||||
Name: "directory",
|
Name: "directory",
|
||||||
Type: "dir",
|
Type: restic.NodeTypeDir,
|
||||||
Mode: os.ModeDir | 0755,
|
Mode: os.ModeDir | 0755,
|
||||||
ModTime: time.Date(2020, 1, 2, 3, 4, 5, 0, time.UTC),
|
ModTime: time.Date(2020, 1, 2, 3, 4, 5, 0, time.UTC),
|
||||||
AccessTime: time.Date(2021, 2, 3, 4, 5, 6, 7, time.UTC),
|
AccessTime: time.Date(2021, 2, 3, 4, 5, 6, 7, time.UTC),
|
||||||
@ -79,7 +79,7 @@ var lsTestNodes = []lsTestNode{
|
|||||||
path: "/some/sticky",
|
path: "/some/sticky",
|
||||||
Node: restic.Node{
|
Node: restic.Node{
|
||||||
Name: "sticky",
|
Name: "sticky",
|
||||||
Type: "dir",
|
Type: restic.NodeTypeDir,
|
||||||
Mode: os.ModeDir | 0755 | os.ModeSetuid | os.ModeSetgid | os.ModeSticky,
|
Mode: os.ModeDir | 0755 | os.ModeSetuid | os.ModeSetgid | os.ModeSticky,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -139,19 +139,19 @@ func TestLsNcdu(t *testing.T) {
|
|||||||
Paths: []string{"/example"},
|
Paths: []string{"/example"},
|
||||||
})
|
})
|
||||||
printer.Node("/directory", &restic.Node{
|
printer.Node("/directory", &restic.Node{
|
||||||
Type: "dir",
|
Type: restic.NodeTypeDir,
|
||||||
Name: "directory",
|
Name: "directory",
|
||||||
ModTime: modTime,
|
ModTime: modTime,
|
||||||
}, false)
|
}, false)
|
||||||
printer.Node("/directory/data", &restic.Node{
|
printer.Node("/directory/data", &restic.Node{
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
Name: "data",
|
Name: "data",
|
||||||
Size: 42,
|
Size: 42,
|
||||||
ModTime: modTime,
|
ModTime: modTime,
|
||||||
}, false)
|
}, false)
|
||||||
printer.LeaveDir("/directory")
|
printer.LeaveDir("/directory")
|
||||||
printer.Node("/file", &restic.Node{
|
printer.Node("/file", &restic.Node{
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
Name: "file",
|
Name: "file",
|
||||||
Size: 12345,
|
Size: 12345,
|
||||||
ModTime: modTime,
|
ModTime: modTime,
|
||||||
|
@ -15,7 +15,6 @@ import (
|
|||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
|
|
||||||
resticfs "github.com/restic/restic/internal/fs"
|
|
||||||
"github.com/restic/restic/internal/fuse"
|
"github.com/restic/restic/internal/fuse"
|
||||||
|
|
||||||
systemFuse "github.com/anacrolix/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
|
// Check the existence of the mount point at the earliest stage to
|
||||||
// prevent unnecessary computations while opening the repository.
|
// 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)
|
Verbosef("Mountpoint %s doesn't exist\n", mountpoint)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ func runRecover(ctx context.Context, gopts GlobalOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, node := range tree.Nodes {
|
for _, node := range tree.Nodes {
|
||||||
if node.Type == "dir" && node.Subtree != nil {
|
if node.Type == restic.NodeTypeDir && node.Subtree != nil {
|
||||||
trees[*node.Subtree] = true
|
trees[*node.Subtree] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -128,7 +128,7 @@ func runRecover(ctx context.Context, gopts GlobalOptions) error {
|
|||||||
for id := range roots {
|
for id := range roots {
|
||||||
var subtreeID = id
|
var subtreeID = id
|
||||||
node := restic.Node{
|
node := restic.Node{
|
||||||
Type: "dir",
|
Type: restic.NodeTypeDir,
|
||||||
Name: id.Str(),
|
Name: id.Str(),
|
||||||
Mode: 0755,
|
Mode: 0755,
|
||||||
Subtree: &subtreeID,
|
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)
|
// - files whose contents are not fully available (-> file will be modified)
|
||||||
rewriter := walker.NewTreeRewriter(walker.RewriteOpts{
|
rewriter := walker.NewTreeRewriter(walker.RewriteOpts{
|
||||||
RewriteNode: func(node *restic.Node, path string) *restic.Node {
|
RewriteNode: func(node *restic.Node, path string) *restic.Node {
|
||||||
if node.Type != "file" {
|
if node.Type != restic.NodeTypeFile {
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,7 +276,7 @@ func statsWalkTree(repo restic.Loader, opts StatsOptions, stats *statsContainer,
|
|||||||
// will still be restored
|
// will still be restored
|
||||||
stats.TotalFileCount++
|
stats.TotalFileCount++
|
||||||
|
|
||||||
if node.Links == 1 || node.Type == "dir" {
|
if node.Links == 1 || node.Type == restic.NodeTypeDir {
|
||||||
stats.TotalSize += node.Size
|
stats.TotalSize += node.Size
|
||||||
} else {
|
} else {
|
||||||
// if hardlinks are present only count each deviceID+inode once
|
// 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 {
|
switch n.Type {
|
||||||
case "file":
|
case restic.NodeTypeFile:
|
||||||
mode = 0
|
mode = 0
|
||||||
case "dir":
|
case restic.NodeTypeDir:
|
||||||
mode = os.ModeDir
|
mode = os.ModeDir
|
||||||
case "symlink":
|
case restic.NodeTypeSymlink:
|
||||||
mode = os.ModeSymlink
|
mode = os.ModeSymlink
|
||||||
target = fmt.Sprintf(" -> %v", n.LinkTarget)
|
target = fmt.Sprintf(" -> %v", n.LinkTarget)
|
||||||
case "dev":
|
case restic.NodeTypeDev:
|
||||||
mode = os.ModeDevice
|
mode = os.ModeDevice
|
||||||
case "chardev":
|
case restic.NodeTypeCharDev:
|
||||||
mode = os.ModeDevice | os.ModeCharDevice
|
mode = os.ModeDevice | os.ModeCharDevice
|
||||||
case "fifo":
|
case restic.NodeTypeFifo:
|
||||||
mode = os.ModeNamedPipe
|
mode = os.ModeNamedPipe
|
||||||
case "socket":
|
case restic.NodeTypeSocket:
|
||||||
mode = os.ModeSocket
|
mode = os.ModeSocket
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ func TestFormatNode(t *testing.T) {
|
|||||||
testPath := "/test/path"
|
testPath := "/test/path"
|
||||||
node := restic.Node{
|
node := restic.Node{
|
||||||
Name: "baz",
|
Name: "baz",
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
Size: 14680064,
|
Size: 14680064,
|
||||||
UID: 1000,
|
UID: 1000,
|
||||||
GID: 2000,
|
GID: 2000,
|
||||||
|
@ -29,7 +29,6 @@ import (
|
|||||||
"github.com/restic/restic/internal/backend/sftp"
|
"github.com/restic/restic/internal/backend/sftp"
|
||||||
"github.com/restic/restic/internal/backend/swift"
|
"github.com/restic/restic/internal/backend/swift"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/fs"
|
|
||||||
"github.com/restic/restic/internal/options"
|
"github.com/restic/restic/internal/options"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
@ -548,7 +547,7 @@ func OpenRepository(ctx context.Context, opts GlobalOptions) (*repository.Reposi
|
|||||||
}
|
}
|
||||||
for _, item := range oldCacheDirs {
|
for _, item := range oldCacheDirs {
|
||||||
dir := filepath.Join(c.Base, item.Name())
|
dir := filepath.Join(c.Base, item.Name())
|
||||||
err = fs.RemoveAll(dir)
|
err = os.RemoveAll(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Warnf("unable to remove %v: %v\n", dir, err)
|
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 {
|
switch current.Type {
|
||||||
case "dir":
|
case restic.NodeTypeDir:
|
||||||
switch {
|
switch {
|
||||||
case previous == nil:
|
case previous == nil:
|
||||||
arch.summary.Dirs.New++
|
arch.summary.Dirs.New++
|
||||||
@ -242,7 +242,7 @@ func (arch *Archiver) trackItem(item string, previous, current *restic.Node, s I
|
|||||||
arch.summary.Dirs.Changed++
|
arch.summary.Dirs.Changed++
|
||||||
}
|
}
|
||||||
|
|
||||||
case "file":
|
case restic.NodeTypeFile:
|
||||||
switch {
|
switch {
|
||||||
case previous == nil:
|
case previous == nil:
|
||||||
arch.summary.Files.New++
|
arch.summary.Files.New++
|
||||||
@ -261,7 +261,7 @@ func (arch *Archiver) nodeFromFileInfo(snPath, filename string, fi os.FileInfo,
|
|||||||
node.AccessTime = node.ModTime
|
node.AccessTime = node.ModTime
|
||||||
}
|
}
|
||||||
if feature.Flag.Enabled(feature.DeviceIDForHardlinks) {
|
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
|
// the DeviceID is only necessary for hardlinked files
|
||||||
// when using subvolumes or snapshots their deviceIDs tend to change which causes
|
// when using subvolumes or snapshots their deviceIDs tend to change which causes
|
||||||
// restic to upload new tree blobs
|
// 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.
|
// 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.
|
// 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) {
|
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
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -583,7 +583,7 @@ func fileChanged(fs fs.FS, fi os.FileInfo, node *restic.Node, ignoreFlags uint)
|
|||||||
switch {
|
switch {
|
||||||
case node == nil:
|
case node == nil:
|
||||||
return true
|
return true
|
||||||
case node.Type != "file":
|
case node.Type != restic.NodeTypeFile:
|
||||||
// We're only called for regular files, so this is a type change.
|
// We're only called for regular files, so this is a type change.
|
||||||
return true
|
return true
|
||||||
case uint64(fi.Size()) != node.Size:
|
case uint64(fi.Size()) != node.Size:
|
||||||
|
@ -730,7 +730,7 @@ func TestFilChangedSpecialCases(t *testing.T) {
|
|||||||
t.Run("type-change", func(t *testing.T) {
|
t.Run("type-change", func(t *testing.T) {
|
||||||
fi := lstat(t, filename)
|
fi := lstat(t, filename)
|
||||||
node := nodeFromFI(t, filename, fi)
|
node := nodeFromFI(t, filename, fi)
|
||||||
node.Type = "symlink"
|
node.Type = "restic.NodeTypeSymlink"
|
||||||
if !fileChanged(&fs.Local{}, fi, node, 0) {
|
if !fileChanged(&fs.Local{}, fi, node, 0) {
|
||||||
t.Fatal("node with changed type detected as unchanged")
|
t.Fatal("node with changed type detected as unchanged")
|
||||||
}
|
}
|
||||||
@ -846,7 +846,7 @@ func TestArchiverSaveDir(t *testing.T) {
|
|||||||
back := rtest.Chdir(t, chdir)
|
back := rtest.Chdir(t, chdir)
|
||||||
defer back()
|
defer back()
|
||||||
|
|
||||||
fi, err := fs.Lstat(test.target)
|
fi, err := os.Lstat(test.target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -920,7 +920,7 @@ func TestArchiverSaveDirIncremental(t *testing.T) {
|
|||||||
arch.runWorkers(ctx, wg)
|
arch.runWorkers(ctx, wg)
|
||||||
arch.summary = &Summary{}
|
arch.summary = &Summary{}
|
||||||
|
|
||||||
fi, err := fs.Lstat(tempdir)
|
fi, err := os.Lstat(tempdir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -163,7 +163,7 @@ func (s *fileSaver) saveFile(ctx context.Context, chnker *chunker.Chunker, snPat
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if node.Type != "file" {
|
if node.Type != restic.NodeTypeFile {
|
||||||
_ = f.Close()
|
_ = f.Close()
|
||||||
completeError(errors.Errorf("node type %q is wrong", node.Type))
|
completeError(errors.Errorf("node type %q is wrong", node.Type))
|
||||||
return
|
return
|
||||||
|
@ -95,17 +95,17 @@ func TestCreateFiles(t testing.TB, target string, dir TestDir) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
case TestSymlink:
|
case TestSymlink:
|
||||||
err := fs.Symlink(filepath.FromSlash(it.Target), targetPath)
|
err := os.Symlink(filepath.FromSlash(it.Target), targetPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
case TestHardlink:
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
case TestDir:
|
case TestDir:
|
||||||
err := fs.Mkdir(targetPath, 0755)
|
err := os.Mkdir(targetPath, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -157,7 +157,7 @@ func TestEnsureFiles(t testing.TB, target string, dir TestDir) {
|
|||||||
|
|
||||||
// first, test that all items are there
|
// first, test that all items are there
|
||||||
TestWalkFiles(t, target, dir, func(path string, item interface{}) error {
|
TestWalkFiles(t, target, dir, func(path string, item interface{}) error {
|
||||||
fi, err := fs.Lstat(path)
|
fi, err := os.Lstat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -188,7 +188,7 @@ func TestEnsureFiles(t testing.TB, target string, dir TestDir) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
target, err := fs.Readlink(path)
|
target, err := os.Readlink(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -289,7 +289,7 @@ func TestEnsureTree(ctx context.Context, t testing.TB, prefix string, repo resti
|
|||||||
|
|
||||||
switch e := entry.(type) {
|
switch e := entry.(type) {
|
||||||
case TestDir:
|
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")
|
t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "dir")
|
||||||
return
|
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)
|
TestEnsureTree(ctx, t, path.Join(prefix, node.Name), repo, *node.Subtree, e)
|
||||||
case TestFile:
|
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")
|
t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "file")
|
||||||
}
|
}
|
||||||
TestEnsureFileContent(ctx, t, repo, nodePrefix, node, e)
|
TestEnsureFileContent(ctx, t, repo, nodePrefix, node, e)
|
||||||
case TestSymlink:
|
case TestSymlink:
|
||||||
if node.Type != "symlink" {
|
if node.Type != restic.NodeTypeSymlink {
|
||||||
t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "file")
|
t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "symlink")
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Target != node.LinkTarget {
|
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{}) {
|
func createFilesAt(t testing.TB, targetdir string, files map[string]interface{}) {
|
||||||
for name, item := range files {
|
for name, item := range files {
|
||||||
target := filepath.Join(targetdir, filepath.FromSlash(name))
|
target := filepath.Join(targetdir, filepath.FromSlash(name))
|
||||||
err := fs.MkdirAll(filepath.Dir(target), 0700)
|
err := os.MkdirAll(filepath.Dir(target), 0700)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -66,7 +66,7 @@ func createFilesAt(t testing.TB, targetdir string, files map[string]interface{})
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
case TestSymlink:
|
case TestSymlink:
|
||||||
err := fs.Symlink(filepath.FromSlash(it.Target), target)
|
err := os.Symlink(filepath.FromSlash(it.Target), target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -105,7 +105,7 @@ func TestTestCreateFiles(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("", func(t *testing.T) {
|
t.Run("", func(t *testing.T) {
|
||||||
tempdir := filepath.Join(tempdir, fmt.Sprintf("test-%d", i))
|
tempdir := filepath.Join(tempdir, fmt.Sprintf("test-%d", i))
|
||||||
err := fs.MkdirAll(tempdir, 0700)
|
err := os.MkdirAll(tempdir, 0700)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -114,7 +114,7 @@ func TestTestCreateFiles(t *testing.T) {
|
|||||||
|
|
||||||
for name, item := range test.files {
|
for name, item := range test.files {
|
||||||
targetPath := filepath.Join(tempdir, filepath.FromSlash(name))
|
targetPath := filepath.Join(tempdir, filepath.FromSlash(name))
|
||||||
fi, err := fs.Lstat(targetPath)
|
fi, err := os.Lstat(targetPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
continue
|
continue
|
||||||
@ -142,7 +142,7 @@ func TestTestCreateFiles(t *testing.T) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
target, err := fs.Readlink(targetPath)
|
target, err := os.Readlink(targetPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
continue
|
continue
|
||||||
@ -455,7 +455,7 @@ func TestTestEnsureSnapshot(t *testing.T) {
|
|||||||
tempdir := rtest.TempDir(t)
|
tempdir := rtest.TempDir(t)
|
||||||
|
|
||||||
targetDir := filepath.Join(tempdir, "target")
|
targetDir := filepath.Join(tempdir, "target")
|
||||||
err := fs.Mkdir(targetDir, 0700)
|
err := os.Mkdir(targetDir, 0700)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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/pkg/errors"
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/fs"
|
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -54,7 +53,7 @@ const cachedirTagSignature = "Signature: 8a477f597d28d172789f06886806bc55\n"
|
|||||||
|
|
||||||
func writeCachedirTag(dir string) error {
|
func writeCachedirTag(dir string) error {
|
||||||
tagfile := filepath.Join(dir, "CACHEDIR.TAG")
|
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 err != nil {
|
||||||
if errors.Is(err, os.ErrExist) {
|
if errors.Is(err, os.ErrExist) {
|
||||||
return nil
|
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 {
|
if err != nil {
|
||||||
return nil, errors.WithStack(err)
|
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):
|
case errors.Is(err, os.ErrNotExist):
|
||||||
// Create the repo cache dir. The parent exists, so Mkdir suffices.
|
// Create the repo cache dir. The parent exists, so Mkdir suffices.
|
||||||
err := fs.Mkdir(cachedir, dirMode)
|
err := os.Mkdir(cachedir, dirMode)
|
||||||
switch {
|
switch {
|
||||||
case err == nil:
|
case err == nil:
|
||||||
created = true
|
created = true
|
||||||
@ -134,7 +133,7 @@ func New(id string, basedir string) (c *Cache, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range cacheLayoutPaths {
|
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)
|
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.
|
// directory d to the current time.
|
||||||
func updateTimestamp(d string) error {
|
func updateTimestamp(d string) error {
|
||||||
t := time.Now()
|
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.
|
// 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.
|
// listCacheDirs returns the list of cache directories.
|
||||||
func listCacheDirs(basedir string) ([]os.FileInfo, error) {
|
func listCacheDirs(basedir string) ([]os.FileInfo, error) {
|
||||||
f, err := fs.Open(basedir)
|
f, err := os.Open(basedir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
err = nil
|
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/backend/util"
|
||||||
"github.com/restic/restic/internal/crypto"
|
"github.com/restic/restic/internal/crypto"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/fs"
|
|
||||||
"github.com/restic/restic/internal/restic"
|
"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")
|
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 {
|
if err != nil {
|
||||||
return nil, false, errors.WithStack(err)
|
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)
|
finalname := c.filename(h)
|
||||||
dir := filepath.Dir(finalname)
|
dir := filepath.Dir(finalname)
|
||||||
err := fs.Mkdir(dir, 0700)
|
err := os.Mkdir(dir, 0700)
|
||||||
if err != nil && !errors.Is(err, os.ErrExist) {
|
if err != nil && !errors.Is(err, os.ErrExist) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -106,26 +105,26 @@ func (c *Cache) save(h backend.Handle, rd io.Reader) error {
|
|||||||
n, err := io.Copy(f, rd)
|
n, err := io.Copy(f, rd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = f.Close()
|
_ = f.Close()
|
||||||
_ = fs.Remove(f.Name())
|
_ = os.Remove(f.Name())
|
||||||
return errors.Wrap(err, "Copy")
|
return errors.Wrap(err, "Copy")
|
||||||
}
|
}
|
||||||
|
|
||||||
if n <= int64(crypto.CiphertextLength(0)) {
|
if n <= int64(crypto.CiphertextLength(0)) {
|
||||||
_ = f.Close()
|
_ = f.Close()
|
||||||
_ = fs.Remove(f.Name())
|
_ = os.Remove(f.Name())
|
||||||
debug.Log("trying to cache truncated file %v, removing", h)
|
debug.Log("trying to cache truncated file %v, removing", h)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close, then rename. Windows doesn't like the reverse order.
|
// Close, then rename. Windows doesn't like the reverse order.
|
||||||
if err = f.Close(); err != nil {
|
if err = f.Close(); err != nil {
|
||||||
_ = fs.Remove(f.Name())
|
_ = os.Remove(f.Name())
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = fs.Rename(f.Name(), finalname)
|
err = os.Rename(f.Name(), finalname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = fs.Remove(f.Name())
|
_ = os.Remove(f.Name())
|
||||||
}
|
}
|
||||||
if runtime.GOOS == "windows" && errors.Is(err, os.ErrPermission) {
|
if runtime.GOOS == "windows" && errors.Is(err, os.ErrPermission) {
|
||||||
// On Windows, renaming over an existing file is ok
|
// 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
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := fs.Remove(c.filename(h))
|
err := os.Remove(c.filename(h))
|
||||||
removed := err == nil
|
removed := err == nil
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
err = nil
|
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
|
// 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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -236,6 +235,6 @@ func (c *Cache) Has(h backend.Handle) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := fs.Stat(c.filename(h))
|
_, err := os.Stat(c.filename(h))
|
||||||
return err == nil
|
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/backend"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/fs"
|
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
|
|
||||||
@ -278,7 +277,7 @@ func TestFileSaveConcurrent(t *testing.T) {
|
|||||||
|
|
||||||
func TestFileSaveAfterDamage(t *testing.T) {
|
func TestFileSaveAfterDamage(t *testing.T) {
|
||||||
c := TestNewCache(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
|
// save a few bytes of data in the cache
|
||||||
data := rtest.Random(123456789, 42)
|
data := rtest.Random(123456789, 42)
|
||||||
|
@ -40,7 +40,7 @@ func NewFactory() location.Factory {
|
|||||||
func open(cfg Config) (*Local, error) {
|
func open(cfg Config) (*Local, error) {
|
||||||
l := layout.NewDefaultLayout(cfg.Path, filepath.Join)
|
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)
|
m := util.DeriveModesFromFileInfo(fi, err)
|
||||||
debug.Log("using (%03O file, %03O dir) permissions", m.File, m.Dir)
|
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
|
// 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 {
|
if err == nil {
|
||||||
return nil, errors.New("config file already exists")
|
return nil, errors.New("config file already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
// create paths for data and refs
|
// create paths for data and refs
|
||||||
for _, d := range be.Paths() {
|
for _, d := range be.Paths() {
|
||||||
err := fs.MkdirAll(d, be.Modes.Dir)
|
err := os.MkdirAll(d, be.Modes.Dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.WithStack(err)
|
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)
|
debug.Log("error %v: creating dir", err)
|
||||||
|
|
||||||
// error is caused by a missing directory, try to create it
|
// 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 {
|
if mkdirErr != nil {
|
||||||
debug.Log("error creating dir %v: %v", dir, mkdirErr)
|
debug.Log("error creating dir %v: %v", dir, mkdirErr)
|
||||||
} else {
|
} 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
|
// temporary's name and no other goroutine will get the same data to
|
||||||
// Save, so the temporary name should never be reused by another
|
// Save, so the temporary name should never be reused by another
|
||||||
// goroutine.
|
// goroutine.
|
||||||
_ = fs.Remove(f.Name())
|
_ = os.Remove(f.Name())
|
||||||
}
|
}
|
||||||
}(f)
|
}(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) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// Stat returns information about a blob.
|
||||||
func (b *Local) Stat(_ context.Context, h backend.Handle) (backend.FileInfo, error) {
|
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 {
|
if err != nil {
|
||||||
return backend.FileInfo{}, errors.WithStack(err)
|
return backend.FileInfo{}, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
@ -258,12 +258,12 @@ func (b *Local) Remove(_ context.Context, h backend.Handle) error {
|
|||||||
fn := b.Filename(h)
|
fn := b.Filename(h)
|
||||||
|
|
||||||
// reset read-only flag
|
// reset read-only flag
|
||||||
err := fs.Chmod(fn, 0666)
|
err := os.Chmod(fn, 0666)
|
||||||
if err != nil && !os.IsPermission(err) {
|
if err != nil && !os.IsPermission(err) {
|
||||||
return errors.WithStack(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
|
// 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
|
// Also, visitDirs assumes it sees a directory full of directories, while
|
||||||
// visitFiles wants a directory full or regular files.
|
// visitFiles wants a directory full or regular files.
|
||||||
func visitDirs(ctx context.Context, dir string, fn func(backend.FileInfo) error) error {
|
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 {
|
if err != nil {
|
||||||
return err
|
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 {
|
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 {
|
if err != nil {
|
||||||
return err
|
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.
|
// Delete removes the repository and all files.
|
||||||
func (b *Local) Delete(_ context.Context) error {
|
func (b *Local) Delete(_ context.Context) error {
|
||||||
return fs.RemoveAll(b.Path)
|
return os.RemoveAll(b.Path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes all open files.
|
// Close closes all open files.
|
||||||
|
@ -8,8 +8,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/fs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// fsyncDir flushes changes to the directory dir.
|
// fsyncDir flushes changes to the directory dir.
|
||||||
@ -45,5 +43,5 @@ func isMacENOTTY(err error) bool {
|
|||||||
|
|
||||||
// set file to readonly
|
// set file to readonly
|
||||||
func setFileReadonly(f string, mode os.FileMode) error {
|
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 {
|
for _, node := range tree.Nodes {
|
||||||
switch node.Type {
|
switch node.Type {
|
||||||
case "file":
|
case restic.NodeTypeFile:
|
||||||
if node.Content == nil {
|
if node.Content == nil {
|
||||||
errs = append(errs, &Error{TreeID: id, Err: errors.Errorf("file %q has nil blob list", node.Name)})
|
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()
|
c.blobRefs.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
case "dir":
|
case restic.NodeTypeDir:
|
||||||
if node.Subtree == nil {
|
if node.Subtree == nil {
|
||||||
errs = append(errs, &Error{TreeID: id, Err: errors.Errorf("dir node %q has no subtree", node.Name)})
|
errs = append(errs, &Error{TreeID: id, Err: errors.Errorf("dir node %q has no subtree", node.Name)})
|
||||||
continue
|
continue
|
||||||
@ -391,7 +391,7 @@ func (c *Checker) checkTree(id restic.ID, tree *restic.Tree) (errs []error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
case "symlink", "socket", "chardev", "dev", "fifo":
|
case restic.NodeTypeSymlink, restic.NodeTypeSocket, restic.NodeTypeCharDev, restic.NodeTypeDev, restic.NodeTypeFifo:
|
||||||
// nothing to check
|
// nothing to check
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -482,7 +482,7 @@ func TestCheckerBlobTypeConfusion(t *testing.T) {
|
|||||||
|
|
||||||
damagedNode := &restic.Node{
|
damagedNode := &restic.Node{
|
||||||
Name: "damaged",
|
Name: "damaged",
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
Mode: 0644,
|
Mode: 0644,
|
||||||
Size: 42,
|
Size: 42,
|
||||||
Content: restic.IDs{restic.TestParseID("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")},
|
Content: restic.IDs{restic.TestParseID("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")},
|
||||||
@ -507,14 +507,14 @@ func TestCheckerBlobTypeConfusion(t *testing.T) {
|
|||||||
|
|
||||||
malNode := &restic.Node{
|
malNode := &restic.Node{
|
||||||
Name: "aaaaa",
|
Name: "aaaaa",
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
Mode: 0644,
|
Mode: 0644,
|
||||||
Size: uint64(len(buf)),
|
Size: uint64(len(buf)),
|
||||||
Content: restic.IDs{id},
|
Content: restic.IDs{id},
|
||||||
}
|
}
|
||||||
dirNode := &restic.Node{
|
dirNode := &restic.Node{
|
||||||
Name: "bbbbb",
|
Name: "bbbbb",
|
||||||
Type: "dir",
|
Type: restic.NodeTypeDir,
|
||||||
Mode: 0755,
|
Mode: 0755,
|
||||||
Subtree: &id,
|
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 this is no directory we are finished
|
||||||
if !IsDir(root) {
|
if root.Type != restic.NodeTypeDir {
|
||||||
return nil
|
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)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,18 +176,3 @@ func (d *Dumper) writeNode(ctx context.Context, w io.Writer, node *restic.Node)
|
|||||||
|
|
||||||
return wg.Wait()
|
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
|
header.Mode |= cISVTX
|
||||||
}
|
}
|
||||||
|
|
||||||
if IsFile(node) {
|
if node.Type == restic.NodeTypeFile {
|
||||||
header.Typeflag = tar.TypeReg
|
header.Typeflag = tar.TypeReg
|
||||||
}
|
}
|
||||||
|
|
||||||
if IsLink(node) {
|
if node.Type == restic.NodeTypeSymlink {
|
||||||
header.Typeflag = tar.TypeSymlink
|
header.Typeflag = tar.TypeSymlink
|
||||||
header.Linkname = node.LinkTarget
|
header.Linkname = node.LinkTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
if IsDir(node) {
|
if node.Type == restic.NodeTypeDir {
|
||||||
header.Typeflag = tar.TypeDir
|
header.Typeflag = tar.TypeDir
|
||||||
header.Name += "/"
|
header.Name += "/"
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/fs"
|
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
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)
|
return fmt.Errorf("foldernames must end with separator got %v", hdr.Name)
|
||||||
}
|
}
|
||||||
case tar.TypeSymlink:
|
case tar.TypeSymlink:
|
||||||
target, err := fs.Readlink(matchPath)
|
target, err := os.Readlink(matchPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -124,7 +123,7 @@ func TestFieldTooLong(t *testing.T) {
|
|||||||
node := restic.Node{
|
node := restic.Node{
|
||||||
Name: "file_with_xattr",
|
Name: "file_with_xattr",
|
||||||
Path: "/file_with_xattr",
|
Path: "/file_with_xattr",
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
Mode: 0644,
|
Mode: 0644,
|
||||||
ExtendedAttributes: []restic.ExtendedAttribute{
|
ExtendedAttributes: []restic.ExtendedAttribute{
|
||||||
{
|
{
|
||||||
|
@ -40,7 +40,7 @@ func (d *Dumper) dumpNodeZip(ctx context.Context, node *restic.Node, zw *zip.Wri
|
|||||||
}
|
}
|
||||||
header.SetMode(node.Mode)
|
header.SetMode(node.Mode)
|
||||||
|
|
||||||
if IsDir(node) {
|
if node.Type == restic.NodeTypeDir {
|
||||||
header.Name += "/"
|
header.Name += "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ func (d *Dumper) dumpNodeZip(ctx context.Context, node *restic.Node, zw *zip.Wri
|
|||||||
return errors.Wrap(err, "ZipHeader")
|
return errors.Wrap(err, "ZipHeader")
|
||||||
}
|
}
|
||||||
|
|
||||||
if IsLink(node) {
|
if node.Type == restic.NodeTypeSymlink {
|
||||||
if _, err = w.Write([]byte(node.LinkTarget)); err != nil {
|
if _, err = w.Write([]byte(node.LinkTarget)); err != nil {
|
||||||
return errors.Wrap(err, "Write")
|
return errors.Wrap(err, "Write")
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/fs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestWriteZip(t *testing.T) {
|
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)
|
return fmt.Errorf("foldernames must end with separator got %v", f.Name)
|
||||||
}
|
}
|
||||||
case f.Mode()&os.ModeSymlink != 0:
|
case f.Mode()&os.ModeSymlink != 0:
|
||||||
target, err := fs.Readlink(matchPath)
|
target, err := os.Readlink(matchPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -56,14 +56,14 @@ var (
|
|||||||
errEaValueTooLarge = errors.New("extended attribute value too large")
|
errEaValueTooLarge = errors.New("extended attribute value too large")
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExtendedAttribute represents a single Windows EA.
|
// extendedAttribute represents a single Windows EA.
|
||||||
type ExtendedAttribute struct {
|
type extendedAttribute struct {
|
||||||
Name string
|
Name string
|
||||||
Value []byte
|
Value []byte
|
||||||
Flags uint8
|
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
|
var info fileFullEaInformation
|
||||||
err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info)
|
err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -90,9 +90,9 @@ func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) {
|
|||||||
return ea, nb, err
|
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.
|
// 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 {
|
for len(b) != 0 {
|
||||||
ea, nb, err := parseEa(b)
|
ea, nb, err := parseEa(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -105,7 +105,7 @@ func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) {
|
|||||||
return eas, err
|
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) {
|
if int(uint8(len(ea.Name))) != len(ea.Name) {
|
||||||
return errEaNameTooLarge
|
return errEaNameTooLarge
|
||||||
}
|
}
|
||||||
@ -153,9 +153,9 @@ func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error {
|
|||||||
return nil
|
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.
|
// buffer for use with BackupWrite, ZwSetEaFile, etc.
|
||||||
func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) {
|
func encodeExtendedAttributes(eas []extendedAttribute) ([]byte, error) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
for i := range eas {
|
for i := range eas {
|
||||||
last := false
|
last := false
|
||||||
@ -217,11 +217,11 @@ const (
|
|||||||
STATUS_NO_EAS_ON_FILE = -1073741742
|
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).
|
// `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 extended file attribute names in windows are case-insensitive and when fetching
|
||||||
// the attributes the names are generally returned in UPPER case.
|
// 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
|
// default buffer size to start with
|
||||||
bufLen := 1024
|
bufLen := 1024
|
||||||
buf := make([]byte, bufLen)
|
buf := make([]byte, bufLen)
|
||||||
@ -246,13 +246,13 @@ func GetFileEA(handle windows.Handle) ([]ExtendedAttribute, error) {
|
|||||||
}
|
}
|
||||||
break
|
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).
|
// handle must have been opened with the file access flag FILE_WRITE_EA(0x10).
|
||||||
func SetFileEA(handle windows.Handle, attrs []ExtendedAttribute) error {
|
func fsetEA(handle windows.Handle, attrs []extendedAttribute) error {
|
||||||
encodedEA, err := EncodeExtendedAttributes(attrs)
|
encodedEA, err := encodeExtendedAttributes(attrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to encoded extended attributes: %w", err)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// PathSupportsExtendedAttributes returns true if the path supports extended attributes.
|
// pathSupportsExtendedAttributes returns true if the path supports extended attributes.
|
||||||
func PathSupportsExtendedAttributes(path string) (supported bool, err error) {
|
func pathSupportsExtendedAttributes(path string) (supported bool, err error) {
|
||||||
var fileSystemFlags uint32
|
var fileSystemFlags uint32
|
||||||
utf16Path, err := windows.UTF16PtrFromString(path)
|
utf16Path, err := windows.UTF16PtrFromString(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -300,8 +300,8 @@ func PathSupportsExtendedAttributes(path string) (supported bool, err error) {
|
|||||||
return supported, nil
|
return supported, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVolumePathName returns the volume path name for the given path.
|
// getVolumePathName returns the volume path name for the given path.
|
||||||
func GetVolumePathName(path string) (volumeName string, err error) {
|
func getVolumePathName(path string) (volumeName string, err error) {
|
||||||
utf16Path, err := windows.UTF16PtrFromString(path)
|
utf16Path, err := windows.UTF16PtrFromString(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -46,7 +46,7 @@ import (
|
|||||||
// under MIT license.
|
// under MIT license.
|
||||||
|
|
||||||
var (
|
var (
|
||||||
testEas = []ExtendedAttribute{
|
testEas = []extendedAttribute{
|
||||||
{Name: "foo", Value: []byte("bar")},
|
{Name: "foo", Value: []byte("bar")},
|
||||||
{Name: "fizz", Value: []byte("buzz")},
|
{Name: "fizz", Value: []byte("buzz")},
|
||||||
}
|
}
|
||||||
@ -58,14 +58,14 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestRoundTripEas(t *testing.T) {
|
func TestRoundTripEas(t *testing.T) {
|
||||||
b, err := EncodeExtendedAttributes(testEas)
|
b, err := encodeExtendedAttributes(testEas)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(testEasEncoded, b) {
|
if !reflect.DeepEqual(testEasEncoded, b) {
|
||||||
t.Fatalf("Encoded mismatch %v %v", testEasEncoded, b)
|
t.Fatalf("Encoded mismatch %v %v", testEasEncoded, b)
|
||||||
}
|
}
|
||||||
eas, err := DecodeExtendedAttributes(b)
|
eas, err := decodeExtendedAttributes(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -75,7 +75,7 @@ func TestRoundTripEas(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEasDontNeedPaddingAtEnd(t *testing.T) {
|
func TestEasDontNeedPaddingAtEnd(t *testing.T) {
|
||||||
eas, err := DecodeExtendedAttributes(testEasNotPadded)
|
eas, err := decodeExtendedAttributes(testEasNotPadded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -85,21 +85,21 @@ func TestEasDontNeedPaddingAtEnd(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTruncatedEasFailCorrectly(t *testing.T) {
|
func TestTruncatedEasFailCorrectly(t *testing.T) {
|
||||||
_, err := DecodeExtendedAttributes(testEasTruncated)
|
_, err := decodeExtendedAttributes(testEasTruncated)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error")
|
t.Fatal("expected error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNilEasEncodeAndDecodeAsNil(t *testing.T) {
|
func TestNilEasEncodeAndDecodeAsNil(t *testing.T) {
|
||||||
b, err := EncodeExtendedAttributes(nil)
|
b, err := encodeExtendedAttributes(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if len(b) != 0 {
|
if len(b) != 0 {
|
||||||
t.Fatal("expected empty")
|
t.Fatal("expected empty")
|
||||||
}
|
}
|
||||||
eas, err := DecodeExtendedAttributes(nil)
|
eas, err := decodeExtendedAttributes(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -178,8 +178,8 @@ func setupTestFolder(t *testing.T) string {
|
|||||||
return testfolderPath
|
return testfolderPath
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateTestEAs(t *testing.T, nAttrs int, path string) []ExtendedAttribute {
|
func generateTestEAs(t *testing.T, nAttrs int, path string) []extendedAttribute {
|
||||||
testEAs := make([]ExtendedAttribute, nAttrs)
|
testEAs := make([]extendedAttribute, nAttrs)
|
||||||
for i := 0; i < nAttrs; i++ {
|
for i := 0; i < nAttrs; i++ {
|
||||||
testEAs[i].Name = fmt.Sprintf("TESTEA%d", i+1)
|
testEAs[i].Name = fmt.Sprintf("TESTEA%d", i+1)
|
||||||
testEAs[i].Value = make([]byte, getRandomInt())
|
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) {
|
func testSetGetEA(t *testing.T, path string, handle windows.Handle, testEAs []extendedAttribute) {
|
||||||
if err := SetFileEA(handle, testEAs); err != nil {
|
if err := fsetEA(handle, testEAs); err != nil {
|
||||||
t.Fatalf("set EA for path %s failed: %s", path, err)
|
t.Fatalf("set EA for path %s failed: %s", path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
readEAs, err := GetFileEA(handle)
|
readEAs, err := fgetEA(handle)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("get EA for path %s failed: %s", path, err)
|
t.Fatalf("get EA for path %s failed: %s", path, err)
|
||||||
}
|
}
|
||||||
@ -262,7 +262,7 @@ func TestPathSupportsExtendedAttributes(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
supported, err := PathSupportsExtendedAttributes(tc.path)
|
supported, err := pathSupportsExtendedAttributes(tc.path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -273,7 +273,7 @@ func TestPathSupportsExtendedAttributes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test with an invalid path
|
// Test with an invalid path
|
||||||
_, err := PathSupportsExtendedAttributes("Z:\\NonExistentPath-UAS664da5s4dyu56das45f5as")
|
_, err := pathSupportsExtendedAttributes("Z:\\NonExistentPath-UAS664da5s4dyu56das45f5as")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Expected an error for non-existent path, but got 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 {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
volumeName, err := GetVolumePathName(tc.path)
|
volumeName, err := getVolumePathName(tc.path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v", err)
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -316,7 +316,7 @@ func TestGetVolumePathName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test with an invalid path
|
// Test with an invalid path
|
||||||
_, err := GetVolumePathName("Z:\\NonExistentPath")
|
_, err := getVolumePathName("Z:\\NonExistentPath")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Expected an error for non-existent path, but got nil")
|
t.Error("Expected an error for non-existent path, but got nil")
|
||||||
}
|
}
|
||||||
|
@ -3,15 +3,8 @@ package fs
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"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,
|
// 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
|
// 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,
|
// 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)
|
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.
|
// Remove removes the named file or directory.
|
||||||
// If there is an error, it will be of type *PathError.
|
// If there is an error, it will be of type *PathError.
|
||||||
func Remove(name string) error {
|
func Remove(name string) error {
|
||||||
@ -40,32 +27,12 @@ func RemoveAll(path string) error {
|
|||||||
return os.RemoveAll(fixpath(path))
|
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.
|
// Link creates newname as a hard link to oldname.
|
||||||
// If there is an error, it will be of type *LinkError.
|
// If there is an error, it will be of type *LinkError.
|
||||||
func Link(oldname, newname string) error {
|
func Link(oldname, newname string) error {
|
||||||
return os.Link(fixpath(oldname), fixpath(newname))
|
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.
|
// Lstat returns the FileInfo structure describing the named file.
|
||||||
// If the file is a symbolic link, the returned FileInfo
|
// If the file is a symbolic link, the returned FileInfo
|
||||||
// describes the symbolic link. Lstat makes no attempt to follow the link.
|
// 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))
|
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
|
// OpenFile is the generalized open call; most users will use Open
|
||||||
// or Create instead. It opens the named file with specified flag
|
// or Create instead. It opens the named file with specified flag
|
||||||
// (O_RDONLY etc.) and perm, (0666 etc.) if applicable. If successful,
|
// (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)
|
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.
|
// IsAccessDenied checks if the error is due to permission error.
|
||||||
func IsAccessDenied(err error) bool {
|
func IsAccessDenied(err error) bool {
|
||||||
return os.IsPermission(err)
|
return os.IsPermission(err)
|
||||||
|
@ -37,8 +37,8 @@ func isNotSupported(err error) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chmod changes the mode of the named file to mode.
|
// 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 {
|
||||||
err := os.Chmod(fixpath(name), mode)
|
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)
|
// 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"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/restic"
|
||||||
"golang.org/x/sys/windows"
|
"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.
|
// 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)
|
return os.Chmod(fixpath(name), mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearSystem removes the system attribute from the file.
|
// clearSystem removes the system attribute from the file.
|
||||||
func ClearSystem(path string) error {
|
func clearSystem(path string) error {
|
||||||
return ClearAttribute(path, windows.FILE_ATTRIBUTE_SYSTEM)
|
return clearAttribute(path, windows.FILE_ATTRIBUTE_SYSTEM)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearAttribute removes the specified attribute from the file.
|
// clearAttribute removes the specified attribute from the file.
|
||||||
func ClearAttribute(path string, attribute uint32) error {
|
func clearAttribute(path string, attribute uint32) error {
|
||||||
ptr, err := windows.UTF16PtrFromString(fixpath(path))
|
ptr, err := windows.UTF16PtrFromString(fixpath(path))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -104,8 +105,8 @@ func ClearAttribute(path string, attribute uint32) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenHandleForEA return a file handle for file or dir for setting/getting EAs
|
// openHandleForEA return a file handle for file or dir for setting/getting EAs
|
||||||
func OpenHandleForEA(nodeType, path string, writeAccess bool) (handle windows.Handle, err error) {
|
func openHandleForEA(nodeType restic.NodeType, path string, writeAccess bool) (handle windows.Handle, err error) {
|
||||||
path = fixpath(path)
|
path = fixpath(path)
|
||||||
fileAccess := windows.FILE_READ_EA
|
fileAccess := windows.FILE_READ_EA
|
||||||
if writeAccess {
|
if writeAccess {
|
||||||
@ -113,10 +114,10 @@ func OpenHandleForEA(nodeType, path string, writeAccess bool) (handle windows.Ha
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch nodeType {
|
switch nodeType {
|
||||||
case "file":
|
case restic.NodeTypeFile:
|
||||||
utf16Path := windows.StringToUTF16Ptr(path)
|
utf16Path := windows.StringToUTF16Ptr(path)
|
||||||
handle, err = windows.CreateFile(utf16Path, uint32(fileAccess), 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, 0)
|
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)
|
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)
|
handle, err = windows.CreateFile(utf16Path, uint32(fileAccess), 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_BACKUP_SEMANTICS, 0)
|
||||||
default:
|
default:
|
||||||
|
@ -79,7 +79,7 @@ func parseMountPoints(list string, msgError ErrorHandler) (volumes map[string]st
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, s := range strings.Split(list, ";") {
|
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))
|
msgError(s, errors.Errorf("failed to parse vss.exclude-volumes [%s]: %s", s, err))
|
||||||
} else {
|
} else {
|
||||||
if volumes == nil {
|
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)
|
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) {
|
func (fs *LocalVss) Stat(name string) (os.FileInfo, error) {
|
||||||
return os.Stat(fs.snapshotPath(name))
|
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) {
|
func (fs *LocalVss) Lstat(name string) (os.FileInfo, error) {
|
||||||
return os.Lstat(fs.snapshotPath(name))
|
return os.Lstat(fs.snapshotPath(name))
|
||||||
}
|
}
|
||||||
@ -146,7 +146,7 @@ func (fs *LocalVss) isMountPointIncluded(mountPoint string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
volume, err := GetVolumeNameForVolumeMountPoint(mountPoint)
|
volume, err := getVolumeNameForVolumeMountPoint(mountPoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fs.msgError(mountPoint, errors.Errorf("failed to get volume from mount point [%s]: %s", mountPoint, err))
|
fs.msgError(mountPoint, errors.Errorf("failed to get volume from mount point [%s]: %s", mountPoint, err))
|
||||||
return true
|
return true
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
//go:build windows
|
||||||
// +build windows
|
// +build windows
|
||||||
|
|
||||||
package fs
|
package fs
|
||||||
@ -120,10 +121,10 @@ func TestVSSConfig(t *testing.T) {
|
|||||||
func TestParseMountPoints(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}\}\\$`)
|
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
|
// It's not a good idea to test functions based on getVolumeNameForVolumeMountPoint by calling
|
||||||
// GetVolumeNameForVolumeMountPoint itself, but we have restricted test environment:
|
// getVolumeNameForVolumeMountPoint itself, but we have restricted test environment:
|
||||||
// cannot manage volumes and can only be sure that the mount point C:\ exists
|
// 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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -229,22 +229,10 @@ type fakeFile struct {
|
|||||||
// ensure that fakeFile implements File
|
// ensure that fakeFile implements File
|
||||||
var _ File = fakeFile{}
|
var _ File = fakeFile{}
|
||||||
|
|
||||||
func (f fakeFile) Fd() uintptr {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f fakeFile) Readdirnames(_ int) ([]string, error) {
|
func (f fakeFile) Readdirnames(_ int) ([]string, error) {
|
||||||
return nil, pathError("readdirnames", f.name, os.ErrInvalid)
|
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) {
|
func (f fakeFile) Read(_ []byte) (int, error) {
|
||||||
return 0, pathError("read", f.name, os.ErrInvalid)
|
return 0, pathError("read", f.name, os.ErrInvalid)
|
||||||
}
|
}
|
||||||
@ -279,13 +267,6 @@ func (d fakeDir) Readdirnames(n int) ([]string, error) {
|
|||||||
return names, nil
|
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.
|
// fakeFileInfo implements the bare minimum of os.FileInfo.
|
||||||
type fakeFileInfo struct {
|
type fakeFileInfo struct {
|
||||||
name string
|
name string
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/restic/restic/internal/errors"
|
"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
|
// a io.ReadCloser. Close() waits for the command to terminate, reporting
|
||||||
// any error back to the caller.
|
// any error back to the caller.
|
||||||
type CommandReader struct {
|
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) {
|
func checkFileInfo(t testing.TB, fi os.FileInfo, filename string, modtime time.Time, mode os.FileMode, isdir bool) {
|
||||||
if fi.IsDir() != isdir {
|
if fi.IsDir() != isdir {
|
||||||
t.Errorf("IsDir returned %t, want %t", 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})
|
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",
|
name: "file/OpenFile",
|
||||||
f: func(t *testing.T, fs FS) {
|
f: func(t *testing.T, fs FS) {
|
||||||
|
@ -29,10 +29,7 @@ type File interface {
|
|||||||
io.Reader
|
io.Reader
|
||||||
io.Closer
|
io.Closer
|
||||||
|
|
||||||
Fd() uintptr
|
|
||||||
Readdirnames(n int) ([]string, error)
|
Readdirnames(n int) ([]string, error)
|
||||||
Readdir(int) ([]os.FileInfo, error)
|
|
||||||
Seek(int64, int) (int64, error)
|
|
||||||
Stat() (os.FileInfo, error)
|
Stat() (os.FileInfo, error)
|
||||||
Name() string
|
Name() string
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
@ -25,7 +24,7 @@ func NodeFromFileInfo(path string, fi os.FileInfo, ignoreXattrListError bool) (*
|
|||||||
}
|
}
|
||||||
|
|
||||||
node.Type = nodeTypeFromFileInfo(fi)
|
node.Type = nodeTypeFromFileInfo(fi)
|
||||||
if node.Type == "file" {
|
if node.Type == restic.NodeTypeFile {
|
||||||
node.Size = uint64(fi.Size())
|
node.Size = uint64(fi.Size())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,32 +32,31 @@ func NodeFromFileInfo(path string, fi os.FileInfo, ignoreXattrListError bool) (*
|
|||||||
return node, err
|
return node, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeTypeFromFileInfo(fi os.FileInfo) string {
|
func nodeTypeFromFileInfo(fi os.FileInfo) restic.NodeType {
|
||||||
switch fi.Mode() & os.ModeType {
|
switch fi.Mode() & os.ModeType {
|
||||||
case 0:
|
case 0:
|
||||||
return "file"
|
return restic.NodeTypeFile
|
||||||
case os.ModeDir:
|
case os.ModeDir:
|
||||||
return "dir"
|
return restic.NodeTypeDir
|
||||||
case os.ModeSymlink:
|
case os.ModeSymlink:
|
||||||
return "symlink"
|
return restic.NodeTypeSymlink
|
||||||
case os.ModeDevice | os.ModeCharDevice:
|
case os.ModeDevice | os.ModeCharDevice:
|
||||||
return "chardev"
|
return restic.NodeTypeCharDev
|
||||||
case os.ModeDevice:
|
case os.ModeDevice:
|
||||||
return "dev"
|
return restic.NodeTypeDev
|
||||||
case os.ModeNamedPipe:
|
case os.ModeNamedPipe:
|
||||||
return "fifo"
|
return restic.NodeTypeFifo
|
||||||
case os.ModeSocket:
|
case os.ModeSocket:
|
||||||
return "socket"
|
return restic.NodeTypeSocket
|
||||||
case os.ModeIrregular:
|
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 {
|
func nodeFillExtra(node *restic.Node, path string, fi os.FileInfo, ignoreXattrListError bool) error {
|
||||||
stat, ok := toStatT(fi.Sys())
|
if fi.Sys() == nil {
|
||||||
if !ok {
|
|
||||||
// fill minimal info with current values for uid, gid
|
// fill minimal info with current values for uid, gid
|
||||||
node.UID = uint32(os.Getuid())
|
node.UID = uint32(os.Getuid())
|
||||||
node.GID = uint32(os.Getgid())
|
node.GID = uint32(os.Getgid())
|
||||||
@ -66,38 +64,43 @@ func nodeFillExtra(node *restic.Node, path string, fi os.FileInfo, ignoreXattrLi
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
node.Inode = uint64(stat.ino())
|
stat := ExtendedStat(fi)
|
||||||
node.DeviceID = uint64(stat.dev())
|
|
||||||
|
|
||||||
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 {
|
switch node.Type {
|
||||||
case "file":
|
case restic.NodeTypeFile:
|
||||||
node.Size = uint64(stat.size())
|
node.Size = uint64(stat.Size)
|
||||||
node.Links = uint64(stat.nlink())
|
node.Links = stat.Links
|
||||||
case "dir":
|
case restic.NodeTypeDir:
|
||||||
case "symlink":
|
case restic.NodeTypeSymlink:
|
||||||
var err error
|
var err error
|
||||||
node.LinkTarget, err = Readlink(path)
|
node.LinkTarget, err = os.Readlink(fixpath(path))
|
||||||
node.Links = uint64(stat.nlink())
|
node.Links = stat.Links
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
case "dev":
|
case restic.NodeTypeDev:
|
||||||
node.Device = uint64(stat.rdev())
|
node.Device = stat.Device
|
||||||
node.Links = uint64(stat.nlink())
|
node.Links = stat.Links
|
||||||
case "chardev":
|
case restic.NodeTypeCharDev:
|
||||||
node.Device = uint64(stat.rdev())
|
node.Device = stat.Device
|
||||||
node.Links = uint64(stat.nlink())
|
node.Links = stat.Links
|
||||||
case "fifo":
|
case restic.NodeTypeFifo:
|
||||||
case "socket":
|
case restic.NodeTypeSocket:
|
||||||
default:
|
default:
|
||||||
return errors.Errorf("unsupported file type %q", node.Type)
|
return errors.Errorf("unsupported file type %q", node.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
allowExtended, err := nodeFillGenericAttributes(node, path, fi, stat)
|
allowExtended, err := nodeFillGenericAttributes(node, path, &stat)
|
||||||
if allowExtended {
|
if allowExtended {
|
||||||
// Skip processing ExtendedAttributes if allowExtended is false.
|
// Skip processing ExtendedAttributes if allowExtended is false.
|
||||||
err = errors.CombineErrors(err, nodeFillExtendedAttributes(node, path, ignoreXattrListError))
|
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
|
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 (
|
var (
|
||||||
uidLookupCache = make(map[uint32]string)
|
uidLookupCache = make(map[uint32]string)
|
||||||
uidLookupCacheMutex = sync.RWMutex{}
|
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)
|
debug.Log("create node %v at %v", node.Name, path)
|
||||||
|
|
||||||
switch node.Type {
|
switch node.Type {
|
||||||
case "dir":
|
case restic.NodeTypeDir:
|
||||||
if err := nodeCreateDirAt(node, path); err != nil {
|
if err := nodeCreateDirAt(node, path); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "file":
|
case restic.NodeTypeFile:
|
||||||
if err := nodeCreateFileAt(path); err != nil {
|
if err := nodeCreateFileAt(path); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "symlink":
|
case restic.NodeTypeSymlink:
|
||||||
if err := nodeCreateSymlinkAt(node, path); err != nil {
|
if err := nodeCreateSymlinkAt(node, path); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "dev":
|
case restic.NodeTypeDev:
|
||||||
if err := nodeCreateDevAt(node, path); err != nil {
|
if err := nodeCreateDevAt(node, path); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "chardev":
|
case restic.NodeTypeCharDev:
|
||||||
if err := nodeCreateCharDevAt(node, path); err != nil {
|
if err := nodeCreateCharDevAt(node, path); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "fifo":
|
case restic.NodeTypeFifo:
|
||||||
if err := nodeCreateFifoAt(path); err != nil {
|
if err := nodeCreateFifoAt(path); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "socket":
|
case restic.NodeTypeSocket:
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return errors.Errorf("filetype %q not implemented", node.Type)
|
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 {
|
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) {
|
if err != nil && !os.IsExist(err) {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
@ -234,7 +223,7 @@ func nodeCreateFileAt(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func nodeCreateSymlinkAt(node *restic.Node, 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)
|
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)
|
debug.Log("error restoring timestamps for %v: %v", path, err)
|
||||||
if firsterr == nil {
|
if firsterr == nil {
|
||||||
firsterr = err
|
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
|
// 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
|
// calling Chmod below will no longer allow any modifications to be made on the file and the
|
||||||
// calls above would fail.
|
// calls above would fail.
|
||||||
if node.Type != "symlink" {
|
if node.Type != restic.NodeTypeSymlink {
|
||||||
if err := Chmod(path, node.Mode); err != nil {
|
if err := chmod(path, node.Mode); err != nil {
|
||||||
if firsterr == nil {
|
if firsterr == nil {
|
||||||
firsterr = errors.WithStack(err)
|
firsterr = errors.WithStack(err)
|
||||||
}
|
}
|
||||||
@ -316,13 +305,13 @@ func nodeRestoreMetadata(node *restic.Node, path string, warn func(msg string))
|
|||||||
return firsterr
|
return firsterr
|
||||||
}
|
}
|
||||||
|
|
||||||
func NodeRestoreTimestamps(node *restic.Node, path string) error {
|
func nodeRestoreTimestamps(node *restic.Node, path string) error {
|
||||||
var utimes = [...]syscall.Timespec{
|
var utimes = [...]syscall.Timespec{
|
||||||
syscall.NsecToTimespec(node.AccessTime.UnixNano()),
|
syscall.NsecToTimespec(node.AccessTime.UnixNano()),
|
||||||
syscall.NsecToTimespec(node.ModTime.UnixNano()),
|
syscall.NsecToTimespec(node.ModTime.UnixNano()),
|
||||||
}
|
}
|
||||||
|
|
||||||
if node.Type == "symlink" {
|
if node.Type == restic.NodeTypeSymlink {
|
||||||
return nodeRestoreSymlinkTimestamps(path, utimes)
|
return nodeRestoreSymlinkTimestamps(path, utimes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
package fs
|
package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
@ -14,17 +13,6 @@ func nodeRestoreSymlinkTimestamps(_ string, _ [2]syscall.Timespec) error {
|
|||||||
return nil
|
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.
|
// nodeRestoreExtendedAttributes is a no-op on AIX.
|
||||||
func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error {
|
func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error {
|
||||||
return nil
|
return nil
|
||||||
@ -35,17 +23,12 @@ func nodeFillExtendedAttributes(_ *restic.Node, _ string, _ bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isListxattrPermissionError is a no-op on AIX.
|
|
||||||
func isListxattrPermissionError(_ error) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// nodeRestoreGenericAttributes is no-op on AIX.
|
// nodeRestoreGenericAttributes is no-op on AIX.
|
||||||
func nodeRestoreGenericAttributes(node *restic.Node, _ string, warn func(msg string)) error {
|
func nodeRestoreGenericAttributes(node *restic.Node, _ string, warn func(msg string)) error {
|
||||||
return restic.HandleAllUnknownGenericAttributesFound(node.GenericAttributes, warn)
|
return restic.HandleAllUnknownGenericAttributesFound(node.GenericAttributes, warn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// nodeFillGenericAttributes is a no-op on AIX.
|
// 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
|
return true, nil
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,3 @@ import "syscall"
|
|||||||
func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
|
func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
|
||||||
return nil
|
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) {
|
func mknod(path string, mode uint32, dev uint64) (err error) {
|
||||||
return syscall.Mknod(path, mode, dev)
|
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
|
package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
@ -10,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
|
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 {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
@ -30,7 +31,3 @@ func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error
|
|||||||
|
|
||||||
return dir.Close()
|
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
|
package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
@ -11,10 +10,6 @@ func nodeRestoreSymlinkTimestamps(_ string, _ [2]syscall.Timespec) error {
|
|||||||
return nil
|
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.
|
// nodeRestoreExtendedAttributes is a no-op on netbsd.
|
||||||
func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error {
|
func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error {
|
||||||
return nil
|
return nil
|
||||||
@ -25,17 +20,12 @@ func nodeFillExtendedAttributes(_ *restic.Node, _ string, _ bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isListxattrPermissionError is a no-op on netbsd.
|
|
||||||
func isListxattrPermissionError(_ error) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// nodeRestoreGenericAttributes is no-op on netbsd.
|
// nodeRestoreGenericAttributes is no-op on netbsd.
|
||||||
func nodeRestoreGenericAttributes(node *restic.Node, _ string, warn func(msg string)) error {
|
func nodeRestoreGenericAttributes(node *restic.Node, _ string, warn func(msg string)) error {
|
||||||
return restic.HandleAllUnknownGenericAttributesFound(node.GenericAttributes, warn)
|
return restic.HandleAllUnknownGenericAttributesFound(node.GenericAttributes, warn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// nodeFillGenericAttributes is a no-op on netbsd.
|
// 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
|
return true, nil
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package fs
|
package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
@ -11,10 +10,6 @@ func nodeRestoreSymlinkTimestamps(_ string, _ [2]syscall.Timespec) error {
|
|||||||
return nil
|
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.
|
// nodeRestoreExtendedAttributes is a no-op on openbsd.
|
||||||
func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error {
|
func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error {
|
||||||
return nil
|
return nil
|
||||||
@ -25,17 +20,12 @@ func nodeFillExtendedAttributes(_ *restic.Node, _ string, _ bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isListxattrPermissionError is a no-op on openbsd.
|
|
||||||
func isListxattrPermissionError(_ error) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// nodeRestoreGenericAttributes is no-op on openbsd.
|
// nodeRestoreGenericAttributes is no-op on openbsd.
|
||||||
func nodeRestoreGenericAttributes(node *restic.Node, _ string, warn func(msg string)) error {
|
func nodeRestoreGenericAttributes(node *restic.Node, _ string, warn func(msg string)) error {
|
||||||
return restic.HandleAllUnknownGenericAttributesFound(node.GenericAttributes, warn)
|
return restic.HandleAllUnknownGenericAttributesFound(node.GenericAttributes, warn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fillGenericAttributes is a no-op on openbsd.
|
// 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
|
return true, nil
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,3 @@ import "syscall"
|
|||||||
func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
|
func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
|
||||||
return nil
|
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{
|
var nodeTests = []restic.Node{
|
||||||
{
|
{
|
||||||
Name: "testFile",
|
Name: "testFile",
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
Content: restic.IDs{},
|
Content: restic.IDs{},
|
||||||
UID: uint32(os.Getuid()),
|
UID: uint32(os.Getuid()),
|
||||||
GID: uint32(os.Getgid()),
|
GID: uint32(os.Getgid()),
|
||||||
@ -90,7 +90,7 @@ var nodeTests = []restic.Node{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "testSuidFile",
|
Name: "testSuidFile",
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
Content: restic.IDs{},
|
Content: restic.IDs{},
|
||||||
UID: uint32(os.Getuid()),
|
UID: uint32(os.Getuid()),
|
||||||
GID: uint32(os.Getgid()),
|
GID: uint32(os.Getgid()),
|
||||||
@ -101,7 +101,7 @@ var nodeTests = []restic.Node{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "testSuidFile2",
|
Name: "testSuidFile2",
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
Content: restic.IDs{},
|
Content: restic.IDs{},
|
||||||
UID: uint32(os.Getuid()),
|
UID: uint32(os.Getuid()),
|
||||||
GID: uint32(os.Getgid()),
|
GID: uint32(os.Getgid()),
|
||||||
@ -112,7 +112,7 @@ var nodeTests = []restic.Node{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "testSticky",
|
Name: "testSticky",
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
Content: restic.IDs{},
|
Content: restic.IDs{},
|
||||||
UID: uint32(os.Getuid()),
|
UID: uint32(os.Getuid()),
|
||||||
GID: uint32(os.Getgid()),
|
GID: uint32(os.Getgid()),
|
||||||
@ -123,7 +123,7 @@ var nodeTests = []restic.Node{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "testDir",
|
Name: "testDir",
|
||||||
Type: "dir",
|
Type: restic.NodeTypeDir,
|
||||||
Subtree: nil,
|
Subtree: nil,
|
||||||
UID: uint32(os.Getuid()),
|
UID: uint32(os.Getuid()),
|
||||||
GID: uint32(os.Getgid()),
|
GID: uint32(os.Getgid()),
|
||||||
@ -134,7 +134,7 @@ var nodeTests = []restic.Node{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "testSymlink",
|
Name: "testSymlink",
|
||||||
Type: "symlink",
|
Type: restic.NodeTypeSymlink,
|
||||||
LinkTarget: "invalid",
|
LinkTarget: "invalid",
|
||||||
UID: uint32(os.Getuid()),
|
UID: uint32(os.Getuid()),
|
||||||
GID: uint32(os.Getgid()),
|
GID: uint32(os.Getgid()),
|
||||||
@ -148,7 +148,7 @@ var nodeTests = []restic.Node{
|
|||||||
// metadata, so we can test if CreateAt works with pre-existing files.
|
// metadata, so we can test if CreateAt works with pre-existing files.
|
||||||
{
|
{
|
||||||
Name: "testFile",
|
Name: "testFile",
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
Content: restic.IDs{},
|
Content: restic.IDs{},
|
||||||
UID: uint32(os.Getuid()),
|
UID: uint32(os.Getuid()),
|
||||||
GID: uint32(os.Getgid()),
|
GID: uint32(os.Getgid()),
|
||||||
@ -159,7 +159,7 @@ var nodeTests = []restic.Node{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "testDir",
|
Name: "testDir",
|
||||||
Type: "dir",
|
Type: restic.NodeTypeDir,
|
||||||
Subtree: nil,
|
Subtree: nil,
|
||||||
UID: uint32(os.Getuid()),
|
UID: uint32(os.Getuid()),
|
||||||
GID: uint32(os.Getgid()),
|
GID: uint32(os.Getgid()),
|
||||||
@ -170,7 +170,7 @@ var nodeTests = []restic.Node{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "testXattrFile",
|
Name: "testXattrFile",
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
Content: restic.IDs{},
|
Content: restic.IDs{},
|
||||||
UID: uint32(os.Getuid()),
|
UID: uint32(os.Getuid()),
|
||||||
GID: uint32(os.Getgid()),
|
GID: uint32(os.Getgid()),
|
||||||
@ -184,7 +184,7 @@ var nodeTests = []restic.Node{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "testXattrDir",
|
Name: "testXattrDir",
|
||||||
Type: "dir",
|
Type: restic.NodeTypeDir,
|
||||||
Subtree: nil,
|
Subtree: nil,
|
||||||
UID: uint32(os.Getuid()),
|
UID: uint32(os.Getuid()),
|
||||||
GID: uint32(os.Getgid()),
|
GID: uint32(os.Getgid()),
|
||||||
@ -198,7 +198,7 @@ var nodeTests = []restic.Node{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "testXattrFileMacOSResourceFork",
|
Name: "testXattrFileMacOSResourceFork",
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
Content: restic.IDs{},
|
Content: restic.IDs{},
|
||||||
UID: uint32(os.Getuid()),
|
UID: uint32(os.Getuid()),
|
||||||
GID: uint32(os.Getgid()),
|
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)
|
"%v: UID doesn't match (%v != %v)", test.Type, test.UID, n2.UID)
|
||||||
rtest.Assert(t, test.GID == n2.GID,
|
rtest.Assert(t, test.GID == n2.GID,
|
||||||
"%v: GID doesn't match (%v != %v)", test.Type, 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)).
|
// On OpenBSD only root can set sticky bit (see sticky(8)).
|
||||||
if runtime.GOOS != "openbsd" && runtime.GOOS != "netbsd" && runtime.GOOS != "solaris" && test.Name == "testSticky" {
|
if runtime.GOOS != "openbsd" && runtime.GOOS != "netbsd" && runtime.GOOS != "solaris" && test.Name == "testSticky" {
|
||||||
rtest.Assert(t, test.Mode == n2.Mode,
|
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
|
var equal bool
|
||||||
|
|
||||||
// Go currently doesn't support setting timestamps of symbolic links on darwin and bsd
|
// Go currently doesn't support setting timestamps of symbolic links on darwin and bsd
|
||||||
if nodeType == "symlink" {
|
if nodeType == restic.NodeTypeSymlink {
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "darwin", "freebsd", "openbsd", "netbsd", "solaris":
|
case "darwin", "freebsd", "openbsd", "netbsd", "solaris":
|
||||||
return
|
return
|
||||||
|
@ -5,27 +5,8 @@ package fs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func lchown(name string, uid, gid int) error {
|
func lchown(name string, uid, gid int) error {
|
||||||
return os.Lchown(name, uid, gid)
|
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
|
package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
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
|
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()
|
t.Helper()
|
||||||
|
|
||||||
|
stat := fi.Sys().(*syscall.Stat_t)
|
||||||
|
|
||||||
if uint32(node.Mode.Perm()) != uint32(stat.Mode&0777) {
|
if uint32(node.Mode.Perm()) != uint32(stat.Mode&0777) {
|
||||||
t.Errorf("Mode does not match, want %v, got %v", stat.Mode&0777, node.Mode)
|
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)
|
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)
|
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
|
// use the os dependent function to compare the timestamps
|
||||||
s, ok := toStatT(stat)
|
s := ExtendedStat(fi)
|
||||||
if !ok {
|
if node.ModTime != s.ModTime {
|
||||||
return
|
t.Errorf("ModTime does not match, want %v, got %v", s.ModTime, node.ModTime)
|
||||||
}
|
}
|
||||||
|
if node.ChangeTime != s.ChangeTime {
|
||||||
mtime := s.mtim()
|
t.Errorf("ChangeTime does not match, want %v, got %v", s.ChangeTime, node.ChangeTime)
|
||||||
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.AccessTime != s.AccessTime {
|
||||||
ctime := s.ctim()
|
t.Errorf("AccessTime does not match, want %v, got %v", s.AccessTime, node.AccessTime)
|
||||||
if node.ChangeTime != time.Unix(ctime.Unix()) {
|
|
||||||
t.Errorf("ChangeTime does not match, want %v, got %v", time.Unix(ctime.Unix()), node.ChangeTime)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
if node.Device != uint64(stat.Rdev) {
|
||||||
t.Errorf("Rdev does not match, want %v, got %v", stat.Rdev, node.Device)
|
t.Errorf("Rdev does not match, want %v, got %v", stat.Rdev, node.Device)
|
||||||
}
|
}
|
||||||
@ -123,23 +117,17 @@ func TestNodeFromFileInfo(t *testing.T) {
|
|||||||
return
|
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)
|
node, err := NodeFromFileInfo(test.filename, fi, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch node.Type {
|
switch node.Type {
|
||||||
case "file", "symlink":
|
case restic.NodeTypeFile, restic.NodeTypeSymlink:
|
||||||
checkFile(t, s, node)
|
checkFile(t, fi, node)
|
||||||
case "dev", "chardev":
|
case restic.NodeTypeDev, restic.NodeTypeCharDev:
|
||||||
checkFile(t, s, node)
|
checkFile(t, fi, node)
|
||||||
checkDevice(t, s, node)
|
checkDevice(t, fi, node)
|
||||||
default:
|
default:
|
||||||
t.Fatalf("invalid node type %q", node.Type)
|
t.Fatalf("invalid node type %q", node.Type)
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,8 @@ package fs
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
@ -18,17 +16,6 @@ import (
|
|||||||
"golang.org/x/sys/windows"
|
"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 (
|
var (
|
||||||
modAdvapi32 = syscall.NewLazyDLL("advapi32.dll")
|
modAdvapi32 = syscall.NewLazyDLL("advapi32.dll")
|
||||||
procEncryptFile = modAdvapi32.NewProc("EncryptFileW")
|
procEncryptFile = modAdvapi32.NewProc("EncryptFileW")
|
||||||
@ -58,7 +45,7 @@ func lchown(_ string, _ int, _ int) (err error) {
|
|||||||
// restoreSymlinkTimestamps restores timestamps for symlinks
|
// restoreSymlinkTimestamps restores timestamps for symlinks
|
||||||
func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
|
func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
|
||||||
// tweaked version of UtimesNano from go/src/syscall/syscall_windows.go
|
// 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 {
|
if e != nil {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
@ -85,9 +72,9 @@ func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error
|
|||||||
func nodeRestoreExtendedAttributes(node *restic.Node, path string) (err error) {
|
func nodeRestoreExtendedAttributes(node *restic.Node, path string) (err error) {
|
||||||
count := len(node.ExtendedAttributes)
|
count := len(node.ExtendedAttributes)
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
eas := make([]ExtendedAttribute, count)
|
eas := make([]extendedAttribute, count)
|
||||||
for i, attr := range node.ExtendedAttributes {
|
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 {
|
if errExt := restoreExtendedAttributes(node.Type, path, eas); errExt != nil {
|
||||||
return errExt
|
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.
|
// fill extended attributes in the node. This also includes the Generic attributes for windows.
|
||||||
func nodeFillExtendedAttributes(node *restic.Node, path string, _ bool) (err error) {
|
func nodeFillExtendedAttributes(node *restic.Node, path string, _ bool) (err error) {
|
||||||
var fileHandle windows.Handle
|
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
|
return nil
|
||||||
}
|
}
|
||||||
if err != 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
|
defer closeFileHandle(fileHandle, path) // Replaced inline defer with named function call
|
||||||
//Get the windows Extended Attributes using the file handle
|
//Get the windows Extended Attributes using the file handle
|
||||||
var extAtts []ExtendedAttribute
|
var extAtts []extendedAttribute
|
||||||
extAtts, err = GetFileEA(fileHandle)
|
extAtts, err = fgetEA(fileHandle)
|
||||||
debug.Log("fillExtendedAttributes(%v) %v", path, extAtts)
|
debug.Log("fillExtendedAttributes(%v) %v", path, extAtts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Errorf("get EA failed for path %v, with: %v", path, err)
|
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.
|
// 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.
|
// 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
|
var fileHandle windows.Handle
|
||||||
if fileHandle, err = OpenHandleForEA(nodeType, path, true); fileHandle == 0 {
|
if fileHandle, err = openHandleForEA(nodeType, path, true); fileHandle == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err != 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
|
defer closeFileHandle(fileHandle, path) // Replaced inline defer with named function call
|
||||||
|
|
||||||
// clear old unexpected xattrs by setting them to an empty value
|
// clear old unexpected xattrs by setting them to an empty value
|
||||||
oldEAs, err := GetFileEA(fileHandle)
|
oldEAs, err := fgetEA(fileHandle)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -165,50 +152,16 @@ func restoreExtendedAttributes(nodeType, path string, eas []ExtendedAttribute) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
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 errors.Errorf("set EA failed for path %v, with: %v", path, err)
|
||||||
}
|
}
|
||||||
return nil
|
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
|
// restoreGenericAttributes restores generic attributes for Windows
|
||||||
func nodeRestoreGenericAttributes(node *restic.Node, path string, warn func(msg string)) (err error) {
|
func nodeRestoreGenericAttributes(node *restic.Node, path string, warn func(msg string)) (err error) {
|
||||||
if len(node.GenericAttributes) == 0 {
|
if len(node.GenericAttributes) == 0 {
|
||||||
@ -230,7 +183,7 @@ func nodeRestoreGenericAttributes(node *restic.Node, path string, warn func(msg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if windowsAttributes.SecurityDescriptor != nil {
|
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))
|
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.
|
// 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()
|
waValue := reflect.ValueOf(&windowsAttributes).Elem()
|
||||||
unknownAttribs, err = restic.GenericAttributesToOSAttrs(attrs, reflect.TypeOf(windowsAttributes), &waValue, "windows")
|
unknownAttribs, err = restic.GenericAttributesToOSAttrs(attrs, reflect.TypeOf(windowsAttributes), &waValue, "windows")
|
||||||
return windowsAttributes, unknownAttribs, err
|
return windowsAttributes, unknownAttribs, err
|
||||||
@ -296,7 +249,7 @@ func fixEncryptionAttribute(path string, attrs *uint32, pathPointer *uint16) (er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to encrypt file: failed to reset permissions: %s : %v", path, err)
|
return fmt.Errorf("failed to encrypt file: failed to reset permissions: %s : %v", path, err)
|
||||||
}
|
}
|
||||||
err = ClearSystem(path)
|
err = clearSystem(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to encrypt file: failed to clear system flag: %s : %v", path, err)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to encrypt file: failed to reset permissions: %s : %v", path, err)
|
return fmt.Errorf("failed to encrypt file: failed to reset permissions: %s : %v", path, err)
|
||||||
}
|
}
|
||||||
err = ClearSystem(path)
|
err = clearSystem(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to decrypt file: failed to clear system flag: %s : %v", path, err)
|
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.
|
// Created time and Security Descriptors.
|
||||||
// It also checks if the volume supports extended attributes and stores the result in a map
|
// 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.
|
// 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), ":") {
|
if strings.Contains(filepath.Base(path), ":") {
|
||||||
// Do not process for Alternate Data Streams in Windows
|
// Do not process for Alternate Data Streams in Windows
|
||||||
// Also do not allow processing of extended attributes for ADS.
|
// 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
|
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
|
// Check EA support and get security descriptor for file/dir only
|
||||||
allowExtended, err = checkAndStoreEASupport(path)
|
allowExtended, err = checkAndStoreEASupport(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if sd, err = GetSecurityDescriptor(path); err != nil {
|
if sd, err = getSecurityDescriptor(path); err != nil {
|
||||||
return allowExtended, err
|
return allowExtended, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
winFI := stat.Sys().(*syscall.Win32FileAttributeData)
|
||||||
|
|
||||||
// Add Windows attributes
|
// Add Windows attributes
|
||||||
node.GenericAttributes, err = WindowsAttrsToGenericAttributes(WindowsAttributes{
|
node.GenericAttributes, err = restic.WindowsAttrsToGenericAttributes(restic.WindowsAttributes{
|
||||||
CreationTime: getCreationTime(fi, path),
|
CreationTime: &winFI.CreationTime,
|
||||||
FileAttributes: &stat.FileAttributes,
|
FileAttributes: &winFI.FileAttributes,
|
||||||
SecurityDescriptor: sd,
|
SecurityDescriptor: sd,
|
||||||
})
|
})
|
||||||
return allowExtended, err
|
return allowExtended, err
|
||||||
@ -422,7 +378,7 @@ func checkAndStoreEASupport(path string) (isEASupportedVolume bool, err error) {
|
|||||||
return eaSupportedValue.(bool), nil
|
return eaSupportedValue.(bool), nil
|
||||||
}
|
}
|
||||||
// If not found, check if EA is supported with manually prepared volume name
|
// 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 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) {
|
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)
|
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
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If an entry is not found, get the actual volume name using the GetVolumePathName function
|
// If an entry is not found, get the actual volume name
|
||||||
volumeNameActual, err := GetVolumePathName(path)
|
volumeNameActual, err := getVolumePathName(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Log("Error getting actual volume name %s for path %s: %v", volumeName, path, err)
|
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.
|
// 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
|
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
|
// 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
|
// Debug log for cases where the prepared volume name is not valid
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Log("Error checking if extended attributes are supported for actual volume name %s: %v", volumeNameActual, err)
|
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
|
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) {
|
func TestRestoreSecurityDescriptors(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
tempDir := t.TempDir()
|
tempDir := t.TempDir()
|
||||||
for i, sd := range TestFileSDs {
|
for i, sd := range testFileSDs {
|
||||||
testRestoreSecurityDescriptor(t, sd, tempDir, "file", fmt.Sprintf("testfile%d", i))
|
testRestoreSecurityDescriptor(t, sd, tempDir, restic.NodeTypeFile, fmt.Sprintf("testfile%d", i))
|
||||||
}
|
}
|
||||||
for i, sd := range TestDirSDs {
|
for i, sd := range testDirSDs {
|
||||||
testRestoreSecurityDescriptor(t, sd, tempDir, "dir", fmt.Sprintf("testdir%d", i))
|
testRestoreSecurityDescriptor(t, sd, tempDir, restic.NodeTypeDir, fmt.Sprintf("testdir%d", i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRestoreSecurityDescriptor(t *testing.T, sd string, tempDir, fileType, fileName string) {
|
func testRestoreSecurityDescriptor(t *testing.T, sd string, tempDir string, fileType restic.NodeType, fileName string) {
|
||||||
// Decode the encoded string SD to get the security descriptor input in bytes.
|
// Decode the encoded string SD to get the security descriptor input in bytes.
|
||||||
sdInputBytes, err := base64.StdEncoding.DecodeString(sd)
|
sdInputBytes, err := base64.StdEncoding.DecodeString(sd)
|
||||||
test.OK(t, errors.Wrapf(err, "Error decoding SD for: %s", fileName))
|
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.
|
// 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))
|
test.OK(t, errors.Wrapf(err, "Error constructing windows attributes for: %s", fileName))
|
||||||
// Construct a Node with the generic attributes.
|
// Construct a Node with the generic attributes.
|
||||||
expectedNode := getNode(fileName, fileType, genericAttributes)
|
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
|
sdByteFromRestoredNode := getWindowsAttr(t, testPath, node).SecurityDescriptor
|
||||||
|
|
||||||
// Get the security descriptor for the test path after the restore.
|
// 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))
|
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.
|
// 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.
|
// 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{
|
return restic.Node{
|
||||||
Name: name,
|
Name: name,
|
||||||
Type: fileType,
|
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)
|
windowsAttributes, unknownAttribs, err := genericAttributesToWindowsAttrs(node.GenericAttributes)
|
||||||
test.OK(t, errors.Wrapf(err, "Error getting windows attr from generic attr: %s", testPath))
|
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)
|
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()
|
path := t.TempDir()
|
||||||
fi, err := os.Lstat(path)
|
fi, err := os.Lstat(path)
|
||||||
test.OK(t, errors.Wrapf(err, "Could not Lstat for path: %s", path))
|
test.OK(t, errors.Wrapf(err, "Could not Lstat for path: %s", path))
|
||||||
creationTimeAttribute := getCreationTime(fi, path)
|
attr := fi.Sys().(*syscall.Win32FileAttributeData)
|
||||||
test.OK(t, errors.Wrapf(err, "Could not get creation time for path: %s", path))
|
creationTimeAttribute := attr.CreationTime
|
||||||
//Using the temp dir creation time as the test creation time for the test file and folder
|
//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) {
|
func TestRestoreFileAttributes(t *testing.T) {
|
||||||
@ -95,7 +95,7 @@ func TestRestoreFileAttributes(t *testing.T) {
|
|||||||
system := uint32(syscall.FILE_ATTRIBUTE_SYSTEM)
|
system := uint32(syscall.FILE_ATTRIBUTE_SYSTEM)
|
||||||
archive := uint32(syscall.FILE_ATTRIBUTE_ARCHIVE)
|
archive := uint32(syscall.FILE_ATTRIBUTE_ARCHIVE)
|
||||||
encrypted := uint32(windows.FILE_ATTRIBUTE_ENCRYPTED)
|
encrypted := uint32(windows.FILE_ATTRIBUTE_ENCRYPTED)
|
||||||
fileAttributes := []WindowsAttributes{
|
fileAttributes := []restic.WindowsAttributes{
|
||||||
//normal
|
//normal
|
||||||
{FileAttributes: &normal},
|
{FileAttributes: &normal},
|
||||||
//hidden
|
//hidden
|
||||||
@ -108,12 +108,12 @@ func TestRestoreFileAttributes(t *testing.T) {
|
|||||||
{FileAttributes: &encrypted},
|
{FileAttributes: &encrypted},
|
||||||
}
|
}
|
||||||
for i, fileAttr := range fileAttributes {
|
for i, fileAttr := range fileAttributes {
|
||||||
genericAttrs, err := WindowsAttrsToGenericAttributes(fileAttr)
|
genericAttrs, err := restic.WindowsAttrsToGenericAttributes(fileAttr)
|
||||||
test.OK(t, err)
|
test.OK(t, err)
|
||||||
expectedNodes := []restic.Node{
|
expectedNodes := []restic.Node{
|
||||||
{
|
{
|
||||||
Name: fmt.Sprintf("testfile%d", i),
|
Name: fmt.Sprintf("testfile%d", i),
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
Mode: 0655,
|
Mode: 0655,
|
||||||
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
||||||
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
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)
|
system = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY | windows.FILE_ATTRIBUTE_SYSTEM)
|
||||||
archive = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY | windows.FILE_ATTRIBUTE_ARCHIVE)
|
archive = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY | windows.FILE_ATTRIBUTE_ARCHIVE)
|
||||||
encrypted = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY | windows.FILE_ATTRIBUTE_ENCRYPTED)
|
encrypted = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY | windows.FILE_ATTRIBUTE_ENCRYPTED)
|
||||||
folderAttributes := []WindowsAttributes{
|
folderAttributes := []restic.WindowsAttributes{
|
||||||
//normal
|
//normal
|
||||||
{FileAttributes: &normal},
|
{FileAttributes: &normal},
|
||||||
//hidden
|
//hidden
|
||||||
@ -141,12 +141,12 @@ func TestRestoreFileAttributes(t *testing.T) {
|
|||||||
{FileAttributes: &encrypted},
|
{FileAttributes: &encrypted},
|
||||||
}
|
}
|
||||||
for i, folderAttr := range folderAttributes {
|
for i, folderAttr := range folderAttributes {
|
||||||
genericAttrs, err := WindowsAttrsToGenericAttributes(folderAttr)
|
genericAttrs, err := restic.WindowsAttrsToGenericAttributes(folderAttr)
|
||||||
test.OK(t, err)
|
test.OK(t, err)
|
||||||
expectedNodes := []restic.Node{
|
expectedNodes := []restic.Node{
|
||||||
{
|
{
|
||||||
Name: fmt.Sprintf("testdirectory%d", i),
|
Name: fmt.Sprintf("testdirectory%d", i),
|
||||||
Type: "dir",
|
Type: restic.NodeTypeDir,
|
||||||
Mode: 0755,
|
Mode: 0755,
|
||||||
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
||||||
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
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) {
|
func runGenericAttributesTest(t *testing.T, tempDir string, genericAttributeName restic.GenericAttributeType, genericAttributeExpected restic.WindowsAttributes, warningExpected bool) {
|
||||||
genericAttributes, err := WindowsAttrsToGenericAttributes(genericAttributeExpected)
|
genericAttributes, err := restic.WindowsAttrsToGenericAttributes(genericAttributeExpected)
|
||||||
test.OK(t, err)
|
test.OK(t, err)
|
||||||
expectedNodes := []restic.Node{
|
expectedNodes := []restic.Node{
|
||||||
{
|
{
|
||||||
Name: "testfile",
|
Name: "testfile",
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
Mode: 0644,
|
Mode: 0644,
|
||||||
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
||||||
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
||||||
@ -173,7 +173,7 @@ func runGenericAttributesTest(t *testing.T, tempDir string, genericAttributeName
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "testdirectory",
|
Name: "testdirectory",
|
||||||
Type: "dir",
|
Type: restic.NodeTypeDir,
|
||||||
Mode: 0755,
|
Mode: 0755,
|
||||||
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
||||||
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
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)
|
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 {
|
for _, testNode := range expectedNodes {
|
||||||
testPath, node := restoreAndGetNode(t, tempDir, &testNode, warningExpected)
|
testPath, node := restoreAndGetNode(t, tempDir, &testNode, warningExpected)
|
||||||
rawMessage := node.GenericAttributes[genericAttr]
|
rawMessage := node.GenericAttributes[genericAttr]
|
||||||
genericAttrsExpected, err := WindowsAttrsToGenericAttributes(genericAttributeExpected)
|
genericAttrsExpected, err := restic.WindowsAttrsToGenericAttributes(genericAttributeExpected)
|
||||||
test.OK(t, err)
|
test.OK(t, err)
|
||||||
rawMessageExpected := genericAttrsExpected[genericAttr]
|
rawMessageExpected := genericAttrsExpected[genericAttr]
|
||||||
test.Equals(t, rawMessageExpected, rawMessage, "Generic attribute: %s got from NodeFromFileInfo not equal for path: %s", string(genericAttr), testPath)
|
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)
|
err := os.MkdirAll(filepath.Dir(testPath), testNode.Mode)
|
||||||
test.OK(t, errors.Wrapf(err, "Failed to create parent directories for: %s", testPath))
|
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)
|
testFile, err := os.Create(testPath)
|
||||||
test.OK(t, errors.Wrapf(err, "Failed to create test file: %s", testPath))
|
test.OK(t, errors.Wrapf(err, "Failed to create test file: %s", testPath))
|
||||||
testFile.Close()
|
testFile.Close()
|
||||||
} else if testNode.Type == "dir" {
|
} else if testNode.Type == restic.NodeTypeDir {
|
||||||
|
|
||||||
err := os.Mkdir(testPath, testNode.Mode)
|
err := os.Mkdir(testPath, testNode.Mode)
|
||||||
test.OK(t, errors.Wrapf(err, "Failed to create test directory: %s", testPath))
|
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{
|
expectedNodes := []restic.Node{
|
||||||
{
|
{
|
||||||
Name: "testfile",
|
Name: "testfile",
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
Mode: 0644,
|
Mode: 0644,
|
||||||
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
||||||
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
||||||
@ -251,7 +251,7 @@ func TestNewGenericAttributeType(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "testdirectory",
|
Name: "testdirectory",
|
||||||
Type: "dir",
|
Type: restic.NodeTypeDir,
|
||||||
Mode: 0755,
|
Mode: 0755,
|
||||||
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
||||||
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
||||||
@ -274,7 +274,7 @@ func TestRestoreExtendedAttributes(t *testing.T) {
|
|||||||
expectedNodes := []restic.Node{
|
expectedNodes := []restic.Node{
|
||||||
{
|
{
|
||||||
Name: "testfile",
|
Name: "testfile",
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
Mode: 0644,
|
Mode: 0644,
|
||||||
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
||||||
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
||||||
@ -285,7 +285,7 @@ func TestRestoreExtendedAttributes(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "testdirectory",
|
Name: "testdirectory",
|
||||||
Type: "dir",
|
Type: restic.NodeTypeDir,
|
||||||
Mode: 0755,
|
Mode: 0755,
|
||||||
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
||||||
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
||||||
@ -301,9 +301,9 @@ func TestRestoreExtendedAttributes(t *testing.T) {
|
|||||||
var handle windows.Handle
|
var handle windows.Handle
|
||||||
var err error
|
var err error
|
||||||
utf16Path := windows.StringToUTF16Ptr(testPath)
|
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)
|
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)
|
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))
|
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))
|
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.OK(t, errors.Wrapf(err, "Error getting extended attributes for: %s", testPath))
|
||||||
test.Equals(t, len(node.ExtendedAttributes), len(extAttr))
|
test.Equals(t, len(node.ExtendedAttributes), len(extAttr))
|
||||||
|
|
||||||
for _, expectedExtAttr := range node.ExtendedAttributes {
|
for _, expectedExtAttr := range node.ExtendedAttributes {
|
||||||
var foundExtAttr *ExtendedAttribute
|
var foundExtAttr *extendedAttribute
|
||||||
for _, ea := range extAttr {
|
for _, ea := range extAttr {
|
||||||
if strings.EqualFold(ea.Name, expectedExtAttr.Name) {
|
if strings.EqualFold(ea.Name, expectedExtAttr.Name) {
|
||||||
foundExtAttr = &ea
|
foundExtAttr = &ea
|
||||||
@ -491,13 +491,13 @@ func TestPrepareVolumeName(t *testing.T) {
|
|||||||
test.Equals(t, tc.expectedVolume, volume)
|
test.Equals(t, tc.expectedVolume, volume)
|
||||||
|
|
||||||
if tc.isRealPath {
|
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.
|
// If the prepared volume name is not valid, we will next fetch the actual volume name.
|
||||||
test.OK(t, err)
|
test.OK(t, err)
|
||||||
|
|
||||||
test.Equals(t, tc.expectedEASupported, isEASupportedVolume)
|
test.Equals(t, tc.expectedEASupported, isEASupportedVolume)
|
||||||
|
|
||||||
actualVolume, err := GetVolumePathName(tc.path)
|
actualVolume, err := getVolumePathName(tc.path)
|
||||||
test.OK(t, err)
|
test.OK(t, err)
|
||||||
test.Equals(t, tc.expectedVolume, actualVolume)
|
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.
|
// 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
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,13 +23,13 @@ func setAndVerifyXattr(t *testing.T, file string, attrs []restic.ExtendedAttribu
|
|||||||
}
|
}
|
||||||
|
|
||||||
node := &restic.Node{
|
node := &restic.Node{
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
ExtendedAttributes: attrs,
|
ExtendedAttributes: attrs,
|
||||||
}
|
}
|
||||||
rtest.OK(t, nodeRestoreExtendedAttributes(node, file))
|
rtest.OK(t, nodeRestoreExtendedAttributes(node, file))
|
||||||
|
|
||||||
nodeActual := &restic.Node{
|
nodeActual := &restic.Node{
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
}
|
}
|
||||||
rtest.OK(t, nodeFillExtendedAttributes(nodeActual, file, false))
|
rtest.OK(t, nodeFillExtendedAttributes(nodeActual, file, false))
|
||||||
|
|
||||||
|
@ -19,14 +19,14 @@ var (
|
|||||||
onceBackup sync.Once
|
onceBackup sync.Once
|
||||||
onceRestore sync.Once
|
onceRestore sync.Once
|
||||||
|
|
||||||
// SeBackupPrivilege allows the application to bypass file and directory ACLs to back up files and directories.
|
// seBackupPrivilege allows the application to bypass file and directory ACLs to back up files and directories.
|
||||||
SeBackupPrivilege = "SeBackupPrivilege"
|
seBackupPrivilege = "SeBackupPrivilege"
|
||||||
// SeRestorePrivilege allows the application to bypass file and directory ACLs to restore files and directories.
|
// seRestorePrivilege allows the application to bypass file and directory ACLs to restore files and directories.
|
||||||
SeRestorePrivilege = "SeRestorePrivilege"
|
seRestorePrivilege = "SeRestorePrivilege"
|
||||||
// SeSecurityPrivilege allows read and write access to all SACLs.
|
// seSecurityPrivilege allows read and write access to all SACLs.
|
||||||
SeSecurityPrivilege = "SeSecurityPrivilege"
|
seSecurityPrivilege = "SeSecurityPrivilege"
|
||||||
// SeTakeOwnershipPrivilege allows the application to take ownership of files and directories, regardless of the permissions set on them.
|
// seTakeOwnershipPrivilege allows the application to take ownership of files and directories, regardless of the permissions set on them.
|
||||||
SeTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege"
|
seTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege"
|
||||||
|
|
||||||
lowerPrivileges atomic.Bool
|
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.
|
// 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
|
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.
|
// 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.
|
// 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)
|
onceBackup.Do(enableBackupPrivilege)
|
||||||
|
|
||||||
var sd *windows.SECURITY_DESCRIPTOR
|
var sd *windows.SECURITY_DESCRIPTOR
|
||||||
@ -59,7 +59,7 @@ func GetSecurityDescriptor(filePath string) (securityDescriptor *[]byte, err err
|
|||||||
if !useLowerPrivileges && isHandlePrivilegeNotHeldError(err) {
|
if !useLowerPrivileges && isHandlePrivilegeNotHeldError(err) {
|
||||||
// If ERROR_PRIVILEGE_NOT_HELD is encountered, fallback to backups/restores using lower non-admin privileges.
|
// If ERROR_PRIVILEGE_NOT_HELD is encountered, fallback to backups/restores using lower non-admin privileges.
|
||||||
lowerPrivileges.Store(true)
|
lowerPrivileges.Store(true)
|
||||||
return GetSecurityDescriptor(filePath)
|
return getSecurityDescriptor(filePath)
|
||||||
} else if errors.Is(err, windows.ERROR_NOT_SUPPORTED) {
|
} else if errors.Is(err, windows.ERROR_NOT_SUPPORTED) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
} else {
|
} else {
|
||||||
@ -74,15 +74,15 @@ func GetSecurityDescriptor(filePath string) (securityDescriptor *[]byte, err err
|
|||||||
return &sdBytes, nil
|
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
|
// This needs admin permissions or SeRestorePrivilege, SeSecurityPrivilege and SeTakeOwnershipPrivilege
|
||||||
// for setting the full SD.
|
// for setting the full SD.
|
||||||
// If there are no admin permissions/required privileges, only the DACL from the SD can be set and
|
// 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.
|
// 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)
|
onceRestore.Do(enableRestorePrivilege)
|
||||||
// Set the security descriptor on the file
|
// Set the security descriptor on the file
|
||||||
sd, err := SecurityDescriptorBytesToStruct(*securityDescriptor)
|
sd, err := securityDescriptorBytesToStruct(*securityDescriptor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error converting bytes to security descriptor: %w", err)
|
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 !useLowerPrivileges && isHandlePrivilegeNotHeldError(err) {
|
||||||
// If ERROR_PRIVILEGE_NOT_HELD is encountered, fallback to backups/restores using lower non-admin privileges.
|
// If ERROR_PRIVILEGE_NOT_HELD is encountered, fallback to backups/restores using lower non-admin privileges.
|
||||||
lowerPrivileges.Store(true)
|
lowerPrivileges.Store(true)
|
||||||
return SetSecurityDescriptor(filePath, securityDescriptor)
|
return setSecurityDescriptor(filePath, securityDescriptor)
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("set named security info failed with: %w", err)
|
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
|
// enableBackupPrivilege enables privilege for backing up security descriptors
|
||||||
func enableBackupPrivilege() {
|
func enableBackupPrivilege() {
|
||||||
err := enableProcessPrivileges([]string{SeBackupPrivilege})
|
err := enableProcessPrivileges([]string{seBackupPrivilege})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Log("error enabling backup privilege: %v", err)
|
debug.Log("error enabling backup privilege: %v", err)
|
||||||
}
|
}
|
||||||
@ -158,7 +158,7 @@ func enableBackupPrivilege() {
|
|||||||
|
|
||||||
// enableBackupPrivilege enables privilege for restoring security descriptors
|
// enableBackupPrivilege enables privilege for restoring security descriptors
|
||||||
func enableRestorePrivilege() {
|
func enableRestorePrivilege() {
|
||||||
err := enableProcessPrivileges([]string{SeRestorePrivilege, SeSecurityPrivilege, SeTakeOwnershipPrivilege})
|
err := enableProcessPrivileges([]string{seRestorePrivilege, seSecurityPrivilege, seTakeOwnershipPrivilege})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Log("error enabling restore/security privilege: %v", err)
|
debug.Log("error enabling restore/security privilege: %v", err)
|
||||||
}
|
}
|
||||||
@ -174,9 +174,9 @@ func isHandlePrivilegeNotHeldError(err error) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// SecurityDescriptorBytesToStruct converts the security descriptor bytes representation
|
// securityDescriptorBytesToStruct converts the security descriptor bytes representation
|
||||||
// into a pointer to windows SECURITY_DESCRIPTOR.
|
// 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 {
|
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)
|
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
|
privNameMutex sync.Mutex
|
||||||
)
|
)
|
||||||
|
|
||||||
// PrivilegeError represents an error enabling privileges.
|
// privilegeError represents an error enabling privileges.
|
||||||
type PrivilegeError struct {
|
type privilegeError struct {
|
||||||
privileges []uint64
|
privileges []uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error returns the string message for the error.
|
// Error returns the string message for the error.
|
||||||
func (e *PrivilegeError) Error() string {
|
func (e *privilegeError) Error() string {
|
||||||
s := "Could not enable privilege "
|
s := "Could not enable privilege "
|
||||||
if len(e.privileges) > 1 {
|
if len(e.privileges) > 1 {
|
||||||
s = "Could not enable privileges "
|
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) {
|
func TestSetGetFolderSecurityDescriptors(t *testing.T) {
|
||||||
@ -40,7 +40,7 @@ func TestSetGetFolderSecurityDescriptors(t *testing.T) {
|
|||||||
t.Fatalf("failed to create temporary file: %s", err)
|
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) {
|
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)
|
sdInputBytes, err := base64.StdEncoding.DecodeString(testSD)
|
||||||
test.OK(t, errors.Wrapf(err, "Error decoding SD: %s", testPath))
|
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))
|
test.OK(t, errors.Wrapf(err, "Error setting file security descriptor for: %s", testPath))
|
||||||
|
|
||||||
var sdOutputBytes *[]byte
|
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))
|
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 (
|
var (
|
||||||
TestFileSDs = []string{"AQAUvBQAAAAwAAAAAAAAAEwAAAABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvqAwAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIAfAAEAAAAAAAkAKkAEgABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvtAwAAABAUAP8BHwABAQAAAAAABRIAAAAAEBgA/wEfAAECAAAAAAAFIAAAACACAAAAECQA/wEfAAEFAAAAAAAFFQAAAIifWK5WoILqzL0mq+oDAAA=",
|
testFileSDs = []string{"AQAUvBQAAAAwAAAAAAAAAEwAAAABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvqAwAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIAfAAEAAAAAAAkAKkAEgABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvtAwAAABAUAP8BHwABAQAAAAAABRIAAAAAEBgA/wEfAAECAAAAAAAFIAAAACACAAAAECQA/wEfAAEFAAAAAAAFFQAAAIifWK5WoILqzL0mq+oDAAA=",
|
||||||
"AQAUvBQAAAAwAAAAAAAAAEwAAAABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvqAwAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIAyAAHAAAAAAAUAKkAEgABAQAAAAAABQcAAAAAABQAiQASAAEBAAAAAAAFBwAAAAAAJACpABIAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSar7QMAAAAAJAC/ARMAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSar6gMAAAAAFAD/AR8AAQEAAAAAAAUSAAAAAAAYAP8BHwABAgAAAAAABSAAAAAgAgAAAAAkAP8BHwABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvqAwAA",
|
"AQAUvBQAAAAwAAAAAAAAAEwAAAABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvqAwAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIAyAAHAAAAAAAUAKkAEgABAQAAAAAABQcAAAAAABQAiQASAAEBAAAAAAAFBwAAAAAAJACpABIAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSar7QMAAAAAJAC/ARMAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSar6gMAAAAAFAD/AR8AAQEAAAAAAAUSAAAAAAAYAP8BHwABAgAAAAAABSAAAAAgAgAAAAAkAP8BHwABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvqAwAA",
|
||||||
"AQAUvBQAAAAwAAAA7AAAAEwAAAABBQAAAAAABRUAAAAvr7t03PyHGk2FokNHCAAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIAoAAFAAAAAAAkAP8BHwABBQAAAAAABRUAAAAvr7t03PyHGk2FokNHCAAAAAAkAKkAEgABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvtAwAAABAUAP8BHwABAQAAAAAABRIAAAAAEBgA/wEfAAECAAAAAAAFIAAAACACAAAAECQA/wEfAAEFAAAAAAAFFQAAAIifWK5WoILqzL0mq+oDAAACAHQAAwAAAAKAJAC/AQIAAQUAAAAAAAUVAAAAL6+7dNz8hxpNhaJDtgQAAALAJAC/AQMAAQUAAAAAAAUVAAAAL6+7dNz8hxpNhaJDPgkAAAJAJAD/AQ8AAQUAAAAAAAUVAAAAL6+7dNz8hxpNhaJDtQQAAA==",
|
"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=",
|
"AQAUvBQAAAAwAAAAAAAAAEwAAAABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvqAwAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIA3AAIAAAAAAIUAKkAEgABAQAAAAAABQcAAAAAAxQAiQASAAEBAAAAAAAFBwAAAAAAJACpABIAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSar7QMAAAAAJAC/ARMAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSar6gMAAAALFAC/ARMAAQEAAAAAAAMAAAAAABMUAP8BHwABAQAAAAAABRIAAAAAExgA/wEfAAECAAAAAAAFIAAAACACAAAAEyQA/wEfAAEFAAAAAAAFFQAAAIifWK5WoILqzL0mq+oDAAA=",
|
||||||
"AQAUvBQAAAAwAAAA7AAAAEwAAAABBQAAAAAABRUAAAAvr7t03PyHGk2FokNHCAAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIAoAAFAAAAAAAkAP8BHwABBQAAAAAABRUAAAAvr7t03PyHGk2FokNHCAAAAAAkAKkAEgABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvtAwAAABMUAP8BHwABAQAAAAAABRIAAAAAExgA/wEfAAECAAAAAAAFIAAAACACAAAAEyQA/wEfAAEFAAAAAAAFFQAAAIifWK5WoILqzL0mq+oDAAACAHQAAwAAAAKAJAC/AQIAAQUAAAAAAAUVAAAAL6+7dNz8hxpNhaJDtgQAAALAJAC/AQMAAQUAAAAAAAUVAAAAL6+7dNz8hxpNhaJDPgkAAAJAJAD/AQ8AAQUAAAAAAAUVAAAAL6+7dNz8hxpNhaJDtQQAAA==",
|
"AQAUvBQAAAAwAAAA7AAAAEwAAAABBQAAAAAABRUAAAAvr7t03PyHGk2FokNHCAAAAQUAAAAAAAUVAAAAiJ9YrlaggurMvSarAQIAAAIAoAAFAAAAAAAkAP8BHwABBQAAAAAABRUAAAAvr7t03PyHGk2FokNHCAAAAAAkAKkAEgABBQAAAAAABRUAAACIn1iuVqCC6sy9JqvtAwAAABMUAP8BHwABAQAAAAAABRIAAAAAExgA/wEfAAECAAAAAAAFIAAAACACAAAAEyQA/wEfAAEFAAAAAAAFFQAAAIifWK5WoILqzL0mq+oDAAACAHQAAwAAAAKAJAC/AQIAAQUAAAAAAAUVAAAAL6+7dNz8hxpNhaJDtgQAAALAJAC/AQMAAQUAAAAAAAUVAAAAL6+7dNz8hxpNhaJDPgkAAAJAJAD/AQ8AAQUAAAAAAAUVAAAAL6+7dNz8hxpNhaJDtQQAAA==",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsAdmin checks if current user is an administrator.
|
// isAdmin checks if current user is an administrator.
|
||||||
func IsAdmin() (isAdmin bool, err error) {
|
func isAdmin() (isAdmin bool, err error) {
|
||||||
var sid *windows.SID
|
var sid *windows.SID
|
||||||
err = windows.AllocateAndInitializeSid(&windows.SECURITY_NT_AUTHORITY, 2, windows.SECURITY_BUILTIN_DOMAIN_RID, windows.DOMAIN_ALIAS_RID_ADMINS,
|
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)
|
0, 0, 0, 0, 0, 0, &sid)
|
||||||
@ -40,15 +40,15 @@ func IsAdmin() (isAdmin bool, err error) {
|
|||||||
return member, nil
|
return member, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompareSecurityDescriptors runs tests for comparing 2 security descriptors in []byte format.
|
// compareSecurityDescriptors runs tests for comparing 2 security descriptors in []byte format.
|
||||||
func CompareSecurityDescriptors(t *testing.T, testPath string, sdInputBytes, sdOutputBytes []byte) {
|
func compareSecurityDescriptors(t *testing.T, testPath string, sdInputBytes, sdOutputBytes []byte) {
|
||||||
sdInput, err := SecurityDescriptorBytesToStruct(sdInputBytes)
|
sdInput, err := securityDescriptorBytesToStruct(sdInputBytes)
|
||||||
test.OK(t, errors.Wrapf(err, "Error converting SD to struct for: %s", testPath))
|
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))
|
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))
|
test.OK(t, errors.Wrapf(err, "Error checking if user is admin: %s", testPath))
|
||||||
|
|
||||||
var ownerExpected *windows.SID
|
var ownerExpected *windows.SID
|
||||||
|
@ -19,7 +19,7 @@ func TestNoatime(t *testing.T) {
|
|||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = f.Close()
|
_ = f.Close()
|
||||||
err = Remove(f.Name())
|
err = os.Remove(f.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ func extendedStat(fi os.FileInfo) ExtendedFileInfo {
|
|||||||
|
|
||||||
extFI := ExtendedFileInfo{
|
extFI := ExtendedFileInfo{
|
||||||
FileInfo: fi,
|
FileInfo: fi,
|
||||||
Size: int64(s.FileSizeLow) + int64(s.FileSizeHigh)<<32,
|
Size: int64(s.FileSizeLow) | (int64(s.FileSizeHigh) << 32),
|
||||||
}
|
}
|
||||||
|
|
||||||
atime := syscall.NsecToTimespec(s.LastAccessTime.Nanoseconds())
|
atime := syscall.NsecToTimespec(s.LastAccessTime.Nanoseconds())
|
||||||
@ -28,6 +28,7 @@ func extendedStat(fi os.FileInfo) ExtendedFileInfo {
|
|||||||
mtime := syscall.NsecToTimespec(s.LastWriteTime.Nanoseconds())
|
mtime := syscall.NsecToTimespec(s.LastWriteTime.Nanoseconds())
|
||||||
extFI.ModTime = time.Unix(mtime.Unix())
|
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
|
extFI.ChangeTime = extFI.ModTime
|
||||||
|
|
||||||
return extFI
|
return extFI
|
||||||
|
@ -33,9 +33,9 @@ func HasSufficientPrivilegesForVSS() error {
|
|||||||
return errors.New("VSS snapshots are only supported on windows")
|
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.
|
// and calls the equivalent windows api.
|
||||||
func GetVolumeNameForVolumeMountPoint(mountPoint string) (string, error) {
|
func getVolumeNameForVolumeMountPoint(mountPoint string) (string, error) {
|
||||||
return mountPoint, nil
|
return mountPoint, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
type HRESULT uint
|
type HRESULT uint
|
||||||
|
|
||||||
// HRESULT constant values necessary for using VSS api.
|
// HRESULT constant values necessary for using VSS api.
|
||||||
|
//
|
||||||
//nolint:golint
|
//nolint:golint
|
||||||
const (
|
const (
|
||||||
S_OK HRESULT = 0x00000000
|
S_OK HRESULT = 0x00000000
|
||||||
@ -830,9 +831,9 @@ func HasSufficientPrivilegesForVSS() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVolumeNameForVolumeMountPoint add trailing backslash to input parameter
|
// getVolumeNameForVolumeMountPoint add trailing backslash to input parameter
|
||||||
// and calls the equivalent windows api.
|
// 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 {
|
if mountPoint != "" && mountPoint[len(mountPoint)-1] != filepath.Separator {
|
||||||
mountPoint += string(filepath.Separator)
|
mountPoint += string(filepath.Separator)
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ func unwrapCtxCanceled(err error) error {
|
|||||||
// replaceSpecialNodes replaces nodes with name "." and "/" by their contents.
|
// replaceSpecialNodes replaces nodes with name "." and "/" by their contents.
|
||||||
// Otherwise, the node is returned.
|
// Otherwise, the node is returned.
|
||||||
func replaceSpecialNodes(ctx context.Context, repo restic.BlobLoader, node *restic.Node) ([]*restic.Node, error) {
|
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
|
return []*restic.Node{node}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +147,7 @@ func (d *dir) calcNumberOfLinks() uint32 {
|
|||||||
// of directories contained by d
|
// of directories contained by d
|
||||||
count := uint32(2)
|
count := uint32(2)
|
||||||
for _, node := range d.items {
|
for _, node := range d.items {
|
||||||
if node.Type == "dir" {
|
if node.Type == restic.NodeTypeDir {
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -182,11 +182,11 @@ func (d *dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
|||||||
name := cleanupNodeName(node.Name)
|
name := cleanupNodeName(node.Name)
|
||||||
var typ fuse.DirentType
|
var typ fuse.DirentType
|
||||||
switch node.Type {
|
switch node.Type {
|
||||||
case "dir":
|
case restic.NodeTypeDir:
|
||||||
typ = fuse.DT_Dir
|
typ = fuse.DT_Dir
|
||||||
case "file":
|
case restic.NodeTypeFile:
|
||||||
typ = fuse.DT_File
|
typ = fuse.DT_File
|
||||||
case "symlink":
|
case restic.NodeTypeSymlink:
|
||||||
typ = fuse.DT_Link
|
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)
|
inode := inodeFromNode(d.inode, node)
|
||||||
switch node.Type {
|
switch node.Type {
|
||||||
case "dir":
|
case restic.NodeTypeDir:
|
||||||
return newDir(d.root, inode, d.inode, node)
|
return newDir(d.root, inode, d.inode, node)
|
||||||
case "file":
|
case restic.NodeTypeFile:
|
||||||
return newFile(d.root, inode, node)
|
return newFile(d.root, inode, node)
|
||||||
case "symlink":
|
case restic.NodeTypeSymlink:
|
||||||
return newLink(d.root, inode, node)
|
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)
|
return newOther(d.root, inode, node)
|
||||||
default:
|
default:
|
||||||
debug.Log(" node %v has unknown type %v", name, node.Type)
|
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) {
|
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)
|
ino1 := inodeFromNode(1, node)
|
||||||
ino2 := inodeFromNode(2, node)
|
ino2 := inodeFromNode(2, node)
|
||||||
rtest.Assert(t, ino1 == ino2, "inodes %d, %d of hard links differ", ino1, ino2)
|
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
|
// Regression test: in a path a/b/b, the grandchild should not get the
|
||||||
// same inode as the grandparent.
|
// same inode as the grandparent.
|
||||||
a := &restic.Node{Name: "a", Type: "dir", Links: 2}
|
a := &restic.Node{Name: "a", Type: restic.NodeTypeDir, Links: 2}
|
||||||
ab := &restic.Node{Name: "b", Type: "dir", Links: 2}
|
ab := &restic.Node{Name: "b", Type: restic.NodeTypeDir, Links: 2}
|
||||||
abb := &restic.Node{Name: "b", Type: "dir", Links: 2}
|
abb := &restic.Node{Name: "b", Type: restic.NodeTypeDir, Links: 2}
|
||||||
inoA := inodeFromNode(1, a)
|
inoA := inodeFromNode(1, a)
|
||||||
inoAb := inodeFromNode(inoA, ab)
|
inoAb := inodeFromNode(inoA, ab)
|
||||||
inoAbb := inodeFromNode(inoAb, abb)
|
inoAbb := inodeFromNode(inoAb, abb)
|
||||||
@ -272,7 +272,7 @@ func TestInodeFromNode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestLink(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")},
|
{Name: "foo", Value: []byte("bar")},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
@ -305,11 +305,11 @@ func BenchmarkInode(b *testing.B) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no_hard_links",
|
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",
|
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) {
|
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.
|
// inodeFromNode generates an inode number for a file within a snapshot.
|
||||||
func inodeFromNode(parent uint64, node *restic.Node) (inode uint64) {
|
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,
|
// If node has hard links, give them all the same inode,
|
||||||
// irrespective of the parent.
|
// irrespective of the parent.
|
||||||
var buf [16]byte
|
var buf [16]byte
|
||||||
|
@ -46,7 +46,7 @@ func FindUsedBlobs(ctx context.Context, repo Loader, treeIDs IDs, blobs FindBlob
|
|||||||
lock.Lock()
|
lock.Lock()
|
||||||
for _, node := range tree.Nodes {
|
for _, node := range tree.Nodes {
|
||||||
switch node.Type {
|
switch node.Type {
|
||||||
case "file":
|
case NodeTypeFile:
|
||||||
for _, blob := range node.Content {
|
for _, blob := range node.Content {
|
||||||
blobs.Insert(BlobHandle{ID: blob, Type: DataBlob})
|
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.
|
// Node is a file, directory or other item in a backup.
|
||||||
type Node struct {
|
type Node struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type string `json:"type"`
|
Type NodeType `json:"type"`
|
||||||
Mode os.FileMode `json:"mode,omitempty"`
|
Mode os.FileMode `json:"mode,omitempty"`
|
||||||
ModTime time.Time `json:"mtime,omitempty"`
|
ModTime time.Time `json:"mtime,omitempty"`
|
||||||
AccessTime time.Time `json:"atime,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 {
|
func (node Node) String() string {
|
||||||
var mode os.FileMode
|
var mode os.FileMode
|
||||||
switch node.Type {
|
switch node.Type {
|
||||||
case "file":
|
case NodeTypeFile:
|
||||||
mode = 0
|
mode = 0
|
||||||
case "dir":
|
case NodeTypeDir:
|
||||||
mode = os.ModeDir
|
mode = os.ModeDir
|
||||||
case "symlink":
|
case NodeTypeSymlink:
|
||||||
mode = os.ModeSymlink
|
mode = os.ModeSymlink
|
||||||
case "dev":
|
case NodeTypeDev:
|
||||||
mode = os.ModeDevice
|
mode = os.ModeDevice
|
||||||
case "chardev":
|
case NodeTypeCharDev:
|
||||||
mode = os.ModeDevice | os.ModeCharDevice
|
mode = os.ModeDevice | os.ModeCharDevice
|
||||||
case "fifo":
|
case NodeTypeFifo:
|
||||||
mode = os.ModeNamedPipe
|
mode = os.ModeNamedPipe
|
||||||
case "socket":
|
case NodeTypeSocket:
|
||||||
mode = os.ModeSocket
|
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{
|
node := &Node{
|
||||||
Name: fmt.Sprintf("dir-%v", treeSeed),
|
Name: fmt.Sprintf("dir-%v", treeSeed),
|
||||||
Type: "dir",
|
Type: NodeTypeDir,
|
||||||
Mode: 0755,
|
Mode: 0755,
|
||||||
Subtree: &id,
|
Subtree: &id,
|
||||||
}
|
}
|
||||||
@ -95,7 +95,7 @@ func (fs *fakeFileSystem) saveTree(ctx context.Context, seed int64, depth int) I
|
|||||||
|
|
||||||
node := &Node{
|
node := &Node{
|
||||||
Name: fmt.Sprintf("file-%v", fileSeed),
|
Name: fmt.Sprintf("file-%v", fileSeed),
|
||||||
Type: "file",
|
Type: NodeTypeFile,
|
||||||
Mode: 0644,
|
Mode: 0644,
|
||||||
Size: uint64(fileSize),
|
Size: uint64(fileSize),
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ func (t *Tree) Sort() {
|
|||||||
// Subtrees returns a slice of all subtree IDs of the tree.
|
// Subtrees returns a slice of all subtree IDs of the tree.
|
||||||
func (t *Tree) Subtrees() (trees IDs) {
|
func (t *Tree) Subtrees() (trees IDs) {
|
||||||
for _, node := range t.Nodes {
|
for _, node := range t.Nodes {
|
||||||
if node.Type == "dir" && node.Subtree != nil {
|
if node.Type == NodeTypeDir && node.Subtree != nil {
|
||||||
trees = append(trees, *node.Subtree)
|
trees = append(trees, *node.Subtree)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,7 +208,7 @@ func FindTreeDirectory(ctx context.Context, repo BlobLoader, id *ID, dir string)
|
|||||||
if node == nil {
|
if node == nil {
|
||||||
return nil, fmt.Errorf("path %s: not found", subfolder)
|
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)
|
return nil, fmt.Errorf("path %s: not a directory", subfolder)
|
||||||
}
|
}
|
||||||
id = node.Subtree
|
id = node.Subtree
|
||||||
|
@ -202,18 +202,18 @@ func (res *Restorer) traverseTreeInner(ctx context.Context, target, location str
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sockets cannot be restored
|
// sockets cannot be restored
|
||||||
if node.Type == "socket" {
|
if node.Type == restic.NodeTypeSocket {
|
||||||
continue
|
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)
|
debug.Log("SelectFilter returned %v %v for %q", selectedForRestore, childMayBeSelected, nodeLocation)
|
||||||
|
|
||||||
if selectedForRestore {
|
if selectedForRestore {
|
||||||
hasRestored = true
|
hasRestored = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if node.Type == "dir" {
|
if node.Type == restic.NodeTypeDir {
|
||||||
if node.Subtree == nil {
|
if node.Subtree == nil {
|
||||||
return nil, hasRestored, errors.Errorf("Dir without subtree in tree %v", treeID.Str())
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if node.Type != "file" {
|
if node.Type != restic.NodeTypeFile {
|
||||||
res.opts.Progress.AddFile(0)
|
res.opts.Progress.AddFile(0)
|
||||||
return nil
|
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{
|
err = res.traverseTree(ctx, dst, *res.sn.Tree, treeVisitor{
|
||||||
visitNode: func(node *restic.Node, target, location string) error {
|
visitNode: func(node *restic.Node, target, location string) error {
|
||||||
debug.Log("second pass, visitNode: restore node %q", location)
|
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 {
|
_, err := res.withOverwriteCheck(ctx, node, target, location, false, nil, func(_ bool, _ *fileState) error {
|
||||||
return res.restoreNodeTo(node, target, location)
|
return res.restoreNodeTo(node, target, location)
|
||||||
})
|
})
|
||||||
@ -547,7 +547,7 @@ func (res *Restorer) withOverwriteCheck(ctx context.Context, node *restic.Node,
|
|||||||
|
|
||||||
var matches *fileState
|
var matches *fileState
|
||||||
updateMetadataOnly := false
|
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
|
// 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)
|
matches, buf, _ = res.verifyFile(ctx, target, node, false, res.opts.Overwrite == OverwriteIfChanged, buf)
|
||||||
// skip files that are already correct completely
|
// 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{
|
err := res.traverseTree(ctx, dst, *res.sn.Tree, treeVisitor{
|
||||||
visitNode: func(node *restic.Node, target, location string) error {
|
visitNode: func(node *restic.Node, target, location string) error {
|
||||||
if node.Type != "file" {
|
if node.Type != restic.NodeTypeFile {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if metadataOnly, ok := res.hasRestoredFile(location); !ok || metadataOnly {
|
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
|
mode = 0644
|
||||||
}
|
}
|
||||||
err := tree.Insert(&restic.Node{
|
err := tree.Insert(&restic.Node{
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
ModTime: node.ModTime,
|
ModTime: node.ModTime,
|
||||||
Name: name,
|
Name: name,
|
||||||
@ -123,7 +123,7 @@ func saveDir(t testing.TB, repo restic.BlobSaver, nodes map[string]Node, inode u
|
|||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
case Symlink:
|
case Symlink:
|
||||||
err := tree.Insert(&restic.Node{
|
err := tree.Insert(&restic.Node{
|
||||||
Type: "symlink",
|
Type: restic.NodeTypeSymlink,
|
||||||
Mode: os.ModeSymlink | 0o777,
|
Mode: os.ModeSymlink | 0o777,
|
||||||
ModTime: node.ModTime,
|
ModTime: node.ModTime,
|
||||||
Name: name,
|
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{
|
err := tree.Insert(&restic.Node{
|
||||||
Type: "dir",
|
Type: restic.NodeTypeDir,
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
ModTime: node.ModTime,
|
ModTime: node.ModTime,
|
||||||
Name: name,
|
Name: name,
|
||||||
@ -1223,7 +1223,7 @@ func TestRestorerOverwriteSpecial(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for filename, target := range links {
|
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.OK(t, err)
|
||||||
rtest.Equals(t, link, target, "wrong symlink target")
|
rtest.Equals(t, link, target, "wrong symlink target")
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/fs"
|
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/test"
|
"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
|
//If the node is a directory add FILE_ATTRIBUTE_DIRECTORY to attributes
|
||||||
fileattr |= windows.FILE_ATTRIBUTE_DIRECTORY
|
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)
|
test.OK(t, err)
|
||||||
return attrs
|
return attrs
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,7 @@ func (p *Progress) CompleteItem(item string, previous, current *restic.Node, s a
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch current.Type {
|
switch current.Type {
|
||||||
case "dir":
|
case restic.NodeTypeDir:
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
p.addProcessed(Counter{Dirs: 1})
|
p.addProcessed(Counter{Dirs: 1})
|
||||||
p.mu.Unlock()
|
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)
|
p.printer.CompleteItem("dir modified", item, s, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
case "file":
|
case restic.NodeTypeFile:
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
p.addProcessed(Counter{Files: 1})
|
p.addProcessed(Counter{Files: 1})
|
||||||
delete(p.currentFiles, item)
|
delete(p.currentFiles, item)
|
||||||
|
@ -55,10 +55,10 @@ func TestProgress(t *testing.T) {
|
|||||||
prog.CompleteBlob(1024)
|
prog.CompleteBlob(1024)
|
||||||
|
|
||||||
// "dir unchanged"
|
// "dir unchanged"
|
||||||
node := restic.Node{Type: "dir"}
|
node := restic.Node{Type: restic.NodeTypeDir}
|
||||||
prog.CompleteItem("foo", &node, &node, archiver.ItemStats{}, 0)
|
prog.CompleteItem("foo", &node, &node, archiver.ItemStats{}, 0)
|
||||||
// "file new"
|
// "file new"
|
||||||
node.Type = "file"
|
node.Type = restic.NodeTypeFile
|
||||||
prog.CompleteItem("foo", nil, &node, archiver.ItemStats{}, 0)
|
prog.CompleteItem("foo", nil, &node, archiver.ItemStats{}, 0)
|
||||||
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
@ -65,7 +65,7 @@ func NewSnapshotSizeRewriter(rewriteNode NodeRewriteFunc) (*TreeRewriter, QueryR
|
|||||||
t := NewTreeRewriter(RewriteOpts{
|
t := NewTreeRewriter(RewriteOpts{
|
||||||
RewriteNode: func(node *restic.Node, path string) *restic.Node {
|
RewriteNode: func(node *restic.Node, path string) *restic.Node {
|
||||||
node = rewriteNode(node, path)
|
node = rewriteNode(node, path)
|
||||||
if node != nil && node.Type == "file" {
|
if node != nil && node.Type == restic.NodeTypeFile {
|
||||||
count++
|
count++
|
||||||
size += node.Size
|
size += node.Size
|
||||||
}
|
}
|
||||||
@ -126,7 +126,7 @@ func (t *TreeRewriter) RewriteTree(ctx context.Context, repo BlobLoadSaver, node
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if node.Type != "dir" {
|
if node.Type != restic.NodeTypeDir {
|
||||||
err = tb.AddNode(node)
|
err = tb.AddNode(node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return restic.ID{}, err
|
return restic.ID{}, err
|
||||||
|
@ -110,7 +110,7 @@ func checkIncreaseNodeSize(increase uint64) checkRewriteFunc {
|
|||||||
return func(t testing.TB) (rewriter *TreeRewriter, final func(testing.TB)) {
|
return func(t testing.TB) (rewriter *TreeRewriter, final func(testing.TB)) {
|
||||||
rewriter = NewTreeRewriter(RewriteOpts{
|
rewriter = NewTreeRewriter(RewriteOpts{
|
||||||
RewriteNode: func(node *restic.Node, path string) *restic.Node {
|
RewriteNode: func(node *restic.Node, path string) *restic.Node {
|
||||||
if node.Type == "file" {
|
if node.Type == restic.NodeTypeFile {
|
||||||
node.Size += increase
|
node.Size += increase
|
||||||
}
|
}
|
||||||
return node
|
return node
|
||||||
@ -329,7 +329,7 @@ func TestSnapshotSizeQuery(t *testing.T) {
|
|||||||
if path == "/bar" {
|
if path == "/bar" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if node.Type == "file" {
|
if node.Type == restic.NodeTypeFile {
|
||||||
node.Size += 21
|
node.Size += 21
|
||||||
}
|
}
|
||||||
return node
|
return node
|
||||||
|
@ -63,11 +63,11 @@ func walk(ctx context.Context, repo restic.BlobLoader, prefix string, parentTree
|
|||||||
|
|
||||||
p := path.Join(prefix, node.Name)
|
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)
|
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)
|
err := visitor.ProcessNode(parentTreeID, p, node, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == ErrSkipNode {
|
if err == ErrSkipNode {
|
||||||
|
@ -38,7 +38,7 @@ func buildTreeMap(tree TestTree, m TreeMap) restic.ID {
|
|||||||
case TestFile:
|
case TestFile:
|
||||||
err := tb.AddNode(&restic.Node{
|
err := tb.AddNode(&restic.Node{
|
||||||
Name: name,
|
Name: name,
|
||||||
Type: "file",
|
Type: restic.NodeTypeFile,
|
||||||
Size: elem.Size,
|
Size: elem.Size,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -49,7 +49,7 @@ func buildTreeMap(tree TestTree, m TreeMap) restic.ID {
|
|||||||
err := tb.AddNode(&restic.Node{
|
err := tb.AddNode(&restic.Node{
|
||||||
Name: name,
|
Name: name,
|
||||||
Subtree: &id,
|
Subtree: &id,
|
||||||
Type: "dir",
|
Type: restic.NodeTypeDir,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
Loading…
Reference in New Issue
Block a user