Commit 6cc11438 authored by lch's avatar lch Committed by lchopn

fs: add file ext attr support for Darwin and FreeBSD and fix loopback fs

test

Change-Id: Idcb3ee99c1afdde410e41e3a67c29c3143f602c3
parent 68881a8c
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fs
import "syscall"
// ENOATTR indicates that an extended attribute was not present.
var ENOATTR = syscall.ENOATTR
......@@ -4,7 +4,7 @@
package fs
import "syscall"
import "golang.org/x/sys/unix"
// ENOATTR indicates that an extended attribute was not present.
var ENOATTR = syscall.ENODATA
const ENOATTR = unix.ENODATA
//go:build !linux
package fs
import "golang.org/x/sys/unix"
const ENOATTR = unix.ENOATTR
......@@ -12,6 +12,7 @@ import (
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/renameat"
"golang.org/x/sys/unix"
)
// LoopbackRoot holds the parameters for creating a new loopback
......@@ -441,6 +442,46 @@ func (n *LoopbackNode) Setattr(ctx context.Context, f FileHandle, in *fuse.SetAt
return OK
}
var _ = (NodeGetxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
sz, err := unix.Lgetxattr(n.path(), attr, dest)
return uint32(sz), ToErrno(err)
}
var _ = (NodeSetxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno {
err := unix.Lsetxattr(n.path(), attr, data, int(flags))
return ToErrno(err)
}
var _ = (NodeRemovexattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno {
err := unix.Lremovexattr(n.path(), attr)
return ToErrno(err)
}
var _ = (NodeCopyFileRanger)((*LoopbackNode)(nil))
func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle,
offIn uint64, out *Inode, fhOut FileHandle, offOut uint64,
len uint64, flags uint64) (uint32, syscall.Errno) {
lfIn, ok := fhIn.(*loopbackFile)
if !ok {
return 0, unix.ENOTSUP
}
lfOut, ok := fhOut.(*loopbackFile)
if !ok {
return 0, unix.ENOTSUP
}
signedOffIn := int64(offIn)
signedOffOut := int64(offOut)
doCopyFileRange(lfIn.fd, signedOffIn, lfOut.fd, signedOffOut, int(len), int(flags))
return 0, syscall.ENOSYS
}
// NewLoopbackRoot returns a root node for a loopback file system whose
// root is at the given root. This node implements all NodeXxxxer
// operations available.
......
......@@ -8,35 +8,10 @@
package fs
import (
"context"
"syscall"
"time"
)
var _ = (NodeGetxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
return 0, syscall.ENOSYS
}
var _ = (NodeSetxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno {
return syscall.ENOSYS
}
var _ = (NodeRemovexattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno {
return syscall.ENOSYS
}
var _ = (NodeListxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
return 0, syscall.ENOSYS
}
// timeToTimeval - Convert time.Time to syscall.Timeval
//
// Note: This does not use syscall.NsecToTimespec because
......@@ -49,11 +24,8 @@ func timeToTimeval(t *time.Time) syscall.Timeval {
return tv
}
var _ = (NodeCopyFileRanger)((*LoopbackNode)(nil))
func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle,
offIn uint64, out *Inode, fhOut FileHandle, offOut uint64,
len uint64, flags uint64) (uint32, syscall.Errno) {
func doCopyFileRange(fdIn int, offIn int64, fdOut int, offOut int64,
len int, flags int) (uint32, syscall.Errno) {
return 0, syscall.ENOSYS
}
......
......@@ -3,40 +3,86 @@ package fs
import (
"context"
"syscall"
"golang.org/x/sys/unix"
)
var _ = (NodeGetxattrer)((*LoopbackNode)(nil))
// FreeBSD has added copy_file_range(2) since FreeBSD 12. However,
// golang.org/x/sys/unix hasn't add corresponding syscall constant or
// wrap function. Here we define the syscall constant until sys/unix
// provides.
const sys_COPY_FILE_RANGE = 569
func (n *LoopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
return 0, syscall.ENOSYS
// TODO: replace the manual syscall when sys/unix provides CopyFileRange
// for FreeBSD
func doCopyFileRange(fdIn int, offIn int64, fdOut int, offOut int64,
len int, flags int) (uint32, syscall.Errno) {
count, _, errno := unix.Syscall6(sys_COPY_FILE_RANGE,
uintptr(fdIn), uintptr(offIn), uintptr(fdOut), uintptr(offOut),
uintptr(len), uintptr(flags),
)
return uint32(count), errno
}
var _ = (NodeSetxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno {
return syscall.ENOSYS
func intDev(dev uint32) uint64 {
return uint64(dev)
}
var _ = (NodeRemovexattrer)((*LoopbackNode)(nil))
// BSDs syscall use different convention of data buf retrieved
// through syscall `unix.Listxattr`.
// Ref: extattr_list_file(2)
func retrieveAttrName(buf []byte) [][]byte {
var attrList [][]byte
for p := 0; p < len(buf); {
attrNameLen := int(buf[p])
p++
attrName := buf[p : p+attrNameLen]
attrList = append(attrList, attrName)
p += attrNameLen
}
return attrList
}
func (n *LoopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno {
return syscall.ENOSYS
// Since FUSE on FreeBSD expect Linux flavor data format of
// listxattr, we should reconstruct it with data returned by
// FreeBSD's syscall. And here we have added a "user." prefix
// to put them under "user" namespace, which is readable and
// writable for normal user, for a userspace implemented FS.
func rebuildAttrBuf(attrList [][]byte) []byte {
ret := make([]byte, 0)
for _, attrName := range attrList {
nsAttrName := append([]byte("user."), attrName...)
ret = append(ret, nsAttrName...)
ret = append(ret, 0x0)
}
return ret
}
var _ = (NodeListxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
return 0, syscall.ENOSYS
}
var _ = (NodeCopyFileRanger)((*LoopbackNode)(nil))
func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle,
offIn uint64, out *Inode, fhOut FileHandle, offOut uint64,
len uint64, flags uint64) (uint32, syscall.Errno) {
return 0, syscall.ENOSYS
}
func intDev(dev uint32) uint64 {
return uint64(dev)
// In order to simulate same data format as Linux does,
// and the size of returned buf is required to match, we must
// call unix.Llistxattr twice.
sz, err := unix.Llistxattr(n.path(), nil)
if err != nil {
return uint32(sz), ToErrno(err)
}
rawBuf := make([]byte, sz)
sz, err = unix.Llistxattr(n.path(), rawBuf)
if err != nil {
return uint32(sz), ToErrno(err)
}
attrList := retrieveAttrName(rawBuf)
rebuiltBuf := rebuildAttrBuf(attrList)
sz = len(rebuiltBuf)
if len(dest) != 0 {
// When len(dest) is 0, which means that caller wants to get
// the size. If len(dest) is less than len(rebuiltBuf), but greater
// than 0 dest will be also filled with data from rebuiltBuf,
// but truncated to len(dest). copy() function will do the same.
// And this behaviour is same as FreeBSD's syscall extattr_list_file(2).
sz = copy(dest, rebuiltBuf)
}
return uint32(sz), ToErrno(err)
}
......@@ -8,57 +8,14 @@
package fs
import (
"context"
"syscall"
"golang.org/x/sys/unix"
)
var _ = (NodeGetxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) {
sz, err := unix.Lgetxattr(n.path(), attr, dest)
return uint32(sz), ToErrno(err)
}
var _ = (NodeSetxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno {
err := unix.Lsetxattr(n.path(), attr, data, int(flags))
return ToErrno(err)
}
var _ = (NodeRemovexattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno {
err := unix.Lremovexattr(n.path(), attr)
return ToErrno(err)
}
var _ = (NodeListxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
sz, err := unix.Llistxattr(n.path(), dest)
return uint32(sz), ToErrno(err)
}
var _ = (NodeCopyFileRanger)((*LoopbackNode)(nil))
func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle,
offIn uint64, out *Inode, fhOut FileHandle, offOut uint64,
len uint64, flags uint64) (uint32, syscall.Errno) {
lfIn, ok := fhIn.(*loopbackFile)
if !ok {
return 0, syscall.ENOTSUP
}
lfOut, ok := fhOut.(*loopbackFile)
if !ok {
return 0, syscall.ENOTSUP
}
signedOffIn := int64(offIn)
signedOffOut := int64(offOut)
count, err := unix.CopyFileRange(lfIn.fd, &signedOffIn, lfOut.fd, &signedOffOut, int(len), int(flags))
func doCopyFileRange(fdIn int, offIn int64, fdOut int, offOut int64,
len int, flags int) (uint32, syscall.Errno) {
count, err := unix.CopyFileRange(fdIn, &offIn, fdOut, &offOut, len, flags)
return uint32(count), ToErrno(err)
}
......
......@@ -45,63 +45,6 @@ func TestRenameNoOverwrite(t *testing.T) {
}
}
func TestXAttr(t *testing.T) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
tc.writeOrig("file", "", 0644)
buf := make([]byte, 1024)
attr := "user.xattrtest"
if _, err := syscall.Getxattr(tc.mntDir+"/file", attr, buf); err == syscall.ENOTSUP {
t.Skip("$TMP does not support xattrs. Rerun this test with a $TMPDIR override")
}
if _, err := syscall.Getxattr(tc.mntDir+"/file", attr, buf); err != syscall.ENODATA {
t.Fatalf("got %v want ENOATTR", err)
}
value := []byte("value")
if err := syscall.Setxattr(tc.mntDir+"/file", attr, value, 0); err != nil {
t.Fatalf("Setxattr: %v", err)
}
sz, err := syscall.Listxattr(tc.mntDir+"/file", nil)
if err != nil {
t.Fatalf("Listxattr: %v", err)
}
buf = make([]byte, sz)
if _, err := syscall.Listxattr(tc.mntDir+"/file", buf); err != nil {
t.Fatalf("Listxattr: %v", err)
} else {
attributes := bytes.Split(buf[:sz], []byte{0})
found := false
for _, a := range attributes {
if string(a) == attr {
found = true
break
}
}
if !found {
t.Fatalf("Listxattr: %q (not found: %q", buf[:sz], attr)
}
}
sz, err = syscall.Getxattr(tc.mntDir+"/file", attr, buf)
if err != nil {
t.Fatalf("Getxattr: %v", err)
}
if bytes.Compare(buf[:sz], value) != 0 {
t.Fatalf("Getxattr got %q want %q", buf[:sz], value)
}
if err := syscall.Removexattr(tc.mntDir+"/file", attr); err != nil {
t.Fatalf("Removexattr: %v", err)
}
if _, err := syscall.Getxattr(tc.mntDir+"/file", attr, buf); err != syscall.ENODATA {
t.Fatalf("got %v want ENOATTR", err)
}
}
// TestXAttrSymlink verifies that we did not forget to use Lgetxattr instead
// of Getxattr. This test is Linux-specific because it depends on the behavoir
// of the `security` namespace.
......
package fs
import (
"bytes"
"fmt"
"os"
"reflect"
"syscall"
......@@ -40,7 +42,11 @@ func TestRenameExchange(t *testing.T) {
}
if err := renameat.Renameat(f1, "file", f2, "file", renameat.RENAME_EXCHANGE); err != nil {
t.Errorf("rename EXCHANGE: %v", err)
if err == unix.ENOSYS {
t.Skip("rename EXCHANGE not support on current system")
} else {
t.Errorf("rename EXCHANGE: %v", err)
}
}
var after1, after2 unix.Stat_t
......@@ -82,3 +88,62 @@ func TestRenameExchange(t *testing.T) {
t.Errorf("got inode %d for %q want %d", ino2.StableAttr().Ino, "dir/file", after2.Ino)
}
}
func TestXAttr(t *testing.T) {
tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true})
tc.writeOrig("file", "", 0644)
buf := make([]byte, 1024)
attrNameSpace := "user"
attrName := "xattrtest"
attr := fmt.Sprintf("%s.%s", attrNameSpace, attrName)
if _, err := unix.Getxattr(tc.mntDir+"/file", attr, buf); err == unix.ENOTSUP {
t.Skip("$TMP does not support xattrs. Rerun this test with a $TMPDIR override")
}
if _, err := unix.Getxattr(tc.mntDir+"/file", attr, buf); err != ENOATTR {
t.Fatalf("got %v want ENOATTR", err)
}
value := []byte("value")
if err := unix.Setxattr(tc.mntDir+"/file", attr, value, 0); err != nil {
t.Fatalf("Setxattr: %v", err)
}
sz, err := unix.Listxattr(tc.mntDir+"/file", nil)
if err != nil {
t.Fatalf("Listxattr: %v", err)
}
buf = make([]byte, sz)
if _, err := unix.Listxattr(tc.mntDir+"/file", buf); err != nil {
t.Fatalf("Listxattr: %v", err)
} else {
attributes := retrieveAttrName(buf[:sz])
found := false
for _, a := range attributes {
if string(a) == attr || attrNameSpace+string(a) == attr {
found = true
break
}
}
if !found {
t.Fatalf("Listxattr: %q (not found: %q", attributes, attr)
}
}
sz, err = unix.Getxattr(tc.mntDir+"/file", attr, buf)
if err != nil {
t.Fatalf("Getxattr: %v", err)
}
if bytes.Compare(buf[:sz], value) != 0 {
t.Fatalf("Getxattr got %q want %q", buf[:sz], value)
}
if err := unix.Removexattr(tc.mntDir+"/file", attr); err != nil {
t.Fatalf("Removexattr: %v", err)
}
if _, err := unix.Getxattr(tc.mntDir+"/file", attr, buf); err != ENOATTR {
t.Fatalf("got %v want ENOATTR", err)
}
}
//go:build !freebsd
package fs
import (
"bytes"
"context"
"syscall"
"golang.org/x/sys/unix"
)
func retrieveAttrName(buf []byte) [][]byte {
attributes := bytes.Split(buf, []byte{0})
return attributes
}
var _ = (NodeListxattrer)((*LoopbackNode)(nil))
func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) {
sz, err := unix.Llistxattr(n.path(), dest)
return uint32(sz), ToErrno(err)
}
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