Browse Source

Only set the terminal in raw mode for commands which need it

The raw mode is actually only needed when you attach to a container.
Having it enabled all the time can be a pain, e.g: if docker crashes
your terminal will end up in a broken state.

Since we are currently missing a real API for the docker daemon to
negotiate this kind of options, this changeset actually enable the raw
mode on the login (because it outputs a password), run and attach
commands.

This "optional raw mode" is implemented by passing a more complicated
interface than io.Writer as the stdout argument of each command. This
interface (DockerConn) exposes a method which allows the command to set
the terminal in raw mode or not.

Finally, the code added by this changeset will be deprecated by a real
API for the docker daemon.
Louis Opter 12 years ago
parent
commit
7d0ab3858e
4 changed files with 237 additions and 55 deletions
  1. 32 28
      commands.go
  2. 73 21
      docker/docker.go
  3. 91 4
      rcli/tcp.go
  4. 41 2
      rcli/types.go

+ 32 - 28
commands.go

@@ -62,7 +62,7 @@ func (srv *Server) Help() string {
 }
 
 // 'docker login': login / register a user to registry service.
-func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	// Read a line on raw terminal with support for simple backspace
 	// sequences and echo.
 	//
@@ -71,7 +71,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin
 	// - we have to read a password (without echoing it);
 	// - the rcli "protocol" only supports cannonical and raw modes and you
 	//   can't tune it once the command as been started.
-	var readStringOnRawTerminal = func(stdin io.Reader, stdout io.Writer, echo bool) string {
+	var readStringOnRawTerminal = func(stdin io.Reader, stdout rcli.DockerConn, echo bool) string {
 		char := make([]byte, 1)
 		buffer := make([]byte, 64)
 		var i = 0
@@ -106,13 +106,15 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin
 		}
 		return string(buffer[:i])
 	}
-	var readAndEchoString = func(stdin io.Reader, stdout io.Writer) string {
+	var readAndEchoString = func(stdin io.Reader, stdout rcli.DockerConn) string {
 		return readStringOnRawTerminal(stdin, stdout, true)
 	}
-	var readString = func(stdin io.Reader, stdout io.Writer) string {
+	var readString = func(stdin io.Reader, stdout rcli.DockerConn) string {
 		return readStringOnRawTerminal(stdin, stdout, false)
 	}
 
+	stdout.SetOptionRawTerminal()
+
 	cmd := rcli.Subcmd(stdout, "login", "", "Register or Login to the docker registry server")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -158,7 +160,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin
 }
 
 // 'docker wait': block until a container stops
-func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdWait(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	cmd := rcli.Subcmd(stdout, "wait", "[OPTIONS] NAME", "Block until a container stops, then print its exit code.")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -178,14 +180,14 @@ func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string
 }
 
 // 'docker version': show version information
-func (srv *Server) CmdVersion(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdVersion(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	fmt.Fprintf(stdout, "Version:%s\n", VERSION)
 	fmt.Fprintf(stdout, "Git Commit:%s\n", GIT_COMMIT)
 	return nil
 }
 
 // 'docker info': display system-wide information.
-func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	images, _ := srv.runtime.graph.All()
 	var imgcount int
 	if images == nil {
@@ -214,7 +216,7 @@ func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string
 	return nil
 }
 
-func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdStop(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	cmd := rcli.Subcmd(stdout, "stop", "[OPTIONS] NAME", "Stop a running container")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -236,7 +238,7 @@ func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string
 	return nil
 }
 
-func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	cmd := rcli.Subcmd(stdout, "restart", "[OPTIONS] NAME", "Restart a running container")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -258,7 +260,7 @@ func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...str
 	return nil
 }
 
-func (srv *Server) CmdStart(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdStart(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	cmd := rcli.Subcmd(stdout, "start", "[OPTIONS] NAME", "Start a stopped container")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -280,7 +282,7 @@ func (srv *Server) CmdStart(stdin io.ReadCloser, stdout io.Writer, args ...strin
 	return nil
 }
 
-func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	cmd := rcli.Subcmd(stdout, "inspect", "[OPTIONS] CONTAINER", "Return low-level information on a container")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -315,7 +317,7 @@ func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...str
 	return nil
 }
 
-func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdPort(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	cmd := rcli.Subcmd(stdout, "port", "[OPTIONS] CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -339,7 +341,7 @@ func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string
 }
 
 // 'docker rmi NAME' removes all images with the name NAME
-func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string) (err error) {
+func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) (err error) {
 	cmd := rcli.Subcmd(stdout, "rmimage", "[OPTIONS] IMAGE", "Remove an image")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -356,7 +358,7 @@ func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string)
 	return nil
 }
 
-func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	cmd := rcli.Subcmd(stdout, "history", "[OPTIONS] IMAGE", "Show the history of an image")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -382,7 +384,7 @@ func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...str
 	})
 }
 
