package libcontainer import ( "fmt" "os" "path/filepath" "syscall" "unsafe" "github.com/opencontainers/runc/libcontainer/label" ) // NewConsole returns an initalized 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. func NewConsole(uid, gid int) (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 } if err := os.Chmod(console, 0600); err != nil { return nil, err } if err := os.Chown(console, uid, gid); err != nil { return nil, err } return &linuxConsole{ slavePath: console, master: master, }, 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 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) error { oldMask := syscall.Umask(0000) defer syscall.Umask(oldMask) 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 dups 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.Dup3(fd, i, 0); 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 }