1
0
Fork 0
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:
Michael Eischer 2024-11-03 15:09:59 +01:00
parent 0bf8af7188
commit e70fa4f9f5
14 changed files with 223 additions and 112 deletions

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
View 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)
}

View 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
View file

@ -0,0 +1,8 @@
//go:build !windows
// +build !windows
package fs
func (p *pathMetadataHandle) SecurityDescriptor() (*[]byte, error) {
return nil, nil
}

View 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
View 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
}

View file

@ -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)

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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)
}