process.go 10 KB

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