mirror of https://github.com/perkeep/perkeep.git
595 lines
12 KiB
Go
595 lines
12 KiB
Go
// Copyright 2012 The Go 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 fuse
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"runtime"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
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("/bin/fusermount", "-u", dir).Run()
|
|
}
|
|
}
|
|
|
|
func TestFuse(t *testing.T) {
|
|
Debugf = log.Printf
|
|
dir, err := ioutil.TempDir("", "fusetest")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
os.MkdirAll(dir, 0777)
|
|
|
|
c, err := Mount(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer umount(dir)
|
|
|
|
go func() {
|
|
err := c.Serve(testFS{})
|
|
if err != nil {
|
|
fmt.Println("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")
|
|
}
|
|
|
|
var fuseTests = []struct {
|
|
name string
|
|
node interface {
|
|
Node
|
|
test(string, *testing.T)
|
|
}
|
|
}{
|
|
{"readAll", readAll{}},
|
|
{"readAll1", &readAll1{}},
|
|
{"write", &write{}},
|
|
{"writeAll", &writeAll{}},
|
|
{"writeAll2", &writeAll2{}},
|
|
{"release", &release{}},
|
|
{"mkdir1", &mkdir1{}},
|
|
{"create1", &create1{}},
|
|
{"create2", &create2{}},
|
|
{"symlink1", &symlink1{}},
|
|
{"link1", &link1{}},
|
|
{"rename1", &rename1{}},
|
|
{"mknod1", &mknod1{}},
|
|
}
|
|
|
|
// TO TEST:
|
|
// Statfs
|
|
// Lookup(*LookupRequest, *LookupResponse)
|
|
// Getattr(*GetattrRequest, *GetattrResponse)
|
|
// Attr with explicit inode
|
|
// Setattr(*SetattrRequest, *SetattrResponse)
|
|
// Access(*AccessRequest)
|
|
// Open(*OpenRequest, *OpenResponse)
|
|
// Getxattr, Setxattr, Listxattr, Removexattr
|
|
// Write(*WriteRequest, *WriteResponse)
|
|
// Flush(*FlushRequest, *FlushResponse)
|
|
|
|
// Test Read calling ReadAll.
|
|
|
|
type readAll struct{ file }
|
|
|
|
const hi = "hello, world"
|
|
|
|
func (readAll) ReadAll(intr Intr) ([]byte, 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 *ReadRequest, resp *ReadResponse, intr Intr) Error {
|
|
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
|
|
data []byte
|
|
gotfsync bool
|
|
}
|
|
|
|
func (w *write) Write(req *WriteRequest, resp *WriteResponse, intr Intr) Error {
|
|
w.data = append(w.data, req.Data...)
|
|
resp.Size = len(req.Data)
|
|
return nil
|
|
}
|
|
|
|
func (w *write) Fsync(r *FsyncRequest, intr Intr) Error {
|
|
w.gotfsync = true
|
|
return nil
|
|
}
|
|
|
|
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.gotfsync {
|
|
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 string(w.data) != hi {
|
|
t.Errorf("writeAll = %q, want %q", w.data, hi)
|
|
}
|
|
}
|
|
|
|
// Test Write calling WriteAll.
|
|
|
|
type writeAll struct {
|
|
file
|
|
data []byte
|
|
gotfsync bool
|
|
}
|
|
|
|
func (w *writeAll) Fsync(r *FsyncRequest, intr Intr) Error {
|
|
w.gotfsync = true
|
|
return nil
|
|
}
|
|
|
|
func (w *writeAll) WriteAll(data []byte, intr Intr) Error {
|
|
w.data = data
|
|
return nil
|
|
}
|
|
|
|
func (w *writeAll) test(path string, t *testing.T) {
|
|
err := ioutil.WriteFile(path, []byte(hi), 0666)
|
|
if err != nil {
|
|
t.Fatalf("WriteFile: %v", err)
|
|
return
|
|
}
|
|
if string(w.data) != hi {
|
|
t.Errorf("writeAll = %q, want %q", w.data, hi)
|
|
}
|
|
}
|
|
|
|
// Test Write calling Setattr+Write+Flush.
|
|
|
|
type writeAll2 struct {
|
|
file
|
|
data []byte
|
|
setattr bool
|
|
flush bool
|
|
}
|
|
|
|
func (w *writeAll2) Setattr(req *SetattrRequest, resp *SetattrResponse, intr Intr) Error {
|
|
w.setattr = true
|
|
return nil
|
|
}
|
|
|
|
func (w *writeAll2) Flush(req *FlushRequest, intr Intr) Error {
|
|
w.flush = true
|
|
return nil
|
|
}
|
|
|
|
func (w *writeAll2) Write(req *WriteRequest, resp *WriteResponse, intr Intr) Error {
|
|
w.data = append(w.data, req.Data...)
|
|
resp.Size = len(req.Data)
|
|
return nil
|
|
}
|
|
|
|
func (w *writeAll2) test(path string, t *testing.T) {
|
|
err := ioutil.WriteFile(path, []byte(hi), 0666)
|
|
if err != nil {
|
|
t.Errorf("WriteFile: %v", err)
|
|
return
|
|
}
|
|
if !w.setattr || string(w.data) != hi || !w.flush {
|
|
t.Errorf("writeAll = %v, %q, %v, want %v, %q, %v", w.setattr, string(w.data), w.flush, true, hi, true)
|
|
}
|
|
}
|
|
|
|
// Test Mkdir.
|
|
|
|
type mkdir1 struct {
|
|
dir
|
|
name string
|
|
}
|
|
|
|
func (f *mkdir1) Mkdir(req *MkdirRequest, intr Intr) (Node, Error) {
|
|
f.name = req.Name
|
|
return &mkdir1{}, nil
|
|
}
|
|
|
|
func (f *mkdir1) test(path string, t *testing.T) {
|
|
f.name = ""
|
|
err := os.Mkdir(path+"/foo", 0777)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
if f.name != "foo" {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Test Create (and fsync)
|
|
|
|
type create1 struct {
|
|
dir
|
|
name string
|
|
f *writeAll
|
|
}
|
|
|
|
func (f *create1) Create(req *CreateRequest, resp *CreateResponse, intr Intr) (Node, Handle, Error) {
|
|
f.name = req.Name
|
|
f.f = &writeAll{}
|
|
return f.f, f.f, nil
|
|
}
|
|
|
|
func (f *create1) test(path string, t *testing.T) {
|
|
f.name = ""
|
|
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.gotfsync {
|
|
t.Errorf("never received expected fsync call")
|
|
}
|
|
|
|
ff.Close()
|
|
if f.name != "foo" {
|
|
t.Errorf("create1 name=%q want foo", f.name)
|
|
}
|
|
}
|
|
|
|
// Test Create + WriteAll + Remove
|
|
|
|
type create2 struct {
|
|
dir
|
|
name string
|
|
f *writeAll
|
|
fooExists bool
|
|
}
|
|
|
|
func (f *create2) Create(req *CreateRequest, resp *CreateResponse, intr Intr) (Node, Handle, Error) {
|
|
f.name = req.Name
|
|
f.f = &writeAll{}
|
|
return f.f, f.f, nil
|
|
}
|
|
|
|
func (f *create2) Lookup(name string, intr Intr) (Node, Error) {
|
|
if f.fooExists && name == "foo" {
|
|
return file{}, nil
|
|
}
|
|
return nil, ENOENT
|
|
}
|
|
|
|
func (f *create2) Remove(r *RemoveRequest, intr Intr) Error {
|
|
if f.fooExists && r.Name == "foo" && !r.Dir {
|
|
f.fooExists = false
|
|
return nil
|
|
}
|
|
return ENOENT
|
|
}
|
|
|
|
func (f *create2) test(path string, t *testing.T) {
|
|
f.name = ""
|
|
err := ioutil.WriteFile(path+"/foo", []byte(hi), 0666)
|
|
if err != nil {
|
|
t.Fatalf("create2 WriteFile: %v", err)
|
|
}
|
|
if string(f.f.data) != hi {
|
|
t.Fatalf("create2 writeAll = %q, want %q", f.f.data, 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
|
|
newName, target string
|
|
}
|
|
|
|
func (f *symlink1) Symlink(req *SymlinkRequest, intr Intr) (Node, Error) {
|
|
f.newName = req.NewName
|
|
f.target = req.Target
|
|
return symlink{target: req.Target}, nil
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
if f.newName != "symlink.file" {
|
|
t.Errorf("symlink newName = %q; want %q", f.newName, "symlink.file")
|
|
}
|
|
if f.target != target {
|
|
t.Errorf("symlink target = %q; want %q", f.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
|
|
newName string
|
|
}
|
|
|
|
func (f *link1) Lookup(name string, intr Intr) (Node, Error) {
|
|
if name == "old" {
|
|
return file{}, nil
|
|
}
|
|
return nil, ENOENT
|
|
}
|
|
|
|
func (f *link1) Link(r *LinkRequest, old Node, intr Intr) (Node, Error) {
|
|
f.newName = r.NewName
|
|
return file{}, nil
|
|
}
|
|
|
|
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 f.newName != "new" {
|
|
t.Fatalf("saw Link for newName %q; want %q", f.newName, "new")
|
|
}
|
|
}
|
|
|
|
// Test Rename
|
|
|
|
type rename1 struct {
|
|
dir
|
|
renames int
|
|
}
|
|
|
|
func (f *rename1) Lookup(name string, intr Intr) (Node, Error) {
|
|
if name == "old" {
|
|
return file{}, nil
|
|
}
|
|
return nil, ENOENT
|
|
}
|
|
|
|
func (f *rename1) Rename(r *RenameRequest, newDir Node, intr Intr) Error {
|
|
if r.OldName == "old" && r.NewName == "new" && newDir == f {
|
|
f.renames++
|
|
return nil
|
|
}
|
|
return 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 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
|
|
did bool
|
|
}
|
|
|
|
func (r *release) Release(*ReleaseRequest, Intr) Error {
|
|
r.did = true
|
|
return nil
|
|
}
|
|
|
|
func (r *release) test(path string, t *testing.T) {
|
|
r.did = false
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
f.Close()
|
|
time.Sleep(1 * time.Second)
|
|
if !r.did {
|
|
t.Error("Close did not Release")
|
|
}
|
|
}
|
|
|
|
// Test mknod
|
|
|
|
type mknod1 struct {
|
|
dir
|
|
gotr *MknodRequest
|
|
}
|
|
|
|
func (f *mknod1) Mknod(r *MknodRequest, intr Intr) (Node, Error) {
|
|
f.gotr = r
|
|
return fifo{}, nil
|
|
}
|
|
|
|
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)
|
|
}
|
|
if f.gotr == nil {
|
|
t.Fatalf("no recorded MknodRequest")
|
|
}
|
|
if g, e := f.gotr.Name, "node"; g != e {
|
|
t.Errorf("got Name = %q; want %q", g, e)
|
|
}
|
|
if g, e := f.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 := f.gotr.Mode, os.FileMode(os.ModeNamedPipe|0666); g != e {
|
|
t.Errorf("got Mode = %v; want %v", g, e)
|
|
}
|
|
t.Logf("Got request: %#v", f.gotr)
|
|
}
|
|
|
|
type file struct{}
|
|
type dir struct{}
|
|
type fifo struct{}
|
|
type symlink struct {
|
|
target string
|
|
}
|
|
|
|
func (f file) Attr() Attr { return Attr{Mode: 0666} }
|
|
func (f dir) Attr() Attr { return Attr{Mode: os.ModeDir | 0777} }
|
|
func (f fifo) Attr() Attr { return Attr{Mode: os.ModeNamedPipe | 0666} }
|
|
func (f symlink) Attr() Attr { return Attr{Mode: os.ModeSymlink | 0666} }
|
|
|
|
func (f symlink) Readlink(*ReadlinkRequest, Intr) (string, Error) {
|
|
return f.target, nil
|
|
}
|
|
|
|
type testFS struct{}
|
|
|
|
func (testFS) Root() (Node, Error) {
|
|
return testFS{}, nil
|
|
}
|
|
|
|
func (testFS) Attr() Attr {
|
|
return Attr{Mode: os.ModeDir | 0555}
|
|
}
|
|
|
|
func (testFS) Lookup(name string, intr Intr) (Node, Error) {
|
|
for _, tt := range fuseTests {
|
|
if tt.name == name {
|
|
return tt.node, nil
|
|
}
|
|
}
|
|
return nil, ENOENT
|
|
}
|
|
|
|
func (testFS) ReadDir(intr Intr) ([]Dirent, Error) {
|
|
var dirs []Dirent
|
|
for _, tt := range fuseTests {
|
|
if *fuseRun == "" || *fuseRun == tt.name {
|
|
log.Printf("Readdir; adding %q", tt.name)
|
|
dirs = append(dirs, Dirent{Name: tt.name})
|
|
}
|
|
}
|
|
return dirs, nil
|
|
}
|