Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
J
jacobsa-fuse
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
jacobsa-fuse
Commits
da343d6b
Commit
da343d6b
authored
May 12, 2017
by
Aaron Jacobs
Browse files
Options
Browse Files
Download
Plain Diff
Add support for xattr ops.
Closes #21.
parents
77e8f7f7
6770eccb
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
468 additions
and
12 deletions
+468
-12
connection.go
connection.go
+5
-1
conversions.go
conversions.go
+154
-10
debug.go
debug.go
+9
-0
errors.go
errors.go
+1
-0
fuseops/ops.go
fuseops/ops.go
+78
-0
fuseutil/file_system.go
fuseutil/file_system.go
+16
-0
fuseutil/not_implemented_file_system.go
fuseutil/not_implemented_file_system.go
+28
-0
internal/fusekernel/fuse_kernel.go
internal/fusekernel/fuse_kernel.go
+5
-0
samples/memfs/inode.go
samples/memfs/inode.go
+5
-1
samples/memfs/memfs.go
samples/memfs/memfs.go
+86
-0
samples/memfs/memfs_test.go
samples/memfs/memfs_test.go
+81
-0
No files found.
connection.go
View file @
da343d6b
...
...
@@ -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
()
)
}
}
}
...
...
conversions.go
View file @
da343d6b
...
...
@@ -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
}
debug.go
View file @
da343d6b
...
...
@@ -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.
...
...
errors.go
View file @
da343d6b
...
...
@@ -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
...
...
fuseops/ops.go
View file @
da343d6b
...
...
@@ -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
}
fuseutil/file_system.go
View file @
da343d6b
...
...
@@ -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
)
...
...
fuseutil/not_implemented_file_system.go
View file @
da343d6b
...
...
@@ -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
()
{
}
internal/fusekernel/fuse_kernel.go
View file @
da343d6b
...
...
@@ -660,6 +660,11 @@ type GetxattrOut struct {
Padding
uint32
}
type
ListxattrIn
struct
{
Size
uint32
Padding
uint32
}
type
LkIn
struct
{
Fh
uint64
Owner
uint64
...
...
samples/memfs/inode.go
View file @
da343d6b
...
...
@@ -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
...
...
samples/memfs/memfs.go
View file @
da343d6b
...
...
@@ -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
}
samples/memfs/memfs_test.go
View file @
da343d6b
...
...
@@ -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
////////////////////////////////////////////////////////////////////////
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment