Add GenerateConsoleCtrlEvent call to internal syscall package.
Define ErrProcessDone while reviewing handling of os.Signal().
Update test to run for windows using the added call.
Fixes #42311
Fixes #46354
Change-Id: I460955efc76c4febe04b612ac9a0670e62ba5ff3
Reviewed-on: https://go-review.googlesource.com/c/go/+/367495
Trust: Patrik Nyblom <pnyb@google.com>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
//sys DestroyEnvironmentBlock(block *uint16) (err error) = userenv.DestroyEnvironmentBlock
//sys RtlGenRandom(buf []byte) (err error) = advapi32.SystemFunction036
+//sys GenerateConsoleCtrlEvent(ctrlEvent uint32, processGroupID uint32) (err error) = kernel32.GenerateConsoleCtrlEvent
procSetTokenInformation = modadvapi32.NewProc("SetTokenInformation")
procSystemFunction036 = modadvapi32.NewProc("SystemFunction036")
procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses")
+ procGenerateConsoleCtrlEvent = modkernel32.NewProc("GenerateConsoleCtrlEvent")
procGetACP = modkernel32.NewProc("GetACP")
procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW")
procGetConsoleCP = modkernel32.NewProc("GetConsoleCP")
return
}
+func GenerateConsoleCtrlEvent(ctrlEvent uint32, processGroupID uint32) (err error) {
+ r1, _, e1 := syscall.Syscall(procGenerateConsoleCtrlEvent.Addr(), 2, uintptr(ctrlEvent), uintptr(processGroupID), 0)
+ if r1 == 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
+
func GetACP() (acp uint32) {
r0, _, _ := syscall.Syscall(procGetACP.Addr(), 0, 0, 0, 0)
acp = uint32(r0)
package exec_test
import (
+ "internal/testenv"
"io"
"os"
"os/exec"
t.Fatalf("got exit code %d; want 88", exitError.ExitCode())
}
}
+
+func TestErrProcessDone(t *testing.T) {
+ testenv.MustHaveGoBuild(t)
+ // On Windows, ProcAttr cannot be empty
+ p, err := os.StartProcess(testenv.GoToolPath(t), []string{""},
+ &os.ProcAttr{Dir: "", Env: nil, Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}, Sys: nil})
+ if err != nil {
+ t.Errorf("starting test process: %v", err)
+ }
+ _, err = p.Wait()
+ if err != nil {
+ t.Errorf("Wait: %v", err)
+ }
+ if got := p.Signal(os.Kill); got != os.ErrProcessDone {
+ t.Fatalf("got %v want %v", got, os.ErrProcessDone)
+ }
+}
// The only signal values guaranteed to be present in the os package on all
// systems are os.Interrupt (send the process an interrupt) and os.Kill (force
-// the process to exit). On Windows, sending os.Interrupt to a process with
-// os.Process.Signal is not implemented; it will return an error instead of
-// sending a signal.
+// the process to exit).
var (
Interrupt Signal = syscall.SIGINT
Kill Signal = syscall.SIGKILL
func (p *Process) signal(sig Signal) error {
handle := atomic.LoadUintptr(&p.handle)
- if handle == uintptr(syscall.InvalidHandle) {
- return syscall.EINVAL
- }
if p.done() {
return ErrProcessDone
}
- if sig == Kill {
+ s, ok := sig.(syscall.Signal)
+ if !ok {
+ return syscall.EWINDOWS
+ }
+ if s == syscall.SIGKILL {
var terminationHandle syscall.Handle
e := syscall.DuplicateHandle(^syscall.Handle(0), syscall.Handle(handle), ^syscall.Handle(0), &terminationHandle, syscall.PROCESS_TERMINATE, false, 0)
if e != nil {
}
runtime.KeepAlive(p)
defer syscall.CloseHandle(terminationHandle)
- e = syscall.TerminateProcess(syscall.Handle(terminationHandle), 1)
+ e = syscall.TerminateProcess(terminationHandle, 1)
return NewSyscallError("TerminateProcess", e)
}
- // TODO(rsc): Handle Interrupt too?
- return syscall.Errno(syscall.EWINDOWS)
+ if s == syscall.SIGINT {
+ e := windows.GenerateConsoleCtrlEvent(syscall.CTRL_BREAK_EVENT, uint32(p.Pid))
+ if e != nil {
+ return NewSyscallError("GenerateConsoleCtrlEvent", e)
+ }
+ return nil
+ }
+ return syscall.EWINDOWS
}
func (p *Process) release() error {
"time"
)
-func sendCtrlBreak(t *testing.T, pid int) {
- d, e := syscall.LoadDLL("kernel32.dll")
- if e != nil {
- t.Fatalf("LoadDLL: %v\n", e)
- }
- p, e := d.FindProc("GenerateConsoleCtrlEvent")
- if e != nil {
- t.Fatalf("FindProc: %v\n", e)
- }
- r, _, e := p.Call(syscall.CTRL_BREAK_EVENT, uintptr(pid))
- if r == 0 {
- t.Fatalf("GenerateConsoleCtrlEvent: %v\n", e)
- }
-}
-
func TestCtrlBreak(t *testing.T) {
// create source file
const source = `
}
go func() {
time.Sleep(1 * time.Second)
- sendCtrlBreak(t, cmd.Process.Pid)
+ cmd.Process.Signal(os.Interrupt)
}()
err = cmd.Wait()
if err != nil {
}
}
-func sendCtrlBreak(pid int) error {
- kernel32, err := syscall.LoadDLL("kernel32.dll")
- if err != nil {
- return fmt.Errorf("LoadDLL: %v\n", err)
- }
- generateEvent, err := kernel32.FindProc("GenerateConsoleCtrlEvent")
- if err != nil {
- return fmt.Errorf("FindProc: %v\n", err)
- }
- result, _, err := generateEvent.Call(syscall.CTRL_BREAK_EVENT, uintptr(pid))
- if result == 0 {
- return fmt.Errorf("GenerateConsoleCtrlEvent: %v\n", err)
- }
- return nil
-}
-
// TestCtrlHandler tests that Go can gracefully handle closing the console window.
// See https://golang.org/issues/41884.
func TestCtrlHandler(t *testing.T) {
} else if strings.TrimSpace(line) != "ready" {
errCh <- fmt.Errorf("unexpected message: %v", line)
} else {
- errCh <- sendCtrlBreak(cmd.Process.Pid)
+ errCh <- cmd.Process.Signal(syscall.SIGINT)
}
}()