Skip to content

Commit 0817db4

Browse files
committed
Add full implementation of xattr for linux and darwin
Include support for no follow symbolic link on both systems Signed-off-by: Derek McGowan <[email protected]> (github: dmcgowan)
1 parent cb63fde commit 0817db4

15 files changed

Lines changed: 932 additions & 175 deletions

driver_unix.go

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1+
// +build linux darwin
2+
13
package continuity
24

35
import (
46
"fmt"
57
"sort"
8+
9+
"github.com/stevvooe/continuity/sysx"
610
)
711

812
// Getxattr returns all of the extended attributes for the file at path p.
913
func (d *driver) Getxattr(p string) (map[string][]byte, error) {
10-
xattrs, err := Listxattr(p)
14+
xattrs, err := sysx.Listxattr(p)
1115
if err != nil {
1216
return nil, fmt.Errorf("listing %s xattrs: %v", p, err)
1317
}
@@ -16,9 +20,9 @@ func (d *driver) Getxattr(p string) (map[string][]byte, error) {
1620
m := make(map[string][]byte, len(xattrs))
1721

1822
for _, attr := range xattrs {
19-
value, err := Getxattr(p, attr)
23+
value, err := sysx.Getxattr(p, attr)
2024
if err != nil {
21-
return nil, fmt.Errorf("getting %q xattr on %s: %v", p, attr, err)
25+
return nil, fmt.Errorf("getting %q xattr on %s: %v", attr, p, err)
2226
}
2327

2428
// NOTE(stevvooe): This append/copy tricky relies on unique
@@ -34,6 +38,52 @@ func (d *driver) Getxattr(p string) (map[string][]byte, error) {
3438
// any symbolic links, if necessary. All attributes on the target are
3539
// replaced by the values from attr. If the operation fails to set any
3640
// attribute, those already applied will not be rolled back.
37-
func (d *driver) Setxattr(path string, attr map[string][]byte) error {
38-
panic("not implemented")
41+
func (d *driver) Setxattr(path string, attrMap map[string][]byte) error {
42+
for attr, value := range attrMap {
43+
if err := sysx.Setxattr(path, attr, value, 0); err != nil {
44+
return fmt.Errorf("error setting xattr %q on %s: %v", attr, path, err)
45+
}
46+
}
47+
48+
return nil
49+
}
50+
51+
// LGetxattr returns all of the extended attributes for the file at path p
52+
// not following symbolic links.
53+
func (d *driver) LGetxattr(p string) (map[string][]byte, error) {
54+
xattrs, err := sysx.LListxattr(p)
55+
if err != nil {
56+
return nil, fmt.Errorf("listing %s xattrs: %v", p, err)
57+
}
58+
59+
sort.Strings(xattrs)
60+
m := make(map[string][]byte, len(xattrs))
61+
62+
for _, attr := range xattrs {
63+
value, err := sysx.LGetxattr(p, attr)
64+
if err != nil {
65+
return nil, fmt.Errorf("getting %q xattr on %s: %v", attr, p, err)
66+
}
67+
68+
// NOTE(stevvooe): This append/copy tricky relies on unique
69+
// xattrs. Break this out into an alloc/copy if xattrs are no
70+
// longer unique.
71+
m[attr] = append(m[attr], value...)
72+
}
73+
74+
return m, nil
75+
}
76+
77+
// LSetxattr sets all of the extended attributes on file at path, not
78+
// following any symbolic links. All attributes on the target are
79+
// replaced by the values from attr. If the operation fails to set any
80+
// attribute, those already applied will not be rolled back.
81+
func (d *driver) LSetxattr(path string, attrMap map[string][]byte) error {
82+
for attr, value := range attrMap {
83+
if err := sysx.LSetxattr(path, attr, value, 0); err != nil {
84+
return fmt.Errorf("error setting xattr %q on %s: %v", attr, path, err)
85+
}
86+
}
87+
88+
return nil
3989
}
File renamed without changes.

sysx/generate.sh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/bin/sh
2+
3+
set -e
4+
5+
mksyscall="$(go env GOROOT)/src/syscall/mksyscall.pl"
6+
7+
fix() {
8+
sed 's,^package syscall$,package sysx,' \
9+
| sed 's,^import "unsafe"$,import (\n\t"syscall"\n\t"unsafe"\n),' \
10+
| gofmt -r='BytePtrFromString -> syscall.BytePtrFromString' \
11+
| gofmt -r='Syscall6 -> syscall.Syscall6' \
12+
| gofmt -r='Syscall -> syscall.Syscall' \
13+
| gofmt -r='SYS_GETXATTR -> syscall.SYS_GETXATTR' \
14+
| gofmt -r='SYS_LISTXATTR -> syscall.SYS_LISTXATTR' \
15+
| gofmt -r='SYS_SETXATTR -> syscall.SYS_SETXATTR' \
16+
| gofmt -r='SYS_REMOVEXATTR -> syscall.SYS_REMOVEXATTR' \
17+
| gofmt -r='SYS_LGETXATTR -> syscall.SYS_LGETXATTR' \
18+
| gofmt -r='SYS_LLISTXATTR -> syscall.SYS_LLISTXATTR' \
19+
| gofmt -r='SYS_LSETXATTR -> syscall.SYS_LSETXATTR' \
20+
| gofmt -r='SYS_LREMOVEXATTR -> syscall.SYS_LREMOVEXATTR'
21+
}
22+
23+
if [ "$GOARCH" == "" ] || [ "$GOOS" == "" ]; then
24+
echo "Must specify \$GOARCH and \$GOOS"
25+
exit 1
26+
fi
27+
28+
mkargs=""
29+
30+
if [ "$GOARCH" == "386" ] || [ "$GOARCH" == "arm" ]; then
31+
mkargs="-l32"
32+
fi
33+
34+
for f in "$@"; do
35+
$mksyscall $mkargs "${f}_${GOOS}.go" | fix > "${f}_${GOOS}_${GOARCH}.go"
36+
done
37+

sysx/sys.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package sysx
2+
3+
import (
4+
"syscall"
5+
"unsafe"
6+
)
7+
8+
var _zero uintptr
9+
10+
// use is a no-op, but the compiler cannot see that it is.
11+
// Calling use(p) ensures that p is kept live until that point.
12+
//go:noescape
13+
func use(p unsafe.Pointer)
14+
15+
// Do the interface allocations only once for common
16+
// Errno values.
17+
var (
18+
errEAGAIN error = syscall.EAGAIN
19+
errEINVAL error = syscall.EINVAL
20+
errENOENT error = syscall.ENOENT
21+
)
22+
23+
// errnoErr returns common boxed Errno values, to prevent
24+
// allocations at runtime.
25+
func errnoErr(e syscall.Errno) error {
26+
switch e {
27+
case 0:
28+
return nil
29+
case syscall.EAGAIN:
30+
return errEAGAIN
31+
case syscall.EINVAL:
32+
return errEINVAL
33+
case syscall.ENOENT:
34+
return errENOENT
35+
}
36+
return e
37+
}

xattr.go renamed to sysx/xattr.go

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,19 @@
1-
package continuity
1+
package sysx
22

33
import (
44
"bytes"
55
"syscall"
66
)
77

8-
const (
9-
XATTR_NOFOLLOW = iota << 1
10-
XATTR_CREATE
11-
XATTR_REPLACE
12-
)
13-
148
const defaultXattrBufferSize = 5
159

16-
func Setxattr(path, name string, data []byte) error {
17-
return setxattr(path, name, data, XATTR_NOFOLLOW)
18-
}
10+
type listxattrFunc func(path string, dest []byte) (int, error)
1911

20-
func Listxattr(path string) ([]string, error) {
12+
func listxattrAll(path string, listFunc listxattrFunc) ([]string, error) {
2113
var p []byte // nil on first execution
2214

2315
for {
24-
n, err := listxattr(path, p, XATTR_NOFOLLOW) // first call gets buffer size.
16+
n, err := listFunc(path, p) // first call gets buffer size.
2517
if err != nil {
2618
return nil, err
2719
}
@@ -46,10 +38,12 @@ func Listxattr(path string) ([]string, error) {
4638
}
4739
}
4840

49-
func Getxattr(path, attr string) ([]byte, error) {
50-
var p []byte = make([]byte, defaultXattrBufferSize)
41+
type getxattrFunc func(string, string, []byte) (int, error)
42+
43+
func getxattrAll(path, attr string, getFunc getxattrFunc) ([]byte, error) {
44+
p := make([]byte, defaultXattrBufferSize)
5145
for {
52-
n, err := getxattr(path, attr, p)
46+
n, err := getFunc(path, attr, p)
5347
if err != nil {
5448
if errno, ok := err.(syscall.Errno); ok && errno == syscall.ERANGE {
5549
p = make([]byte, len(p)*2) // this can't be ideal.

sysx/xattr_darwin.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package sysx
2+
3+
// These functions will be generated by generate.sh
4+
// $ GOOS=darwin GOARCH=386 ./generate.sh xattr
5+
// $ GOOS=darwin GOARCH=amd64 ./generate.sh xattr
6+
7+
//sys getxattr(path string, attr string, dest []byte, pos int, options int) (sz int, err error)
8+
//sys setxattr(path string, attr string, data []byte, flags int) (err error)
9+
//sys removexattr(path string, attr string, options int) (err error)
10+
//sys listxattr(path string, dest []byte, options int) (sz int, err error)
11+
12+
const (
13+
xattrNoFollow = 0x01
14+
)
15+
16+
func listxattrFollow(path string, dest []byte) (sz int, err error) {
17+
return listxattr(path, dest, 0)
18+
}
19+
20+
// Listxattr calls syscall getxattr
21+
func Listxattr(path string) ([]string, error) {
22+
return listxattrAll(path, listxattrFollow)
23+
}
24+
25+
// Removexattr calls syscall getxattr
26+
func Removexattr(path string, attr string) (err error) {
27+
return removexattr(path, attr, 0)
28+
}
29+
30+
// Setxattr calls syscall setxattr
31+
func Setxattr(path string, attr string, data []byte, flags int) (err error) {
32+
return setxattr(path, attr, data, flags)
33+
}
34+
35+
func getxattrFollow(path, attr string, dest []byte) (sz int, err error) {
36+
return getxattr(path, attr, dest, 0, 0)
37+
}
38+
39+
// Getxattr calls syscall getxattr
40+
func Getxattr(path, attr string) ([]byte, error) {
41+
return getxattrAll(path, attr, getxattrFollow)
42+
}
43+
44+
func listxattrNoFollow(path string, dest []byte) (sz int, err error) {
45+
return listxattr(path, dest, xattrNoFollow)
46+
}
47+
48+
// LListxattr calls syscall listxattr with XATTR_NOFOLLOW
49+
func LListxattr(path string) ([]string, error) {
50+
return listxattrAll(path, listxattrNoFollow)
51+
}
52+
53+
// LRemovexattr calls syscall removexattr with XATTR_NOFOLLOW
54+
func LRemovexattr(path string, attr string) (err error) {
55+
return removexattr(path, attr, xattrNoFollow)
56+
}
57+
58+
// Setxattr calls syscall setxattr with XATTR_NOFOLLOW
59+
func LSetxattr(path string, attr string, data []byte, flags int) (err error) {
60+
return setxattr(path, attr, data, flags|xattrNoFollow)
61+
}
62+
63+
func getxattrNoFollow(path, attr string, dest []byte) (sz int, err error) {
64+
return getxattr(path, attr, dest, 0, xattrNoFollow)
65+
}
66+
67+
// LGetxattr calls syscall getxattr with XATTR_NOFOLLOW
68+
func LGetxattr(path, attr string) ([]byte, error) {
69+
return getxattrAll(path, attr, getxattrNoFollow)
70+
}

sysx/xattr_darwin_386.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// mksyscall.pl -l32 xattr_darwin.go
2+
// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
3+
4+
package sysx
5+
6+
import (
7+
"syscall"
8+
"unsafe"
9+
)
10+
11+
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
12+
13+
func getxattr(path string, attr string, dest []byte, pos int, options int) (sz int, err error) {
14+
var _p0 *byte
15+
_p0, err = syscall.BytePtrFromString(path)
16+
if err != nil {
17+
return
18+
}
19+
var _p1 *byte
20+
_p1, err = syscall.BytePtrFromString(attr)
21+
if err != nil {
22+
return
23+
}
24+
var _p2 unsafe.Pointer
25+
if len(dest) > 0 {
26+
_p2 = unsafe.Pointer(&dest[0])
27+
} else {
28+
_p2 = unsafe.Pointer(&_zero)
29+
}
30+
r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(dest)), uintptr(pos), uintptr(options))
31+
use(unsafe.Pointer(_p0))
32+
use(unsafe.Pointer(_p1))
33+
sz = int(r0)
34+
if e1 != 0 {
35+
err = errnoErr(e1)
36+
}
37+
return
38+
}
39+
40+
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
41+
42+
func setxattr(path string, attr string, data []byte, flags int) (err error) {
43+
var _p0 *byte
44+
_p0, err = syscall.BytePtrFromString(path)
45+
if err != nil {
46+
return
47+
}
48+
var _p1 *byte
49+
_p1, err = syscall.BytePtrFromString(attr)
50+
if err != nil {
51+
return
52+
}
53+
var _p2 unsafe.Pointer
54+
if len(data) > 0 {
55+
_p2 = unsafe.Pointer(&data[0])
56+
} else {
57+
_p2 = unsafe.Pointer(&_zero)
58+
}
59+
_, _, e1 := syscall.Syscall6(syscall.SYS_SETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(_p2), uintptr(len(data)), uintptr(flags), 0)
60+
use(unsafe.Pointer(_p0))
61+
use(unsafe.Pointer(_p1))
62+
if e1 != 0 {
63+
err = errnoErr(e1)
64+
}
65+
return
66+
}
67+
68+
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
69+
70+
func removexattr(path string, attr string, options int) (err error) {
71+
var _p0 *byte
72+
_p0, err = syscall.BytePtrFromString(path)
73+
if err != nil {
74+
return
75+
}
76+
var _p1 *byte
77+
_p1, err = syscall.BytePtrFromString(attr)
78+
if err != nil {
79+
return
80+
}
81+
_, _, e1 := syscall.Syscall(syscall.SYS_REMOVEXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_p1)), uintptr(options))
82+
use(unsafe.Pointer(_p0))
83+
use(unsafe.Pointer(_p1))
84+
if e1 != 0 {
85+
err = errnoErr(e1)
86+
}
87+
return
88+
}
89+
90+
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
91+
92+
func listxattr(path string, dest []byte, options int) (sz int, err error) {
93+
var _p0 *byte
94+
_p0, err = syscall.BytePtrFromString(path)
95+
if err != nil {
96+
return
97+
}
98+
var _p1 unsafe.Pointer
99+
if len(dest) > 0 {
100+
_p1 = unsafe.Pointer(&dest[0])
101+
} else {
102+
_p1 = unsafe.Pointer(&_zero)
103+
}
104+
r0, _, e1 := syscall.Syscall6(syscall.SYS_LISTXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(dest)), uintptr(options), 0, 0)
105+
use(unsafe.Pointer(_p0))
106+
sz = int(r0)
107+
if e1 != 0 {
108+
err = errnoErr(e1)
109+
}
110+
return
111+
}

0 commit comments

Comments
 (0)