Skip to content

Commit

Permalink
Add support for solaris (and illumos). (#54)
Browse files Browse the repository at this point in the history
  • Loading branch information
andy-js authored Dec 3, 2020
1 parent 9943167 commit f0370f5
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 5 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

xattr
=====
Extended attribute support for Go (linux + darwin + freebsd + netbsd).
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)

Expand Down
2 changes: 1 addition & 1 deletion xattr_flags_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// +build linux darwin
// +build linux darwin solaris

package xattr

Expand Down
161 changes: 161 additions & 0 deletions xattr_solaris.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// +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) {
fd, err := unix.Open(path, unix.O_RDONLY, 0)
if err != nil {
return 0, err
}
defer func() {
_ = unix.Close(fd)
}()
return fgetxattr(os.NewFile(uintptr(fd), path), 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 {
fd, err := unix.Open(path, unix.O_RDONLY, 0)
if err != nil {
return err
}
if err = fsetxattr(os.NewFile(uintptr(fd), path), name, data, flags); err != nil {
_ = unix.Close(fd)
return err
}
return unix.Close(fd)
}

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
}
defer func() {
_ = unix.Close(fd)
}()
return fremovexattr(os.NewFile(uintptr(fd), path), 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) {
fd, err := unix.Open(path, unix.O_RDONLY, 0)
if err != nil {
return 0, err
}
defer func() {
_ = unix.Close(fd)
}()
return flistxattr(os.NewFile(uintptr(fd), path), 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, err
}
defer func() {
_ = unix.Close(fd)
}()
names, err := os.NewFile(uintptr(fd), f.Name()).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
}
11 changes: 9 additions & 2 deletions xattr_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// +build linux darwin freebsd netbsd
// +build linux darwin freebsd netbsd solaris

package xattr

Expand Down Expand Up @@ -147,6 +147,9 @@ func TestNoData(t *testing.T) {
// 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)
Expand Down Expand Up @@ -281,7 +284,11 @@ func unpackSysErr(err error) syscall.Errno {
if !ok {
log.Panicf("cannot unpack err=%#v", err)
}
return err2.Err.(syscall.Errno)
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
Expand Down
2 changes: 1 addition & 1 deletion xattr_unsupported.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// +build !linux,!freebsd,!netbsd,!darwin
// +build !linux,!freebsd,!netbsd,!darwin,!solaris

package xattr

Expand Down

0 comments on commit f0370f5

Please sign in to comment.