github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/libfuse/mounter.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 "bytes" 12 "errors" 13 "fmt" 14 "os" 15 "os/exec" 16 "path" 17 "runtime" 18 "strings" 19 20 "bazil.org/fuse" 21 "github.com/keybase/client/go/kbconst" 22 "github.com/keybase/client/go/libkb" 23 "github.com/keybase/client/go/logger" 24 keybase1 "github.com/keybase/client/go/protocol/keybase1" 25 ) 26 27 type mounter struct { 28 options StartOptions 29 c *fuse.Conn 30 log logger.Logger 31 runMode kbconst.RunMode 32 } 33 34 // fuseMount tries to mount the mountpoint. 35 // On a force mount then unmount, re-mount if unsuccessful 36 func (m *mounter) Mount() (err error) { 37 defer func() { 38 if err != nil { 39 msg := fmt.Sprintf("KBFS failed to FUSE mount at %s: %s", m.options.MountPoint, err) 40 fmt.Println(msg) 41 m.log.Warning(msg) 42 } 43 }() 44 45 m.c, err = fuseMountDir(m.options.MountPoint, m.options.PlatformParams) 46 // Exit if we were successful or we are not a force mounting on error. 47 // Otherwise, try unmounting and mounting again. 48 if err == nil || !m.options.ForceMount { 49 return err 50 } 51 52 // Mount failed, let's try to unmount and then try mounting again, even 53 // if unmounting errors here. 54 _ = m.Unmount() 55 56 // In case we are on darwin, ask the installer to reinstall the mount dir 57 // and try again as the last resort. This specifically fixes a situation 58 // where /keybase gets created and owned by root after Keybase app is 59 // started, and `kbfs` later fails to mount because of a permission error. 60 m.reinstallMountDirIfPossible() 61 m.c, err = fuseMountDir(m.options.MountPoint, m.options.PlatformParams) 62 63 return err 64 } 65 66 func fuseMountDir(dir string, platformParams PlatformParams) (*fuse.Conn, error) { 67 // Create mountdir directory on Linux. 68 switch libkb.RuntimeGroup() { 69 case keybase1.RuntimeGroup_LINUXLIKE: 70 // Inherit permissions from containing directory and umask. 71 err := os.MkdirAll(dir, os.ModeDir|os.ModePerm) 72 if err != nil { 73 return nil, err 74 } 75 default: 76 } 77 78 fi, err := os.Stat(dir) 79 if err != nil { 80 return nil, err 81 } 82 if !fi.IsDir() { 83 return nil, errors.New("mount point is not a directory") 84 } 85 options, err := getPlatformSpecificMountOptions(dir, platformParams) 86 if err != nil { 87 return nil, err 88 } 89 c, err := fuse.Mount(dir, options...) 90 if err != nil { 91 err = translatePlatformSpecificError(err, platformParams) 92 return nil, err 93 } 94 return c, nil 95 } 96 97 func isFusermountMountNotFoundError(output []byte, err error) bool { 98 if err == nil { 99 return false 100 } 101 return bytes.Contains(output, []byte("not found in /etc/mtab")) 102 } 103 104 func (m *mounter) Unmount() (err error) { 105 m.log.Info("Unmounting") 106 dir := m.options.MountPoint 107 // Try normal unmount 108 switch runtime.GOOS { 109 case "darwin": // no ios 110 _, err = exec.Command("/sbin/umount", dir).Output() 111 case "linux": 112 fusermountOutput, fusermountErr := exec.Command("fusermount", "-u", dir).CombinedOutput() 113 // Only clean up mountdir on a clean unmount. 114 if fusermountErr == nil { 115 m.log.Info("Successfully unmounted") 116 defer m.DeleteMountdirIfEmpty() 117 } 118 if fusermountErr != nil { 119 // Ignore errors where the mount was never mounted in the first place 120 if isFusermountMountNotFoundError(fusermountOutput, fusermountErr) { 121 m.log.Info("Ignoring mount-not-found fusermount error") 122 } else { 123 returnErr := fmt.Errorf("fusermount unmount resulted in unknown error: output=%v; err=%s", fusermountOutput, fusermountErr) 124 m.log.Warning(returnErr.Error()) 125 err = returnErr 126 } 127 } 128 default: 129 err = fuse.Unmount(dir) 130 } 131 if err != nil && m.options.ForceMount { 132 // Unmount failed, so let's try and force it. 133 switch runtime.GOOS { 134 case "darwin": // no ios 135 _, err = exec.Command( 136 "/usr/sbin/diskutil", "unmountDisk", "force", dir).Output() 137 case "linux": 138 // Lazy unmount; will unmount when KBFS is no longer in use. 139 _, err = exec.Command("fusermount", "-u", "-z", dir).Output() 140 default: 141 err = errors.New("Forced unmount is not supported on this platform yet") 142 } 143 } 144 if execErr, ok := err.(*exec.ExitError); ok && execErr.Stderr != nil { 145 err = fmt.Errorf("%s (%s)", execErr, execErr.Stderr) 146 } 147 return 148 } 149 150 func (m *mounter) DeleteMountdirIfEmpty() { 151 m.log.Info("Deleting mountdir") 152 // os.Remove refuses to delete non-empty directories. 153 err := os.Remove(m.options.MountPoint) 154 if err != nil { 155 m.log.Errorf("Unable to delete mountdir: %s", err) 156 } 157 } 158 159 // volumeName returns the first word of the directory (base) name 160 func volumeName(dir string) (string, error) { // nolint 161 volName := path.Base(dir) 162 if volName == "." || volName == "/" { 163 err := fmt.Errorf("Bad volume name: %v", volName) 164 return "", err 165 } 166 s := strings.Split(volName, " ") 167 if len(s) == 0 { 168 return "", fmt.Errorf("Bad volume name: %v", volName) 169 } 170 return s[0], nil 171 }