github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/util/process/process_windows.go (about)

     1  package process
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"sync"
     7  	"syscall"
     8  
     9  	"golang.org/x/sys/windows"
    10  )
    11  
    12  var (
    13  	kernel32                  = syscall.NewLazyDLL("kernel32.dll")
    14  	procAttachConsole         = kernel32.NewProc("AttachConsole")
    15  	procFreeConsole           = kernel32.NewProc("FreeConsole")
    16  	procSetConsoleCtrlHandler = kernel32.NewProc("SetConsoleCtrlHandler")
    17  )
    18  
    19  func Exists(pid int) bool {
    20  	const da = syscall.STANDARD_RIGHTS_READ |
    21  		syscall.PROCESS_QUERY_INFORMATION |
    22  		syscall.SYNCHRONIZE
    23  	h, err := syscall.OpenProcess(da, false, uint32(pid))
    24  	if err != nil {
    25  		return false
    26  	}
    27  	defer func() {
    28  		_ = windows.Close(windows.Handle(h))
    29  	}()
    30  	// Refer to Microsoft documentation:
    31  	// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess
    32  	var exitCode uint32
    33  	err = windows.GetExitCodeProcess(windows.Handle(h), &exitCode)
    34  	if err != nil {
    35  		return false
    36  	}
    37  	const STILL_ACTIVE = 259
    38  	return exitCode == STILL_ACTIVE
    39  }
    40  
    41  // SendSignal sends a specified signal to a console process group that shares
    42  // the console associated with the calling process.
    43  //
    44  // Any signal except Interrupt simply terminates the given process p. SIGINT is
    45  // sent as a CTRL+C event to the console. It is important to note that the call
    46  // disables event handling for the current process.
    47  func SendSignal(p *os.Process, s os.Signal) error {
    48  	if p == nil || p.Pid == 0 {
    49  		return nil
    50  	}
    51  	// On Windows Go runtime does not handle signals (events) other than SIGINT.
    52  	if s == syscall.SIGINT {
    53  		if err := sendCtrlC(p.Pid); err == nil {
    54  			return nil
    55  		}
    56  	}
    57  	return p.Kill()
    58  }
    59  
    60  // A process can be attached to at most one console.
    61  // Refer to https://docs.microsoft.com/en-us/windows/console/attachconsole.
    62  var console sync.Mutex
    63  
    64  // Interrupt sends CTRL+C signal to the console.
    65  // Refer to https://github.com/golang/go/issues/6720 for details.
    66  func sendCtrlC(pid int) (err error) {
    67  	console.Lock()
    68  	defer console.Unlock()
    69  	ret, _, err := procAttachConsole.Call(uintptr(pid))
    70  	if ret == 0 && err != windows.ERROR_ACCESS_DENIED {
    71  		// A process can be attached to at most one console. If the calling
    72  		// process is already attached to a console, the error code returned is
    73  		// ERROR_ACCESS_DENIED (5). If the specified process does not have a
    74  		// console, the error code returned is ERROR_INVALID_HANDLE (6). If the
    75  		// specified process does not exist, the error code returned is
    76  		// ERROR_INVALID_PARAMETER (87).
    77  		return fmt.Errorf("AttachConsole: %w", err)
    78  	}
    79  	// Disable events handling for the current process
    80  	ret, _, err = procSetConsoleCtrlHandler.Call(0, 1)
    81  	if ret == 0 {
    82  		return fmt.Errorf("SetConsoleCtrlHandler: %w", err)
    83  	}
    84  	// Note on CTRL_C_EVENT: This signal cannot be generated for process
    85  	// groups. If dwProcessGroupId is nonzero, this function will succeed, but
    86  	// the CTRL+C signal will not be received by processes within the specified
    87  	// process group.
    88  	if err = windows.GenerateConsoleCtrlEvent(windows.CTRL_C_EVENT, 0); err != nil {
    89  		return fmt.Errorf("GenerateConsoleCtrlEvent: %w", err)
    90  	}
    91  	// A process can use the FreeConsole function to detach itself from its
    92  	// console. If other processes share the console, the console is not
    93  	// destroyed, but the process that called FreeConsole cannot refer to it.
    94  	// If the calling process is not already attached to a console,
    95  	// the error code returned is ERROR_INVALID_PARAMETER (87).
    96  	// The console must be released immediately after GenerateConsoleCtrlEvent.
    97  	_, _, _ = procFreeConsole.Call()
    98  	return nil
    99  }