Tizen_4.0 base
[platform/upstream/docker-engine.git] / vendor / github.com / Microsoft / hcsshim / process.go
1 package hcsshim
2
3 import (
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
14 type process struct {
15         handleLock     sync.RWMutex
16         handle         hcsProcess
17         processID      int
18         container      *container
19         cachedPipes    *cachedPipes
20         callbackNumber uintptr
21 }
22
23 type cachedPipes struct {
24         stdIn  syscall.Handle
25         stdOut syscall.Handle
26         stdErr syscall.Handle
27 }
28
29 type processModifyRequest struct {
30         Operation   string
31         ConsoleSize *consoleSize `json:",omitempty"`
32         CloseHandle *closeHandle `json:",omitempty"`
33 }
34
35 type consoleSize struct {
36         Height uint16
37         Width  uint16
38 }
39
40 type closeHandle struct {
41         Handle string
42 }
43
44 type processStatus struct {
45         ProcessID      uint32
46         Exited         bool
47         ExitCode       uint32
48         LastWaitResult int32
49 }
50
51 const (
52         stdIn  string = "StdIn"
53         stdOut string = "StdOut"
54         stdErr string = "StdErr"
55 )
56
57 const (
58         modifyConsoleSize string = "ConsoleSize"
59         modifyCloseHandle string = "CloseHandle"
60 )
61
62 // Pid returns the process ID of the process within the container.
63 func (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.
68 func (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.
91 func (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.
107 func (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.
123 func (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.
152 func (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
189 func (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.
221 func (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.
265 func (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.
303 func (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
329 func (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
351 func (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 }