mirror of https://github.com/restic/restic.git
235 lines
7.2 KiB
Go
235 lines
7.2 KiB
Go
/*
|
|
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) Error() string {
|
|
return e.Op + " " + e.Path + " " + e.Name + ": " + e.Err.Error()
|
|
}
|
|
|
|
// 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 neccessary.
|
|
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
|
|
}
|