Merge pull request #809 from restic/fix-xattr

WIP: fix panic
This commit is contained in:
Alexander Neumann 2017-02-18 15:00:49 +01:00
commit 23c2717ab2
20 changed files with 465 additions and 577 deletions

View file

@ -577,9 +577,8 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error {
case "symlink":
node.LinkTarget, err = fs.Readlink(path)
node.Links = uint64(stat.nlink())
err = errors.Wrap(err, "Readlink")
if err != nil {
return err
return errors.Wrap(err, "Readlink")
}
case "dev":
node.Device = uint64(stat.rdev())
@ -597,26 +596,36 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error {
return err
}
return err
return nil
}
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 {
debug.Log("fillExtendedAttributes(%v) %v %v", path, xattrs, err)
if err != nil {
return err
}
node.ExtendedAttributes = make([]ExtendedAttribute, 0, len(xattrs))
for _, 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)
fmt.Fprintf(os.Stderr, "can not obtain extended attribute %v for %v:\n", attr, path)
continue
}
node.ExtendedAttributes[i].Name = attr
node.ExtendedAttributes[i].Value = attrVal
attr := ExtendedAttribute{
Name: attr,
Value: attrVal,
}
node.ExtendedAttributes = append(node.ExtendedAttributes, attr)
}
return err
return nil
}
type statT interface {

View file

@ -9,19 +9,3 @@ 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

@ -1,38 +1,39 @@
// +build !openbsd
// +build !windows
// +build !freebsd
package restic
import (
"github.com/ivaxer/go-xattr"
"restic/errors"
"syscall"
"github.com/pkg/xattr"
)
// 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 {
b, e := xattr.Getxattr(path, name)
if err, ok := e.(*xattr.XAttrError); ok && err.Err == syscall.ENOTSUP {
return nil, nil
}
return b, e
return b, errors.Wrap(e, "Getxattr")
}
// 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 {
s, e := xattr.Listxattr(path)
if err, ok := e.(*xattr.XAttrError); ok && err.Err == syscall.ENOTSUP {
return nil, nil
}
return s, e
return s, errors.Wrap(e, "Listxattr")
}
// 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 {
e := xattr.Setxattr(path, name, data)
if err, ok := e.(*xattr.XAttrError); ok && err.Err == syscall.ENOTSUP {
return nil
}
return e
return errors.Wrap(e, "Setxattr")
}

12
vendor/manifest vendored
View file

@ -19,12 +19,6 @@
"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",
@ -55,6 +49,12 @@
"revision": "8197a2e580736b78d704be0fc47b2324c0591a32",
"branch": "master"
},
{
"importpath": "github.com/pkg/xattr",
"repository": "https://github.com/pkg/xattr",
"revision": "1d40b70a947cd8e8457e4715e1123f8e99f5f241",
"branch": "master"
},
{
"importpath": "github.com/restic/chunker",
"repository": "https://github.com/restic/chunker",

View file

@ -1,10 +0,0 @@
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

@ -1,156 +0,0 @@
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

@ -1,21 +0,0 @@
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

@ -1,171 +0,0 @@
// 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

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

View file

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

View file

@ -1,153 +0,0 @@
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)
}

View file

@ -1,5 +1,5 @@
Copyright (c) 2012 Dave Cheney. All rights reserved.
Copyright (c) 2013 Alexey Palazhchenko. All rights reserved.
Copyright (c) 2014 Kuba Podgórski. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are

View file

