joonas-fi / joonas.fi

My personal blog
https://joonas.fi/
Apache License 2.0
3 stars 1 forks source link

SIGINT on Microsoft's glue huffing OS #52

Open joonas-fi opened 4 years ago

joonas-fi commented 4 years ago
package main

import (
    "fmt"
    "log"
    "os"
    "os/exec"
    "os/signal"
    "syscall"
    "time"

    "golang.org/x/sys/windows"
)

func main() {
    if err := logic(); err != nil {
        panic(err)
    }
}

func logic() error {
    switch {
    case len(os.Args) == 1:
        return parent()
    case len(os.Args) == 2 && os.Args[1] == "child":
        return child()
    default:
        return fmt.Errorf(`usage: %s ["child"]`, os.Args[0])
    }
}

func child() error {
    sigs := catchAllSignals()

    log.Println("[CHILD] started")

    select {
    case <-time.After(5 * time.Second):
        log.Println("[CHILD] reached timeout, bye")
        return nil
    case sig := <-sigs:
        log.Printf("[CHILD] got %s", sig)
        return nil
    }
}

func parent() error {
    go func() {
        log.Printf("[PARENT] got UNEXPECTED %s - exiting", <-catchAllSignals())
        time.Sleep(1 * time.Second) // to log our "child exited" msg
        os.Exit(1)
    }()

    childCmd := exec.Command(os.Args[0], "child")
    childCmd.SysProcAttr = &syscall.SysProcAttr{
        CreationFlags: windows.CREATE_NEW_PROCESS_GROUP,
    }
    childCmd.Stdout = os.Stdout
    childCmd.Stderr = os.Stderr

    if err := childCmd.Start(); err != nil {
        return err
    }

    clientExit := make(chan error, 1)

    go func() {
        clientExit <- childCmd.Wait()
    }()

    // make sure child gets time to set up signal handlers
    time.Sleep(1 * time.Second)

    /*  Summary:

        CTRL_CLOSE_EVENT works for different process group: NO
        CTRL_C_EVENT works for different process group: NO
        CTRL_BREAK_EVENT works for different process group: YES
    */
    // if err := windows.GenerateConsoleCtrlEvent(windows.CTRL_C_EVENT, uint32(client.Process.Pid)); err != nil {
    // if err := windows.GenerateConsoleCtrlEvent(windows.CTRL_CLOSE_EVENT, uint32(client.Process.Pid)); err != nil {
    if err := windows.GenerateConsoleCtrlEvent(windows.CTRL_BREAK_EVENT, uint32(childCmd.Process.Pid)); err != nil {
        return err
    }

    log.Printf("[PARENT] child exited: %v", <-clientExit)

    return nil
}

func catchAllSignals() <-chan os.Signal {
    sigs := make(chan os.Signal, 1)
    signal.Notify(sigs) // = all signals
    return sigs
}