process.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. package runtime
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io"
  6. "io/ioutil"
  7. "os"
  8. "path/filepath"
  9. "strconv"
  10. "syscall"
  11. "time"
  12. "github.com/docker/containerd/specs"
  13. "github.com/docker/containerd/subreaper/exec"
  14. "golang.org/x/sys/unix"
  15. )
  16. type Process interface {
  17. io.Closer
  18. // ID of the process.
  19. // This is either "init" when it is the container's init process or
  20. // it is a user provided id for the process similar to the container id
  21. ID() string
  22. CloseStdin() error
  23. Resize(int, int) error
  24. // ExitFD returns the fd the provides an event when the process exits
  25. ExitFD() int
  26. // ExitStatus returns the exit status of the process or an error if it
  27. // has not exited
  28. ExitStatus() (int, error)
  29. // Spec returns the process spec that created the process
  30. Spec() specs.ProcessSpec
  31. // Signal sends the provided signal to the process
  32. Signal(os.Signal) error
  33. // Container returns the container that the process belongs to
  34. Container() Container
  35. // Stdio of the container
  36. Stdio() Stdio
  37. // SystemPid is the pid on the system
  38. SystemPid() int
  39. // State returns if the process is running or not
  40. State() State
  41. // Start executes the process
  42. Start() error
  43. }
  44. type processConfig struct {
  45. id string
  46. root string
  47. processSpec specs.ProcessSpec
  48. spec *specs.Spec
  49. c *container
  50. stdio Stdio
  51. exec bool
  52. checkpoint string
  53. }
  54. func newProcess(config *processConfig) (*process, error) {
  55. p := &process{
  56. root: config.root,
  57. id: config.id,
  58. container: config.c,
  59. spec: config.processSpec,
  60. stdio: config.stdio,
  61. }
  62. uid, gid, err := getRootIDs(config.spec)
  63. if err != nil {
  64. return nil, err
  65. }
  66. f, err := os.Create(filepath.Join(config.root, "process.json"))
  67. if err != nil {
  68. return nil, err
  69. }
  70. defer f.Close()
  71. ps := ProcessState{
  72. ProcessSpec: config.processSpec,
  73. Exec: config.exec,
  74. PlatformProcessState: PlatformProcessState{
  75. Checkpoint: config.checkpoint,
  76. RootUID: uid,
  77. RootGID: gid,
  78. },
  79. Stdin: config.stdio.Stdin,
  80. Stdout: config.stdio.Stdout,
  81. Stderr: config.stdio.Stderr,
  82. RuntimeArgs: config.c.runtimeArgs,
  83. NoPivotRoot: config.c.noPivotRoot,
  84. }
  85. if err := json.NewEncoder(f).Encode(ps); err != nil {
  86. return nil, err
  87. }
  88. exit, err := getExitPipe(filepath.Join(config.root, ExitFile))
  89. if err != nil {
  90. return nil, err
  91. }
  92. control, err := getControlPipe(filepath.Join(config.root, ControlFile))
  93. if err != nil {
  94. return nil, err
  95. }
  96. p.exitPipe = exit
  97. p.controlPipe = control
  98. return p, nil
  99. }
  100. func (p *process) Start() error {
  101. cmd := exec.Command(p.container.shim,
  102. p.container.id, p.container.bundle, p.container.runtime,
  103. )
  104. cmd.Dir = p.root
  105. cmd.SysProcAttr = &syscall.SysProcAttr{
  106. Setpgid: true,
  107. }
  108. return p.startCmd(cmd)
  109. }
  110. func loadProcess(root, id string, c *container, s *ProcessState) (*process, error) {
  111. p := &process{
  112. root: root,
  113. id: id,
  114. container: c,
  115. spec: s.ProcessSpec,
  116. stdio: Stdio{
  117. Stdin: s.Stdin,
  118. Stdout: s.Stdout,
  119. Stderr: s.Stderr,
  120. },
  121. }
  122. if _, err := p.getPidFromFile(); err != nil {
  123. return nil, err
  124. }
  125. if _, err := p.ExitStatus(); err != nil {
  126. if err == ErrProcessNotExited {
  127. exit, err := getExitPipe(filepath.Join(root, ExitFile))
  128. if err != nil {
  129. return nil, err
  130. }
  131. p.exitPipe = exit
  132. return p, nil
  133. }
  134. return nil, err
  135. }
  136. return p, nil
  137. }
  138. type process struct {
  139. root string
  140. id string
  141. pid int
  142. exitPipe *os.File
  143. controlPipe *os.File
  144. container *container
  145. spec specs.ProcessSpec
  146. stdio Stdio
  147. }
  148. func (p *process) ID() string {
  149. return p.id
  150. }
  151. func (p *process) Container() Container {
  152. return p.container
  153. }
  154. func (p *process) SystemPid() int {
  155. return p.pid
  156. }
  157. // ExitFD returns the fd of the exit pipe
  158. func (p *process) ExitFD() int {
  159. return int(p.exitPipe.Fd())
  160. }
  161. func (p *process) CloseStdin() error {
  162. _, err := fmt.Fprintf(p.controlPipe, "%d %d %d\n", 0, 0, 0)
  163. return err
  164. }
  165. func (p *process) Resize(w, h int) error {
  166. _, err := fmt.Fprintf(p.controlPipe, "%d %d %d\n", 1, w, h)
  167. return err
  168. }
  169. func (p *process) ExitStatus() (int, error) {
  170. data, err := ioutil.ReadFile(filepath.Join(p.root, ExitStatusFile))
  171. if err != nil {
  172. if os.IsNotExist(err) {
  173. return -1, ErrProcessNotExited
  174. }
  175. return -1, err
  176. }
  177. if len(data) == 0 {
  178. return -1, ErrProcessNotExited
  179. }
  180. return strconv.Atoi(string(data))
  181. }
  182. func (p *process) Spec() specs.ProcessSpec {
  183. return p.spec
  184. }
  185. func (p *process) Stdio() Stdio {
  186. return p.stdio
  187. }
  188. // Close closes any open files and/or resouces on the process
  189. func (p *process) Close() error {
  190. return p.exitPipe.Close()
  191. }
  192. func (p *process) State() State {
  193. if p.pid == 0 {
  194. return Stopped
  195. }
  196. err := syscall.Kill(p.pid, 0)
  197. if err != nil && err == syscall.ESRCH {
  198. return Stopped
  199. }
  200. return Running
  201. }
  202. func (p *process) getPidFromFile() (int, error) {
  203. data, err := ioutil.ReadFile(filepath.Join(p.root, "pid"))
  204. if err != nil {
  205. return -1, err
  206. }
  207. i, err := strconv.Atoi(string(data))
  208. if err != nil {
  209. return -1, errInvalidPidInt
  210. }
  211. p.pid = i
  212. return i, nil
  213. }
  214. func getExitPipe(path string) (*os.File, error) {
  215. if err := unix.Mkfifo(path, 0755); err != nil && !os.IsExist(err) {
  216. return nil, err
  217. }
  218. // add NONBLOCK in case the other side has already closed or else
  219. // this function would never return
  220. return os.OpenFile(path, syscall.O_RDONLY|syscall.O_NONBLOCK, 0)
  221. }
  222. func getControlPipe(path string) (*os.File, error) {
  223. if err := unix.Mkfifo(path, 0755); err != nil && !os.IsExist(err) {
  224. return nil, err
  225. }
  226. return os.OpenFile(path, syscall.O_RDWR|syscall.O_NONBLOCK, 0)
  227. }
  228. // Signal sends the provided signal to the process
  229. func (p *process) Signal(s os.Signal) error {
  230. return syscall.Kill(p.pid, s.(syscall.Signal))
  231. }
  232. func (p *process) startCmd(cmd *exec.Cmd) error {
  233. if err := cmd.Start(); err != nil {
  234. if exErr, ok := err.(*exec.Error); ok {
  235. if exErr.Err == exec.ErrNotFound || exErr.Err == os.ErrNotExist {
  236. return fmt.Errorf("%s not installed on system", p.container.shim)
  237. }
  238. }
  239. return err
  240. }
  241. if err := p.waitForStart(cmd); err != nil {
  242. return err
  243. }
  244. return nil
  245. }
  246. func (p *process) waitForStart(cmd *exec.Cmd) error {
  247. wc := make(chan error, 1)
  248. go func() {
  249. for {
  250. if _, err := p.getPidFromFile(); err != nil {
  251. if os.IsNotExist(err) || err == errInvalidPidInt {
  252. alive, err := isAlive(cmd)
  253. if err != nil {
  254. wc <- err
  255. return
  256. }
  257. if !alive {
  258. // runc could have failed to run the container so lets get the error
  259. // out of the logs or the shim could have encountered an error
  260. messages, err := readLogMessages(filepath.Join(p.root, "shim-log.json"))
  261. if err != nil && !os.IsNotExist(err) {
  262. wc <- err
  263. return
  264. }
  265. for _, m := range messages {
  266. if m.Level == "error" {
  267. wc <- fmt.Errorf("shim error: %v", m.Msg)
  268. return
  269. }
  270. }
  271. // no errors reported back from shim, check for runc/runtime errors
  272. messages, err = readLogMessages(filepath.Join(p.root, "log.json"))
  273. if err != nil {
  274. if os.IsNotExist(err) {
  275. err = ErrContainerNotStarted
  276. }
  277. wc <- err
  278. return
  279. }
  280. for _, m := range messages {
  281. if m.Level == "error" {
  282. wc <- fmt.Errorf("oci runtime error: %v", m.Msg)
  283. return
  284. }
  285. }
  286. wc <- ErrContainerNotStarted
  287. return
  288. }
  289. time.Sleep(15 * time.Millisecond)
  290. continue
  291. }
  292. wc <- err
  293. return
  294. }
  295. // the pid file was read successfully
  296. wc <- nil
  297. return
  298. }
  299. }()
  300. select {
  301. case err := <-wc:
  302. if err != nil {
  303. return err
  304. }
  305. return nil
  306. case <-time.After(p.container.timeout):
  307. cmd.Process.Kill()
  308. cmd.Wait()
  309. return ErrContainerStartTimeout
  310. }
  311. }