mirror of
https://github.com/octoleo/restic.git
synced 2024-11-27 07:16:40 +00:00
Add support for Windows EA in node
Refactor Extended Attribute related functions in node files as windows apis get and set EA in bulk
This commit is contained in:
parent
d6708505b9
commit
5cff6e084e
@ -284,16 +284,6 @@ func (node Node) restoreMetadata(path string, warn func(msg string)) error {
|
||||
return firsterr
|
||||
}
|
||||
|
||||
func (node Node) restoreExtendedAttributes(path string) error {
|
||||
for _, attr := range node.ExtendedAttributes {
|
||||
err := Setxattr(path, attr.Name, attr.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (node Node) RestoreTimestamps(path string) error {
|
||||
var utimes = [...]syscall.Timespec{
|
||||
syscall.NsecToTimespec(node.AccessTime.UnixNano()),
|
||||
@ -726,34 +716,6 @@ func (node *Node) fillExtra(path string, fi os.FileInfo, ignoreXattrListError bo
|
||||
return err
|
||||
}
|
||||
|
||||
func (node *Node) fillExtendedAttributes(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([]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 := ExtendedAttribute{
|
||||
Name: attr,
|
||||
Value: attrVal,
|
||||
}
|
||||
|
||||
node.ExtendedAttributes = append(node.ExtendedAttributes, attr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mkfifo(path string, mode uint32) (err error) {
|
||||
return mknod(path, mode|syscall.S_IFIFO, 0)
|
||||
}
|
||||
|
@ -23,25 +23,21 @@ 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) }
|
||||
|
||||
// Getxattr is a no-op on AIX.
|
||||
func Getxattr(path, name string) ([]byte, error) {
|
||||
return nil, nil
|
||||
// restoreExtendedAttributes is a no-op on AIX.
|
||||
func (node Node) restoreExtendedAttributes(_ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Listxattr is a no-op on AIX.
|
||||
func Listxattr(path string) ([]string, error) {
|
||||
return nil, nil
|
||||
// fillExtendedAttributes is a no-op on AIX.
|
||||
func (node *Node) fillExtendedAttributes(_ string, _ bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsListxattrPermissionError is a no-op on AIX.
|
||||
func IsListxattrPermissionError(_ error) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Setxattr is a no-op on AIX.
|
||||
func Setxattr(path, name string, data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// restoreGenericAttributes is no-op on AIX.
|
||||
func (node *Node) restoreGenericAttributes(_ string, warn func(msg string)) error {
|
||||
return node.handleAllUnknownGenericAttributesFound(warn)
|
||||
|
@ -13,25 +13,21 @@ 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 }
|
||||
|
||||
// Getxattr is a no-op on netbsd.
|
||||
func Getxattr(path, name string) ([]byte, error) {
|
||||
return nil, nil
|
||||
// restoreExtendedAttributes is a no-op on netbsd.
|
||||
func (node Node) restoreExtendedAttributes(_ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Listxattr is a no-op on netbsd.
|
||||
func Listxattr(path string) ([]string, error) {
|
||||
return nil, nil
|
||||
// fillExtendedAttributes is a no-op on netbsd.
|
||||
func (node *Node) fillExtendedAttributes(_ string, _ bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsListxattrPermissionError is a no-op on netbsd.
|
||||
func IsListxattrPermissionError(_ error) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Setxattr is a no-op on netbsd.
|
||||
func Setxattr(path, name string, data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// restoreGenericAttributes is no-op on netbsd.
|
||||
func (node *Node) restoreGenericAttributes(_ string, warn func(msg string)) error {
|
||||
return node.handleAllUnknownGenericAttributesFound(warn)
|
||||
|
@ -13,25 +13,21 @@ 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 }
|
||||
|
||||
// Getxattr is a no-op on openbsd.
|
||||
func Getxattr(path, name string) ([]byte, error) {
|
||||
return nil, nil
|
||||
// restoreExtendedAttributes is a no-op on openbsd.
|
||||
func (node Node) restoreExtendedAttributes(_ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Listxattr is a no-op on openbsd.
|
||||
func Listxattr(path string) ([]string, error) {
|
||||
return nil, nil
|
||||
// fillExtendedAttributes is a no-op on openbsd.
|
||||
func (node *Node) fillExtendedAttributes(_ string, _ bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsListxattrPermissionError is a no-op on openbsd.
|
||||
func IsListxattrPermissionError(_ error) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Setxattr is a no-op on openbsd.
|
||||
func Setxattr(path, name string, data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// restoreGenericAttributes is no-op on openbsd.
|
||||
func (node *Node) restoreGenericAttributes(_ string, warn func(msg string)) error {
|
||||
return node.handleAllUnknownGenericAttributesFound(warn)
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -205,8 +206,18 @@ func TestNodeRestoreAt(t *testing.T) {
|
||||
var nodePath string
|
||||
if test.ExtendedAttributes != nil {
|
||||
if runtime.GOOS == "windows" {
|
||||
// restic does not support xattrs on windows
|
||||
return
|
||||
// In windows extended attributes are case insensitive and windows returns
|
||||
// the extended attributes in UPPER case.
|
||||
// Update the tests to use UPPER case xattr names for windows.
|
||||
extAttrArr := test.ExtendedAttributes
|
||||
// Iterate through the array using pointers
|
||||
for i := 0; i < len(extAttrArr); i++ {
|
||||
// Get the pointer to the current element
|
||||
namePtr := &extAttrArr[i].Name
|
||||
|
||||
// Modify the value through the pointer
|
||||
*namePtr = strings.ToUpper(*namePtr)
|
||||
}
|
||||
}
|
||||
|
||||
// tempdir might be backed by a filesystem that does not support
|
||||
|
@ -70,26 +70,109 @@ func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespe
|
||||
return syscall.SetFileTime(h, nil, &a, &w)
|
||||
}
|
||||
|
||||
// Getxattr retrieves extended attribute data associated with path.
|
||||
func Getxattr(path, name string) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Listxattr retrieves a list of names of extended attributes associated with the
|
||||
// given path in the file system.
|
||||
func Listxattr(path string) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func IsListxattrPermissionError(_ error) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Setxattr associates name and data together as an attribute of path.
|
||||
func Setxattr(path, name string, data []byte) error {
|
||||
// restore extended attributes for windows
|
||||
func (node Node) restoreExtendedAttributes(path string) (err error) {
|
||||
eas := []fs.ExtendedAttribute{}
|
||||
for _, attr := range node.ExtendedAttributes {
|
||||
extr := new(fs.ExtendedAttribute)
|
||||
extr.Name = attr.Name
|
||||
extr.Value = attr.Value
|
||||
eas = append(eas, *extr)
|
||||
}
|
||||
if len(eas) > 0 {
|
||||
if errExt := restoreExtendedAttributes(node.Type, path, eas); errExt != nil {
|
||||
return errExt
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fill extended attributes in the node. This also includes the Generic attributes for windows.
|
||||
func (node *Node) fillExtendedAttributes(path string, _ bool) (err error) {
|
||||
var fileHandle windows.Handle
|
||||
|
||||
//Get file handle for file or dir
|
||||
if node.Type == "file" {
|
||||
if strings.HasSuffix(filepath.Clean(path), `\`) {
|
||||
return nil
|
||||
}
|
||||
utf16Path := windows.StringToUTF16Ptr(path)
|
||||
fileAccessRightReadWriteEA := (0x8 | 0x10)
|
||||
fileHandle, err = windows.CreateFile(utf16Path, uint32(fileAccessRightReadWriteEA), 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, 0)
|
||||
} else if node.Type == "dir" {
|
||||
utf16Path := windows.StringToUTF16Ptr(path)
|
||||
fileAccessRightReadWriteEA := (0x8 | 0x10)
|
||||
fileHandle, err = windows.CreateFile(utf16Path, uint32(fileAccessRightReadWriteEA), 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_BACKUP_SEMANTICS, 0)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
err = errors.Errorf("open file failed for path: %s, with: %v", path, err)
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
err := windows.CloseHandle(fileHandle)
|
||||
if err != nil {
|
||||
debug.Log("Error closing file handle for %s: %v\n", path, err)
|
||||
}
|
||||
}()
|
||||
|
||||
//Get the windows Extended Attributes using the file handle
|
||||
extAtts, err := fs.GetFileEA(fileHandle)
|
||||
debug.Log("fillExtendedAttributes(%v) %v", path, extAtts)
|
||||
if err != nil {
|
||||
debug.Log("open file failed for path: %s : %v", path, err)
|
||||
return err
|
||||
} else 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 {
|
||||
if err != nil {
|
||||
err = errors.Errorf("can not obtain extended attribute for path %v, attr: %v, err: %v\n,", path, attr, err)
|
||||
continue
|
||||
}
|
||||
extendedAttr := ExtendedAttribute{
|
||||
Name: attr.Name,
|
||||
Value: attr.Value,
|
||||
}
|
||||
|
||||
node.ExtendedAttributes = append(node.ExtendedAttributes, extendedAttr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// restoreExtendedAttributes handles restore of the Windows Extended Attributes to the specified path.
|
||||
// The Windows API requires setting of all the Extended Attributes in one call.
|
||||
func restoreExtendedAttributes(nodeType, path string, eas []fs.ExtendedAttribute) (err error) {
|
||||
var fileHandle windows.Handle
|
||||
switch nodeType {
|
||||
case "file":
|
||||
utf16Path := windows.StringToUTF16Ptr(path)
|
||||
fileAccessRightReadWriteEA := (0x8 | 0x10)
|
||||
fileHandle, err = windows.CreateFile(utf16Path, uint32(fileAccessRightReadWriteEA), 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, 0)
|
||||
case "dir":
|
||||
utf16Path := windows.StringToUTF16Ptr(path)
|
||||
fileAccessRightReadWriteEA := (0x8 | 0x10)
|
||||
fileHandle, err = windows.CreateFile(utf16Path, uint32(fileAccessRightReadWriteEA), 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_BACKUP_SEMANTICS, 0)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
defer func() {
|
||||
err := windows.CloseHandle(fileHandle)
|
||||
if err != nil {
|
||||
debug.Log("Error closing file handle for %s: %v\n", path, err)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
err = errors.Errorf("open file failed for path %v, with: %v:\n", path, err)
|
||||
} else if err = fs.SetFileEA(fileHandle, eas); err != nil {
|
||||
err = errors.Errorf("set EA failed for path %v, with: %v:\n", path, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type statT syscall.Win32FileAttributeData
|
||||
|
||||
func toStatT(i interface{}) (*statT, bool) {
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
@ -265,3 +266,68 @@ func TestNewGenericAttributeType(t *testing.T) {
|
||||
test.Assert(t, len(ua) == 0, "Unkown attributes: %s found for path: %s", ua, testPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestoreExtendedAttributes(t *testing.T) {
|
||||
t.Parallel()
|
||||
tempDir := t.TempDir()
|
||||
expectedNodes := []Node{
|
||||
{
|
||||
Name: "testfile",
|
||||
Type: "file",
|
||||
Mode: 0644,
|
||||
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
||||
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
||||
ChangeTime: parseTime("2005-05-14 21:07:05.333"),
|
||||
ExtendedAttributes: []ExtendedAttribute{
|
||||
{"user.foo", []byte("bar")},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "testdirectory",
|
||||
Type: "dir",
|
||||
Mode: 0755,
|
||||
ModTime: parseTime("2005-05-14 21:07:03.111"),
|
||||
AccessTime: parseTime("2005-05-14 21:07:04.222"),
|
||||
ChangeTime: parseTime("2005-05-14 21:07:05.333"),
|
||||
ExtendedAttributes: []ExtendedAttribute{
|
||||
{"user.foo", []byte("bar")},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, testNode := range expectedNodes {
|
||||
testPath, node := restoreAndGetNode(t, tempDir, testNode, false)
|
||||
|
||||
var handle windows.Handle
|
||||
var err error
|
||||
utf16Path := windows.StringToUTF16Ptr(testPath)
|
||||
if node.Type == "file" {
|
||||
handle, err = windows.CreateFile(utf16Path, windows.FILE_READ_EA, 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, 0)
|
||||
} else if node.Type == "dir" {
|
||||
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))
|
||||
defer func() {
|
||||
err := windows.Close(handle)
|
||||
test.OK(t, errors.Wrapf(err, "Error closing file for: %s", testPath))
|
||||
}()
|
||||
|
||||
if len(node.ExtendedAttributes) > 0 {
|
||||
extAttr, err := fs.GetFileEA(handle)
|
||||
test.OK(t, errors.Wrapf(err, "Error getting extended attributes for: %s", testPath))
|
||||
test.Equals(t, len(node.ExtendedAttributes), len(extAttr))
|
||||
|
||||
for _, expectedExtAttr := range node.ExtendedAttributes {
|
||||
var foundExtAttr *fs.ExtendedAttribute
|
||||
for _, ea := range extAttr {
|
||||
if strings.EqualFold(ea.Name, expectedExtAttr.Name) {
|
||||
foundExtAttr = &ea
|
||||
break
|
||||
|
||||
}
|
||||
}
|
||||
test.Assert(t, foundExtAttr != nil, "Expected extended attribute not found")
|
||||
test.Equals(t, expectedExtAttr.Value, foundExtAttr.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,23 +4,25 @@
|
||||
package restic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
|
||||
"github.com/pkg/xattr"
|
||||
)
|
||||
|
||||
// Getxattr retrieves extended attribute data associated with path.
|
||||
func Getxattr(path, name string) ([]byte, error) {
|
||||
// getxattr retrieves extended attribute data associated with path.
|
||||
func getxattr(path, name string) ([]byte, error) {
|
||||
b, err := xattr.LGet(path, name)
|
||||
return b, handleXattrErr(err)
|
||||
}
|
||||
|
||||
// Listxattr retrieves a list of names of extended attributes associated with the
|
||||
// listxattr retrieves a list of names of extended attributes associated with the
|
||||
// given path in the file system.
|
||||
func Listxattr(path string) ([]string, error) {
|
||||
func listxattr(path string) ([]string, error) {
|
||||
l, err := xattr.LList(path)
|
||||
return l, handleXattrErr(err)
|
||||
}
|
||||
@ -33,8 +35,8 @@ func IsListxattrPermissionError(err error) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Setxattr associates name and data together as an attribute of path.
|
||||
func Setxattr(path, name string, data []byte) error {
|
||||
// setxattr associates name and data together as an attribute of path.
|
||||
func setxattr(path, name string, data []byte) error {
|
||||
return handleXattrErr(xattr.LSet(path, name, data))
|
||||
}
|
||||
|
||||
@ -66,3 +68,41 @@ func (node *Node) restoreGenericAttributes(_ string, warn func(msg string)) erro
|
||||
func (node *Node) fillGenericAttributes(_ string, _ os.FileInfo, _ *statT) (allowExtended bool, err error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (node Node) restoreExtendedAttributes(path string) error {
|
||||
for _, attr := range node.ExtendedAttributes {
|
||||
err := setxattr(path, attr.Name, attr.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (node *Node) fillExtendedAttributes(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([]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 := ExtendedAttribute{
|
||||
Name: attr,
|
||||
Value: attrVal,
|
||||
}
|
||||
|
||||
node.ExtendedAttributes = append(node.ExtendedAttributes, attr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user