mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
7321067176
This changes the way the exec drivers work by not specifing a -driver flag on reexec. For each of the exec drivers they register their own functions that will be matched aginst the argv 0 on exec and called if they match. This also allows any functionality to be added to docker so that the binary can be reexec'd and any type of function can be called. I moved the flag parsing on docker exec to the specific initializers so that the implementations do not bleed into one another. This also allows for more flexability within reexec initializers to specify their own flags and options. Signed-off-by: Michael Crosby <michael@docker.com>
307 lines
7 KiB
Go
307 lines
7 KiB
Go
// +build linux,cgo
|
|
|
|
package native
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"github.com/docker/docker/daemon/execdriver"
|
|
"github.com/docker/docker/pkg/term"
|
|
"github.com/docker/libcontainer"
|
|
"github.com/docker/libcontainer/apparmor"
|
|
"github.com/docker/libcontainer/cgroups/fs"
|
|
"github.com/docker/libcontainer/cgroups/systemd"
|
|
consolepkg "github.com/docker/libcontainer/console"
|
|
"github.com/docker/libcontainer/namespaces"
|
|
"github.com/docker/libcontainer/system"
|
|
)
|
|
|
|
const (
|
|
DriverName = "native"
|
|
Version = "0.2"
|
|
)
|
|
|
|
type activeContainer struct {
|
|
container *libcontainer.Config
|
|
cmd *exec.Cmd
|
|
}
|
|
|
|
type driver struct {
|
|
root string
|
|
initPath string
|
|
activeContainers map[string]*activeContainer
|
|
sync.Mutex
|
|
}
|
|
|
|
func NewDriver(root, initPath string) (*driver, error) {
|
|
if err := os.MkdirAll(root, 0700); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// native driver root is at docker_root/execdriver/native. Put apparmor at docker_root
|
|
if err := apparmor.InstallDefaultProfile(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &driver{
|
|
root: root,
|
|
initPath: initPath,
|
|
activeContainers: make(map[string]*activeContainer),
|
|
}, nil
|
|
}
|
|
|
|
func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
|
|
// take the Command and populate the libcontainer.Config from it
|
|
container, err := d.createContainer(c)
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
|
|
var term execdriver.Terminal
|
|
|
|
if c.Tty {
|
|
term, err = NewTtyConsole(c, pipes)
|
|
} else {
|
|
term, err = execdriver.NewStdConsole(c, pipes)
|
|
}
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
c.Terminal = term
|
|
|
|
d.Lock()
|
|
d.activeContainers[c.ID] = &activeContainer{
|
|
container: container,
|
|
cmd: &c.Cmd,
|
|
}
|
|
d.Unlock()
|
|
|
|
var (
|
|
dataPath = filepath.Join(d.root, c.ID)
|
|
args = append([]string{c.Entrypoint}, c.Arguments...)
|
|
)
|
|
|
|
if err := d.createContainerRoot(c.ID); err != nil {
|
|
return -1, err
|
|
}
|
|
defer d.removeContainerRoot(c.ID)
|
|
|
|
if err := d.writeContainerFile(container, c.ID); err != nil {
|
|
return -1, err
|
|
}
|
|
|
|
return namespaces.Exec(container, c.Stdin, c.Stdout, c.Stderr, c.Console, c.Rootfs, dataPath, args, func(container *libcontainer.Config, console, rootfs, dataPath, init string, child *os.File, args []string) *exec.Cmd {
|
|
c.Path = d.initPath
|
|
c.Args = append([]string{
|
|
DriverName,
|
|
"-console", console,
|
|
"-pipe", "3",
|
|
"-root", filepath.Join(d.root, c.ID),
|
|
"--",
|
|
}, args...)
|
|
|
|
// set this to nil so that when we set the clone flags anything else is reset
|
|
c.SysProcAttr = &syscall.SysProcAttr{
|
|
Cloneflags: uintptr(namespaces.GetNamespaceFlags(container.Namespaces)),
|
|
}
|
|
c.ExtraFiles = []*os.File{child}
|
|
|
|
c.Env = container.Env
|
|
c.Dir = c.Rootfs
|
|
|
|
return &c.Cmd
|
|
}, func() {
|
|
if startCallback != nil {
|
|
c.ContainerPid = c.Process.Pid
|
|
startCallback(c)
|
|
}
|
|
})
|
|
}
|
|
|
|
func (d *driver) Kill(p *execdriver.Command, sig int) error {
|
|
return syscall.Kill(p.Process.Pid, syscall.Signal(sig))
|
|
}
|
|
|
|
func (d *driver) Pause(c *execdriver.Command) error {
|
|
active := d.activeContainers[c.ID]
|
|
if active == nil {
|
|
return fmt.Errorf("active container for %s does not exist", c.ID)
|
|
}
|
|
active.container.Cgroups.Freezer = "FROZEN"
|
|
if systemd.UseSystemd() {
|
|
return systemd.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer)
|
|
}
|
|
return fs.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer)
|
|
}
|
|
|
|
func (d *driver) Unpause(c *execdriver.Command) error {
|
|
active := d.activeContainers[c.ID]
|
|
if active == nil {
|
|
return fmt.Errorf("active container for %s does not exist", c.ID)
|
|
}
|
|
active.container.Cgroups.Freezer = "THAWED"
|
|
if systemd.UseSystemd() {
|
|
return systemd.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer)
|
|
}
|
|
return fs.Freeze(active.container.Cgroups, active.container.Cgroups.Freezer)
|
|
}
|
|
|
|
func (d *driver) Terminate(p *execdriver.Command) error {
|
|
// lets check the start time for the process
|
|
state, err := libcontainer.GetState(filepath.Join(d.root, p.ID))
|
|
if err != nil {
|
|
if !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
// TODO: Remove this part for version 1.2.0
|
|
// This is added only to ensure smooth upgrades from pre 1.1.0 to 1.1.0
|
|
data, err := ioutil.ReadFile(filepath.Join(d.root, p.ID, "start"))
|
|
if err != nil {
|
|
// if we don't have the data on disk then we can assume the process is gone
|
|
// because this is only removed after we know the process has stopped
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
state = &libcontainer.State{InitStartTime: string(data)}
|
|
}
|
|
|
|
currentStartTime, err := system.GetProcessStartTime(p.Process.Pid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if state.InitStartTime == currentStartTime {
|
|
err = syscall.Kill(p.Process.Pid, 9)
|
|
syscall.Wait4(p.Process.Pid, nil, 0, nil)
|
|
}
|
|
d.removeContainerRoot(p.ID)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
func (d *driver) Info(id string) execdriver.Info {
|
|
return &info{
|
|
ID: id,
|
|
driver: d,
|
|
}
|
|
}
|
|
|
|
func (d *driver) Name() string {
|
|
return fmt.Sprintf("%s-%s", DriverName, Version)
|
|
}
|
|
|
|
func (d *driver) GetPidsForContainer(id string) ([]int, error) {
|
|
d.Lock()
|
|
active := d.activeContainers[id]
|
|
d.Unlock()
|
|
|
|
if active == nil {
|
|
return nil, fmt.Errorf("active container for %s does not exist", id)
|
|
}
|
|
c := active.container.Cgroups
|
|
|
|
if systemd.UseSystemd() {
|
|
return systemd.GetPids(c)
|
|
}
|
|
return fs.GetPids(c)
|
|
}
|
|
|
|
func (d *driver) writeContainerFile(container *libcontainer.Config, id string) error {
|
|
data, err := json.Marshal(container)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return ioutil.WriteFile(filepath.Join(d.root, id, "container.json"), data, 0655)
|
|
}
|
|
|
|
func (d *driver) createContainerRoot(id string) error {
|
|
return os.MkdirAll(filepath.Join(d.root, id), 0655)
|
|
}
|
|
|
|
func (d *driver) removeContainerRoot(id string) error {
|
|
d.Lock()
|
|
delete(d.activeContainers, id)
|
|
d.Unlock()
|
|
|
|
return os.RemoveAll(filepath.Join(d.root, id))
|
|
}
|
|
|
|
func getEnv(key string, env []string) string {
|
|
for _, pair := range env {
|
|
parts := strings.Split(pair, "=")
|
|
if parts[0] == key {
|
|
return parts[1]
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type TtyConsole struct {
|
|
MasterPty *os.File
|
|
}
|
|
|
|
func NewTtyConsole(command *execdriver.Command, pipes *execdriver.Pipes) (*TtyConsole, error) {
|
|
ptyMaster, console, err := consolepkg.CreateMasterAndConsole()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tty := &TtyConsole{
|
|
MasterPty: ptyMaster,
|
|
}
|
|
|
|
if err := tty.AttachPipes(&command.Cmd, pipes); err != nil {
|
|
tty.Close()
|
|
return nil, err
|
|
}
|
|
|
|
command.Console = console
|
|
|
|
return tty, nil
|
|
}
|
|
|
|
func (t *TtyConsole) Master() *os.File {
|
|
return t.MasterPty
|
|
}
|
|
|
|
func (t *TtyConsole) Resize(h, w int) error {
|
|
return term.SetWinsize(t.MasterPty.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)})
|
|
}
|
|
|
|
func (t *TtyConsole) AttachPipes(command *exec.Cmd, pipes *execdriver.Pipes) error {
|
|
go func() {
|
|
if wb, ok := pipes.Stdout.(interface {
|
|
CloseWriters() error
|
|
}); ok {
|
|
defer wb.CloseWriters()
|
|
}
|
|
|
|
io.Copy(pipes.Stdout, t.MasterPty)
|
|
}()
|
|
|
|
if pipes.Stdin != nil {
|
|
go func() {
|
|
io.Copy(t.MasterPty, pipes.Stdin)
|
|
|
|
pipes.Stdin.Close()
|
|
}()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (t *TtyConsole) Close() error {
|
|
return t.MasterPty.Close()
|
|
}
|