github.com/gdevillele/moby@v1.13.0/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) 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  	iopipe, err := ctr.openFifos(spec.Process.Terminal)
   104  	if err != nil {
   105  		return err
   106  	}
   107  
   108  	var stdinOnce sync.Once
   109  
   110  	// we need to delay stdin closure after container start or else "stdin close"
   111  	// event will be rejected by containerd.
   112  	// stdin closure happens in attachStdio
   113  	stdin := iopipe.Stdin
   114  	iopipe.Stdin = ioutils.NewWriteCloserWrapper(stdin, func() error {
   115  		var err error
   116  		stdinOnce.Do(func() { // on error from attach we don't know if stdin was already closed
   117  			err = stdin.Close()
   118  			go func() {
   119  				select {
   120  				case <-ready:
   121  				case <-ctx.Done():
   122  				}
   123  				select {
   124  				case <-ready:
   125  					if err := ctr.sendCloseStdin(); err != nil {
   126  						logrus.Warnf("failed to close stdin: %+v", err)
   127  					}
   128  				default:
   129  				}
   130  			}()
   131  		})
   132  		return err
   133  	})
   134  
   135  	r := &containerd.CreateContainerRequest{
   136  		Id:            ctr.containerID,
   137  		BundlePath:    ctr.dir,
   138  		Stdin:         ctr.fifo(syscall.Stdin),
   139  		Stdout:        ctr.fifo(syscall.Stdout),
   140  		Stderr:        ctr.fifo(syscall.Stderr),
   141  		Checkpoint:    checkpoint,
   142  		CheckpointDir: checkpointDir,
   143  		// check to see if we are running in ramdisk to disable pivot root
   144  		NoPivotRoot: os.Getenv("DOCKER_RAMDISK") != "",
   145  		Runtime:     ctr.runtime,
   146  		RuntimeArgs: ctr.runtimeArgs,
   147  	}
   148  	ctr.client.appendContainer(ctr)
   149  
   150  	if err := attachStdio(*iopipe); err != nil {
   151  		ctr.closeFifos(iopipe)
   152  		return err
   153  	}
   154  
   155  	resp, err := ctr.client.remote.apiClient.CreateContainer(context.Background(), r)
   156  	if err != nil {
   157  		ctr.closeFifos(iopipe)
   158  		return err
   159  	}
   160  	ctr.systemPid = systemPid(resp.Container)
   161  	close(ready)
   162  
   163  	return ctr.client.backend.StateChanged(ctr.containerID, StateInfo{
   164  		CommonStateInfo: CommonStateInfo{
   165  			State: StateStart,
   166  			Pid:   ctr.systemPid,
   167  		}})
   168  }
   169  
   170  func (ctr *container) newProcess(friendlyName string) *process {
   171  	return &process{
   172  		dir: ctr.dir,
   173  		processCommon: processCommon{
   174  			containerID:  ctr.containerID,
   175  			friendlyName: friendlyName,
   176  			client:       ctr.client,
   177  		},
   178  	}
   179  }
   180  
   181  func (ctr *container) handleEvent(e *containerd.Event) error {
   182  	ctr.client.lock(ctr.containerID)
   183  	defer ctr.client.unlock(ctr.containerID)
   184  	switch e.Type {
   185  	case StateExit, StatePause, StateResume, StateOOM:
   186  		st := StateInfo{
   187  			CommonStateInfo: CommonStateInfo{
   188  				State:    e.Type,
   189  				ExitCode: e.Status,
   190  			},
   191  			OOMKilled: e.Type == StateExit && ctr.oom,
   192  		}
   193  		if e.Type == StateOOM {
   194  			ctr.oom = true
   195  		}
   196  		if e.Type == StateExit && e.Pid != InitFriendlyName {
   197  			st.ProcessID = e.Pid
   198  			st.State = StateExitProcess
   199  		}
   200  
   201  		// Remove process from list if we have exited
   202  		switch st.State {
   203  		case StateExit:
   204  			ctr.clean()
   205  			ctr.client.deleteContainer(e.Id)
   206  		case StateExitProcess:
   207  			ctr.cleanProcess(st.ProcessID)
   208  		}
   209  		ctr.client.q.append(e.Id, func() {
   210  			if err := ctr.client.backend.StateChanged(e.Id, st); err != nil {
   211  				logrus.Errorf("libcontainerd: backend.StateChanged(): %v", err)
   212  			}
   213  			if e.Type == StatePause || e.Type == StateResume {
   214  				ctr.pauseMonitor.handle(e.Type)
   215  			}
   216  			if e.Type == StateExit {
   217  				if en := ctr.client.getExitNotifier(e.Id); en != nil {
   218  					en.close()
   219  				}
   220  			}
   221  		})
   222  
   223  	default:
   224  		logrus.Debugf("libcontainerd: event unhandled: %+v", e)
   225  	}
   226  	return nil
   227  }
   228  
   229  // discardFifos attempts to fully read the container fifos to unblock processes
   230  // that may be blocked on the writer side.
   231  func (ctr *container) discardFifos() {
   232  	ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
   233  	for _, i := range []int{syscall.Stdout, syscall.Stderr} {
   234  		f, err := fifo.OpenFifo(ctx, ctr.fifo(i), syscall.O_RDONLY|syscall.O_NONBLOCK, 0)
   235  		if err != nil {
   236  			logrus.Warnf("error opening fifo %v for discarding: %+v", f, err)
   237  			continue
   238  		}
   239  		go func() {
   240  			io.Copy(ioutil.Discard, f)
   241  		}()
   242  	}
   243  }