Remove console package and add Console type

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
Michael Crosby 2015-02-09 14:07:18 -08:00
parent 20daff5e2c
commit b0e274c0d2
8 changed files with 247 additions and 216 deletions

15
console.go Normal file
View File

@ -0,0 +1,15 @@
package libcontainer
import "io"
// Console is a psuedo TTY.
type Console interface {
io.ReadWriter
io.Closer
// Path returns the filesystem path to the slave side of the pty.
Path() string
// Fd returns the fd for the master of the pty.
Fd() uintptr
}

View File

@ -1,128 +0,0 @@
// +build linux
package console
import (
"fmt"
"os"
"path/filepath"
"syscall"
"unsafe"
"github.com/docker/libcontainer/label"
)
// Setup initializes the proper /dev/console inside the rootfs path
func Setup(rootfs, consolePath, mountLabel string, hostRootUid, hostRootGid int) error {
oldMask := syscall.Umask(0000)
defer syscall.Umask(oldMask)
if err := os.Chmod(consolePath, 0600); err != nil {
return err
}
if err := os.Chown(consolePath, hostRootUid, hostRootGid); err != nil {
return err
}
if err := label.SetFileLabel(consolePath, mountLabel); err != nil {
return fmt.Errorf("set file label %s %s", consolePath, err)
}
dest := filepath.Join(rootfs, "dev/console")
f, err := os.Create(dest)
if err != nil && !os.IsExist(err) {
return fmt.Errorf("create %s %s", dest, err)
}
if f != nil {
f.Close()
}
if err := syscall.Mount(consolePath, dest, "bind", syscall.MS_BIND, ""); err != nil {
return fmt.Errorf("bind %s to %s %s", consolePath, dest, err)
}
return nil
}
func OpenAndDup(consolePath string) error {
slave, err := OpenTerminal(consolePath, syscall.O_RDWR)
if err != nil {
return fmt.Errorf("open terminal %s", err)
}
if err := syscall.Dup2(int(slave.Fd()), 0); err != nil {
return err
}
if err := syscall.Dup2(int(slave.Fd()), 1); err != nil {
return err
}
return syscall.Dup2(int(slave.Fd()), 2)
}
// Unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
// Unlockpt should be called before opening the slave side of a pseudoterminal.
func Unlockpt(f *os.File) error {
var u int32
return Ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u)))
}
// Ptsname retrieves the name of the first available pts for the given master.
func Ptsname(f *os.File) (string, error) {
var n int32
if err := Ioctl(f.Fd(), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n))); err != nil {
return "", err
}
return fmt.Sprintf("/dev/pts/%d", n), nil
}
// CreateMasterAndConsole will open /dev/ptmx on the host and retreive the
// pts name for use as the pty slave inside the container
func CreateMasterAndConsole() (*os.File, string, error) {
master, err := os.OpenFile("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0)
if err != nil {
return nil, "", err
}
console, err := Ptsname(master)
if err != nil {
return nil, "", err
}
if err := Unlockpt(master); err != nil {
return nil, "", err
}
return master, console, nil
}
// OpenPtmx opens /dev/ptmx, i.e. the PTY master.
func OpenPtmx() (*os.File, error) {
// O_NOCTTY and O_CLOEXEC are not present in os package so we use the syscall's one for all.
return os.OpenFile("/dev/ptmx", syscall.O_RDONLY|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0)
}
// OpenTerminal is a clone of os.OpenFile without the O_CLOEXEC
// used to open the pty slave inside the container namespace
func OpenTerminal(name string, flag int) (*os.File, error) {
r, e := syscall.Open(name, flag, 0)
if e != nil {
return nil, &os.PathError{Op: "open", Path: name, Err: e}
}
return os.NewFile(uintptr(r), name), nil
}
func Ioctl(fd uintptr, flag, data uintptr) error {
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, flag, data); err != 0 {
return err
}
return nil
}

145
linux_console.go Normal file
View File

