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