diff --git a/xattr_linux.go b/xattr_linux.go index a25d671..de68d30 100644 --- a/xattr_linux.go +++ b/xattr_linux.go @@ -3,6 +3,7 @@ package xattr import ( + "errors" "os" "syscall" @@ -22,52 +23,109 @@ const ( 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 !errors.Is(err, unix.EINTR) { + break + } + } + return err +} + func getxattr(path string, name string, data []byte) (int, error) { - return unix.Getxattr(path, name, data) + 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) { - return unix.Lgetxattr(path, name, data) + 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) { - return unix.Fgetxattr(int(f.Fd()), name, data) + 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 unix.Setxattr(path, name, data, flags) + return ignoringEINTR(func() (err 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) + 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 unix.Fsetxattr(int(f.Fd()), name, data, flags) + return ignoringEINTR(func() (err error) { + return unix.Fsetxattr(int(f.Fd()), name, data, flags) + }) } func removexattr(path string, name string) error { - return unix.Removexattr(path, name) + return ignoringEINTR(func() (err error) { + return unix.Removexattr(path, name) + }) } func lremovexattr(path string, name string) error { - return unix.Lremovexattr(path, name) + return ignoringEINTR(func() (err error) { + return unix.Lremovexattr(path, name) + }) } func fremovexattr(f *os.File, name string) error { - return unix.Fremovexattr(int(f.Fd()), name) + return ignoringEINTR(func() (err error) { + return unix.Fremovexattr(int(f.Fd()), name) + }) } func listxattr(path string, data []byte) (int, error) { - return unix.Listxattr(path, data) + 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) { - return unix.Llistxattr(path, data) + 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) { - return unix.Flistxattr(int(f.Fd()), data) + 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. 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) + } +}