libcontainerd/container_unix.go
934328d8
 // +build linux solaris
 
9c4570a9
 package libcontainerd
 
 import (
 	"encoding/json"
d705dab1
 	"io"
9c4570a9
 	"io/ioutil"
 	"os"
 	"path/filepath"
6f2658fb
 	"sync"
6d264645
 	"time"
9c4570a9
 
 	"github.com/Sirupsen/logrus"
0ea0b2be
 	containerd "github.com/containerd/containerd/api/grpc/types"
02d19342
 	"github.com/docker/docker/pkg/ioutils"
ee9d28bd
 	specs "github.com/opencontainers/runtime-spec/specs-go"
6d264645
 	"github.com/tonistiigi/fifo"
9c4570a9
 	"golang.org/x/net/context"
069fdc8a
 	"golang.org/x/sys/unix"
9c4570a9
 )
 
 type container struct {
 	containerCommon
 
 	// Platform specific fields are below here.
 	pauseMonitor
7b2e5216
 	oom         bool
 	runtime     string
 	runtimeArgs []string
 }
 
 type runtime struct {
 	path string
 	args []string
 }
 
 // WithRuntime sets the runtime to be used for the created container
 func WithRuntime(path string, args []string) CreateOption {
 	return runtime{path, args}
 }
 
 func (rt runtime) Apply(p interface{}) error {
 	if pr, ok := p.(*container); ok {
 		pr.runtime = rt.path
 		pr.runtimeArgs = rt.args
 	}
 	return nil
9c4570a9
 }
 
 func (ctr *container) clean() error {
7bf07737
 	if os.Getenv("LIBCONTAINERD_NOCLEAN") == "1" {
 		return nil
 	}
9c4570a9
 	if _, err := os.Lstat(ctr.dir); err != nil {
 		if os.IsNotExist(err) {
 			return nil
 		}
 		return err
 	}
 
 	if err := os.RemoveAll(ctr.dir); err != nil {
 		return err
 	}
 	return nil
 }
 
84d17012
 // cleanProcess removes the fifos used by an additional process.
 // Caller needs to lock container ID before calling this method.
 func (ctr *container) cleanProcess(id string) {
 	if p, ok := ctr.processes[id]; ok {
069fdc8a
 		for _, i := range []int{unix.Stdin, unix.Stdout, unix.Stderr} {
ee9d28bd
 			if err := os.Remove(p.fifo(i)); err != nil && !os.IsNotExist(err) {
5231c553
 				logrus.Warnf("libcontainerd: failed to remove %v for process %v: %v", p.fifo(i), id, err)
84d17012
 			}
 		}
 	}
 	delete(ctr.processes, id)
 }
 
9c4570a9
 func (ctr *container) spec() (*specs.Spec, error) {
 	var spec specs.Spec
 	dt, err := ioutil.ReadFile(filepath.Join(ctr.dir, configFilename))
 	if err != nil {
 		return nil, err
 	}
 	if err := json.Unmarshal(dt, &spec); err != nil {
 		return nil, err
 	}
 	return &spec, nil
 }
 
