123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- package runtime
- import (
- "encoding/json"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path/filepath"
- "strconv"
- "syscall"
- "time"
- "github.com/docker/containerd/specs"
- "github.com/docker/containerd/subreaper/exec"
- "golang.org/x/sys/unix"
- )
- type Process interface {
- io.Closer
- // ID of the process.
- // This is either "init" when it is the container's init process or
- // it is a user provided id for the process similar to the container id
- ID() string
- CloseStdin() error
- Resize(int, int) error
- // ExitFD returns the fd the provides an event when the process exits
- ExitFD() int
- // ExitStatus returns the exit status of the process or an error if it
- // has not exited
- ExitStatus() (int, error)
- // Spec returns the process spec that created the process
- Spec() specs.ProcessSpec
- // Signal sends the provided signal to the process
- Signal(os.Signal) error
- // Container returns the container that the process belongs to
- Container() Container
- // Stdio of the container
- Stdio() Stdio
- // SystemPid is the pid on the system
- SystemPid() int
- // State returns if the process is running or not
- State() State
- // Start executes the process
- Start() error
- }
- type processConfig struct {
- id string
- root string
- processSpec specs.ProcessSpec
- spec *specs.Spec
- c *container
- stdio Stdio
- exec bool
- checkpoint string
- }
- func newProcess(config *processConfig) (*process, error) {
- p := &process{
- root: config.root,
- id: config.id,
- container: config.c,
- spec: config.processSpec,
- stdio: config.stdio,
- }
- uid, gid, err := getRootIDs(config.spec)
- if err != nil {
- return nil, err
- }
- f, err := os.Create(filepath.Join(config.root, "process.json"))
- if err != nil {
- return nil, err
- }
- defer f.Close()
- ps := ProcessState{
- ProcessSpec: config.processSpec,
- Exec: config.exec,
- PlatformProcessState: PlatformProcessState{
- Checkpoint: config.checkpoint,
- RootUID: uid,
- RootGID: gid,
- },
- Stdin: config.stdio.Stdin,
- Stdout: config.stdio.Stdout,
- Stderr: config.stdio.Stderr,
- RuntimeArgs: config.c.runtimeArgs,
- NoPivotRoot: config.c.noPivotRoot,
- }
- if err := json.NewEncoder(f).Encode(ps); err != nil {
- return nil, err
- }
- exit, err := getExitPipe(filepath.Join(config.root, ExitFile))
- if err != nil {
- return nil, err
- }
- control, err := getControlPipe(filepath.Join(config.root, ControlFile))
- if err != nil {
- return nil, err
- }
- p.exitPipe = exit
- p.controlPipe = control
- return p, nil
- }
- func (p *process) Start() error {
- cmd := exec.Command(p.container.shim,
- p.container.id, p.container.bundle, p.container.runtime,
- )
- cmd.Dir = p.root
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Setpgid: true,
- }
- return p.startCmd(cmd)
- }
- func loadProcess(root, id string, c *container, s *ProcessState) (*process, error) {
- p := &process{
- root: root,
- id: id,
- container: c,
- spec: s.ProcessSpec,
- stdio: Stdio{
- Stdin: s.Stdin,
- Stdout: s.Stdout,
- Stderr: s.Stderr,
- },
- }
- if _, err := p.getPidFromFile(); err != nil {
- return nil, err
- }
- if _, err := p.ExitStatus(); err != nil {
- if err == ErrProcessNotExited {
- exit, err := getExitPipe(filepath.Join(root, ExitFile))
- if err != nil {
- return nil, err
- }
- p.exitPipe = exit
- return p, nil
- }
- return nil, err
- }
- return p, nil
- }
- type process struct {
- root string
- id string
- pid int
- exitPipe *os.File
- controlPipe *os.File
- container *container
- spec specs.ProcessSpec
- stdio Stdio
- }
- func (p *process) ID() string {
- return p.id
- }
- func (p *process) Container() Container {
- return p.container
- }
- func (p *process) SystemPid() int {
- return p.pid
- }
- // ExitFD returns the fd of the exit pipe
- func (p *process) ExitFD() int {
- return int(p.exitPipe.Fd())
- }
- func (p *process) CloseStdin() error {
- _, err := fmt.Fprintf(p.controlPipe, "%d %d %d\n", 0, 0, 0)
- return err
- }
- func (p *process) Resize(w, h int) error {
- _, err := fmt.Fprintf(p.controlPipe, "%d %d %d\n", 1, w, h)
- return err
- }
- func (p *process) ExitStatus() (int, error) {
- data, err := ioutil.ReadFile(filepath.Join(p.root, ExitStatusFile))
- if err != nil {
- if os.IsNotExist(err) {
- return -1, ErrProcessNotExited
- }
- return -1, err
- }
- if len(data) == 0 {
- return -1, ErrProcessNotExited
- }
- return strconv.Atoi(string(data))
- }
- func (p *process) Spec() specs.ProcessSpec {
- return p.spec
- }
- func (p *process) Stdio() Stdio {
- return p.stdio
- }
- // Close closes any open files and/or resouces on the process
- func (p *process) Close() error {
- return p.exitPipe.Close()
- }
- func (p *process) State() State {
- if p.pid == 0 {
- return Stopped
- }
- err := syscall.Kill(p.pid, 0)
- if err != nil && err == syscall.ESRCH {
- return Stopped
- }
- return Running
- }
- func (p *process) getPidFromFile() (int, error) {
- data, err := ioutil.ReadFile(filepath.Join(p.root, "pid"))
- if err != nil {
- return -1, err
- }
- i, err := strconv.Atoi(string(data))
- if err != nil {
- return -1, errInvalidPidInt
- }
- p.pid = i
- return i, nil
- }
- func getExitPipe(path string) (*os.File, error) {
- if err := unix.Mkfifo(path, 0755); err != nil && !os.IsExist(err) {
- return nil, err
- }
- // add NONBLOCK in case the other side has already closed or else
- // this function would never return
- return os.OpenFile(path, syscall.O_RDONLY|syscall.O_NONBLOCK, 0)
- }
- func getControlPipe(path string) (*os.File, error) {
- if err := unix.Mkfifo(path, 0755); err != nil && !os.IsExist(err) {
- return nil, err
- }
- return os.OpenFile(path, syscall.O_RDWR|syscall.O_NONBLOCK, 0)
- }
- // Signal sends the provided signal to the process
- func (p *process) Signal(s os.Signal) error {
- return syscall.Kill(p.pid, s.(syscall.Signal))
- }
- func (p *process) startCmd(cmd *exec.Cmd) error {
- if err := cmd.Start(); err != nil {
- if exErr, ok := err.(*exec.Error); ok {
- if exErr.Err == exec.ErrNotFound || exErr.Err == os.ErrNotExist {
- return fmt.Errorf("%s not installed on system", p.container.shim)
- }
- }
- return err
- }
- if err := p.waitForStart(cmd); err != nil {
- return err
- }
- return nil
- }
- func (p *process) waitForStart(cmd *exec.Cmd) error {
- wc := make(chan error, 1)
- go func() {
- for {
- if _, err := p.getPidFromFile(); err != nil {
- if os.IsNotExist(err) || err == errInvalidPidInt {
- alive, err := isAlive(cmd)
- if err != nil {
- wc <- err
- return
- }
- if !alive {
- // runc could have failed to run the container so lets get the error
- // out of the logs or the shim could have encountered an error
- messages, err := readLogMessages(filepath.Join(p.root, "shim-log.json"))
- if err != nil && !os.IsNotExist(err) {
- wc <- err
- return
- }
- for _, m := range messages {
- if m.Level == "error" {
- wc <- fmt.Errorf("shim error: %v", m.Msg)
- return
- }
- }
- // no errors reported back from shim, check for runc/runtime errors
- messages, err = readLogMessages(filepath.Join(p.root, "log.json"))
- if err != nil {
- if os.IsNotExist(err) {
- err = ErrContainerNotStarted
- }
- wc <- err
- return
- }
- for _, m := range messages {
- if m.Level == "error" {
- wc <- fmt.Errorf("oci runtime error: %v", m.Msg)
- return
- }
- }
- wc <- ErrContainerNotStarted
- return
- }
- time.Sleep(15 * time.Millisecond)
- continue
- }
- wc <- err
- return
- }
- // the pid file was read successfully
- wc <- nil
- return
- }
- }()
- select {
- case err := <-wc:
- if err != nil {
- return err
- }
- return nil
- case <-time.After(p.container.timeout):
- cmd.Process.Kill()
- cmd.Wait()
- return ErrContainerStartTimeout
- }
- }
|