Refactor and improve libcontainer and driver

Remove logging for now because it is complicating things
Docker-DCO-1.1-Signed-off-by: Michael Crosby <michael@crosbymichael.com> (github: crosbymichael)
This commit is contained in:
Michael Crosby 2014-02-24 21:11:52 -08:00
parent 0e4d946dc4
commit 6daf56799f
11 changed files with 84 additions and 116 deletions

View file

@ -6,6 +6,9 @@ import (
"github.com/dotcloud/docker/pkg/libcontainer/utils"
)
// Veth is a network strategy that uses a bridge and creates
// a veth pair, one that stays outside on the host and the other
// is placed inside the container's namespace
type Veth struct {
}

View file

@ -8,8 +8,11 @@ import (
"syscall"
)
// CommandFactory takes the container's configuration and options passed by the
// parent processes and creates an *exec.Cmd that will be used to fork/exec the
// namespaced init process
type CommandFactory interface {
Create(container *libcontainer.Container, console, logFile string, syncFd uintptr, args []string) *exec.Cmd
Create(container *libcontainer.Container, console string, syncFd uintptr, args []string) *exec.Cmd
}
type DefaultCommandFactory struct{}
@ -17,13 +20,12 @@ type DefaultCommandFactory struct{}
// Create will return an exec.Cmd with the Cloneflags set to the proper namespaces
// defined on the container's configuration and use the current binary as the init with the
// args provided
func (c *DefaultCommandFactory) Create(container *libcontainer.Container, console, logFile string, pipe uintptr, args []string) *exec.Cmd {
func (c *DefaultCommandFactory) Create(container *libcontainer.Container, console string, pipe uintptr, args []string) *exec.Cmd {
// get our binary name so we can always reexec ourself
name := os.Args[0]
command := exec.Command(name, append([]string{
"-console", console,
"-pipe", fmt.Sprint(pipe),
"-log", logFile,
"init"}, args...)...)
command.SysProcAttr = &syscall.SysProcAttr{

View file

@ -28,31 +28,27 @@ func (ns *linuxNs) Exec(container *libcontainer.Container, term Terminal, args [
}
if container.Tty {
ns.logger.Printf("setting up master and console")
master, console, err = CreateMasterAndConsole()
master, console, err = system.CreateMasterAndConsole()
if err != nil {
return -1, err
}
term.SetMaster(master)
}
command := ns.commandFactory.Create(container, console, ns.logFile, syncPipe.child.Fd(), args)
command := ns.commandFactory.Create(container, console, syncPipe.child.Fd(), args)
if err := term.Attach(command); err != nil {
return -1, err
}
defer term.Close()
ns.logger.Printf("staring init")
if err := command.Start(); err != nil {
return -1, err
}
ns.logger.Printf("writing state file")
if err := ns.stateWriter.WritePid(command.Process.Pid); err != nil {
command.Process.Kill()
return -1, err
}
defer func() {
ns.logger.Printf("removing state file")
ns.stateWriter.DeletePid()
}()
@ -68,24 +64,18 @@ func (ns *linuxNs) Exec(container *libcontainer.Container, term Terminal, args [
}
// Sync with child
ns.logger.Printf("closing sync pipes")
syncPipe.Close()
ns.logger.Printf("waiting on process")
if err := command.Wait(); err != nil {
if _, ok := err.(*exec.ExitError); !ok {
return -1, err
}
}
exitCode := command.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
ns.logger.Printf("process ended with exit code %d", exitCode)
return exitCode, nil
return command.ProcessState.Sys().(syscall.WaitStatus).ExitStatus(), nil
}
func (ns *linuxNs) SetupCgroups(container *libcontainer.Container, nspid int) error {
if container.Cgroups != nil {
ns.logger.Printf("setting up cgroups")
if err := container.Cgroups.Apply(nspid); err != nil {
return err
}
@ -95,7 +85,6 @@ func (ns *linuxNs) SetupCgroups(container *libcontainer.Container, nspid int) er
func (ns *linuxNs) InitializeNetworking(container *libcontainer.Container, nspid int, pipe *SyncPipe) error {
if container.Network != nil {
ns.logger.Printf("creating host network configuration type %s", container.Network.Type)
strategy, err := network.GetStrategy(container.Network.Type)
if err != nil {
return err
@ -104,27 +93,9 @@ func (ns *linuxNs) InitializeNetworking(container *libcontainer.Container, nspid
if err != nil {
return err
}
ns.logger.Printf("sending %v as network context", networkContext)
if err := pipe.SendToChild(networkContext); err != nil {
return err
}
}
return nil
}
// CreateMasterAndConsole will open /dev/ptmx on the host and retreive the
// pts name for use as the pty slave inside the container
func CreateMasterAndConsole() (*os.File, string, error) {
master, err := os.OpenFile("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0)
if err != nil {
return nil, "", err
}
console, err := system.Ptsname(master)
if err != nil {
return nil, "", err
}
if err := system.Unlockpt(master); err != nil {
return nil, "", err
}
return master, console, nil
}

View file

@ -18,7 +18,7 @@ func (ns *linuxNs) ExecIn(container *libcontainer.Container, nspid int, args []s
return -1, err
}
}
fds, err := getNsFds(nspid, container)
fds, err := ns.getNsFds(nspid, container)
closeFds := func() {
for _, f := range fds {
system.Closefd(f)
@ -75,13 +75,13 @@ dropAndExec:
if err := capabilities.DropCapabilities(container); err != nil {
return -1, fmt.Errorf("drop capabilities %s", err)
}
if err := system.Exec(args[0], args[0:], container.Env); err != nil {
if err := system.Execv(args[0], args[0:], container.Env); err != nil {
return -1, err
}
panic("unreachable")
}
func getNsFds(pid int, container *libcontainer.Container) ([]uintptr, error) {
func (ns *linuxNs) getNsFds(pid int, container *libcontainer.Container) ([]uintptr, error) {
fds := make([]uintptr, len(container.Namespaces))
for i, ns := range container.Namespaces {
f, err := os.OpenFile(filepath.Join("/proc/", strconv.Itoa(pid), "ns", namespaceFileMap[ns]), os.O_RDONLY, 0)

View file

@ -7,18 +7,17 @@ import (
"github.com/dotcloud/docker/pkg/libcontainer"
"github.com/dotcloud/docker/pkg/libcontainer/capabilities"
"github.com/dotcloud/docker/pkg/libcontainer/network"
"github.com/dotcloud/docker/pkg/libcontainer/utils"
"github.com/dotcloud/docker/pkg/system"
"github.com/dotcloud/docker/pkg/user"
"os"
"os/exec"
"path/filepath"
"syscall"
)
// Init is the init process that first runs inside a new namespace to setup mounts, users, networking,
// and other options required for the new container.
func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, console string, syncPipe *SyncPipe, args []string) error {
rootfs, err := resolveRootfs(uncleanRootfs)
rootfs, err := utils.ResolveRootfs(uncleanRootfs)
if err != nil {
return err
}
@ -34,7 +33,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol
if console != "" {
// close pipes so that we can replace it with the pty
closeStdPipes()
slave, err := openTerminal(console, syscall.O_RDWR)
slave, err := system.OpenTerminal(console, syscall.O_RDWR)
if err != nil {
return fmt.Errorf("open terminal %s", err)
}
@ -50,6 +49,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol
return fmt.Errorf("setctty %s", err)
}
}
if err := system.ParentDeathSignal(); err != nil {
return fmt.Errorf("parent deth signal %s", err)
}
@ -73,18 +73,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol
return fmt.Errorf("chdir to %s %s", container.WorkingDir, err)
}
}
return execArgs(args, container.Env)
}
func execArgs(args []string, env []string) error {
name, err := exec.LookPath(args[0])
if err != nil {
return err
}
if err := system.Exec(name, args[0:], env); err != nil {
return fmt.Errorf("exec %s", err)
}
panic("unreachable")
return system.Execv(args[0], args[0:], container.Env)
}
func closeStdPipes() {
@ -93,18 +82,19 @@ func closeStdPipes() {
os.Stderr.Close()
}
// resolveRootfs ensures that the current working directory is
// not a symlink and returns the absolute path to the rootfs
func resolveRootfs(uncleanRootfs string) (string, error) {
rootfs, err := filepath.Abs(uncleanRootfs)
if err != nil {
return "", err
}
return filepath.EvalSymlinks(rootfs)
}
func setupUser(container *libcontainer.Container) error {
if container.User != "" && container.User != "root" {
switch container.User {
case "root", "":
if err := system.Setgroups(nil); err != nil {
return err
}
if err := system.Setresgid(0, 0, 0); err != nil {
return err
}
if err := system.Setresuid(0, 0, 0); err != nil {
return err
}
default:
uid, gid, suppGids, err := user.GetUserGroupSupplementary(container.User, syscall.Getuid(), syscall.Getgid())
if err != nil {
return err
@ -118,16 +108,6 @@ func setupUser(container *libcontainer.Container) error {
if err := system.Setuid(uid); err != nil {
return err
}
} else {
if err := system.Setgroups(nil); err != nil {
return err
}
if err := system.Setresgid(0, 0, 0); err != nil {
return err
}
if err := system.Setresuid(0, 0, 0); err != nil {
return err
}
}
return nil
}
@ -147,16 +127,6 @@ func dupSlave(slave *os.File) error {
return nil
}
// openTerminal is a clone of os.OpenFile without the O_CLOEXEC
// used to open the pty slave inside the container namespace
func openTerminal(name string, flag int) (*os.File, error) {
r, e := syscall.Open(name, flag, 0)
if e != nil {
return nil, &os.PathError{"open", name, e}
}
return os.NewFile(uintptr(r), name), nil
}
// setupVethNetwork uses the Network config if it is not nil to initialize
// the new veth interface inside the container for use by changing the name to eth0
// setting the MTU and IP address along with the default gateway

View file

@ -2,9 +2,10 @@ package nsinit
import (
"github.com/dotcloud/docker/pkg/libcontainer"
"log"
)
// NsInit is an interface with the public facing methods to provide high level
// exec operations on a container
type NsInit interface {
Exec(container *libcontainer.Container, term Terminal, args []string) (int, error)
ExecIn(container *libcontainer.Container, nspid int, args []string) (int, error)
@ -13,17 +14,13 @@ type NsInit interface {
type linuxNs struct {
root string
logFile string
logger *log.Logger
commandFactory CommandFactory
stateWriter StateWriter
}
func NewNsInit(logger *log.Logger, logFile string, command CommandFactory, state StateWriter) NsInit {
func NewNsInit(command CommandFactory, state StateWriter) NsInit {
return &linuxNs{
logger: logger,
commandFactory: command,
stateWriter: state,
logFile: logFile,
}
}

View file

@ -6,7 +6,6 @@ import (
"flag"
"github.com/dotcloud/docker/pkg/libcontainer"
"github.com/dotcloud/docker/pkg/libcontainer/nsinit"
"io"
"io/ioutil"
"log"
"os"
@ -16,7 +15,6 @@ import (
var (
console string
pipeFd int
logFile string
)
var (
@ -26,7 +24,6 @@ var (
func registerFlags() {
flag.StringVar(&console, "console", "", "console (pty slave) path")
flag.StringVar(&logFile, "log", "none", "log options (none, stderr, or a file path)")
flag.IntVar(&pipeFd, "pipe", 0, "sync pipe fd")
flag.Parse()
@ -113,26 +110,5 @@ func readPid() (int, error) {
}
func newNsInit() (nsinit.NsInit, error) {
logger, err := setupLogging()
if err != nil {
return nil, err
}
return nsinit.NewNsInit(logger, logFile, &nsinit.DefaultCommandFactory{}, &nsinit.DefaultStateWriter{}), nil
}
func setupLogging() (logger *log.Logger, err error) {
var writer io.Writer
switch logFile {
case "stderr":
writer = os.Stderr
case "none", "":
writer = ioutil.Discard
default:
if writer, err = os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0755); err != nil {
return
}
}
logger = log.New(writer, "", log.LstdFlags)
return
return nsinit.NewNsInit(&nsinit.DefaultCommandFactory{}, &nsinit.DefaultStateWriter{}), nil
}

View file

@ -7,6 +7,8 @@ import (
"path/filepath"
)
// StateWriter handles writing and deleting the pid file
// on disk
type StateWriter interface {
WritePid(pid int) error
DeletePid() error

View file

@ -4,6 +4,7 @@ import (
"crypto/rand"
"encoding/hex"
"io"
"path/filepath"
)
// GenerateRandomName returns a new name joined with a prefix. This size
@ -15,3 +16,13 @@ func GenerateRandomName(prefix string, size int) (string, error) {
}
return prefix + hex.EncodeToString(id)[:size], nil
}
// ResolveRootfs ensures that the current working directory is
// not a symlink and returns the absolute path to the rootfs
func ResolveRootfs(uncleanRootfs string) (string, error) {
rootfs, err := filepath.Abs(uncleanRootfs)
if err != nil {
return "", err
}
return filepath.EvalSymlinks(rootfs)
}

View file

@ -1,6 +1,7 @@
package system
import (
"os/exec"
"syscall"
)
@ -16,6 +17,14 @@ func Exec(cmd string, args []string, env []string) error {
return syscall.Exec(cmd, args, env)
}
func Execv(cmd string, args []string, env []string) error {
name, err := exec.LookPath(cmd)
if err != nil {
return err
}
return Exec(name, args, env)
}
func Fork() (int, error) {
syscall.ForkLock.Lock()
pid, _, err := syscall.Syscall(syscall.SYS_FORK, 0, 0, 0)

View file

@ -24,8 +24,35 @@ func Ptsname(f *os.File) (string, error) {
return fmt.Sprintf("/dev/pts/%d", n), nil
}
// CreateMasterAndConsole will open /dev/ptmx on the host and retreive the
// pts name for use as the pty slave inside the container
func CreateMasterAndConsole() (*os.File, string, error) {
master, err := os.OpenFile("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0)
if err != nil {
return nil, "", err
}
console, err := Ptsname(master)
if err != nil {
return nil, "", err
}
if err := Unlockpt(master); err != nil {
return nil, "", err
}
return master, console, nil
}
// OpenPtmx opens /dev/ptmx, i.e. the PTY master.
func OpenPtmx() (*os.File, error) {
// O_NOCTTY and O_CLOEXEC are not present in os package so we use the syscall's one for all.
return os.OpenFile("/dev/ptmx", syscall.O_RDONLY|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0)
}
// OpenTerminal is a clone of os.OpenFile without the O_CLOEXEC
// used to open the pty slave inside the container namespace
func OpenTerminal(name string, flag int) (*os.File, error) {
r, e := syscall.Open(name, flag, 0)
if e != nil {
return nil, &os.PathError{"open", name, e}
}
return os.NewFile(uintptr(r), name), nil
}