package runtime import ( "encoding/json" "fmt" "io" "io/ioutil" "os" "path/filepath" "strconv" "syscall" "github.com/docker/containerd/specs" ) 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 } 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 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 := syscall.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 := syscall.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)) }