process.go 11 KB

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