Commit da343d6b authored by Aaron Jacobs's avatar Aaron Jacobs

Add support for xattr ops.

Closes #21.
parents 77e8f7f7 6770eccb
......@@ -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())
}
}
}
......
......@@ -420,6 +420,107 @@ 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]),
}
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
}
to := &fuseops.ListXattrOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
}
o = to
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
}
sh := (*reflect.SliceHeader)(unsafe.Pointer(&to.Dst))
sh.Data = uintptr(p)
sh.Len = readSize
sh.Cap = readSize
}
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)]
o = &fuseops.SetXattrOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
Name: string(name),
Value: value,
Flags: in.Flags,
}
default:
o = &unknownOp{
OpCode: inMsg.Header().Opcode,
......@@ -459,17 +560,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
}
}
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)
}
// 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.
......@@ -623,6 +739,29 @@ func (c *Connection) kernelResponseForOp(
out.St.Bsize = o.IoSize
out.St.Frsize = o.BlockSize
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 *fuseops.SetXattrOp:
// Empty response
case *initOp:
out := (*fusekernel.InitOut)(m.Grow(int(unsafe.Sizeof(fusekernel.InitOut{}))))
......@@ -744,3 +883,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
}
......@@ -89,6 +89,15 @@ 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)
case *fuseops.SetXattrOp:
addComponent("name %s", typed.Name)
}
// Use just the name if there is no extra info.
......
......@@ -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
......
......@@ -767,3 +767,81 @@ type ReadSymlinkOp struct {
// Set by the file system: the target of the symlink.
Target string
}
////////////////////////////////////////////////////////////////////////
// eXtended attributes
////////////////////////////////////////////////////////////////////////
// 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 removing an extended attribute from.
Inode InodeID
// The name of the extended attribute.
Name string
}
// 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 whose extended attribute 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 (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 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.
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 (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 whose extended attribute we are setting.
Inode InodeID
// The name of the extended attribute
Name string
// 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
}
......@@ -57,6 +57,10 @@ 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
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
......@@ -186,6 +190,18 @@ func (s *fileSystemServer) handleOp(
case *fuseops.ReadSymlinkOp:
err = s.fs.ReadSymlink(ctx, typed)
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)
case *fuseops.SetXattrOp:
err = s.fs.SetXattr(ctx, typed)
}
c.Reply(ctx, err)
......
......@@ -183,5 +183,33 @@ func (fs *NotImplementedFileSystem) ReadSymlink(
return
}
func (fs *NotImplementedFileSystem) RemoveXattr(
ctx context.Context,
op *fuseops.RemoveXattrOp) (err error) {
err = fuse.ENOSYS
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) SetXattr(
ctx context.Context,
op *fuseops.SetXattrOp) (err error) {
err = fuse.ENOSYS
return
}
func (fs *NotImplementedFileSystem) Destroy() {
}
......@@ -660,6 +660,11 @@ type GetxattrOut struct {
Padding uint32
}
type ListxattrIn struct {
Size uint32
Padding uint32
}
type LkIn struct {
Fh uint64
Owner uint64
......
......@@ -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
......
......@@ -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
}
......@@ -27,11 +27,13 @@ import (
"testing"
"time"
"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) }
......@@ -1611,6 +1613,85 @@ func (t *MemFSTest) RenameNonExistentFile() {
ExpectThat(err, Error(HasSubstr("no such file")))
}
func (t *MemFSTest) NoXattrs() {
var err error
// 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)
ExpectThat(names, ElementsAre())
// Attempt to read a non-existent xattr.
_, err = xattr.Getxattr(filePath, "foo", nil)
ExpectEq(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"), xattr.REPLACE)
AssertEq(fuse.ENOATTR, err)
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"), xattr.REPLACE)
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"), xattr.CREATE)
AssertEq(nil, err)
err = xattr.Removexattr(filePath, "foo")
AssertEq(nil, err)
_, err = xattr.Getxattr(filePath, "foo", nil)
AssertEq(fuse.ENOATTR, err)
}
////////////////////////////////////////////////////////////////////////
// Mknod
////////////////////////////////////////////////////////////////////////
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment