os: add handling of os.Interrupt for windows

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>
This commit is contained in:
Constantin Konstantinidis 2021-11-28 15:19:15 +01:00 committed by Ian Lance Taylor
parent 85b5f86584
commit 345184496c
7 changed files with 45 additions and 43 deletions

View File

@ -344,3 +344,4 @@ func LoadGetFinalPathNameByHandle() error {
//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

View File

@ -54,6 +54,7 @@ var (
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")
@ -161,6 +162,14 @@ func GetAdaptersAddresses(family uint32, flags uint32, reserved uintptr, adapter
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)

View File

@ -7,6 +7,7 @@
package exec_test
import (
"internal/testenv"
"io"
"os"
"os/exec"
@ -54,3 +55,20 @@ func TestNoInheritHandles(t *testing.T) {
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)
}
}

View File

@ -15,9 +15,7 @@ import (
// 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

View File

@ -47,13 +47,14 @@ func (p *Process) wait() (ps *ProcessState, err error) {
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 {
@ -61,11 +62,17 @@ func (p *Process) signal(sig Signal) error {
}
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 {

View File

@ -15,21 +15,6 @@ import (
"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 = `
@ -90,7 +75,7 @@ func main() {
}
go func() {
time.Sleep(1 * time.Second)
sendCtrlBreak(t, cmd.Process.Pid)
cmd.Process.Signal(os.Interrupt)
}()
err = cmd.Wait()
if err != nil {

View File

@ -59,22 +59,6 @@ func TestVectoredHandlerDontCrashOnLibrary(t *testing.T) {
}
}
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) {
@ -183,7 +167,7 @@ func TestLibraryCtrlHandler(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)
}
}()