1
2
3 package jobcontainers
4
5 import (
6 "context"
7 "fmt"
8 "io"
9 "os"
10 "sync"
11
12 "github.com/pkg/errors"
13 "golang.org/x/sys/windows"
14
15 "github.com/Microsoft/hcsshim/internal/conpty"
16 "github.com/Microsoft/hcsshim/internal/cow"
17 "github.com/Microsoft/hcsshim/internal/exec"
18 "github.com/Microsoft/hcsshim/internal/hcs"
19 "github.com/Microsoft/hcsshim/internal/log"
20 "github.com/Microsoft/hcsshim/internal/protocol/guestresource"
21 "github.com/Microsoft/hcsshim/internal/winapi"
22 )
23
24
25 type JobProcess struct {
26 cmd *exec.Exec
27 cpty *conpty.Pty
28 procLock sync.Mutex
29 stdioLock sync.Mutex
30 stdin io.WriteCloser
31 stdout io.ReadCloser
32 stderr io.ReadCloser
33 waitBlock chan struct{}
34 closedWaitOnce sync.Once
35 waitError error
36 }
37
38 var sigMap = map[string]int{
39 "CtrlC": windows.CTRL_C_EVENT,
40 "CtrlBreak": windows.CTRL_BREAK_EVENT,
41 "CtrlClose": windows.CTRL_CLOSE_EVENT,
42 "CtrlLogOff": windows.CTRL_LOGOFF_EVENT,
43 "CtrlShutdown": windows.CTRL_SHUTDOWN_EVENT,
44 }
45
46 var _ cow.Process = &JobProcess{}
47
48 func newProcess(cmd *exec.Exec, cpty *conpty.Pty) *JobProcess {
49 return &JobProcess{
50 cmd: cmd,
51 cpty: cpty,
52 waitBlock: make(chan struct{}),
53 }
54 }
55
56 func (p *JobProcess) ResizeConsole(ctx context.Context, width, height uint16) error {
57 if p.cpty == nil {
58 return errors.New("no pseudo console assigned for process")
59 }
60 if err := p.cpty.Resize(int16(width), int16(height)); err != nil {
61 return fmt.Errorf("failed to resize pseudo console for job container: %w", err)
62 }
63 return nil
64 }
65
66
67 func (p *JobProcess) Stdio() (io.Writer, io.Reader, io.Reader) {
68 return p.stdin, p.stdout, p.stderr
69 }
70
71
72 func (p *JobProcess) Signal(ctx context.Context, options interface{}) (bool, error) {
73 p.procLock.Lock()
74 defer p.procLock.Unlock()
75
76 if p.exited() {
77 return false, fmt.Errorf("signal not sent. process has already exited: %w", hcs.ErrProcessAlreadyStopped)
78 }
79
80
81 if options == nil {
82 if err := p.cmd.Kill(); err != nil {
83 return false, err
84 }
85 return true, nil
86 }
87
88 signalOptions, ok := options.(*guestresource.SignalProcessOptionsWCOW)
89 if !ok {
90 return false, errors.New("unknown signal options")
91 }
92
93 signal, ok := sigMap[string(signalOptions.Signal)]
94 if !ok {
95 return false, fmt.Errorf("unknown signal %s encountered", signalOptions.Signal)
96 }
97
98 if err := signalProcess(uint32(p.cmd.Pid()), signal); err != nil {
99 if errors.Is(err, os.ErrPermission) || hcs.IsAlreadyStopped(err) {
100
101 return false, fmt.Errorf("failed to send signal: %w", hcs.ErrProcessAlreadyStopped)
102 }
103 return false, errors.Wrap(err, "failed to send signal")
104 }
105 return true, nil
106 }
107
108
109 func (p *JobProcess) CloseStdin(ctx context.Context) error {
110 p.stdioLock.Lock()
111 defer p.stdioLock.Unlock()
112 if p.stdin != nil {
113 if err := p.stdin.Close(); err != nil {
114 return errors.Wrap(err, "failed to close job container stdin")
115 }
116 p.stdin = nil
117 }
118 return nil
119 }
120
121
122 func (p *JobProcess) CloseStdout(ctx context.Context) error {
123 p.stdioLock.Lock()
124 defer p.stdioLock.Unlock()
125 if p.stdout != nil {
126 if err := p.stdout.Close(); err != nil {
127 return errors.Wrap(err, "failed to close job container stdout")
128 }
129 p.stdout = nil
130 }
131 return nil
132 }
133
134
135 func (p *JobProcess) CloseStderr(ctx context.Context) error {
136 p.stdioLock.Lock()
137 defer p.stdioLock.Unlock()
138 if p.stderr != nil {
139 if err := p.stderr.Close(); err != nil {
140 return errors.Wrap(err, "failed to close job container stderr")
141 }
142 p.stderr = nil
143 }
144 return nil
145 }
146
147
148
149 func (p *JobProcess) Wait() error {
150 <-p.waitBlock
151 return p.waitError
152 }
153
154
155 func (p *JobProcess) Start() error {
156 return p.cmd.Start()
157 }
158
159
160 func (p *JobProcess) waitBackground(ctx context.Context) {
161 log.G(ctx).WithField("pid", p.Pid()).Debug("waitBackground for JobProcess")
162
163
164 err := p.cmd.Wait()
165
166 p.stdioLock.Lock()
167
168 if p.cpty != nil {
169 p.cpty.Close()
170 p.cpty = nil
171 }
172
173 p.stdin = nil
174 p.stdout = nil
175 p.stderr = nil
176 p.stdioLock.Unlock()
177
178 p.closedWaitOnce.Do(func() {
179 p.waitError = err
180 close(p.waitBlock)
181 })
182 }
183
184
185 func (p *JobProcess) ExitCode() (int, error) {
186 p.procLock.Lock()
187 defer p.procLock.Unlock()
188
189 if !p.exited() {
190 return -1, errors.New("process has not exited")
191 }
192 return p.cmd.ExitCode(), nil
193 }
194
195
196 func (p *JobProcess) Pid() int {
197 return p.cmd.Pid()
198 }
199
200
201 func (p *JobProcess) Close() error {
202 p.stdioLock.Lock()
203 if p.cpty != nil {
204 p.cpty.Close()
205 p.cpty = nil
206 }
207 if p.stdin != nil {
208 p.stdin.Close()
209 p.stdin = nil
210 }
211 if p.stdout != nil {
212 p.stdout.Close()
213 p.stdout = nil
214 }
215 if p.stderr != nil {
216 p.stderr.Close()
217 p.stderr = nil
218 }
219 p.stdioLock.Unlock()
220
221 p.closedWaitOnce.Do(func() {
222 p.waitError = hcs.ErrAlreadyClosed
223 close(p.waitBlock)
224 })
225 return nil
226 }
227
228
229
230 func (p *JobProcess) Kill(ctx context.Context) (bool, error) {
231 log.G(ctx).WithField("pid", p.Pid()).Debug("killing job process")
232
233 p.procLock.Lock()
234 defer p.procLock.Unlock()
235
236 if p.exited() {
237 return false, errors.New("kill not sent. process already exited")
238 }
239
240 if err := p.cmd.Kill(); err != nil {
241 return false, err
242 }
243 return true, nil
244 }
245
246 func (p *JobProcess) exited() bool {
247 return p.cmd.Exited()
248 }
249
250
251 func signalProcess(pid uint32, signal int) error {
252 hProc, err := windows.OpenProcess(winapi.PROCESS_ALL_ACCESS, true, pid)
253 if err != nil {
254 return errors.Wrap(err, "failed to open process")
255 }
256 defer func() {
257 _ = windows.Close(hProc)
258 }()
259
260
261
262
263
264
265
266
267
268 k32, err := windows.LoadLibrary("kernel32.dll")
269 if err != nil {
270 return errors.Wrap(err, "failed to load kernel32 library")
271 }
272 defer func() {
273 _ = windows.FreeLibrary(k32)
274 }()
275
276 proc, err := windows.GetProcAddress(k32, "CtrlRoutine")
277 if err != nil {
278 return errors.Wrap(err, "failed to load CtrlRoutine")
279 }
280
281 threadHandle, err := winapi.CreateRemoteThread(hProc, nil, 0, proc, uintptr(signal), 0, nil)
282 if err != nil {
283 return errors.Wrapf(err, "failed to open remote thread in target process %d", pid)
284 }
285 defer func() {
286 _ = windows.Close(threadHandle)
287 }()
288 return nil
289 }
290
View as plain text