Skip to content

Commit

Permalink
Merge pull request #766 from jgfrm/nissue25
Browse files Browse the repository at this point in the history
Add support for extended attributes (e.g. ACL)

Closes #766
  • Loading branch information
fd0 committed Feb 16, 2017
2 parents 40685a0 + 49cae09 commit 0674f32
Show file tree
Hide file tree
Showing 18 changed files with 811 additions and 22 deletions.
2 changes: 1 addition & 1 deletion src/restic/archiver/archiver.go
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions src/restic/fuse/dir.go
Expand Up @@ -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
}
18 changes: 18 additions & 0 deletions src/restic/fuse/file.go
Expand Up @@ -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
}
147 changes: 127 additions & 20 deletions src/restic/node.go
Expand Up @@ -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"`

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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())
Expand All @@ -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
}

Expand Down
16 changes: 16 additions & 0 deletions src/restic/node_freebsd.go
Expand Up @@ -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
}
16 changes: 16 additions & 0 deletions src/restic/node_openbsd.go
Expand Up @@ -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
}
16 changes: 16 additions & 0 deletions src/restic/node_windows.go
Expand Up @@ -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.
Expand Down
38 changes: 38 additions & 0 deletions src/restic/node_xattr.go
@@ -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 {

This comment has been minimized.

Copy link
@ThomasWaldmann

ThomasWaldmann Mar 21, 2017

Contributor

you may also encounter E2BIG (made on OS X, too big for linux).

return nil
}
return e
}
2 changes: 1 addition & 1 deletion src/restic/tree_test.go
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions vendor/manifest
Expand Up @@ -19,6 +19,12 @@
"revision": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75",
"branch": "master"
},
{
"importpath": "github.com/ivaxer/go-xattr",
"repository": "https://github.com/ivaxer/go-xattr",
"revision": "1a541654d8e447148cf23d472c948f9f0078ac50",
"branch": "master"
},
{
"importpath": "github.com/kr/fs",
"repository": "https://github.com/kr/fs",
Expand Down

0 comments on commit 0674f32

Please sign in to comment.