mirror of
https://github.com/restic/restic.git
synced 2026-04-24 13:49:24 +00:00
Add support for extended attributes (e.g. ACL)
This commit is contained in:
committed by
Alexander Neumann
parent
40685a0e61
commit
49cae0904f
@@ -385,7 +385,7 @@ func (arch *Archiver) dirWorker(wg *sync.WaitGroup, p *restic.Progress, done <-c
|
||||
node := &restic.Node{}
|
||||
|
||||
if dir.Path() != "" && dir.Info() != nil {
|
||||
n, err := restic.NodeFromFileInfo(dir.Path(), dir.Info())
|
||||
n, err := restic.NodeFromFileInfo(dir.Fullpath(), dir.Info())
|
||||
if err != nil {
|
||||
n.Error = err.Error()
|
||||
dir.Result() <- n
|
||||
|
||||
@@ -177,3 +177,21 @@ func (d *dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
||||
return nil, fuse.ENOENT
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dir) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error {
|
||||
debug.Log("Listxattr(%v, %v)", d.node.Name, req.Size)
|
||||
for _, attr := range d.node.ExtendedAttributes {
|
||||
resp.Append(attr.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dir) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {
|
||||
debug.Log("Getxattr(%v, %v, %v)", d.node.Name, req.Name, req.Size)
|
||||
attrval := d.node.GetExtendedAttribute(req.Name)
|
||||
if attrval != nil {
|
||||
resp.Xattr = attrval
|
||||
return nil
|
||||
}
|
||||
return fuse.ErrNoXattr
|
||||
}
|
||||
|
||||
@@ -164,3 +164,21 @@ func (f *file) Release(ctx context.Context, req *fuse.ReleaseRequest) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *file) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error {
|
||||
debug.Log("Listxattr(%v, %v)", f.node.Name, req.Size)
|
||||
for _, attr := range f.node.ExtendedAttributes {
|
||||
resp.Append(attr.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *file) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {
|
||||
debug.Log("Getxattr(%v, %v, %v)", f.node.Name, req.Name, req.Size)
|
||||
attrval := f.node.GetExtendedAttribute(req.Name)
|
||||
if attrval != nil {
|
||||
resp.Xattr = attrval
|
||||
return nil
|
||||
}
|
||||
return fuse.ErrNoXattr
|
||||
}
|
||||
|
||||
@@ -12,31 +12,38 @@ import (
|
||||
|
||||
"restic/errors"
|
||||
|
||||
"runtime"
|
||||
|
||||
"bytes"
|
||||
"restic/debug"
|
||||
"restic/fs"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// ExtendedAttribute is a tuple storing the xattr name and value.
|
||||
type ExtendedAttribute struct {
|
||||
Name string `json:"name"`
|
||||
Value []byte `json:"value"`
|
||||
}
|
||||
|
||||
// Node is a file, directory or other item in a backup.
|
||||
type Node struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Mode os.FileMode `json:"mode,omitempty"`
|
||||
ModTime time.Time `json:"mtime,omitempty"`
|
||||
AccessTime time.Time `json:"atime,omitempty"`
|
||||
ChangeTime time.Time `json:"ctime,omitempty"`
|
||||
UID uint32 `json:"uid"`
|
||||
GID uint32 `json:"gid"`
|
||||
User string `json:"user,omitempty"`
|
||||
Group string `json:"group,omitempty"`
|
||||
Inode uint64 `json:"inode,omitempty"`
|
||||
Size uint64 `json:"size,omitempty"`
|
||||
Links uint64 `json:"links,omitempty"`
|
||||
LinkTarget string `json:"linktarget,omitempty"`
|
||||
Device uint64 `json:"device,omitempty"`
|
||||
Content IDs `json:"content"`
|
||||
Subtree *ID `json:"subtree,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Mode os.FileMode `json:"mode,omitempty"`
|
||||
ModTime time.Time `json:"mtime,omitempty"`
|
||||
AccessTime time.Time `json:"atime,omitempty"`
|
||||
ChangeTime time.Time `json:"ctime,omitempty"`
|
||||
UID uint32 `json:"uid"`
|
||||
GID uint32 `json:"gid"`
|
||||
User string `json:"user,omitempty"`
|
||||
Group string `json:"group,omitempty"`
|
||||
Inode uint64 `json:"inode,omitempty"`
|
||||
Size uint64 `json:"size,omitempty"`
|
||||
Links uint64 `json:"links,omitempty"`
|
||||
LinkTarget string `json:"linktarget,omitempty"`
|
||||
ExtendedAttributes []ExtendedAttribute `json:"extended_attributes,omitempty"`
|
||||
Device uint64 `json:"device,omitempty"`
|
||||
Content IDs `json:"content"`
|
||||
Subtree *ID `json:"subtree,omitempty"`
|
||||
|
||||
Error string `json:"error,omitempty"`
|
||||
|
||||
@@ -96,6 +103,16 @@ func nodeTypeFromFileInfo(fi os.FileInfo) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetExtendedAttribute gets the extended attribute.
|
||||
func (node Node) GetExtendedAttribute(a string) []byte {
|
||||
for _, attr := range node.ExtendedAttributes {
|
||||
if attr.Name == a {
|
||||
return attr.Value
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateAt creates the node at the given path and restores all the meta data.
|
||||
func (node *Node) CreateAt(path string, repo Repository, idx *HardlinkIndex) error {
|
||||
debug.Log("create node %v at %v", node.Name, path)
|
||||
@@ -162,6 +179,22 @@ func (node Node) restoreMetadata(path string) error {
|
||||
}
|
||||
}
|
||||
|
||||
err = node.restoreExtendedAttributes(path)
|
||||
if err != nil {
|
||||
debug.Log("error restoring extended attributes for %v: %v", path, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return 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
|
||||
}
|
||||
|
||||
@@ -350,6 +383,9 @@ func (node Node) Equals(other Node) bool {
|
||||
if !node.sameContent(other) {
|
||||
return false
|
||||
}
|
||||
if !node.sameExtendedAttributes(other) {
|
||||
return false
|
||||
}
|
||||
if node.Subtree != nil {
|
||||
if other.Subtree == nil {
|
||||
return false
|
||||
@@ -388,6 +424,51 @@ func (node Node) sameContent(other Node) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (node Node) sameExtendedAttributes(other Node) bool {
|
||||
if len(node.ExtendedAttributes) != len(other.ExtendedAttributes) {
|
||||
return false
|
||||
}
|
||||
|
||||
// build a set of all attributes that node has
|
||||
type mapvalue struct {
|
||||
value []byte
|
||||
present bool
|
||||
}
|
||||
attributes := make(map[string]mapvalue)
|
||||
for _, attr := range node.ExtendedAttributes {
|
||||
attributes[attr.Name] = mapvalue{value: attr.Value}
|
||||
}
|
||||
|
||||
for _, attr := range other.ExtendedAttributes {
|
||||
v, ok := attributes[attr.Name]
|
||||
if !ok {
|
||||
// extended attribute is not set for node
|
||||
debug.Log("other node has attribute %v, which is not present in node", attr.Name)
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
if !bytes.Equal(v.value, attr.Value) {
|
||||
// attribute has different value
|
||||
debug.Log("attribute %v has different value", attr.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
// remember that this attribute is present in other.
|
||||
v.present = true
|
||||
attributes[attr.Name] = v
|
||||
}
|
||||
|
||||
// check for attributes that are not present in other
|
||||
for name, v := range attributes {
|
||||
if !v.present {
|
||||
debug.Log("attribute %v not present in other node", name)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -497,6 +578,9 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error {
|
||||
node.LinkTarget, err = fs.Readlink(path)
|
||||
node.Links = uint64(stat.nlink())
|
||||
err = errors.Wrap(err, "Readlink")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "dev":
|
||||
node.Device = uint64(stat.rdev())
|
||||
node.Links = uint64(stat.nlink())
|
||||
@@ -506,9 +590,32 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error {
|
||||
case "fifo":
|
||||
case "socket":
|
||||
default:
|
||||
err = errors.Errorf("invalid node type %q", node.Type)
|
||||
return errors.Errorf("invalid node type %q", node.Type)
|
||||
}
|
||||
|
||||
if err = node.fillExtendedAttributes(path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (node *Node) fillExtendedAttributes(path string) error {
|
||||
if node.Type == "symlink" {
|
||||
return nil
|
||||
}
|
||||
xattrs, err := Listxattr(path)
|
||||
if err == nil {
|
||||
node.ExtendedAttributes = make([]ExtendedAttribute, len(xattrs))
|
||||
for i, attr := range xattrs {
|
||||
attrVal, err := Getxattr(path, attr)
|
||||
if err != nil {
|
||||
return errors.Errorf("can not obtain extended attribute %v for %v:\n", attr, path)
|
||||
}
|
||||
node.ExtendedAttributes[i].Name = attr
|
||||
node.ExtendedAttributes[i].Value = attrVal
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -9,3 +9,19 @@ func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespe
|
||||
func (s statUnix) atim() syscall.Timespec { return s.Atimespec }
|
||||
func (s statUnix) mtim() syscall.Timespec { return s.Mtimespec }
|
||||
func (s statUnix) ctim() syscall.Timespec { return s.Ctimespec }
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Setxattr associates name and data together as an attribute of path.
|
||||
func Setxattr(path, name string, data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,3 +9,19 @@ func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespe
|
||||
func (s statUnix) atim() syscall.Timespec { return s.Atim }
|
||||
func (s statUnix) mtim() syscall.Timespec { return s.Mtim }
|
||||
func (s statUnix) ctim() syscall.Timespec { return s.Ctim }
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Setxattr associates name and data together as an attribute of path.
|
||||
func Setxattr(path, name string, data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -22,6 +22,22 @@ func (node Node) restoreSymlinkTimestamps(path string, utimes [2]syscall.Timespe
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Setxattr associates name and data together as an attribute of path.
|
||||
func Setxattr(path, name string, data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type statWin syscall.Win32FileAttributeData
|
||||
|
||||
//ToStatT call the Windows system call Win32FileAttributeData.
|
||||
|
||||
38
src/restic/node_xattr.go
Normal file
38
src/restic/node_xattr.go
Normal file
@@ -0,0 +1,38 @@
|
||||
// +build !openbsd
|
||||
// +build !windows
|
||||
// +build !freebsd
|
||||
|
||||
package restic
|
||||
|
||||
import (
|
||||
"github.com/ivaxer/go-xattr"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Getxattr retrieves extended attribute data associated with path.
|
||||
func Getxattr(path, name string) ([]byte, error) {
|
||||
b, e := xattr.Get(path, name)
|
||||
if e == syscall.ENOTSUP {
|
||||
return nil, nil
|
||||
}
|
||||
return b, e
|
||||
}
|
||||
|
||||
// Listxattr retrieves a list of names of extended attributes associated with the
|
||||
// given path in the file system.
|
||||
func Listxattr(path string) ([]string, error) {
|
||||
s, e := xattr.List(path)
|
||||
if e == syscall.ENOTSUP {
|
||||
return nil, nil
|
||||
}
|
||||
return s, e
|
||||
}
|
||||
|
||||
// Setxattr associates name and data together as an attribute of path.
|
||||
func Setxattr(path, name string, data []byte) error {
|
||||
e := xattr.Set(path, name, data)
|
||||
if e == syscall.ENOTSUP {
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
}
|
||||
@@ -82,7 +82,7 @@ func TestNodeComparison(t *testing.T) {
|
||||
fi, err := os.Lstat("tree_test.go")
|
||||
OK(t, err)
|
||||
|
||||
node, err := restic.NodeFromFileInfo("foo", fi)
|
||||
node, err := restic.NodeFromFileInfo("tree_test.go", fi)
|
||||
OK(t, err)
|
||||
|
||||
n2 := *node
|
||||
|
||||
Reference in New Issue
Block a user