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:
aneesh-n 2024-05-17 14:18:20 -06:00
parent d6708505b9
commit 5cff6e084e
No known key found for this signature in database
GPG key ID: 6F5A52831C046F44
8 changed files with 246 additions and 96 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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