github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/test/engine_fs_test.go (about) 1 // Copyright 2016 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 // Without any build tags the tests are run on libkbfs directly. 6 // With the tag dokan all tests are run through a dokan filesystem. 7 // With the tag fuse all tests are run through a fuse filesystem. 8 // Note that fuse cannot be compiled on Windows and Dokan can only 9 // be compiled on Windows. 10 11 package test 12 13 import ( 14 "encoding/json" 15 "fmt" 16 "io" 17 "os" 18 "path/filepath" 19 "testing" 20 "time" 21 22 "github.com/keybase/client/go/kbfs/data" 23 "github.com/keybase/client/go/kbfs/ioutil" 24 "github.com/keybase/client/go/kbfs/kbfsmd" 25 "github.com/keybase/client/go/kbfs/libcontext" 26 "github.com/keybase/client/go/kbfs/libfs" 27 "github.com/keybase/client/go/kbfs/libkbfs" 28 "github.com/keybase/client/go/kbfs/tlf" 29 kbname "github.com/keybase/client/go/kbun" 30 "github.com/keybase/client/go/protocol/keybase1" 31 "golang.org/x/net/context" 32 ) 33 34 type createUserFn func( // nolint 35 tb testing.TB, ith int, config *libkbfs.ConfigLocal, 36 opTimeout time.Duration) *fsUser 37 38 type fsEngine struct { // nolint 39 name string 40 tb testing.TB 41 createUser createUserFn 42 // journal directory 43 journalDir string 44 } 45 type fsNode struct { // nolint 46 path string 47 } 48 49 type fsUser struct { // nolint 50 mntDir string 51 username kbname.NormalizedUsername 52 config *libkbfs.ConfigLocal 53 cancel func() 54 close func() 55 } 56 57 // It's important that this be called, even on error paths, as it may 58 // do unmounts and release locks. 59 func (u *fsUser) shutdown() { 60 u.cancel() 61 u.close() 62 } 63 64 // Name returns the name of the Engine. 65 func (e *fsEngine) Name() string { 66 return e.name 67 } 68 69 // GetUID is called by the test harness to retrieve a user instance's UID. 70 func (e *fsEngine) GetUID(user User) keybase1.UID { 71 u := user.(*fsUser) 72 ctx := context.Background() 73 session, err := u.config.KBPKI().GetCurrentSession(ctx) 74 if err != nil { 75 e.tb.Fatalf("GetUID: GetCurrentSession failed with %v", err) 76 } 77 return session.UID 78 } 79 80 func buildRootPath(u *fsUser, t tlf.Type) string { // nolint 81 var path string 82 switch t { 83 case tlf.Public: 84 // TODO: Consolidate all "public" and "private" 85 // constants in libkbfs. 86 path = filepath.Join(u.mntDir, "public") 87 case tlf.Private: 88 path = filepath.Join(u.mntDir, "private") 89 case tlf.SingleTeam: 90 path = filepath.Join(u.mntDir, "team") 91 default: 92 panic(fmt.Sprintf("Unknown TLF type: %s", t)) 93 } 94 return path 95 } 96 97 func buildTlfPath(u *fsUser, tlfName string, t tlf.Type) string { // nolint 98 return filepath.Join(buildRootPath(u, t), tlfName) 99 } 100 101 func (e *fsEngine) GetFavorites(user User, t tlf.Type) (map[string]bool, error) { 102 u := user.(*fsUser) 103 path := buildRootPath(u, t) 104 f, err := os.Open(path) 105 if err != nil { 106 return nil, err 107 } 108 defer f.Close() 109 fis, err := f.Readdir(-1) 110 if err != nil { 111 return nil, fmt.Errorf("Readdir on %v failed: %q", f, err.Error()) 112 } 113 favorites := make(map[string]bool) 114 for _, fi := range fis { 115 favorites[fi.Name()] = true 116 } 117 return favorites, nil 118 } 119 120 // GetRootDir implements the Engine interface. 121 func (e *fsEngine) GetRootDir(user User, tlfName string, t tlf.Type, expectedCanonicalTlfName string) (dir Node, err error) { 122 u := user.(*fsUser) 123 preferredName, err := tlf.CanonicalToPreferredName(u.username, 124 tlf.CanonicalName(tlfName)) 125 if err != nil { 126 return nil, err 127 } 128 expectedPreferredName, err := tlf.CanonicalToPreferredName(u.username, 129 tlf.CanonicalName(expectedCanonicalTlfName)) 130 if err != nil { 131 return nil, err 132 } 133 path := buildTlfPath(u, tlfName, t) 134 var realPath string 135 if e.name == "dokan" { 136 // TODO avoid trying to dereference symlinks for dokan. This 137 // works but is not ideal. (See Lookup.) 138 realPath = buildTlfPath(u, string(preferredName), t) 139 } else { 140 realPath, err = filepath.EvalSymlinks(path) 141 if err != nil { 142 return nil, err 143 } 144 if preferredName != expectedPreferredName { 145 realName := filepath.Base(realPath) 146 if realName != string(expectedPreferredName) { 147 return nil, fmt.Errorf( 148 "Expected preferred TLF name %s, got %s", 149 expectedPreferredName, realName) 150 } 151 } 152 } 153 return fsNode{realPath}, nil 154 } 155 156 // GetRootDirAtRevision implements the Engine interface. 157 func (e *fsEngine) GetRootDirAtRevision( 158 u User, tlfName string, t tlf.Type, rev kbfsmd.Revision, 159 expectedCanonicalTlfName string) (dir Node, err error) { 160 d, err := e.GetRootDir(u, tlfName, t, expectedCanonicalTlfName) 161 if err != nil { 162 return nil, err 163 } 164 p := d.(fsNode) 165 revDir := libfs.ArchivedRevDirPrefix + rev.String() 166 return fsNode{filepath.Join(p.path, revDir)}, nil 167 } 168 169 // GetRootDirAtTimeString implements the Engine interface. 170 func (e *fsEngine) GetRootDirAtTimeString( 171 u User, tlfName string, t tlf.Type, timeString string, 172 expectedCanonicalTlfName string) (dir Node, err error) { 173 d, err := e.GetRootDir(u, tlfName, t, expectedCanonicalTlfName) 174 if err != nil { 175 return nil, err 176 } 177 p := d.(fsNode) 178 timeLink := libfs.ArchivedTimeLinkPrefix + timeString 179 return fsNode{filepath.Join(p.path, timeLink)}, nil 180 } 181 182 // GetRootDirAtRelTimeString implements the Engine interface. 183 func (e *fsEngine) GetRootDirAtRelTimeString( 184 u User, tlfName string, t tlf.Type, relTimeString string, 185 expectedCanonicalTlfName string) (dir Node, err error) { 186 d, err := e.GetRootDir(u, tlfName, t, expectedCanonicalTlfName) 187 if err != nil { 188 return nil, err 189 } 190 p := d.(fsNode) 191 fileName := libfs.ArchivedRelTimeFilePrefix + relTimeString 192 revDir, err := ioutil.ReadFile(filepath.Join(p.path, fileName)) 193 if err != nil { 194 return nil, err 195 } 196 197 return fsNode{filepath.Join(p.path, string(revDir))}, nil 198 } 199 200 // CreateDir is called by the test harness to create a directory relative to the passed 201 // parent directory for the given user. 202 func (*fsEngine) CreateDir(u User, parentDir Node, name string) (dir Node, err error) { 203 p := parentDir.(fsNode) 204 path := filepath.Join(p.path, name) 205 err = ioutil.Mkdir(path, 0755) 206 if err != nil { 207 return nil, err 208 } 209 return fsNode{path}, nil 210 } 211 212 // CreateFile is called by the test harness to create a file in the given directory as 213 // the given user. 214 func (*fsEngine) CreateFile(u User, parentDir Node, name string) (file Node, err error) { 215 p := parentDir.(fsNode) 216 path := filepath.Join(p.path, name) 217 f, err := os.Create(path) 218 if err != nil { 219 return nil, err 220 } 221 f.Close() 222 return fsNode{path}, nil 223 } 224 225 // CreateFileExcl is called by the test harness to exclusively create a file in 226 // the given directory as the given user. The file is created with 227 // O_RDWR|O_CREATE|O_EXCL. 228 func (*fsEngine) CreateFileExcl(u User, parentDir Node, name string) (file Node, err error) { 229 p := parentDir.(fsNode).path 230 f, err := os.OpenFile(filepath.Join(p, name), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) 231 if err != nil { 232 return nil, err 233 } 234 f.Close() 235 return fsNode{p}, nil 236 } 237 238 // WriteFile is called by the test harness to write to the given file as the given user. 239 func (*fsEngine) WriteFile(u User, file Node, data []byte, off int64, sync bool) (err error) { 240 n := file.(fsNode) 241 f, err := os.OpenFile(n.path, os.O_RDWR|os.O_CREATE, 0644) 242 if err != nil { 243 return err 244 } 245 defer f.Close() 246 _, err = f.Seek(off, 0) 247 if err != nil { 248 return err 249 } 250 _, err = f.Write(data) 251 if err != nil { 252 return err 253 } 254 if !sync { 255 return nil 256 } 257 return f.Sync() 258 } 259 260 // TruncateFile is called by the test harness to truncate the given file as the given user to the given size. 261 func (*fsEngine) TruncateFile(u User, file Node, size uint64, sync bool) (err error) { 262 n := file.(fsNode) 263 f, err := os.OpenFile(n.path, os.O_RDWR|os.O_CREATE, 0644) 264 if err != nil { 265 return err 266 } 267 defer f.Close() 268 err = f.Truncate(int64(size)) 269 if err != nil { 270 return err 271 } 272 if !sync { 273 return nil 274 } 275 return f.Sync() 276 } 277 278 // RemoveDir is called by the test harness as the given user to remove a subdirectory. 279 func (*fsEngine) RemoveDir(u User, dir Node, name string) (err error) { 280 n := dir.(fsNode) 281 return ioutil.Remove(filepath.Join(n.path, name)) 282 } 283 284 // RemoveEntry is called by the test harness as the given user to remove a directory entry. 285 func (*fsEngine) RemoveEntry(u User, dir Node, name string) (err error) { 286 n := dir.(fsNode) 287 return ioutil.Remove(filepath.Join(n.path, name)) 288 } 289 290 // Rename is called by the test harness as the given user to rename a node. 291 func (*fsEngine) Rename(u User, srcDir Node, srcName string, dstDir Node, dstName string) (err error) { 292 snode := srcDir.(fsNode) 293 dnode := dstDir.(fsNode) 294 return ioutil.Rename( 295 filepath.Join(snode.path, srcName), 296 filepath.Join(dnode.path, dstName)) 297 } 298 299 // ReadFile is called by the test harness to read from the given file as the given user. 300 func (e *fsEngine) ReadFile(u User, file Node, off int64, bs []byte) (int, error) { 301 n := file.(fsNode) 302 f, err := os.Open(n.path) 303 if err != nil { 304 return 0, err 305 } 306 defer f.Close() 307 return io.ReadFull(io.NewSectionReader(f, off, int64(len(bs))), bs) 308 } 309 310 // GetDirChildrenTypes is called by the test harness as the given user to return a map of child nodes 311 // and their type names. 312 func (*fsEngine) GetDirChildrenTypes(u User, parentDir Node) (children map[string]string, err error) { 313 n := parentDir.(fsNode) 314 f, err := os.Open(n.path) 315 if err != nil { 316 return nil, err 317 } 318 defer f.Close() 319 fis, err := f.Readdir(-1) 320 if err != nil { 321 return nil, fmt.Errorf("Readdir on %v failed: %q", f, err.Error()) 322 } 323 children = map[string]string{} 324 for _, fi := range fis { 325 children[fi.Name()] = fiTypeString(fi) 326 } 327 return children, nil 328 } 329 330 func (*fsEngine) DisableUpdatesForTesting(user User, tlfName string, t tlf.Type) (err error) { 331 u := user.(*fsUser) 332 path := buildTlfPath(u, tlfName, t) 333 return ioutil.WriteFile( 334 filepath.Join(path, libfs.DisableUpdatesFileName), 335 []byte("off"), 0644) 336 } 337 338 // MakeNaïveStaller implements the Engine interface. 339 func (*fsEngine) MakeNaïveStaller(u User) *libkbfs.NaïveStaller { 340 return libkbfs.NewNaïveStaller(u.(*fsUser).config) 341 } 342 343 // ReenableUpdatesForTesting is called by the test harness as the given user to resume updates 344 // if previously disabled for testing. 345 func (*fsEngine) ReenableUpdates(user User, tlfName string, t tlf.Type) (err error) { 346 u := user.(*fsUser) 347 path := buildTlfPath(u, tlfName, t) 348 return ioutil.WriteFile( 349 filepath.Join(path, libfs.EnableUpdatesFileName), 350 []byte("on"), 0644) 351 } 352 353 // SyncFromServer is called by the test harness as the given user to 354 // actively retrieve new metadata for a folder. 355 func (e *fsEngine) SyncFromServer(user User, tlfName string, t tlf.Type) (err error) { 356 u := user.(*fsUser) 357 path := buildTlfPath(u, tlfName, t) 358 return ioutil.WriteFile( 359 filepath.Join(path, libfs.SyncFromServerFileName), 360 []byte("x"), 0644) 361 } 362 363 // ForceQuotaReclamation implements the Engine interface. 364 func (*fsEngine) ForceQuotaReclamation(user User, tlfName string, t tlf.Type) (err error) { 365 u := user.(*fsUser) 366 path := buildTlfPath(u, tlfName, t) 367 return ioutil.WriteFile( 368 filepath.Join(path, libfs.ReclaimQuotaFileName), 369 []byte("x"), 0644) 370 } 371 372 // AddNewAssertion implements the Engine interface. 373 func (e *fsEngine) AddNewAssertion(user User, oldAssertion, newAssertion string) error { 374 u := user.(*fsUser) 375 return libkbfs.AddNewAssertionForTest(u.config, oldAssertion, newAssertion) 376 } 377 378 // ChangeTeamName implements the Engine interface. 379 func (e *fsEngine) ChangeTeamName(user User, oldName, newName string) error { 380 u := user.(*fsUser) 381 return libkbfs.ChangeTeamNameForTest(u.config, oldName, newName) 382 } 383 384 // Rekey implements the Engine interface. 385 func (*fsEngine) Rekey(user User, tlfName string, t tlf.Type) error { 386 u := user.(*fsUser) 387 path := buildTlfPath(u, tlfName, t) 388 return ioutil.WriteFile( 389 filepath.Join(path, libfs.RekeyFileName), 390 []byte("x"), 0644) 391 } 392 393 // EnableJournal is called by the test harness as the given user to 394 // enable journaling. 395 func (*fsEngine) EnableJournal(user User, tlfName string, 396 t tlf.Type) (err error) { 397 u := user.(*fsUser) 398 path := buildTlfPath(u, tlfName, t) 399 return ioutil.WriteFile( 400 filepath.Join(path, libfs.EnableJournalFileName), 401 []byte("on"), 0644) 402 } 403 404 // PauseJournal is called by the test harness as the given user to 405 // pause journaling. 406 func (*fsEngine) PauseJournal(user User, tlfName string, 407 t tlf.Type) (err error) { 408 u := user.(*fsUser) 409 path := buildTlfPath(u, tlfName, t) 410 return ioutil.WriteFile( 411 filepath.Join(path, libfs.PauseJournalBackgroundWorkFileName), 412 []byte("on"), 0644) 413 } 414 415 // ResumeJournal is called by the test harness as the given user to 416 // resume journaling. 417 func (*fsEngine) ResumeJournal(user User, tlfName string, 418 t tlf.Type) (err error) { 419 u := user.(*fsUser) 420 path := buildTlfPath(u, tlfName, t) 421 return ioutil.WriteFile( 422 filepath.Join(path, libfs.ResumeJournalBackgroundWorkFileName), 423 []byte("on"), 0644) 424 } 425 426 // FlushJournal is called by the test harness as the given user to 427 // wait for the journal to flush, if enabled. 428 func (*fsEngine) FlushJournal(user User, tlfName string, 429 t tlf.Type) (err error) { 430 u := user.(*fsUser) 431 path := buildTlfPath(u, tlfName, t) 432 return ioutil.WriteFile( 433 filepath.Join(path, libfs.FlushJournalFileName), 434 []byte("on"), 0644) 435 } 436 437 // UnflushedPaths implements the Engine interface. 438 func (*fsEngine) UnflushedPaths(user User, tlfName string, t tlf.Type) ( 439 []string, error) { 440 u := user.(*fsUser) 441 path := buildTlfPath(u, tlfName, t) 442 buf, err := ioutil.ReadFile(filepath.Join(path, libfs.StatusFileName)) 443 if err != nil { 444 return nil, err 445 } 446 447 var bufStatus libkbfs.FolderBranchStatus 448 err = json.Unmarshal(buf, &bufStatus) 449 if err != nil { 450 return nil, err 451 } 452 453 return bufStatus.Journal.UnflushedPaths, nil 454 } 455 456 // UserEditHistory implements the Engine interface. 457 func (*fsEngine) UserEditHistory(user User) ( 458 history []keybase1.FSFolderEditHistory, err error) { 459 u := user.(*fsUser) 460 buf, err := ioutil.ReadFile( 461 filepath.Join(u.mntDir, libfs.EditHistoryName)) 462 if err != nil { 463 return nil, err 464 } 465 466 err = json.Unmarshal(buf, &history) 467 if err != nil { 468 return nil, err 469 } 470 471 return history, nil 472 } 473 474 // DirtyPaths implements the Engine interface. 475 func (*fsEngine) DirtyPaths(user User, tlfName string, t tlf.Type) ( 476 []string, error) { 477 u := user.(*fsUser) 478 path := buildTlfPath(u, tlfName, t) 479 buf, err := ioutil.ReadFile(filepath.Join(path, libfs.StatusFileName)) 480 if err != nil { 481 return nil, err 482 } 483 484 var bufStatus libkbfs.FolderBranchStatus 485 err = json.Unmarshal(buf, &bufStatus) 486 if err != nil { 487 return nil, err 488 } 489 490 return bufStatus.DirtyPaths, nil 491 } 492 493 // TogglePrefetch implements the Engine interface. 494 func (*fsEngine) TogglePrefetch(user User, enable bool) error { 495 u := user.(*fsUser) 496 filename := libfs.DisableBlockPrefetchingFileName 497 if enable { 498 filename = libfs.EnableBlockPrefetchingFileName 499 } 500 return ioutil.WriteFile( 501 filepath.Join(u.mntDir, filename), 502 []byte("1"), 0644) 503 } 504 505 // ForceConflict implements the Engine interface. 506 func (*fsEngine) ForceConflict(user User, tlfName string, t tlf.Type) error { 507 u := user.(*fsUser) 508 509 ctx, cancel := context.WithCancel(context.Background()) 510 defer cancel() 511 ctx, err := libcontext.NewContextWithCancellationDelayer( 512 libcontext.NewContextReplayable( 513 ctx, func(ctx context.Context) context.Context { return ctx })) 514 if err != nil { 515 return err 516 } 517 518 root, err := getRootNode(ctx, u.config, tlfName, t) 519 if err != nil { 520 return err 521 } 522 523 return u.config.KBFSOps().ForceStuckConflictForTesting( 524 ctx, root.GetFolderBranch().Tlf) 525 } 526 527 // ClearConflicts implements the Engine interface. 528 func (*fsEngine) ClearConflicts(user User, tlfName string, t tlf.Type) error { 529 u := user.(*fsUser) 530 531 ctx, cancel := context.WithCancel(context.Background()) 532 defer cancel() 533 ctx, err := libcontext.NewContextWithCancellationDelayer( 534 libcontext.NewContextReplayable( 535 ctx, func(ctx context.Context) context.Context { return ctx })) 536 if err != nil { 537 return err 538 } 539 540 root, err := getRootNode(ctx, u.config, tlfName, t) 541 if err != nil { 542 return err 543 } 544 545 return u.config.KBFSOps().ClearConflictView(ctx, root.GetFolderBranch().Tlf) 546 } 547 548 // Shutdown is called by the test harness when it is done with the 549 // given user. 550 func (e *fsEngine) Shutdown(user User) error { 551 u := user.(*fsUser) 552 u.shutdown() 553 554 // Get the user name before shutting everything down. 555 var userName kbname.NormalizedUsername 556 if e.journalDir != "" { 557 session, err := 558 u.config.KBPKI().GetCurrentSession(context.Background()) 559 if err != nil { 560 return err 561 } 562 userName = session.Name 563 } 564 565 ctx := context.Background() 566 if err := u.config.Shutdown(ctx); err != nil { 567 return err 568 } 569 570 if e.journalDir != "" { 571 // Remove the user journal. 572 if err := ioutil.RemoveAll( 573 filepath.Join(e.journalDir, userName.String())); err != nil { 574 return err 575 } 576 // Remove the overall journal dir if it's empty. 577 if err := ioutil.Remove(e.journalDir); err != nil { 578 e.tb.Logf("Journal dir %s not empty yet", e.journalDir) 579 } 580 } 581 return nil 582 } 583 584 // CreateLink is called by the test harness to create a symlink in the given directory as 585 // the given user. 586 func (*fsEngine) CreateLink(u User, parentDir Node, fromName string, toPath string) (err error) { 587 n := parentDir.(fsNode) 588 return os.Symlink(toPath, filepath.Join(n.path, fromName)) 589 } 590 591 // Lookup is called by the test harness to return a node in the given directory by 592 // its name for the given user. In the case of a symlink the symPath will be set and 593 // the node will be nil. 594 func (e *fsEngine) Lookup(u User, parentDir Node, name string) (file Node, symPath string, err error) { 595 n := parentDir.(fsNode) 596 path := filepath.Join(n.path, name) 597 fi, err := ioutil.Lstat(path) 598 if err != nil { 599 return nil, "", err 600 } 601 // Return if not a symlink 602 // TODO currently we pretend that Dokan has no symbolic links 603 // here and end up deferencing them. This works but is not 604 // ideal. (See GetRootDir.) 605 if fi.Mode()&os.ModeSymlink == 0 || e.name == "dokan" { 606 return fsNode{path}, "", nil 607 } 608 symPath, err = os.Readlink(path) 609 if err != nil { 610 return nil, "", err 611 } 612 return fsNode{path}, symPath, err 613 } 614 615 // SetEx is called by the test harness as the given user to set/unset the executable bit on the 616 // given file. 617 func (*fsEngine) SetEx(u User, file Node, ex bool) (err error) { 618 n := file.(fsNode) 619 var mode os.FileMode = 0644 620 if ex { 621 mode = 0755 622 } 623 return os.Chmod(n.path, mode) 624 } 625 626 // SetMtime is called by the test harness as the given user to set the 627 // mtime on the given file. 628 func (*fsEngine) SetMtime(u User, file Node, mtime time.Time) (err error) { 629 n := file.(fsNode) 630 // KBFS doesn't respect the atime, but we have to give it something 631 atime := mtime 632 return os.Chtimes(n.path, atime, mtime) 633 } 634 635 // GetMtime implements the Engine interface. 636 func (*fsEngine) GetMtime(u User, file Node) (mtime time.Time, err error) { 637 n := file.(fsNode) 638 fi, err := ioutil.Lstat(n.path) 639 if err != nil { 640 return time.Time{}, err 641 } 642 return fi.ModTime(), err 643 } 644 645 type prevRevisions struct { // nolint 646 PrevRevisions data.PrevRevisions 647 } 648 649 // GetPrevRevisions implements the Engine interface. 650 func (*fsEngine) GetPrevRevisions(u User, file Node) ( 651 revs data.PrevRevisions, err error) { 652 n := file.(fsNode) 653 d, f := filepath.Split(n.path) 654 fullPath := filepath.Join(d, libfs.FileInfoPrefix+f) 655 buf, err := ioutil.ReadFile(fullPath) 656 if err != nil { 657 return nil, err 658 } 659 var pr prevRevisions 660 err = json.Unmarshal(buf, &pr) 661 if err != nil { 662 return nil, err 663 } 664 return pr.PrevRevisions, nil 665 } 666 667 // SyncAll implements the Engine interface. 668 func (e *fsEngine) SyncAll( 669 user User, tlfName string, t tlf.Type) (err error) { 670 u := user.(*fsUser) 671 ctx, cancel := context.WithCancel(context.Background()) 672 defer cancel() 673 ctx, err = libcontext.NewContextWithCancellationDelayer( 674 libcontext.NewContextReplayable( 675 ctx, func(ctx context.Context) context.Context { return ctx })) 676 if err != nil { 677 return err 678 } 679 dir, err := getRootNode(ctx, u.config, tlfName, t) 680 if err != nil { 681 return err 682 } 683 // Sadly golang doesn't support syncing on a directory handle, so 684 // we have to hack it by syncing directly with the KBFSOps 685 // instance. TODO: implement a `.kbfs_sync_all` file to be used 686 // here, or maybe use a direct OS syscall? 687 return u.config.KBFSOps().SyncAll(ctx, dir.GetFolderBranch()) 688 } 689 690 func fiTypeString(fi os.FileInfo) string { // nolint 691 m := fi.Mode() 692 switch { 693 case m&os.ModeSymlink != 0: 694 return "SYM" 695 case m.IsRegular() && m&0100 == 0100: 696 return "EXEC" 697 case m.IsRegular(): 698 return "FILE" 699 case m.IsDir(): 700 return "DIR" 701 } 702 return "OTHER" 703 } 704 705 func (e *fsEngine) InitTest(ver kbfsmd.MetadataVer, 706 blockSize int64, blockChangeSize int64, batchSize int, bwKBps int, 707 opTimeout time.Duration, users []kbname.NormalizedUsername, 708 teams, implicitTeams teamMap, clock libkbfs.Clock, 709 journal bool) map[kbname.NormalizedUsername]User { 710 res := map[kbname.NormalizedUsername]User{} 711 initSuccess := false 712 defer func() { 713 if !initSuccess { 714 for _, user := range res { 715 user.(*fsUser).shutdown() 716 } 717 } 718 }() 719 720 if int(opTimeout) > 0 { 721 // TODO: wrap fs calls in our own timeout-able layer? 722 e.tb.Log("Ignoring op timeout for FS test") 723 } 724 725 // create the first user specially 726 config0 := libkbfs.MakeTestConfigOrBust(e.tb, users...) 727 config0.SetMetadataVersion(ver) 728 config0.SetClock(clock) 729 730 setBlockSizes(e.tb, config0, blockSize, blockChangeSize) 731 if batchSize > 0 { 732 config0.SetBGFlushDirOpBatchSize(batchSize) 733 } 734 maybeSetBw(e.tb, config0, bwKBps) 735 uids := make([]keybase1.UID, len(users)) 736 cfgs := make([]*libkbfs.ConfigLocal, len(users)) 737 cfgs[0] = config0 738 uids[0] = nameToUID(e.tb, config0) 739 for i, name := range users[1:] { 740 c := libkbfs.ConfigAsUser(config0, name) 741 setBlockSizes(e.tb, c, blockSize, blockChangeSize) 742 if batchSize > 0 { 743 c.SetBGFlushDirOpBatchSize(batchSize) 744 } 745 c.SetClock(clock) 746 cfgs[i+1] = c 747 uids[i+1] = nameToUID(e.tb, c) 748 } 749 750 for i, name := range users { 751 res[name] = e.createUser(e.tb, i, cfgs[i], opTimeout) 752 } 753 754 if journal { 755 jdir, err := ioutil.TempDir(os.TempDir(), "kbfs_journal") 756 if err != nil { 757 e.tb.Fatalf("Couldn't enable journaling: %v", err) 758 } 759 e.journalDir = jdir 760 e.tb.Logf("Journal directory: %s", e.journalDir) 761 for i, c := range cfgs { 762 journalRoot := filepath.Join(jdir, users[i].String()) 763 err = c.EnableDiskLimiter(journalRoot) 764 if err != nil { 765 panic(fmt.Sprintf("No disk limiter for %d: %+v", i, err)) 766 } 767 err = c.EnableJournaling(context.Background(), 768 journalRoot, libkbfs.TLFJournalBackgroundWorkEnabled) 769 if err != nil { 770 panic(fmt.Sprintf("Couldn't enable journaling: %+v", err)) 771 } 772 jManager, err := libkbfs.GetJournalManager(c) 773 if err != nil { 774 panic(fmt.Sprintf("No journal server for %d: %+v", i, err)) 775 } 776 err = jManager.DisableAuto(context.Background()) 777 if err != nil { 778 panic(fmt.Sprintf("Couldn't disable journaling: %+v", err)) 779 } 780 } 781 } 782 783 for _, c := range cfgs { 784 makeTeams(e.tb, c, e, teams, res) 785 makeImplicitTeams(e.tb, c, e, implicitTeams, res) 786 } 787 788 initSuccess = true 789 return res 790 } 791 792 func nameToUID(t testing.TB, config libkbfs.Config) keybase1.UID { // nolint 793 session, err := config.KBPKI().GetCurrentSession(context.Background()) 794 if err != nil { 795 t.Fatal(err) 796 } 797 return session.UID 798 }