github.com/jaegerpicker/docker@v0.7.7-0.20150325003727-22dba32b4dab/daemon/execdriver/lxc/driver.go (about)

     1  package lxc
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"os/exec"
    11  	"path"
    12  	"path/filepath"
    13  	"strconv"
    14  	"strings"
    15  	"sync"
    16  	"syscall"
    17  	"time"
    18  
    19  	log "github.com/Sirupsen/logrus"
    20  	"github.com/docker/docker/daemon/execdriver"
    21  	sysinfo "github.com/docker/docker/pkg/system"
    22  	"github.com/docker/docker/pkg/term"
    23  	"github.com/docker/docker/pkg/version"
    24  	"github.com/docker/docker/utils"
    25  	"github.com/docker/libcontainer"
    26  	"github.com/docker/libcontainer/cgroups"
    27  	"github.com/docker/libcontainer/configs"
    28  	"github.com/docker/libcontainer/system"
    29  	"github.com/docker/libcontainer/user"
    30  	"github.com/kr/pty"
    31  )
    32  
    33  const DriverName = "lxc"
    34  
    35  var ErrExec = errors.New("Unsupported: Exec is not supported by the lxc driver")
    36  
    37  type driver struct {
    38  	root             string // root path for the driver to use
    39  	initPath         string
    40  	apparmor         bool
    41  	sharedRoot       bool
    42  	activeContainers map[string]*activeContainer
    43  	machineMemory    int64
    44  	sync.Mutex
    45  }
    46  
    47  type activeContainer struct {
    48  	container *configs.Config
    49  	cmd       *exec.Cmd
    50  }
    51  
    52  func NewDriver(root, initPath string, apparmor bool) (*driver, error) {
    53  	if err := os.MkdirAll(root, 0700); err != nil {
    54  		return nil, err
    55  	}
    56  	// setup unconfined symlink
    57  	if err := linkLxcStart(root); err != nil {
    58  		return nil, err
    59  	}
    60  	meminfo, err := sysinfo.ReadMemInfo()
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	return &driver{
    65  		apparmor:         apparmor,
    66  		root:             root,
    67  		initPath:         initPath,
    68  		sharedRoot:       rootIsShared(),
    69  		activeContainers: make(map[string]*activeContainer),
    70  		machineMemory:    meminfo.MemTotal,
    71  	}, nil
    72  }
    73  
    74  func (d *driver) Name() string {
    75  	version := d.version()
    76  	return fmt.Sprintf("%s-%s", DriverName, version)
    77  }
    78  
    79  func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
    80  	var (
    81  		term     execdriver.Terminal
    82  		err      error
    83  		dataPath = d.containerDir(c.ID)
    84  	)
    85  
    86  	if c.ProcessConfig.Tty {
    87  		term, err = NewTtyConsole(&c.ProcessConfig, pipes)
    88  	} else {
    89  		term, err = execdriver.NewStdConsole(&c.ProcessConfig, pipes)
    90  	}
    91  	c.ProcessConfig.Terminal = term
    92  	container, err := d.createContainer(c)
    93  	if err != nil {
    94  		return execdriver.ExitStatus{ExitCode: -1}, err
    95  	}
    96  	d.Lock()
    97  	d.activeContainers[c.ID] = &activeContainer{
    98  		container: container,
    99  		cmd:       &c.ProcessConfig.Cmd,
   100  	}
   101  	d.Unlock()
   102  
   103  	c.Mounts = append(c.Mounts, execdriver.Mount{
   104  		Source:      d.initPath,
   105  		Destination: c.InitPath,
   106  		Writable:    false,
   107  		Private:     true,
   108  	})
   109  
   110  	if err := d.generateEnvConfig(c); err != nil {
   111  		return execdriver.ExitStatus{ExitCode: -1}, err
   112  	}
   113  	configPath, err := d.generateLXCConfig(c)
   114  	if err != nil {
   115  		return execdriver.ExitStatus{ExitCode: -1}, err
   116  	}
   117  	params := []string{
   118  		"lxc-start",
   119  		"-n", c.ID,
   120  		"-f", configPath,
   121  	}
   122  
   123  	// From lxc>=1.1 the default behavior is to daemonize containers after start
   124  	lxcVersion := version.Version(d.version())
   125  	if lxcVersion.GreaterThanOrEqualTo(version.Version("1.1")) {
   126  		params = append(params, "-F")
   127  	}
   128  
   129  	if c.Network.ContainerID != "" {
   130  		params = append(params,
   131  			"--share-net", c.Network.ContainerID,
   132  		)
   133  	}
   134  	if c.Ipc != nil {
   135  		if c.Ipc.ContainerID != "" {
   136  			params = append(params,
   137  				"--share-ipc", c.Ipc.ContainerID,
   138  			)
   139  		} else if c.Ipc.HostIpc {
   140  			params = append(params,
   141  				"--share-ipc", "1",
   142  			)
   143  		}
   144  	}
   145  
   146  	params = append(params,
   147  		"--",
   148  		c.InitPath,
   149  	)
   150  	if c.Network.Interface != nil {
   151  		params = append(params,
   152  			"-g", c.Network.Interface.Gateway,
   153  			"-i", fmt.Sprintf("%s/%d", c.Network.Interface.IPAddress, c.Network.Interface.IPPrefixLen),
   154  		)
   155  	}
   156  	params = append(params,
   157  		"-mtu", strconv.Itoa(c.Network.Mtu),
   158  	)
   159  
   160  	if c.ProcessConfig.User != "" {
   161  		params = append(params, "-u", c.ProcessConfig.User)
   162  	}
   163  
   164  	if c.ProcessConfig.Privileged {
   165  		if d.apparmor {
   166  			params[0] = path.Join(d.root, "lxc-start-unconfined")
   167  
   168  		}
   169  		params = append(params, "-privileged")
   170  	}
   171  
   172  	if c.WorkingDir != "" {
   173  		params = append(params, "-w", c.WorkingDir)
   174  	}
   175  
   176  	params = append(params, "--", c.ProcessConfig.Entrypoint)
   177  	params = append(params, c.ProcessConfig.Arguments...)
   178  
   179  	if d.sharedRoot {
   180  		// lxc-start really needs / to be non-shared, or all kinds of stuff break
   181  		// when lxc-start unmount things and those unmounts propagate to the main
   182  		// mount namespace.
   183  		// What we really want is to clone into a new namespace and then
   184  		// mount / MS_REC|MS_SLAVE, but since we can't really clone or fork
   185  		// without exec in go we have to do this horrible shell hack...
   186  		shellString :=
   187  			"mount --make-rslave /; exec " +
   188  				utils.ShellQuoteArguments(params)
   189  
   190  		params = []string{
   191  			"unshare", "-m", "--", "/bin/sh", "-c", shellString,
   192  		}
   193  	}
   194  	log.Debugf("lxc params %s", params)
   195  	var (
   196  		name = params[0]
   197  		arg  = params[1:]
   198  	)
   199  	aname, err := exec.LookPath(name)
   200  	if err != nil {
   201  		aname = name
   202  	}
   203  	c.ProcessConfig.Path = aname
   204  	c.ProcessConfig.Args = append([]string{name}, arg...)
   205  
   206  	if err := createDeviceNodes(c.Rootfs, c.AutoCreatedDevices); err != nil {
   207  		return execdriver.ExitStatus{ExitCode: -1}, err
   208  	}
   209  
   210  	if err := c.ProcessConfig.Start(); err != nil {
   211  		return execdriver.ExitStatus{ExitCode: -1}, err
   212  	}
   213  
   214  	var (
   215  		waitErr  error
   216  		waitLock = make(chan struct{})
   217  	)
   218  
   219  	go func() {
   220  		if err := c.ProcessConfig.Wait(); err != nil {
   221  			if _, ok := err.(*exec.ExitError); !ok { // Do not propagate the error if it's simply a status code != 0
   222  				waitErr = err
   223  			}
   224  		}
   225  		close(waitLock)
   226  	}()
   227  
   228  	terminate := func(terr error) (execdriver.ExitStatus, error) {
   229  		if c.ProcessConfig.Process != nil {
   230  			c.ProcessConfig.Process.Kill()
   231  			c.ProcessConfig.Wait()
   232  		}
   233  		return execdriver.ExitStatus{ExitCode: -1}, terr
   234  	}
   235  	// Poll lxc for RUNNING status
   236  	pid, err := d.waitForStart(c, waitLock)
   237  	if err != nil {
   238  		return terminate(err)
   239  	}
   240  
   241  	cgroupPaths, err := cgroupPaths(c.ID)
   242  	if err != nil {
   243  		return terminate(err)
   244  	}
   245  
   246  	state := &libcontainer.State{
   247  		InitProcessPid: pid,
   248  		CgroupPaths:    cgroupPaths,
   249  	}
   250  
   251  	f, err := os.Create(filepath.Join(dataPath, "state.json"))
   252  	if err != nil {
   253  		return terminate(err)
   254  	}
   255  	defer f.Close()
   256  
   257  	if err := json.NewEncoder(f).Encode(state); err != nil {
   258  		return terminate(err)
   259  	}
   260  
   261  	c.ContainerPid = pid
   262  
   263  	if startCallback != nil {
   264  		log.Debugf("Invoking startCallback")
   265  		startCallback(&c.ProcessConfig, pid)
   266  	}
   267  
   268  	oomKill := false
   269  	oomKillNotification, err := notifyOnOOM(cgroupPaths)
   270  
   271  	<-waitLock
   272  
   273  	if err == nil {
   274  		_, oomKill = <-oomKillNotification
   275  		log.Debugf("oomKill error %s waitErr %s", oomKill, waitErr)
   276  	} else {
   277  		log.Warnf("Your kernel does not support OOM notifications: %s", err)
   278  	}
   279  
   280  	// check oom error
   281  	exitCode := getExitCode(c)
   282  	if oomKill {
   283  		exitCode = 137
   284  	}
   285  	return execdriver.ExitStatus{ExitCode: exitCode, OOMKilled: oomKill}, waitErr
   286  }
   287  
   288  // copy from libcontainer
   289  func notifyOnOOM(paths map[string]string) (<-chan struct{}, error) {
   290  	dir := paths["memory"]
   291  	if dir == "" {
   292  		return nil, fmt.Errorf("There is no path for %q in state", "memory")
   293  	}
   294  	oomControl, err := os.Open(filepath.Join(dir, "memory.oom_control"))
   295  	if err != nil {
   296  		return nil, err
   297  	}
   298  	fd, _, syserr := syscall.RawSyscall(syscall.SYS_EVENTFD2, 0, syscall.FD_CLOEXEC, 0)
   299  	if syserr != 0 {
   300  		oomControl.Close()
   301  		return nil, syserr
   302  	}
   303  
   304  	eventfd := os.NewFile(fd, "eventfd")
   305  
   306  	eventControlPath := filepath.Join(dir, "cgroup.event_control")
   307  	data := fmt.Sprintf("%d %d", eventfd.Fd(), oomControl.Fd())
   308  	if err := ioutil.WriteFile(eventControlPath, []byte(data), 0700); err != nil {
   309  		eventfd.Close()
   310  		oomControl.Close()
   311  		return nil, err
   312  	}
   313  	ch := make(chan struct{})
   314  	go func() {
   315  		defer func() {
   316  			close(ch)
   317  			eventfd.Close()
   318  			oomControl.Close()
   319  		}()
   320  		buf := make([]byte, 8)
   321  		for {
   322  			if _, err := eventfd.Read(buf); err != nil {
   323  				return
   324  			}
   325  			// When a cgroup is destroyed, an event is sent to eventfd.
   326  			// So if the control path is gone, return instead of notifying.
   327  			if _, err := os.Lstat(eventControlPath); os.IsNotExist(err) {
   328  				return
   329  			}
   330  			ch <- struct{}{}
   331  		}
   332  	}()
   333  	return ch, nil
   334  }
   335  
   336  // createContainer populates and configures the container type with the
   337  // data provided by the execdriver.Command
   338  func (d *driver) createContainer(c *execdriver.Command) (*configs.Config, error) {
   339  	container := execdriver.InitContainer(c)
   340  	if err := execdriver.SetupCgroups(container, c); err != nil {
   341  		return nil, err
   342  	}
   343  	return container, nil
   344  }
   345  
   346  // Return an map of susbystem -> container cgroup
   347  func cgroupPaths(containerId string) (map[string]string, error) {
   348  	subsystems, err := cgroups.GetAllSubsystems()
   349  	if err != nil {
   350  		return nil, err
   351  	}
   352  	log.Debugf("subsystems: %s", subsystems)
   353  	paths := make(map[string]string)
   354  	for _, subsystem := range subsystems {
   355  		cgroupRoot, cgroupDir, err := findCgroupRootAndDir(subsystem)
   356  		log.Debugf("cgroup path %s %s", cgroupRoot, cgroupDir)
   357  		if err != nil {
   358  			//unsupported subystem
   359  			continue
   360  		}
   361  		path := filepath.Join(cgroupRoot, cgroupDir, "lxc", containerId)
   362  		paths[subsystem] = path
   363  	}
   364  
   365  	return paths, nil
   366  }
   367  
   368  // this is copy from old libcontainer nodes.go
   369  func createDeviceNodes(rootfs string, nodesToCreate []*configs.Device) error {
   370  	oldMask := syscall.Umask(0000)
   371  	defer syscall.Umask(oldMask)
   372  
   373  	for _, node := range nodesToCreate {
   374  		if err := createDeviceNode(rootfs, node); err != nil {
   375  			return err
   376  		}
   377  	}
   378  	return nil
   379  }
   380  
   381  // Creates the device node in the rootfs of the container.
   382  func createDeviceNode(rootfs string, node *configs.Device) error {
   383  	var (
   384  		dest   = filepath.Join(rootfs, node.Path)
   385  		parent = filepath.Dir(dest)
   386  	)
   387  
   388  	if err := os.MkdirAll(parent, 0755); err != nil {
   389  		return err
   390  	}
   391  
   392  	fileMode := node.FileMode
   393  	switch node.Type {
   394  	case 'c':
   395  		fileMode |= syscall.S_IFCHR
   396  	case 'b':
   397  		fileMode |= syscall.S_IFBLK
   398  	default:
   399  		return fmt.Errorf("%c is not a valid device type for device %s", node.Type, node.Path)
   400  	}
   401  
   402  	if err := syscall.Mknod(dest, uint32(fileMode), node.Mkdev()); err != nil && !os.IsExist(err) {
   403  		return fmt.Errorf("mknod %s %s", node.Path, err)
   404  	}
   405  
   406  	if err := syscall.Chown(dest, int(node.Uid), int(node.Gid)); err != nil {
   407  		return fmt.Errorf("chown %s to %d:%d", node.Path, node.Uid, node.Gid)
   408  	}
   409  
   410  	return nil
   411  }
   412  
   413  // setupUser changes the groups, gid, and uid for the user inside the container
   414  // copy from libcontainer, cause not it's private
   415  func setupUser(userSpec string) error {
   416  	// Set up defaults.
   417  	defaultExecUser := user.ExecUser{
   418  		Uid:  syscall.Getuid(),
   419  		Gid:  syscall.Getgid(),
   420  		Home: "/",
   421  	}
   422  	passwdPath, err := user.GetPasswdPath()
   423  	if err != nil {
   424  		return err
   425  	}
   426  	groupPath, err := user.GetGroupPath()
   427  	if err != nil {
   428  		return err
   429  	}
   430  	execUser, err := user.GetExecUserPath(userSpec, &defaultExecUser, passwdPath, groupPath)
   431  	if err != nil {
   432  		return err
   433  	}
   434  	if err := syscall.Setgroups(execUser.Sgids); err != nil {
   435  		return err
   436  	}
   437  	if err := system.Setgid(execUser.Gid); err != nil {
   438  		return err
   439  	}
   440  	if err := system.Setuid(execUser.Uid); err != nil {
   441  		return err
   442  	}
   443  	// if we didn't get HOME already, set it based on the user's HOME
   444  	if envHome := os.Getenv("HOME"); envHome == "" {
   445  		if err := os.Setenv("HOME", execUser.Home); err != nil {
   446  			return err
   447  		}
   448  	}
   449  	return nil
   450  }
   451  
   452  /// Return the exit code of the process
   453  // if the process has not exited -1 will be returned
   454  func getExitCode(c *execdriver.Command) int {
   455  	if c.ProcessConfig.ProcessState == nil {
   456  		return -1
   457  	}
   458  	return c.ProcessConfig.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
   459  }
   460  
   461  func (d *driver) Kill(c *execdriver.Command, sig int) error {
   462  	return KillLxc(c.ID, sig)
   463  }
   464  
   465  func (d *driver) Pause(c *execdriver.Command) error {
   466  	_, err := exec.LookPath("lxc-freeze")
   467  	if err == nil {
   468  		output, errExec := exec.Command("lxc-freeze", "-n", c.ID).CombinedOutput()
   469  		if errExec != nil {
   470  			return fmt.Errorf("Err: %s Output: %s", errExec, output)
   471  		}
   472  	}
   473  
   474  	return err
   475  }
   476  
   477  func (d *driver) Unpause(c *execdriver.Command) error {
   478  	_, err := exec.LookPath("lxc-unfreeze")
   479  	if err == nil {
   480  		output, errExec := exec.Command("lxc-unfreeze", "-n", c.ID).CombinedOutput()
   481  		if errExec != nil {
   482  			return fmt.Errorf("Err: %s Output: %s", errExec, output)
   483  		}
   484  	}
   485  
   486  	return err
   487  }
   488  
   489  func (d *driver) Terminate(c *execdriver.Command) error {
   490  	return KillLxc(c.ID, 9)
   491  }
   492  
   493  func (d *driver) version() string {
   494  	var (
   495  		version string
   496  		output  []byte
   497  		err     error
   498  	)
   499  	if _, errPath := exec.LookPath("lxc-version"); errPath == nil {
   500  		output, err = exec.Command("lxc-version").CombinedOutput()
   501  	} else {
   502  		output, err = exec.Command("lxc-start", "--version").CombinedOutput()
   503  	}
   504  	if err == nil {
   505  		version = strings.TrimSpace(string(output))
   506  		if parts := strings.SplitN(version, ":", 2); len(parts) == 2 {
   507  			version = strings.TrimSpace(parts[1])
   508  		}
   509  	}
   510  	return version
   511  }
   512  
   513  func KillLxc(id string, sig int) error {
   514  	var (
   515  		err    error
   516  		output []byte
   517  	)
   518  	_, err = exec.LookPath("lxc-kill")
   519  	if err == nil {
   520  		output, err = exec.Command("lxc-kill", "-n", id, strconv.Itoa(sig)).CombinedOutput()
   521  	} else {
   522  		output, err = exec.Command("lxc-stop", "-k", "-n", id, strconv.Itoa(sig)).CombinedOutput()
   523  	}
   524  	if err != nil {
   525  		return fmt.Errorf("Err: %s Output: %s", err, output)
   526  	}
   527  	return nil
   528  }
   529  
   530  // wait for the process to start and return the pid for the process
   531  func (d *driver) waitForStart(c *execdriver.Command, waitLock chan struct{}) (int, error) {
   532  	var (
   533  		err    error
   534  		output []byte
   535  	)
   536  	// We wait for the container to be fully running.
   537  	// Timeout after 5 seconds. In case of broken pipe, just retry.
   538  	// Note: The container can run and finish correctly before
   539  	// the end of this loop
   540  	for now := time.Now(); time.Since(now) < 5*time.Second; {
   541  		select {
   542  		case <-waitLock:
   543  			// If the process dies while waiting for it, just return
   544  			return -1, nil
   545  		default:
   546  		}
   547  
   548  		output, err = d.getInfo(c.ID)
   549  		if err == nil {
   550  			info, err := parseLxcInfo(string(output))
   551  			if err != nil {
   552  				return -1, err
   553  			}
   554  			if info.Running {
   555  				return info.Pid, nil
   556  			}
   557  		}
   558  		time.Sleep(50 * time.Millisecond)
   559  	}
   560  	return -1, execdriver.ErrNotRunning
   561  }
   562  
   563  func (d *driver) getInfo(id string) ([]byte, error) {
   564  	return exec.Command("lxc-info", "-n", id).CombinedOutput()
   565  }
   566  
   567  type info struct {
   568  	ID     string
   569  	driver *driver
   570  }
   571  
   572  func (i *info) IsRunning() bool {
   573  	var running bool
   574  
   575  	output, err := i.driver.getInfo(i.ID)
   576  	if err != nil {
   577  		log.Errorf("Error getting info for lxc container %s: %s (%s)", i.ID, err, output)
   578  		return false
   579  	}
   580  	if strings.Contains(string(output), "RUNNING") {
   581  		running = true
   582  	}
   583  	return running
   584  }
   585  
   586  func (d *driver) Info(id string) execdriver.Info {
   587  	return &info{
   588  		ID:     id,
   589  		driver: d,
   590  	}
   591  }
   592  
   593  func findCgroupRootAndDir(subsystem string) (string, string, error) {
   594  	cgroupRoot, err := cgroups.FindCgroupMountpoint(subsystem)
   595  	if err != nil {
   596  		return "", "", err
   597  	}
   598  
   599  	cgroupDir, err := cgroups.GetThisCgroupDir(subsystem)
   600  	if err != nil {
   601  		return "", "", err
   602  	}
   603  	return cgroupRoot, cgroupDir, nil
   604  }
   605  
   606  func (d *driver) GetPidsForContainer(id string) ([]int, error) {
   607  	pids := []int{}
   608  
   609  	// cpu is chosen because it is the only non optional subsystem in cgroups
   610  	subsystem := "cpu"
   611  	cgroupRoot, cgroupDir, err := findCgroupRootAndDir(subsystem)
   612  	if err != nil {
   613  		return pids, err
   614  	}
   615  
   616  	filename := filepath.Join(cgroupRoot, cgroupDir, id, "tasks")
   617  	if _, err := os.Stat(filename); os.IsNotExist(err) {
   618  		// With more recent lxc versions use, cgroup will be in lxc/
   619  		filename = filepath.Join(cgroupRoot, cgroupDir, "lxc", id, "tasks")
   620  	}
   621  
   622  	output, err := ioutil.ReadFile(filename)
   623  	if err != nil {
   624  		return pids, err
   625  	}
   626  	for _, p := range strings.Split(string(output), "\n") {
   627  		if len(p) == 0 {
   628  			continue
   629  		}
   630  		pid, err := strconv.Atoi(p)
   631  		if err != nil {
   632  			return pids, fmt.Errorf("Invalid pid '%s': %s", p, err)
   633  		}
   634  		pids = append(pids, pid)
   635  	}
   636  	return pids, nil
   637  }
   638  
   639  func linkLxcStart(root string) error {
   640  	sourcePath, err := exec.LookPath("lxc-start")
   641  	if err != nil {
   642  		return err
   643  	}
   644  	targetPath := path.Join(root, "lxc-start-unconfined")
   645  
   646  	if _, err := os.Lstat(targetPath); err != nil && !os.IsNotExist(err) {
   647  		return err
   648  	} else if err == nil {
   649  		if err := os.Remove(targetPath); err != nil {
   650  			return err
   651  		}
   652  	}
   653  	return os.Symlink(sourcePath, targetPath)
   654  }
   655  
   656  // TODO: This can be moved to the mountinfo reader in the mount pkg
   657  func rootIsShared() bool {
   658  	if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil {
   659  		for _, line := range strings.Split(string(data), "\n") {
   660  			cols := strings.Split(line, " ")
   661  			if len(cols) >= 6 && cols[4] == "/" {
   662  				return strings.HasPrefix(cols[6], "shared")
   663  			}
   664  		}
   665  	}
   666  
   667  	// No idea, probably safe to assume so
   668  	return true
   669  }
   670  
   671  func (d *driver) containerDir(containerId string) string {
   672  	return path.Join(d.root, "containers", containerId)
   673  }
   674  
   675  func (d *driver) generateLXCConfig(c *execdriver.Command) (string, error) {
   676  	root := path.Join(d.containerDir(c.ID), "config.lxc")
   677  
   678  	fo, err := os.Create(root)
   679  	if err != nil {
   680  		return "", err
   681  	}
   682  	defer fo.Close()
   683  
   684  	if err := LxcTemplateCompiled.Execute(fo, struct {
   685  		*execdriver.Command
   686  		AppArmor bool
   687  	}{
   688  		Command:  c,
   689  		AppArmor: d.apparmor,
   690  	}); err != nil {
   691  		return "", err
   692  	}
   693  
   694  	return root, nil
   695  }
   696  
   697  func (d *driver) generateEnvConfig(c *execdriver.Command) error {
   698  	data, err := json.Marshal(c.ProcessConfig.Env)
   699  	if err != nil {
   700  		return err
   701  	}
   702  	p := path.Join(d.root, "containers", c.ID, "config.env")
   703  	c.Mounts = append(c.Mounts, execdriver.Mount{
   704  		Source:      p,
   705  		Destination: "/.dockerenv",
   706  		Writable:    false,
   707  		Private:     true,
   708  	})
   709  
   710  	return ioutil.WriteFile(p, data, 0600)
   711  }
   712  
   713  // Clean not implemented for lxc
   714  func (d *driver) Clean(id string) error {
   715  	return nil
   716  }
   717  
   718  type TtyConsole struct {
   719  	MasterPty *os.File
   720  	SlavePty  *os.File
   721  }
   722  
   723  func NewTtyConsole(processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes) (*TtyConsole, error) {
   724  	// lxc is special in that we cannot create the master outside of the container without
   725  	// opening the slave because we have nothing to provide to the cmd.  We have to open both then do
   726  	// the crazy setup on command right now instead of passing the console path to lxc and telling it
   727  	// to open up that console.  we save a couple of openfiles in the native driver because we can do
   728  	// this.
   729  	ptyMaster, ptySlave, err := pty.Open()
   730  	if err != nil {
   731  		return nil, err
   732  	}
   733  
   734  	tty := &TtyConsole{
   735  		MasterPty: ptyMaster,
   736  		SlavePty:  ptySlave,
   737  	}
   738  
   739  	if err := tty.AttachPipes(&processConfig.Cmd, pipes); err != nil {
   740  		tty.Close()
   741  		return nil, err
   742  	}
   743  
   744  	processConfig.Console = tty.SlavePty.Name()
   745  
   746  	return tty, nil
   747  }
   748  
   749  func (t *TtyConsole) Master() *os.File {
   750  	return t.MasterPty
   751  }
   752  
   753  func (t *TtyConsole) Resize(h, w int) error {
   754  	return term.SetWinsize(t.MasterPty.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)})
   755  }
   756  
   757  func (t *TtyConsole) AttachPipes(command *exec.Cmd, pipes *execdriver.Pipes) error {
   758  	command.Stdout = t.SlavePty
   759  	command.Stderr = t.SlavePty
   760  
   761  	go func() {
   762  		if wb, ok := pipes.Stdout.(interface {
   763  			CloseWriters() error
   764  		}); ok {
   765  			defer wb.CloseWriters()
   766  		}
   767  
   768  		io.Copy(pipes.Stdout, t.MasterPty)
   769  	}()
   770  
   771  	if pipes.Stdin != nil {
   772  		command.Stdin = t.SlavePty
   773  		command.SysProcAttr.Setctty = true
   774  
   775  		go func() {
   776  			io.Copy(t.MasterPty, pipes.Stdin)
   777  
   778  			pipes.Stdin.Close()
   779  		}()
   780  	}
   781  	return nil
   782  }
   783  
   784  func (t *TtyConsole) Close() error {
   785  	t.SlavePty.Close()
   786  	return t.MasterPty.Close()
   787  }
   788  
   789  func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
   790  	return -1, ErrExec
   791  }
   792  
   793  func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) {
   794  	return execdriver.Stats(d.containerDir(id), d.activeContainers[id].container.Cgroups.Memory, d.machineMemory)
   795  }