mirror of
https://github.com/octoleo/restic.git
synced 2024-11-22 21:05:10 +00:00
Add support for extended attributes (e.g. ACL)
This commit is contained in:
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,12 +12,18 @@ 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"`
|
||||
@ -34,6 +40,7 @@ type Node struct {
|
||||
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"`
|
||||
@ -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
|
||||
|
6
vendor/manifest
vendored
6
vendor/manifest
vendored
@ -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",
|
||||
|
25
vendor/src/github.com/ivaxer/go-xattr/LICENCE
vendored
Normal file
25
vendor/src/github.com/ivaxer/go-xattr/LICENCE
vendored
Normal 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.
|
10
vendor/src/github.com/ivaxer/go-xattr/README.md
vendored
Normal file
10
vendor/src/github.com/ivaxer/go-xattr/README.md
vendored
Normal 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).
|
156
vendor/src/github.com/ivaxer/go-xattr/syscall_darwin.go
vendored
Normal file
156
vendor/src/github.com/ivaxer/go-xattr/syscall_darwin.go
vendored
Normal 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
|
||||
}
|
21
vendor/src/github.com/ivaxer/go-xattr/syscall_linux.go
vendored
Normal file
21
vendor/src/github.com/ivaxer/go-xattr/syscall_linux.go
vendored
Normal 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)
|
||||
}
|
171
vendor/src/github.com/ivaxer/go-xattr/xattr.go
vendored
Normal file
171
vendor/src/github.com/ivaxer/go-xattr/xattr.go
vendored
Normal 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
|
||||
}
|
9
vendor/src/github.com/ivaxer/go-xattr/xattr_darwin.go
vendored
Normal file
9
vendor/src/github.com/ivaxer/go-xattr/xattr_darwin.go
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
package xattr
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func isNotExist(err error) bool {
|
||||
return err == syscall.ENOATTR
|
||||
}
|
9
vendor/src/github.com/ivaxer/go-xattr/xattr_linux.go
vendored
Normal file
9
vendor/src/github.com/ivaxer/go-xattr/xattr_linux.go
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
package xattr
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func isNotExist(err error) bool {
|
||||
return err == syscall.ENODATA
|
||||
}
|
153
vendor/src/github.com/ivaxer/go-xattr/xattr_test.go
vendored
Normal file
153
vendor/src/github.com/ivaxer/go-xattr/xattr_test.go
vendored
Normal 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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user