github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libfuse/file.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  	"sync"
    14  
    15  	"bazil.org/fuse"
    16  	"bazil.org/fuse/fs"
    17  	"github.com/keybase/client/go/kbfs/data"
    18  	"github.com/keybase/client/go/kbfs/libcontext"
    19  	"github.com/keybase/client/go/kbfs/libkbfs"
    20  	"github.com/keybase/client/go/libkb"
    21  	"golang.org/x/net/context"
    22  )
    23  
    24  type eiCache struct {
    25  	ei    data.EntryInfo
    26  	reqID string
    27  }
    28  
    29  // eiCacheHolder caches the EntryInfo for a particular reqID. It's used for the
    30  // Attr call after Create. This should only be used for operations with same
    31  // reqID.
    32  type eiCacheHolder struct {
    33  	mu    sync.Mutex
    34  	cache *eiCache
    35  }
    36  
    37  func (c *eiCacheHolder) destroy() {
    38  	c.mu.Lock()
    39  	defer c.mu.Unlock()
    40  	c.cache = nil
    41  }
    42  
    43  func (c *eiCacheHolder) getAndDestroyIfMatches(reqID string) (ei *data.EntryInfo) {
    44  	c.mu.Lock()
    45  	defer c.mu.Unlock()
    46  	if c.cache != nil && c.cache.reqID == reqID {
    47  		ei = &c.cache.ei
    48  		c.cache = nil
    49  	}
    50  	return ei
    51  }
    52  
    53  func (c *eiCacheHolder) set(reqID string, ei data.EntryInfo) {
    54  	c.mu.Lock()
    55  	defer c.mu.Unlock()
    56  	c.cache = &eiCache{
    57  		ei:    ei,
    58  		reqID: reqID,
    59  	}
    60  }
    61  
    62  // File represents KBFS files.
    63  type File struct {
    64  	folder *Folder
    65  	node   libkbfs.Node
    66  	inode  uint64
    67  	XattrHandler
    68  
    69  	eiCache eiCacheHolder
    70  }
    71  
    72  var _ fs.Node = (*File)(nil)
    73  
    74  func (f *File) fillAttrWithMode(
    75  	ctx context.Context, ei *data.EntryInfo, a *fuse.Attr) (err error) {
    76  	if err = f.folder.fillAttrWithUIDAndWritePerm(
    77  		ctx, f.node, ei, a); err != nil {
    78  		return err
    79  	}
    80  	a.Mode |= 0400
    81  	if ei.Type == data.Exec {
    82  		a.Mode |= 0100
    83  	}
    84  
    85  	a.Inode = f.inode
    86  	return nil
    87  }
    88  
    89  // Attr implements the fs.Node interface for File.
    90  func (f *File) Attr(ctx context.Context, a *fuse.Attr) (err error) {
    91  	ctx = f.folder.fs.config.MaybeStartTrace(
    92  		ctx, "File.Attr", f.node.GetBasename().String())
    93  	defer func() { f.folder.fs.config.MaybeFinishTrace(ctx, err) }()
    94  
    95  	f.folder.fs.vlog.CLogf(ctx, libkb.VLog1, "File Attr")
    96  	defer func() { err = f.folder.processError(ctx, libkbfs.ReadMode, err) }()
    97  
    98  	if reqID, ok := ctx.Value(CtxIDKey).(string); ok {
    99  		if ei := f.eiCache.getAndDestroyIfMatches(reqID); ei != nil {
   100  			return f.fillAttrWithMode(ctx, ei, a)
   101  		}
   102  	}
   103  
   104  	// This fits in situation 1 as described in libkbfs/delayed_cancellation.go
   105  	err = libcontext.EnableDelayedCancellationWithGracePeriod(
   106  		ctx, f.folder.fs.config.DelayedCancellationGracePeriod())
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	return f.attr(ctx, a)
   112  }
   113  
   114  func (f *File) attr(ctx context.Context, a *fuse.Attr) (err error) {
   115  	de, err := f.folder.fs.config.KBFSOps().Stat(ctx, f.node)
   116  	if err != nil {
   117  		if isNoSuchNameError(err) {
   118  			return fuse.ESTALE
   119  		}
   120  		return err
   121  	}
   122  
   123  	f.node.FillCacheDuration(&a.Valid)
   124  
   125  	return f.fillAttrWithMode(ctx, &de, a)
   126  }
   127  
   128  var _ fs.NodeAccesser = (*File)(nil)
   129  
   130  // Access implements the fs.NodeAccesser interface for File. This is necessary
   131  // for macOS to correctly identify plaintext files as plaintext. If not
   132  // implemented, bazil-fuse returns a nil error for every call, so when macOS
   133  // checks for executable bit using Access (instead of Attr!), it gets a
   134  // success, which makes it think the file is executable, yielding a "Unix
   135  // executable" UTI.
   136  func (f *File) Access(ctx context.Context, r *fuse.AccessRequest) (err error) {
   137  	ctx = f.folder.fs.config.MaybeStartTrace(
   138  		ctx, "File.Access", f.node.GetBasename().String())
   139  	defer func() { f.folder.fs.config.MaybeFinishTrace(ctx, err) }()
   140  
   141  	if int(r.Uid) != os.Getuid() &&
   142  		// Finder likes to use UID 0 for some operations. osxfuse already allows
   143  		// ACCESS and GETXATTR requests from root to go through. This allows root
   144  		// in ACCESS handler. See KBFS-1733 for more details.
   145  		int(r.Uid) != 0 {
   146  		// short path: not accessible by anybody other than root or the user who
   147  		// executed the kbfsfuse process.
   148  		return fuse.EPERM
   149  	}
   150  
   151  	if r.Mask&03 == 0 {
   152  		// Since we only check for w and x bits, we can return nil early here.
   153  		return nil
   154  	}
   155  
   156  	if r.Mask&01 != 0 {
   157  		ei, err := f.folder.fs.config.KBFSOps().Stat(ctx, f.node)
   158  		if err != nil {
   159  			if isNoSuchNameError(err) {
   160  				return fuse.ESTALE
   161  			}
   162  			return err
   163  		}
   164  		if ei.Type != data.Exec {
   165  			return fuse.EPERM
   166  		}
   167  	}
   168  
   169  	if r.Mask&02 != 0 {
   170  		iw, err := f.folder.isWriter(ctx)
   171  		if err != nil {
   172  			return err
   173  		}
   174  		if !iw {
   175  			return fuse.EPERM
   176  		}
   177  	}
   178  
   179  	return nil
   180  }
   181  
   182  var _ fs.NodeFsyncer = (*File)(nil)
   183  
   184  func (f *File) sync(ctx context.Context) error {
   185  	f.eiCache.destroy()
   186  	err := f.folder.fs.config.KBFSOps().SyncAll(ctx, f.node.GetFolderBranch())
   187  	if err != nil {
   188  		return err
   189  	}
   190  
   191  	return nil
   192  }
   193  
   194  // Fsync implements the fs.NodeFsyncer interface for File.
   195  func (f *File) Fsync(ctx context.Context, req *fuse.FsyncRequest) (err error) {
   196  	ctx, maybeUnmounting, cancel := wrapCtxWithShorterTimeoutForUnmount(ctx, f.folder.fs.log, int(req.Pid))
   197  	defer cancel()
   198  	if maybeUnmounting {
   199  		f.folder.fs.log.CInfof(ctx, "Fsync: maybeUnmounting=true")
   200  	}
   201  
   202  	ctx = f.folder.fs.config.MaybeStartTrace(
   203  		ctx, "File.Fsync", f.node.GetBasename().String())
   204  	defer func() { f.folder.fs.config.MaybeFinishTrace(ctx, err) }()
   205  
   206  	f.folder.fs.vlog.CLogf(ctx, libkb.VLog1, "File Fsync")
   207  	defer func() { err = f.folder.processError(ctx, libkbfs.WriteMode, err) }()
   208  
   209  	if !maybeUnmounting {
   210  		// This fits in situation 1 as described in
   211  		// libkbfs/delayed_cancellation.go
   212  		err = libcontext.EnableDelayedCancellationWithGracePeriod(
   213  			ctx, f.folder.fs.config.DelayedCancellationGracePeriod())
   214  		if err != nil {
   215  			return err
   216  		}
   217  	}
   218  
   219  	return f.sync(ctx)
   220  }
   221  
   222  var _ fs.Handle = (*File)(nil)
   223  
   224  var _ fs.HandleReader = (*File)(nil)
   225  
   226  // Read implements the fs.HandleReader interface for File.
   227  func (f *File) Read(ctx context.Context, req *fuse.ReadRequest,
   228  	resp *fuse.ReadResponse) (err error) {
   229  	off := req.Offset
   230  	sz := cap(resp.Data)
   231  	ctx = f.folder.fs.config.MaybeStartTrace(ctx, "File.Read",
   232  		fmt.Sprintf("%s off=%d sz=%d", f.node.GetBasename(), off, sz))
   233  	defer func() { f.folder.fs.config.MaybeFinishTrace(ctx, err) }()
   234  
   235  	f.folder.fs.vlog.CLogf(ctx, libkb.VLog1, "File Read off=%d sz=%d", off, sz)
   236  	defer func() { err = f.folder.processError(ctx, libkbfs.ReadMode, err) }()
   237  
   238  	n, err := f.folder.fs.config.KBFSOps().Read(
   239  		ctx, f.node, resp.Data[:sz], off)
   240  	if err != nil {
   241  		return err
   242  	}
   243  	resp.Data = resp.Data[:n]
   244  	return nil
   245  }
   246  
   247  var _ fs.HandleWriter = (*File)(nil)
   248  
   249  // Write implements the fs.HandleWriter interface for File.
   250  func (f *File) Write(ctx context.Context, req *fuse.WriteRequest,
   251  	resp *fuse.WriteResponse) (err error) {
   252  	sz := len(req.Data)
   253  	ctx = f.folder.fs.config.MaybeStartTrace(ctx, "File.Write",
   254  		fmt.Sprintf("%s sz=%d", f.node.GetBasename(), sz))
   255  	defer func() { f.folder.fs.config.MaybeFinishTrace(ctx, err) }()
   256  
   257  	f.folder.fs.vlog.CLogf(ctx, libkb.VLog1, "File Write sz=%d ", sz)
   258  	defer func() { err = f.folder.processError(ctx, libkbfs.WriteMode, err) }()
   259  
   260  	f.eiCache.destroy()
   261  	if err := f.folder.fs.config.KBFSOps().Write(
   262  		ctx, f.node, req.Data, req.Offset); err != nil {
   263  		return err
   264  	}
   265  	resp.Size = len(req.Data)
   266  	return nil
   267  }
   268  
   269  var _ fs.NodeSetattrer = (*File)(nil)
   270  
   271  // Setattr implements the fs.NodeSetattrer interface for File.
   272  func (f *File) Setattr(ctx context.Context, req *fuse.SetattrRequest,
   273  	resp *fuse.SetattrResponse) (err error) {
   274  	valid := req.Valid
   275  	ctx = f.folder.fs.config.MaybeStartTrace(ctx, "File.SetAttr",
   276  		fmt.Sprintf("%s %s", f.node.GetBasename(), valid))
   277  	defer func() { f.folder.fs.config.MaybeFinishTrace(ctx, err) }()
   278  
   279  	f.folder.fs.vlog.CLogf(ctx, libkb.VLog1, "File SetAttr %s", valid)
   280  	defer func() { err = f.folder.processError(ctx, libkbfs.WriteMode, err) }()
   281  
   282  	f.eiCache.destroy()
   283  
   284  	if valid.Size() {
   285  		if err := f.folder.fs.config.KBFSOps().Truncate(
   286  			ctx, f.node, req.Size); err != nil {
   287  			return err
   288  		}
   289  		valid &^= fuse.SetattrSize
   290  	}
   291  
   292  	if valid.Mode() {
   293  		// Unix has 3 exec bits, KBFS has one; we follow the user-exec bit.
   294  		exec := req.Mode&0100 != 0
   295  		err := f.folder.fs.config.KBFSOps().SetEx(
   296  			ctx, f.node, exec)
   297  		if err != nil {
   298  			return err
   299  		}
   300  		valid &^= fuse.SetattrMode
   301  	}
   302  
   303  	if valid.Mtime() {
   304  		err := f.folder.fs.config.KBFSOps().SetMtime(
   305  			ctx, f.node, &req.Mtime)
   306  		if err != nil {
   307  			return err
   308  		}
   309  		valid &^= fuse.SetattrMtime | fuse.SetattrMtimeNow
   310  	}
   311  
   312  	if valid.Uid() || valid.Gid() {
   313  		// You can't set the UID/GID on KBFS files, but we don't want
   314  		// to return ENOSYS because that causes scary warnings on some
   315  		// programs like mv.  Instead ignore it, print a debug
   316  		// message, and advertise this behavior on the
   317  		// "understand_kbfs" doc online.
   318  		f.folder.fs.vlog.CLogf(
   319  			ctx, libkb.VLog1, "Ignoring unsupported attempt to set "+
   320  				"the UID/GID on a file")
   321  		valid &^= fuse.SetattrUid | fuse.SetattrGid
   322  	}
   323  
   324  	// KBFS has no concept of persistent atime; explicitly don't handle it
   325  	valid &^= fuse.SetattrAtime | fuse.SetattrAtimeNow
   326  
   327  	// things we don't need to explicitly handle
   328  	valid &^= fuse.SetattrLockOwner | fuse.SetattrHandle
   329  
   330  	// KBFS has no concept of chflags(2); explicitly ignore those
   331  	valid &^= fuse.SetattrFlags
   332  
   333  	if valid != 0 {
   334  		// don't let an unhandled operation slip by without error
   335  		f.folder.fs.log.CInfof(ctx, "Setattr did not handle %v", valid)
   336  		return fuse.ENOSYS
   337  	}
   338  
   339  	return f.attr(ctx, &resp.Attr)
   340  }
   341  
   342  var _ fs.NodeForgetter = (*File)(nil)
   343  
   344  // Forget kernel reference to this node.
   345  func (f *File) Forget() {
   346  	f.eiCache.destroy()
   347  	f.folder.forgetNode(f.node)
   348  }