github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/test/engine_libkbfs.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  package test
     6  
     7  import (
     8  	"fmt"
     9  	"os"
    10  	"path/filepath"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/keybase/client/go/kbfs/data"
    15  	"github.com/keybase/client/go/kbfs/idutil"
    16  	"github.com/keybase/client/go/kbfs/ioutil"
    17  	"github.com/keybase/client/go/kbfs/kbfsmd"
    18  	"github.com/keybase/client/go/kbfs/libcontext"
    19  	"github.com/keybase/client/go/kbfs/libfs"
    20  	"github.com/keybase/client/go/kbfs/libkbfs"
    21  	"github.com/keybase/client/go/kbfs/tlf"
    22  	"github.com/keybase/client/go/kbfs/tlfhandle"
    23  	kbname "github.com/keybase/client/go/kbun"
    24  	"github.com/keybase/client/go/logger"
    25  	"github.com/keybase/client/go/protocol/keybase1"
    26  	"github.com/pkg/errors"
    27  	"golang.org/x/net/context"
    28  )
    29  
    30  // LibKBFS implements the Engine interface for direct test harness usage of libkbfs.
    31  type LibKBFS struct {
    32  	// hack: hold references on behalf of the test harness
    33  	refs map[libkbfs.Config]map[libkbfs.Node]bool
    34  	// channels used to re-enable updates if disabled
    35  	updateChannels map[libkbfs.Config]map[data.FolderBranch]chan<- struct{}
    36  	// test object, for logging.
    37  	tb testing.TB
    38  	// timeout for all KBFS calls
    39  	opTimeout time.Duration
    40  	// journal directory
    41  	journalDir string
    42  }
    43  
    44  // Check that LibKBFS fully implements the Engine interface.
    45  var _ Engine = (*LibKBFS)(nil)
    46  
    47  // Name returns the name of the Engine.
    48  func (k *LibKBFS) Name() string {
    49  	return "libkbfs"
    50  }
    51  
    52  // InitTest implements the Engine interface.
    53  func (k *LibKBFS) InitTest(ver kbfsmd.MetadataVer,
    54  	blockSize int64, blockChangeSize int64, batchSize int, bwKBps int,
    55  	opTimeout time.Duration, users []kbname.NormalizedUsername,
    56  	teams, implicitTeams teamMap, clock libkbfs.Clock,
    57  	journal bool) map[kbname.NormalizedUsername]User {
    58  	userMap := make(map[kbname.NormalizedUsername]User)
    59  	// create the first user specially
    60  	config := libkbfs.MakeTestConfigOrBust(k.tb, users...)
    61  	config.SetMetadataVersion(ver)
    62  
    63  	setBlockSizes(k.tb, config, blockSize, blockChangeSize)
    64  	if batchSize > 0 {
    65  		config.SetBGFlushDirOpBatchSize(batchSize)
    66  	}
    67  	maybeSetBw(k.tb, config, bwKBps)
    68  	k.opTimeout = opTimeout
    69  
    70  	config.SetClock(clock)
    71  	userMap[users[0]] = config
    72  	k.refs[config] = make(map[libkbfs.Node]bool)
    73  	k.updateChannels[config] = make(map[data.FolderBranch]chan<- struct{})
    74  
    75  	// create the rest of the users as copies of the original config
    76  	for _, name := range users[1:] {
    77  		c := libkbfs.ConfigAsUser(config, name)
    78  		setBlockSizes(k.tb, c, blockSize, blockChangeSize)
    79  		if batchSize > 0 {
    80  			c.SetBGFlushDirOpBatchSize(batchSize)
    81  		}
    82  		c.SetClock(clock)
    83  		userMap[name] = c
    84  		k.refs[c] = make(map[libkbfs.Node]bool)
    85  		k.updateChannels[c] = make(map[data.FolderBranch]chan<- struct{})
    86  	}
    87  
    88  	if journal {
    89  		jdir, err := os.MkdirTemp(os.TempDir(), "kbfs_journal")
    90  		if err != nil {
    91  			k.tb.Fatalf("Couldn't enable journaling: %v", err)
    92  		}
    93  		k.journalDir = jdir
    94  		k.tb.Logf("Journal directory: %s", k.journalDir)
    95  		for name, c := range userMap {
    96  			config := c.(*libkbfs.ConfigLocal)
    97  			journalRoot := filepath.Join(jdir, name.String())
    98  			err = config.EnableDiskLimiter(journalRoot)
    99  			if err != nil {
   100  				panic(fmt.Sprintf("No disk limiter for %s: %+v", name, err))
   101  			}
   102  			err = config.EnableJournaling(context.Background(), journalRoot,
   103  				libkbfs.TLFJournalBackgroundWorkEnabled)
   104  			if err != nil {
   105  				panic(fmt.Sprintf("Couldn't enable journaling: %+v", err))
   106  			}
   107  			jManager, err := libkbfs.GetJournalManager(config)
   108  			if err != nil {
   109  				panic(fmt.Sprintf("No journal server for %s: %+v", name, err))
   110  			}
   111  			err = jManager.DisableAuto(context.Background())
   112  			if err != nil {
   113  				panic(fmt.Sprintf("Couldn't disable journaling: %+v", err))
   114  			}
   115  		}
   116  	}
   117  
   118  	for _, u := range userMap {
   119  		c := u.(libkbfs.Config)
   120  		makeTeams(k.tb, c, k, teams, userMap)
   121  		makeImplicitTeams(k.tb, c, k, implicitTeams, userMap)
   122  	}
   123  
   124  	return userMap
   125  }
   126  
   127  const (
   128  	// CtxOpID is the display name for the unique operation test ID tag.
   129  	CtxOpID = "TID"
   130  
   131  	// CtxOpUser is the display name for the user tag.
   132  	CtxOpUser = "User"
   133  )
   134  
   135  // CtxTagKey is the type used for unique context tags
   136  type CtxTagKey int
   137  
   138  const (
   139  	// CtxIDKey is the type of the tag for unique operation IDs.
   140  	CtxIDKey CtxTagKey = iota
   141  
   142  	// CtxUserKey is the type of the user tag.
   143  	CtxUserKey
   144  )
   145  
   146  func (k *LibKBFS) newContext(u User) (context.Context, context.CancelFunc) {
   147  	ctx := context.Background()
   148  
   149  	config, ok := u.(libkbfs.Config)
   150  	if !ok {
   151  		panic("passed parameter isn't a config object")
   152  	}
   153  	session, err := config.KBPKI().GetCurrentSession(ctx)
   154  	if err != nil {
   155  		panic(err)
   156  	}
   157  
   158  	var cancel context.CancelFunc
   159  	if k.opTimeout > 0 {
   160  		ctx, cancel = context.WithTimeout(ctx, k.opTimeout)
   161  	} else {
   162  		ctx, cancel = context.WithCancel(ctx)
   163  	}
   164  
   165  	id, errRandomRequestID := libkbfs.MakeRandomRequestID()
   166  	ctx, err = libcontext.NewContextWithCancellationDelayer(libcontext.NewContextReplayable(
   167  		ctx, func(ctx context.Context) context.Context {
   168  			logTags := logger.CtxLogTags{
   169  				CtxIDKey:   CtxOpID,
   170  				CtxUserKey: CtxOpUser,
   171  			}
   172  			ctx = logger.NewContextWithLogTags(ctx, logTags)
   173  
   174  			// Add a unique ID to this context, identifying a particular
   175  			// request.
   176  			if errRandomRequestID == nil {
   177  				ctx = context.WithValue(ctx, CtxIDKey, id)
   178  			}
   179  
   180  			ctx = context.WithValue(ctx, CtxUserKey, session.Name)
   181  
   182  			return ctx
   183  		}))
   184  	if err != nil {
   185  		panic(err)
   186  	}
   187  
   188  	return ctx, func() {
   189  		err := libcontext.CleanupCancellationDelayer(ctx)
   190  		if err != nil {
   191  			panic(err)
   192  		}
   193  		cancel()
   194  	}
   195  }
   196  
   197  // GetUID implements the Engine interface.
   198  func (k *LibKBFS) GetUID(u User) (uid keybase1.UID) {
   199  	config, ok := u.(libkbfs.Config)
   200  	if !ok {
   201  		panic("passed parameter isn't a config object")
   202  	}
   203  	var err error
   204  	ctx, cancel := k.newContext(u)
   205  	defer cancel()
   206  	session, err := config.KBPKI().GetCurrentSession(ctx)
   207  	if err != nil {
   208  		panic(err.Error())
   209  	}
   210  	return session.UID
   211  }
   212  
   213  func parseTlfHandle(
   214  	ctx context.Context, kbpki libkbfs.KBPKI, mdOps libkbfs.MDOps,
   215  	osg idutil.OfflineStatusGetter, tlfName string, t tlf.Type) (
   216  	h *tlfhandle.Handle, err error) {
   217  	// Limit to one non-canonical name for now.
   218  outer:
   219  	for i := 0; i < 2; i++ {
   220  		h, err = tlfhandle.ParseHandle(ctx, kbpki, mdOps, osg, tlfName, t)
   221  		switch err := errors.Cause(err).(type) {
   222  		case nil:
   223  			break outer
   224  		case idutil.TlfNameNotCanonical:
   225  			tlfName = err.NameToTry
   226  		default:
   227  			return nil, err
   228  		}
   229  	}
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  	return h, nil
   234  }
   235  
   236  // GetFavorites implements the Engine interface.
   237  func (k *LibKBFS) GetFavorites(u User, t tlf.Type) (map[string]bool, error) {
   238  	config := u.(*libkbfs.ConfigLocal)
   239  	ctx, cancel := k.newContext(u)
   240  	defer cancel()
   241  	favorites, err := config.KBFSOps().GetFavorites(ctx)
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  	favoritesMap := make(map[string]bool)
   246  	for _, f := range favorites {
   247  		if f.Type != t {
   248  			continue
   249  		}
   250  		favoritesMap[f.Name] = true
   251  	}
   252  	return favoritesMap, nil
   253  }
   254  
   255  func (k *LibKBFS) getRootDir(
   256  	u User, tlfName string, t tlf.Type, branch data.BranchName,
   257  	expectedCanonicalTlfName string) (dir Node, err error) {
   258  	config := u.(*libkbfs.ConfigLocal)
   259  
   260  	ctx, cancel := k.newContext(u)
   261  	defer cancel()
   262  	h, err := parseTlfHandle(
   263  		ctx, config.KBPKI(), config.MDOps(), config, tlfName, t)
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  
   268  	if string(h.GetCanonicalName()) != expectedCanonicalTlfName {
   269  		return nil, fmt.Errorf("Expected canonical TLF name %s, got %s",
   270  			expectedCanonicalTlfName, h.GetCanonicalName())
   271  	}
   272  
   273  	if h.IsLocalConflict() {
   274  		b, ok := data.MakeConflictBranchName(h)
   275  		if ok {
   276  			branch = b
   277  		}
   278  	}
   279  
   280  	if branch == data.MasterBranch {
   281  		dir, _, err = config.KBFSOps().GetOrCreateRootNode(ctx, h, branch)
   282  	} else {
   283  		dir, _, err = config.KBFSOps().GetRootNode(ctx, h, branch)
   284  	}
   285  	if err != nil {
   286  		return nil, err
   287  	}
   288  
   289  	k.refs[config][dir.(libkbfs.Node)] = true
   290  	return dir, nil
   291  }
   292  
   293  // GetRootDir implements the Engine interface.
   294  func (k *LibKBFS) GetRootDir(
   295  	u User, tlfName string, t tlf.Type, expectedCanonicalTlfName string) (
   296  	dir Node, err error) {
   297  	return k.getRootDir(
   298  		u, tlfName, t, data.MasterBranch, expectedCanonicalTlfName)
   299  }
   300  
   301  // GetRootDirAtRevision implements the Engine interface.
   302  func (k *LibKBFS) GetRootDirAtRevision(
   303  	u User, tlfName string, t tlf.Type, rev kbfsmd.Revision,
   304  	expectedCanonicalTlfName string) (dir Node, err error) {
   305  	return k.getRootDir(
   306  		u, tlfName, t, data.MakeRevBranchName(rev), expectedCanonicalTlfName)
   307  }
   308  
   309  // GetRootDirAtTimeString implements the Engine interface.
   310  func (k *LibKBFS) GetRootDirAtTimeString(
   311  	u User, tlfName string, t tlf.Type, timeString string,
   312  	expectedCanonicalTlfName string) (dir Node, err error) {
   313  	config := u.(*libkbfs.ConfigLocal)
   314  	ctx, cancel := k.newContext(u)
   315  	defer cancel()
   316  	h, err := parseTlfHandle(
   317  		ctx, config.KBPKI(), config.MDOps(), config, tlfName, t)
   318  	if err != nil {
   319  		return nil, err
   320  	}
   321  
   322  	rev, err := libfs.RevFromTimeString(ctx, config, h, timeString)
   323  	if err != nil {
   324  		return nil, err
   325  	}
   326  
   327  	return k.getRootDir(
   328  		u, tlfName, t, data.MakeRevBranchName(rev), expectedCanonicalTlfName)
   329  }
   330  
   331  // GetRootDirAtRelTimeString implements the Engine interface.
   332  func (k *LibKBFS) GetRootDirAtRelTimeString(
   333  	u User, tlfName string, t tlf.Type, relTimeString string,
   334  	expectedCanonicalTlfName string) (dir Node, err error) {
   335  	config := u.(*libkbfs.ConfigLocal)
   336  	ctx, cancel := k.newContext(u)
   337  	defer cancel()
   338  	h, err := parseTlfHandle(
   339  		ctx, config.KBPKI(), config.MDOps(), config, tlfName, t)
   340  	if err != nil {
   341  		return nil, err
   342  	}
   343  
   344  	rev, err := libfs.RevFromRelativeTimeString(ctx, config, h, relTimeString)
   345  	if err != nil {
   346  		return nil, err
   347  	}
   348  
   349  	return k.getRootDir(
   350  		u, tlfName, t, data.MakeRevBranchName(rev), expectedCanonicalTlfName)
   351  }
   352  
   353  // CreateDir implements the Engine interface.
   354  func (k *LibKBFS) CreateDir(u User, parentDir Node, name string) (dir Node, err error) {
   355  	config := u.(*libkbfs.ConfigLocal)
   356  	kbfsOps := config.KBFSOps()
   357  	ctx, cancel := k.newContext(u)
   358  	defer cancel()
   359  	n := parentDir.(libkbfs.Node)
   360  	dir, _, err = kbfsOps.CreateDir(ctx, n, n.ChildName(name))
   361  	if err != nil {
   362  		return dir, err
   363  	}
   364  	k.refs[config][dir.(libkbfs.Node)] = true
   365  	return dir, nil
   366  }
   367  
   368  // CreateFile implements the Engine interface.
   369  func (k *LibKBFS) CreateFile(u User, parentDir Node, name string) (file Node, err error) {
   370  	config := u.(*libkbfs.ConfigLocal)
   371  	kbfsOps := config.KBFSOps()
   372  	ctx, cancel := k.newContext(u)
   373  	defer cancel()
   374  	n := parentDir.(libkbfs.Node)
   375  	file, _, err = kbfsOps.CreateFile(
   376  		ctx, n, n.ChildName(name), false, libkbfs.NoExcl)
   377  	if err != nil {
   378  		return file, err
   379  	}
   380  	k.refs[config][file.(libkbfs.Node)] = true
   381  	return file, nil
   382  }
   383  
   384  // CreateFileExcl implements the Engine interface.
   385  func (k *LibKBFS) CreateFileExcl(u User, parentDir Node, name string) (file Node, err error) {
   386  	config := u.(*libkbfs.ConfigLocal)
   387  	kbfsOps := config.KBFSOps()
   388  	ctx, cancel := k.newContext(u)
   389  	defer cancel()
   390  	n := parentDir.(libkbfs.Node)
   391  	file, _, err = kbfsOps.CreateFile(
   392  		ctx, n, n.ChildName(name), false, libkbfs.WithExcl)
   393  	if err != nil {
   394  		return nil, err
   395  	}
   396  	k.refs[config][file.(libkbfs.Node)] = true
   397  	return file, nil
   398  }
   399  
   400  // CreateLink implements the Engine interface.
   401  func (k *LibKBFS) CreateLink(
   402  	u User, parentDir Node, fromName, toPath string) (err error) {
   403  	config := u.(*libkbfs.ConfigLocal)
   404  	kbfsOps := config.KBFSOps()
   405  	ctx, cancel := k.newContext(u)
   406  	defer cancel()
   407  	n := parentDir.(libkbfs.Node)
   408  	_, err = kbfsOps.CreateLink(
   409  		ctx, n, n.ChildName(fromName), n.ChildName(toPath))
   410  	return err
   411  }
   412  
   413  // RemoveDir implements the Engine interface.
   414  func (k *LibKBFS) RemoveDir(u User, dir Node, name string) (err error) {
   415  	kbfsOps := u.(*libkbfs.ConfigLocal).KBFSOps()
   416  	ctx, cancel := k.newContext(u)
   417  	defer cancel()
   418  	n := dir.(libkbfs.Node)
   419  	return kbfsOps.RemoveDir(ctx, n, n.ChildName(name))
   420  }
   421  
   422  // RemoveEntry implements the Engine interface.
   423  func (k *LibKBFS) RemoveEntry(u User, dir Node, name string) (err error) {
   424  	kbfsOps := u.(*libkbfs.ConfigLocal).KBFSOps()
   425  	ctx, cancel := k.newContext(u)
   426  	defer cancel()
   427  	n := dir.(libkbfs.Node)
   428  	return kbfsOps.RemoveEntry(ctx, n, n.ChildName(name))
   429  }
   430  
   431  // Rename implements the Engine interface.
   432  func (k *LibKBFS) Rename(u User, srcDir Node, srcName string,
   433  	dstDir Node, dstName string) (err error) {
   434  	kbfsOps := u.(*libkbfs.ConfigLocal).KBFSOps()
   435  	ctx, cancel := k.newContext(u)
   436  	defer cancel()
   437  	srcN := srcDir.(libkbfs.Node)
   438  	dstN := dstDir.(libkbfs.Node)
   439  	return kbfsOps.Rename(
   440  		ctx, srcN, srcN.ChildName(srcName), dstN, dstN.ChildName(dstName))
   441  }
   442  
   443  // WriteFile implements the Engine interface.
   444  func (k *LibKBFS) WriteFile(u User, file Node, data []byte, off int64, sync bool) (err error) {
   445  	kbfsOps := u.(*libkbfs.ConfigLocal).KBFSOps()
   446  	ctx, cancel := k.newContext(u)
   447  	defer cancel()
   448  	err = kbfsOps.Write(ctx, file.(libkbfs.Node), data, off)
   449  	if err != nil {
   450  		return err
   451  	}
   452  	if sync {
   453  		ctx, cancel := k.newContext(u)
   454  		defer cancel()
   455  		err = kbfsOps.SyncAll(ctx, file.(libkbfs.Node).GetFolderBranch())
   456  	}
   457  	return err
   458  }
   459  
   460  // TruncateFile implements the Engine interface.
   461  func (k *LibKBFS) TruncateFile(u User, file Node, size uint64, sync bool) (err error) {
   462  	kbfsOps := u.(*libkbfs.ConfigLocal).KBFSOps()
   463  	ctx, cancel := k.newContext(u)
   464  	defer cancel()
   465  	err = kbfsOps.Truncate(ctx, file.(libkbfs.Node), size)
   466  	if err != nil {
   467  		return err
   468  	}
   469  	if sync {
   470  		ctx, cancel := k.newContext(u)
   471  		defer cancel()
   472  		err = kbfsOps.SyncAll(ctx, file.(libkbfs.Node).GetFolderBranch())
   473  	}
   474  	return err
   475  }
   476  
   477  // ReadFile implements the Engine interface.
   478  func (k *LibKBFS) ReadFile(u User, file Node, off int64, buf []byte) (length int, err error) {
   479  	kbfsOps := u.(*libkbfs.ConfigLocal).KBFSOps()
   480  	var numRead int64
   481  	ctx, cancel := k.newContext(u)
   482  	defer cancel()
   483  	numRead, err = kbfsOps.Read(ctx, file.(libkbfs.Node), buf, off)
   484  	if err != nil {
   485  		return 0, err
   486  	}
   487  	return int(numRead), nil
   488  }
   489  
   490  type libkbfsSymNode struct {
   491  	parentDir Node
   492  	name      string
   493  }
   494  
   495  // Lookup implements the Engine interface.
   496  func (k *LibKBFS) Lookup(u User, parentDir Node, name string) (file Node, symPath string, err error) {
   497  	config := u.(*libkbfs.ConfigLocal)
   498  	kbfsOps := config.KBFSOps()
   499  	ctx, cancel := k.newContext(u)
   500  	defer cancel()
   501  	n := parentDir.(libkbfs.Node)
   502  	file, ei, err := kbfsOps.Lookup(ctx, n, n.ChildName(name))
   503  	if err != nil {
   504  		return file, symPath, err
   505  	}
   506  	if file != nil {
   507  		k.refs[config][file.(libkbfs.Node)] = true
   508  	}
   509  	if ei.Type == data.Sym {
   510  		symPath = ei.SymPath
   511  	}
   512  	if file == nil {
   513  		// For symlnks, return a special kind of node that can be used
   514  		// to look up stats about the symlink.
   515  		return libkbfsSymNode{parentDir, name}, symPath, nil
   516  	}
   517  	return file, symPath, nil
   518  }
   519  
   520  // GetDirChildrenTypes implements the Engine interface.
   521  func (k *LibKBFS) GetDirChildrenTypes(u User, parentDir Node) (childrenTypes map[string]string, err error) {
   522  	kbfsOps := u.(*libkbfs.ConfigLocal).KBFSOps()
   523  	ctx, cancel := k.newContext(u)
   524  	defer cancel()
   525  	entries, err := kbfsOps.GetDirChildren(ctx, parentDir.(libkbfs.Node))
   526  	if err != nil {
   527  		return childrenTypes, err
   528  	}
   529  	childrenTypes = make(map[string]string)
   530  	for name, entryInfo := range entries {
   531  		childrenTypes[name.Plaintext()] = entryInfo.Type.String()
   532  	}
   533  	return childrenTypes, nil
   534  }
   535  
   536  // SetEx implements the Engine interface.
   537  func (k *LibKBFS) SetEx(u User, file Node, ex bool) (err error) {
   538  	config := u.(*libkbfs.ConfigLocal)
   539  	kbfsOps := config.KBFSOps()
   540  	ctx, cancel := k.newContext(u)
   541  	defer cancel()
   542  	return kbfsOps.SetEx(ctx, file.(libkbfs.Node), ex)
   543  }
   544  
   545  // SetMtime implements the Engine interface.
   546  func (k *LibKBFS) SetMtime(u User, file Node, mtime time.Time) (err error) {
   547  	config := u.(*libkbfs.ConfigLocal)
   548  	kbfsOps := config.KBFSOps()
   549  	ctx, cancel := k.newContext(u)
   550  	defer cancel()
   551  	return kbfsOps.SetMtime(ctx, file.(libkbfs.Node), &mtime)
   552  }
   553  
   554  // SyncAll implements the Engine interface.
   555  func (k *LibKBFS) SyncAll(
   556  	u User, tlfName string, t tlf.Type) (err error) {
   557  	config := u.(*libkbfs.ConfigLocal)
   558  
   559  	ctx, cancel := k.newContext(u)
   560  	defer cancel()
   561  	dir, err := getRootNode(ctx, config, tlfName, t)
   562  	if err != nil {
   563  		return err
   564  	}
   565  
   566  	return config.KBFSOps().SyncAll(ctx, dir.GetFolderBranch())
   567  }
   568  
   569  // GetMtime implements the Engine interface.
   570  func (k *LibKBFS) GetMtime(u User, file Node) (mtime time.Time, err error) {
   571  	config := u.(*libkbfs.ConfigLocal)
   572  	kbfsOps := config.KBFSOps()
   573  	var info data.EntryInfo
   574  	ctx, cancel := k.newContext(u)
   575  	defer cancel()
   576  	if node, ok := file.(libkbfs.Node); ok {
   577  		info, err = kbfsOps.Stat(ctx, node)
   578  	} else if node, ok := file.(libkbfsSymNode); ok {
   579  		// Stat doesn't work for symlinks, so use lookup
   580  		n := node.parentDir.(libkbfs.Node)
   581  		_, info, err = kbfsOps.Lookup(ctx, n, n.ChildName(node.name))
   582  	}
   583  	if err != nil {
   584  		return time.Time{}, err
   585  	}
   586  	return time.Unix(0, info.Mtime), nil
   587  }
   588  
   589  // GetPrevRevisions implements the Engine interface.
   590  func (k *LibKBFS) GetPrevRevisions(u User, file Node) (
   591  	revs data.PrevRevisions, err error) {
   592  	config := u.(*libkbfs.ConfigLocal)
   593  	kbfsOps := config.KBFSOps()
   594  	var info data.EntryInfo
   595  	ctx, cancel := k.newContext(u)
   596  	defer cancel()
   597  	if node, ok := file.(libkbfs.Node); ok {
   598  		info, err = kbfsOps.Stat(ctx, node)
   599  	} else if node, ok := file.(libkbfsSymNode); ok {
   600  		// Stat doesn't work for symlinks, so use lookup
   601  		n := node.parentDir.(libkbfs.Node)
   602  		_, info, err = kbfsOps.Lookup(ctx, n, n.ChildName(node.name))
   603  	}
   604  	if err != nil {
   605  		return nil, err
   606  	}
   607  	return info.PrevRevisions, nil
   608  }
   609  
   610  // getRootNode is like GetRootDir, but doesn't check the canonical TLF
   611  // name.
   612  func getRootNode(ctx context.Context, config libkbfs.Config, tlfName string,
   613  	t tlf.Type) (libkbfs.Node, error) {
   614  	h, err := parseTlfHandle(
   615  		ctx, config.KBPKI(), config.MDOps(), config, tlfName, t)
   616  	if err != nil {
   617  		return nil, err
   618  	}
   619  
   620  	// TODO: we should cache the root node, to more faithfully
   621  	// simulate real-world callers and avoid unnecessary work.
   622  	kbfsOps := config.KBFSOps()
   623  	if h.IsLocalConflict() {
   624  		b, ok := data.MakeConflictBranchName(h)
   625  		if ok {
   626  			dir, _, err := kbfsOps.GetRootNode(ctx, h, b)
   627  			if err != nil {
   628  				return nil, err
   629  			}
   630  			return dir, nil
   631  		}
   632  	}
   633  
   634  	dir, _, err := kbfsOps.GetOrCreateRootNode(ctx, h, data.MasterBranch)
   635  	if err != nil {
   636  		return nil, err
   637  	}
   638  	return dir, nil
   639  }
   640  
   641  // DisableUpdatesForTesting implements the Engine interface.
   642  func (k *LibKBFS) DisableUpdatesForTesting(u User, tlfName string, t tlf.Type) (err error) {
   643  	config := u.(*libkbfs.ConfigLocal)
   644  
   645  	ctx, cancel := k.newContext(u)
   646  	defer cancel()
   647  	dir, err := getRootNode(ctx, config, tlfName, t)
   648  	if err != nil {
   649  		return err
   650  	}
   651  
   652  	if _, ok := k.updateChannels[config][dir.GetFolderBranch()]; ok {
   653  		// Updates are already disabled.
   654  		return nil
   655  	}
   656  
   657  	var c chan<- struct{}
   658  	c, err = libkbfs.DisableUpdatesForTesting(config, dir.GetFolderBranch())
   659  	if err != nil {
   660  		return err
   661  	}
   662  	k.updateChannels[config][dir.GetFolderBranch()] = c
   663  	// Also stop conflict resolution.
   664  	err = libkbfs.DisableCRForTesting(config, dir.GetFolderBranch())
   665  	if err != nil {
   666  		return err
   667  	}
   668  	return nil
   669  }
   670  
   671  // MakeNaïveStaller implements the Engine interface.
   672  func (*LibKBFS) MakeNaïveStaller(u User) *libkbfs.NaïveStaller {
   673  	return libkbfs.NewNaïveStaller(u.(*libkbfs.ConfigLocal))
   674  }
   675  
   676  // ReenableUpdates implements the Engine interface.
   677  func (k *LibKBFS) ReenableUpdates(u User, tlfName string, t tlf.Type) error {
   678  	config := u.(*libkbfs.ConfigLocal)
   679  
   680  	ctx, cancel := k.newContext(u)
   681  	defer cancel()
   682  	dir, err := getRootNode(ctx, config, tlfName, t)
   683  	if err != nil {
   684  		return err
   685  	}
   686  
   687  	c, ok := k.updateChannels[config][dir.GetFolderBranch()]
   688  	if !ok {
   689  		return fmt.Errorf(
   690  			"Couldn't re-enable updates for %s (type=%s)", tlfName, t)
   691  	}
   692  
   693  	// Restart CR using a clean context, since we will cancel ctx when
   694  	// we return.
   695  	err = libkbfs.RestartCRForTesting(
   696  		libcontext.BackgroundContextWithCancellationDelayer(), config,
   697  		dir.GetFolderBranch())
   698  	if err != nil {
   699  		return err
   700  	}
   701  
   702  	c <- struct{}{}
   703  	close(c)
   704  	delete(k.updateChannels[config], dir.GetFolderBranch())
   705  	return nil
   706  }
   707  
   708  // SyncFromServer implements the Engine interface.
   709  func (k *LibKBFS) SyncFromServer(u User, tlfName string, t tlf.Type) (err error) {
   710  	config := u.(*libkbfs.ConfigLocal)
   711  
   712  	ctx, cancel := k.newContext(u)
   713  	defer cancel()
   714  	dir, err := getRootNode(ctx, config, tlfName, t)
   715  	if err != nil {
   716  		return err
   717  	}
   718  
   719  	return config.KBFSOps().SyncFromServer(ctx,
   720  		dir.GetFolderBranch(), nil)
   721  }
   722  
   723  // ForceQuotaReclamation implements the Engine interface.
   724  func (k *LibKBFS) ForceQuotaReclamation(u User, tlfName string, t tlf.Type) (err error) {
   725  	config := u.(*libkbfs.ConfigLocal)
   726  
   727  	ctx, cancel := k.newContext(u)
   728  	defer cancel()
   729  	dir, err := getRootNode(ctx, config, tlfName, t)
   730  	if err != nil {
   731  		return err
   732  	}
   733  
   734  	return libkbfs.ForceQuotaReclamationForTesting(
   735  		config, dir.GetFolderBranch())
   736  }
   737  
   738  // AddNewAssertion implements the Engine interface.
   739  func (k *LibKBFS) AddNewAssertion(u User, oldAssertion, newAssertion string) error {
   740  	config := u.(*libkbfs.ConfigLocal)
   741  	return libkbfs.AddNewAssertionForTest(config, oldAssertion, newAssertion)
   742  }
   743  
   744  // ChangeTeamName implements the Engine interface.
   745  func (k *LibKBFS) ChangeTeamName(u User, oldName, newName string) error {
   746  	config := u.(*libkbfs.ConfigLocal)
   747  	return libkbfs.ChangeTeamNameForTest(config, oldName, newName)
   748  }
   749  
   750  // Rekey implements the Engine interface.
   751  func (k *LibKBFS) Rekey(u User, tlfName string, t tlf.Type) error {
   752  	config := u.(*libkbfs.ConfigLocal)
   753  
   754  	ctx, cancel := k.newContext(u)
   755  	defer cancel()
   756  	dir, err := getRootNode(ctx, config, tlfName, t)
   757  	if err != nil {
   758  		return err
   759  	}
   760  
   761  	_, err = libkbfs.RequestRekeyAndWaitForOneFinishEvent(ctx,
   762  		config.KBFSOps(), dir.GetFolderBranch().Tlf)
   763  	return err
   764  }
   765  
   766  // EnableJournal implements the Engine interface.
   767  func (k *LibKBFS) EnableJournal(u User, tlfName string, t tlf.Type) error {
   768  	config := u.(*libkbfs.ConfigLocal)
   769  
   770  	ctx, cancel := k.newContext(u)
   771  	defer cancel()
   772  	dir, err := getRootNode(ctx, config, tlfName, t)
   773  	if err != nil {
   774  		return err
   775  	}
   776  
   777  	jManager, err := libkbfs.GetJournalManager(config)
   778  	if err != nil {
   779  		return err
   780  	}
   781  
   782  	h, err := parseTlfHandle(
   783  		ctx, config.KBPKI(), config.MDOps(), config, tlfName, t)
   784  	if err != nil {
   785  		return err
   786  	}
   787  
   788  	return jManager.Enable(ctx, dir.GetFolderBranch().Tlf, h,
   789  		libkbfs.TLFJournalBackgroundWorkEnabled)
   790  }
   791  
   792  // PauseJournal implements the Engine interface.
   793  func (k *LibKBFS) PauseJournal(u User, tlfName string, t tlf.Type) error {
   794  	config := u.(*libkbfs.ConfigLocal)
   795  
   796  	ctx, cancel := k.newContext(u)
   797  	defer cancel()
   798  	dir, err := getRootNode(ctx, config, tlfName, t)
   799  	if err != nil {
   800  		return err
   801  	}
   802  
   803  	jManager, err := libkbfs.GetJournalManager(config)
   804  	if err != nil {
   805  		return err
   806  	}
   807  
   808  	jManager.PauseBackgroundWork(ctx, dir.GetFolderBranch().Tlf)
   809  	return nil
   810  }
   811  
   812  // ResumeJournal implements the Engine interface.
   813  func (k *LibKBFS) ResumeJournal(u User, tlfName string, t tlf.Type) error {
   814  	config := u.(*libkbfs.ConfigLocal)
   815  
   816  	ctx, cancel := k.newContext(u)
   817  	defer cancel()
   818  	dir, err := getRootNode(ctx, config, tlfName, t)
   819  	if err != nil {
   820  		return err
   821  	}
   822  
   823  	jManager, err := libkbfs.GetJournalManager(config)
   824  	if err != nil {
   825  		return err
   826  	}
   827  
   828  	jManager.ResumeBackgroundWork(ctx, dir.GetFolderBranch().Tlf)
   829  	return nil
   830  }
   831  
   832  // FlushJournal implements the Engine interface.
   833  func (k *LibKBFS) FlushJournal(u User, tlfName string, t tlf.Type) error {
   834  	config := u.(*libkbfs.ConfigLocal)
   835  
   836  	ctx, cancel := k.newContext(u)
   837  	defer cancel()
   838  	dir, err := getRootNode(ctx, config, tlfName, t)
   839  	if err != nil {
   840  		return err
   841  	}
   842  
   843  	jManager, err := libkbfs.GetJournalManager(config)
   844  	if err != nil {
   845  		return err
   846  	}
   847  
   848  	return jManager.Flush(ctx, dir.GetFolderBranch().Tlf)
   849  }
   850  
   851  // UnflushedPaths implements the Engine interface.
   852  func (k *LibKBFS) UnflushedPaths(u User, tlfName string, t tlf.Type) (
   853  	[]string, error) {
   854  	config := u.(*libkbfs.ConfigLocal)
   855  
   856  	ctx, cancel := k.newContext(u)
   857  	defer cancel()
   858  	dir, err := getRootNode(ctx, config, tlfName, t)
   859  	if err != nil {
   860  		return nil, err
   861  	}
   862  
   863  	status, _, err := config.KBFSOps().FolderStatus(ctx, dir.GetFolderBranch())
   864  	if err != nil {
   865  		return nil, err
   866  	}
   867  
   868  	return status.Journal.UnflushedPaths, nil
   869  }
   870  
   871  // UserEditHistory implements the Engine interface.
   872  func (k *LibKBFS) UserEditHistory(u User) (
   873  	[]keybase1.FSFolderEditHistory, error) {
   874  	config := u.(*libkbfs.ConfigLocal)
   875  
   876  	ctx, cancel := k.newContext(u)
   877  	defer cancel()
   878  	session, err := idutil.GetCurrentSessionIfPossible(
   879  		ctx, config.KBPKI(), true)
   880  	if err != nil {
   881  		return nil, err
   882  	}
   883  
   884  	history := config.UserHistory().Get(string(session.Name))
   885  	return history, nil
   886  }
   887  
   888  // DirtyPaths implements the Engine interface.
   889  func (k *LibKBFS) DirtyPaths(u User, tlfName string, t tlf.Type) (
   890  	[]string, error) {
   891  	config := u.(*libkbfs.ConfigLocal)
   892  
   893  	ctx, cancel := k.newContext(u)
   894  	defer cancel()
   895  	dir, err := getRootNode(ctx, config, tlfName, t)
   896  	if err != nil {
   897  		return nil, err
   898  	}
   899  
   900  	status, _, err := config.KBFSOps().FolderStatus(ctx, dir.GetFolderBranch())
   901  	if err != nil {
   902  		return nil, err
   903  	}
   904  
   905  	return status.DirtyPaths, nil
   906  }
   907  
   908  // TogglePrefetch implements the Engine interface.
   909  func (k *LibKBFS) TogglePrefetch(u User, enable bool) error {
   910  	config := u.(*libkbfs.ConfigLocal)
   911  
   912  	_ = config.BlockOps().TogglePrefetcher(enable)
   913  	return nil
   914  }
   915  
   916  // ForceConflict implements the Engine interface.
   917  func (k *LibKBFS) ForceConflict(u User, tlfName string, t tlf.Type) error {
   918  	config := u.(*libkbfs.ConfigLocal)
   919  
   920  	ctx, cancel := k.newContext(u)
   921  	defer cancel()
   922  
   923  	root, err := getRootNode(ctx, config, tlfName, t)
   924  	if err != nil {
   925  		return err
   926  	}
   927  
   928  	return config.KBFSOps().ForceStuckConflictForTesting(
   929  		ctx, root.GetFolderBranch().Tlf)
   930  }
   931  
   932  // ClearConflicts implements the Engine interface.
   933  func (k *LibKBFS) ClearConflicts(u User, tlfName string, t tlf.Type) error {
   934  	config := u.(*libkbfs.ConfigLocal)
   935  
   936  	ctx, cancel := k.newContext(u)
   937  	defer cancel()
   938  
   939  	root, err := getRootNode(ctx, config, tlfName, t)
   940  	if err != nil {
   941  		return err
   942  	}
   943  
   944  	return config.KBFSOps().ClearConflictView(ctx, root.GetFolderBranch().Tlf)
   945  }
   946  
   947  // Shutdown implements the Engine interface.
   948  func (k *LibKBFS) Shutdown(u User) error {
   949  	config := u.(*libkbfs.ConfigLocal)
   950  	// drop references
   951  	k.refs[config] = make(map[libkbfs.Node]bool)
   952  	delete(k.refs, config)
   953  	// clear update channels
   954  	k.updateChannels[config] = make(map[data.FolderBranch]chan<- struct{})
   955  	delete(k.updateChannels, config)
   956  
   957  	// Get the user name before shutting everything down.
   958  	var userName kbname.NormalizedUsername
   959  	if k.journalDir != "" {
   960  		var err error
   961  		session, err :=
   962  			config.KBPKI().GetCurrentSession(context.Background())
   963  		if err != nil {
   964  			return err
   965  		}
   966  		userName = session.Name
   967  	}
   968  
   969  	// shutdown
   970  	ctx := context.Background()
   971  	if err := config.Shutdown(ctx); err != nil {
   972  		return err
   973  	}
   974  
   975  	if k.journalDir != "" {
   976  		// Remove the user journal.
   977  		if err := ioutil.RemoveAll(
   978  			filepath.Join(k.journalDir, userName.String())); err != nil {
   979  			return err
   980  		}
   981  		// Remove the overall journal dir if it's empty.
   982  		if err := ioutil.Remove(k.journalDir); err != nil {
   983  			k.tb.Logf("Journal dir %s not empty yet", k.journalDir)
   984  		}
   985  	}
   986  	return nil
   987  }