github.com/codemac/docker@v1.2.1-0.20150518222241-6a18412d5b9c/daemon/execdriver/lxc/driver.go (about)

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