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