github.com/grange74/docker@v1.6.0-rc3/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  	"github.com/docker/docker/pkg/reexec"
    21  	sysinfo "github.com/docker/docker/pkg/system"
    22  	"github.com/docker/docker/pkg/term"
    23  	"github.com/docker/libcontainer"
    24  	"github.com/docker/libcontainer/apparmor"
    25  	"github.com/docker/libcontainer/cgroups/systemd"
    26  	"github.com/docker/libcontainer/configs"
    27  	"github.com/docker/libcontainer/system"
    28  	"github.com/docker/libcontainer/utils"
    29  )
    30  
    31  const (
    32  	DriverName = "native"
    33  	Version    = "0.2"
    34  )
    35  
    36  type driver struct {
    37  	root             string
    38  	initPath         string
    39  	activeContainers map[string]libcontainer.Container
    40  	machineMemory    int64
    41  	factory          libcontainer.Factory
    42  	sync.Mutex
    43  }
    44  
    45  func NewDriver(root, initPath string) (*driver, error) {
    46  	meminfo, err := sysinfo.ReadMemInfo()
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	if err := os.MkdirAll(root, 0700); err != nil {
    52  		return nil, err
    53  	}
    54  	// native driver root is at docker_root/execdriver/native. Put apparmor at docker_root
    55  	if err := apparmor.InstallDefaultProfile(); err != nil {
    56  		return nil, err
    57  	}
    58  	cgm := libcontainer.Cgroupfs
    59  	if systemd.UseSystemd() {
    60  		cgm = libcontainer.SystemdCgroups
    61  	}
    62  
    63  	f, err := libcontainer.New(
    64  		root,
    65  		cgm,
    66  		libcontainer.InitPath(reexec.Self(), DriverName),
    67  	)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	return &driver{
    73  		root:             root,
    74  		initPath:         initPath,
    75  		activeContainers: make(map[string]libcontainer.Container),
    76  		machineMemory:    meminfo.MemTotal,
    77  		factory:          f,
    78  	}, nil
    79  }
    80  
    81  type execOutput struct {
    82  	exitCode int
    83  	err      error
    84  }
    85  
    86  func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
    87  	// take the Command and populate the libcontainer.Config from it
    88  	container, err := d.createContainer(c)
    89  	if err != nil {
    90  		return execdriver.ExitStatus{ExitCode: -1}, err
    91  	}
    92  
    93  	var term execdriver.Terminal
    94  
    95  	p := &libcontainer.Process{
    96  		Args: append([]string{c.ProcessConfig.Entrypoint}, c.ProcessConfig.Arguments...),
    97  		Env:  c.ProcessConfig.Env,
    98  		Cwd:  c.WorkingDir,
    99  		User: c.ProcessConfig.User,
   100  	}
   101  
   102  	if c.ProcessConfig.Tty {
   103  		rootuid, err := container.HostUID()
   104  		if err != nil {
   105  			return execdriver.ExitStatus{ExitCode: -1}, err
   106  		}
   107  		cons, err := p.NewConsole(rootuid)
   108  		if err != nil {
   109  			return execdriver.ExitStatus{ExitCode: -1}, err
   110  		}
   111  		term, err = NewTtyConsole(cons, pipes, rootuid)
   112  	} else {
   113  		p.Stdout = pipes.Stdout
   114  		p.Stderr = pipes.Stderr
   115  		r, w, err := os.Pipe()
   116  		if err != nil {
   117  			return execdriver.ExitStatus{ExitCode: -1}, err
   118  		}
   119  		if pipes.Stdin != nil {
   120  			go func() {
   121  				io.Copy(w, pipes.Stdin)
   122  				w.Close()
   123  			}()
   124  			p.Stdin = r
   125  		}
   126  		term = &execdriver.StdConsole{}
   127  	}
   128  	if err != nil {
   129  		return execdriver.ExitStatus{ExitCode: -1}, err
   130  	}
   131  	c.ProcessConfig.Terminal = term
   132  
   133  	cont, err := d.factory.Create(c.ID, container)
   134  	if err != nil {
   135  		return execdriver.ExitStatus{ExitCode: -1}, err
   136  	}
   137  	d.Lock()
   138  	d.activeContainers[c.ID] = cont
   139  	d.Unlock()
   140  	defer func() {
   141  		cont.Destroy()
   142  		d.cleanContainer(c.ID)
   143  	}()
   144  
   145  	if err := cont.Start(p); err != nil {
   146  		return execdriver.ExitStatus{ExitCode: -1}, err
   147  	}
   148  
   149  	if startCallback != nil {
   150  		pid, err := p.Pid()
   151  		if err != nil {
   152  			p.Signal(os.Kill)
   153  			p.Wait()
   154  			return execdriver.ExitStatus{ExitCode: -1}, err
   155  		}
   156  		startCallback(&c.ProcessConfig, pid)
   157  	}
   158  
   159  	oomKillNotification, err := cont.NotifyOOM()
   160  	if err != nil {
   161  		oomKillNotification = nil
   162  		log.Warnf("Your kernel does not support OOM notifications: %s", err)
   163  	}
   164  	waitF := p.Wait
   165  	if nss := cont.Config().Namespaces; !nss.Contains(configs.NEWPID) {
   166  		// we need such hack for tracking processes with inerited fds,
   167  		// because cmd.Wait() waiting for all streams to be copied
   168  		waitF = waitInPIDHost(p, cont)
   169  	}
   170  	ps, err := waitF()
   171  	if err != nil {
   172  		execErr, ok := err.(*exec.ExitError)
   173  		if !ok {
   174  			return execdriver.ExitStatus{ExitCode: -1}, err
   175  		}
   176  		ps = execErr.ProcessState
   177  	}
   178  	cont.Destroy()
   179  
   180  	_, oomKill := <-oomKillNotification
   181  
   182  	return execdriver.ExitStatus{ExitCode: utils.ExitStatus(ps.Sys().(syscall.WaitStatus)), OOMKilled: oomKill}, nil
   183  }
   184  
   185  func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*os.ProcessState, error) {
   186  	return func() (*os.ProcessState, error) {
   187  		pid, err := p.Pid()
   188  		if err != nil {
   189  			return nil, err
   190  		}
   191  
   192  		processes, err := c.Processes()
   193  
   194  		process, err := os.FindProcess(pid)
   195  		s, err := process.Wait()
   196  		if err != nil {
   197  			execErr, ok := err.(*exec.ExitError)
   198  			if !ok {
   199  				return s, err
   200  			}
   201  			s = execErr.ProcessState
   202  		}
   203  		if err != nil {
   204  			return s, err
   205  		}
   206  
   207  		for _, pid := range processes {
   208  			process, err := os.FindProcess(pid)
   209  			if err != nil {
   210  				log.Errorf("Failed to kill process: %d", pid)
   211  				continue
   212  			}
   213  			process.Kill()
   214  		}
   215  
   216  		p.Wait()
   217  		return s, err
   218  	}
   219  }
   220  
   221  func (d *driver) Kill(c *execdriver.Command, sig int) error {
   222  	active := d.activeContainers[c.ID]
   223  	if active == nil {
   224  		return fmt.Errorf("active container for %s does not exist", c.ID)
   225  	}
   226  	state, err := active.State()
   227  	if err != nil {
   228  		return err
   229  	}
   230  	return syscall.Kill(state.InitProcessPid, syscall.Signal(sig))
   231  }
   232  
   233  func (d *driver) Pause(c *execdriver.Command) error {
   234  	active := d.activeContainers[c.ID]
   235  	if active == nil {
   236  		return fmt.Errorf("active container for %s does not exist", c.ID)
   237  	}
   238  	return active.Pause()
   239  }
   240  
   241  func (d *driver) Unpause(c *execdriver.Command) error {
   242  	active := d.activeContainers[c.ID]
   243  	if active == nil {
   244  		return fmt.Errorf("active container for %s does not exist", c.ID)
   245  	}
   246  	return active.Resume()
   247  }
   248  
   249  func (d *driver) Terminate(c *execdriver.Command) error {
   250  	defer d.cleanContainer(c.ID)
   251  	// lets check the start time for the process
   252  	active := d.activeContainers[c.ID]
   253  	if active == nil {
   254  		return fmt.Errorf("active container for %s does not exist", c.ID)
   255  	}
   256  	state, err := active.State()
   257  	if err != nil {
   258  		return err
   259  	}
   260  	pid := state.InitProcessPid
   261  
   262  	currentStartTime, err := system.GetProcessStartTime(pid)
   263  	if err != nil {
   264  		return err
   265  	}
   266  
   267  	if state.InitProcessStartTime == currentStartTime {
   268  		err = syscall.Kill(pid, 9)
   269  		syscall.Wait4(pid, nil, 0, nil)
   270  	}
   271  
   272  	return err
   273  
   274  }
   275  
   276  func (d *driver) Info(id string) execdriver.Info {
   277  	return &info{
   278  		ID:     id,
   279  		driver: d,
   280  	}
   281  }
   282  
   283  func (d *driver) Name() string {
   284  	return fmt.Sprintf("%s-%s", DriverName, Version)
   285  }
   286  
   287  func (d *driver) GetPidsForContainer(id string) ([]int, error) {
   288  	d.Lock()
   289  	active := d.activeContainers[id]
   290  	d.Unlock()
   291  
   292  	if active == nil {
   293  		return nil, fmt.Errorf("active container for %s does not exist", id)
   294  	}
   295  	return active.Processes()
   296  }
   297  
   298  func (d *driver) writeContainerFile(container *configs.Config, id string) error {
   299  	data, err := json.Marshal(container)
   300  	if err != nil {
   301  		return err
   302  	}
   303  	return ioutil.WriteFile(filepath.Join(d.root, id, "container.json"), data, 0655)
   304  }
   305  
   306  func (d *driver) cleanContainer(id string) error {
   307  	d.Lock()
   308  	delete(d.activeContainers, id)
   309  	d.Unlock()
   310  	return os.RemoveAll(filepath.Join(d.root, id))
   311  }
   312  
   313  func (d *driver) createContainerRoot(id string) error {
   314  	return os.MkdirAll(filepath.Join(d.root, id), 0655)
   315  }
   316  
   317  func (d *driver) Clean(id string) error {
   318  	return os.RemoveAll(filepath.Join(d.root, id))
   319  }
   320  
   321  func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) {
   322  	c := d.activeContainers[id]
   323  	if c == nil {
   324  		return nil, execdriver.ErrNotRunning
   325  	}
   326  	now := time.Now()
   327  	stats, err := c.Stats()
   328  	if err != nil {
   329  		return nil, err
   330  	}
   331  	memoryLimit := c.Config().Cgroups.Memory
   332  	// if the container does not have any memory limit specified set the
   333  	// limit to the machines memory
   334  	if memoryLimit == 0 {
   335  		memoryLimit = d.machineMemory
   336  	}
   337  	return &execdriver.ResourceStats{
   338  		Stats:       stats,
   339  		Read:        now,
   340  		MemoryLimit: memoryLimit,
   341  	}, nil
   342  }
   343  
   344  func getEnv(key string, env []string) string {
   345  	for _, pair := range env {
   346  		parts := strings.Split(pair, "=")
   347  		if parts[0] == key {
   348  			return parts[1]
   349  		}
   350  	}
   351  	return ""
   352  }
   353  
   354  type TtyConsole struct {
   355  	console libcontainer.Console
   356  }
   357  
   358  func NewTtyConsole(console libcontainer.Console, pipes *execdriver.Pipes, rootuid int) (*TtyConsole, error) {
   359  	tty := &TtyConsole{
   360  		console: console,
   361  	}
   362  
   363  	if err := tty.AttachPipes(pipes); err != nil {
   364  		tty.Close()
   365  		return nil, err
   366  	}
   367  
   368  	return tty, nil
   369  }
   370  
   371  func (t *TtyConsole) Master() libcontainer.Console {
   372  	return t.console
   373  }
   374  
   375  func (t *TtyConsole) Resize(h, w int) error {
   376  	return term.SetWinsize(t.console.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)})
   377  }
   378  
   379  func (t *TtyConsole) AttachPipes(pipes *execdriver.Pipes) error {
   380  	go func() {
   381  		if wb, ok := pipes.Stdout.(interface {
   382  			CloseWriters() error
   383  		}); ok {
   384  			defer wb.CloseWriters()
   385  		}
   386  
   387  		io.Copy(pipes.Stdout, t.console)
   388  	}()
   389  
   390  	if pipes.Stdin != nil {
   391  		go func() {
   392  			io.Copy(t.console, pipes.Stdin)
   393  
   394  			pipes.Stdin.Close()
   395  		}()
   396  	}
   397  
   398  	return nil
   399  }
   400  
   401  func (t *TtyConsole) Close() error {
   402  	return t.console.Close()
   403  }