1package fs_test
2
3import (
4	"bytes"
5	"context"
6	"errors"
7	"fmt"
8	"io"
9	"io/ioutil"
10	"log"
11	"net/http"
12	"os"
13	"path"
14	"path/filepath"
15	"runtime"
16	"strings"
17	"sync"
18	"sync/atomic"
19	"syscall"
20	"testing"
21	"time"
22
23	"bazil.org/fuse"
24	"bazil.org/fuse/fs"
25	"bazil.org/fuse/fs/fstestutil"
26	"bazil.org/fuse/fs/fstestutil/record"
27	"bazil.org/fuse/fs/fstestutil/spawntest"
28	"bazil.org/fuse/fs/fstestutil/spawntest/httpjson"
29	"bazil.org/fuse/fuseutil"
30	"golang.org/x/sys/unix"
31)
32
33func maybeParallel(t *testing.T) {
34	// t.Parallel()
35}
36
37var helpers spawntest.Registry
38
39// TO TEST:
40//	Lookup(*LookupRequest, *LookupResponse)
41//	Getattr(*GetattrRequest, *GetattrResponse)
42//	Attr with explicit inode
43//	Setattr(*SetattrRequest, *SetattrResponse)
44//	Access(*AccessRequest)
45//	Open(*OpenRequest, *OpenResponse)
46//	Write(*WriteRequest, *WriteResponse)
47//	Flush(*FlushRequest, *FlushResponse)
48
49func init() {
50	fstestutil.DebugByDefault()
51}
52
53// symlink can be embedded in a struct to make it look like a symlink.
54type symlink struct {
55}
56
57func (f symlink) Attr(ctx context.Context, a *fuse.Attr) error {
58	a.Mode = os.ModeSymlink | 0o666
59	return nil
60}
61
62// fifo can be embedded in a struct to make it look like a named pipe.
63type fifo struct{}
64
65func (f fifo) Attr(ctx context.Context, a *fuse.Attr) error {
66	a.Mode = os.ModeNamedPipe | 0o666
67	return nil
68}
69
70func TestMountpointDoesNotExist(t *testing.T) {
71	maybeParallel(t)
72	tmp, err := ioutil.TempDir("", "fusetest")
73	if err != nil {
74		t.Fatal(err)
75	}
76	defer os.Remove(tmp)
77
78	mountpoint := path.Join(tmp, "does-not-exist")
79	conn, err := fuse.Mount(mountpoint)
80	if err == nil {
81		conn.Close()
82		t.Fatalf("expected error with non-existent mountpoint")
83	}
84	if _, ok := err.(*fuse.MountpointDoesNotExistError); !ok {
85		t.Fatalf("wrong error from mount: %T: %v", err, err)
86	}
87}
88
89type badRootFS struct{}
90
91func (badRootFS) Root() (fs.Node, error) {
92	// pick a really distinct error, to identify it later
93	return nil, fuse.Errno(syscall.ENAMETOOLONG)
94}
95
96func TestRootErr(t *testing.T) {
97	maybeParallel(t)
98	mnt, err := fstestutil.MountedT(t, badRootFS{}, nil)
99	if err == nil {
100		// path for synchronous mounts (linux): started out fine, now
101		// wait for Serve to cycle through
102		err = <-mnt.Error
103		// without this, unmount will keep failing with EBUSY; nudge
104		// kernel into realizing InitResponse will not happen
105		mnt.Conn.Close()
106		mnt.Close()
107	}
108
109	if err == nil {
110		t.Fatal("expected an error")
111	}
112	// TODO this should not be a textual comparison, Serve hides
113	// details
114	if err.Error() != "cannot obtain root node: file name too long" {
115		t.Errorf("Unexpected error: %v", err)
116	}
117}
118
119type testPanic struct{}
120
121type panicSentinel struct{}
122
123var _ error = panicSentinel{}
124
125func (panicSentinel) Error() string { return "just a test" }
126
127var _ fuse.ErrorNumber = panicSentinel{}
128
129func (panicSentinel) Errno() fuse.Errno {
130	return fuse.Errno(syscall.ENAMETOOLONG)
131}
132
133func (f testPanic) Root() (fs.Node, error) {
134	return f, nil
135}
136
137func (f testPanic) Attr(ctx context.Context, a *fuse.Attr) error {
138	a.Inode = 1
139	a.Mode = os.ModeDir | 0o777
140	return nil
141}
142
143func (f testPanic) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) {
144	panic(panicSentinel{})
145}
146
147func doPanic(ctx context.Context, dir string) (*struct{}, error) {
148	err := os.Mkdir(dir+"/trigger-a-panic", 0o700)
149	if nerr, ok := err.(*os.PathError); !ok || nerr.Err != syscall.ENAMETOOLONG {
150		return nil, fmt.Errorf("wrong error from panicking handler: %T: %v", err, err)
151	}
152	return &struct{}{}, nil
153}
154
155var panicHelper = helpers.Register("panic", httpjson.ServePOST(doPanic))
156
157func TestPanic(t *testing.T) {
158	maybeParallel(t)
159	ctx, cancel := context.WithCancel(context.Background())
160	defer cancel()
161	mnt, err := fstestutil.MountedT(t, testPanic{}, nil)
162	if err != nil {
163		t.Fatal(err)
164	}
165	defer mnt.Close()
166	control := panicHelper.Spawn(ctx, t)
167	defer control.Close()
168	var nothing struct{}
169	if err := control.JSON("/").Call(ctx, mnt.Dir, &nothing); err != nil {
170		t.Fatalf("calling helper: %v", err)
171	}
172}
173
174type testStatFS struct{}
175
176func (f testStatFS) Root() (fs.Node, error) {
177	return f, nil
178}
179
180func (f testStatFS) Attr(ctx context.Context, a *fuse.Attr) error {
181	a.Inode = 1
182	a.Mode = os.ModeDir | 0o777
183	return nil
184}
185
186func (f testStatFS) Statfs(ctx context.Context, req *fuse.StatfsRequest, resp *fuse.StatfsResponse) error {
187	resp.Blocks = 42
188	resp.Bfree = 10
189	resp.Bavail = 3
190	resp.Files = 13
191	resp.Ffree = 11
192	resp.Bsize = 1000
193	resp.Namelen = 34
194	resp.Frsize = 7
195	return nil
196}
197
198type statfsResult struct {
199	Blocks  uint64
200	Bfree   uint64
201	Bavail  uint64
202	Files   uint64
203	Ffree   uint64
204	Bsize   int64
205	Namelen int64
206	Frsize  int64
207}
208
209func doStatfs(ctx context.Context, dir string) (*statfsResult, error) {
210	var st syscall.Statfs_t
211	if err := syscall.Statfs(dir, &st); err != nil {
212		return nil, fmt.Errorf("Statfs failed: %v", err)
213	}
214	log.Printf("Statfs got: %#v", st)
215	r := platformStatfs(&st)
216	return r, nil
217}
218
219var statfsHelper = helpers.Register("statfs", httpjson.ServePOST(doStatfs))
220
221func testStatfs(t *testing.T, helper *spawntest.Helper) {
222	maybeParallel(t)
223	ctx, cancel := context.WithCancel(context.Background())
224	defer cancel()
225	mnt, err := fstestutil.MountedT(t, testStatFS{}, nil)
226	if err != nil {
227		t.Fatal(err)
228	}
229	defer mnt.Close()
230
231	control := helper.Spawn(ctx, t)
232	defer control.Close()
233	var got statfsResult
234	if err := control.JSON("/").Call(ctx, mnt.Dir, &got); err != nil {
235		t.Fatalf("calling helper: %v", err)
236	}
237
238	if g, e := got.Blocks, uint64(42); g != e {
239		t.Errorf("got Blocks = %d; want %d", g, e)
240	}
241	if g, e := got.Bfree, uint64(10); g != e {
242		t.Errorf("got Bfree = %d; want %d", g, e)
243	}
244	if g, e := got.Bavail, uint64(3); g != e {
245		t.Errorf("got Bavail = %d; want %d", g, e)
246	}
247	if g, e := got.Files, uint64(13); g != e {
248		t.Errorf("got Files = %d; want %d", g, e)
249	}
250	if g, e := got.Ffree, uint64(11); g != e {
251		t.Errorf("got Ffree = %d; want %d", g, e)
252	}
253	switch runtime.GOOS {
254	case "freebsd":
255		// freebsd gives 65536 here regardless of the fuse fs
256		if got.Bsize != 65536 {
257			t.Errorf("freebsd now implements statfs Bsize, please fix tests")
258		}
259	default:
260		if g, e := got.Bsize, int64(1000); g != e {
261			t.Errorf("got Bsize = %d; want %d", g, e)
262		}
263	}
264	if g, e := got.Namelen, int64(34); g != e {
265		t.Errorf("got Namelen = %d; want %d", g, e)
266	}
267	if g, e := got.Frsize, int64(7); g != e {
268		t.Errorf("got Frsize = %d; want %d", g, e)
269	}
270}
271
272func TestStatfs(t *testing.T) {
273	testStatfs(t, statfsHelper)
274}
275
276func doFstatfs(ctx context.Context, dir string) (*statfsResult, error) {
277	f, err := os.Open(dir)
278	if err != nil {
279		return nil, fmt.Errorf("Open for fstatfs failed: %v", err)
280	}
281	defer f.Close()
282	var st syscall.Statfs_t
283	err = syscall.Fstatfs(int(f.Fd()), &st)
284	if err != nil {
285		return nil, fmt.Errorf("Fstatfs failed: %v", err)
286	}
287	log.Printf("Fstatfs got: %#v", st)
288	r := platformStatfs(&st)
289	return r, nil
290}
291
292var fstatfsHelper = helpers.Register("fstatfs", httpjson.ServePOST(doFstatfs))
293
294func TestFstatfs(t *testing.T) {
295	testStatfs(t, fstatfsHelper)
296}
297
298// Test Stat of root.
299
300type root struct{}
301
302func (f root) Root() (fs.Node, error) {
303	return f, nil
304}
305
306func (root) Attr(ctx context.Context, a *fuse.Attr) error {
307	a.Inode = 1
308	a.Mode = os.ModeDir | 0o555
309	// This has to be a power of two, but try to pick something that's an unlikely default.
310	a.BlockSize = 65536
311	return nil
312}
313
314type statResult struct {
315	Mode    os.FileMode
316	Ino     uint64
317	Nlink   uint64
318	UID     uint32
319	GID     uint32
320	Blksize int64
321}
322
323func doStat(ctx context.Context, path string) (*statResult, error) {
324	fi, err := os.Stat(path)
325	if err != nil {
326		return nil, err
327	}
328	r := platformStat(fi)
329	return r, nil
330}
331
332var statHelper = helpers.Register("stat", httpjson.ServePOST(doStat))
333
334func TestStatRoot(t *testing.T) {
335	maybeParallel(t)
336	ctx, cancel := context.WithCancel(context.Background())
337	defer cancel()
338	mnt, err := fstestutil.MountedT(t, root{}, nil)
339	if err != nil {
340		t.Fatal(err)
341	}
342	defer mnt.Close()
343	control := statHelper.Spawn(ctx, t)
344	defer control.Close()
345	var got statResult
346	if err := control.JSON("/").Call(ctx, mnt.Dir, &got); err != nil {
347		t.Fatalf("calling helper: %v", err)
348	}
349	if (got.Mode & os.ModeType) != os.ModeDir {
350		t.Errorf("root is not a directory: %v", got.Mode)
351	}
352	if p := got.Mode.Perm(); p != 0o555 {
353		t.Errorf("root has weird access mode: %v", p)
354	}
355	if got.Ino != 1 {
356		t.Errorf("root has wrong inode: %v", got.Ino)
357	}
358	if got.Nlink != 1 {
359		t.Errorf("root has wrong link count: %v", got.Nlink)
360	}
361	if got.UID != 0 {
362		t.Errorf("root has wrong uid: %d", got.UID)
363	}
364	if got.GID != 0 {
365		t.Errorf("root has wrong gid: %d", got.GID)
366	}
367	if g, e := got.Blksize, int64(65536); g != e {
368		t.Errorf("root has wrong blocksize: %d != %d", g, e)
369	}
370}
371
372// Test Read calling ReadAll.
373
374type readAll struct {
375	fstestutil.File
376}
377
378const hi = "hello, world"
379
380func (readAll) Attr(ctx context.Context, a *fuse.Attr) error {
381	a.Mode = 0o666
382	a.Size = uint64(len(hi))
383	return nil
384}
385
386func (readAll) ReadAll(ctx context.Context) ([]byte, error) {
387	return []byte(hi), nil
388}
389
390type readResult struct {
391	Data []byte
392}
393
394func doRead(ctx context.Context, path string) (*readResult, error) {
395	f, err := os.Open(path)
396	if err != nil {
397		return nil, err
398	}
399	defer f.Close()
400	data := make([]byte, 4096)
401	n, err := f.Read(data)
402	if err != nil {
403		return nil, err
404	}
405	r := &readResult{Data: data[:n]}
406	return r, nil
407}
408
409var readHelper = helpers.Register("read", httpjson.ServePOST(doRead))
410
411func TestReadAll(t *testing.T) {
412	maybeParallel(t)
413	ctx, cancel := context.WithCancel(context.Background())
414	defer cancel()
415	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": readAll{}}}, nil)
416	if err != nil {
417		t.Fatal(err)
418	}
419	defer mnt.Close()
420	control := readHelper.Spawn(ctx, t)
421	defer control.Close()
422	var got readResult
423	if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil {
424		t.Fatalf("calling helper: %v", err)
425	}
426	if g, e := string(got.Data), hi; g != e {
427		t.Errorf("readAll = %q, want %q", g, e)
428	}
429}
430
431// Test Read.
432
433type readWithHandleRead struct {
434	fstestutil.File
435}
436
437func (readWithHandleRead) Attr(ctx context.Context, a *fuse.Attr) error {
438	a.Mode = 0o666
439	a.Size = uint64(len(hi))
440	return nil
441}
442
443func (readWithHandleRead) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
444	fuseutil.HandleRead(req, resp, []byte(hi))
445	return nil
446}
447
448func TestReadAllWithHandleRead(t *testing.T) {
449	maybeParallel(t)
450	ctx, cancel := context.WithCancel(context.Background())
451	defer cancel()
452	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": readWithHandleRead{}}}, nil)
453	if err != nil {
454		t.Fatal(err)
455	}
456	defer mnt.Close()
457	control := readHelper.Spawn(ctx, t)
458	defer control.Close()
459	var got readResult
460	if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil {
461		t.Fatalf("calling helper: %v", err)
462	}
463	if g, e := string(got.Data), hi; g != e {
464		t.Errorf("readAll = %q, want %q", g, e)
465	}
466}
467
468type readFlags struct {
469	fstestutil.File
470	fileFlags record.Recorder
471}
472
473func (r *readFlags) Attr(ctx context.Context, a *fuse.Attr) error {
474	a.Mode = 0o666
475	a.Size = uint64(len(hi))
476	return nil
477}
478
479func (r *readFlags) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
480	r.fileFlags.Record(req.FileFlags)
481	fuseutil.HandleRead(req, resp, []byte(hi))
482	return nil
483}
484
485func doReadFileFlags(ctx context.Context, path string) (*struct{}, error) {
486	f, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND, 0o666)
487	if err != nil {
488		return nil, err
489	}
490	defer f.Close()
491	if _, err := f.Read(make([]byte, 4096)); err != nil {
492		return nil, err
493	}
494	_ = f.Close()
495	return &struct{}{}, nil
496}
497
498var readFileFlagsHelper = helpers.Register("readFileFlags", httpjson.ServePOST(doReadFileFlags))
499
500func TestReadFileFlags(t *testing.T) {
501	maybeParallel(t)
502	ctx, cancel := context.WithCancel(context.Background())
503	defer cancel()
504	r := &readFlags{}
505	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": r}}, nil)
506	if err != nil {
507		t.Fatal(err)
508	}
509	defer mnt.Close()
510
511	control := readFileFlagsHelper.Spawn(ctx, t)
512	defer control.Close()
513	var nothing struct{}
514	if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &nothing); err != nil {
515		t.Fatalf("calling helper: %v", err)
516	}
517
518	got := r.fileFlags.Recorded().(fuse.OpenFlags)
519	got &^= fuse.OpenNonblock
520	want := fuse.OpenReadWrite | fuse.OpenAppend
521	if runtime.GOOS == "freebsd" {
522		// FreeBSD doesn't pass append to FUSE?
523		want ^= fuse.OpenAppend
524	}
525	if g, e := got, want; g != e {
526		t.Errorf("read saw file flags %+v, want %+v", g, e)
527	}
528}
529
530type writeFlags struct {
531	fstestutil.File
532	fileFlags record.Recorder
533}
534
535func (r *writeFlags) Attr(ctx context.Context, a *fuse.Attr) error {
536	a.Mode = 0o666
537	// do not set Size here or FreeBSD will do a read-modify-write,
538	// even if the write replaces whole page contents
539	return nil
540}
541
542func (r *writeFlags) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error {
543	r.fileFlags.Record(req.FileFlags)
544	resp.Size = len(req.Data)
545	return nil
546}
547
548func doWriteFileFlags(ctx context.Context, path string) (*struct{}, error) {
549	f, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND, 0o666)
550	if err != nil {
551		return nil, err
552	}
553	defer f.Close()
554	if _, err := f.Write(make([]byte, 4096)); err != nil {
555		return nil, err
556	}
557	_ = f.Close()
558	return &struct{}{}, nil
559}
560
561var writeFileFlagsHelper = helpers.Register("writeFileFlags", httpjson.ServePOST(doWriteFileFlags))
562
563func TestWriteFileFlags(t *testing.T) {
564	maybeParallel(t)
565	ctx, cancel := context.WithCancel(context.Background())
566	defer cancel()
567	r := &writeFlags{}
568	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": r}}, nil)
569	if err != nil {
570		t.Fatal(err)
571	}
572	defer mnt.Close()
573
574	control := writeFileFlagsHelper.Spawn(ctx, t)
575	defer control.Close()
576	var nothing struct{}
577	if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &nothing); err != nil {
578		t.Fatalf("calling helper: %v", err)
579	}
580
581	got := r.fileFlags.Recorded().(fuse.OpenFlags)
582	got &^= fuse.OpenNonblock
583	want := fuse.OpenReadWrite | fuse.OpenAppend
584	if runtime.GOOS == "freebsd" {
585		// FreeBSD doesn't pass append to FUSE?
586		want &^= fuse.OpenAppend
587	}
588	if g, e := got, want; g != e {
589		t.Errorf("write saw file flags %+v, want %+v", g, e)
590	}
591}
592
593// Test Release.
594
595type release struct {
596	fstestutil.File
597	record.ReleaseWaiter
598}
599
600func doOpen(ctx context.Context, path string) (*struct{}, error) {
601	f, err := os.Open(path)
602	if err != nil {
603		return nil, err
604	}
605	f.Close()
606	return &struct{}{}, nil
607}
608
609var openHelper = helpers.Register("open", httpjson.ServePOST(doOpen))
610
611func TestRelease(t *testing.T) {
612	maybeParallel(t)
613	ctx, cancel := context.WithCancel(context.Background())
614	defer cancel()
615	r := &release{}
616	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": r}}, nil)
617	if err != nil {
618		t.Fatal(err)
619	}
620	defer mnt.Close()
621
622	control := openHelper.Spawn(ctx, t)
623	defer control.Close()
624	var nothing struct{}
625	if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &nothing); err != nil {
626		t.Fatalf("calling helper: %v", err)
627	}
628	got, ok := r.WaitForRelease(1 * time.Second)
629	if !ok {
630		t.Error("Close did not Release in time")
631	}
632	// dynamic values that are too hard to control
633	if got.Handle == 0 {
634		t.Errorf("got ReleaseRequest with no Handle")
635	}
636	got.Handle = 0
637	want := &fuse.ReleaseRequest{
638		Flags: fuse.OpenReadOnly | fuse.OpenNonblock,
639	}
640	if runtime.GOOS == "freebsd" {
641		// Go on FreeBSD isn't using the netpoller for os.File?
642		want.Flags &^= fuse.OpenNonblock
643		// no locking used but FreeBSD sets LockOwner?
644		got.LockOwner = 0
645	}
646	if g, e := got, want; *g != *e {
647		t.Errorf("bad release:\ngot\t%v\nwant\t%v", g, e)
648	}
649}
650
651// Test Write calling basic Write, with an fsync thrown in too.
652
653type write struct {
654	fstestutil.File
655	record.Writes
656	record.Fsyncs
657}
658
659type createWriteFsyncHelp struct {
660	mu   sync.Mutex
661	file *os.File
662}
663
664func (cwf *createWriteFsyncHelp) ServeHTTP(w http.ResponseWriter, req *http.Request) {
665	switch req.URL.Path {
666	case "/createWrite":
667		httpjson.ServePOST(cwf.doCreateWrite).ServeHTTP(w, req)
668	case "/fsync":
669		httpjson.ServePOST(cwf.doFsync).ServeHTTP(w, req)
670	case "/close":
671		httpjson.ServePOST(cwf.doClose).ServeHTTP(w, req)
672	default:
673		http.NotFound(w, req)
674	}
675}
676
677func (cwf *createWriteFsyncHelp) doCreateWrite(ctx context.Context, path string) (*struct{}, error) {
678	cwf.mu.Lock()
679	defer cwf.mu.Unlock()
680	f, err := os.Create(path)
681	if err != nil {
682		return nil, fmt.Errorf("Create: %v", err)
683	}
684	cwf.file = f
685	n, err := f.Write([]byte(hi))
686	if err != nil {
687		return nil, fmt.Errorf("Write: %v", err)
688	}
689	if n != len(hi) {
690		return nil, fmt.Errorf("short write; n=%d; hi=%d", n, len(hi))
691	}
692	return &struct{}{}, nil
693}
694
695func (cwf *createWriteFsyncHelp) doFsync(ctx context.Context, _ struct{}) (*struct{}, error) {
696	cwf.mu.Lock()
697	defer cwf.mu.Unlock()
698	if err := cwf.file.Sync(); err != nil {
699		return nil, fmt.Errorf("Fsync = %v", err)
700	}
701	return &struct{}{}, nil
702}
703
704func (cwf *createWriteFsyncHelp) doClose(ctx context.Context, _ struct{}) (*struct{}, error) {
705	cwf.mu.Lock()
706	defer cwf.mu.Unlock()
707	if err := cwf.file.Close(); err != nil {
708		return nil, fmt.Errorf("Close: %v", err)
709	}
710
711	return &struct{}{}, nil
712}
713
714var createWriteFsyncHelper = helpers.Register("createWriteFsync", &createWriteFsyncHelp{})
715
716func TestWrite(t *testing.T) {
717	maybeParallel(t)
718	ctx, cancel := context.WithCancel(context.Background())
719	defer cancel()
720	w := &write{}
721	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": w}}, nil)
722	if err != nil {
723		t.Fatal(err)
724	}
725	defer mnt.Close()
726
727	control := createWriteFsyncHelper.Spawn(ctx, t)
728	defer control.Close()
729	var nothing struct{}
730	if err := control.JSON("/createWrite").Call(ctx, mnt.Dir+"/child", &nothing); err != nil {
731		t.Fatalf("calling helper: %v", err)
732	}
733	if err := control.JSON("/fsync").Call(ctx, struct{}{}, &nothing); err != nil {
734		t.Fatalf("calling helper: %v", err)
735	}
736	if w.RecordedFsync() == (fuse.FsyncRequest{}) {
737		t.Errorf("never received expected fsync call")
738	}
739	if got := string(w.RecordedWriteData()); got != hi {
740		t.Errorf("write = %q, want %q", got, hi)
741	}
742	if err := control.JSON("/close").Call(ctx, struct{}{}, &nothing); err != nil {
743		t.Fatalf("calling helper: %v", err)
744	}
745}
746
747// Test Write of a larger buffer.
748
749func makeLargeData() (one, large []byte) {
750	o := []byte("xyzzyfoo")
751	l := bytes.Repeat(o, 8192)
752	return o, l
753}
754
755func doWriteLarge(ctx context.Context, path string) (*struct{}, error) {
756	f, err := os.Create(path)
757	if err != nil {
758		return nil, fmt.Errorf("Create: %v", err)
759	}
760	defer f.Close()
761	_, large := makeLargeData()
762	n, err := f.Write(large)
763	if err != nil {
764		return nil, fmt.Errorf("Write: %v", err)
765	}
766	if g, e := n, len(large); g != e {
767		return nil, fmt.Errorf("short write: %d != %d", g, e)
768	}
769	err = f.Close()
770	if err != nil {
771		return nil, fmt.Errorf("Close: %v", err)
772	}
773	return &struct{}{}, nil
774}
775
776var writeLargeHelper = helpers.Register("writeLarge", httpjson.ServePOST(doWriteLarge))
777
778func TestWriteLarge(t *testing.T) {
779	maybeParallel(t)
780	ctx, cancel := context.WithCancel(context.Background())
781	defer cancel()
782	w := &write{}
783	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": w}}, nil)
784	if err != nil {
785		t.Fatal(err)
786	}
787	defer mnt.Close()
788
789	control := writeLargeHelper.Spawn(ctx, t)
790	defer control.Close()
791	var nothing struct{}
792	if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &nothing); err != nil {
793		t.Fatalf("calling helper: %v", err)
794	}
795
796	got := w.RecordedWriteData()
797	one, large := makeLargeData()
798	if g, e := len(got), len(large); g != e {
799		t.Errorf("write wrong length: %d != %d", g, e)
800	}
801	if g := bytes.Replace(got, one, nil, -1); len(g) > 0 {
802		t.Errorf("write wrong data: expected repeats of %q, also got %q", one, g)
803	}
804}
805
806// Test Write calling Setattr+Write+Flush.
807
808type writeTruncateFlush struct {
809	fstestutil.File
810	record.Writes
811	record.Setattrs
812	record.Flushes
813}
814
815type writeFileRequest struct {
816	Path string
817	Data []byte
818}
819
820func doWriteFile(ctx context.Context, req writeFileRequest) (*struct{}, error) {
821	if err := ioutil.WriteFile(req.Path, req.Data, 0o666); err != nil {
822		return nil, fmt.Errorf("WriteFile: %v", err)
823	}
824	return &struct{}{}, nil
825}
826
827var writeFileHelper = helpers.Register("writeFile", httpjson.ServePOST(doWriteFile))
828
829func TestWriteTruncateFlush(t *testing.T) {
830	maybeParallel(t)
831	ctx, cancel := context.WithCancel(context.Background())
832	defer cancel()
833	w := &writeTruncateFlush{}
834	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": w}}, nil)
835	if err != nil {
836		t.Fatal(err)
837	}
838	defer mnt.Close()
839
840	control := writeFileHelper.Spawn(ctx, t)
841	defer control.Close()
842	var nothing struct{}
843	req := writeFileRequest{
844		Path: mnt.Dir + "/child",
845		Data: []byte(hi),
846	}
847	if err := control.JSON("/").Call(ctx, req, &nothing); err != nil {
848		t.Fatalf("calling helper: %v", err)
849	}
850	if w.RecordedSetattr() == (fuse.SetattrRequest{}) {
851		t.Errorf("writeTruncateFlush expected Setattr")
852	}
853	if !w.RecordedFlush() {
854		t.Errorf("writeTruncateFlush expected Setattr")
855	}
856	if got := string(w.RecordedWriteData()); got != hi {
857		t.Errorf("writeTruncateFlush = %q, want %q", got, hi)
858	}
859}
860
861// Test Mkdir.
862
863type mkdir1 struct {
864	fstestutil.Dir
865	record.Mkdirs
866}
867
868func (f *mkdir1) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) {
869	f.Mkdirs.Mkdir(ctx, req)
870	return &mkdir1{}, nil
871}
872
873func doMkdir(ctx context.Context, path string) (*struct{}, error) {
874	// uniform umask needed to make os.Mkdir's mode into something
875	// reproducible
876	syscall.Umask(0o022)
877	if err := os.Mkdir(path, 0o771); err != nil {
878		return nil, fmt.Errorf("mkdir: %v", err)
879	}
880	return &struct{}{}, nil
881}
882
883var mkdirHelper = helpers.Register("mkdir", httpjson.ServePOST(doMkdir))
884
885func TestMkdir(t *testing.T) {
886	maybeParallel(t)
887	ctx, cancel := context.WithCancel(context.Background())
888	defer cancel()
889	f := &mkdir1{}
890	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil)
891	if err != nil {
892		t.Fatal(err)
893	}
894	defer mnt.Close()
895
896	control := mkdirHelper.Spawn(ctx, t)
897	defer control.Close()
898	var nothing struct{}
899	if err := control.JSON("/").Call(ctx, mnt.Dir+"/foo", &nothing); err != nil {
900		t.Fatalf("calling helper: %v", err)
901	}
902
903	want := fuse.MkdirRequest{
904		Name:  "foo",
905		Mode:  os.ModeDir | 0o751,
906		Umask: 0o022,
907	}
908	if g, e := f.RecordedMkdir(), want; g != e {
909		t.Errorf("mkdir saw %+v, want %+v", g, e)
910	}
911}
912
913// Test Create
914
915type create1file struct {
916	fstestutil.File
917	record.Creates
918}
919
920type create1 struct {
921	fstestutil.Dir
922	f create1file
923}
924
925func (f *create1) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
926	if req.Name != "foo" {
927		log.Printf("ERROR create1.Create unexpected name: %q\n", req.Name)
928		return nil, nil, syscall.EPERM
929	}
930
931	_, _, _ = f.f.Creates.Create(ctx, req, resp)
932	return &f.f, &f.f, nil
933}
934
935func doCreate(ctx context.Context, path string) (*struct{}, error) {
936	// uniform umask needed to make os.Mkdir's mode into something
937	// reproducible
938	syscall.Umask(0o022)
939	f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o640)
940	if err != nil {
941		return nil, fmt.Errorf("create1 WriteFile: %v", err)
942	}
943	_ = f.Close()
944	return &struct{}{}, nil
945}
946
947var createHelper = helpers.Register("create", httpjson.ServePOST(doCreate))
948
949func TestCreate(t *testing.T) {
950	maybeParallel(t)
951	ctx, cancel := context.WithCancel(context.Background())
952	defer cancel()
953	f := &create1{}
954	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil)
955	if err != nil {
956		t.Fatal(err)
957	}
958	defer mnt.Close()
959
960	control := createHelper.Spawn(ctx, t)
961	defer control.Close()
962	var nothing struct{}
963	if err := control.JSON("/").Call(ctx, mnt.Dir+"/foo", &nothing); err != nil {
964		t.Fatalf("calling helper: %v", err)
965	}
966
967	want := fuse.CreateRequest{
968		Name:  "foo",
969		Flags: fuse.OpenReadWrite | fuse.OpenCreate | fuse.OpenTruncate,
970		Mode:  0o640,
971		Umask: 0o022,
972	}
973	if runtime.GOOS == "freebsd" {
974		// FreeBSD doesn't pass truncate to FUSE?; as this is a
975		// Create, that's acceptable
976		want.Flags &^= fuse.OpenTruncate
977	}
978	got := f.f.RecordedCreate()
979	if runtime.GOOS == "linux" {
980		// Linux <3.7 accidentally leaks O_CLOEXEC through to FUSE;
981		// avoid spurious test failures
982		got.Flags &^= fuse.OpenFlags(syscall.O_CLOEXEC)
983	}
984	if g, e := got, want; g != e {
985		t.Fatalf("create saw %+v, want %+v", g, e)
986	}
987}
988
989// Test Create + Write + Remove
990
991type create3file struct {
992	fstestutil.File
993	record.Writes
994}
995
996type create3 struct {
997	fstestutil.Dir
998	f          create3file
999	fooCreated record.MarkRecorder
1000	fooRemoved record.MarkRecorder
1001}
1002
1003func (f *create3) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
1004	if req.Name != "foo" {
1005		log.Printf("ERROR create3.Create unexpected name: %q\n", req.Name)
1006		return nil, nil, syscall.EPERM
1007	}
1008	f.fooCreated.Mark()
1009	return &f.f, &f.f, nil
1010}
1011
1012func (f *create3) Lookup(ctx context.Context, name string) (fs.Node, error) {
1013	if f.fooCreated.Recorded() && !f.fooRemoved.Recorded() && name == "foo" {
1014		return &f.f, nil
1015	}
1016	return nil, syscall.ENOENT
1017}
1018
1019func (f *create3) Remove(ctx context.Context, r *fuse.RemoveRequest) error {
1020	if f.fooCreated.Recorded() && !f.fooRemoved.Recorded() &&
1021		r.Name == "foo" && !r.Dir {
1022		f.fooRemoved.Mark()
1023		return nil
1024	}
1025	return syscall.ENOENT
1026}
1027
1028func doCreateWriteRemove(ctx context.Context, path string) (*struct{}, error) {
1029	if err := ioutil.WriteFile(path, []byte(hi), 0o666); err != nil {
1030		return nil, fmt.Errorf("WriteFile: %v", err)
1031	}
1032	if err := os.Remove(path); err != nil {
1033		return nil, fmt.Errorf("Remove: %v", err)
1034	}
1035	if err := os.Remove(path); !errors.Is(err, syscall.ENOENT) {
1036		return nil, fmt.Errorf("second Remove: wrong error: %v", err)
1037	}
1038	return &struct{}{}, nil
1039}
1040
1041var createWriteRemoveHelper = helpers.Register("createWriteRemove", httpjson.ServePOST(doCreateWriteRemove))
1042
1043func TestCreateWriteRemove(t *testing.T) {
1044	maybeParallel(t)
1045	ctx, cancel := context.WithCancel(context.Background())
1046	defer cancel()
1047	f := &create3{}
1048	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil)
1049	if err != nil {
1050		t.Fatal(err)
1051	}
1052	defer mnt.Close()
1053	control := createWriteRemoveHelper.Spawn(ctx, t)
1054	defer control.Close()
1055	var nothing struct{}
1056	if err := control.JSON("/").Call(ctx, mnt.Dir+"/foo", &nothing); err != nil {
1057		t.Fatalf("calling helper: %v", err)
1058	}
1059}
1060
1061// Test symlink + readlink
1062
1063// is a Node that is a symlink to target
1064type symlink1link struct {
1065	symlink
1066	fs *symlink1
1067}
1068
1069func (f symlink1link) Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error) {
1070	return f.fs.RecordedSymlink().Target, nil
1071}
1072
1073type symlink1 struct {
1074	fstestutil.Dir
1075	record.Symlinks
1076}
1077
1078var _ fs.NodeStringLookuper = (*symlink1)(nil)
1079
1080func (f *symlink1) Lookup(ctx context.Context, name string) (fs.Node, error) {
1081	if name != "symlink.file" {
1082		return nil, syscall.ENOENT
1083	}
1084	if f.RecordedSymlink() == (fuse.SymlinkRequest{}) {
1085		return nil, syscall.ENOENT
1086	}
1087	return symlink1link{fs: f}, nil
1088}
1089
1090func (f *symlink1) Symlink(ctx context.Context, req *fuse.SymlinkRequest) (fs.Node, error) {
1091	if f.RecordedSymlink() != (fuse.SymlinkRequest{}) {
1092		log.Print("this test is not prepared to handle multiple symlinks")
1093		return nil, fuse.Errno(syscall.ENAMETOOLONG)
1094	}
1095	f.Symlinks.Symlink(ctx, req)
1096	return symlink1link{fs: f}, nil
1097}
1098
1099type symlinkHelp struct{}
1100
1101func (i *symlinkHelp) ServeHTTP(w http.ResponseWriter, req *http.Request) {
1102	switch req.URL.Path {
1103	case "/symlink":
1104		httpjson.ServePOST(i.doSymlink).ServeHTTP(w, req)
1105	case "/readlink":
1106		httpjson.ServePOST(i.doReadlink).ServeHTTP(w, req)
1107	default:
1108		http.NotFound(w, req)
1109	}
1110}
1111
1112type symlinkRequest struct {
1113	Target string
1114	Path   string
1115}
1116
1117func (i *symlinkHelp) doSymlink(ctx context.Context, req symlinkRequest) (*struct{}, error) {
1118	if err := os.Symlink(req.Target, req.Path); err != nil {
1119		return nil, err
1120	}
1121	return &struct{}{}, nil
1122}
1123
1124func (i *symlinkHelp) doReadlink(ctx context.Context, path string) (string, error) {
1125	return os.Readlink(path)
1126}
1127
1128var symlinkHelper = helpers.Register("symlink", &symlinkHelp{})
1129
1130func TestSymlink(t *testing.T) {
1131	maybeParallel(t)
1132	ctx, cancel := context.WithCancel(context.Background())
1133	defer cancel()
1134	f := &symlink1{}
1135	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil)
1136	if err != nil {
1137		t.Fatal(err)
1138	}
1139	defer mnt.Close()
1140	control := symlinkHelper.Spawn(ctx, t)
1141	defer control.Close()
1142
1143	const target = "/some-target"
1144	path := mnt.Dir + "/symlink.file"
1145	req := symlinkRequest{
1146		Target: target,
1147		Path:   path,
1148	}
1149	var nothing struct{}
1150	if err := control.JSON("/symlink").Call(ctx, req, &nothing); err != nil {
1151		t.Fatalf("calling helper: %v", err)
1152	}
1153	want := fuse.SymlinkRequest{NewName: "symlink.file", Target: target}
1154	if g, e := f.RecordedSymlink(), want; g != e {
1155		t.Errorf("symlink saw %+v, want %+v", g, e)
1156	}
1157
1158	var gotName string
1159	if err := control.JSON("/readlink").Call(ctx, path, &gotName); err != nil {
1160		t.Fatalf("calling helper: %v", err)
1161	}
1162	if gotName != target {
1163		t.Errorf("os.Readlink = %q; want %q", gotName, target)
1164	}
1165}
1166
1167// Test link
1168
1169type link1 struct {
1170	fstestutil.Dir
1171	record.Links
1172}
1173
1174func (f *link1) Lookup(ctx context.Context, name string) (fs.Node, error) {
1175	if name == "old" {
1176		return fstestutil.File{}, nil
1177	}
1178	return nil, syscall.ENOENT
1179}
1180
1181func (f *link1) Link(ctx context.Context, r *fuse.LinkRequest, old fs.Node) (fs.Node, error) {
1182	f.Links.Link(ctx, r, old)
1183	return fstestutil.File{}, nil
1184}
1185
1186type linkRequest struct {
1187	OldName string
1188	NewName string
1189}
1190
1191func doLink(ctx context.Context, req linkRequest) (*struct{}, error) {
1192	if err := os.Link(req.OldName, req.NewName); err != nil {
1193		return nil, err
1194	}
1195	return &struct{}{}, nil
1196}
1197
1198var linkHelper = helpers.Register("link", httpjson.ServePOST(doLink))
1199
1200func TestLink(t *testing.T) {
1201	maybeParallel(t)
1202	ctx, cancel := context.WithCancel(context.Background())
1203	defer cancel()
1204	f := &link1{}
1205	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil)
1206	if err != nil {
1207		t.Fatal(err)
1208	}
1209	defer mnt.Close()
1210	control := linkHelper.Spawn(ctx, t)
1211	defer control.Close()
1212
1213	req := linkRequest{
1214		OldName: mnt.Dir + "/old",
1215		NewName: mnt.Dir + "/new",
1216	}
1217	var nothing struct{}
1218	if err := control.JSON("/").Call(ctx, req, &nothing); err != nil {
1219		t.Fatalf("calling helper: %v", err)
1220	}
1221
1222	got := f.RecordedLink()
1223	want := fuse.LinkRequest{
1224		NewName: "new",
1225		// unpredictable
1226		OldNode: got.OldNode,
1227	}
1228	if g, e := got, want; g != e {
1229		t.Fatalf("link saw %+v, want %+v", g, e)
1230	}
1231}
1232
1233// Test Rename
1234
1235type rename1 struct {
1236	fstestutil.Dir
1237	renamed record.Counter
1238}
1239
1240func (f *rename1) Lookup(ctx context.Context, name string) (fs.Node, error) {
1241	if name == "old" {
1242		return fstestutil.File{}, nil
1243	}
1244	return nil, syscall.ENOENT
1245}
1246
1247func (f *rename1) Rename(ctx context.Context, r *fuse.RenameRequest, newDir fs.Node) error {
1248	if r.OldName == "old" && r.NewName == "new" && newDir == f {
1249		f.renamed.Inc()
1250		return nil
1251	}
1252	return syscall.EIO
1253}
1254
1255type renameRequest struct {
1256	OldName   string
1257	NewName   string
1258	WantErrno syscall.Errno
1259}
1260
1261func doRename(ctx context.Context, req renameRequest) (*struct{}, error) {
1262	var want error
1263	if req.WantErrno > 0 {
1264		want = req.WantErrno
1265	}
1266	if err := os.Rename(req.OldName, req.NewName); !errors.Is(err, want) {
1267		return nil, fmt.Errorf("wrong error: %v", err)
1268	}
1269	return &struct{}{}, nil
1270}
1271
1272var renameHelper = helpers.Register("rename", httpjson.ServePOST(doRename))
1273
1274func TestRename(t *testing.T) {
1275	maybeParallel(t)
1276	ctx, cancel := context.WithCancel(context.Background())
1277	defer cancel()
1278	f := &rename1{}
1279	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil)
1280	if err != nil {
1281		t.Fatal(err)
1282	}
1283	defer mnt.Close()
1284	control := renameHelper.Spawn(ctx, t)
1285	defer control.Close()
1286
1287	{
1288		req := renameRequest{
1289			OldName: mnt.Dir + "/old",
1290			NewName: mnt.Dir + "/new",
1291		}
1292		var nothing struct{}
1293		if err := control.JSON("/").Call(ctx, req, &nothing); err != nil {
1294			t.Fatalf("calling helper: %v", err)
1295		}
1296	}
1297	if g, e := f.renamed.Count(), uint32(1); g != e {
1298		t.Fatalf("expected rename didn't happen: %d != %d", g, e)
1299	}
1300	{
1301		req := renameRequest{
1302			OldName:   mnt.Dir + "/old2",
1303			NewName:   mnt.Dir + "/new2",
1304			WantErrno: syscall.ENOENT,
1305		}
1306		var nothing struct{}
1307		if err := control.JSON("/").Call(ctx, req, &nothing); err != nil {
1308			t.Fatalf("calling helper: %v", err)
1309		}
1310	}
1311}
1312
1313// Test mknod
1314
1315type mknod1 struct {
1316	fstestutil.Dir
1317	record.Mknods
1318}
1319
1320func (f *mknod1) Mknod(ctx context.Context, r *fuse.MknodRequest) (fs.Node, error) {
1321	f.Mknods.Mknod(ctx, r)
1322	return fifo{}, nil
1323}
1324
1325func doMknod(ctx context.Context, path string) (*struct{}, error) {
1326	// uniform umask needed to make mknod's mode into something
1327	// reproducible
1328	syscall.Umask(0o022)
1329	if err := syscall.Mknod(path, syscall.S_IFIFO|0o660, 123); err != nil {
1330		return nil, err
1331	}
1332	return &struct{}{}, nil
1333}
1334
1335var mknodHelper = helpers.Register("mknod", httpjson.ServePOST(doMknod))
1336
1337func TestMknod(t *testing.T) {
1338	if os.Getuid() != 0 {
1339		t.Skip("skipping unless root")
1340	}
1341	maybeParallel(t)
1342	ctx, cancel := context.WithCancel(context.Background())
1343	defer cancel()
1344
1345	f := &mknod1{}
1346	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil)
1347	if err != nil {
1348		t.Fatal(err)
1349	}
1350	defer mnt.Close()
1351	control := mknodHelper.Spawn(ctx, t)
1352	defer control.Close()
1353
1354	var nothing struct{}
1355	if err := control.JSON("/").Call(ctx, mnt.Dir+"/node", &nothing); err != nil {
1356		t.Fatalf("calling helper: %v", err)
1357	}
1358
1359	want := fuse.MknodRequest{
1360		Name:  "node",
1361		Mode:  os.FileMode(os.ModeNamedPipe | 0o640),
1362		Rdev:  uint32(123),
1363		Umask: 0o022,
1364	}
1365	if runtime.GOOS == "linux" {
1366		// Linux fuse doesn't echo back the rdev if the node
1367		// isn't a device (we're using a FIFO here, as that
1368		// bit is portable.)
1369		want.Rdev = 0
1370	}
1371	if g, e := f.RecordedMknod(), want; g != e {
1372		t.Fatalf("mknod saw %+v, want %+v", g, e)
1373	}
1374}
1375
1376// Test Read served with DataHandle.
1377
1378type dataHandleTest struct {
1379	fstestutil.File
1380}
1381
1382func (dataHandleTest) Attr(ctx context.Context, a *fuse.Attr) error {
1383	a.Mode = 0o666
1384	a.Size = uint64(len(hi))
1385	return nil
1386}
1387
1388func (dataHandleTest) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
1389	return fs.DataHandle([]byte(hi)), nil
1390}
1391
1392func TestDataHandle(t *testing.T) {
1393	maybeParallel(t)
1394	ctx, cancel := context.WithCancel(context.Background())
1395	defer cancel()
1396	f := &dataHandleTest{}
1397	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil)
1398	if err != nil {
1399		t.Fatal(err)
1400	}
1401	defer mnt.Close()
1402	control := readHelper.Spawn(ctx, t)
1403	defer control.Close()
1404
1405	var got readResult
1406	if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil {
1407		t.Fatalf("calling helper: %v", err)
1408	}
1409	if g, e := string(got.Data), hi; g != e {
1410		t.Errorf("readAll = %q, want %q", g, e)
1411	}
1412}
1413
1414// Test interrupt
1415
1416type interrupt struct {
1417	fstestutil.File
1418
1419	// strobes to signal we have a read hanging
1420	hanging chan struct{}
1421	// strobes to signal kernel asked us to interrupt read
1422	interrupted chan struct{}
1423}
1424
1425func (interrupt) Attr(ctx context.Context, a *fuse.Attr) error {
1426	a.Mode = 0o666
1427	a.Size = 1
1428	return nil
1429}
1430
1431func (it *interrupt) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
1432	select {
1433	case it.hanging <- struct{}{}:
1434	default:
1435	}
1436	log.Printf("reading...")
1437	<-ctx.Done()
1438	log.Printf("read done")
1439	select {
1440	case it.interrupted <- struct{}{}:
1441	default:
1442	}
1443	return ctx.Err()
1444}
1445
1446type interruptHelp struct {
1447	mu     sync.Mutex
1448	result *interruptResult
1449}
1450
1451type interruptResult struct {
1452	OK    bool
1453	Read  []byte
1454	Error string
1455}
1456
1457func (i *interruptHelp) ServeHTTP(w http.ResponseWriter, req *http.Request) {
1458	switch req.URL.Path {
1459	case "/read":
1460		httpjson.ServePOST(i.doRead).ServeHTTP(w, req)
1461	case "/report":
1462		httpjson.ServePOST(i.doReport).ServeHTTP(w, req)
1463	default:
1464		http.NotFound(w, req)
1465	}
1466}
1467
1468func (i *interruptHelp) doRead(ctx context.Context, dir string) (struct{}, error) {
1469	log.SetPrefix("interrupt child: ")
1470	log.SetFlags(0)
1471
1472	log.Printf("starting...")
1473
1474	f, err := os.Open(filepath.Join(dir, "child"))
1475	if err != nil {
1476		log.Fatalf("cannot open file: %v", err)
1477	}
1478
1479	i.mu.Lock()
1480	// background this so we can return a response to the test
1481	go func() {
1482		defer i.mu.Unlock()
1483		defer f.Close()
1484		log.Printf("reading...")
1485		buf := make([]byte, 4096)
1486		n, err := syscall.Read(int(f.Fd()), buf)
1487		var r interruptResult
1488		switch err {
1489		case nil:
1490			buf = buf[:n]
1491			log.Printf("read: expected error, got data: %q", buf)
1492			r.Read = buf
1493		case syscall.EINTR:
1494			log.Printf("read: saw EINTR, all good")
1495			r.OK = true
1496		default:
1497			msg := err.Error()
1498			log.Printf("read: wrong error: %s", msg)
1499			r.Error = msg
1500		}
1501		i.result = &r
1502		log.Printf("read done...")
1503	}()
1504	return struct{}{}, nil
1505}
1506
1507func (i *interruptHelp) doReport(ctx context.Context, _ struct{}) (*interruptResult, error) {
1508	i.mu.Lock()
1509	defer i.mu.Unlock()
1510	if i.result == nil {
1511		return nil, errors.New("no result yet")
1512	}
1513	return i.result, nil
1514}
1515
1516var interruptHelper = helpers.Register("interrupt", &interruptHelp{})
1517
1518func TestInterrupt(t *testing.T) {
1519	if runtime.GOOS == "freebsd" {
1520		t.Skip("don't know how to trigger EINTR from read syscall on FreeBSD")
1521	}
1522	maybeParallel(t)
1523	ctx, cancel := context.WithCancel(context.Background())
1524	defer cancel()
1525
1526	f := &interrupt{
1527		hanging:     make(chan struct{}, 1),
1528		interrupted: make(chan struct{}, 1),
1529	}
1530	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil)
1531	if err != nil {
1532		t.Fatal(err)
1533	}
1534	defer mnt.Close()
1535
1536	// start a subprocess that can hang until signaled
1537	control := interruptHelper.Spawn(ctx, t)
1538	defer control.Close()
1539
1540	var nothing struct{}
1541	if err := control.JSON("/read").Call(ctx, mnt.Dir, &nothing); err != nil {
1542		t.Fatalf("calling helper: %v", err)
1543	}
1544
1545	// wait till we're sure it's hanging in read
1546	<-f.hanging
1547
1548	if err := control.Signal(syscall.SIGSTOP); err != nil {
1549		t.Errorf("cannot send SIGSTOP: %v", err)
1550		return
1551	}
1552
1553	// give the process enough time to receive SIGSTOP, otherwise it
1554	// won't interrupt the syscall.
1555	<-f.interrupted
1556
1557	if err := control.Signal(syscall.SIGCONT); err != nil {
1558		t.Errorf("cannot send SIGCONT: %v", err)
1559		return
1560	}
1561
1562	var result interruptResult
1563	if err := control.JSON("/report").Call(ctx, struct{}{}, &result); err != nil {
1564		t.Fatalf("calling helper: %v", err)
1565	}
1566	if !result.OK {
1567		if msg := result.Error; msg != "" {
1568			t.Errorf("unexpected error from read: %v", msg)
1569		}
1570		if data := result.Read; len(data) > 0 {
1571			t.Errorf("unexpected successful read: %q", data)
1572		}
1573	}
1574}
1575
1576// Test deadline
1577
1578type deadline struct {
1579	fstestutil.File
1580}
1581
1582var _ fs.NodeOpener = (*deadline)(nil)
1583
1584func (it *deadline) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
1585	<-ctx.Done()
1586	return nil, ctx.Err()
1587}
1588
1589type openRequest struct {
1590	Path      string
1591	Flags     int
1592	Perm      os.FileMode
1593	WantErrno syscall.Errno
1594}
1595
1596func doOpenErr(ctx context.Context, req openRequest) (*struct{}, error) {
1597	f, err := os.OpenFile(req.Path, req.Flags, req.Perm)
1598	if err == nil {
1599		f.Close()
1600	}
1601	if !errors.Is(err, req.WantErrno) {
1602		return nil, fmt.Errorf("wrong error: %v", err)
1603	}
1604	return &struct{}{}, nil
1605}
1606
1607var openErrHelper = helpers.Register("openErr", httpjson.ServePOST(doOpenErr))
1608
1609func TestDeadline(t *testing.T) {
1610	maybeParallel(t)
1611	ctx, cancel := context.WithCancel(context.Background())
1612	defer cancel()
1613	child := &deadline{}
1614	config := &fs.Config{
1615		WithContext: func(ctx context.Context, req fuse.Request) context.Context {
1616			// return a context that has already deadlined
1617
1618			// Server.serve will cancel the parent context, which will
1619			// cancel this one, so discarding cancel here should be
1620			// safe.
1621			ctx, _ = context.WithDeadline(ctx, time.Unix(0, 0))
1622			return ctx
1623		},
1624	}
1625	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": child}}, config)
1626	if err != nil {
1627		t.Fatal(err)
1628	}
1629	defer mnt.Close()
1630	control := openErrHelper.Spawn(ctx, t)
1631	defer control.Close()
1632
1633	req := openRequest{
1634		Path:  mnt.Dir + "/child",
1635		Flags: os.O_RDONLY,
1636		Perm:  0,
1637		// not caused by signal -> should not get EINTR;
1638		// context.DeadlineExceeded will be translated into EIO
1639		WantErrno: syscall.EIO,
1640	}
1641	var nothing struct{}
1642	if err := control.JSON("/").Call(ctx, req, &nothing); err != nil {
1643		t.Fatalf("calling helper: %v", err)
1644	}
1645}
1646
1647// Test truncate
1648
1649type truncate struct {
1650	fstestutil.File
1651	record.Setattrs
1652}
1653
1654type truncateRequest struct {
1655	Path   string
1656	ToSize int64
1657}
1658
1659func doTruncate(ctx context.Context, req truncateRequest) (*struct{}, error) {
1660	if err := os.Truncate(req.Path, req.ToSize); err != nil {
1661		return nil, err
1662	}
1663	return &struct{}{}, nil
1664}
1665
1666var truncateHelper = helpers.Register("truncate", httpjson.ServePOST(doTruncate))
1667
1668func testTruncate(t *testing.T, toSize int64) {
1669	maybeParallel(t)
1670	ctx, cancel := context.WithCancel(context.Background())
1671	defer cancel()
1672	f := &truncate{}
1673	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil)
1674	if err != nil {
1675		t.Fatal(err)
1676	}
1677	defer mnt.Close()
1678	control := truncateHelper.Spawn(ctx, t)
1679	defer control.Close()
1680
1681	req := truncateRequest{
1682		Path:   mnt.Dir + "/child",
1683		ToSize: toSize,
1684	}
1685	var nothing struct{}
1686	if err := control.JSON("/").Call(ctx, req, &nothing); err != nil {
1687		t.Fatalf("calling helper: %v", err)
1688	}
1689
1690	gotr := f.RecordedSetattr()
1691	if gotr == (fuse.SetattrRequest{}) {
1692		t.Fatalf("no recorded SetattrRequest")
1693	}
1694	if g, e := gotr.Size, uint64(toSize); g != e {
1695		t.Errorf("got Size = %q; want %q", g, e)
1696	}
1697	if g, e := gotr.Valid&^fuse.SetattrLockOwner, fuse.SetattrSize; g != e {
1698		t.Errorf("got Valid = %q; want %q", g, e)
1699	}
1700	t.Logf("Got request: %#v", gotr)
1701}
1702
1703func TestTruncate(t *testing.T) {
1704	t.Run("42", func(t *testing.T) { testTruncate(t, 42) })
1705	t.Run("0", func(t *testing.T) { testTruncate(t, 0) })
1706}
1707
1708// Test ftruncate
1709
1710type ftruncate struct {
1711	fstestutil.File
1712	record.Setattrs
1713}
1714
1715func doFtruncate(ctx context.Context, req truncateRequest) (*struct{}, error) {
1716	f, err := os.OpenFile(req.Path, os.O_WRONLY, 0o666)
1717	if err != nil {
1718		return nil, err
1719	}
1720	defer f.Close()
1721	if err := f.Truncate(req.ToSize); err != nil {
1722		return nil, err
1723	}
1724	return &struct{}{}, nil
1725}
1726
1727var ftruncateHelper = helpers.Register("ftruncate", httpjson.ServePOST(doFtruncate))
1728
1729func testFtruncate(t *testing.T, toSize int64) {
1730	maybeParallel(t)
1731	ctx, cancel := context.WithCancel(context.Background())
1732	defer cancel()
1733	f := &ftruncate{}
1734	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil)
1735	if err != nil {
1736		t.Fatal(err)
1737	}
1738	defer mnt.Close()
1739	control := ftruncateHelper.Spawn(ctx, t)
1740	defer control.Close()
1741
1742	req := truncateRequest{
1743		Path:   mnt.Dir + "/child",
1744		ToSize: toSize,
1745	}
1746	var nothing struct{}
1747	if err := control.JSON("/").Call(ctx, req, &nothing); err != nil {
1748		t.Fatalf("calling helper: %v", err)
1749	}
1750
1751	gotr := f.RecordedSetattr()
1752	if gotr == (fuse.SetattrRequest{}) {
1753		t.Fatalf("no recorded SetattrRequest")
1754	}
1755	if g, e := gotr.Size, uint64(toSize); g != e {
1756		t.Errorf("got Size = %q; want %q", g, e)
1757	}
1758	if g, e := gotr.Valid&^fuse.SetattrLockOwner, fuse.SetattrHandle|fuse.SetattrSize; g != e {
1759		t.Errorf("got Valid = %q; want %q", g, e)
1760	}
1761	t.Logf("Got request: %#v", gotr)
1762}
1763
1764func TestFtruncate(t *testing.T) {
1765	t.Run("42", func(t *testing.T) { testFtruncate(t, 42) })
1766	t.Run("0", func(t *testing.T) { testFtruncate(t, 0) })
1767}
1768
1769// Test opening existing file truncates
1770
1771type truncateWithOpen struct {
1772	fstestutil.File
1773	record.Setattrs
1774}
1775
1776func doTruncateWithOpen(ctx context.Context, path string) (*struct{}, error) {
1777	fil, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0o666)
1778	if err != nil {
1779		return nil, err
1780	}
1781	_ = fil.Close()
1782	return &struct{}{}, nil
1783}
1784
1785var truncateWithOpenHelper = helpers.Register("truncateWithOpen", httpjson.ServePOST(doTruncateWithOpen))
1786
1787func TestTruncateWithOpen(t *testing.T) {
1788	maybeParallel(t)
1789	ctx, cancel := context.WithCancel(context.Background())
1790	defer cancel()
1791	f := &truncateWithOpen{}
1792	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil)
1793	if err != nil {
1794		t.Fatal(err)
1795	}
1796	defer mnt.Close()
1797	control := truncateWithOpenHelper.Spawn(ctx, t)
1798	defer control.Close()
1799
1800	var nothing struct{}
1801	if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &nothing); err != nil {
1802		t.Fatalf("calling helper: %v", err)
1803	}
1804
1805	gotr := f.RecordedSetattr()
1806	if gotr == (fuse.SetattrRequest{}) {
1807		t.Fatalf("no recorded SetattrRequest")
1808	}
1809	if g, e := gotr.Size, uint64(0); g != e {
1810		t.Errorf("got Size = %q; want %q", g, e)
1811	}
1812	got := gotr.Valid
1813	if runtime.GOOS == "freebsd" {
1814		// FreeBSD seems to set this but Linux doesn't??? Want to
1815		// detect if Linux starts adding it. I assume the logic is
1816		// something like the truncate happens before the open; or it
1817		// just slipped by.
1818		got &^= fuse.SetattrHandle
1819	}
1820	if g, e := got&^fuse.SetattrLockOwner, fuse.SetattrSize; g != e {
1821		t.Errorf("got Valid = %q; want %q", g, e)
1822	}
1823	t.Logf("Got request: %#v", gotr)
1824}
1825
1826// Test readdir calling ReadDirAll
1827
1828type readDirAll struct {
1829	fstestutil.Dir
1830}
1831
1832func (d *readDirAll) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
1833	return []fuse.Dirent{
1834		{Name: "one", Inode: 11, Type: fuse.DT_Dir},
1835		{Name: "three", Inode: 13},
1836		{Name: "two", Inode: 12, Type: fuse.DT_File},
1837	}, nil
1838}
1839
1840func doReaddir(ctx context.Context, path string) ([]string, error) {
1841	f, err := os.Open(path)
1842	if err != nil {
1843		return nil, err
1844	}
1845	defer f.Close()
1846
1847	// go Readdir is just Readdirnames + Lstat, there's no point in
1848	// testing that here; we have no consumption API for the real
1849	// dirent data
1850	names, err := f.Readdirnames(-1)
1851	if err != nil {
1852		return nil, err
1853	}
1854	return names, nil
1855}
1856
1857var readdirHelper = helpers.Register("readdir", httpjson.ServePOST(doReaddir))
1858
1859func TestReadDirAll(t *testing.T) {
1860	maybeParallel(t)
1861	ctx, cancel := context.WithCancel(context.Background())
1862	defer cancel()
1863	f := &readDirAll{}
1864	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil)
1865	if err != nil {
1866		t.Fatal(err)
1867	}
1868	defer mnt.Close()
1869	control := readdirHelper.Spawn(ctx, t)
1870	defer control.Close()
1871
1872	var names []string
1873	if err := control.JSON("/").Call(ctx, mnt.Dir, &names); err != nil {
1874		t.Fatalf("calling helper: %v", err)
1875	}
1876	t.Logf("Got readdir: %q", names)
1877
1878	if len(names) != 3 ||
1879		names[0] != "one" ||
1880		names[1] != "three" ||
1881		names[2] != "two" {
1882		t.Errorf(`expected 3 entries of "one", "three", "two", got: %q`, names)
1883		return
1884	}
1885}
1886
1887type readDirAllBad struct {
1888	fstestutil.Dir
1889}
1890
1891func (d *readDirAllBad) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
1892	r := []fuse.Dirent{
1893		{Name: "one", Inode: 11, Type: fuse.DT_Dir},
1894		{Name: "three", Inode: 13},
1895		{Name: "two", Inode: 12, Type: fuse.DT_File},
1896	}
1897	// pick a really distinct error, to identify it later
1898	return r, fuse.Errno(syscall.ENAMETOOLONG)
1899}
1900
1901func doReaddirBad(ctx context.Context, path string) (*struct{}, error) {
1902	fil, err := os.Open(path)
1903	if err != nil {
1904		return nil, err
1905	}
1906	defer fil.Close()
1907
1908	var names []string
1909	for {
1910		n, err := fil.Readdirnames(1)
1911		if err != nil {
1912			if !errors.Is(err, syscall.ENAMETOOLONG) {
1913				return nil, fmt.Errorf("wrong error: %v", err)
1914			}
1915			break
1916		}
1917		names = append(names, n...)
1918	}
1919
1920	log.Printf("Got readdir: %q", names)
1921
1922	// TODO could serve partial results from ReadDirAll but the
1923	// shandle.readData mechanism makes that awkward.
1924	if len(names) != 0 {
1925		return nil, fmt.Errorf("expected 0 entries, got: %q", names)
1926	}
1927	return &struct{}{}, nil
1928}
1929
1930var readdirBadHelper = helpers.Register("readdirBad", httpjson.ServePOST(doReaddirBad))
1931
1932func TestReadDirAllBad(t *testing.T) {
1933	maybeParallel(t)
1934	ctx, cancel := context.WithCancel(context.Background())
1935	defer cancel()
1936	f := &readDirAllBad{}
1937	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil)
1938	if err != nil {
1939		t.Fatal(err)
1940	}
1941	defer mnt.Close()
1942	control := readdirBadHelper.Spawn(ctx, t)
1943	defer control.Close()
1944
1945	var nothing struct{}
1946	if err := control.JSON("/").Call(ctx, mnt.Dir, &nothing); err != nil {
1947		t.Fatalf("calling helper: %v", err)
1948	}
1949}
1950
1951// Test readdir without any ReadDir methods implemented.
1952
1953type readDirNotImplemented struct {
1954	fstestutil.Dir
1955}
1956
1957func TestReadDirNotImplemented(t *testing.T) {
1958	maybeParallel(t)
1959	ctx, cancel := context.WithCancel(context.Background())
1960	defer cancel()
1961	f := &readDirNotImplemented{}
1962	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil)
1963	if err != nil {
1964		t.Fatal(err)
1965	}
1966	defer mnt.Close()
1967	control := readdirHelper.Spawn(ctx, t)
1968	defer control.Close()
1969
1970	var names []string
1971	if err := control.JSON("/").Call(ctx, mnt.Dir, &names); err != nil {
1972		t.Fatalf("calling helper: %v", err)
1973	}
1974	t.Logf("Got readdir: %q", names)
1975
1976	if len(names) != 0 {
1977		t.Errorf(`expected 0 entries, got: %q`, names)
1978	}
1979}
1980
1981type readDirAllRewind struct {
1982	fstestutil.Dir
1983	entries atomic.Value
1984}
1985
1986func (d *readDirAllRewind) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
1987	entries := d.entries.Load().([]fuse.Dirent)
1988	return entries, nil
1989}
1990
1991type readdirRewindHelp struct {
1992	mu   sync.Mutex
1993	file *os.File
1994}
1995
1996func (r *readdirRewindHelp) ServeHTTP(w http.ResponseWriter, req *http.Request) {
1997	switch req.URL.Path {
1998	case "/openReaddir":
1999		httpjson.ServePOST(r.doOpenReaddir).ServeHTTP(w, req)
2000	case "/rewindReaddirClose":
2001		httpjson.ServePOST(r.doRewindReaddirClose).ServeHTTP(w, req)
2002	default:
2003		http.NotFound(w, req)
2004	}
2005}
2006
2007func (r *readdirRewindHelp) doOpenReaddir(ctx context.Context, path string) ([]string, error) {
2008	r.mu.Lock()
2009	defer r.mu.Unlock()
2010	f, err := os.Open(path)
2011	if err != nil {
2012		return nil, err
2013	}
2014	r.file = f
2015	names, err := f.Readdirnames(100)
2016	if err != nil {
2017		return nil, err
2018	}
2019	return names, nil
2020}
2021
2022func (r *readdirRewindHelp) doRewindReaddirClose(ctx context.Context, _ struct{}) ([]string, error) {
2023	r.mu.Lock()
2024	defer r.mu.Unlock()
2025	defer r.file.Close()
2026	if _, err := r.file.Seek(0, io.SeekStart); err != nil {
2027		return nil, err
2028	}
2029	names, err := r.file.Readdirnames(100)
2030	if err != nil {
2031		return nil, err
2032	}
2033	return names, nil
2034}
2035
2036var readdirRewindHelper = helpers.Register("readdirRewind", &readdirRewindHelp{})
2037
2038func TestReadDirAllRewind(t *testing.T) {
2039	maybeParallel(t)
2040	ctx, cancel := context.WithCancel(context.Background())
2041	defer cancel()
2042	f := &readDirAllRewind{}
2043	f.entries.Store([]fuse.Dirent{
2044		{Name: "one", Inode: 11, Type: fuse.DT_Dir},
2045	})
2046	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil)
2047	if err != nil {
2048		t.Fatal(err)
2049	}
2050	defer mnt.Close()
2051	control := readdirRewindHelper.Spawn(ctx, t)
2052	defer control.Close()
2053
2054	{
2055		var names []string
2056		if err := control.JSON("/openReaddir").Call(ctx, mnt.Dir, &names); err != nil {
2057			t.Fatalf("calling helper: %v", err)
2058		}
2059		t.Logf("Got readdir: %q", names)
2060		if len(names) != 1 ||
2061			names[0] != "one" {
2062			t.Errorf(`expected  entry of "one", got: %q`, names)
2063			return
2064		}
2065	}
2066
2067	f.entries.Store([]fuse.Dirent{
2068		{Name: "two", Inode: 12, Type: fuse.DT_File},
2069		{Name: "one", Inode: 11, Type: fuse.DT_Dir},
2070	})
2071	{
2072		var names []string
2073		if err := control.JSON("/rewindReaddirClose").Call(ctx, struct{}{}, &names); err != nil {
2074			t.Fatalf("calling helper: %v", err)
2075		}
2076		t.Logf("Got readdir: %q", names)
2077		if len(names) != 2 ||
2078			names[0] != "two" ||
2079			names[1] != "one" {
2080			t.Errorf(`expected 2 entries of "two", "one", got: %q`, names)
2081			return
2082		}
2083	}
2084}
2085
2086// Test Chmod.
2087
2088type chmod struct {
2089	fstestutil.File
2090	record.Setattrs
2091}
2092
2093func (f *chmod) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error {
2094	if !req.Valid.Mode() {
2095		log.Printf("setattr not a chmod: %v", req.Valid)
2096		return syscall.EIO
2097	}
2098	f.Setattrs.Setattr(ctx, req, resp)
2099	return nil
2100}
2101
2102type chmodRequest struct {
2103	Path string
2104	Mode os.FileMode
2105}
2106
2107func doChmod(ctx context.Context, req chmodRequest) (*struct{}, error) {
2108	if err := os.Chmod(req.Path, req.Mode); err != nil {
2109		return nil, err
2110	}
2111	return &struct{}{}, nil
2112}
2113
2114var chmodHelper = helpers.Register("chmod", httpjson.ServePOST(doChmod))
2115
2116func TestChmod(t *testing.T) {
2117	maybeParallel(t)
2118	ctx, cancel := context.WithCancel(context.Background())
2119	defer cancel()
2120	f := &chmod{}
2121	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil)
2122	if err != nil {
2123		t.Fatal(err)
2124	}
2125	defer mnt.Close()
2126	control := chmodHelper.Spawn(ctx, t)
2127	defer control.Close()
2128
2129	req := chmodRequest{
2130		Path: mnt.Dir + "/child",
2131		Mode: 0o764,
2132	}
2133	var nothing struct{}
2134	if err := control.JSON("/").Call(ctx, req, &nothing); err != nil {
2135		t.Fatalf("calling helper: %v", err)
2136	}
2137
2138	got := f.RecordedSetattr()
2139	if g, e := got.Mode.Perm(), os.FileMode(0o764); g != e {
2140		t.Errorf("wrong mode: %o %v != %o %v", g, g, e, e)
2141	}
2142	ftype := got.Mode & os.ModeType
2143	switch {
2144	case runtime.GOOS == "freebsd" && ftype == os.ModeIrregular:
2145		// acceptable but unfortunate
2146	default:
2147		if !ftype.IsRegular() {
2148			t.Errorf("mode is not regular: %o %v", got.Mode, got.Mode)
2149		}
2150	}
2151}
2152
2153// Test open
2154
2155type open struct {
2156	fstestutil.File
2157	record.Opens
2158}
2159
2160func (f *open) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
2161	f.Opens.Open(ctx, req, resp)
2162	// pick a really distinct error, to identify it later
2163	return nil, fuse.Errno(syscall.ENAMETOOLONG)
2164}
2165
2166func TestOpen(t *testing.T) {
2167	maybeParallel(t)
2168	ctx, cancel := context.WithCancel(context.Background())
2169	defer cancel()
2170	f := &open{}
2171	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil)
2172	if err != nil {
2173		t.Fatal(err)
2174	}
2175	defer mnt.Close()
2176	control := openErrHelper.Spawn(ctx, t)
2177	defer control.Close()
2178
2179	req := openRequest{
2180		Path:  mnt.Dir + "/child",
2181		Flags: os.O_WRONLY | os.O_APPEND,
2182		// note: mode only matters with O_CREATE
2183		Perm:      0,
2184		WantErrno: syscall.ENAMETOOLONG,
2185	}
2186	var nothing struct{}
2187	if err := control.JSON("/").Call(ctx, req, &nothing); err != nil {
2188		t.Fatalf("calling helper: %v", err)
2189	}
2190
2191	want := fuse.OpenRequest{Dir: false, Flags: fuse.OpenWriteOnly | fuse.OpenAppend}
2192	got := f.RecordedOpen()
2193
2194	if runtime.GOOS == "linux" {
2195		// Linux <3.7 accidentally leaks O_CLOEXEC through to FUSE;
2196		// avoid spurious test failures
2197		got.Flags &^= fuse.OpenFlags(syscall.O_CLOEXEC)
2198	}
2199	if runtime.GOOS == "freebsd" {
2200		// FreeBSD doesn't pass append to FUSE?
2201		want.Flags &^= fuse.OpenAppend
2202	}
2203
2204	if g, e := got, want; g != e {
2205		t.Errorf("open saw %+v, want %+v", g, e)
2206		return
2207	}
2208}
2209
2210type openNonSeekable struct {
2211	fstestutil.File
2212}
2213
2214func (f *openNonSeekable) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
2215	resp.Flags |= fuse.OpenNonSeekable
2216	return f, nil
2217}
2218
2219func doOpenNonseekable(ctx context.Context, path string) (*struct{}, error) {
2220	f, err := os.Open(path)
2221	if err != nil {
2222		return nil, err
2223	}
2224	defer f.Close()
2225
2226	if _, err := f.Seek(0, io.SeekStart); !errors.Is(err, syscall.ESPIPE) {
2227		return nil, fmt.Errorf("wrong error: %v", err)
2228	}
2229	return &struct{}{}, nil
2230}
2231
2232var openNonseekableHelper = helpers.Register("openNonseekable", httpjson.ServePOST(doOpenNonseekable))
2233
2234func TestOpenNonSeekable(t *testing.T) {
2235	if runtime.GOOS == "freebsd" {
2236		// behavior observed: seek calls succeed, but file offset does
2237		// not change
2238		t.Skip("FreeBSD seems to ignore OpenNonSeekable")
2239	}
2240
2241	maybeParallel(t)
2242	ctx, cancel := context.WithCancel(context.Background())
2243	defer cancel()
2244	f := &openNonSeekable{}
2245	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil)
2246	if err != nil {
2247		t.Fatal(err)
2248	}
2249	defer mnt.Close()
2250	control := openNonseekableHelper.Spawn(ctx, t)
2251	defer control.Close()
2252
2253	var nothing struct{}
2254	if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &nothing); err != nil {
2255		t.Fatalf("calling helper: %v", err)
2256	}
2257}
2258
2259// Test Fsync on a dir
2260
2261type fsyncDir struct {
2262	fstestutil.Dir
2263	record.Fsyncs
2264}
2265
2266func doOpenFsyncClose(ctx context.Context, path string) (*struct{}, error) {
2267	f, err := os.Open(path)
2268	if err != nil {
2269		return nil, err
2270	}
2271	defer f.Close()
2272	err = f.Sync()
2273	if err != nil {
2274		return nil, err
2275	}
2276	return &struct{}{}, nil
2277}
2278
2279var openFsyncCloseHelper = helpers.Register("openFsyncClose", httpjson.ServePOST(doOpenFsyncClose))
2280
2281func TestFsyncDir(t *testing.T) {
2282	maybeParallel(t)
2283	ctx, cancel := context.WithCancel(context.Background())
2284	defer cancel()
2285	f := &fsyncDir{}
2286	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{f}, nil)
2287	if err != nil {
2288		t.Fatal(err)
2289	}
2290	defer mnt.Close()
2291	control := openFsyncCloseHelper.Spawn(ctx, t)
2292	defer control.Close()
2293
2294	var nothing struct{}
2295	if err := control.JSON("/").Call(ctx, mnt.Dir, &nothing); err != nil {
2296		t.Fatalf("calling helper: %v", err)
2297	}
2298
2299	got := f.RecordedFsync()
2300	want := fuse.FsyncRequest{
2301		Flags: 0,
2302		Dir:   true,
2303		// unpredictable
2304		Handle: got.Handle,
2305	}
2306	if g, e := got, want; g != e {
2307		t.Fatalf("fsyncDir saw %+v, want %+v", g, e)
2308	}
2309}
2310
2311// Test Getxattr
2312
2313type getxattr struct {
2314	fstestutil.File
2315	record.Getxattrs
2316}
2317
2318func (f *getxattr) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {
2319	f.Getxattrs.Getxattr(ctx, req, resp)
2320	resp.Xattr = []byte("hello, world")
2321	return nil
2322}
2323
2324type getxattrRequest struct {
2325	Path      string
2326	Name      string
2327	Size      int
2328	WantErrno syscall.Errno
2329}
2330
2331type getxattrResult struct {
2332	// only one of Data and Size is set
2333
2334	Data []byte
2335	Size int
2336}
2337
2338func doGetxattr(ctx context.Context, req getxattrRequest) (*getxattrResult, error) {
2339	buf := make([]byte, req.Size)
2340	n, err := unix.Getxattr(req.Path, req.Name, buf)
2341	if req.WantErrno != 0 {
2342		if !errors.Is(err, req.WantErrno) {
2343			return nil, fmt.Errorf("wrong error: %v", err)
2344		}
2345		return nil, nil
2346	}
2347	if err != nil {
2348		return nil, fmt.Errorf("unexpected error: %v", err)
2349	}
2350	if req.Size == 0 {
2351		r := &getxattrResult{
2352			Size: n,
2353		}
2354		return r, nil
2355	}
2356	r := &getxattrResult{
2357		Data: buf[:n],
2358	}
2359	return r, nil
2360}
2361
2362var getxattrHelper = helpers.Register("getxattr", httpjson.ServePOST(doGetxattr))
2363
2364func TestGetxattr(t *testing.T) {
2365	maybeParallel(t)
2366	ctx, cancel := context.WithCancel(context.Background())
2367	defer cancel()
2368	f := &getxattr{}
2369	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil)
2370	if err != nil {
2371		t.Fatal(err)
2372	}
2373	defer mnt.Close()
2374	control := getxattrHelper.Spawn(ctx, t)
2375	defer control.Close()
2376
2377	req := getxattrRequest{
2378		Path: mnt.Dir + "/child",
2379		Name: "user.dummyxattr",
2380		Size: 8192,
2381	}
2382	var res getxattrResult
2383	if err := control.JSON("/").Call(ctx, req, &res); err != nil {
2384		t.Fatalf("calling helper: %v", err)
2385	}
2386	if g, e := string(res.Data), "hello, world"; g != e {
2387		t.Errorf("wrong getxattr content: %#v != %#v", g, e)
2388	}
2389	seen := f.RecordedGetxattr()
2390	if g, e := seen.Name, "user.dummyxattr"; g != e {
2391		t.Errorf("wrong getxattr name: %#v != %#v", g, e)
2392	}
2393}
2394
2395// Test Getxattr that has no space to return value
2396
2397type getxattrTooSmall struct {
2398	fstestutil.File
2399}
2400
2401func (f *getxattrTooSmall) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {
2402	resp.Xattr = []byte("hello, world")
2403	return nil
2404}
2405
2406func TestGetxattrTooSmall(t *testing.T) {
2407	maybeParallel(t)
2408	ctx, cancel := context.WithCancel(context.Background())
2409	defer cancel()
2410	f := &getxattrTooSmall{}
2411	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil)
2412	if err != nil {
2413		t.Fatal(err)
2414	}
2415	defer mnt.Close()
2416	control := getxattrHelper.Spawn(ctx, t)
2417	defer control.Close()
2418
2419	req := getxattrRequest{
2420		Path:      mnt.Dir + "/child",
2421		Name:      "user.dummyxattr",
2422		Size:      3,
2423		WantErrno: syscall.ERANGE,
2424	}
2425	var res getxattrResult
2426	if err := control.JSON("/").Call(ctx, req, &res); err != nil {
2427		t.Fatalf("calling helper: %v", err)
2428	}
2429}
2430
2431// Test Getxattr used to probe result size
2432
2433type getxattrSize struct {
2434	fstestutil.File
2435}
2436
2437func (f *getxattrSize) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {
2438	resp.Xattr = []byte("hello, world")
2439	return nil
2440}
2441
2442func TestGetxattrSize(t *testing.T) {
2443	maybeParallel(t)
2444	ctx, cancel := context.WithCancel(context.Background())
2445	defer cancel()
2446	f := &getxattrSize{}
2447	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil)
2448	if err != nil {
2449		t.Fatal(err)
2450	}
2451	defer mnt.Close()
2452	control := getxattrHelper.Spawn(ctx, t)
2453	defer control.Close()
2454
2455	req := getxattrRequest{
2456		Path: mnt.Dir + "/child",
2457		Name: "user.dummyxattr",
2458		Size: 0,
2459	}
2460	var res getxattrResult
2461	if err := control.JSON("/").Call(ctx, req, &res); err != nil {
2462		t.Fatalf("calling helper: %v", err)
2463	}
2464	if g, e := res.Size, len("hello, world"); g != e {
2465		t.Errorf("Getxattr incorrect size: %d != %d", g, e)
2466	}
2467}
2468
2469// Test Listxattr
2470
2471type listxattr struct {
2472	fstestutil.File
2473	record.Listxattrs
2474}
2475
2476func (f *listxattr) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error {
2477	f.Listxattrs.Listxattr(ctx, req, resp)
2478	resp.Append("user.one", "user.two")
2479	return nil
2480}
2481
2482type listxattrRequest struct {
2483	Path      string
2484	Size      int
2485	WantErrno syscall.Errno
2486}
2487
2488type listxattrResult struct {
2489	// only one of Data and Size is set
2490
2491	Data []byte
2492	Size int
2493}
2494
2495func doListxattr(ctx context.Context, req listxattrRequest) (*listxattrResult, error) {
2496	buf := make([]byte, req.Size)
2497	n, err := unix.Listxattr(req.Path, buf)
2498	if req.WantErrno != 0 {
2499		if !errors.Is(err, req.WantErrno) {
2500			return nil, fmt.Errorf("wrong error: %v", err)
2501		}
2502		return nil, nil
2503	}
2504	if err != nil {
2505		return nil, fmt.Errorf("unexpected error: %v", err)
2506	}
2507	if req.Size == 0 {
2508		r := &listxattrResult{
2509			Size: n,
2510		}
2511		return r, nil
2512	}
2513	buf = buf[:n]
2514
2515	if runtime.GOOS == "freebsd" {
2516		// Normalize FreeBSD listxattr syscall response to the same
2517		// zero-terminated format as others. This is just the
2518		// client-side syscall; the FUSE interaction still uses the
2519		// nil-terminated strings with namespace prefixes.
2520		//
2521		// Length-prefixed, no namespace (you have to query per
2522		// namespace on FreeBSD).
2523		var out []byte
2524		for len(buf) > 0 {
2525			size := int(buf[0])
2526			out = append(out, []byte("user.")...)
2527			out = append(out, buf[1:1+size]...)
2528			out = append(out, '\x00')
2529			buf = buf[1+size:]
2530		}
2531		buf = out
2532	}
2533
2534	r := &listxattrResult{
2535		Data: buf,
2536	}
2537	return r, nil
2538}
2539
2540var listxattrHelper = helpers.Register("listxattr", httpjson.ServePOST(doListxattr))
2541
2542func TestListxattr(t *testing.T) {
2543	maybeParallel(t)
2544	ctx, cancel := context.WithCancel(context.Background())
2545	defer cancel()
2546	f := &listxattr{}
2547	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil)
2548	if err != nil {
2549		t.Fatal(err)
2550	}
2551	defer mnt.Close()
2552	control := listxattrHelper.Spawn(ctx, t)
2553	defer control.Close()
2554
2555	req := listxattrRequest{
2556		Path: mnt.Dir + "/child",
2557		Size: 8192,
2558	}
2559	var res listxattrResult
2560	if err := control.JSON("/").Call(ctx, req, &res); err != nil {
2561		t.Fatalf("calling helper: %v", err)
2562	}
2563	if g, e := string(res.Data), "user.one\x00user.two\x00"; g != e {
2564		t.Errorf("wrong listxattr content: %#v != %#v", g, e)
2565	}
2566
2567	want := fuse.ListxattrRequest{
2568		Size: 8192,
2569	}
2570	if runtime.GOOS == "freebsd" {
2571		// FreeBSD seems to always probe the size for you, even when
2572		// userspace passed a large enough buffer. This means two (or
2573		// more, if the size keeps growing!) Listxattr FUSE requests,
2574		// with the last one likely having the perfect size (except
2575		// when the size changed downward between the calls). Blargh.
2576		want.Size = uint32(len("user.one\x00user.two\x00"))
2577	}
2578	if g, e := f.RecordedListxattr(), want; g != e {
2579		t.Fatalf("listxattr saw %+v, want %+v", g, e)
2580	}
2581}
2582
2583// Test Listxattr that has no space to return value
2584
2585type listxattrTooSmall struct {
2586	fstestutil.File
2587}
2588
2589func (f *listxattrTooSmall) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error {
2590	resp.Xattr = []byte("one\x00two\x00")
2591	return nil
2592}
2593
2594func TestListxattrTooSmall(t *testing.T) {
2595	if runtime.GOOS == "freebsd" {
2596		t.Skip("FreeBSD xattr list format is different and the kernel has intermediate buffer; can't drive FUSE requests directly from userspace")
2597	}
2598	maybeParallel(t)
2599	ctx, cancel := context.WithCancel(context.Background())
2600	defer cancel()
2601	f := &listxattrTooSmall{}
2602	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil)
2603	if err != nil {
2604		t.Fatal(err)
2605	}
2606	defer mnt.Close()
2607	control := listxattrHelper.Spawn(ctx, t)
2608	defer control.Close()
2609
2610	req := listxattrRequest{
2611		Path:      mnt.Dir + "/child",
2612		Size:      3,
2613		WantErrno: syscall.ERANGE,
2614	}
2615	var res listxattrResult
2616	if err := control.JSON("/").Call(ctx, req, &res); err != nil {
2617		t.Fatalf("calling helper: %v", err)
2618	}
2619}
2620
2621// Test Listxattr used to probe result size
2622
2623type listxattrSize struct {
2624	fstestutil.File
2625}
2626
2627func (f *listxattrSize) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error {
2628	resp.Xattr = []byte("one\x00two\x00")
2629	return nil
2630}
2631
2632func TestListxattrSize(t *testing.T) {
2633	if runtime.GOOS == "freebsd" {
2634		t.Skip("FreeBSD xattr list format is different and the kernel has intermediate buffer; can't drive FUSE requests directly from userspace")
2635	}
2636	maybeParallel(t)
2637	ctx, cancel := context.WithCancel(context.Background())
2638	defer cancel()
2639	f := &listxattrSize{}
2640	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil)
2641	if err != nil {
2642		t.Fatal(err)
2643	}
2644	defer mnt.Close()
2645	control := listxattrHelper.Spawn(ctx, t)
2646	defer control.Close()
2647
2648	req := listxattrRequest{
2649		Path: mnt.Dir + "/child",
2650		Size: 0,
2651	}
2652	var res listxattrResult
2653	if err := control.JSON("/").Call(ctx, req, &res); err != nil {
2654		t.Fatalf("calling helper: %v", err)
2655	}
2656	if g, e := res.Size, len("one\x00two\x00"); g != e {
2657		t.Errorf("Listxattr incorrect size: %d != %d", g, e)
2658	}
2659}
2660
2661// Test Setxattr
2662
2663type setxattr struct {
2664	fstestutil.File
2665	record.Setxattrs
2666}
2667
2668type setxattrRequest struct {
2669	Path  string
2670	Name  string
2671	Data  []byte
2672	Flags int
2673}
2674
2675func doSetxattr(ctx context.Context, req setxattrRequest) (*struct{}, error) {
2676	if err := unix.Setxattr(req.Path, req.Name, req.Data, req.Flags); err != nil {
2677		return nil, err
2678	}
2679	return &struct{}{}, nil
2680}
2681
2682var setxattrHelper = helpers.Register("setxattr", httpjson.ServePOST(doSetxattr))
2683
2684func testSetxattr(t *testing.T, size int) {
2685	const linux_XATTR_NAME_MAX = 64 * 1024
2686	if size > linux_XATTR_NAME_MAX && runtime.GOOS == "linux" {
2687		t.Skip("large xattrs are not supported by linux")
2688	}
2689	if runtime.GOOS == "freebsd" && size > 135106 {
2690		// no idea what that magic number is but it seems like a very
2691		// repeatable exact cutoff for me
2692		t.Skip("FreeBSD setxattr seems to hang on large values")
2693	}
2694
2695	maybeParallel(t)
2696	ctx, cancel := context.WithCancel(context.Background())
2697	defer cancel()
2698	f := &setxattr{}
2699	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil)
2700	if err != nil {
2701		t.Fatal(err)
2702	}
2703	defer mnt.Close()
2704	control := setxattrHelper.Spawn(ctx, t)
2705	defer control.Close()
2706
2707	const g = "hello, world"
2708	greeting := strings.Repeat(g, size/len(g)+1)[:size]
2709	req := setxattrRequest{
2710		Path:  mnt.Dir + "/child",
2711		Name:  "user.greeting",
2712		Data:  []byte(greeting),
2713		Flags: 0,
2714	}
2715	var nothing struct{}
2716	if err := control.JSON("/").Call(ctx, req, &nothing); err != nil {
2717		t.Fatalf("calling helper: %v", err)
2718	}
2719
2720	// fuse.SetxattrRequest contains a byte slice and thus cannot be
2721	// directly compared
2722	got := f.RecordedSetxattr()
2723
2724	if g, e := got.Name, "user.greeting"; g != e {
2725		t.Errorf("Setxattr incorrect name: %q != %q", g, e)
2726	}
2727
2728	if g, e := got.Flags, uint32(0); g != e {
2729		t.Errorf("Setxattr incorrect flags: %d != %d", g, e)
2730	}
2731
2732	if g, e := string(got.Xattr), greeting; g != e {
2733		t.Errorf("Setxattr incorrect data: %q != %q", g, e)
2734	}
2735}
2736
2737func TestSetxattr(t *testing.T) {
2738	t.Run("20", func(t *testing.T) { testSetxattr(t, 20) })
2739	t.Run("64kB", func(t *testing.T) { testSetxattr(t, 64*1024) })
2740	t.Run("16MB", func(t *testing.T) { testSetxattr(t, 16*1024*1024) })
2741}
2742
2743// Test Removexattr
2744
2745type removexattr struct {
2746	fstestutil.File
2747	record.Removexattrs
2748}
2749
2750type removexattrRequest struct {
2751	Path string
2752	Name string
2753}
2754
2755func doRemovexattr(ctx context.Context, req removexattrRequest) (*struct{}, error) {
2756	if err := unix.Removexattr(req.Path, req.Name); err != nil {
2757		return nil, err
2758	}
2759	return &struct{}{}, nil
2760}
2761
2762var removexattrHelper = helpers.Register("removexattr", httpjson.ServePOST(doRemovexattr))
2763
2764func TestRemovexattr(t *testing.T) {
2765	maybeParallel(t)
2766	ctx, cancel := context.WithCancel(context.Background())
2767	defer cancel()
2768	f := &removexattr{}
2769	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": f}}, nil)
2770	if err != nil {
2771		t.Fatal(err)
2772	}
2773	defer mnt.Close()
2774	control := removexattrHelper.Spawn(ctx, t)
2775	defer control.Close()
2776
2777	req := removexattrRequest{
2778		Path: mnt.Dir + "/child",
2779		Name: "user.greeting",
2780	}
2781	var nothing struct{}
2782	if err := control.JSON("/").Call(ctx, req, &nothing); err != nil {
2783		t.Fatalf("calling helper: %v", err)
2784	}
2785
2786	want := fuse.RemovexattrRequest{Name: "user.greeting"}
2787	if g, e := f.RecordedRemovexattr(), want; g != e {
2788		t.Errorf("removexattr saw %v, want %v", g, e)
2789	}
2790}
2791
2792// Test default error.
2793
2794type defaultErrno struct {
2795	fstestutil.Dir
2796}
2797
2798func (f defaultErrno) Lookup(ctx context.Context, name string) (fs.Node, error) {
2799	return nil, errors.New("bork")
2800}
2801
2802type statErrRequest struct {
2803	Path      string
2804	WantErrno syscall.Errno
2805}
2806
2807func doStatErr(ctx context.Context, req statErrRequest) (*struct{}, error) {
2808	if _, err := os.Stat(req.Path); !errors.Is(err, req.WantErrno) {
2809		return nil, fmt.Errorf("wrong error: %v", err)
2810	}
2811	return &struct{}{}, nil
2812}
2813
2814var statErrHelper = helpers.Register("statErr", httpjson.ServePOST(doStatErr))
2815
2816func TestDefaultErrno(t *testing.T) {
2817	maybeParallel(t)
2818	ctx, cancel := context.WithCancel(context.Background())
2819	defer cancel()
2820	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{defaultErrno{}}, nil)
2821	if err != nil {
2822		t.Fatal(err)
2823	}
2824	defer mnt.Close()
2825	control := statErrHelper.Spawn(ctx, t)
2826	defer control.Close()
2827
2828	req := statErrRequest{
2829		Path:      mnt.Dir + "/child",
2830		WantErrno: syscall.EIO,
2831	}
2832	var nothing struct{}
2833	if err := control.JSON("/").Call(ctx, req, &nothing); err != nil {
2834		t.Fatalf("calling helper: %v", err)
2835	}
2836}
2837
2838// Test custom error.
2839
2840type customErrNode struct {
2841	fstestutil.Dir
2842}
2843
2844type myCustomError struct {
2845	fuse.ErrorNumber
2846}
2847
2848var _ fuse.ErrorNumber = myCustomError{}
2849
2850func (myCustomError) Error() string {
2851	return "bork"
2852}
2853
2854func (f customErrNode) Lookup(ctx context.Context, name string) (fs.Node, error) {
2855	return nil, myCustomError{
2856		ErrorNumber: fuse.Errno(syscall.ENAMETOOLONG),
2857	}
2858}
2859
2860func TestCustomErrno(t *testing.T) {
2861	maybeParallel(t)
2862	ctx, cancel := context.WithCancel(context.Background())
2863	defer cancel()
2864	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{customErrNode{}}, nil)
2865	if err != nil {
2866		t.Fatal(err)
2867	}
2868	defer mnt.Close()
2869	control := statErrHelper.Spawn(ctx, t)
2870	defer control.Close()
2871
2872	req := statErrRequest{
2873		Path:      mnt.Dir + "/child",
2874		WantErrno: syscall.ENAMETOOLONG,
2875	}
2876	var nothing struct{}
2877	if err := control.JSON("/").Call(ctx, req, &nothing); err != nil {
2878		t.Fatalf("calling helper: %v", err)
2879	}
2880}
2881
2882// Test returning syscall.Errno
2883
2884type syscallErrNode struct {
2885	fstestutil.Dir
2886}
2887
2888func (f syscallErrNode) Lookup(ctx context.Context, name string) (fs.Node, error) {
2889	return nil, syscall.ENAMETOOLONG
2890}
2891
2892func TestSyscallErrno(t *testing.T) {
2893	maybeParallel(t)
2894	ctx, cancel := context.WithCancel(context.Background())
2895	defer cancel()
2896	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{syscallErrNode{}}, nil)
2897	if err != nil {
2898		t.Fatal(err)
2899	}
2900	defer mnt.Close()
2901	control := statErrHelper.Spawn(ctx, t)
2902	defer control.Close()
2903
2904	req := statErrRequest{
2905		Path:      mnt.Dir + "/child",
2906		WantErrno: syscall.ENAMETOOLONG,
2907	}
2908	var nothing struct{}
2909	if err := control.JSON("/").Call(ctx, req, &nothing); err != nil {
2910		t.Fatalf("calling helper: %v", err)
2911	}
2912}
2913
2914// Test Mmap writing
2915
2916type inMemoryFile struct {
2917	mu   sync.Mutex
2918	data []byte
2919}
2920
2921func (f *inMemoryFile) bytes() []byte {
2922	f.mu.Lock()
2923	defer f.mu.Unlock()
2924
2925	return f.data
2926}
2927
2928func (f *inMemoryFile) Attr(ctx context.Context, a *fuse.Attr) error {
2929	f.mu.Lock()
2930	defer f.mu.Unlock()
2931
2932	a.Mode = 0o666
2933	a.Size = uint64(len(f.data))
2934	return nil
2935}
2936
2937func (f *inMemoryFile) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
2938	f.mu.Lock()
2939	defer f.mu.Unlock()
2940
2941	fuseutil.HandleRead(req, resp, f.data)
2942	return nil
2943}
2944
2945func (f *inMemoryFile) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error {
2946	f.mu.Lock()
2947	defer f.mu.Unlock()
2948
2949	resp.Size = copy(f.data[req.Offset:], req.Data)
2950	return nil
2951}
2952
2953const mmapSize = 16 * 4096
2954
2955var mmapWrites = map[int]byte{
2956	10:              'a',
2957	4096:            'b',
2958	4097:            'c',
2959	mmapSize - 4096: 'd',
2960	mmapSize - 1:    'z',
2961}
2962
2963func doMmap(ctx context.Context, dir string) (*struct{}, error) {
2964	f, err := os.Create(filepath.Join(dir, "child"))
2965	if err != nil {
2966		return nil, fmt.Errorf("Create: %v", err)
2967	}
2968	defer f.Close()
2969	data, err := syscall.Mmap(int(f.Fd()), 0, mmapSize, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
2970	if err != nil {
2971		return nil, fmt.Errorf("Mmap: %v", err)
2972	}
2973	for i, b := range mmapWrites {
2974		data[i] = b
2975	}
2976	if err := unix.Msync(data, syscall.MS_SYNC); err != nil {
2977		return nil, fmt.Errorf("Msync: %v", err)
2978	}
2979	if err := syscall.Munmap(data); err != nil {
2980		return nil, fmt.Errorf("Munmap: %v", err)
2981	}
2982	if err := f.Sync(); err != nil {
2983		return nil, fmt.Errorf("Fsync = %v", err)
2984	}
2985	if err := f.Close(); err != nil {
2986		return nil, fmt.Errorf("Close: %v", err)
2987	}
2988	return &struct{}{}, nil
2989}
2990
2991var mmapHelper = helpers.Register("mmap", httpjson.ServePOST(doMmap))
2992
2993type mmap struct {
2994	inMemoryFile
2995	// We don't actually care about whether the fsync happened or not;
2996	// this just lets us force the page cache to send the writes to
2997	// FUSE, so we can reliably verify they came through.
2998	record.Fsyncs
2999}
3000
3001func TestMmap(t *testing.T) {
3002	ctx, cancel := context.WithCancel(context.Background())
3003	defer cancel()
3004
3005	w := &mmap{}
3006	w.data = make([]byte, mmapSize)
3007	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": w}}, nil)
3008	if err != nil {
3009		t.Fatal(err)
3010	}
3011	defer mnt.Close()
3012
3013	// Run the mmap-using parts of the test in a subprocess, to avoid
3014	// an intentional page fault hanging the whole process (because it
3015	// would need to be served by the same process, and there might
3016	// not be a thread free to do that). Merely bumping GOMAXPROCS is
3017	// not enough to prevent the hangs reliably.
3018	control := mmapHelper.Spawn(ctx, t)
3019	defer control.Close()
3020	var nothing struct{}
3021	if err := control.JSON("/").Call(ctx, mnt.Dir, &nothing); err != nil {
3022		t.Fatalf("calling helper: %v", err)
3023	}
3024
3025	got := w.bytes()
3026	if g, e := len(got), mmapSize; g != e {
3027		t.Fatalf("bad write length: %d != %d", g, e)
3028	}
3029	for i, g := range got {
3030		// default '\x00' for writes[i] is good here
3031		if e := mmapWrites[i]; g != e {
3032			t.Errorf("wrong byte at offset %d: %q != %q", i, g, e)
3033		}
3034	}
3035}
3036
3037// Test direct Read.
3038
3039type directRead struct {
3040	fstestutil.File
3041}
3042
3043// explicitly not defining Attr and setting Size
3044
3045func (f directRead) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
3046	// do not allow the kernel to use page cache
3047	resp.Flags |= fuse.OpenDirectIO
3048	return f, nil
3049}
3050
3051func (directRead) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
3052	fuseutil.HandleRead(req, resp, []byte(hi))
3053	return nil
3054}
3055
3056func TestDirectRead(t *testing.T) {
3057	maybeParallel(t)
3058	ctx, cancel := context.WithCancel(context.Background())
3059	defer cancel()
3060	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": directRead{}}}, nil)
3061	if err != nil {
3062		t.Fatal(err)
3063	}
3064	defer mnt.Close()
3065	control := readHelper.Spawn(ctx, t)
3066	defer control.Close()
3067	var got readResult
3068	if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil {
3069		t.Fatalf("calling helper: %v", err)
3070	}
3071	if g, e := string(got.Data), hi; g != e {
3072		t.Errorf("readAll = %q, want %q", g, e)
3073	}
3074}
3075
3076// Test direct Write.
3077
3078type directWrite struct {
3079	fstestutil.File
3080	record.Writes
3081}
3082
3083// explicitly not defining Attr / Setattr and managing Size
3084
3085func (f *directWrite) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
3086	// do not allow the kernel to use page cache
3087	resp.Flags |= fuse.OpenDirectIO
3088	return f, nil
3089}
3090
3091func TestDirectWrite(t *testing.T) {
3092	maybeParallel(t)
3093	ctx, cancel := context.WithCancel(context.Background())
3094	defer cancel()
3095	w := &directWrite{}
3096	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": w}}, nil)
3097	if err != nil {
3098		t.Fatal(err)
3099	}
3100	defer mnt.Close()
3101	control := writeFileHelper.Spawn(ctx, t)
3102	defer control.Close()
3103
3104	var nothing struct{}
3105	req := writeFileRequest{
3106		Path: mnt.Dir + "/child",
3107		Data: []byte(hi),
3108	}
3109	if err := control.JSON("/").Call(ctx, req, &nothing); err != nil {
3110		t.Fatalf("calling helper: %v", err)
3111	}
3112
3113	if got := string(w.RecordedWriteData()); got != hi {
3114		t.Errorf("write = %q, want %q", got, hi)
3115	}
3116}
3117
3118// Test Attr
3119
3120// attrUnlinked is a file that is unlinked (Nlink==0).
3121type attrUnlinked struct {
3122	fstestutil.File
3123}
3124
3125var _ fs.Node = attrUnlinked{}
3126
3127func (f attrUnlinked) Attr(ctx context.Context, a *fuse.Attr) error {
3128	if err := f.File.Attr(ctx, a); err != nil {
3129		return err
3130	}
3131	a.Nlink = 0
3132	return nil
3133}
3134
3135func TestAttrUnlinked(t *testing.T) {
3136	maybeParallel(t)
3137	ctx, cancel := context.WithCancel(context.Background())
3138	defer cancel()
3139	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": attrUnlinked{}}}, nil)
3140	if err != nil {
3141		t.Fatal(err)
3142	}
3143	defer mnt.Close()
3144	control := statHelper.Spawn(ctx, t)
3145	defer control.Close()
3146
3147	var got statResult
3148	if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil {
3149		t.Fatalf("calling helper: %v", err)
3150	}
3151	if g, e := got.Nlink, uint64(0); g != e {
3152		t.Errorf("wrong link count: %v != %v", g, e)
3153	}
3154}
3155
3156// Test behavior when Attr method fails
3157
3158type attrBad struct {
3159}
3160
3161var _ fs.Node = attrBad{}
3162
3163func (attrBad) Attr(ctx context.Context, attr *fuse.Attr) error {
3164	return fuse.Errno(syscall.ENAMETOOLONG)
3165}
3166
3167func TestAttrBad(t *testing.T) {
3168	maybeParallel(t)
3169	ctx, cancel := context.WithCancel(context.Background())
3170	defer cancel()
3171	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": attrBad{}}}, nil)
3172	if err != nil {
3173		t.Fatal(err)
3174	}
3175	defer mnt.Close()
3176	control := statErrHelper.Spawn(ctx, t)
3177	defer control.Close()
3178
3179	req := statErrRequest{
3180		Path:      mnt.Dir + "/child",
3181		WantErrno: syscall.ENAMETOOLONG,
3182	}
3183	var nothing struct{}
3184	if err := control.JSON("/").Call(ctx, req, &nothing); err != nil {
3185		t.Fatalf("calling helper: %v", err)
3186	}
3187}
3188
3189// Test kernel cache invalidation
3190
3191type invalidateAttr struct {
3192	t    testing.TB
3193	attr record.Counter
3194}
3195
3196var _ fs.Node = (*invalidateAttr)(nil)
3197
3198func (i *invalidateAttr) Attr(ctx context.Context, a *fuse.Attr) error {
3199	i.attr.Inc()
3200	i.t.Logf("Attr called, #%d", i.attr.Count())
3201	a.Mode = 0o600
3202	return nil
3203}
3204
3205func TestInvalidateNodeAttr(t *testing.T) {
3206	// This test may see false positive failures when run under
3207	// extreme memory pressure.
3208	maybeParallel(t)
3209	ctx, cancel := context.WithCancel(context.Background())
3210	defer cancel()
3211	a := &invalidateAttr{
3212		t: t,
3213	}
3214	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": a}}, nil)
3215	if err != nil {
3216		t.Fatal(err)
3217	}
3218	defer mnt.Close()
3219	control := statHelper.Spawn(ctx, t)
3220	defer control.Close()
3221
3222	for i := 0; i < 10; i++ {
3223		var got statResult
3224		if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil {
3225			t.Fatalf("calling helper: %v", err)
3226		}
3227	}
3228	before := a.attr.Count()
3229	if before == 0 {
3230		t.Error("no Attr call seen")
3231	}
3232	if g, e := before, uint32(1); g > e {
3233		t.Errorf("too many Attr calls seen: %d > %d", g, e)
3234	}
3235
3236	t.Logf("invalidating...")
3237	if err := mnt.Server.InvalidateNodeAttr(a); err != nil {
3238		t.Fatalf("invalidate error: %v", err)
3239	}
3240
3241	for i := 0; i < 10; i++ {
3242		var got statResult
3243		if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil {
3244			t.Fatalf("calling helper: %v", err)
3245		}
3246	}
3247	if g, e := a.attr.Count(), before+1; g != e {
3248		t.Errorf("wrong Attr call count: %d != %d", g, e)
3249	}
3250}
3251
3252type invalidateData struct {
3253	t    testing.TB
3254	attr record.Counter
3255	read record.Counter
3256	data atomic.Value
3257}
3258
3259const (
3260	invalidateDataContent1 = "hello, world\n"
3261	invalidateDataContent2 = "so long!\n"
3262)
3263
3264var _ fs.Node = (*invalidateData)(nil)
3265
3266func (i *invalidateData) Attr(ctx context.Context, a *fuse.Attr) error {
3267	i.attr.Inc()
3268	i.t.Logf("Attr called, #%d", i.attr.Count())
3269	a.Mode = 0o600
3270	a.Size = uint64(len(i.data.Load().(string)))
3271	return nil
3272}
3273
3274var _ fs.HandleReader = (*invalidateData)(nil)
3275
3276func (i *invalidateData) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
3277	i.read.Inc()
3278	i.t.Logf("Read called, #%d", i.read.Count())
3279	fuseutil.HandleRead(req, resp, []byte(i.data.Load().(string)))
3280	return nil
3281}
3282
3283type fstatHelp struct {
3284	mu   sync.Mutex
3285	file *os.File
3286}
3287
3288func (f *fstatHelp) ServeHTTP(w http.ResponseWriter, req *http.Request) {
3289	switch req.URL.Path {
3290	case "/open":
3291		httpjson.ServePOST(f.doOpen).ServeHTTP(w, req)
3292	case "/fstat":
3293		httpjson.ServePOST(f.doFstat).ServeHTTP(w, req)
3294	case "/close":
3295		httpjson.ServePOST(f.doClose).ServeHTTP(w, req)
3296	default:
3297		http.NotFound(w, req)
3298	}
3299}
3300
3301func (f *fstatHelp) doOpen(ctx context.Context, path string) (*struct{}, error) {
3302	f.mu.Lock()
3303	defer f.mu.Unlock()
3304	fil, err := os.Open(path)
3305	if err != nil {
3306		return nil, err
3307	}
3308	f.file = fil
3309	return &struct{}{}, nil
3310}
3311
3312func (f *fstatHelp) doFstat(ctx context.Context, _ struct{}) (*statResult, error) {
3313	f.mu.Lock()
3314	defer f.mu.Unlock()
3315	fi, err := f.file.Stat()
3316	if err != nil {
3317		return nil, err
3318	}
3319	r := platformStat(fi)
3320	return r, nil
3321}
3322
3323func (f *fstatHelp) doClose(ctx context.Context, _ struct{}) (*struct{}, error) {
3324	f.mu.Lock()
3325	defer f.mu.Unlock()
3326	f.file.Close()
3327	return &struct{}{}, nil
3328}
3329
3330var fstatHelper = helpers.Register("fstat", &fstatHelp{})
3331
3332func TestInvalidateNodeDataInvalidatesAttr(t *testing.T) {
3333	// This test may see false positive failures when run under
3334	// extreme memory pressure.
3335	maybeParallel(t)
3336	ctx, cancel := context.WithCancel(context.Background())
3337	defer cancel()
3338	a := &invalidateData{
3339		t: t,
3340	}
3341	a.data.Store(invalidateDataContent1)
3342	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": a}}, nil)
3343	if err != nil {
3344		t.Fatal(err)
3345	}
3346	defer mnt.Close()
3347	control := fstatHelper.Spawn(ctx, t)
3348	defer control.Close()
3349
3350	var nothing struct{}
3351	if err := control.JSON("/open").Call(ctx, mnt.Dir+"/child", &nothing); err != nil {
3352		t.Fatalf("calling helper: %v", err)
3353	}
3354
3355	attrBefore := a.attr.Count()
3356	if g, min := attrBefore, uint32(1); g < min {
3357		t.Errorf("wrong Attr call count: %d < %d", g, min)
3358	}
3359
3360	t.Logf("invalidating...")
3361	a.data.Store(invalidateDataContent2)
3362	if err := mnt.Server.InvalidateNodeData(a); err != nil {
3363		t.Fatalf("invalidate error: %v", err)
3364	}
3365
3366	if g, prev := a.attr.Count(), attrBefore; g != prev {
3367		t.Errorf("invalidate caused an Attr call: %d != %d", g, prev)
3368	}
3369
3370	var got statResult
3371	if err := control.JSON("/fstat").Call(ctx, struct{}{}, &got); err != nil {
3372		t.Fatalf("calling helper: %v", err)
3373	}
3374	if g, prev := a.attr.Count(), attrBefore; g != prev+1 {
3375		t.Errorf("none or too many Attr calls after stat: %d != %d+1", g, prev)
3376	}
3377}
3378
3379type manyReadsHelp struct {
3380	mu   sync.Mutex
3381	file *os.File
3382}
3383
3384func (m *manyReadsHelp) ServeHTTP(w http.ResponseWriter, req *http.Request) {
3385	switch req.URL.Path {
3386	case "/open":
3387		httpjson.ServePOST(m.doOpen).ServeHTTP(w, req)
3388	case "/readAt":
3389		httpjson.ServePOST(m.doReadAt).ServeHTTP(w, req)
3390	case "/close":
3391		httpjson.ServePOST(m.doClose).ServeHTTP(w, req)
3392	default:
3393		http.NotFound(w, req)
3394	}
3395}
3396
3397func (m *manyReadsHelp) doOpen(ctx context.Context, path string) (*struct{}, error) {
3398	m.mu.Lock()
3399	defer m.mu.Unlock()
3400	fil, err := os.Open(path)
3401	if err != nil {
3402		return nil, err
3403	}
3404	m.file = fil
3405	return &struct{}{}, nil
3406}
3407
3408type readAtRequest struct {
3409	Offset int64
3410	Length int
3411}
3412
3413func (m *manyReadsHelp) doReadAt(ctx context.Context, req readAtRequest) ([]byte, error) {
3414	m.mu.Lock()
3415	defer m.mu.Unlock()
3416	buf := make([]byte, req.Length)
3417	n, err := m.file.ReadAt(buf, req.Offset)
3418	if err != nil && err != io.EOF {
3419		return nil, err
3420	}
3421	buf = buf[:n]
3422	return buf, nil
3423}
3424
3425func (m *manyReadsHelp) doClose(ctx context.Context, _ struct{}) (*struct{}, error) {
3426	m.mu.Lock()
3427	defer m.mu.Unlock()
3428	m.file.Close()
3429	return &struct{}{}, nil
3430}
3431
3432var manyReadsHelper = helpers.Register("manyReads", &manyReadsHelp{})
3433
3434func TestInvalidateNodeDataInvalidatesData(t *testing.T) {
3435	// This test may see false positive failures when run under
3436	// extreme memory pressure.
3437	maybeParallel(t)
3438	ctx, cancel := context.WithCancel(context.Background())
3439	defer cancel()
3440	a := &invalidateData{
3441		t: t,
3442	}
3443	a.data.Store(invalidateDataContent1)
3444	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": a}}, nil)
3445	if err != nil {
3446		t.Fatal(err)
3447	}
3448	defer mnt.Close()
3449	control := manyReadsHelper.Spawn(ctx, t)
3450	defer control.Close()
3451
3452	var nothing struct{}
3453	if err := control.JSON("/open").Call(ctx, mnt.Dir+"/child", &nothing); err != nil {
3454		t.Fatalf("calling helper: %v", err)
3455	}
3456
3457	{
3458		for i := 0; i < 10; i++ {
3459			req := readAtRequest{
3460				Offset: 0,
3461				Length: 100,
3462			}
3463			var got []byte
3464			if err := control.JSON("/readAt").Call(ctx, req, &got); err != nil {
3465				t.Fatalf("calling helper: %v", err)
3466			}
3467			if g, e := string(got), invalidateDataContent1; g != e {
3468				t.Errorf("wrong content: %q != %q", g, e)
3469			}
3470		}
3471	}
3472	if g, e := a.read.Count(), uint32(1); g != e {
3473		t.Errorf("wrong Read call count: %d != %d", g, e)
3474	}
3475
3476	t.Logf("invalidating...")
3477	a.data.Store(invalidateDataContent2)
3478	if err := mnt.Server.InvalidateNodeData(a); err != nil {
3479		t.Fatalf("invalidate error: %v", err)
3480	}
3481
3482	if g, e := a.read.Count(), uint32(1); g != e {
3483		t.Errorf("wrong Read call count: %d != %d", g, e)
3484	}
3485
3486	{
3487		// explicitly don't cross the EOF, to trigger more edge cases
3488		// (Linux will always do Getattr if you cross what it believes
3489		// the EOF to be)
3490		const bufSize = len(invalidateDataContent2) - 3
3491		for i := 0; i < 10; i++ {
3492			req := readAtRequest{
3493				Offset: 0,
3494				Length: bufSize,
3495			}
3496			var got []byte
3497			if err := control.JSON("/readAt").Call(ctx, req, &got); err != nil {
3498				t.Fatalf("calling helper: %v", err)
3499			}
3500			if g, e := string(got), invalidateDataContent2[:bufSize]; g != e {
3501				t.Errorf("wrong content: %q != %q", g, e)
3502			}
3503		}
3504	}
3505	if g, e := a.read.Count(), uint32(2); g != e {
3506		t.Errorf("wrong Read call count: %d != %d", g, e)
3507	}
3508}
3509
3510type invalidateDataPartial struct {
3511	t    testing.TB
3512	attr record.Counter
3513	read record.Counter
3514}
3515
3516var invalidateDataPartialContent = strings.Repeat("hello, world\n", 1000)
3517
3518var _ fs.Node = (*invalidateDataPartial)(nil)
3519
3520func (i *invalidateDataPartial) Attr(ctx context.Context, a *fuse.Attr) error {
3521	i.attr.Inc()
3522	i.t.Logf("Attr called, #%d", i.attr.Count())
3523	a.Mode = 0o600
3524	a.Size = uint64(len(invalidateDataPartialContent))
3525	return nil
3526}
3527
3528var _ fs.HandleReader = (*invalidateDataPartial)(nil)
3529
3530func (i *invalidateDataPartial) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
3531	i.read.Inc()
3532	i.t.Logf("Read called, #%d", i.read.Count())
3533	fuseutil.HandleRead(req, resp, []byte(invalidateDataPartialContent))
3534	return nil
3535}
3536
3537func TestInvalidateNodeDataRangeMiss(t *testing.T) {
3538	if runtime.GOOS == "freebsd" {
3539		t.Skip("FreeBSD seems to always invalidate whole file")
3540	}
3541	// This test may see false positive failures when run under
3542	// extreme memory pressure.
3543	maybeParallel(t)
3544	ctx, cancel := context.WithCancel(context.Background())
3545	defer cancel()
3546	a := &invalidateDataPartial{
3547		t: t,
3548	}
3549	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": a}}, nil)
3550	if err != nil {
3551		t.Fatal(err)
3552	}
3553	defer mnt.Close()
3554	control := manyReadsHelper.Spawn(ctx, t)
3555	defer control.Close()
3556
3557	var nothing struct{}
3558	if err := control.JSON("/open").Call(ctx, mnt.Dir+"/child", &nothing); err != nil {
3559		t.Fatalf("calling helper: %v", err)
3560	}
3561
3562	for i := 0; i < 10; i++ {
3563		req := readAtRequest{
3564			Offset: 0,
3565			Length: 4,
3566		}
3567		var got []byte
3568		if err := control.JSON("/readAt").Call(ctx, req, &got); err != nil {
3569			t.Fatalf("calling helper: %v", err)
3570		}
3571	}
3572	if g, e := a.read.Count(), uint32(1); g != e {
3573		t.Errorf("wrong Read call count: %d != %d", g, e)
3574	}
3575
3576	t.Logf("invalidating an uninteresting block...")
3577	if err := mnt.Server.InvalidateNodeDataRange(a, 4096, 4096); err != nil {
3578		t.Fatalf("invalidate error: %v", err)
3579	}
3580
3581	for i := 0; i < 10; i++ {
3582		req := readAtRequest{
3583			Offset: 0,
3584			Length: 4,
3585		}
3586		var got []byte
3587		if err := control.JSON("/readAt").Call(ctx, req, &got); err != nil {
3588			t.Fatalf("calling helper: %v", err)
3589		}
3590	}
3591	// The page invalidated is not the page we're reading, so it
3592	// should stay in cache.
3593	if g, e := a.read.Count(), uint32(1); g != e {
3594		t.Errorf("wrong Read call count: %d != %d", g, e)
3595	}
3596}
3597
3598func TestInvalidateNodeDataRangeHit(t *testing.T) {
3599	// This test may see false positive failures when run under
3600	// extreme memory pressure.
3601	maybeParallel(t)
3602	ctx, cancel := context.WithCancel(context.Background())
3603	defer cancel()
3604	a := &invalidateDataPartial{
3605		t: t,
3606	}
3607	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": a}}, nil)
3608	if err != nil {
3609		t.Fatal(err)
3610	}
3611	defer mnt.Close()
3612	control := manyReadsHelper.Spawn(ctx, t)
3613	defer control.Close()
3614
3615	var nothing struct{}
3616	if err := control.JSON("/open").Call(ctx, mnt.Dir+"/child", &nothing); err != nil {
3617		t.Fatalf("calling helper: %v", err)
3618	}
3619
3620	const offset = 4096
3621	for i := 0; i < 10; i++ {
3622		req := readAtRequest{
3623			Offset: offset,
3624			Length: 4,
3625		}
3626		var got []byte
3627		if err := control.JSON("/readAt").Call(ctx, req, &got); err != nil {
3628			t.Fatalf("calling helper: %v", err)
3629		}
3630	}
3631	if g, e := a.read.Count(), uint32(1); g != e {
3632		t.Errorf("wrong Read call count: %d != %d", g, e)
3633	}
3634
3635	t.Logf("invalidating where the reads are...")
3636	if err := mnt.Server.InvalidateNodeDataRange(a, offset, 4096); err != nil {
3637		t.Fatalf("invalidate error: %v", err)
3638	}
3639
3640	for i := 0; i < 10; i++ {
3641		req := readAtRequest{
3642			Offset: offset,
3643			Length: 4,
3644		}
3645		var got []byte
3646		if err := control.JSON("/readAt").Call(ctx, req, &got); err != nil {
3647			t.Fatalf("calling helper: %v", err)
3648		}
3649	}
3650	// One new read
3651	if g, e := a.read.Count(), uint32(2); g != e {
3652		t.Errorf("wrong Read call count: %d != %d", g, e)
3653	}
3654}
3655
3656type invalidateEntryRoot struct {
3657	t      testing.TB
3658	lookup record.Counter
3659}
3660
3661var _ fs.Node = (*invalidateEntryRoot)(nil)
3662
3663func (i *invalidateEntryRoot) Attr(ctx context.Context, a *fuse.Attr) error {
3664	a.Mode = 0o600 | os.ModeDir
3665	return nil
3666}
3667
3668var _ fs.NodeStringLookuper = (*invalidateEntryRoot)(nil)
3669
3670func (i *invalidateEntryRoot) Lookup(ctx context.Context, name string) (fs.Node, error) {
3671	if name != "child" {
3672		return nil, syscall.ENOENT
3673	}
3674	i.lookup.Inc()
3675	i.t.Logf("Lookup called, #%d", i.lookup.Count())
3676	return fstestutil.File{}, nil
3677}
3678
3679func TestInvalidateEntry(t *testing.T) {
3680	// This test may see false positive failures when run under
3681	// extreme memory pressure.
3682	maybeParallel(t)
3683	ctx, cancel := context.WithCancel(context.Background())
3684	defer cancel()
3685	a := &invalidateEntryRoot{
3686		t: t,
3687	}
3688	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{a}, nil)
3689	if err != nil {
3690		t.Fatal(err)
3691	}
3692	defer mnt.Close()
3693	control := statHelper.Spawn(ctx, t)
3694	defer control.Close()
3695
3696	for i := 0; i < 10; i++ {
3697		var got statResult
3698		if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil {
3699			t.Fatalf("calling helper: %v", err)
3700		}
3701	}
3702	if g, e := a.lookup.Count(), uint32(1); g != e {
3703		t.Errorf("wrong Lookup call count: %d != %d", g, e)
3704	}
3705
3706	t.Logf("invalidating...")
3707	if err := mnt.Server.InvalidateEntry(a, "child"); err != nil {
3708		t.Fatalf("invalidate error: %v", err)
3709	}
3710
3711	for i := 0; i < 10; i++ {
3712		var got statResult
3713		if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil {
3714			t.Fatalf("calling helper: %v", err)
3715		}
3716	}
3717	if g, e := a.lookup.Count(), uint32(2); g != e {
3718		t.Errorf("wrong Lookup call count: %d != %d", g, e)
3719	}
3720}
3721
3722type cachedFile struct {
3723}
3724
3725var _ fs.Node = cachedFile{}
3726
3727func (f cachedFile) Attr(ctx context.Context, a *fuse.Attr) error {
3728	a.Mode = 0o666
3729	// FreeBSD won't issue reads if the file is empty.
3730	a.Size = 4096
3731	return nil
3732}
3733
3734var _ fs.NodeOpener = cachedFile{}
3735
3736func (f cachedFile) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
3737	resp.Flags |= fuse.OpenKeepCache
3738	return f, nil
3739}
3740
3741type readErrRequest struct {
3742	Path      string
3743	WantErrno syscall.Errno
3744}
3745
3746func doReadErr(ctx context.Context, req readErrRequest) (*struct{}, error) {
3747	f, err := os.Open(req.Path)
3748	if err != nil {
3749		return nil, err
3750	}
3751	defer f.Close()
3752	data := make([]byte, 4096)
3753	if _, err := f.Read(data); !errors.Is(err, req.WantErrno) {
3754		return nil, fmt.Errorf("wrong error: %v", err)
3755	}
3756	return &struct{}{}, nil
3757}
3758
3759var readErrHelper = helpers.Register("readErr", httpjson.ServePOST(doReadErr))
3760
3761func TestNotifyStore(t *testing.T) {
3762	// This test may see false positive failures when run under
3763	// extreme memory pressure.
3764	maybeParallel(t)
3765	ctx, cancel := context.WithCancel(context.Background())
3766	defer cancel()
3767	child := cachedFile{}
3768	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": child}}, nil)
3769	if err != nil {
3770		t.Fatal(err)
3771	}
3772	defer mnt.Close()
3773	control := readHelper.Spawn(ctx, t)
3774	defer control.Close()
3775
3776	// prove that read doesn't work, and make sure node is cached
3777	{
3778		control := readErrHelper.Spawn(ctx, t)
3779		defer control.Close()
3780		req := readErrRequest{
3781			Path:      mnt.Dir + "/child",
3782			WantErrno: syscall.EOPNOTSUPP,
3783		}
3784		var nothing struct{}
3785		if err := control.JSON("/").Call(ctx, req, &nothing); err != nil {
3786			t.Fatalf("calling helper: %v", err)
3787		}
3788	}
3789
3790	greeting := strings.Repeat("testing store\n", 500)
3791	if l := len(greeting); l < syscall.Getpagesize() {
3792		t.Fatalf("must fill at least one page to avoid second Read: len=%d", l)
3793	}
3794	t.Logf("storing...")
3795	if err := mnt.Server.NotifyStore(child, 0, []byte(greeting)); err != nil {
3796		if runtime.GOOS == "freebsd" && errors.Is(err, syscall.ENOSYS) {
3797			t.Skip("FreeBSD does not support NotifyStore")
3798		}
3799		t.Fatalf("store error: %v", err)
3800	}
3801	if runtime.GOOS == "freebsd" {
3802		t.Errorf("FreeBSD started supporting NotifyStore, update code")
3803	}
3804
3805	var got readResult
3806	if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil {
3807		t.Fatalf("calling helper: %v", err)
3808	}
3809	// we have just read data without implementing Read!
3810
3811	// doRead caps result at 4 kiB
3812	if g, e := string(got.Data), greeting[:4096]; g != e {
3813		t.Errorf("readAll = %q, want %q", g, e)
3814	}
3815}
3816
3817func TestNotifyRetrieve(t *testing.T) {
3818	// This test may see false positive failures when run under
3819	// extreme memory pressure.
3820	maybeParallel(t)
3821	ctx, cancel := context.WithCancel(context.Background())
3822	defer cancel()
3823	child := readAll{}
3824	mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": child}}, nil)
3825	if err != nil {
3826		t.Fatal(err)
3827	}
3828	defer mnt.Close()
3829	control := readHelper.Spawn(ctx, t)
3830	defer control.Close()
3831
3832	// read to fill page cache
3833	var got readResult
3834	if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil {
3835		t.Fatalf("calling helper: %v", err)
3836	}
3837
3838	t.Logf("retrieving...")
3839	data, err := mnt.Server.NotifyRetrieve(child, 0, 5)
3840	if err != nil {
3841		if runtime.GOOS == "freebsd" && errors.Is(err, syscall.ENOSYS) {
3842			t.Skip("FreeBSD does not support NotifyRetrieve")
3843		}
3844		t.Fatalf("retrieve error: %v", err)
3845	}
3846	if runtime.GOOS == "freebsd" {
3847		t.Errorf("FreeBSD started supporting NotifyRetrieve, update code")
3848	}
3849
3850	if g, e := string(data), hi[:5]; g != e {
3851		t.Errorf("retrieve = %q, want %q", g, e)
3852	}
3853}
3854
3855type contextFile struct {
3856	fstestutil.File
3857}
3858
3859var contextFileSentinel int
3860
3861func (contextFile) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
3862	v := ctx.Value(&contextFileSentinel)
3863	if v == nil {
3864		return nil, syscall.ESTALE
3865	}
3866	data, ok := v.(string)
3867	if !ok {
3868		return nil, syscall.EIO
3869	}
3870	resp.Flags |= fuse.OpenDirectIO
3871	return fs.DataHandle([]byte(data)), nil
3872}
3873
3874func TestContext(t *testing.T) {
3875	maybeParallel(t)
3876	ctx, cancel := context.WithCancel(context.Background())
3877	defer cancel()
3878	const input = "kilroy was here"
3879	mnt, err := fstestutil.MountedT(t,
3880		fstestutil.SimpleFS{&fstestutil.ChildMap{"child": contextFile{}}},
3881		&fs.Config{
3882			WithContext: func(ctx context.Context, req fuse.Request) context.Context {
3883				return context.WithValue(ctx, &contextFileSentinel, input)
3884			},
3885		})
3886	if err != nil {
3887		t.Fatal(err)
3888	}
3889	defer mnt.Close()
3890	control := readHelper.Spawn(ctx, t)
3891	defer control.Close()
3892
3893	var got readResult
3894	if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil {
3895		t.Fatalf("calling helper: %v", err)
3896	}
3897	if g, e := string(got.Data), input; g != e {
3898		t.Errorf("read wrong data: %q != %q", g, e)
3899	}
3900}
3901
3902type goexitFile struct {
3903	fstestutil.File
3904}
3905
3906func (goexitFile) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
3907	log.Println("calling runtime.Goexit...")
3908	runtime.Goexit()
3909	panic("not reached")
3910}
3911
3912func TestGoexit(t *testing.T) {
3913	maybeParallel(t)
3914	ctx, cancel := context.WithCancel(context.Background())
3915	defer cancel()
3916	mnt, err := fstestutil.MountedT(t,
3917		fstestutil.SimpleFS{&fstestutil.ChildMap{"child": goexitFile{}}}, nil)
3918	if err != nil {
3919		t.Fatal(err)
3920	}
3921	defer mnt.Close()
3922	control := openErrHelper.Spawn(ctx, t)
3923	defer control.Close()
3924
3925	req := openRequest{
3926		Path:      mnt.Dir + "/child",
3927		Flags:     os.O_RDONLY,
3928		Perm:      0,
3929		WantErrno: syscall.EIO,
3930	}
3931	var nothing struct{}
3932	if err := control.JSON("/").Call(ctx, req, &nothing); err != nil {
3933		t.Fatalf("calling helper: %v", err)
3934	}
3935}
3936
3937// Test Poll: NodePoller and HandlePoller
3938
3939// pollDelayRead is a HandleReader that only lets a read succeed after
3940// one round of polling.
3941type pollDelayRead struct {
3942	t      testing.TB
3943	server *fs.Server
3944
3945	mu   sync.Mutex
3946	seen []string
3947
3948	wakeup atomic.Value
3949	ready  uint64
3950}
3951
3952// Can be used as either Handle or Node. If these interfaces diverge,
3953// change this to a common core with two wrappers.
3954var _ fs.HandlePoller = (*pollDelayRead)(nil)
3955var _ fs.NodePoller = (*pollDelayRead)(nil)
3956
3957func (r *pollDelayRead) saw(s string) {
3958	r.mu.Lock()
3959	defer r.mu.Unlock()
3960	r.t.Logf("saw %s", s)
3961	r.seen = append(r.seen, s)
3962}
3963
3964func (n *pollDelayRead) Poll(ctx context.Context, req *fuse.PollRequest, resp *fuse.PollResponse) error {
3965	if w, ok := req.Wakeup(); ok {
3966		n.wakeup.Store(w)
3967	}
3968	resp.REvents = fuse.PollOut
3969	if atomic.LoadUint64(&n.ready) == 1 {
3970		resp.REvents |= fuse.PollIn
3971		return nil
3972	}
3973	return nil
3974}
3975
3976func (n *pollDelayRead) doWakeup() {
3977	n.saw("wakeup")
3978	atomic.StoreUint64(&n.ready, 1)
3979	if w, ok := n.wakeup.Load().(fuse.PollWakeup); ok {
3980		if err := n.server.NotifyPollWakeup(w); err != nil {
3981			n.t.Errorf("wakeup error: %v", err)
3982		}
3983	}
3984}
3985
3986var _ fs.HandleReader = (*pollDelayRead)(nil)
3987
3988func (n *pollDelayRead) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
3989	if req.FileFlags&fuse.OpenNonblock == 0 {
3990		n.t.Errorf("expected a non-blocking read")
3991		return syscall.ENAMETOOLONG
3992	}
3993	if atomic.LoadUint64(&n.ready) == 0 {
3994		n.saw("read-eagain")
3995		time.AfterFunc(1*time.Millisecond, n.doWakeup)
3996		return syscall.EAGAIN
3997	}
3998	n.saw("read-ready")
3999	fuseutil.HandleRead(req, resp, []byte(hi))
4000	return nil
4001}
4002
4003// Test NodePoller
4004
4005type readPolledNode struct {
4006	pollDelayRead
4007}
4008
4009var _ fs.Node = (*readPolledNode)(nil)
4010
4011func (readPolledNode) Attr(ctx context.Context, a *fuse.Attr) error {
4012	a.Mode = 0o666
4013	a.Size = uint64(len(hi))
4014	return nil
4015}
4016
4017func TestReadPollNode(t *testing.T) {
4018	if runtime.GOOS == "freebsd" {
4019		t.Skip("no poll on FreeBSD")
4020	}
4021	maybeParallel(t)
4022	ctx, cancel := context.WithCancel(context.Background())
4023	defer cancel()
4024	child := &readPolledNode{
4025		pollDelayRead: pollDelayRead{
4026			t: t,
4027		},
4028	}
4029	filesys := fstestutil.SimpleFS{&fstestutil.ChildMap{"child": child}}
4030	setup := func(mnt *fstestutil.Mount) fs.FS {
4031		child.server = mnt.Server
4032		return filesys
4033	}
4034	mnt, err := fstestutil.MountedFuncT(t, setup, nil)
4035	if err != nil {
4036		t.Fatal(err)
4037	}
4038	defer mnt.Close()
4039	control := readHelper.Spawn(ctx, t)
4040	defer control.Close()
4041	var got readResult
4042	if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil {
4043		t.Fatalf("calling helper: %v", err)
4044	}
4045	if g, e := string(got.Data), hi; g != e {
4046		t.Errorf("readAll = %q, want %q", g, e)
4047	}
4048	if g, e := strings.Join(child.seen, " "), "read-eagain wakeup read-ready"; g != e {
4049		t.Errorf("wrong events: %q != %q", g, e)
4050	}
4051}
4052
4053// Test HandlePoller
4054
4055type readPolledNodeWithHandle struct {
4056	handle pollDelayRead
4057}
4058
4059var _ fs.Node = (*readPolledNodeWithHandle)(nil)
4060
4061func (readPolledNodeWithHandle) Attr(ctx context.Context, a *fuse.Attr) error {
4062	a.Mode = 0o666
4063	a.Size = uint64(len(hi))
4064	return nil
4065}
4066
4067var _ fs.NodeOpener = (*readPolledNodeWithHandle)(nil)
4068
4069func (f *readPolledNodeWithHandle) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
4070	return &f.handle, nil
4071}
4072
4073func TestReadPollHandle(t *testing.T) {
4074	if runtime.GOOS == "freebsd" {
4075		t.Skip("no poll on FreeBSD")
4076	}
4077	maybeParallel(t)
4078	ctx, cancel := context.WithCancel(context.Background())
4079	defer cancel()
4080	child := &readPolledNodeWithHandle{
4081		handle: pollDelayRead{
4082			t: t,
4083		},
4084	}
4085	filesys := fstestutil.SimpleFS{&fstestutil.ChildMap{"child": child}}
4086	setup := func(mnt *fstestutil.Mount) fs.FS {
4087		child.handle.server = mnt.Server
4088		return filesys
4089	}
4090	mnt, err := fstestutil.MountedFuncT(t, setup, nil)
4091	if err != nil {
4092		t.Fatal(err)
4093	}
4094	defer mnt.Close()
4095	control := readHelper.Spawn(ctx, t)
4096	defer control.Close()
4097	var got readResult
4098	if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &got); err != nil {
4099		t.Fatalf("calling helper: %v", err)
4100	}
4101	if g, e := string(got.Data), hi; g != e {
4102		t.Errorf("readAll = %q, want %q", g, e)
4103	}
4104	if g, e := strings.Join(child.handle.seen, " "), "read-eagain wakeup read-ready"; g != e {
4105		t.Errorf("wrong events: %q != %q", g, e)
4106	}
4107}
4108
4109// Test flock
4110
4111// Go syscall & golang.org/x/sys/unix do a horrible thing where they
4112// muddle the difference between fcntl and flock by naming the syscall
4113// "FcntlFlock" and the result type "Flock_t". Make no mistake that is
4114// fcntl and has nothing to do with flock.
4115
4116type lockFile struct {
4117	fstestutil.File
4118
4119	lock    record.RequestRecorder
4120	unlock  record.RequestRecorder
4121	release record.ReleaseWaiter
4122	flush   record.RequestRecorder
4123}
4124
4125var _ fs.NodeOpener = (*lockFile)(nil)
4126
4127func (f *lockFile) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
4128	h := &lockHandle{
4129		f: f,
4130	}
4131	return h, nil
4132}
4133
4134type lockHandle struct {
4135	f *lockFile
4136}
4137
4138var _ fs.Handle = (*lockHandle)(nil)
4139
4140var _ fs.HandleLocker = (*lockHandle)(nil)
4141
4142func (h *lockHandle) Lock(ctx context.Context, req *fuse.LockRequest) error {
4143	tmp := *req
4144	h.f.lock.RecordRequest(&tmp)
4145	return nil
4146}
4147
4148func (h *lockHandle) LockWait(ctx context.Context, req *fuse.LockWaitRequest) error {
4149	tmp := *req
4150	h.f.lock.RecordRequest(&tmp)
4151	return nil
4152}
4153
4154func (h *lockHandle) Unlock(ctx context.Context, req *fuse.UnlockRequest) error {
4155	tmp := *req
4156	h.f.unlock.RecordRequest(&tmp)
4157	return nil
4158}
4159
4160func (h *lockHandle) QueryLock(ctx context.Context, req *fuse.QueryLockRequest, resp *fuse.QueryLockResponse) error {
4161	return nil
4162}
4163
4164var _ fs.HandleFlockLocker = (*lockHandle)(nil)
4165
4166var _ fs.HandleReleaser = (*lockHandle)(nil)
4167
4168func (h *lockHandle) Release(ctx context.Context, req *fuse.ReleaseRequest) error {
4169	return h.f.release.Release(ctx, req)
4170}
4171
4172var _ fs.HandlePOSIXLocker = (*lockHandle)(nil)
4173
4174var _ fs.HandleFlusher = (*lockHandle)(nil)
4175
4176func (h *lockHandle) Flush(ctx context.Context, req *fuse.FlushRequest) error {
4177	tmp := *req
4178	h.f.flush.RecordRequest(&tmp)
4179	return nil
4180}
4181
4182type lockHelp struct {
4183	lockFn   func(fd uintptr, req *lockReq) error
4184	unlockFn func(fd uintptr, req *lockReq) error
4185	queryFn  func(fd uintptr, lk *unix.Flock_t) error
4186
4187	mu   sync.Mutex
4188	file *os.File
4189}
4190
4191func (lh *lockHelp) ServeHTTP(w http.ResponseWriter, req *http.Request) {
4192	switch req.URL.Path {
4193	case "/lock":
4194		httpjson.ServePOST(lh.doLock).ServeHTTP(w, req)
4195	case "/unlock":
4196		httpjson.ServePOST(lh.doUnlock).ServeHTTP(w, req)
4197	case "/close":
4198		httpjson.ServePOST(lh.doClose).ServeHTTP(w, req)
4199	case "/query":
4200		httpjson.ServePOST(lh.doQuery).ServeHTTP(w, req)
4201	default:
4202		http.NotFound(w, req)
4203	}
4204}
4205
4206type lockReq struct {
4207	Path  string
4208	Wait  bool
4209	Start int64
4210	Len   int64
4211}
4212
4213func (lh *lockHelp) doLock(ctx context.Context, req *lockReq) (*struct{}, error) {
4214	lh.mu.Lock()
4215	defer lh.mu.Unlock()
4216	f, err := os.OpenFile(req.Path, os.O_RDWR, 0o644)
4217	if err != nil {
4218		return nil, fmt.Errorf("open: %v", err)
4219	}
4220	lh.file = f
4221	c, err := lh.file.SyscallConn()
4222	if err != nil {
4223		return nil, fmt.Errorf("syscallconn: %v", err)
4224	}
4225	var outerr error
4226	lockFn := func(fd uintptr) {
4227		outerr = lh.lockFn(fd, req)
4228	}
4229	if err := c.Control(lockFn); err != nil {
4230		return nil, fmt.Errorf("error calling lock: %v", err)
4231	}
4232	if err := outerr; err != nil {
4233		return nil, fmt.Errorf("lock error: %v", err)
4234	}
4235	return &struct{}{}, nil
4236}
4237
4238func (lh *lockHelp) doUnlock(ctx context.Context, req *lockReq) (*struct{}, error) {
4239	lh.mu.Lock()
4240	defer lh.mu.Unlock()
4241	if lh.file == nil {
4242		return nil, errors.New("file not open")
4243	}
4244	c, err := lh.file.SyscallConn()
4245	if err != nil {
4246		return nil, fmt.Errorf("syscallconn: %v", err)
4247	}
4248	var outerr error
4249	unlockFn := func(fd uintptr) {
4250		outerr = lh.unlockFn(fd, req)
4251	}
4252	if err := c.Control(unlockFn); err != nil {
4253		return nil, fmt.Errorf("error calling unlock: %v", err)
4254	}
4255	if err := outerr; err != nil {
4256		return nil, fmt.Errorf("unlock error: %v", err)
4257	}
4258	return &struct{}{}, nil
4259}
4260
4261func (lh *lockHelp) doClose(ctx context.Context, _ struct{}) (*struct{}, error) {
4262	lh.mu.Lock()
4263	defer lh.mu.Unlock()
4264	if err := lh.file.Close(); err != nil {
4265		return nil, fmt.Errorf("Close: %v", err)
4266	}
4267	return &struct{}{}, nil
4268}
4269
4270func (lh *lockHelp) doQuery(ctx context.Context, req *lockReq) (*unix.Flock_t, error) {
4271	lh.mu.Lock()
4272	defer lh.mu.Unlock()
4273	if lh.file == nil {
4274		return nil, errors.New("file not open")
4275	}
4276	c, err := lh.file.SyscallConn()
4277	if err != nil {
4278		return nil, fmt.Errorf("syscallconn: %v", err)
4279	}
4280	var outerr error
4281	lk := unix.Flock_t{
4282		Type:   unix.F_WRLCK,
4283		Whence: int16(io.SeekStart),
4284		Start:  req.Start,
4285		Len:    req.Len,
4286	}
4287	queryFn := func(fd uintptr) {
4288		outerr = lh.queryFn(fd, &lk)
4289	}
4290	if err := c.Control(queryFn); err != nil {
4291		return nil, fmt.Errorf("error calling getlk: %v", err)
4292	}
4293	if err := outerr; err != nil {
4294		return nil, fmt.Errorf("query lock error: %v", err)
4295	}
4296	return &lk, nil
4297}
4298
4299var lockFlockHelper = helpers.Register("lock-flock", &lockHelp{
4300	lockFn: func(fd uintptr, req *lockReq) error {
4301		flags := unix.LOCK_EX
4302		if !req.Wait {
4303			flags |= unix.LOCK_NB
4304		}
4305		return unix.Flock(int(fd), flags)
4306	},
4307	unlockFn: func(fd uintptr, req *lockReq) error {
4308		return unix.Flock(int(fd), unix.LOCK_UN)
4309	},
4310	queryFn: func(fd uintptr, lk *unix.Flock_t) error {
4311		return errors.New("no query in flock api")
4312	},
4313})
4314
4315var lockPOSIXHelper = helpers.Register("lock-posix", &lockHelp{
4316	lockFn: func(fd uintptr, req *lockReq) error {
4317		lk := unix.Flock_t{
4318			Type:   unix.F_WRLCK,
4319			Whence: int16(io.SeekStart),
4320			Start:  req.Start,
4321			Len:    req.Len,
4322		}
4323		cmd := unix.F_SETLK
4324		if req.Wait {
4325			cmd = unix.F_SETLKW
4326		}
4327		//		return unix.FcntlFlock(fd, cmd, &lk)
4328		err := unix.FcntlFlock(fd, cmd, &lk)
4329		log.Printf("WTF L %d %v %#v: %v", fd, cmd, lk, err)
4330		return err
4331	},
4332	unlockFn: func(fd uintptr, req *lockReq) error {
4333		lk := unix.Flock_t{
4334			Type:   unix.F_UNLCK,
4335			Whence: int16(io.SeekStart),
4336			Start:  req.Start,
4337			Len:    req.Len,
4338		}
4339		cmd := unix.F_SETLK
4340		//		return unix.FcntlFlock(fd, cmd, &lk)
4341		err := unix.FcntlFlock(fd, cmd, &lk)
4342		log.Printf("WTF U %d %v %#v: %v", fd, cmd, lk, err)
4343		return err
4344	},
4345	queryFn: func(fd uintptr, lk *unix.Flock_t) error {
4346		cmd := unix.F_GETLK
4347		return unix.FcntlFlock(fd, cmd, lk)
4348	},
4349})
4350
4351// ugly kludge to have platform-specific subtests. filled in
4352// elsewhere, when on linux.
4353var lockOFDHelper *spawntest.Helper
4354
4355type lockTest struct {
4356	*testing.T
4357	ctx     context.Context
4358	kind    string
4359	child   *lockFile
4360	mnt     *fstestutil.Mount
4361	control *spawntest.Control
4362}
4363
4364func (t *lockTest) recordedLockRequest() (_ *fuse.LockRequest, waited bool) {
4365	switch req := t.child.lock.Recorded().(type) {
4366	case *fuse.LockRequest:
4367		return req, false
4368	case *fuse.LockWaitRequest:
4369		return (*fuse.LockRequest)(req), true
4370	default:
4371		t.Fatalf("bad lock request: %#v", req)
4372	}
4373	panic("not reached")
4374}
4375
4376func (t *lockTest) callLock(req *lockReq) {
4377	t.Logf("calling lock")
4378	if req.Path == "" {
4379		req.Path = "child"
4380	}
4381	req.Path = filepath.Join(t.mnt.Dir, req.Path)
4382	var nothing struct{}
4383	if err := t.control.JSON("/lock").Call(t.ctx, req, &nothing); err != nil {
4384		t.Fatalf("calling helper: %v", err)
4385	}
4386	want := &fuse.LockRequest{
4387		LockFlags: 0,
4388		Lock: fuse.FileLock{
4389			Start: uint64(req.Start),
4390			End:   uint64(req.Start) + uint64(req.Len) - 1,
4391			Type:  fuse.LockWrite,
4392			PID:   0,
4393		},
4394	}
4395	if t.kind == "flock" {
4396		want.LockFlags |= fuse.LockFlock
4397		want.Lock.Start = 0
4398		want.Lock.End = 0x7fff_ffff_ffff_ffff
4399	}
4400	got, waited := t.recordedLockRequest()
4401	if g, e := waited, req.Wait; g != e {
4402		t.Errorf("lock non-blocking field is bad: %v != %v", g, e)
4403	}
4404	// dynamic values that are too hard to control
4405	if got.Handle == 0 {
4406		t.Errorf("got LockRequest with no Handle")
4407	}
4408	want.Handle = got.Handle
4409	if got.LockOwner == 0 {
4410		t.Errorf("got LockRequest with no LockOwner")
4411	}
4412	want.LockOwner = got.LockOwner
4413	if got.Lock.PID == 0 {
4414		t.Errorf("got LockRequest with no PID")
4415	}
4416	want.Lock.PID = got.Lock.PID
4417	if g, e := got, want; *g != *e {
4418		t.Errorf("lock bad request\ngot\t%v\nwant\t%v", g, e)
4419	}
4420}
4421
4422func (t *lockTest) callUnlock(req *lockReq) {
4423	t.Logf("calling unlock")
4424	var nothing struct{}
4425	if err := t.control.JSON("/unlock").Call(t.ctx, req, &nothing); err != nil {
4426		t.Fatalf("calling helper: %v", err)
4427	}
4428	lockReq, _ := t.recordedLockRequest()
4429	t.Logf("previous lock request: %v", lockReq)
4430	want := &fuse.UnlockRequest{
4431		LockOwner: lockReq.LockOwner,
4432		LockFlags: 0,
4433		Lock: fuse.FileLock{
4434			Start: uint64(req.Start),
4435			End:   uint64(req.Start) + uint64(req.Len) - 1,
4436			Type:  fuse.LockUnlock,
4437			PID:   0,
4438		},
4439	}
4440	if t.kind == "flock" {
4441		want.LockFlags |= fuse.LockFlock
4442		want.Lock.Start = 0
4443		want.Lock.End = 0x7fff_ffff_ffff_ffff
4444	}
4445	got := t.child.unlock.Recorded().(*fuse.UnlockRequest)
4446	// dynamic values that are too hard to control
4447	if got.Handle == 0 {
4448		t.Errorf("got UnlockRequest with no Handle")
4449	}
4450	want.Handle = got.Handle
4451	if g, e := got, want; *g != *e {
4452		t.Errorf("unlock bad request\ngot\t%v\nwant\t%v", g, e)
4453	}
4454}
4455
4456func (t *lockTest) callCloseToUnlock() {
4457	t.Logf("calling close with automatic unlock")
4458	var nothing struct{}
4459	if err := t.control.JSON("/close").Call(t.ctx, struct{}{}, &nothing); err != nil {
4460		t.Fatalf("calling helper: %v", err)
4461	}
4462
4463	if t.kind == "posix" {
4464		lockReq, _ := t.recordedLockRequest()
4465		want := &fuse.FlushRequest{
4466			LockOwner: lockReq.LockOwner,
4467		}
4468		got := t.child.flush.Recorded().(*fuse.FlushRequest)
4469		// dynamic values that are too hard to control
4470		if got.Handle == 0 {
4471			t.Errorf("got FlushRequest with no Handle")
4472		}
4473		want.Handle = got.Handle
4474		if g, e := got, want; *g != *e {
4475			t.Errorf("close to unlock bad flush request\ngot\t%v\nwant\t%v", g, e)
4476		}
4477	}
4478
4479	if t.kind == "flock" || t.kind == "ofd" {
4480		lockReq, _ := t.recordedLockRequest()
4481		want := &fuse.ReleaseRequest{
4482			Flags:     fuse.OpenReadWrite | fuse.OpenNonblock,
4483			LockOwner: lockReq.LockOwner,
4484		}
4485		if t.kind == "ofd" {
4486			// TODO linux kernel ofd FUSE support is very partial;
4487			// disable parts that don't work
4488			want.LockOwner = 0
4489		}
4490		if t.kind == "flock" {
4491			want.ReleaseFlags |= fuse.ReleaseFlockUnlock
4492		}
4493		got, ok := t.child.release.WaitForRelease(1 * time.Second)
4494		if !ok {
4495			t.Fatalf("Close did not Release in time")
4496		}
4497		// dynamic values that are too hard to control
4498		if got.Handle == 0 {
4499			t.Errorf("got ReleaseRequest with no Handle")
4500		}
4501		want.Handle = got.Handle
4502		if g, e := got, want; *g != *e {
4503			t.Errorf("bad release:\ngot\t%v\nwant\t%v", g, e)
4504		}
4505	}
4506}
4507
4508func (t *lockTest) callQueryLock(req *lockReq) *unix.Flock_t {
4509	t.Logf("calling queryLock")
4510	var resp unix.Flock_t
4511	if err := t.control.JSON("/query").Call(t.ctx, req, &resp); err != nil {
4512		t.Fatalf("calling helper: %v", err)
4513	}
4514	return &resp
4515}
4516
4517type lockFamily struct {
4518	name         string
4519	mountOptions []fuse.MountOption
4520	helper       *spawntest.Helper
4521}
4522
4523func (family lockFamily) run(t *testing.T, name string, fn func(t *lockTest)) {
4524	t.Helper()
4525	t.Run(name, func(t *testing.T) {
4526		maybeParallel(t)
4527		ctx, cancel := context.WithCancel(context.Background())
4528		defer cancel()
4529		child := &lockFile{}
4530		mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": child}}, nil, family.mountOptions...)
4531		if err != nil {
4532			t.Fatal(err)
4533		}
4534		defer mnt.Close()
4535		control := family.helper.Spawn(ctx, t)
4536		defer control.Close()
4537		lt := &lockTest{
4538			T:       t,
4539			ctx:     ctx,
4540			kind:    family.name,
4541			child:   child,
4542			mnt:     mnt,
4543			control: control,
4544		}
4545		fn(lt)
4546	})
4547}
4548
4549func TestLocking(t *testing.T) {
4550	if runtime.GOOS == "freebsd" {
4551		// Non-exhaustive list of issues encountered, too many to add
4552		// workarounds or kludge tests:
4553		//
4554		//     - flock is not implemented
4555		//     - F_SETLKW comes through as non-blocking
4556		//     - fcntl F_UNLCK calls give EINVAL for some reason
4557		//     - LockRequest.LockOwner == 0
4558		//     - LockRequest.Lock.PID == 0, while Linux fills it
4559		t.Skip("FreeBSD locking support does not work")
4560	}
4561
4562	t.Run("Flock", func(t *testing.T) {
4563		run := lockFamily{
4564			name:         "flock",
4565			mountOptions: []fuse.MountOption{fuse.LockingFlock()},
4566			helper:       lockFlockHelper,
4567		}.run
4568		run(t, "Nonblock", func(t *lockTest) {
4569			t.callLock(&lockReq{})
4570			t.callUnlock(&lockReq{})
4571		})
4572		run(t, "Wait", func(t *lockTest) {
4573			t.callLock(&lockReq{
4574				Wait: true,
4575			})
4576			t.callUnlock(&lockReq{})
4577		})
4578		run(t, "CloseUnlocks", func(t *lockTest) {
4579			t.callLock(&lockReq{})
4580			t.callCloseToUnlock()
4581		})
4582	})
4583
4584	t.Run("POSIX", func(t *testing.T) {
4585		run := lockFamily{
4586			name:         "posix",
4587			mountOptions: []fuse.MountOption{fuse.LockingPOSIX()},
4588			helper:       lockPOSIXHelper,
4589		}.run
4590		run(t, "Nonblock", func(t *lockTest) {
4591			lr := &lockReq{
4592				Start: 42,
4593				Len:   13,
4594			}
4595			t.callLock(lr)
4596			t.callUnlock(lr)
4597		})
4598		run(t, "Wait", func(t *lockTest) {
4599			lr := &lockReq{
4600				Wait:  true,
4601				Start: 42,
4602				Len:   13,
4603			}
4604			t.callLock(lr)
4605			t.callUnlock(lr)
4606		})
4607		run(t, "CloseUnlocks", func(t *lockTest) {
4608			t.callLock(&lockReq{
4609				Wait:  true,
4610				Start: 42,
4611				Len:   13,
4612			})
4613			t.callCloseToUnlock()
4614		})
4615		run(t, "QueryLock", func(t *lockTest) {
4616			t.callLock(&lockReq{
4617				Wait:  true,
4618				Start: 42,
4619				Len:   13,
4620			})
4621			got := t.callQueryLock(&lockReq{
4622				Start: 40,
4623				Len:   10,
4624			})
4625			want := &unix.Flock_t{
4626				Type:   unix.F_UNLCK,
4627				Whence: io.SeekStart,
4628				Start:  40,
4629				Len:    10,
4630				Pid:    0,
4631			}
4632			if g, e := got, want; *g != *e {
4633				t.Errorf("bad query lock\ngot\t%#v\nwant\t%#v", g, e)
4634			}
4635		})
4636	})
4637
4638	t.Run("OpenFileDescription", func(t *testing.T) {
4639		if runtime.GOOS != "linux" {
4640			t.Skip("Open File Descriptor locks are Linux-only")
4641		}
4642		run := lockFamily{
4643			name:         "ofd",
4644			mountOptions: []fuse.MountOption{fuse.LockingPOSIX()},
4645			helper:       lockOFDHelper,
4646		}.run
4647		run(t, "Nonblock", func(t *lockTest) {
4648			lr := &lockReq{
4649				Start: 42,
4650				Len:   13,
4651			}
4652			t.callLock(lr)
4653			t.callUnlock(lr)
4654		})
4655		run(t, "Wait", func(t *lockTest) {
4656			lr := &lockReq{
4657				Wait:  true,
4658				Start: 42,
4659				Len:   13,
4660			}
4661			t.callLock(lr)
4662			t.callUnlock(lr)
4663		})
4664		run(t, "CloseUnlocks", func(t *lockTest) {
4665			t.callLock(&lockReq{
4666				Wait:  true,
4667				Start: 42,
4668				Len:   13,
4669			})
4670			t.callCloseToUnlock()
4671		})
4672		run(t, "QueryLock", func(t *lockTest) {
4673			t.callLock(&lockReq{
4674				Wait:  true,
4675				Start: 42,
4676				Len:   13,
4677			})
4678			got := t.callQueryLock(&lockReq{
4679				Start: 40,
4680				Len:   10,
4681			})
4682			want := &unix.Flock_t{
4683				Type:   unix.F_UNLCK,
4684				Whence: io.SeekStart,
4685				Start:  40,
4686				Len:    10,
4687				Pid:    0,
4688			}
4689			if g, e := got, want; *g != *e {
4690				t.Errorf("bad query lock\ngot\t%#v\nwant\t%#v", g, e)
4691			}
4692		})
4693	})
4694
4695}
4696