@ -0,0 +1,145 @@
// +build linux
package libcontainer
import (
"fmt"
"os"
"path/filepath"
"syscall"
"unsafe"
"github.com/docker/libcontainer/label"
)
func NewConsole() (Console, error) {
master, err := os.OpenFile("/dev/ptmx", syscall.O_RDWR|syscall.O_NOCTTY|syscall.O_CLOEXEC, 0)
if err != nil {
return nil, err
}
console, err := ptsname(master)
if err != nil {
return nil, err
}
if err := unlockpt(master); err != nil {
return nil, err
}
return &linuxConsole{
slavePath: console,
master: master,
}, nil
}
// newConsoleFromPath is an internal fucntion returning an initialzied console for use inside
// a container's MNT namespace.
func newConsoleFromPath(slavePath string) *linuxConsole {
return &linuxConsole{
slavePath: slavePath,
}
}
// linuxConsole is a linux psuedo TTY for use within a container.
type linuxConsole struct {
master *os.File
slavePath string
}
func (c *linuxConsole) Fd() uintptr {
return c.master.Fd()
}
func (c *linuxConsole) Path() string {
return c.slavePath
}
func (c *linuxConsole) Read(b []byte) (int, error) {
return c.master.Read(b)
}
func (c *linuxConsole) Write(b []byte) (int, error) {
return c.master.Write(b)
}
func (c *linuxConsole) Close() error {
if m := c.master; m != nil {
return m.Close()
}
return nil
}
// mount initializes the console inside the rootfs mounting with the specified mount label
// and applying the correct ownership of the console.
func (c *linuxConsole) mount(rootfs, mountLabel string, uid, gid int) error {
oldMask := syscall.Umask(0000)
defer syscall.Umask(oldMask)
if err := os.Chmod(c.slavePath, 0600); err != nil {
return err
}
if err := os.Chown(c.slavePath, uid, gid); err != nil {
return err
}
if err := label.SetFileLabel(c.slavePath, mountLabel); err != nil {
return err
}
dest := filepath.Join(rootfs, "dev/console")
f, err := os.Create(dest)
if err != nil && !os.IsExist(err) {
return err
}
if f != nil {
f.Close()
}
return syscall.Mount(c.slavePath, dest, "bind", syscall.MS_BIND, "")
}
// dupStdio opens the slavePath for the console and dup2s the fds to the current
// processes stdio, fd 0,1,2.
func (c *linuxConsole) dupStdio() error {
slave, err := c.open(syscall.O_RDWR)
if err != nil {
return err
}
fd := int(slave.Fd())
for _, i := range []int{0, 1, 2} {
if err := syscall.Dup2(fd, i); err != nil {
return err
}
}
return nil
}
// open is a clone of os.OpenFile without the O_CLOEXEC used to open the pty slave.
func (c *linuxConsole) open(flag int) (*os.File, error) {
r, e := syscall.Open(c.slavePath, flag, 0)
if e != nil {
return nil, &os.PathError{
Op: "open",
Path: c.slavePath,
Err: e,
}
}
return os.NewFile(uintptr(r), c.slavePath), nil
}
func ioctl(fd uintptr, flag, data uintptr) error {
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, flag, data); err != 0 {
return err
}
return nil
}
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
// unlockpt should be called before opening the slave side of a pty.
func unlockpt(f *os.File) error {
var u int32
return ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u)))
}
// ptsname retrieves the name of the first available pts for the given master.
func ptsname(f *os.File) (string, error) {
var n int32
if err := ioctl(f.Fd(), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n))); err != nil {
return "", err
}
return fmt.Sprintf("/dev/pts/%d", n), nil
}

View File

@ -10,7 +10,6 @@ import (
"syscall"
"github.com/docker/libcontainer/configs"
"github.com/docker/libcontainer/console"
"github.com/docker/libcontainer/label"
)
@ -237,7 +236,8 @@ func setupPtmx(config *configs.Config) error {
if err != nil {
return err
}
return console.Setup(config.Rootfs, config.Console, config.MountLabel, uid, gid)
console := newConsoleFromPath(config.Console)
return console.mount(config.Rootfs, config.MountLabel, uid, gid)
}
return nil
}

View File