-func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdRm(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	cmd := rcli.Subcmd(stdout, "rm", "[OPTIONS] CONTAINER", "Remove a container")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -400,7 +402,7 @@ func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string)
 }
 
 // 'docker kill NAME' kills a running container
-func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdKill(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	cmd := rcli.Subcmd(stdout, "kill", "[OPTIONS] CONTAINER [CONTAINER...]", "Kill a running container")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -417,7 +419,7 @@ func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string
 	return nil
 }
 
-func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	cmd := rcli.Subcmd(stdout, "import", "[OPTIONS] URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball")
 	var archive io.Reader
 	var resp *http.Response
@@ -464,7 +466,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...stri
 	return nil
 }
 
-func (srv *Server) CmdPush(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	cmd := rcli.Subcmd(stdout, "push", "NAME", "Push an image or a repository to the registry")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -523,7 +525,7 @@ func (srv *Server) CmdPush(stdin io.ReadCloser, stdout io.Writer, args ...string
 	return nil
 }
 
-func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdPull(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	cmd := rcli.Subcmd(stdout, "pull", "NAME", "Pull an image or a repository from the registry")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -548,7 +550,7 @@ func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string
 	return nil
 }
 
-func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdImages(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	cmd := rcli.Subcmd(stdout, "images", "[OPTIONS] [NAME]", "List images")
 	//limit := cmd.Int("l", 0, "Only show the N most recent versions of each image")
 	quiet := cmd.Bool("q", false, "only show numeric IDs")
@@ -638,7 +640,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri
 	return nil
 }
 
-func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdPs(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	cmd := rcli.Subcmd(stdout,
 		"ps", "[OPTIONS]", "List containers")
 	quiet := cmd.Bool("q", false, "Only display numeric IDs")
@@ -685,7 +687,7 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string)
 	return nil
 }
 
-func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	cmd := rcli.Subcmd(stdout,
 		"commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]",
 		"Create a new image from a container's changes")
@@ -706,7 +708,7 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri
 	return nil
 }
 
-func (srv *Server) CmdExport(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdExport(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	cmd := rcli.Subcmd(stdout,
 		"export", "CONTAINER",
 		"Export the contents of a filesystem as a tar archive")
@@ -728,7 +730,7 @@ func (srv *Server) CmdExport(stdin io.ReadCloser, stdout io.Writer, args ...stri
 	return fmt.Errorf("No such container: %s", name)
 }
 
-func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	cmd := rcli.Subcmd(stdout,
 		"diff", "CONTAINER [OPTIONS]",
 		"Inspect changes on a container's filesystem")
@@ -752,7 +754,7 @@ func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string
 	return nil
 }
 
-func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	cmd := rcli.Subcmd(stdout, "logs", "[OPTIONS] CONTAINER", "Fetch the logs of a container")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -784,7 +786,8 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string
 	return fmt.Errorf("No such container: %s", cmd.Arg(0))
 }
 
