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