github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libdokan/tlf.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 libdokan
     6  
     7  import (
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/keybase/client/go/kbfs/data"
    12  	"github.com/keybase/client/go/kbfs/dokan"
    13  	"github.com/keybase/client/go/kbfs/libfs"
    14  	"github.com/keybase/client/go/kbfs/libkbfs"
    15  	"github.com/keybase/client/go/kbfs/tlf"
    16  	"github.com/keybase/client/go/kbfs/tlfhandle"
    17  	"github.com/keybase/client/go/libkb"
    18  	"golang.org/x/net/context"
    19  )
    20  
    21  // TLF represents the root directory of a TLF. It wraps a lazy-loaded
    22  // Dir.
    23  type TLF struct {
    24  	refcount refcount
    25  
    26  	folder *Folder
    27  
    28  	dirLock sync.RWMutex
    29  	dir     *Dir
    30  
    31  	emptyFile
    32  }
    33  
    34  func newTLF(fl *FolderList, h *tlfhandle.Handle,
    35  	name tlf.PreferredName) *TLF {
    36  	folder := newFolder(fl, h, name)
    37  	tlf := &TLF{
    38  		folder: folder,
    39  	}
    40  	tlf.refcount.Increase()
    41  	return tlf
    42  }
    43  
    44  func (tlf *TLF) getStoredDir() *Dir {
    45  	tlf.dirLock.RLock()
    46  	defer tlf.dirLock.RUnlock()
    47  	return tlf.dir
    48  }
    49  
    50  func (tlf *TLF) loadDirHelper(ctx context.Context, info string,
    51  	mode libkbfs.ErrorModeType, branch data.BranchName, filterErr bool) (
    52  	dir *Dir, exitEarly bool, err error) {
    53  	dir = tlf.getStoredDir()
    54  	if dir != nil {
    55  		return dir, false, nil
    56  	}
    57  
    58  	tlf.dirLock.Lock()
    59  	defer tlf.dirLock.Unlock()
    60  	// Need to check for nilness again to avoid racing with other
    61  	// calls to loadDir().
    62  	if tlf.dir != nil {
    63  		return tlf.dir, false, nil
    64  	}
    65  
    66  	name := tlf.folder.name()
    67  
    68  	tlf.folder.fs.log.CDebugf(ctx, "Loading root directory for folder %s "+
    69  		"(type: %s, filter error: %t) for %s",
    70  		name, tlf.folder.list.tlfType, filterErr, info)
    71  	defer func() {
    72  		if filterErr {
    73  			exitEarly, err = libfs.FilterTLFEarlyExitError(ctx, err, tlf.folder.fs.log, name)
    74  		}
    75  
    76  		tlf.folder.reportErr(ctx, mode, err)
    77  	}()
    78  
    79  	handle, err := tlf.folder.resolve(ctx)
    80  	if err != nil {
    81  		return nil, false, err
    82  	}
    83  
    84  	if branch == data.MasterBranch {
    85  		conflictBranch, isLocalConflictBranch :=
    86  			data.MakeConflictBranchName(handle)
    87  		if isLocalConflictBranch {
    88  			branch = conflictBranch
    89  		}
    90  	}
    91  
    92  	var rootNode libkbfs.Node
    93  	if filterErr {
    94  		rootNode, _, err = tlf.folder.fs.config.KBFSOps().GetRootNode(
    95  			ctx, handle, branch)
    96  		if err != nil {
    97  			return nil, false, err
    98  		}
    99  		// If not fake an empty directory.
   100  		if rootNode == nil {
   101  			return nil, false, libfs.TlfDoesNotExist{}
   102  		}
   103  	} else {
   104  		rootNode, _, err = tlf.folder.fs.config.KBFSOps().GetOrCreateRootNode(
   105  			ctx, handle, branch)
   106  		if err != nil {
   107  			return nil, false, err
   108  		}
   109  	}
   110  
   111  	err = tlf.folder.setFolderBranch(rootNode.GetFolderBranch())
   112  	if err != nil {
   113  		return nil, false, err
   114  	}
   115  
   116  	tlf.folder.nodes[rootNode.GetID()] = tlf
   117  	tlf.dir = newDir(tlf.folder, rootNode, string(name), nil)
   118  	// TLFs should be cached.
   119  	tlf.dir.refcount.Increase()
   120  	tlf.folder.lockedAddNode(rootNode, tlf.dir)
   121  
   122  	return tlf.dir, false, nil
   123  }
   124  
   125  func (tlf *TLF) loadDir(ctx context.Context, info string) (*Dir, error) {
   126  	dir, _, err := tlf.loadDirHelper(
   127  		ctx, info, libkbfs.WriteMode, data.MasterBranch, false)
   128  	return dir, err
   129  }
   130  
   131  // loadDirAllowNonexistent loads a TLF if it's not already loaded.  If
   132  // the TLF doesn't yet exist, it still returns a nil error and
   133  // indicates that the calling function should pretend it's an empty
   134  // folder.
   135  func (tlf *TLF) loadDirAllowNonexistent(ctx context.Context, info string) (
   136  	*Dir, bool, error) {
   137  	return tlf.loadDirHelper(
   138  		ctx, info, libkbfs.ReadMode, data.MasterBranch, true)
   139  }
   140  
   141  func (tlf *TLF) loadArchivedDir(
   142  	ctx context.Context, info string, branch data.BranchName) (
   143  	*Dir, bool, error) {
   144  	// Always filter errors for archive TLF directories, so that we
   145  	// don't try to initialize them.
   146  	return tlf.loadDirHelper(ctx, info, libkbfs.ReadMode, branch, true)
   147  }
   148  
   149  // SetFileTime sets mtime for FSOs (File and Dir).
   150  func (tlf *TLF) SetFileTime(ctx context.Context, fi *dokan.FileInfo, creation time.Time, lastAccess time.Time, lastWrite time.Time) (err error) {
   151  	tlf.folder.fs.logEnter(ctx, "TLF SetFileTime")
   152  
   153  	dir, err := tlf.loadDir(ctx, "TLF SetFileTime")
   154  	if err != nil {
   155  		return err
   156  	}
   157  	return dir.SetFileTime(ctx, fi, creation, lastAccess, lastWrite)
   158  }
   159  
   160  // SetFileAttributes for Dokan.
   161  func (tlf *TLF) SetFileAttributes(ctx context.Context, fi *dokan.FileInfo, fileAttributes dokan.FileAttribute) error {
   162  	tlf.folder.fs.logEnter(ctx, "TLF SetFileAttributes")
   163  	dir, err := tlf.loadDir(ctx, "TLF SetFileAttributes")
   164  	if err != nil {
   165  		return err
   166  	}
   167  	return dir.SetFileAttributes(ctx, fi, fileAttributes)
   168  }
   169  
   170  // GetFileInformation for dokan.
   171  func (tlf *TLF) GetFileInformation(ctx context.Context, fi *dokan.FileInfo) (st *dokan.Stat, err error) {
   172  	dir := tlf.getStoredDir()
   173  	if dir == nil {
   174  		return defaultDirectoryInformation()
   175  	}
   176  
   177  	return dir.GetFileInformation(ctx, fi)
   178  }
   179  
   180  // open tries to open a file.
   181  func (tlf *TLF) open(ctx context.Context, oc *openContext, path []string) (
   182  	dokan.File, dokan.CreateStatus, error) {
   183  	if len(path) == 0 {
   184  		if err := oc.ReturningDirAllowed(); err != nil {
   185  			return nil, 0, err
   186  		}
   187  		tlf.refcount.Increase()
   188  		return tlf, dokan.ExistingDir, nil
   189  	}
   190  
   191  	mode := libkbfs.ReadMode
   192  	if oc.isCreation() {
   193  		mode = libkbfs.WriteMode
   194  	}
   195  	// If it is a creation then we need the dir for real.
   196  	dir, exitEarly, err :=
   197  		tlf.loadDirHelper(
   198  			ctx, "open", mode, data.MasterBranch, !oc.isCreation())
   199  	if err != nil {
   200  		return nil, 0, err
   201  	}
   202  	if exitEarly {
   203  		specialNode := handleTLFSpecialFile(lastStr(path), tlf.folder)
   204  		if specialNode != nil {
   205  			return specialNode, dokan.ExistingFile, nil
   206  		}
   207  
   208  		return nil, 0, dokan.ErrObjectNameNotFound
   209  	}
   210  
   211  	branch, isArchivedBranch := libfs.BranchNameFromArchiveRefDir(path[0])
   212  	if isArchivedBranch {
   213  		archivedTLF := newTLF(
   214  			tlf.folder.list, tlf.folder.h, tlf.folder.hPreferredName)
   215  		_, _, err := archivedTLF.loadArchivedDir(ctx, "open", branch)
   216  		if err != nil {
   217  			return nil, 0, err
   218  		}
   219  		return archivedTLF.open(ctx, oc, path[1:])
   220  	}
   221  
   222  	linkTarget, isArchivedTimeLink, err := libfs.LinkTargetFromTimeString(
   223  		ctx, tlf.folder.fs.config, tlf.folder.h, path[0])
   224  	if err != nil {
   225  		return nil, 0, err
   226  	}
   227  	if isArchivedTimeLink {
   228  		if len(path) == 1 && oc.isOpenReparsePoint() {
   229  			// TODO handle dir/non-dir here, semantics?
   230  			return &Alias{canon: linkTarget}, dokan.ExistingDir, nil
   231  		}
   232  		path[0] = linkTarget
   233  		return tlf.open(ctx, oc, path)
   234  	}
   235  
   236  	_, isRelTimeLink, err := libfs.FileDataFromRelativeTimeString(
   237  		ctx, tlf.folder.fs.config, tlf.folder.h, path[0])
   238  	if err != nil {
   239  		return nil, 0, err
   240  	}
   241  	if isRelTimeLink {
   242  		return NewArchiveRelTimeFile(tlf.folder.fs, tlf.folder.h, path[0]),
   243  			dokan.ExistingFile, nil
   244  	}
   245  
   246  	return dir.open(ctx, oc, path)
   247  }
   248  
   249  // FindFiles does readdir for dokan.
   250  func (tlf *TLF) FindFiles(ctx context.Context, fi *dokan.FileInfo, pattern string, callback func(*dokan.NamedStat) error) (err error) {
   251  	tlf.folder.fs.logEnter(ctx, "TLF FindFiles")
   252  	dir, exitEarly, err := tlf.loadDirAllowNonexistent(ctx, "FindFiles")
   253  	if err != nil {
   254  		return errToDokan(err)
   255  	}
   256  	if exitEarly {
   257  		return dokan.ErrObjectNameNotFound
   258  	}
   259  	return dir.FindFiles(ctx, fi, pattern, callback)
   260  }
   261  
   262  // CanDeleteDirectory - return just nil because tlfs
   263  // can always be removed from favorites.
   264  func (tlf *TLF) CanDeleteDirectory(ctx context.Context, fi *dokan.FileInfo) (err error) {
   265  	return nil
   266  }
   267  
   268  // Cleanup - forget references, perform deletions etc.
   269  func (tlf *TLF) Cleanup(ctx context.Context, fi *dokan.FileInfo) {
   270  	var err error
   271  	if fi != nil && fi.IsDeleteOnClose() {
   272  		tlf.folder.handleMu.Lock()
   273  		fav := tlf.folder.h.ToFavorite()
   274  		tlf.folder.handleMu.Unlock()
   275  		tlf.folder.fs.vlog.CLogf(
   276  			ctx, libkb.VLog1, "TLF Removing favorite %q", fav.Name)
   277  		defer func() {
   278  			tlf.folder.reportErr(ctx, libkbfs.WriteMode, err)
   279  		}()
   280  		err = tlf.folder.fs.config.KBFSOps().DeleteFavorite(ctx, fav)
   281  	}
   282  
   283  	if tlf.refcount.Decrease() {
   284  		dir := tlf.getStoredDir()
   285  		if dir == nil {
   286  			return
   287  		}
   288  		dir.Cleanup(ctx, fi)
   289  	}
   290  }