mirror of
https://github.com/octoleo/restic.git
synced 2024-11-04 20:37:49 +00:00
Merge pull request #766 from jgfrm/nissue25
Add support for extended attributes (e.g. ACL) Closes #766
This commit is contained in:
commit
0674f32d79
@ -385,7 +385,7 @@ func (arch *Archiver) dirWorker(wg *sync.WaitGroup, p *restic.Progress, done <-c
|
|||||||
node := &restic.Node{}
|
node := &restic.Node{}
|
||||||
|
|
||||||
if dir.Path() != "" && dir.Info() != nil {
|
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 {
|
if err != nil {
|
||||||
n.Error = err.Error()
|
n.Error = err.Error()
|
||||||
dir.Result() <- n
|
dir.Result() <- n
|
||||||
|
@ -177,3 +177,21 @@ func (d *dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
|||||||
return nil, fuse.ENOENT
|
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
|
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"
|
"restic/errors"
|
||||||
|
|
||||||
"runtime"
|
"bytes"
|
||||||
|
|
||||||
"restic/debug"
|
"restic/debug"
|
||||||
"restic/fs"
|
"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.
|
// Node is a file, directory or other item in a backup.
|
||||||
type Node struct {
|
type Node struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@ -34,6 +40,7 @@ type Node struct {
|
|||||||
Size uint64 `json:"size,omitempty"`
|
Size uint64 `json:"size,omitempty"`
|
||||||
Links uint64 `json:"links,omitempty"`
|
Links uint64 `json:"links,omitempty"`
|
||||||
LinkTarget string `json:"linktarget,omitempty"`
|
LinkTarget string `json:"linktarget,omitempty"`
|
||||||
|
ExtendedAttributes []ExtendedAttribute `json:"extended_attributes,omitempty"`
|
||||||
Device uint64 `json:"device,omitempty"`
|
Device uint64 `json:"device,omitempty"`
|
||||||
Content IDs `json:"content"`
|
Content IDs `json:"content"`
|
||||||
Subtree *ID `json:"subtree,omitempty"`
|
Subtree *ID `json:"subtree,omitempty"`
|
||||||
@ -96,6 +103,16 @@ func nodeTypeFromFileInfo(fi os.FileInfo) string {
|
|||||||
return ""
|
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.
|
// 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 {
|
func (node *Node) CreateAt(path string, repo Repository, idx *HardlinkIndex) error {
|
||||||
debug.Log("create node %v at %v", node.Name, path)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,6 +383,9 @@ func (node Node) Equals(other Node) bool {
|
|||||||
if !node.sameContent(other) {
|
if !node.sameContent(other) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if !node.sameExtendedAttributes(other) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if node.Subtree != nil {
|
if node.Subtree != nil {
|
||||||
if other.Subtree == nil {
|
if other.Subtree == nil {
|
||||||
return false
|
return false
|
||||||
@ -388,6 +424,51 @@ func (node Node) sameContent(other Node) bool {
|
|||||||
return false
|
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
|
return true
|
||||||
}
|
}
|
||||||
@ -497,6 +578,9 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error {
|
|||||||
node.LinkTarget, err = fs.Readlink(path)
|
node.LinkTarget, err = fs.Readlink(path)
|
||||||
node.Links = uint64(stat.nlink())
|
node.Links = uint64(stat.nlink())
|
||||||
err = errors.Wrap(err, "Readlink")
|
err = errors.Wrap(err, "Readlink")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
case "dev":
|
case "dev":
|
||||||
node.Device = uint64(stat.rdev())
|
node.Device = uint64(stat.rdev())
|
||||||
node.Links = uint64(stat.nlink())
|
node.Links = uint64(stat.nlink())
|
||||||
@ -506,9 +590,32 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error {
|
|||||||
case "fifo":
|
case "fifo":
|
||||||
case "socket":
|
case "socket":
|
||||||
default:
|
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
|
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) atim() syscall.Timespec { return s.Atimespec }
|
||||||
func (s statUnix) mtim() syscall.Timespec { return s.Mtimespec }
|
func (s statUnix) mtim() syscall.Timespec { return s.Mtimespec }
|
||||||
func (s statUnix) ctim() syscall.Timespec { return s.Ctimespec }
|
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) atim() syscall.Timespec { return s.Atim }
|
||||||
func (s statUnix) mtim() syscall.Timespec { return s.Mtim }
|
func (s statUnix) mtim() syscall.Timespec { return s.Mtim }
|
||||||
func (s statUnix) ctim() syscall.Timespec { return s.Ctim }
|
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
|
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
|
type statWin syscall.Win32FileAttributeData
|
||||||
|
|
||||||
//ToStatT call the Windows system call 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")
|
fi, err := os.Lstat("tree_test.go")
|
||||||
OK(t, err)
|
OK(t, err)
|
||||||
|
|
||||||
node, err := restic.NodeFromFileInfo("foo", fi)
|
node, err := restic.NodeFromFileInfo("tree_test.go", fi)
|
||||||
OK(t, err)
|
OK(t, err)
|
||||||
|
|
||||||
n2 := *node
|
n2 := *node
|
||||||
|
6
vendor/manifest
vendored
6
vendor/manifest
vendored
@ -19,6 +19,12 @@
|
|||||||
"revision": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75",
|
"revision": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75",
|
||||||
"branch": "master"
|
"branch": "master"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"importpath": "github.com/ivaxer/go-xattr",
|
||||||
|
"repository": "https://github.com/ivaxer/go-xattr",
|
||||||
|
"revision": "1a541654d8e447148cf23d472c948f9f0078ac50",
|
||||||
|
"branch": "master"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"importpath": "github.com/kr/fs",
|
"importpath": "github.com/kr/fs",
|
||||||
"repository": "https://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