diff --git a/cmd/azemu/adapter.go b/cmd/azemu/adapter.go index 49bd4f5..f4f6d95 100644 --- a/cmd/azemu/adapter.go +++ b/cmd/azemu/adapter.go @@ -7,7 +7,6 @@ import ( "os" "os/exec" "path/filepath" - "syscall" "time" "github.com/rs/zerolog/log" @@ -60,7 +59,7 @@ func execBinary(name string, args []string) error { log.Info().Str("binary", bin).Strs("args", args).Msg("exec") argv := append([]string{name}, args...) - return syscall.Exec(bin, argv, os.Environ()) + return execProcess(bin, argv, os.Environ()) } // probeHealth sends a single GET to the health endpoint and returns true if @@ -111,9 +110,7 @@ func startAzemuBackground() (*os.Process, error) { proc, err := os.StartProcess(self, []string{"azemu", "serve"}, &os.ProcAttr{ Env: os.Environ(), Files: []*os.File{os.Stdin, logFile, logFile}, - Sys: &syscall.SysProcAttr{ - Setsid: true, - }, + Sys: detachSysProcAttr(), }) logFile.Close() if err != nil { diff --git a/cmd/azemu/adapter_unix.go b/cmd/azemu/adapter_unix.go new file mode 100644 index 0000000..f5d50f5 --- /dev/null +++ b/cmd/azemu/adapter_unix.go @@ -0,0 +1,17 @@ +//go:build !windows + +package main + +import "syscall" + +// execProcess replaces the current process image with the named binary, the +// classic exec(3) behaviour. On success it never returns. +func execProcess(bin string, argv, env []string) error { + return syscall.Exec(bin, argv, env) +} + +// detachSysProcAttr starts the child in its own session (setsid) so it +// survives the parent's exit and detaches from the controlling terminal. +func detachSysProcAttr() *syscall.SysProcAttr { + return &syscall.SysProcAttr{Setsid: true} +} diff --git a/cmd/azemu/adapter_windows.go b/cmd/azemu/adapter_windows.go new file mode 100644 index 0000000..7e811e3 --- /dev/null +++ b/cmd/azemu/adapter_windows.go @@ -0,0 +1,38 @@ +//go:build windows + +package main + +import ( + "errors" + "os" + "os/exec" + "syscall" +) + +// execProcess emulates Unix exec on Windows, which has no execve. It runs the +// binary as a child inheriting stdio, waits for it, and exits with the child's +// exit code so the wrapper is transparent to the caller. +func execProcess(bin string, argv, env []string) error { + cmd := exec.Command(bin, argv[1:]...) //nolint:gosec // bin is resolved via exec.LookPath + cmd.Args = argv + cmd.Env = env + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + var exit *exec.ExitError + if errors.As(err, &exit) { + os.Exit(exit.ExitCode()) + } + return err + } + os.Exit(0) + return nil +} + +// detachSysProcAttr starts the child in a new process group so it is not killed +// when the parent's console closes. Windows has no setsid equivalent. +func detachSysProcAttr() *syscall.SysProcAttr { + return &syscall.SysProcAttr{CreationFlags: 0x00000200} // CREATE_NEW_PROCESS_GROUP +}