unix: add F*xattr on Darwin

Add Fgetxattr, Flistxattr, Fremovexattr and Fsetxattr on Darwin. Also
add a corresponding test.

Updates golang/go#26832

Change-Id: Id75bfce90ccc024b567a7b066a9188a615b9eec4
Reviewed-on: https://go-review.googlesource.com/128537
Run-TryBot: Tobias Klauser <tobias.klauser@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/unix/syscall_darwin.go b/unix/syscall_darwin.go
index 166ac0b..1aabc56 100644
--- a/unix/syscall_darwin.go
+++ b/unix/syscall_darwin.go
@@ -199,7 +199,13 @@
 	return getxattr(link, attr, xattrPointer(dest), len(dest), 0, XATTR_NOFOLLOW)
 }
 
-//sys  setxattr(path string, attr string, data *byte, size int, position uint32, options int) (err error)
+//sys	fgetxattr(fd int, attr string, dest *byte, size int, position uint32, options int) (sz int, err error)
+
+func Fgetxattr(fd int, attr string, dest []byte) (sz int, err error) {
+	return fgetxattr(fd, attr, xattrPointer(dest), len(dest), 0, 0)
+}
+
+//sys	setxattr(path string, attr string, data *byte, size int, position uint32, options int) (err error)
 
 func Setxattr(path string, attr string, data []byte, flags int) (err error) {
 	// The parameters for the OS X implementation vary slightly compared to the
@@ -235,7 +241,13 @@
 	return setxattr(link, attr, xattrPointer(data), len(data), 0, flags|XATTR_NOFOLLOW)
 }
 
-//sys removexattr(path string, attr string, options int) (err error)
+//sys	fsetxattr(fd int, attr string, data *byte, size int, position uint32, options int) (err error)
+
+func Fsetxattr(fd int, attr string, data []byte, flags int) (err error) {
+	return fsetxattr(fd, attr, xattrPointer(data), len(data), 0, 0)
+}
+
+//sys	removexattr(path string, attr string, options int) (err error)
 
 func Removexattr(path string, attr string) (err error) {
 	// We wrap around and explicitly zero out the options provided to the OS X
@@ -248,6 +260,12 @@
 	return removexattr(link, attr, XATTR_NOFOLLOW)
 }
 
+//sys	fremovexattr(fd int, attr string, options int) (err error)
+
+func Fremovexattr(fd int, attr string) (err error) {
+	return fremovexattr(fd, attr, 0)
+}
+
 //sys	listxattr(path string, dest *byte, size int, options int) (sz int, err error)
 
 func Listxattr(path string, dest []byte) (sz int, err error) {
@@ -258,6 +276,12 @@
 	return listxattr(link, xattrPointer(dest), len(dest), XATTR_NOFOLLOW)
 }
 
+//sys	flistxattr(fd int, dest *byte, size int, options int) (sz int, err error)
+
+func Flistxattr(fd int, dest []byte) (sz int, err error) {
+	return flistxattr(fd, xattrPointer(dest), len(dest), 0)
+}
+
 func setattrlistTimes(path string, times []Timespec, flags int) error {
 	_p0, err := BytePtrFromString(path)
 	if err != nil {
@@ -529,10 +553,6 @@
 // Watchevent
 // Waitevent
 // Modwatch
-// Fgetxattr
-// Fsetxattr
-// Fremovexattr
-// Flistxattr
 // Fsctl
 // Initgroups
 // Posix_spawn
diff --git a/unix/xattr_test.go b/unix/xattr_test.go
index b8b28d0..595492e 100644
--- a/unix/xattr_test.go
+++ b/unix/xattr_test.go
@@ -7,6 +7,7 @@
 package unix_test
 
 import (
+	"io/ioutil"
 	"os"
 	"runtime"
 	"strings"
@@ -117,3 +118,84 @@
 		}
 	}
 }
