diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 0147bc5428a..625164a2390 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -394,6 +394,7 @@ Kgm kiali kib killall +Kinfo kiwano KNOWNFOLDERID kontainer @@ -596,6 +597,7 @@ PFlags PGID pgrep pidfile +pidfd pids PII pikachu @@ -627,6 +629,7 @@ PQgrl prakhar prebuilds Privs +PROCARGS progresskey projectroletemplatebinding Prometheis diff --git a/src/go/rdctl/cmd/factoryReset.go b/src/go/rdctl/cmd/factoryReset.go index a52f9d36afc..bddcad4c18e 100644 --- a/src/go/rdctl/cmd/factoryReset.go +++ b/src/go/rdctl/cmd/factoryReset.go @@ -51,7 +51,7 @@ Use the --remove-kubernetes-cache=BOOLEAN flag to also remove the cached Kuberne if err != nil { return fmt.Errorf("failed to get paths: %w", err) } - return factoryreset.DeleteData(paths, removeKubernetesCache) + return factoryreset.DeleteData(cmd.Context(), paths, removeKubernetesCache) }, } diff --git a/src/go/rdctl/pkg/directories/directories.go b/src/go/rdctl/pkg/directories/directories.go new file mode 100644 index 00000000000..0f00fb2dddb --- /dev/null +++ b/src/go/rdctl/pkg/directories/directories.go @@ -0,0 +1,53 @@ +/* +Copyright © 2024 SUSE LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package directories + +import ( + "os" + "path/filepath" + "runtime" +) + +// GetApplicationDirectory returns the installation directory of the application. +func GetApplicationDirectory() (string, error) { + exePathWithSymlinks, err := os.Executable() + if err != nil { + return "", err + } + + exePath, err := filepath.EvalSymlinks(exePathWithSymlinks) + if err != nil { + return "", err + } + + platform := runtime.GOOS + if runtime.GOOS == "windows" { + // On Windows, we use "win32" instead of "windows". + platform = "win32" + } + + // Given the path to the exe, find its directory, and drop the + // "resources\win32\bin" suffix (possibly with another "resources" in front). + // On mac, we need to drop "Contents/Resources/resources/darwin/bin". + resultDir := filepath.Dir(exePath) + for _, part := range []string{"bin", platform, "resources", "Resources", "Contents"} { + for filepath.Base(resultDir) == part { + resultDir = filepath.Dir(resultDir) + } + } + return resultDir, nil +} diff --git a/src/go/rdctl/pkg/directories/directories_test.go b/src/go/rdctl/pkg/directories/directories_test.go new file mode 100644 index 00000000000..53848e344c4 --- /dev/null +++ b/src/go/rdctl/pkg/directories/directories_test.go @@ -0,0 +1,31 @@ +/* +Copyright © 2024 SUSE LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package directories_test + +import ( + "testing" + + "github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/directories" + "github.com/stretchr/testify/assert" +) + +func TestGetApplicationDirectory(t *testing.T) { + _, err := directories.GetApplicationDirectory() + assert.NoError(t, err) + // `go test` makes a temporary directory, so we can't sensibly test the + // return value. +} diff --git a/src/go/rdctl/pkg/directories/directories_windows.go b/src/go/rdctl/pkg/directories/directories_windows.go index ae4811c73b2..02841954ee0 100644 --- a/src/go/rdctl/pkg/directories/directories_windows.go +++ b/src/go/rdctl/pkg/directories/directories_windows.go @@ -19,7 +19,6 @@ package directories import ( "errors" "fmt" - "path/filepath" "unsafe" "golang.org/x/sys/windows" @@ -44,43 +43,6 @@ func InvokeWin32WithBuffer(cb func(size int) error) error { } } -// GetApplicationDirectory returns the installation directory of the application. -func GetApplicationDirectory() (string, error) { - var exePath string - err := InvokeWin32WithBuffer(func(bufSize int) error { - buf := make([]uint16, bufSize) - n, err := windows.GetModuleFileName(windows.Handle(0), &buf[0], uint32(bufSize)) - if err != nil { - return err - } - if n == uint32(bufSize) { - // If the buffer is too small, GetModuleFileName returns the buffer size, - // and the result includes the null character. If the buffer is large - // enough, GetModuleFileName returns the string length, _excluding_ the - // null character. - if buf[bufSize-1] == 0 { - // The buffer contains a null character - return windows.ERROR_INSUFFICIENT_BUFFER - } - } - exePath = windows.UTF16ToString(buf[:n]) - return nil - }) - if err != nil { - return "", err - } - - // Given the path to the exe, find its directory, and drop the - // "resources\win32\bin" suffix (possibly with another "resources" in front). - resultDir := filepath.Dir(exePath) - for _, part := range []string{"bin", "win32", "resources"} { - for filepath.Base(resultDir) == part { - resultDir = filepath.Dir(resultDir) - } - } - return resultDir, nil -} - func GetLocalAppDataDirectory() (string, error) { dir, err := getKnownFolder(windows.FOLDERID_LocalAppData) if err != nil { diff --git a/src/go/rdctl/pkg/directories/directories_windows_test.go b/src/go/rdctl/pkg/directories/directories_windows_test.go index 13ac10385b6..a2067f4a56d 100644 --- a/src/go/rdctl/pkg/directories/directories_windows_test.go +++ b/src/go/rdctl/pkg/directories/directories_windows_test.go @@ -23,13 +23,6 @@ import ( "golang.org/x/sys/windows" ) -func TestGetApplicationDirectory(t *testing.T) { - _, err := GetApplicationDirectory() - assert.NoError(t, err) - // `go test` makes a temporary directory, so we can't sensibly test the - // return value. -} - func TestGetKnownFolder(t *testing.T) { t.Run("AppData", func(t *testing.T) { expected := os.Getenv("APPDATA") diff --git a/src/go/rdctl/pkg/factoryreset/delete_data_darwin.go b/src/go/rdctl/pkg/factoryreset/delete_data_darwin.go index ee81dab6171..9354b46945b 100644 --- a/src/go/rdctl/pkg/factoryreset/delete_data_darwin.go +++ b/src/go/rdctl/pkg/factoryreset/delete_data_darwin.go @@ -1,19 +1,25 @@ package factoryreset import ( + "context" "os" "path/filepath" "github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/autostart" "github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/paths" + "github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/process" "github.com/sirupsen/logrus" ) -func DeleteData(appPaths paths.Paths, removeKubernetesCache bool) error { +func DeleteData(ctx context.Context, appPaths paths.Paths, removeKubernetesCache bool) error { if err := autostart.EnsureAutostart(false); err != nil { logrus.Errorf("Failed to remove autostart configuration: %s", err) } + if err := process.TerminateProcessInDirectory(appPaths.ExtensionRoot, false); err != nil { + logrus.Errorf("Failed to stop extension processes, ignoring: %s", err) + } + pathList := []string{ appPaths.AltAppHome, appPaths.Config, diff --git a/src/go/rdctl/pkg/factoryreset/delete_data_linux.go b/src/go/rdctl/pkg/factoryreset/delete_data_linux.go index 3d7aba2a96f..183f03935e2 100644 --- a/src/go/rdctl/pkg/factoryreset/delete_data_linux.go +++ b/src/go/rdctl/pkg/factoryreset/delete_data_linux.go @@ -1,15 +1,17 @@ package factoryreset import ( + "context" "os" "path/filepath" "github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/autostart" "github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/paths" + "github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/process" "github.com/sirupsen/logrus" ) -func DeleteData(appPaths paths.Paths, removeKubernetesCache bool) error { +func DeleteData(ctx context.Context, appPaths paths.Paths, removeKubernetesCache bool) error { if err := autostart.EnsureAutostart(false); err != nil { logrus.Errorf("Failed to remove autostart configuration: %s", err) } @@ -19,6 +21,10 @@ func DeleteData(appPaths paths.Paths, removeKubernetesCache bool) error { logrus.Errorf("Error getting home directory: %s", err) } + if err := process.TerminateProcessInDirectory(appPaths.ExtensionRoot, false); err != nil { + logrus.Errorf("Failed to stop extension processes, ignoring: %s", err) + } + pathList := []string{ appPaths.AltAppHome, appPaths.Config, diff --git a/src/go/rdctl/pkg/factoryreset/delete_data_windows.go b/src/go/rdctl/pkg/factoryreset/delete_data_windows.go index 231027dcc31..ca1b0a7f9ab 100644 --- a/src/go/rdctl/pkg/factoryreset/delete_data_windows.go +++ b/src/go/rdctl/pkg/factoryreset/delete_data_windows.go @@ -1,13 +1,16 @@ package factoryreset import ( + "context" + "github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/autostart" "github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/paths" + "github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/process" "github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/wsl" "github.com/sirupsen/logrus" ) -func DeleteData(paths paths.Paths, removeKubernetesCache bool) error { +func DeleteData(ctx context.Context, appPaths paths.Paths, removeKubernetesCache bool) error { if err := autostart.EnsureAutostart(false); err != nil { logrus.Errorf("Failed to remove autostart configuration: %s", err) } @@ -16,6 +19,9 @@ func DeleteData(paths paths.Paths, removeKubernetesCache bool) error { logrus.Errorf("could not unregister WSL: %s", err) return err } + if err := process.TerminateProcessInDirectory(appPaths.ExtensionRoot, false); err != nil { + logrus.Errorf("Failed to stop extension processes, ignoring: %s", err) + } if err := deleteWindowsData(!removeKubernetesCache, "rancher-desktop"); err != nil { logrus.Errorf("could not delete data: %s", err) return err diff --git a/src/go/rdctl/pkg/factoryreset/factory_reset_windows.go b/src/go/rdctl/pkg/factoryreset/factory_reset_windows.go index c06f73b5615..25cd13095c2 100644 --- a/src/go/rdctl/pkg/factoryreset/factory_reset_windows.go +++ b/src/go/rdctl/pkg/factoryreset/factory_reset_windows.go @@ -25,20 +25,13 @@ import ( "os" "os/exec" "path/filepath" - "sort" - "strings" - "unsafe" "github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/directories" + "github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/process" "github.com/sirupsen/logrus" "golang.org/x/sys/windows" ) -var ( - pKernel32 = windows.NewLazySystemDLL("kernel32.dll") - pEnumProcesses = pKernel32.NewProc("K32EnumProcesses") -) - // CheckProcessWindows - returns true if Rancher Desktop is still running, false if it isn't // along with an error condition if there's a problem detecting that. // @@ -75,106 +68,9 @@ func KillRancherDesktop() error { return fmt.Errorf("could not find application directory: %w", err) } - var processes []uint32 - err = directories.InvokeWin32WithBuffer(func(size int) error { - processes = make([]uint32, size) - var bytesReturned uint32 - // We can't use `windows.EnumProcesses`, because it passes in an incorrect - // value for the second argument (`cb`). - elementSize := unsafe.Sizeof(uint32(0)) - bufferSize := uintptr(len(processes)) * elementSize - n, _, err := pEnumProcesses.Call( - uintptr(unsafe.Pointer(&processes[0])), - bufferSize, - uintptr(unsafe.Pointer(&bytesReturned)), - ) - if n == 0 { - return err - } - if uintptr(bytesReturned) >= bufferSize { - return windows.ERROR_INSUFFICIENT_BUFFER - } - processesFound := uintptr(bytesReturned) / elementSize - logrus.Tracef("got %d processes", processesFound) - processes = processes[:processesFound] - return nil - }) + err = process.TerminateProcessInDirectory(appDir, true) if err != nil { - return fmt.Errorf("could not get process list: %w", err) - } - - sort.Slice(processes, func(i, j int) bool { - return processes[i] < processes[j] - }) - var processesToKill []uint32 - for _, pid := range processes { - // Add a scope to help with defer - (func(pid uint32) { - if pid == uint32(os.Getpid()) { - // Skip the current process. - return - } - - hProc, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid) - if err != nil { - // We can't open privileged processes, processes that have exited since, - // idle process, etc.; so we log this at trace level instead. - logrus.Tracef("failed to open pid %d: %s (skipping)", pid, err) - return - } - defer func() { _ = windows.CloseHandle(hProc) }() - - var imageName string - err = directories.InvokeWin32WithBuffer(func(size int) error { - nameBuf := make([]uint16, size) - charsWritten := uint32(size) - err := windows.QueryFullProcessImageName(hProc, 0, &nameBuf[0], &charsWritten) - if err != nil { - logrus.Tracef("failed to get image name for pid %d: %s", pid, err) - return err - } - if charsWritten >= uint32(size)-1 { - logrus.Tracef("buffer too small for pid %d image name", pid) - return windows.ERROR_INSUFFICIENT_BUFFER - } - imageName = windows.UTF16ToString(nameBuf) - return nil - }) - if err != nil { - logrus.Debugf("failed to get process name of pid %d: %s (skipping)", pid, err) - return - } - - relPath, err := filepath.Rel(appDir, imageName) - if err != nil { - // This may be because they're on different drives, network shares, etc. - logrus.Tracef("failed to make pid %d image %s relative to %s: %s", pid, imageName, appDir, err) - return - } - if strings.HasPrefix(relPath, "..") { - // Relative path includes "../" prefix, not a child of appDir - logrus.Tracef("skipping pid %d (%s), not in app %s", pid, imageName, appDir) - return - } - - logrus.Tracef("will terminate pid %d image %s", pid, imageName) - processesToKill = append(processesToKill, pid) - })(pid) - } - - for _, pid := range processesToKill { - (func() { - hProc, err := windows.OpenProcess(windows.PROCESS_TERMINATE, false, pid) - if err != nil { - logrus.Infof("failed to open process %d for termination, skipping", pid) - return - } - defer func() { _ = windows.CloseHandle(hProc) }() - - if err = windows.TerminateProcess(hProc, 0); err != nil { - logrus.Infof("failed to terminate process %d: %s", pid, err) - } - })() + return err } return nil diff --git a/src/go/rdctl/pkg/process/process_darwin.go b/src/go/rdctl/pkg/process/process_darwin.go new file mode 100644 index 00000000000..66bda9f7feb --- /dev/null +++ b/src/go/rdctl/pkg/process/process_darwin.go @@ -0,0 +1,85 @@ +/* +Copyright © 2024 SUSE LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package process + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "slices" + "strings" + + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +const ( + CTL_KERN = "kern" + KERN_PROCARGS = 38 +) + +// TerminateProcessInDirectory terminates all processes where the executable +// resides within the given directory, as gracefully as possible. If `force` is +// set, SIGKILL is used instead. +func TerminateProcessInDirectory(directory string, force bool) error { + procs, err := unix.SysctlKinfoProcSlice("kern.proc.all") + if err != nil { + return fmt.Errorf("failed to list processes: %w", err) + } + for _, proc := range procs { + pid := int(proc.Proc.P_pid) + // Don't kill the current process + if pid == os.Getpid() { + continue + } + buf, err := unix.SysctlRaw(CTL_KERN, KERN_PROCARGS, pid) + if err != nil { + if !errors.Is(err, unix.EINVAL) { + logrus.Infof("Failed to get command line of pid %d: %s", pid, err) + } + continue + } + // The buffer starts with a null-terminated executable path, plus + // command line arguments and things. + index := slices.Index(buf, 0) + if index < 0 { + // If we have unexpected data, don't fall over. + continue + } + procPath := string(buf[:index]) + relPath, err := filepath.Rel(directory, procPath) + if err != nil || strings.HasPrefix(relPath, "../") { + continue + } + process, err := os.FindProcess(pid) + if err != nil { + continue + } + if force { + err = process.Signal(unix.SIGKILL) + } else { + err = process.Signal(unix.SIGTERM) + } + if err == nil { + logrus.Infof("Terminated process %d (%s)", pid, procPath) + } else if !errors.Is(err, unix.EINVAL) { + logrus.Infof("Ignoring failure to terminate pid %d (%s): %s", pid, procPath, err) + } + } + return nil +} diff --git a/src/go/rdctl/pkg/process/process_linux.go b/src/go/rdctl/pkg/process/process_linux.go new file mode 100644 index 00000000000..e3cafe955e1 --- /dev/null +++ b/src/go/rdctl/pkg/process/process_linux.go @@ -0,0 +1,77 @@ +/* +Copyright © 2024 SUSE LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package process + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +// TerminateProcessInDirectory terminates all processes where the executable +// resides within the given directory, as gracefully as possible. If `force` is +// set, SIGKILL is used instead. +func TerminateProcessInDirectory(directory string, force bool) error { + // Check /proc//exe to see if they're the correct file. + pidfds, err := os.ReadDir("/proc") + if err != nil { + return fmt.Errorf("error listing processes: %w", err) + } + for _, pidfd := range pidfds { + if !pidfd.IsDir() { + continue + } + pid, err := strconv.Atoi(pidfd.Name()) + if err != nil { + continue + } + // Don't kill the current process + if pid == os.Getpid() { + continue + } + procPath, err := os.Readlink(filepath.Join("/proc", pidfd.Name(), "exe")) + if err != nil { + continue + } + relPath, err := filepath.Rel(directory, procPath) + if err != nil || strings.HasPrefix(relPath, "../") { + continue + } + proc, err := os.FindProcess(pid) + if err != nil { + continue + } + if force { + err = proc.Signal(unix.SIGKILL) + } else { + err = proc.Signal(unix.SIGTERM) + } + if err == nil { + logrus.Infof("Terminated process %d (%s)", pid, procPath) + } else if !errors.Is(err, unix.EINVAL) { + logrus.Infof("Ignoring failure to terminate pid %d (%s): %s", pid, procPath, err) + } + } + + return nil +} diff --git a/src/go/rdctl/pkg/process/process_windows.go b/src/go/rdctl/pkg/process/process_windows.go new file mode 100644 index 00000000000..f50b80364a4 --- /dev/null +++ b/src/go/rdctl/pkg/process/process_windows.go @@ -0,0 +1,116 @@ +/* +Copyright © 2024 SUSE LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package process + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "unsafe" + + "github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/directories" + "github.com/sirupsen/logrus" + "golang.org/x/sys/windows" +) + +// TerminateProcessInDirectory terminates all processes where the executable +// resides within the given directory, as gracefully as possible. If `force` is +// set, SIGKILL is used instead. +func TerminateProcessInDirectory(directory string, force bool) error { + var pids []uint32 + // Try EnumProcesses until the number of pids returned is less than the + // buffer size. + err := directories.InvokeWin32WithBuffer(func(size int) error { + pids = make([]uint32, size) + var bytesReturned uint32 + err := windows.EnumProcesses(pids, &bytesReturned) + if err != nil || len(pids) < 1 { + return fmt.Errorf("failed to enumerate processes: %w", err) + } + pidsReturned := uintptr(bytesReturned) / unsafe.Sizeof(pids[0]) + if pidsReturned < uintptr(len(pids)) { + // Remember to truncate the pids to only the valid set. + pids = pids[:pidsReturned] + return nil + } + return windows.ERROR_INSUFFICIENT_BUFFER + }) + if err != nil { + return fmt.Errorf("could not get process list: %w", err) + } + + for _, pid := range pids { + // Don't kill the current process + if pid == uint32(os.Getpid()) { + continue + } + // Do each iteration in a function so defer statements run faster. + (func() { + hProc, err := windows.OpenProcess( + windows.PROCESS_QUERY_LIMITED_INFORMATION|windows.PROCESS_TERMINATE, + false, + pid) + if err != nil { + logrus.Infof("Ignoring error opening process %d: %s", pid, err) + return + } + defer func() { + _ = windows.CloseHandle(hProc) + }() + + var executablePath string + err = directories.InvokeWin32WithBuffer(func(size int) error { + nameBuf := make([]uint16, size) + charsWritten := uint32(size) + err := windows.QueryFullProcessImageName(hProc, 0, &nameBuf[0], &charsWritten) + if err != nil { + logrus.Tracef("failed to get image name for pid %d: %s", pid, err) + return err + } + if charsWritten >= uint32(size)-1 { + return windows.ERROR_INSUFFICIENT_BUFFER + } + executablePath = windows.UTF16ToString(nameBuf) + return nil + }) + if err != nil { + logrus.Debugf("failed to get process name of pid %d: %s (skipping)", pid, err) + return + } + + relPath, err := filepath.Rel(directory, executablePath) + if err != nil { + // This may be because they're on different drives, network shares, etc. + logrus.Tracef("failed to make pid %d image %s relative to %s: %s", pid, executablePath, directory, err) + return + } + if strings.HasPrefix(relPath, "..") { + // Relative path includes "../" prefix, not a child of given directory. + logrus.Tracef("skipping pid %d (%s), not in %s", pid, executablePath, directory) + return + } + + logrus.Tracef("will terminate pid %d image %s", pid, executablePath) + if err = windows.TerminateProcess(hProc, 0); err != nil { + logrus.Errorf("failed to terminate pid %d (%s): %s", pid, executablePath, err) + } + })() + } + + return nil +} diff --git a/src/go/rdctl/pkg/shutdown/shutdown.go b/src/go/rdctl/pkg/shutdown/shutdown.go index dbcf4c89563..d1a6c077b27 100644 --- a/src/go/rdctl/pkg/shutdown/shutdown.go +++ b/src/go/rdctl/pkg/shutdown/shutdown.go @@ -28,6 +28,7 @@ import ( "github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/directories" "github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/factoryreset" p "github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/paths" + "github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/process" "github.com/sirupsen/logrus" ) @@ -92,11 +93,15 @@ func FinishShutdown(waitForShutdown bool, initiatingCommand InitiatingCommand) e if err != nil { logrus.Errorf("Ignoring error trying to kill qemu: %s", err) } + appDir, err := directories.GetApplicationDirectory() + if err != nil { + return fmt.Errorf("failed to find application directory: %w", err) + } switch runtime.GOOS { case "darwin": - return s.waitForAppToDieOrKillIt(checkProcessDarwin, pkillDarwin, 5, 1, "the app") + return s.waitForAppToDieOrKillIt(checkProcessDarwin, killFunc(appDir), 5, 1, "the app") case "linux": - return s.waitForAppToDieOrKillIt(checkProcessLinux, pkillLinux, 5, 1, "the app") + return s.waitForAppToDieOrKillIt(checkProcessLinux, killFunc(appDir), 5, 1, "the app") default: return fmt.Errorf("unhandled runtime: %q", runtime.GOOS) } @@ -208,18 +213,8 @@ func deleteLima() error { return runCommandIgnoreOutput(exec.Command(limaCtlPath, "delete", "--force", "0")) } -func pkillDarwin() error { - err := pkill("-9", "-a", "-l", "-f", "Contents/MacOS/Rancher Desktop") - if err != nil { - return fmt.Errorf("failed to kill Rancher Desktop: %w", err) +func killFunc(directory string) func() error { + return func() error { + return process.TerminateProcessInDirectory(directory, true) } - return nil -} - -func pkillLinux() error { - err := pkill("-9", "rancher-desktop") - if err != nil { - return fmt.Errorf("failed to kill Rancher Desktop: %w", err) - } - return nil }