github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libfuse/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  //go:build !windows
     6  // +build !windows
     7  
     8  package libfuse
     9  
    10  import (
    11  	"fmt"
    12  	"os"
    13  	"strings"
    14  	"sync"
    15  	"syscall"
    16  
    17  	"bazil.org/fuse"
    18  	"bazil.org/fuse/fs"
    19  	"github.com/keybase/client/go/kbfs/favorites"
    20  	"github.com/keybase/client/go/kbfs/idutil"
    21  	"github.com/keybase/client/go/kbfs/libkbfs"
    22  	"github.com/keybase/client/go/kbfs/tlf"
    23  	"github.com/keybase/client/go/kbfs/tlfhandle"
    24  	kbname "github.com/keybase/client/go/kbun"
    25  	"github.com/keybase/client/go/libkb"
    26  	"github.com/pkg/errors"
    27  	"golang.org/x/net/context"
    28  )
    29  
    30  // FolderList is a node that can list all of the logged-in user's
    31  // favorite top-level folders, on either a public or private basis.
    32  type FolderList struct {
    33  	fs *FS
    34  	// only accept public folders
    35  	tlfType tlf.Type
    36  	inode   uint64
    37  
    38  	mu      sync.Mutex
    39  	folders map[string]*TLF
    40  }
    41  
    42  var _ fs.NodeAccesser = (*FolderList)(nil)
    43  
    44  // Access implements fs.NodeAccesser interface for *FolderList.
    45  func (*FolderList) Access(ctx context.Context, r *fuse.AccessRequest) error {
    46  	if int(r.Uid) != os.Getuid() &&
    47  		// Finder likes to use UID 0 for some operations. osxfuse already allows
    48  		// ACCESS and GETXATTR requests from root to go through. This allows root
    49  		// in ACCESS handler. See KBFS-1733 for more details.
    50  		int(r.Uid) != 0 {
    51  		// short path: not accessible by anybody other than root or the user who
    52  		// executed the kbfsfuse process.
    53  		return fuse.EPERM
    54  	}
    55  
    56  	if r.Mask&02 != 0 {
    57  		return fuse.EPERM
    58  	}
    59  
    60  	return nil
    61  }
    62  
    63  var _ fs.Node = (*FolderList)(nil)
    64  
    65  // Attr implements the fs.Node interface.
    66  func (fl *FolderList) Attr(ctx context.Context, a *fuse.Attr) error {
    67  	a.Mode = os.ModeDir | 0500
    68  	a.Uid = uint32(os.Getuid())
    69  	a.Inode = fl.inode
    70  	return nil
    71  }
    72  
    73  var _ fs.NodeRequestLookuper = (*FolderList)(nil)
    74  
    75  func (fl *FolderList) processError(ctx context.Context,
    76  	mode libkbfs.ErrorModeType, tlfName tlf.CanonicalName, err error) error {
    77  	if err == nil {
    78  		fl.fs.errLog.CDebugf(ctx, "Request complete")
    79  		return nil
    80  	}
    81  
    82  	fl.fs.config.Reporter().ReportErr(ctx, tlfName, fl.tlfType, mode, err)
    83  	// We just log the error as debug, rather than error, because it
    84  	// might just indicate an expected error such as an ENOENT.
    85  	//
    86  	// TODO: Classify errors and escalate the logging level of the
    87  	// important ones.
    88  	fl.fs.errLog.CDebugf(ctx, err.Error())
    89  	return filterError(err)
    90  }
    91  
    92  // PathType returns PathType for this folder
    93  func (fl *FolderList) PathType() tlfhandle.PathType {
    94  	switch fl.tlfType {
    95  	case tlf.Private:
    96  		return tlfhandle.PrivatePathType
    97  	case tlf.Public:
    98  		return tlfhandle.PublicPathType
    99  	case tlf.SingleTeam:
   100  		return tlfhandle.SingleTeamPathType
   101  	default:
   102  		panic(fmt.Sprintf("Unsupported tlf type: %s", fl.tlfType))
   103  	}
   104  }
   105  
   106  // Create implements the fs.NodeCreater interface for FolderList.
   107  func (fl *FolderList) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (_ fs.Node, _ fs.Handle, err error) {
   108  	fl.fs.vlog.CLogf(ctx, libkb.VLog1, "FL Create")
   109  	tlfName := tlf.CanonicalName(req.Name)
   110  	defer func() { err = fl.processError(ctx, libkbfs.WriteMode, tlfName, err) }()
   111  	if strings.HasPrefix(req.Name, "._") {
   112  		// Quietly ignore writes to special macOS files, without
   113  		// triggering a notification.
   114  		return nil, nil, syscall.ENOENT
   115  	}
   116  	return nil, nil, libkbfs.NewWriteUnsupportedError(tlfhandle.BuildCanonicalPath(fl.PathType(), string(tlfName)))
   117  }
   118  
   119  // Mkdir implements the fs.NodeMkdirer interface for FolderList.
   120  func (fl *FolderList) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (_ fs.Node, err error) {
   121  	fl.fs.vlog.CLogf(ctx, libkb.VLog1, "FL Mkdir")
   122  	tlfName := tlf.CanonicalName(req.Name)
   123  	defer func() { err = fl.processError(ctx, libkbfs.WriteMode, tlfName, err) }()
   124  	return nil, libkbfs.NewWriteUnsupportedError(tlfhandle.BuildCanonicalPath(fl.PathType(), string(tlfName)))
   125  }
   126  
   127  // Lookup implements the fs.NodeRequestLookuper interface.
   128  func (fl *FolderList) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (node fs.Node, err error) {
   129  	fl.fs.vlog.CLogf(ctx, libkb.VLog1, "FL Lookup %s", req.Name)
   130  	defer func() {
   131  		err = fl.processError(ctx, libkbfs.ReadMode,
   132  			tlf.CanonicalName(req.Name), err)
   133  	}()
   134  	fl.mu.Lock()
   135  	defer fl.mu.Unlock()
   136  
   137  	specialNode := handleNonTLFSpecialFile(
   138  		req.Name, fl.fs, &resp.EntryValid)
   139  	if specialNode != nil {
   140  		return specialNode, nil
   141  	}
   142  
   143  	if child, ok := fl.folders[req.Name]; ok {
   144  		return child, nil
   145  	}
   146  
   147  	// Shortcut for dreaded extraneous OSX finder lookups
   148  	if strings.HasPrefix(req.Name, "._") {
   149  		return nil, fuse.ENOENT
   150  	}
   151  
   152  	h, err := tlfhandle.ParseHandlePreferredQuick(
   153  		ctx, fl.fs.config.KBPKI(), fl.fs.config, req.Name, fl.tlfType)
   154  	switch e := errors.Cause(err).(type) {
   155  	case nil:
   156  		// no error
   157  
   158  	case idutil.TlfNameNotCanonical:
   159  		// Only permit Aliases to targets that contain no errors.
   160  		if !fl.isValidAliasTarget(ctx, e.NameToTry) {
   161  			fl.fs.log.CDebugf(ctx, "FL Refusing alias to non-valid target %q", e.NameToTry)
   162  			return nil, fuse.ENOENT
   163  		}
   164  		// Non-canonical name.
   165  		n := &Alias{
   166  			realPath: e.NameToTry,
   167  			inode:    0,
   168  		}
   169  		return n, nil
   170  
   171  	case idutil.NoSuchNameError, idutil.BadTLFNameError,
   172  		tlf.NoSuchUserError, idutil.NoSuchUserError:
   173  		// Invalid TLF.
   174  		return nil, fuse.ENOENT
   175  
   176  	default:
   177  		// Some other error.
   178  		return nil, err
   179  	}
   180  
   181  	session, err := idutil.GetCurrentSessionIfPossible(
   182  		ctx, fl.fs.config.KBPKI(), h.Type() == tlf.Public)
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  	child := newTLF(ctx, fl, h, h.GetPreferredFormat(session.Name))
   187  	fl.folders[req.Name] = child
   188  	return child, nil
   189  }
   190  
   191  func (fl *FolderList) isValidAliasTarget(ctx context.Context, nameToTry string) bool {
   192  	return tlfhandle.CheckHandleOffline(ctx, nameToTry, fl.tlfType) == nil
   193  }
   194  
   195  func (fl *FolderList) forgetFolder(folderName string) {
   196  	fl.mu.Lock()
   197  	defer fl.mu.Unlock()
   198  	delete(fl.folders, folderName)
   199  }
   200  
   201  var _ fs.Handle = (*FolderList)(nil)
   202  
   203  var _ fs.HandleReadDirAller = (*FolderList)(nil)
   204  
   205  // ReadDirAll implements the ReadDirAll interface.
   206  func (fl *FolderList) ReadDirAll(ctx context.Context) (res []fuse.Dirent, err error) {
   207  	fl.fs.vlog.CLogf(ctx, libkb.VLog1, "FL ReadDirAll")
   208  	defer func() {
   209  		err = fl.fs.processError(ctx, libkbfs.ReadMode, err)
   210  	}()
   211  	session, err := fl.fs.config.KBPKI().GetCurrentSession(ctx)
   212  	isLoggedIn := err == nil
   213  
   214  	var favs []favorites.Folder
   215  	if isLoggedIn {
   216  		favs, err = fl.fs.config.KBFSOps().GetFavorites(ctx)
   217  		if err != nil {
   218  			return nil, err
   219  		}
   220  	}
   221  
   222  	res = make([]fuse.Dirent, 0, len(favs))
   223  	for _, fav := range favs {
   224  		if fav.Type != fl.tlfType {
   225  			continue
   226  		}
   227  		pname, err := tlf.CanonicalToPreferredName(
   228  			session.Name, tlf.CanonicalName(fav.Name))
   229  		if err != nil {
   230  			fl.fs.log.Errorf("CanonicalToPreferredName: %q %v", fav.Name, err)
   231  			continue
   232  		}
   233  		res = append(res, fuse.Dirent{
   234  			Type: fuse.DT_Dir,
   235  			Name: string(pname),
   236  		})
   237  	}
   238  	return res, nil
   239  }
   240  
   241  var _ fs.NodeRemover = (*FolderList)(nil)
   242  
   243  // Remove implements the fs.NodeRemover interface for FolderList.
   244  func (fl *FolderList) Remove(ctx context.Context, req *fuse.RemoveRequest) (err error) {
   245  	fl.fs.vlog.CLogf(ctx, libkb.VLog1, "FolderList Remove %s", req.Name)
   246  	defer func() { err = fl.fs.processError(ctx, libkbfs.WriteMode, err) }()
   247  
   248  	h, err := tlfhandle.ParseHandlePreferredQuick(
   249  		ctx, fl.fs.config.KBPKI(), fl.fs.config, req.Name, fl.tlfType)
   250  
   251  	switch err := errors.Cause(err).(type) {
   252  	case nil:
   253  		func() {
   254  			fl.mu.Lock()
   255  			defer fl.mu.Unlock()
   256  			if tlf, ok := fl.folders[req.Name]; ok {
   257  				// Fake future attr calls for this TLF until the user
   258  				// actually opens the TLF again, because some OSes (*cough
   259  				// OS X cough*) like to do immediate lookup/attr calls
   260  				// right after doing a remove, which would otherwise end
   261  				// up re-adding the favorite.
   262  				tlf.clearStoredDir()
   263  			}
   264  		}()
   265  
   266  		// TODO how to handle closing down the folderbranchops
   267  		// object? Open files may still exist long after removing
   268  		// the favorite.
   269  		return fl.fs.config.KBFSOps().DeleteFavorite(ctx, h.ToFavorite())
   270  
   271  	case idutil.TlfNameNotCanonical:
   272  		return nil
   273  
   274  	case idutil.NoSuchNameError, idutil.BadTLFNameError,
   275  		tlf.NoSuchUserError, idutil.NoSuchUserError:
   276  		// Invalid TLF.
   277  		return fuse.ENOENT
   278  
   279  	default:
   280  		return err
   281  	}
   282  }
   283  
   284  var _ fs.NodeSymlinker = (*FolderList)(nil)
   285  
   286  // Symlink implements the fs.NodeSymlinker interface for FolderList.
   287  func (fl *FolderList) Symlink(
   288  	_ context.Context, _ *fuse.SymlinkRequest) (fs.Node, error) {
   289  	return nil, fuse.ENOTSUP
   290  }
   291  
   292  var _ fs.NodeLinker = (*FolderList)(nil)
   293  
   294  // Link implements the fs.NodeLinker interface for FolderList.
   295  func (fl *FolderList) Link(
   296  	_ context.Context, _ *fuse.LinkRequest, _ fs.Node) (fs.Node, error) {
   297  	return nil, fuse.ENOTSUP
   298  }
   299  
   300  func (fl *FolderList) updateTlfName(ctx context.Context, oldName string,
   301  	newName string) {
   302  	ok := func() bool {
   303  		fl.mu.Lock()
   304  		defer fl.mu.Unlock()
   305  		tlf, ok := fl.folders[oldName]
   306  		if !ok {
   307  			return false
   308  		}
   309  
   310  		fl.fs.vlog.CLogf(
   311  			ctx, libkb.VLog1, "Folder name updated: %s -> %s", oldName, newName)
   312  		delete(fl.folders, oldName)
   313  		fl.folders[newName] = tlf
   314  		return true
   315  	}()
   316  	if !ok {
   317  		return
   318  	}
   319  
   320  	if err := fl.fs.fuse.InvalidateEntry(fl, oldName); err != nil {
   321  		// TODO we have no mechanism to do anything about this
   322  		fl.fs.log.CErrorf(ctx, "FUSE invalidate error for oldName=%s: %v",
   323  			oldName, err)
   324  	}
   325  	if err := fl.fs.fuse.InvalidateEntry(fl, newName); err != nil {
   326  		// TODO we have no mechanism to do anything about this
   327  		fl.fs.log.CErrorf(ctx, "FUSE invalidate error for newName=%s: %v",
   328  			newName, err)
   329  	}
   330  }
   331  
   332  // update things after user changed.
   333  func (fl *FolderList) userChanged(ctx context.Context, _, newUser kbname.NormalizedUsername) {
   334  	var fs []*Folder
   335  	func() {
   336  		fl.mu.Lock()
   337  		defer fl.mu.Unlock()
   338  		for _, tlf := range fl.folders {
   339  			fs = append(fs, tlf.folder)
   340  		}
   341  	}()
   342  	for _, f := range fs {
   343  		f.TlfHandleChange(ctx, nil)
   344  	}
   345  	if newUser != kbname.NormalizedUsername("") {
   346  		fl.fs.config.KBFSOps().ForceFastForward(ctx)
   347  	}
   348  }
   349  
   350  func (fl *FolderList) openFileCount() (ret int64) {
   351  	fl.mu.Lock()
   352  	defer fl.mu.Unlock()
   353  	for _, tlf := range fl.folders {
   354  		ret += tlf.openFileCount()
   355  	}
   356  	return ret + int64(len(fl.folders))
   357  }
   358  
   359  // Forget kernel reference to this node.
   360  func (fl *FolderList) Forget() {
   361  	fl.fs.root.forgetFolderList(fl.tlfType)
   362  }