123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476 |
- package runtime
- import (
- "encoding/json"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "os/exec"
- "path/filepath"
- "strconv"
- "strings"
- "sync"
- "syscall"
- "time"
- "github.com/Sirupsen/logrus"
- "github.com/containerd/containerd/osutils"
- "github.com/containerd/containerd/specs"
- "golang.org/x/sys/unix"
- )
- // Process holds the operation allowed on a container's process
- 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
- // Start unblocks the associated container init process.
- // This should only be called on the process with ID "init"
- Start() error
- 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() (uint32, 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
- // Wait reaps the shim process if avaliable
- Wait()
- }
- 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,
- cmdDoneCh: make(chan struct{}),
- state: Running,
- }
- 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,
- },
- state: Stopped,
- }
- startTime, err := ioutil.ReadFile(filepath.Join(p.root, StartTimeFile))
- if err != nil && !os.IsNotExist(err) {
- return nil, err
- }
- p.startTime = string(startTime)
- 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
- control, err := getControlPipe(filepath.Join(root, ControlFile))
- if err != nil {
- return nil, err
- }
- p.controlPipe = control
- p.state = Running
- return p, nil
- }
- return nil, err
- }
- return p, nil
- }
- func readProcStatField(pid int, field int) (string, error) {
- data, err := ioutil.ReadFile(filepath.Join(string(filepath.Separator), "proc", strconv.Itoa(pid), "stat"))
- if err != nil {
- return "", err
- }
- if field > 2 {
- // First, split out the name since he could contains spaces.
- parts := strings.Split(string(data), ") ")
- // Now split out the rest, we end up with 2 fields less
- parts = strings.Split(parts[1], " ")
- return parts[field-2-1], nil // field count start at 1 in manual
- }
- parts := strings.Split(string(data), " (")
- if field == 1 {
- return parts[0], nil
- }
- parts = strings.Split(parts[1], ") ")
- return parts[0], nil
- }
- type process struct {
- root string
- id string
- pid int
- exitPipe *os.File
- controlPipe *os.File
- container *container
- spec specs.ProcessSpec
- stdio Stdio
- cmd *exec.Cmd
- cmdSuccess bool
- cmdDoneCh chan struct{}
- state State
- stateLock sync.Mutex
- startTime string
- }
- 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) updateExitStatusFile(status uint32) (uint32, error) {
- p.stateLock.Lock()
- p.state = Stopped
- p.stateLock.Unlock()
- err := ioutil.WriteFile(filepath.Join(p.root, ExitStatusFile), []byte(fmt.Sprintf("%u", status)), 0644)
- return status, err
- }
- func (p *process) handleSigkilledShim(rst uint32, rerr error) (uint32, error) {
- if p.cmd == nil || p.cmd.Process == nil {
- e := unix.Kill(p.pid, 0)
- if e == syscall.ESRCH {
- logrus.Warnf("containerd: %s:%s (pid %d) does not exist", p.container.id, p.id, p.pid)
- // The process died while containerd was down (probably of
- // SIGKILL, but no way to be sure)
- return p.updateExitStatusFile(UnknownStatus)
- }
- // If it's not the same process, just mark it stopped and set
- // the status to the UnknownStatus value (i.e. 255)
- if same, err := p.isSameProcess(); !same {
- logrus.Warnf("containerd: %s:%s (pid %d) is not the same process anymore (%v)", p.container.id, p.id, p.pid, err)
- // Create the file so we get the exit event generated once monitor kicks in
- // without having to go through all this process again
- return p.updateExitStatusFile(UnknownStatus)
- }
- ppid, err := readProcStatField(p.pid, 4)
- if err != nil {
- return rst, fmt.Errorf("could not check process ppid: %v (%v)", err, rerr)
- }
- if ppid == "1" {
- logrus.Warnf("containerd: %s:%s shim died, killing associated process", p.container.id, p.id)
- unix.Kill(p.pid, syscall.SIGKILL)
- if err != nil && err != syscall.ESRCH {
- return UnknownStatus, fmt.Errorf("containerd: unable to SIGKILL %s:%s (pid %v): %v", p.container.id, p.id, p.pid, err)
- }
- // wait for the process to die
- for {
- e := unix.Kill(p.pid, 0)
- if e == syscall.ESRCH {
- break
- }
- time.Sleep(5 * time.Millisecond)
- }
- // Create the file so we get the exit event generated once monitor kicks in
- // without having to go through all this process again
- return p.updateExitStatusFile(128 + uint32(syscall.SIGKILL))
- }
- return rst, rerr
- }
- // Possible that the shim was SIGKILLED
- e := unix.Kill(p.cmd.Process.Pid, 0)
- if e != syscall.ESRCH {
- return rst, rerr
- }
- // Ensure we got the shim ProcessState
- <-p.cmdDoneCh
- shimStatus := p.cmd.ProcessState.Sys().(syscall.WaitStatus)
- if shimStatus.Signaled() && shimStatus.Signal() == syscall.SIGKILL {
- 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)
- rerr = nil
- rst = 128 + uint32(shimStatus.Signal())
- p.stateLock.Lock()
- p.state = Stopped
- p.stateLock.Unlock()
- }
- return rst, rerr
- }
- func (p *process) ExitStatus() (rst uint32, rerr error) {
- data, err := ioutil.ReadFile(filepath.Join(p.root, ExitStatusFile))
- defer func() {
- if rerr != nil {
- rst, rerr = p.handleSigkilledShim(rst, rerr)
- }
- }()
- if err != nil {
- if os.IsNotExist(err) {
- return UnknownStatus, ErrProcessNotExited
- }
- return UnknownStatus, err
- }
- if len(data) == 0 {
- return UnknownStatus, ErrProcessNotExited
- }
- p.stateLock.Lock()
- p.state = Stopped
- p.stateLock.Unlock()
- i, err := strconv.ParseUint(string(data), 10, 32)
- return uint32(i), err
- }
- 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 {
- err := p.exitPipe.Close()
- if cerr := p.controlPipe.Close(); err == nil {
- err = cerr
- }
- return err
- }
- func (p *process) State() State {
- p.stateLock.Lock()
- defer p.stateLock.Unlock()
- return p.state
- }
- func (p *process) readStartTime() (string, error) {
- return readProcStatField(p.pid, 22)
- }
- func (p *process) saveStartTime() error {
- startTime, err := p.readStartTime()
- if err != nil {
- return err
- }
- p.startTime = startTime
- return ioutil.WriteFile(filepath.Join(p.root, StartTimeFile), []byte(startTime), 0644)
- }
- func (p *process) isSameProcess() (bool, error) {
- if p.pid == 0 {
- _, err := p.getPidFromFile()
- if err != nil {
- return false, err
- }
- }
- // for backward compat assume it's the same if startTime wasn't set
- if p.startTime == "" {
- // Sometimes the process dies before we can get the starttime,
- // check that the process actually exists
- if err := unix.Kill(p.pid, 0); err != syscall.ESRCH {
- return true, nil
- }
- return false, nil
- }
- startTime, err := p.readStartTime()
- if err != nil {
- return false, err
- }
- return startTime == p.startTime, nil
- }
- // Wait will reap the shim process
- func (p *process) Wait() {
- if p.cmdDoneCh != nil {
- <-p.cmdDoneCh
- }
- }
- 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))
- }
- // Start unblocks the associated container init process.
- // This should only be called on the process with ID "init"
- func (p *process) Start() error {
- if p.ID() == InitProcessID {
- var (
- errC = make(chan error, 1)
- args = append(p.container.runtimeArgs, "start", p.container.id)
- cmd = exec.Command(p.container.runtime, args...)
- )
- go func() {
- out, err := cmd.CombinedOutput()
- if err != nil {
- errC <- fmt.Errorf("%s: %q", err.Error(), out)
- }
- errC <- nil
- }()
- select {
- case err := <-errC:
- if err != nil {
- return err
- }
- case <-p.cmdDoneCh:
- if !p.cmdSuccess {
- if cmd.Process != nil {
- cmd.Process.Kill()
- }
- cmd.Wait()
- return ErrShimExited
- }
- err := <-errC
- if err != nil {
- return err
- }
- }
- }
- return nil
- }
- // Delete delete any resources held by the container
- func (p *process) Delete() error {
- var (
- args = append(p.container.runtimeArgs, "delete", "-f", p.container.id)
- cmd = exec.Command(p.container.runtime, args...)
- )
- cmd.SysProcAttr = osutils.SetPDeathSig()
- out, err := cmd.CombinedOutput()
- if err != nil {
- return fmt.Errorf("%s: %v", out, err)
- }
- return nil
- }
|