Add separate console socket
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
parent
697cb97cb7
commit
00a0ecf554
|
@ -7,6 +7,12 @@ import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ConsoleFromFile(f *os.File) Console {
|
||||||
|
return &linuxConsole{
|
||||||
|
master: f,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// newConsole returns an initialized console that can be used within a container by copying bytes
|
// newConsole returns an initialized console that can be used within a container by copying bytes
|
||||||
// from the master side to the slave that is attached as the tty for the container's init process.
|
// from the master side to the slave that is attached as the tty for the container's init process.
|
||||||
func newConsole() (Console, error) {
|
func newConsole() (Console, error) {
|
||||||
|
|
|
@ -335,7 +335,7 @@ func (c *linuxContainer) deleteExecFifo() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProcess, error) {
|
func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProcess, error) {
|
||||||
parentPipe, childPipe, err := newPipe()
|
parentPipe, childPipe, err := utils.NewSockPair("init")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, newSystemErrorWithCause(err, "creating new init pipe")
|
return nil, newSystemErrorWithCause(err, "creating new init pipe")
|
||||||
}
|
}
|
||||||
|
@ -370,9 +370,17 @@ 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(cmd.ExtraFiles, p.ExtraFiles...)
|
||||||
|
if p.ConsoleSocket != nil {
|
||||||
|
cmd.ExtraFiles = append(cmd.ExtraFiles, p.ConsoleSocket)
|
||||||
|
cmd.Env = append(cmd.Env,
|
||||||
|
fmt.Sprintf("_LIBCONTAINER_CONSOLE=%d", stdioFdCount+len(cmd.ExtraFiles)-1),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
cmd.ExtraFiles = append(cmd.ExtraFiles, childPipe)
|
||||||
cmd.Env = append(cmd.Env,
|
cmd.Env = append(cmd.Env,
|
||||||
fmt.Sprintf("_LIBCONTAINER_INITPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-1))
|
fmt.Sprintf("_LIBCONTAINER_INITPIPE=%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.
|
||||||
|
@ -395,7 +403,6 @@ func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, c
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
p.consoleChan = make(chan *os.File, 1)
|
|
||||||
return &initProcess{
|
return &initProcess{
|
||||||
cmd: cmd,
|
cmd: cmd,
|
||||||
childPipe: childPipe,
|
childPipe: childPipe,
|
||||||
|
@ -422,8 +429,6 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// TODO: set on container for process management
|
|
||||||
p.consoleChan = make(chan *os.File, 1)
|
|
||||||
return &setnsProcess{
|
return &setnsProcess{
|
||||||
cmd: cmd,
|
cmd: cmd,
|
||||||
cgroupPaths: c.cgroupManager.GetPaths(),
|
cgroupPaths: c.cgroupManager.GetPaths(),
|
||||||
|
@ -463,28 +468,10 @@ func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
|
||||||
if len(process.Rlimits) > 0 {
|
if len(process.Rlimits) > 0 {
|
||||||
cfg.Rlimits = process.Rlimits
|
cfg.Rlimits = process.Rlimits
|
||||||
}
|
}
|
||||||
/*
|
cfg.CreateConsole = process.ConsoleSocket != nil
|
||||||
* TODO: This should not be automatically computed. We should implement
|
|
||||||
* this as a field in libcontainer.Process, and then we only dup the
|
|
||||||
* new console over the file descriptors which were not explicitly
|
|
||||||
* set with process.Std{in,out,err}. The reason I've left this as-is
|
|
||||||
* is because the GetConsole() interface is new, there's no need to
|
|
||||||
* polish this interface right now.
|
|
||||||
*/
|
|
||||||
if process.Stdin == nil && process.Stdout == nil && process.Stderr == nil {
|
|
||||||
cfg.CreateConsole = true
|
|
||||||
}
|
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPipe() (parent *os.File, child *os.File, err error) {
|
|
||||||
fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return os.NewFile(uintptr(fds[1]), "parent"), os.NewFile(uintptr(fds[0]), "child"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *linuxContainer) Destroy() error {
|
func (c *linuxContainer) Destroy() error {
|
||||||
c.m.Lock()
|
c.m.Lock()
|
||||||
defer c.m.Unlock()
|
defer c.m.Unlock()
|
||||||
|
|
|
@ -222,8 +222,10 @@ func (l *LinuxFactory) Type() string {
|
||||||
func (l *LinuxFactory) StartInitialization() (err error) {
|
func (l *LinuxFactory) StartInitialization() (err error) {
|
||||||
var (
|
var (
|
||||||
pipefd, rootfd int
|
pipefd, rootfd int
|
||||||
|
consoleSocket *os.File
|
||||||
envInitPipe = os.Getenv("_LIBCONTAINER_INITPIPE")
|
envInitPipe = os.Getenv("_LIBCONTAINER_INITPIPE")
|
||||||
envStateDir = os.Getenv("_LIBCONTAINER_STATEDIR")
|
envStateDir = os.Getenv("_LIBCONTAINER_STATEDIR")
|
||||||
|
envConsole = os.Getenv("_LIBCONTAINER_CONSOLE")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Get the INITPIPE.
|
// Get the INITPIPE.
|
||||||
|
@ -241,12 +243,20 @@ func (l *LinuxFactory) StartInitialization() (err error) {
|
||||||
// Only init processes have STATEDIR.
|
// Only init processes have STATEDIR.
|
||||||
rootfd = -1
|
rootfd = -1
|
||||||
if it == initStandard {
|
if it == initStandard {
|
||||||
rootfd, err = strconv.Atoi(envStateDir)
|
if rootfd, err = strconv.Atoi(envStateDir); err != nil {
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to convert _LIBCONTAINER_STATEDIR=%s to int: %s", envStateDir, err)
|
return fmt.Errorf("unable to convert _LIBCONTAINER_STATEDIR=%s to int: %s", envStateDir, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if envConsole != "" {
|
||||||
|
console, err := strconv.Atoi(envConsole)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to convert _LIBCONTAINER_CONSOLE=%s to int: %s", envConsole, err)
|
||||||
|
}
|
||||||
|
consoleSocket = os.NewFile(uintptr(console), "console-socket")
|
||||||
|
defer consoleSocket.Close()
|
||||||
|
}
|
||||||
|
|
||||||
// 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()
|
||||||
|
@ -269,7 +279,7 @@ func (l *LinuxFactory) StartInitialization() (err error) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
i, err := newContainerInit(it, pipe, rootfd)
|
i, err := newContainerInit(it, pipe, consoleSocket, rootfd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ type initer interface {
|
||||||
Init() error
|
Init() error
|
||||||
}
|
}
|
||||||
|
|
||||||
func newContainerInit(t initType, pipe *os.File, stateDirFD int) (initer, error) {
|
func newContainerInit(t initType, pipe *os.File, consoleSocket *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
|
||||||
|
@ -77,15 +77,17 @@ func newContainerInit(t initType, pipe *os.File, stateDirFD int) (initer, error)
|
||||||
switch t {
|
switch t {
|
||||||
case initSetns:
|
case initSetns:
|
||||||
return &linuxSetnsInit{
|
return &linuxSetnsInit{
|
||||||
pipe: pipe,
|
pipe: pipe,
|
||||||
config: config,
|
consoleSocket: consoleSocket,
|
||||||
|
config: config,
|
||||||
}, nil
|
}, nil
|
||||||
case initStandard:
|
case initStandard:
|
||||||
return &linuxStandardInit{
|
return &linuxStandardInit{
|
||||||
pipe: pipe,
|
pipe: pipe,
|
||||||
parentPid: syscall.Getppid(),
|
consoleSocket: consoleSocket,
|
||||||
config: config,
|
parentPid: syscall.Getppid(),
|
||||||
stateDirFD: stateDirFD,
|
config: config,
|
||||||
|
stateDirFD: stateDirFD,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("unknown init type %q", t)
|
return nil, fmt.Errorf("unknown init type %q", t)
|
||||||
|
@ -155,7 +157,8 @@ func finalizeNamespace(config *initConfig) error {
|
||||||
// consoles are scoped to a container properly (see runc#814 and the many
|
// consoles are scoped to a container properly (see runc#814 and the many
|
||||||
// issues related to that). This has to be run *after* we've pivoted to the new
|
// issues related to that). This has to be run *after* we've pivoted to the new
|
||||||
// rootfs (and the users' configuration is entirely set up).
|
// rootfs (and the users' configuration is entirely set up).
|
||||||
func setupConsole(pipe *os.File, config *initConfig, mount bool) error {
|
func setupConsole(socket *os.File, config *initConfig, mount bool) error {
|
||||||
|
defer socket.Close()
|
||||||
// At this point, /dev/ptmx points to something that we would expect. We
|
// At this point, /dev/ptmx points to something that we would expect. We
|
||||||
// used to change the owner of the slave path, but since the /dev/pts mount
|
// used to change the owner of the slave path, but since the /dev/pts mount
|
||||||
// can have gid=X set (at the users' option). So touching the owner of the
|
// can have gid=X set (at the users' option). So touching the owner of the
|
||||||
|
@ -174,37 +177,16 @@ func setupConsole(pipe *os.File, config *initConfig, mount bool) error {
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("failed to cast console to *linuxConsole")
|
return fmt.Errorf("failed to cast console to *linuxConsole")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mount the console inside our rootfs.
|
// Mount the console inside our rootfs.
|
||||||
if mount {
|
if mount {
|
||||||
if err := linuxConsole.mount(); err != nil {
|
if err := linuxConsole.mount(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := writeSync(pipe, procConsole); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to have a two-way synchronisation here. Though it might seem
|
|
||||||
// pointless, it's important to make sure that the sendmsg(2) payload
|
|
||||||
// doesn't get swallowed by an out-of-place read(2) [which happens if the
|
|
||||||
// syscalls get reordered so that sendmsg(2) is before the other side's
|
|
||||||
// read(2) of procConsole].
|
|
||||||
if err := readSync(pipe, procConsoleReq); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// While we can access console.master, using the API is a good idea.
|
// While we can access console.master, using the API is a good idea.
|
||||||
if err := utils.SendFd(pipe, linuxConsole.File()); err != nil {
|
if err := utils.SendFd(socket, linuxConsole.File()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the other side received the fd.
|
|
||||||
if err := readSync(pipe, procConsoleAck); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now, dup over all the things.
|
// Now, dup over all the things.
|
||||||
return linuxConsole.dupStdio()
|
return linuxConsole.dupStdio()
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
|
|
||||||
"github.com/opencontainers/runc/libcontainer"
|
"github.com/opencontainers/runc/libcontainer"
|
||||||
"github.com/opencontainers/runc/libcontainer/configs"
|
"github.com/opencontainers/runc/libcontainer/configs"
|
||||||
|
"github.com/opencontainers/runc/libcontainer/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExecIn(t *testing.T) {
|
func TestExecIn(t *testing.T) {
|
||||||
|
@ -279,9 +280,36 @@ func TestExecInTTY(t *testing.T) {
|
||||||
Args: []string{"ps"},
|
Args: []string{"ps"},
|
||||||
Env: standardEnvironment,
|
Env: standardEnvironment,
|
||||||
}
|
}
|
||||||
|
parent, child, err := utils.NewSockPair("console")
|
||||||
|
if err != nil {
|
||||||
|
ok(t, err)
|
||||||
|
}
|
||||||
|
defer parent.Close()
|
||||||
|
defer child.Close()
|
||||||
|
ps.ConsoleSocket = child
|
||||||
|
type cdata struct {
|
||||||
|
c libcontainer.Console
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
dc := make(chan *cdata, 1)
|
||||||
|
go func() {
|
||||||
|
f, err := utils.RecvFd(parent)
|
||||||
|
if err != nil {
|
||||||
|
dc <- &cdata{
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dc <- &cdata{
|
||||||
|
c: libcontainer.ConsoleFromFile(f),
|
||||||
|
}
|
||||||
|
}()
|
||||||
err = container.Run(ps)
|
err = container.Run(ps)
|
||||||
ok(t, err)
|
ok(t, err)
|
||||||
console, err := ps.GetConsole()
|
data := <-dc
|
||||||
|
if data.err != nil {
|
||||||
|
ok(t, data.err)
|
||||||
|
}
|
||||||
|
console := data.c
|
||||||
copy := make(chan struct{})
|
copy := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
io.Copy(&stdout, console)
|
io.Copy(&stdout, console)
|
||||||
|
|
|
@ -47,10 +47,6 @@ type Process struct {
|
||||||
// ExtraFiles specifies additional open files to be inherited by the container
|
// ExtraFiles specifies additional open files to be inherited by the container
|
||||||
ExtraFiles []*os.File
|
ExtraFiles []*os.File
|
||||||
|
|
||||||
// consoleChan provides the masterfd console.
|
|
||||||
// TODO: Make this persistent in Process.
|
|
||||||
consoleChan chan *os.File
|
|
||||||
|
|
||||||
// Capabilities specify the capabilities to keep when executing the process inside the container
|
// Capabilities specify the capabilities to keep when executing the process inside the container
|
||||||
// All capabilities not specified will be dropped from the processes capability mask
|
// All capabilities not specified will be dropped from the processes capability mask
|
||||||
Capabilities *configs.Capabilities
|
Capabilities *configs.Capabilities
|
||||||
|
@ -69,6 +65,9 @@ type Process struct {
|
||||||
// If Rlimits are not set, the container will inherit rlimits from the parent process
|
// If Rlimits are not set, the container will inherit rlimits from the parent process
|
||||||
Rlimits []configs.Rlimit
|
Rlimits []configs.Rlimit
|
||||||
|
|
||||||
|
// ConsoleSocket provides the masterfd console.
|
||||||
|
ConsoleSocket *os.File
|
||||||
|
|
||||||
ops processOperations
|
ops processOperations
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,15 +104,3 @@ type IO struct {
|
||||||
Stdout io.ReadCloser
|
Stdout io.ReadCloser
|
||||||
Stderr io.ReadCloser
|
Stderr io.ReadCloser
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Process) GetConsole() (Console, error) {
|
|
||||||
consoleFd, ok := <-p.consoleChan
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("failed to get console from process")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Fix this so that it used the console API.
|
|
||||||
return &linuxConsole{
|
|
||||||
master: consoleFd,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -100,25 +100,6 @@ func (p *setnsProcess) start() (err error) {
|
||||||
|
|
||||||
ierr := parseSync(p.parentPipe, func(sync *syncT) error {
|
ierr := parseSync(p.parentPipe, func(sync *syncT) error {
|
||||||
switch sync.Type {
|
switch sync.Type {
|
||||||
case procConsole:
|
|
||||||
if err := writeSync(p.parentPipe, procConsoleReq); err != nil {
|
|
||||||
return newSystemErrorWithCause(err, "writing syncT 'request fd'")
|
|
||||||
}
|
|
||||||
|
|
||||||
masterFile, err := utils.RecvFd(p.parentPipe)
|
|
||||||
if err != nil {
|
|
||||||
return newSystemErrorWithCause(err, "getting master pty from child pipe")
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.process.consoleChan == nil {
|
|
||||||
// TODO: Don't panic here, do something more sane.
|
|
||||||
panic("consoleChan is nil")
|
|
||||||
}
|
|
||||||
p.process.consoleChan <- masterFile
|
|
||||||
|
|
||||||
if err := writeSync(p.parentPipe, procConsoleAck); err != nil {
|
|
||||||
return newSystemErrorWithCause(err, "writing syncT 'ack fd'")
|
|
||||||
}
|
|
||||||
case procReady:
|
case procReady:
|
||||||
// This shouldn't happen.
|
// This shouldn't happen.
|
||||||
panic("unexpected procReady in setns")
|
panic("unexpected procReady in setns")
|
||||||
|
@ -128,7 +109,6 @@ func (p *setnsProcess) start() (err error) {
|
||||||
default:
|
default:
|
||||||
return newSystemError(fmt.Errorf("invalid JSON payload from child"))
|
return newSystemError(fmt.Errorf("invalid JSON payload from child"))
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := syscall.Shutdown(int(p.parentPipe.Fd()), syscall.SHUT_WR); err != nil {
|
if err := syscall.Shutdown(int(p.parentPipe.Fd()), syscall.SHUT_WR); err != nil {
|
||||||
|
@ -264,7 +244,7 @@ func (p *initProcess) start() error {
|
||||||
return newSystemErrorWithCause(err, "starting init process command")
|
return newSystemErrorWithCause(err, "starting init process command")
|
||||||
}
|
}
|
||||||
if _, err := io.Copy(p.parentPipe, p.bootstrapData); err != nil {
|
if _, err := io.Copy(p.parentPipe, p.bootstrapData); err != nil {
|
||||||
return err
|
return newSystemErrorWithCause(err, "copying bootstrap data to pipe")
|
||||||
}
|
}
|
||||||
if err := p.execSetns(); err != nil {
|
if err := p.execSetns(); err != nil {
|
||||||
return newSystemErrorWithCause(err, "running exec setns process for init")
|
return newSystemErrorWithCause(err, "running exec setns process for init")
|
||||||
|
@ -301,25 +281,6 @@ func (p *initProcess) start() error {
|
||||||
|
|
||||||
ierr := parseSync(p.parentPipe, func(sync *syncT) error {
|
ierr := parseSync(p.parentPipe, func(sync *syncT) error {
|
||||||
switch sync.Type {
|
switch sync.Type {
|
||||||
case procConsole:
|
|
||||||
if err := writeSync(p.parentPipe, procConsoleReq); err != nil {
|
|
||||||
return newSystemErrorWithCause(err, "writing syncT 'request fd'")
|
|
||||||
}
|
|
||||||
|
|
||||||
masterFile, err := utils.RecvFd(p.parentPipe)
|
|
||||||
if err != nil {
|
|
||||||
return newSystemErrorWithCause(err, "getting master pty from child pipe")
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.process.consoleChan == nil {
|
|
||||||
// TODO: Don't panic here, do something more sane.
|
|
||||||
panic("consoleChan is nil")
|
|
||||||
}
|
|
||||||
p.process.consoleChan <- masterFile
|
|
||||||
|
|
||||||
if err := writeSync(p.parentPipe, procConsoleAck); err != nil {
|
|
||||||
return newSystemErrorWithCause(err, "writing syncT 'ack fd'")
|
|
||||||
}
|
|
||||||
case procReady:
|
case procReady:
|
||||||
if err := p.manager.Set(p.config.Config); err != nil {
|
if err := p.manager.Set(p.config.Config); err != nil {
|
||||||
return newSystemErrorWithCause(err, "setting cgroup config for ready process")
|
return newSystemErrorWithCause(err, "setting cgroup config for ready process")
|
||||||
|
|
|
@ -16,8 +16,9 @@ import (
|
||||||
// linuxSetnsInit performs the container's initialization for running a new process
|
// linuxSetnsInit performs the container's initialization for running a new process
|
||||||
// inside an existing container.
|
// inside an existing container.
|
||||||
type linuxSetnsInit struct {
|
type linuxSetnsInit struct {
|
||||||
pipe *os.File
|
pipe *os.File
|
||||||
config *initConfig
|
consoleSocket *os.File
|
||||||
|
config *initConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *linuxSetnsInit) getSessionRingName() string {
|
func (l *linuxSetnsInit) getSessionRingName() string {
|
||||||
|
@ -32,7 +33,7 @@ func (l *linuxSetnsInit) Init() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if l.config.CreateConsole {
|
if l.config.CreateConsole {
|
||||||
if err := setupConsole(l.pipe, l.config, false); err != nil {
|
if err := setupConsole(l.consoleSocket, l.config, false); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := system.Setctty(); err != nil {
|
if err := system.Setctty(); err != nil {
|
||||||
|
|
|
@ -17,10 +17,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type linuxStandardInit struct {
|
type linuxStandardInit struct {
|
||||||
pipe *os.File
|
pipe *os.File
|
||||||
parentPid int
|
consoleSocket *os.File
|
||||||
stateDirFD int
|
parentPid int
|
||||||
config *initConfig
|
stateDirFD int
|
||||||
|
config *initConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *linuxStandardInit) getSessionRingParams() (string, uint32, uint32) {
|
func (l *linuxStandardInit) getSessionRingParams() (string, uint32, uint32) {
|
||||||
|
@ -78,7 +79,7 @@ func (l *linuxStandardInit) Init() error {
|
||||||
// but *after* we've given the user the chance to set up all of the mounts
|
// but *after* we've given the user the chance to set up all of the mounts
|
||||||
// they wanted.
|
// they wanted.
|
||||||
if l.config.CreateConsole {
|
if l.config.CreateConsole {
|
||||||
if err := setupConsole(l.pipe, l.config, true); err != nil {
|
if err := setupConsole(l.consoleSocket, l.config, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := system.Setctty(); err != nil {
|
if err := system.Setctty(); err != nil {
|
||||||
|
|
|
@ -27,14 +27,11 @@ type syncType string
|
||||||
// procReady --> [final setup]
|
// procReady --> [final setup]
|
||||||
// <-- procRun
|
// <-- procRun
|
||||||
const (
|
const (
|
||||||
procError syncType = "procError"
|
procError syncType = "procError"
|
||||||
procReady syncType = "procReady"
|
procReady syncType = "procReady"
|
||||||
procRun syncType = "procRun"
|
procRun syncType = "procRun"
|
||||||
procHooks syncType = "procHooks"
|
procHooks syncType = "procHooks"
|
||||||
procResume syncType = "procResume"
|
procResume syncType = "procResume"
|
||||||
procConsole syncType = "procConsole"
|
|
||||||
procConsoleReq syncType = "procConsoleReq"
|
|
||||||
procConsoleAck syncType = "procConsoleAck"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type syncT struct {
|
type syncT struct {
|
||||||
|
|
|
@ -4,6 +4,7 @@ package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
@ -31,3 +32,12 @@ func CloseExecFrom(minFd int) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewSockPair returns a new unix socket pair
|
||||||
|
func NewSockPair(name string) (parent *os.File, child *os.File, err error) {
|
||||||
|
fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return os.NewFile(uintptr(fds[1]), name+"-p"), os.NewFile(uintptr(fds[0]), name+"-c"), nil
|
||||||
|
}
|
||||||
|
|
|
@ -161,7 +161,7 @@ func restoreContainer(context *cli.Context, spec *specs.Spec, config *configs.Co
|
||||||
defer destroy(container)
|
defer destroy(container)
|
||||||
}
|
}
|
||||||
process := &libcontainer.Process{}
|
process := &libcontainer.Process{}
|
||||||
tty, err := setupIO(process, rootuid, rootgid, false, detach)
|
tty, err := setupIO(process, rootuid, rootgid, false, detach, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
|
36
tty.go
36
tty.go
|
@ -19,6 +19,7 @@ type tty struct {
|
||||||
closers []io.Closer
|
closers []io.Closer
|
||||||
postStart []io.Closer
|
postStart []io.Closer
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
|
consoleC chan error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tty) copyIO(w io.Writer, r io.ReadCloser) {
|
func (t *tty) copyIO(w io.Writer, r io.ReadCloser) {
|
||||||
|
@ -68,37 +69,30 @@ func inheritStdio(process *libcontainer.Process) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tty) recvtty(process *libcontainer.Process, detach bool) error {
|
func (t *tty) recvtty(process *libcontainer.Process, socket *os.File) error {
|
||||||
console, err := process.GetConsole()
|
f, err := utils.RecvFd(socket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
console := libcontainer.ConsoleFromFile(f)
|
||||||
if !detach {
|
go io.Copy(console, os.Stdin)
|
||||||
go io.Copy(console, os.Stdin)
|
t.wg.Add(1)
|
||||||
t.wg.Add(1)
|
go t.copyIO(os.Stdout, console)
|
||||||
go t.copyIO(os.Stdout, console)
|
state, err := term.SetRawTerminal(os.Stdin.Fd())
|
||||||
|
if err != nil {
|
||||||
state, err := term.SetRawTerminal(os.Stdin.Fd())
|
return fmt.Errorf("failed to set the terminal from the stdin: %v", err)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to set the terminal from the stdin: %v", err)
|
|
||||||
}
|
|
||||||
t.state = state
|
|
||||||
}
|
}
|
||||||
|
t.state = state
|
||||||
t.console = console
|
t.console = console
|
||||||
t.closers = []io.Closer{console}
|
t.closers = []io.Closer{console}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tty) sendtty(socket *os.File, ti *libcontainer.TerminalInfo) error {
|
func (t *tty) waitConsole() error {
|
||||||
if t.console == nil {
|
if t.consoleC != nil {
|
||||||
return fmt.Errorf("tty.console not set")
|
return <-t.consoleC
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
// Create a fake file to contain the terminal info.
|
|
||||||
console := os.NewFile(t.console.File().Fd(), ti.String())
|
|
||||||
return utils.SendFd(socket, console)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClosePostStart closes any fds that are provided to the container and dup2'd
|
// ClosePostStart closes any fds that are provided to the container and dup2'd
|
||||||
|
|
123
utils_linux.go
123
utils_linux.go
|
@ -17,6 +17,7 @@ import (
|
||||||
"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
|
"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
|
||||||
"github.com/opencontainers/runc/libcontainer/configs"
|
"github.com/opencontainers/runc/libcontainer/configs"
|
||||||
"github.com/opencontainers/runc/libcontainer/specconv"
|
"github.com/opencontainers/runc/libcontainer/specconv"
|
||||||
|
"github.com/opencontainers/runc/libcontainer/utils"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
@ -110,13 +111,45 @@ func destroy(container libcontainer.Container) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// setupIO modifies the given process config according to the options.
|
// setupIO modifies the given process config according to the options.
|
||||||
func setupIO(process *libcontainer.Process, rootuid, rootgid int, createTTY, detach bool) (*tty, error) {
|
func setupIO(process *libcontainer.Process, rootuid, rootgid int, createTTY, detach bool, sockpath string) (*tty, error) {
|
||||||
// This is entirely handled by recvtty.
|
|
||||||
if createTTY {
|
if createTTY {
|
||||||
process.Stdin = nil
|
process.Stdin = nil
|
||||||
process.Stdout = nil
|
process.Stdout = nil
|
||||||
process.Stderr = nil
|
process.Stderr = nil
|
||||||
return &tty{}, nil
|
t := &tty{}
|
||||||
|
if !detach {
|
||||||
|
parent, child, err := utils.NewSockPair("console")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
process.ConsoleSocket = child
|
||||||
|
t.postStart = append(t.postStart, parent, child)
|
||||||
|
t.consoleC = make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
if err := t.recvtty(process, parent); err != nil {
|
||||||
|
t.consoleC <- err
|
||||||
|
}
|
||||||
|
t.consoleC <- nil
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
// the caller of runc will handle receiving the console master
|
||||||
|
conn, err := net.Dial("unix", sockpath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
uc, ok := conn.(*net.UnixConn)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("casting to UnixConn failed")
|
||||||
|
}
|
||||||
|
t.postStart = append(t.postStart, uc)
|
||||||
|
socket, err := uc.File()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
t.postStart = append(t.postStart, socket)
|
||||||
|
process.ConsoleSocket = socket
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
}
|
}
|
||||||
// when runc will detach the caller provides the stdio to runc via runc's 0,1,2
|
// when runc will detach the caller provides the stdio to runc via runc's 0,1,2
|
||||||
// and the container's process inherits runc's stdio.
|
// and the container's process inherits runc's stdio.
|
||||||
|
@ -190,48 +223,37 @@ func (r *runner) terminalinfo() *libcontainer.TerminalInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *runner) run(config *specs.Process) (int, error) {
|
func (r *runner) run(config *specs.Process) (int, error) {
|
||||||
|
if err := r.checkTerminal(config); err != nil {
|
||||||
|
r.destroy()
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
process, err := newProcess(*config)
|
process, err := newProcess(*config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.destroy()
|
r.destroy()
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.listenFDs) > 0 {
|
if len(r.listenFDs) > 0 {
|
||||||
process.Env = append(process.Env, fmt.Sprintf("LISTEN_FDS=%d", len(r.listenFDs)), "LISTEN_PID=1")
|
process.Env = append(process.Env, fmt.Sprintf("LISTEN_FDS=%d", len(r.listenFDs)), "LISTEN_PID=1")
|
||||||
process.ExtraFiles = append(process.ExtraFiles, r.listenFDs...)
|
process.ExtraFiles = append(process.ExtraFiles, r.listenFDs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
baseFd := 3 + len(process.ExtraFiles)
|
baseFd := 3 + len(process.ExtraFiles)
|
||||||
for i := baseFd; i < baseFd+r.preserveFDs; i++ {
|
for i := baseFd; i < baseFd+r.preserveFDs; i++ {
|
||||||
process.ExtraFiles = append(process.ExtraFiles, os.NewFile(uintptr(i), "PreserveFD:"+strconv.Itoa(i)))
|
process.ExtraFiles = append(process.ExtraFiles, os.NewFile(uintptr(i), "PreserveFD:"+strconv.Itoa(i)))
|
||||||
}
|
}
|
||||||
|
|
||||||
rootuid, err := r.container.Config().HostUID()
|
rootuid, err := r.container.Config().HostUID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.destroy()
|
r.destroy()
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rootgid, err := r.container.Config().HostGID()
|
rootgid, err := r.container.Config().HostGID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.destroy()
|
r.destroy()
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
var (
|
||||||
detach := r.detach || r.create
|
detach = r.detach || r.create
|
||||||
|
startFn = r.container.Start
|
||||||
// Check command-line for sanity.
|
)
|
||||||
if detach && config.Terminal && r.consoleSocket == "" {
|
|
||||||
r.destroy()
|
|
||||||
return -1, fmt.Errorf("cannot allocate tty if runc will detach without setting console socket")
|
|
||||||
}
|
|
||||||
// XXX: Should we change this?
|
|
||||||
if (!detach || !config.Terminal) && r.consoleSocket != "" {
|
|
||||||
r.destroy()
|
|
||||||
return -1, fmt.Errorf("cannot use console socket if runc will not detach or allocate tty")
|
|
||||||
}
|
|
||||||
|
|
||||||
startFn := r.container.Start
|
|
||||||
if !r.create {
|
if !r.create {
|
||||||
startFn = r.container.Run
|
startFn = r.container.Run
|
||||||
}
|
}
|
||||||
|
@ -239,7 +261,7 @@ func (r *runner) run(config *specs.Process) (int, error) {
|
||||||
// with detaching containers, and then we get a tty after the container has
|
// with detaching containers, and then we get a tty after the container has
|
||||||
// started.
|
// started.
|
||||||
handler := newSignalHandler(r.enableSubreaper, r.notifySocket)
|
handler := newSignalHandler(r.enableSubreaper, r.notifySocket)
|
||||||
tty, err := setupIO(process, rootuid, rootgid, config.Terminal, detach)
|
tty, err := setupIO(process, rootuid, rootgid, config.Terminal, detach, r.consoleSocket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.destroy()
|
r.destroy()
|
||||||
return -1, err
|
return -1, err
|
||||||
|
@ -249,46 +271,11 @@ func (r *runner) run(config *specs.Process) (int, error) {
|
||||||
r.destroy()
|
r.destroy()
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
if config.Terminal {
|
if err := tty.waitConsole(); err != nil {
|
||||||
if err = tty.recvtty(process, r.detach || r.create); err != nil {
|
r.terminate(process)
|
||||||
r.terminate(process)
|
r.destroy()
|
||||||
r.destroy()
|
return -1, err
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Terminal && detach {
|
|
||||||
conn, err := net.Dial("unix", r.consoleSocket)
|
|
||||||
if err != nil {
|
|
||||||
r.terminate(process)
|
|
||||||
r.destroy()
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
unixconn, ok := conn.(*net.UnixConn)
|
|
||||||
if !ok {
|
|
||||||
r.terminate(process)
|
|
||||||
r.destroy()
|
|
||||||
return -1, fmt.Errorf("casting to UnixConn failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
socket, err := unixconn.File()
|
|
||||||
if err != nil {
|
|
||||||
r.terminate(process)
|
|
||||||
r.destroy()
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
defer socket.Close()
|
|
||||||
|
|
||||||
err = tty.sendtty(socket, r.terminalinfo())
|
|
||||||
if err != nil {
|
|
||||||
r.terminate(process)
|
|
||||||
r.destroy()
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = tty.ClosePostStart(); err != nil {
|
if err = tty.ClosePostStart(); err != nil {
|
||||||
r.terminate(process)
|
r.terminate(process)
|
||||||
r.destroy()
|
r.destroy()
|
||||||
|
@ -323,6 +310,18 @@ func (r *runner) terminate(p *libcontainer.Process) {
|
||||||
_, _ = p.Wait()
|
_, _ = p.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *runner) checkTerminal(config *specs.Process) error {
|
||||||
|
detach := r.detach || r.create
|
||||||
|
// Check command-line for sanity.
|
||||||
|
if detach && config.Terminal && r.consoleSocket == "" {
|
||||||
|
return fmt.Errorf("cannot allocate tty if runc will detach without setting console socket")
|
||||||
|
}
|
||||||
|
if (!detach || !config.Terminal) && r.consoleSocket != "" {
|
||||||
|
return fmt.Errorf("cannot use console socket if runc will not detach or allocate tty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func validateProcessSpec(spec *specs.Process) error {
|
func validateProcessSpec(spec *specs.Process) error {
|
||||||
if spec.Cwd == "" {
|
if spec.Cwd == "" {
|
||||||
return fmt.Errorf("Cwd property must not be empty")
|
return fmt.Errorf("Cwd property must not be empty")
|
||||||
|
|
Loading…
Reference in New Issue