github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libdokan/dir.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  	"fmt"
     9  	"strings"
    10  	"sync"
    11  
    12  	"github.com/keybase/client/go/kbfs/data"
    13  	"github.com/keybase/client/go/kbfs/dokan"
    14  	"github.com/keybase/client/go/kbfs/idutil"
    15  	"github.com/keybase/client/go/kbfs/libfs"
    16  	"github.com/keybase/client/go/kbfs/libkbfs"
    17  	"github.com/keybase/client/go/kbfs/tlf"
    18  	"github.com/keybase/client/go/kbfs/tlfhandle"
    19  	"github.com/keybase/client/go/libkb"
    20  	"github.com/pkg/errors"
    21  	"golang.org/x/net/context"
    22  )
    23  
    24  // HiddenFilePrefix is the prefix for files to be hidden.
    25  const HiddenFilePrefix = `._`
    26  
    27  // Folder represents KBFS top-level folders
    28  type Folder struct {
    29  	fs   *FS
    30  	list *FolderList
    31  
    32  	handleMu       sync.RWMutex
    33  	h              *tlfhandle.Handle
    34  	hPreferredName tlf.PreferredName
    35  
    36  	folderBranchMu sync.Mutex
    37  	folderBranch   data.FolderBranch
    38  
    39  	// Protects the nodes map.
    40  	mu sync.Mutex
    41  	// Map KBFS nodes to FUSE nodes, to be able to handle multiple
    42  	// lookups and incoming change notifications. A node is present
    43  	// here if the kernel holds a reference to it.
    44  	//
    45  	// If we ever support hardlinks, this would need refcounts.
    46  	//
    47  	// Children must call folder.forgetChildLocked on receiving the
    48  	// FUSE Forget request.
    49  	nodes map[libkbfs.NodeID]dokan.File
    50  
    51  	// Protects the updateChan.
    52  	updateMu sync.Mutex
    53  	// updateChan is non-nil when the user disables updates via the
    54  	// file system.  Sending a struct{}{} on this channel will unpause
    55  	// the updates.
    56  	updateChan chan<- struct{}
    57  
    58  	// noForget is turned on when the folder may not be forgotten
    59  	// because it has attached special file state with it.
    60  	noForget bool
    61  }
    62  
    63  func newFolder(fl *FolderList, h *tlfhandle.Handle,
    64  	hPreferredName tlf.PreferredName) *Folder {
    65  	f := &Folder{
    66  		fs:             fl.fs,
    67  		list:           fl,
    68  		h:              h,
    69  		hPreferredName: hPreferredName,
    70  		nodes:          map[libkbfs.NodeID]dokan.File{},
    71  	}
    72  	return f
    73  }
    74  
    75  func (f *Folder) name() tlf.CanonicalName {
    76  	f.handleMu.RLock()
    77  	defer f.handleMu.RUnlock()
    78  	return tlf.CanonicalName(f.hPreferredName)
    79  }
    80  
    81  func (f *Folder) setFolderBranch(folderBranch data.FolderBranch) error {
    82  	f.folderBranchMu.Lock()
    83  	defer f.folderBranchMu.Unlock()
    84  
    85  	// TODO unregister all at unmount
    86  	err := f.list.fs.config.Notifier().RegisterForChanges(
    87  		[]data.FolderBranch{folderBranch}, f)
    88  	if err != nil {
    89  		return err
    90  	}
    91  	f.folderBranch = folderBranch
    92  	return nil
    93  }
    94  
    95  func (f *Folder) unsetFolderBranch(ctx context.Context) {
    96  	f.folderBranchMu.Lock()
    97  	defer f.folderBranchMu.Unlock()
    98  	if f.folderBranch == (data.FolderBranch{}) {
    99  		// Wasn't set.
   100  		return
   101  	}
   102  
   103  	err := f.list.fs.config.Notifier().UnregisterFromChanges([]data.FolderBranch{f.folderBranch}, f)
   104  	if err != nil {
   105  		f.fs.log.Info("cannot unregister change notifier for folder %q: %v",
   106  			f.name(), err)
   107  	}
   108  	f.folderBranch = data.FolderBranch{}
   109  }
   110  
   111  func (f *Folder) getFolderBranch() data.FolderBranch {
   112  	f.folderBranchMu.Lock()
   113  	defer f.folderBranchMu.Unlock()
   114  	return f.folderBranch
   115  }
   116  
   117  // forgetNode forgets a formerly active child with basename name.
   118  func (f *Folder) forgetNode(ctx context.Context, node libkbfs.Node) {
   119  	f.mu.Lock()
   120  	defer f.mu.Unlock()
   121  
   122  	delete(f.nodes, node.GetID())
   123  	if len(f.nodes) == 0 && !f.noForget {
   124  		f.unsetFolderBranch(ctx)
   125  		f.list.forgetFolder(string(f.name()))
   126  	}
   127  }
   128  
   129  func (f *Folder) reportErr(ctx context.Context,
   130  	mode libkbfs.ErrorModeType, err error) {
   131  	if err == nil {
   132  		f.fs.vlog.CLogf(ctx, libkb.VLog1, "Request complete")
   133  		return
   134  	}
   135  
   136  	f.fs.config.Reporter().ReportErr(ctx, f.name(), f.list.tlfType, mode, err)
   137  	// We just log the error as debug, rather than error, because it
   138  	// might just indicate an expected error such as an ENOENT.
   139  	//
   140  	// TODO: Classify errors and escalate the logging level of the
   141  	// important ones.
   142  	f.fs.log.CDebugf(ctx, err.Error())
   143  }
   144  
   145  func (f *Folder) lockedAddNode(node libkbfs.Node, val dokan.File) {
   146  	f.mu.Lock()
   147  	f.nodes[node.GetID()] = val
   148  	f.mu.Unlock()
   149  }
   150  
   151  // LocalChange is called for changes originating within in this process.
   152  func (f *Folder) LocalChange(ctx context.Context, node libkbfs.Node, write libkbfs.WriteRange) {
   153  	f.fs.queueNotification(func() {})
   154  }
   155  
   156  // BatchChanges is called for changes originating anywhere, including
   157  // other hosts.
   158  func (f *Folder) BatchChanges(
   159  	ctx context.Context, changes []libkbfs.NodeChange, _ []libkbfs.NodeID) {
   160  	f.fs.queueNotification(func() {})
   161  }
   162  
   163  // TlfHandleChange is called when the name of a folder changes.
   164  // Note that newHandle may be nil. Then the handle in the folder is used.
   165  // This is used on e.g. logout/login.
   166  func (f *Folder) TlfHandleChange(ctx context.Context,
   167  	newHandle *tlfhandle.Handle) {
   168  	f.fs.log.CDebugf(ctx, "TlfHandleChange called on %q",
   169  		canonicalNameIfNotNil(newHandle))
   170  
   171  	// Handle in the background because we shouldn't lock during
   172  	// the notification
   173  	f.fs.queueNotification(func() {
   174  		ctx := context.Background()
   175  		session, err := idutil.GetCurrentSessionIfPossible(ctx, f.fs.config.KBPKI(), f.list.tlfType == tlf.Public)
   176  		// Here we get an error, but there is little that can be done.
   177  		// session will be empty in the error case in which case we will default to the
   178  		// canonical format.
   179  		if err != nil {
   180  			f.fs.log.CDebugf(ctx,
   181  				"tlfHandleChange: GetCurrentUserInfoIfPossible failed: %v", err)
   182  		}
   183  		oldName, newName := func() (tlf.PreferredName, tlf.PreferredName) {
   184  			f.handleMu.Lock()
   185  			defer f.handleMu.Unlock()
   186  			oldName := f.hPreferredName
   187  			if newHandle != nil {
   188  				f.h = newHandle
   189  			}
   190  			f.hPreferredName = f.h.GetPreferredFormat(session.Name)
   191  			return oldName, f.hPreferredName
   192  		}()
   193  
   194  		if oldName != newName {
   195  			f.list.updateTlfName(ctx, string(oldName), string(newName))
   196  		}
   197  	})
   198  }
   199  
   200  func canonicalNameIfNotNil(h *tlfhandle.Handle) string {
   201  	if h == nil {
   202  		return "(nil)"
   203  	}
   204  	return string(h.GetCanonicalName())
   205  }
   206  
   207  func (f *Folder) resolve(ctx context.Context) (*tlfhandle.Handle, error) {
   208  	if f.h.TlfID() == tlf.NullID {
   209  		// If the handle doesn't have a TLF ID yet, fetch it now.
   210  		handle, err := tlfhandle.ParseHandlePreferred(
   211  			ctx, f.fs.config.KBPKI(), f.fs.config.MDOps(), f.fs.config,
   212  			string(f.hPreferredName), f.h.Type())
   213  		switch errors.Cause(err).(type) {
   214  		case nil:
   215  			f.TlfHandleChange(ctx, handle)
   216  			return handle, nil
   217  		case idutil.NoSuchNameError, idutil.BadTLFNameError,
   218  			tlf.NoSuchUserError, idutil.NoSuchUserError:
   219  			return nil, dokan.ErrObjectNameNotFound
   220  		default:
   221  			return nil, err
   222  		}
   223  	}
   224  
   225  	// In case there were any unresolved assertions, try them again on
   226  	// the first load.  Otherwise, since we haven't subscribed to
   227  	// updates yet for this folder, we might have missed a name
   228  	// change.
   229  	handle, err := f.h.ResolveAgain(
   230  		ctx, f.fs.config.KBPKI(), f.fs.config.MDOps(), f.fs.config)
   231  	if err != nil {
   232  		return nil, err
   233  	}
   234  	eq, err := f.h.Equals(f.fs.config.Codec(), *handle)
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  	if !eq {
   239  		// Make sure the name changes in the folder and the folder list
   240  		f.TlfHandleChange(ctx, handle)
   241  	}
   242  	return handle, nil
   243  }
   244  
   245  // Dir represents KBFS subdirectories.
   246  type Dir struct {
   247  	FSO
   248  }
   249  
   250  func newDir(folder *Folder, node libkbfs.Node, name string, parent libkbfs.Node) *Dir {
   251  	d := &Dir{FSO{
   252  		name:   name,
   253  		parent: parent,
   254  		folder: folder,
   255  		node:   node,
   256  	}}
   257  	d.refcount.Increase()
   258  	return d
   259  }
   260  
   261  // GetFileInformation for dokan.
   262  func (d *Dir) GetFileInformation(ctx context.Context, fi *dokan.FileInfo) (st *dokan.Stat, err error) {
   263  	d.folder.fs.logEnter(ctx, "Dir GetFileInformation")
   264  	defer func() { d.folder.reportErr(ctx, libkbfs.ReadMode, err) }()
   265  
   266  	return eiToStat(d.folder.fs.config.KBFSOps().Stat(ctx, d.node))
   267  }
   268  
   269  // SetFileAttributes for Dokan.
   270  func (d *Dir) SetFileAttributes(ctx context.Context, fi *dokan.FileInfo, fileAttributes dokan.FileAttribute) error {
   271  	d.folder.fs.logEnter(ctx, "Dir SetFileAttributes")
   272  	// TODO handle attributes for real.
   273  	return nil
   274  }
   275  
   276  // isNoSuchNameError checks for libkbfs.NoSuchNameError.
   277  func isNoSuchNameError(err error) bool {
   278  	_, ok := err.(idutil.NoSuchNameError)
   279  	return ok
   280  }
   281  
   282  // lastStr returns last string in a string slice or "" if the slice is empty.
   283  func lastStr(strs []string) string {
   284  	if len(strs) == 0 {
   285  		return ""
   286  	}
   287  	return strs[len(strs)-1]
   288  }
   289  
   290  // isSafeFolder returns whether a Folder is considered safe.
   291  func isSafeFolder(ctx context.Context, f *Folder) bool {
   292  	return libkbfs.IsOnlyWriterInNonTeamTlf(ctx, f.list.fs.config.KBPKI(), f.h)
   293  }
   294  
   295  // open tries to open a file.
   296  func (d *Dir) open(ctx context.Context, oc *openContext, path []string) (dokan.File, dokan.CreateStatus, error) {
   297  	d.folder.fs.vlog.CLogf(ctx, libkb.VLog1, "Dir openDir %s", path)
   298  
   299  	specialNode := handleTLFSpecialFile(lastStr(path), d.folder)
   300  	if specialNode != nil {
   301  		return oc.returnFileNoCleanup(specialNode)
   302  	}
   303  
   304  	origPath := path
   305  	rootDir := d
   306  	for len(path) > 0 {
   307  		// Handle upper case filenames from junctions etc
   308  		if c := lowerTranslateCandidate(oc, path[0]); c != "" {
   309  			var hit string
   310  			var nhits int
   311  			err := d.FindFiles(ctx, nil, c, func(ns *dokan.NamedStat) error {
   312  				if strings.ToLower(ns.Name) == c {
   313  					hit = ns.Name
   314  					nhits++
   315  				}
   316  				return nil
   317  			})
   318  			if err != nil {
   319  				return nil, 0, dokan.ErrObjectNameNotFound
   320  			}
   321  			if nhits != 1 {
   322  				return nil, 0, dokan.ErrObjectNameNotFound
   323  			}
   324  			path[0] = hit
   325  		}
   326  
   327  		leaf := len(path) == 1
   328  
   329  		// Check if this is a per-file metainformation file, if so
   330  		// return the corresponding SpecialReadFile.
   331  		if leaf && strings.HasPrefix(path[0], libfs.FileInfoPrefix) {
   332  			if err := oc.ReturningFileAllowed(); err != nil {
   333  				return nil, 0, err
   334  			}
   335  			name := path[0][len(libfs.FileInfoPrefix):]
   336  			kbfsName, err := libkb.DecodeWindowsNameForKbfs(name)
   337  			if err != nil {
   338  				return nil, 0, err
   339  			}
   340  			return NewFileInfoFile(d.folder.fs, d.node, kbfsName), 0, nil
   341  		}
   342  
   343  		kbfsName, err := libkb.DecodeWindowsNameForKbfs(path[0])
   344  		if err != nil {
   345  			return nil, 0, err
   346  		}
   347  		newNode, de, err := d.folder.fs.config.KBFSOps().Lookup(
   348  			ctx, d.node, d.node.ChildName(kbfsName))
   349  
   350  		// If we are in the final component, check if it is a creation.
   351  		if leaf {
   352  			notFound := isNoSuchNameError(err)
   353  			switch {
   354  			case notFound && oc.isCreateDirectory():
   355  				return d.mkdir(ctx, oc, path[0])
   356  			case notFound && oc.isCreation():
   357  				return d.create(ctx, oc, path[0])
   358  			case !notFound && oc.isExistingError():
   359  				return nil, 0, dokan.ErrFileAlreadyExists
   360  			}
   361  		}
   362  
   363  		// Return errors from Lookup
   364  		if err != nil {
   365  			return nil, 0, err
   366  		}
   367  
   368  		// Refuse to execute files by checking FILE_EXECUTE (not exported by syscall)
   369  		// in TLFs considered unsafe.
   370  		if de.Type.IsFile() && oc.CreateData.DesiredAccess&0x20 != 0 && !isSafeFolder(ctx, d.folder) {
   371  			d.folder.fs.log.CErrorf(ctx, "Denying execution access to: %q", path[0])
   372  
   373  			return nil, 0, dokan.ErrAccessDenied
   374  		}
   375  
   376  		if newNode != nil {
   377  			d.folder.mu.Lock()
   378  			f := d.folder.nodes[newNode.GetID()]
   379  			d.folder.mu.Unlock()
   380  			// Symlinks don't have stored nodes, so they are impossible here.
   381  			switch x := f.(type) {
   382  			default:
   383  				return nil, 0, fmt.Errorf("unhandled node type: %T", f)
   384  			case nil:
   385  			case *File:
   386  				if err := oc.ReturningFileAllowed(); err != nil {
   387  					return nil, 0, err
   388  				}
   389  				x.refcount.Increase()
   390  				return openFile(ctx, oc, path, x)
   391  			case *Dir:
   392  				d = x
   393  				path = path[1:]
   394  				continue
   395  			}
   396  		}
   397  		switch de.Type {
   398  		default:
   399  			return nil, 0, fmt.Errorf("unhandled entry type: %v", de.Type)
   400  		case data.File, data.Exec:
   401  			if err := oc.ReturningFileAllowed(); err != nil {
   402  				return nil, 0, err
   403  			}
   404  			child := newFile(d.folder, newNode, path[0], d.node)
   405  			f, _, err := openFile(ctx, oc, path, child)
   406  			if err == nil {
   407  				d.folder.lockedAddNode(newNode, child)
   408  			}
   409  			return f, dokan.ExistingFile, err
   410  		case data.Dir:
   411  			child := newDir(d.folder, newNode, path[0], d.node)
   412  			d.folder.lockedAddNode(newNode, child)
   413  			d = child
   414  			path = path[1:]
   415  		case data.Sym:
   416  			return openSymlink(ctx, oc, d, rootDir, origPath, path, de.SymPath)
   417  		}
   418  	}
   419  	if err := oc.ReturningDirAllowed(); err != nil {
   420  		return nil, 0, err
   421  	}
   422  	d.refcount.Increase()
   423  	return d, dokan.ExistingDir, nil
   424  }
   425  
   426  func openFile(ctx context.Context, oc *openContext, path []string, f *File) (dokan.File, dokan.CreateStatus, error) {
   427  	var err error
   428  	// Files only allowed as leafs...
   429  	if len(path) > 1 {
   430  		return nil, 0, dokan.ErrObjectNameNotFound
   431  	}
   432  	if oc.isTruncate() {
   433  		err = f.folder.fs.config.KBFSOps().Truncate(ctx, f.node, 0)
   434  	}
   435  	if err != nil {
   436  		return nil, 0, err
   437  	}
   438  	return f, dokan.ExistingFile, nil
   439  }
   440  
   441  func openSymlink(ctx context.Context, oc *openContext, parent *Dir, rootDir *Dir, origPath, path []string, target string) (dokan.File, dokan.CreateStatus, error) {
   442  	// TODO handle file/directory type flags here from CreateOptions.
   443  	if !oc.reduceRedirectionsLeft() {
   444  		return nil, 0, dokan.ErrObjectNameNotFound
   445  	}
   446  	// Take relevant prefix of original path.
   447  	origPath = origPath[:len(origPath)-len(path)]
   448  	if len(path) == 1 && oc.isOpenReparsePoint() {
   449  		// a Symlink is never included in Folder.nodes, as it doesn't
   450  		// have a libkbfs.Node to keep track of renames.
   451  		// Here we may get an error if the symlink destination does not exist.
   452  		// which is fine, treat such non-existing targets as symlinks to a file.
   453  		cst, err := resolveSymlinkIsDir(ctx, oc, rootDir, origPath, target)
   454  		parent.folder.fs.vlog.CLogf(
   455  			ctx, libkb.VLog1, "openSymlink leaf returned %v,%v => %v,%v",
   456  			origPath, target, cst, err)
   457  		return &Symlink{parent: parent, name: path[0], isTargetADirectory: cst.IsDir()}, cst, nil
   458  	}
   459  	// reference symlink, symbolic links always use '/' instead of '\'.
   460  	if target == "" || target[0] == '/' {
   461  		return nil, 0, dokan.ErrNotSupported
   462  	}
   463  
   464  	dst, err := resolveSymlinkPath(ctx, origPath, target)
   465  	parent.folder.fs.vlog.CLogf(
   466  		ctx, libkb.VLog1, "openSymlink resolve returned %v,%v => %v,%v",
   467  		origPath, target, dst, err)
   468  	if err != nil {
   469  		return nil, 0, err
   470  	}
   471  	dst = append(dst, path[1:]...)
   472  	return rootDir.open(ctx, oc, dst)
   473  }
   474  
   475  func getExclFromOpenContext(oc *openContext) libkbfs.Excl {
   476  	return libkbfs.Excl(oc.CreateDisposition == dokan.FileCreate)
   477  }
   478  
   479  func (d *Dir) create(ctx context.Context, oc *openContext, name string) (f dokan.File, cst dokan.CreateStatus, err error) {
   480  	if name, err = libkb.DecodeWindowsNameForKbfs(name); err != nil {
   481  		return nil, 0, err
   482  	}
   483  	namePPS := d.node.ChildName(name)
   484  	d.folder.fs.vlog.CLogf(ctx, libkb.VLog1, "Dir Create %s", namePPS)
   485  	defer func() { d.folder.reportErr(ctx, libkbfs.WriteMode, err) }()
   486  
   487  	isExec := false // Windows lacks executable modes.
   488  	excl := getExclFromOpenContext(oc)
   489  	newNode, _, err := d.folder.fs.config.KBFSOps().CreateFile(
   490  		ctx, d.node, namePPS, isExec, excl)
   491  	if err != nil {
   492  		return nil, 0, err
   493  	}
   494  
   495  	child := newFile(d.folder, newNode, name, d.node)
   496  	d.folder.lockedAddNode(newNode, child)
   497  	return child, dokan.NewFile, nil
   498  }
   499  
   500  func (d *Dir) mkdir(ctx context.Context, oc *openContext, name string) (
   501  	f *Dir, cst dokan.CreateStatus, err error) {
   502  	if name, err = libkb.DecodeWindowsNameForKbfs(name); err != nil {
   503  		return nil, 0, err
   504  	}
   505  	namePPS := d.node.ChildName(name)
   506  	d.folder.fs.vlog.CLogf(ctx, libkb.VLog1, "Dir Mkdir %s", namePPS)
   507  	defer func() { d.folder.reportErr(ctx, libkbfs.WriteMode, err) }()
   508  
   509  	newNode, _, err := d.folder.fs.config.KBFSOps().CreateDir(
   510  		ctx, d.node, namePPS)
   511  	if err != nil {
   512  		return nil, 0, err
   513  	}
   514  
   515  	child := newDir(d.folder, newNode, name, d.node)
   516  	d.folder.lockedAddNode(newNode, child)
   517  	return child, dokan.NewDir, nil
   518  }
   519  
   520  // FindFiles does readdir for dokan.
   521  func (d *Dir) FindFiles(ctx context.Context, fi *dokan.FileInfo, ignored string, callback func(*dokan.NamedStat) error) (err error) {
   522  	d.folder.fs.logEnter(ctx, "Dir FindFiles")
   523  	defer func() { d.folder.reportErr(ctx, libkbfs.ReadMode, err) }()
   524  
   525  	children, err := d.folder.fs.config.KBFSOps().GetDirChildren(ctx, d.node)
   526  	if err != nil {
   527  		return err
   528  	}
   529  
   530  	empty := true
   531  	var ns dokan.NamedStat
   532  	for pps, de := range children {
   533  		windowsName := libkb.EncodeKbfsNameForWindows(pps.Plaintext())
   534  		empty = false
   535  		ns.Name = windowsName
   536  		// TODO perhaps resolve symlinks here?
   537  		fillStat(&ns.Stat, &de)
   538  		if strings.HasPrefix(windowsName, HiddenFilePrefix) {
   539  			addFileAttribute(&ns.Stat, dokan.FileAttributeHidden)
   540  		}
   541  		err = callback(&ns)
   542  		if err != nil {
   543  			return err
   544  		}
   545  	}
   546  	if empty {
   547  		return dokan.ErrObjectNameNotFound
   548  	}
   549  	return nil
   550  }
   551  
   552  // CanDeleteDirectory - return just nil
   553  // TODO check for permissions here.
   554  func (d *Dir) CanDeleteDirectory(ctx context.Context, fi *dokan.FileInfo) (err error) {
   555  	d.folder.fs.logEnterf(ctx, "Dir CanDeleteDirectory %q", d.name)
   556  	defer func() { d.folder.reportErr(ctx, libkbfs.WriteMode, err) }()
   557  
   558  	children, err := d.folder.fs.config.KBFSOps().GetDirChildren(ctx, d.node)
   559  	if err != nil {
   560  		return errToDokan(err)
   561  	}
   562  	if len(children) > 0 {
   563  		return dokan.ErrDirectoryNotEmpty
   564  	}
   565  
   566  	return nil
   567  }
   568  
   569  // Cleanup - forget references, perform deletions etc.
   570  // If Cleanup is called with non-nil FileInfo that has IsDeleteOnClose()
   571  // no libdokan locks should be held prior to the call.
   572  func (d *Dir) Cleanup(ctx context.Context, fi *dokan.FileInfo) {
   573  	namePPS := d.node.ChildName(d.name)
   574  	var err error
   575  	if fi != nil {
   576  		d.folder.fs.logEnterf(ctx, "Dir Cleanup %s delete=%v", namePPS,
   577  			fi.IsDeleteOnClose())
   578  	} else {
   579  		d.folder.fs.logEnterf(ctx, "Dir Cleanup %s", namePPS)
   580  	}
   581  	defer func() { d.folder.reportErr(ctx, libkbfs.WriteMode, err) }()
   582  
   583  	if fi != nil && fi.IsDeleteOnClose() && d.parent != nil {
   584  		// renameAndDeletionLock should be the first lock to be grabbed in libdokan.
   585  		d.folder.fs.renameAndDeletionLock.Lock()
   586  		defer d.folder.fs.renameAndDeletionLock.Unlock()
   587  		d.folder.fs.vlog.CLogf(
   588  			ctx, libkb.VLog1, "Removing (Delete) dir in cleanup %s", namePPS)
   589  
   590  		err = d.folder.fs.config.KBFSOps().RemoveDir(ctx, d.parent, namePPS)
   591  	}
   592  
   593  	if d.refcount.Decrease() {
   594  		d.folder.forgetNode(ctx, d.node)
   595  	}
   596  }
   597  
   598  func resolveSymlinkPath(ctx context.Context, origPath []string, targetPath string) ([]string, error) {
   599  	pathComponents := make([]string, len(origPath), len(origPath)+1)
   600  	copy(pathComponents, origPath)
   601  
   602  	for _, p := range strings.FieldsFunc(targetPath, isPathSeparator) {
   603  		switch p {
   604  		case ".":
   605  		case "..":
   606  			if len(pathComponents) == 0 {
   607  				return nil, dokan.ErrNotSupported
   608  			}
   609  			pathComponents = pathComponents[:len(pathComponents)-1]
   610  		default:
   611  			pathComponents = append(pathComponents, p)
   612  		}
   613  	}
   614  	return pathComponents, nil
   615  }
   616  
   617  func resolveSymlinkIsDir(ctx context.Context, oc *openContext, rootDir *Dir, origPath []string, targetPath string) (dokan.CreateStatus, error) {
   618  	dst, err := resolveSymlinkPath(ctx, origPath, targetPath)
   619  	if err != nil {
   620  		return dokan.NewFile, err
   621  	}
   622  	obj, cst, err := rootDir.open(ctx, oc, dst)
   623  	if err == nil {
   624  		obj.Cleanup(ctx, nil)
   625  	}
   626  	return cst, err
   627  }
   628  func isPathSeparator(r rune) bool {
   629  	return r == '/' || r == '\\'
   630  }
   631  
   632  func asDir(ctx context.Context, f dokan.File) *Dir {
   633  	switch x := f.(type) {
   634  	case *Dir:
   635  		return x
   636  	case *TLF:
   637  		branch := x.folder.getFolderBranch().Branch
   638  		filterErr := false
   639  		if branch != data.MasterBranch {
   640  			filterErr = true
   641  		}
   642  		d, _, _ := x.loadDirHelper(
   643  			ctx, "asDir", libkbfs.WriteMode, branch, filterErr)
   644  		return d
   645  	}
   646  	return nil
   647  }