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  }