*: console rewrite
This implements {createTTY, detach} and all of the combinations and negations of the two that were previously implemented. There are some valid questions about out-of-OCI-scope topics like !createTTY and how things should be handled (why do we dup the current stdio to the process, and how is that not a security issue). However, these will be dealt with in a separate patchset. In order to allow for late console setup, split setupRootfs into the "preparation" section where all of the mounts are created and the "finalize" section where we pivot_root and set things as ro. In between the two we can set up all of the console mountpoints and symlinks we need. We use two-stage synchronisation to ensures that when the syscalls are reordered in a suboptimal way, an out-of-place read() on the parentPipe will not gobble the ancilliary information. This patch is part of the console rewrite patchset. Signed-off-by: Aleksa Sarai <asarai@suse.de>
This commit is contained in:
parent
4776b4326a
commit
244c9fc426
|
@ -29,11 +29,6 @@ command(s) that get executed on start, edit the args parameter of the spec. See
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: `path to the root of the bundle directory, defaults to the current directory`,
|
Usage: `path to the root of the bundle directory, defaults to the current directory`,
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
|
||||||
Name: "console",
|
|
||||||
Value: "",
|
|
||||||
Usage: "specify the pty slave path for use with the container",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "pid-file",
|
Name: "pid-file",
|
||||||
Value: "",
|
Value: "",
|
||||||
|
|
7
exec.go
7
exec.go
|
@ -26,13 +26,9 @@ Where "<container-id>" is the name for the instance of the container and
|
||||||
EXAMPLE:
|
EXAMPLE:
|
||||||
For example, if the container is configured to run the linux ps command the
|
For example, if the container is configured to run the linux ps command the
|
||||||
following will output a list of processes running in the container:
|
following will output a list of processes running in the container:
|
||||||
|
|
||||||
# runc exec <container-id> ps`,
|
# runc exec <container-id> ps`,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
|
||||||
Name: "console",
|
|
||||||
Usage: "specify the pty slave path for use with the container",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "cwd",
|
Name: "cwd",
|
||||||
Usage: "current working directory in the container",
|
Usage: "current working directory in the container",
|
||||||
|
@ -131,7 +127,6 @@ func execProcess(context *cli.Context) (int, error) {
|
||||||
enableSubreaper: false,
|
enableSubreaper: false,
|
||||||
shouldDestroy: false,
|
shouldDestroy: false,
|
||||||
container: container,
|
container: container,
|
||||||
console: context.String("console"),
|
|
||||||
detach: detach,
|
detach: detach,
|
||||||
pidFile: context.String("pid-file"),
|
pidFile: context.String("pid-file"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,3 +13,6 @@ type Console interface {
|
||||||
// Fd returns the fd for the master of the pty.
|
// Fd returns the fd for the master of the pty.
|
||||||
Fd() uintptr
|
Fd() uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConsoleData represents arbitrary setup data used when setting up console
|
||||||
|
// handling. It is
|
||||||
|
|
|
@ -6,8 +6,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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(uid, gid int) (Console, error) {
|
func newConsole(uid, gid int) (Console, error) {
|
||||||
return nil, errors.New("libcontainer console is not supported on FreeBSD")
|
return nil, errors.New("libcontainer console is not supported on FreeBSD")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,15 @@ package libcontainer
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/opencontainers/runc/libcontainer/label"
|
"github.com/opencontainers/runc/libcontainer/label"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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(uid, gid int) (Console, error) {
|
func newConsole(uid, gid int) (Console, error) {
|
||||||
master, err := os.OpenFile("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0)
|
master, err := os.OpenFile("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -39,14 +38,6 @@ func NewConsole(uid, gid int) (Console, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// newConsoleFromPath is an internal function returning an initialized console for use inside
|
|
||||||
// a container's MNT namespace.
|
|
||||||
func newConsoleFromPath(slavePath string) *linuxConsole {
|
|
||||||
return &linuxConsole{
|
|
||||||
slavePath: slavePath,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// linuxConsole is a linux pseudo TTY for use within a container.
|
// linuxConsole is a linux pseudo TTY for use within a container.
|
||||||
type linuxConsole struct {
|
type linuxConsole struct {
|
||||||
master *os.File
|
master *os.File
|
||||||
|
@ -78,21 +69,20 @@ func (c *linuxConsole) Close() error {
|
||||||
|
|
||||||
// mount initializes the console inside the rootfs mounting with the specified mount label
|
// mount initializes the console inside the rootfs mounting with the specified mount label
|
||||||
// and applying the correct ownership of the console.
|
// and applying the correct ownership of the console.
|
||||||
func (c *linuxConsole) mount(rootfs, mountLabel string) error {
|
func (c *linuxConsole) mount(mountLabel string) error {
|
||||||
oldMask := syscall.Umask(0000)
|
oldMask := syscall.Umask(0000)
|
||||||
defer syscall.Umask(oldMask)
|
defer syscall.Umask(oldMask)
|
||||||
if err := label.SetFileLabel(c.slavePath, mountLabel); err != nil {
|
if err := label.SetFileLabel(c.slavePath, mountLabel); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dest := filepath.Join(rootfs, "/dev/console")
|
f, err := os.Create("/dev/console")
|
||||||
f, err := os.Create(dest)
|
|
||||||
if err != nil && !os.IsExist(err) {
|
if err != nil && !os.IsExist(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if f != nil {
|
if f != nil {
|
||||||
f.Close()
|
f.Close()
|
||||||
}
|
}
|
||||||
return syscall.Mount(c.slavePath, dest, "bind", syscall.MS_BIND, "")
|
return syscall.Mount(c.slavePath, "/dev/console", "bind", syscall.MS_BIND, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// dupStdio opens the slavePath for the console and dups the fds to the current
|
// dupStdio opens the slavePath for the console and dups the fds to the current
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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(uid, gid int) (Console, error) {
|
func newConsole(uid, gid int) (Console, error) {
|
||||||
return nil, errors.New("libcontainer console is not supported on Solaris")
|
return nil, errors.New("libcontainer console is not supported on Solaris")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package libcontainer
|
package libcontainer
|
||||||
|
|
||||||
// NewConsole returns an initialized console that can be used within a container
|
// newConsole returns an initialized console that can be used within a container
|
||||||
func NewConsole(uid, gid int) (Console, error) {
|
func newConsole(uid, gid int) (Console, error) {
|
||||||
return &windowsConsole{}, nil
|
return &windowsConsole{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -342,10 +342,11 @@ func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, sharePidns := nsMaps[configs.NEWPID]
|
_, sharePidns := nsMaps[configs.NEWPID]
|
||||||
data, err := c.bootstrapData(c.config.Namespaces.CloneFlags(), nsMaps, "")
|
data, err := c.bootstrapData(c.config.Namespaces.CloneFlags(), nsMaps)
|
||||||
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,
|
||||||
|
@ -368,11 +369,12 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe,
|
||||||
}
|
}
|
||||||
// for setns process, we dont have to set cloneflags as the process namespaces
|
// for setns process, we dont have to set cloneflags as the process namespaces
|
||||||
// will only be set via setns syscall
|
// will only be set via setns syscall
|
||||||
data, err := c.bootstrapData(0, state.NamespacePaths, p.consolePath)
|
data, err := c.bootstrapData(0, state.NamespacePaths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// TODO: set on container for process management
|
// 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(),
|
||||||
|
@ -393,7 +395,6 @@ func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
|
||||||
User: process.User,
|
User: process.User,
|
||||||
AdditionalGroups: process.AdditionalGroups,
|
AdditionalGroups: process.AdditionalGroups,
|
||||||
Cwd: process.Cwd,
|
Cwd: process.Cwd,
|
||||||
Console: process.consolePath,
|
|
||||||
Capabilities: process.Capabilities,
|
Capabilities: process.Capabilities,
|
||||||
PassedFilesCount: len(process.ExtraFiles),
|
PassedFilesCount: len(process.ExtraFiles),
|
||||||
ContainerId: c.ID(),
|
ContainerId: c.ID(),
|
||||||
|
@ -415,6 +416,17 @@ 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
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1281,7 +1293,7 @@ func encodeIDMapping(idMap []configs.IDMap) ([]byte, error) {
|
||||||
// such as one that uses nsenter package to bootstrap the container's
|
// such as one that uses nsenter package to bootstrap the container's
|
||||||
// init process correctly, i.e. with correct namespaces, uid/gid
|
// init process correctly, i.e. with correct namespaces, uid/gid
|
||||||
// mapping etc.
|
// mapping etc.
|
||||||
func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.NamespaceType]string, consolePath string) (io.Reader, error) {
|
func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.NamespaceType]string) (io.Reader, error) {
|
||||||
// create the netlink message
|
// create the netlink message
|
||||||
r := nl.NewNetlinkRequest(int(InitMsg), 0)
|
r := nl.NewNetlinkRequest(int(InitMsg), 0)
|
||||||
|
|
||||||
|
@ -1291,14 +1303,6 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na
|
||||||
Value: uint32(cloneFlags),
|
Value: uint32(cloneFlags),
|
||||||
})
|
})
|
||||||
|
|
||||||
// write console path
|
|
||||||
if consolePath != "" {
|
|
||||||
r.AddData(&Bytemsg{
|
|
||||||
Type: ConsolePathAttr,
|
|
||||||
Value: []byte(consolePath),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// write custom namespace paths
|
// write custom namespace paths
|
||||||
if len(nsMaps) > 0 {
|
if len(nsMaps) > 0 {
|
||||||
nsPaths, err := c.orderNamespacePaths(nsMaps)
|
nsPaths, err := c.orderNamespacePaths(nsMaps)
|
||||||
|
|
|
@ -54,12 +54,12 @@ type initConfig struct {
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
AdditionalGroups []string `json:"additional_groups"`
|
AdditionalGroups []string `json:"additional_groups"`
|
||||||
Config *configs.Config `json:"config"`
|
Config *configs.Config `json:"config"`
|
||||||
Console string `json:"console"`
|
|
||||||
Networks []*network `json:"network"`
|
Networks []*network `json:"network"`
|
||||||
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"`
|
ExecFifoPath string `json:"start_pipe_path"`
|
||||||
|
CreateConsole bool `json:"create_console"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type initer interface {
|
type initer interface {
|
||||||
|
@ -77,6 +77,7 @@ 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,
|
||||||
config: config,
|
config: config,
|
||||||
}, nil
|
}, nil
|
||||||
case initStandard:
|
case initStandard:
|
||||||
|
@ -150,6 +151,60 @@ func finalizeNamespace(config *initConfig) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setupConsole sets up the console from inside the container, and sends the
|
||||||
|
// master pty fd to the config.Pipe (using cmsg). This is done to ensure that
|
||||||
|
// 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
|
||||||
|
// rootfs (and the users' configuration is entirely set up).
|
||||||
|
func setupConsole(pipe *os.File, config *initConfig, mount bool) error {
|
||||||
|
// At this point, /dev/ptmx points to something that we would expect.
|
||||||
|
console, err := newConsole(0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// After we return from here, we don't need the console anymore.
|
||||||
|
defer console.Close()
|
||||||
|
|
||||||
|
linuxConsole, ok := console.(*linuxConsole)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("failed to cast console to *linuxConsole")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mount the console inside our rootfs.
|
||||||
|
if mount {
|
||||||
|
if err := linuxConsole.mount(config.ProcessLabel); err != nil {
|
||||||
|
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.
|
||||||
|
consoleFile := os.NewFile(linuxConsole.Fd(), "[master-pty]")
|
||||||
|
if err := utils.SendFd(pipe, consoleFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the other side recieved the fd.
|
||||||
|
if err := readSync(pipe, procConsoleAck); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, dup over all the things.
|
||||||
|
return linuxConsole.dupStdio()
|
||||||
|
}
|
||||||
|
|
||||||
// syncParentReady sends to the given pipe a JSON payload which indicates that
|
// syncParentReady sends to the given pipe a JSON payload which indicates that
|
||||||
// the init is ready to Exec the child process. It then waits for the parent to
|
// the init is ready to Exec the child process. It then waits for the parent to
|
||||||
// indicate that it is cleared to Exec.
|
// indicate that it is cleared to Exec.
|
||||||
|
|
|
@ -247,6 +247,8 @@ func TestExecInError(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XXX: This test will fail.
|
||||||
|
/*
|
||||||
func TestExecInTTY(t *testing.T) {
|
func TestExecInTTY(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
return
|
return
|
||||||
|
@ -306,6 +308,7 @@ func TestExecInTTY(t *testing.T) {
|
||||||
t.Fatalf("unexpected carriage-return in output")
|
t.Fatalf("unexpected carriage-return in output")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func TestExecInEnvironment(t *testing.T) {
|
func TestExecInEnvironment(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
|
|
|
@ -11,13 +11,12 @@ import (
|
||||||
// list of known message types we want to send to bootstrap program
|
// list of known message types we want to send to bootstrap program
|
||||||
// The number is randomly chosen to not conflict with known netlink types
|
// The number is randomly chosen to not conflict with known netlink types
|
||||||
const (
|
const (
|
||||||
InitMsg uint16 = 62000
|
InitMsg uint16 = 62000
|
||||||
CloneFlagsAttr uint16 = 27281
|
CloneFlagsAttr uint16 = 27281
|
||||||
ConsolePathAttr uint16 = 27282
|
NsPathsAttr uint16 = 27282
|
||||||
NsPathsAttr uint16 = 27283
|
UidmapAttr uint16 = 27283
|
||||||
UidmapAttr uint16 = 27284
|
GidmapAttr uint16 = 27284
|
||||||
GidmapAttr uint16 = 27285
|
SetgroupAttr uint16 = 27285
|
||||||
SetgroupAttr uint16 = 27286
|
|
||||||
// When syscall.NLA_HDRLEN is in gccgo, take this out.
|
// When syscall.NLA_HDRLEN is in gccgo, take this out.
|
||||||
syscall_NLA_HDRLEN = (syscall.SizeofNlAttr + syscall.NLA_ALIGNTO - 1) & ^(syscall.NLA_ALIGNTO - 1)
|
syscall_NLA_HDRLEN = (syscall.SizeofNlAttr + syscall.NLA_ALIGNTO - 1) & ^(syscall.NLA_ALIGNTO - 1)
|
||||||
)
|
)
|
||||||
|
|
|
@ -71,7 +71,6 @@ struct nlconfig_t {
|
||||||
char *namespaces;
|
char *namespaces;
|
||||||
size_t namespaces_len;
|
size_t namespaces_len;
|
||||||
uint8_t is_setgroup;
|
uint8_t is_setgroup;
|
||||||
int consolefd;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -80,11 +79,10 @@ struct nlconfig_t {
|
||||||
*/
|
*/
|
||||||
#define INIT_MSG 62000
|
#define INIT_MSG 62000
|
||||||
#define CLONE_FLAGS_ATTR 27281
|
#define CLONE_FLAGS_ATTR 27281
|
||||||
#define CONSOLE_PATH_ATTR 27282
|
#define NS_PATHS_ATTR 27282
|
||||||
#define NS_PATHS_ATTR 27283
|
#define UIDMAP_ATTR 27283
|
||||||
#define UIDMAP_ATTR 27284
|
#define GIDMAP_ATTR 27284
|
||||||
#define GIDMAP_ATTR 27285
|
#define SETGROUP_ATTR 27285
|
||||||
#define SETGROUP_ATTR 27286
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Use the raw syscall for versions of glibc which don't include a function for
|
* Use the raw syscall for versions of glibc which don't include a function for
|
||||||
|
@ -306,7 +304,6 @@ static void nl_parse(int fd, struct nlconfig_t *config)
|
||||||
|
|
||||||
/* Parse the netlink payload. */
|
/* Parse the netlink payload. */
|
||||||
config->data = data;
|
config->data = data;
|
||||||
config->consolefd = -1;
|
|
||||||
while (current < data + size) {
|
while (current < data + size) {
|
||||||
struct nlattr *nlattr = (struct nlattr *)current;
|
struct nlattr *nlattr = (struct nlattr *)current;
|
||||||
size_t payload_len = nlattr->nla_len - NLA_HDRLEN;
|
size_t payload_len = nlattr->nla_len - NLA_HDRLEN;
|
||||||
|
@ -319,15 +316,6 @@ static void nl_parse(int fd, struct nlconfig_t *config)
|
||||||
case CLONE_FLAGS_ATTR:
|
case CLONE_FLAGS_ATTR:
|
||||||
config->cloneflags = readint32(current);
|
config->cloneflags = readint32(current);
|
||||||
break;
|
break;
|
||||||
case CONSOLE_PATH_ATTR:
|
|
||||||
/*
|
|
||||||
* We open the console here because we currently evaluate console
|
|
||||||
* paths from the *host* namespaces.
|
|
||||||
*/
|
|
||||||
config->consolefd = open(current, O_RDWR);
|
|
||||||
if (config->consolefd < 0)
|
|
||||||
bail("failed to open console %s", current);
|
|
||||||
break;
|
|
||||||
case NS_PATHS_ATTR:
|
case NS_PATHS_ATTR:
|
||||||
config->namespaces = current;
|
config->namespaces = current;
|
||||||
config->namespaces_len = payload_len;
|
config->namespaces_len = payload_len;
|
||||||
|
@ -722,7 +710,6 @@ void nsexec(void)
|
||||||
* We're inside the child now, having jumped from the
|
* We're inside the child now, having jumped from the
|
||||||
* start_child() code after forking in the parent.
|
* start_child() code after forking in the parent.
|
||||||
*/
|
*/
|
||||||
int consolefd = config.consolefd;
|
|
||||||
enum sync_t s;
|
enum sync_t s;
|
||||||
|
|
||||||
/* We're in a child and thus need to tell the parent if we die. */
|
/* We're in a child and thus need to tell the parent if we die. */
|
||||||
|
@ -743,17 +730,6 @@ void nsexec(void)
|
||||||
if (setgroups(0, NULL) < 0)
|
if (setgroups(0, NULL) < 0)
|
||||||
bail("setgroups failed");
|
bail("setgroups failed");
|
||||||
|
|
||||||
if (consolefd != -1) {
|
|
||||||
if (ioctl(consolefd, TIOCSCTTY, 0) < 0)
|
|
||||||
bail("ioctl TIOCSCTTY failed");
|
|
||||||
if (dup3(consolefd, STDIN_FILENO, 0) != STDIN_FILENO)
|
|
||||||
bail("failed to dup stdin");
|
|
||||||
if (dup3(consolefd, STDOUT_FILENO, 0) != STDOUT_FILENO)
|
|
||||||
bail("failed to dup stdout");
|
|
||||||
if (dup3(consolefd, STDERR_FILENO, 0) != STDERR_FILENO)
|
|
||||||
bail("failed to dup stderr");
|
|
||||||
}
|
|
||||||
|
|
||||||
s = SYNC_CHILD_READY;
|
s = SYNC_CHILD_READY;
|
||||||
if (write(syncfd, &s, sizeof(s)) != sizeof(s))
|
if (write(syncfd, &s, sizeof(s)) != sizeof(s))
|
||||||
bail("failed to sync with patent: write(SYNC_CHILD_READY)");
|
bail("failed to sync with patent: write(SYNC_CHILD_READY)");
|
||||||
|
|
|
@ -36,19 +36,20 @@ type Process struct {
|
||||||
Cwd string
|
Cwd string
|
||||||
|
|
||||||
// Stdin is a pointer to a reader which provides the standard input stream.
|
// Stdin is a pointer to a reader which provides the standard input stream.
|
||||||
Stdin io.Reader
|
Stdin *os.File
|
||||||
|
|
||||||
// Stdout is a pointer to a writer which receives the standard output stream.
|
// Stdout is a pointer to a writer which receives the standard output stream.
|
||||||
Stdout io.Writer
|
Stdout *os.File
|
||||||
|
|
||||||
// Stderr is a pointer to a writer which receives the standard error stream.
|
// Stderr is a pointer to a writer which receives the standard error stream.
|
||||||
Stderr io.Writer
|
Stderr *os.File
|
||||||
|
|
||||||
// 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
|
||||||
|
|
||||||
// consolePath is the path to the console allocated to the container.
|
// consoleChan provides the masterfd console.
|
||||||
consolePath string
|
// 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
|
||||||
|
@ -105,21 +106,14 @@ type IO struct {
|
||||||
Stderr io.ReadCloser
|
Stderr io.ReadCloser
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConsole creates new console for process and returns it
|
func (p *Process) GetConsole() (Console, error) {
|
||||||
func (p *Process) NewConsole(rootuid, rootgid int) (Console, error) {
|
consoleFd, ok := <-p.consoleChan
|
||||||
console, err := NewConsole(rootuid, rootgid)
|
if !ok {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("failed to get console from process")
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
p.consolePath = console.Path()
|
|
||||||
return console, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConsoleFromPath sets the process's console with the path provided
|
// TODO: Fix this so that it used the console API.
|
||||||
func (p *Process) ConsoleFromPath(path string) error {
|
return &linuxConsole{
|
||||||
if p.consolePath != "" {
|
master: consoleFd,
|
||||||
return newGenericError(fmt.Errorf("console path already exists for process"), ConsoleExists)
|
}, nil
|
||||||
}
|
|
||||||
p.consolePath = path
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,8 +101,26 @@ func (p *setnsProcess) start() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ierr := parseSync(p.parentPipe, func(sync *syncT) error {
|
ierr := parseSync(p.parentPipe, func(sync *syncT) error {
|
||||||
// Currently this will noop.
|
|
||||||
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")
|
||||||
|
@ -285,6 +303,25 @@ 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")
|
||||||
|
@ -316,7 +353,7 @@ func (p *initProcess) start() error {
|
||||||
}
|
}
|
||||||
// Sync with child.
|
// Sync with child.
|
||||||
if err := writeSync(p.parentPipe, procRun); err != nil {
|
if err := writeSync(p.parentPipe, procRun); err != nil {
|
||||||
return newSystemErrorWithCause(err, "writing syncT run type")
|
return newSystemErrorWithCause(err, "writing syncT 'run'")
|
||||||
}
|
}
|
||||||
sentRun = true
|
sentRun = true
|
||||||
case procHooks:
|
case procHooks:
|
||||||
|
@ -336,7 +373,7 @@ func (p *initProcess) start() error {
|
||||||
}
|
}
|
||||||
// Sync with child.
|
// Sync with child.
|
||||||
if err := writeSync(p.parentPipe, procResume); err != nil {
|
if err := writeSync(p.parentPipe, procResume); err != nil {
|
||||||
return newSystemErrorWithCause(err, "writing syncT resume type")
|
return newSystemErrorWithCause(err, "writing syncT 'resume'")
|
||||||
}
|
}
|
||||||
sentResume = true
|
sentResume = true
|
||||||
default:
|
default:
|
||||||
|
@ -432,6 +469,8 @@ func getPipeFds(pid int) ([]string, error) {
|
||||||
|
|
||||||
dirPath := filepath.Join("/proc", strconv.Itoa(pid), "/fd")
|
dirPath := filepath.Join("/proc", strconv.Itoa(pid), "/fd")
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
|
// XXX: This breaks if the path is not a valid symlink (which can
|
||||||
|
// happen in certain particularly unlucky mount namespace setups).
|
||||||
f := filepath.Join(dirPath, strconv.Itoa(i))
|
f := filepath.Join(dirPath, strconv.Itoa(i))
|
||||||
target, err := os.Readlink(f)
|
target, err := os.Readlink(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -442,8 +481,10 @@ func getPipeFds(pid int) ([]string, error) {
|
||||||
return fds, nil
|
return fds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitializeIO creates pipes for use with the process's STDIO
|
// InitializeIO creates pipes for use with the process's stdio and returns the
|
||||||
// and returns the opposite side for each
|
// opposite side for each. Do not use this if you want to have a pseudoterminal
|
||||||
|
// set up for you by libcontainer (TODO: fix that too).
|
||||||
|
// TODO: This is mostly unnecessary, and should be handled by clients.
|
||||||
func (p *Process) InitializeIO(rootuid, rootgid int) (i *IO, err error) {
|
func (p *Process) InitializeIO(rootuid, rootgid int) (i *IO, err error) {
|
||||||
var fds []uintptr
|
var fds []uintptr
|
||||||
i = &IO{}
|
i = &IO{}
|
||||||
|
|
|
@ -36,9 +36,11 @@ func needsSetupDev(config *configs.Config) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// setupRootfs sets up the devices, mount points, and filesystems for use inside a
|
// prepareRootfs sets up the devices, mount points, and filesystems for use
|
||||||
// new mount namespace.
|
// inside a new mount namespace. It doesn't set anything as ro or pivot_root,
|
||||||
func setupRootfs(config *configs.Config, console *linuxConsole, pipe io.ReadWriter) (err error) {
|
// because console setup happens inside the caller. You must call
|
||||||
|
// finalizeRootfs in order to finish the rootfs setup.
|
||||||
|
func prepareRootfs(pipe io.ReadWriter, config *configs.Config) (err error) {
|
||||||
if err := prepareRoot(config); err != nil {
|
if err := prepareRoot(config); err != nil {
|
||||||
return newSystemErrorWithCause(err, "preparing rootfs")
|
return newSystemErrorWithCause(err, "preparing rootfs")
|
||||||
}
|
}
|
||||||
|
@ -50,6 +52,7 @@ func setupRootfs(config *configs.Config, console *linuxConsole, pipe io.ReadWrit
|
||||||
return newSystemErrorWithCause(err, "running premount command")
|
return newSystemErrorWithCause(err, "running premount command")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := mountToRootfs(m, config.Rootfs, config.MountLabel); err != nil {
|
if err := mountToRootfs(m, config.Rootfs, config.MountLabel); err != nil {
|
||||||
return newSystemErrorWithCausef(err, "mounting %q to rootfs %q at %q", m.Source, config.Rootfs, m.Destination)
|
return newSystemErrorWithCausef(err, "mounting %q to rootfs %q at %q", m.Source, config.Rootfs, m.Destination)
|
||||||
}
|
}
|
||||||
|
@ -60,17 +63,19 @@ func setupRootfs(config *configs.Config, console *linuxConsole, pipe io.ReadWrit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if setupDev {
|
if setupDev {
|
||||||
if err := createDevices(config); err != nil {
|
if err := createDevices(config); err != nil {
|
||||||
return newSystemErrorWithCause(err, "creating device nodes")
|
return newSystemErrorWithCause(err, "creating device nodes")
|
||||||
}
|
}
|
||||||
if err := setupPtmx(config, console); err != nil {
|
if err := setupPtmx(config); err != nil {
|
||||||
return newSystemErrorWithCause(err, "setting up ptmx")
|
return newSystemErrorWithCause(err, "setting up ptmx")
|
||||||
}
|
}
|
||||||
if err := setupDevSymlinks(config.Rootfs); err != nil {
|
if err := setupDevSymlinks(config.Rootfs); err != nil {
|
||||||
return newSystemErrorWithCause(err, "setting up /dev symlinks")
|
return newSystemErrorWithCause(err, "setting up /dev symlinks")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signal the parent to run the pre-start hooks.
|
// Signal the parent to run the pre-start hooks.
|
||||||
// The hooks are run after the mounts are setup, but before we switch to the new
|
// The hooks are run after the mounts are setup, but before we switch to the new
|
||||||
// root, so that the old root is still available in the hooks for any mount
|
// root, so that the old root is still available in the hooks for any mount
|
||||||
|
@ -78,9 +83,19 @@ func setupRootfs(config *configs.Config, console *linuxConsole, pipe io.ReadWrit
|
||||||
if err := syncParentHooks(pipe); err != nil {
|
if err := syncParentHooks(pipe); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The reason these operations are done here rather than in finalizeRootfs
|
||||||
|
// is because the console-handling code gets quite sticky if we have to set
|
||||||
|
// up the console before doing the pivot_root(2). This is because the
|
||||||
|
// Console API has to also work with the ExecIn case, which means that the
|
||||||
|
// API must be able to deal with being inside as well as outside the
|
||||||
|
// container. It's just cleaner to do this here (at the expense of the
|
||||||
|
// operation not being perfectly split).
|
||||||
|
|
||||||
if err := syscall.Chdir(config.Rootfs); err != nil {
|
if err := syscall.Chdir(config.Rootfs); err != nil {
|
||||||
return newSystemErrorWithCausef(err, "changing dir to %q", config.Rootfs)
|
return newSystemErrorWithCausef(err, "changing dir to %q", config.Rootfs)
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.NoPivotRoot {
|
if config.NoPivotRoot {
|
||||||
err = msMoveRoot(config.Rootfs)
|
err = msMoveRoot(config.Rootfs)
|
||||||
} else {
|
} else {
|
||||||
|
@ -89,11 +104,19 @@ func setupRootfs(config *configs.Config, console *linuxConsole, pipe io.ReadWrit
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newSystemErrorWithCause(err, "jailing process inside rootfs")
|
return newSystemErrorWithCause(err, "jailing process inside rootfs")
|
||||||
}
|
}
|
||||||
|
|
||||||
if setupDev {
|
if setupDev {
|
||||||
if err := reOpenDevNull(); err != nil {
|
if err := reOpenDevNull(); err != nil {
|
||||||
return newSystemErrorWithCause(err, "reopening /dev/null inside container")
|
return newSystemErrorWithCause(err, "reopening /dev/null inside container")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// finalizeRootfs actually switches the root of the process and sets anything
|
||||||
|
// to ro if necessary. You must call prepareRootfs first.
|
||||||
|
func finalizeRootfs(config *configs.Config) (err error) {
|
||||||
// remount dev as ro if specified
|
// remount dev as ro if specified
|
||||||
for _, m := range config.Mounts {
|
for _, m := range config.Mounts {
|
||||||
if libcontainerUtils.CleanPath(m.Destination) == "/dev" {
|
if libcontainerUtils.CleanPath(m.Destination) == "/dev" {
|
||||||
|
@ -105,12 +128,14 @@ func setupRootfs(config *configs.Config, console *linuxConsole, pipe io.ReadWrit
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set rootfs ( / ) as readonly
|
// set rootfs ( / ) as readonly
|
||||||
if config.Readonlyfs {
|
if config.Readonlyfs {
|
||||||
if err := setReadonly(); err != nil {
|
if err := setReadonly(); err != nil {
|
||||||
return newSystemErrorWithCause(err, "setting rootfs as readonly")
|
return newSystemErrorWithCause(err, "setting rootfs as readonly")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
syscall.Umask(0022)
|
syscall.Umask(0022)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -578,7 +603,7 @@ func setReadonly() error {
|
||||||
return syscall.Mount("/", "/", "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, "")
|
return syscall.Mount("/", "/", "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupPtmx(config *configs.Config, console *linuxConsole) error {
|
func setupPtmx(config *configs.Config) error {
|
||||||
ptmx := filepath.Join(config.Rootfs, "dev/ptmx")
|
ptmx := filepath.Join(config.Rootfs, "dev/ptmx")
|
||||||
if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) {
|
if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) {
|
||||||
return err
|
return err
|
||||||
|
@ -586,9 +611,6 @@ func setupPtmx(config *configs.Config, console *linuxConsole) error {
|
||||||
if err := os.Symlink("pts/ptmx", ptmx); err != nil {
|
if err := os.Symlink("pts/ptmx", ptmx); err != nil {
|
||||||
return fmt.Errorf("symlink dev ptmx %s", err)
|
return fmt.Errorf("symlink dev ptmx %s", err)
|
||||||
}
|
}
|
||||||
if console != nil {
|
|
||||||
return console.mount(config.Rootfs, config.MountLabel)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ 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
|
||||||
config *initConfig
|
config *initConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +31,14 @@ func (l *linuxSetnsInit) Init() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if l.config.CreateConsole {
|
||||||
|
if err := setupConsole(l.pipe, l.config, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := system.Setctty(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if l.config.NoNewPrivileges {
|
if l.config.NoNewPrivileges {
|
||||||
if err := system.Prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); err != nil {
|
if err := system.Prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -4,7 +4,6 @@ package libcontainer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
@ -18,7 +17,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type linuxStandardInit struct {
|
type linuxStandardInit struct {
|
||||||
pipe io.ReadWriteCloser
|
pipe *os.File
|
||||||
parentPid int
|
parentPid int
|
||||||
stateDirFD int
|
stateDirFD int
|
||||||
config *initConfig
|
config *initConfig
|
||||||
|
@ -59,18 +58,6 @@ func (l *linuxStandardInit) Init() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var console *linuxConsole
|
|
||||||
if l.config.Console != "" {
|
|
||||||
console = newConsoleFromPath(l.config.Console)
|
|
||||||
if err := console.dupStdio(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if console != nil {
|
|
||||||
if err := system.Setctty(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := setupNetwork(l.config); err != nil {
|
if err := setupNetwork(l.config); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -79,12 +66,33 @@ func (l *linuxStandardInit) Init() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
label.Init()
|
label.Init()
|
||||||
// InitializeMountNamespace() can be executed only for a new mount namespace
|
|
||||||
|
// prepareRootfs() can be executed only for a new mount namespace.
|
||||||
if l.config.Config.Namespaces.Contains(configs.NEWNS) {
|
if l.config.Config.Namespaces.Contains(configs.NEWNS) {
|
||||||
if err := setupRootfs(l.config.Config, console, l.pipe); err != nil {
|
if err := prepareRootfs(l.pipe, l.config.Config); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up the console. This has to be done *before* we finalize the rootfs,
|
||||||
|
// but *after* we've given the user the chance to set up all of the mounts
|
||||||
|
// they wanted.
|
||||||
|
if l.config.CreateConsole {
|
||||||
|
if err := setupConsole(l.pipe, l.config, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := system.Setctty(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish the rootfs setup.
|
||||||
|
if l.config.Config.Namespaces.Contains(configs.NEWNS) {
|
||||||
|
if err := finalizeRootfs(l.config.Config); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if hostname := l.config.Config.Hostname; hostname != "" {
|
if hostname := l.config.Config.Hostname; hostname != "" {
|
||||||
if err := syscall.Sethostname([]byte(hostname)); err != nil {
|
if err := syscall.Sethostname([]byte(hostname)); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"github.com/opencontainers/runc/libcontainer/utils"
|
"github.com/opencontainers/runc/libcontainer/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type syncType uint8
|
type syncType string
|
||||||
|
|
||||||
// Constants that are used for synchronisation between the parent and child
|
// Constants that are used for synchronisation between the parent and child
|
||||||
// during container setup. They come in pairs (with procError being a generic
|
// during container setup. They come in pairs (with procError being a generic
|
||||||
|
@ -19,14 +19,22 @@ type syncType uint8
|
||||||
// procHooks --> [run hooks]
|
// procHooks --> [run hooks]
|
||||||
// <-- procResume
|
// <-- procResume
|
||||||
//
|
//
|
||||||
|
// procConsole -->
|
||||||
|
// <-- procConsoleReq
|
||||||
|
// [send(fd)] --> [recv(fd)]
|
||||||
|
// <-- procConsoleAck
|
||||||
|
//
|
||||||
// procReady --> [final setup]
|
// procReady --> [final setup]
|
||||||
// <-- procRun
|
// <-- procRun
|
||||||
const (
|
const (
|
||||||
procError syncType = iota
|
procError syncType = "procError"
|
||||||
procReady
|
procReady syncType = "procReady"
|
||||||
procRun
|
procRun syncType = "procRun"
|
||||||
procHooks
|
procHooks syncType = "procHooks"
|
||||||
procResume
|
procResume syncType = "procResume"
|
||||||
|
procConsole syncType = "procConsole"
|
||||||
|
procConsoleReq syncType = "procConsoleReq"
|
||||||
|
procConsoleAck syncType = "procConsoleAck"
|
||||||
)
|
)
|
||||||
|
|
||||||
type syncT struct {
|
type syncT struct {
|
||||||
|
|
|
@ -158,15 +158,16 @@ 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
|
||||||
}
|
}
|
||||||
defer tty.Close()
|
handler := newSignalHandler(!context.Bool("no-subreaper"))
|
||||||
handler := newSignalHandler(tty, !context.Bool("no-subreaper"))
|
|
||||||
if err := container.Restore(process, options); err != nil {
|
if err := container.Restore(process, options); err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
// We don't need to do a tty.recvtty because config.Terminal is always false.
|
||||||
|
defer tty.Close()
|
||||||
if err := tty.ClosePostStart(); err != nil {
|
if err := tty.ClosePostStart(); err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
@ -180,7 +181,7 @@ func restoreContainer(context *cli.Context, spec *specs.Spec, config *configs.Co
|
||||||
if detach {
|
if detach {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
return handler.forward(process)
|
return handler.forward(process, tty)
|
||||||
}
|
}
|
||||||
|
|
||||||
func criuOptions(context *cli.Context) *libcontainer.CriuOpts {
|
func criuOptions(context *cli.Context) *libcontainer.CriuOpts {
|
||||||
|
|
5
run.go
5
run.go
|
@ -31,11 +31,6 @@ command(s) that get executed on start, edit the args parameter of the spec. See
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: `path to the root of the bundle directory, defaults to the current directory`,
|
Usage: `path to the root of the bundle directory, defaults to the current directory`,
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
|
||||||
Name: "console",
|
|
||||||
Value: "",
|
|
||||||
Usage: "specify the pty slave path for use with the container",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "detach, d",
|
Name: "detach, d",
|
||||||
Usage: "detach from the container's process",
|
Usage: "detach from the container's process",
|
||||||
|
|
10
signals.go
10
signals.go
|
@ -17,7 +17,7 @@ const signalBufferSize = 2048
|
||||||
|
|
||||||
// newSignalHandler returns a signal handler for processing SIGCHLD and SIGWINCH signals
|
// newSignalHandler returns a signal handler for processing SIGCHLD and SIGWINCH signals
|
||||||
// while still forwarding all other signals to the process.
|
// while still forwarding all other signals to the process.
|
||||||
func newSignalHandler(tty *tty, enableSubreaper bool) *signalHandler {
|
func newSignalHandler(enableSubreaper bool) *signalHandler {
|
||||||
if enableSubreaper {
|
if enableSubreaper {
|
||||||
// set us as the subreaper before registering the signal handler for the container
|
// set us as the subreaper before registering the signal handler for the container
|
||||||
if err := system.SetSubreaper(1); err != nil {
|
if err := system.SetSubreaper(1); err != nil {
|
||||||
|
@ -30,7 +30,6 @@ func newSignalHandler(tty *tty, enableSubreaper bool) *signalHandler {
|
||||||
// handle all signals for the process.
|
// handle all signals for the process.
|
||||||
signal.Notify(s)
|
signal.Notify(s)
|
||||||
return &signalHandler{
|
return &signalHandler{
|
||||||
tty: tty,
|
|
||||||
signals: s,
|
signals: s,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,12 +43,11 @@ type exit struct {
|
||||||
|
|
||||||
type signalHandler struct {
|
type signalHandler struct {
|
||||||
signals chan os.Signal
|
signals chan os.Signal
|
||||||
tty *tty
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// forward handles the main signal event loop forwarding, resizing, or reaping depending
|
// forward handles the main signal event loop forwarding, resizing, or reaping depending
|
||||||
// on the signal received.
|
// on the signal received.
|
||||||
func (h *signalHandler) forward(process *libcontainer.Process) (int, error) {
|
func (h *signalHandler) forward(process *libcontainer.Process, tty *tty) (int, error) {
|
||||||
// make sure we know the pid of our main process so that we can return
|
// make sure we know the pid of our main process so that we can return
|
||||||
// after it dies.
|
// after it dies.
|
||||||
pid1, err := process.Pid()
|
pid1, err := process.Pid()
|
||||||
|
@ -57,11 +55,11 @@ func (h *signalHandler) forward(process *libcontainer.Process) (int, error) {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
// perform the initial tty resize.
|
// perform the initial tty resize.
|
||||||
h.tty.resize()
|
tty.resize()
|
||||||
for s := range h.signals {
|
for s := range h.signals {
|
||||||
switch s {
|
switch s {
|
||||||
case syscall.SIGWINCH:
|
case syscall.SIGWINCH:
|
||||||
h.tty.resize()
|
tty.resize()
|
||||||
case syscall.SIGCHLD:
|
case syscall.SIGCHLD:
|
||||||
exits, err := h.reap()
|
exits, err := h.reap()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
83
tty.go
83
tty.go
|
@ -7,11 +7,26 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
"github.com/opencontainers/runc/libcontainer"
|
"github.com/opencontainers/runc/libcontainer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type tty struct {
|
||||||
|
console libcontainer.Console
|
||||||
|
state *term.State
|
||||||
|
closers []io.Closer
|
||||||
|
postStart []io.Closer
|
||||||
|
wg sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *tty) copyIO(w io.Writer, r io.ReadCloser) {
|
||||||
|
defer t.wg.Done()
|
||||||
|
io.Copy(w, r)
|
||||||
|
r.Close()
|
||||||
|
}
|
||||||
|
|
||||||
// setup standard pipes so that the TTY of the calling runc process
|
// setup standard pipes so that the TTY of the calling runc process
|
||||||
// is not inherited by the container.
|
// is not inherited by the container.
|
||||||
func createStdioPipes(p *libcontainer.Process, rootuid, rootgid int) (*tty, error) {
|
func createStdioPipes(p *libcontainer.Process, rootuid, rootgid int) (*tty, error) {
|
||||||
|
@ -46,45 +61,43 @@ func createStdioPipes(p *libcontainer.Process, rootuid, rootgid int) (*tty, erro
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tty) copyIO(w io.Writer, r io.ReadCloser) {
|
func dupStdio(process *libcontainer.Process, rootuid, rootgid int) error {
|
||||||
defer t.wg.Done()
|
process.Stdin = os.Stdin
|
||||||
io.Copy(w, r)
|
process.Stdout = os.Stdout
|
||||||
r.Close()
|
process.Stderr = os.Stderr
|
||||||
}
|
for _, fd := range []uintptr{
|
||||||
|
os.Stdin.Fd(),
|
||||||
func createTty(p *libcontainer.Process, rootuid, rootgid int, consolePath string) (*tty, error) {
|
os.Stdout.Fd(),
|
||||||
if consolePath != "" {
|
os.Stderr.Fd(),
|
||||||
if err := p.ConsoleFromPath(consolePath); err != nil {
|
} {
|
||||||
return nil, err
|
if err := syscall.Fchown(int(fd), rootuid, rootgid); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return &tty{}, nil
|
|
||||||
}
|
}
|
||||||
console, err := p.NewConsole(rootuid, rootgid)
|
return nil
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
go io.Copy(console, os.Stdin)
|
|
||||||
go io.Copy(os.Stdout, console)
|
|
||||||
|
|
||||||
state, err := term.SetRawTerminal(os.Stdin.Fd())
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to set the terminal from the stdin: %v", err)
|
|
||||||
}
|
|
||||||
return &tty{
|
|
||||||
console: console,
|
|
||||||
state: state,
|
|
||||||
closers: []io.Closer{
|
|
||||||
console,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type tty struct {
|
func (t *tty) recvtty(process *libcontainer.Process, detach bool) error {
|
||||||
console libcontainer.Console
|
console, err := process.GetConsole()
|
||||||
state *term.State
|
if err != nil {
|
||||||
closers []io.Closer
|
return err
|
||||||
postStart []io.Closer
|
}
|
||||||
wg sync.WaitGroup
|
|
||||||
|
if !detach {
|
||||||
|
go io.Copy(console, os.Stdin)
|
||||||
|
t.wg.Add(1)
|
||||||
|
go t.copyIO(os.Stdout, console)
|
||||||
|
|
||||||
|
state, err := term.SetRawTerminal(os.Stdin.Fd())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to set the terminal from the stdin: %v", err)
|
||||||
|
}
|
||||||
|
t.state = state
|
||||||
|
}
|
||||||
|
|
||||||
|
t.console = console
|
||||||
|
t.closers = []io.Closer{console}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
|
|
@ -94,22 +94,6 @@ func newProcess(p specs.Process) (*libcontainer.Process, error) {
|
||||||
return lp, nil
|
return lp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dupStdio(process *libcontainer.Process, rootuid, rootgid int) error {
|
|
||||||
process.Stdin = os.Stdin
|
|
||||||
process.Stdout = os.Stdout
|
|
||||||
process.Stderr = os.Stderr
|
|
||||||
for _, fd := range []uintptr{
|
|
||||||
os.Stdin.Fd(),
|
|
||||||
os.Stdout.Fd(),
|
|
||||||
os.Stderr.Fd(),
|
|
||||||
} {
|
|
||||||
if err := syscall.Fchown(int(fd), rootuid, rootgid); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If systemd is supporting sd_notify protocol, this function will add support
|
// If systemd is supporting sd_notify protocol, this function will add support
|
||||||
// for sd_notify protocol from within the container.
|
// for sd_notify protocol from within the container.
|
||||||
func setupSdNotify(spec *specs.Spec, notifySocket string) {
|
func setupSdNotify(spec *specs.Spec, notifySocket string) {
|
||||||
|
@ -123,23 +107,27 @@ func destroy(container libcontainer.Container) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setupIO sets the proper IO on the process depending on the configuration
|
// setupIO modifies the given process config according to the options.
|
||||||
// If there is a nil error then there must be a non nil tty returned
|
func setupIO(process *libcontainer.Process, rootuid, rootgid int, createTTY, detach bool) (*tty, error) {
|
||||||
func setupIO(process *libcontainer.Process, rootuid, rootgid int, console string, createTTY, detach bool) (*tty, error) {
|
// This is entirely handled by recvtty.
|
||||||
// detach and createTty will not work unless a console path is passed
|
|
||||||
// so error out here before changing any terminal settings
|
|
||||||
if createTTY && detach && console == "" {
|
|
||||||
return nil, fmt.Errorf("cannot allocate tty if runc will detach")
|
|
||||||
}
|
|
||||||
if createTTY {
|
if createTTY {
|
||||||
return createTty(process, rootuid, rootgid, console)
|
process.Stdin = nil
|
||||||
|
process.Stdout = nil
|
||||||
|
process.Stderr = nil
|
||||||
|
return &tty{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When we detach, we just dup over stdio and call it a day. There's no
|
||||||
|
// requirement that we set up anything nice for our caller or the
|
||||||
|
// container.
|
||||||
if detach {
|
if detach {
|
||||||
|
// TODO: Actually set rootuid, rootgid.
|
||||||
if err := dupStdio(process, rootuid, rootgid); err != nil {
|
if err := dupStdio(process, rootuid, rootgid); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &tty{}, nil
|
return &tty{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return createStdioPipes(process, rootuid, rootgid)
|
return createStdioPipes(process, rootuid, rootgid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,7 +180,6 @@ type runner struct {
|
||||||
detach bool
|
detach bool
|
||||||
listenFDs []*os.File
|
listenFDs []*os.File
|
||||||
pidFile string
|
pidFile string
|
||||||
console string
|
|
||||||
container libcontainer.Container
|
container libcontainer.Container
|
||||||
create bool
|
create bool
|
||||||
}
|
}
|
||||||
|
@ -217,21 +204,31 @@ func (r *runner) run(config *specs.Process) (int, error) {
|
||||||
r.destroy()
|
r.destroy()
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
tty, err := setupIO(process, rootuid, rootgid, r.console, config.Terminal, r.detach || r.create)
|
|
||||||
if err != nil {
|
|
||||||
r.destroy()
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
handler := newSignalHandler(tty, r.enableSubreaper)
|
|
||||||
startFn := r.container.Start
|
startFn := r.container.Start
|
||||||
if !r.create {
|
if !r.create {
|
||||||
startFn = r.container.Run
|
startFn = r.container.Run
|
||||||
}
|
}
|
||||||
defer tty.Close()
|
// Setting up IO is a two stage process. We need to modify process to deal
|
||||||
|
// with detaching containers, and then we get a tty after the container has
|
||||||
|
// started.
|
||||||
|
handler := newSignalHandler(r.enableSubreaper)
|
||||||
|
tty, err := setupIO(process, rootuid, rootgid, config.Terminal, r.detach || r.create)
|
||||||
|
if err != nil {
|
||||||
|
r.destroy()
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
if err := startFn(process); err != nil {
|
if err := startFn(process); err != nil {
|
||||||
r.destroy()
|
r.destroy()
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
if config.Terminal {
|
||||||
|
if err := tty.recvtty(process, r.detach || r.create); err != nil {
|
||||||
|
r.terminate(process)
|
||||||
|
r.destroy()
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer tty.Close()
|
||||||
if err := tty.ClosePostStart(); err != nil {
|
if err := tty.ClosePostStart(); err != nil {
|
||||||
r.terminate(process)
|
r.terminate(process)
|
||||||
r.destroy()
|
r.destroy()
|
||||||
|
@ -247,7 +244,7 @@ func (r *runner) run(config *specs.Process) (int, error) {
|
||||||
if r.detach || r.create {
|
if r.detach || r.create {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
status, err := handler.forward(process)
|
status, err := handler.forward(process, tty)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.terminate(process)
|
r.terminate(process)
|
||||||
}
|
}
|
||||||
|
@ -298,7 +295,6 @@ func startContainer(context *cli.Context, spec *specs.Spec, create bool) (int, e
|
||||||
shouldDestroy: true,
|
shouldDestroy: true,
|
||||||
container: container,
|
container: container,
|
||||||
listenFDs: listenFDs,
|
listenFDs: listenFDs,
|
||||||
console: context.String("console"),
|
|
||||||
detach: context.Bool("detach"),
|
detach: context.Bool("detach"),
|
||||||
pidFile: context.String("pid-file"),
|
pidFile: context.String("pid-file"),
|
||||||
create: create,
|
create: create,
|
||||||
|
|
Loading…
Reference in New Issue