github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/install/fuse_status_darwin.go (about) 1 // Copyright 2015 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 //go:build darwin && !ios 5 // +build darwin,!ios 6 7 package install 8 9 import ( 10 "fmt" 11 "io" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "regexp" 16 "strings" 17 18 "github.com/keybase/client/go/libkb" 19 keybase1 "github.com/keybase/client/go/protocol/keybase1" 20 "github.com/keybase/go-kext" 21 ) 22 23 const installPath = "/Library/Filesystems/kbfuse.fs" 24 25 // KeybaseFuseStatus returns Fuse status 26 func KeybaseFuseStatus(bundleVersion string, log Log) keybase1.FuseStatus { 27 st := keybase1.FuseStatus{ 28 BundleVersion: bundleVersion, 29 InstallStatus: keybase1.InstallStatus_UNKNOWN, 30 InstallAction: keybase1.InstallAction_UNKNOWN, 31 } 32 33 var kextInfo *kext.Info 34 35 if _, err := os.Stat(installPath); err == nil { 36 st.Path = installPath 37 kextID := "com.github.kbfuse.filesystems.kbfuse" 38 var loadErr error 39 kextInfo, loadErr = kext.LoadInfo(kextID) 40 if loadErr != nil { 41 st.InstallStatus = keybase1.InstallStatus_ERROR 42 st.InstallAction = keybase1.InstallAction_REINSTALL 43 st.Status = keybase1.Status{Code: libkb.SCGeneric, Name: "INSTALL_ERROR", Desc: fmt.Sprintf("Error loading kext info: %s", loadErr)} 44 return st 45 } 46 if kextInfo == nil { 47 log.Debug("No kext info available (kext not loaded)") 48 // This means the kext isn't loaded, which is ok, kbfs will call 49 // load_kbfuse when it starts up. 50 // We have to get the version from the installed plist. 51 installedVersion, fivErr := fuseInstallVersion(log) 52 if fivErr != nil { 53 st.InstallStatus = keybase1.InstallStatus_ERROR 54 st.InstallAction = keybase1.InstallAction_REINSTALL 55 st.Status = keybase1.Status{Code: libkb.SCGeneric, Name: "INSTALL_ERROR", Desc: fmt.Sprintf("Error loading (plist) info: %s", fivErr)} 56 return st 57 } 58 if installedVersion != "" { 59 kextInfo = &kext.Info{ 60 Version: installedVersion, 61 Started: false, 62 } 63 } 64 } 65 66 // Installed 67 st.KextID = kextID 68 } 69 70 // If neither is found, we have no install 71 if st.KextID == "" || kextInfo == nil { 72 st.InstallStatus = keybase1.InstallStatus_NOT_INSTALLED 73 st.InstallAction = keybase1.InstallAction_INSTALL 74 return st 75 } 76 77 // Try to get mount info, it's non-critical if we fail though. 78 mountInfos, err := mountInfo("kbfuse") 79 if err != nil { 80 log.Errorf("Error trying to read mount info: %s", err) 81 } 82 st.MountInfos = mountInfos 83 84 st.Version = kextInfo.Version 85 st.KextStarted = kextInfo.Started 86 87 installStatus, installAction, status := ResolveInstallStatus(st.Version, st.BundleVersion, "", log) 88 st.InstallStatus = installStatus 89 st.InstallAction = installAction 90 st.Status = status 91 92 return st 93 } 94 95 func mountInfo(fstype string) ([]keybase1.FuseMountInfo, error) { 96 out, err := exec.Command("/sbin/mount", "-t", fstype).Output() 97 if err != nil { 98 return nil, err 99 } 100 lines := strings.Split(string(out), "\n") 101 mountInfos := []keybase1.FuseMountInfo{} 102 for _, line := range lines { 103 if strings.TrimSpace(line) == "" { 104 continue 105 } 106 info := strings.SplitN(line, " ", 4) 107 path := "" 108 if len(info) >= 2 { 109 path = info[2] 110 } 111 mountInfos = append(mountInfos, keybase1.FuseMountInfo{ 112 Fstype: fstype, 113 Path: path, 114 Output: line, 115 }) 116 } 117 return mountInfos, nil 118 } 119 120 func findStringInPlist(key string, plistData []byte, log Log) string { 121 // Hack to parse plist, instead of parsing we'll use a regex 122 res := fmt.Sprintf(`<key>%s<\/key>\s*<string>([\S ]+)<\/string>`, key) 123 re := regexp.MustCompile(res) 124 submatch := re.FindStringSubmatch(string(plistData)) 125 if len(submatch) == 2 { 126 return submatch[1] 127 } 128 log.Debug("No key (%s) found", key) 129 return "" 130 } 131 132 func loadPlist(plistPath string, log Log) ([]byte, error) { 133 if _, err := os.Stat(plistPath); os.IsNotExist(err) { 134 log.Debug("No plist found: %s", plistPath) 135 return nil, err 136 } 137 log.Debug("Loading plist: %s", plistPath) 138 plistFile, err := os.Open(plistPath) 139 defer func() { 140 if err := plistFile.Close(); err != nil { 141 log.Debug("unable to close file: %s", err) 142 } 143 }() 144 if err != nil { 145 return nil, err 146 } 147 return io.ReadAll(plistFile) 148 } 149 150 func fuseInstallVersion(log Log) (string, error) { 151 plistPath := filepath.Join(installPath, "Contents/Info.plist") 152 plistData, err := loadPlist(plistPath, log) 153 if err != nil { 154 return "", err 155 } 156 return findStringInPlist("CFBundleVersion", plistData, log), nil 157 }