github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/installmgr/stop.go (about)

     1  package installmgr
     2  
     3  import (
     4  	"errors"
     5  	"os"
     6  	"runtime"
     7  	"strings"
     8  	"syscall"
     9  	"time"
    10  
    11  	"github.com/ActiveState/cli/internal/condition"
    12  	"github.com/ActiveState/cli/internal/config"
    13  	"github.com/ActiveState/cli/internal/constants"
    14  	"github.com/ActiveState/cli/internal/errs"
    15  	"github.com/ActiveState/cli/internal/fileutils"
    16  	"github.com/ActiveState/cli/internal/installation"
    17  	"github.com/ActiveState/cli/internal/locale"
    18  	"github.com/ActiveState/cli/internal/logging"
    19  	"github.com/ActiveState/cli/internal/multilog"
    20  	"github.com/ActiveState/cli/internal/osutils"
    21  	"github.com/ActiveState/cli/internal/rtutils"
    22  	"github.com/shirou/gopsutil/v3/process"
    23  )
    24  
    25  func StopRunning(installPath string) (rerr error) {
    26  	cfg, err := config.New()
    27  	if err != nil {
    28  		return errs.Wrap(err, "Could not get config")
    29  	}
    30  	defer rtutils.Closer(cfg.Close, &rerr)
    31  
    32  	err = stopSvc(installPath)
    33  	if err != nil {
    34  		multilog.Critical("Could not stop running service, error: %v", errs.JoinMessage(err))
    35  		return locale.WrapError(err, "err_stop_svc", "Unable to stop state-svc process. Please manually kill any running processes with name [NOTICE]state-svc[/RESET] and try again")
    36  	}
    37  
    38  	return nil
    39  }
    40  
    41  func stopSvc(installPath string) error {
    42  	svcExec, err := installation.ServiceExecFromDir(installPath)
    43  	if err != nil && !errors.Is(err, fileutils.ErrorFileNotFound) {
    44  		return locale.WrapError(err, "err_service_exec_dir", "", installPath)
    45  	}
    46  
    47  	if fileutils.FileExists(svcExec) {
    48  		exitCode, _, err := osutils.Execute(svcExec, []string{"stop"}, nil)
    49  		if err != nil {
    50  			// We don't return these errors because we want to fall back on killing the process
    51  			multilog.Error("Stopping %s returned error: %s", constants.SvcAppName, errs.JoinMessage(err))
    52  		} else if exitCode != 0 {
    53  			multilog.Error("Stopping %s exited with code %d", constants.SvcAppName, exitCode)
    54  		}
    55  	}
    56  
    57  	if condition.OnCI() { // prevent killing valid parallel instances while on CI
    58  		return nil
    59  	}
    60  
    61  	procs, err := process.Processes()
    62  	if err != nil {
    63  		return errs.Wrap(err, "Could not get list of running processes")
    64  	}
    65  
    66  	// This is a bit heavy handed but ensures that there are
    67  	// no running state-svc processes which could lead to
    68  	// errors when updating
    69  	for _, p := range procs {
    70  		n, err := p.Name()
    71  		if err != nil {
    72  			logging.Debug("Could not get process name: %v", err) // maybe we don't have permission
    73  			continue
    74  		}
    75  
    76  		svcName := constants.ServiceCommandName + osutils.ExeExtension
    77  		if n == svcName {
    78  			exe, err := p.Exe()
    79  			if err != nil {
    80  				if runtime.GOOS == "darwin" && strings.Contains(err.Error(), "bad call to lsof") {
    81  					// There's nothing we can do about this, so just debug log it.
    82  					logging.Debug("Could not get executable path for state-svc process, error: %v", err)
    83  					continue
    84  				}
    85  				multilog.Error("Could not get executable path for state-svc process, error: %v", err)
    86  				continue
    87  			}
    88  
    89  			if !strings.Contains(strings.ToLower(exe), "activestate") {
    90  				multilog.Error("Found state-svc process in unexpected directory: %s", exe)
    91  				continue
    92  			}
    93  
    94  			logging.Debug("Found running state-svc process with PID %d, at %s", p.Pid, exe)
    95  			err = stopSvcProcess(p, n)
    96  			if err != nil {
    97  				return errs.Wrap(err, "Could not stop service process")
    98  			}
    99  		}
   100  	}
   101  
   102  	return nil
   103  }
   104  
   105  func stopSvcProcess(proc *process.Process, name string) error {
   106  	// Process library does not have support for sending signals to Windows processes
   107  	if runtime.GOOS == "windows" {
   108  		return killProcess(proc, name)
   109  	}
   110  
   111  	signalErrs := make(chan error)
   112  	go func() {
   113  		signalErrs <- proc.SendSignal(syscall.SIGTERM)
   114  	}()
   115  
   116  	select {
   117  	case err := <-signalErrs:
   118  		if err != nil {
   119  			multilog.Error("Could not send SIGTERM to %s  process, error: %v", name, err)
   120  			return killProcess(proc, name)
   121  		}
   122  
   123  		running, err := proc.IsRunning()
   124  		if err != nil {
   125  			return errs.Wrap(err, "Could not check if %s is still running, error: %v", name, err)
   126  		}
   127  		if running {
   128  			return killProcess(proc, name)
   129  		}
   130  
   131  		logging.Debug("Stopped %s process with SIGTERM", name)
   132  		return nil
   133  	case <-time.After(time.Second):
   134  		return killProcess(proc, name)
   135  	}
   136  }
   137  
   138  func killProcess(proc *process.Process, name string) error {
   139  	children, err := proc.Children()
   140  	if err == nil {
   141  		for _, c := range children {
   142  			err = c.Kill()
   143  			if err != nil {
   144  				if osutils.IsAccessDeniedError(err) {
   145  					return locale.WrapExternalError(err, "err_insufficient_permissions")
   146  				} else if errors.Is(err, os.ErrProcessDone) {
   147  					return nil
   148  				}
   149  				return errs.Wrap(err, "Could not kill child process of %s", name)
   150  			}
   151  		}
   152  	} else {
   153  		logging.Error("Could not get child process: %v", err)
   154  	}
   155  
   156  	err = proc.Kill()
   157  	if err != nil {
   158  		if osutils.IsAccessDeniedError(err) {
   159  			return locale.WrapExternalError(err, "err_insufficient_permissions")
   160  		} else if errors.Is(err, os.ErrProcessDone) {
   161  			return nil
   162  		}
   163  		return errs.Wrap(err, "Could not kill %s process", name)
   164  	}
   165  
   166  	logging.Debug("Stopped %s process with SIGKILL", name)
   167  	return nil
   168  }