diff --git a/connection.go b/connection.go index 0f8a13f..d28bf1a 100644 --- a/connection.go +++ b/connection.go @@ -434,6 +434,10 @@ func (c *Connection) shouldLogError( return false } + case *fuseops.GetXattrOp: + if err == syscall.ENODATA || err == syscall.ERANGE { + return false + } case *unknownOp: // Don't bother the user with methods we intentionally don't support. if err == syscall.ENOSYS { @@ -489,7 +493,7 @@ func (c *Connection) Reply(ctx context.Context, opErr error) { if !noResponse { err := c.writeMessage(outMsg.Bytes()) if err != nil && c.errorLogger != nil { - c.errorLogger.Printf("writeMessage: %v", err) + c.errorLogger.Printf("writeMessage: %v %v", err, outMsg.Bytes()) } } } diff --git a/conversions.go b/conversions.go index 755cdbe..1a77652 100644 --- a/conversions.go +++ b/conversions.go @@ -433,6 +433,61 @@ func convertInMessage( Name: string(buf[:n-1]), } + case fusekernel.OpGetxattr: + type input fusekernel.GetxattrIn + in := (*input)(inMsg.Consume(unsafe.Sizeof(input{}))) + if in == nil { + err = errors.New("Corrupt OpGetxattr") + return + } + + name := inMsg.ConsumeBytes(inMsg.Len()) + i := bytes.IndexByte(name, '\x00') + if i < 0 { + err = errors.New("Corrupt OpGetxattr") + return + } + name = name[:i] + + to := &fuseops.GetXattrOp{ + Inode: fuseops.InodeID(inMsg.Header().Nodeid), + Name: string(name), + } + o = to + + readSize := int(in.Size) + p := outMsg.GrowNoZero(readSize) + if p == nil { + err = fmt.Errorf("Can't grow for %d-byte read", readSize) + return + } + + sh := (*reflect.SliceHeader)(unsafe.Pointer(&to.Dst)) + sh.Data = uintptr(p) + sh.Len = readSize + sh.Cap = readSize + + case fusekernel.OpListxattr: + type input fusekernel.ListxattrIn + in := (*input)(inMsg.Consume(unsafe.Sizeof(input{}))) + if in == nil { + err = errors.New("Corrupt OpListxattr") + return + } + + o = &fuseops.ListXattrOp{ + Inode: fuseops.InodeID(inMsg.Header().Nodeid), + } + + readSize := int(in.Size) + if readSize != 0 { + p := outMsg.GrowNoZero(readSize) + if p == nil { + err = fmt.Errorf("Can't grow for %d-byte read", readSize) + return + } + } + default: o = &unknownOp{ OpCode: inMsg.Header().Opcode, @@ -472,17 +527,32 @@ func (c *Connection) kernelResponse( // If the user returned the error, fill in the error field of the outgoing // message header. if opErr != nil { - m.OutHeader().Error = -int32(syscall.EIO) - if errno, ok := opErr.(syscall.Errno); ok { - m.OutHeader().Error = -int32(errno) + handled := false + + if opErr == syscall.ERANGE { + switch o := op.(type) { + case *fuseops.GetXattrOp: + writeXattrSize(m, uint32(o.BytesRead)) + handled = true + case *fuseops.ListXattrOp: + writeXattrSize(m, uint32(o.BytesRead)) + handled = true + } } - // Special case: for some types, convertInMessage grew the message in order - // to obtain a destination buffer. Make sure that we shrink back to just - // the header, because on OS X the kernel otherwise returns EINVAL when we - // attempt to write an error response with a length that extends beyond the - // header. - m.ShrinkTo(buffer.OutMessageHeaderSize) + if !handled { + m.OutHeader().Error = -int32(syscall.EIO) + if errno, ok := opErr.(syscall.Errno); ok { + m.OutHeader().Error = -int32(errno) + } + + // Special case: for some types, convertInMessage grew the message in order + // to obtain a destination buffer. Make sure that we shrink back to just + // the header, because on OS X the kernel otherwise returns EINVAL when we + // attempt to write an error response with a length that extends beyond the + // header. + m.ShrinkTo(buffer.OutMessageHeaderSize) + } } // Otherwise, fill in the rest of the response. @@ -639,6 +709,23 @@ func (c *Connection) kernelResponseForOp( case *fuseops.RemoveXattrOp: // Empty response + case *fuseops.GetXattrOp: + // convertInMessage already set up the destination buffer to be at the end + // of the out message. We need only shrink to the right size based on how + // much the user read. + if o.BytesRead == 0 { + writeXattrSize(m, uint32(o.BytesRead)) + } else { + m.ShrinkTo(buffer.OutMessageHeaderSize + o.BytesRead) + } + + case *fuseops.ListXattrOp: + if o.BytesRead == 0 { + writeXattrSize(m, uint32(o.BytesRead)) + } else { + m.ShrinkTo(buffer.OutMessageHeaderSize + o.BytesRead) + } + case *initOp: out := (*fusekernel.InitOut)(m.Grow(int(unsafe.Sizeof(fusekernel.InitOut{})))) @@ -760,3 +847,8 @@ func convertFileMode(unixMode uint32) os.FileMode { } return mode } + +func writeXattrSize(m *buffer.OutMessage, size uint32) { + out := (*fusekernel.GetxattrOut)(m.Grow(int(unsafe.Sizeof(fusekernel.GetxattrOut{})))) + out.Size = size +} diff --git a/debug.go b/debug.go index 1cf95bd..b59e698 100644 --- a/debug.go +++ b/debug.go @@ -89,6 +89,12 @@ func describeRequest(op interface{}) (s string) { addComponent("handle %d", typed.Handle) addComponent("offset %d", typed.Offset) addComponent("%d bytes", len(typed.Data)) + + case *fuseops.RemoveXattrOp: + addComponent("name %s", typed.Name) + + case *fuseops.GetXattrOp: + addComponent("name %s", typed.Name) } // Use just the name if there is no extra info. diff --git a/fuseops/ops.go b/fuseops/ops.go index f4bc89d..3579d1a 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -780,3 +780,38 @@ type RemoveXattrOp struct { // The name of the extended attribute Name string } + +// Get an extended attribute +type GetXattrOp struct { + // The inode that we are reading + Inode InodeID + + // The name of the extended attribute + Name string + + // The destination buffer. If the size is too small for the + // value, the ERANGE error should be sent. + Dst []byte + + // Set by the file system: the number of bytes read into Dst, or + // the number of bytes that would have been read into Dst if Dst was + // big enough + BytesRead int +} + +type ListXattrOp struct { + // The inode that we are reading + Inode InodeID + + // The destination buffer. If the size is too small for the + // value, the ERANGE error should be sent. + // + // The output data should consist of a sequence of NUL-terminated strings, + // one for each xattr + Dst []byte + + // Set by the file system: the number of bytes read into Dst, or + // the number of bytes that would have been read into Dst if Dst was + // big enough + BytesRead int +} diff --git a/fuseutil/file_system.go b/fuseutil/file_system.go index 88f529c..302d402 100644 --- a/fuseutil/file_system.go +++ b/fuseutil/file_system.go @@ -58,6 +58,8 @@ type FileSystem interface { ReleaseFileHandle(context.Context, *fuseops.ReleaseFileHandleOp) error ReadSymlink(context.Context, *fuseops.ReadSymlinkOp) error RemoveXattr(context.Context, *fuseops.RemoveXattrOp) error + GetXattr(context.Context, *fuseops.GetXattrOp) error + ListXattr(context.Context, *fuseops.ListXattrOp) error // Regard all inodes (including the root inode) as having their lookup counts // decremented to zero, and clean up any resources associated with the file @@ -190,6 +192,12 @@ func (s *fileSystemServer) handleOp( case *fuseops.RemoveXattrOp: err = s.fs.RemoveXattr(ctx, typed) + + case *fuseops.GetXattrOp: + err = s.fs.GetXattr(ctx, typed) + + case *fuseops.ListXattrOp: + err = s.fs.ListXattr(ctx, typed) } c.Reply(ctx, err) diff --git a/fuseutil/not_implemented_file_system.go b/fuseutil/not_implemented_file_system.go index 5df1fb3..a3f0142 100644 --- a/fuseutil/not_implemented_file_system.go +++ b/fuseutil/not_implemented_file_system.go @@ -190,5 +190,19 @@ func (fs *NotImplementedFileSystem) RemoveXattr( return } +func (fs *NotImplementedFileSystem) GetXattr( + ctx context.Context, + op *fuseops.GetXattrOp) (err error) { + err = fuse.ENOSYS + return +} + +func (fs *NotImplementedFileSystem) ListXattr( + ctx context.Context, + op *fuseops.ListXattrOp) (err error) { + err = fuse.ENOSYS + return +} + func (fs *NotImplementedFileSystem) Destroy() { } diff --git a/internal/fusekernel/fuse_kernel.go b/internal/fusekernel/fuse_kernel.go index 778f5fe..ef543cb 100644 --- a/internal/fusekernel/fuse_kernel.go +++ b/internal/fusekernel/fuse_kernel.go @@ -660,6 +660,11 @@ type GetxattrOut struct { Padding uint32 } +type ListxattrIn struct { + Size uint32 + Padding uint32 +} + type LkIn struct { Fh uint64 Owner uint64