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) } }