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, ¬hing); 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", ¬hing); 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", ¬hing); 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", ¬hing); 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", ¬hing); err != nil { 731 t.Fatalf("calling helper: %v", err) 732 } 733 if err := control.JSON("/fsync").Call(ctx, struct{}{}, ¬hing); 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{}{}, ¬hing); 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", ¬hing); 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, ¬hing); 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", ¬hing); 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", ¬hing); 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", ¬hing); 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, ¬hing); 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, ¬hing); 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, ¬hing); 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, ¬hing); 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", ¬hing); 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, ¬hing); 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, ¬hing); 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, ¬hing); 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, ¬hing); 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", ¬hing); 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, ¬hing); 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, ¬hing); 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, ¬hing); 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", ¬hing); 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, ¬hing); 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, ¬hing); 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, ¬hing); 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, ¬hing); 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, ¬hing); 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, ¬hing); 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, ¬hing); 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, ¬hing); 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, ¬hing); 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", ¬hing); 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", ¬hing); 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", ¬hing); 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", ¬hing); 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, ¬hing); 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, ¬hing); 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, ¬hing); 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, ¬hing); 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{}{}, ¬hing); 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