commit
da343d6bf5
|
@ -434,6 +434,10 @@ func (c *Connection) shouldLogError(
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case *fuseops.GetXattrOp:
|
||||||
|
if err == syscall.ENODATA || err == syscall.ERANGE {
|
||||||
|
return false
|
||||||
|
}
|
||||||
case *unknownOp:
|
case *unknownOp:
|
||||||
// Don't bother the user with methods we intentionally don't support.
|
// Don't bother the user with methods we intentionally don't support.
|
||||||
if err == syscall.ENOSYS {
|
if err == syscall.ENOSYS {
|
||||||
|
@ -489,7 +493,7 @@ func (c *Connection) Reply(ctx context.Context, opErr error) {
|
||||||
if !noResponse {
|
if !noResponse {
|
||||||
err := c.writeMessage(outMsg.Bytes())
|
err := c.writeMessage(outMsg.Bytes())
|
||||||
if err != nil && c.errorLogger != nil {
|
if err != nil && c.errorLogger != nil {
|
||||||
c.errorLogger.Printf("writeMessage: %v", err)
|
c.errorLogger.Printf("writeMessage: %v %v", err, outMsg.Bytes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
144
conversions.go
144
conversions.go
|
@ -420,6 +420,107 @@ func convertInMessage(
|
||||||
Flags: fusekernel.InitFlags(in.Flags),
|
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:
|
default:
|
||||||
o = &unknownOp{
|
o = &unknownOp{
|
||||||
OpCode: inMsg.Header().Opcode,
|
OpCode: inMsg.Header().Opcode,
|
||||||
|
@ -459,6 +560,20 @@ func (c *Connection) kernelResponse(
|
||||||
// If the user returned the error, fill in the error field of the outgoing
|
// If the user returned the error, fill in the error field of the outgoing
|
||||||
// message header.
|
// message header.
|
||||||
if opErr != nil {
|
if opErr != nil {
|
||||||
|
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)
|
m.OutHeader().Error = -int32(syscall.EIO)
|
||||||
if errno, ok := opErr.(syscall.Errno); ok {
|
if errno, ok := opErr.(syscall.Errno); ok {
|
||||||
m.OutHeader().Error = -int32(errno)
|
m.OutHeader().Error = -int32(errno)
|
||||||
|
@ -471,6 +586,7 @@ func (c *Connection) kernelResponse(
|
||||||
// header.
|
// header.
|
||||||
m.ShrinkTo(buffer.OutMessageHeaderSize)
|
m.ShrinkTo(buffer.OutMessageHeaderSize)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Otherwise, fill in the rest of the response.
|
// Otherwise, fill in the rest of the response.
|
||||||
if opErr == nil {
|
if opErr == nil {
|
||||||
|
@ -623,6 +739,29 @@ func (c *Connection) kernelResponseForOp(
|
||||||
out.St.Bsize = o.IoSize
|
out.St.Bsize = o.IoSize
|
||||||
out.St.Frsize = o.BlockSize
|
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:
|
case *initOp:
|
||||||
out := (*fusekernel.InitOut)(m.Grow(int(unsafe.Sizeof(fusekernel.InitOut{}))))
|
out := (*fusekernel.InitOut)(m.Grow(int(unsafe.Sizeof(fusekernel.InitOut{}))))
|
||||||
|
|
||||||
|
@ -744,3 +883,8 @@ func convertFileMode(unixMode uint32) os.FileMode {
|
||||||
}
|
}
|
||||||
return mode
|
return mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeXattrSize(m *buffer.OutMessage, size uint32) {
|
||||||
|
out := (*fusekernel.GetxattrOut)(m.Grow(int(unsafe.Sizeof(fusekernel.GetxattrOut{}))))
|
||||||
|
out.Size = size
|
||||||
|
}
|
||||||
|
|
9
debug.go
9
debug.go
|
@ -89,6 +89,15 @@ func describeRequest(op interface{}) (s string) {
|
||||||
addComponent("handle %d", typed.Handle)
|
addComponent("handle %d", typed.Handle)
|
||||||
addComponent("offset %d", typed.Offset)
|
addComponent("offset %d", typed.Offset)
|
||||||
addComponent("%d bytes", len(typed.Data))
|
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.
|
// Use just the name if there is no extra info.
|
||||||
|
|
|
@ -22,6 +22,7 @@ const (
|
||||||
EEXIST = syscall.EEXIST
|
EEXIST = syscall.EEXIST
|
||||||
EINVAL = syscall.EINVAL
|
EINVAL = syscall.EINVAL
|
||||||
EIO = syscall.EIO
|
EIO = syscall.EIO
|
||||||
|
ENOATTR = syscall.ENODATA
|
||||||
ENOENT = syscall.ENOENT
|
ENOENT = syscall.ENOENT
|
||||||
ENOSYS = syscall.ENOSYS
|
ENOSYS = syscall.ENOSYS
|
||||||
ENOTDIR = syscall.ENOTDIR
|
ENOTDIR = syscall.ENOTDIR
|
||||||
|
|
|
@ -767,3 +767,81 @@ type ReadSymlinkOp struct {
|
||||||
// Set by the file system: the target of the symlink.
|
// Set by the file system: the target of the symlink.
|
||||||
Target string
|
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
|
FlushFile(context.Context, *fuseops.FlushFileOp) error
|
||||||
ReleaseFileHandle(context.Context, *fuseops.ReleaseFileHandleOp) error
|
ReleaseFileHandle(context.Context, *fuseops.ReleaseFileHandleOp) error
|
||||||
ReadSymlink(context.Context, *fuseops.ReadSymlinkOp) 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
|
// Regard all inodes (including the root inode) as having their lookup counts
|
||||||
// decremented to zero, and clean up any resources associated with the file
|
// decremented to zero, and clean up any resources associated with the file
|
||||||
|
@ -186,6 +190,18 @@ func (s *fileSystemServer) handleOp(
|
||||||
|
|
||||||
case *fuseops.ReadSymlinkOp:
|
case *fuseops.ReadSymlinkOp:
|
||||||
err = s.fs.ReadSymlink(ctx, typed)
|
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)
|
c.Reply(ctx, err)
|
||||||
|
|
|
@ -183,5 +183,33 @@ func (fs *NotImplementedFileSystem) ReadSymlink(
|
||||||
return
|
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() {
|
func (fs *NotImplementedFileSystem) Destroy() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -660,6 +660,11 @@ type GetxattrOut struct {
|
||||||
Padding uint32
|
Padding uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ListxattrIn struct {
|
||||||
|
Size uint32
|
||||||
|
Padding uint32
|
||||||
|
}
|
||||||
|
|
||||||
type LkIn struct {
|
type LkIn struct {
|
||||||
Fh uint64
|
Fh uint64
|
||||||
Owner uint64
|
Owner uint64
|
||||||
|
|
|
@ -61,6 +61,9 @@ type inode struct {
|
||||||
//
|
//
|
||||||
// INVARIANT: If !isSymlink(), len(target) == 0
|
// INVARIANT: If !isSymlink(), len(target) == 0
|
||||||
target string
|
target string
|
||||||
|
|
||||||
|
// extended attributes and values
|
||||||
|
xattrs map[string][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -79,6 +82,7 @@ func newInode(
|
||||||
// Create the object.
|
// Create the object.
|
||||||
in = &inode{
|
in = &inode{
|
||||||
attrs: attrs,
|
attrs: attrs,
|
||||||
|
xattrs: make(map[string][]byte),
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
@ -628,3 +629,88 @@ func (fs *memFS) ReadSymlink(
|
||||||
|
|
||||||
return
|
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"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/jacobsa/fuse"
|
||||||
"github.com/jacobsa/fuse/fusetesting"
|
"github.com/jacobsa/fuse/fusetesting"
|
||||||
"github.com/jacobsa/fuse/samples"
|
"github.com/jacobsa/fuse/samples"
|
||||||
"github.com/jacobsa/fuse/samples/memfs"
|
"github.com/jacobsa/fuse/samples/memfs"
|
||||||
. "github.com/jacobsa/oglematchers"
|
. "github.com/jacobsa/oglematchers"
|
||||||
. "github.com/jacobsa/ogletest"
|
. "github.com/jacobsa/ogletest"
|
||||||
|
"github.com/kahing/go-xattr"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMemFS(t *testing.T) { RunTests(t) }
|
func TestMemFS(t *testing.T) { RunTests(t) }
|
||||||
|
@ -1611,6 +1613,85 @@ func (t *MemFSTest) RenameNonExistentFile() {
|
||||||
ExpectThat(err, Error(HasSubstr("no such file")))
|
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
|
// Mknod
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
Loading…
Reference in New Issue