@ -0,0 +1,21 @@
xattr
=====
Extended attribute support for Go (linux + darwin + freebsd).
"Extended attributes are name:value pairs associated permanently with files and directories, similar to the environment strings associated with a process. An attribute may be defined or undefined. If it is defined, its value may be empty or non-empty." [See more...](https://en.wikipedia.org/wiki/Extended_file_attributes)
### Example
```
const path = "/tmp/myfile"
const prefix = "user."
if err = Setxattr(path, prefix+"test", []byte("test-attr-value")); err != nil {
t.Fatal(err)
}
var data []byte
data, err = Getxattr(path, prefix+"test"); err != nil {
t.Fatal(err)
}
```

View file

@ -0,0 +1,39 @@
// +build darwin
package xattr
import (
"syscall"
"unsafe"
)
func getxattr(path string, name string, value *byte, size int, pos int, options int) (int, error) {
r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(syscall.StringBytePtr(path))), uintptr(unsafe.Pointer(syscall.StringBytePtr(name))), uintptr(unsafe.Pointer(value)), uintptr(size), uintptr(pos), uintptr(options))
if e1 != syscall.Errno(0) {
return int(r0), e1
}
return int(r0), nil
}
func listxattr(path string, namebuf *byte, size int, options int) (int, error) {
r0, _, e1 := syscall.Syscall6(syscall.SYS_LISTXATTR, uintptr(unsafe.Pointer(syscall.StringBytePtr(path))), uintptr(unsafe.Pointer(namebuf)), uintptr(size), uintptr(options), 0, 0)
if e1 != syscall.Errno(0) {
return int(r0), e1
}
return int(r0), nil
}
func setxattr(path string, name string, value *byte, size int, pos int, options int) error {
if _, _, e1 := syscall.Syscall6(syscall.SYS_SETXATTR, uintptr(unsafe.Pointer(syscall.StringBytePtr(path))), uintptr(unsafe.Pointer(syscall.StringBytePtr(name))), uintptr(unsafe.Pointer(value)), uintptr(size), uintptr(pos), uintptr(options)); e1 != syscall.Errno(0) {
return e1
}
return nil
}
func removexattr(path string, name string, options int) error {
if _, _, e1 := syscall.Syscall(syscall.SYS_REMOVEXATTR, uintptr(unsafe.Pointer(syscall.StringBytePtr(path))), uintptr(unsafe.Pointer(syscall.StringBytePtr(name))), uintptr(options)); e1 != syscall.Errno(0) {
return e1
}
return nil
}

View file

@ -0,0 +1,91 @@
// +build freebsd
package xattr
import (
"syscall"
"unsafe"
)
/*
ssize_t
extattr_get_file(const char *path, int attrnamespace,
const char *attrname, void *data, size_t nbytes);
ssize_t
extattr_set_file(const char *path, int attrnamespace,
const char *attrname, const void *data, size_t nbytes);
int
extattr_delete_file(const char *path, int attrnamespace,
const char *attrname);
ssize_t
extattr_list_file(const char *path, int attrnamespace, void *data,
size_t nbytes);
*/
func extattr_get_file(path string, attrnamespace int, attrname string, data *byte, nbytes int) (int, error) {
r, _, e := syscall.Syscall6(
syscall.SYS_EXTATTR_GET_FILE,
uintptr(unsafe.Pointer(syscall.StringBytePtr(path))),
uintptr(attrnamespace),
uintptr(unsafe.Pointer(syscall.StringBytePtr(attrname))),
uintptr(unsafe.Pointer(data)),
uintptr(nbytes),
0,
)
var err error
if e != 0 {
err = e
}
return int(r), err
}
func extattr_set_file(path string, attrnamespace int, attrname string, data *byte, nbytes int) (int, error) {
r, _, e := syscall.Syscall6(
syscall.SYS_EXTATTR_SET_FILE,
uintptr(unsafe.Pointer(syscall.StringBytePtr(path))),
uintptr(attrnamespace),
uintptr(unsafe.Pointer(syscall.StringBytePtr(attrname))),
uintptr(unsafe.Pointer(data)),
uintptr(nbytes),
0,
)
var err error
if e != 0 {
err = e
}
return int(r), err
}
func extattr_delete_file(path string, attrnamespace int, attrname string) error {
_, _, e := syscall.Syscall(
syscall.SYS_EXTATTR_DELETE_FILE,
uintptr(unsafe.Pointer(syscall.StringBytePtr(path))),
uintptr(attrnamespace),
uintptr(unsafe.Pointer(syscall.StringBytePtr(attrname))),
)
var err error
if e != 0 {
err = e
}
return err
}
func extattr_list_file(path string, attrnamespace int, data *byte, nbytes int) (int, error) {
r, _, e := syscall.Syscall6(
syscall.SYS_EXTATTR_LIST_FILE,
uintptr(unsafe.Pointer(syscall.StringBytePtr(path))),
uintptr(attrnamespace),
uintptr(unsafe.Pointer(data)),
uintptr(nbytes),
0,
0,
)
var err error
if e != 0 {
err = e
}
return int(r), err
}

