github.com/runcom/containerd@v0.0.0-20160708090337-9bff9f934c0d/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  	"syscall"
    13  
    14  	"github.com/docker/containerd/specs"
    15  	"golang.org/x/sys/unix"
    16  )
    17  
    18  // Process holds the operation allowed on a container's process
    19  type Process interface {
    20  	io.Closer
    21  
    22  	// ID of the process.
    23  	// This is either "init" when it is the container's init process or
    24  	// it is a user provided id for the process similar to the container id
    25  	ID() string
    26  	// Start unblocks the associated container init process.
    27  	// This should only be called on the process with ID "init"
    28  	Start() error
    29  	CloseStdin() error
    30  	Resize(int, int) error
    31  	// ExitFD returns the fd the provides an event when the process exits
    32  	ExitFD() int
    33  	// ExitStatus returns the exit status of the process or an error if it
    34  	// has not exited
    35  	ExitStatus() (int, error)
    36  	// Spec returns the process spec that created the process
    37  	Spec() specs.ProcessSpec
    38  	// Signal sends the provided signal to the process
    39  	Signal(os.Signal) error
    40  	// Container returns the container that the process belongs to
    41  	Container() Container
    42  	// Stdio of the container
    43  	Stdio() Stdio
    44  	// SystemPid is the pid on the system
    45  	SystemPid() int
    46  	// State returns if the process is running or not
    47  	State() State
    48  	// Wait reaps the shim process if avaliable
    49  	Wait()
    50  }
    51  
    52  type processConfig struct {
    53  	id          string
    54  	root        string
    55  	processSpec specs.ProcessSpec
    56  	spec        *specs.Spec
    57  	c           *container
    58  	stdio       Stdio
    59  	exec        bool
    60  	checkpoint  string
    61  }
    62  
    63  func newProcess(config *processConfig) (*process, error) {
    64  	p := &process{
    65  		root:      config.root,
    66  		id:        config.id,
    67  		container: config.c,
    68  		spec:      config.processSpec,
    69  		stdio:     config.stdio,
    70  		cmdDoneCh: make(chan struct{}),
    71  	}
    72  	uid, gid, err := getRootIDs(config.spec)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	f, err := os.Create(filepath.Join(config.root, "process.json"))
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	defer f.Close()
    81  
    82  	ps := ProcessState{
    83  		ProcessSpec: config.processSpec,
    84  		Exec:        config.exec,
    85  		PlatformProcessState: PlatformProcessState{
    86  			Checkpoint: config.checkpoint,
    87  			RootUID:    uid,
    88  			RootGID:    gid,
    89  		},
    90  		Stdin:       config.stdio.Stdin,
    91  		Stdout:      config.stdio.Stdout,
    92  		Stderr:      config.stdio.Stderr,
    93  		RuntimeArgs: config.c.runtimeArgs,
    94  		NoPivotRoot: config.c.noPivotRoot,
    95  	}
    96  
    97  	if err := json.NewEncoder(f).Encode(ps); err != nil {
    98  		return nil, err
    99  	}
   100  	exit, err := getExitPipe(filepath.Join(config.root, ExitFile))
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	control, err := getControlPipe(filepath.Join(config.root, ControlFile))
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	p.exitPipe = exit
   109  	p.controlPipe = control
   110  	return p, nil
   111  }
   112  
   113  func loadProcess(root, id string, c *container, s *ProcessState) (*process, error) {
   114  	p := &process{
   115  		root:      root,
   116  		id:        id,
   117  		container: c,
   118  		spec:      s.ProcessSpec,
   119  		stdio: Stdio{
   120  			Stdin:  s.Stdin,
   121  			Stdout: s.Stdout,
   122  			Stderr: s.Stderr,
   123  		},
   124  	}
   125  	if _, err := p.getPidFromFile(); err != nil {
   126  		return nil, err
   127  	}
   128  	if _, err := p.ExitStatus(); err != nil {
   129  		if err == ErrProcessNotExited {
   130  			exit, err := getExitPipe(filepath.Join(root, ExitFile))
   131  			if err != nil {
   132  				return nil, err
   133  			}
   134  			p.exitPipe = exit
   135  
   136  			control, err := getControlPipe(filepath.Join(root, ControlFile))
   137  			if err != nil {
   138  				return nil, err
   139  			}
   140  			p.controlPipe = control
   141  
   142  			return p, nil
   143  		}
   144  		return nil, err
   145  	}
   146  	return p, nil
   147  }
   148  
   149  type process struct {
   150  	root        string
   151  	id          string
   152  	pid         int
   153  	exitPipe    *os.File
   154  	controlPipe *os.File
   155  	container   *container
   156  	spec        specs.ProcessSpec
   157  	stdio       Stdio
   158  	cmd         *exec.Cmd
   159  	cmdSuccess  bool
   160  	cmdDoneCh   chan struct{}
   161  }
   162  
   163  func (p *process) ID() string {
   164  	return p.id
   165  }
   166  
   167  func (p *process) Container() Container {
   168  	return p.container
   169  }
   170  
   171  func (p *process) SystemPid() int {
   172  	return p.pid
   173  }
   174  
   175  // ExitFD returns the fd of the exit pipe
   176  func (p *process) ExitFD() int {
   177  	return int(p.exitPipe.Fd())
   178  }
   179  
   180  func (p *process) CloseStdin() error {
   181  	_, err := fmt.Fprintf(p.controlPipe, "%d %d %d\n", 0, 0, 0)
   182  	return err
   183  }
   184  
   185  func (p *process) Resize(w, h int) error {
   186  	_, err := fmt.Fprintf(p.controlPipe, "%d %d %d\n", 1, w, h)
   187  	return err
   188  }
   189  
   190  func (p *process) ExitStatus() (int, error) {
   191  	data, err := ioutil.ReadFile(filepath.Join(p.root, ExitStatusFile))
   192  	if err != nil {
   193  		if os.IsNotExist(err) {
   194  			return -1, ErrProcessNotExited
   195  		}
   196  		return -1, err
   197  	}
   198  	if len(data) == 0 {
   199  		return -1, ErrProcessNotExited
   200  	}
   201  	return strconv.Atoi(string(data))
   202  }
   203  
   204  func (p *process) Spec() specs.ProcessSpec {
   205  	return p.spec
   206  }
   207  
   208  func (p *process) Stdio() Stdio {
   209  	return p.stdio
   210  }
   211  
   212  // Close closes any open files and/or resouces on the process
   213  func (p *process) Close() error {
   214  	err := p.exitPipe.Close()
   215  	if cerr := p.controlPipe.Close(); err == nil {
   216  		err = cerr
   217  	}
   218  	return err
   219  }
   220  
   221  func (p *process) State() State {
   222  	if p.pid == 0 {
   223  		return Stopped
   224  	}
   225  	err := syscall.Kill(p.pid, 0)
   226  	if err != nil && err == syscall.ESRCH {
   227  		return Stopped
   228  	}
   229  	return Running
   230  }
   231  
   232  func (p *process) getPidFromFile() (int, error) {
   233  	data, err := ioutil.ReadFile(filepath.Join(p.root, "pid"))
   234  	if err != nil {
   235  		return -1, err
   236  	}
   237  	i, err := strconv.Atoi(string(data))
   238  	if err != nil {
   239  		return -1, errInvalidPidInt
   240  	}
   241  	p.pid = i
   242  	return i, nil
   243  }
   244  
   245  // Wait will reap the shim process
   246  func (p *process) Wait() {
   247  	if p.cmdDoneCh != nil {
   248  		<-p.cmdDoneCh
   249  	}
   250  }
   251  
   252  func getExitPipe(path string) (*os.File, error) {
   253  	if err := unix.Mkfifo(path, 0755); err != nil && !os.IsExist(err) {
   254  		return nil, err
   255  	}
   256  	// add NONBLOCK in case the other side has already closed or else
   257  	// this function would never return
   258  	return os.OpenFile(path, syscall.O_RDONLY|syscall.O_NONBLOCK, 0)
   259  }
   260  
   261  func getControlPipe(path string) (*os.File, error) {
   262  	if err := unix.Mkfifo(path, 0755); err != nil && !os.IsExist(err) {
   263  		return nil, err
   264  	}
   265  	return os.OpenFile(path, syscall.O_RDWR|syscall.O_NONBLOCK, 0)
   266  }
   267  
   268  // Signal sends the provided signal to the process
   269  func (p *process) Signal(s os.Signal) error {
   270  	return syscall.Kill(p.pid, s.(syscall.Signal))
   271  }
   272  
   273  // Start unblocks the associated container init process.
   274  // This should only be called on the process with ID "init"
   275  func (p *process) Start() error {
   276  	if p.ID() == InitProcessID {
   277  		var (
   278  			errC = make(chan error, 1)
   279  			args = append(p.container.runtimeArgs, "start", p.container.id)
   280  			cmd  = exec.Command(p.container.runtime, args...)
   281  		)
   282  		go func() {
   283  			out, err := cmd.CombinedOutput()
   284  			if err != nil {
   285  				errC <- fmt.Errorf("%s: %q", err.Error(), out)
   286  			}
   287  			errC <- nil
   288  		}()
   289  		select {
   290  		case err := <-errC:
   291  			if err != nil {
   292  				return err
   293  			}
   294  		case <-p.cmdDoneCh:
   295  			if !p.cmdSuccess {
   296  				cmd.Process.Kill()
   297  				cmd.Wait()
   298  				return ErrShimExited
   299  			}
   300  			err := <-errC
   301  			if err != nil {
   302  				return err
   303  			}
   304  		}
   305  	}
   306  	return nil
   307  }