Merge remote-tracking branch 'origin/upstream'
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..2f1adf7
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,35 @@
+name: build
+on: [push]
+
+jobs:
+  build:
+    strategy:
+        matrix:
+          go-version: [1.15, 1.16, 1.17, 1.18]
+          os: [ubuntu-latest, macos-latest]
+    runs-on: ${{ matrix.os }}
+    steps:
+    - name: Set up Go
+      uses: actions/setup-go@v2
+      with:
+        go-version: ${{ matrix.go-version }}
+
+    - name: Checkout code
+      uses: actions/checkout@v2
+
+    - name: Build
+      run: |
+        GOOS=freebsd go build
+        GOOS=windows go build
+        GOOS=openbsd go build
+        go build -v .
+
+    - name: Test
+      run: |
+        go vet
+        go test -v -race -coverprofile=coverage.txt -covermode=atomic
+
+    - name: After success
+      run: |
+        bash <(curl -s https://codecov.io/bash)
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d8b3265
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,26 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+.DS_Store
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+
+*.swp
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..99d2e9d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+Copyright (c) 2012 Dave Cheney. 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
+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.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..03160f9
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,15 @@
+name: "xattr"
+description:
+    "Extended attribute support for Go (linux + darwin + freebsd)"
+
+third_party {
+  identifier {
+    type: "Git"
+    value: "https://github.com/pkg/xattr"
+    primary_source: true
+    version: "v0.4.9"
+  }
+  version: "v0.4.9"
+  last_upgrade_date { year: 2024 month: 4 day: 22 }
+  license_type: NOTICE
+}
diff --git a/MODULE_LICENSE_BSD b/MODULE_LICENSE_BSD
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_BSD
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..2e8f086
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+include platform/system/core:main:/janitors/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0662c02
--- /dev/null
+++ b/README.md
@@ -0,0 +1,45 @@
+[![GoDoc](https://godoc.org/github.com/pkg/xattr?status.svg)](http://godoc.org/github.com/pkg/xattr)
+[![Go Report Card](https://goreportcard.com/badge/github.com/pkg/xattr)](https://goreportcard.com/report/github.com/pkg/xattr)
+[![Build Status](https://github.com/pkg/xattr/workflows/build/badge.svg)](https://github.com/pkg/xattr/actions?query=workflow%3Abuild)
+[![Codecov](https://codecov.io/gh/pkg/xattr/branch/master/graph/badge.svg)](https://codecov.io/gh/pkg/xattr)
+
+xattr
+=====
+Extended attribute support for Go (linux + darwin + freebsd + netbsd + solaris).
+
+"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)
+
+`SetWithFlags` allows to additionally pass system flags to be forwarded to the underlying calls. FreeBSD and NetBSD do not support this and the parameter will be ignored.
+
+The `L` variants of all functions (`LGet/LSet/...`) are identical to `Get/Set/...` except that they
+do not reference a symlink that appears at the end of a path. See
+[GoDoc](http://godoc.org/github.com/pkg/xattr) for details.
+
+### Example
+```go
+  const path = "/tmp/myfile"
+  const prefix = "user."
+
+  if err := xattr.Set(path, prefix+"test", []byte("test-attr-value")); err != nil {
+  	log.Fatal(err)
+  }
+
+  var list []string
+  if list, err = xattr.List(path); err != nil {
+  	log.Fatal(err)
+  }
+
+  var data []byte
+  if data, err = xattr.Get(path, prefix+"test"); err != nil {
+  	log.Fatal(err)
+  }
+
+  if err = xattr.Remove(path, prefix+"test"); err != nil {
+  	log.Fatal(err)
+  }
+
+  // One can also specify the flags parameter to be passed to the OS.
+  if err := xattr.SetWithFlags(path, prefix+"test", []byte("test-attr-value"), xattr.XATTR_CREATE); err != nil {
+  	log.Fatal(err)
+  }
+```
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..16df7bf
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,5 @@
+module github.com/pkg/xattr
+
+go 1.14
+
+require golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..c444bb9
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,4 @@
+golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 h1:a/mKvvZr9Jcc8oKfcmgzyp7OwF73JPWsQLvH1z2Kxck=
+golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f h1:8w7RhxzTVgUzw/AH/9mUV5q0vMgy40SQRursCcfmkCw=
+golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/xattr.go b/xattr.go
new file mode 100644
index 0000000..8b2b5fe
--- /dev/null
+++ b/xattr.go
@@ -0,0 +1,257 @@
+/*
+Package xattr provides support for extended attributes on linux, darwin and 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.
+More details you can find here: https://en.wikipedia.org/wiki/Extended_file_attributes .
+
+All functions are provided in triples: Get/LGet/FGet, Set/LSet/FSet etc. The "L"
+variant will not follow a symlink at the end of the path, and "F" variant accepts
+a file descriptor instead of a path.
+
+Example for "L" variant, assuming path is "/symlink1/symlink2", where both components are
+symlinks:
+Get will follow "symlink1" and "symlink2" and operate on the target of
+"symlink2". LGet will follow "symlink1" but operate directly on "symlink2".
+*/
+package xattr
+
+import (
+	"os"
+	"syscall"
+)
+
+// Error records an error and the operation, file path and attribute that caused it.
+type Error struct {
+	Op   string
+	Path string
+	Name string
+	Err  error
+}
+
+func (e *Error) Unwrap() error { return e.Err }
+
+func (e *Error) Error() (errstr string) {
+	if e.Op != "" {
+		errstr += e.Op
+	}
+	if e.Path != "" {
+		if errstr != "" {
+			errstr += " "
+		}
+		errstr += e.Path
+	}
+	if e.Name != "" {
+		if errstr != "" {
+			errstr += " "
+		}
+		errstr += e.Name
+	}
+	if e.Err != nil {
+		if errstr != "" {
+			errstr += ": "
+		}
+		errstr += e.Err.Error()
+	}
+	return
+}
+
+// Get retrieves extended attribute data associated with path. It will follow
+// all symlinks along the path.
+func Get(path, name string) ([]byte, error) {
+	return get(path, name, func(name string, data []byte) (int, error) {
+		return getxattr(path, name, data)
+	})
+}
+
+// LGet is like Get but does not follow a symlink at the end of the path.
+func LGet(path, name string) ([]byte, error) {
+	return get(path, name, func(name string, data []byte) (int, error) {
+		return lgetxattr(path, name, data)
+	})
+}
+
+// FGet is like Get but accepts a os.File instead of a file path.
+func FGet(f *os.File, name string) ([]byte, error) {
+	return get(f.Name(), name, func(name string, data []byte) (int, error) {
+		return fgetxattr(f, name, data)
+	})
+}
+
+type getxattrFunc func(name string, data []byte) (int, error)
+
+// get contains the buffer allocation logic used by both Get and LGet.
+func get(path string, name string, getxattrFunc getxattrFunc) ([]byte, error) {
+	const (
+		// Start with a 1 KB buffer for the xattr value
+		initialBufSize = 1024
+
+		// The theoretical maximum xattr value size on MacOS is 64 MB. On Linux it's
+		// much smaller at 64 KB. Unless the kernel is evil or buggy, we should never
+		// hit the limit.
+		maxBufSize = 64 * 1024 * 1024
+
+		// Function name as reported in error messages
+		myname = "xattr.get"
+	)
+
+	size := initialBufSize
+	for {
+		data := make([]byte, size)
+		read, err := getxattrFunc(name, data)
+
+		// If the buffer was too small to fit the value, Linux and MacOS react
+		// differently:
+		// Linux: returns an ERANGE error and "-1" bytes.
+		// MacOS: truncates the value and returns "size" bytes. If the value
+		//   happens to be exactly as big as the buffer, we cannot know if it was
+		//   truncated, and we retry with a bigger buffer. Contrary to documentation,
+		//   MacOS never seems to return ERANGE!
+		// To keep the code simple, we always check both conditions, and sometimes
+		// double the buffer size without it being strictly necessary.
+		if err == syscall.ERANGE || read == size {
+			// The buffer was too small. Try again.
+			size <<= 1
+			if size >= maxBufSize {
+				return nil, &Error{myname, path, name, syscall.EOVERFLOW}
+			}
+			continue
+		}
+		if err != nil {
+			return nil, &Error{myname, path, name, err}
+		}
+		return data[:read], nil
+	}
+}
+
+// Set associates name and data together as an attribute of path.
+func Set(path, name string, data []byte) error {
+	if err := setxattr(path, name, data, 0); err != nil {
+		return &Error{"xattr.Set", path, name, err}
+	}
+	return nil
+}
+
+// LSet is like Set but does not follow a symlink at
+// the end of the path.
+func LSet(path, name string, data []byte) error {
+	if err := lsetxattr(path, name, data, 0); err != nil {
+		return &Error{"xattr.LSet", path, name, err}
+	}
+	return nil
+}
+
+// FSet is like Set but accepts a os.File instead of a file path.
+func FSet(f *os.File, name string, data []byte) error {
+	if err := fsetxattr(f, name, data, 0); err != nil {
+		return &Error{"xattr.FSet", f.Name(), name, err}
+	}
+	return nil
+}
+
+// SetWithFlags associates name and data together as an attribute of path.
+// Forwards the flags parameter to the syscall layer.
+func SetWithFlags(path, name string, data []byte, flags int) error {
+	if err := setxattr(path, name, data, flags); err != nil {
+		return &Error{"xattr.SetWithFlags", path, name, err}
+	}
+	return nil
+}
+
+// LSetWithFlags is like SetWithFlags but does not follow a symlink at
+// the end of the path.
+func LSetWithFlags(path, name string, data []byte, flags int) error {
+	if err := lsetxattr(path, name, data, flags); err != nil {
+		return &Error{"xattr.LSetWithFlags", path, name, err}
+	}
+	return nil
+}
+
+// FSetWithFlags is like SetWithFlags but accepts a os.File instead of a file path.
+func FSetWithFlags(f *os.File, name string, data []byte, flags int) error {
+	if err := fsetxattr(f, name, data, flags); err != nil {
+		return &Error{"xattr.FSetWithFlags", f.Name(), name, err}
+	}
+	return nil
+}
+
+// Remove removes the attribute associated with the given path.
+func Remove(path, name string) error {
+	if err := removexattr(path, name); err != nil {
+		return &Error{"xattr.Remove", path, name, err}
+	}
+	return nil
+}
+
+// LRemove is like Remove but does not follow a symlink at the end of the
+// path.
+func LRemove(path, name string) error {
+	if err := lremovexattr(path, name); err != nil {
+		return &Error{"xattr.LRemove", path, name, err}
+	}
+	return nil
+}
+
+// FRemove is like Remove but accepts a os.File instead of a file path.
+func FRemove(f *os.File, name string) error {
+	if err := fremovexattr(f, name); err != nil {
+		return &Error{"xattr.FRemove", f.Name(), name, err}
+	}
+	return nil
+}
+
+// List retrieves a list of names of extended attributes associated
+// with the given path in the file system.
+func List(path string) ([]string, error) {
+	return list(path, func(data []byte) (int, error) {
+		return listxattr(path, data)
+	})
+}
+
+// LList is like List but does not follow a symlink at the end of the
+// path.
+func LList(path string) ([]string, error) {
+	return list(path, func(data []byte) (int, error) {
+		return llistxattr(path, data)
+	})
+}
+
+// FList is like List but accepts a os.File instead of a file path.
+func FList(f *os.File) ([]string, error) {
+	return list(f.Name(), func(data []byte) (int, error) {
+		return flistxattr(f, data)
+	})
+}
+
+type listxattrFunc func(data []byte) (int, error)
+
+// list contains the buffer allocation logic used by both List and LList.
+func list(path string, listxattrFunc listxattrFunc) ([]string, error) {
+	myname := "xattr.list"
+	// find size.
+	size, err := listxattrFunc(nil)
+	if err != nil {
+		return nil, &Error{myname, path, "", err}
+	}
+	if size > 0 {
+		// `size + 1` because of ERANGE error when reading
+		// from a SMB1 mount point (https://github.com/pkg/xattr/issues/16).
+		buf := make([]byte, size+1)
+		// Read into buffer of that size.
+		read, err := listxattrFunc(buf)
+		if err != nil {
+			return nil, &Error{myname, path, "", err}
+		}
+		return stringsFromByteSlice(buf[:read]), nil
+	}
+	return []string{}, nil
+}
+
+// bytePtrFromSlice returns a pointer to array of bytes and a size.
+func bytePtrFromSlice(data []byte) (ptr *byte, size int) {
+	size = len(data)
+	if size > 0 {
+		ptr = &data[0]
+	}
+	return
+}
diff --git a/xattr_bsd.go b/xattr_bsd.go
new file mode 100644
index 0000000..f4a3f95
--- /dev/null
+++ b/xattr_bsd.go
@@ -0,0 +1,201 @@
+//go:build freebsd || netbsd
+// +build freebsd netbsd
+
+package xattr
+
+import (
+	"os"
+	"syscall"
+	"unsafe"
+)
+
+const (
+	// XATTR_SUPPORTED will be true if the current platform is supported
+	XATTR_SUPPORTED = true
+
+	EXTATTR_NAMESPACE_USER = 1
+
+	// ENOATTR is not exported by the syscall package on Linux, because it is
+	// an alias for ENODATA. We export it here so it is available on all
+	// our supported platforms.
+	ENOATTR = syscall.ENOATTR
+)
+
+func getxattr(path string, name string, data []byte) (int, error) {
+	return sysGet(syscall.SYS_EXTATTR_GET_FILE, path, name, data)
+}
+
+func lgetxattr(path string, name string, data []byte) (int, error) {
+	return sysGet(syscall.SYS_EXTATTR_GET_LINK, path, name, data)
+}
+
+func fgetxattr(f *os.File, name string, data []byte) (int, error) {
+	return getxattr(f.Name(), name, data)
+}
+
+// sysGet is called by getxattr and lgetxattr with the appropriate syscall
+// number. This works because syscalls have the same signature and return
+// values.
+func sysGet(syscallNum uintptr, path string, name string, data []byte) (int, error) {
+	ptr, nbytes := bytePtrFromSlice(data)
+	/*
+		ssize_t extattr_get_file(
+			const char *path,
+			int attrnamespace,
+			const char *attrname,
+			void *data,
+			size_t nbytes);
+
+		ssize_t extattr_get_link(
+			const char *path,
+			int attrnamespace,
+			const char *attrname,
+			void *data,
+			size_t nbytes);
+	*/
+	r0, _, err := syscall.Syscall6(syscallNum, uintptr(unsafe.Pointer(syscall.StringBytePtr(path))),
+		EXTATTR_NAMESPACE_USER, uintptr(unsafe.Pointer(syscall.StringBytePtr(name))),
+		uintptr(unsafe.Pointer(ptr)), uintptr(nbytes), 0)
+	if err != syscall.Errno(0) {
+		return int(r0), err
+	}
+	return int(r0), nil
+}
+
+func setxattr(path string, name string, data []byte, flags int) error {
+	return sysSet(syscall.SYS_EXTATTR_SET_FILE, path, name, data)
+}
+
+func lsetxattr(path string, name string, data []byte, flags int) error {
+	return sysSet(syscall.SYS_EXTATTR_SET_LINK, path, name, data)
+}
+
+func fsetxattr(f *os.File, name string, data []byte, flags int) error {
+	return setxattr(f.Name(), name, data, flags)
+}
+
+// sysSet is called by setxattr and lsetxattr with the appropriate syscall
+// number. This works because syscalls have the same signature and return
+// values.
+func sysSet(syscallNum uintptr, path string, name string, data []byte) error {
+	ptr, nbytes := bytePtrFromSlice(data)
+	/*
+		ssize_t extattr_set_file(
+			const char *path,
+			int attrnamespace,
+			const char *attrname,
+			const void *data,
+			size_t nbytes
+		);
+
+		ssize_t extattr_set_link(
+			const char *path,
+			int attrnamespace,
+			const char *attrname,
+			const void *data,
+			size_t nbytes
+		);
+	*/
+	r0, _, err := syscall.Syscall6(syscallNum, uintptr(unsafe.Pointer(syscall.StringBytePtr(path))),
+		EXTATTR_NAMESPACE_USER, uintptr(unsafe.Pointer(syscall.StringBytePtr(name))),
+		uintptr(unsafe.Pointer(ptr)), uintptr(nbytes), 0)
+	if err != syscall.Errno(0) {
+		return err
+	}
+	if int(r0) != nbytes {
+		return syscall.E2BIG
+	}
+	return nil
+}
+
+func removexattr(path string, name string) error {
+	return sysRemove(syscall.SYS_EXTATTR_DELETE_FILE, path, name)
+}
+
+func lremovexattr(path string, name string) error {
+	return sysRemove(syscall.SYS_EXTATTR_DELETE_LINK, path, name)
+}
+
+func fremovexattr(f *os.File, name string) error {
+	return removexattr(f.Name(), name)
+}
+
+// sysSet is called by removexattr and lremovexattr with the appropriate syscall
+// number. This works because syscalls have the same signature and return
+// values.
+func sysRemove(syscallNum uintptr, path string, name string) error {
+	/*
+		int extattr_delete_file(
+			const char *path,
+			int attrnamespace,
+			const char *attrname
+		);
+
+		int extattr_delete_link(
+			const char *path,
+			int attrnamespace,
+			const char *attrname
+		);
+	*/
+	_, _, err := syscall.Syscall(syscallNum, uintptr(unsafe.Pointer(syscall.StringBytePtr(path))),
+		EXTATTR_NAMESPACE_USER, uintptr(unsafe.Pointer(syscall.StringBytePtr(name))),
+	)
+	if err != syscall.Errno(0) {
+		return err
+	}
+	return nil
+}
+
+func listxattr(path string, data []byte) (int, error) {
+	return sysList(syscall.SYS_EXTATTR_LIST_FILE, path, data)
+}
+
+func llistxattr(path string, data []byte) (int, error) {
+	return sysList(syscall.SYS_EXTATTR_LIST_LINK, path, data)
+}
+
+func flistxattr(f *os.File, data []byte) (int, error) {
+	return listxattr(f.Name(), data)
+}
+
+// sysSet is called by listxattr and llistxattr with the appropriate syscall
+// number. This works because syscalls have the same signature and return
+// values.
+func sysList(syscallNum uintptr, path string, data []byte) (int, error) {
+	ptr, nbytes := bytePtrFromSlice(data)
+	/*
+		ssize_t extattr_list_file(
+				const char *path,
+				int attrnamespace,
+				void *data,
+				size_t nbytes
+			);
+
+		ssize_t extattr_list_link(
+			const char *path,
+			int attrnamespace,
+			void *data,
+			size_t nbytes
+		);
+	*/
+	r0, _, err := syscall.Syscall6(syscallNum, uintptr(unsafe.Pointer(syscall.StringBytePtr(path))),
+		EXTATTR_NAMESPACE_USER, uintptr(unsafe.Pointer(ptr)), uintptr(nbytes), 0, 0)
+	if err != syscall.Errno(0) {
+		return int(r0), err
+	}
+	return int(r0), nil
+}
+
+// stringsFromByteSlice converts a sequence of attributes to a []string.
+// On FreeBSD, 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 NULL.
+func stringsFromByteSlice(buf []byte) (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
+}
diff --git a/xattr_darwin.go b/xattr_darwin.go
new file mode 100644
index 0000000..ee7a501
--- /dev/null
+++ b/xattr_darwin.go
@@ -0,0 +1,90 @@
+//go:build darwin
+// +build darwin
+
+package xattr
+
+import (
+	"os"
+	"syscall"
+
+	"golang.org/x/sys/unix"
+)
+
+// See https://opensource.apple.com/source/xnu/xnu-1504.15.3/bsd/sys/xattr.h.auto.html
+const (
+	// XATTR_SUPPORTED will be true if the current platform is supported
+	XATTR_SUPPORTED = true
+
+	XATTR_NOFOLLOW        = 0x0001
+	XATTR_CREATE          = 0x0002
+	XATTR_REPLACE         = 0x0004
+	XATTR_NOSECURITY      = 0x0008
+	XATTR_NODEFAULT       = 0x0010
+	XATTR_SHOWCOMPRESSION = 0x0020
+
+	// ENOATTR is not exported by the syscall package on Linux, because it is
+	// an alias for ENODATA. We export it here so it is available on all
+	// our supported platforms.
+	ENOATTR = syscall.ENOATTR
+)
+
+func getxattr(path string, name string, data []byte) (int, error) {
+	return unix.Getxattr(path, name, data)
+}
+
+func lgetxattr(path string, name string, data []byte) (int, error) {
+	return unix.Lgetxattr(path, name, data)
+}
+
+func fgetxattr(f *os.File, name string, data []byte) (int, error) {
+	return getxattr(f.Name(), name, data)
+}
+
+func setxattr(path string, name string, data []byte, flags int) error {
+	return unix.Setxattr(path, name, data, flags)
+}
+
+func lsetxattr(path string, name string, data []byte, flags int) error {
+	return unix.Lsetxattr(path, name, data, flags)
+}
+
+func fsetxattr(f *os.File, name string, data []byte, flags int) error {
+	return setxattr(f.Name(), name, data, flags)
+}
+
+func removexattr(path string, name string) error {
+	return unix.Removexattr(path, name)
+}
+
+func lremovexattr(path string, name string) error {
+	return unix.Lremovexattr(path, name)
+}
+
+func fremovexattr(f *os.File, name string) error {
+	return removexattr(f.Name(), name)
+}
+
+func listxattr(path string, data []byte) (int, error) {
+	return unix.Listxattr(path, data)
+}
+
+func llistxattr(path string, data []byte) (int, error) {
+	return unix.Llistxattr(path, data)
+}
+
+func flistxattr(f *os.File, data []byte) (int, error) {
+	return listxattr(f.Name(), data)
+}
+
+// stringsFromByteSlice converts a sequence of attributes to a []string.
+// On Darwin and Linux, each entry is a NULL-terminated string.
+func stringsFromByteSlice(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
+}
diff --git a/xattr_flags_test.go b/xattr_flags_test.go
new file mode 100644
index 0000000..96dd428
--- /dev/null
+++ b/xattr_flags_test.go
@@ -0,0 +1,39 @@
+//go:build linux || darwin || solaris
+// +build linux darwin solaris
+
+package xattr
+
+import (
+	"io/ioutil"
+	"os"
+	"testing"
+)
+
+func TestFlags(t *testing.T) {
+	tmp, err := ioutil.TempFile("", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(tmp.Name())
+
+	err = SetWithFlags(tmp.Name(), UserPrefix+"flags-test", []byte("flags-test-attr-value"), 0)
+	checkIfError(t, err)
+
+	err = SetWithFlags(tmp.Name(), UserPrefix+"flags-test", []byte("flags-test-attr-value"), XATTR_CREATE)
+	if err == nil {
+		t.Fatalf("XATTR_CREATE should have failed because the xattr already exists")
+	}
+	t.Log(err)
+
+	err = SetWithFlags(tmp.Name(), UserPrefix+"flags-test", []byte("flags-test-attr-value"), XATTR_REPLACE)
+	checkIfError(t, err)
+
+	err = Remove(tmp.Name(), UserPrefix+"flags-test")
+	checkIfError(t, err)
+
+	err = SetWithFlags(tmp.Name(), UserPrefix+"flags-test", []byte("flags-test-attr-value"), XATTR_REPLACE)
+	if err == nil {
+		t.Fatalf("XATTR_REPLACE should have failed because there is nothing to replace")
+	}
+	t.Log(err)
+}
diff --git a/xattr_linux.go b/xattr_linux.go
new file mode 100644
index 0000000..879085e
--- /dev/null
+++ b/xattr_linux.go
@@ -0,0 +1,142 @@
+//go:build linux
+// +build linux
+
+package xattr
+
+import (
+	"os"
+	"syscall"
+
+	"golang.org/x/sys/unix"
+)
+
+const (
+	// XATTR_SUPPORTED will be true if the current platform is supported
+	XATTR_SUPPORTED = true
+
+	XATTR_CREATE  = unix.XATTR_CREATE
+	XATTR_REPLACE = unix.XATTR_REPLACE
+
+	// ENOATTR is not exported by the syscall package on Linux, because it is
+	// an alias for ENODATA. We export it here so it is available on all
+	// our supported platforms.
+	ENOATTR = syscall.ENODATA
+)
+
+// On Linux, FUSE and CIFS filesystems can return EINTR for interrupted system
+// calls. This function works around this by retrying system calls until they
+// stop returning EINTR.
+//
+// See https://github.com/golang/go/commit/6b420169d798c7ebe733487b56ea5c3fa4aab5ce.
+func ignoringEINTR(fn func() error) (err error) {
+	for {
+		err = fn()
+		if err != unix.EINTR {
+			break
+		}
+	}
+	return err
+}
+
+func getxattr(path string, name string, data []byte) (int, error) {
+	var r int
+	err := ignoringEINTR(func() (err error) {
+		r, err = unix.Getxattr(path, name, data)
+		return err
+	})
+	return r, err
+}
+
+func lgetxattr(path string, name string, data []byte) (int, error) {
+	var r int
+	err := ignoringEINTR(func() (err error) {
+		r, err = unix.Lgetxattr(path, name, data)
+		return err
+	})
+	return r, err
+}
+
+func fgetxattr(f *os.File, name string, data []byte) (int, error) {
+	var r int
+	err := ignoringEINTR(func() (err error) {
+		r, err = unix.Fgetxattr(int(f.Fd()), name, data)
+		return err
+	})
+	return r, err
+}
+
+func setxattr(path string, name string, data []byte, flags int) error {
+	return ignoringEINTR(func() (err error) {
+		return unix.Setxattr(path, name, data, flags)
+	})
+}
+
+func lsetxattr(path string, name string, data []byte, flags int) error {
+	return ignoringEINTR(func() (err error) {
+		return unix.Lsetxattr(path, name, data, flags)
+	})
+}
+
+func fsetxattr(f *os.File, name string, data []byte, flags int) error {
+	return ignoringEINTR(func() (err error) {
+		return unix.Fsetxattr(int(f.Fd()), name, data, flags)
+	})
+}
+
+func removexattr(path string, name string) error {
+	return ignoringEINTR(func() (err error) {
+		return unix.Removexattr(path, name)
+	})
+}
+
+func lremovexattr(path string, name string) error {
+	return ignoringEINTR(func() (err error) {
+		return unix.Lremovexattr(path, name)
+	})
+}
+
+func fremovexattr(f *os.File, name string) error {
+	return ignoringEINTR(func() (err error) {
+		return unix.Fremovexattr(int(f.Fd()), name)
+	})
+}
+
+func listxattr(path string, data []byte) (int, error) {
+	var r int
+	err := ignoringEINTR(func() (err error) {
+		r, err = unix.Listxattr(path, data)
+		return err
+	})
+	return r, err
+}
+
+func llistxattr(path string, data []byte) (int, error) {
+	var r int
+	err := ignoringEINTR(func() (err error) {
+		r, err = unix.Llistxattr(path, data)
+		return err
+	})
+	return r, err
+}
+
+func flistxattr(f *os.File, data []byte) (int, error) {
+	var r int
+	err := ignoringEINTR(func() (err error) {
+		r, err = unix.Flistxattr(int(f.Fd()), data)
+		return err
+	})
+	return r, err
+}
+
+// stringsFromByteSlice converts a sequence of attributes to a []string.
+// On Darwin and Linux, each entry is a NULL-terminated string.
+func stringsFromByteSlice(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
+}
diff --git a/xattr_linux_test.go b/xattr_linux_test.go
new file mode 100644
index 0000000..7986511
--- /dev/null
+++ b/xattr_linux_test.go
@@ -0,0 +1,21 @@
+package xattr
+
+import (
+	"syscall"
+	"testing"
+)
+
+func TestIgnoringEINTR(t *testing.T) {
+	eintrs := 100
+	err := ignoringEINTR(func() error {
+		if eintrs == 0 {
+			return nil
+		}
+		eintrs--
+		return syscall.EINTR
+	})
+
+	if err != nil {
+		t.Fatal(err)
+	}
+}
diff --git a/xattr_solaris.go b/xattr_solaris.go
new file mode 100644
index 0000000..8d65b8d
--- /dev/null
+++ b/xattr_solaris.go
@@ -0,0 +1,165 @@
+//go:build solaris
+// +build solaris
+
+package xattr
+
+import (
+	"os"
+	"syscall"
+
+	"golang.org/x/sys/unix"
+)
+
+const (
+	// XATTR_SUPPORTED will be true if the current platform is supported
+	XATTR_SUPPORTED = true
+
+	XATTR_CREATE  = 0x1
+	XATTR_REPLACE = 0x2
+
+	// ENOATTR is not exported by the syscall package on Linux, because it is
+	// an alias for ENODATA. We export it here so it is available on all
+	// our supported platforms.
+	ENOATTR = syscall.ENODATA
+)
+
+func getxattr(path string, name string, data []byte) (int, error) {
+	f, err := os.OpenFile(path, os.O_RDONLY, 0)
+	if err != nil {
+		return 0, err
+	}
+	defer func() {
+		_ = f.Close()
+	}()
+	return fgetxattr(f, name, data)
+}
+
+func lgetxattr(path string, name string, data []byte) (int, error) {
+	return 0, unix.ENOTSUP
+}
+
+func fgetxattr(f *os.File, name string, data []byte) (int, error) {
+	fd, err := unix.Openat(int(f.Fd()), name, unix.O_RDONLY|unix.O_XATTR, 0)
+	if err != nil {
+		return 0, err
+	}
+	defer func() {
+		_ = unix.Close(fd)
+	}()
+	return unix.Read(fd, data)
+}
+
+func setxattr(path string, name string, data []byte, flags int) error {
+	f, err := os.OpenFile(path, os.O_RDONLY, 0)
+	if err != nil {
+		return err
+	}
+	err = fsetxattr(f, name, data, flags)
+	if err != nil {
+		_ = f.Close()
+		return err
+	}
+	return f.Close()
+}
+
+func lsetxattr(path string, name string, data []byte, flags int) error {
+	return unix.ENOTSUP
+}
+
+func fsetxattr(f *os.File, name string, data []byte, flags int) error {
+	mode := unix.O_WRONLY | unix.O_XATTR
+	if flags&XATTR_REPLACE != 0 {
+		mode |= unix.O_TRUNC
+	} else if flags&XATTR_CREATE != 0 {
+		mode |= unix.O_CREAT | unix.O_EXCL
+	} else {
+		mode |= unix.O_CREAT | unix.O_TRUNC
+	}
+	fd, err := unix.Openat(int(f.Fd()), name, mode, 0666)
+	if err != nil {
+		return err
+	}
+	if _, err = unix.Write(fd, data); err != nil {
+		_ = unix.Close(fd)
+		return err
+	}
+	return unix.Close(fd)
+}
+
+func removexattr(path string, name string) error {
+	fd, err := unix.Open(path, unix.O_RDONLY|unix.O_XATTR, 0)
+	if err != nil {
+		return err
+	}
+	f := os.NewFile(uintptr(fd), path)
+	defer func() {
+		_ = f.Close()
+	}()
+	return fremovexattr(f, name)
+}
+
+func lremovexattr(path string, name string) error {
+	return unix.ENOTSUP
+}
+
+func fremovexattr(f *os.File, name string) error {
+	fd, err := unix.Openat(int(f.Fd()), ".", unix.O_XATTR, 0)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		_ = unix.Close(fd)
+	}()
+	return unix.Unlinkat(fd, name, 0)
+}
+
+func listxattr(path string, data []byte) (int, error) {
+	f, err := os.OpenFile(path, os.O_RDONLY, 0)
+	if err != nil {
+		return 0, err
+	}
+	defer func() {
+		_ = f.Close()
+	}()
+	return flistxattr(f, data)
+}
+
+func llistxattr(path string, data []byte) (int, error) {
+	return 0, unix.ENOTSUP
+}
+
+func flistxattr(f *os.File, data []byte) (int, error) {
+	fd, err := unix.Openat(int(f.Fd()), ".", unix.O_RDONLY|unix.O_XATTR, 0)
+	if err != nil {
+		return 0, unix.ENOTSUP
+	}
+	xf := os.NewFile(uintptr(fd), f.Name())
+	defer func() {
+		_ = xf.Close()
+	}()
+	names, err := xf.Readdirnames(-1)
+	if err != nil {
+		return 0, err
+	}
+	var buf []byte
+	for _, name := range names {
+		buf = append(buf, append([]byte(name), '\000')...)
+	}
+	if data == nil {
+		return len(buf), nil
+	}
+	return copy(data, buf), nil
+}
+
+// stringsFromByteSlice converts a sequence of attributes to a []string.
+// On Darwin and Linux, each entry is a NULL-terminated string.
+func stringsFromByteSlice(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
+}
diff --git a/xattr_test.go b/xattr_test.go
new file mode 100644
index 0000000..b6b2048
--- /dev/null
+++ b/xattr_test.go
@@ -0,0 +1,340 @@
+//go:build linux || darwin || freebsd || netbsd || solaris
+// +build linux darwin freebsd netbsd solaris
+
+package xattr
+
+import (
+	"bytes"
+	"io/ioutil"
+	"log"
+	"os"
+	"runtime"
+	"syscall"
+	"testing"
+)
+
+const UserPrefix = "user."
+
+type funcFamily struct {
+	familyName   string
+	get          func(path, name string) ([]byte, error)
+	set          func(path, name string, data []byte) error
+	setWithFlags func(path, name string, data []byte, flags int) error
+	remove       func(path, name string) error
+	list         func(path string) ([]string, error)
+}
+
+// Test Get, Set, List, Remove on a regular file
+func TestRegularFile(t *testing.T) {
+	families := []funcFamily{
+		{
+			familyName:   "Get and friends",
+			get:          Get,
+			set:          Set,
+			setWithFlags: SetWithFlags,
+			remove:       Remove,
+			list:         List,
+		},
+		{
+			familyName:   "LGet and friends",
+			get:          LGet,
+			set:          LSet,
+			setWithFlags: LSetWithFlags,
+			remove:       LRemove,
+			list:         LList,
+		},
+		{
+			familyName:   "FGet and friends",
+			get:          wrapFGet,
+			set:          wrapFSet,
+			setWithFlags: wrapFSetWithFlags,
+			remove:       wrapFRemove,
+			list:         wrapFList,
+		},
+	}
+	for _, ff := range families {
+		t.Run(ff.familyName, func(t *testing.T) {
+			t.Logf("Testing %q on a regular file", ff.familyName)
+			testRegularFile(t, ff)
+		})
+	}
+}
+
+// testRegularFile is called with the "Get and friends" and the
+// "LGet and friends" function family. Both families should behave
+// the same on a regular file.
+func testRegularFile(t *testing.T, ff funcFamily) {
+	tmp, err := ioutil.TempFile("", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(tmp.Name())
+
+	xName := UserPrefix + "test"
+	xVal := []byte("test-attr-value")
+
+	// Test that SetWithFlags succeeds and that the xattr shows up in List()
+	err = ff.setWithFlags(tmp.Name(), xName, xVal, 0)
+	checkIfError(t, err)
+	list, err := ff.list(tmp.Name())
+	checkIfError(t, err)
+	found := false
+	for _, name := range list {
+		if name == xName {
+			found = true
+		}
+	}
+	if !found {
+		t.Fatalf("List/LList did not return test attribute: %q", list)
+	}
+	err = ff.remove(tmp.Name(), xName)
+	checkIfError(t, err)
+
+	// Test that Set succeeds and that the the xattr shows up in List()
+	err = ff.set(tmp.Name(), xName, xVal)
+	checkIfError(t, err)
+	list, err = ff.list(tmp.Name())
+	checkIfError(t, err)
+	found = false
+	for _, name := range list {
+		if name == xName {
+			found = true
+		}
+	}
+	if !found {
+		t.Fatalf("List/LList did not return test attribute: %q", list)
+	}
+
+	var data []byte
+	data, err = ff.get(tmp.Name(), xName)
+	checkIfError(t, err)
+
+	value := string(data)
+	t.Log(value)
+	if string(xVal) != value {
+		t.Fail()
+	}
+
+	err = ff.remove(tmp.Name(), xName)
+	checkIfError(t, err)
+}
+
+// Test that setting an xattr with an empty value works.
+func TestNoData(t *testing.T) {
+	tmp, err := ioutil.TempFile("", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(tmp.Name())
+
+	err = Set(tmp.Name(), UserPrefix+"test", []byte{})
+	checkIfError(t, err)
+
+	list, err := List(tmp.Name())
+	checkIfError(t, err)
+
+	found := false
+	for _, name := range list {
+		if name == UserPrefix+"test" {
+			found = true
+		}
+	}
+
+	if !found {
+		t.Fatal("Listxattr did not return test attribute")
+	}
+}
+
+// Test that Get/LGet, Set/LSet etc operate as expected on symlinks. The
+// functions should behave differently when operating on a symlink.
+func TestSymlink(t *testing.T) {
+	if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" {
+		t.Skipf("extended attributes aren't supported for symlinks on %s", runtime.GOOS)
+	}
+	dir, err := ioutil.TempDir("", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	s := dir + "/symlink1"
+	err = os.Symlink(dir+"/some/nonexistent/path", s)
+	if err != nil {
+		t.Fatal(err)
+	}
+	xName := UserPrefix + "TestSymlink"
+	xVal := []byte("test")
+
+	// Test Set/LSet
+	if err := Set(s, xName, xVal); err == nil {
+		t.Error("Set on a broken symlink should fail, but did not")
+	}
+	err = LSet(s, xName, xVal)
+	errno := unpackSysErr(err)
+	setOk := true
+	if runtime.GOOS == "linux" && errno == syscall.EPERM {
+		// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/xattr.c?h=v4.17-rc5#n122 :
+		// In the user.* namespace, only regular files and directories can have
+		// extended attributes.
+		t.Log("got EPERM, adjusting test scope")
+		setOk = false
+	} else {
+		checkIfError(t, err)
+	}
+
+	// Test List/LList
+	_, err = List(s)
+	errno = unpackSysErr(err)
+	if errno != syscall.ENOENT {
+		t.Errorf("List() on a broken symlink should fail with ENOENT, got %q", errno)
+	}
+	data, err := LList(s)
+	checkIfError(t, err)
+	if setOk {
+		found := false
+		for _, n := range data {
+			if n == xName {
+				found = true
+				break
+			}
+		}
+		if !found {
+			t.Errorf("xattr %q did not show up in Llist output: %q", xName, data)
+		}
+	}
+
+	// Test Get/LGet
+	_, err = Get(s, xName)
+	errno = unpackSysErr(err)
+	if errno != syscall.ENOENT {
+		t.Errorf("Get() on a broken symlink should fail with ENOENT, got %q", errno)
+	}
+	val, err := LGet(s, xName)
+	if setOk {
+		checkIfError(t, err)
+		if !bytes.Equal(xVal, val) {
+			t.Errorf("wrong xattr value: want=%q have=%q", xVal, val)
+		}
+	} else {
+		errno = unpackSysErr(err)
+		if errno != ENOATTR {
+			t.Errorf("expected ENOATTR, got %q", errno)
+		}
+	}
+
+	// Test Remove/Lremove
+	err = Remove(s, xName)
+	errno = unpackSysErr(err)
+	if errno != syscall.ENOENT {
+		t.Errorf("Remove() on a broken symlink should fail with ENOENT, got %q", errno)
+	}
+	err = LRemove(s, xName)
+	if setOk {
+		checkIfError(t, err)
+	} else {
+		errno = unpackSysErr(err)
+		if errno != syscall.EPERM {
+			t.Errorf("expected EPERM, got %q", errno)
+		}
+	}
+}
+
+// Verify that Get() handles values larger than the default buffer size (1 KB)
+func TestLargeVal(t *testing.T) {
+	tmp, err := ioutil.TempFile("", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(tmp.Name())
+	path := tmp.Name()
+
+	key := UserPrefix + "TestERANGE"
+	// On ext4, key + value length must be <= 4096. Use 4000 so we can test
+	// reliably on ext4.
+	val := bytes.Repeat([]byte("z"), 4000)
+	err = Set(path, key, val)
+	checkIfError(t, err)
+
+	val2, err := Get(path, key)
+	checkIfError(t, err)
+	if !bytes.Equal(val, val2) {
+		t.Errorf("wrong result from Get: want=%s have=%s", string(val), string(val2))
+	}
+}
+
+// checkIfError calls t.Skip() if the underlying syscall.Errno is
+// ENOTSUP or EOPNOTSUPP. It calls t.Fatal() on any other non-zero error.
+func checkIfError(t *testing.T, err error) {
+	errno := unpackSysErr(err)
+	if errno == syscall.Errno(0) {
+		return
+	}
+	// check if filesystem supports extended attributes
+	if errno == syscall.Errno(syscall.ENOTSUP) || errno == syscall.Errno(syscall.EOPNOTSUPP) {
+		t.Skip("Skipping test - filesystem does not support extended attributes")
+	} else {
+		t.Fatal(err)
+	}
+}
+
+// unpackSysErr unpacks the underlying syscall.Errno from an error value
+// returned by Get/Set/...
+func unpackSysErr(err error) syscall.Errno {
+	if err == nil {
+		return syscall.Errno(0)
+	}
+	err2, ok := err.(*Error)
+	if !ok {
+		log.Panicf("cannot unpack err=%#v", err)
+	}
+	err3, ok := err2.Err.(syscall.Errno)
+	if !ok {
+		log.Panicf("cannot unpack err2=%#v", err2)
+	}
+	return err3
+}
+
+// wrappers to adapt "F" variants to the test
+
+func wrapFGet(path, name string) ([]byte, error) {
+	f, err := os.Open(path)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+	return FGet(f, name)
+}
+
+func wrapFSet(path, name string, data []byte) error {
+	f, err := os.Open(path)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	return FSet(f, name, data)
+}
+
+func wrapFSetWithFlags(path, name string, data []byte, flags int) error {
+	f, err := os.Open(path)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	return FSetWithFlags(f, name, data, flags)
+}
+
+func wrapFRemove(path, name string) error {
+	f, err := os.Open(path)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	return FRemove(f, name)
+}
+
+func wrapFList(path string) ([]string, error) {
+	f, err := os.Open(path)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+	return FList(f)
+}
diff --git a/xattr_unsupported.go b/xattr_unsupported.go
new file mode 100644
index 0000000..8886fbd
--- /dev/null
+++ b/xattr_unsupported.go
@@ -0,0 +1,70 @@
+//go:build !linux && !freebsd && !netbsd && !darwin && !solaris
+// +build !linux,!freebsd,!netbsd,!darwin,!solaris
+
+package xattr
+
+import (
+	"os"
+	"syscall"
+)
+
+const (
+	// We need to use the default for non supported operating systems
+	ENOATTR = syscall.Errno(0x59)
+)
+
+// XATTR_SUPPORTED will be true if the current platform is supported
+const XATTR_SUPPORTED = false
+
+func getxattr(path string, name string, data []byte) (int, error) {
+	return 0, nil
+}
+
+func lgetxattr(path string, name string, data []byte) (int, error) {
+	return 0, nil
+}
+
+func fgetxattr(f *os.File, name string, data []byte) (int, error) {
+	return 0, nil
+}
+
+func setxattr(path string, name string, data []byte, flags int) error {
+	return nil
+}
+
+func lsetxattr(path string, name string, data []byte, flags int) error {
+	return nil
+}
+
+func fsetxattr(f *os.File, name string, data []byte, flags int) error {
+	return nil
+}
+
+func removexattr(path string, name string) error {
+	return nil
+}
+
+func lremovexattr(path string, name string) error {
+	return nil
+}
+
+func fremovexattr(f *os.File, name string) error {
+	return nil
+}
+
+func listxattr(path string, data []byte) (int, error) {
+	return 0, nil
+}
+
+func llistxattr(path string, data []byte) (int, error) {
+	return 0, nil
+}
+
+func flistxattr(f *os.File, data []byte) (int, error) {
+	return 0, nil
+}
+
+// dummy
+func stringsFromByteSlice(buf []byte) (result []string) {
+	return []string{}
+}