github.com/docker/containerd@v0.2.9-0.20170509230648-8ef7df579710/runtime/process.go (about)

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