github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libfuse/mounter_osx.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 darwin 6 // +build darwin 7 8 package libfuse 9 10 // #include <libproc.h> 11 // #include <stdlib.h> 12 // #include <errno.h> 13 import "C" 14 15 import ( 16 "context" 17 "errors" 18 "strconv" 19 "time" 20 "unsafe" 21 22 "bazil.org/fuse" 23 "github.com/keybase/client/go/install/libnativeinstaller" 24 "github.com/keybase/client/go/logger" 25 ) 26 27 var kbfusePath = fuse.OSXFUSEPaths{ 28 DevicePrefix: "/dev/kbfuse", 29 Load: "/Library/Filesystems/kbfuse.fs/Contents/Resources/load_kbfuse", 30 Mount: "/Library/Filesystems/kbfuse.fs/Contents/Resources/mount_kbfuse", 31 DaemonVar: "_FUSE_DAEMON_PATH", 32 } 33 34 func getPlatformSpecificMountOptions(dir string, platformParams PlatformParams) ([]fuse.MountOption, error) { 35 options := []fuse.MountOption{} 36 37 var locationOption fuse.MountOption 38 if platformParams.UseSystemFuse { 39 // Only allow osxfuse 3.x. 40 locationOption = fuse.OSXFUSELocations(fuse.OSXFUSELocationV3) 41 } else { 42 // Only allow kbfuse. 43 locationOption = fuse.OSXFUSELocations(kbfusePath) 44 } 45 options = append(options, locationOption) 46 47 // Volume name option is only used on OSX (ignored on other platforms). 48 volName, err := volumeName(dir) 49 if err != nil { 50 return nil, err 51 } 52 53 options = append(options, fuse.VolumeName(volName)) 54 options = append(options, fuse.ExclCreate()) 55 56 if platformParams.UseLocal { 57 options = append(options, fuse.LocalVolume()) 58 } 59 60 return options, nil 61 } 62 63 // GetPlatformSpecificMountOptionsForTest makes cross-platform tests work 64 func GetPlatformSpecificMountOptionsForTest() []fuse.MountOption { 65 // For now, test with either kbfuse or OSXFUSE for now. 66 // TODO: Consider mandate testing with kbfuse? 67 return []fuse.MountOption{ 68 fuse.OSXFUSELocations(kbfusePath, fuse.OSXFUSELocationV3), 69 fuse.ExclCreate(), 70 71 // We are diabling the 'local' mount option in tests for now since it 72 // causes out tests to leave a bunch of entries behind in Finder's sidebar. 73 // TODO: fix this. 74 // fuse.LocalVolume(), 75 } 76 } 77 78 func translatePlatformSpecificError(err error, platformParams PlatformParams) error { 79 if err == fuse.ErrOSXFUSENotFound { 80 if platformParams.UseSystemFuse { 81 return errors.New( 82 "cannot locate OSXFUSE 3.x") 83 } 84 return errors.New( 85 "cannot locate kbfuse; either install the Keybase " + 86 "app, or install OSXFUSE 3.x " + 87 "and pass in --use-system-fuse") 88 } 89 return err 90 } 91 92 func (m *mounter) reinstallMountDirIfPossible() { 93 err := libnativeinstaller.UninstallMountDir(m.runMode, m.log) 94 m.log.Debug("UninstallMountDir: err=%v", err) 95 err = libnativeinstaller.InstallMountDir(m.runMode, m.log) 96 m.log.Debug("InstallMountDir: err=%v", err) 97 } 98 99 const unmountCallTolerance = time.Second 100 101 var unmountingExecPaths = map[string]bool{ 102 "/usr/sbin/diskutil": true, 103 "/usr/libexec/lsd": true, 104 "/sbin/umount": true, 105 } 106 107 var noop = func() {} 108 109 // pidPath returns the exec path for process pid. Adapted from 110 // https://ops.tips/blog/macos-pid-absolute-path-and-procfs-exploration/ 111 func pidPath(pid int) (path string, err error) { 112 const bufSize = C.PROC_PIDPATHINFO_MAXSIZE 113 buf := C.CString(string(make([]byte, bufSize))) 114 defer C.free(unsafe.Pointer(buf)) 115 116 ret, err := C.proc_pidpath(C.int(pid), unsafe.Pointer(buf), bufSize) 117 if err != nil { 118 return "", err 119 } 120 if ret < 0 { 121 return "", errors.New( 122 "error calling proc_pidpath. exit code: " + strconv.Itoa(int(ret))) 123 } 124 if ret == 0 { 125 return "", errors.New("proc_pidpath returned empty buffer") 126 } 127 128 path = C.GoString(buf) 129 return 130 } 131 132 // wrapCtxWithShorterTimeoutForUnmount wraps ctx witha a timeout of 133 // unmountCallTolerance if pid is /usr/sbin/diskutil, /usr/libexec/lsd, or 134 // /sbin/umount. This is useful for calls that usually happen during unmounting 135 // such as Statfs and Fsync. If we block on those calls, `diskutil umount force 136 // <mnt>` is blocked as well. So make them timeout after 2s to make unmounting 137 // work. 138 func wrapCtxWithShorterTimeoutForUnmount(ctx context.Context, 139 log logger.Logger, pid int) ( 140 newCtx context.Context, maybeUnmounting bool, cancel context.CancelFunc) { 141 p, err := pidPath(pid) 142 if err != nil { 143 return ctx, false, noop 144 } 145 if unmountingExecPaths[p] { 146 log.CDebugf(ctx, "wrapping context with timeout for %s", p) 147 newCtx, cancel = context.WithTimeout(ctx, unmountCallTolerance) 148 return newCtx, true, cancel 149 } 150 return ctx, false, noop 151 }