github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/simplefs/simplefs_test.go (about)

     1  // Copyright 2016-2017 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 simplefs
     6  
     7  import (
     8  	"archive/zip"
     9  	"context"
    10  	"encoding/json"
    11  	"fmt"
    12  
    13  	"os"
    14  	"path"
    15  	"path/filepath"
    16  	"sort"
    17  	"strings"
    18  	"sync"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/keybase/client/go/kbfs/data"
    23  	"github.com/keybase/client/go/kbfs/env"
    24  	"github.com/keybase/client/go/kbfs/libfs"
    25  	"github.com/keybase/client/go/kbfs/libkbfs"
    26  	"github.com/keybase/client/go/kbfs/test/clocktest"
    27  	"github.com/keybase/client/go/kbfs/tlf"
    28  	"github.com/keybase/client/go/kbfs/tlfhandle"
    29  	"github.com/keybase/client/go/libkb"
    30  	"github.com/keybase/client/go/protocol/keybase1"
    31  	"github.com/stretchr/testify/require"
    32  	billy "gopkg.in/src-d/go-billy.v4"
    33  )
    34  
    35  const TempDirBase = "."
    36  
    37  func syncFS(ctx context.Context, t *testing.T, fs *SimpleFS, tlf string) {
    38  	ctx, err := fs.startOpWrapContext(ctx)
    39  	require.NoError(t, err)
    40  	remoteFS, _, err := fs.getFS(ctx, keybase1.NewPathWithKbfsPath(tlf))
    41  	require.NoError(t, err)
    42  	if fs, ok := remoteFS.(*libfs.FS); ok {
    43  		err = fs.SyncAll()
    44  	} else if fs, ok := remoteFS.(*fsBlocker); ok {
    45  		err = fs.SyncAll()
    46  	}
    47  	require.NoError(t, err)
    48  }
    49  
    50  func closeSimpleFS(ctx context.Context, t *testing.T, fs *SimpleFS) {
    51  	// Sync in-memory data to disk before shutting down and flushing
    52  	// the journal.
    53  	syncFS(ctx, t, fs, "/private/jdoe")
    54  	err := fs.Shutdown(ctx)
    55  	require.NoError(t, err)
    56  	err = fs.config.Shutdown(ctx)
    57  	require.NoError(t, err)
    58  }
    59  
    60  func deleteTempLocalPath(path keybase1.Path) {
    61  	os.RemoveAll(path.Local())
    62  }
    63  
    64  // "pending" tells whether we expect the operation to still be
    65  // there, because there is no "none" in AsyncOps
    66  func checkPendingOp(ctx context.Context,
    67  	t *testing.T,
    68  	sfs *SimpleFS,
    69  	opid keybase1.OpID,
    70  	expectedOp keybase1.AsyncOps,
    71  	src keybase1.Path,
    72  	dest keybase1.Path,
    73  	pending bool) {
    74  
    75  	// TODO: what do we expect the progress to be?
    76  	_, err := sfs.SimpleFSCheck(ctx, opid)
    77  	if pending {
    78  		require.NoError(t, err)
    79  	} else {
    80  		require.Error(t, err)
    81  	}
    82  
    83  	ops, err := sfs.SimpleFSGetOps(ctx)
    84  	require.NoError(t, err)
    85  
    86  	if !pending {
    87  		require.Len(t, ops, 0, "Expected zero pending operations")
    88  		return
    89  	}
    90  
    91  	require.True(t, len(ops) > 0, "Expected at least one pending operation")
    92  
    93  	o := ops[0]
    94  	op, err := o.AsyncOp()
    95  	require.NoError(t, err)
    96  	require.Equal(t, expectedOp, op, "Expected at least one pending operation")
    97  
    98  	// TODO: verify read/write arguments
    99  	switch op {
   100  	case keybase1.AsyncOps_LIST:
   101  		list := o.List()
   102  		require.Equal(t, list.Path, src, "Expected matching path in operation")
   103  	case keybase1.AsyncOps_LIST_RECURSIVE:
   104  		list := o.ListRecursive()
   105  		require.Equal(t, list.Path, src, "Expected matching path in operation")
   106  	case keybase1.AsyncOps_LIST_RECURSIVE_TO_DEPTH:
   107  		list := o.ListRecursiveToDepth()
   108  		require.Equal(t, list.Path, src, "Expected matching path in operation")
   109  	// TODO: read is not async
   110  	case keybase1.AsyncOps_READ:
   111  		read := o.Read()
   112  		require.Equal(t, read.Path, src, "Expected matching path in operation")
   113  	// TODO: write is not asynce
   114  	case keybase1.AsyncOps_WRITE:
   115  		write := o.Write()
   116  		require.Equal(t, write.Path, src, "Expected matching path in operation")
   117  	case keybase1.AsyncOps_COPY:
   118  		copy := o.Copy()
   119  		require.Equal(t, copy.Src, src, "Expected matching path in operation")
   120  		require.Equal(t, copy.Dest, dest, "Expected matching path in operation")
   121  	case keybase1.AsyncOps_MOVE:
   122  		move := o.Move()
   123  		require.Equal(t, move.Src, src, "Expected matching path in operation")
   124  		require.Equal(t, move.Dest, dest, "Expected matching path in operation")
   125  	case keybase1.AsyncOps_REMOVE:
   126  		remove := o.Remove()
   127  		require.Equal(t, remove.Path, src, "Expected matching path in operation")
   128  	}
   129  }
   130  
   131  func testListWithFilterAndUsername(
   132  	ctx context.Context, t *testing.T, sfs *SimpleFS, path keybase1.Path,
   133  	filter keybase1.ListFilter, username string, expectedEntries ...string) {
   134  	opid, err := sfs.SimpleFSMakeOpid(ctx)
   135  	require.NoError(t, err)
   136  	err = sfs.SimpleFSList(ctx, keybase1.SimpleFSListArg{
   137  		OpID:   opid,
   138  		Path:   path,
   139  		Filter: filter,
   140  	})
   141  	require.NoError(t, err)
   142  	checkPendingOp(
   143  		ctx, t, sfs, opid, keybase1.AsyncOps_LIST, path, keybase1.Path{}, true)
   144  	err = sfs.SimpleFSWait(ctx, opid)
   145  	require.NoError(t, err)
   146  	listResult, err := sfs.SimpleFSReadList(ctx, opid)
   147  	require.NoError(t, err)
   148  	require.Len(t, listResult.Entries, len(expectedEntries))
   149  	sort.Slice(listResult.Entries, func(i, j int) bool {
   150  		return strings.Compare(listResult.Entries[i].Name,
   151  			listResult.Entries[j].Name) < 0
   152  	})
   153  	sort.Strings(expectedEntries)
   154  	for i, entry := range listResult.Entries {
   155  		require.Equal(t, expectedEntries[i], entry.Name)
   156  		require.Equal(t, username, entry.LastWriterUnverified.Username)
   157  	}
   158  
   159  	// Assume we've exhausted the list now, so expect error
   160  	_, err = sfs.SimpleFSReadList(ctx, opid)
   161  	require.Error(t, err)
   162  
   163  	// Verify error on double wait
   164  	err = sfs.SimpleFSWait(ctx, opid)
   165  	require.Error(t, err)
   166  }
   167  
   168  func testList(
   169  	ctx context.Context, t *testing.T, sfs *SimpleFS, path keybase1.Path,
   170  	expectedEntries ...string) {
   171  	testListWithFilterAndUsername(
   172  		ctx, t, sfs, path, keybase1.ListFilter_NO_FILTER, "jdoe",
   173  		expectedEntries...)
   174  }
   175  
   176  func TestStatNonExistent(t *testing.T) {
   177  	ctx := context.Background()
   178  	config := libkbfs.MakeTestConfigOrBust(t, "dog", "cat")
   179  	defer func() {
   180  		err := config.Shutdown(ctx)
   181  		require.NoError(t, err)
   182  	}()
   183  	sfs := newSimpleFS(env.EmptyAppStateUpdater{}, config)
   184  
   185  	t.Logf("/private/dog,cat should be writable for dog")
   186  	p := keybase1.NewPathWithKbfsPath("/private/dog,cat")
   187  	de, err := sfs.SimpleFSStat(ctx, keybase1.SimpleFSStatArg{
   188  		Path: p,
   189  	})
   190  	require.NoError(t, err)
   191  	require.True(t, de.Writable)
   192  
   193  	t.Logf("/private/cat#dog should not be writable for dog")
   194  	p = keybase1.NewPathWithKbfsPath("/private/cat#dog")
   195  	de, err = sfs.SimpleFSStat(ctx, keybase1.SimpleFSStatArg{
   196  		Path: p,
   197  	})
   198  	require.NoError(t, err)
   199  	require.False(t, de.Writable)
   200  }
   201  
   202  func TestSymlink(t *testing.T) {
   203  	ctx := context.Background()
   204  	config := libkbfs.MakeTestConfigOrBust(t, "jdoe")
   205  	sfs := newSimpleFS(env.EmptyAppStateUpdater{}, config)
   206  	defer closeSimpleFS(ctx, t, sfs)
   207  
   208  	t.Logf("Make a file and then symlink it")
   209  	p := keybase1.NewPathWithKbfsPath(`/private/jdoe`)
   210  	target := pathAppend(p, `test1.txt`)
   211  	writeRemoteFile(ctx, t, sfs, target, []byte(`foo`))
   212  	linkName := "link"
   213  	link := pathAppend(p, linkName)
   214  	targetName := path.Base(target.String())
   215  	err := sfs.SimpleFSSymlink(ctx, keybase1.SimpleFSSymlinkArg{
   216  		Target: targetName,
   217  		Link:   link,
   218  	})
   219  	require.NoError(t, err)
   220  	require.Equal(t, "foo",
   221  		string(readRemoteFile(ctx, t, sfs, link)))
   222  
   223  	t.Log("Make sure the symlink ls entry has a target")
   224  	opid, err := sfs.SimpleFSMakeOpid(ctx)
   225  	require.NoError(t, err)
   226  	err = sfs.SimpleFSList(ctx, keybase1.SimpleFSListArg{
   227  		OpID:   opid,
   228  		Path:   p,
   229  		Filter: keybase1.ListFilter_NO_FILTER,
   230  	})
   231  	require.NoError(t, err)
   232  	checkPendingOp(
   233  		ctx, t, sfs, opid, keybase1.AsyncOps_LIST, p, keybase1.Path{}, true)
   234  	err = sfs.SimpleFSWait(ctx, opid)
   235  	require.NoError(t, err)
   236  	listResult, err := sfs.SimpleFSReadList(ctx, opid)
   237  	require.NoError(t, err)
   238  	require.Len(t, listResult.Entries, 2)
   239  	found := false
   240  	for _, e := range listResult.Entries {
   241  		if e.Name != linkName {
   242  			continue
   243  		}
   244  		require.Equal(t, targetName, e.SymlinkTarget)
   245  		found = true
   246  	}
   247  	require.True(t, found)
   248  
   249  	// Regression for HOTPOT-1276
   250  	t.Log("Make sure a link called . will fail")
   251  	badLink := pathAppend(p, `.`)
   252  	err = sfs.SimpleFSSymlink(ctx, keybase1.SimpleFSSymlinkArg{
   253  		Target: path.Base(target.String()),
   254  		Link:   badLink,
   255  	})
   256  	require.Error(t, err)
   257  
   258  	// Regression test for HOTPOT-2007.
   259  	t.Log("Make sure a symlink can be deleted")
   260  	opid, err = sfs.SimpleFSMakeOpid(ctx)
   261  	require.NoError(t, err)
   262  	err = sfs.SimpleFSRemove(ctx, keybase1.SimpleFSRemoveArg{
   263  		OpID:      opid,
   264  		Path:      link,
   265  		Recursive: true,
   266  	})
   267  	require.NoError(t, err)
   268  	checkPendingOp(
   269  		ctx, t, sfs, opid, keybase1.AsyncOps_REMOVE, link, keybase1.Path{},
   270  		true)
   271  	err = sfs.SimpleFSWait(ctx, opid)
   272  	require.NoError(t, err)
   273  	testList(ctx, t, sfs, p, "test1.txt")
   274  }
   275  
   276  func TestList(t *testing.T) {
   277  	ctx := context.Background()
   278  	config := libkbfs.MakeTestConfigOrBust(t, "jdoe")
   279  	clock := &clocktest.TestClock{}
   280  	clock.Set(time.Now())
   281  	config.SetClock(clock)
   282  	sfs := newSimpleFS(env.EmptyAppStateUpdater{}, config)
   283  	defer closeSimpleFS(ctx, t, sfs)
   284  
   285  	pathRoot := keybase1.NewPathWithKbfsPath(`/`)
   286  	testListWithFilterAndUsername(
   287  		ctx, t, sfs, pathRoot, keybase1.ListFilter_NO_FILTER, "",
   288  		"private", "public", "team")
   289  
   290  	pathPrivate := keybase1.NewPathWithKbfsPath(`/private`)
   291  	testListWithFilterAndUsername(
   292  		ctx, t, sfs, pathPrivate, keybase1.ListFilter_NO_FILTER, "",
   293  		"jdoe")
   294  
   295  	t.Log("List directory before it's created")
   296  	path1 := keybase1.NewPathWithKbfsPath(`/private/jdoe`)
   297  	testList(ctx, t, sfs, path1)
   298  
   299  	t.Log("Shouldn't have created the TLF")
   300  	h, err := tlfhandle.ParseHandle(
   301  		ctx, config.KBPKI(), config.MDOps(), config, "jdoe", tlf.Private)
   302  	require.NoError(t, err)
   303  	rootNode, _, err := config.KBFSOps().GetRootNode(
   304  		ctx, h, data.MasterBranch)
   305  	require.NoError(t, err)
   306  	require.Nil(t, rootNode)
   307  
   308  	clock.Add(1 * time.Minute)
   309  	syncFS(ctx, t, sfs, "/private/jdoe")
   310  
   311  	rev1Time := clock.Now()
   312  	clock.Add(1 * time.Minute)
   313  
   314  	// make a temp remote directory + files we will clean up later
   315  	writeRemoteFile(ctx, t, sfs, pathAppend(path1, `test1.txt`), []byte(`foo`))
   316  	syncFS(ctx, t, sfs, "/private/jdoe") // Make a revision.
   317  	writeRemoteFile(ctx, t, sfs, pathAppend(path1, `test2.txt`), []byte(`foo`))
   318  	writeRemoteFile(ctx, t, sfs, pathAppend(path1, `.testfile`), []byte(`foo`))
   319  
   320  	testListWithFilterAndUsername(
   321  		ctx, t, sfs, path1, keybase1.ListFilter_FILTER_ALL_HIDDEN, "jdoe",
   322  		"test1.txt", "test2.txt")
   323  
   324  	testList(ctx, t, sfs, pathAppend(path1, `test1.txt`), "test1.txt")
   325  
   326  	// Check for hidden files too.
   327  	testList(
   328  		ctx, t, sfs, path1, "test1.txt", "test2.txt", ".testfile")
   329  
   330  	// A single, requested hidden file shows up even if the filter is on.
   331  	testList(ctx, t, sfs, pathAppend(path1, `.testfile`), ".testfile")
   332  
   333  	// Test that the first archived revision shows no directory entries.
   334  	pathArchivedRev1 := keybase1.NewPathWithKbfsArchived(
   335  		keybase1.KBFSArchivedPath{
   336  			Path:          `/private/jdoe`,
   337  			ArchivedParam: keybase1.NewKBFSArchivedParamWithRevision(1),
   338  		})
   339  	testList(ctx, t, sfs, pathArchivedRev1)
   340  
   341  	pathArchivedRev2 := keybase1.NewPathWithKbfsArchived(
   342  		keybase1.KBFSArchivedPath{
   343  			Path:          `/private/jdoe`,
   344  			ArchivedParam: keybase1.NewKBFSArchivedParamWithRevision(2),
   345  		})
   346  	testList(ctx, t, sfs, pathArchivedRev2, "test1.txt")
   347  
   348  	// Same test, with by-time archived paths.
   349  	pathArchivedTime := keybase1.NewPathWithKbfsArchived(
   350  		keybase1.KBFSArchivedPath{
   351  			Path: `/private/jdoe`,
   352  			ArchivedParam: keybase1.NewKBFSArchivedParamWithTime(
   353  				keybase1.ToTime(rev1Time)),
   354  		})
   355  	testList(ctx, t, sfs, pathArchivedTime)
   356  
   357  	pathArchivedTimeString := keybase1.NewPathWithKbfsArchived(
   358  		keybase1.KBFSArchivedPath{
   359  			Path: `/private/jdoe`,
   360  			ArchivedParam: keybase1.NewKBFSArchivedParamWithTimeString(
   361  				rev1Time.String()),
   362  		})
   363  	testList(ctx, t, sfs, pathArchivedTimeString)
   364  
   365  	pathArchivedRelTimeString := keybase1.NewPathWithKbfsArchived(
   366  		keybase1.KBFSArchivedPath{
   367  			Path: `/private/jdoe`,
   368  			ArchivedParam: keybase1.NewKBFSArchivedParamWithRelTimeString(
   369  				"45s"),
   370  		})
   371  	testList(ctx, t, sfs, pathArchivedRelTimeString)
   372  
   373  	clock.Add(1 * time.Minute)
   374  	testList(ctx, t, sfs, pathArchivedRelTimeString, "test1.txt")
   375  }
   376  
   377  func TestListRecursive(t *testing.T) {
   378  	ctx := context.Background()
   379  	sfs := newSimpleFS(env.EmptyAppStateUpdater{}, libkbfs.MakeTestConfigOrBust(t, "jdoe"))
   380  	defer closeSimpleFS(ctx, t, sfs)
   381  
   382  	t.Log("List directory before it's created")
   383  	pathJDoe := keybase1.NewPathWithKbfsPath(`/private/jdoe`)
   384  	opid, err := sfs.SimpleFSMakeOpid(ctx)
   385  	require.NoError(t, err)
   386  	err = sfs.SimpleFSListRecursive(ctx, keybase1.SimpleFSListRecursiveArg{
   387  		OpID: opid,
   388  		Path: pathJDoe,
   389  	})
   390  	require.NoError(t, err)
   391  	checkPendingOp(
   392  		ctx, t, sfs, opid, keybase1.AsyncOps_LIST_RECURSIVE, pathJDoe,
   393  		keybase1.Path{}, true)
   394  	err = sfs.SimpleFSWait(ctx, opid)
   395  	require.NoError(t, err)
   396  	listResult, err := sfs.SimpleFSReadList(ctx, opid)
   397  	require.NoError(t, err)
   398  	require.Len(t, listResult.Entries, 0,
   399  		"Expected 0 directory entries in listing")
   400  
   401  	// make a temp remote directory + files we will clean up later
   402  	writeRemoteDir(ctx, t, sfs, pathAppend(pathJDoe, `a`))
   403  	patha := keybase1.NewPathWithKbfsPath(`/private/jdoe/a`)
   404  	writeRemoteDir(ctx, t, sfs, pathAppend(patha, `aa`))
   405  	pathaa := keybase1.NewPathWithKbfsPath(`/private/jdoe/a/aa`)
   406  	writeRemoteDir(ctx, t, sfs, pathAppend(patha, `ab`))
   407  	pathab := keybase1.NewPathWithKbfsPath(`/private/jdoe/a/ab`)
   408  	writeRemoteDir(ctx, t, sfs, pathAppend(pathaa, `aaa`))
   409  	pathaaa := keybase1.NewPathWithKbfsPath(`/private/jdoe/a/aa/aaa`)
   410  	writeRemoteFile(ctx, t, sfs, pathAppend(pathaaa, `test1.txt`), []byte(`foo`))
   411  	writeRemoteFile(ctx, t, sfs, pathAppend(pathab, `test2.txt`), []byte(`foo`))
   412  	writeRemoteFile(ctx, t, sfs, pathAppend(patha, `.testfile`), []byte(`foo`))
   413  
   414  	opid, err = sfs.SimpleFSMakeOpid(ctx)
   415  	require.NoError(t, err)
   416  	err = sfs.SimpleFSListRecursive(ctx, keybase1.SimpleFSListRecursiveArg{
   417  		OpID: opid,
   418  		Path: pathJDoe,
   419  	})
   420  	require.NoError(t, err)
   421  	checkPendingOp(ctx, t, sfs, opid, keybase1.AsyncOps_LIST_RECURSIVE, pathJDoe, keybase1.Path{}, true)
   422  	err = sfs.SimpleFSWait(ctx, opid)
   423  	require.NoError(t, err)
   424  	listResult, err = sfs.SimpleFSReadList(ctx, opid)
   425  	require.NoError(t, err)
   426  	expected := []string{
   427  		"a",
   428  		"a/.testfile",
   429  		"a/aa",
   430  		"a/aa/aaa",
   431  		"a/aa/aaa/test1.txt",
   432  		"a/ab",
   433  		"a/ab/test2.txt",
   434  	}
   435  	require.Len(t, listResult.Entries, len(expected))
   436  	sort.Slice(listResult.Entries, func(i, j int) bool {
   437  		return strings.Compare(listResult.Entries[i].Name,
   438  			listResult.Entries[j].Name) < 0
   439  	})
   440  	for i, e := range expected {
   441  		require.Equal(t, e, listResult.Entries[i].Name)
   442  	}
   443  
   444  	opid, err = sfs.SimpleFSMakeOpid(ctx)
   445  	require.NoError(t, err)
   446  	err = sfs.SimpleFSListRecursiveToDepth(ctx, keybase1.SimpleFSListRecursiveToDepthArg{
   447  		OpID:  opid,
   448  		Path:  patha,
   449  		Depth: 1,
   450  	})
   451  	require.NoError(t, err)
   452  	checkPendingOp(ctx, t, sfs, opid, keybase1.AsyncOps_LIST_RECURSIVE_TO_DEPTH, patha, keybase1.Path{}, true)
   453  	err = sfs.SimpleFSWait(ctx, opid)
   454  	require.NoError(t, err)
   455  	listResult, err = sfs.SimpleFSReadList(ctx, opid)
   456  	require.NoError(t, err)
   457  	expected = []string{
   458  		".testfile",
   459  		"aa",
   460  		"aa/aaa",
   461  		"ab",
   462  		"ab/test2.txt",
   463  	}
   464  	require.Len(t, listResult.Entries, len(expected))
   465  	sort.Slice(listResult.Entries, func(i, j int) bool {
   466  		return strings.Compare(listResult.Entries[i].Name,
   467  			listResult.Entries[j].Name) < 0
   468  	})
   469  	for i, e := range expected {
   470  		require.Equal(t, e, listResult.Entries[i].Name)
   471  	}
   472  }
   473  
   474  func TestCopyToLocal(t *testing.T) {
   475  	ctx := context.Background()
   476  	sfs := newSimpleFS(env.EmptyAppStateUpdater{}, libkbfs.MakeTestConfigOrBust(t, "jdoe"))
   477  	defer closeSimpleFS(ctx, t, sfs)
   478  
   479  	// make a temp remote directory + file(s) we will clean up later
   480  	path1 := keybase1.NewPathWithKbfsPath(`/private/jdoe`)
   481  	writeRemoteFile(ctx, t, sfs, pathAppend(path1, "test1.txt"), []byte("foo"))
   482  
   483  	// make a temp local dest directory + files we will clean up later
   484  	tempdir2, err := os.MkdirTemp(TempDirBase, "simpleFstest")
   485  	defer os.RemoveAll(tempdir2)
   486  	require.NoError(t, err)
   487  	path2 := keybase1.NewPathWithLocal(tempdir2)
   488  
   489  	opid, err := sfs.SimpleFSMakeOpid(ctx)
   490  	require.NoError(t, err)
   491  
   492  	srcPath := pathAppend(path1, "test1.txt")
   493  	destPath := pathAppend(path2, "test1.txt")
   494  
   495  	err = sfs.SimpleFSCopy(ctx, keybase1.SimpleFSCopyArg{
   496  		OpID: opid,
   497  		Src:  srcPath,
   498  		Dest: destPath,
   499  	})
   500  	require.NoError(t, err)
   501  
   502  	checkPendingOp(ctx, t, sfs, opid, keybase1.AsyncOps_COPY, srcPath, destPath, true)
   503  	err = sfs.SimpleFSWait(ctx, opid)
   504  	require.NoError(t, err)
   505  
   506  	checkPendingOp(ctx, t, sfs, opid, keybase1.AsyncOps_COPY, srcPath, destPath, false)
   507  	// Verify error on double wait
   508  	err = sfs.SimpleFSWait(ctx, opid)
   509  	require.Error(t, err)
   510  
   511  	exists, err := libkb.FileExists(filepath.Join(tempdir2, "test1.txt"))
   512  	require.NoError(t, err)
   513  	require.True(t, exists, "File copy destination must exist")
   514  }
   515  
   516  func TestCopyRecursive(t *testing.T) {
   517  	ctx := context.Background()
   518  	sfs := newSimpleFS(env.EmptyAppStateUpdater{}, libkbfs.MakeTestConfigOrBust(t, "jdoe"))
   519  	defer closeSimpleFS(ctx, t, sfs)
   520  
   521  	// make a temp local dest directory + files we will clean up later
   522  	tempdir, err := os.MkdirTemp(TempDirBase, "simpleFstest")
   523  	require.NoError(t, err)
   524  	defer os.RemoveAll(tempdir)
   525  
   526  	// First try copying from a TLF that doesn't exist yet, which
   527  	// shouldn't do anything.
   528  	testdir := filepath.Join(tempdir, "testdir")
   529  	pathLocal := keybase1.NewPathWithLocal(filepath.ToSlash(testdir))
   530  	pathKbfsEmpty := keybase1.NewPathWithKbfsPath(`/private/jdoe`)
   531  	opid, err := sfs.SimpleFSMakeOpid(ctx)
   532  	require.NoError(t, err)
   533  	err = sfs.SimpleFSCopyRecursive(ctx, keybase1.SimpleFSCopyRecursiveArg{
   534  		OpID: opid,
   535  		Src:  pathKbfsEmpty,
   536  		Dest: pathLocal,
   537  	})
   538  	require.NoError(t, err)
   539  	checkPendingOp(
   540  		ctx, t, sfs, opid, keybase1.AsyncOps_COPY, pathKbfsEmpty, pathLocal,
   541  		true)
   542  	err = sfs.SimpleFSWait(ctx, opid)
   543  	require.NoError(t, err)
   544  	d, err := os.Open(testdir)
   545  	require.NoError(t, err)
   546  	fis, err := d.Readdir(0)
   547  	require.NoError(t, err)
   548  	require.Len(t, fis, 0)
   549  
   550  	// Populate local starting directory.
   551  	err = os.WriteFile(
   552  		filepath.Join(tempdir, "testdir", "test1.txt"), []byte("foo"), 0600)
   553  	require.NoError(t, err)
   554  	err = os.WriteFile(
   555  		filepath.Join(tempdir, "testdir", "test2.txt"), []byte("bar"), 0600)
   556  	require.NoError(t, err)
   557  
   558  	opid, err = sfs.SimpleFSMakeOpid(ctx)
   559  	require.NoError(t, err)
   560  
   561  	// Copy it into KBFS.
   562  	pathKbfs := keybase1.NewPathWithKbfsPath(`/private/jdoe/testdir`)
   563  	err = sfs.SimpleFSCopyRecursive(ctx, keybase1.SimpleFSCopyRecursiveArg{
   564  		OpID: opid,
   565  		Src:  pathLocal,
   566  		Dest: pathKbfs,
   567  	})
   568  	require.NoError(t, err)
   569  	checkPendingOp(
   570  		ctx, t, sfs, opid, keybase1.AsyncOps_COPY, pathLocal, pathKbfs, true)
   571  	err = sfs.SimpleFSWait(ctx, opid)
   572  	require.NoError(t, err)
   573  
   574  	require.Equal(t, "foo",
   575  		string(readRemoteFile(ctx, t, sfs, pathAppend(pathKbfs, "test1.txt"))))
   576  	require.Equal(t, "bar",
   577  		string(readRemoteFile(ctx, t, sfs, pathAppend(pathKbfs, "test2.txt"))))
   578  
   579  	// Copy it back.
   580  	tempdir2, err := os.MkdirTemp(TempDirBase, "simpleFstest")
   581  	require.NoError(t, err)
   582  	defer os.RemoveAll(tempdir2)
   583  	path3 := keybase1.NewPathWithLocal(
   584  		filepath.ToSlash(filepath.Join(tempdir2, "testdir")))
   585  	opid2, err := sfs.SimpleFSMakeOpid(ctx)
   586  	require.NoError(t, err)
   587  	err = sfs.SimpleFSCopyRecursive(ctx, keybase1.SimpleFSCopyRecursiveArg{
   588  		OpID: opid2,
   589  		Src:  pathKbfs,
   590  		Dest: path3,
   591  	})
   592  	require.NoError(t, err)
   593  	checkPendingOp(
   594  		ctx, t, sfs, opid2, keybase1.AsyncOps_COPY, pathKbfs, path3, true)
   595  	err = sfs.SimpleFSWait(ctx, opid2)
   596  	require.NoError(t, err)
   597  	dataFoo, err := os.ReadFile(
   598  		filepath.Join(tempdir2, "testdir", "test1.txt"))
   599  	require.NoError(t, err)
   600  	require.Equal(t, "foo", string(dataFoo))
   601  	dataBar, err := os.ReadFile(
   602  		filepath.Join(tempdir2, "testdir", "test2.txt"))
   603  	require.NoError(t, err)
   604  	require.Equal(t, "bar", string(dataBar))
   605  
   606  	// Get current revision number for the KBFS files.
   607  	syncFS(ctx, t, sfs, "/private/jdoe")
   608  	fb, _, err := sfs.getFolderBranchFromPath(ctx, pathKbfs)
   609  	require.NoError(t, err)
   610  	status, _, err := sfs.config.KBFSOps().FolderStatus(ctx, fb)
   611  	require.NoError(t, err)
   612  	rev := status.Revision
   613  	pathKbfsArchived := keybase1.NewPathWithKbfsArchived(
   614  		keybase1.KBFSArchivedPath{
   615  			Path: `/private/jdoe/testdir`,
   616  			ArchivedParam: keybase1.NewKBFSArchivedParamWithRevision(
   617  				keybase1.KBFSRevision(rev)),
   618  		})
   619  
   620  	// Overwrite the files in KBFS.
   621  	writeRemoteFile(
   622  		ctx, t, sfs, pathAppend(pathKbfs, `test1.txt`), []byte(`foo2`))
   623  	writeRemoteFile(
   624  		ctx, t, sfs, pathAppend(pathKbfs, `test2.txt`), []byte(`bar2`))
   625  	syncFS(ctx, t, sfs, "/private/jdoe")
   626  	require.Equal(t, "foo2",
   627  		string(readRemoteFile(ctx, t, sfs, pathAppend(pathKbfs, "test1.txt"))))
   628  	require.Equal(t, "bar2",
   629  		string(readRemoteFile(ctx, t, sfs, pathAppend(pathKbfs, "test2.txt"))))
   630  
   631  	// Read old data from archived path.
   632  	require.Equal(t, "foo",
   633  		string(readRemoteFile(
   634  			ctx, t, sfs, pathAppend(pathKbfsArchived, "test1.txt"))))
   635  	require.Equal(t, "bar",
   636  		string(readRemoteFile(
   637  			ctx, t, sfs, pathAppend(pathKbfsArchived, "test2.txt"))))
   638  }
   639  
   640  func TestCopyToRemote(t *testing.T) {
   641  	ctx := context.Background()
   642  	sfs := newSimpleFS(env.EmptyAppStateUpdater{}, libkbfs.MakeTestConfigOrBust(t, "jdoe"))
   643  	defer closeSimpleFS(ctx, t, sfs)
   644  
   645  	// make a temp remote directory + file(s) we will clean up later
   646  	path2 := keybase1.NewPathWithKbfsPath(`/private/jdoe`)
   647  
   648  	// make a temp local dest directory + files we will clean up later
   649  	tempdir, err := os.MkdirTemp(TempDirBase, "simpleFstest")
   650  	defer os.RemoveAll(tempdir)
   651  	require.NoError(t, err)
   652  	path1 := keybase1.NewPathWithLocal(tempdir)
   653  	defer deleteTempLocalPath(path1)
   654  	err = os.WriteFile(filepath.Join(path1.Local(), "test1.txt"), []byte("foo"), 0644)
   655  	require.NoError(t, err)
   656  
   657  	opid, err := sfs.SimpleFSMakeOpid(ctx)
   658  	require.NoError(t, err)
   659  
   660  	srcPath := keybase1.NewPathWithLocal(
   661  		filepath.ToSlash(filepath.Join(path1.Local(), "test1.txt")))
   662  	destPath := pathAppend(path2, "test1.txt")
   663  	err = sfs.SimpleFSCopy(ctx, keybase1.SimpleFSCopyArg{
   664  		OpID: opid,
   665  		Src:  srcPath,
   666  		Dest: destPath,
   667  	})
   668  	require.NoError(t, err)
   669  
   670  	checkPendingOp(ctx, t, sfs, opid, keybase1.AsyncOps_COPY, srcPath, destPath, true)
   671  
   672  	err = sfs.SimpleFSWait(ctx, opid)
   673  	require.NoError(t, err)
   674  
   675  	checkPendingOp(ctx, t, sfs, opid, keybase1.AsyncOps_COPY, srcPath, destPath, false)
   676  
   677  	// Verify error on double wait
   678  	err = sfs.SimpleFSWait(ctx, opid)
   679  	require.Error(t, err)
   680  
   681  	require.Equal(t, `foo`,
   682  		string(readRemoteFile(ctx, t, sfs, pathAppend(path2, "test1.txt"))))
   683  }
   684  
   685  func writeRemoteFile(ctx context.Context, t *testing.T, sfs *SimpleFS, path keybase1.Path, data []byte) {
   686  	opid, err := sfs.SimpleFSMakeOpid(ctx)
   687  	require.NoError(t, err)
   688  
   689  	err = sfs.SimpleFSOpen(ctx, keybase1.SimpleFSOpenArg{
   690  		OpID:  opid,
   691  		Dest:  path,
   692  		Flags: keybase1.OpenFlags_REPLACE | keybase1.OpenFlags_WRITE,
   693  	})
   694  	defer sfs.SimpleFSClose(ctx, opid)
   695  	require.NoError(t, err)
   696  
   697  	err = sfs.SimpleFSWrite(ctx, keybase1.SimpleFSWriteArg{
   698  		OpID:    opid,
   699  		Offset:  0,
   700  		Content: data,
   701  	})
   702  
   703  	require.NoError(t, err)
   704  }
   705  
   706  func writeRemoteDir(ctx context.Context, t *testing.T, sfs *SimpleFS, path keybase1.Path) {
   707  	opid, err := sfs.SimpleFSMakeOpid(ctx)
   708  	require.NoError(t, err)
   709  
   710  	err = sfs.SimpleFSOpen(ctx, keybase1.SimpleFSOpenArg{
   711  		OpID:  opid,
   712  		Dest:  path,
   713  		Flags: keybase1.OpenFlags_REPLACE | keybase1.OpenFlags_WRITE | keybase1.OpenFlags_DIRECTORY,
   714  	})
   715  	defer sfs.SimpleFSClose(ctx, opid)
   716  	require.NoError(t, err)
   717  }
   718  
   719  func readRemoteFile(ctx context.Context, t *testing.T, sfs *SimpleFS, path keybase1.Path) []byte {
   720  	opid, err := sfs.SimpleFSMakeOpid(ctx)
   721  	require.NoError(t, err)
   722  
   723  	de, err := sfs.SimpleFSStat(ctx, keybase1.SimpleFSStatArg{Path: path})
   724  	require.NoError(t, err)
   725  	t.Logf("Stat remote %q %d bytes", path, de.Size)
   726  
   727  	err = sfs.SimpleFSOpen(ctx, keybase1.SimpleFSOpenArg{
   728  		OpID:  opid,
   729  		Dest:  path,
   730  		Flags: keybase1.OpenFlags_READ | keybase1.OpenFlags_EXISTING,
   731  	})
   732  	defer sfs.SimpleFSClose(ctx, opid)
   733  	require.NoError(t, err)
   734  
   735  	data, err := sfs.SimpleFSRead(ctx, keybase1.SimpleFSReadArg{
   736  		OpID:   opid,
   737  		Offset: 0,
   738  		Size:   de.Size * 2, // Check that reading past the end works.
   739  	})
   740  	require.NoError(t, err)
   741  
   742  	// Profile sizes are dynamic.  For now just read something from
   743  	// it, to test that it's not empty.
   744  	isProfile := strings.Contains(path.String(), libfs.ProfileListDirName)
   745  	if de.DirentType != keybase1.DirentType_SYM && !isProfile {
   746  		require.Len(t, data.Data, de.Size)
   747  	}
   748  
   749  	// Starting the read past the end shouldn't matter either.
   750  	dataPastEnd, err := sfs.SimpleFSRead(ctx, keybase1.SimpleFSReadArg{
   751  		OpID:   opid,
   752  		Offset: int64(de.Size),
   753  		Size:   de.Size,
   754  	})
   755  	require.NoError(t, err)
   756  	if !isProfile {
   757  		require.Len(t, dataPastEnd.Data, 0)
   758  	}
   759  
   760  	return data.Data
   761  }
   762  
   763  type fsBlocker struct {
   764  	*libfs.FS
   765  	signalCh  chan<- struct{}
   766  	unblockCh <-chan struct{}
   767  }
   768  
   769  var _ billy.Filesystem = (*fsBlocker)(nil)
   770  
   771  func (fs *fsBlocker) OpenFile(filename string, flag int, perm os.FileMode) (
   772  	f billy.File, err error) {
   773  	fs.signalCh <- struct{}{}
   774  	<-fs.unblockCh
   775  	return fs.FS.OpenFile(filename, flag, perm)
   776  }
   777  
   778  func (fs *fsBlocker) Create(filename string) (billy.File, error) {
   779  	fs.signalCh <- struct{}{}
   780  	<-fs.unblockCh
   781  	return fs.FS.Create(filename)
   782  }
   783  
   784  func (fs *fsBlocker) Open(filename string) (billy.File, error) {
   785  	fs.signalCh <- struct{}{}
   786  	<-fs.unblockCh
   787  	return fs.FS.Open(filename)
   788  }
   789  
   790  func (fs *fsBlocker) MkdirAll(filename string, perm os.FileMode) (err error) {
   791  	fs.signalCh <- struct{}{}
   792  	<-fs.unblockCh
   793  	return fs.FS.MkdirAll(filename, perm)
   794  }
   795  
   796  func (fs *fsBlocker) ReadDir(p string) (fis []os.FileInfo, err error) {
   797  	fs.signalCh <- struct{}{}
   798  	<-fs.unblockCh
   799  	return fs.FS.ReadDir(p)
   800  }
   801  
   802  func (fs *fsBlocker) Chroot(p string) (newFS billy.Filesystem, err error) {
   803  	chrootFS, err := fs.FS.ChrootAsLibFS(p)
   804  	if err != nil {
   805  		return nil, err
   806  	}
   807  	return &fsBlocker{chrootFS, fs.signalCh, fs.unblockCh}, nil
   808  }
   809  
   810  type fsBlockerMaker struct {
   811  	signalCh  chan<- struct{}
   812  	unblockCh <-chan struct{}
   813  }
   814  
   815  func (maker fsBlockerMaker) makeNewBlocker(
   816  	ctx context.Context, config libkbfs.Config,
   817  	tlfHandle *tlfhandle.Handle, branch data.BranchName, subdir string,
   818  	create bool) (billy.Filesystem, error) {
   819  	fsMaker := libfs.NewFS
   820  	if !create {
   821  		fsMaker = libfs.NewFSIfExists
   822  	}
   823  	fs, err := fsMaker(
   824  		ctx, config, tlfHandle, branch, subdir, "", keybase1.MDPriorityNormal)
   825  	if err != nil {
   826  		return nil, err
   827  	}
   828  	return &fsBlocker{fs, maker.signalCh, maker.unblockCh}, nil
   829  }
   830  
   831  func TestCopyProgress(t *testing.T) {
   832  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   833  	defer cancel()
   834  
   835  	config := libkbfs.MakeTestConfigOrBust(t, "jdoe")
   836  	clock := &clocktest.TestClock{}
   837  	start := time.Now()
   838  	clock.Set(start)
   839  	config.SetClock(clock)
   840  
   841  	sfs := newSimpleFS(env.EmptyAppStateUpdater{}, config)
   842  	defer closeSimpleFS(ctx, t, sfs)
   843  
   844  	waitCh := make(chan struct{})
   845  	unblockCh := make(chan struct{})
   846  	maker := fsBlockerMaker{waitCh, unblockCh}
   847  	sfs.newFS = maker.makeNewBlocker
   848  
   849  	// make a temp local dest directory + files we will clean up later
   850  	tempdir, err := os.MkdirTemp(TempDirBase, "simpleFstest")
   851  	require.NoError(t, err)
   852  	defer os.RemoveAll(tempdir)
   853  
   854  	// Make local starting directory.
   855  	err = os.Mkdir(filepath.Join(tempdir, "testdir"), 0700)
   856  	require.NoError(t, err)
   857  	err = os.WriteFile(
   858  		filepath.Join(tempdir, "testdir", "test1.txt"), []byte("foo"), 0600)
   859  	require.NoError(t, err)
   860  	err = os.WriteFile(
   861  		filepath.Join(tempdir, "testdir", "test2.txt"), []byte("bar"), 0600)
   862  	require.NoError(t, err)
   863  	path1 := keybase1.NewPathWithLocal(
   864  		filepath.ToSlash(filepath.Join(tempdir, "testdir")))
   865  	path2 := keybase1.NewPathWithKbfsPath(`/private/jdoe/testdir`)
   866  
   867  	opid, err := sfs.SimpleFSMakeOpid(ctx)
   868  	require.NoError(t, err)
   869  
   870  	// Copy it into KBFS.
   871  	err = sfs.SimpleFSCopyRecursive(ctx, keybase1.SimpleFSCopyRecursiveArg{
   872  		OpID: opid,
   873  		Src:  path1,
   874  		Dest: path2,
   875  	})
   876  	require.NoError(t, err)
   877  	checkPendingOp(
   878  		ctx, t, sfs, opid, keybase1.AsyncOps_COPY, path1, path2, true)
   879  
   880  	t.Log("Wait for the first mkdir")
   881  	waitFn := func() {
   882  		select {
   883  		case <-waitCh:
   884  		case <-ctx.Done():
   885  			t.Fatal(ctx.Err())
   886  		}
   887  	}
   888  	waitFn()
   889  
   890  	// Check the progress -- there shouldn't be any yet.
   891  	progress, err := sfs.SimpleFSCheck(ctx, opid)
   892  	require.NoError(t, err)
   893  	expectedProgress := keybase1.OpProgress{
   894  		Start:      keybase1.ToTime(start),
   895  		OpType:     keybase1.AsyncOps_COPY,
   896  		BytesTotal: 6,
   897  		FilesTotal: 3,
   898  	}
   899  	require.Equal(t, expectedProgress, progress)
   900  
   901  	t.Log("Unblock the mkdir")
   902  	unblockCh <- struct{}{}
   903  
   904  	t.Log("Wait for the first file")
   905  	waitFn()
   906  
   907  	clock.Add(1 * time.Minute)
   908  	expectedProgress.FilesRead = 1
   909  	expectedProgress.FilesWritten = 1
   910  	// We read one directory but 0 bytes, so we still have no expected
   911  	// end time.
   912  	progress, err = sfs.SimpleFSCheck(ctx, opid)
   913  	require.NoError(t, err)
   914  	require.Equal(t, expectedProgress, progress)
   915  
   916  	t.Log("Unblock the first file")
   917  	unblockCh <- struct{}{}
   918  
   919  	t.Log("Wait for the second file")
   920  	waitFn()
   921  
   922  	clock.Add(1 * time.Minute)
   923  	expectedProgress.FilesRead = 2
   924  	expectedProgress.FilesWritten = 2
   925  	expectedProgress.BytesRead = 3
   926  	expectedProgress.BytesWritten = 3
   927  	progress, err = sfs.SimpleFSCheck(ctx, opid)
   928  	require.NoError(t, err)
   929  
   930  	// We read one file and two minutes have passed, so the estimated
   931  	// time should be two more minutes from now.  But use the float
   932  	// calculation adds some uncertainty, so check it within a small
   933  	// error range, and then set it to the received value for the
   934  	// exact check.
   935  	endEstimate := keybase1.ToTime(start.Add(4 * time.Minute))
   936  	require.InEpsilon(
   937  		t, float64(endEstimate), float64(progress.EndEstimate),
   938  		float64(5*time.Nanosecond))
   939  	expectedProgress.EndEstimate = progress.EndEstimate
   940  
   941  	require.Equal(t, expectedProgress, progress)
   942  
   943  	t.Log("Unblock the second file")
   944  	unblockCh <- struct{}{}
   945  
   946  	err = sfs.SimpleFSWait(ctx, opid)
   947  	require.NoError(t, err)
   948  }
   949  
   950  func TestRemove(t *testing.T) {
   951  	ctx := context.Background()
   952  	sfs := newSimpleFS(
   953  		env.EmptyAppStateUpdater{}, libkbfs.MakeTestConfigOrBust(t, "jdoe"))
   954  	defer closeSimpleFS(ctx, t, sfs)
   955  
   956  	t.Log("Make a file to remove")
   957  	pathKbfs := keybase1.NewPathWithKbfsPath("/private/jdoe")
   958  	writeRemoteFile(
   959  		ctx, t, sfs, pathAppend(pathKbfs, "test.txt"), []byte("foo"))
   960  	syncFS(ctx, t, sfs, "/private/jdoe")
   961  
   962  	t.Log("Make sure the file is there")
   963  	testList(ctx, t, sfs, pathKbfs, "test.txt")
   964  
   965  	t.Log("Remove the file")
   966  	pathFile := keybase1.NewPathWithKbfsPath("/private/jdoe/test.txt")
   967  	opid, err := sfs.SimpleFSMakeOpid(ctx)
   968  	require.NoError(t, err)
   969  	err = sfs.SimpleFSRemove(ctx, keybase1.SimpleFSRemoveArg{
   970  		OpID: opid,
   971  		Path: pathFile,
   972  	})
   973  	require.NoError(t, err)
   974  	checkPendingOp(
   975  		ctx, t, sfs, opid, keybase1.AsyncOps_REMOVE, pathFile, keybase1.Path{},
   976  		true)
   977  	err = sfs.SimpleFSWait(ctx, opid)
   978  	require.NoError(t, err)
   979  
   980  	t.Log("Make sure it's gone")
   981  	testList(ctx, t, sfs, pathKbfs)
   982  }
   983  
   984  func TestRemoveRecursive(t *testing.T) {
   985  	ctx := context.Background()
   986  	sfs := newSimpleFS(
   987  		env.EmptyAppStateUpdater{}, libkbfs.MakeTestConfigOrBust(t, "jdoe"))
   988  	defer closeSimpleFS(ctx, t, sfs)
   989  
   990  	t.Log("Make a directory to remove")
   991  	pathKbfs := keybase1.NewPathWithKbfsPath("/private/jdoe")
   992  	pathDir := pathAppend(pathKbfs, "a")
   993  	writeRemoteDir(ctx, t, sfs, pathDir)
   994  	writeRemoteFile(ctx, t, sfs, pathAppend(pathDir, "test1.txt"), []byte("1"))
   995  	writeRemoteFile(ctx, t, sfs, pathAppend(pathDir, "test2.txt"), []byte("2"))
   996  	pathDir2 := pathAppend(pathDir, "b")
   997  	writeRemoteDir(ctx, t, sfs, pathDir2)
   998  	writeRemoteFile(ctx, t, sfs, pathAppend(pathDir2, "test3.txt"), []byte("3"))
   999  	syncFS(ctx, t, sfs, "/private/jdoe")
  1000  
  1001  	t.Log("Make sure the files are there")
  1002  	testList(ctx, t, sfs, pathDir, "test1.txt", "test2.txt", "b")
  1003  
  1004  	t.Log("Remove dir without recursion, expect error")
  1005  	opid, err := sfs.SimpleFSMakeOpid(ctx)
  1006  	require.NoError(t, err)
  1007  	err = sfs.SimpleFSRemove(ctx, keybase1.SimpleFSRemoveArg{
  1008  		OpID: opid,
  1009  		Path: pathDir,
  1010  	})
  1011  	require.NoError(t, err)
  1012  	checkPendingOp(
  1013  		ctx, t, sfs, opid, keybase1.AsyncOps_REMOVE, pathDir, keybase1.Path{},
  1014  		true)
  1015  	err = sfs.SimpleFSWait(ctx, opid)
  1016  	require.Error(t, err)
  1017  
  1018  	t.Log("Remove the dir recursively")
  1019  	opid, err = sfs.SimpleFSMakeOpid(ctx)
  1020  	require.NoError(t, err)
  1021  	err = sfs.SimpleFSRemove(ctx, keybase1.SimpleFSRemoveArg{
  1022  		OpID:      opid,
  1023  		Path:      pathDir,
  1024  		Recursive: true,
  1025  	})
  1026  	require.NoError(t, err)
  1027  	checkPendingOp(
  1028  		ctx, t, sfs, opid, keybase1.AsyncOps_REMOVE, pathDir, keybase1.Path{},
  1029  		true)
  1030  	err = sfs.SimpleFSWait(ctx, opid)
  1031  	require.NoError(t, err)
  1032  
  1033  	t.Log("Make sure it's gone")
  1034  	testList(ctx, t, sfs, pathKbfs)
  1035  }
  1036  
  1037  func TestMoveWithinTlf(t *testing.T) {
  1038  	ctx := context.Background()
  1039  	sfs := newSimpleFS(
  1040  		env.EmptyAppStateUpdater{}, libkbfs.MakeTestConfigOrBust(t, "jdoe"))
  1041  	defer closeSimpleFS(ctx, t, sfs)
  1042  
  1043  	t.Log("Make a file to move")
  1044  	pathKbfs := keybase1.NewPathWithKbfsPath("/private/jdoe")
  1045  	writeRemoteFile(
  1046  		ctx, t, sfs, pathAppend(pathKbfs, "test1.txt"), []byte("foo"))
  1047  	syncFS(ctx, t, sfs, "/private/jdoe")
  1048  
  1049  	t.Log("Make sure the file is there")
  1050  	testList(ctx, t, sfs, pathKbfs, "test1.txt")
  1051  
  1052  	t.Log("Move the file")
  1053  	pathFileOld := pathAppend(pathKbfs, "test1.txt")
  1054  	pathFileNew := pathAppend(pathKbfs, "test2.txt")
  1055  	opid, err := sfs.SimpleFSMakeOpid(ctx)
  1056  	require.NoError(t, err)
  1057  	err = sfs.SimpleFSMove(ctx, keybase1.SimpleFSMoveArg{
  1058  		OpID: opid,
  1059  		Src:  pathFileOld,
  1060  		Dest: pathFileNew,
  1061  	})
  1062  	require.NoError(t, err)
  1063  	checkPendingOp(
  1064  		ctx, t, sfs, opid, keybase1.AsyncOps_MOVE, pathFileOld, pathFileNew,
  1065  		true)
  1066  	err = sfs.SimpleFSWait(ctx, opid)
  1067  	require.NoError(t, err)
  1068  
  1069  	t.Log("Make sure it's moved")
  1070  	testList(ctx, t, sfs, pathKbfs, "test2.txt")
  1071  
  1072  	t.Log("Move into subdir")
  1073  	pathDir := pathAppend(pathKbfs, "a")
  1074  	writeRemoteDir(ctx, t, sfs, pathDir)
  1075  	pathFileOld = pathFileNew
  1076  	pathFileNew = pathAppend(pathDir, "test3.txt")
  1077  	opid, err = sfs.SimpleFSMakeOpid(ctx)
  1078  	require.NoError(t, err)
  1079  	err = sfs.SimpleFSMove(ctx, keybase1.SimpleFSMoveArg{
  1080  		OpID: opid,
  1081  		Src:  pathFileOld,
  1082  		Dest: pathFileNew,
  1083  	})
  1084  	require.NoError(t, err)
  1085  	checkPendingOp(
  1086  		ctx, t, sfs, opid, keybase1.AsyncOps_MOVE, pathFileOld, pathFileNew,
  1087  		true)
  1088  	err = sfs.SimpleFSWait(ctx, opid)
  1089  	require.NoError(t, err)
  1090  
  1091  	t.Log("Make sure it's moved")
  1092  	testList(ctx, t, sfs, pathKbfs, "a")
  1093  	testList(ctx, t, sfs, pathDir, "test3.txt")
  1094  
  1095  	t.Log("Move into different, parallel subdir")
  1096  	pathDirB := pathAppend(pathKbfs, "b")
  1097  	writeRemoteDir(ctx, t, sfs, pathDirB)
  1098  	pathDirC := pathAppend(pathDirB, "c")
  1099  	writeRemoteDir(ctx, t, sfs, pathDirC)
  1100  	pathFileOld = pathFileNew
  1101  	pathFileNew = pathAppend(pathDirC, "test3.txt")
  1102  	opid, err = sfs.SimpleFSMakeOpid(ctx)
  1103  	require.NoError(t, err)
  1104  	err = sfs.SimpleFSMove(ctx, keybase1.SimpleFSMoveArg{
  1105  		OpID: opid,
  1106  		Src:  pathFileOld,
  1107  		Dest: pathFileNew,
  1108  	})
  1109  	require.NoError(t, err)
  1110  	checkPendingOp(
  1111  		ctx, t, sfs, opid, keybase1.AsyncOps_MOVE, pathFileOld, pathFileNew,
  1112  		true)
  1113  	err = sfs.SimpleFSWait(ctx, opid)
  1114  	require.NoError(t, err)
  1115  
  1116  	t.Log("Make sure it's moved")
  1117  	testList(ctx, t, sfs, pathDir)
  1118  	testList(ctx, t, sfs, pathDirC, "test3.txt")
  1119  }
  1120  
  1121  func TestMoveBetweenTlfs(t *testing.T) {
  1122  	ctx := context.Background()
  1123  	sfs := newSimpleFS(
  1124  		env.EmptyAppStateUpdater{}, libkbfs.MakeTestConfigOrBust(t, "jdoe"))
  1125  	defer closeSimpleFS(ctx, t, sfs)
  1126  
  1127  	t.Log("Make a file to move")
  1128  	pathPrivate := keybase1.NewPathWithKbfsPath("/private/jdoe")
  1129  	writeRemoteFile(
  1130  		ctx, t, sfs, pathAppend(pathPrivate, "test1.txt"), []byte("foo"))
  1131  	syncFS(ctx, t, sfs, "/private/jdoe")
  1132  
  1133  	t.Log("Make sure the file is there")
  1134  	testList(ctx, t, sfs, pathPrivate, "test1.txt")
  1135  
  1136  	t.Log("Move the file")
  1137  	pathFileOld := pathAppend(pathPrivate, "test1.txt")
  1138  	pathPublic := keybase1.NewPathWithKbfsPath("/public/jdoe")
  1139  	pathFileNew := pathAppend(pathPublic, "test2.txt")
  1140  	opid, err := sfs.SimpleFSMakeOpid(ctx)
  1141  	require.NoError(t, err)
  1142  	err = sfs.SimpleFSMove(ctx, keybase1.SimpleFSMoveArg{
  1143  		OpID: opid,
  1144  		Src:  pathFileOld,
  1145  		Dest: pathFileNew,
  1146  	})
  1147  	require.NoError(t, err)
  1148  	checkPendingOp(
  1149  		ctx, t, sfs, opid, keybase1.AsyncOps_MOVE, pathFileOld, pathFileNew,
  1150  		true)
  1151  	err = sfs.SimpleFSWait(ctx, opid)
  1152  	require.NoError(t, err)
  1153  	syncFS(ctx, t, sfs, "/public/jdoe")
  1154  
  1155  	t.Log("Make sure it's moved")
  1156  	testList(ctx, t, sfs, pathPrivate)
  1157  	testList(ctx, t, sfs, pathPublic, "test2.txt")
  1158  
  1159  	t.Log("Now move a whole populated directory")
  1160  	pathDir := pathAppend(pathPrivate, "a")
  1161  	writeRemoteDir(ctx, t, sfs, pathDir)
  1162  	writeRemoteFile(ctx, t, sfs, pathAppend(pathDir, "test1.txt"), []byte("1"))
  1163  	writeRemoteFile(ctx, t, sfs, pathAppend(pathDir, "test2.txt"), []byte("2"))
  1164  	pathDir2 := pathAppend(pathDir, "b")
  1165  	writeRemoteDir(ctx, t, sfs, pathDir2)
  1166  	writeRemoteFile(ctx, t, sfs, pathAppend(pathDir2, "test3.txt"), []byte("3"))
  1167  	syncFS(ctx, t, sfs, "/private/jdoe")
  1168  
  1169  	opid, err = sfs.SimpleFSMakeOpid(ctx)
  1170  	require.NoError(t, err)
  1171  	err = sfs.SimpleFSMove(ctx, keybase1.SimpleFSMoveArg{
  1172  		OpID:                   opid,
  1173  		Src:                    pathDir,
  1174  		Dest:                   pathPublic,
  1175  		OverwriteExistingFiles: true,
  1176  	})
  1177  	require.NoError(t, err)
  1178  	checkPendingOp(
  1179  		ctx, t, sfs, opid, keybase1.AsyncOps_MOVE, pathDir, pathPublic, true)
  1180  	err = sfs.SimpleFSWait(ctx, opid)
  1181  	require.NoError(t, err)
  1182  	syncFS(ctx, t, sfs, "/public/jdoe")
  1183  
  1184  	t.Log("Make sure it's moved (one file was overwritten)")
  1185  	testList(ctx, t, sfs, pathPrivate)
  1186  	testList(ctx, t, sfs, pathPublic, "test1.txt", "test2.txt", "b")
  1187  	testList(ctx, t, sfs, pathAppend(pathPublic, "b"), "test3.txt")
  1188  	require.Equal(t, "2",
  1189  		string(readRemoteFile(
  1190  			ctx, t, sfs, pathAppend(pathPublic, "test2.txt"))))
  1191  }
  1192  
  1193  func TestTlfEditHistory(t *testing.T) {
  1194  	ctx := context.Background()
  1195  	sfs := newSimpleFS(
  1196  		env.EmptyAppStateUpdater{},
  1197  		libkbfs.MakeTestConfigOrBust(t, "jdoe"))
  1198  	defer closeSimpleFS(ctx, t, sfs)
  1199  
  1200  	path := keybase1.NewPathWithKbfsPath(`/private/jdoe`)
  1201  	writeRemoteFile(ctx, t, sfs, pathAppend(path, `test1.txt`), []byte(`foo`))
  1202  	writeRemoteFile(ctx, t, sfs, pathAppend(path, `test2.txt`), []byte(`foo`))
  1203  	syncFS(ctx, t, sfs, "/private/jdoe")
  1204  
  1205  	history, err := sfs.SimpleFSFolderEditHistory(ctx, path)
  1206  	require.NoError(t, err)
  1207  	require.Len(t, history.History, 1)
  1208  	require.Equal(t, "jdoe", history.History[0].WriterName)
  1209  	require.Len(t, history.History[0].Edits, 2)
  1210  }
  1211  
  1212  type subscriptionReporter struct {
  1213  	libkbfs.Reporter
  1214  	lastPathNotify chan struct{}
  1215  
  1216  	lastPathMtx sync.RWMutex
  1217  	lastPath    string
  1218  }
  1219  
  1220  func (sr *subscriptionReporter) NotifyPathUpdated(
  1221  	_ context.Context, path string) {
  1222  	sr.lastPathMtx.Lock()
  1223  	defer sr.lastPathMtx.Unlock()
  1224  	sr.lastPath = path
  1225  	sr.lastPathNotify <- struct{}{}
  1226  }
  1227  
  1228  func (sr *subscriptionReporter) LastPath() string {
  1229  	sr.lastPathMtx.RLock()
  1230  	defer sr.lastPathMtx.RUnlock()
  1231  	return sr.lastPath
  1232  }
  1233  
  1234  func (sr *subscriptionReporter) waitForNotification(t *testing.T) {
  1235  	t.Helper()
  1236  	select {
  1237  	case <-sr.lastPathNotify:
  1238  	case <-time.After(10 * time.Millisecond):
  1239  		t.Fatal("Timed out while waiting for notification")
  1240  	}
  1241  }
  1242  
  1243  func (sr *subscriptionReporter) requireNoNotification(t *testing.T) {
  1244  	t.Helper()
  1245  	select {
  1246  	case <-sr.lastPathNotify:
  1247  		t.Fatalf("Got notification but expected none: %q", sr.lastPath)
  1248  	case <-time.After(10 * time.Millisecond):
  1249  	}
  1250  }
  1251  
  1252  func (sr *subscriptionReporter) depleteExistingNotifications(t *testing.T) {
  1253  	t.Helper()
  1254  	for {
  1255  		select {
  1256  		case <-sr.lastPathNotify:
  1257  		case <-time.After(10 * time.Millisecond):
  1258  			return
  1259  		}
  1260  	}
  1261  }
  1262  
  1263  func TestRefreshSubscription(t *testing.T) {
  1264  	ctx := context.Background()
  1265  	config := libkbfs.MakeTestConfigOrBust(t, "jdoe", "alice")
  1266  	sfs := newSimpleFS(env.EmptyAppStateUpdater{}, config)
  1267  	defer closeSimpleFS(ctx, t, sfs)
  1268  	sr := &subscriptionReporter{Reporter: config.Reporter(), lastPathNotify: make(chan struct{}, 1<<30)}
  1269  	config.SetReporter(sr)
  1270  
  1271  	// Use a non-canonical (possibly preferred) path to make sure notification
  1272  	// comes back with same path.
  1273  	path1 := keybase1.NewPathWithKbfsPath(`/private/jdoe,alice`)
  1274  
  1275  	t.Log("Writing a file with no subscription")
  1276  	writeRemoteFile(ctx, t, sfs, pathAppend(path1, `test1.txt`), []byte(`foo`))
  1277  	syncFS(ctx, t, sfs, "/private/jdoe,alice")
  1278  	sr.requireNoNotification(t)
  1279  	require.Equal(t, "", sr.LastPath())
  1280  
  1281  	t.Log("Subscribe, and make sure we get a notification")
  1282  	opid, err := sfs.SimpleFSMakeOpid(ctx)
  1283  	require.NoError(t, err)
  1284  	err = sfs.SimpleFSList(ctx, keybase1.SimpleFSListArg{
  1285  		OpID:                opid,
  1286  		Path:                path1,
  1287  		RefreshSubscription: true,
  1288  	})
  1289  	require.NoError(t, err)
  1290  	err = sfs.SimpleFSWait(ctx, opid)
  1291  	require.NoError(t, err)
  1292  
  1293  	writeRemoteFile(ctx, t, sfs, pathAppend(path1, `test2.txt`), []byte(`foo`))
  1294  	syncFS(ctx, t, sfs, "/private/jdoe,alice")
  1295  	sr.waitForNotification(t)
  1296  	require.Equal(t, "/keybase"+path1.Kbfs().Path, sr.LastPath())
  1297  
  1298  	t.Log("Make a public TLF")
  1299  	path2 := keybase1.NewPathWithKbfsPath(`/public/jdoe`)
  1300  	// Now subscribe to a different one, before the TLF even exists,
  1301  	// and make sure the old subscription goes away.
  1302  	opid2, err := sfs.SimpleFSMakeOpid(ctx)
  1303  	require.NoError(t, err)
  1304  	err = sfs.SimpleFSList(ctx, keybase1.SimpleFSListArg{
  1305  		OpID:                opid2,
  1306  		Path:                path2,
  1307  		RefreshSubscription: true,
  1308  	})
  1309  	require.NoError(t, err)
  1310  	err = sfs.SimpleFSWait(ctx, opid2)
  1311  	require.NoError(t, err)
  1312  
  1313  	writeRemoteFile(ctx, t, sfs, pathAppend(path2, `test.txt`), []byte(`foo`))
  1314  	syncFS(ctx, t, sfs, "/public/jdoe")
  1315  	sr.waitForNotification(t)
  1316  	require.Equal(t, "/keybase"+path2.Kbfs().Path, sr.LastPath())
  1317  
  1318  	// Make sure notification works with file content change.
  1319  	writeRemoteFile(ctx, t, sfs, pathAppend(path2, `test.txt`), []byte(`poo`))
  1320  	syncFS(ctx, t, sfs, "/public/jdoe")
  1321  	sr.waitForNotification(t)
  1322  	require.Equal(t, "/keybase"+path2.Kbfs().Path, sr.LastPath())
  1323  
  1324  	// We might have more than one notifications in channel here, so deplete
  1325  	// them before attempting more.
  1326  	sr.depleteExistingNotifications(t)
  1327  
  1328  	writeRemoteFile(ctx, t, sfs, pathAppend(path1, `test3.txt`), []byte(`foo`))
  1329  	syncFS(ctx, t, sfs, "/private/jdoe,alice")
  1330  	sr.requireNoNotification(t)
  1331  	require.Equal(t, "/keybase"+path2.Kbfs().Path, sr.LastPath())
  1332  
  1333  	// Now subscribe to the first one again, but using SimpleFSStat.
  1334  	path3 := keybase1.NewPathWithKbfsPath(`/private/jdoe,alice/test3.txt`)
  1335  	_, err = sfs.SimpleFSStat(ctx, keybase1.SimpleFSStatArg{
  1336  		Path:                path3,
  1337  		RefreshSubscription: true,
  1338  	})
  1339  	require.NoError(t, err)
  1340  
  1341  	writeRemoteFile(ctx, t, sfs, pathAppend(path1, `test3.txt`), []byte(`foo`))
  1342  	syncFS(ctx, t, sfs, "/private/jdoe,alice")
  1343  	sr.waitForNotification(t)
  1344  	require.Equal(t, "/keybase/private/jdoe,alice", sr.LastPath())
  1345  }
  1346  
  1347  func TestGetRevisions(t *testing.T) {
  1348  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  1349  	defer cancel()
  1350  
  1351  	config := libkbfs.MakeTestConfigOrBust(t, "jdoe")
  1352  	clock := &clocktest.TestClock{}
  1353  	start := time.Now()
  1354  	clock.Set(start)
  1355  	config.SetClock(clock)
  1356  
  1357  	sfs := newSimpleFS(env.EmptyAppStateUpdater{}, config)
  1358  	defer closeSimpleFS(ctx, t, sfs)
  1359  
  1360  	path := keybase1.NewPathWithKbfsPath(`/private/jdoe`)
  1361  	filePath := pathAppend(path, `test1.txt`)
  1362  
  1363  	getRevisions := func(
  1364  		spanType keybase1.RevisionSpanType) keybase1.GetRevisionsResult {
  1365  		opid, err := sfs.SimpleFSMakeOpid(ctx)
  1366  		require.NoError(t, err)
  1367  		err = sfs.SimpleFSGetRevisions(ctx, keybase1.SimpleFSGetRevisionsArg{
  1368  			OpID:     opid,
  1369  			Path:     filePath,
  1370  			SpanType: spanType,
  1371  		})
  1372  		require.NoError(t, err)
  1373  		err = sfs.SimpleFSWait(ctx, opid)
  1374  		require.NoError(t, err)
  1375  		res, err := sfs.SimpleFSReadRevisions(ctx, opid)
  1376  		require.NoError(t, err)
  1377  		err = sfs.SimpleFSClose(ctx, opid)
  1378  		require.NoError(t, err)
  1379  		return res
  1380  	}
  1381  
  1382  	gcJump := config.Mode().QuotaReclamationMinUnrefAge() + 1*time.Second
  1383  	checkRevisions := func(
  1384  		numExpected, newestRev int, spanType keybase1.RevisionSpanType) {
  1385  		res := getRevisions(spanType)
  1386  		require.Len(t, res.Revisions, numExpected)
  1387  
  1388  		// Default should get the most recent one, and then the 4
  1389  		// earliest ones, while LAST_FIVE should get the last five.
  1390  		expectedTime := clock.Now()
  1391  		expectedRev := keybase1.KBFSRevision(newestRev)
  1392  		for i, r := range res.Revisions {
  1393  			require.Equal(t, keybase1.ToTime(expectedTime), r.Entry.Time, fmt.Sprintf("%d %d", i, r.Revision))
  1394  			require.Equal(t, expectedRev, r.Revision)
  1395  			expectedTime = expectedTime.Add(-1 * time.Minute)
  1396  			expectedRev--
  1397  			// Adjust for the skip-list when the list is full.
  1398  			if newestRev == 7 && i == 3 &&
  1399  				spanType == keybase1.RevisionSpanType_DEFAULT {
  1400  				expectedTime = expectedTime.Add(-1 * time.Minute)
  1401  				expectedRev--
  1402  			} else if newestRev == 9 && i == 0 {
  1403  				expectedTime = expectedTime.Add(-gcJump)
  1404  			}
  1405  		}
  1406  	}
  1407  
  1408  	t.Log("Write 6 revisions of a single file, spaced out a minute each")
  1409  	for i := 0; i < 6; i++ {
  1410  		clock.Add(1 * time.Minute)
  1411  		writeRemoteFile(ctx, t, sfs, filePath, []byte{byte(i)})
  1412  		syncFS(ctx, t, sfs, "/private/jdoe")
  1413  		numExpected := i + 1
  1414  		if numExpected > 5 {
  1415  			numExpected = 5
  1416  		}
  1417  		checkRevisions(numExpected, i+2, keybase1.RevisionSpanType_DEFAULT)
  1418  		checkRevisions(numExpected, i+2, keybase1.RevisionSpanType_LAST_FIVE)
  1419  	}
  1420  
  1421  	t.Log("Jump the clock forward and force quota reclamation")
  1422  	clock.Add(gcJump)
  1423  	fb, _, err := sfs.getFolderBranchFromPath(ctx, path)
  1424  	require.NoError(t, err)
  1425  	err = libkbfs.ForceQuotaReclamationForTesting(config, fb)
  1426  	require.NoError(t, err)
  1427  	err = config.KBFSOps().SyncFromServer(ctx, fb, nil)
  1428  	require.NoError(t, err)
  1429  	syncFS(ctx, t, sfs, "/private/jdoe")
  1430  
  1431  	t.Log("Make a new revision after QR")
  1432  	clock.Add(1 * time.Minute)
  1433  	writeRemoteFile(ctx, t, sfs, filePath, []byte{6})
  1434  	syncFS(ctx, t, sfs, "/private/jdoe")
  1435  
  1436  	// Now we should be able to see two revisions, since the previous
  1437  	// version was live at the time of QR.
  1438  	newestRev := 9 /* Last file revision was at 7, plus one for GC */
  1439  	checkRevisions(2, newestRev, keybase1.RevisionSpanType_DEFAULT)
  1440  	checkRevisions(2, newestRev, keybase1.RevisionSpanType_LAST_FIVE)
  1441  }
  1442  
  1443  func TestOverallStatusFile(t *testing.T) {
  1444  	ctx := context.Background()
  1445  	sfs := newSimpleFS(
  1446  		env.EmptyAppStateUpdater{}, libkbfs.MakeTestConfigOrBust(t, "jdoe"))
  1447  	defer closeSimpleFS(ctx, t, sfs)
  1448  
  1449  	path := keybase1.NewPathWithKbfsPath("/" + libfs.StatusFileName)
  1450  	buf := readRemoteFile(ctx, t, sfs, path)
  1451  	var status libkbfs.KBFSStatus
  1452  	err := json.Unmarshal(buf, &status)
  1453  	require.NoError(t, err)
  1454  	require.Equal(t, "jdoe", status.CurrentUser)
  1455  }
  1456  
  1457  func TestFavoriteConflicts(t *testing.T) {
  1458  	ctx := context.Background()
  1459  	tempdir, err := os.MkdirTemp(TempDirBase, "journal_for_simplefs_cr")
  1460  	defer os.RemoveAll(tempdir)
  1461  	require.NoError(t, err)
  1462  	sfs := newSimpleFS(
  1463  		env.EmptyAppStateUpdater{}, libkbfs.MakeTestConfigOrBust(t, "jdoe"))
  1464  	defer closeSimpleFS(ctx, t, sfs)
  1465  	config := sfs.config.(*libkbfs.ConfigLocal)
  1466  
  1467  	t.Log("Enable journaling")
  1468  	err = config.EnableDiskLimiter(tempdir)
  1469  	require.NoError(t, err)
  1470  	err = config.EnableJournaling(
  1471  		ctx, tempdir, libkbfs.TLFJournalBackgroundWorkEnabled)
  1472  	require.NoError(t, err)
  1473  	jManager, err := libkbfs.GetJournalManager(config)
  1474  	require.NoError(t, err)
  1475  	err = jManager.EnableAuto(ctx)
  1476  	require.NoError(t, err)
  1477  
  1478  	pathPriv := keybase1.NewPathWithKbfsPath(`/private/jdoe`)
  1479  	pathPub := keybase1.NewPathWithKbfsPath(`/public/jdoe`)
  1480  
  1481  	t.Log("Add one file in each directory")
  1482  	writeRemoteFile(
  1483  		ctx, t, sfs, pathAppend(pathPriv, `test.txt`), []byte(`foo`))
  1484  	syncFS(ctx, t, sfs, "/private/jdoe")
  1485  	writeRemoteFile(
  1486  		ctx, t, sfs, pathAppend(pathPub, `test.txt`), []byte(`foo`))
  1487  	syncFS(ctx, t, sfs, "/public/jdoe")
  1488  
  1489  	t.Log("Make sure we see two favorites with no conflicts")
  1490  	favs, err := sfs.SimpleFSListFavorites(ctx)
  1491  	require.NoError(t, err)
  1492  	require.Len(t, favs.FavoriteFolders, 2)
  1493  	for _, f := range favs.FavoriteFolders {
  1494  		require.Nil(t, f.ConflictState)
  1495  	}
  1496  
  1497  	t.Log("Force a stuck conflict and make sure it's captured correctly")
  1498  	err = sfs.SimpleFSForceStuckConflict(ctx, pathPub)
  1499  	require.NoError(t, err)
  1500  	favs, err = sfs.SimpleFSListFavorites(ctx)
  1501  	require.NoError(t, err)
  1502  	require.Len(t, favs.FavoriteFolders, 2)
  1503  	stuck, notStuck := 0, 0
  1504  	for _, f := range favs.FavoriteFolders {
  1505  		if f.FolderType == keybase1.FolderType_PUBLIC {
  1506  			require.NotNil(t, f.ConflictState)
  1507  			conflictStateType, err := f.ConflictState.ConflictStateType()
  1508  			require.NoError(t, err)
  1509  			require.Equal(t, keybase1.ConflictStateType_NormalView,
  1510  				conflictStateType)
  1511  			require.True(t, f.ConflictState.Normalview().ResolvingConflict)
  1512  			require.True(t, f.ConflictState.Normalview().StuckInConflict)
  1513  			stuck++
  1514  		} else {
  1515  			require.Nil(t, f.ConflictState)
  1516  			notStuck++
  1517  		}
  1518  	}
  1519  	require.Equal(t, 1, stuck)
  1520  	require.Equal(t, 1, notStuck)
  1521  
  1522  	t.Log("Check for stuck badge state")
  1523  	badge, err := sfs.SimpleFSGetFilesTabBadge(ctx)
  1524  	require.NoError(t, err)
  1525  	require.Equal(t, keybase1.FilesTabBadge_UPLOADING_STUCK, badge)
  1526  
  1527  	t.Log("Resolve the conflict")
  1528  	err = sfs.SimpleFSClearConflictState(ctx, pathPub)
  1529  	require.NoError(t, err)
  1530  	favs, err = sfs.SimpleFSListFavorites(ctx)
  1531  	require.NoError(t, err)
  1532  	require.Len(t, favs.FavoriteFolders, 3)
  1533  	var pathConflict keybase1.Path
  1534  	var pathLocalView keybase1.Path
  1535  	for _, f := range favs.FavoriteFolders {
  1536  		switch {
  1537  		case tlf.ContainsLocalConflictExtensionPrefix(f.Name):
  1538  			require.NotNil(t, f.ConflictState)
  1539  			ct, err := f.ConflictState.ConflictStateType()
  1540  			require.NoError(t, err)
  1541  			require.Equal(
  1542  				t, keybase1.ConflictStateType_ManualResolvingLocalView, ct)
  1543  			mrlv := f.ConflictState.Manualresolvinglocalview()
  1544  			require.Equal(t, pathPub.String(), mrlv.NormalView.String())
  1545  			pathConflict = keybase1.NewPathWithKbfsPath("/public/" + f.Name)
  1546  		case f.Name == "jdoe" && f.FolderType == keybase1.FolderType_PUBLIC:
  1547  			require.NotNil(t, f.ConflictState)
  1548  			ct, err := f.ConflictState.ConflictStateType()
  1549  			require.NoError(t, err)
  1550  			require.Equal(
  1551  				t, keybase1.ConflictStateType_NormalView, ct)
  1552  			sv := f.ConflictState.Normalview()
  1553  			require.False(t, sv.ResolvingConflict)
  1554  			require.False(t, sv.StuckInConflict)
  1555  			require.Len(t, sv.LocalViews, 1)
  1556  			pathLocalView = sv.LocalViews[0]
  1557  		default:
  1558  			require.Nil(t, f.ConflictState)
  1559  		}
  1560  	}
  1561  	require.NotEqual(t, "", pathConflict.String())
  1562  	require.Equal(t, pathLocalView.String(), pathConflict.String())
  1563  
  1564  	t.Log("Make sure we see all the conflict files in the local branch")
  1565  	opid, err := sfs.SimpleFSMakeOpid(ctx)
  1566  	require.NoError(t, err)
  1567  	err = sfs.SimpleFSList(ctx, keybase1.SimpleFSListArg{
  1568  		OpID: opid,
  1569  		Path: pathConflict,
  1570  	})
  1571  	require.NoError(t, err)
  1572  	err = sfs.SimpleFSWait(ctx, opid)
  1573  	require.NoError(t, err)
  1574  	listResult, err := sfs.SimpleFSReadList(ctx, opid)
  1575  	require.NoError(t, err)
  1576  	require.Len(t, listResult.Entries, 12)
  1577  
  1578  	t.Log("Finish resolving the conflict")
  1579  	err = sfs.SimpleFSFinishResolvingConflict(ctx, pathLocalView)
  1580  	require.NoError(t, err)
  1581  	favs, err = sfs.SimpleFSListFavorites(ctx)
  1582  	require.NoError(t, err)
  1583  	require.Len(t, favs.FavoriteFolders, 2)
  1584  	for _, f := range favs.FavoriteFolders {
  1585  		require.Nil(t, f.ConflictState)
  1586  	}
  1587  
  1588  	t.Log("Try stat'ing the old local view, should get an error")
  1589  	_, err = sfs.SimpleFSStat(ctx, keybase1.SimpleFSStatArg{
  1590  		Path: pathLocalView,
  1591  	})
  1592  	require.Error(t, err)
  1593  }
  1594  
  1595  func TestSyncConfigFavorites(t *testing.T) {
  1596  	ctx := context.Background()
  1597  	config := libkbfs.MakeTestConfigOrBust(t, "jdoe")
  1598  	tempdir, err := os.MkdirTemp(TempDirBase, "journal_for_simplefs_favs")
  1599  	require.NoError(t, err)
  1600  	defer os.RemoveAll(tempdir)
  1601  	err = config.EnableDiskLimiter(tempdir)
  1602  	require.NoError(t, err)
  1603  	config.SetDiskCacheMode(libkbfs.DiskCacheModeLocal)
  1604  	err = config.MakeDiskBlockCacheIfNotExists()
  1605  	require.NoError(t, err)
  1606  	sfs := newSimpleFS(env.EmptyAppStateUpdater{}, config)
  1607  	defer closeSimpleFS(ctx, t, sfs)
  1608  
  1609  	pathPriv := keybase1.NewPathWithKbfsPath(`/private/jdoe`)
  1610  	pathPub := keybase1.NewPathWithKbfsPath(`/public/jdoe`)
  1611  
  1612  	t.Log("Add one file in each directory")
  1613  	writeRemoteFile(
  1614  		ctx, t, sfs, pathAppend(pathPriv, `test.txt`), []byte(`foo`))
  1615  	syncFS(ctx, t, sfs, "/private/jdoe")
  1616  	writeRemoteFile(
  1617  		ctx, t, sfs, pathAppend(pathPub, `test.txt`), []byte(`foo`))
  1618  	syncFS(ctx, t, sfs, "/public/jdoe")
  1619  
  1620  	t.Log("Make sure none are marked for syncing")
  1621  	favs, err := sfs.SimpleFSListFavorites(ctx)
  1622  	require.NoError(t, err)
  1623  	require.Len(t, favs.FavoriteFolders, 2)
  1624  	for _, f := range favs.FavoriteFolders {
  1625  		require.Equal(t, keybase1.FolderSyncMode_DISABLED, f.SyncConfig.Mode)
  1626  	}
  1627  
  1628  	t.Log("Start syncing the public folder")
  1629  	setArg := keybase1.SimpleFSSetFolderSyncConfigArg{
  1630  		Path: pathPub,
  1631  		Config: keybase1.FolderSyncConfig{
  1632  			Mode: keybase1.FolderSyncMode_ENABLED,
  1633  		},
  1634  	}
  1635  	err = sfs.SimpleFSSetFolderSyncConfig(ctx, setArg)
  1636  	require.NoError(t, err)
  1637  	favs, err = sfs.SimpleFSListFavorites(ctx)
  1638  	require.NoError(t, err)
  1639  	require.Len(t, favs.FavoriteFolders, 2)
  1640  	numSyncing := 0
  1641  	for _, f := range favs.FavoriteFolders {
  1642  		if f.FolderType == keybase1.FolderType_PUBLIC {
  1643  			numSyncing++
  1644  			require.Equal(
  1645  				t, keybase1.FolderSyncMode_ENABLED, f.SyncConfig.Mode)
  1646  		} else {
  1647  			require.Equal(
  1648  				t, keybase1.FolderSyncMode_DISABLED, f.SyncConfig.Mode)
  1649  		}
  1650  	}
  1651  	require.Equal(t, 1, numSyncing)
  1652  }
  1653  
  1654  func TestRemoveFavorite(t *testing.T) {
  1655  	ctx := context.Background()
  1656  	config := libkbfs.MakeTestConfigOrBust(t, "jdoe", "alice")
  1657  	sfs := newSimpleFS(env.EmptyAppStateUpdater{}, config)
  1658  	defer closeSimpleFS(ctx, t, sfs)
  1659  
  1660  	t.Log("Write a file in the shared directory")
  1661  	pathPriv := keybase1.NewPathWithKbfsPath(`/private/alice,jdoe`)
  1662  	writeRemoteFile(
  1663  		ctx, t, sfs, pathAppend(pathPriv, `test.txt`), []byte(`foo`))
  1664  	syncFS(ctx, t, sfs, "/private/alice,jdoe")
  1665  
  1666  	t.Log("Make sure it's in the favorites list")
  1667  	favs, err := sfs.SimpleFSListFavorites(ctx)
  1668  	require.NoError(t, err)
  1669  	require.Len(t, favs.FavoriteFolders, 3)
  1670  	find := func() bool {
  1671  		for _, f := range favs.FavoriteFolders {
  1672  			t.Logf("NAME=%s", f.Name)
  1673  			if f.FolderType == keybase1.FolderType_PRIVATE &&
  1674  				f.Name == "alice,jdoe" {
  1675  				return true
  1676  			}
  1677  		}
  1678  		return false
  1679  	}
  1680  	found := find()
  1681  	require.True(t, found)
  1682  
  1683  	t.Log("Remove the favorite")
  1684  	opid, err := sfs.SimpleFSMakeOpid(ctx)
  1685  	require.NoError(t, err)
  1686  	err = sfs.SimpleFSRemove(ctx, keybase1.SimpleFSRemoveArg{
  1687  		OpID: opid,
  1688  		Path: pathPriv,
  1689  	})
  1690  	require.NoError(t, err)
  1691  	checkPendingOp(
  1692  		ctx, t, sfs, opid, keybase1.AsyncOps_REMOVE, pathPriv, keybase1.Path{},
  1693  		true)
  1694  	err = sfs.SimpleFSWait(ctx, opid)
  1695  	require.NoError(t, err)
  1696  
  1697  	t.Log("Check that it's gone")
  1698  	favs, err = sfs.SimpleFSListFavorites(ctx)
  1699  	require.NoError(t, err)
  1700  	require.Len(t, favs.FavoriteFolders, 2)
  1701  	found = find()
  1702  	require.False(t, found)
  1703  }
  1704  
  1705  func TestBadgeState(t *testing.T) {
  1706  	ctx := context.Background()
  1707  	tempdir, err := os.MkdirTemp(TempDirBase, "journal_for_simplefs_badge")
  1708  	defer os.RemoveAll(tempdir)
  1709  	require.NoError(t, err)
  1710  	sfs := newSimpleFS(
  1711  		env.EmptyAppStateUpdater{}, libkbfs.MakeTestConfigOrBust(t, "jdoe"))
  1712  	defer closeSimpleFS(ctx, t, sfs)
  1713  	config := sfs.config.(*libkbfs.ConfigLocal)
  1714  
  1715  	t.Log("Enable journaling")
  1716  	err = config.EnableDiskLimiter(tempdir)
  1717  	require.NoError(t, err)
  1718  	err = config.EnableJournaling(
  1719  		ctx, tempdir, libkbfs.TLFJournalBackgroundWorkEnabled)
  1720  	require.NoError(t, err)
  1721  	jManager, err := libkbfs.GetJournalManager(config)
  1722  	require.NoError(t, err)
  1723  	err = jManager.EnableAuto(ctx)
  1724  	require.NoError(t, err)
  1725  
  1726  	t.Log("No badge yet")
  1727  	badge, err := sfs.SimpleFSGetFilesTabBadge(ctx)
  1728  	require.NoError(t, err)
  1729  	require.Equal(t, keybase1.FilesTabBadge_NONE, badge)
  1730  
  1731  	pathPriv := keybase1.NewPathWithKbfsPath(`/private/jdoe`)
  1732  	pathPub := keybase1.NewPathWithKbfsPath(`/public/jdoe`)
  1733  
  1734  	t.Log("Add one private file.")
  1735  	writeRemoteFile(
  1736  		ctx, t, sfs, pathAppend(pathPriv, `test.txt`), []byte(`foo`))
  1737  	syncFS(ctx, t, sfs, "/private/jdoe")
  1738  	_, tlfIDs := jManager.Status(ctx)
  1739  	require.Len(t, tlfIDs, 1)
  1740  	tlfID := tlfIDs[0]
  1741  	err = jManager.Wait(ctx, tlfID)
  1742  	require.NoError(t, err)
  1743  
  1744  	t.Log("Still no badge yet")
  1745  	badge, err = sfs.SimpleFSGetFilesTabBadge(ctx)
  1746  	require.NoError(t, err)
  1747  	require.Equal(t, keybase1.FilesTabBadge_NONE, badge)
  1748  
  1749  	t.Log("Pause the journal and add another file")
  1750  	jManager.PauseBackgroundWork(ctx, tlfID)
  1751  	writeRemoteFile(
  1752  		ctx, t, sfs, pathAppend(pathPriv, `test2.txt`), []byte(`foo2`))
  1753  	syncFS(ctx, t, sfs, "/private/jdoe")
  1754  	// Wait shouldn't do anything unless there's a bug with pausing,
  1755  	// so do it just in case.
  1756  	err = jManager.Wait(ctx, tlfID)
  1757  	require.NoError(t, err)
  1758  	badge, err = sfs.SimpleFSGetFilesTabBadge(ctx)
  1759  	require.NoError(t, err)
  1760  	require.Equal(t, keybase1.FilesTabBadge_UPLOADING, badge)
  1761  
  1762  	t.Log("Get a different TLF stuck, badge state should update")
  1763  	writeRemoteFile(
  1764  		ctx, t, sfs, pathAppend(pathPub, `test3.txt`), []byte(`foo3`))
  1765  	syncFS(ctx, t, sfs, "/public/jdoe")
  1766  	err = sfs.SimpleFSForceStuckConflict(ctx, pathPub)
  1767  	require.NoError(t, err)
  1768  	badge, err = sfs.SimpleFSGetFilesTabBadge(ctx)
  1769  	require.NoError(t, err)
  1770  	require.Equal(t, keybase1.FilesTabBadge_UPLOADING_STUCK, badge)
  1771  
  1772  	jManager.ResumeBackgroundWork(ctx, tlfID)
  1773  }
  1774  
  1775  func TestProfiles(t *testing.T) {
  1776  	ctx := context.Background()
  1777  	sfs := newSimpleFS(
  1778  		env.EmptyAppStateUpdater{}, libkbfs.MakeTestConfigOrBust(t, "jdoe"))
  1779  	defer closeSimpleFS(ctx, t, sfs)
  1780  	libfs.AddRootWrapper(sfs.config)
  1781  
  1782  	t.Log("Root-level profiles")
  1783  	path := keybase1.NewPathWithKbfsPath("/" + libfs.ProfileListDirName)
  1784  	testListWithFilterAndUsername(
  1785  		ctx, t, sfs, path, keybase1.ListFilter_NO_FILTER, "",
  1786  		libfs.ListProfileNames()...)
  1787  	buf := readRemoteFile(ctx, t, sfs, pathAppend(path, "goroutine"))
  1788  	require.NotEmpty(t, buf)
  1789  
  1790  	t.Log("In-TLF profiles")
  1791  	// Create TLF first.
  1792  	pathPriv := keybase1.NewPathWithKbfsPath(`/private/jdoe`)
  1793  	writeRemoteFile(
  1794  		ctx, t, sfs, pathAppend(pathPriv, `test.txt`), []byte(`foo`))
  1795  	syncFS(ctx, t, sfs, "/private/jdoe")
  1796  	path = pathAppend(pathPriv, libfs.ProfileListDirName)
  1797  	testListWithFilterAndUsername(
  1798  		ctx, t, sfs, path, keybase1.ListFilter_NO_FILTER, "",
  1799  		libfs.ListProfileNames()...)
  1800  	buf = readRemoteFile(ctx, t, sfs, pathAppend(path, "goroutine"))
  1801  	require.NotEmpty(t, buf)
  1802  }
  1803  
  1804  func TestArchiveSymlink(t *testing.T) {
  1805  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
  1806  	defer cancel()
  1807  
  1808  	// make a temp local dest directory + files we will clean up later
  1809  	tempdir, err := os.MkdirTemp(TempDirBase, "simpleFStest")
  1810  	defer os.RemoveAll(tempdir)
  1811  	require.NoError(t, err)
  1812  	t.Logf("temp dir:  %s", tempdir)
  1813  
  1814  	setCacheDirForTest(tempdir)
  1815  	defer unsetCacheDirForTest()
  1816  
  1817  	sfs := newSimpleFS(env.EmptyAppStateUpdater{}, libkbfs.MakeTestConfigOrBust(t, "jdoe"))
  1818  	defer closeSimpleFS(ctx, t, sfs)
  1819  
  1820  	path1 := keybase1.NewPathWithKbfsPath(`/private/jdoe`)
  1821  	writeRemoteFile(ctx, t, sfs, pathAppend(path1, "test1.txt"), []byte("foo"))
  1822  
  1823  	// make some symlinks
  1824  	{
  1825  		{
  1826  			linkName := "link1"
  1827  			link := pathAppend(path1, linkName)
  1828  			err := sfs.SimpleFSSymlink(ctx, keybase1.SimpleFSSymlinkArg{
  1829  				Target: "test1.txt",
  1830  				Link:   link,
  1831  			})
  1832  			require.NoError(t, err)
  1833  		}
  1834  		{
  1835  			linkName := "link-escaping"
  1836  			link := pathAppend(path1, linkName)
  1837  			err := sfs.SimpleFSSymlink(ctx, keybase1.SimpleFSSymlinkArg{
  1838  				Target: "../test1.txt",
  1839  				Link:   link,
  1840  			})
  1841  			require.NoError(t, err)
  1842  		}
  1843  	}
  1844  	syncFS(ctx, t, sfs, "/private/jdoe")
  1845  
  1846  	desc, err := sfs.SimpleFSArchiveStart(ctx, keybase1.SimpleFSArchiveStartArg{
  1847  		ArchiveJobStartPath: keybase1.NewArchiveJobStartPathWithKbfs(path1.Kbfs()),
  1848  		OutputPath:          filepath.Join(tempdir, "archive"),
  1849  	})
  1850  	require.NoError(t, err)
  1851  	require.Equal(t, filepath.Join(tempdir, "archive.zip"), desc.ZipFilePath)
  1852  
  1853  	ticker := time.NewTicker(time.Millisecond * 100)
  1854  loopWait:
  1855  	for {
  1856  		select {
  1857  		case <-ctx.Done():
  1858  			require.NoError(t, ctx.Err())
  1859  		case <-ticker.C:
  1860  		}
  1861  		status, err := sfs.SimpleFSGetArchiveStatus(ctx)
  1862  		require.NoError(t, err)
  1863  		require.Equal(t, 1, len(status.Jobs))
  1864  		job := status.Jobs[0]
  1865  		t.Logf("got job status %#+v", job)
  1866  		require.Nil(t, job.Error)
  1867  		if job.Phase == keybase1.SimpleFSArchiveJobPhase_Done {
  1868  			break loopWait
  1869  		}
  1870  	}
  1871  
  1872  	reader, err := zip.OpenReader(filepath.Join(tempdir, "archive.zip"))
  1873  	defer func() { _ = reader.Close() }()
  1874  	require.NoError(t, err)
  1875  
  1876  	files := map[string]bool{"receipt.json": true, "jdoe/test1.txt": true, "jdoe/link1": true, "jdoe": true, ".": true}
  1877  	require.Equal(t, len(files), len(reader.File))
  1878  	for _, f := range reader.File {
  1879  		delete(files, f.Name)
  1880  	}
  1881  	require.Equal(t, 0, len(files))
  1882  }