2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-02 19:49:44 +00:00

Merge pull request #766 from jgfrm/nissue25

Add support for extended attributes (e.g. ACL)

Closes #766
This commit is contained in:
Alexander Neumann 2017-02-16 11:45:24 +01:00
commit 0674f32d79
18 changed files with 811 additions and 22 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

6
vendor/manifest vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,9 @@
package xattr
import (
"syscall"
)
func isNotExist(err error) bool {
return err == syscall.ENOATTR
}

View File

@ -0,0 +1,9 @@
package xattr
import (
"syscall"
)
func isNotExist(err error) bool {
return err == syscall.ENODATA
}

View File

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