process.go 11 KB

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