github.com/dougm/docker@v1.5.0/daemon/execdriver/native/driver.go (about)

     1  // +build linux,cgo
     2  
     3  package native
     4  
     5  import (
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"strings"
    14  	"sync"
    15  	"syscall"
    16  	"time"
    17  
    18  	log "github.com/Sirupsen/logrus"
    19  	"github.com/docker/docker/daemon/execdriver"
    20  	sysinfo "github.com/docker/docker/pkg/system"
    21  	"github.com/docker/docker/pkg/term"
    22  	"github.com/docker/libcontainer"
    23  	"github.com/docker/libcontainer/apparmor"
    24  	"github.com/docker/libcontainer/cgroups/fs"
    25  	"github.com/docker/libcontainer/cgroups/systemd"
    26  	consolepkg "github.com/docker/libcontainer/console"
    27  	"github.com/docker/libcontainer/namespaces"
    28  	_ "github.com/docker/libcontainer/namespaces/nsenter"
    29  	"github.com/docker/libcontainer/system"
    30  )
    31  
    32  const (
    33  	DriverName = "native"
    34  	Version    = "0.2"
    35  )
    36  
    37  type activeContainer struct {
    38  	container *libcontainer.Config
    39  	cmd       *exec.Cmd
    40  }
    41  
    42  type driver struct {
    43  	root             string
    44  	initPath         string
    45  	activeContainers map[string]*activeContainer
    46  	machineMemory    int64
    47  	sync.Mutex
    48  }
    49  
    50  func NewDriver(root, initPath string) (*driver, error) {
    51  	meminfo, err := sysinfo.ReadMemInfo()
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	if err := os.MkdirAll(root, 0700); err != nil {
    57  		return nil, err
    58  	}
    59  	// native driver root is at docker_root/execdriver/native. Put apparmor at docker_root
    60  	if err := apparmor.InstallDefaultProfile(); err != nil {
    61  		return nil, err
    62  	}
    63  	return &driver{
    64  		root:             root,
    65  		initPath:         initPath,
    66  		activeContainers: make(map[string]*activeContainer),
    67  		machineMemory:    meminfo.MemTotal,
    68  	}, nil
    69  }
    70  
    71  type execOutput struct {
    72  	exitCode int
    73  	err      error
    74  }
    75  
    76  func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
    77  	// take the Command and populate the libcontainer.Config from it
    78  	container, err := d.createContainer(c)
    79  	if err != nil {
    80  		return execdriver.ExitStatus{ExitCode: -1}, err
    81  	}
    82  
    83  	var term execdriver.Terminal
    84  
    85  	if c.ProcessConfig.Tty {
    86  		term, err = NewTtyConsole(&c.ProcessConfig, pipes)
    87  	} else {
    88  		term, err = execdriver.NewStdConsole(&c.ProcessConfig, pipes)
    89  	}
    90  	if err != nil {
    91  		return execdriver.ExitStatus{ExitCode: -1}, err
    92  	}
    93  	c.ProcessConfig.Terminal = term
    94  
    95  	d.Lock()
    96  	d.activeContainers[c.ID] = &activeContainer{
    97  		container: container,
    98  		cmd:       &c.ProcessConfig.Cmd,
    99  	}
   100  	d.Unlock()
   101  
   102  	var (
   103  		dataPath = filepath.Join(d.root, c.ID)
   104  		args     = append([]string{c.ProcessConfig.Entrypoint}, c.ProcessConfig.Arguments...)
   105  	)
   106  
   107  	if err := d.createContainerRoot(c.ID); err != nil {
   108  		return execdriver.ExitStatus{ExitCode: -1}, err
   109  	}
   110  	defer d.cleanContainer(c.ID)
   111  
   112  	if err := d.writeContainerFile(container, c.ID); err != nil {
   113  		return execdriver.ExitStatus{ExitCode: -1}, err
   114  	}
   115  
   116  	execOutputChan := make(chan execOutput, 1)
   117  	waitForStart := make(chan struct{})
   118  
   119  	go func() {
   120  		exitCode, err := namespaces.Exec(container, c.ProcessConfig.Stdin, c.ProcessConfig.Stdout, c.ProcessConfig.Stderr, c.ProcessConfig.Console, dataPath, args, func(container *libcontainer.Config, console, dataPath, init string, child *os.File, args []string) *exec.Cmd {
   121  			c.ProcessConfig.Path = d.initPath
   122  			c.ProcessConfig.Args = append([]string{
   123  				DriverName,
   124  				"-console", console,
   125  				"-pipe", "3",
   126  				"-root", filepath.Join(d.root, c.ID),
   127  				"--",
   128  			}, args...)
   129  
   130  			// set this to nil so that when we set the clone flags anything else is reset
   131  			c.ProcessConfig.SysProcAttr = &syscall.SysProcAttr{
   132  				Cloneflags: uintptr(namespaces.GetNamespaceFlags(container.Namespaces)),
   133  			}
   134  			c.ProcessConfig.ExtraFiles = []*os.File{child}
   135  
   136  			c.ProcessConfig.Env = container.Env
   137  			c.ProcessConfig.Dir = container.RootFs
   138  
   139  			return &c.ProcessConfig.Cmd
   140  		}, func() {
   141  			close(waitForStart)
   142  			if startCallback != nil {
   143  				c.ContainerPid = c.ProcessConfig.Process.Pid
   144  				startCallback(&c.ProcessConfig, c.ContainerPid)
   145  			}
   146  		})
   147  		execOutputChan <- execOutput{exitCode, err}
   148  	}()
   149  
   150  	select {
   151  	case execOutput := <-execOutputChan:
   152  		return execdriver.ExitStatus{ExitCode: execOutput.exitCode}, execOutput.err
   153  	case <-waitForStart:
   154  		break
   155  	}
   156  
   157  	oomKill := false
   158  	state, err := libcontainer.GetState(filepath.Join(d.root, c.ID))
   159  	if err == nil {
   160  		oomKillNotification, err := libcontainer.NotifyOnOOM(state)
   161  		if err == nil {
   162  			_, oomKill = <-oomKillNotification
   163  		} else {
   164  			log.Warnf("WARNING: Your kernel does not support OOM notifications: %s", err)
   165  		}
   166  	} else {
   167  		log.Warnf("Failed to get container state, oom notify will not work: %s", err)
   168  	}
   169  	// wait for the container to exit.
   170  	execOutput := <-execOutputChan
   171  
   172  	return execdriver.ExitStatus{ExitCode: execOutput.exitCode, OOMKilled: oomKill}, execOutput.err
   173  }
   174  
   175  func (d *driver) Kill(p *execdriver.Command, sig int) error {
   176  	return syscall.Kill(p.ProcessConfig.Process.Pid, syscall.Signal(sig))
   177  }
   178  
   179  func (d *driver) Pause(c *execdriver.Command) error {
   180  	active := d.activeContainers[c.ID]
   181  	if active == nil {
   182  		return fmt.Errorf("active container for %s does not exist", c.ID)
   183  	}
   184  	active.container.Cgroups.Freezer = "FROZEN"
   185  	if systemd.UseSystemd() {
   186  		return systemd.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer)
   187  	}
   188  	return fs.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer)
   189  }
   190  
   191  func (d *driver) Unpause(c *execdriver.Command) error {
   192  	active := d.activeContainers[c.ID]
   193  	if active == nil {
   194  		return fmt.Errorf("active container for %s does not exist", c.ID)
   195  	}
   196  	active.container.Cgroups.Freezer = "THAWED"
   197  	if systemd.UseSystemd() {
   198  		return systemd.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer)
   199  	}
   200  	return fs.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer)
   201  }
   202  
   203  func (d *driver) Terminate(p *execdriver.Command) error {
   204  	// lets check the start time for the process
   205  	state, err := libcontainer.GetState(filepath.Join(d.root, p.ID))
   206  	if err != nil {
   207  		if !os.IsNotExist(err) {
   208  			return err
   209  		}
   210  		// TODO: Remove this part for version 1.2.0
   211  		// This is added only to ensure smooth upgrades from pre 1.1.0 to 1.1.0
   212  		data, err := ioutil.ReadFile(filepath.Join(d.root, p.ID, "start"))
   213  		if err != nil {
   214  			// if we don't have the data on disk then we can assume the process is gone
   215  			// because this is only removed after we know the process has stopped
   216  			if os.IsNotExist(err) {
   217  				return nil
   218  			}
   219  			return err
   220  		}
   221  		state = &libcontainer.State{InitStartTime: string(data)}
   222  	}
   223  
   224  	currentStartTime, err := system.GetProcessStartTime(p.ProcessConfig.Process.Pid)
   225  	if err != nil {
   226  		return err
   227  	}
   228  
   229  	if state.InitStartTime == currentStartTime {
   230  		err = syscall.Kill(p.ProcessConfig.Process.Pid, 9)
   231  		syscall.Wait4(p.ProcessConfig.Process.Pid, nil, 0, nil)
   232  	}
   233  	d.cleanContainer(p.ID)
   234  
   235  	return err
   236  
   237  }
   238  
   239  func (d *driver) Info(id string) execdriver.Info {
   240  	return &info{
   241  		ID:     id,
   242  		driver: d,
   243  	}
   244  }
   245  
   246  func (d *driver) Name() string {
   247  	return fmt.Sprintf("%s-%s", DriverName, Version)
   248  }
   249  
   250  func (d *driver) GetPidsForContainer(id string) ([]int, error) {
   251  	d.Lock()
   252  	active := d.activeContainers[id]
   253  	d.Unlock()
   254  
   255  	if active == nil {
   256  		return nil, fmt.Errorf("active container for %s does not exist", id)
   257  	}
   258  	c := active.container.Cgroups
   259  
   260  	if systemd.UseSystemd() {
   261  		return systemd.GetPids(c)
   262  	}
   263  	return fs.GetPids(c)
   264  }
   265  
   266  func (d *driver) writeContainerFile(container *libcontainer.Config, id string) error {
   267  	data, err := json.Marshal(container)
   268  	if err != nil {
   269  		return err
   270  	}
   271  	return ioutil.WriteFile(filepath.Join(d.root, id, "container.json"), data, 0655)
   272  }
   273  
   274  func (d *driver) cleanContainer(id string) error {
   275  	d.Lock()
   276  	delete(d.activeContainers, id)
   277  	d.Unlock()
   278  	return os.RemoveAll(filepath.Join(d.root, id, "container.json"))
   279  }
   280  
   281  func (d *driver) createContainerRoot(id string) error {
   282  	return os.MkdirAll(filepath.Join(d.root, id), 0655)
   283  }
   284  
   285  func (d *driver) Clean(id string) error {
   286  	return os.RemoveAll(filepath.Join(d.root, id))
   287  }
   288  
   289  func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) {
   290  	c := d.activeContainers[id]
   291  	state, err := libcontainer.GetState(filepath.Join(d.root, id))
   292  	if err != nil {
   293  		if os.IsNotExist(err) {
   294  			return nil, execdriver.ErrNotRunning
   295  		}
   296  		return nil, err
   297  	}
   298  	now := time.Now()
   299  	stats, err := libcontainer.GetStats(nil, state)
   300  	if err != nil {
   301  		return nil, err
   302  	}
   303  	memoryLimit := c.container.Cgroups.Memory
   304  	// if the container does not have any memory limit specified set the
   305  	// limit to the machines memory
   306  	if memoryLimit == 0 {
   307  		memoryLimit = d.machineMemory
   308  	}
   309  	return &execdriver.ResourceStats{
   310  		Read:           now,
   311  		ContainerStats: stats,
   312  		MemoryLimit:    memoryLimit,
   313  	}, nil
   314  }
   315  
   316  func getEnv(key string, env []string) string {
   317  	for _, pair := range env {
   318  		parts := strings.Split(pair, "=")
   319  		if parts[0] == key {
   320  			return parts[1]
   321  		}
   322  	}
   323  	return ""
   324  }
   325  
   326  type TtyConsole struct {
   327  	MasterPty *os.File
   328  }
   329  
   330  func NewTtyConsole(processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes) (*TtyConsole, error) {
   331  	ptyMaster, console, err := consolepkg.CreateMasterAndConsole()
   332  	if err != nil {
   333  		return nil, err
   334  	}
   335  
   336  	tty := &TtyConsole{
   337  		MasterPty: ptyMaster,
   338  	}
   339  
   340  	if err := tty.AttachPipes(&processConfig.Cmd, pipes); err != nil {
   341  		tty.Close()
   342  		return nil, err
   343  	}
   344  
   345  	processConfig.Console = console
   346  
   347  	return tty, nil
   348  }
   349  
   350  func (t *TtyConsole) Master() *os.File {
   351  	return t.MasterPty
   352  }
   353  
   354  func (t *TtyConsole) Resize(h, w int) error {
   355  	return term.SetWinsize(t.MasterPty.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)})
   356  }
   357  
   358  func (t *TtyConsole) AttachPipes(command *exec.Cmd, pipes *execdriver.Pipes) error {
   359  	go func() {
   360  		if wb, ok := pipes.Stdout.(interface {
   361  			CloseWriters() error
   362  		}); ok {
   363  			defer wb.CloseWriters()
   364  		}
   365  
   366  		io.Copy(pipes.Stdout, t.MasterPty)
   367  	}()
   368  
   369  	if pipes.Stdin != nil {
   370  		go func() {
   371  			io.Copy(t.MasterPty, pipes.Stdin)
   372  
   373  			pipes.Stdin.Close()
   374  		}()
   375  	}
   376  
   377  	return nil
   378  }
   379  
   380  func (t *TtyConsole) Close() error {
   381  	return t.MasterPty.Close()
   382  }