process.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. package hcs
  2. import (
  3. "encoding/json"
  4. "io"
  5. "sync"
  6. "syscall"
  7. "time"
  8. "github.com/Microsoft/hcsshim/internal/interop"
  9. "github.com/Microsoft/hcsshim/internal/logfields"
  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. logctx logrus.Fields
  21. closedWaitOnce sync.Once
  22. waitBlock chan struct{}
  23. waitError error
  24. }
  25. func newProcess(process hcsProcess, processID int, computeSystem *System) *Process {
  26. return &Process{
  27. handle: process,
  28. processID: processID,
  29. system: computeSystem,
  30. logctx: logrus.Fields{
  31. logfields.ContainerID: computeSystem.ID(),
  32. logfields.ProcessID: processID,
  33. },
  34. waitBlock: make(chan struct{}),
  35. }
  36. }
  37. type cachedPipes struct {
  38. stdIn syscall.Handle
  39. stdOut syscall.Handle
  40. stdErr syscall.Handle
  41. }
  42. type processModifyRequest struct {
  43. Operation string
  44. ConsoleSize *consoleSize `json:",omitempty"`
  45. CloseHandle *closeHandle `json:",omitempty"`
  46. }
  47. type consoleSize struct {
  48. Height uint16
  49. Width uint16
  50. }
  51. type closeHandle struct {
  52. Handle string
  53. }
  54. type ProcessStatus struct {
  55. ProcessID uint32
  56. Exited bool
  57. ExitCode uint32
  58. LastWaitResult int32
  59. }
  60. const (
  61. stdIn string = "StdIn"
  62. stdOut string = "StdOut"
  63. stdErr string = "StdErr"
  64. )
  65. const (
  66. modifyConsoleSize string = "ConsoleSize"
  67. modifyCloseHandle string = "CloseHandle"
  68. )
  69. // Pid returns the process ID of the process within the container.
  70. func (process *Process) Pid() int {
  71. return process.processID
  72. }
  73. // SystemID returns the ID of the process's compute system.
  74. func (process *Process) SystemID() string {
  75. return process.system.ID()
  76. }
  77. func (process *Process) logOperationBegin(operation string) {
  78. logOperationBegin(
  79. process.logctx,
  80. operation+" - Begin Operation")
  81. }
  82. func (process *Process) logOperationEnd(operation string, err error) {
  83. var result string
  84. if err == nil {
  85. result = "Success"
  86. } else {
  87. result = "Error"
  88. }
  89. logOperationEnd(
  90. process.logctx,
  91. operation+" - End Operation - "+result,
  92. err)
  93. }
  94. // Signal signals the process with `options`.
  95. //
  96. // For LCOW `guestrequest.SignalProcessOptionsLCOW`.
  97. //
  98. // For WCOW `guestrequest.SignalProcessOptionsWCOW`.
  99. func (process *Process) Signal(options interface{}) (err error) {
  100. process.handleLock.RLock()
  101. defer process.handleLock.RUnlock()
  102. operation := "hcsshim::Process::Signal"
  103. process.logOperationBegin(operation)
  104. defer func() { process.logOperationEnd(operation, err) }()
  105. if process.handle == 0 {
  106. return makeProcessError(process, operation, ErrAlreadyClosed, nil)
  107. }
  108. optionsb, err := json.Marshal(options)
  109. if err != nil {
  110. return err
  111. }
  112. optionsStr := string(optionsb)
  113. var resultp *uint16
  114. syscallWatcher(process.logctx, func() {
  115. err = hcsSignalProcess(process.handle, optionsStr, &resultp)
  116. })
  117. events := processHcsResult(resultp)
  118. if err != nil {
  119. return makeProcessError(process, operation, err, events)
  120. }
  121. return nil
  122. }
  123. // Kill signals the process to terminate but does not wait for it to finish terminating.
  124. func (process *Process) Kill() (err error) {
  125. process.handleLock.RLock()
  126. defer process.handleLock.RUnlock()
  127. operation := "hcsshim::Process::Kill"
  128. process.logOperationBegin(operation)
  129. defer func() { process.logOperationEnd(operation, err) }()
  130. if process.handle == 0 {
  131. return makeProcessError(process, operation, ErrAlreadyClosed, nil)
  132. }
  133. var resultp *uint16
  134. syscallWatcher(process.logctx, func() {
  135. err = hcsTerminateProcess(process.handle, &resultp)
  136. })
  137. events := processHcsResult(resultp)
  138. if err != nil {
  139. return makeProcessError(process, operation, err, events)
  140. }
  141. return nil
  142. }
  143. // waitBackground waits for the process exit notification. Once received sets
  144. // `process.waitError` (if any) and unblocks all `Wait` and `WaitTimeout` calls.
  145. //
  146. // This MUST be called exactly once per `process.handle` but `Wait` and
  147. // `WaitTimeout` are safe to call multiple times.
  148. func (process *Process) waitBackground() {
  149. process.waitError = waitForNotification(process.callbackNumber, hcsNotificationProcessExited, nil)
  150. process.closedWaitOnce.Do(func() {
  151. close(process.waitBlock)
  152. })
  153. }
  154. // Wait waits for the process to exit. If the process has already exited returns
  155. // the pervious error (if any).
  156. func (process *Process) Wait() (err error) {
  157. operation := "hcsshim::Process::Wait"
  158. process.logOperationBegin(operation)
  159. defer func() { process.logOperationEnd(operation, err) }()
  160. <-process.waitBlock
  161. if process.waitError != nil {
  162. return makeProcessError(process, operation, process.waitError, nil)
  163. }
  164. return nil
  165. }
  166. // WaitTimeout waits for the process to exit or the duration to elapse. If the
  167. // process has already exited returns the pervious error (if any). If a timeout
  168. // occurs returns `ErrTimeout`.
  169. func (process *Process) WaitTimeout(timeout time.Duration) (err error) {
  170. operation := "hcssshim::Process::WaitTimeout"
  171. process.logOperationBegin(operation)
  172. defer func() { process.logOperationEnd(operation, err) }()
  173. select {
  174. case <-process.waitBlock:
  175. if process.waitError != nil {
  176. return makeProcessError(process, operation, process.waitError, nil)
  177. }
  178. return nil
  179. case <-time.After(timeout):
  180. return makeProcessError(process, operation, ErrTimeout, nil)
  181. }
  182. }
  183. // ResizeConsole resizes the console of the process.
  184. func (process *Process) ResizeConsole(width, height uint16) (err error) {
  185. process.handleLock.RLock()
  186. defer process.handleLock.RUnlock()
  187. operation := "hcsshim::Process::ResizeConsole"
  188. process.logOperationBegin(operation)
  189. defer func() { process.logOperationEnd(operation, err) }()
  190. if process.handle == 0 {
  191. return makeProcessError(process, operation, ErrAlreadyClosed, nil)
  192. }
  193. modifyRequest := processModifyRequest{
  194. Operation: modifyConsoleSize,
  195. ConsoleSize: &consoleSize{
  196. Height: height,
  197. Width: width,
  198. },
  199. }
  200. modifyRequestb, err := json.Marshal(modifyRequest)
  201. if err != nil {
  202. return err
  203. }
  204. modifyRequestStr := string(modifyRequestb)
  205. var resultp *uint16
  206. err = hcsModifyProcess(process.handle, modifyRequestStr, &resultp)
  207. events := processHcsResult(resultp)
  208. if err != nil {
  209. return makeProcessError(process, operation, err, events)
  210. }
  211. return nil
  212. }
  213. func (process *Process) Properties() (_ *ProcessStatus, err error) {
  214. process.handleLock.RLock()
  215. defer process.handleLock.RUnlock()
  216. operation := "hcsshim::Process::Properties"
  217. process.logOperationBegin(operation)
  218. defer func() { process.logOperationEnd(operation, err) }()
  219. if process.handle == 0 {
  220. return nil, makeProcessError(process, operation, ErrAlreadyClosed, nil)
  221. }
  222. var (
  223. resultp *uint16
  224. propertiesp *uint16
  225. )
  226. syscallWatcher(process.logctx, func() {
  227. err = hcsGetProcessProperties(process.handle, &propertiesp, &resultp)
  228. })
  229. events := processHcsResult(resultp)
  230. if err != nil {
  231. return nil, makeProcessError(process, operation, err, events)
  232. }
  233. if propertiesp == nil {
  234. return nil, ErrUnexpectedValue
  235. }
  236. propertiesRaw := interop.ConvertAndFreeCoTaskMemBytes(propertiesp)
  237. properties := &ProcessStatus{}
  238. if err := json.Unmarshal(propertiesRaw, properties); err != nil {
  239. return nil, makeProcessError(process, operation, err, nil)
  240. }
  241. return properties, nil
  242. }
  243. // ExitCode returns the exit code of the process. The process must have
  244. // already terminated.
  245. func (process *Process) ExitCode() (_ int, err error) {
  246. operation := "hcsshim::Process::ExitCode"
  247. process.logOperationBegin(operation)
  248. defer func() { process.logOperationEnd(operation, err) }()
  249. properties, err := process.Properties()
  250. if err != nil {
  251. return -1, makeProcessError(process, operation, err, nil)
  252. }
  253. if properties.Exited == false {
  254. return -1, makeProcessError(process, operation, ErrInvalidProcessState, nil)
  255. }
  256. if properties.LastWaitResult != 0 {
  257. logrus.WithFields(logrus.Fields{
  258. logfields.ContainerID: process.SystemID(),
  259. logfields.ProcessID: process.processID,
  260. "wait-result": properties.LastWaitResult,
  261. }).Warn("hcsshim::Process::ExitCode - Non-zero last wait result")
  262. return -1, nil
  263. }
  264. return int(properties.ExitCode), nil
  265. }
  266. // Stdio returns the stdin, stdout, and stderr pipes, respectively. Closing
  267. // these pipes does not close the underlying pipes; it should be possible to
  268. // call this multiple times to get multiple interfaces.
  269. func (process *Process) Stdio() (_ io.WriteCloser, _ io.ReadCloser, _ io.ReadCloser, err error) {
  270. process.handleLock.RLock()
  271. defer process.handleLock.RUnlock()
  272. operation := "hcsshim::Process::Stdio"
  273. process.logOperationBegin(operation)
  274. defer func() { process.logOperationEnd(operation, err) }()
  275. if process.handle == 0 {
  276. return nil, nil, nil, makeProcessError(process, operation, ErrAlreadyClosed, nil)
  277. }
  278. var stdIn, stdOut, stdErr syscall.Handle
  279. if process.cachedPipes == nil {
  280. var (
  281. processInfo hcsProcessInformation
  282. resultp *uint16
  283. )
  284. err = hcsGetProcessInfo(process.handle, &processInfo, &resultp)
  285. events := processHcsResult(resultp)
  286. if err != nil {
  287. return nil, nil, nil, makeProcessError(process, operation, err, events)
  288. }
  289. stdIn, stdOut, stdErr = processInfo.StdInput, processInfo.StdOutput, processInfo.StdError
  290. } else {
  291. // Use cached pipes
  292. stdIn, stdOut, stdErr = process.cachedPipes.stdIn, process.cachedPipes.stdOut, process.cachedPipes.stdErr
  293. // Invalidate the cache
  294. process.cachedPipes = nil
  295. }
  296. pipes, err := makeOpenFiles([]syscall.Handle{stdIn, stdOut, stdErr})
  297. if err != nil {
  298. return nil, nil, nil, makeProcessError(process, operation, err, nil)
  299. }
  300. return pipes[0], pipes[1], pipes[2], nil
  301. }
  302. // CloseStdin closes the write side of the stdin pipe so that the process is
  303. // notified on the read side that there is no more data in stdin.
  304. func (process *Process) CloseStdin() (err error) {
  305. process.handleLock.RLock()
  306. defer process.handleLock.RUnlock()
  307. operation := "hcsshim::Process::CloseStdin"
  308. process.logOperationBegin(operation)
  309. defer func() { process.logOperationEnd(operation, err) }()
  310. if process.handle == 0 {
  311. return makeProcessError(process, operation, ErrAlreadyClosed, nil)
  312. }
  313. modifyRequest := processModifyRequest{
  314. Operation: modifyCloseHandle,
  315. CloseHandle: &closeHandle{
  316. Handle: stdIn,
  317. },
  318. }
  319. modifyRequestb, err := json.Marshal(modifyRequest)
  320. if err != nil {
  321. return err
  322. }
  323. modifyRequestStr := string(modifyRequestb)
  324. var resultp *uint16
  325. err = hcsModifyProcess(process.handle, modifyRequestStr, &resultp)
  326. events := processHcsResult(resultp)
  327. if err != nil {
  328. return makeProcessError(process, operation, err, events)
  329. }
  330. return nil
  331. }
  332. // Close cleans up any state associated with the process but does not kill
  333. // or wait on it.
  334. func (process *Process) Close() (err error) {
  335. process.handleLock.Lock()
  336. defer process.handleLock.Unlock()
  337. operation := "hcsshim::Process::Close"
  338. process.logOperationBegin(operation)
  339. defer func() { process.logOperationEnd(operation, err) }()
  340. // Don't double free this
  341. if process.handle == 0 {
  342. return nil
  343. }
  344. if err = process.unregisterCallback(); err != nil {
  345. return makeProcessError(process, operation, err, nil)
  346. }
  347. if err = hcsCloseProcess(process.handle); err != nil {
  348. return makeProcessError(process, operation, err, nil)
  349. }
  350. process.handle = 0
  351. process.closedWaitOnce.Do(func() {
  352. close(process.waitBlock)
  353. })
  354. return nil
  355. }
  356. func (process *Process) registerCallback() error {
  357. context := &notifcationWatcherContext{
  358. channels: newProcessChannels(),
  359. systemID: process.SystemID(),
  360. processID: process.processID,
  361. }
  362. callbackMapLock.Lock()
  363. callbackNumber := nextCallback
  364. nextCallback++
  365. callbackMap[callbackNumber] = context
  366. callbackMapLock.Unlock()
  367. var callbackHandle hcsCallback
  368. err := hcsRegisterProcessCallback(process.handle, notificationWatcherCallback, callbackNumber, &callbackHandle)
  369. if err != nil {
  370. return err
  371. }
  372. context.handle = callbackHandle
  373. process.callbackNumber = callbackNumber
  374. return nil
  375. }
  376. func (process *Process) unregisterCallback() error {
  377. callbackNumber := process.callbackNumber
  378. callbackMapLock.RLock()
  379. context := callbackMap[callbackNumber]
  380. callbackMapLock.RUnlock()
  381. if context == nil {
  382. return nil
  383. }
  384. handle := context.handle
  385. if handle == 0 {
  386. return nil
  387. }
  388. // hcsUnregisterProcessCallback has its own syncronization
  389. // to wait for all callbacks to complete. We must NOT hold the callbackMapLock.
  390. err := hcsUnregisterProcessCallback(handle)
  391. if err != nil {
  392. return err
  393. }
  394. closeChannels(context.channels)
  395. callbackMapLock.Lock()
  396. delete(callbackMap, callbackNumber)
  397. callbackMapLock.Unlock()
  398. handle = 0
  399. return nil
  400. }