-func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
+	stdout.SetOptionRawTerminal()
 	cmd := rcli.Subcmd(stdout, "attach", "CONTAINER", "Attach to a running container")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -857,7 +860,7 @@ func (opts AttachOpts) Get(val string) bool {
 	return false
 }
 
-func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdTag(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	cmd := rcli.Subcmd(stdout, "tag", "[OPTIONS] IMAGE REPOSITORY [TAG]", "Tag an image into a repository")
 	force := cmd.Bool("f", false, "Force")
 	if err := cmd.Parse(args); err != nil {
@@ -870,7 +873,8 @@ func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string)
 	return srv.runtime.repositories.Set(cmd.Arg(1), cmd.Arg(2), cmd.Arg(0), *force)
 }
 
-func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
+	stdout.SetOptionRawTerminal()
 	config, err := ParseRun(args, stdout)
 	if err != nil {
 		return err

+ 73 - 21
docker/docker.go

@@ -2,6 +2,7 @@ package main
 
 import (
 	"flag"
+	"fmt"
 	"github.com/dotcloud/docker"
 	"github.com/dotcloud/docker/rcli"
 	"github.com/dotcloud/docker/term"
@@ -56,30 +57,82 @@ func daemon() error {
 	return rcli.ListenAndServe("tcp", "127.0.0.1:4242", service)
 }
 
-func runCommand(args []string) error {
-	var oldState *term.State
-	var err error
-	if term.IsTerminal(int(os.Stdin.Fd())) && os.Getenv("NORAW") == "" {
-		oldState, err = term.MakeRaw(int(os.Stdin.Fd()))
-		if err != nil {
-			return err
+func setRawTerminal() (*term.State, error) {
+	oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
+	if err != nil {
+		return nil, err
+	}
+	c := make(chan os.Signal, 1)
+	signal.Notify(c, os.Interrupt)
+	go func() {
+		for _ = range c {
+			term.Restore(int(os.Stdin.Fd()), oldState)
+			log.Printf("\nSIGINT received\n")
+			os.Exit(0)
 		}
-		defer term.Restore(int(os.Stdin.Fd()), oldState)
-		c := make(chan os.Signal, 1)
-		signal.Notify(c, os.Interrupt)
-		go func() {
-			for _ = range c {
-				term.Restore(int(os.Stdin.Fd()), oldState)
-				log.Printf("\nSIGINT received\n")
-				os.Exit(0)
-			}
-		}()
+	}()
+	return oldState, err
+}
+
+func restoreTerminal(state *term.State) {
+	term.Restore(int(os.Stdin.Fd()), state)
+}
+
+type DockerLocalConn struct {
+	file       *os.File
+	savedState *term.State
+}
+
+func newDockerLocalConn(output *os.File) *DockerLocalConn {
+	return &DockerLocalConn{file: output}
+}
+
+func (c *DockerLocalConn) Read(b []byte) (int, error) { return c.file.Read(b) }
+
+func (c *DockerLocalConn) Write(b []byte) (int, error) { return c.file.Write(b) }
+
+func (c *DockerLocalConn) Close() error {
+	if c.savedState != nil {
+		restoreTerminal(c.savedState)
+		c.savedState = nil
+	}
+	return c.file.Close()
+}
+
+func (c *DockerLocalConn) CloseWrite() error { return nil }
+
+func (c *DockerLocalConn) CloseRead() error { return nil }
+
+func (c *DockerLocalConn) GetOptions() *rcli.DockerConnOptions { return nil }
+
+func (c *DockerLocalConn) SetOptionRawTerminal() {
+	if state, err := setRawTerminal(); err != nil {
+		fmt.Fprintf(
+			os.Stderr,
+			"Can't set the terminal in raw mode: %v",
+			err.Error(),
+		)
+	} else {
+		c.savedState = state
 	}
+}
+
+func runCommand(args []string) error {
 	// FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose
 	// CloseWrite(), which we need to cleanly signal that stdin is closed without
 	// closing the connection.
 	// See http://code.google.com/p/go/issues/detail?id=3345
 	if conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...); err == nil {
+		options := conn.GetOptions()
+		if options.RawTerminal &&
+			term.IsTerminal(int(os.Stdin.Fd())) &&
+			os.Getenv("NORAW") == "" {
+			if oldState, err := setRawTerminal(); err != nil {
+				return err
+			} else {
+				defer restoreTerminal(oldState)
+			}
+		}
 		receiveStdout := docker.Go(func() error {
 			_, err := io.Copy(os.Stdout, conn)
 			return err
@@ -104,12 +157,11 @@ func runCommand(args []string) error {
 		if err != nil {
 			return err
 		}
-		if err := rcli.LocalCall(service, os.Stdin, os.Stdout, args...); err != nil {
+		dockerConn := newDockerLocalConn(os.Stdout)
+		defer dockerConn.Close()
+		if err := rcli.LocalCall(service, os.Stdin, dockerConn, args...); err != nil {
 			return err
 		}
 	}
-	if oldState != nil {
-		term.Restore(int(os.Stdin.Fd()), oldState)
-	}
 	return nil
 }

+ 91 - 4
rcli/tcp.go

@@ -2,6 +2,7 @@ package rcli
 
 import (
 	"bufio"
+	"bytes"
 	"encoding/json"
 	"fmt"
 	"io"
@@ -15,22 +16,104 @@ import (
 var DEBUG_FLAG bool = false
 var CLIENT_SOCKET io.Writer = nil
 
+type DockerTCPConn struct {
+	conn       *net.TCPConn
+	options    *DockerConnOptions
+	optionsBuf *[]byte
+	handshaked bool
+	client     bool
+}
+
+func NewDockerTCPConn(conn *net.TCPConn, client bool) *DockerTCPConn {
+	return &DockerTCPConn{
+		conn:    conn,
+		options: &DockerConnOptions{},
+		client:  client,
+	}
+}
+
+func (c *DockerTCPConn) SetOptionRawTerminal() {
+	c.options.RawTerminal = true
+}
+
+func (c *DockerTCPConn) GetOptions() *DockerConnOptions {
+	if c.client && !c.handshaked {
+		// Attempt to parse options encoded as a JSON dict and store
+		// the reminder of what we read from the socket in a buffer.
+		//
+		// bufio (and its ReadBytes method) would have been nice here,
+		// but if json.Unmarshal() fails (which will happen if we speak
+		// to a version of docker that doesn't send any option), then
+		// we can't put the data back in it for the next Read().
+		c.handshaked = true
+		buf := make([]byte, 4096)
+		if n, _ := c.conn.Read(buf); n > 0 {
+			buf = buf[:n]
+			if nl := bytes.IndexByte(buf, '\n'); nl != -1 {
+				if err := json.Unmarshal(buf[:nl], c.options); err == nil {
+					buf = buf[nl+1:]
+				}
+			}
+			c.optionsBuf = &buf
+		}
+	}
+
+	return c.options
+}
+
+func (c *DockerTCPConn) Read(b []byte) (int, error) {
+	if c.optionsBuf != nil {
+		// Consume what we buffered in GetOptions() first:
+		optionsBuf := *c.optionsBuf
+		optionsBuflen := len(optionsBuf)
+		copied := copy(b, optionsBuf)
+		if copied < optionsBuflen {
+			optionsBuf = optionsBuf[copied:]
+			c.optionsBuf = &optionsBuf
+			return copied, nil
+		}
+		c.optionsBuf = nil
+		return copied, nil
+	}
+	return c.conn.Read(b)
+}
+
+func (c *DockerTCPConn) Write(b []byte) (int, error) {
+	optionsLen := 0
+	if !c.client && !c.handshaked {
+		c.handshaked = true
+		options, _ := json.Marshal(c.options)
+		options = append(options, '\n')
+		if optionsLen, err := c.conn.Write(options); err != nil {
+			return optionsLen, err
+		}
+	}
+	n, err := c.conn.Write(b)
+	return n + optionsLen, err
+}
+
+func (c *DockerTCPConn) Close() error { return c.conn.Close() }
+
+func (c *DockerTCPConn) CloseWrite() error { return c.conn.CloseWrite() }
+
+func (c *DockerTCPConn) CloseRead() error { return c.conn.CloseRead() }
+
 // Connect to a remote endpoint using protocol `proto` and address `addr`,
 // issue a single call, and return the result.
 // `proto` may be "tcp", "unix", etc. See the `net` package for available protocols.
-func Call(proto, addr string, args ...string) (*net.TCPConn, error) {
+func Call(proto, addr string, args ...string) (DockerConn, error) {
 	cmd, err := json.Marshal(args)
 	if err != nil {
 		return nil, err
 	}
-	conn, err := net.Dial(proto, addr)
+	conn, err := dialDocker(proto, addr)
 	if err != nil {
 		return nil, err
 	}
 	if _, err := fmt.Fprintln(conn, string(cmd)); err != nil {
 		return nil, err
 	}
-	return conn.(*net.TCPConn), nil
+	return conn, nil
 }
 
 // Listen on `addr`, using protocol `proto`, for incoming rcli calls,
@@ -46,6 +129,10 @@ func ListenAndServe(proto, addr string, service Service) error {
 		if conn, err := listener.Accept(); err != nil {
 			return err
 		} else {
+			conn, err := newDockerServerConn(conn)
+			if err != nil {
+				return err
+			}
 			go func() {
 				if DEBUG_FLAG {
 					CLIENT_SOCKET = conn
@@ -63,7 +150,7 @@ func ListenAndServe(proto, addr string, service Service) error {
 
 // Parse an rcli call on a new connection, and pass it to `service` if it
 // is valid.
-func Serve(conn io.ReadWriter, service Service) error {
+func Serve(conn DockerConn, service Service) error {
 	r := bufio.NewReader(conn)
 	var args []string
 	if line, err := r.ReadString('\n'); err != nil {

+ 41 - 2
rcli/types.go

@@ -13,10 +13,49 @@ import (
 	"fmt"
 	"io"
 	"log"
+	"net"
 	"reflect"
 	"strings"
 )
 
+type DockerConnOptions struct {
+	RawTerminal bool
+}
+
+type DockerConn interface {
+	io.ReadWriteCloser
+	CloseWrite() error
+	CloseRead() error
+	GetOptions() *DockerConnOptions
+	SetOptionRawTerminal()
+}
+
+var UnknownDockerProto = errors.New("Only TCP is actually supported by Docker at the moment")
+
+func dialDocker(proto string, addr string) (DockerConn, error) {
+	conn, err := net.Dial(proto, addr)
+	if err != nil {
+		return nil, err
+	}
+	switch i := conn.(type) {
+	case *net.TCPConn:
+		return NewDockerTCPConn(i, true), nil
+	}
+	return nil, UnknownDockerProto
+}
+
+func newDockerFromConn(conn net.Conn, client bool) (DockerConn, error) {
+	switch i := conn.(type) {
+	case *net.TCPConn:
+		return NewDockerTCPConn(i, client), nil
+	}
+	return nil, UnknownDockerProto
+}
+
+func newDockerServerConn(conn net.Conn) (DockerConn, error) {
+	return newDockerFromConn(conn, false)
+}
+
 type Service interface {
 	Name() string
 	Help() string
@@ -26,11 +65,11 @@ type Cmd func(io.ReadCloser, io.Writer, ...string) error
 type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error
 
 // FIXME: For reverse compatibility
-func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func call(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
 	return LocalCall(service, stdin, stdout, args...)
 }
 
-func LocalCall(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func LocalCall(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
 	if len(args) == 0 {
 		args = []string{"help"}
 	}