process.go 11 KB

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