+
+func TestFdXattr(t *testing.T) {
+	file, err := ioutil.TempFile("", "TestFdXattr")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(file.Name())
+	defer file.Close()
+
+	fd := int(file.Fd())
+	xattrName := "user.test"
+	xattrDataSet := "gopher"
+
+	err = unix.Fsetxattr(fd, xattrName, []byte(xattrDataSet), 0)
+	if err == unix.ENOTSUP || err == unix.EOPNOTSUPP {
+		t.Skip("filesystem does not support extended attributes, skipping test")
+	} else if err != nil {
+		t.Fatalf("Fsetxattr: %v", err)
+	}
+
+	// find size
+	size, err := unix.Flistxattr(fd, nil)
+	if err != nil {
+		t.Fatalf("Flistxattr: %v", err)
+	}
+
+	if size <= 0 {
+		t.Fatalf("Flistxattr returned an empty list of attributes")
+	}
+
+	buf := make([]byte, size)
+	read, err := unix.Flistxattr(fd, buf)
+	if err != nil {
+		t.Fatalf("Flistxattr: %v", err)
+	}
+
+	xattrs := stringsFromByteSlice(buf[:read])
+
+	xattrWant := xattrName
+	if runtime.GOOS == "freebsd" {
+		// On FreeBSD, the namespace is stored separately from the xattr
+		// name and Listxattr doesn't return the namespace prefix.
+		xattrWant = strings.TrimPrefix(xattrWant, "user.")
+	}
+	found := false
+	for _, name := range xattrs {
+		if name == xattrWant {
+			found = true
+		}
+	}
+
+	if !found {
+		t.Errorf("Flistxattr did not return previously set attribute '%s'", xattrName)
+	}
+
+	// find size
+	size, err = unix.Fgetxattr(fd, xattrName, nil)
+	if err != nil {
+		t.Fatalf("Fgetxattr: %v", err)
+	}
+
+	if size <= 0 {
+		t.Fatalf("Fgetxattr returned an empty attribute")
+	}
+
+	xattrDataGet := make([]byte, size)
+	_, err = unix.Fgetxattr(fd, xattrName, xattrDataGet)
+	if err != nil {
+		t.Fatalf("Fgetxattr: %v", err)
+	}
+
+	got := string(xattrDataGet)
+	if got != xattrDataSet {
+		t.Errorf("Fgetxattr: expected attribute value %s, got %s", xattrDataSet, got)
+	}
+
+	err = unix.Fremovexattr(fd, xattrName)
+	if err != nil {
+		t.Fatalf("Fremovexattr: %v", err)
+	}
+}
diff --git a/unix/zsyscall_darwin_386.go b/unix/zsyscall_darwin_386.go
index ac02d4d..9ce06df 100644
--- a/unix/zsyscall_darwin_386.go
+++ b/unix/zsyscall_darwin_386.go
@@ -420,6 +420,22 @@
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func fgetxattr(fd int, attr string, dest *byte, size int, position uint32, options int) (sz int, err error) {
+	var _p0 *byte
+	_p0, err = BytePtrFromString(attr)
+	if err != nil {
+		return
+	}
+	r0, _, e1 := Syscall6(SYS_FGETXATTR, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(dest)), uintptr(size), uintptr(position), uintptr(options))
+	sz = int(r0)
+	if e1 != 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func setxattr(path string, attr string, data *byte, size int, position uint32, options int) (err error) {
 	var _p0 *byte
 	_p0, err = BytePtrFromString(path)
@@ -440,6 +456,21 @@
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func fsetxattr(fd int, attr string, data *byte, size int, position uint32, options int) (err error) {
+	var _p0 *byte
+	_p0, err = BytePtrFromString(attr)
+	if err != nil {
+		return
+	}
+	_, _, e1 := Syscall6(SYS_FSETXATTR, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(data)), uintptr(size), uintptr(position), uintptr(options))
+	if e1 != 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func removexattr(path string, attr string, options int) (err error) {
 	var _p0 *byte
 	_p0, err = BytePtrFromString(path)
@@ -460,6 +491,21 @@
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func fremovexattr(fd int, attr string, options int) (err error) {
+	var _p0 *byte
+	_p0, err = BytePtrFromString(attr)
+	if err != nil {
+		return
+	}
+	_, _, e1 := Syscall(SYS_FREMOVEXATTR, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(options))
+	if e1 != 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func listxattr(path string, dest *byte, size int, options int) (sz int, err error) {
 	var _p0 *byte
 	_p0, err = BytePtrFromString(path)
@@ -476,6 +522,17 @@
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func flistxattr(fd int, dest *byte, size int, options int) (sz int, err error) {
+	r0, _, e1 := Syscall6(SYS_FLISTXATTR, uintptr(fd), uintptr(unsafe.Pointer(dest)), uintptr(size), uintptr(options), 0, 0)
+	sz = int(r0)
+	if e1 != 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func kill(pid int, signum int, posix int) (err error) {
 	_, _, e1 := Syscall(SYS_KILL, uintptr(pid), uintptr(signum), uintptr(posix))
 	if e1 != 0 {
diff --git a/unix/zsyscall_darwin_amd64.go b/unix/zsyscall_darwin_amd64.go
index 1dd3cfa..de99270 100644
--- a/unix/zsyscall_darwin_amd64.go
+++ b/unix/zsyscall_darwin_amd64.go
@@ -420,6 +420,22 @@
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func fgetxattr(fd int, attr string, dest *byte, size int, position uint32, options int) (sz int, err error) {
+	var _p0 *byte
+	_p0, err = BytePtrFromString(attr)
+	if err != nil {
+		return
+	}
+	r0, _, e1 := Syscall6(SYS_FGETXATTR, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(dest)), uintptr(size), uintptr(position), uintptr(options))
+	sz = int(r0)
+	if e1 != 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func setxattr(path string, attr string, data *byte, size int, position uint32, options int) (err error) {
 	var _p0 *byte
 	_p0, err = BytePtrFromString(path)
@@ -440,6 +456,21 @@
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func fsetxattr(fd int, attr string, data *byte, size int, position uint32, options int) (err error) {
+	var _p0 *byte
+	_p0, err = BytePtrFromString(attr)
+	if err != nil {
+		return
+	}
+	_, _, e1 := Syscall6(SYS_FSETXATTR, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(data)), uintptr(size), uintptr(position), uintptr(options))
+	if e1 != 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func removexattr(path string, attr string, options int) (err error) {
 	var _p0 *byte
 	_p0, err = BytePtrFromString(path)
@@ -460,6 +491,21 @@
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func fremovexattr(fd int, attr string, options int) (err error) {
+	var _p0 *byte
+	_p0, err = BytePtrFromString(attr)
+	if err != nil {
+		return
+	}
+	_, _, e1 := Syscall(SYS_FREMOVEXATTR, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(options))
+	if e1 != 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func listxattr(path string, dest *byte, size int, options int) (sz int, err error) {
 	var _p0 *byte
 	_p0, err = BytePtrFromString(path)
@@ -476,6 +522,17 @@
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func flistxattr(fd int, dest *byte, size int, options int) (sz int, err error) {
+	r0, _, e1 := Syscall6(SYS_FLISTXATTR, uintptr(fd), uintptr(unsafe.Pointer(dest)), uintptr(size), uintptr(options), 0, 0)
+	sz = int(r0)
+	if e1 != 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func kill(pid int, signum int, posix int) (err error) {
 	_, _, e1 := Syscall(SYS_KILL, uintptr(pid), uintptr(signum), uintptr(posix))
 	if e1 != 0 {
diff --git a/unix/zsyscall_darwin_arm.go b/unix/zsyscall_darwin_arm.go
index cab46e7..81c4f09 100644
--- a/unix/zsyscall_darwin_arm.go
+++ b/unix/zsyscall_darwin_arm.go
@@ -420,6 +420,22 @@
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func fgetxattr(fd int, attr string, dest *byte, size int, position uint32, options int) (sz int, err error) {
+	var _p0 *byte
+	_p0, err = BytePtrFromString(attr)
+	if err != nil {
+		return
+	}
+	r0, _, e1 := Syscall6(SYS_FGETXATTR, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(dest)), uintptr(size), uintptr(position), uintptr(options))
+	sz = int(r0)
+	if e1 != 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func setxattr(path string, attr string, data *byte, size int, position uint32, options int) (err error) {
 	var _p0 *byte
 	_p0, err = BytePtrFromString(path)
@@ -440,6 +456,21 @@
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func fsetxattr(fd int, attr string, data *byte, size int, position uint32, options int) (err error) {
+	var _p0 *byte
+	_p0, err = BytePtrFromString(attr)
+	if err != nil {
+		return
+	}
+	_, _, e1 := Syscall6(SYS_FSETXATTR, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(data)), uintptr(size), uintptr(position), uintptr(options))
+	if e1 != 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func removexattr(path string, attr string, options int) (err error) {
 	var _p0 *byte
 	_p0, err = BytePtrFromString(path)
@@ -460,6 +491,21 @@
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func fremovexattr(fd int, attr string, options int) (err error) {
+	var _p0 *byte
+	_p0, err = BytePtrFromString(attr)
+	if err != nil {
+		return
+	}
+	_, _, e1 := Syscall(SYS_FREMOVEXATTR, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(options))
+	if e1 != 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func listxattr(path string, dest *byte, size int, options int) (sz int, err error) {
 	var _p0 *byte
 	_p0, err = BytePtrFromString(path)
@@ -476,6 +522,17 @@
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func flistxattr(fd int, dest *byte, size int, options int) (sz int, err error) {
+	r0, _, e1 := Syscall6(SYS_FLISTXATTR, uintptr(fd), uintptr(unsafe.Pointer(dest)), uintptr(size), uintptr(options), 0, 0)
+	sz = int(r0)
+	if e1 != 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func kill(pid int, signum int, posix int) (err error) {
 	_, _, e1 := Syscall(SYS_KILL, uintptr(pid), uintptr(signum), uintptr(posix))
 	if e1 != 0 {
diff --git a/unix/zsyscall_darwin_arm64.go b/unix/zsyscall_darwin_arm64.go
index 13478dd..338c32d 100644
--- a/unix/zsyscall_darwin_arm64.go
+++ b/unix/zsyscall_darwin_arm64.go
@@ -420,6 +420,22 @@
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func fgetxattr(fd int, attr string, dest *byte, size int, position uint32, options int) (sz int, err error) {
+	var _p0 *byte
+	_p0, err = BytePtrFromString(attr)
+	if err != nil {
+		return
+	}
+	r0, _, e1 := Syscall6(SYS_FGETXATTR, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(dest)), uintptr(size), uintptr(position), uintptr(options))
+	sz = int(r0)
+	if e1 != 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func setxattr(path string, attr string, data *byte, size int, position uint32, options int) (err error) {
 	var _p0 *byte
 	_p0, err = BytePtrFromString(path)
@@ -440,6 +456,21 @@
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func fsetxattr(fd int, attr string, data *byte, size int, position uint32, options int) (err error) {
+	var _p0 *byte
+	_p0, err = BytePtrFromString(attr)
+	if err != nil {
+		return
+	}
+	_, _, e1 := Syscall6(SYS_FSETXATTR, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(data)), uintptr(size), uintptr(position), uintptr(options))
+	if e1 != 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func removexattr(path string, attr string, options int) (err error) {
 	var _p0 *byte
 	_p0, err = BytePtrFromString(path)
@@ -460,6 +491,21 @@
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func fremovexattr(fd int, attr string, options int) (err error) {
+	var _p0 *byte
+	_p0, err = BytePtrFromString(attr)
+	if err != nil {
+		return
+	}
+	_, _, e1 := Syscall(SYS_FREMOVEXATTR, uintptr(fd), uintptr(unsafe.Pointer(_p0)), uintptr(options))
+	if e1 != 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func listxattr(path string, dest *byte, size int, options int) (sz int, err error) {
 	var _p0 *byte
 	_p0, err = BytePtrFromString(path)
@@ -476,6 +522,17 @@
 
 // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
 
+func flistxattr(fd int, dest *byte, size int, options int) (sz int, err error) {
+	r0, _, e1 := Syscall6(SYS_FLISTXATTR, uintptr(fd), uintptr(unsafe.Pointer(dest)), uintptr(size), uintptr(options), 0, 0)
+	sz = int(r0)
+	if e1 != 0 {
+		err = errnoErr(e1)
+	}
+	return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
 func kill(pid int, signum int, posix int) (err error) {
 	_, _, e1 := Syscall(SYS_KILL, uintptr(pid), uintptr(signum), uintptr(posix))
 	if e1 != 0 {