mirror of
https://github.com/restic/restic.git
synced 2025-03-09 00:00:02 +01:00
fs: abstract away filesystem ops from nodeFromFileInfo
This commit is contained in:
parent
0bf8af7188
commit
e70fa4f9f5
14 changed files with 223 additions and 112 deletions
|
@ -264,6 +264,10 @@ func (arch *Archiver) trackItem(item string, previous, current *restic.Node, s I
|
|||
// nodeFromFileInfo returns the restic node from an os.FileInfo.
|
||||
func (arch *Archiver) nodeFromFileInfo(snPath, filename string, meta ToNoder, ignoreXattrListError bool) (*restic.Node, error) {
|
||||
node, err := meta.ToNode(ignoreXattrListError)
|
||||
if node == nil {
|
||||
// do not filter error on total failure
|
||||
return node, err
|
||||
}
|
||||
if !arch.WithAtime {
|
||||
node.AccessTime = node.ModTime
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
|
@ -115,22 +114,13 @@ func clearAttribute(path string, attribute uint32) error {
|
|||
}
|
||||
|
||||
// openHandleForEA return a file handle for file or dir for setting/getting EAs
|
||||
func openHandleForEA(nodeType restic.NodeType, path string, writeAccess bool) (handle windows.Handle, err error) {
|
||||
func openHandleForEA(path string, writeAccess bool) (handle windows.Handle, err error) {
|
||||
path = fixpath(path)
|
||||
fileAccess := windows.FILE_READ_EA
|
||||
if writeAccess {
|
||||
fileAccess = fileAccess | windows.FILE_WRITE_EA
|
||||
}
|
||||
|
||||
switch nodeType {
|
||||
case restic.NodeTypeFile:
|
||||
utf16Path := windows.StringToUTF16Ptr(path)
|
||||
handle, err = windows.CreateFile(utf16Path, uint32(fileAccess), 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, 0)
|
||||
case restic.NodeTypeDir:
|
||||
utf16Path := windows.StringToUTF16Ptr(path)
|
||||
handle, err = windows.CreateFile(utf16Path, uint32(fileAccess), 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_BACKUP_SEMANTICS, 0)
|
||||
default:
|
||||
return 0, nil
|
||||
}
|
||||
return handle, err
|
||||
utf16Path := windows.StringToUTF16Ptr(path)
|
||||
return windows.CreateFile(utf16Path, uint32(fileAccess), 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_BACKUP_SEMANTICS, 0)
|
||||
}
|
||||
|
|
|
@ -90,12 +90,14 @@ type localFile struct {
|
|||
flag int
|
||||
f *os.File
|
||||
fi *ExtendedFileInfo
|
||||
meta metadataHandle
|
||||
}
|
||||
|
||||
// See the File interface for a description of each method
|
||||
var _ File = &localFile{}
|
||||
|
||||
func newLocalFile(name string, flag int, metadataOnly bool) (*localFile, error) {
|
||||
meta := newPathMetadataHandle(name, flag)
|
||||
var f *os.File
|
||||
if !metadataOnly {
|
||||
var err error
|
||||
|
@ -105,11 +107,14 @@ func newLocalFile(name string, flag int, metadataOnly bool) (*localFile, error)
|
|||
}
|
||||
_ = setFlags(f)
|
||||
}
|
||||
return &localFile{
|
||||
file := &localFile{
|
||||
name: name,
|
||||
flag: flag,
|
||||
f: f,
|
||||
}, nil
|
||||
meta: meta,
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func (f *localFile) MakeReadable() error {
|
||||
|
@ -130,20 +135,17 @@ func (f *localFile) cacheFI() error {
|
|||
if f.fi != nil {
|
||||
return nil
|
||||
}
|
||||
var fi os.FileInfo
|
||||
var err error
|
||||
if f.f != nil {
|
||||
fi, err = f.f.Stat()
|
||||
} else if f.flag&O_NOFOLLOW != 0 {
|
||||
fi, err = os.Lstat(f.name)
|
||||
fi, err := f.f.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.fi = extendedStat(fi)
|
||||
} else {
|
||||
fi, err = os.Stat(f.name)
|
||||
f.fi, err = f.meta.Stat()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.fi = extendedStat(fi)
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *localFile) Stat() (*ExtendedFileInfo, error) {
|
||||
|
@ -156,7 +158,7 @@ func (f *localFile) ToNode(ignoreXattrListError bool) (*restic.Node, error) {
|
|||
if err := f.cacheFI(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nodeFromFileInfo(f.name, f.fi, ignoreXattrListError)
|
||||
return nodeFromFileInfo(f.name, &cachedMetadataHandle{f.meta, f}, ignoreXattrListError)
|
||||
}
|
||||
|
||||
func (f *localFile) Read(p []byte) (n int, err error) {
|
||||
|
@ -173,3 +175,14 @@ func (f *localFile) Close() error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// metadata handle with FileInfo from localFile
|
||||
// This ensures that Stat() and ToNode() use the exact same data.
|
||||
type cachedMetadataHandle struct {
|
||||
metadataHandle
|
||||
f *localFile
|
||||
}
|
||||
|
||||
func (c *cachedMetadataHandle) Stat() (*ExtendedFileInfo, error) {
|
||||
return c.f.Stat()
|
||||
}
|
||||
|
|
52
internal/fs/meta.go
Normal file
52
internal/fs/meta.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/restic/restic/internal/restic"
|
||||
)
|
||||
|
||||
type metadataHandle interface {
|
||||
Name() string
|
||||
Stat() (*ExtendedFileInfo, error)
|
||||
Readlink() (string, error)
|
||||
Xattr(ignoreListError bool) ([]restic.ExtendedAttribute, error)
|
||||
// windows only
|
||||
SecurityDescriptor() (*[]byte, error)
|
||||
}
|
||||
|
||||
type pathMetadataHandle struct {
|
||||
name string
|
||||
flag int
|
||||
}
|
||||
|
||||
var _ metadataHandle = &pathMetadataHandle{}
|
||||
|
||||
func newPathMetadataHandle(name string, flag int) *pathMetadataHandle {
|
||||
return &pathMetadataHandle{
|
||||
name: fixpath(name),
|
||||
flag: flag,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pathMetadataHandle) Name() string {
|
||||
return p.name
|
||||
}
|
||||
|
||||
func (p *pathMetadataHandle) Stat() (*ExtendedFileInfo, error) {
|
||||
var fi os.FileInfo
|
||||
var err error
|
||||
if p.flag&O_NOFOLLOW != 0 {
|
||||
fi, err = os.Lstat(p.name)
|
||||
} else {
|
||||
fi, err = os.Stat(p.name)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return extendedStat(fi), nil
|
||||
}
|
||||
|
||||
func (p *pathMetadataHandle) Readlink() (string, error) {
|
||||
return os.Readlink(p.name)
|
||||
}
|
10
internal/fs/meta_noxattr.go
Normal file
10
internal/fs/meta_noxattr.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
//go:build aix || dragonfly || netbsd || openbsd
|
||||
// +build aix dragonfly netbsd openbsd
|
||||
|
||||
package fs
|
||||
|
||||
import "github.com/restic/restic/internal/restic"
|
||||
|
||||
func (p *pathMetadataHandle) Xattr(ignoreListError bool) ([]restic.ExtendedAttribute, error) {
|
||||
return nil, nil
|
||||
}
|
8
internal/fs/meta_unix.go
Normal file
8
internal/fs/meta_unix.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package fs
|
||||
|
||||
func (p *pathMetadataHandle) SecurityDescriptor() (*[]byte, error) {
|
||||
return nil, nil
|
||||
}
|
47
internal/fs/meta_windows.go
Normal file
47
internal/fs/meta_windows.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func (p *pathMetadataHandle) Xattr(_ bool) ([]restic.ExtendedAttribute, error) {
|
||||
allowExtended, err := checkAndStoreEASupport(p.name)
|
||||
if err != nil || !allowExtended {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var fileHandle windows.Handle
|
||||
if fileHandle, err = openHandleForEA(p.name, false); err != nil {
|
||||
return nil, errors.Errorf("get EA failed while opening file handle for path %v, with: %v", p.name, err)
|
||||
}
|
||||
defer closeFileHandle(fileHandle, p.name)
|
||||
|
||||
//Get the windows Extended Attributes using the file handle
|
||||
var extAtts []extendedAttribute
|
||||
extAtts, err = fgetEA(fileHandle)
|
||||
debug.Log("fillExtendedAttributes(%v) %v", p.name, extAtts)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("get EA failed for path %v, with: %v", p.name, err)
|
||||
}
|
||||
if len(extAtts) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
extendedAttrs := make([]restic.ExtendedAttribute, 0, len(extAtts))
|
||||
for _, attr := range extAtts {
|
||||
extendedAttr := restic.ExtendedAttribute{
|
||||
Name: attr.Name,
|
||||
Value: attr.Value,
|
||||
}
|
||||
|
||||
extendedAttrs = append(extendedAttrs, extendedAttr)
|
||||
}
|
||||
return extendedAttrs, nil
|
||||
}
|
||||
|
||||
func (p *pathMetadataHandle) SecurityDescriptor() (*[]byte, error) {
|
||||
return getSecurityDescriptor(p.name)
|
||||
}
|
40
internal/fs/meta_xattr.go
Normal file
40
internal/fs/meta_xattr.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
//go:build darwin || freebsd || linux || solaris
|
||||
// +build darwin freebsd linux solaris
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
)
|
||||
|
||||
func (p *pathMetadataHandle) Xattr(ignoreListError bool) ([]restic.ExtendedAttribute, error) {
|
||||
xattrs, err := listxattr(p.name)
|
||||
debug.Log("fillExtendedAttributes(%v) %v %v", p.name, xattrs, err)
|
||||
if err != nil {
|
||||
if ignoreListError && isListxattrPermissionError(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
extendedAttrs := make([]restic.ExtendedAttribute, 0, len(xattrs))
|
||||
for _, attr := range xattrs {
|
||||
attrVal, err := getxattr(p.name, attr)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "can not obtain extended attribute %v for %v:\n", attr, p.name)
|
||||
continue
|
||||
}
|
||||
attr := restic.ExtendedAttribute{
|
||||
Name: attr,
|
||||
Value: attrVal,
|
||||
}
|
||||
|
||||
extendedAttrs = append(extendedAttrs, attr)
|
||||
}
|
||||
|
||||
return extendedAttrs, nil
|
||||
}
|
|
@ -13,17 +13,22 @@ import (
|
|||
"github.com/restic/restic/internal/restic"
|
||||
)
|
||||
|
||||
// nodeFromFileInfo returns a new node from the given path and FileInfo. It
|
||||
// returns the first error that is encountered, together with a node.
|
||||
func nodeFromFileInfo(path string, fi *ExtendedFileInfo, ignoreXattrListError bool) (*restic.Node, error) {
|
||||
// nodeFromFileInfo returns a new node from the given path and metadata handle.
|
||||
// Can return a nil node if no file metadata can be retrieved. Otherwise, it returns
|
||||
// the first error that is encountered, together with a node.
|
||||
func nodeFromFileInfo(path string, meta metadataHandle, ignoreXattrListError bool) (*restic.Node, error) {
|
||||
fi, err := meta.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node := buildBasicNode(path, fi)
|
||||
|
||||
if err := nodeFillExtendedStat(node, path, fi); err != nil {
|
||||
if err := nodeFillExtendedStat(node, meta, fi); err != nil {
|
||||
return node, err
|
||||
}
|
||||
|
||||
err := nodeFillGenericAttributes(node, path, fi)
|
||||
err = errors.Join(err, nodeFillExtendedAttributes(node, path, ignoreXattrListError))
|
||||
err = nodeFillGenericAttributes(node, meta, fi)
|
||||
err = errors.Join(err, nodeFillExtendedAttributes(node, meta, ignoreXattrListError))
|
||||
return node, err
|
||||
}
|
||||
|
||||
|
@ -66,7 +71,7 @@ func nodeTypeFromFileInfo(mode os.FileMode) restic.NodeType {
|
|||
return restic.NodeTypeInvalid
|
||||
}
|
||||
|
||||
func nodeFillExtendedStat(node *restic.Node, path string, stat *ExtendedFileInfo) error {
|
||||
func nodeFillExtendedStat(node *restic.Node, meta metadataHandle, stat *ExtendedFileInfo) error {
|
||||
node.Inode = stat.Inode
|
||||
node.DeviceID = stat.DeviceID
|
||||
node.ChangeTime = stat.ChangeTime
|
||||
|
@ -84,7 +89,7 @@ func nodeFillExtendedStat(node *restic.Node, path string, stat *ExtendedFileInfo
|
|||
case restic.NodeTypeDir:
|
||||
case restic.NodeTypeSymlink:
|
||||
var err error
|
||||
node.LinkTarget, err = os.Readlink(fixpath(path))
|
||||
node.LinkTarget, err = meta.Readlink()
|
||||
node.Links = stat.Links
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
|
|
|
@ -13,6 +13,6 @@ func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error {
|
|||
}
|
||||
|
||||
// nodeFillExtendedAttributes is a no-op
|
||||
func nodeFillExtendedAttributes(_ *restic.Node, _ string, _ bool) error {
|
||||
func nodeFillExtendedAttributes(_ *restic.Node, _ metadataHandle, _ bool) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -19,6 +19,6 @@ func nodeRestoreGenericAttributes(node *restic.Node, _ string, warn func(msg str
|
|||
}
|
||||
|
||||
// nodeFillGenericAttributes is a no-op.
|
||||
func nodeFillGenericAttributes(_ *restic.Node, _ string, _ *ExtendedFileInfo) error {
|
||||
func nodeFillGenericAttributes(_ *restic.Node, _ metadataHandle, _ *ExtendedFileInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -84,56 +84,18 @@ func nodeRestoreExtendedAttributes(node *restic.Node, path string) (err error) {
|
|||
}
|
||||
|
||||
// fill extended attributes in the node
|
||||
// It also checks if the volume supports extended attributes and stores the result in a map
|
||||
// so that it does not have to be checked again for subsequent calls for paths in the same volume.
|
||||
func nodeFillExtendedAttributes(node *restic.Node, path string, _ bool) (err error) {
|
||||
if strings.Contains(filepath.Base(path), ":") {
|
||||
func nodeFillExtendedAttributes(node *restic.Node, meta metadataHandle, _ bool) (err error) {
|
||||
if strings.Contains(filepath.Base(meta.Name()), ":") {
|
||||
// Do not process for Alternate Data Streams in Windows
|
||||
return nil
|
||||
}
|
||||
|
||||
// only capture xattrs for file/dir
|
||||
if node.Type != restic.NodeTypeFile && node.Type != restic.NodeTypeDir {
|
||||
return nil
|
||||
}
|
||||
|
||||
allowExtended, err := checkAndStoreEASupport(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !allowExtended {
|
||||
return nil
|
||||
}
|
||||
|
||||
var fileHandle windows.Handle
|
||||
if fileHandle, err = openHandleForEA(node.Type, path, false); fileHandle == 0 {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Errorf("get EA failed while opening file handle for path %v, with: %v", path, err)
|
||||
}
|
||||
defer closeFileHandle(fileHandle, path) // Replaced inline defer with named function call
|
||||
//Get the windows Extended Attributes using the file handle
|
||||
var extAtts []extendedAttribute
|
||||
extAtts, err = fgetEA(fileHandle)
|
||||
debug.Log("fillExtendedAttributes(%v) %v", path, extAtts)
|
||||
if err != nil {
|
||||
return errors.Errorf("get EA failed for path %v, with: %v", path, err)
|
||||
}
|
||||
if len(extAtts) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
//Fill the ExtendedAttributes in the node using the name/value pairs in the windows EA
|
||||
for _, attr := range extAtts {
|
||||
extendedAttr := restic.ExtendedAttribute{
|
||||
Name: attr.Name,
|
||||
Value: attr.Value,
|
||||
}
|
||||
|
||||
node.ExtendedAttributes = append(node.ExtendedAttributes, extendedAttr)
|
||||
}
|
||||
return nil
|
||||
node.ExtendedAttributes, err = meta.Xattr(false)
|
||||
return err
|
||||
}
|
||||
|
||||
// closeFileHandle safely closes a file handle and logs any errors.
|
||||
|
@ -147,11 +109,13 @@ func closeFileHandle(fileHandle windows.Handle, path string) {
|
|||
// restoreExtendedAttributes handles restore of the Windows Extended Attributes to the specified path.
|
||||
// The Windows API requires setting of all the Extended Attributes in one call.
|
||||
func restoreExtendedAttributes(nodeType restic.NodeType, path string, eas []extendedAttribute) (err error) {
|
||||
var fileHandle windows.Handle
|
||||
if fileHandle, err = openHandleForEA(nodeType, path, true); fileHandle == 0 {
|
||||
// only restore xattrs for file/dir
|
||||
if nodeType != restic.NodeTypeFile && nodeType != restic.NodeTypeDir {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
var fileHandle windows.Handle
|
||||
if fileHandle, err = openHandleForEA(path, true); err != nil {
|
||||
return errors.Errorf("set EA failed while opening file handle for path %v, with: %v", path, err)
|
||||
}
|
||||
defer closeFileHandle(fileHandle, path) // Replaced inline defer with named function call
|
||||
|
@ -336,7 +300,8 @@ func decryptFile(pathPointer *uint16) error {
|
|||
|
||||
// nodeFillGenericAttributes fills in the generic attributes for windows like File Attributes,
|
||||
// Created time and Security Descriptors.
|
||||
func nodeFillGenericAttributes(node *restic.Node, path string, stat *ExtendedFileInfo) error {
|
||||
func nodeFillGenericAttributes(node *restic.Node, meta metadataHandle, stat *ExtendedFileInfo) error {
|
||||
path := meta.Name()
|
||||
if strings.Contains(filepath.Base(path), ":") {
|
||||
// Do not process for Alternate Data Streams in Windows
|
||||
return nil
|
||||
|
@ -356,7 +321,7 @@ func nodeFillGenericAttributes(node *restic.Node, path string, stat *ExtendedFil
|
|||
|
||||
var sd *[]byte
|
||||
if node.Type == restic.NodeTypeFile || node.Type == restic.NodeTypeDir {
|
||||
if sd, err = getSecurityDescriptor(path); err != nil {
|
||||
if sd, err = meta.SecurityDescriptor(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,9 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/restic"
|
||||
|
||||
|
@ -92,30 +90,8 @@ func nodeRestoreExtendedAttributes(node *restic.Node, path string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func nodeFillExtendedAttributes(node *restic.Node, path string, ignoreListError bool) error {
|
||||
xattrs, err := listxattr(path)
|
||||
debug.Log("fillExtendedAttributes(%v) %v %v", path, xattrs, err)
|
||||
if err != nil {
|
||||
if ignoreListError && isListxattrPermissionError(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
node.ExtendedAttributes = make([]restic.ExtendedAttribute, 0, len(xattrs))
|
||||
for _, attr := range xattrs {
|
||||
attrVal, err := getxattr(path, attr)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "can not obtain extended attribute %v for %v:\n", attr, path)
|
||||
continue
|
||||
}
|
||||
attr := restic.ExtendedAttribute{
|
||||
Name: attr,
|
||||
Value: attrVal,
|
||||
}
|
||||
|
||||
node.ExtendedAttributes = append(node.ExtendedAttributes, attr)
|
||||
}
|
||||
|
||||
return nil
|
||||
func nodeFillExtendedAttributes(node *restic.Node, meta metadataHandle, ignoreListError bool) error {
|
||||
var err error
|
||||
node.ExtendedAttributes, err = meta.Xattr(ignoreListError)
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -31,7 +31,8 @@ func setAndVerifyXattr(t *testing.T, file string, attrs []restic.ExtendedAttribu
|
|||
nodeActual := &restic.Node{
|
||||
Type: restic.NodeTypeFile,
|
||||
}
|
||||
rtest.OK(t, nodeFillExtendedAttributes(nodeActual, file, false))
|
||||
meta := newPathMetadataHandle(file, 0)
|
||||
rtest.OK(t, nodeFillExtendedAttributes(nodeActual, meta, false))
|
||||
|
||||
rtest.Assert(t, nodeActual.Equals(*node), "xattr mismatch got %v expected %v", nodeActual.ExtendedAttributes, node.ExtendedAttributes)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue