1package hcsshim
2
3import (
4	"encoding/json"
5	"io"
6	"sync"
7	"syscall"
8	"time"
9
10	"github.com/sirupsen/logrus"
11)
12
13// ContainerError is an error encountered in HCS
14type process struct {
15	handleLock     sync.RWMutex
16	handle         hcsProcess
17	processID      int
18	container      *container
19	cachedPipes    *cachedPipes
20	callbackNumber uintptr
21}
22
23type cachedPipes struct {
24	stdIn  syscall.Handle
25	stdOut syscall.Handle
26	stdErr syscall.Handle
27}
28
29type processModifyRequest struct {
30	Operation   string
31	ConsoleSize *consoleSize `json:",omitempty"`
32	CloseHandle *closeHandle `json:",omitempty"`
33}
34
35type consoleSize struct {
36	Height uint16
37	Width  uint16
38}
39
40type closeHandle struct {
41	Handle string
42}
43
44type processStatus struct {
45	ProcessID      uint32
46	Exited         bool
47	ExitCode       uint32
48	LastWaitResult int32
49}
50
51const (
52	stdIn  string = "StdIn"
53	stdOut string = "StdOut"
54	stdErr string = "StdErr"
55)
56
57const (
58	modifyConsoleSize string = "ConsoleSize"
59	modifyCloseHandle string = "CloseHandle"
60)
61
62// Pid returns the process ID of the process within the container.
63func (process *process) Pid() int {
64	return process.processID
65}
66
67// Kill signals the process to terminate but does not wait for it to finish terminating.
68func (process *process) Kill() error {
69	process.handleLock.RLock()
70	defer process.handleLock.RUnlock()
71	operation := "Kill"
72	title := "HCSShim::Process::" + operation
73	logrus.Debugf(title+" processid=%d", process.processID)
74
75	if process.handle == 0 {
76		return makeProcessError(process, operation, "", ErrAlreadyClosed)
77	}
78
79	var resultp *uint16
80	err := hcsTerminateProcess(process.handle, &resultp)
81	err = processHcsResult(err, resultp)
82	if err != nil {
83		return makeProcessError(process, operation, "", err)
84	}
85
86	logrus.Debugf(title+" succeeded processid=%d", process.processID)
87	return nil
88}
89
90// Wait waits for the process to exit.
91func (process *process) Wait() error {
92	operation := "Wait"
93	title := "HCSShim::Process::" + operation
94	logrus.Debugf(title+" processid=%d", process.processID)
95
96	err := waitForNotification(process.callbackNumber, hcsNotificationProcessExited, nil)
97	if err != nil {
98		return makeProcessError(process, operation, "", err)
99	}
100
101	logrus.Debugf(title+" succeeded processid=%d", process.processID)
102	return nil
103}
104
105// WaitTimeout waits for the process to exit or the duration to elapse. It returns
106// false if timeout occurs.
107func (process *process) WaitTimeout(timeout time.Duration) error {
108	operation := "WaitTimeout"
109	title := "HCSShim::Process::" + operation
110	logrus.Debugf(title+" processid=%d", process.processID)
111
112	err := waitForNotification(process.callbackNumber, hcsNotificationProcessExited, &timeout)
113	if err != nil {
114		return makeProcessError(process, operation, "", err)
115	}
116
117	logrus.Debugf(title+" succeeded processid=%d", process.processID)
118	return nil
119}
120
121// ExitCode returns the exit code of the process. The process must have
122// already terminated.
123func (process *process) ExitCode() (int, error) {
124	process.handleLock.RLock()
125	defer process.handleLock.RUnlock()
126	operation := "ExitCode"
127	title := "HCSShim::Process::" + operation
128	logrus.Debugf(title+" processid=%d", process.processID)
129
130	if process.handle == 0 {
131		return 0, makeProcessError(process, operation, "", ErrAlreadyClosed)
132	}
133
134	properties, err := process.properties()
135	if err != nil {
136		return 0, makeProcessError(process, operation, "", err)
137	}
138
139	if properties.Exited == false {
140		return 0, makeProcessError(process, operation, "", ErrInvalidProcessState)
141	}
142
143	if properties.LastWaitResult != 0 {
144		return 0, makeProcessError(process, operation, "", syscall.Errno(properties.LastWaitResult))
145	}
146
147	logrus.Debugf(title+" succeeded processid=%d exitCode=%d", process.processID, properties.ExitCode)
148	return int(properties.ExitCode), nil
149}
150
151// ResizeConsole resizes the console of the process.
152func (process *process) ResizeConsole(width, height uint16) error {
153	process.handleLock.RLock()
154	defer process.handleLock.RUnlock()
155	operation := "ResizeConsole"
156	title := "HCSShim::Process::" + operation
157	logrus.Debugf(title+" processid=%d", process.processID)
158
159	if process.handle == 0 {
160		return makeProcessError(process, operation, "", ErrAlreadyClosed)
161	}
162
163	modifyRequest := processModifyRequest{
164		Operation: modifyConsoleSize,
165		ConsoleSize: &consoleSize{
166			Height: height,
167			Width:  width,
168		},
169	}
170
171	modifyRequestb, err := json.Marshal(modifyRequest)
172	if err != nil {
173		return err
174	}
175
176	modifyRequestStr := string(modifyRequestb)
177
178	var resultp *uint16
179	err = hcsModifyProcess(process.handle, modifyRequestStr, &resultp)
180	err = processHcsResult(err, resultp)
181	if err != nil {
182		return makeProcessError(process, operation, "", err)
183	}
184
185	logrus.Debugf(title+" succeeded processid=%d", process.processID)
186	return nil
187}
188
189func (process *process) properties() (*processStatus, error) {
190	operation := "properties"
191	title := "HCSShim::Process::" + operation
192	logrus.Debugf(title+" processid=%d", process.processID)
193
194	var (
195		resultp     *uint16
196		propertiesp *uint16
197	)
198	err := hcsGetProcessProperties(process.handle, &propertiesp, &resultp)
199	err = processHcsResult(err, resultp)
200	if err != nil {
201		return nil, err
202	}
203
204	if propertiesp == nil {
205		return nil, ErrUnexpectedValue
206	}
207	propertiesRaw := convertAndFreeCoTaskMemBytes(propertiesp)
208
209	properties := &processStatus{}
210	if err := json.Unmarshal(propertiesRaw, properties); err != nil {
211		return nil, err
212	}
213
214	logrus.Debugf(title+" succeeded processid=%d, properties=%s", process.processID, propertiesRaw)
215	return properties, nil
216}
217
218// Stdio returns the stdin, stdout, and stderr pipes, respectively. Closing
219// these pipes does not close the underlying pipes; it should be possible to
220// call this multiple times to get multiple interfaces.
221func (process *process) Stdio() (io.WriteCloser, io.ReadCloser, io.ReadCloser, error) {
222	process.handleLock.RLock()
223	defer process.handleLock.RUnlock()
224	operation := "Stdio"
225	title := "HCSShim::Process::" + operation
226	logrus.Debugf(title+" processid=%d", process.processID)
227
228	if process.handle == 0 {
229		return nil, nil, nil, makeProcessError(process, operation, "", ErrAlreadyClosed)
230	}
231
232	var stdIn, stdOut, stdErr syscall.Handle
233
234	if process.cachedPipes == nil {
235		var (
236			processInfo hcsProcessInformation
237			resultp     *uint16
238		)
239		err := hcsGetProcessInfo(process.handle, &processInfo, &resultp)
240		err = processHcsResult(err, resultp)
241		if err != nil {
242			return nil, nil, nil, makeProcessError(process, operation, "", err)
243		}
244
245		stdIn, stdOut, stdErr = processInfo.StdInput, processInfo.StdOutput, processInfo.StdError
246	} else {
247		// Use cached pipes
248		stdIn, stdOut, stdErr = process.cachedPipes.stdIn, process.cachedPipes.stdOut, process.cachedPipes.stdErr
249
250		// Invalidate the cache
251		process.cachedPipes = nil
252	}
253
254	pipes, err := makeOpenFiles([]syscall.Handle{stdIn, stdOut, stdErr})
255	if err != nil {
256		return nil, nil, nil, makeProcessError(process, operation, "", err)
257	}
258
259	logrus.Debugf(title+" succeeded processid=%d", process.processID)
260	return pipes[0], pipes[1], pipes[2], nil
261}
262
263// CloseStdin closes the write side of the stdin pipe so that the process is
264// notified on the read side that there is no more data in stdin.
265func (process *process) CloseStdin() error {
266	process.handleLock.RLock()
267	defer process.handleLock.RUnlock()
268	operation := "CloseStdin"
269	title := "HCSShim::Process::" + operation
270	logrus.Debugf(title+" processid=%d", process.processID)
271
272	if process.handle == 0 {
273		return makeProcessError(process, operation, "", ErrAlreadyClosed)
274	}
275
276	modifyRequest := processModifyRequest{
277		Operation: modifyCloseHandle,
278		CloseHandle: &closeHandle{
279			Handle: stdIn,
280		},
281	}
282
283	modifyRequestb, err := json.Marshal(modifyRequest)
284	if err != nil {
285		return err
286	}
287
288	modifyRequestStr := string(modifyRequestb)
289
290	var resultp *uint16
291	err = hcsModifyProcess(process.handle, modifyRequestStr, &resultp)
292	err = processHcsResult(err, resultp)
293	if err != nil {
294		return makeProcessError(process, operation, "", err)
295	}
296
297	logrus.Debugf(title+" succeeded processid=%d", process.processID)
298	return nil
299}
300
301// Close cleans up any state associated with the process but does not kill
302// or wait on it.
303func (process *process) Close() error {
304	process.handleLock.Lock()
305	defer process.handleLock.Unlock()
306	operation := "Close"
307	title := "HCSShim::Process::" + operation
308	logrus.Debugf(title+" processid=%d", process.processID)
309
310	// Don't double free this
311	if process.handle == 0 {
312		return nil
313	}
314
315	if err := process.unregisterCallback(); err != nil {
316		return makeProcessError(process, operation, "", err)
317	}
318
319	if err := hcsCloseProcess(process.handle); err != nil {
320		return makeProcessError(process, operation, "", err)
321	}
322
323	process.handle = 0
324
325	logrus.Debugf(title+" succeeded processid=%d", process.processID)
326	return nil
327}
328
329func (process *process) registerCallback() error {
330	context := &notifcationWatcherContext{
331		channels: newChannels(),
332	}
333
334	callbackMapLock.Lock()
335	callbackNumber := nextCallback
336	nextCallback++
337	callbackMap[callbackNumber] = context
338	callbackMapLock.Unlock()
339
340	var callbackHandle hcsCallback
341	err := hcsRegisterProcessCallback(process.handle, notificationWatcherCallback, callbackNumber, &callbackHandle)
342	if err != nil {
343		return err
344	}
345	context.handle = callbackHandle
346	process.callbackNumber = callbackNumber
347
348	return nil
349}
350
351func (process *process) unregisterCallback() error {
352	callbackNumber := process.callbackNumber
353
354	callbackMapLock.RLock()
355	context := callbackMap[callbackNumber]
356	callbackMapLock.RUnlock()
357
358	if context == nil {
359		return nil
360	}
361
362	handle := context.handle
363
364	if handle == 0 {
365		return nil
366	}
367
368	// hcsUnregisterProcessCallback has its own syncronization
369	// to wait for all callbacks to complete. We must NOT hold the callbackMapLock.
370	err := hcsUnregisterProcessCallback(handle)
371	if err != nil {
372		return err
373	}
374
375	closeChannels(context.channels)
376
377	callbackMapLock.Lock()
378	callbackMap[callbackNumber] = nil
379	callbackMapLock.Unlock()
380
381	handle = 0
382
383	return nil
384}
385