diff --git a/libcontainer/nsinit/command.go b/libcontainer/nsinit/command.go new file mode 100644 index 0000000..b1c5631 --- /dev/null +++ b/libcontainer/nsinit/command.go @@ -0,0 +1,34 @@ +package nsinit + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/libcontainer" + "os" + "os/exec" + "syscall" +) + +type CommandFactory interface { + Create(container *libcontainer.Container, console, logFile string, syncFd uintptr, args []string) *exec.Cmd +} + +type DefaultCommandFactory struct{} + +// Create will return an exec.Cmd with the Cloneflags set to the proper namespaces +// defined on the container's configuration and use the current binary as the init with the +// args provided +func (c *DefaultCommandFactory) Create(container *libcontainer.Container, console, logFile string, pipe uintptr, args []string) *exec.Cmd { + // get our binary name so we can always reexec ourself + name := os.Args[0] + command := exec.Command(name, append([]string{ + "-console", console, + "-pipe", fmt.Sprint(pipe), + "-log", logFile, + "init"}, args...)...) + + command.SysProcAttr = &syscall.SysProcAttr{ + Cloneflags: uintptr(GetNamespaceFlags(container.Namespaces)), + } + command.Env = container.Env + return command +} diff --git a/libcontainer/nsinit/exec.go b/libcontainer/nsinit/exec.go index ec75e9c..ee83f4f 100644 --- a/libcontainer/nsinit/exec.go +++ b/libcontainer/nsinit/exec.go @@ -3,13 +3,9 @@ package nsinit import ( - "fmt" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/system" - "github.com/dotcloud/docker/pkg/term" - "io" - "io/ioutil" "log" "os" "os/exec" @@ -18,9 +14,11 @@ import ( // Exec performes setup outside of a namespace so that a container can be // executed. Exec is a high level function for working with container namespaces. -func Exec(container *libcontainer.Container, stdin io.Reader, stdout, stderr io.Writer, - master *os.File, logFile string, args []string) (int, error) { +func Exec(container *libcontainer.Container, + factory CommandFactory, state StateWriter, term Terminal, + logFile string, args []string) (int, error) { var ( + master *os.File console string err error ) @@ -38,37 +36,28 @@ func Exec(container *libcontainer.Container, stdin io.Reader, stdout, stderr io. if err != nil { return -1, err } + term.SetMaster(master) } - command := CreateCommand(container, console, logFile, syncPipe.child.Fd(), args) - if container.Tty { - log.Printf("starting copy for tty") - go io.Copy(stdout, master) - go io.Copy(master, stdin) - - state, err := SetupWindow(master, os.Stdin) - if err != nil { - command.Process.Kill() - return -1, err - } - defer term.RestoreTerminal(os.Stdin.Fd(), state) - } else { - if err := startStdCopy(command, stdin, stdout, stderr); err != nil { - command.Process.Kill() - return -1, err - } + command := factory.Create(container, console, logFile, syncPipe.child.Fd(), args) + if err := term.Attach(command); err != nil { + return -1, err } + defer term.Close() log.Printf("staring init") if err := command.Start(); err != nil { return -1, err } log.Printf("writing state file") - if err := writePidFile(command); err != nil { + if err := state.WritePid(command.Process.Pid); err != nil { command.Process.Kill() return -1, err } - defer deletePidFile() + defer func() { + log.Printf("removing state file") + state.DeletePid() + }() // Do this before syncing with child so that no children // can escape the cgroup @@ -124,19 +113,6 @@ func InitializeNetworking(container *libcontainer.Container, nspid int, pipe *Sy return nil } -// SetupWindow gets the parent window size and sets the master -// pty to the current size and set the parents mode to RAW -func SetupWindow(master, parent *os.File) (*term.State, error) { - ws, err := term.GetWinsize(parent.Fd()) - if err != nil { - return nil, err - } - if err := term.SetWinsize(master.Fd(), ws); err != nil { - return nil, err - } - return term.SetRawTerminal(parent.Fd()) -} - // 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) { @@ -153,58 +129,3 @@ func CreateMasterAndConsole() (*os.File, string, error) { } return master, console, nil } - -// writePidFile writes the namespaced processes pid to .nspid in the rootfs for the container -func writePidFile(command *exec.Cmd) error { - return ioutil.WriteFile(".nspid", []byte(fmt.Sprint(command.Process.Pid)), 0655) -} - -func deletePidFile() error { - log.Printf("removing .nspid file") - return os.Remove(".nspid") -} - -// createCommand will return an exec.Cmd with the Cloneflags set to the proper namespaces -// defined on the container's configuration and use the current binary as the init with the -// args provided -func CreateCommand(container *libcontainer.Container, console, logFile string, pipe uintptr, args []string) *exec.Cmd { - // get our binary name so we can always reexec ourself - name := os.Args[0] - command := exec.Command(name, append([]string{ - "-console", console, - "-pipe", fmt.Sprint(pipe), - "-log", logFile, - "init"}, args...)...) - - command.SysProcAttr = &syscall.SysProcAttr{ - Cloneflags: uintptr(GetNamespaceFlags(container.Namespaces)), - } - command.Env = container.Env - return command -} - -func startStdCopy(command *exec.Cmd, stdin io.Reader, stdout, stderr io.Writer) error { - log.Printf("opening std pipes") - inPipe, err := command.StdinPipe() - if err != nil { - return err - } - outPipe, err := command.StdoutPipe() - if err != nil { - return err - } - errPipe, err := command.StderrPipe() - if err != nil { - return err - } - - log.Printf("starting copy for std pipes") - go func() { - defer inPipe.Close() - io.Copy(inPipe, stdin) - }() - go io.Copy(stdout, outPipe) - go io.Copy(stderr, errPipe) - - return nil -} diff --git a/libcontainer/nsinit/nsinit/main.go b/libcontainer/nsinit/nsinit/main.go index 2400ab6..c299412 100644 --- a/libcontainer/nsinit/nsinit/main.go +++ b/libcontainer/nsinit/nsinit/main.go @@ -57,8 +57,10 @@ func main() { if nspid > 0 { exitCode, err = nsinit.ExecIn(container, nspid, flag.Args()[1:]) } else { + term := nsinit.NewTerminal(os.Stdin, os.Stdout, os.Stderr, container.Tty) exitCode, err = nsinit.Exec(container, - os.Stdin, os.Stdout, os.Stderr, nil, + &nsinit.DefaultCommandFactory{}, &nsinit.DefaultStateWriter{}, + term, logFile, flag.Args()[1:]) } if err != nil { diff --git a/libcontainer/nsinit/state.go b/libcontainer/nsinit/state.go new file mode 100644 index 0000000..1f0fedd --- /dev/null +++ b/libcontainer/nsinit/state.go @@ -0,0 +1,24 @@ +package nsinit + +import ( + "fmt" + "io/ioutil" + "os" +) + +type StateWriter interface { + WritePid(pid int) error + DeletePid() error +} + +type DefaultStateWriter struct { +} + +// writePidFile writes the namespaced processes pid to .nspid in the rootfs for the container +func (*DefaultStateWriter) WritePid(pid int) error { + return ioutil.WriteFile(".nspid", []byte(fmt.Sprint(pid)), 0655) +} + +func (*DefaultStateWriter) DeletePid() error { + return os.Remove(".nspid") +} diff --git a/libcontainer/nsinit/term.go b/libcontainer/nsinit/term.go new file mode 100644 index 0000000..6492468 --- /dev/null +++ b/libcontainer/nsinit/term.go @@ -0,0 +1,109 @@ +package nsinit + +import ( + "github.com/dotcloud/docker/pkg/term" + "io" + "os" + "os/exec" +) + +type Terminal interface { + io.Closer + SetMaster(*os.File) + Attach(*exec.Cmd) error +} + +func NewTerminal(stdin io.Reader, stdout, stderr io.Writer, tty bool) Terminal { + if tty { + return &TtyTerminal{ + stdin: stdin, + stdout: stdout, + stderr: stderr, + } + } + return &StdTerminal{ + stdin: stdin, + stdout: stdout, + stderr: stderr, + } +} + +type TtyTerminal struct { + stdin io.Reader + stdout, stderr io.Writer + master *os.File + state *term.State +} + +func (t *TtyTerminal) SetMaster(master *os.File) { + t.master = master +} + +func (t *TtyTerminal) Attach(command *exec.Cmd) error { + go io.Copy(t.stdout, t.master) + go io.Copy(t.master, t.stdin) + + state, err := t.setupWindow(t.master, os.Stdin) + if err != nil { + command.Process.Kill() + return err + } + t.state = state + return err +} + +// SetupWindow gets the parent window size and sets the master +// pty to the current size and set the parents mode to RAW +func (t *TtyTerminal) setupWindow(master, parent *os.File) (*term.State, error) { + ws, err := term.GetWinsize(parent.Fd()) + if err != nil { + return nil, err + } + if err := term.SetWinsize(master.Fd(), ws); err != nil { + return nil, err + } + return term.SetRawTerminal(parent.Fd()) +} + +func (t *TtyTerminal) Close() error { + term.RestoreTerminal(os.Stdin.Fd(), t.state) + return t.master.Close() +} + +type StdTerminal struct { + stdin io.Reader + stdout, stderr io.Writer +} + +func (s *StdTerminal) SetMaster(*os.File) { + // no need to set master on non tty +} + +func (s *StdTerminal) Close() error { + return nil +} + +func (s *StdTerminal) Attach(command *exec.Cmd) error { + inPipe, err := command.StdinPipe() + if err != nil { + return err + } + outPipe, err := command.StdoutPipe() + if err != nil { + return err + } + errPipe, err := command.StderrPipe() + if err != nil { + return err + } + + go func() { + defer inPipe.Close() + io.Copy(inPipe, s.stdin) + }() + + go io.Copy(s.stdout, outPipe) + go io.Copy(s.stderr, errPipe) + + return nil +}