View file

@ -0,0 +1,26 @@
package xattr
// XAttrError records an error and the operation, file path and attribute that caused it.
type XAttrError struct {
Op string
Path string
Name string
Err error
}
func (e *XAttrError) Error() string {
return e.Op + " " + e.Path + " " + e.Name + ": " + e.Err.Error()
}
// Convert an array of NULL 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
}

View file

@ -0,0 +1,56 @@
// +build darwin
package xattr
// Retrieve extended attribute data associated with path.
func Getxattr(path, name string) ([]byte, error) {
// find size.
size, err := getxattr(path, name, nil, 0, 0, 0)
if err != nil {
return nil, &XAttrError{"getxattr", path, name, err}
}
buf := make([]byte, size)
// Read into buffer of that size.
read, err := getxattr(path, name, &buf[0], size, 0, 0)
if err != nil {
return nil, &XAttrError{"getxattr", path, name, err}
}
return buf[:read], nil
}
// Retrieves a list of names of extended attributes associated with the
// given path in the file system.
func Listxattr(path string) ([]string, error) {
// find size.
size, err := listxattr(path, nil, 0, 0)
if err != nil {
return nil, &XAttrError{"listxattr", path, "", err}
}
if size == 0 {
return []string{}, nil
}
buf := make([]byte, size)
// Read into buffer of that size.
read, err := listxattr(path, &buf[0], size, 0)
if err != nil {
return nil, &XAttrError{"listxattr", path, "", err}
}
return nullTermToStrings(buf[:read]), nil
}
// Associates name and data together as an attribute of path.
func Setxattr(path, name string, data []byte) error {
if err := setxattr(path, name, &data[0], len(data), 0, 0); err != nil {
return &XAttrError{"setxattr", path, name, err}
}
return nil
}
// Remove the attribute.
func Removexattr(path, name string) error {
if err := removexattr(path, name, 0); err != nil {
return &XAttrError{"removexattr", path, name, err}
}
return nil
}

View file

