Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go/client/cmd_fuse_osx.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func NewCmdFuseStatus(cl *libcmdline.CommandLine, g *libkb.GlobalContext) cli.Co
Usage: "Bundle version",
},
},
Usage: "Status for fuse, including for installing or updating",
Usage: "Status for the macOS filesystem driver (FSKit)",
Action: func(c *cli.Context) {
cl.SetLogForward(libcmdline.LogForwardNone)
cl.SetForkCmd(libcmdline.NoFork)
Expand Down
99 changes: 36 additions & 63 deletions go/install/fuse_status_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,21 @@ package install

import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"

"github.com/keybase/client/go/install/libnativeinstaller"
"github.com/keybase/client/go/libkb"
keybase1 "github.com/keybase/client/go/protocol/keybase1"
"github.com/keybase/go-kext"
)

const installPath = "/Library/Filesystems/kbfuse.fs"
const (
installPath = "/Library/Filesystems/keybase.fs"
driverID = "com.keybase.filesystems.kbfs.fskit"
)

// KeybaseFuseStatus returns Fuse status
func KeybaseFuseStatus(bundleVersion string, log Log) keybase1.FuseStatus {
Expand All @@ -30,59 +32,34 @@ func KeybaseFuseStatus(bundleVersion string, log Log) keybase1.FuseStatus {
InstallAction: keybase1.InstallAction_UNKNOWN,
}

var kextInfo *kext.Info

if _, err := os.Stat(installPath); err == nil {
st.Path = installPath
kextID := "com.github.kbfuse.filesystems.kbfuse"
var loadErr error
kextInfo, loadErr = kext.LoadInfo(kextID)
if loadErr != nil {
st.InstallStatus = keybase1.InstallStatus_ERROR
st.InstallAction = keybase1.InstallAction_REINSTALL
st.Status = keybase1.Status{Code: libkb.SCGeneric, Name: "INSTALL_ERROR", Desc: fmt.Sprintf("Error loading kext info: %s", loadErr)}
return st
}
if kextInfo == nil {
log.Debug("No kext info available (kext not loaded)")
// This means the kext isn't loaded, which is ok, kbfs will call
// load_kbfuse when it starts up.
// We have to get the version from the installed plist.
installedVersion, fivErr := fuseInstallVersion(log)
if fivErr != nil {
st.InstallStatus = keybase1.InstallStatus_ERROR
st.InstallAction = keybase1.InstallAction_REINSTALL
st.Status = keybase1.Status{Code: libkb.SCGeneric, Name: "INSTALL_ERROR", Desc: fmt.Sprintf("Error loading (plist) info: %s", fivErr)}
return st
}
if installedVersion != "" {
kextInfo = &kext.Info{
Version: installedVersion,
Started: false,
}
}
}

// Installed
st.KextID = kextID
st.KextID = driverID
}

// If neither is found, we have no install
if st.KextID == "" || kextInfo == nil {
if st.KextID == "" {
st.InstallStatus = keybase1.InstallStatus_NOT_INSTALLED
st.InstallAction = keybase1.InstallAction_INSTALL
return st
}

// Try to get mount info, it's non-critical if we fail though.
mountInfos, err := mountInfo("kbfuse")
mountInfos, err := mountInfo("keybase")
if err != nil {
log.Errorf("Error trying to read mount info: %s", err)
}
st.MountInfos = mountInfos

st.Version = kextInfo.Version
st.KextStarted = kextInfo.Started
installedVersion, fivErr := fuseInstallVersion(log)
if fivErr != nil {
st.InstallStatus = keybase1.InstallStatus_ERROR
st.InstallAction = keybase1.InstallAction_REINSTALL
st.Status = keybase1.Status{Code: libkb.SCGeneric, Name: "INSTALL_ERROR", Desc: fmt.Sprintf("Error loading install metadata: %s", fivErr)}
return st
}
st.Version = installedVersion
st.KextStarted = len(mountInfos) > 0

installStatus, installAction, status := ResolveInstallStatus(st.Version, st.BundleVersion, "", log)
st.InstallStatus = installStatus
Expand All @@ -93,7 +70,7 @@ func KeybaseFuseStatus(bundleVersion string, log Log) keybase1.FuseStatus {
}

func mountInfo(fstype string) ([]keybase1.FuseMountInfo, error) {
out, err := exec.Command("/sbin/mount", "-t", fstype).Output()
out, err := exec.Command("/sbin/mount").Output()
if err != nil {
return nil, err
}
Expand All @@ -103,6 +80,9 @@ func mountInfo(fstype string) ([]keybase1.FuseMountInfo, error) {
if strings.TrimSpace(line) == "" {
continue
}
if !strings.Contains(strings.ToLower(line), fstype) {
continue
}
info := strings.SplitN(line, " ", 4)
path := ""
if len(info) >= 2 {
Expand All @@ -118,7 +98,7 @@ func mountInfo(fstype string) ([]keybase1.FuseMountInfo, error) {
}

func findStringInPlist(key string, plistData []byte, log Log) string {
// Hack to parse plist, instead of parsing we'll use a regex
// Keep regex parsing for compatibility with existing tests.
res := fmt.Sprintf(`<key>%s<\/key>\s*<string>([\S ]+)<\/string>`, key)
re := regexp.MustCompile(res)
submatch := re.FindStringSubmatch(string(plistData))
Expand All @@ -129,29 +109,22 @@ func findStringInPlist(key string, plistData []byte, log Log) string {
return ""
}

func loadPlist(plistPath string, log Log) ([]byte, error) {
if _, err := os.Stat(plistPath); os.IsNotExist(err) {
log.Debug("No plist found: %s", plistPath)
return nil, err
}
log.Debug("Loading plist: %s", plistPath)
plistFile, err := os.Open(plistPath)
defer func() {
if err := plistFile.Close(); err != nil {
log.Debug("unable to close file: %s", err)
}
}()
func fuseInstallVersion(log Log) (string, error) {
appPath, err := libnativeinstaller.AppBundleForPath()
if err != nil {
return nil, err
return "", nil
}
return io.ReadAll(plistFile)
}

func fuseInstallVersion(log Log) (string, error) {
plistPath := filepath.Join(installPath, "Contents/Info.plist")
plistData, err := loadPlist(plistPath, log)
plistPath := filepath.Join(appPath, "Contents/Resources/KeybaseInstaller.app/Contents/Info.plist")
cmd := exec.Command(
"/usr/libexec/PlistBuddy",
"-c",
"Print :FSKitVersion",
plistPath,
)
out, err := cmd.CombinedOutput()
if err != nil {
return "", err
log.Debug("Unable to read FSKit version from installer plist: %s", strings.TrimSpace(string(out)))
return "", nil
}
return findStringInPlist("CFBundleVersion", plistData, log), nil
return strings.TrimSpace(string(out)), nil
}
52 changes: 20 additions & 32 deletions go/install/install_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,32 +406,20 @@ func ServiceStatus(context Context, label ServiceLabel, wait time.Duration, log
}
}

// InstallAuto installs everything it can without asking for privileges or
// extensions. If the user has already installed Fuse, we install everything.
// InstallAuto installs everything needed for macOS KBFS operation.
// FSKit is treated as a first-class required component on darwin.
func InstallAuto(context Context, binPath string, sourcePath string, timeout time.Duration, log Log) keybase1.InstallResult {
var components []string
status := KeybaseFuseStatus("", log)
if status.InstallStatus == keybase1.InstallStatus_INSTALLED {
components = []string{
ComponentNameCLI.String(),
ComponentNameUpdater.String(),
ComponentNameService.String(),
ComponentNameKBFS.String(),
ComponentNameHelper.String(),
ComponentNameFuse.String(),
ComponentNameMountDir.String(),
ComponentNameRedirector.String(),
ComponentNameKBFS.String(),
ComponentNameKBNM.String(),
}
} else {
components = []string{
ComponentNameCLI.String(),
ComponentNameUpdater.String(),
ComponentNameService.String(),
ComponentNameKBFS.String(),
ComponentNameKBNM.String(),
}
components := []string{
ComponentNameCLI.String(),
ComponentNameUpdater.String(),
ComponentNameService.String(),
ComponentNameKBFS.String(),
ComponentNameHelper.String(),
ComponentNameFuse.String(),
ComponentNameMountDir.String(),
ComponentNameRedirector.String(),
ComponentNameKBFS.String(),
ComponentNameKBNM.String(),
}

// A force unmount is needed to change from one mountpoint to another
Expand All @@ -444,7 +432,7 @@ func InstallAuto(context Context, binPath string, sourcePath string, timeout tim
const mountsPresentErrorCode = 7 // See Installer/Installer.m

func installFuse(runMode libkb.RunMode, log Log) error {
err := libnativeinstaller.InstallFuse(runMode, log)
err := libnativeinstaller.InstallFSKit(runMode, log)
switch e := err.(type) {
case nil:
return nil
Expand All @@ -461,7 +449,7 @@ func installFuse(runMode libkb.RunMode, log Log) error {
return err
}

log.Info("Can't install/upgrade fuse when mounts are present. " +
log.Info("Can't install/upgrade FSKit when mounts are present. " +
"Assuming it's the redirector and trying to uninstall it first.")
if err = libnativeinstaller.UninstallRedirector(runMode, log); err != nil {
log.Info("Uninstalling redirector failed. " +
Expand All @@ -474,10 +462,10 @@ func installFuse(runMode libkb.RunMode, log Log) error {
}
}()
log.Info(
"Uninstalling redirector succeeded. Trying to install KBFuse again.")
if err = libnativeinstaller.InstallFuse(runMode, log); err != nil {
log.Info("Installing fuse failed again. " +
"Fuse should be able to update next time the OS reboots.")
"Uninstalling redirector succeeded. Trying to install FSKit again.")
if err = libnativeinstaller.InstallFSKit(runMode, log); err != nil {
log.Info("Installing FSKit failed again. " +
"The driver should be able to update next time the OS reboots.")
return err
}
return nil
Expand Down Expand Up @@ -608,7 +596,7 @@ func Install(context Context, binPath string, sourcePath string, components []st
err = installFuse(context.GetRunMode(), log)
componentResults = append(componentResults, componentResult(string(ComponentNameFuse), err))
if err != nil {
log.Errorf("Error installing KBFuse: %s", err)
log.Errorf("Error installing filesystem driver: %s", err)
}
}

Expand Down
22 changes: 16 additions & 6 deletions go/install/libnativeinstaller/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,26 @@ func UninstallRedirector(runMode libkb.RunMode, log Log) error {
return execNativeInstallerWithArg([]string{"--uninstall-redirector"}, runMode, log)
}

// InstallFuse calls the installer with --install-fuse.
// InstallFSKit calls the installer with --install-fskit.
func InstallFSKit(runMode libkb.RunMode, log Log) error {
log.Info("Installing KBFS FSKit module")
return execNativeInstallerWithArg([]string{"--install-fskit"}, runMode, log)
}

// UninstallFSKit calls the installer with --uninstall-fskit.
func UninstallFSKit(runMode libkb.RunMode, log Log) error {
log.Info("Removing KBFS FSKit module")
return execNativeInstallerWithArg([]string{"--uninstall-fskit"}, runMode, log)
}

// InstallFuse is preserved for RPC/API compatibility and now installs FSKit.
func InstallFuse(runMode libkb.RunMode, log Log) error {
log.Info("Installing KBFuse")
return execNativeInstallerWithArg([]string{"--install-fuse"}, runMode, log)
return InstallFSKit(runMode, log)
}

// UninstallFuse calls the installer with --uninstall-fuse.
// UninstallFuse is preserved for RPC/API compatibility and now uninstalls FSKit.
func UninstallFuse(runMode libkb.RunMode, log Log) error {
log.Info("Removing KBFuse")
return execNativeInstallerWithArg([]string{"--uninstall-fuse"}, runMode, log)
return UninstallFSKit(runMode, log)
}

// InstallHelper calls the installer with --install-helper.
Expand Down
9 changes: 5 additions & 4 deletions go/kbfs/kbfsfuse/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (

"github.com/keybase/client/go/kbfs/env"
"github.com/keybase/client/go/kbfs/libfs"
"github.com/keybase/client/go/kbfs/libfsdriver"
"github.com/keybase/client/go/kbfs/libfuse"
"github.com/keybase/client/go/kbfs/libkbfs"
"github.com/keybase/client/go/libkb"
Expand Down Expand Up @@ -52,7 +53,7 @@ Defaults:
func getUsageString(ctx libkbfs.Context) string {
remoteUsageStr := libkbfs.GetRemoteUsageString()
localUsageStr := libkbfs.GetLocalUsageString()
platformUsageStr := libfuse.GetPlatformUsageString()
platformUsageStr := libfsdriver.GetPlatformUsageString()
defaultUsageStr := libkbfs.GetDefaultsUsageString(ctx)
return fmt.Sprintf(usageFormatStr,
remoteUsageStr, platformUsageStr,
Expand All @@ -63,7 +64,7 @@ func start() *libfs.Error {
ctx := env.NewContextWithPerfLog(libkb.KBFSPerfLogFileName)

kbfsParams := libkbfs.AddFlags(flag.CommandLine, ctx)
platformParams := libfuse.AddPlatformFlags(flag.CommandLine)
platformParams := libfsdriver.AddPlatformFlags(flag.CommandLine)

flag.Parse()

Expand Down Expand Up @@ -105,7 +106,7 @@ func start() *libfs.Error {
logger.EnableBufferedLogging()
defer logger.Shutdown()

options := libfuse.StartOptions{
options := libfsdriver.StartOptions{
KbfsParams: *kbfsParams,
PlatformParams: *platformParams,
RuntimeDir: *runtimeDir,
Expand All @@ -116,7 +117,7 @@ func start() *libfs.Error {
MountPoint: mountDir,
}

return libfuse.Start(options, ctx)
return libfsdriver.Start(options, ctx)
}

func main() {
Expand Down
30 changes: 30 additions & 0 deletions go/kbfs/libfsdriver/platform_flags_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2026 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.
//
//go:build darwin
// +build darwin

package libfsdriver

import "flag"

// PlatformParams contains all platform-specific parameters.
type PlatformParams struct {
// Keep the existing flag for compatibility with tooling/scripts; it is ignored
// by the FSKit backend.
UseLocal bool
}

// GetPlatformUsageString returns a string to be included in usage output.
func GetPlatformUsageString() string {
return "[--local-experimental]\n "
}

// AddPlatformFlags adds platform-specific flags to the given FlagSet.
func AddPlatformFlags(flags *flag.FlagSet) *PlatformParams {
var params PlatformParams
flags.BoolVar(&params.UseLocal, "local-experimental", false,
"Legacy local mode flag (ignored by the macOS FSKit backend)")
return &params
}
Loading