From 8aade5c71f02524323045bb73bdff9240ccbf3f7 Mon Sep 17 00:00:00 2001 From: Ka-Hing Cheung Date: Fri, 4 Dec 2015 12:26:49 -0800 Subject: [PATCH 1/6] RemoveXattr implementation --- conversions.go | 16 ++++++++++++++++ fuseops/ops.go | 13 +++++++++++++ fuseutil/file_system.go | 4 ++++ fuseutil/not_implemented_file_system.go | 7 +++++++ 4 files changed, 40 insertions(+) diff --git a/conversions.go b/conversions.go index b30a643..755cdbe 100644 --- a/conversions.go +++ b/conversions.go @@ -420,6 +420,19 @@ func convertInMessage( Flags: fusekernel.InitFlags(in.Flags), } + case fusekernel.OpRemovexattr: + buf := inMsg.ConsumeBytes(inMsg.Len()) + n := len(buf) + if n == 0 || buf[n-1] != '\x00' { + err = errors.New("Corrupt OpRemovexattr") + return + } + + o = &fuseops.RemoveXattrOp{ + Inode: fuseops.InodeID(inMsg.Header().Nodeid), + Name: string(buf[:n-1]), + } + default: o = &unknownOp{ OpCode: inMsg.Header().Opcode, @@ -623,6 +636,9 @@ func (c *Connection) kernelResponseForOp( out.St.Bsize = o.IoSize out.St.Frsize = o.BlockSize + case *fuseops.RemoveXattrOp: + // Empty response + case *initOp: out := (*fusekernel.InitOut)(m.Grow(int(unsafe.Sizeof(fusekernel.InitOut{})))) diff --git a/fuseops/ops.go b/fuseops/ops.go index f8fa739..f4bc89d 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -767,3 +767,16 @@ type ReadSymlinkOp struct { // Set by the file system: the target of the symlink. Target string } + +//////////////////////////////////////////////////////////////////////// +// eXtended attributes +//////////////////////////////////////////////////////////////////////// + +// Remove an extended attribute +type RemoveXattrOp struct { + // The inode that we are reading + Inode InodeID + + // The name of the extended attribute + Name string +} diff --git a/fuseutil/file_system.go b/fuseutil/file_system.go index 2921c1d..88f529c 100644 --- a/fuseutil/file_system.go +++ b/fuseutil/file_system.go @@ -57,6 +57,7 @@ type FileSystem interface { FlushFile(context.Context, *fuseops.FlushFileOp) error ReleaseFileHandle(context.Context, *fuseops.ReleaseFileHandleOp) error ReadSymlink(context.Context, *fuseops.ReadSymlinkOp) error + RemoveXattr(context.Context, *fuseops.RemoveXattrOp) 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 @@ -186,6 +187,9 @@ func (s *fileSystemServer) handleOp( case *fuseops.ReadSymlinkOp: err = s.fs.ReadSymlink(ctx, typed) + + case *fuseops.RemoveXattrOp: + err = s.fs.RemoveXattr(ctx, typed) } c.Reply(ctx, err) diff --git a/fuseutil/not_implemented_file_system.go b/fuseutil/not_implemented_file_system.go index fc082d2..5df1fb3 100644 --- a/fuseutil/not_implemented_file_system.go +++ b/fuseutil/not_implemented_file_system.go @@ -183,5 +183,12 @@ func (fs *NotImplementedFileSystem) ReadSymlink( return } +func (fs *NotImplementedFileSystem) RemoveXattr( + ctx context.Context, + op *fuseops.RemoveXattrOp) (err error) { + err = fuse.ENOSYS + return +} + func (fs *NotImplementedFileSystem) Destroy() { } From d20c4665c1e4e6bfe07084c1d0b2b79a53802504 Mon Sep 17 00:00:00 2001 From: Ka-Hing Cheung Date: Thu, 10 Dec 2015 12:58:46 -0800 Subject: [PATCH 2/6] GetXattr/ListXattr implementation --- connection.go | 6 +- conversions.go | 110 ++++++++++++++++++++++-- debug.go | 6 ++ fuseops/ops.go | 35 ++++++++ fuseutil/file_system.go | 8 ++ fuseutil/not_implemented_file_system.go | 14 +++ internal/fusekernel/fuse_kernel.go | 5 ++ 7 files changed, 174 insertions(+), 10 deletions(-) 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 From cbeaa5508784b80ec52f08d32be1ff429f8fc2f5 Mon Sep 17 00:00:00 2001 From: Ka-Hing Cheung Date: Sun, 5 Mar 2017 19:40:50 -0800 Subject: [PATCH 3/6] SetXattr support --- conversions.go | 32 +++++++++++++++++++++++++ debug.go | 3 +++ fuseops/ops.go | 15 ++++++++++++ fuseutil/file_system.go | 4 ++++ fuseutil/not_implemented_file_system.go | 7 ++++++ 5 files changed, 61 insertions(+) diff --git a/conversions.go b/conversions.go index 1a77652..05fce2f 100644 --- a/conversions.go +++ b/conversions.go @@ -487,6 +487,35 @@ func convertInMessage( return } } + case fusekernel.OpSetxattr: + type input fusekernel.SetxattrIn + in := (*input)(inMsg.Consume(unsafe.Sizeof(input{}))) + if in == nil { + err = errors.New("Corrupt OpSetxattr") + return + } + + payload := inMsg.ConsumeBytes(inMsg.Len()) + // payload should be "name\x00value" + if len(payload) < 3 { + err = errors.New("Corrupt OpSetxattr") + return + } + i := bytes.IndexByte(payload, '\x00') + if i < 0 { + err = errors.New("Corrupt OpSetxattr") + return + } + + name, value := payload[:i], payload[i+1:len(payload)] + fmt.Printf("Setting %v to %v\n", name, value) + + o = &fuseops.SetXattrOp{ + Inode: fuseops.InodeID(inMsg.Header().Nodeid), + Name: string(name), + Data: value, + Flags: in.Flags, + } default: o = &unknownOp{ @@ -726,6 +755,9 @@ func (c *Connection) kernelResponseForOp( m.ShrinkTo(buffer.OutMessageHeaderSize + o.BytesRead) } + case *fuseops.SetXattrOp: + // Empty response + case *initOp: out := (*fusekernel.InitOut)(m.Grow(int(unsafe.Sizeof(fusekernel.InitOut{})))) diff --git a/debug.go b/debug.go index b59e698..d6f9fdc 100644 --- a/debug.go +++ b/debug.go @@ -95,6 +95,9 @@ func describeRequest(op interface{}) (s string) { case *fuseops.GetXattrOp: addComponent("name %s", typed.Name) + + case *fuseops.SetXattrOp: + 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 3579d1a..a8d8220 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -815,3 +815,18 @@ type ListXattrOp struct { // big enough BytesRead int } + +type SetXattrOp struct { + // The inode that we are changing + Inode InodeID + + // The name of the extended attribute + Name string + + // The data to for the extened attribute. + Data []byte + + // If Flags is 0x1, and the attribute exists already, EEXIST should be returned. + // If Flags is 0x2, and the attribute does not exist, ENOATTR should be returned. + Flags uint32 +} diff --git a/fuseutil/file_system.go b/fuseutil/file_system.go index 302d402..9cc0fa8 100644 --- a/fuseutil/file_system.go +++ b/fuseutil/file_system.go @@ -60,6 +60,7 @@ type FileSystem interface { RemoveXattr(context.Context, *fuseops.RemoveXattrOp) error GetXattr(context.Context, *fuseops.GetXattrOp) error ListXattr(context.Context, *fuseops.ListXattrOp) error + SetXattr(context.Context, *fuseops.SetXattrOp) 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 @@ -198,6 +199,9 @@ func (s *fileSystemServer) handleOp( case *fuseops.ListXattrOp: err = s.fs.ListXattr(ctx, typed) + + case *fuseops.SetXattrOp: + err = s.fs.SetXattr(ctx, typed) } c.Reply(ctx, err) diff --git a/fuseutil/not_implemented_file_system.go b/fuseutil/not_implemented_file_system.go index a3f0142..b21e1e3 100644 --- a/fuseutil/not_implemented_file_system.go +++ b/fuseutil/not_implemented_file_system.go @@ -204,5 +204,12 @@ func (fs *NotImplementedFileSystem) ListXattr( return } +func (fs *NotImplementedFileSystem) SetXattr( + ctx context.Context, + op *fuseops.SetXattrOp) (err error) { + err = fuse.ENOSYS + return +} + func (fs *NotImplementedFileSystem) Destroy() { } From d650fe34ba0156f438de6b786ab5caacd781caa8 Mon Sep 17 00:00:00 2001 From: Ka-Hing Cheung Date: Wed, 15 Mar 2017 00:23:54 -0700 Subject: [PATCH 4/6] added tests and enhanced comments --- conversions.go | 10 +++-- errors.go | 1 + fuseops/ops.go | 41 ++++++++++++------ samples/memfs/inode.go | 6 ++- samples/memfs/memfs.go | 86 +++++++++++++++++++++++++++++++++++++ samples/memfs/memfs_test.go | 79 ++++++++++++++++++++++++++++++++++ 6 files changed, 206 insertions(+), 17 deletions(-) diff --git a/conversions.go b/conversions.go index 05fce2f..6b0f16e 100644 --- a/conversions.go +++ b/conversions.go @@ -475,9 +475,10 @@ func convertInMessage( return } - o = &fuseops.ListXattrOp{ + to := &fuseops.ListXattrOp{ Inode: fuseops.InodeID(inMsg.Header().Nodeid), } + o = to readSize := int(in.Size) if readSize != 0 { @@ -486,6 +487,10 @@ func convertInMessage( 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.OpSetxattr: type input fusekernel.SetxattrIn @@ -508,12 +513,11 @@ func convertInMessage( } name, value := payload[:i], payload[i+1:len(payload)] - fmt.Printf("Setting %v to %v\n", name, value) o = &fuseops.SetXattrOp{ Inode: fuseops.InodeID(inMsg.Header().Nodeid), Name: string(name), - Data: value, + Value: value, Flags: in.Flags, } diff --git a/errors.go b/errors.go index 455c79b..dcf5eef 100644 --- a/errors.go +++ b/errors.go @@ -22,6 +22,7 @@ const ( EEXIST = syscall.EEXIST EINVAL = syscall.EINVAL EIO = syscall.EIO + ENOATTR = syscall.ENODATA ENOENT = syscall.ENOENT ENOSYS = syscall.ENOSYS ENOTDIR = syscall.ENOTDIR diff --git a/fuseops/ops.go b/fuseops/ops.go index a8d8220..a939799 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -772,21 +772,27 @@ type ReadSymlinkOp struct { // eXtended attributes //////////////////////////////////////////////////////////////////////// -// Remove an extended attribute +// Remove an extended attribute. +// +// This is sent in response to removexattr(2). Return ENOATTR if the +// extended attribute does not exist. type RemoveXattrOp struct { - // The inode that we are reading + // The inode that we are removing an extended attribute from. Inode InodeID - // The name of the extended attribute + // The name of the extended attribute. Name string } -// Get an extended attribute +// Get an extended attribute. +// +// This is sent in response to getxattr(2). Return ENOATTR if the +// extended attribute does not exist. type GetXattrOp struct { - // The inode that we are reading + // The inode whose extended attribute we are reading. Inode InodeID - // The name of the extended attribute + // The name of the extended attribute. Name string // The destination buffer. If the size is too small for the @@ -795,38 +801,47 @@ type GetXattrOp struct { // 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 + // big enough (return ERANGE in this case). BytesRead int } +// List all the extended attributes for a file. +// +// This is sent in response to listxattr(2). type ListXattrOp struct { - // The inode that we are reading + // The inode whose extended attributes we are listing. 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 + // 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 + // big enough (return ERANGE in this case). BytesRead int } +// Set an extended attribute. +// +// This is sent in response to setxattr(2). Return ENOSPC if there is +// insufficient space remaining to store the extended attribute. type SetXattrOp struct { - // The inode that we are changing + // The inode whose extended attribute we are setting. Inode InodeID // The name of the extended attribute Name string - // The data to for the extened attribute. - Data []byte + // The value to for the extened attribute. + Value []byte // If Flags is 0x1, and the attribute exists already, EEXIST should be returned. // If Flags is 0x2, and the attribute does not exist, ENOATTR should be returned. + // If Flags is 0x0, the extended attribute will be created if need be, or will + // simply replace the value if the attribute exists. Flags uint32 } diff --git a/samples/memfs/inode.go b/samples/memfs/inode.go index b436a1a..b0471cc 100644 --- a/samples/memfs/inode.go +++ b/samples/memfs/inode.go @@ -61,6 +61,9 @@ type inode struct { // // INVARIANT: If !isSymlink(), len(target) == 0 target string + + // extended attributes and values + xattrs map[string][]byte } //////////////////////////////////////////////////////////////////////// @@ -78,7 +81,8 @@ func newInode( // Create the object. in = &inode{ - attrs: attrs, + attrs: attrs, + xattrs: make(map[string][]byte), } return diff --git a/samples/memfs/memfs.go b/samples/memfs/memfs.go index 9963952..bf7c570 100644 --- a/samples/memfs/memfs.go +++ b/samples/memfs/memfs.go @@ -18,6 +18,7 @@ import ( "fmt" "io" "os" + "syscall" "time" "golang.org/x/net/context" @@ -628,3 +629,88 @@ func (fs *memFS) ReadSymlink( return } + +func (fs *memFS) GetXattr(ctx context.Context, + op *fuseops.GetXattrOp) (err error) { + fs.mu.Lock() + defer fs.mu.Unlock() + + inode := fs.getInodeOrDie(op.Inode) + if value, ok := inode.xattrs[op.Name]; ok { + op.BytesRead = len(value) + if len(op.Dst) >= len(value) { + copy(op.Dst, value) + } else { + err = syscall.ERANGE + } + } else { + err = fuse.ENOATTR + } + + return +} + +func (fs *memFS) ListXattr(ctx context.Context, + op *fuseops.ListXattrOp) (err error) { + fs.mu.Lock() + defer fs.mu.Unlock() + + inode := fs.getInodeOrDie(op.Inode) + + dst := op.Dst[:] + for key := range inode.xattrs { + keyLen := len(key) + 1 + + if err == nil && len(dst) >= keyLen { + copy(dst, key) + dst = dst[keyLen:] + } else { + err = syscall.ERANGE + } + op.BytesRead += keyLen + } + + return +} + +func (fs *memFS) RemoveXattr(ctx context.Context, + op *fuseops.RemoveXattrOp) (err error) { + fs.mu.Lock() + defer fs.mu.Unlock() + inode := fs.getInodeOrDie(op.Inode) + + if _, ok := inode.xattrs[op.Name]; ok { + delete(inode.xattrs, op.Name) + } else { + err = fuse.ENOATTR + } + return +} + +func (fs *memFS) SetXattr(ctx context.Context, + op *fuseops.SetXattrOp) (err error) { + fs.mu.Lock() + defer fs.mu.Unlock() + inode := fs.getInodeOrDie(op.Inode) + + _, ok := inode.xattrs[op.Name] + + switch op.Flags { + case 0x1: + if ok { + err = fuse.EEXIST + } + case 0x2: + if !ok { + err = fuse.ENOATTR + } + } + + if err == nil { + value := make([]byte, len(op.Value)) + copy(value, op.Value) + inode.xattrs[op.Name] = value + } + + return +} diff --git a/samples/memfs/memfs_test.go b/samples/memfs/memfs_test.go index 977dae3..fc38aaf 100644 --- a/samples/memfs/memfs_test.go +++ b/samples/memfs/memfs_test.go @@ -27,6 +27,8 @@ import ( "testing" "time" + "github.com/ivaxer/go-xattr" + "github.com/jacobsa/fuse" "github.com/jacobsa/fuse/fusetesting" "github.com/jacobsa/fuse/samples" "github.com/jacobsa/fuse/samples/memfs" @@ -1611,6 +1613,83 @@ func (t *MemFSTest) RenameNonExistentFile() { ExpectThat(err, Error(HasSubstr("no such file"))) } +func (t *MemFSTest) GetListNoXAttr() { + var err error + + // Create a file + filePath := path.Join(t.Dir, "foo") + err = ioutil.WriteFile(filePath, []byte("taco"), 0400) + AssertEq(nil, err) + + names, err := xattr.List(filePath) + AssertEq(nil, err) + AssertEq(0, len(names)) + + _, err = xattr.Getxattr(filePath, "foo", nil) + AssertEq(fuse.ENOATTR, err) +} + +func (t *MemFSTest) SetXAttr() { + var err error + + // Create a file + filePath := path.Join(t.Dir, "foo") + err = ioutil.WriteFile(filePath, []byte("taco"), 0600) + AssertEq(nil, err) + + err = xattr.Setxattr(filePath, "foo", []byte("bar"), 0x2) + AssertEq(fuse.ENOATTR, err) + + err = xattr.Setxattr(filePath, "foo", []byte("bar"), 0x1) + AssertEq(nil, err) + + value, err := xattr.Get(filePath, "foo") + AssertEq(nil, err) + AssertEq("bar", string(value)) + + err = xattr.Setxattr(filePath, "foo", []byte("hello world"), 0x2) + AssertEq(nil, err) + + value, err = xattr.Get(filePath, "foo") + AssertEq(nil, err) + AssertEq("hello world", string(value)) + + names, err := xattr.List(filePath) + AssertEq(nil, err) + AssertEq(1, len(names)) + AssertEq("foo", names[0]) + + err = xattr.Setxattr(filePath, "bar", []byte("hello world"), 0x0) + AssertEq(nil, err) + + names, err = xattr.List(filePath) + AssertEq(nil, err) + AssertEq(2, len(names)) + ExpectThat(names, Contains("foo")) + ExpectThat(names, Contains("bar")) +} + +func (t *MemFSTest) RemoveXAttr() { + var err error + + // Create a file + filePath := path.Join(t.Dir, "foo") + err = ioutil.WriteFile(filePath, []byte("taco"), 0600) + AssertEq(nil, err) + + err = xattr.Removexattr(filePath, "foo") + AssertEq(fuse.ENOATTR, err) + + err = xattr.Setxattr(filePath, "foo", []byte("bar"), 0x1) + AssertEq(nil, err) + + err = xattr.Removexattr(filePath, "foo") + AssertEq(nil, err) + + _, err = xattr.Getxattr(filePath, "foo", nil) + AssertEq(fuse.ENOATTR, err) +} + //////////////////////////////////////////////////////////////////////// // Mknod //////////////////////////////////////////////////////////////////////// From 3eadae8418603110033740ceae6f9f116b3a5d7e Mon Sep 17 00:00:00 2001 From: Ka-Hing Cheung Date: Fri, 17 Mar 2017 00:00:54 -0700 Subject: [PATCH 5/6] use constants from upstream PR --- samples/memfs/memfs_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/samples/memfs/memfs_test.go b/samples/memfs/memfs_test.go index fc38aaf..9035a55 100644 --- a/samples/memfs/memfs_test.go +++ b/samples/memfs/memfs_test.go @@ -27,13 +27,13 @@ import ( "testing" "time" - "github.com/ivaxer/go-xattr" "github.com/jacobsa/fuse" "github.com/jacobsa/fuse/fusetesting" "github.com/jacobsa/fuse/samples" "github.com/jacobsa/fuse/samples/memfs" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" + "github.com/kahing/go-xattr" ) func TestMemFS(t *testing.T) { RunTests(t) } @@ -1637,17 +1637,17 @@ func (t *MemFSTest) SetXAttr() { err = ioutil.WriteFile(filePath, []byte("taco"), 0600) AssertEq(nil, err) - err = xattr.Setxattr(filePath, "foo", []byte("bar"), 0x2) + err = xattr.Setxattr(filePath, "foo", []byte("bar"), xattr.REPLACE) AssertEq(fuse.ENOATTR, err) - err = xattr.Setxattr(filePath, "foo", []byte("bar"), 0x1) + err = xattr.Setxattr(filePath, "foo", []byte("bar"), xattr.CREATE) AssertEq(nil, err) value, err := xattr.Get(filePath, "foo") AssertEq(nil, err) AssertEq("bar", string(value)) - err = xattr.Setxattr(filePath, "foo", []byte("hello world"), 0x2) + err = xattr.Setxattr(filePath, "foo", []byte("hello world"), xattr.REPLACE) AssertEq(nil, err) value, err = xattr.Get(filePath, "foo") @@ -1680,7 +1680,7 @@ func (t *MemFSTest) RemoveXAttr() { err = xattr.Removexattr(filePath, "foo") AssertEq(fuse.ENOATTR, err) - err = xattr.Setxattr(filePath, "foo", []byte("bar"), 0x1) + err = xattr.Setxattr(filePath, "foo", []byte("bar"), xattr.CREATE) AssertEq(nil, err) err = xattr.Removexattr(filePath, "foo") From 6770eccb1cf402ec38811f191bc1cd8fc113cb3f Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Thu, 16 Mar 2017 09:05:32 +1100 Subject: [PATCH 6/6] memfs_test: touch up the style of xattr tests. --- samples/memfs/memfs_test.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/samples/memfs/memfs_test.go b/samples/memfs/memfs_test.go index 9035a55..dcdad01 100644 --- a/samples/memfs/memfs_test.go +++ b/samples/memfs/memfs_test.go @@ -1613,26 +1613,28 @@ func (t *MemFSTest) RenameNonExistentFile() { ExpectThat(err, Error(HasSubstr("no such file"))) } -func (t *MemFSTest) GetListNoXAttr() { +func (t *MemFSTest) NoXattrs() { var err error - // Create a file + // Create a file. filePath := path.Join(t.Dir, "foo") err = ioutil.WriteFile(filePath, []byte("taco"), 0400) AssertEq(nil, err) + // List xattr names. names, err := xattr.List(filePath) AssertEq(nil, err) - AssertEq(0, len(names)) + ExpectThat(names, ElementsAre()) + // Attempt to read a non-existent xattr. _, err = xattr.Getxattr(filePath, "foo", nil) - AssertEq(fuse.ENOATTR, err) + ExpectEq(fuse.ENOATTR, err) } func (t *MemFSTest) SetXAttr() { var err error - // Create a file + // Create a file. filePath := path.Join(t.Dir, "foo") err = ioutil.WriteFile(filePath, []byte("taco"), 0600) AssertEq(nil, err)