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 }