-
Notifications
You must be signed in to change notification settings - Fork 5
/
resume.go
106 lines (96 loc) · 2.76 KB
/
resume.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// +build windows
package winjob
import (
"fmt"
"os/exec"
"unsafe"
"golang.org/x/sys/windows"
)
// Start creates a job object with the limits specified and starts the given
// command within the job. The process is created with suspended threads which
// are resumed when the process has been added to the job object.
func Start(cmd *exec.Cmd, limits ...Limit) (*JobObject, error) {
job, err := Create("", limits...)
if err != nil {
return nil, err
}
if err := StartInJobObject(cmd, job); err != nil {
_ = job.Close()
return nil, err
}
return job, nil
}
// StartInJobObject starts the given command within the job objects specified.
// The process is created with suspended threads which are resumed when the
// process is added to the job.
func StartInJobObject(cmd *exec.Cmd, job *JobObject) error {
if cmd.SysProcAttr == nil {
cmd.SysProcAttr = new(windows.SysProcAttr)
}
cmd.SysProcAttr.CreationFlags |= windows.CREATE_SUSPENDED
if err := cmd.Start(); err != nil {
return err
}
if err := job.Assign(cmd.Process); err != nil {
return err
}
return Resume(cmd)
}
// Resume resumes the process of the given command. The command should be
// created with CREATE_SUSPENDED flag:
//
// cmd.SysProcAttr = &windows.SysProcAttr{
// CreationFlags: windows.CREATE_SUSPENDED,
// }
//
// CREATE_SUSPENDED specifies that the primary thread of the new process is
// created in a suspended state, and does not run until the ResumeThread
// windows function is called.
func Resume(cmd *exec.Cmd) error {
if cmd.Process == nil {
return fmt.Errorf("process is nil")
}
return ResumeProcess(cmd.Process.Pid)
}
// ResumeProcess resumes the first found thread of the process.
func ResumeProcess(pid int) (err error) {
s, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPTHREAD, uint32(pid))
if err != nil {
return fmt.Errorf("CreateToolhelp32Snapshot: %w", err)
}
defer func() {
_ = windows.Close(s)
}()
var e windows.ThreadEntry32
e.Size = uint32(unsafe.Sizeof(e))
if err := windows.Thread32First(s, &e); err != nil {
return fmt.Errorf("Thread32First: %w", err)
}
for {
err := windows.Thread32Next(s, &e)
switch err {
default:
return fmt.Errorf("Thread32Next: %w", err)
case windows.ERROR_NO_MORE_FILES:
return fmt.Errorf("no threads found")
case nil:
if int(e.OwnerProcessID) == pid && e.ThreadID != 0 {
return ResumeThread(e.ThreadID)
}
}
}
}
// ResumeThread resumes given thread.
func ResumeThread(tid uint32) (err error) {
hThread, err := windows.OpenThread(windows.THREAD_SUSPEND_RESUME, false, tid)
if err != nil {
return fmt.Errorf("OpenThread: %w", err)
}
defer func() {
_ = windows.Close(hThread)
}()
if _, err = windows.ResumeThread(hThread); err != nil {
return fmt.Errorf("ResumeThread: %w", err)
}
return nil
}