From 49cae0904fd7f83ec6133cdac49339cd142b5e4a Mon Sep 17 00:00:00 2001 From: Jaap Gordijn Date: Thu, 2 Feb 2017 12:23:13 +0100 Subject: [PATCH] Add support for extended attributes (e.g. ACL) --- src/restic/archiver/archiver.go | 2 +- src/restic/fuse/dir.go | 18 ++ src/restic/fuse/file.go | 18 ++ src/restic/node.go | 147 +++++++++++++-- src/restic/node_freebsd.go | 16 ++ src/restic/node_openbsd.go | 16 ++ src/restic/node_windows.go | 16 ++ src/restic/node_xattr.go | 38 ++++ src/restic/tree_test.go | 2 +- vendor/manifest | 6 + vendor/src/github.com/ivaxer/go-xattr/LICENCE | 25 +++ .../src/github.com/ivaxer/go-xattr/README.md | 10 + .../ivaxer/go-xattr/syscall_darwin.go | 156 ++++++++++++++++ .../ivaxer/go-xattr/syscall_linux.go | 21 +++ .../src/github.com/ivaxer/go-xattr/xattr.go | 171 ++++++++++++++++++ .../ivaxer/go-xattr/xattr_darwin.go | 9 + .../github.com/ivaxer/go-xattr/xattr_linux.go | 9 + .../github.com/ivaxer/go-xattr/xattr_test.go | 153 ++++++++++++++++ 18 files changed, 811 insertions(+), 22 deletions(-) create mode 100644 src/restic/node_xattr.go create mode 100644 vendor/src/github.com/ivaxer/go-xattr/LICENCE create mode 100644 vendor/src/github.com/ivaxer/go-xattr/README.md create mode 100644 vendor/src/github.com/ivaxer/go-xattr/syscall_darwin.go create mode 100644 vendor/src/github.com/ivaxer/go-xattr/syscall_linux.go create mode 100644 vendor/src/github.com/ivaxer/go-xattr/xattr.go create mode 100644 vendor/src/github.com/ivaxer/go-xattr/xattr_darwin.go create mode 100644 vendor/src/github.com/ivaxer/go-xattr/xattr_linux.go create mode 100644 vendor/src/github.com/ivaxer/go-xattr/xattr_test.go diff --git a/src/restic/archiver/archiver.go b/src/restic/archiver/archiver.go index 9670db1aa..3412f4b35 100644 --- a/src/restic/archiver/archiver.go +++ b/src/restic/archiver/archiver.go @@ -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 diff --git a/src/restic/fuse/dir.go b/src/restic/fuse/dir.go index 9cdefaa3c..7968a2ee5 100644 --- a/src/restic/fuse/dir.go +++ b/src/restic/fuse/dir.go @@ -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 +} diff --git a/src/restic/fuse/file.go b/src/restic/fuse/file.go index 95bf0c074..dfc8aa308 100644 --- a/src/restic/fuse/file.go +++ b/src/restic/fuse/file.go @@ -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 +} diff --git a/src/restic/node.go b/src/restic/node.go index 5349b5494..1512848c8 100644 --- a/src/restic/node.go +++ b/src/restic/node.go @@ -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 } diff --git a/src/restic/node_freebsd.go b/src/restic/node_freebsd.go index a3f97096d..b588a2b68 100644 --- a/src/restic/node_freebsd.go +++ b/src/restic/node_freebsd.go @@ -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 +} diff --git a/src/restic/node_openbsd.go b/src/restic/node_openbsd.go index 4c7779835..8ca4f95b8 100644 --- a/src/restic/node_openbsd.go +++ b/src/restic/node_openbsd.go @@ -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 +} diff --git a/src/restic/node_windows.go b/src/restic/node_windows.go index 43b6d9b62..b75f1489f 100644 --- a/src/restic/node_windows.go +++ b/src/restic/node_windows.go @@ -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. diff --git a/src/restic/node_xattr.go b/src/restic/node_xattr.go new file mode 100644 index 000000000..87038d24f --- /dev/null +++ b/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 { + return nil + } + return e +} diff --git a/src/restic/tree_test.go b/src/restic/tree_test.go index 1d23e9240..967ac18cc 100644 --- a/src/restic/tree_test.go +++ b/src/restic/tree_test.go @@ -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 diff --git a/vendor/manifest b/vendor/manifest index 1d128b73a..bbefc5d0b 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -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", diff --git a/vendor/src/github.com/ivaxer/go-xattr/LICENCE b/vendor/src/github.com/ivaxer/go-xattr/LICENCE new file mode 100644 index 000000000..75451ac03 --- /dev/null +++ b/vendor/src/github.com/ivaxer/go-xattr/LICENCE @@ -0,0 +1,25 @@ +Copyright (c) 2012 Dave Cheney. All rights reserved. +Copyright (c) 2013 Alexey Palazhchenko. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/src/github.com/ivaxer/go-xattr/README.md b/vendor/src/github.com/ivaxer/go-xattr/README.md new file mode 100644 index 000000000..9f3a71b56 --- /dev/null +++ b/vendor/src/github.com/ivaxer/go-xattr/README.md @@ -0,0 +1,10 @@ +xattr +===== + +Package xattr provides a simple interface to user extended attributes on Linux and OSX. + +Install it: `go get github.com/ivaxer/go-xattr` + +Documentation is available on [godoc.org](http://godoc.org/github.com/ivaxer/go-xattr). + +License: Simplified BSD License (see LICENSE). diff --git a/vendor/src/github.com/ivaxer/go-xattr/syscall_darwin.go b/vendor/src/github.com/ivaxer/go-xattr/syscall_darwin.go new file mode 100644 index 000000000..eee8c9a54 --- /dev/null +++ b/vendor/src/github.com/ivaxer/go-xattr/syscall_darwin.go @@ -0,0 +1,156 @@ +package xattr + +import ( + "syscall" + "unsafe" +) + +func get(path, attr string, buf []byte) (rs int, err error) { + return getxattr(path, attr, buf, 0, 0) +} + +// getxattr retrieves value of the extended attribute identified by attr +// associated with given path in filesystem into buffer buf. +// +// options specify options for retrieving extended attributes: +// - syscall.XATTR_NOFOLLOW +// - syscall.XATTR_SHOWCOMPRESSION +// +// position should be zero. For advanded usage see getxattr(2). +// +// On success, buf contains data associated with attr, retrieved value size sz +// and nil error returned. +// +// On error, non-nil error returned. It returns error if buf was to small. +// +// A nil slice can be passed as buf to get current size of attribute value, +// which can be used to estimate buf length for value associated with attr. +// +// See getxattr(2) for more details. +// +// ssize_t getxattr(const char *path, const char *name, void *value, size_t size, u_int32_t position, int options); +func getxattr(path, name string, buf []byte, position, options int) (sz int, err error) { + p, err := syscall.BytePtrFromString(path) + if err != nil { + return + } + + n, err := syscall.BytePtrFromString(name) + if err != nil { + return + } + + var b *byte + if len(buf) > 0 { + b = &buf[0] + } + + r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, + uintptr(unsafe.Pointer(p)), + uintptr(unsafe.Pointer(n)), + uintptr(unsafe.Pointer(b)), + uintptr(len(buf)), + uintptr(position), + uintptr(options)) + + sz = int(r0) + if e1 != 0 { + err = e1 + } + + return +} + +func list(path string, dest []byte) (sz int, err error) { + return listxattr(path, dest, 0) +} + +// ssize_t listxattr(const char *path, char *namebuf, size_t size, int options); +func listxattr(path string, buf []byte, options int) (sz int, err error) { + p, err := syscall.BytePtrFromString(path) + if err != nil { + return + } + + var b *byte + if len(buf) > 0 { + b = &buf[0] + } + + r0, _, e1 := syscall.Syscall6(syscall.SYS_LISTXATTR, + uintptr(unsafe.Pointer(p)), + uintptr(unsafe.Pointer(b)), + uintptr(len(buf)), + uintptr(options), 0, 0) + + sz = int(r0) + if e1 != 0 { + err = e1 + } + + return +} + +func set(path, attr string, data []byte, flags int) error { + return setxattr(path, attr, data, 0, flags) +} + +// int setxattr(const char *path, const char *name, void *value, size_t size, u_int32_t position, int options); +func setxattr(path string, name string, data []byte, position, options int) (err error) { + p, err := syscall.BytePtrFromString(path) + if err != nil { + return + } + + n, err := syscall.BytePtrFromString(name) + if err != nil { + return + } + + var b *byte + if len(data) > 0 { + b = &data[0] + } + + _, _, e1 := syscall.Syscall6(syscall.SYS_SETXATTR, + uintptr(unsafe.Pointer(p)), + uintptr(unsafe.Pointer(n)), + uintptr(unsafe.Pointer(b)), + uintptr(len(data)), + uintptr(position), + uintptr(options)) + + if e1 != 0 { + err = e1 + } + + return +} + +func remove(path, attr string) error { + return removexattr(path, attr, 0) +} + +// int removexattr(const char *path, const char *name, int options); +func removexattr(path string, name string, options int) (err error) { + p, err := syscall.BytePtrFromString(path) + if err != nil { + return + } + + n, err := syscall.BytePtrFromString(name) + if err != nil { + return + } + + _, _, e1 := syscall.Syscall(syscall.SYS_REMOVEXATTR, + uintptr(unsafe.Pointer(p)), + uintptr(unsafe.Pointer(n)), + uintptr(options)) + + if e1 != 0 { + err = e1 + } + + return +} diff --git a/vendor/src/github.com/ivaxer/go-xattr/syscall_linux.go b/vendor/src/github.com/ivaxer/go-xattr/syscall_linux.go new file mode 100644 index 000000000..4aa5380ec --- /dev/null +++ b/vendor/src/github.com/ivaxer/go-xattr/syscall_linux.go @@ -0,0 +1,21 @@ +package xattr + +import ( + "syscall" +) + +func get(path, attr string, dest []byte) (sz int, err error) { + return syscall.Getxattr(path, attr, dest) +} + +func list(path string, dest []byte) (sz int, err error) { + return syscall.Listxattr(path, dest) +} + +func set(path, attr string, data []byte, flags int) error { + return syscall.Setxattr(path, attr, data, flags) +} + +func remove(path, attr string) error { + return syscall.Removexattr(path, attr) +} diff --git a/vendor/src/github.com/ivaxer/go-xattr/xattr.go b/vendor/src/github.com/ivaxer/go-xattr/xattr.go new file mode 100644 index 000000000..8de7b4278 --- /dev/null +++ b/vendor/src/github.com/ivaxer/go-xattr/xattr.go @@ -0,0 +1,171 @@ +// Package xattr provides a simple interface to user extended attributes on +// Linux and OSX. Support for xattrs is filesystem dependant, so not a given +// even if you are running one of those operating systems. +// +// On Linux you have to edit /etc/fstab to include "user_xattr". Also, on Linux +// user's extended attributes have a manditory prefix of "user.". +package xattr + +import ( + "os" +) + +// IsNotExist returns a boolean indicating whether the error is known to report +// that an extended attribute does not exist. +func IsNotExist(err error) bool { + if e, ok := err.(*os.PathError); ok { + err = e.Err + } + + return isNotExist(err) +} + +// Converts an array of NUL terminated UTF-8 strings +// to a []string. +func nullTermToStrings(buf []byte) (result []string) { + offset := 0 + for index, b := range buf { + if b == 0 { + result = append(result, string(buf[offset:index])) + offset = index + 1 + } + } + return +} + +// Getxattr retrieves value of the extended attribute identified by attr +// associated with given path in filesystem into buffer dest. +// +// On success, dest contains data associated with attr, retrieved value size sz +// and nil error are returned. +// +// On error, non-nil error is returned. Getxattr returns error if dest was too +// small for attribute value. +// +// A nil slice can be passed as dest to get current size of attribute value, +// which can be used to estimate dest length for value associated with attr. +// +// See getxattr(2) for more information. +// +// Get is high-level function on top of Getxattr. Getxattr more efficient, +// because it issues one syscall per call, doesn't allocate memory for +// attribute data (caller can reuse buffer). +func Getxattr(path, attr string, dest []byte) (sz int, err error) { + return get(path, attr, dest) +} + +// Get retrieves extended attribute data associated with path. If there is an +// error, it will be of type *os.PathError. +// +// See Getxattr for low-level usage. +func Get(path, attr string) ([]byte, error) { + // find size + size, err := Getxattr(path, attr, nil) + if err != nil { + return nil, &os.PathError{"getxattr", path, err} + } + if size == 0 { + return []byte{}, nil + } + + // read into buffer of that size + buf := make([]byte, size) + size, err = Getxattr(path, attr, buf) + if err != nil { + return nil, &os.PathError{"getxattr", path, err} + } + return buf[:size], nil +} + +// Listxattr retrieves the list of extended attribute names associated with +// path. The list is set of NULL-terminated names. +// +// On success, dest containes list of NULL-terminated names, the length of the +// extended attribute list and nil error are returned. +// +// On error, non nil error is returned. Listxattr returns error if dest buffer +// was too small for extended attribute list. +// +// The list of names is returned as an unordered array of NULL-terminated +// character strings (attribute names are separated by NULL characters), like +// this: +// user.name1\0system.name1\0user.name2\0 +// +// A nil slice can be passed as dest to get the current size of the list of +// extended attribute names, which can be used to estimate dest length for +// the list of names. +// +// See listxattr(2) for more information. +// +// List is high-level function on top of Listxattr. +func Listxattr(path string, dest []byte) (sz int, err error) { + return list(path, dest) +} + +// List retrieves a list of names of extended attributes associated with path. +// If there is an error, it will be of type *os.PathError. +// +// See Listxattr for low-level usage. +func List(path string) ([]string, error) { + // find size + size, err := Listxattr(path, nil) + if err != nil { + return nil, &os.PathError{"listxattr", path, err} + } + if size == 0 { + return []string{}, nil + } + + // read into buffer of that size + buf := make([]byte, size) + size, err = Listxattr(path, buf) + if err != nil { + return nil, &os.PathError{"listxattr", path, err} + } + return nullTermToStrings(buf[:size]), nil +} + +// Setxattr sets value in data of extended attribute attr and accosiated with +// path. +// +// The flags refine the semantic of the operation. XATTR_CREATE specifies pure +// create, which fails if attr already exists. XATTR_REPLACE specifies a pure +// replace operation, which fails if the attr does not already exist. By +// default (no flags), the attr will be created if need be, or will simply +// replace the value if attr exists. +// +// On error, non nil error is returned. +// +// See setxattr(2) for more information. +func Setxattr(path, attr string, data []byte, flags int) error { + return set(path, attr, data, flags) +} + +// Set associates data as an extended attribute of path. If there is an error, +// it will be of type *os.PathError. +// +// See Setxattr for low-level usage. +func Set(path, attr string, data []byte) error { + if err := Setxattr(path, attr, data, 0); err != nil { + return &os.PathError{"setxattr", path, err} + } + return nil +} + +// Removexattr removes the extended attribute attr accosiated with path. +// +// On error, non-nil error is returned. +// +// See removexattr(2) for more information. +func Removexattr(path, attr string) error { + return remove(path, attr) +} + +// Remove removes the extended attribute. If there is an error, it will be of +// type *os.PathError. +func Remove(path, attr string) error { + if err := Removexattr(path, attr); err != nil { + return &os.PathError{"removexattr", path, err} + } + return nil +} diff --git a/vendor/src/github.com/ivaxer/go-xattr/xattr_darwin.go b/vendor/src/github.com/ivaxer/go-xattr/xattr_darwin.go new file mode 100644 index 000000000..ff19634bd --- /dev/null +++ b/vendor/src/github.com/ivaxer/go-xattr/xattr_darwin.go @@ -0,0 +1,9 @@ +package xattr + +import ( + "syscall" +) + +func isNotExist(err error) bool { + return err == syscall.ENOATTR +} diff --git a/vendor/src/github.com/ivaxer/go-xattr/xattr_linux.go b/vendor/src/github.com/ivaxer/go-xattr/xattr_linux.go new file mode 100644 index 000000000..60b4d5a79 --- /dev/null +++ b/vendor/src/github.com/ivaxer/go-xattr/xattr_linux.go @@ -0,0 +1,9 @@ +package xattr + +import ( + "syscall" +) + +func isNotExist(err error) bool { + return err == syscall.ENODATA +} diff --git a/vendor/src/github.com/ivaxer/go-xattr/xattr_test.go b/vendor/src/github.com/ivaxer/go-xattr/xattr_test.go new file mode 100644 index 000000000..12d01926a --- /dev/null +++ b/vendor/src/github.com/ivaxer/go-xattr/xattr_test.go @@ -0,0 +1,153 @@ +package xattr + +import ( + "bytes" + "io/ioutil" + "os" + "sort" + "testing" +) + +var tmpdir = os.Getenv("TEST_XATTR_PATH") + +func mktemp(t *testing.T) *os.File { + file, err := ioutil.TempFile(tmpdir, "test_xattr_") + if err != nil { + t.Fatalf("TempFile() failed: %v", err) + } + + return file +} + +func stringsEqual(got, expected []string) bool { + if len(got) != len(expected) { + return false + } + + for i := range got { + if got[i] != expected[i] { + return false + } + } + + return true +} + +// expected must be sorted slice of attribute names. +func checkList(t *testing.T, path string, expected []string) { + got, err := List(path) + if err != nil { + t.Fatalf("List(%q) failed: %v", path, err) + } + + sort.Strings(got) + + if !stringsEqual(got, expected) { + t.Errorf("List(%q): expected %v, got %v", path, got, expected) + } +} + +func checkListError(t *testing.T, path string, f func(error) bool) { + got, err := List(path) + if !f(err) { + t.Errorf("List(%q): unexpected error value: %v", path, err) + } + + if got != nil { + t.Error("List(): expected nil slice on error") + } +} + +func checkSet(t *testing.T, path, attr string, data []byte) { + if err := Set(path, attr, data); err != nil { + t.Fatalf("Set(%q, %q, %v) failed: %v", path, attr, data, err) + } +} + +func checkSetError(t *testing.T, path, attr string, data []byte, f func(error) bool) { + if err := Set(path, attr, data); !f(err) { + t.Fatalf("Set(%q, %q, %v): unexpected error value: %v", path, attr, data, err) + } +} + +func checkGet(t *testing.T, path, attr string, expected []byte) { + got, err := Get(path, attr) + if err != nil { + t.Fatalf("Get(%q, %q) failed: %v", path, attr, err) + } + + if !bytes.Equal(got, expected) { + t.Errorf("Get(%q, %q): got %v, expected %v", path, attr, got, expected) + } +} + +func checkGetError(t *testing.T, path, attr string, f func(error) bool) { + got, err := Get(path, attr) + if !f(err) { + t.Errorf("Get(%q, %q): unexpected error value: %v", path, attr, err) + } + + if got != nil { + t.Error("Get(): expected nil slice on error") + } +} + +func checkRemove(t *testing.T, path, attr string) { + if err := Remove(path, attr); err != nil { + t.Fatalf("Remove(%q, %q) failed: %v", path, attr, err) + } +} + +func checkRemoveError(t *testing.T, path, attr string, f func(error) bool) { + if err := Remove(path, attr); !f(err) { + t.Errorf("Remove(%q, %q): unexpected error value: %v", path, attr, err) + } +} + +func TestFlow(t *testing.T) { + f := mktemp(t) + defer func() { f.Close(); os.Remove(f.Name()) }() + + path := f.Name() + data := []byte("test xattr data") + attr := "user.test xattr" + attr2 := "user.text xattr 2" + + checkList(t, path, []string{}) + checkSet(t, path, attr, data) + checkList(t, path, []string{attr}) + checkSet(t, path, attr2, data) + checkList(t, path, []string{attr, attr2}) + checkGet(t, path, attr, data) + checkGetError(t, path, "user.unknown attr", IsNotExist) + checkRemove(t, path, attr) + checkList(t, path, []string{attr2}) + checkRemove(t, path, attr2) + checkList(t, path, []string{}) +} + +func TestEmptyAttr(t *testing.T) { + f := mktemp(t) + defer func() { f.Close(); os.Remove(f.Name()) }() + + path := f.Name() + attr := "user.test xattr" + data := []byte{} + + checkSet(t, path, attr, data) + checkList(t, path, []string{attr}) + checkGet(t, path, attr, []byte{}) + checkRemove(t, path, attr) + checkList(t, path, []string{}) +} + +func TestNoFile(t *testing.T) { + path := "no-such-file" + attr := "user.test xattr" + data := []byte("test_xattr data") + + checkListError(t, path, os.IsNotExist) + checkSetError(t, path, attr, data, os.IsNotExist) + checkGetError(t, path, attr, os.IsNotExist) + checkRemoveError(t, path, attr, os.IsNotExist) +}