//go:build windows package process import ( "errors" "fmt" "os/exec" "syscall" "unsafe" "golang.org/x/sys/windows" ) type scope struct { cmd *exec.Cmd job windows.Handle } func newScope(cmd *exec.Cmd) (*scope, error) { job, err := windows.CreateJobObject(nil, nil) if err != nil { return nil, err } limits := windows.JOBOBJECT_EXTENDED_LIMIT_INFORMATION{} limits.BasicLimitInformation.LimitFlags = windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE _, err = windows.SetInformationJobObject(job, windows.JobObjectExtendedLimitInformation, uintptr(unsafe.Pointer(&limits)), uint32(unsafe.Sizeof(limits))) if err != nil { windows.CloseHandle(job) return nil, err } if cmd.SysProcAttr == nil { cmd.SysProcAttr = &syscall.SysProcAttr{} } // Prevent the child from spawning descendants before it belongs to our job. cmd.SysProcAttr.CreationFlags |= windows.CREATE_SUSPENDED return &scope{cmd: cmd, job: job}, nil } func (s *scope) attach() error { handle, err := windows.OpenProcess(windows.PROCESS_SET_QUOTA|windows.PROCESS_TERMINATE, false, uint32(s.cmd.Process.Pid)) if err != nil { return err } defer windows.CloseHandle(handle) if err := windows.AssignProcessToJobObject(s.job, handle); err != nil { _ = s.cmd.Process.Kill() return fmt.Errorf("own command process before execution: %w", err) } return nil } func (s *scope) resume() error { snapshot, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPTHREAD, 0) if err != nil { return err } defer windows.CloseHandle(snapshot) entry := windows.ThreadEntry32{} entry.Size = uint32(unsafe.Sizeof(entry)) for err = windows.Thread32First(snapshot, &entry); err == nil; err = windows.Thread32Next(snapshot, &entry) { if entry.OwnerProcessID != uint32(s.cmd.Process.Pid) { continue } thread, err := windows.OpenThread(windows.THREAD_SUSPEND_RESUME, false, entry.ThreadID) if err != nil { return err } _, resumeErr := windows.ResumeThread(thread) return errors.Join(resumeErr, windows.CloseHandle(thread)) } return fmt.Errorf("locate suspended command thread: %w", err) } func (s *scope) kill() error { return windows.TerminateJobObject(s.job, 1) } func (s *scope) close() { _ = windows.CloseHandle(s.job) }