/* 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() (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 }