github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/install/install_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  package install
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"strconv"
    15  	"strings"
    16  	"syscall"
    17  	"time"
    18  
    19  	"github.com/blang/semver"
    20  	"github.com/keybase/client/go/install/libnativeinstaller"
    21  	kbnminstaller "github.com/keybase/client/go/kbnm/installer"
    22  	"github.com/keybase/client/go/launchd"
    23  	"github.com/keybase/client/go/libkb"
    24  	"github.com/keybase/client/go/logger"
    25  	"github.com/keybase/client/go/mounter"
    26  	"github.com/keybase/client/go/protocol/keybase1"
    27  )
    28  
    29  // defaultLaunchdWait is how long we should wait after install & start.
    30  // We should make this shorter if the app is started by the user (so
    31  // they get more immediate feedback), and longer if the app is started
    32  // after boot (when it takes longer for things to start).
    33  const defaultLaunchdWait = 20 * time.Second
    34  
    35  // ServiceLabel is an identifier string for a service
    36  type ServiceLabel string
    37  
    38  const (
    39  	// AppServiceLabel is the service label for the keybase launchd service in Keybase.app
    40  	AppServiceLabel ServiceLabel = "keybase.service"
    41  	// AppKBFSLabel is the service label for the kbfs launchd service in Keybase.app
    42  	AppKBFSLabel ServiceLabel = "keybase.kbfs"
    43  	// AppUpdaterLabel is the service label for the updater launchd service in Keybase.app
    44  	AppUpdaterLabel ServiceLabel = "keybase.updater"
    45  	// BrewServiceLabel is the service label for the updater launchd service in homebrew
    46  	BrewServiceLabel ServiceLabel = "homebrew.mxcl.keybase"
    47  	// BrewKBFSLabel is the service label for the kbfs launchd service in homebrew
    48  	BrewKBFSLabel ServiceLabel = "homebrew.mxcl.kbfs"
    49  	// UnknownLabel is an empty/unknown label
    50  	UnknownLabel ServiceLabel = ""
    51  
    52  	// See osx/Installer/Installer.m : KBExitAuthCanceledError
    53  	installHelperExitCodeAuthCanceled int = 6
    54  	// See osx/Installer/Installer.m : KBExitFuseCriticalUpdate
    55  	installHelperExitCodeFuseCriticalUpdate int = 8
    56  	// our own exit code
    57  	exitCodeFuseCriticalUpdateFailed int = 300
    58  )
    59  
    60  // KeybaseServiceStatus returns service status for Keybase service
    61  func KeybaseServiceStatus(context Context, label string, wait time.Duration, log Log) (status keybase1.ServiceStatus) {
    62  	if label == "" {
    63  		status = keybase1.ServiceStatus{Status: keybase1.StatusFromCode(keybase1.StatusCode_SCServiceStatusError, "No service label")}
    64  		return
    65  	}
    66  	kbService := launchd.NewService(label)
    67  
    68  	status, err := serviceStatusFromLaunchd(kbService, context.GetServiceInfoPath(), wait, log)
    69  	status.BundleVersion = libkb.VersionString()
    70  	if err != nil {
    71  		return
    72  	}
    73  	if status.InstallStatus == keybase1.InstallStatus_NOT_INSTALLED {
    74  		return
    75  	}
    76  
    77  	installStatus, installAction, kbStatus := ResolveInstallStatus(status.Version, status.BundleVersion, status.LastExitStatus, log)
    78  	status.InstallStatus = installStatus
    79  	status.InstallAction = installAction
    80  	status.Status = kbStatus
    81  	return
    82  }
    83  
    84  // KBFSServiceStatus returns service status for KBFS
    85  func KBFSServiceStatus(context Context, label string, wait time.Duration, log Log) (status keybase1.ServiceStatus) {
    86  	if label == "" {
    87  		status = keybase1.ServiceStatus{Status: keybase1.StatusFromCode(keybase1.StatusCode_SCServiceStatusError, "No service label")}
    88  		return
    89  	}
    90  	kbfsService := launchd.NewService(label)
    91  
    92  	status, err := serviceStatusFromLaunchd(kbfsService, context.GetKBFSInfoPath(), wait, log)
    93  	if err != nil {
    94  		return
    95  	}
    96  	bundleVersion, err := KBFSBundleVersion(context, "")
    97  	if err != nil {
    98  		status.Status = keybase1.StatusFromCode(keybase1.StatusCode_SCServiceStatusError, err.Error())
    99  		return
   100  	}
   101  	status.BundleVersion = bundleVersion
   102  	if status.InstallStatus == keybase1.InstallStatus_NOT_INSTALLED {
   103  		return
   104  	}
   105  
   106  	installStatus, installAction, kbStatus := ResolveInstallStatus(status.Version, status.BundleVersion, status.LastExitStatus, log)
   107  	status.InstallStatus = installStatus
   108  	status.InstallAction = installAction
   109  	status.Status = kbStatus
   110  	return
   111  }
   112  
   113  // UpdaterServiceStatus returns service status for the Updater service
   114  func UpdaterServiceStatus(context Context, label string) keybase1.ServiceStatus {
   115  	if label == "" {
   116  		return keybase1.ServiceStatus{Status: keybase1.StatusFromCode(keybase1.StatusCode_SCServiceStatusError, "No service label")}
   117  	}
   118  	serviceStatus := keybase1.ServiceStatus{Label: label}
   119  	updaterService := launchd.NewService(label)
   120  	status, err := updaterService.WaitForStatus(defaultLaunchdWait, 500*time.Millisecond)
   121  	if err != nil {
   122  		serviceStatus.Status = keybase1.StatusFromCode(keybase1.StatusCode_SCServiceStatusError, err.Error())
   123  		return serviceStatus
   124  	}
   125  	if status != nil {
   126  		serviceStatus.Pid = status.Pid()
   127  		serviceStatus.LastExitStatus = status.LastExitStatus()
   128  	}
   129  	if serviceStatus.Pid != "" {
   130  		serviceStatus.InstallStatus = keybase1.InstallStatus_INSTALLED
   131  		serviceStatus.InstallAction = keybase1.InstallAction_NONE
   132  	} else {
   133  		serviceStatus.InstallStatus = keybase1.InstallStatus_NOT_INSTALLED
   134  		serviceStatus.InstallAction = keybase1.InstallAction_INSTALL
   135  	}
   136  	serviceStatus.Status = keybase1.StatusOK("")
   137  	return serviceStatus
   138  }
   139  
   140  func serviceStatusFromLaunchd(ls launchd.Service, infoPath string, wait time.Duration, log Log) (status keybase1.ServiceStatus, err error) {
   141  	status = keybase1.ServiceStatus{
   142  		Label: ls.Label(),
   143  	}
   144  
   145  	launchdStatus, err := ls.WaitForStatus(wait, 500*time.Millisecond)
   146  	if err != nil {
   147  		status.InstallStatus = keybase1.InstallStatus_ERROR
   148  		status.InstallAction = keybase1.InstallAction_NONE
   149  		status.Status = keybase1.StatusFromCode(keybase1.StatusCode_SCServiceStatusError, err.Error())
   150  		return
   151  	}
   152  
   153  	if launchdStatus == nil {
   154  		status.InstallStatus = keybase1.InstallStatus_NOT_INSTALLED
   155  		status.InstallAction = keybase1.InstallAction_INSTALL
   156  		status.Status = keybase1.Status{Name: "OK"}
   157  		return
   158  	}
   159  
   160  	status.Label = launchdStatus.Label()
   161  	status.Pid = launchdStatus.Pid()
   162  	status.LastExitStatus = launchdStatus.LastExitStatus()
   163  
   164  	// Check service info file (if present) and if the service is running (has a PID)
   165  	var serviceInfo *libkb.ServiceInfo
   166  	if infoPath != "" {
   167  		if status.Pid != "" {
   168  			serviceInfo, err = libkb.WaitForServiceInfoFile(infoPath, status.Label, status.Pid, wait, log)
   169  			if err != nil {
   170  				status.InstallStatus = keybase1.InstallStatus_ERROR
   171  				status.InstallAction = keybase1.InstallAction_REINSTALL
   172  				status.Status = keybase1.StatusFromCode(keybase1.StatusCode_SCServiceStatusError, err.Error())
   173  				return
   174  			}
   175  		}
   176  		if serviceInfo != nil {
   177  			status.Version = serviceInfo.Version
   178  		}
   179  	}
   180  
   181  	if status.Pid == "" {
   182  		status.InstallStatus = keybase1.InstallStatus_ERROR
   183  		status.InstallAction = keybase1.InstallAction_REINSTALL
   184  		err = fmt.Errorf("%s is not running", status.Label)
   185  		status.Status = keybase1.StatusFromCode(keybase1.StatusCode_SCServiceStatusError, err.Error())
   186  		return
   187  	}
   188  
   189  	status.Status = keybase1.Status{Name: "OK"}
   190  	return
   191  }
   192  
   193  func serviceStatusesFromLaunchd(context Context, ls []launchd.Service, wait time.Duration, log Log) []keybase1.ServiceStatus {
   194  	c := []keybase1.ServiceStatus{}
   195  	for _, l := range ls {
   196  		s, _ := serviceStatusFromLaunchd(l, "", wait, log)
   197  		c = append(c, s)
   198  	}
   199  	return c
   200  }
   201  
   202  // ListServices returns status for all services
   203  func ListServices(context Context, wait time.Duration, log Log) (*keybase1.ServicesStatus, error) {
   204  	services, err := launchd.ListServices([]string{"keybase.service", "homebrew.mxcl.keybase"})
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  	kbfs, err := launchd.ListServices([]string{"keybase.kbfs.", "homebrew.mxcl.kbfs"})
   209  	if err != nil {
   210  		return nil, err
   211  	}
   212  	updater, err := launchd.ListServices([]string{"keybase.updater."})
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  
   217  	return &keybase1.ServicesStatus{
   218  		Service: serviceStatusesFromLaunchd(context, services, wait, log),
   219  		Kbfs:    serviceStatusesFromLaunchd(context, kbfs, wait, log),
   220  		Updater: serviceStatusesFromLaunchd(context, updater, wait, log),
   221  	}, nil
   222  }
   223  
   224  // DefaultLaunchdEnvVars returns default environment vars for launchd
   225  func DefaultLaunchdEnvVars(label string) []launchd.EnvVar {
   226  	return []launchd.EnvVar{
   227  		launchd.NewEnvVar("KEYBASE_LABEL", label),
   228  		launchd.NewEnvVar("KEYBASE_SERVICE_TYPE", "launchd"),
   229  	}
   230  }
   231  
   232  // DefaultServiceLabel returns the default label for Keybase service in launchd
   233  func DefaultServiceLabel(runMode libkb.RunMode) string {
   234  	return defaultServiceLabel(runMode, libkb.IsBrewBuild)
   235  }
   236  
   237  func defaultServiceLabel(runMode libkb.RunMode, isBrew bool) string {
   238  	label := AppServiceLabel.String()
   239  	if isBrew {
   240  		label = BrewServiceLabel.String()
   241  	}
   242  	if runMode != libkb.ProductionRunMode {
   243  		label = label + "." + string(runMode)
   244  	}
   245  	return label
   246  }
   247  
   248  // DefaultKBFSLabel returns the default label for KBFS service in launchd
   249  func DefaultKBFSLabel(runMode libkb.RunMode) string {
   250  	return defaultKBFSLabel(runMode, libkb.IsBrewBuild)
   251  }
   252  
   253  func defaultKBFSLabel(runMode libkb.RunMode, isBrew bool) string {
   254  	label := AppKBFSLabel.String()
   255  	if isBrew {
   256  		label = BrewKBFSLabel.String()
   257  	}
   258  	if runMode != libkb.ProductionRunMode {
   259  		label = label + "." + string(runMode)
   260  	}
   261  	return label
   262  }
   263  
   264  // DefaultUpdaterLabel returns the default label for the update service in launchd
   265  func DefaultUpdaterLabel(runMode libkb.RunMode) string {
   266  	label := AppUpdaterLabel.String()
   267  	if runMode != libkb.ProductionRunMode {
   268  		label = label + "." + string(runMode)
   269  	}
   270  	return label
   271  }
   272  
   273  const defaultPlistComment = "It's not advisable to edit this plist, it may be overwritten"
   274  
   275  func keybasePlist(context Context, binPath string, label string, log Log) (launchd.Plist, error) {
   276  	// TODO: Remove -d when doing real release
   277  	logFile := filepath.Join(context.GetLogDir(), libkb.ServiceLogFileName)
   278  	startLogFile := filepath.Join(context.GetLogDir(), libkb.StartLogFileName)
   279  	err := libkb.MakeParentDirs(log, startLogFile)
   280  	if err != nil {
   281  		return launchd.Plist{}, err
   282  	}
   283  	plistArgs := []string{"-d", fmt.Sprintf("--log-file=%s", logFile), "service"}
   284  	envVars := DefaultLaunchdEnvVars(label)
   285  	envVars = append(envVars, launchd.NewEnvVar("KEYBASE_RUN_MODE", string(context.GetRunMode())))
   286  	return launchd.NewPlist(label, binPath, plistArgs, envVars, startLogFile, defaultPlistComment), nil
   287  }
   288  
   289  func installKeybaseService(context Context, service launchd.Service, plist launchd.Plist, wait time.Duration, log Log) (*keybase1.ServiceStatus, error) {
   290  	err := launchd.Install(plist, wait, log)
   291  	if err != nil {
   292  		log.Warning("error installing keybase service via launchd: %s", err)
   293  		return nil, err
   294  	}
   295  
   296  	st, err := serviceStatusFromLaunchd(service, context.GetServiceInfoPath(), wait, log)
   297  	return &st, err
   298  }
   299  
   300  // UninstallKeybaseServices removes the keybase service (includes homebrew)
   301  func UninstallKeybaseServices(context Context, log Log) error {
   302  	runMode := context.GetRunMode()
   303  	err0 := fallbackKillProcess(context, log, defaultServiceLabel(runMode, false), context.GetServiceInfoPath(), "")
   304  	err1 := launchd.Uninstall(defaultServiceLabel(runMode, false), defaultLaunchdWait, log)
   305  	err2 := launchd.Uninstall(defaultServiceLabel(runMode, true), defaultLaunchdWait, log)
   306  	return libkb.CombineErrors(err0, err1, err2)
   307  }
   308  
   309  func kbfsPlist(context Context, kbfsBinPath string, label string, mountDir string, skipMount bool, log Log) (launchd.Plist, error) {
   310  	logFile := filepath.Join(context.GetLogDir(), libkb.KBFSLogFileName)
   311  	startLogFile := filepath.Join(context.GetLogDir(), libkb.StartLogFileName)
   312  	if err := libkb.MakeParentDirs(log, startLogFile); err != nil {
   313  		return launchd.Plist{}, err
   314  	}
   315  	// TODO: Remove debug flag when doing real release
   316  	plistArgs := []string{
   317  		"-debug",
   318  		fmt.Sprintf("-log-file=%s", logFile),
   319  		fmt.Sprintf("-runtime-dir=%s", context.GetRuntimeDir()),
   320  	}
   321  
   322  	if context.GetRunMode() == libkb.DevelRunMode {
   323  		plistArgs = append(plistArgs, fmt.Sprintf("-server-root=%s", context.GetRuntimeDir()))
   324  	}
   325  
   326  	if skipMount {
   327  		plistArgs = append(plistArgs, "-mount-type=none")
   328  	}
   329  
   330  	plistArgs = append(plistArgs, mountDir)
   331  
   332  	envVars := DefaultLaunchdEnvVars(label)
   333  	envVars = append(envVars, launchd.NewEnvVar("KEYBASE_RUN_MODE", string(context.GetRunMode())))
   334  	plist := launchd.NewPlist(label, kbfsBinPath, plistArgs, envVars, startLogFile, defaultPlistComment)
   335  	return plist, nil
   336  }
   337  
   338  func installKBFSService(context Context, service launchd.Service, plist launchd.Plist, wait time.Duration, log Log) (*keybase1.ServiceStatus, error) {
   339  	err := launchd.Install(plist, wait, log)
   340  	if err != nil {
   341  		log.Warning("error installing kbfs service via launchd: %s", err)
   342  		return nil, err
   343  	}
   344  
   345  	st, err := serviceStatusFromLaunchd(service, "", wait, log)
   346  	return &st, err
   347  }
   348  
   349  // UninstallKBFSServices removes KBFS service (including homebrew)
   350  func UninstallKBFSServices(context Context, log Log) error {
   351  	runMode := context.GetRunMode()
   352  	err0 := fallbackKillProcess(context, log, defaultKBFSLabel(runMode, false), context.GetKBFSInfoPath(), "")
   353  	err1 := launchd.Uninstall(defaultKBFSLabel(runMode, false), defaultLaunchdWait, log)
   354  	err2 := launchd.Uninstall(defaultKBFSLabel(runMode, true), defaultLaunchdWait, log)
   355  	return libkb.CombineErrors(err0, err1, err2)
   356  }
   357  
   358  // NewServiceLabel constructs a service label
   359  func NewServiceLabel(s string) (ServiceLabel, error) {
   360  	switch s {
   361  	case string(AppServiceLabel):
   362  		return AppServiceLabel, nil
   363  	case string(BrewServiceLabel):
   364  		return BrewServiceLabel, nil
   365  	case string(AppKBFSLabel):
   366  		return AppKBFSLabel, nil
   367  	case string(BrewKBFSLabel):
   368  		return BrewKBFSLabel, nil
   369  	case string(AppUpdaterLabel):
   370  		return AppUpdaterLabel, nil
   371  	}
   372  	return UnknownLabel, fmt.Errorf("Unknown service label: %s", s)
   373  }
   374  
   375  func (l ServiceLabel) String() string {
   376  	return string(l)
   377  }
   378  
   379  // ComponentName returns the component name for a service label
   380  func (l ServiceLabel) ComponentName() ComponentName {
   381  	switch l {
   382  	case AppServiceLabel, BrewServiceLabel:
   383  		return ComponentNameService
   384  	case AppKBFSLabel, BrewKBFSLabel:
   385  		return ComponentNameKBFS
   386  	case AppUpdaterLabel:
   387  		return ComponentNameUpdater
   388  	}
   389  	return ComponentNameUnknown
   390  }
   391  
   392  // ServiceStatus returns status for a service named by label
   393  func ServiceStatus(context Context, label ServiceLabel, wait time.Duration, log Log) (*keybase1.ServiceStatus, error) {
   394  	switch label.ComponentName() {
   395  	case ComponentNameService:
   396  		st := KeybaseServiceStatus(context, string(label), wait, log)
   397  		return &st, nil
   398  	case ComponentNameKBFS:
   399  		st := KBFSServiceStatus(context, string(label), wait, log)
   400  		return &st, nil
   401  	case ComponentNameUpdater:
   402  		st := UpdaterServiceStatus(context, string(label))
   403  		return &st, nil
   404  	default:
   405  		return nil, fmt.Errorf("Invalid label: %s", label)
   406  	}
   407  }
   408  
   409  // InstallAuto installs everything it can without asking for privileges or
   410  // extensions. If the user has already installed Fuse, we install everything.
   411  func InstallAuto(context Context, binPath string, sourcePath string, timeout time.Duration, log Log) keybase1.InstallResult {
   412  	var components []string
   413  	status := KeybaseFuseStatus("", log)
   414  	if status.InstallStatus == keybase1.InstallStatus_INSTALLED {
   415  		components = []string{
   416  			ComponentNameCLI.String(),
   417  			ComponentNameUpdater.String(),
   418  			ComponentNameService.String(),
   419  			ComponentNameKBFS.String(),
   420  			ComponentNameHelper.String(),
   421  			ComponentNameFuse.String(),
   422  			ComponentNameMountDir.String(),
   423  			ComponentNameRedirector.String(),
   424  			ComponentNameKBFS.String(),
   425  			ComponentNameKBNM.String(),
   426  		}
   427  	} else {
   428  		components = []string{
   429  			ComponentNameCLI.String(),
   430  			ComponentNameUpdater.String(),
   431  			ComponentNameService.String(),
   432  			ComponentNameKBFS.String(),
   433  			ComponentNameKBNM.String(),
   434  		}
   435  	}
   436  
   437  	// A force unmount is needed to change from one mountpoint to another
   438  	// if the mount is in use after an upgrade, and install-auto is
   439  	// invoked from the updater.
   440  	forceUnmount := true
   441  	return Install(context, binPath, sourcePath, components, forceUnmount, timeout, log)
   442  }
   443  
   444  const mountsPresentErrorCode = 7 // See Installer/Installer.m
   445  
   446  func installFuse(runMode libkb.RunMode, log Log) error {
   447  	err := libnativeinstaller.InstallFuse(runMode, log)
   448  	switch e := err.(type) {
   449  	case nil:
   450  		return nil
   451  	case (*exec.ExitError):
   452  		if waitStatus, ok := e.Sys().(syscall.WaitStatus); ok {
   453  			if waitStatus.ExitStatus() != mountsPresentErrorCode {
   454  				return err
   455  			}
   456  			// Otherwise, continue with the logic after switch.
   457  		} else {
   458  			return err
   459  		}
   460  	default:
   461  		return err
   462  	}
   463  
   464  	log.Info("Can't install/upgrade fuse when mounts are present. " +
   465  		"Assuming it's the redirector and trying to uninstall it first.")
   466  	if err = libnativeinstaller.UninstallRedirector(runMode, log); err != nil {
   467  		log.Info("Uninstalling redirector failed. " +
   468  			"Fuse should be able to update next time the OS reboots.")
   469  		return err
   470  	}
   471  	defer func() {
   472  		if err := libnativeinstaller.InstallRedirector(runMode, log); err != nil {
   473  			log.Info("Installing redirector failed. %s", err)
   474  		}
   475  	}()
   476  	log.Info(
   477  		"Uninstalling redirector succeeded. Trying to install KBFuse again.")
   478  	if err = libnativeinstaller.InstallFuse(runMode, log); err != nil {
   479  		log.Info("Installing fuse failed again. " +
   480  			"Fuse should be able to update next time the OS reboots.")
   481  		return err
   482  	}
   483  	return nil
   484  }
   485  
   486  // Install installs specified components
   487  func Install(context Context, binPath string, sourcePath string, components []string, force bool, timeout time.Duration, log Log) keybase1.InstallResult {
   488  	var err error
   489  	componentResults := []keybase1.ComponentResult{}
   490  
   491  	log.Debug("Installing components: %s", components)
   492  
   493  	if libkb.IsIn(string(ComponentNameCLI), components, false) {
   494  		err = installCommandLine(context, binPath, true, log) // Always force CLI install
   495  		componentResults = append(componentResults, componentResult(string(ComponentNameCLI), err))
   496  		if err != nil {
   497  			log.Errorf("Error installing CLI: %s", err)
   498  		}
   499  	}
   500  
   501  	if libkb.IsIn(string(ComponentNameApp), components, false) {
   502  		err = libnativeinstaller.InstallAppBundle(context, sourcePath, log)
   503  		componentResults = append(componentResults, componentResult(string(ComponentNameApp), err))
   504  		if err != nil {
   505  			log.Errorf("Error installing app bundle: %s", err)
   506  		}
   507  	}
   508  
   509  	if libkb.IsIn(string(ComponentNameUpdater), components, false) {
   510  		err = InstallUpdater(context, binPath, force, timeout, log)
   511  		componentResults = append(componentResults, componentResult(string(ComponentNameUpdater), err))
   512  		if err != nil {
   513  			log.Errorf("Error installing updater: %s", err)
   514  		}
   515  	}
   516  
   517  	if libkb.IsIn(string(ComponentNameService), components, false) {
   518  		err = InstallService(context, binPath, force, timeout, log)
   519  		componentResults = append(componentResults, componentResult(string(ComponentNameService), err))
   520  		if err != nil {
   521  			log.Errorf("Error installing service: %s", err)
   522  		}
   523  	}
   524  
   525  	helperCanceled := false
   526  	if libkb.IsIn(string(ComponentNameHelper), components, false) {
   527  		err = libnativeinstaller.InstallHelper(context.GetRunMode(), log)
   528  		cr := componentResult(string(ComponentNameHelper), err)
   529  		componentResults = append(componentResults, cr)
   530  		if err != nil {
   531  			log.Errorf("Error installing Helper: %v", err)
   532  		}
   533  		shouldUninstallKBFS := false
   534  		shouldUninstallHelper := false
   535  		switch cr.ExitCode {
   536  		case installHelperExitCodeAuthCanceled:
   537  			log.Debug("Auth canceled; uninstalling mountdir and fuse")
   538  			helperCanceled = true
   539  			shouldUninstallKBFS = true
   540  		case installHelperExitCodeFuseCriticalUpdate:
   541  			log.Debug("FUSE critical update; uninstalling mountdir and fuse")
   542  			shouldUninstallKBFS = true
   543  			helperCanceled = true
   544  			shouldUninstallHelper = true
   545  		}
   546  		if shouldUninstallKBFS {
   547  			// Unmount the user's KBFS directory.
   548  			mountDir, err := context.GetMountDir()
   549  			if err == nil {
   550  				err = UninstallKBFS(context, mountDir, true, log)
   551  			}
   552  			if err != nil {
   553  				log.Errorf("Error uninstalling KBFS: %s", err)
   554  			}
   555  
   556  			// For older systems, check `/keybase` too, just in case.
   557  			var oldMountDir string
   558  			switch context.GetRunMode() {
   559  			case libkb.ProductionRunMode:
   560  				oldMountDir = "/keybase"
   561  			case libkb.StagingRunMode:
   562  				oldMountDir = "/keybase.staging"
   563  			default:
   564  				oldMountDir = "/keybase.devel"
   565  			}
   566  			err = unmount(oldMountDir, true, log)
   567  			if err != nil {
   568  				log.Debug("Error unmounting old mount dir %s: %v", oldMountDir,
   569  					err)
   570  			}
   571  
   572  			err = libnativeinstaller.UninstallMountDir(
   573  				context.GetRunMode(), log)
   574  			if err != nil {
   575  				log.Errorf("Error uninstalling mount directory: %s", err)
   576  			}
   577  
   578  			err = libnativeinstaller.UninstallRedirector(
   579  				context.GetRunMode(), log)
   580  			if err != nil {
   581  				log.Errorf("Error stopping redirector: %s", err)
   582  			}
   583  
   584  			err = libnativeinstaller.UninstallFuse(context.GetRunMode(), log)
   585  			if err != nil {
   586  				log.Errorf("Error uninstalling FUSE: %s", err)
   587  				if shouldUninstallKBFS {
   588  					log.Errorf("Returning critical update failure result since FUSE uninstall failed")
   589  					return newInstallResult([]keybase1.ComponentResult{{
   590  						Name: "helper",
   591  						Status: keybase1.StatusFromCode(keybase1.StatusCode_SCGeneric,
   592  							"FUSE uninstall failed"),
   593  						ExitCode: exitCodeFuseCriticalUpdateFailed,
   594  					}})
   595  				}
   596  			}
   597  		}
   598  		if shouldUninstallHelper {
   599  			err = libnativeinstaller.UninstallHelper(context.GetRunMode(), log)
   600  			if err != nil {
   601  				log.Errorf("Error uninstalling helper: %s", err)
   602  			}
   603  		}
   604  	}
   605  
   606  	if !helperCanceled &&
   607  		libkb.IsIn(string(ComponentNameFuse), components, false) {
   608  		err = installFuse(context.GetRunMode(), log)
   609  		componentResults = append(componentResults, componentResult(string(ComponentNameFuse), err))
   610  		if err != nil {
   611  			log.Errorf("Error installing KBFuse: %s", err)
   612  		}
   613  	}
   614  
   615  	if !helperCanceled &&
   616  		libkb.IsIn(string(ComponentNameMountDir), components, false) {
   617  		err = libnativeinstaller.InstallMountDir(context.GetRunMode(), log)
   618  		componentResults = append(componentResults, componentResult(string(ComponentNameMountDir), err))
   619  		if err != nil {
   620  			log.Errorf("Error installing mount directory: %s", err)
   621  		}
   622  	}
   623  
   624  	if libkb.IsIn(string(ComponentNameKBFS), components, false) {
   625  		err = InstallKBFS(context, binPath, force, true, timeout, log)
   626  		componentResults = append(componentResults, componentResult(string(ComponentNameKBFS), err))
   627  		if err != nil {
   628  			log.Errorf("Error installing KBFS: %s", err)
   629  		}
   630  	}
   631  
   632  	if !helperCanceled &&
   633  		libkb.IsIn(string(ComponentNameRedirector), components, false) {
   634  		err = libnativeinstaller.InstallRedirector(context.GetRunMode(), log)
   635  		componentResults = append(componentResults, componentResult(string(ComponentNameRedirector), err))
   636  		if err != nil {
   637  			log.Errorf("Error starting redirector: %s", err)
   638  		}
   639  	}
   640  
   641  	if libkb.IsIn(string(ComponentNameKBNM), components, false) {
   642  		err = InstallKBNM(context, binPath, log)
   643  		componentResults = append(componentResults, componentResult(string(ComponentNameKBNM), err))
   644  		if err != nil {
   645  			log.Errorf("Error installing KBNM: %s", err)
   646  		}
   647  	}
   648  
   649  	if libkb.IsIn(string(ComponentNameCLIPaths), components, false) {
   650  		err = libnativeinstaller.InstallCommandLinePrivileged(context.GetRunMode(), log)
   651  		componentResults = append(componentResults, componentResult(string(ComponentNameCLIPaths), err))
   652  		if err != nil {
   653  			log.Errorf("Error installing command line (privileged): %s", err)
   654  		}
   655  	}
   656  
   657  	return newInstallResult(componentResults)
   658  }
   659  
   660  func installCommandLine(context Context, binPath string, force bool, log Log) error {
   661  	bp, err := chooseBinPath(binPath)
   662  	if err != nil {
   663  		return err
   664  	}
   665  	linkPath, err := defaultLinkPath()
   666  	if err != nil {
   667  		return err
   668  	}
   669  	if linkPath == bp {
   670  		return fmt.Errorf("We can't symlink to ourselves: %s", bp)
   671  	}
   672  	log.Info("Checking %s (%s)", linkPath, bp)
   673  	err = installCommandLineForBinPath(bp, linkPath, force, log)
   674  	if err != nil {
   675  		log.Errorf("Command line not installed properly (%s)", err)
   676  		return err
   677  	}
   678  
   679  	// Now the git remote helper. Lives next to the keybase binary, same dir
   680  	gitBinFilename := "git-remote-keybase"
   681  	gitBinPath := filepath.Join(filepath.Dir(bp), gitBinFilename)
   682  	gitLinkPath := filepath.Join(filepath.Dir(linkPath), gitBinFilename)
   683  	err = installCommandLineForBinPath(gitBinPath, gitLinkPath, force, log)
   684  	if err != nil {
   685  		log.Errorf("Git remote helper not installed properly (%s)", err)
   686  		return err
   687  	}
   688  
   689  	return nil
   690  }
   691  
   692  func createCommandLine(binPath string, linkPath string, log Log) error {
   693  	if _, err := os.Lstat(linkPath); err == nil {
   694  		err := os.Remove(linkPath)
   695  		if err != nil {
   696  			return err
   697  		}
   698  	}
   699  
   700  	log.Info("Linking %s to %s", linkPath, binPath)
   701  	return os.Symlink(binPath, linkPath)
   702  }
   703  
   704  func installCommandLineForBinPath(binPath string, linkPath string, force bool, log Log) error {
   705  	fi, err := os.Lstat(linkPath)
   706  	if os.IsNotExist(err) {
   707  		// Doesn't exist, create
   708  		return createCommandLine(binPath, linkPath, log)
   709  	}
   710  	isLink := (fi.Mode()&os.ModeSymlink != 0)
   711  	if !isLink {
   712  		if force {
   713  			log.Warning("Path is not a symlink: %s, forcing overwrite", linkPath)
   714  			return createCommandLine(binPath, linkPath, log)
   715  		}
   716  		return fmt.Errorf("Path is not a symlink: %s", linkPath)
   717  	}
   718  
   719  	// Check that the symlink evals to this binPath or error
   720  	dest, err := filepath.EvalSymlinks(linkPath)
   721  	if err == nil && binPath != dest {
   722  		err = fmt.Errorf("We are not symlinked to %s", linkPath)
   723  	}
   724  	if err != nil {
   725  		if force {
   726  			log.Warning("We are not symlinked to %s, forcing overwrite", linkPath)
   727  			return createCommandLine(binPath, linkPath, log)
   728  		}
   729  		return fmt.Errorf("We are not symlinked to %s", linkPath)
   730  	}
   731  
   732  	return nil
   733  }
   734  
   735  // InstallService installs the launchd service
   736  func InstallService(context Context, binPath string, force bool, timeout time.Duration, log Log) error {
   737  	resolvedBinPath, err := chooseBinPath(binPath)
   738  	if err != nil {
   739  		return err
   740  	}
   741  	log.Debug("Using binPath: %s", resolvedBinPath)
   742  
   743  	label := DefaultServiceLabel(context.GetRunMode())
   744  	service := launchd.NewService(label)
   745  	plist, err := keybasePlist(context, resolvedBinPath, label, log)
   746  	if err != nil {
   747  		return err
   748  	}
   749  	if err = UninstallKeybaseServices(context, log); err != nil {
   750  		log.Debug("unable to uninstall service: %s", err)
   751  	}
   752  	log.Debug("Installing service (%s, timeout=%s)", label, timeout)
   753  	if _, err := installKeybaseService(context, service, plist, timeout, log); err != nil {
   754  		log.Errorf("Error installing Keybase service: %s", err)
   755  		pid, err := fallbackStartProcessAndWaitForInfo(context, service, plist, context.GetServiceInfoPath(), timeout, log)
   756  		if err != nil {
   757  			return err
   758  		}
   759  		log.Debug("fallback keybase service started, pid=%d", pid)
   760  		return nil
   761  	}
   762  	log.Debug("keybase service installed via launchd successfully")
   763  	return nil
   764  }
   765  
   766  // InstallKBFS installs the KBFS launchd service
   767  func InstallKBFS(context Context, binPath string, force bool, skipMountIfNotAvailable bool, timeout time.Duration, log Log) error {
   768  	runMode := context.GetRunMode()
   769  	label := DefaultKBFSLabel(runMode)
   770  	kbfsService := launchd.NewService(label)
   771  	kbfsBinPath, err := KBFSBinPath(runMode, binPath)
   772  	if err != nil {
   773  		return err
   774  	}
   775  	// Unmount any existing KBFS directory for the user.
   776  	mountDir, err := context.GetMountDir()
   777  	if err != nil {
   778  		return err
   779  	}
   780  
   781  	skipMount := false
   782  	_, err = os.Stat(mountDir)
   783  	if err != nil {
   784  		if skipMountIfNotAvailable {
   785  			skipMount = true
   786  		} else {
   787  			return err
   788  		}
   789  	}
   790  
   791  	plist, err := kbfsPlist(context, kbfsBinPath, label, mountDir, skipMount, log)
   792  	if err != nil {
   793  		return err
   794  	}
   795  
   796  	if err = UninstallKBFSServices(context, log); err != nil {
   797  		log.Debug("unable to uninstall kbfs %s", err)
   798  	}
   799  	log.Debug("Installing KBFS (%s, timeout=%s)", label, timeout)
   800  	if _, err := installKBFSService(context, kbfsService, plist, timeout, log); err != nil {
   801  		log.Errorf("error installing KBFS: %s", err)
   802  		pid, err := fallbackStartProcessAndWaitForInfo(context, kbfsService, plist, context.GetKBFSInfoPath(), timeout, log)
   803  		if err != nil {
   804  			return err
   805  		}
   806  		log.Debug("fallback KBFS service started, pid=%d", pid)
   807  		return nil
   808  	}
   809  
   810  	log.Debug("KBFS installed via launchd successfully")
   811  	return nil
   812  }
   813  
   814  func uninstallCommandLine(log Log) error {
   815  	linkPath, err := defaultLinkPath()
   816  	if err != nil {
   817  		return nil
   818  	}
   819  
   820  	err = uninstallLink(linkPath, log)
   821  	if err != nil {
   822  		return err
   823  	}
   824  
   825  	// Now the git binary.
   826  	gitBinFilename := "git-remote-keybase"
   827  	gitLinkPath := filepath.Join(filepath.Dir(linkPath), gitBinFilename)
   828  	return uninstallLink(gitLinkPath, log)
   829  }
   830  
   831  // InstallKBNM installs the Keybase NativeMessaging whitelist
   832  func InstallKBNM(context Context, binPath string, log Log) error {
   833  	// Find path of the keybase binary
   834  	keybasePath, err := chooseBinPath(binPath)
   835  	if err != nil {
   836  		return err
   837  	}
   838  	// kbnm binary is next to the keybase binary, same dir
   839  	hostPath := filepath.Join(filepath.Dir(keybasePath), "kbnm")
   840  
   841  	log.Info("Installing KBNM NativeMessaging whitelists for binary: %s", hostPath)
   842  	return kbnminstaller.InstallKBNM(hostPath)
   843  }
   844  
   845  // UninstallKBNM removes the Keybase NativeMessaging whitelist
   846  func UninstallKBNM(log Log) error {
   847  	log.Info("Uninstalling KBNM NativeMessaging whitelists")
   848  	return kbnminstaller.UninstallKBNM()
   849  }
   850  
   851  // Uninstall uninstalls all keybase services
   852  func Uninstall(context Context, components []string, log Log) keybase1.UninstallResult {
   853  	var err error
   854  	componentResults := []keybase1.ComponentResult{}
   855  
   856  	log.Debug("Uninstalling components: %s", components)
   857  
   858  	if libkb.IsIn(string(ComponentNameRedirector), components, false) {
   859  		err = libnativeinstaller.UninstallRedirector(context.GetRunMode(), log)
   860  		componentResults = append(componentResults, componentResult(string(ComponentNameRedirector), err))
   861  		if err != nil {
   862  			log.Errorf("Error stopping the redirector: %s", err)
   863  		}
   864  	}
   865  
   866  	if libkb.IsIn(string(ComponentNameKBFS), components, false) {
   867  		var mountDir string
   868  		mountDir, err = context.GetMountDir()
   869  		if err == nil {
   870  			err = UninstallKBFS(context, mountDir, true, log)
   871  		}
   872  		componentResults = append(componentResults, componentResult(string(ComponentNameKBFS), err))
   873  		if err != nil {
   874  			log.Errorf("Error uninstalling KBFS: %s", err)
   875  		}
   876  	}
   877  
   878  	if libkb.IsIn(string(ComponentNameService), components, false) {
   879  		err = UninstallKeybaseServices(context, log)
   880  		componentResults = append(componentResults, componentResult(string(ComponentNameService), err))
   881  		if err != nil {
   882  			log.Errorf("Error uninstalling service: %s", err)
   883  		}
   884  	}
   885  
   886  	if libkb.IsIn(string(ComponentNameUpdater), components, false) {
   887  		err = UninstallUpdaterService(context, log)
   888  		componentResults = append(componentResults, componentResult(string(ComponentNameUpdater), err))
   889  		if err != nil {
   890  			log.Errorf("Error uninstalling updater: %s", err)
   891  		}
   892  	}
   893  
   894  	if libkb.IsIn(string(ComponentNameMountDir), components, false) {
   895  		err = libnativeinstaller.UninstallMountDir(context.GetRunMode(), log)
   896  		componentResults = append(componentResults, componentResult(string(ComponentNameMountDir), err))
   897  		if err != nil {
   898  			log.Errorf("Error uninstalling mount dir: %s", err)
   899  		}
   900  	}
   901  
   902  	if libkb.IsIn(string(ComponentNameFuse), components, false) {
   903  		err = libnativeinstaller.UninstallFuse(context.GetRunMode(), log)
   904  		componentResults = append(componentResults, componentResult(string(ComponentNameFuse), err))
   905  		if err != nil {
   906  			log.Errorf("Error uninstalling fuse: %s", err)
   907  		}
   908  	}
   909  
   910  	if libkb.IsIn(string(ComponentNameApp), components, false) {
   911  		err = libnativeinstaller.UninstallApp(context.GetRunMode(), log)
   912  		componentResults = append(componentResults, componentResult(string(ComponentNameApp), err))
   913  		if err != nil {
   914  			log.Errorf("Error uninstalling app: %s", err)
   915  		}
   916  	}
   917  
   918  	if libkb.IsIn(string(ComponentNameKBNM), components, false) {
   919  		err = UninstallKBNM(log)
   920  		componentResults = append(componentResults, componentResult(string(ComponentNameKBNM), err))
   921  		if err != nil {
   922  			log.Errorf("Error uninstalling kbnm: %s", err)
   923  		}
   924  	}
   925  
   926  	if libkb.IsIn(string(ComponentNameCLIPaths), components, false) {
   927  		err = libnativeinstaller.UninstallCommandLinePrivileged(context.GetRunMode(), log)
   928  		componentResults = append(componentResults, componentResult(string(ComponentNameCLIPaths), err))
   929  		if err != nil {
   930  			log.Errorf("Error uninstalling command line (privileged): %s", err)
   931  		}
   932  	}
   933  
   934  	if libkb.IsIn(string(ComponentNameHelper), components, false) {
   935  		err = libnativeinstaller.UninstallHelper(context.GetRunMode(), log)
   936  		componentResults = append(componentResults, componentResult(string(ComponentNameHelper), err))
   937  		if err != nil {
   938  			log.Errorf("Error uninstalling helper: %s", err)
   939  		}
   940  	}
   941  
   942  	if libkb.IsIn(string(ComponentNameCLI), components, false) {
   943  		err = uninstallCommandLine(log)
   944  		componentResults = append(componentResults, componentResult(string(ComponentNameCLI), err))
   945  		if err != nil {
   946  			log.Errorf("Error uninstalling command line: %s", err)
   947  		}
   948  	}
   949  
   950  	return newUninstallResult(componentResults)
   951  }
   952  
   953  // UninstallKBFSOnStop removes KBFS services and unmounts and removes /keybase from the system
   954  func UninstallKBFSOnStop(context Context, log Log) error {
   955  	runMode := context.GetRunMode()
   956  	mountDir, err := context.GetMountDir()
   957  	if err != nil {
   958  		return err
   959  	}
   960  	log.Info("UninstallKBFSOnStop: uninstalling from mountdir: %s", mountDir)
   961  
   962  	if err := UninstallKBFS(context, mountDir, false, log); err != nil {
   963  		return err
   964  	}
   965  
   966  	log.Info("Uninstalled mount: %s", mountDir)
   967  	if err := libnativeinstaller.UninstallMountDir(runMode, log); err != nil {
   968  		return fmt.Errorf("Error uninstalling mount: %s", err)
   969  	}
   970  
   971  	return nil
   972  }
   973  
   974  func unmount(mountDir string, forceUnmount bool, log Log) error {
   975  	log.Debug("Checking if mounted: %s", mountDir)
   976  	if _, serr := os.Stat(mountDir); os.IsNotExist(serr) {
   977  		return nil
   978  	}
   979  
   980  	mounted, err := mounter.IsMounted(mountDir, log)
   981  	if err != nil {
   982  		return err
   983  	}
   984  	log.Debug("Mounted: %s", strconv.FormatBool(mounted))
   985  	if mounted {
   986  		err = mounter.Unmount(mountDir, forceUnmount, log)
   987  		if err != nil {
   988  			return err
   989  		}
   990  	}
   991  	empty, err := libkb.IsDirEmpty(mountDir)
   992  	if err != nil {
   993  		return err
   994  	}
   995  	if !empty {
   996  		return fmt.Errorf("Mount has files after unmounting: %s", mountDir)
   997  	}
   998  	return nil
   999  }
  1000  
  1001  // UninstallKBFS uninstalls all KBFS services, unmounts and optionally removes the mount directory
  1002  func UninstallKBFS(context Context, mountDir string, forceUnmount bool, log Log) error {
  1003  	err := UninstallKBFSServices(context, log)
  1004  	if err != nil {
  1005  		log.Warning("Couldn't stop KBFS: %+v", err)
  1006  		// Continue despite the error, since the uninstall doesn't
  1007  		// seem to be resilient against the "fallback" PID getting out
  1008  		// of sync with the true KBFS PID.  TODO: fix the fallback PID
  1009  		// logic?
  1010  	}
  1011  
  1012  	return unmount(mountDir, forceUnmount, log)
  1013  }
  1014  
  1015  // AutoInstallWithStatus runs the auto install and returns a result
  1016  func AutoInstallWithStatus(context Context, binPath string, force bool, timeout time.Duration, log Log) keybase1.InstallResult {
  1017  	_, res, err := autoInstall(context, binPath, force, timeout, log)
  1018  	if err != nil {
  1019  		return keybase1.InstallResult{Status: keybase1.StatusFromCode(keybase1.StatusCode_SCInstallError, err.Error())}
  1020  	}
  1021  	return newInstallResult(res)
  1022  }
  1023  
  1024  // AutoInstall runs the auto install
  1025  func AutoInstall(context Context, binPath string, force bool, timeout time.Duration, log Log) (newProc bool, err error) {
  1026  	if context.GetRunMode() != libkb.ProductionRunMode {
  1027  		return false, fmt.Errorf("Auto install is only supported in production")
  1028  	}
  1029  
  1030  	newProc, _, err = autoInstall(context, binPath, force, timeout, log)
  1031  	return
  1032  }
  1033  
  1034  func autoInstall(context Context, binPath string, force bool, timeout time.Duration, log Log) (newProc bool, componentResults []keybase1.ComponentResult, err error) {
  1035  	log.Debug("+ AutoInstall for launchd")
  1036  	defer func() {
  1037  		log.Debug("- AutoInstall -> %v, %v", newProc, err)
  1038  	}()
  1039  	label := DefaultServiceLabel(context.GetRunMode())
  1040  	if label == "" {
  1041  		err = fmt.Errorf("No service label to install")
  1042  		return
  1043  	}
  1044  	resolvedBinPath, err := chooseBinPath(binPath)
  1045  	if err != nil {
  1046  		return
  1047  	}
  1048  	log.Debug("Using binPath: %s", resolvedBinPath)
  1049  
  1050  	service := launchd.NewService(label)
  1051  	plist, err := keybasePlist(context, resolvedBinPath, label, log)
  1052  	if err != nil {
  1053  		return
  1054  	}
  1055  
  1056  	// Check if plist is valid. If so we're already installed and return.
  1057  	plistValid, err := service.CheckPlist(plist)
  1058  	if err != nil || plistValid {
  1059  		return
  1060  	}
  1061  
  1062  	err = InstallService(context, binPath, true, timeout, log)
  1063  	componentResults = append(componentResults, componentResult(string(ComponentNameService), err))
  1064  	if err != nil {
  1065  		return
  1066  	}
  1067  
  1068  	newProc = true
  1069  	return
  1070  }
  1071  
  1072  // CheckIfValidLocation checks if the current environment is running from a valid location.
  1073  // For example, this will return an error if this isn't running from /Applications/Keybase.app on MacOS.
  1074  func CheckIfValidLocation() *keybase1.Error {
  1075  	keybasePath, err := BinPath()
  1076  	if err != nil {
  1077  		return keybase1.FromError(err)
  1078  	}
  1079  	inDMG, _, err := isPathInDMG(keybasePath)
  1080  	if err != nil {
  1081  		return keybase1.FromError(err)
  1082  	}
  1083  	if inDMG {
  1084  		return keybase1.NewError(keybase1.StatusCode_SCInvalidLocationError, "You should copy Keybase to /Applications before running.")
  1085  	}
  1086  	return nil
  1087  }
  1088  
  1089  // isPathInDMG errors if the path is inside dmg
  1090  func isPathInDMG(p string) (inDMG bool, bundlePath string, err error) {
  1091  	var stat syscall.Statfs_t
  1092  	err = syscall.Statfs(p, &stat)
  1093  	if err != nil {
  1094  		return
  1095  	}
  1096  
  1097  	// mntRootFS identifies the root filesystem (http://www.opensource.apple.com/source/xnu/xnu-344.26/bsd/sys/mount.h)
  1098  	const mntRootFS = 0x00004000
  1099  
  1100  	if (stat.Flags & mntRootFS) != 0 {
  1101  		// We're on the root filesystem so we're not in a DMG
  1102  		return
  1103  	}
  1104  
  1105  	bundlePath = bundleDirForPath(p)
  1106  	if bundlePath != "" {
  1107  		// Look for Applications symlink in the same folder as Keybase.app, and if
  1108  		// we find it, we're really likely to be in a mounted dmg
  1109  		appLink := filepath.Join(filepath.Dir(bundlePath), "Applications")
  1110  		fi, ferr := os.Lstat(appLink)
  1111  		if os.IsNotExist(ferr) {
  1112  			return
  1113  		}
  1114  		isLink := (fi.Mode()&os.ModeSymlink != 0)
  1115  		if isLink {
  1116  			inDMG = true
  1117  			return
  1118  		}
  1119  	}
  1120  
  1121  	return
  1122  }
  1123  
  1124  func bundleDirForPath(p string) string {
  1125  	paths := libkb.SplitPath(p)
  1126  	pathJoined := ""
  1127  	if strings.HasPrefix(p, "/") {
  1128  		pathJoined = "/"
  1129  	}
  1130  	found := false
  1131  	for _, sp := range paths {
  1132  		pathJoined = filepath.Join(pathJoined, sp)
  1133  		if sp == "Keybase.app" {
  1134  			found = true
  1135  			break
  1136  		}
  1137  	}
  1138  	if !found {
  1139  		return ""
  1140  	}
  1141  	return filepath.Clean(pathJoined)
  1142  }
  1143  
  1144  func newInstallResult(componentResults []keybase1.ComponentResult) keybase1.InstallResult {
  1145  	return keybase1.InstallResult{ComponentResults: componentResults, Status: statusFromResults(componentResults)}
  1146  }
  1147  
  1148  func newUninstallResult(componentResults []keybase1.ComponentResult) keybase1.UninstallResult {
  1149  	return keybase1.UninstallResult{ComponentResults: componentResults, Status: statusFromResults(componentResults)}
  1150  }
  1151  
  1152  func statusFromResults(componentResults []keybase1.ComponentResult) keybase1.Status {
  1153  	var errorMessages []string
  1154  	for _, cs := range componentResults {
  1155  		if cs.Status.Code != 0 {
  1156  			errorMessages = append(errorMessages, fmt.Sprintf("%s (%s)", cs.Status.Desc, cs.Name))
  1157  		}
  1158  	}
  1159  
  1160  	if len(errorMessages) > 0 {
  1161  		return keybase1.StatusFromCode(keybase1.StatusCode_SCInstallError, strings.Join(errorMessages, ". "))
  1162  	}
  1163  
  1164  	return keybase1.StatusOK("")
  1165  }
  1166  
  1167  func componentResult(name string, err error) keybase1.ComponentResult {
  1168  	if err != nil {
  1169  		exitCode := 0
  1170  		if exitError, ok := err.(*exec.ExitError); ok {
  1171  			ws := exitError.Sys().(syscall.WaitStatus)
  1172  			exitCode = ws.ExitStatus()
  1173  		}
  1174  		return keybase1.ComponentResult{Name: name, Status: keybase1.StatusFromCode(keybase1.StatusCode_SCInstallError, err.Error()), ExitCode: exitCode}
  1175  	}
  1176  	return keybase1.ComponentResult{Name: name, Status: keybase1.StatusOK("")}
  1177  }
  1178  
  1179  // KBFSBinPath returns the path to the KBFS executable.
  1180  // If binPath (directory) is specified, it will override the default (which is in
  1181  // the same directory where the keybase executable is).
  1182  func KBFSBinPath(runMode libkb.RunMode, binPath string) (string, error) {
  1183  	// If it's brew lookup path by formula name
  1184  	if libkb.IsBrewBuild {
  1185  		if runMode != libkb.ProductionRunMode {
  1186  			return "", fmt.Errorf("Not supported in this run mode")
  1187  		}
  1188  		kbfsBinName := kbfsBinName()
  1189  		prefix, err := brewPath(kbfsBinName)
  1190  		if err != nil {
  1191  			return "", err
  1192  		}
  1193  		return filepath.Join(prefix, "bin", kbfsBinName), nil
  1194  	}
  1195  
  1196  	return kbfsBinPathDefault(runMode, binPath)
  1197  }
  1198  
  1199  func brewPath(formula string) (string, error) {
  1200  	// Get the homebrew install path prefix for this formula
  1201  	prefixOutput, err := exec.Command("brew", "--prefix", formula).Output()
  1202  	if err != nil {
  1203  		return "", fmt.Errorf("Error checking brew path: %s", err)
  1204  	}
  1205  	prefix := strings.TrimSpace(string(prefixOutput))
  1206  	return prefix, nil
  1207  }
  1208  
  1209  // OSVersion returns the OS version
  1210  func OSVersion() (semver.Version, error) {
  1211  	out, err := exec.Command("sw_vers", "-productVersion").Output()
  1212  	if err != nil {
  1213  		return semver.Version{}, err
  1214  	}
  1215  	swver := strings.TrimSpace(string(out))
  1216  	// The version might not be semver compliant for beta macOS (e.g. "10.12")
  1217  	if strings.Count(swver, ".") == 1 {
  1218  		swver += ".0"
  1219  	}
  1220  	return semver.Make(swver)
  1221  }
  1222  
  1223  // InstallUpdater installs the updater launchd service
  1224  func InstallUpdater(context Context, keybaseBinPath string, force bool, timeout time.Duration, log Log) error {
  1225  	if context.GetRunMode() != libkb.ProductionRunMode {
  1226  		return fmt.Errorf("Updater not supported in this run mode")
  1227  	}
  1228  	keybaseBinPath, err := chooseBinPath(keybaseBinPath)
  1229  	if err != nil {
  1230  		return err
  1231  	}
  1232  	updaterBinPath := filepath.Join(filepath.Dir(keybaseBinPath), "updater")
  1233  	if err != nil {
  1234  		return err
  1235  	}
  1236  	log.Debug("Using updater path: %s", updaterBinPath)
  1237  
  1238  	label := DefaultUpdaterLabel(context.GetRunMode())
  1239  	service := launchd.NewService(label)
  1240  	plist, err := updaterPlist(context, label, updaterBinPath, keybaseBinPath, log)
  1241  	if err != nil {
  1242  		return err
  1243  	}
  1244  
  1245  	if err = UninstallUpdaterService(context, log); err != nil {
  1246  		log.Debug("unable uninstall updater service: %s", err)
  1247  	}
  1248  	log.Debug("Installing updater service (%s, timeout=%s)", label, timeout)
  1249  	if _, err := installUpdaterService(context, service, plist, timeout, log); err != nil {
  1250  		log.Errorf("Error installing updater service: %s", err)
  1251  		_, err = fallbackStartProcess(context, service, plist, log)
  1252  		return err
  1253  	}
  1254  	return nil
  1255  }
  1256  
  1257  func updaterPlist(context Context, label string, serviceBinPath string, keybaseBinPath string, log Log) (launchd.Plist, error) {
  1258  	plistArgs := []string{fmt.Sprintf("-path-to-keybase=%s", keybaseBinPath)}
  1259  	envVars := DefaultLaunchdEnvVars(label)
  1260  	comment := "It's not advisable to edit this plist, it may be overwritten"
  1261  	logFile := filepath.Join(context.GetLogDir(), libkb.UpdaterLogFileName)
  1262  	err := libkb.MakeParentDirs(log, logFile)
  1263  	if err != nil {
  1264  		return launchd.Plist{}, err
  1265  	}
  1266  	return launchd.NewPlist(label, serviceBinPath, plistArgs, envVars, logFile, comment), nil
  1267  }
  1268  
  1269  func installUpdaterService(context Context, service launchd.Service, plist launchd.Plist, wait time.Duration, log Log) (*keybase1.ServiceStatus, error) {
  1270  	err := launchd.Install(plist, wait, log)
  1271  	if err != nil {
  1272  		log.Warning("error installing updater service via launchd: %s", err)
  1273  		return nil, err
  1274  	}
  1275  
  1276  	st, err := serviceStatusFromLaunchd(service, "", wait, log)
  1277  	return &st, err
  1278  }
  1279  
  1280  // UninstallUpdaterService removes updater launchd service
  1281  func UninstallUpdaterService(context Context, log Log) error {
  1282  	runMode := context.GetRunMode()
  1283  	pidFile := filepath.Join(context.GetCacheDir(), "updater.pid")
  1284  	err0 := fallbackKillProcess(context, log, DefaultUpdaterLabel(runMode), "", pidFile)
  1285  	err1 := launchd.Uninstall(DefaultUpdaterLabel(runMode), defaultLaunchdWait, log)
  1286  	return libkb.CombineErrors(err0, err1)
  1287  }
  1288  
  1289  // kbfsBinName returns the name for the KBFS executable
  1290  func kbfsBinName() string {
  1291  	return "kbfs"
  1292  }
  1293  
  1294  func updaterBinName() (string, error) {
  1295  	return "updater", nil
  1296  }
  1297  
  1298  // RunApp starts the app
  1299  func RunApp(context Context, log Log) error {
  1300  	appPath, err := libnativeinstaller.AppBundleForPath()
  1301  	if err != nil {
  1302  		return err
  1303  	}
  1304  	ver, err := OSVersion()
  1305  	if err != nil {
  1306  		log.Errorf("Error trying to determine OS version: %s", err)
  1307  		return nil
  1308  	}
  1309  	if ver.LT(semver.MustParse("10.0.0")) {
  1310  		return fmt.Errorf("App isn't supported on this OS version: %s", ver)
  1311  	}
  1312  
  1313  	log.Info("Opening %s", appPath)
  1314  	// If app is already open this is a no-op, the -g option will cause to open
  1315  	// in background.
  1316  	out, err := exec.Command("/usr/bin/open", "-g", appPath).Output()
  1317  	if err != nil {
  1318  		return fmt.Errorf("Error trying to open %s: %s; %s", appPath, err, out)
  1319  	}
  1320  	return nil
  1321  }
  1322  
  1323  // InstallLogPath doesn't exist on darwin as an independent log file (see desktop app log)
  1324  func InstallLogPath() (string, error) {
  1325  	return "", nil
  1326  }
  1327  
  1328  // WatchdogLogPath doesn't exist on darwin as an independent log file (see desktop app log)
  1329  func WatchdogLogPath(string) (string, error) {
  1330  	return "", nil
  1331  }
  1332  
  1333  // SystemLogPath is where privileged keybase processes log to on darwin
  1334  func SystemLogPath() string {
  1335  	return "/Library/Logs/keybase.system.log"
  1336  }
  1337  
  1338  func fallbackPIDFilename(service launchd.Service) string {
  1339  	return filepath.Join(os.TempDir(), "kbfb."+service.Label())
  1340  }
  1341  
  1342  func fallbackStartProcessAndWaitForInfo(context Context, service launchd.Service, plist launchd.Plist, infoPath string, timeout time.Duration, log Log) (int, error) {
  1343  	pid, err := fallbackStartProcess(context, service, plist, log)
  1344  	if err != nil {
  1345  		return 0, err
  1346  	}
  1347  	log.Debug("%s process started: %d, waiting for service info file %s to exist", service.Label(), pid, context.GetServiceInfoPath())
  1348  	spid := strconv.Itoa(pid)
  1349  	_, err = libkb.WaitForServiceInfoFile(infoPath, service.Label(), spid, timeout, log)
  1350  	if err != nil {
  1351  		log.Warning("error waiting for %s info file %s: %s", service.Label(), infoPath, err)
  1352  		return 0, err
  1353  	}
  1354  	log.Debug("%s info file %s exists, fallback service start worked", service.Label(), infoPath)
  1355  	return pid, nil
  1356  }
  1357  
  1358  func fallbackStartProcess(context Context, service launchd.Service, plist launchd.Plist, log Log) (int, error) {
  1359  	log.Info("falling back to starting %s process manually", service.Label())
  1360  
  1361  	cmd := plist.FallbackCommand()
  1362  	log.Info("fallback command: %s %v (env: %v)", cmd.Path, cmd.Args, cmd.Env)
  1363  	err := cmd.Start()
  1364  	if err != nil {
  1365  		log.Warning("error starting fallback command for %s (%s): %s", service.Label(), cmd.Path, err)
  1366  		return 0, err
  1367  	}
  1368  
  1369  	if cmd.Process == nil {
  1370  		log.Warning("no process after starting command %s", cmd.Path)
  1371  		return 0, fmt.Errorf("failed to start %s (%s)", service.Label(), cmd.Path)
  1372  	}
  1373  
  1374  	log.Info("fallback command started: %s, pid = %d", cmd.Path, cmd.Process.Pid)
  1375  
  1376  	// save pid in a fallback file so uninstall can check
  1377  	f, err := os.Create(fallbackPIDFilename(service))
  1378  	if err != nil {
  1379  		log.Warning("failed to create fallback pid file %s: %s", fallbackPIDFilename(service), err)
  1380  	} else {
  1381  		if _, err := f.Write([]byte(fmt.Sprintf("%d", cmd.Process.Pid))); err != nil {
  1382  			log.Warning("failed to write to fallback pid file %s: %s", fallbackPIDFilename(service), err)
  1383  		}
  1384  		f.Close()
  1385  	}
  1386  
  1387  	return cmd.Process.Pid, nil
  1388  }
  1389  
  1390  func fallbackKillProcess(context Context, log Log, label string, infoPath, pidPath string) error {
  1391  	svc := launchd.NewService(label)
  1392  	svc.SetLogger(log)
  1393  
  1394  	fpid := fallbackPIDFilename(svc)
  1395  
  1396  	exists, err := libkb.FileExists(fpid)
  1397  	if err != nil {
  1398  		return err
  1399  	}
  1400  	if !exists {
  1401  		log.Debug("no fallback pid file exists for %s (%s)", svc.Label(), fpid)
  1402  		return nil
  1403  	}
  1404  
  1405  	log.Debug("fallback pid file exists for %s", svc.Label())
  1406  	p, err := os.ReadFile(fpid)
  1407  	if err != nil {
  1408  		return err
  1409  	}
  1410  	pid := string(bytes.TrimSpace(p))
  1411  
  1412  	found := false
  1413  	if infoPath != "" {
  1414  		serviceInfo, err := libkb.LoadServiceInfo(infoPath)
  1415  		if err != nil {
  1416  			log.Warning("error loading service info for %s in file %s: %s", svc.Label(), infoPath, err)
  1417  			return err
  1418  		}
  1419  		if serviceInfo != nil {
  1420  			if strconv.Itoa(serviceInfo.Pid) != pid {
  1421  				log.Warning("service info pid %d does not match fallback pid %s, not killing anything", serviceInfo.Pid, pid)
  1422  				return errors.New("fallback PID mismatch")
  1423  			}
  1424  			found = true
  1425  		}
  1426  	}
  1427  
  1428  	if !found && pidPath != "" {
  1429  		lp, err := os.ReadFile(pidPath)
  1430  		if err != nil {
  1431  			return err
  1432  		}
  1433  		lpid := string(bytes.TrimSpace(lp))
  1434  		if lpid != pid {
  1435  			log.Warning("pid in file %s (%d) does not match fallback pid %s, not killing anything", pidPath, lpid, pid)
  1436  			return errors.New("fallback PID mismatch")
  1437  		}
  1438  		found = true
  1439  	}
  1440  
  1441  	if !found {
  1442  		log.Warning("neither infoPath or pidPath specified, cannot verify fallback PID.")
  1443  		return errors.New("unable to verify fallback PID")
  1444  	}
  1445  
  1446  	log.Debug("stopping process %s for %s", pid, svc.Label())
  1447  	cmd := exec.Command("kill", pid)
  1448  	out, err := cmd.CombinedOutput()
  1449  	if err != nil {
  1450  		log.Warning("error stopping process %s for %s: %s", pid, svc.Label(), err)
  1451  		log.Debug("command output: %s", out)
  1452  		return err
  1453  	}
  1454  	log.Debug("process %s for %s stopped", pid, svc.Label())
  1455  	if err := os.Remove(fpid); err != nil {
  1456  		log.Warning("error removing fallback pid file %s: %s", fpid, err)
  1457  		return err
  1458  	}
  1459  	log.Debug("fallback pid file %s for %s removed", fpid, svc.Label())
  1460  
  1461  	return nil
  1462  }
  1463  
  1464  // StartUpdateIfNeeded starts to update the app if there's one available. It
  1465  // calls `updater check` internally so it ignores the snooze.
  1466  func StartUpdateIfNeeded(ctx context.Context, log logger.Logger) error {
  1467  	updaterPath, err := UpdaterBinPath()
  1468  	if err != nil {
  1469  		return err
  1470  	}
  1471  	cmd := exec.Command(updaterPath, "check")
  1472  	// Run it in a new process group so when we are killed eventually by the
  1473  	// updater, we don't bring down the updater too.
  1474  	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
  1475  	if err = cmd.Start(); err != nil {
  1476  		return err
  1477  	}
  1478  	pid := -1
  1479  	if cmd.Process != nil {
  1480  		pid = cmd.Process.Pid
  1481  	}
  1482  	log.Debug("Started background updater process (%s). pid=%d", updaterPath, pid)
  1483  	if err = cmd.Wait(); err != nil {
  1484  		log.Debug("updater cmd failed: %s", err)
  1485  	}
  1486  	// Ignore the exit status here as user may have hit "Ignore". If we are
  1487  	// here without getting killed, it's likely user has hit "Ignore". Just
  1488  	// just return `nil` and GUI would check for update info again where it'd
  1489  	// know we don't need to update anymore.
  1490  	return nil
  1491  }