github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libfuse/quarantine_darwin.go (about)

     1  // Copyright 2018 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  	"strconv"
    13  	"time"
    14  
    15  	"bazil.org/fuse"
    16  	"github.com/keybase/client/go/kbfs/idutil"
    17  	"github.com/keybase/client/go/kbfs/libcontext"
    18  	"github.com/keybase/client/go/kbfs/libkbfs"
    19  	"github.com/pkg/errors"
    20  	ldberrors "github.com/syndtr/goleveldb/leveldb/errors"
    21  	"golang.org/x/net/context"
    22  )
    23  
    24  const quarantineXattrName = "com.apple.quarantine"
    25  
    26  // There are much more than this, but a call using Apple's library to set
    27  // kLSQuarantineTypeOtherDownload produces this flag.
    28  const _kLSQuarantineTypeOtherDownload = "0081;"
    29  
    30  const quarantineAgentName = ";Keybase;"
    31  
    32  func makeQuarantine(timestamp time.Time) []byte {
    33  	// Omit the identifier in the end. Example:
    34  	// []byte("0081;5bdb56a3;Keybase;")
    35  	return append(
    36  		strconv.AppendInt(
    37  			[]byte(_kLSQuarantineTypeOtherDownload), timestamp.Unix(), 16),
    38  		quarantineAgentName...)
    39  }
    40  
    41  // QuarantineXattrHandler implements bazil.org/fuse/fs.NodeGetxattrer,
    42  // bazil.org/fuse/fs.NodeSetxattrer, and bazil.org/fuse/fs.NodeRemovexattrer,
    43  // that only handles a single xattr quarantineXattrName (com.apple.quarantine).
    44  // For all other requests, we return fuse.ENOTSUP which causes the OS to handle
    45  // it by creating and interacting with ._ files.
    46  type QuarantineXattrHandler struct {
    47  	node   libkbfs.Node
    48  	folder *Folder
    49  }
    50  
    51  // NewQuarantineXattrHandler returns a handler that handles
    52  // com.apple.quarantine, but returns fuse.ENOTSUP for all other xattrs.
    53  func NewQuarantineXattrHandler(node libkbfs.Node, folder *Folder,
    54  ) XattrHandler {
    55  	return &QuarantineXattrHandler{
    56  		node:   node,
    57  		folder: folder,
    58  	}
    59  }
    60  
    61  var _ XattrHandler = (*QuarantineXattrHandler)(nil)
    62  
    63  // Getxattr implements the fs.NodeGetxattrer interface.
    64  func (h *QuarantineXattrHandler) Getxattr(ctx context.Context,
    65  	req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) (err error) {
    66  	ctx = h.folder.fs.config.MaybeStartTrace(ctx, "QuarantineXattrHandler.Getxattr",
    67  		fmt.Sprintf("%s %s", h.node.GetBasename(), req.Name))
    68  	defer func() { h.folder.fs.config.MaybeFinishTrace(ctx, err) }()
    69  
    70  	h.folder.fs.log.CDebugf(ctx,
    71  		"QuarantineXattrHandler Getxattr %s %s", h.node.GetBasename(), req.Name)
    72  	defer func() { err = h.folder.processError(ctx, libkbfs.ReadMode, err) }()
    73  
    74  	if req.Name != quarantineXattrName {
    75  		// The request is not about quarantine. Let the OS fallback to ._ file
    76  		// based method.
    77  		return fuse.ENOTSUP
    78  	}
    79  
    80  	xattr, err := h.folder.fs.config.XattrStore().GetXattr(
    81  		ctx, h.node.GetBlockID(), libkbfs.XattrAppleQuarantine)
    82  	switch errors.Cause(err) {
    83  	case nil:
    84  		if len(xattr) == 0 {
    85  			return fuse.ENOATTR
    86  		}
    87  		resp.Xattr = xattr
    88  		return nil
    89  	case ldberrors.ErrNotFound:
    90  		// We don't have an Xattr value stored locally, so just use a
    91  		// quarantine value.
    92  
    93  		// Stat on the node to get the Mtime.
    94  
    95  		// This fits in situation 1 as described in
    96  		// libkbfs/delayed_cancellation.go
    97  		if err = libcontext.EnableDelayedCancellationWithGracePeriod(ctx,
    98  			h.folder.fs.config.DelayedCancellationGracePeriod()); err != nil {
    99  			return err
   100  		}
   101  		de, err := h.folder.fs.config.KBFSOps().Stat(ctx, h.node)
   102  		if err != nil {
   103  			if _, ok := err.(idutil.NoSuchNameError); ok {
   104  				// The node is not found, so just return ENOTSUP
   105  				return fuse.ENOTSUP
   106  			}
   107  			return err
   108  		}
   109  
   110  		resp.Xattr = makeQuarantine(time.Unix(0, de.Mtime))
   111  		return nil
   112  	default:
   113  		return err
   114  	}
   115  }
   116  
   117  // Setxattr implements the fs.NodeSetxattrer interface.
   118  func (h *QuarantineXattrHandler) Setxattr(ctx context.Context,
   119  	req *fuse.SetxattrRequest) (err error) {
   120  	ctx = h.folder.fs.config.MaybeStartTrace(ctx, "QuarantineXattrHandler.Setxattr",
   121  		fmt.Sprintf("%s %s", h.node.GetBasename(), req.Name))
   122  	defer func() { h.folder.fs.config.MaybeFinishTrace(ctx, err) }()
   123  
   124  	h.folder.fs.log.CDebugf(ctx, "QuarantineXattrHandler Setxattr %s", req.Name)
   125  	defer func() { err = h.folder.processError(ctx, libkbfs.ReadMode, err) }()
   126  
   127  	if req.Name != quarantineXattrName {
   128  		// The request is not about quarantine. Let the OS fallback to ._ file
   129  		// based method.
   130  		return fuse.ENOTSUP
   131  	}
   132  
   133  	return h.folder.fs.config.XattrStore().SetXattr(ctx,
   134  		h.node.GetBlockID(), libkbfs.XattrAppleQuarantine, req.Xattr)
   135  }
   136  
   137  // Removexattr implements the fs.NodeRemovexattrer interface.
   138  func (h *QuarantineXattrHandler) Removexattr(
   139  	ctx context.Context, req *fuse.RemovexattrRequest) error {
   140  	if req.Name != quarantineXattrName {
   141  		// The request is not about quarantine. Let the OS fallback to ._ file
   142  		// based method.
   143  		return fuse.ENOTSUP
   144  	}
   145  
   146  	return h.folder.fs.config.XattrStore().SetXattr(ctx,
   147  		h.node.GetBlockID(), libkbfs.XattrAppleQuarantine, nil)
   148  }