This code only works for backends that directly spawn the child via the Command. It will not work for the libvirt backend. So we move this code into the individual backends that need it. Docker-DCO-1.1-Signed-off-by: Alexander Larsson <alexl@redhat.com> (github: alexlarsson) Upstream-commit: 9ad70528b723810d98e77368458408e85eebdfee Component: engine
335 lines
7.0 KiB
Go
335 lines
7.0 KiB
Go
package lxc
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/dotcloud/docker/execdriver"
|
|
"github.com/dotcloud/docker/utils"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
const DriverName = "lxc"
|
|
|
|
func init() {
|
|
execdriver.RegisterInitFunc(DriverName, func(args *execdriver.InitArgs) error {
|
|
if err := setupHostname(args); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := setupNetworking(args); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := setupCapabilities(args); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := setupWorkingDirectory(args); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := changeUser(args); err != nil {
|
|
return err
|
|
}
|
|
|
|
path, err := exec.LookPath(args.Args[0])
|
|
if err != nil {
|
|
log.Printf("Unable to locate %v", args.Args[0])
|
|
os.Exit(127)
|
|
}
|
|
if err := syscall.Exec(path, args.Args, os.Environ()); err != nil {
|
|
return fmt.Errorf("dockerinit unable to execute %s - %s", path, err)
|
|
}
|
|
panic("Unreachable")
|
|
})
|
|
}
|
|
|
|
type driver struct {
|
|
root string // root path for the driver to use
|
|
apparmor bool
|
|
sharedRoot bool
|
|
}
|
|
|
|
func NewDriver(root string, apparmor bool) (*driver, error) {
|
|
// setup unconfined symlink
|
|
if err := linkLxcStart(root); err != nil {
|
|
return nil, err
|
|
}
|
|
return &driver{
|
|
apparmor: apparmor,
|
|
root: root,
|
|
sharedRoot: rootIsShared(),
|
|
}, nil
|
|
}
|
|
|
|
func (d *driver) Name() string {
|
|
version := d.version()
|
|
return fmt.Sprintf("%s-%s", DriverName, version)
|
|
}
|
|
|
|
func (d *driver) Run(c *execdriver.Command, startCallback execdriver.StartCallback) (int, error) {
|
|
configPath, err := d.generateLXCConfig(c)
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
params := []string{
|
|
"lxc-start",
|
|
"-n", c.ID,
|
|
"-f", configPath,
|
|
"--",
|
|
c.InitPath,
|
|
"-driver",
|
|
DriverName,
|
|
}
|
|
|
|
if c.Network != nil {
|
|
params = append(params,
|
|
"-g", c.Network.Gateway,
|
|
"-i", fmt.Sprintf("%s/%d", c.Network.IPAddress, c.Network.IPPrefixLen),
|
|
"-mtu", strconv.Itoa(c.Network.Mtu),
|
|
)
|
|
}
|
|
|
|
if c.User != "" {
|
|
params = append(params, "-u", c.User)
|
|
}
|
|
|
|
if c.Privileged {
|
|
if d.apparmor {
|
|
params[0] = path.Join(d.root, "lxc-start-unconfined")
|
|
|
|
}
|
|
params = append(params, "-privileged")
|
|
}
|
|
|
|
if c.WorkingDir != "" {
|
|
params = append(params, "-w", c.WorkingDir)
|
|
}
|
|
|
|
params = append(params, "--", c.Entrypoint)
|
|
params = append(params, c.Arguments...)
|
|
|
|
if d.sharedRoot {
|
|
// lxc-start really needs / to be non-shared, or all kinds of stuff break
|
|
// when lxc-start unmount things and those unmounts propagate to the main
|
|
// mount namespace.
|
|
// What we really want is to clone into a new namespace and then
|
|
// mount / MS_REC|MS_SLAVE, but since we can't really clone or fork
|
|
// without exec in go we have to do this horrible shell hack...
|
|
shellString :=
|
|
"mount --make-rslave /; exec " +
|
|
utils.ShellQuoteArguments(params)
|
|
|
|
params = []string{
|
|
"unshare", "-m", "--", "/bin/sh", "-c", shellString,
|
|
}
|
|
}
|
|
|
|
var (
|
|
name = params[0]
|
|
arg = params[1:]
|
|
)
|
|
aname, err := exec.LookPath(name)
|
|
if err != nil {
|
|
aname = name
|
|
}
|
|
c.Path = aname
|
|
c.Args = append([]string{name}, arg...)
|
|
|
|
if err := c.Start(); err != nil {
|
|
return -1, err
|
|
}
|
|
|
|
var (
|
|
waitErr error
|
|
waitLock = make(chan struct{})
|
|
)
|
|
go func() {
|
|
if err := c.Wait(); err != nil {
|
|
waitErr = err
|
|
}
|
|
close(waitLock)
|
|
}()
|
|
|
|
// Poll lxc for RUNNING status
|
|
if err := d.waitForStart(c, waitLock); err != nil {
|
|
return -1, err
|
|
}
|
|
|
|
if startCallback != nil {
|
|
startCallback(c)
|
|
}
|
|
|
|
<-waitLock
|
|
|
|
return getExitCode(c), waitErr
|
|
}
|
|
|
|
/// Return the exit code of the process
|
|
// if the process has not exited -1 will be returned
|
|
func getExitCode(c *execdriver.Command) int {
|
|
if c.ProcessState == nil {
|
|
return -1
|
|
}
|
|
return c.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
|
|
}
|
|
|
|
func (d *driver) Kill(c *execdriver.Command, sig int) error {
|
|
return d.kill(c, sig)
|
|
}
|
|
|
|
func (d *driver) Restore(c *execdriver.Command) error {
|
|
for {
|
|
output, err := exec.Command("lxc-info", "-n", c.ID).CombinedOutput()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !strings.Contains(string(output), "RUNNING") {
|
|
return nil
|
|
}
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
func (d *driver) version() string {
|
|
version := ""
|
|
if output, err := exec.Command("lxc-version").CombinedOutput(); err == nil {
|
|
outputStr := string(output)
|
|
if len(strings.SplitN(outputStr, ":", 2)) == 2 {
|
|
version = strings.TrimSpace(strings.SplitN(outputStr, ":", 2)[1])
|
|
}
|
|
}
|
|
return version
|
|
}
|
|
|
|
func (d *driver) kill(c *execdriver.Command, sig int) error {
|
|
output, err := exec.Command("lxc-kill", "-n", c.ID, strconv.Itoa(sig)).CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("Err: %s Output: %s", err, output)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *driver) waitForStart(c *execdriver.Command, waitLock chan struct{}) error {
|
|
var (
|
|
err error
|
|
output []byte
|
|
)
|
|
// We wait for the container to be fully running.
|
|
// Timeout after 5 seconds. In case of broken pipe, just retry.
|
|
// Note: The container can run and finish correctly before
|
|
// the end of this loop
|
|
for now := time.Now(); time.Since(now) < 5*time.Second; {
|
|
select {
|
|
case <-waitLock:
|
|
// If the process dies while waiting for it, just return
|
|
return nil
|
|
if c.ProcessState != nil && c.ProcessState.Exited() {
|
|
return nil
|
|
}
|
|
default:
|
|
}
|
|
|
|
output, err = d.getInfo(c.ID)
|
|
if err != nil {
|
|
output, err = d.getInfo(c.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if strings.Contains(string(output), "RUNNING") {
|
|
return nil
|
|
}
|
|
time.Sleep(50 * time.Millisecond)
|
|
}
|
|
return execdriver.ErrNotRunning
|
|
}
|
|
|
|
func (d *driver) getInfo(id string) ([]byte, error) {
|
|
return exec.Command("lxc-info", "-s", "-n", id).CombinedOutput()
|
|
}
|
|
|
|
type info struct {
|
|
ID string
|
|
driver *driver
|
|
}
|
|
|
|
func (i *info) IsRunning() bool {
|
|
var running bool
|
|
|
|
output, err := i.driver.getInfo(i.ID)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if strings.Contains(string(output), "RUNNING") {
|
|
running = true
|
|
}
|
|
return running
|
|
}
|
|
|
|
func (d *driver) Info(id string) execdriver.Info {
|
|
return &info{
|
|
ID: id,
|
|
driver: d,
|
|
}
|
|
}
|
|
|
|
func linkLxcStart(root string) error {
|
|
sourcePath, err := exec.LookPath("lxc-start")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
targetPath := path.Join(root, "lxc-start-unconfined")
|
|
|
|
if _, err := os.Lstat(targetPath); err != nil && !os.IsNotExist(err) {
|
|
return err
|
|
} else if err == nil {
|
|
if err := os.Remove(targetPath); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return os.Symlink(sourcePath, targetPath)
|
|
}
|
|
|
|
// TODO: This can be moved to the mountinfo reader in the mount pkg
|
|
func rootIsShared() bool {
|
|
if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil {
|
|
for _, line := range strings.Split(string(data), "\n") {
|
|
cols := strings.Split(line, " ")
|
|
if len(cols) >= 6 && cols[4] == "/" {
|
|
return strings.HasPrefix(cols[6], "shared")
|
|
}
|
|
}
|
|
}
|
|
|
|
// No idea, probably safe to assume so
|
|
return true
|
|
}
|
|
|
|
func (d *driver) generateLXCConfig(c *execdriver.Command) (string, error) {
|
|
root := path.Join(d.root, "containers", c.ID, "config.lxc")
|
|
fo, err := os.Create(root)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer fo.Close()
|
|
|
|
if err := LxcTemplateCompiled.Execute(fo, struct {
|
|
*execdriver.Command
|
|
AppArmor bool
|
|
}{
|
|
Command: c,
|
|
AppArmor: d.apparmor,
|
|
}); err != nil {
|
|
return "", err
|
|
}
|
|
return root, nil
|
|
}
|