github.com/jingleWang/moby@v1.13.1/libcontainerd/container_unix.go (about)

     1  // +build linux solaris
     2  
     3  package libcontainerd
     4  
     5  import (
     6  	"encoding/json"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"sync"
    12  	"syscall"
    13  	"time"
    14  
    15  	"github.com/Sirupsen/logrus"
    16  	containerd "github.com/docker/containerd/api/grpc/types"
    17  	"github.com/docker/docker/pkg/ioutils"
    18  	specs "github.com/opencontainers/runtime-spec/specs-go"
    19  	"github.com/tonistiigi/fifo"
    20  	"golang.org/x/net/context"
    21  )
    22  
    23  type container struct {
    24  	containerCommon
    25  
    26  	// Platform specific fields are below here.
    27  	pauseMonitor
    28  	oom         bool
    29  	runtime     string
    30  	runtimeArgs []string
    31  }
    32  
    33  type runtime struct {
    34  	path string
    35  	args []string
    36  }
    37  
    38  // WithRuntime sets the runtime to be used for the created container
    39  func WithRuntime(path string, args []string) CreateOption {
    40  	return runtime{path, args}
    41  }
    42  
    43  func (rt runtime) Apply(p interface{}) error {
    44  	if pr, ok := p.(*container); ok {
    45  		pr.runtime = rt.path
    46  		pr.runtimeArgs = rt.args
    47  	}
    48  	return nil
    49  }
    50  
    51  func (ctr *container) clean() error {
    52  	if os.Getenv("LIBCONTAINERD_NOCLEAN") == "1" {
    53  		return nil
    54  	}
    55  	if _, err := os.Lstat(ctr.dir); err != nil {
    56  		if os.IsNotExist(err) {
    57  			return nil
    58  		}
    59  		return err
    60  	}
    61  
    62  	if err := os.RemoveAll(ctr.dir); err != nil {
    63  		return err
    64  	}
    65  	return nil
    66  }
    67  
    68  // cleanProcess removes the fifos used by an additional process.
    69  // Caller needs to lock container ID before calling this method.
    70  func (ctr *container) cleanProcess(id string) {
    71  	if p, ok := ctr.processes[id]; ok {
    72  		for _, i := range []int{syscall.Stdin, syscall.Stdout, syscall.Stderr} {
    73  			if err := os.Remove(p.fifo(i)); err != nil && !os.IsNotExist(err) {
    74  				logrus.Warnf("libcontainerd: failed to remove %v for process %v: %v", p.fifo(i), id, err)
    75  			}
    76  		}
    77  	}
    78  	delete(ctr.processes, id)
    79  }
    80  
    81  func (ctr *container) spec() (*specs.Spec, error) {
    82  	var spec specs.Spec
    83  	dt, err := ioutil.ReadFile(filepath.Join(ctr.dir, configFilename))
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	if err := json.Unmarshal(dt, &spec); err != nil {
    88  		return nil, err
    89  	}
    90  	return &spec, nil
    91  }
    92  
    93  func (ctr *container) start(checkpoint string, checkpointDir string, attachStdio StdioCallback) (err error) {
    94  	spec, err := ctr.spec()
    95  	if err != nil {
    96  		return nil
    97  	}
    98  
    99  	ctx, cancel := context.WithCancel(context.Background())
   100  	defer cancel()
   101  	ready := make(chan struct{})
   102  
   103  	fifoCtx, cancel := context.WithCancel(context.Background())
   104  	defer func() {
   105  		if err != nil {
   106  			cancel()
   107  		}
   108  	}()
   109  
   110  	iopipe, err := ctr.openFifos(fifoCtx, spec.Process.Terminal)
   111  	if err != nil {
   112  		return err
   113  	}
   114  
   115  	var stdinOnce sync.Once
   116  
   117  	// we need to delay stdin closure after container start or else "stdin close"
   118  	// event will be rejected by containerd.
   119  	// stdin closure happens in attachStdio
   120  	stdin := iopipe.Stdin
   121  	iopipe.Stdin = ioutils.NewWriteCloserWrapper(stdin, func() error {
   122  		var err error
   123  		stdinOnce.Do(func() { // on error from attach we don't know if stdin was already closed
   124  			err = stdin.Close()
   125  			go func() {
   126  				select {
   127  				case <-ready:
   128  				case <-ctx.Done():
   129  				}
   130  				select {
   131  				case <-ready:
   132  					if err := ctr.sendCloseStdin(); err != nil {
   133  						logrus.Warnf("failed to close stdin: %+v", err)
   134  					}
   135  				default:
   136  				}
   137  			}()
   138  		})
   139  		return err
   140  	})
   141  
   142  	r := &containerd.CreateContainerRequest{
   143  		Id:            ctr.containerID,
   144  		BundlePath:    ctr.dir,
   145  		Stdin:         ctr.fifo(syscall.Stdin),
   146  		Stdout:        ctr.fifo(syscall.Stdout),
   147  		Stderr:        ctr.fifo(syscall.Stderr),
   148  		Checkpoint:    checkpoint,
   149  		CheckpointDir: checkpointDir,
   150  		// check to see if we are running in ramdisk to disable pivot root
   151  		NoPivotRoot: os.Getenv("DOCKER_RAMDISK") != "",
   152  		Runtime:     ctr.runtime,
   153  		RuntimeArgs: ctr.runtimeArgs,
   154  	}
   155  	ctr.client.appendContainer(ctr)
   156  
   157  	if err := attachStdio(*iopipe); err != nil {
   158  		ctr.closeFifos(iopipe)
   159  		return err
   160  	}
   161  
   162  	resp, err := ctr.client.remote.apiClient.CreateContainer(context.Background(), r)
   163  	if err != nil {
   164  		ctr.closeFifos(iopipe)
   165  		return err
   166  	}
   167  	ctr.systemPid = systemPid(resp.Container)
   168  	close(ready)
   169  
   170  	return ctr.client.backend.StateChanged(ctr.containerID, StateInfo{
   171  		CommonStateInfo: CommonStateInfo{
   172  			State: StateStart,
   173  			Pid:   ctr.systemPid,
   174  		}})
   175  }
   176  
   177  func (ctr *container) newProcess(friendlyName string) *process {
   178  	return &process{
   179  		dir: ctr.dir,
   180  		processCommon: processCommon{
   181  			containerID:  ctr.containerID,
   182  			friendlyName: friendlyName,
   183  			client:       ctr.client,
   184  		},
   185  	}
   186  }
   187  
   188  func (ctr *container) handleEvent(e *containerd.Event) error {
   189  	ctr.client.lock(ctr.containerID)
   190  	defer ctr.client.unlock(ctr.containerID)
   191  	switch e.Type {
   192  	case StateExit, StatePause, StateResume, StateOOM:
   193  		st := StateInfo{
   194  			CommonStateInfo: CommonStateInfo{
   195  				State:    e.Type,
   196  				ExitCode: e.Status,
   197  			},
   198  			OOMKilled: e.Type == StateExit && ctr.oom,
   199  		}
   200  		if e.Type == StateOOM {
   201  			ctr.oom = true
   202  		}
   203  		if e.Type == StateExit && e.Pid != InitFriendlyName {
   204  			st.ProcessID = e.Pid
   205  			st.State = StateExitProcess
   206  		}
   207  
   208  		// Remove process from list if we have exited
   209  		switch st.State {
   210  		case StateExit:
   211  			ctr.clean()
   212  			ctr.client.deleteContainer(e.Id)
   213  		case StateExitProcess:
   214  			ctr.cleanProcess(st.ProcessID)
   215  		}
   216  		ctr.client.q.append(e.Id, func() {
   217  			if err := ctr.client.backend.StateChanged(e.Id, st); err != nil {
   218  				logrus.Errorf("libcontainerd: backend.StateChanged(): %v", err)
   219  			}
   220  			if e.Type == StatePause || e.Type == StateResume {
   221  				ctr.pauseMonitor.handle(e.Type)
   222  			}
   223  			if e.Type == StateExit {
   224  				if en := ctr.client.getExitNotifier(e.Id); en != nil {
   225  					en.close()
   226  				}
   227  			}
   228  		})
   229  
   230  	default:
   231  		logrus.Debugf("libcontainerd: event unhandled: %+v", e)
   232  	}
   233  	return nil
   234  }
   235  
   236  // discardFifos attempts to fully read the container fifos to unblock processes
   237  // that may be blocked on the writer side.
   238  func (ctr *container) discardFifos() {
   239  	ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
   240  	for _, i := range []int{syscall.Stdout, syscall.Stderr} {
   241  		f, err := fifo.OpenFifo(ctx, ctr.fifo(i), syscall.O_RDONLY|syscall.O_NONBLOCK, 0)
   242  		if err != nil {
   243  			logrus.Warnf("error opening fifo %v for discarding: %+v", f, err)
   244  			continue
   245  		}
   246  		go func() {
   247  			io.Copy(ioutil.Discard, f)
   248  		}()
   249  	}
   250  }