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