process.go 9.7 KB

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