8d588d9c
 func (ctr *container) start(spec *specs.Spec, checkpoint, checkpointDir string, attachStdio StdioCallback) (err error) {
6f2658fb
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 	ready := make(chan struct{})
 
c178700a
 	fifoCtx, cancel := context.WithCancel(context.Background())
 	defer func() {
 		if err != nil {
 			cancel()
 		}
 	}()
 
 	iopipe, err := ctr.openFifos(fifoCtx, spec.Process.Terminal)
9c4570a9
 	if err != nil {
 		return err
 	}
 
6f2658fb
 	var stdinOnce sync.Once
 
02d19342
 	// we need to delay stdin closure after container start or else "stdin close"
 	// event will be rejected by containerd.
37a3be24
 	// stdin closure happens in attachStdio
02d19342
 	stdin := iopipe.Stdin
 	iopipe.Stdin = ioutils.NewWriteCloserWrapper(stdin, func() error {
6f2658fb
 		var err error
 		stdinOnce.Do(func() { // on error from attach we don't know if stdin was already closed
 			err = stdin.Close()
 			go func() {
 				select {
 				case <-ready:
4e262f63
 				case <-ctx.Done():
 				}
 				select {
 				case <-ready:
6f2658fb
 					if err := ctr.sendCloseStdin(); err != nil {
aa01ee4a
 						logrus.Warnf("failed to close stdin: %+v", err)
6f2658fb
 					}
4e262f63
 				default:
6f2658fb
 				}
 			}()
 		})
 		return err
02d19342
 	})
 
9c4570a9
 	r := &containerd.CreateContainerRequest{
d8fef66b
 		Id:            ctr.containerID,
 		BundlePath:    ctr.dir,
069fdc8a
 		Stdin:         ctr.fifo(unix.Stdin),
 		Stdout:        ctr.fifo(unix.Stdout),
 		Stderr:        ctr.fifo(unix.Stderr),
d8fef66b
 		Checkpoint:    checkpoint,
 		CheckpointDir: checkpointDir,
8a4225cd
 		// check to see if we are running in ramdisk to disable pivot root
 		NoPivotRoot: os.Getenv("DOCKER_RAMDISK") != "",
7b2e5216
 		Runtime:     ctr.runtime,
 		RuntimeArgs: ctr.runtimeArgs,
9c4570a9
 	}
 	ctr.client.appendContainer(ctr)
 
37a3be24
 	if err := attachStdio(*iopipe); err != nil {
9c4570a9
 		ctr.closeFifos(iopipe)
 		return err
 	}
 
02d19342
 	resp, err := ctr.client.remote.apiClient.CreateContainer(context.Background(), r)
 	if err != nil {
 		ctr.closeFifos(iopipe)
9c4570a9
 		return err
 	}
 	ctr.systemPid = systemPid(resp.Container)
6f2658fb
 	close(ready)
9c4570a9
 
 	return ctr.client.backend.StateChanged(ctr.containerID, StateInfo{
818a5198
 		CommonStateInfo: CommonStateInfo{
 			State: StateStart,
 			Pid:   ctr.systemPid,
 		}})
8d588d9c
 
9c4570a9
 }
 
 func (ctr *container) newProcess(friendlyName string) *process {
 	return &process{
 		dir: ctr.dir,
 		processCommon: processCommon{
 			containerID:  ctr.containerID,
 			friendlyName: friendlyName,
 			client:       ctr.client,
 		},
 	}
 }
 
 func (ctr *container) handleEvent(e *containerd.Event) error {
 	ctr.client.lock(ctr.containerID)
 	defer ctr.client.unlock(ctr.containerID)
 	switch e.Type {
 	case StateExit, StatePause, StateResume, StateOOM:
 		st := StateInfo{
818a5198
 			CommonStateInfo: CommonStateInfo{
 				State:    e.Type,
 				ExitCode: e.Status,
 			},
9c4570a9
 			OOMKilled: e.Type == StateExit && ctr.oom,
 		}
 		if e.Type == StateOOM {
 			ctr.oom = true
 		}
 		if e.Type == StateExit && e.Pid != InitFriendlyName {
 			st.ProcessID = e.Pid
 			st.State = StateExitProcess
 		}
495448b2
 
 		// Remove process from list if we have exited
 		switch st.State {
 		case StateExit:
 			ctr.clean()
 			ctr.client.deleteContainer(e.Id)
 		case StateExitProcess:
 			ctr.cleanProcess(st.ProcessID)
 		}
 		ctr.client.q.append(e.Id, func() {
 			if err := ctr.client.backend.StateChanged(e.Id, st); err != nil {
 				logrus.Errorf("libcontainerd: backend.StateChanged(): %v", err)
 			}
9c4570a9
 			if e.Type == StatePause || e.Type == StateResume {
 				ctr.pauseMonitor.handle(e.Type)
 			}
 			if e.Type == StateExit {
 				if en := ctr.client.getExitNotifier(e.Id); en != nil {
 					en.close()
 				}
 			}
 		})
 
 	default:
5231c553
 		logrus.Debugf("libcontainerd: event unhandled: %+v", e)
9c4570a9
 	}
 	return nil
 }
d705dab1
 
 // discardFifos attempts to fully read the container fifos to unblock processes
 // that may be blocked on the writer side.
 func (ctr *container) discardFifos() {
6d264645
 	ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
069fdc8a
 	for _, i := range []int{unix.Stdout, unix.Stderr} {
 		f, err := fifo.OpenFifo(ctx, ctr.fifo(i), unix.O_RDONLY|unix.O_NONBLOCK, 0)
6d264645
 		if err != nil {
 			logrus.Warnf("error opening fifo %v for discarding: %+v", f, err)
 			continue
 		}
d705dab1
 		go func() {
6d264645
 			io.Copy(ioutil.Discard, f)
d705dab1
 		}()
 	}
 }