github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libfuse/fs.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  //go:build !windows
     6  // +build !windows
     7  
     8  package libfuse
     9  
    10  import (
    11  	"net"
    12  	"net/http"
    13  	"net/http/pprof"
    14  	"os"
    15  	"strconv"
    16  	"strings"
    17  	"sync"
    18  	"syscall"
    19  	"time"
    20  
    21  	"bazil.org/fuse"
    22  	"bazil.org/fuse/fs"
    23  	"github.com/keybase/client/go/kbfs/idutil"
    24  	"github.com/keybase/client/go/kbfs/libcontext"
    25  	"github.com/keybase/client/go/kbfs/libfs"
    26  	"github.com/keybase/client/go/kbfs/libkbfs"
    27  	"github.com/keybase/client/go/kbfs/tlf"
    28  	"github.com/keybase/client/go/kbfs/tlfhandle"
    29  	kbname "github.com/keybase/client/go/kbun"
    30  	"github.com/keybase/client/go/libkb"
    31  	"github.com/keybase/client/go/logger"
    32  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    33  	"github.com/pkg/errors"
    34  	"golang.org/x/net/context"
    35  	"golang.org/x/net/trace"
    36  )
    37  
    38  // FS implements the newfuse FS interface for KBFS.
    39  type FS struct {
    40  	config  libkbfs.Config
    41  	fuse    *fs.Server
    42  	conn    *fuse.Conn
    43  	log     logger.Logger
    44  	errLog  logger.Logger
    45  	vlog    *libkb.VDebugLog
    46  	errVlog *libkb.VDebugLog
    47  
    48  	// Protects debugServerListener and debugServer.addr.
    49  	debugServerLock     sync.Mutex
    50  	debugServerListener net.Listener
    51  	// An HTTP server used for debugging. Normally off unless
    52  	// turned on via enableDebugServer().
    53  	debugServer *http.Server
    54  
    55  	notifications *libfs.FSNotifications
    56  
    57  	// remoteStatus is the current status of remote connections.
    58  	remoteStatus libfs.RemoteStatus
    59  
    60  	// this is like time.AfterFunc, except that in some tests this can be
    61  	// overridden to execute f without any delay.
    62  	execAfterDelay func(d time.Duration, f func())
    63  
    64  	root *Root
    65  
    66  	platformParams PlatformParams
    67  
    68  	inodeLock sync.Mutex
    69  	nextInode uint64
    70  }
    71  
    72  func makeTraceHandler(renderFn func(http.ResponseWriter, *http.Request, bool)) func(http.ResponseWriter, *http.Request) {
    73  	return func(w http.ResponseWriter, req *http.Request) {
    74  		any, sensitive := trace.AuthRequest(req)
    75  		if !any {
    76  			http.Error(w, "not allowed", http.StatusUnauthorized)
    77  			return
    78  		}
    79  		w.Header().Set("Content-Type", "text/html; charset=utf-8")
    80  		renderFn(w, req, sensitive)
    81  	}
    82  }
    83  
    84  // NewFS creates an FS. Note that this isn't the only constructor; see
    85  // makeFS in libfuse/mount_test.go.
    86  func NewFS(config libkbfs.Config, conn *fuse.Conn, debug bool,
    87  	platformParams PlatformParams) *FS {
    88  	log := config.MakeLogger("kbfsfuse")
    89  	// We need extra depth for errors, so that we can report the line
    90  	// number for the caller of processError, not processError itself.
    91  	errLog := log.CloneWithAddedDepth(1)
    92  	if debug {
    93  		// Turn on debugging.  TODO: allow a proper log file and
    94  		// style to be specified.
    95  		log.Configure("", true, "")
    96  		errLog.Configure("", true, "")
    97  	}
    98  
    99  	serveMux := http.NewServeMux()
   100  
   101  	// Replicate the default endpoints from pprof's init function.
   102  	serveMux.HandleFunc("/debug/pprof/", pprof.Index)
   103  	serveMux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
   104  	serveMux.HandleFunc("/debug/pprof/profile", pprof.Profile)
   105  	serveMux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
   106  	serveMux.HandleFunc("/debug/pprof/trace", pprof.Trace)
   107  
   108  	// Replicate the default endpoints from net/trace's init function.
   109  	serveMux.HandleFunc("/debug/requests", makeTraceHandler(func(w http.ResponseWriter, req *http.Request, sensitive bool) {
   110  		trace.Render(w, req, sensitive)
   111  	}))
   112  	serveMux.HandleFunc("/debug/events", makeTraceHandler(trace.RenderEvents))
   113  
   114  	// Leave Addr blank to be set in enableDebugServer() and
   115  	// disableDebugServer().
   116  	debugServer := &http.Server{
   117  		Handler:      serveMux,
   118  		ReadTimeout:  10 * time.Second,
   119  		WriteTimeout: 10 * time.Second,
   120  	}
   121  
   122  	fs := &FS{
   123  		config:         config,
   124  		conn:           conn,
   125  		log:            log,
   126  		errLog:         errLog,
   127  		vlog:           config.MakeVLogger(log),
   128  		errVlog:        config.MakeVLogger(errLog),
   129  		debugServer:    debugServer,
   130  		notifications:  libfs.NewFSNotifications(log),
   131  		root:           NewRoot(),
   132  		platformParams: platformParams,
   133  		nextInode:      2, // root is 1
   134  	}
   135  	fs.root.private = &FolderList{
   136  		fs:      fs,
   137  		tlfType: tlf.Private,
   138  		folders: make(map[string]*TLF),
   139  		inode:   fs.assignInode(),
   140  	}
   141  	fs.root.public = &FolderList{
   142  		fs:      fs,
   143  		tlfType: tlf.Public,
   144  		folders: make(map[string]*TLF),
   145  		inode:   fs.assignInode(),
   146  	}
   147  	fs.root.team = &FolderList{
   148  		fs:      fs,
   149  		tlfType: tlf.SingleTeam,
   150  		folders: make(map[string]*TLF),
   151  		inode:   fs.assignInode(),
   152  	}
   153  	fs.execAfterDelay = func(d time.Duration, f func()) {
   154  		time.AfterFunc(d, f)
   155  	}
   156  	return fs
   157  }
   158  
   159  func (f *FS) assignInode() uint64 {
   160  	f.inodeLock.Lock()
   161  	defer f.inodeLock.Unlock()
   162  	next := f.nextInode
   163  	f.nextInode++
   164  	return next
   165  }
   166  
   167  // tcpKeepAliveListener is copied from net/http/server.go, since it is
   168  // used in http.(*Server).ListenAndServe() which we want to emulate in
   169  // enableDebugServer.
   170  type tcpKeepAliveListener struct {
   171  	*net.TCPListener
   172  }
   173  
   174  func (tkal tcpKeepAliveListener) Accept() (c net.Conn, err error) {
   175  	tc, err := tkal.AcceptTCP()
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  	err = tc.SetKeepAlive(true)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  	err = tc.SetKeepAlivePeriod(3 * time.Minute)
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  	return tc, nil
   188  }
   189  
   190  func (f *FS) enableDebugServer(ctx context.Context, port uint16) error {
   191  	f.debugServerLock.Lock()
   192  	defer f.debugServerLock.Unlock()
   193  
   194  	// Note that f.debugServer may be nil if f was created via
   195  	// makeFS. But in that case we shouldn't be calling this
   196  	// function then anyway.
   197  	if f.debugServer.Addr != "" {
   198  		return errors.Errorf("Debug server already enabled at %s",
   199  			f.debugServer.Addr)
   200  	}
   201  
   202  	addr := net.JoinHostPort("localhost",
   203  		strconv.FormatUint(uint64(port), 10))
   204  	f.log.CDebugf(ctx, "Enabling debug http server at %s", addr)
   205  
   206  	// Do Listen and Serve separately so we can catch errors with
   207  	// the port (e.g. "port already in use") and return it.
   208  	listener, err := net.Listen("tcp", addr)
   209  	if err != nil {
   210  		f.log.CDebugf(ctx, "Got error when listening on %s: %+v",
   211  			addr, err)
   212  		return err
   213  	}
   214  
   215  	f.debugServer.Addr = addr
   216  	f.debugServerListener =
   217  		tcpKeepAliveListener{listener.(*net.TCPListener)}
   218  
   219  	// This seems racy because the spawned goroutine may be
   220  	// scheduled to run after disableDebugServer is called. But
   221  	// that's okay since Serve will error out immediately after
   222  	// f.debugServerListener.Close() is called.
   223  	go func(server *http.Server, listener net.Listener) {
   224  		err := server.Serve(listener)
   225  		f.log.Debug("Debug http server ended with %+v", err)
   226  	}(f.debugServer, f.debugServerListener)
   227  
   228  	// TODO: Perhaps enable turning tracing on and off
   229  	// independently from the debug server.
   230  	f.config.SetTraceOptions(true)
   231  
   232  	return nil
   233  }
   234  
   235  func (f *FS) disableDebugServer(ctx context.Context) error {
   236  	f.debugServerLock.Lock()
   237  	defer f.debugServerLock.Unlock()
   238  
   239  	// Note that f.debugServer may be nil if f was created via
   240  	// makeFS. But in that case we shouldn't be calling this
   241  	// function then anyway.
   242  	if f.debugServer.Addr == "" {
   243  		return errors.New("Debug server already disabled")
   244  	}
   245  
   246  	f.log.CDebugf(ctx, "Disabling debug http server at %s",
   247  		f.debugServer.Addr)
   248  	// TODO: Use f.debugServer.Close() or f.debugServer.Shutdown()
   249  	// when we switch to go 1.8.
   250  	err := f.debugServerListener.Close()
   251  	f.log.CDebugf(ctx, "Debug http server shutdown with %+v", err)
   252  
   253  	// Assume the close succeeds in stopping the server, even if
   254  	// it returns an error.
   255  	f.debugServer.Addr = ""
   256  	f.debugServerListener = nil
   257  
   258  	f.config.SetTraceOptions(false)
   259  
   260  	return err
   261  }
   262  
   263  // SetFuseConn sets fuse connection for this FS.
   264  func (f *FS) SetFuseConn(fuse *fs.Server, conn *fuse.Conn) {
   265  	f.fuse = fuse
   266  	f.conn = conn
   267  }
   268  
   269  // NotificationGroupWait - wait on the notification group.
   270  func (f *FS) NotificationGroupWait() {
   271  	f.notifications.Wait()
   272  }
   273  
   274  func (f *FS) queueNotification(fn func()) {
   275  	f.notifications.QueueNotification(fn)
   276  }
   277  
   278  // LaunchNotificationProcessor launches the notification processor.
   279  func (f *FS) LaunchNotificationProcessor(ctx context.Context) {
   280  	f.notifications.LaunchProcessor(ctx)
   281  }
   282  
   283  // WithContext adds app- and request-specific values to the context.
   284  // libkbfs.NewContextWithCancellationDelayer is called before returning the
   285  // context to ensure the cancellation is controllable.
   286  //
   287  // It is called by FUSE for normal runs, but may be called explicitly in other
   288  // settings, such as tests.
   289  func (f *FS) WithContext(ctx context.Context) context.Context {
   290  	id, errRandomReqID := libkbfs.MakeRandomRequestID()
   291  	if errRandomReqID != nil {
   292  		f.log.Errorf("Couldn't make request ID: %v", errRandomReqID)
   293  	}
   294  
   295  	// context.WithDeadline uses clock from `time` package, so we are not using
   296  	// f.config.Clock() here
   297  	start := time.Now()
   298  	ctx, err := libcontext.NewContextWithCancellationDelayer(
   299  		libcontext.NewContextReplayable(ctx, func(ctx context.Context) context.Context {
   300  			ctx = context.WithValue(ctx, libfs.CtxAppIDKey, f)
   301  			logTags := make(logger.CtxLogTags)
   302  			logTags[CtxIDKey] = CtxOpID
   303  			ctx = logger.NewContextWithLogTags(ctx, logTags)
   304  
   305  			if errRandomReqID == nil {
   306  				// Add a unique ID to this context, identifying a particular
   307  				// request.
   308  				ctx = context.WithValue(ctx, CtxIDKey, id)
   309  			}
   310  
   311  			if libkb.RuntimeGroup() == keybase1.RuntimeGroup_DARWINLIKE {
   312  				// Timeout operations before they hit the osxfuse time limit,
   313  				// so we don't hose the entire mount (Fixed in OSXFUSE 3.2.0).
   314  				// The timeout is 60 seconds, but it looks like sometimes it
   315  				// tries multiple attempts within that 60 seconds, so let's go
   316  				// a little under 60/3 to be safe.
   317  				//
   318  				// It should be safe to ignore the CancelFunc here because our
   319  				// parent context will be canceled by the FUSE serve loop.
   320  				ctx, _ = context.WithDeadline(ctx, start.Add(19*time.Second))
   321  			}
   322  
   323  			return ctx
   324  
   325  		}))
   326  
   327  	if err != nil {
   328  		panic(err) // this should never happen
   329  	}
   330  
   331  	return ctx
   332  }
   333  
   334  // Serve FS. Will block.
   335  func (f *FS) Serve(ctx context.Context) error {
   336  	srv := fs.New(f.conn, &fs.Config{
   337  		WithContext: func(ctx context.Context, _ fuse.Request) context.Context {
   338  			return f.WithContext(ctx)
   339  		},
   340  	})
   341  	f.fuse = srv
   342  
   343  	f.notifications.LaunchProcessor(ctx)
   344  	f.remoteStatus.Init(ctx, f.log, f.config, f)
   345  	// Blocks forever, unless an interrupt signal is received
   346  	// (handled by libkbfs.Init).
   347  	return srv.Serve(f)
   348  }
   349  
   350  // UserChanged is called from libfs.
   351  func (f *FS) UserChanged(ctx context.Context, oldName, newName kbname.NormalizedUsername) {
   352  	f.log.CDebugf(ctx, "User changed: %q -> %q", oldName, newName)
   353  	f.root.public.userChanged(ctx, oldName, newName)
   354  	f.root.private.userChanged(ctx, oldName, newName)
   355  }
   356  
   357  var _ libfs.RemoteStatusUpdater = (*FS)(nil)
   358  
   359  var _ fs.FS = (*FS)(nil)
   360  
   361  var _ fs.FSStatfser = (*FS)(nil)
   362  
   363  func (f *FS) processError(ctx context.Context,
   364  	mode libkbfs.ErrorModeType, err error) error {
   365  	if err == nil {
   366  		f.errVlog.CLogf(ctx, libkb.VLog1, "Request complete")
   367  		return nil
   368  	}
   369  
   370  	f.config.Reporter().ReportErr(ctx, "", tlf.Private, mode, err)
   371  	// We just log the error as debug, rather than error, because it
   372  	// might just indicate an expected error such as an ENOENT.
   373  	//
   374  	// TODO: Classify errors and escalate the logging level of the
   375  	// important ones.
   376  	f.errLog.CDebugf(ctx, err.Error())
   377  	return filterError(err)
   378  }
   379  
   380  // Root implements the fs.FS interface for FS.
   381  func (f *FS) Root() (fs.Node, error) {
   382  	return f.root, nil
   383  }
   384  
   385  // quotaUsageStaleTolerance is the lifespan of stale usage data that libfuse
   386  // accepts in the Statfs handler. In other words, this causes libkbfs to issue
   387  // a fresh RPC call if cached usage data is older than 10s.
   388  const quotaUsageStaleTolerance = 10 * time.Second
   389  
   390  // Statfs implements the fs.FSStatfser interface for FS.
   391  func (f *FS) Statfs(ctx context.Context, req *fuse.StatfsRequest, resp *fuse.StatfsResponse) error {
   392  	ctx, maybeUnmounting, cancel := wrapCtxWithShorterTimeoutForUnmount(ctx, f.log, int(req.Pid))
   393  	defer cancel()
   394  	if maybeUnmounting {
   395  		f.log.CInfof(ctx, "Statfs: maybeUnmounting=true")
   396  	}
   397  
   398  	*resp = fuse.StatfsResponse{
   399  		Bsize:   fuseBlockSize,
   400  		Namelen: ^uint32(0),
   401  		Frsize:  fuseBlockSize,
   402  	}
   403  
   404  	if f.remoteStatus.ExtraFileName() != "" {
   405  		f.vlog.CLogf(
   406  			ctx, libkb.VLog1,
   407  			"Skipping quota usage check while errors are present")
   408  		return nil
   409  	}
   410  
   411  	session, err := idutil.GetCurrentSessionIfPossible(
   412  		ctx, f.config.KBPKI(), true)
   413  	if err != nil {
   414  		return err
   415  	} else if session == (idutil.SessionInfo{}) {
   416  		// If user is not logged in, don't bother getting quota info. Otherwise
   417  		// reading a public TLF while logged out can fail on macOS.
   418  		return nil
   419  	}
   420  	_, usageBytes, _, limitBytes, err := f.config.GetQuotaUsage(
   421  		session.UID.AsUserOrTeam()).Get(
   422  		ctx, quotaUsageStaleTolerance/2, quotaUsageStaleTolerance)
   423  	if err != nil {
   424  		f.vlog.CLogf(ctx, libkb.VLog1, "Getting quota usage error: %v", err)
   425  		return err
   426  	}
   427  
   428  	total := getNumBlocksFromSize(uint64(limitBytes))
   429  	used := getNumBlocksFromSize(uint64(usageBytes))
   430  	resp.Blocks = total
   431  	resp.Bavail = total - used
   432  	resp.Bfree = total - used
   433  
   434  	return nil
   435  }
   436  
   437  // Root represents the root of the KBFS file system.
   438  type Root struct {
   439  	private *FolderList
   440  	public  *FolderList
   441  	team    *FolderList
   442  
   443  	lookupLock sync.RWMutex
   444  	lookupMap  map[tlf.Type]bool
   445  }
   446  
   447  // NewRoot creates a new root structure for KBFS FUSE mounts.
   448  func NewRoot() *Root {
   449  	return &Root{
   450  		lookupMap: make(map[tlf.Type]bool),
   451  	}
   452  }
   453  
   454  var _ fs.NodeAccesser = (*FolderList)(nil)
   455  
   456  // Access implements fs.NodeAccesser interface for *Root.
   457  func (*Root) Access(ctx context.Context, r *fuse.AccessRequest) error {
   458  	if int(r.Uid) != os.Getuid() &&
   459  		// Finder likes to use UID 0 for some operations. osxfuse already allows
   460  		// ACCESS and GETXATTR requests from root to go through. This allows root
   461  		// in ACCESS handler. See KBFS-1733 for more details.
   462  		int(r.Uid) != 0 {
   463  		// short path: not accessible by anybody other than root or the user who
   464  		// executed the kbfsfuse process.
   465  		return fuse.EPERM
   466  	}
   467  
   468  	if r.Mask&02 != 0 {
   469  		return fuse.EPERM
   470  	}
   471  
   472  	return nil
   473  }
   474  
   475  var _ fs.Node = (*Root)(nil)
   476  
   477  // Attr implements the fs.Node interface for Root.
   478  func (*Root) Attr(ctx context.Context, a *fuse.Attr) error {
   479  	a.Mode = os.ModeDir | 0500
   480  	a.Inode = 1
   481  	return nil
   482  }
   483  
   484  var _ fs.NodeRequestLookuper = (*Root)(nil)
   485  
   486  // Lookup implements the fs.NodeRequestLookuper interface for Root.
   487  func (r *Root) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (_ fs.Node, err error) {
   488  	r.private.fs.vlog.CLogf(ctx, libkb.VLog1, "FS Lookup %s", req.Name)
   489  	defer func() { err = r.private.fs.processError(ctx, libkbfs.ReadMode, err) }()
   490  
   491  	specialNode := handleNonTLFSpecialFile(
   492  		req.Name, r.private.fs, &resp.EntryValid)
   493  	if specialNode != nil {
   494  		return specialNode, nil
   495  	}
   496  
   497  	platformNode, err := r.platformLookup(ctx, req, resp)
   498  	if platformNode != nil || err != nil {
   499  		return platformNode, err
   500  	}
   501  
   502  	r.lookupLock.Lock()
   503  	defer r.lookupLock.Unlock()
   504  	switch req.Name {
   505  	case PrivateName:
   506  		r.lookupMap[tlf.Private] = true
   507  		return r.private, nil
   508  	case PublicName:
   509  		r.lookupMap[tlf.Public] = true
   510  		return r.public, nil
   511  	case TeamName:
   512  		r.lookupMap[tlf.SingleTeam] = true
   513  		return r.team, nil
   514  	}
   515  
   516  	// Don't want to pop up errors on special OS files.
   517  	if strings.HasPrefix(req.Name, ".") {
   518  		return nil, fuse.ENOENT
   519  	}
   520  
   521  	nameToLog := req.Name
   522  	// This error is logged, but we don't have a handy obfuscator
   523  	// here, so just log a special string to avoid exposing the user's
   524  	// typos or misdirected lookups.
   525  	if r.private.fs.config.Mode().DoLogObfuscation() {
   526  		nameToLog = "<obfuscated>"
   527  	}
   528  
   529  	return nil, libkbfs.NoSuchFolderListError{
   530  		Name:      req.Name,
   531  		NameToLog: nameToLog,
   532  		PrivName:  PrivateName,
   533  		PubName:   PublicName,
   534  	}
   535  }
   536  
   537  // PathType returns PathType for this folder
   538  func (r *Root) PathType() tlfhandle.PathType {
   539  	return tlfhandle.KeybasePathType
   540  }
   541  
   542  var _ fs.NodeCreater = (*Root)(nil)
   543  
   544  // Create implements the fs.NodeCreater interface for Root.
   545  func (r *Root) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (_ fs.Node, _ fs.Handle, err error) {
   546  	r.log().CDebugf(ctx, "FS Create")
   547  	defer func() { err = r.private.fs.processError(ctx, libkbfs.WriteMode, err) }()
   548  	if strings.HasPrefix(req.Name, "._") {
   549  		// Quietly ignore writes to special macOS files, without
   550  		// triggering a notification.
   551  		return nil, nil, syscall.ENOENT
   552  	}
   553  	return nil, nil, libkbfs.NewWriteUnsupportedError(tlfhandle.BuildCanonicalPath(r.PathType(), req.Name))
   554  }
   555  
   556  // Mkdir implements the fs.NodeMkdirer interface for Root.
   557  func (r *Root) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (_ fs.Node, err error) {
   558  	r.log().CDebugf(ctx, "FS Mkdir")
   559  	defer func() { err = r.private.fs.processError(ctx, libkbfs.WriteMode, err) }()
   560  	return nil, libkbfs.NewWriteUnsupportedError(tlfhandle.BuildCanonicalPath(r.PathType(), req.Name))
   561  }
   562  
   563  var _ fs.Handle = (*Root)(nil)
   564  
   565  var _ fs.HandleReadDirAller = (*Root)(nil)
   566  
   567  // ReadDirAll implements the ReadDirAll interface for Root.
   568  func (r *Root) ReadDirAll(ctx context.Context) (res []fuse.Dirent, err error) {
   569  	r.log().CDebugf(ctx, "FS ReadDirAll")
   570  	defer func() { err = r.private.fs.processError(ctx, libkbfs.ReadMode, err) }()
   571  	res = []fuse.Dirent{
   572  		{
   573  			Type: fuse.DT_Dir,
   574  			Name: PrivateName,
   575  		},
   576  		{
   577  			Type: fuse.DT_Dir,
   578  			Name: PublicName,
   579  		},
   580  		{
   581  			Type: fuse.DT_Dir,
   582  			Name: TeamName,
   583  		},
   584  	}
   585  	if r.private.fs.platformParams.shouldAppendPlatformRootDirs() {
   586  		res = append(res, platformRootDirs...)
   587  	}
   588  
   589  	if name := r.private.fs.remoteStatus.ExtraFileName(); name != "" {
   590  		res = append(res, fuse.Dirent{Type: fuse.DT_File, Name: name})
   591  	}
   592  	return res, nil
   593  }
   594  
   595  var _ fs.NodeSymlinker = (*Root)(nil)
   596  
   597  // Symlink implements the fs.NodeSymlinker interface for Root.
   598  func (r *Root) Symlink(
   599  	_ context.Context, _ *fuse.SymlinkRequest) (fs.Node, error) {
   600  	return nil, fuse.ENOTSUP
   601  }
   602  
   603  var _ fs.NodeLinker = (*Root)(nil)
   604  
   605  // Link implements the fs.NodeLinker interface for Root.
   606  func (r *Root) Link(
   607  	_ context.Context, _ *fuse.LinkRequest, _ fs.Node) (fs.Node, error) {
   608  	return nil, fuse.ENOTSUP
   609  }
   610  
   611  func (r *Root) log() logger.Logger {
   612  	return r.private.fs.log
   613  }
   614  func (r *Root) openFileCount() (ret int64) {
   615  	ret += r.private.openFileCount()
   616  	ret += r.public.openFileCount()
   617  	ret += r.team.openFileCount()
   618  
   619  	r.lookupLock.RLock()
   620  	defer r.lookupLock.RUnlock()
   621  	return ret + int64(len(r.lookupMap))
   622  }
   623  
   624  func (r *Root) forgetFolderList(t tlf.Type) {
   625  	// If the kernel ever forgets a folder list, reset the lookup
   626  	// function for that type and decrement the lookup counter.
   627  	r.lookupLock.Lock()
   628  	defer r.lookupLock.Unlock()
   629  	delete(r.lookupMap, t)
   630  }