Skip to content

Commit da343d6

Browse files
committed
Add support for xattr ops.
Closes #21.
2 parents 77e8f7f + 6770ecc commit da343d6

11 files changed

Lines changed: 468 additions & 12 deletions

File tree

connection.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,10 @@ func (c *Connection) shouldLogError(
434434
return false
435435
}
436436

437+
case *fuseops.GetXattrOp:
438+
if err == syscall.ENODATA || err == syscall.ERANGE {
439+
return false
440+
}
437441
case *unknownOp:
438442
// Don't bother the user with methods we intentionally don't support.
439443
if err == syscall.ENOSYS {
@@ -489,7 +493,7 @@ func (c *Connection) Reply(ctx context.Context, opErr error) {
489493
if !noResponse {
490494
err := c.writeMessage(outMsg.Bytes())
491495
if err != nil && c.errorLogger != nil {
492-
c.errorLogger.Printf("writeMessage: %v", err)
496+
c.errorLogger.Printf("writeMessage: %v %v", err, outMsg.Bytes())
493497
}
494498
}
495499
}

conversions.go

Lines changed: 154 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,107 @@ func convertInMessage(
420420
Flags: fusekernel.InitFlags(in.Flags),
421421
}
422422

423+
case fusekernel.OpRemovexattr:
424+
buf := inMsg.ConsumeBytes(inMsg.Len())
425+
n := len(buf)
426+
if n == 0 || buf[n-1] != '\x00' {
427+
err = errors.New("Corrupt OpRemovexattr")
428+
return
429+
}
430+
431+
o = &fuseops.RemoveXattrOp{
432+
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
433+
Name: string(buf[:n-1]),
434+
}
435+
436+
case fusekernel.OpGetxattr:
437+
type input fusekernel.GetxattrIn
438+
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
439+
if in == nil {
440+
err = errors.New("Corrupt OpGetxattr")
441+
return
442+
}
443+
444+
name := inMsg.ConsumeBytes(inMsg.Len())
445+
i := bytes.IndexByte(name, '\x00')
446+
if i < 0 {
447+
err = errors.New("Corrupt OpGetxattr")
448+
return
449+
}
450+
name = name[:i]
451+
452+
to := &fuseops.GetXattrOp{
453+
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
454+
Name: string(name),
455+
}
456+
o = to
457+
458+
readSize := int(in.Size)
459+
p := outMsg.GrowNoZero(readSize)
460+
if p == nil {
461+
err = fmt.Errorf("Can't grow for %d-byte read", readSize)
462+
return
463+
}
464+
465+
sh := (*reflect.SliceHeader)(unsafe.Pointer(&to.Dst))
466+
sh.Data = uintptr(p)
467+
sh.Len = readSize
468+
sh.Cap = readSize
469+
470+
case fusekernel.OpListxattr:
471+
type input fusekernel.ListxattrIn
472+
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
473+
if in == nil {
474+
err = errors.New("Corrupt OpListxattr")
475+
return
476+
}
477+
478+
to := &fuseops.ListXattrOp{
479+
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
480+
}
481+
o = to
482+
483+
readSize := int(in.Size)
484+
if readSize != 0 {
485+
p := outMsg.GrowNoZero(readSize)
486+
if p == nil {
487+
err = fmt.Errorf("Can't grow for %d-byte read", readSize)
488+
return
489+
}
490+
sh := (*reflect.SliceHeader)(unsafe.Pointer(&to.Dst))
491+
sh.Data = uintptr(p)
492+
sh.Len = readSize
493+
sh.Cap = readSize
494+
}
495+
case fusekernel.OpSetxattr:
496+
type input fusekernel.SetxattrIn
497+
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
498+
if in == nil {
499+
err = errors.New("Corrupt OpSetxattr")
500+
return
501+
}
502+
503+
payload := inMsg.ConsumeBytes(inMsg.Len())
504+
// payload should be "name\x00value"
505+
if len(payload) < 3 {
506+
err = errors.New("Corrupt OpSetxattr")
507+
return
508+
}
509+
i := bytes.IndexByte(payload, '\x00')
510+
if i < 0 {
511+
err = errors.New("Corrupt OpSetxattr")
512+
return
513+
}
514+
515+
name, value := payload[:i], payload[i+1:len(payload)]
516+
517+
o = &fuseops.SetXattrOp{
518+
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
519+
Name: string(name),
520+
Value: value,
521+
Flags: in.Flags,
522+
}
523+
423524
default:
424525
o = &unknownOp{
425526
OpCode: inMsg.Header().Opcode,
@@ -459,17 +560,32 @@ func (c *Connection) kernelResponse(
459560
// If the user returned the error, fill in the error field of the outgoing
460561
// message header.
461562
if opErr != nil {
462-
m.OutHeader().Error = -int32(syscall.EIO)
463-
if errno, ok := opErr.(syscall.Errno); ok {
464-
m.OutHeader().Error = -int32(errno)
563+
handled := false
564+
565+
if opErr == syscall.ERANGE {
566+
switch o := op.(type) {
567+
case *fuseops.GetXattrOp:
568+
writeXattrSize(m, uint32(o.BytesRead))
569+
handled = true
570+
case *fuseops.ListXattrOp:
571+
writeXattrSize(m, uint32(o.BytesRead))
572+
handled = true
573+
}
574+
}
575+
576+
if !handled {
577+
m.OutHeader().Error = -int32(syscall.EIO)
578+
if errno, ok := opErr.(syscall.Errno); ok {
579+
m.OutHeader().Error = -int32(errno)
580+
}
581+
582+
// Special case: for some types, convertInMessage grew the message in order
583+
// to obtain a destination buffer. Make sure that we shrink back to just
584+
// the header, because on OS X the kernel otherwise returns EINVAL when we
585+
// attempt to write an error response with a length that extends beyond the
586+
// header.
587+
m.ShrinkTo(buffer.OutMessageHeaderSize)
465588
}
466-
467-
// Special case: for some types, convertInMessage grew the message in order
468-
// to obtain a destination buffer. Make sure that we shrink back to just
469-
// the header, because on OS X the kernel otherwise returns EINVAL when we
470-
// attempt to write an error response with a length that extends beyond the
471-
// header.
472-
m.ShrinkTo(buffer.OutMessageHeaderSize)
473589
}
474590

475591
// Otherwise, fill in the rest of the response.
@@ -623,6 +739,29 @@ func (c *Connection) kernelResponseForOp(
623739
out.St.Bsize = o.IoSize
624740
out.St.Frsize = o.BlockSize
625741

742+
case *fuseops.RemoveXattrOp:
743+
// Empty response
744+
745+
case *fuseops.GetXattrOp:
746+
// convertInMessage already set up the destination buffer to be at the end
747+
// of the out message. We need only shrink to the right size based on how
748+
// much the user read.
749+
if o.BytesRead == 0 {
750+
writeXattrSize(m, uint32(o.BytesRead))
751+
} else {
752+
m.ShrinkTo(buffer.OutMessageHeaderSize + o.BytesRead)
753+
}
754+
755+
case *fuseops.ListXattrOp:
756+
if o.BytesRead == 0 {
757+
writeXattrSize(m, uint32(o.BytesRead))
758+
} else {
759+
m.ShrinkTo(buffer.OutMessageHeaderSize + o.BytesRead)
760+
}
761+
762+
case *fuseops.SetXattrOp:
763+
// Empty response
764+
626765
case *initOp:
627766
out := (*fusekernel.InitOut)(m.Grow(int(unsafe.Sizeof(fusekernel.InitOut{}))))
628767

@@ -744,3 +883,8 @@ func convertFileMode(unixMode uint32) os.FileMode {
744883
}
745884
return mode
746885
}
886+
887+
func writeXattrSize(m *buffer.OutMessage, size uint32) {
888+
out := (*fusekernel.GetxattrOut)(m.Grow(int(unsafe.Sizeof(fusekernel.GetxattrOut{}))))
889+
out.Size = size
890+
}

debug.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@ func describeRequest(op interface{}) (s string) {
8989
addComponent("handle %d", typed.Handle)
9090
addComponent("offset %d", typed.Offset)
9191
addComponent("%d bytes", len(typed.Data))
92+
93+
case *fuseops.RemoveXattrOp:
94+
addComponent("name %s", typed.Name)
95+
96+
case *fuseops.GetXattrOp:
97+
addComponent("name %s", typed.Name)
98+
99+
case *fuseops.SetXattrOp:
100+
addComponent("name %s", typed.Name)
92101
}
93102

94103
// Use just the name if there is no extra info.

errors.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const (
2222
EEXIST = syscall.EEXIST
2323
EINVAL = syscall.EINVAL
2424
EIO = syscall.EIO
25+
ENOATTR = syscall.ENODATA
2526
ENOENT = syscall.ENOENT
2627
ENOSYS = syscall.ENOSYS
2728
ENOTDIR = syscall.ENOTDIR

fuseops/ops.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,3 +767,81 @@ type ReadSymlinkOp struct {
767767
// Set by the file system: the target of the symlink.
768768
Target string
769769
}
770+
771+
////////////////////////////////////////////////////////////////////////
772+
// eXtended attributes
773+
////////////////////////////////////////////////////////////////////////
774+
775+
// Remove an extended attribute.
776+
//
777+
// This is sent in response to removexattr(2). Return ENOATTR if the
778+
// extended attribute does not exist.
779+
type RemoveXattrOp struct {
780+
// The inode that we are removing an extended attribute from.
781+
Inode InodeID
782+
783+
// The name of the extended attribute.
784+
Name string
785+
}
786+
787+
// Get an extended attribute.
788+
//
789+
// This is sent in response to getxattr(2). Return ENOATTR if the
790+
// extended attribute does not exist.
791+
type GetXattrOp struct {
792+
// The inode whose extended attribute we are reading.
793+
Inode InodeID
794+
795+
// The name of the extended attribute.
796+
Name string
797+
798+
// The destination buffer. If the size is too small for the
799+
// value, the ERANGE error should be sent.
800+
Dst []byte
801+
802+
// Set by the file system: the number of bytes read into Dst, or
803+
// the number of bytes that would have been read into Dst if Dst was
804+
// big enough (return ERANGE in this case).
805+
BytesRead int
806+
}
807+
808+
// List all the extended attributes for a file.
809+
//
810+
// This is sent in response to listxattr(2).
811+
type ListXattrOp struct {
812+
// The inode whose extended attributes we are listing.
813+
Inode InodeID
814+
815+
// The destination buffer. If the size is too small for the
816+
// value, the ERANGE error should be sent.
817+
//
818+
// The output data should consist of a sequence of NUL-terminated strings,
819+
// one for each xattr.
820+
Dst []byte
821+
822+
// Set by the file system: the number of bytes read into Dst, or
823+
// the number of bytes that would have been read into Dst if Dst was
824+
// big enough (return ERANGE in this case).
825+
BytesRead int
826+
}
827+
828+
// Set an extended attribute.
829+
//
830+
// This is sent in response to setxattr(2). Return ENOSPC if there is
831+
// insufficient space remaining to store the extended attribute.
832+
type SetXattrOp struct {
833+
// The inode whose extended attribute we are setting.
834+
Inode InodeID
835+
836+
// The name of the extended attribute
837+
Name string
838+
839+
// The value to for the extened attribute.
840+
Value []byte
841+
842+
// If Flags is 0x1, and the attribute exists already, EEXIST should be returned.
843+
// If Flags is 0x2, and the attribute does not exist, ENOATTR should be returned.
844+
// If Flags is 0x0, the extended attribute will be created if need be, or will
845+
// simply replace the value if the attribute exists.
846+
Flags uint32
847+
}

fuseutil/file_system.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ type FileSystem interface {
5757
FlushFile(context.Context, *fuseops.FlushFileOp) error
5858
ReleaseFileHandle(context.Context, *fuseops.ReleaseFileHandleOp) error
5959
ReadSymlink(context.Context, *fuseops.ReadSymlinkOp) error
60+
RemoveXattr(context.Context, *fuseops.RemoveXattrOp) error
61+
GetXattr(context.Context, *fuseops.GetXattrOp) error
62+
ListXattr(context.Context, *fuseops.ListXattrOp) error
63+
SetXattr(context.Context, *fuseops.SetXattrOp) error
6064

6165
// Regard all inodes (including the root inode) as having their lookup counts
6266
// decremented to zero, and clean up any resources associated with the file
@@ -186,6 +190,18 @@ func (s *fileSystemServer) handleOp(
186190

187191
case *fuseops.ReadSymlinkOp:
188192
err = s.fs.ReadSymlink(ctx, typed)
193+
194+
case *fuseops.RemoveXattrOp:
195+
err = s.fs.RemoveXattr(ctx, typed)
196+
197+
case *fuseops.GetXattrOp:
198+
err = s.fs.GetXattr(ctx, typed)
199+
200+
case *fuseops.ListXattrOp:
201+
err = s.fs.ListXattr(ctx, typed)
202+
203+
case *fuseops.SetXattrOp:
204+
err = s.fs.SetXattr(ctx, typed)
189205
}
190206

191207
c.Reply(ctx, err)

fuseutil/not_implemented_file_system.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,5 +183,33 @@ func (fs *NotImplementedFileSystem) ReadSymlink(
183183
return
184184
}
185185

186+
func (fs *NotImplementedFileSystem) RemoveXattr(
187+
ctx context.Context,
188+
op *fuseops.RemoveXattrOp) (err error) {
189+
err = fuse.ENOSYS
190+
return
191+
}
192+
193+
func (fs *NotImplementedFileSystem) GetXattr(
194+
ctx context.Context,
195+
op *fuseops.GetXattrOp) (err error) {
196+
err = fuse.ENOSYS
197+
return
198+
}
199+
200+
func (fs *NotImplementedFileSystem) ListXattr(
201+
ctx context.Context,
202+
op *fuseops.ListXattrOp) (err error) {
203+
err = fuse.ENOSYS
204+
return
205+
}
206+
207+
func (fs *NotImplementedFileSystem) SetXattr(
208+
ctx context.Context,
209+
op *fuseops.SetXattrOp) (err error) {
210+
err = fuse.ENOSYS
211+
return
212+
}
213+
186214
func (fs *NotImplementedFileSystem) Destroy() {
187215
}

internal/fusekernel/fuse_kernel.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,11 @@ type GetxattrOut struct {
660660
Padding uint32
661661
}
662662

663+
type ListxattrIn struct {
664+
Size uint32
665+
Padding uint32
666+
}
667+
663668
type LkIn struct {
664669
Fh uint64
665670
Owner uint64

0 commit comments

Comments
 (0)