@ -7,7 +7,6 @@ import (
"github.com/docker/libcontainer/apparmor"
"github.com/docker/libcontainer/configs"
consolepkg "github.com/docker/libcontainer/console"
"github.com/docker/libcontainer/label"
"github.com/docker/libcontainer/security/restrict"
"github.com/docker/libcontainer/system"
@ -22,16 +21,17 @@ func (l *linuxStandardInit) Init() error {
if err := joinExistingNamespaces(l.config.Config.Namespaces); err != nil {
return err
}
console := l.config.Config.Console
if console != "" {
if err := consolepkg.OpenAndDup(console); err != nil {
consolePath := l.config.Config.Console
if consolePath != "" {
console := newConsoleFromPath(consolePath)
if err := console.dupStdio(); err != nil {
return err
}
}
if _, err := syscall.Setsid(); err != nil {
return err
}
if console != "" {
if consolePath != "" {
if err := system.Setctty(); err != nil {
return err
}

View File

@ -6,7 +6,6 @@ import (
"syscall"
"github.com/docker/libcontainer/apparmor"
consolepkg "github.com/docker/libcontainer/console"
"github.com/docker/libcontainer/label"
"github.com/docker/libcontainer/security/restrict"
"github.com/docker/libcontainer/system"
@ -21,16 +20,17 @@ func (l *linuxUsernsInit) Init() error {
if err := joinExistingNamespaces(l.config.Config.Namespaces); err != nil {
return err
}
console := l.config.Config.Console
if console != "" {
if err := consolepkg.OpenAndDup("/dev/console"); err != nil {
consolePath := l.config.Config.Console
if consolePath != "" {
console := newConsoleFromPath(consolePath)
if err := console.dupStdio(); err != nil {
return err
}
}
if _, err := syscall.Setsid(); err != nil {
return err
}
if console != "" {
if consolePath != "" {
if err := system.Setctty(); err != nil {
return err
}

View File

@ -1,55 +1,19 @@
package main
import (
"io"
"os"
"os/signal"
"syscall"
"github.com/codegangsta/cli"
"github.com/docker/docker/pkg/term"
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/configs"
consolepkg "github.com/docker/libcontainer/console"
"github.com/docker/libcontainer/utils"
)
type tty struct {
master *os.File
console string
state *term.State
}
func (t *tty) Close() error {
if t.master != nil {
t.master.Close()
}
if t.state != nil {
term.RestoreTerminal(os.Stdin.Fd(), t.state)
}
return nil
}
func (t *tty) set(config *configs.Config) {
config.Console = t.console
}
func (t *tty) attach(process *libcontainer.Process) {
if t.master != nil {
process.Stderr = nil
process.Stdout = nil
process.Stdin = nil
}
}
func (t *tty) resize() error {
if t.master == nil {
return nil
}
ws, err := term.GetWinsize(os.Stdin.Fd())
if err != nil {
return err
}
return term.SetWinsize(t.master.Fd(), ws)
var standardEnvironment = &cli.StringSlice{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"HOSTNAME=nsinit",
"TERM=xterm",
}
var execCommand = cli.Command{
@ -60,6 +24,8 @@ var execCommand = cli.Command{
cli.BoolFlag{Name: "tty", Usage: "allocate a TTY to the container"},
cli.StringFlag{Name: "id", Value: "nsinit", Usage: "specify the ID for a container"},
cli.StringFlag{Name: "config", Value: "container.json", Usage: "path to the configuration file"},
cli.StringFlag{Name: "user,u", Value: "root", Usage: "set the user, uid, and/or gid for the process"},
cli.StringSliceFlag{Name: "env", Value: standardEnvironment, Usage: "set environment variables for the process"},
},
}
@ -81,7 +47,7 @@ func execAction(context *cli.Context) {
if err != nil {
fatal(err)
}
tty.set(config)
config.Console = tty.console.Path()
if container, err = factory.Create(context.String("id"), config); err != nil {
fatal(err)
}
@ -89,6 +55,8 @@ func execAction(context *cli.Context) {
go handleSignals(container, tty)
process := &libcontainer.Process{
Args: context.Args(),
Env: context.StringSlice("env"),
User: context.String("user"),
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
@ -109,19 +77,7 @@ func execAction(context *cli.Context) {
if err := container.Destroy(); err != nil {
fatal(err)
}
exit(status.Sys().(syscall.WaitStatus))
}
func exit(status syscall.WaitStatus) {
var exitCode int
if status.Exited() {
exitCode = status.ExitStatus()
} else if status.Signaled() {
exitCode = -int(status.Signal())
} else {
fatalf("Unexpected status")
}
os.Exit(exitCode)
os.Exit(utils.ExitStatus(status.Sys().(syscall.WaitStatus)))
}
func handleSignals(container libcontainer.Container, tty *tty) {
@ -137,24 +93,3 @@ func handleSignals(container libcontainer.Container, tty *tty) {
}
}
}
func newTty(context *cli.Context) (*tty, error) {
if context.Bool("tty") {
master, console, err := consolepkg.CreateMasterAndConsole()
if err != nil {
return nil, err
}
go io.Copy(master, os.Stdin)
go io.Copy(os.Stdout, master)
state, err := term.SetRawTerminal(os.Stdin.Fd())
if err != nil {
return nil, err
}
return &tty{
master: master,
console: console,
state: state,
}, nil
}
return &tty{}, nil
}

64
nsinit/tty.go Normal file
View File

@ -0,0 +1,64 @@
package main
import (
"io"
"os"
"github.com/codegangsta/cli"
"github.com/docker/docker/pkg/term"
"github.com/docker/libcontainer"
)
func newTty(context *cli.Context) (*tty, error) {
if context.Bool("tty") {
console, err := libcontainer.NewConsole()
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, err
}
return &tty{
console: console,
state: state,
}, nil
}
return &tty{}, nil
}
type tty struct {
console libcontainer.Console
state *term.State
}
func (t *tty) Close() error {
if t.console != nil {
t.console.Close()
}
if t.state != nil {
term.RestoreTerminal(os.Stdin.Fd(), t.state)
}
return nil
}
func (t *tty) attach(process *libcontainer.Process) {
if t.console != nil {
process.Stderr = nil
process.Stdout = nil
process.Stdin = nil
}
}
func (t *tty) resize() error {
if t.console == nil {
return nil
}
ws, err := term.GetWinsize(os.Stdin.Fd())
if err != nil {
return err
}
return term.SetWinsize(t.console.Fd(), ws)
}