From d650fe34ba0156f438de6b786ab5caacd781caa8 Mon Sep 17 00:00:00 2001 From: Ka-Hing Cheung Date: Wed, 15 Mar 2017 00:23:54 -0700 Subject: [PATCH] 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 ////////////////////////////////////////////////////////////////////////