Use fifo for create/start

This removes the use of a signal handler and SIGCONT to signal the init
process to exec the users process.

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
Michael Crosby 2016-06-06 13:15:18 -07:00
parent 8fbe19e020
commit 3aacff695d
12 changed files with 124 additions and 75 deletions

View File

@ -124,8 +124,8 @@ type BaseContainer interface {
Start(process *Process) (err error) Start(process *Process) (err error)
// Run immediatly starts the process inside the conatiner. Returns error if process // Run immediatly starts the process inside the conatiner. Returns error if process
// fails to start. It does not block waiting for a SIGCONT after start returns but // fails to start. It does not block waiting for the exec fifo after start returns but
// sends the signal when the process has completed. // opens the fifo after start returns.
// //
// errors: // errors:
// ContainerDestroyed - Container no longer exists, // ContainerDestroyed - Container no longer exists,
@ -148,4 +148,10 @@ type BaseContainer interface {
// errors: // errors:
// SystemError - System error. // SystemError - System error.
Signal(s os.Signal) error Signal(s os.Signal) error
// Exec signals the container to exec the users process at the end of the init.
//
// errors:
// SystemError - System error.
Exec() error
} }

View File

@ -29,10 +29,6 @@ import (
const stdioFdCount = 3 const stdioFdCount = 3
// InitContinueSignal is used to signal the container init process to
// start the users specified process after the container create has finished.
const InitContinueSignal = syscall.SIGCONT
type linuxContainer struct { type linuxContainer struct {
id string id string
root string root string
@ -195,16 +191,39 @@ func (c *linuxContainer) Run(process *Process) error {
if err != nil { if err != nil {
return err return err
} }
isInit := status == Stopped if err := c.start(process, status == Stopped); err != nil {
if err := c.start(process, isInit); err != nil {
return err return err
} }
if isInit { if status == Stopped {
return process.ops.signal(InitContinueSignal) return c.exec()
} }
return nil return nil
} }
func (c *linuxContainer) Exec() error {
c.m.Lock()
defer c.m.Unlock()
return c.exec()
}
func (c *linuxContainer) exec() error {
path := filepath.Join(c.root, execFifoFilename)
f, err := os.OpenFile(path, os.O_RDONLY, 0)
if err != nil {
return newSystemErrorWithCause(err, "open exec fifo for reading")
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
return err
}
if len(data) > 0 {
os.Remove(path)
return nil
}
return fmt.Errorf("cannot start an already running container")
}
func (c *linuxContainer) start(process *Process, isInit bool) error { func (c *linuxContainer) start(process *Process, isInit bool) error {
parent, err := c.newParentProcess(process, isInit) parent, err := c.newParentProcess(process, isInit)
if err != nil { if err != nil {
@ -262,17 +281,21 @@ func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProces
if err != nil { if err != nil {
return nil, newSystemErrorWithCause(err, "creating new init pipe") return nil, newSystemErrorWithCause(err, "creating new init pipe")
} }
cmd, err := c.commandTemplate(p, childPipe) rootDir, err := os.Open(c.root)
if err != nil {
return nil, err
}
cmd, err := c.commandTemplate(p, childPipe, rootDir)
if err != nil { if err != nil {
return nil, newSystemErrorWithCause(err, "creating new command template") return nil, newSystemErrorWithCause(err, "creating new command template")
} }
if !doInit { if !doInit {
return c.newSetnsProcess(p, cmd, parentPipe, childPipe) return c.newSetnsProcess(p, cmd, parentPipe, childPipe, rootDir)
} }
return c.newInitProcess(p, cmd, parentPipe, childPipe) return c.newInitProcess(p, cmd, parentPipe, childPipe, rootDir)
} }
func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.Cmd, error) { func (c *linuxContainer) commandTemplate(p *Process, childPipe, rootDir *os.File) (*exec.Cmd, error) {
cmd := &exec.Cmd{ cmd := &exec.Cmd{
Path: c.initPath, Path: c.initPath,
Args: c.initArgs, Args: c.initArgs,
@ -284,8 +307,10 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.
if cmd.SysProcAttr == nil { if cmd.SysProcAttr == nil {
cmd.SysProcAttr = &syscall.SysProcAttr{} cmd.SysProcAttr = &syscall.SysProcAttr{}
} }
cmd.ExtraFiles = append(p.ExtraFiles, childPipe) cmd.ExtraFiles = append(p.ExtraFiles, childPipe, rootDir)
cmd.Env = append(cmd.Env, fmt.Sprintf("_LIBCONTAINER_INITPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-1)) cmd.Env = append(cmd.Env,
fmt.Sprintf("_LIBCONTAINER_INITPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-2),
fmt.Sprintf("_LIBCONTAINER_STATEDIR=%d", stdioFdCount+len(cmd.ExtraFiles)-1))
// NOTE: when running a container with no PID namespace and the parent process spawning the container is // NOTE: when running a container with no PID namespace and the parent process spawning the container is
// PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason // PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason
// even with the parent still running. // even with the parent still running.
@ -295,7 +320,7 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.
return cmd, nil return cmd, nil
} }
func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) (*initProcess, error) { func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe, rootDir *os.File) (*initProcess, error) {
cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initStandard)) cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initStandard))
nsMaps := make(map[configs.NamespaceType]string) nsMaps := make(map[configs.NamespaceType]string)
for _, ns := range c.config.Namespaces { for _, ns := range c.config.Namespaces {
@ -318,10 +343,11 @@ func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, c
process: p, process: p,
bootstrapData: data, bootstrapData: data,
sharePidns: sharePidns, sharePidns: sharePidns,
rootDir: rootDir,
}, nil }, nil
} }
func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) (*setnsProcess, error) { func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe, rootDir *os.File) (*setnsProcess, error) {
cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initSetns)) cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initSetns))
state, err := c.currentState() state, err := c.currentState()
if err != nil { if err != nil {
@ -342,6 +368,7 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe,
config: c.newInitConfig(p), config: c.newInitConfig(p),
process: p, process: p,
bootstrapData: data, bootstrapData: data,
rootDir: rootDir,
}, nil }, nil
} }
@ -360,6 +387,7 @@ func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
AppArmorProfile: c.config.AppArmorProfile, AppArmorProfile: c.config.AppArmorProfile,
ProcessLabel: c.config.ProcessLabel, ProcessLabel: c.config.ProcessLabel,
Rlimits: c.config.Rlimits, Rlimits: c.config.Rlimits,
ExecFifoPath: filepath.Join(c.root, execFifoFilename),
} }
if process.NoNewPrivileges != nil { if process.NoNewPrivileges != nil {
cfg.NoNewPrivileges = *process.NoNewPrivileges cfg.NoNewPrivileges = *process.NoNewPrivileges

View File

@ -7,7 +7,6 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"os/signal"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime/debug" "runtime/debug"
@ -25,6 +24,7 @@ import (
const ( const (
stateFilename = "state.json" stateFilename = "state.json"
execFifoFilename = "exec.fifo"
) )
var ( var (
@ -168,6 +168,9 @@ func (l *LinuxFactory) Create(id string, config *configs.Config) (Container, err
if err := os.MkdirAll(containerRoot, 0700); err != nil { if err := os.MkdirAll(containerRoot, 0700); err != nil {
return nil, newGenericError(err, SystemError) return nil, newGenericError(err, SystemError)
} }
if err := syscall.Mkfifo(filepath.Join(containerRoot, execFifoFilename), 0666); err != nil {
return nil, newGenericError(err, SystemError)
}
c := &linuxContainer{ c := &linuxContainer{
id: id, id: id,
root: containerRoot, root: containerRoot,
@ -220,13 +223,18 @@ func (l *LinuxFactory) Type() string {
// StartInitialization loads a container by opening the pipe fd from the parent to read the configuration and state // StartInitialization loads a container by opening the pipe fd from the parent to read the configuration and state
// This is a low level implementation detail of the reexec and should not be consumed externally // This is a low level implementation detail of the reexec and should not be consumed externally
func (l *LinuxFactory) StartInitialization() (err error) { func (l *LinuxFactory) StartInitialization() (err error) {
// start the signal handler as soon as we can var pipefd, rootfd int
s := make(chan os.Signal, 1) for k, v := range map[string]*int{
signal.Notify(s, InitContinueSignal) "_LIBCONTAINER_INITPIPE": &pipefd,
fdStr := os.Getenv("_LIBCONTAINER_INITPIPE") "_LIBCONTAINER_STATEDIR": &rootfd,
pipefd, err := strconv.Atoi(fdStr) } {
s := os.Getenv(k)
i, err := strconv.Atoi(s)
if err != nil { if err != nil {
return fmt.Errorf("error converting env var _LIBCONTAINER_INITPIPE(%q) to an int: %s", fdStr, err) return fmt.Errorf("unable to convert %s=%s to int", k, s)
}
*v = i
} }
var ( var (
pipe = os.NewFile(uintptr(pipefd), "pipe") pipe = os.NewFile(uintptr(pipefd), "pipe")
@ -235,6 +243,7 @@ func (l *LinuxFactory) StartInitialization() (err error) {
// clear the current process's environment to clean any libcontainer // clear the current process's environment to clean any libcontainer
// specific env vars. // specific env vars.
os.Clearenv() os.Clearenv()
var i initer var i initer
defer func() { defer func() {
// We have an error during the initialization of the container's init, // We have an error during the initialization of the container's init,
@ -253,18 +262,16 @@ func (l *LinuxFactory) StartInitialization() (err error) {
// ensure that this pipe is always closed // ensure that this pipe is always closed
pipe.Close() pipe.Close()
}() }()
defer func() { defer func() {
if e := recover(); e != nil { if e := recover(); e != nil {
err = fmt.Errorf("panic from initialization: %v, %v", e, string(debug.Stack())) err = fmt.Errorf("panic from initialization: %v, %v", e, string(debug.Stack()))
} }
}() }()
i, err = newContainerInit(it, pipe, rootfd)
i, err = newContainerInit(it, pipe)
if err != nil { if err != nil {
return err return err
} }
return i.Init(s) return i.Init()
} }
func (l *LinuxFactory) loadState(root string) (*State, error) { func (l *LinuxFactory) loadState(root string) (*State, error) {

View File

@ -58,13 +58,14 @@ type initConfig struct {
PassedFilesCount int `json:"passed_files_count"` PassedFilesCount int `json:"passed_files_count"`
ContainerId string `json:"containerid"` ContainerId string `json:"containerid"`
Rlimits []configs.Rlimit `json:"rlimits"` Rlimits []configs.Rlimit `json:"rlimits"`
ExecFifoPath string `json:"start_pipe_path"`
} }
type initer interface { type initer interface {
Init(s chan os.Signal) error Init() error
} }
func newContainerInit(t initType, pipe *os.File) (initer, error) { func newContainerInit(t initType, pipe *os.File, stateDirFD int) (initer, error) {
var config *initConfig var config *initConfig
if err := json.NewDecoder(pipe).Decode(&config); err != nil { if err := json.NewDecoder(pipe).Decode(&config); err != nil {
return nil, err return nil, err
@ -82,6 +83,7 @@ func newContainerInit(t initType, pipe *os.File) (initer, error) {
pipe: pipe, pipe: pipe,
parentPid: syscall.Getppid(), parentPid: syscall.Getppid(),
config: config, config: config,
stateDirFD: stateDirFD,
}, nil }, nil
} }
return nil, fmt.Errorf("unknown init type %q", t) return nil, fmt.Errorf("unknown init type %q", t)

View File

@ -42,13 +42,13 @@ func TestMain(m *testing.M) {
logrus.SetOutput(os.Stderr) logrus.SetOutput(os.Stderr)
logrus.SetLevel(logrus.InfoLevel) logrus.SetLevel(logrus.InfoLevel)
factory, err = libcontainer.New(".", libcontainer.Cgroupfs) factory, err = libcontainer.New("/run/libctTests", libcontainer.Cgroupfs)
if err != nil { if err != nil {
logrus.Error(err) logrus.Error(err)
os.Exit(1) os.Exit(1)
} }
if systemd.UseSystemd() { if systemd.UseSystemd() {
systemdFactory, err = libcontainer.New(".", libcontainer.SystemdCgroups) systemdFactory, err = libcontainer.New("/run/libctTests", libcontainer.SystemdCgroups)
if err != nil { if err != nil {
logrus.Error(err) logrus.Error(err)
os.Exit(1) os.Exit(1)

View File

@ -101,8 +101,8 @@ func TestSeccompPermitWriteConditional(t *testing.T) {
Args: []*configs.Arg{ Args: []*configs.Arg{
{ {
Index: 0, Index: 0,
Value: 1, Value: 2,
Op: configs.GreaterThan, Op: configs.EqualTo,
}, },
}, },
}, },
@ -162,8 +162,8 @@ func TestSeccompDenyWriteConditional(t *testing.T) {
Args: []*configs.Arg{ Args: []*configs.Arg{
{ {
Index: 0, Index: 0,
Value: 1, Value: 2,
Op: configs.GreaterThan, Op: configs.EqualTo,
}, },
}, },
}, },

View File

@ -2,6 +2,8 @@ package integration
import ( import (
"bytes" "bytes"
"crypto/md5"
"encoding/hex"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@ -11,6 +13,7 @@ import (
"strings" "strings"
"syscall" "syscall"
"testing" "testing"
"time"
"github.com/opencontainers/runc/libcontainer" "github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/configs" "github.com/opencontainers/runc/libcontainer/configs"
@ -92,7 +95,9 @@ func copyBusybox(dest string) error {
} }
func newContainer(config *configs.Config) (libcontainer.Container, error) { func newContainer(config *configs.Config) (libcontainer.Container, error) {
return newContainerWithName("testCT", config) h := md5.New()
h.Write([]byte(time.Now().String()))
return newContainerWithName(hex.EncodeToString(h.Sum(nil)), config)
} }
func newContainerWithName(name string, config *configs.Config) (libcontainer.Container, error) { func newContainerWithName(name string, config *configs.Config) (libcontainer.Container, error) {

View File

@ -51,6 +51,7 @@ type setnsProcess struct {
fds []string fds []string
process *Process process *Process
bootstrapData io.Reader bootstrapData io.Reader
rootDir *os.File
} }
func (p *setnsProcess) startTime() (string, error) { func (p *setnsProcess) startTime() (string, error) {
@ -69,6 +70,7 @@ func (p *setnsProcess) start() (err error) {
defer p.parentPipe.Close() defer p.parentPipe.Close()
err = p.cmd.Start() err = p.cmd.Start()
p.childPipe.Close() p.childPipe.Close()
p.rootDir.Close()
if err != nil { if err != nil {
return newSystemErrorWithCause(err, "starting setns process") return newSystemErrorWithCause(err, "starting setns process")
} }
@ -186,6 +188,7 @@ type initProcess struct {
process *Process process *Process
bootstrapData io.Reader bootstrapData io.Reader
sharePidns bool sharePidns bool
rootDir *os.File
} }
func (p *initProcess) pid() int { func (p *initProcess) pid() int {
@ -230,6 +233,7 @@ func (p *initProcess) start() error {
err := p.cmd.Start() err := p.cmd.Start()
p.process.ops = p p.process.ops = p
p.childPipe.Close() p.childPipe.Close()
p.rootDir.Close()
if err != nil { if err != nil {
p.process.ops = nil p.process.ops = nil
return newSystemErrorWithCause(err, "starting init process command") return newSystemErrorWithCause(err, "starting init process command")

View File

@ -5,7 +5,6 @@ package libcontainer
import ( import (
"fmt" "fmt"
"os" "os"
"os/signal"
"github.com/opencontainers/runc/libcontainer/apparmor" "github.com/opencontainers/runc/libcontainer/apparmor"
"github.com/opencontainers/runc/libcontainer/keys" "github.com/opencontainers/runc/libcontainer/keys"
@ -24,7 +23,7 @@ func (l *linuxSetnsInit) getSessionRingName() string {
return fmt.Sprintf("_ses.%s", l.config.ContainerId) return fmt.Sprintf("_ses.%s", l.config.ContainerId)
} }
func (l *linuxSetnsInit) Init(s chan os.Signal) error { func (l *linuxSetnsInit) Init() error {
if !l.config.Config.NoNewKeyring { if !l.config.Config.NoNewKeyring {
// do not inherit the parent's session keyring // do not inherit the parent's session keyring
if _, err := keyctl.JoinSessionKeyring(l.getSessionRingName()); err != nil { if _, err := keyctl.JoinSessionKeyring(l.getSessionRingName()); err != nil {
@ -50,7 +49,5 @@ func (l *linuxSetnsInit) Init(s chan os.Signal) error {
if err := label.SetProcessLabel(l.config.ProcessLabel); err != nil { if err := label.SetProcessLabel(l.config.ProcessLabel); err != nil {
return err return err
} }
signal.Stop(s)
close(s)
return system.Execv(l.config.Args[0], l.config.Args[0:], os.Environ()) return system.Execv(l.config.Args[0], l.config.Args[0:], os.Environ())
} }

View File

@ -7,7 +7,6 @@ import (
"io" "io"
"os" "os"
"os/exec" "os/exec"
"os/signal"
"syscall" "syscall"
"github.com/opencontainers/runc/libcontainer/apparmor" "github.com/opencontainers/runc/libcontainer/apparmor"
@ -21,6 +20,7 @@ import (
type linuxStandardInit struct { type linuxStandardInit struct {
pipe io.ReadWriteCloser pipe io.ReadWriteCloser
parentPid int parentPid int
stateDirFD int
config *initConfig config *initConfig
} }
@ -44,7 +44,7 @@ func (l *linuxStandardInit) getSessionRingParams() (string, uint32, uint32) {
// the kernel // the kernel
const PR_SET_NO_NEW_PRIVS = 0x26 const PR_SET_NO_NEW_PRIVS = 0x26
func (l *linuxStandardInit) Init(s chan os.Signal) error { func (l *linuxStandardInit) Init() error {
if !l.config.Config.NoNewKeyring { if !l.config.Config.NoNewKeyring {
ringname, keepperms, newperms := l.getSessionRingParams() ringname, keepperms, newperms := l.getSessionRingParams()
@ -149,23 +149,27 @@ func (l *linuxStandardInit) Init(s chan os.Signal) error {
if syscall.Getppid() != l.parentPid { if syscall.Getppid() != l.parentPid {
return syscall.Kill(syscall.Getpid(), syscall.SIGKILL) return syscall.Kill(syscall.Getpid(), syscall.SIGKILL)
} }
// check for the arg before waiting to make sure it exists and it is returned
// as a create time error.
name, err := exec.LookPath(l.config.Args[0])
if err != nil {
return err
}
// close the pipe to signal that we have completed our init.
l.pipe.Close()
// wait for the fifo to be opened on the other side before
// exec'ing the users process.
fd, err := syscall.Openat(l.stateDirFD, execFifoFilename, os.O_WRONLY|syscall.O_CLOEXEC, 0)
if err != nil {
return err
}
if _, err := syscall.Write(fd, []byte("0")); err != nil {
return err
}
if l.config.Config.Seccomp != nil && l.config.NoNewPrivileges { if l.config.Config.Seccomp != nil && l.config.NoNewPrivileges {
if err := seccomp.InitSeccomp(l.config.Config.Seccomp); err != nil { if err := seccomp.InitSeccomp(l.config.Config.Seccomp); err != nil {
return err return err
} }
} }
// check for the arg before waiting to make sure it exists and it is returned
// as a create time error
name, err := exec.LookPath(l.config.Args[0])
if err != nil {
return err
}
// close the pipe to signal that we have completed our init
l.pipe.Close()
// wait for the signal to exec the users process
<-s
// clean up the signal handler
signal.Stop(s)
close(s)
return syscall.Exec(name, l.config.Args[0:], os.Environ()) return syscall.Exec(name, l.config.Args[0:], os.Environ())
} }

View File

@ -27,7 +27,7 @@ your host.`,
} }
switch status { switch status {
case libcontainer.Created: case libcontainer.Created:
return container.Signal(libcontainer.InitContinueSignal) return container.Exec()
case libcontainer.Stopped: case libcontainer.Stopped:
return fmt.Errorf("cannot start a container that has run and stopped") return fmt.Errorf("cannot start a container that has run and stopped")
case libcontainer.Running: case libcontainer.Running:

View File

@ -229,7 +229,11 @@ func (r *runner) run(config *specs.Process) (int, error) {
return -1, err return -1, err
} }
handler := newSignalHandler(tty, r.enableSubreaper) handler := newSignalHandler(tty, r.enableSubreaper)
if err := r.container.Start(process); err != nil { startFn := r.container.Start
if !r.create {
startFn = r.container.Run
}
if err := startFn(process); err != nil {
r.destroy() r.destroy()
tty.Close() tty.Close()
return -1, err return -1, err
@ -248,14 +252,6 @@ func (r *runner) run(config *specs.Process) (int, error) {
return -1, err return -1, err
} }
} }
if !r.create {
if err := process.Signal(libcontainer.InitContinueSignal); err != nil {
r.terminate(process)
r.destroy()
tty.Close()
return -1, err
}
}
if r.detach || r.create { if r.detach || r.create {
tty.Close() tty.Close()
return 0, nil return 0, nil