pax_global_header00006660000000000000000000000064146473013110014512gustar00rootroot0000000000000052 comment=74a35a9f3c0d860cfb50d06e1f4e7ad8a906fb97 xattr-0.4.10/000077500000000000000000000000001464730131100127365ustar00rootroot00000000000000xattr-0.4.10/.github/000077500000000000000000000000001464730131100142765ustar00rootroot00000000000000xattr-0.4.10/.github/workflows/000077500000000000000000000000001464730131100163335ustar00rootroot00000000000000xattr-0.4.10/.github/workflows/build.yml000066400000000000000000000013221464730131100201530ustar00rootroot00000000000000name: build on: [push] jobs: build: strategy: matrix: go-version: ['1.16', '1.17', '1.18', '1.19', '1.20', '1.21', '1.22'] 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=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) xattr-0.4.10/.gitignore000066400000000000000000000004241464730131100147260ustar00rootroot00000000000000# 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 xattr-0.4.10/LICENSE000066400000000000000000000025051464730131100137450ustar00rootroot00000000000000Copyright (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. xattr-0.4.10/README.md000066400000000000000000000036431464730131100142230ustar00rootroot00000000000000[![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) } ``` xattr-0.4.10/go.mod000066400000000000000000000001421464730131100140410ustar00rootroot00000000000000module github.com/pkg/xattr go 1.14 require golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f xattr-0.4.10/go.sum000066400000000000000000000006361464730131100140760ustar00rootroot00000000000000golang.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= xattr-0.4.10/xattr.go000066400000000000000000000171711464730131100144360ustar00rootroot00000000000000/* 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: documented at 64 KB. However, at least on TrueNAS SCALE, a // Debian-based Linux distro, it can be larger. 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. However, the TrueNAS // SCALE distro sometimes returns E2BIG. // 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 || err == syscall.E2BIG || 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 } xattr-0.4.10/xattr_bsd.go000066400000000000000000000126561464730131100152710ustar00rootroot00000000000000//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 } xattr-0.4.10/xattr_darwin.go000066400000000000000000000043671464730131100160050ustar00rootroot00000000000000//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 } xattr-0.4.10/xattr_flags_test.go000066400000000000000000000017701464730131100166470ustar00rootroot00000000000000//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) } xattr-0.4.10/xattr_linux.go000066400000000000000000000064131464730131100156520ustar00rootroot00000000000000//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 } xattr-0.4.10/xattr_linux_test.go000066400000000000000000000003751464730131100167120ustar00rootroot00000000000000package 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) } } xattr-0.4.10/xattr_solaris.go000066400000000000000000000074061464730131100161720ustar00rootroot00000000000000//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 := openNonblock(path) 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 := openNonblock(path) 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 { mode := unix.O_RDONLY | unix.O_XATTR | unix.O_NONBLOCK | unix.O_CLOEXEC fd, err := unix.Open(path, mode, 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 := openNonblock(path) 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 } // Like os.Open, but passes O_NONBLOCK to the open(2) syscall. func openNonblock(path string) (*os.File, error) { fd, err := unix.Open(path, unix.O_RDONLY|unix.O_CLOEXEC|unix.O_NONBLOCK, 0) if err != nil { return nil, err } return os.NewFile(uintptr(fd), path), err } // stringsFromByteSlice converts a sequence of attributes to a []string. // We simulate Linux/Darwin, where 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 } xattr-0.4.10/xattr_test.go000066400000000000000000000207471464730131100155000ustar00rootroot00000000000000//go:build linux || darwin || freebsd || netbsd || solaris // +build linux darwin freebsd netbsd solaris package xattr import ( "bytes" "io/ioutil" "log" "os" "path/filepath" "runtime" "syscall" "testing" "golang.org/x/sys/unix" ) 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)) } } // Get should work on a FIFO that is not opened by any other process. // This is mainly relevant on Solaris, where getting the attributes // requires opening the file and doing that the wrong way blocks the caller. func TestFIFO(t *testing.T) { d, err := ioutil.TempDir("", "pkg-xattr-testfifo-*") if err != nil { t.Fatal(err) } t.Log("tempdir:", d) defer os.RemoveAll(d) fifo := filepath.Join(d, "fifo") err = unix.Mkfifo(fifo, 0777) if err != nil { t.Fatal(err) } // We only care about this not blocking. _ = Set(fifo, "foo", []byte("bar")) _, _ = Get(fifo, "foo") _, _ = List(fifo) _ = Remove(fifo, "foo") } // 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) } xattr-0.4.10/xattr_unsupported.go000066400000000000000000000025701464730131100171030ustar00rootroot00000000000000//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{} }