github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libdokan/folderlist.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/dokan"
    12  	"github.com/keybase/client/go/kbfs/favorites"
    13  	"github.com/keybase/client/go/kbfs/idutil"
    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  	kbname "github.com/keybase/client/go/kbun"
    18  	"github.com/keybase/client/go/libkb"
    19  	"github.com/pkg/errors"
    20  	"golang.org/x/net/context"
    21  )
    22  
    23  type fileOpener interface {
    24  	open(ctx context.Context, oc *openContext, path []string) (
    25  		f dokan.File, cst dokan.CreateStatus, err error)
    26  	dokan.File
    27  }
    28  
    29  // FolderList is a node that can list all of the logged-in user's
    30  // favorite top-level folders, on either a public or private basis.
    31  type FolderList struct {
    32  	emptyFile
    33  	fs      *FS
    34  	tlfType tlf.Type
    35  
    36  	mu         sync.Mutex
    37  	folders    map[string]fileOpener
    38  	aliasCache map[string]string
    39  }
    40  
    41  // GetFileInformation for dokan.
    42  func (*FolderList) GetFileInformation(context.Context, *dokan.FileInfo) (*dokan.Stat, error) {
    43  	return defaultDirectoryInformation()
    44  }
    45  
    46  func (fl *FolderList) reportErr(ctx context.Context,
    47  	mode libkbfs.ErrorModeType, tlfName tlf.CanonicalName, err error, cancelFn func()) {
    48  	if cancelFn != nil {
    49  		defer cancelFn()
    50  	}
    51  	if err == nil {
    52  		fl.fs.vlog.CLogf(ctx, libkb.VLog1, "Request complete")
    53  		return
    54  	}
    55  
    56  	fl.fs.config.Reporter().ReportErr(ctx, tlfName, fl.tlfType, mode, err)
    57  	// We just log the error as debug, rather than error, because it
    58  	// might just indicate an expected error such as an ENOENT.
    59  	//
    60  	// TODO: Classify errors and escalate the logging level of the
    61  	// important ones.
    62  	fl.fs.log.CDebugf(ctx, err.Error())
    63  
    64  }
    65  
    66  // open tries to open the correct thing. Following aliases and deferring to
    67  // Dir.open as necessary.
    68  func (fl *FolderList) open(ctx context.Context, oc *openContext, path []string) (f dokan.File, cst dokan.CreateStatus, err error) {
    69  	fl.fs.vlog.CLogf(ctx, libkb.VLog1, "FL Lookup %#v type=%s upper=%v",
    70  		path, fl.tlfType, oc.isUppercasePath)
    71  	if len(path) == 0 {
    72  		return oc.returnDirNoCleanup(fl)
    73  	}
    74  
    75  	defer func() {
    76  		fl.reportErr(ctx, libkbfs.ReadMode, tlf.CanonicalName(path[0]), err, nil)
    77  	}()
    78  
    79  	// TODO: A simple lower-casing is not good enough - see CORE-2967
    80  	// However libkbfs does this too in tlf_handle.go...
    81  	// The case of possible redirections will be ok, so we only need
    82  	// to do this initially.
    83  	if c := lowerTranslateCandidate(oc, path[0]); c != "" {
    84  		path[0] = c
    85  	}
    86  
    87  	for oc.reduceRedirectionsLeft() {
    88  		name := path[0]
    89  
    90  		if name == "desktop.ini" || name == "DESKTOP.INI" {
    91  			fl.fs.vlog.CLogf(ctx, libkb.VLog1, "FL Lookup ignoring desktop.ini")
    92  			return nil, 0, dokan.ErrObjectNameNotFound
    93  		}
    94  
    95  		var aliasTarget string
    96  		fl.mu.Lock()
    97  		child, ok := fl.folders[name]
    98  		if !ok {
    99  			aliasTarget = fl.aliasCache[name]
   100  		}
   101  		fl.mu.Unlock()
   102  
   103  		if ok {
   104  			fl.fs.vlog.CLogf(
   105  				ctx, libkb.VLog1, "FL Lookup recursing to child %q", name)
   106  			return child.open(ctx, oc, path[1:])
   107  		}
   108  
   109  		if len(path) == 1 && isNewFolderName(name) {
   110  			if !oc.isCreateDirectory() {
   111  				return nil, 0, dokan.ErrObjectNameNotFound
   112  			}
   113  			fl.fs.vlog.CLogf(
   114  				ctx, libkb.VLog1, "FL Lookup creating EmptyFolder for Explorer")
   115  			e := &EmptyFolder{}
   116  			fl.lockedAddChild(name, e)
   117  			return e, dokan.NewDir, nil
   118  		}
   119  
   120  		if aliasTarget != "" {
   121  			fl.fs.vlog.CLogf(
   122  				ctx, libkb.VLog1, "FL Lookup aliasCache hit: %q -> %q",
   123  				name, aliasTarget)
   124  			if len(path) == 1 && oc.isOpenReparsePoint() {
   125  				// TODO handle dir/non-dir here, semantics?
   126  				return &Alias{canon: aliasTarget}, dokan.ExistingDir, nil
   127  			}
   128  			path[0] = aliasTarget
   129  			continue
   130  		}
   131  
   132  		h, err := tlfhandle.ParseHandlePreferredQuick(
   133  			ctx, fl.fs.config.KBPKI(), fl.fs.config, name, fl.tlfType)
   134  		fl.fs.vlog.CLogf(
   135  			ctx, libkb.VLog1, "FL Lookup continuing -> %v,%v", h, err)
   136  		switch e := errors.Cause(err).(type) {
   137  		case nil:
   138  			// no error
   139  
   140  		case idutil.TlfNameNotCanonical:
   141  			// Only permit Aliases to targets that contain no errors.
   142  			aliasTarget = e.NameToTry
   143  			fl.fs.vlog.CLogf(
   144  				ctx, libkb.VLog1, "FL Lookup set alias: %q -> %q",
   145  				name, aliasTarget)
   146  			if !fl.isValidAliasTarget(ctx, aliasTarget) {
   147  				fl.fs.vlog.CLogf(
   148  					ctx, libkb.VLog1,
   149  					"FL Refusing alias to non-valid target %q", aliasTarget)
   150  				return nil, 0, dokan.ErrObjectNameNotFound
   151  			}
   152  			fl.mu.Lock()
   153  			fl.aliasCache[name] = aliasTarget
   154  			fl.mu.Unlock()
   155  
   156  			if len(path) == 1 && oc.isOpenReparsePoint() {
   157  				// TODO handle dir/non-dir here, semantics?
   158  				fl.fs.vlog.CLogf(
   159  					ctx, libkb.VLog1, "FL Lookup ret alias, oc: %#v",
   160  					oc.CreateData)
   161  				return &Alias{canon: aliasTarget}, dokan.ExistingDir, nil
   162  			}
   163  			path[0] = aliasTarget
   164  			continue
   165  
   166  		case idutil.NoSuchNameError, idutil.BadTLFNameError,
   167  			tlf.NoSuchUserError, idutil.NoSuchUserError:
   168  			return nil, 0, dokan.ErrObjectNameNotFound
   169  
   170  		default:
   171  			// Some other error.
   172  			return nil, 0, err
   173  		}
   174  
   175  		fl.fs.vlog.CLogf(ctx, libkb.VLog1, "FL Lookup adding new child")
   176  		session, err := idutil.GetCurrentSessionIfPossible(ctx, fl.fs.config.KBPKI(), h.Type() == tlf.Public)
   177  		if err != nil {
   178  			return nil, 0, err
   179  		}
   180  		child = newTLF(fl, h, h.GetPreferredFormat(session.Name))
   181  		fl.lockedAddChild(name, child)
   182  		return child.open(ctx, oc, path[1:])
   183  	}
   184  	return nil, 0, dokan.ErrObjectNameNotFound
   185  }
   186  
   187  func (fl *FolderList) forgetFolder(folderName string) {
   188  	fl.mu.Lock()
   189  	defer fl.mu.Unlock()
   190  	delete(fl.folders, folderName)
   191  }
   192  
   193  // FindFiles for dokan.
   194  func (fl *FolderList) FindFiles(ctx context.Context, fi *dokan.FileInfo, ignored string, callback func(*dokan.NamedStat) error) (err error) {
   195  	fl.fs.logEnter(ctx, "FL FindFiles")
   196  	defer func() { fl.fs.reportErr(ctx, libkbfs.ReadMode, err) }()
   197  
   198  	session, err := fl.fs.config.KBPKI().GetCurrentSession(ctx)
   199  	isLoggedIn := err == nil
   200  
   201  	var favs []favorites.Folder
   202  	if isLoggedIn {
   203  		favs, err = fl.fs.config.KBFSOps().GetFavorites(ctx)
   204  		if err != nil {
   205  			return err
   206  		}
   207  	}
   208  	var ns dokan.NamedStat
   209  	ns.FileAttributes = dokan.FileAttributeDirectory
   210  	empty := true
   211  	for _, fav := range favs {
   212  		if fav.Type != fl.tlfType {
   213  			continue
   214  		}
   215  		pname, err := tlf.CanonicalToPreferredName(session.Name,
   216  			tlf.CanonicalName(fav.Name))
   217  		if err != nil {
   218  			fl.fs.log.CErrorf(ctx, "CanonicalToPreferredName: %q %v", fav.Name, err)
   219  			continue
   220  		}
   221  		empty = false
   222  		ns.Name = string(pname)
   223  		err = callback(&ns)
   224  		if err != nil {
   225  			return err
   226  		}
   227  	}
   228  	if empty {
   229  		return dokan.ErrObjectNameNotFound
   230  	}
   231  	return nil
   232  }
   233  
   234  func (fl *FolderList) isValidAliasTarget(ctx context.Context, nameToTry string) bool {
   235  	return tlfhandle.CheckHandleOffline(ctx, nameToTry, fl.tlfType) == nil
   236  }
   237  
   238  func (fl *FolderList) lockedAddChild(name string, val fileOpener) {
   239  	fl.mu.Lock()
   240  	fl.folders[name] = val
   241  	fl.mu.Unlock()
   242  }
   243  
   244  func (fl *FolderList) updateTlfName(ctx context.Context, oldName string,
   245  	newName string) {
   246  	fl.mu.Lock()
   247  	defer fl.mu.Unlock()
   248  	tlf, ok := fl.folders[oldName]
   249  	if !ok {
   250  		return
   251  	}
   252  
   253  	fl.fs.log.CDebugf(ctx, "Folder name updated: %s -> %s", oldName, newName)
   254  	delete(fl.folders, oldName)
   255  	fl.folders[newName] = tlf
   256  	// TODO: invalidate kernel cache for this name? (Make sure to
   257  	// do so outside of the lock!)
   258  }
   259  
   260  func (fl *FolderList) clearAliasCache() {
   261  	fl.mu.Lock()
   262  	fl.aliasCache = map[string]string{}
   263  	fl.mu.Unlock()
   264  }
   265  
   266  func clearFolderListCacheLoop(ctx context.Context, r *Root) {
   267  	t := time.NewTicker(time.Hour)
   268  	for {
   269  		select {
   270  		case <-ctx.Done():
   271  			return
   272  		case <-t.C:
   273  		}
   274  		r.private.clearAliasCache()
   275  		r.public.clearAliasCache()
   276  	}
   277  }
   278  
   279  // update things after user changed.
   280  func (fl *FolderList) userChanged(ctx context.Context, _, newUser kbname.NormalizedUsername) {
   281  	var fs []*Folder
   282  	func() {
   283  		fl.mu.Lock()
   284  		defer fl.mu.Unlock()
   285  		for _, tlf := range fl.folders {
   286  			if tlf, ok := tlf.(*TLF); ok {
   287  				fs = append(fs, tlf.folder)
   288  			}
   289  		}
   290  	}()
   291  	for _, f := range fs {
   292  		f.TlfHandleChange(ctx, nil)
   293  	}
   294  	if newUser != kbname.NormalizedUsername("") {
   295  		fl.fs.config.KBFSOps().ForceFastForward(ctx)
   296  	}
   297  }