mirror of https://github.com/perkeep/perkeep.git
1535 lines
33 KiB
Go
1535 lines
33 KiB
Go
package fs
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"runtime"
|
|
"sync/atomic"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
import (
|
|
"camlistore.org/third_party/bazil.org/fuse"
|
|
"camlistore.org/third_party/bazil.org/fuse/fuseutil"
|
|
"camlistore.org/third_party/bazil.org/fuse/syscallx"
|
|
)
|
|
|
|
var fuseRun = flag.String("fuserun", "", "which fuse test to run. runs all if empty.")
|
|
|
|
// umount tries its best to unmount dir.
|
|
func umount(dir string) {
|
|
err := exec.Command("umount", dir).Run()
|
|
if err != nil && runtime.GOOS == "linux" {
|
|
exec.Command("fusermount", "-u", dir).Run()
|
|
}
|
|
}
|
|
|
|
func gather(ch chan []byte) []byte {
|
|
var buf []byte
|
|
for b := range ch {
|
|
buf = append(buf, b...)
|
|
}
|
|
return buf
|
|
}
|
|
|
|
// debug adapts fuse.Debug to match t.Log calling convention; due to
|
|
// varargs, we can't just assign tb.Log to fuse.Debug
|
|
func debug(tb testing.TB) func(msg interface{}) {
|
|
return func(msg interface{}) {
|
|
tb.Log(msg)
|
|
}
|
|
}
|
|
|
|
type badRootFS struct{}
|
|
|
|
func (badRootFS) Root() (Node, fuse.Error) {
|
|
// pick a really distinct error, to identify it later
|
|
return nil, fuse.Errno(syscall.ENAMETOOLONG)
|
|
}
|
|
|
|
func TestRootErr(t *testing.T) {
|
|
fuse.Debug = debug(t)
|
|
dir, err := ioutil.TempDir("", "fusetest")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
os.MkdirAll(dir, 0777)
|
|
|
|
c, err := fuse.Mount(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer umount(dir)
|
|
|
|
ch := make(chan error, 1)
|
|
go func() {
|
|
ch <- Serve(c, badRootFS{})
|
|
}()
|
|
|
|
select {
|
|
case err := <-ch:
|
|
// TODO this is not be a textual comparison, Serve hides
|
|
// details
|
|
if err.Error() != "cannot obtain root node: file name too long" {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatal("Serve did not return an error as expected, aborting")
|
|
}
|
|
}
|
|
|
|
type testStatFS struct{}
|
|
|
|
func (f testStatFS) Root() (Node, fuse.Error) {
|
|
return f, nil
|
|
}
|
|
|
|
func (f testStatFS) Attr() fuse.Attr {
|
|
return fuse.Attr{Inode: 1, Mode: os.ModeDir | 0777}
|
|
}
|
|
|
|
func (f testStatFS) Statfs(req *fuse.StatfsRequest, resp *fuse.StatfsResponse, int Intr) fuse.Error {
|
|
resp.Blocks = 42
|
|
resp.Files = 13
|
|
return nil
|
|
}
|
|
|
|
func TestStatfs(t *testing.T) {
|
|
fuse.Debug = debug(t)
|
|
dir, err := ioutil.TempDir("", "fusetest")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
os.MkdirAll(dir, 0777)
|
|
|
|
c, err := fuse.Mount(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer umount(dir)
|
|
|
|
go func() {
|
|
err := Serve(c, testStatFS{})
|
|
if err != nil {
|
|
fmt.Printf("SERVE ERROR: %v\n", err)
|
|
}
|
|
}()
|
|
|
|
waitForMount_inode1(t, dir)
|
|
|
|
{
|
|
var st syscall.Statfs_t
|
|
err = syscall.Statfs(dir, &st)
|
|
if err != nil {
|
|
t.Errorf("Statfs failed: %v", err)
|
|
}
|
|
t.Logf("Statfs got: %#v", st)
|
|
if g, e := st.Blocks, uint64(42); g != e {
|
|
t.Errorf("got Blocks = %q; want %q", g, e)
|
|
}
|
|
if g, e := st.Files, uint64(13); g != e {
|
|
t.Errorf("got Files = %d; want %d", g, e)
|
|
}
|
|
}
|
|
|
|
{
|
|
var st syscall.Statfs_t
|
|
f, err := os.Open(dir)
|
|
if err != nil {
|
|
t.Errorf("Open for fstatfs failed: %v", err)
|
|
}
|
|
defer f.Close()
|
|
err = syscall.Fstatfs(int(f.Fd()), &st)
|
|
if err != nil {
|
|
t.Errorf("Fstatfs failed: %v", err)
|
|
}
|
|
t.Logf("Fstatfs got: %#v", st)
|
|
if g, e := st.Blocks, uint64(42); g != e {
|
|
t.Errorf("got Blocks = %q; want %q", g, e)
|
|
}
|
|
if g, e := st.Files, uint64(13); g != e {
|
|
t.Errorf("got Files = %d; want %d", g, e)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func TestFuse(t *testing.T) {
|
|
fuse.Debug = debug(t)
|
|
dir, err := ioutil.TempDir("", "fusetest")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
os.MkdirAll(dir, 0777)
|
|
|
|
for _, tt := range fuseTests {
|
|
if *fuseRun == "" || *fuseRun == tt.name {
|
|
if st, ok := tt.node.(interface {
|
|
setup(*testing.T)
|
|
}); ok {
|
|
t.Logf("setting up %T", tt.node)
|
|
st.setup(t)
|
|
}
|
|
}
|
|
}
|
|
|
|
c, err := fuse.Mount(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer umount(dir)
|
|
|
|
go func() {
|
|
err := Serve(c, testFS{})
|
|
if err != nil {
|
|
fmt.Printf("SERVE ERROR: %v\n", err)
|
|
}
|
|
}()
|
|
|
|
waitForMount(t, dir)
|
|
|
|
for _, tt := range fuseTests {
|
|
if *fuseRun == "" || *fuseRun == tt.name {
|
|
t.Logf("running %T", tt.node)
|
|
tt.node.test(dir+"/"+tt.name, t)
|
|
}
|
|
}
|
|
}
|
|
|
|
func waitForMount(t *testing.T, dir string) {
|
|
// Filename to wait for in dir:
|
|
probeEntry := *fuseRun
|
|
if probeEntry == "" {
|
|
probeEntry = fuseTests[0].name
|
|
}
|
|
for tries := 0; tries < 100; tries++ {
|
|
_, err := os.Stat(dir + "/" + probeEntry)
|
|
if err == nil {
|
|
return
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
t.Fatalf("mount did not work")
|
|
}
|
|
|
|
// TODO maybe wait for fstype to change to FUse (verify it's not fuse to begin with)
|
|
func waitForMount_inode1(t *testing.T, dir string) {
|
|
for tries := 0; tries < 100; tries++ {
|
|
fi, err := os.Stat(dir)
|
|
if err == nil {
|
|
if si, ok := fi.Sys().(*syscall.Stat_t); ok {
|
|
if si.Ino == 1 {
|
|
return
|
|
}
|
|
t.Logf("waiting for root: %v", si.Ino)
|
|
}
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
t.Fatalf("mount did not work")
|
|
}
|
|
|
|
var fuseTests = []struct {
|
|
name string
|
|
node interface {
|
|
Node
|
|
test(string, *testing.T)
|
|
}
|
|
}{
|
|
{"root", &root{}},
|
|
{"readAll", readAll{}},
|
|
{"readAll1", &readAll1{}},
|
|
{"release", &release{}},
|
|
{"write", &write{}},
|
|
{"writeTruncateFlush", &writeTruncateFlush{}},
|
|
{"mkdir1", &mkdir1{}},
|
|
{"create1", &create1{}},
|
|
{"create3", &create3{}},
|
|
{"symlink1", &symlink1{}},
|
|
{"link1", &link1{}},
|
|
{"rename1", &rename1{}},
|
|
{"mknod1", &mknod1{}},
|
|
{"dataHandle", dataHandleTest{}},
|
|
{"interrupt", &interrupt{}},
|
|
{"truncate42", &truncate{toSize: 42}},
|
|
{"truncate0", &truncate{toSize: 0}},
|
|
{"ftruncate42", &ftruncate{toSize: 42}},
|
|
{"ftruncate0", &ftruncate{toSize: 0}},
|
|
{"truncateWithOpen", &truncateWithOpen{}},
|
|
{"readdir", &readdir{}},
|
|
{"chmod", &chmod{}},
|
|
{"open", &open{}},
|
|
{"fsyncDir", &fsyncDir{}},
|
|
{"getxattr", &getxattr{}},
|
|
{"getxattrTooSmall", &getxattrTooSmall{}},
|
|
{"getxattrSize", &getxattrSize{}},
|
|
{"listxattr", &listxattr{}},
|
|
{"listxattrTooSmall", &listxattrTooSmall{}},
|
|
{"listxattrSize", &listxattrSize{}},
|
|
{"setxattr", &setxattr{}},
|
|
{"removexattr", &removexattr{}},
|
|
}
|
|
|
|
// TO TEST:
|
|
// Lookup(*LookupRequest, *LookupResponse)
|
|
// Getattr(*GetattrRequest, *GetattrResponse)
|
|
// Attr with explicit inode
|
|
// Setattr(*SetattrRequest, *SetattrResponse)
|
|
// Access(*AccessRequest)
|
|
// Open(*OpenRequest, *OpenResponse)
|
|
// Write(*WriteRequest, *WriteResponse)
|
|
// Flush(*FlushRequest, *FlushResponse)
|
|
|
|
// Test Stat of root.
|
|
|
|
type root struct {
|
|
dir
|
|
}
|
|
|
|
func (f *root) test(path string, t *testing.T) {
|
|
fi, err := os.Stat(path + "/..")
|
|
if err != nil {
|
|
t.Fatalf("root getattr failed with %v", err)
|
|
}
|
|
mode := fi.Mode()
|
|
if (mode & os.ModeType) != os.ModeDir {
|
|
t.Errorf("root is not a directory: %#v", fi)
|
|
}
|
|
if mode.Perm() != 0555 {
|
|
t.Errorf("root has weird access mode: %v", mode.Perm())
|
|
}
|
|
switch stat := fi.Sys().(type) {
|
|
case *syscall.Stat_t:
|
|
if stat.Ino != 1 {
|
|
t.Errorf("root has wrong inode: %v", stat.Ino)
|
|
}
|
|
if stat.Nlink != 1 {
|
|
t.Errorf("root has wrong link count: %v", stat.Nlink)
|
|
}
|
|
if stat.Uid != 0 {
|
|
t.Errorf("root has wrong uid: %d", stat.Uid)
|
|
}
|
|
if stat.Gid != 0 {
|
|
t.Errorf("root has wrong gid: %d", stat.Gid)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test Read calling ReadAll.
|
|
|
|
type readAll struct{ file }
|
|
|
|
const hi = "hello, world"
|
|
|
|
func (readAll) ReadAll(intr Intr) ([]byte, fuse.Error) {
|
|
return []byte(hi), nil
|
|
}
|
|
|
|
func (readAll) test(path string, t *testing.T) {
|
|
data, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
t.Errorf("readAll: %v", err)
|
|
return
|
|
}
|
|
if string(data) != hi {
|
|
t.Errorf("readAll = %q, want %q", data, hi)
|
|
}
|
|
}
|
|
|
|
// Test Read.
|
|
|
|
type readAll1 struct{ file }
|
|
|
|
func (readAll1) Read(req *fuse.ReadRequest, resp *fuse.ReadResponse, intr Intr) fuse.Error {
|
|
fuseutil.HandleRead(req, resp, []byte(hi))
|
|
return nil
|
|
}
|
|
|
|
func (readAll1) test(path string, t *testing.T) {
|
|
readAll{}.test(path, t)
|
|
}
|
|
|
|
// Test Write calling basic Write, with an fsync thrown in too.
|
|
|
|
type write struct {
|
|
file
|
|
seen struct {
|
|
data chan []byte
|
|
fsync chan bool
|
|
}
|
|
}
|
|
|
|
func (w *write) Write(req *fuse.WriteRequest, resp *fuse.WriteResponse, intr Intr) fuse.Error {
|
|
w.seen.data <- req.Data
|
|
resp.Size = len(req.Data)
|
|
return nil
|
|
}
|
|
|
|
func (w *write) Fsync(r *fuse.FsyncRequest, intr Intr) fuse.Error {
|
|
w.seen.fsync <- true
|
|
return nil
|
|
}
|
|
|
|
func (w *write) Release(r *fuse.ReleaseRequest, intr Intr) fuse.Error {
|
|
close(w.seen.data)
|
|
return nil
|
|
}
|
|
|
|
func (w *write) setup(t *testing.T) {
|
|
w.seen.data = make(chan []byte, 10)
|
|
w.seen.fsync = make(chan bool, 1)
|
|
}
|
|
|
|
func (w *write) test(path string, t *testing.T) {
|
|
log.Printf("pre-write Create")
|
|
f, err := os.Create(path)
|
|
if err != nil {
|
|
t.Fatalf("Create: %v", err)
|
|
}
|
|
log.Printf("pre-write Write")
|
|
n, err := f.Write([]byte(hi))
|
|
if err != nil {
|
|
t.Fatalf("Write: %v", err)
|
|
}
|
|
if n != len(hi) {
|
|
t.Fatalf("short write; n=%d; hi=%d", n, len(hi))
|
|
}
|
|
|
|
err = syscall.Fsync(int(f.Fd()))
|
|
if err != nil {
|
|
t.Fatalf("Fsync = %v", err)
|
|
}
|
|
if !<-w.seen.fsync {
|
|
t.Errorf("never received expected fsync call")
|
|
}
|
|
|
|
log.Printf("pre-write Close")
|
|
err = f.Close()
|
|
if err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
log.Printf("post-write Close")
|
|
if got := string(gather(w.seen.data)); got != hi {
|
|
t.Errorf("write = %q, want %q", got, hi)
|
|
}
|
|
}
|
|
|
|
// Test Write calling Setattr+Write+Flush.
|
|
|
|
type writeTruncateFlush struct {
|
|
file
|
|
seen struct {
|
|
data chan []byte
|
|
setattr chan bool
|
|
flush chan bool
|
|
}
|
|
}
|
|
|
|
func (w *writeTruncateFlush) Setattr(req *fuse.SetattrRequest, resp *fuse.SetattrResponse, intr Intr) fuse.Error {
|
|
w.seen.setattr <- true
|
|
return nil
|
|
}
|
|
|
|
func (w *writeTruncateFlush) Flush(req *fuse.FlushRequest, intr Intr) fuse.Error {
|
|
w.seen.flush <- true
|
|
return nil
|
|
}
|
|
|
|
func (w *writeTruncateFlush) Write(req *fuse.WriteRequest, resp *fuse.WriteResponse, intr Intr) fuse.Error {
|
|
w.seen.data <- req.Data
|
|
resp.Size = len(req.Data)
|
|
return nil
|
|
}
|
|
|
|
func (w *writeTruncateFlush) Release(r *fuse.ReleaseRequest, intr Intr) fuse.Error {
|
|
close(w.seen.data)
|
|
return nil
|
|
}
|
|
|
|
func (w *writeTruncateFlush) setup(t *testing.T) {
|
|
w.seen.data = make(chan []byte, 100)
|
|
w.seen.setattr = make(chan bool, 1)
|
|
w.seen.flush = make(chan bool, 1)
|
|
}
|
|
|
|
func (w *writeTruncateFlush) test(path string, t *testing.T) {
|
|
err := ioutil.WriteFile(path, []byte(hi), 0666)
|
|
if err != nil {
|
|
t.Errorf("WriteFile: %v", err)
|
|
return
|
|
}
|
|
if !<-w.seen.setattr {
|
|
t.Errorf("writeTruncateFlush expected Setattr")
|
|
}
|
|
if !<-w.seen.flush {
|
|
t.Errorf("writeTruncateFlush expected Setattr")
|
|
}
|
|
if got := string(gather(w.seen.data)); got != hi {
|
|
t.Errorf("writeTruncateFlush = %q, want %q", got, hi)
|
|
}
|
|
}
|
|
|
|
// Test Mkdir.
|
|
|
|
type mkdirSeen struct {
|
|
name string
|
|
mode os.FileMode
|
|
}
|
|
|
|
func (s mkdirSeen) String() string {
|
|
return fmt.Sprintf("%T{name:%q mod:%v}", s, s.name, s.mode)
|
|
}
|
|
|
|
type mkdir1 struct {
|
|
dir
|
|
seen chan mkdirSeen
|
|
}
|
|
|
|
func (f *mkdir1) Mkdir(req *fuse.MkdirRequest, intr Intr) (Node, fuse.Error) {
|
|
f.seen <- mkdirSeen{
|
|
name: req.Name,
|
|
mode: req.Mode,
|
|
}
|
|
return &mkdir1{}, nil
|
|
}
|
|
|
|
func (f *mkdir1) setup(t *testing.T) {
|
|
f.seen = make(chan mkdirSeen, 1)
|
|
}
|
|
|
|
func (f *mkdir1) test(path string, t *testing.T) {
|
|
// uniform umask needed to make os.Mkdir's mode into something
|
|
// reproducible
|
|
defer syscall.Umask(syscall.Umask(0022))
|
|
err := os.Mkdir(path+"/foo", 0771)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
want := mkdirSeen{name: "foo", mode: os.ModeDir | 0751}
|
|
if g, e := <-f.seen, want; g != e {
|
|
t.Errorf("mkdir saw %v, want %v", g, e)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Test Create (and fsync)
|
|
|
|
type create1 struct {
|
|
dir
|
|
f write
|
|
}
|
|
|
|
func (f *create1) Create(req *fuse.CreateRequest, resp *fuse.CreateResponse, intr Intr) (Node, Handle, fuse.Error) {
|
|
if req.Name != "foo" {
|
|
log.Printf("ERROR create1.Create unexpected name: %q\n", req.Name)
|
|
return nil, nil, fuse.EPERM
|
|
}
|
|
flags := req.Flags
|
|
// OS X does not pass O_TRUNC here, Linux does; as this is a
|
|
// Create, that's acceptable
|
|
flags &^= fuse.OpenFlags(os.O_TRUNC)
|
|
if g, e := flags, fuse.OpenFlags(os.O_CREATE|os.O_RDWR); g != e {
|
|
log.Printf("ERROR create1.Create unexpected flags: %v != %v\n", g, e)
|
|
return nil, nil, fuse.EPERM
|
|
}
|
|
if g, e := req.Mode, os.FileMode(0644); g != e {
|
|
log.Printf("ERROR create1.Create unexpected mode: %v != %v\n", g, e)
|
|
return nil, nil, fuse.EPERM
|
|
}
|
|
return &f.f, &f.f, nil
|
|
}
|
|
|
|
func (f *create1) setup(t *testing.T) {
|
|
f.f.setup(t)
|
|
}
|
|
|
|
func (f *create1) test(path string, t *testing.T) {
|
|
// uniform umask needed to make os.Create's 0666 into something
|
|
// reproducible
|
|
defer syscall.Umask(syscall.Umask(0022))
|
|
ff, err := os.Create(path + "/foo")
|
|
if err != nil {
|
|
t.Errorf("create1 WriteFile: %v", err)
|
|
return
|
|
}
|
|
|
|
err = syscall.Fsync(int(ff.Fd()))
|
|
if err != nil {
|
|
t.Fatalf("Fsync = %v", err)
|
|
}
|
|
|
|
if !<-f.f.seen.fsync {
|
|
t.Errorf("never received expected fsync call")
|
|
}
|
|
|
|
ff.Close()
|
|
}
|
|
|
|
// Test Create + WriteAll + Remove
|
|
|
|
type create3 struct {
|
|
dir
|
|
f write
|
|
fooExists bool
|
|
}
|
|
|
|
func (f *create3) Create(req *fuse.CreateRequest, resp *fuse.CreateResponse, intr Intr) (Node, Handle, fuse.Error) {
|
|
if req.Name != "foo" {
|
|
log.Printf("ERROR create3.Create unexpected name: %q\n", req.Name)
|
|
return nil, nil, fuse.EPERM
|
|
}
|
|
return &f.f, &f.f, nil
|
|
}
|
|
|
|
func (f *create3) Lookup(name string, intr Intr) (Node, fuse.Error) {
|
|
if f.fooExists && name == "foo" {
|
|
return file{}, nil
|
|
}
|
|
return nil, fuse.ENOENT
|
|
}
|
|
|
|
func (f *create3) Remove(r *fuse.RemoveRequest, intr Intr) fuse.Error {
|
|
if f.fooExists && r.Name == "foo" && !r.Dir {
|
|
f.fooExists = false
|
|
return nil
|
|
}
|
|
return fuse.ENOENT
|
|
}
|
|
|
|
func (f *create3) setup(t *testing.T) {
|
|
f.f.setup(t)
|
|
}
|
|
|
|
func (f *create3) test(path string, t *testing.T) {
|
|
err := ioutil.WriteFile(path+"/foo", []byte(hi), 0666)
|
|
if err != nil {
|
|
t.Fatalf("create3 WriteFile: %v", err)
|
|
}
|
|
if got := string(gather(f.f.seen.data)); got != hi {
|
|
t.Fatalf("create3 write = %q, want %q", got, hi)
|
|
}
|
|
|
|
f.fooExists = true
|
|
log.Printf("pre-Remove")
|
|
err = os.Remove(path + "/foo")
|
|
if err != nil {
|
|
t.Fatalf("Remove: %v", err)
|
|
}
|
|
err = os.Remove(path + "/foo")
|
|
if err == nil {
|
|
t.Fatalf("second Remove = nil; want some error")
|
|
}
|
|
}
|
|
|
|
// Test symlink + readlink
|
|
|
|
type symlink1 struct {
|
|
dir
|
|
seen struct {
|
|
req chan *fuse.SymlinkRequest
|
|
}
|
|
}
|
|
|
|
func (f *symlink1) Symlink(req *fuse.SymlinkRequest, intr Intr) (Node, fuse.Error) {
|
|
f.seen.req <- req
|
|
return symlink{target: req.Target}, nil
|
|
}
|
|
|
|
func (f *symlink1) setup(t *testing.T) {
|
|
f.seen.req = make(chan *fuse.SymlinkRequest, 1)
|
|
}
|
|
|
|
func (f *symlink1) test(path string, t *testing.T) {
|
|
const target = "/some-target"
|
|
|
|
err := os.Symlink(target, path+"/symlink.file")
|
|
if err != nil {
|
|
t.Errorf("os.Symlink: %v", err)
|
|
return
|
|
}
|
|
|
|
req := <-f.seen.req
|
|
|
|
if req.NewName != "symlink.file" {
|
|
t.Errorf("symlink newName = %q; want %q", req.NewName, "symlink.file")
|
|
}
|
|
if req.Target != target {
|
|
t.Errorf("symlink target = %q; want %q", req.Target, target)
|
|
}
|
|
|
|
gotName, err := os.Readlink(path + "/symlink.file")
|
|
if err != nil {
|
|
t.Errorf("os.Readlink: %v", err)
|
|
return
|
|
}
|
|
if gotName != target {
|
|
t.Errorf("os.Readlink = %q; want %q", gotName, target)
|
|
}
|
|
}
|
|
|
|
// Test link
|
|
|
|
type link1 struct {
|
|
dir
|
|
seen struct {
|
|
newName chan string
|
|
}
|
|
}
|
|
|
|
func (f *link1) Lookup(name string, intr Intr) (Node, fuse.Error) {
|
|
if name == "old" {
|
|
return file{}, nil
|
|
}
|
|
return nil, fuse.ENOENT
|
|
}
|
|
|
|
func (f *link1) Link(r *fuse.LinkRequest, old Node, intr Intr) (Node, fuse.Error) {
|
|
f.seen.newName <- r.NewName
|
|
return file{}, nil
|
|
}
|
|
|
|
func (f *link1) setup(t *testing.T) {
|
|
f.seen.newName = make(chan string, 1)
|
|
}
|
|
|
|
func (f *link1) test(path string, t *testing.T) {
|
|
err := os.Link(path+"/old", path+"/new")
|
|
if err != nil {
|
|
t.Fatalf("Link: %v", err)
|
|
}
|
|
if got := <-f.seen.newName; got != "new" {
|
|
t.Fatalf("saw Link for newName %q; want %q", got, "new")
|
|
}
|
|
}
|
|
|
|
// Test Rename
|
|
|
|
type rename1 struct {
|
|
dir
|
|
renames int32
|
|
}
|
|
|
|
func (f *rename1) Lookup(name string, intr Intr) (Node, fuse.Error) {
|
|
if name == "old" {
|
|
return file{}, nil
|
|
}
|
|
return nil, fuse.ENOENT
|
|
}
|
|
|
|
func (f *rename1) Rename(r *fuse.RenameRequest, newDir Node, intr Intr) fuse.Error {
|
|
if r.OldName == "old" && r.NewName == "new" && newDir == f {
|
|
atomic.AddInt32(&f.renames, 1)
|
|
return nil
|
|
}
|
|
return fuse.EIO
|
|
}
|
|
|
|
func (f *rename1) test(path string, t *testing.T) {
|
|
err := os.Rename(path+"/old", path+"/new")
|
|
if err != nil {
|
|
t.Fatalf("Rename: %v", err)
|
|
}
|
|
if atomic.LoadInt32(&f.renames) != 1 {
|
|
t.Fatalf("expected rename didn't happen")
|
|
}
|
|
err = os.Rename(path+"/old2", path+"/new2")
|
|
if err == nil {
|
|
t.Fatal("expected error on second Rename; got nil")
|
|
}
|
|
}
|
|
|
|
// Test Release.
|
|
|
|
type release struct {
|
|
file
|
|
seen struct {
|
|
did chan bool
|
|
}
|
|
}
|
|
|
|
func (r *release) Release(*fuse.ReleaseRequest, Intr) fuse.Error {
|
|
r.seen.did <- true
|
|
return nil
|
|
}
|
|
|
|
func (r *release) setup(t *testing.T) {
|
|
r.seen.did = make(chan bool, 1)
|
|
}
|
|
|
|
func (r *release) test(path string, t *testing.T) {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
f.Close()
|
|
time.Sleep(1 * time.Second)
|
|
if !<-r.seen.did {
|
|
t.Error("Close did not Release")
|
|
}
|
|
}
|
|
|
|
// Test mknod
|
|
|
|
type mknod1 struct {
|
|
dir
|
|
seen struct {
|
|
gotr chan *fuse.MknodRequest
|
|
}
|
|
}
|
|
|
|
func (f *mknod1) Mknod(r *fuse.MknodRequest, intr Intr) (Node, fuse.Error) {
|
|
f.seen.gotr <- r
|
|
return fifo{}, nil
|
|
}
|
|
|
|
func (f *mknod1) setup(t *testing.T) {
|
|
f.seen.gotr = make(chan *fuse.MknodRequest, 1)
|
|
}
|
|
|
|
func (f *mknod1) test(path string, t *testing.T) {
|
|
if os.Getuid() != 0 {
|
|
t.Logf("skipping unless root")
|
|
return
|
|
}
|
|
defer syscall.Umask(syscall.Umask(0))
|
|
err := syscall.Mknod(path+"/node", syscall.S_IFIFO|0666, 123)
|
|
if err != nil {
|
|
t.Fatalf("Mknod: %v", err)
|
|
}
|
|
gotr := <-f.seen.gotr
|
|
if gotr == nil {
|
|
t.Fatalf("no recorded MknodRequest")
|
|
}
|
|
if g, e := gotr.Name, "node"; g != e {
|
|
t.Errorf("got Name = %q; want %q", g, e)
|
|
}
|
|
if g, e := gotr.Rdev, uint32(123); g != e {
|
|
if runtime.GOOS == "linux" {
|
|
// Linux fuse doesn't echo back the rdev if the node
|
|
// isn't a device (we're using a FIFO here, as that
|
|
// bit is portable.)
|
|
} else {
|
|
t.Errorf("got Rdev = %v; want %v", g, e)
|
|
}
|
|
}
|
|
if g, e := gotr.Mode, os.FileMode(os.ModeNamedPipe|0666); g != e {
|
|
t.Errorf("got Mode = %v; want %v", g, e)
|
|
}
|
|
t.Logf("Got request: %#v", gotr)
|
|
}
|
|
|
|
type file struct{}
|
|
type dir struct{}
|
|
type fifo struct{}
|
|
type symlink struct {
|
|
target string
|
|
}
|
|
|
|
func (f file) Attr() fuse.Attr { return fuse.Attr{Mode: 0666} }
|
|
func (f dir) Attr() fuse.Attr { return fuse.Attr{Mode: os.ModeDir | 0777} }
|
|
func (f fifo) Attr() fuse.Attr { return fuse.Attr{Mode: os.ModeNamedPipe | 0666} }
|
|
func (f symlink) Attr() fuse.Attr { return fuse.Attr{Mode: os.ModeSymlink | 0666} }
|
|
|
|
func (f symlink) Readlink(*fuse.ReadlinkRequest, Intr) (string, fuse.Error) {
|
|
return f.target, nil
|
|
}
|
|
|
|
type testFS struct{}
|
|
|
|
func (testFS) Root() (Node, fuse.Error) {
|
|
return testFS{}, nil
|
|
}
|
|
|
|
func (testFS) Attr() fuse.Attr {
|
|
return fuse.Attr{Inode: 1, Mode: os.ModeDir | 0555}
|
|
}
|
|
|
|
func (testFS) Lookup(name string, intr Intr) (Node, fuse.Error) {
|
|
for _, tt := range fuseTests {
|
|
if tt.name == name {
|
|
return tt.node, nil
|
|
}
|
|
}
|
|
return nil, fuse.ENOENT
|
|
}
|
|
|
|
func (testFS) ReadDir(intr Intr) ([]fuse.Dirent, fuse.Error) {
|
|
var dirs []fuse.Dirent
|
|
for _, tt := range fuseTests {
|
|
if *fuseRun == "" || *fuseRun == tt.name {
|
|
log.Printf("Readdir; adding %q", tt.name)
|
|
dirs = append(dirs, fuse.Dirent{Name: tt.name})
|
|
}
|
|
}
|
|
return dirs, nil
|
|
}
|
|
|
|
// Test Read served with DataHandle.
|
|
|
|
type dataHandleTest struct {
|
|
file
|
|
}
|
|
|
|
func (dataHandleTest) Open(*fuse.OpenRequest, *fuse.OpenResponse, Intr) (Handle, fuse.Error) {
|
|
return DataHandle([]byte(hi)), nil
|
|
}
|
|
|
|
func (dataHandleTest) test(path string, t *testing.T) {
|
|
data, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
t.Errorf("readAll: %v", err)
|
|
return
|
|
}
|
|
if string(data) != hi {
|
|
t.Errorf("readAll = %q, want %q", data, hi)
|
|
}
|
|
}
|
|
|
|
// Test interrupt
|
|
|
|
type interrupt struct {
|
|
file
|
|
|
|
// closed to signal we have a read hanging
|
|
hanging chan struct{}
|
|
}
|
|
|
|
func (it *interrupt) Read(req *fuse.ReadRequest, resp *fuse.ReadResponse, intr Intr) fuse.Error {
|
|
if it.hanging == nil {
|
|
fuseutil.HandleRead(req, resp, []byte("don't read this outside of the test"))
|
|
return nil
|
|
}
|
|
|
|
close(it.hanging)
|
|
<-intr
|
|
return fuse.EINTR
|
|
}
|
|
|
|
func (it *interrupt) setup(t *testing.T) {
|
|
it.hanging = make(chan struct{})
|
|
}
|
|
|
|
func (it *interrupt) test(path string, t *testing.T) {
|
|
|
|
// start a subprocess that can hang until signaled
|
|
cmd := exec.Command("cat", path)
|
|
|
|
err := cmd.Start()
|
|
if err != nil {
|
|
t.Errorf("interrupt: cannot start cat: %v", err)
|
|
return
|
|
}
|
|
|
|
// try to clean up if child is still alive when returning
|
|
defer cmd.Process.Kill()
|
|
|
|
// wait till we're sure it's hanging in read
|
|
<-it.hanging
|
|
|
|
err = cmd.Process.Signal(os.Interrupt)
|
|
if err != nil {
|
|
t.Errorf("interrupt: cannot interrupt cat: %v", err)
|
|
return
|
|
}
|
|
|
|
p, err := cmd.Process.Wait()
|
|
if err != nil {
|
|
t.Errorf("interrupt: cat bork: %v", err)
|
|
return
|
|
}
|
|
switch ws := p.Sys().(type) {
|
|
case syscall.WaitStatus:
|
|
if ws.CoreDump() {
|
|
t.Errorf("interrupt: didn't expect cat to dump core: %v", ws)
|
|
}
|
|
|
|
if ws.Exited() {
|
|
t.Errorf("interrupt: didn't expect cat to exit normally: %v", ws)
|
|
}
|
|
|
|
if !ws.Signaled() {
|
|
t.Errorf("interrupt: expected cat to get a signal: %v", ws)
|
|
} else {
|
|
if ws.Signal() != os.Interrupt {
|
|
t.Errorf("interrupt: cat got wrong signal: %v", ws)
|
|
}
|
|
}
|
|
default:
|
|
t.Logf("interrupt: this platform has no test coverage")
|
|
}
|
|
}
|
|
|
|
// Test truncate
|
|
|
|
type truncate struct {
|
|
toSize int64
|
|
|
|
file
|
|
seen struct {
|
|
gotr chan *fuse.SetattrRequest
|
|
}
|
|
}
|
|
|
|
// present purely to trigger bugs in WriteAll logic
|
|
func (*truncate) WriteAll(data []byte, intr Intr) fuse.Error {
|
|
return nil
|
|
}
|
|
|
|
func (f *truncate) Setattr(req *fuse.SetattrRequest, resp *fuse.SetattrResponse, intr Intr) fuse.Error {
|
|
f.seen.gotr <- req
|
|
return nil
|
|
}
|
|
|
|
func (f *truncate) setup(t *testing.T) {
|
|
f.seen.gotr = make(chan *fuse.SetattrRequest, 1)
|
|
}
|
|
|
|
func (f *truncate) test(path string, t *testing.T) {
|
|
err := os.Truncate(path, f.toSize)
|
|
if err != nil {
|
|
t.Fatalf("Truncate: %v", err)
|
|
}
|
|
gotr := <-f.seen.gotr
|
|
if gotr == nil {
|
|
t.Fatalf("no recorded SetattrRequest")
|
|
}
|
|
if g, e := gotr.Size, uint64(f.toSize); g != e {
|
|
t.Errorf("got Size = %q; want %q", g, e)
|
|
}
|
|
if g, e := gotr.Valid&^fuse.SetattrLockOwner, fuse.SetattrSize; g != e {
|
|
t.Errorf("got Valid = %q; want %q", g, e)
|
|
}
|
|
t.Logf("Got request: %#v", gotr)
|
|
}
|
|
|
|
// Test ftruncate
|
|
|
|
type ftruncate struct {
|
|
toSize int64
|
|
|
|
file
|
|
seen struct {
|
|
gotr chan *fuse.SetattrRequest
|
|
}
|
|
}
|
|
|
|
// present purely to trigger bugs in WriteAll logic
|
|
func (*ftruncate) WriteAll(data []byte, intr Intr) fuse.Error {
|
|
return nil
|
|
}
|
|
|
|
func (f *ftruncate) Setattr(req *fuse.SetattrRequest, resp *fuse.SetattrResponse, intr Intr) fuse.Error {
|
|
f.seen.gotr <- req
|
|
return nil
|
|
}
|
|
|
|
func (f *ftruncate) setup(t *testing.T) {
|
|
f.seen.gotr = make(chan *fuse.SetattrRequest, 1)
|
|
}
|
|
|
|
func (f *ftruncate) test(path string, t *testing.T) {
|
|
{
|
|
fil, err := os.OpenFile(path, os.O_WRONLY, 0666)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
defer fil.Close()
|
|
|
|
err = fil.Truncate(f.toSize)
|
|
if err != nil {
|
|
t.Fatalf("Ftruncate: %v", err)
|
|
}
|
|
}
|
|
gotr := <-f.seen.gotr
|
|
if gotr == nil {
|
|
t.Fatalf("no recorded SetattrRequest")
|
|
}
|
|
if g, e := gotr.Size, uint64(f.toSize); g != e {
|
|
t.Errorf("got Size = %q; want %q", g, e)
|
|
}
|
|
if g, e := gotr.Valid&^fuse.SetattrLockOwner, fuse.SetattrHandle|fuse.SetattrSize; g != e {
|
|
t.Errorf("got Valid = %q; want %q", g, e)
|
|
}
|
|
t.Logf("Got request: %#v", gotr)
|
|
}
|
|
|
|
// Test opening existing file truncates
|
|
|
|
type truncateWithOpen struct {
|
|
file
|
|
seen struct {
|
|
gotr chan *fuse.SetattrRequest
|
|
}
|
|
}
|
|
|
|
// present purely to trigger bugs in WriteAll logic
|
|
func (*truncateWithOpen) WriteAll(data []byte, intr Intr) fuse.Error {
|
|
return nil
|
|
}
|
|
|
|
func (f *truncateWithOpen) Setattr(req *fuse.SetattrRequest, resp *fuse.SetattrResponse, intr Intr) fuse.Error {
|
|
f.seen.gotr <- req
|
|
return nil
|
|
}
|
|
|
|
func (f *truncateWithOpen) setup(t *testing.T) {
|
|
f.seen.gotr = make(chan *fuse.SetattrRequest, 1)
|
|
}
|
|
|
|
func (f *truncateWithOpen) test(path string, t *testing.T) {
|
|
fil, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0666)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
fil.Close()
|
|
|
|
gotr := <-f.seen.gotr
|
|
if gotr == nil {
|
|
t.Fatalf("no recorded SetattrRequest")
|
|
}
|
|
if g, e := gotr.Size, uint64(0); g != e {
|
|
t.Errorf("got Size = %q; want %q", g, e)
|
|
}
|
|
// osxfuse sets SetattrHandle here, linux does not
|
|
if g, e := gotr.Valid&^(fuse.SetattrLockOwner|fuse.SetattrHandle), fuse.SetattrSize; g != e {
|
|
t.Errorf("got Valid = %q; want %q", g, e)
|
|
}
|
|
t.Logf("Got request: %#v", gotr)
|
|
}
|
|
|
|
// Test readdir
|
|
|
|
type readdir struct {
|
|
dir
|
|
}
|
|
|
|
func (d *readdir) ReadDir(intr Intr) ([]fuse.Dirent, fuse.Error) {
|
|
return []fuse.Dirent{
|
|
{Name: "one", Inode: 11, Type: fuse.DT_Dir},
|
|
{Name: "three", Inode: 13},
|
|
{Name: "two", Inode: 12, Type: fuse.DT_File},
|
|
}, nil
|
|
}
|
|
|
|
func (f *readdir) test(path string, t *testing.T) {
|
|
fil, err := os.Open(path)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
defer fil.Close()
|
|
|
|
// go Readdir is just Readdirnames + Lstat, there's no point in
|
|
// testing that here; we have no consumption API for the real
|
|
// dirent data
|
|
names, err := fil.Readdirnames(100)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
|
|
t.Logf("Got readdir: %q", names)
|
|
|
|
if len(names) != 3 ||
|
|
names[0] != "one" ||
|
|
names[1] != "three" ||
|
|
names[2] != "two" {
|
|
t.Errorf(`expected 3 entries of "one", "three", "two", got: %q`, names)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Test Chmod.
|
|
|
|
type chmodSeen struct {
|
|
mode os.FileMode
|
|
}
|
|
|
|
type chmod struct {
|
|
file
|
|
seen chan chmodSeen
|
|
}
|
|
|
|
func (f *chmod) Setattr(req *fuse.SetattrRequest, resp *fuse.SetattrResponse, intr Intr) fuse.Error {
|
|
if !req.Valid.Mode() {
|
|
log.Printf("setattr not a chmod: %v", req.Valid)
|
|
return fuse.EIO
|
|
}
|
|
f.seen <- chmodSeen{mode: req.Mode}
|
|
return nil
|
|
}
|
|
|
|
func (f *chmod) setup(t *testing.T) {
|
|
f.seen = make(chan chmodSeen, 1)
|
|
}
|
|
|
|
func (f *chmod) test(path string, t *testing.T) {
|
|
err := os.Chmod(path, 0764)
|
|
if err != nil {
|
|
t.Errorf("chmod: %v", err)
|
|
return
|
|
}
|
|
close(f.seen)
|
|
got := <-f.seen
|
|
if g, e := got.mode, os.FileMode(0764); g != e {
|
|
t.Errorf("wrong mode: %v != %v", g, e)
|
|
}
|
|
}
|
|
|
|
// Test open
|
|
|
|
type openSeen struct {
|
|
dir bool
|
|
flags fuse.OpenFlags
|
|
}
|
|
|
|
func (s openSeen) String() string {
|
|
return fmt.Sprintf("%T{dir:%v flags:%v}", s, s.dir, s.flags)
|
|
}
|
|
|
|
type open struct {
|
|
file
|
|
seen chan openSeen
|
|
}
|
|
|
|
func (f *open) Open(req *fuse.OpenRequest, resp *fuse.OpenResponse, intr Intr) (Handle, fuse.Error) {
|
|
f.seen <- openSeen{dir: req.Dir, flags: req.Flags}
|
|
// pick a really distinct error, to identify it later
|
|
return nil, fuse.Errno(syscall.ENAMETOOLONG)
|
|
|
|
}
|
|
|
|
func (f *open) setup(t *testing.T) {
|
|
f.seen = make(chan openSeen, 1)
|
|
}
|
|
|
|
func (f *open) test(path string, t *testing.T) {
|
|
// node: mode only matters with O_CREATE
|
|
fil, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0)
|
|
if err == nil {
|
|
t.Error("Open err == nil, expected ENAMETOOLONG")
|
|
fil.Close()
|
|
return
|
|
}
|
|
|
|
switch err2 := err.(type) {
|
|
case *os.PathError:
|
|
if err2.Err == syscall.ENAMETOOLONG {
|
|
break
|
|
}
|
|
t.Errorf("unexpected inner error: %#v", err2)
|
|
default:
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
|
|
want := openSeen{dir: false, flags: fuse.OpenFlags(os.O_WRONLY | os.O_APPEND)}
|
|
if runtime.GOOS == "darwin" {
|
|
// osxfuse does not let O_APPEND through at all
|
|
//
|
|
// https://code.google.com/p/macfuse/issues/detail?id=233
|
|
// https://code.google.com/p/macfuse/issues/detail?id=132
|
|
// https://code.google.com/p/macfuse/issues/detail?id=133
|
|
want.flags &^= fuse.OpenFlags(os.O_APPEND)
|
|
}
|
|
if g, e := <-f.seen, want; g != e {
|
|
t.Errorf("open saw %v, want %v", g, e)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Test Fsync on a dir
|
|
|
|
type fsyncSeen struct {
|
|
flags uint32
|
|
dir bool
|
|
}
|
|
|
|
type fsyncDir struct {
|
|
dir
|
|
seen chan fsyncSeen
|
|
}
|
|
|
|
func (f *fsyncDir) Fsync(r *fuse.FsyncRequest, intr Intr) fuse.Error {
|
|
f.seen <- fsyncSeen{flags: r.Flags, dir: r.Dir}
|
|
return nil
|
|
}
|
|
|
|
func (f *fsyncDir) setup(t *testing.T) {
|
|
f.seen = make(chan fsyncSeen, 1)
|
|
}
|
|
|
|
func (f *fsyncDir) test(path string, t *testing.T) {
|
|
fil, err := os.Open(path)
|
|
if err != nil {
|
|
t.Errorf("fsyncDir open: %v", err)
|
|
return
|
|
}
|
|
defer fil.Close()
|
|
err = fil.Sync()
|
|
if err != nil {
|
|
t.Errorf("fsyncDir sync: %v", err)
|
|
return
|
|
}
|
|
|
|
close(f.seen)
|
|
got := <-f.seen
|
|
want := uint32(0)
|
|
if runtime.GOOS == "darwin" {
|
|
// TODO document the meaning of these flags, figure out why
|
|
// they differ
|
|
want = 1
|
|
}
|
|
if g, e := got.flags, want; g != e {
|
|
t.Errorf("fsyncDir bad flags: %v != %v", g, e)
|
|
}
|
|
if g, e := got.dir, true; g != e {
|
|
t.Errorf("fsyncDir bad dir: %v != %v", g, e)
|
|
}
|
|
}
|
|
|
|
// Test Getxattr
|
|
|
|
type getxattrSeen struct {
|
|
name string
|
|
}
|
|
|
|
type getxattr struct {
|
|
file
|
|
seen chan getxattrSeen
|
|
}
|
|
|
|
func (f *getxattr) Getxattr(req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse, intr Intr) fuse.Error {
|
|
f.seen <- getxattrSeen{name: req.Name}
|
|
resp.Xattr = []byte("hello, world")
|
|
return nil
|
|
}
|
|
|
|
func (f *getxattr) setup(t *testing.T) {
|
|
f.seen = make(chan getxattrSeen, 1)
|
|
}
|
|
|
|
func (f *getxattr) test(path string, t *testing.T) {
|
|
buf := make([]byte, 8192)
|
|
n, err := syscallx.Getxattr(path, "not-there", buf)
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
return
|
|
}
|
|
buf = buf[:n]
|
|
if g, e := string(buf), "hello, world"; g != e {
|
|
t.Errorf("wrong getxattr content: %#v != %#v", g, e)
|
|
}
|
|
close(f.seen)
|
|
seen := <-f.seen
|
|
if g, e := seen.name, "not-there"; g != e {
|
|
t.Errorf("wrong getxattr name: %#v != %#v", g, e)
|
|
}
|
|
}
|
|
|
|
// Test Getxattr that has no space to return value
|
|
|
|
type getxattrTooSmall struct {
|
|
file
|
|
}
|
|
|
|
func (f *getxattrTooSmall) Getxattr(req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse, intr Intr) fuse.Error {
|
|
resp.Xattr = []byte("hello, world")
|
|
return nil
|
|
}
|
|
|
|
func (f *getxattrTooSmall) test(path string, t *testing.T) {
|
|
buf := make([]byte, 3)
|
|
_, err := syscallx.Getxattr(path, "whatever", buf)
|
|
if err == nil {
|
|
t.Error("Getxattr = nil; want some error")
|
|
}
|
|
if err != syscall.ERANGE {
|
|
t.Errorf("unexpected error: %v", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Test Getxattr used to probe result size
|
|
|
|
type getxattrSize struct {
|
|
file
|
|
}
|
|
|
|
func (f *getxattrSize) Getxattr(req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse, intr Intr) fuse.Error {
|
|
resp.Xattr = []byte("hello, world")
|
|
return nil
|
|
}
|
|
|
|
func (f *getxattrSize) test(path string, t *testing.T) {
|
|
n, err := syscallx.Getxattr(path, "whatever", nil)
|
|
if err != nil {
|
|
t.Errorf("Getxattr unexpected error: %v", err)
|
|
return
|
|
}
|
|
if g, e := n, len("hello, world"); g != e {
|
|
t.Errorf("Getxattr incorrect size: %d != %d", g, e)
|
|
}
|
|
}
|
|
|
|
// Test Listxattr
|
|
|
|
type listxattr struct {
|
|
file
|
|
seen chan bool
|
|
}
|
|
|
|
func (f *listxattr) Listxattr(req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse, intr Intr) fuse.Error {
|
|
f.seen <- true
|
|
resp.Append("one", "two")
|
|
return nil
|
|
}
|
|
|
|
func (f *listxattr) setup(t *testing.T) {
|
|
f.seen = make(chan bool, 1)
|
|
}
|
|
|
|
func (f *listxattr) test(path string, t *testing.T) {
|
|
buf := make([]byte, 8192)
|
|
n, err := syscallx.Listxattr(path, buf)
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
return
|
|
}
|
|
buf = buf[:n]
|
|
if g, e := string(buf), "one\x00two\x00"; g != e {
|
|
t.Errorf("wrong listxattr content: %#v != %#v", g, e)
|
|
}
|
|
close(f.seen)
|
|
seen := <-f.seen
|
|
if g, e := seen, true; g != e {
|
|
t.Errorf("listxattr not seen: %#v != %#v", g, e)
|
|
}
|
|
}
|
|
|
|
// Test Listxattr that has no space to return value
|
|
|
|
type listxattrTooSmall struct {
|
|
file
|
|
}
|
|
|
|
func (f *listxattrTooSmall) Listxattr(req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse, intr Intr) fuse.Error {
|
|
resp.Xattr = []byte("one\x00two\x00")
|
|
return nil
|
|
}
|
|
|
|
func (f *listxattrTooSmall) test(path string, t *testing.T) {
|
|
buf := make([]byte, 3)
|
|
_, err := syscallx.Listxattr(path, buf)
|
|
if err == nil {
|
|
t.Error("Listxattr = nil; want some error")
|
|
}
|
|
if err != syscall.ERANGE {
|
|
t.Errorf("unexpected error: %v", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Test Listxattr used to probe result size
|
|
|
|
type listxattrSize struct {
|
|
file
|
|
}
|
|
|
|
func (f *listxattrSize) Listxattr(req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse, intr Intr) fuse.Error {
|
|
resp.Xattr = []byte("one\x00two\x00")
|
|
return nil
|
|
}
|
|
|
|
func (f *listxattrSize) test(path string, t *testing.T) {
|
|
n, err := syscallx.Listxattr(path, nil)
|
|
if err != nil {
|
|
t.Errorf("Listxattr unexpected error: %v", err)
|
|
return
|
|
}
|
|
if g, e := n, len("one\x00two\x00"); g != e {
|
|
t.Errorf("Getxattr incorrect size: %d != %d", g, e)
|
|
}
|
|
}
|
|
|
|
// Test Setxattr
|
|
|
|
type setxattrSeen struct {
|
|
name string
|
|
flags uint32
|
|
value string
|
|
}
|
|
|
|
type setxattr struct {
|
|
file
|
|
seen chan setxattrSeen
|
|
}
|
|
|
|
func (f *setxattr) Setxattr(req *fuse.SetxattrRequest, intr Intr) fuse.Error {
|
|
f.seen <- setxattrSeen{
|
|
name: req.Name,
|
|
flags: req.Flags,
|
|
value: string(req.Xattr),
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *setxattr) setup(t *testing.T) {
|
|
f.seen = make(chan setxattrSeen, 1)
|
|
}
|
|
|
|
func (f *setxattr) test(path string, t *testing.T) {
|
|
err := syscallx.Setxattr(path, "greeting", []byte("hello, world"), 0)
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
return
|
|
}
|
|
close(f.seen)
|
|
want := setxattrSeen{flags: 0, name: "greeting", value: "hello, world"}
|
|
if g, e := <-f.seen, want; g != e {
|
|
t.Errorf("setxattr saw %v, want %v", g, e)
|
|
}
|
|
}
|
|
|
|
// Test Removexattr
|
|
|
|
type removexattrSeen struct {
|
|
name string
|
|
}
|
|
|
|
type removexattr struct {
|
|
file
|
|
seen chan removexattrSeen
|
|
}
|
|
|
|
func (f *removexattr) Removexattr(req *fuse.RemovexattrRequest, intr Intr) fuse.Error {
|
|
f.seen <- removexattrSeen{name: req.Name}
|
|
return nil
|
|
}
|
|
|
|
func (f *removexattr) setup(t *testing.T) {
|
|
f.seen = make(chan removexattrSeen, 1)
|
|
}
|
|
|
|
func (f *removexattr) test(path string, t *testing.T) {
|
|
err := syscallx.Removexattr(path, "greeting")
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
return
|
|
}
|
|
close(f.seen)
|
|
want := removexattrSeen{name: "greeting"}
|
|
if g, e := <-f.seen, want; g != e {
|
|
t.Errorf("removexattr saw %v, want %v", g, e)
|
|
}
|
|
}
|