process.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. package runtime
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io"
  6. "io/ioutil"
  7. "os"
  8. "os/exec"
  9. "path/filepath"
  10. "strconv"
  11. "strings"
  12. "sync"
  13. "syscall"
  14. "time"
  15. "github.com/Sirupsen/logrus"
  16. "github.com/containerd/containerd/osutils"
  17. "github.com/containerd/containerd/specs"
  18. "golang.org/x/sys/unix"
  19. )
  20. // Process holds the operation allowed on a container's process
  21. type Process interface {
  22. io.Closer
  23. // ID of the process.
  24. // This is either "init" when it is the container's init process or
  25. // it is a user provided id for the process similar to the container id
  26. ID() string
  27. // Start unblocks the associated container init process.
  28. // This should only be called on the process with ID "init"
  29. Start() error
  30. CloseStdin() error
  31. Resize(int, int) error
  32. // ExitFD returns the fd the provides an event when the process exits
  33. ExitFD() int
  34. // ExitStatus returns the exit status of the process or an error if it
  35. // has not exited
  36. ExitStatus() (uint32, error)
  37. // Spec returns the process spec that created the process
  38. Spec() specs.ProcessSpec
  39. // Signal sends the provided signal to the process
  40. Signal(os.Signal) error
  41. // Container returns the container that the process belongs to
  42. Container() Container
  43. // Stdio of the container
  44. Stdio() Stdio
  45. // SystemPid is the pid on the system
  46. SystemPid() int
  47. // State returns if the process is running or not
  48. State() State
  49. // Wait reaps the shim process if avaliable
  50. Wait()
  51. }
  52. type processConfig struct {
  53. id string
  54. root string
  55. processSpec specs.ProcessSpec
  56. spec *specs.Spec
  57. c *container
  58. stdio Stdio
  59. exec bool
  60. checkpoint string
  61. }
  62. func newProcess(config *processConfig) (*process, error) {
  63. p := &process{
  64. root: config.root,
  65. id: config.id,
  66. container: config.c,
  67. spec: config.processSpec,
  68. stdio: config.stdio,
  69. cmdDoneCh: make(chan struct{}),
  70. state: Running,
  71. }
  72. uid, gid, err := getRootIDs(config.spec)
  73. if err != nil {
  74. return nil, err
  75. }
  76. f, err := os.Create(filepath.Join(config.root, "process.json"))
  77. if err != nil {
  78. return nil, err
  79. }
  80. defer f.Close()
  81. ps := ProcessState{
  82. ProcessSpec: config.processSpec,
  83. Exec: config.exec,
  84. PlatformProcessState: PlatformProcessState{
  85. Checkpoint: config.checkpoint,
  86. RootUID: uid,
  87. RootGID: gid,
  88. },
  89. Stdin: config.stdio.Stdin,
  90. Stdout: config.stdio.Stdout,
  91. Stderr: config.stdio.Stderr,
  92. RuntimeArgs: config.c.runtimeArgs,
  93. NoPivotRoot: config.c.noPivotRoot,
  94. }
  95. if err := json.NewEncoder(f).Encode(ps); err != nil {
  96. return nil, err
  97. }
  98. exit, err := getExitPipe(filepath.Join(config.root, ExitFile))
  99. if err != nil {
  100. return nil, err
  101. }
  102. control, err := getControlPipe(filepath.Join(config.root, ControlFile))
  103. if err != nil {
  104. return nil, err
  105. }
  106. p.exitPipe = exit
  107. p.controlPipe = control
  108. return p, nil
  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. state: Stopped,
  122. }
  123. startTime, err := ioutil.ReadFile(filepath.Join(p.root, StartTimeFile))
  124. if err != nil && !os.IsNotExist(err) {
  125. return nil, err
  126. }
  127. p.startTime = string(startTime)
  128. if _, err := p.getPidFromFile(); err != nil {
  129. return nil, err
  130. }
  131. if _, err := p.ExitStatus(); err != nil {
  132. if err == ErrProcessNotExited {
  133. exit, err := getExitPipe(filepath.Join(root, ExitFile))
  134. if err != nil {
  135. return nil, err
  136. }
  137. p.exitPipe = exit
  138. control, err := getControlPipe(filepath.Join(root, ControlFile))
  139. if err != nil {
  140. return nil, err
  141. }
  142. p.controlPipe = control
  143. p.state = Running
  144. return p, nil
  145. }
  146. return nil, err
  147. }
  148. return p, nil
  149. }
  150. func readProcStatField(pid int, field int) (string, error) {
  151. data, err := ioutil.ReadFile(filepath.Join(string(filepath.Separator), "proc", strconv.Itoa(pid), "stat"))
  152. if err != nil {
  153. return "", err
  154. }
  155. if field > 2 {
  156. // First, split out the name since he could contains spaces.
  157. parts := strings.Split(string(data), ") ")
  158. // Now split out the rest, we end up with 2 fields less
  159. parts = strings.Split(parts[1], " ")
  160. return parts[field-2-1], nil // field count start at 1 in manual
  161. }
  162. parts := strings.Split(string(data), " (")
  163. if field == 1 {
  164. return parts[0], nil
  165. }
  166. parts = strings.Split(parts[1], ") ")
  167. return parts[0], nil
  168. }
  169. type process struct {
  170. root string
  171. id string
  172. pid int
  173. exitPipe *os.File
  174. controlPipe *os.File
  175. container *container
  176. spec specs.ProcessSpec
  177. stdio Stdio
  178. cmd *exec.Cmd
  179. cmdSuccess bool
  180. cmdDoneCh chan struct{}
  181. state State
  182. stateLock sync.Mutex
  183. startTime string
  184. }
  185. func (p *process) ID() string {
  186. return p.id
  187. }
  188. func (p *process) Container() Container {
  189. return p.container
  190. }
  191. func (p *process) SystemPid() int {
  192. return p.pid
  193. }
  194. // ExitFD returns the fd of the exit pipe
  195. func (p *process) ExitFD() int {
  196. return int(p.exitPipe.Fd())
  197. }
  198. func (p *process) CloseStdin() error {
  199. _, err := fmt.Fprintf(p.controlPipe, "%d %d %d\n", 0, 0, 0)
  200. return err
  201. }
  202. func (p *process) Resize(w, h int) error {
  203. _, err := fmt.Fprintf(p.controlPipe, "%d %d %d\n", 1, w, h)
  204. return err
  205. }
  206. func (p *process) updateExitStatusFile(status uint32) (uint32, error) {
  207. p.stateLock.Lock()
  208. p.state = Stopped
  209. p.stateLock.Unlock()
  210. err := ioutil.WriteFile(filepath.Join(p.root, ExitStatusFile), []byte(fmt.Sprintf("%u", status)), 0644)
  211. return status, err
  212. }
  213. func (p *process) handleSigkilledShim(rst uint32, rerr error) (uint32, error) {
  214. if p.cmd == nil || p.cmd.Process == nil {
  215. e := unix.Kill(p.pid, 0)
  216. if e == syscall.ESRCH {
  217. logrus.Warnf("containerd: %s:%s (pid %d) does not exist", p.container.id, p.id, p.pid)
  218. // The process died while containerd was down (probably of
  219. // SIGKILL, but no way to be sure)
  220. return p.updateExitStatusFile(UnknownStatus)
  221. }
  222. // If it's not the same process, just mark it stopped and set
  223. // the status to the UnknownStatus value (i.e. 255)
  224. if same, err := p.isSameProcess(); !same {
  225. logrus.Warnf("containerd: %s:%s (pid %d) is not the same process anymore (%v)", p.container.id, p.id, p.pid, err)
  226. // Create the file so we get the exit event generated once monitor kicks in
  227. // without having to go through all this process again
  228. return p.updateExitStatusFile(UnknownStatus)
  229. }
  230. ppid, err := readProcStatField(p.pid, 4)
  231. if err != nil {
  232. return rst, fmt.Errorf("could not check process ppid: %v (%v)", err, rerr)
  233. }
  234. if ppid == "1" {
  235. logrus.Warnf("containerd: %s:%s shim died, killing associated process", p.container.id, p.id)
  236. unix.Kill(p.pid, syscall.SIGKILL)
  237. if err != nil && err != syscall.ESRCH {
  238. return UnknownStatus, fmt.Errorf("containerd: unable to SIGKILL %s:%s (pid %v): %v", p.container.id, p.id, p.pid, err)
  239. }
  240. // wait for the process to die
  241. for {
  242. e := unix.Kill(p.pid, 0)
  243. if e == syscall.ESRCH {
  244. break
  245. }
  246. time.Sleep(5 * time.Millisecond)
  247. }
  248. // Create the file so we get the exit event generated once monitor kicks in
  249. // without having to go through all this process again
  250. return p.updateExitStatusFile(128 + uint32(syscall.SIGKILL))
  251. }
  252. return rst, rerr
  253. }
  254. // Possible that the shim was SIGKILLED
  255. e := unix.Kill(p.cmd.Process.Pid, 0)
  256. if e != syscall.ESRCH {
  257. return rst, rerr
  258. }
  259. // Ensure we got the shim ProcessState
  260. <-p.cmdDoneCh
  261. shimStatus := p.cmd.ProcessState.Sys().(syscall.WaitStatus)
  262. if shimStatus.Signaled() && shimStatus.Signal() == syscall.SIGKILL {
  263. logrus.Debugf("containerd: ExitStatus(container: %s, process: %s): shim was SIGKILL'ed reaping its child with pid %d", p.container.id, p.id, p.pid)
  264. rerr = nil
  265. rst = 128 + uint32(shimStatus.Signal())
  266. p.stateLock.Lock()
  267. p.state = Stopped
  268. p.stateLock.Unlock()
  269. }
  270. return rst, rerr
  271. }
  272. func (p *process) ExitStatus() (rst uint32, rerr error) {
  273. data, err := ioutil.ReadFile(filepath.Join(p.root, ExitStatusFile))
  274. defer func() {
  275. if rerr != nil {
  276. rst, rerr = p.handleSigkilledShim(rst, rerr)
  277. }
  278. }()
  279. if err != nil {
  280. if os.IsNotExist(err) {
  281. return UnknownStatus, ErrProcessNotExited
  282. }
  283. return UnknownStatus, err
  284. }
  285. if len(data) == 0 {
  286. return UnknownStatus, ErrProcessNotExited
  287. }
  288. p.stateLock.Lock()
  289. p.state = Stopped
  290. p.stateLock.Unlock()
  291. i, err := strconv.ParseUint(string(data), 10, 32)
  292. return uint32(i), err
  293. }
  294. func (p *process) Spec() specs.ProcessSpec {
  295. return p.spec
  296. }
  297. func (p *process) Stdio() Stdio {
  298. return p.stdio
  299. }
  300. // Close closes any open files and/or resouces on the process
  301. func (p *process) Close() error {
  302. err := p.exitPipe.Close()
  303. if cerr := p.controlPipe.Close(); err == nil {
  304. err = cerr
  305. }
  306. return err
  307. }
  308. func (p *process) State() State {
  309. p.stateLock.Lock()
  310. defer p.stateLock.Unlock()
  311. return p.state
  312. }
  313. func (p *process) readStartTime() (string, error) {
  314. return readProcStatField(p.pid, 22)
  315. }
  316. func (p *process) saveStartTime() error {
  317. startTime, err := p.readStartTime()
  318. if err != nil {
  319. return err
  320. }
  321. p.startTime = startTime
  322. return ioutil.WriteFile(filepath.Join(p.root, StartTimeFile), []byte(startTime), 0644)
  323. }
  324. func (p *process) isSameProcess() (bool, error) {
  325. if p.pid == 0 {
  326. _, err := p.getPidFromFile()
  327. if err != nil {
  328. return false, err
  329. }
  330. }
  331. // for backward compat assume it's the same if startTime wasn't set
  332. if p.startTime == "" {
  333. // Sometimes the process dies before we can get the starttime,
  334. // check that the process actually exists
  335. if err := unix.Kill(p.pid, 0); err != syscall.ESRCH {
  336. return true, nil
  337. }
  338. return false, nil
  339. }
  340. startTime, err := p.readStartTime()
  341. if err != nil {
  342. return false, err
  343. }
  344. return startTime == p.startTime, nil
  345. }
  346. // Wait will reap the shim process
  347. func (p *process) Wait() {
  348. if p.cmdDoneCh != nil {
  349. <-p.cmdDoneCh
  350. }
  351. }
  352. func getExitPipe(path string) (*os.File, error) {
  353. if err := unix.Mkfifo(path, 0755); err != nil && !os.IsExist(err) {
  354. return nil, err
  355. }
  356. // add NONBLOCK in case the other side has already closed or else
  357. // this function would never return
  358. return os.OpenFile(path, syscall.O_RDONLY|syscall.O_NONBLOCK, 0)
  359. }
  360. func getControlPipe(path string) (*os.File, error) {
  361. if err := unix.Mkfifo(path, 0755); err != nil && !os.IsExist(err) {
  362. return nil, err
  363. }
  364. return os.OpenFile(path, syscall.O_RDWR|syscall.O_NONBLOCK, 0)
  365. }
  366. // Signal sends the provided signal to the process
  367. func (p *process) Signal(s os.Signal) error {
  368. return syscall.Kill(p.pid, s.(syscall.Signal))
  369. }
  370. // Start unblocks the associated container init process.
  371. // This should only be called on the process with ID "init"
  372. func (p *process) Start() error {
  373. if p.ID() == InitProcessID {
  374. var (
  375. errC = make(chan error, 1)
  376. args = append(p.container.runtimeArgs, "start", p.container.id)
  377. cmd = exec.Command(p.container.runtime, args...)
  378. )
  379. go func() {
  380. out, err := cmd.CombinedOutput()
  381. if err != nil {
  382. errC <- fmt.Errorf("%s: %q", err.Error(), out)
  383. }
  384. errC <- nil
  385. }()
  386. select {
  387. case err := <-errC:
  388. if err != nil {
  389. return err
  390. }
  391. case <-p.cmdDoneCh:
  392. if !p.cmdSuccess {
  393. if cmd.Process != nil {
  394. cmd.Process.Kill()
  395. }
  396. cmd.Wait()
  397. return ErrShimExited
  398. }
  399. err := <-errC
  400. if err != nil {
  401. return err
  402. }
  403. }
  404. }
  405. return nil
  406. }
  407. // Delete delete any resources held by the container
  408. func (p *process) Delete() error {
  409. var (
  410. args = append(p.container.runtimeArgs, "delete", "-f", p.container.id)
  411. cmd = exec.Command(p.container.runtime, args...)
  412. )
  413. cmd.SysProcAttr = osutils.SetPDeathSig()
  414. out, err := cmd.CombinedOutput()
  415. if err != nil {
  416. return fmt.Errorf("%s: %v", out, err)
  417. }
  418. return nil
  419. }