@ -0,0 +1,79 @@
// +build freebsd
package xattr
import (
"syscall"
)
const (
EXTATTR_NAMESPACE_USER = 1
)
// Retrieve extended attribute data associated with path.
func Getxattr(path, name string) ([]byte, error) {
// find size.
size, err := extattr_get_file(path, EXTATTR_NAMESPACE_USER, name, nil, 0)
if err != nil {
return nil, &XAttrError{"extattr_get_file", path, name, err}
}
buf := make([]byte, size)
// Read into buffer of that size.
read, err := extattr_get_file(path, EXTATTR_NAMESPACE_USER, name, &buf[0], size)
if err != nil {
return nil, &XAttrError{"extattr_get_file", path, name, err}
}
return buf[:read], nil
}
// Retrieves a list of names of extended attributes associated with the
// given path in the file system.
func Listxattr(path string) ([]string, error) {
// find size.
size, err := extattr_list_file(path, EXTATTR_NAMESPACE_USER, nil, 0)
if err != nil {
return nil, &XAttrError{"extattr_list_file", path, "", err}
}
buf := make([]byte, size)
// Read into buffer of that size.
read, err := extattr_list_file(path, EXTATTR_NAMESPACE_USER, &buf[0], size)
if err != nil {
return nil, &XAttrError{"extattr_list_file", path, "", err}
}
return attrListToStrings(buf[:read]), nil
}
// Associates name and data together as an attribute of path.
func Setxattr(path, name string, data []byte) error {
written, err := extattr_set_file(path, EXTATTR_NAMESPACE_USER, name, &data[0], len(data))
if err != nil {
return &XAttrError{"extattr_set_file", path, name, err}
}
if written != len(data) {
return &XAttrError{"extattr_set_file", path, name, syscall.E2BIG}
}
return nil
}
// Remove the attribute.
func Removexattr(path, name string) error {
if err := extattr_delete_file(path, EXTATTR_NAMESPACE_USER, name); err != nil {
return &XAttrError{"extattr_delete_file", path, name, err}
}
return nil
}
// Convert a sequnce of attribute name entries to a []string.
// Each entry consists of a single byte containing the length
// of the attribute name, followed by the attribute name.
// The name is _not_ terminated by NUL.
func attrListToStrings(buf []byte) []string {
var result []string
index := 0
for index < len(buf) {
next := index + 1 + int(buf[index])
result = append(result, string(buf[index+1:next]))
index = next
}
return result
}

View file

@ -0,0 +1,54 @@
// +build linux
package xattr
import "syscall"
// Retrieve extended attribute data associated with path.
func Getxattr(path, name string) ([]byte, error) {
// find size.
size, err := syscall.Getxattr(path, name, nil)
if err != nil {
return nil, &XAttrError{"getxattr", path, name, err}
}
data := make([]byte, size)
// Read into buffer of that size.
read, err := syscall.Getxattr(path, name, data)
if err != nil {
return nil, &XAttrError{"getxattr", path, name, err}
}
return data[:read], nil
}
// Retrieves a list of names of extended attributes associated with the
// given path in the file system.
func Listxattr(path string) ([]string, error) {
// find size.
size, err := syscall.Listxattr(path, nil)
if err != nil {
return nil, &XAttrError{"listxattr", path, "", err}
}
buf := make([]byte, size)
// Read into buffer of that size.
read, err := syscall.Listxattr(path, buf)
if err != nil {
return nil, &XAttrError{"listxattr", path, "", err}
}
return nullTermToStrings(buf[:read]), nil
}
// Associates name and data together as an attribute of path.
func Setxattr(path, name string, data []byte) error {
if err := syscall.Setxattr(path, name, data, 0); err != nil {
return &XAttrError{"setxattr", path, name, err}
}
return nil
}
// Remove the attribute.
func Removexattr(path, name string) error {
if err := syscall.Removexattr(path, name); err != nil {
return &XAttrError{"removexattr", path, name, err}
}
return nil
}

View file

@ -0,0 +1,57 @@
// +build linux darwin freebsd
package xattr
import (
"io/ioutil"
"os"
"testing"
)
const UserPrefix = "user."
func Test_setxattr(t *testing.T) {
tmp, err := ioutil.TempFile("", "")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmp.Name())
err = Setxattr(tmp.Name(), UserPrefix+"test", []byte("test-attr-value"))
if err != nil {
t.Fatal(err)
}
list, err := Listxattr(tmp.Name())
if err != nil {
t.Fatal(err)
}
found := false
for _, name := range list {
if name == UserPrefix+"test" {
found = true
}
}
if !found {
t.Fatal("Listxattr did not return test attribute")
}
var data []byte
data, err = Getxattr(tmp.Name(), UserPrefix+"test")
if err != nil {
t.Fatal(err)
}
value := string(data)
t.Log(value)
if "test-attr-value" != value {
t.Fail()
}
err = Removexattr(tmp.Name(), UserPrefix+"test")
if err != nil {
t.Fatal(err)
}
}