Browse code

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 authored on 2013/04/04 04:25:19
Showing 4 changed files
... ...
@@ -62,7 +62,7 @@ func (srv *Server) Help() string {
62 62
 }
63 63
 
64 64
 // 'docker login': login / register a user to registry service.
65
-func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
65
+func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
66 66
 	// Read a line on raw terminal with support for simple backspace
67 67
 	// sequences and echo.
68 68
 	//
... ...
@@ -71,7 +71,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin
71 71
 	// - we have to read a password (without echoing it);
72 72
 	// - the rcli "protocol" only supports cannonical and raw modes and you
73 73
 	//   can't tune it once the command as been started.
74
-	var readStringOnRawTerminal = func(stdin io.Reader, stdout io.Writer, echo bool) string {
74
+	var readStringOnRawTerminal = func(stdin io.Reader, stdout rcli.DockerConn, echo bool) string {
75 75
 		char := make([]byte, 1)
76 76
 		buffer := make([]byte, 64)
77 77
 		var i = 0
... ...
@@ -106,13 +106,15 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin
106 106
 		}
107 107
 		return string(buffer[:i])
108 108
 	}
109
-	var readAndEchoString = func(stdin io.Reader, stdout io.Writer) string {
109
+	var readAndEchoString = func(stdin io.Reader, stdout rcli.DockerConn) string {
110 110
 		return readStringOnRawTerminal(stdin, stdout, true)
111 111
 	}
112
-	var readString = func(stdin io.Reader, stdout io.Writer) string {
112
+	var readString = func(stdin io.Reader, stdout rcli.DockerConn) string {
113 113
 		return readStringOnRawTerminal(stdin, stdout, false)
114 114
 	}
115 115
 
116
+	stdout.SetOptionRawTerminal()
117
+
116 118
 	cmd := rcli.Subcmd(stdout, "login", "", "Register or Login to the docker registry server")
117 119
 	if err := cmd.Parse(args); err != nil {
118 120
 		return nil
... ...
@@ -158,7 +160,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin
158 158
 }
159 159
 
160 160
 // 'docker wait': block until a container stops
161
-func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
161
+func (srv *Server) CmdWait(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
162 162
 	cmd := rcli.Subcmd(stdout, "wait", "[OPTIONS] NAME", "Block until a container stops, then print its exit code.")
163 163
 	if err := cmd.Parse(args); err != nil {
164 164
 		return nil
... ...
@@ -178,14 +180,14 @@ func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string
178 178
 }
179 179
 
180 180
 // 'docker version': show version information
181
-func (srv *Server) CmdVersion(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
181
+func (srv *Server) CmdVersion(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
182 182
 	fmt.Fprintf(stdout, "Version:%s\n", VERSION)
183 183
 	fmt.Fprintf(stdout, "Git Commit:%s\n", GIT_COMMIT)
184 184
 	return nil
185 185
 }
186 186
 
187 187
 // 'docker info': display system-wide information.
188
-func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
188
+func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
189 189
 	images, _ := srv.runtime.graph.All()
190 190
 	var imgcount int
191 191
 	if images == nil {
... ...
@@ -214,7 +216,7 @@ func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string
214 214
 	return nil
215 215
 }
216 216
 
217
-func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
217
+func (srv *Server) CmdStop(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
218 218
 	cmd := rcli.Subcmd(stdout, "stop", "[OPTIONS] NAME", "Stop a running container")
219 219
 	if err := cmd.Parse(args); err != nil {
220 220
 		return nil
... ...
@@ -236,7 +238,7 @@ func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string
236 236
 	return nil
237 237
 }
238 238
 
239
-func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
239
+func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
240 240
 	cmd := rcli.Subcmd(stdout, "restart", "[OPTIONS] NAME", "Restart a running container")
241 241
 	if err := cmd.Parse(args); err != nil {
242 242
 		return nil
... ...
@@ -258,7 +260,7 @@ func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...str
258 258
 	return nil
259 259
 }
260 260
 
261
-func (srv *Server) CmdStart(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
261
+func (srv *Server) CmdStart(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
262 262
 	cmd := rcli.Subcmd(stdout, "start", "[OPTIONS] NAME", "Start a stopped container")
263 263
 	if err := cmd.Parse(args); err != nil {
264 264
 		return nil
... ...
@@ -280,7 +282,7 @@ func (srv *Server) CmdStart(stdin io.ReadCloser, stdout io.Writer, args ...strin
280 280
 	return nil
281 281
 }
282 282
 
283
-func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
283
+func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
284 284
 	cmd := rcli.Subcmd(stdout, "inspect", "[OPTIONS] CONTAINER", "Return low-level information on a container")
285 285
 	if err := cmd.Parse(args); err != nil {
286 286
 		return nil
... ...
@@ -315,7 +317,7 @@ func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...str
315 315
 	return nil
316 316
 }
317 317
 
318
-func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
318
+func (srv *Server) CmdPort(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
319 319
 	cmd := rcli.Subcmd(stdout, "port", "[OPTIONS] CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT")
320 320
 	if err := cmd.Parse(args); err != nil {
321 321
 		return nil
... ...
@@ -339,7 +341,7 @@ func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string
339 339
 }
340 340
 
341 341
 // 'docker rmi NAME' removes all images with the name NAME
342
-func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string) (err error) {
342
+func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) (err error) {
343 343
 	cmd := rcli.Subcmd(stdout, "rmimage", "[OPTIONS] IMAGE", "Remove an image")
344 344
 	if err := cmd.Parse(args); err != nil {
345 345
 		return nil
... ...
@@ -356,7 +358,7 @@ func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string)
356 356
 	return nil
357 357
 }
358 358
 
359
-func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
359
+func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
360 360
 	cmd := rcli.Subcmd(stdout, "history", "[OPTIONS] IMAGE", "Show the history of an image")
361 361
 	if err := cmd.Parse(args); err != nil {
362 362
 		return nil
... ...
@@ -382,7 +384,7 @@ func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...str
382 382
 	})
383 383
 }
384 384
 
385
-func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
385
+func (srv *Server) CmdRm(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
386 386
 	cmd := rcli.Subcmd(stdout, "rm", "[OPTIONS] CONTAINER", "Remove a container")
387 387
 	if err := cmd.Parse(args); err != nil {
388 388
 		return nil
... ...
@@ -400,7 +402,7 @@ func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string)
400 400
 }
401 401
 
402 402
 // 'docker kill NAME' kills a running container
403
-func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
403
+func (srv *Server) CmdKill(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
404 404
 	cmd := rcli.Subcmd(stdout, "kill", "[OPTIONS] CONTAINER [CONTAINER...]", "Kill a running container")
405 405
 	if err := cmd.Parse(args); err != nil {
406 406
 		return nil
... ...
@@ -417,7 +419,7 @@ func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string
417 417
 	return nil
418 418
 }
419 419
 
420
-func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
420
+func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
421 421
 	cmd := rcli.Subcmd(stdout, "import", "[OPTIONS] URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball")
422 422
 	var archive io.Reader
423 423
 	var resp *http.Response
... ...
@@ -464,7 +466,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...stri
464 464
 	return nil
465 465
 }
466 466
 
467
-func (srv *Server) CmdPush(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
467
+func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
468 468
 	cmd := rcli.Subcmd(stdout, "push", "NAME", "Push an image or a repository to the registry")
469 469
 	if err := cmd.Parse(args); err != nil {
470 470
 		return nil
... ...
@@ -523,7 +525,7 @@ func (srv *Server) CmdPush(stdin io.ReadCloser, stdout io.Writer, args ...string
523 523
 	return nil
524 524
 }
525 525
 
526
-func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
526
+func (srv *Server) CmdPull(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
527 527
 	cmd := rcli.Subcmd(stdout, "pull", "NAME", "Pull an image or a repository from the registry")
528 528
 	if err := cmd.Parse(args); err != nil {
529 529
 		return nil
... ...
@@ -548,7 +550,7 @@ func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string
548 548
 	return nil
549 549
 }
550 550
 
551
-func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
551
+func (srv *Server) CmdImages(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
552 552
 	cmd := rcli.Subcmd(stdout, "images", "[OPTIONS] [NAME]", "List images")
553 553
 	//limit := cmd.Int("l", 0, "Only show the N most recent versions of each image")
554 554
 	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
638 638
 	return nil
639 639
 }
640 640
 
641
-func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
641
+func (srv *Server) CmdPs(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
642 642
 	cmd := rcli.Subcmd(stdout,
643 643
 		"ps", "[OPTIONS]", "List containers")
644 644
 	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)
685 685
 	return nil
686 686
 }
687 687
 
688
-func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
688
+func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
689 689
 	cmd := rcli.Subcmd(stdout,
690 690
 		"commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]",
691 691
 		"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
706 706
 	return nil
707 707
 }
708 708
 
709
-func (srv *Server) CmdExport(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
709
+func (srv *Server) CmdExport(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
710 710
 	cmd := rcli.Subcmd(stdout,
711 711
 		"export", "CONTAINER",
712 712
 		"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
728 728
 	return fmt.Errorf("No such container: %s", name)
729 729
 }
730 730
 
731
-func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
731
+func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
732 732
 	cmd := rcli.Subcmd(stdout,
733 733
 		"diff", "CONTAINER [OPTIONS]",
734 734
 		"Inspect changes on a container's filesystem")
... ...
@@ -752,7 +754,7 @@ func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string
752 752
 	return nil
753 753
 }
754 754
 
755
-func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
755
+func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
756 756
 	cmd := rcli.Subcmd(stdout, "logs", "[OPTIONS] CONTAINER", "Fetch the logs of a container")
757 757
 	if err := cmd.Parse(args); err != nil {
758 758
 		return nil
... ...
@@ -784,7 +786,8 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string
784 784
 	return fmt.Errorf("No such container: %s", cmd.Arg(0))
785 785
 }
786 786
 
787
-func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
787
+func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
788
+	stdout.SetOptionRawTerminal()
788 789
 	cmd := rcli.Subcmd(stdout, "attach", "CONTAINER", "Attach to a running container")
789 790
 	if err := cmd.Parse(args); err != nil {
790 791
 		return nil
... ...
@@ -857,7 +860,7 @@ func (opts AttachOpts) Get(val string) bool {
857 857
 	return false
858 858
 }
859 859
 
860
-func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
860
+func (srv *Server) CmdTag(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
861 861
 	cmd := rcli.Subcmd(stdout, "tag", "[OPTIONS] IMAGE REPOSITORY [TAG]", "Tag an image into a repository")
862 862
 	force := cmd.Bool("f", false, "Force")
863 863
 	if err := cmd.Parse(args); err != nil {
... ...
@@ -870,7 +873,8 @@ func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string)
870 870
 	return srv.runtime.repositories.Set(cmd.Arg(1), cmd.Arg(2), cmd.Arg(0), *force)
871 871
 }
872 872
 
873
-func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
873
+func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
874
+	stdout.SetOptionRawTerminal()
874 875
 	config, err := ParseRun(args, stdout)
875 876
 	if err != nil {
876 877
 		return err
... ...
@@ -2,6 +2,7 @@ package main
2 2
 
3 3
 import (
4 4
 	"flag"
5
+	"fmt"
5 6
 	"github.com/dotcloud/docker"
6 7
 	"github.com/dotcloud/docker/rcli"
7 8
 	"github.com/dotcloud/docker/term"
... ...
@@ -56,30 +57,82 @@ func daemon() error {
56 56
 	return rcli.ListenAndServe("tcp", "127.0.0.1:4242", service)
57 57
 }
58 58
 
59
-func runCommand(args []string) error {
60
-	var oldState *term.State
61
-	var err error
62
-	if term.IsTerminal(int(os.Stdin.Fd())) && os.Getenv("NORAW") == "" {
63
-		oldState, err = term.MakeRaw(int(os.Stdin.Fd()))
64
-		if err != nil {
65
-			return err
59
+func setRawTerminal() (*term.State, error) {
60
+	oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
61
+	if err != nil {
62
+		return nil, err
63
+	}
64
+	c := make(chan os.Signal, 1)
65
+	signal.Notify(c, os.Interrupt)
66
+	go func() {
67
+		for _ = range c {
68
+			term.Restore(int(os.Stdin.Fd()), oldState)
69
+			log.Printf("\nSIGINT received\n")
70
+			os.Exit(0)
66 71
 		}
67
-		defer term.Restore(int(os.Stdin.Fd()), oldState)
68
-		c := make(chan os.Signal, 1)
69
-		signal.Notify(c, os.Interrupt)
70
-		go func() {
71
-			for _ = range c {
72
-				term.Restore(int(os.Stdin.Fd()), oldState)
73
-				log.Printf("\nSIGINT received\n")
74
-				os.Exit(0)
75
-			}
76
-		}()
72
+	}()
73
+	return oldState, err
74
+}
75
+
76
+func restoreTerminal(state *term.State) {
77
+	term.Restore(int(os.Stdin.Fd()), state)
78
+}
79
+
80
+type DockerLocalConn struct {
81
+	file       *os.File
82
+	savedState *term.State
83
+}
84
+
85
+func newDockerLocalConn(output *os.File) *DockerLocalConn {
86
+	return &DockerLocalConn{file: output}
87
+}
88
+
89
+func (c *DockerLocalConn) Read(b []byte) (int, error) { return c.file.Read(b) }
90
+
91
+func (c *DockerLocalConn) Write(b []byte) (int, error) { return c.file.Write(b) }
92
+
93
+func (c *DockerLocalConn) Close() error {
94
+	if c.savedState != nil {
95
+		restoreTerminal(c.savedState)
96
+		c.savedState = nil
97
+	}
98
+	return c.file.Close()
99
+}
100
+
101
+func (c *DockerLocalConn) CloseWrite() error { return nil }
102
+
103
+func (c *DockerLocalConn) CloseRead() error { return nil }
104
+
105
+func (c *DockerLocalConn) GetOptions() *rcli.DockerConnOptions { return nil }
106
+
107
+func (c *DockerLocalConn) SetOptionRawTerminal() {
108
+	if state, err := setRawTerminal(); err != nil {
109
+		fmt.Fprintf(
110
+			os.Stderr,
111
+			"Can't set the terminal in raw mode: %v",
112
+			err.Error(),
113
+		)
114
+	} else {
115
+		c.savedState = state
77 116
 	}
117
+}
118
+
119
+func runCommand(args []string) error {
78 120
 	// FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose
79 121
 	// CloseWrite(), which we need to cleanly signal that stdin is closed without
80 122
 	// closing the connection.
81 123
 	// See http://code.google.com/p/go/issues/detail?id=3345
82 124
 	if conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...); err == nil {
125
+		options := conn.GetOptions()
126
+		if options.RawTerminal &&
127
+			term.IsTerminal(int(os.Stdin.Fd())) &&
128
+			os.Getenv("NORAW") == "" {
129
+			if oldState, err := setRawTerminal(); err != nil {
130
+				return err
131
+			} else {
132
+				defer restoreTerminal(oldState)
133
+			}
134
+		}
83 135
 		receiveStdout := docker.Go(func() error {
84 136
 			_, err := io.Copy(os.Stdout, conn)
85 137
 			return err
... ...
@@ -104,12 +157,11 @@ func runCommand(args []string) error {
104 104
 		if err != nil {
105 105
 			return err
106 106
 		}
107
-		if err := rcli.LocalCall(service, os.Stdin, os.Stdout, args...); err != nil {
107
+		dockerConn := newDockerLocalConn(os.Stdout)
108
+		defer dockerConn.Close()
109
+		if err := rcli.LocalCall(service, os.Stdin, dockerConn, args...); err != nil {
108 110
 			return err
109 111
 		}
110 112
 	}
111
-	if oldState != nil {
112
-		term.Restore(int(os.Stdin.Fd()), oldState)
113
-	}
114 113
 	return nil
115 114
 }
... ...
@@ -2,6 +2,7 @@ package rcli
2 2
 
3 3
 import (
4 4
 	"bufio"
5
+	"bytes"
5 6
 	"encoding/json"
6 7
 	"fmt"
7 8
 	"io"
... ...
@@ -15,22 +16,104 @@ import (
15 15
 var DEBUG_FLAG bool = false
16 16
 var CLIENT_SOCKET io.Writer = nil
17 17
 
18
+type DockerTCPConn struct {
19
+	conn       *net.TCPConn
20
+	options    *DockerConnOptions
21
+	optionsBuf *[]byte
22
+	handshaked bool
23
+	client     bool
24
+}
25
+
26
+func NewDockerTCPConn(conn *net.TCPConn, client bool) *DockerTCPConn {
27
+	return &DockerTCPConn{
28
+		conn:    conn,
29
+		options: &DockerConnOptions{},
30
+		client:  client,
31
+	}
32
+}
33
+
34
+func (c *DockerTCPConn) SetOptionRawTerminal() {
35
+	c.options.RawTerminal = true
36
+}
37
+
38
+func (c *DockerTCPConn) GetOptions() *DockerConnOptions {
39
+	if c.client && !c.handshaked {
40
+		// Attempt to parse options encoded as a JSON dict and store
41
+		// the reminder of what we read from the socket in a buffer.
42
+		//
43
+		// bufio (and its ReadBytes method) would have been nice here,
44
+		// but if json.Unmarshal() fails (which will happen if we speak
45
+		// to a version of docker that doesn't send any option), then
46
+		// we can't put the data back in it for the next Read().
47
+		c.handshaked = true
48
+		buf := make([]byte, 4096)
49
+		if n, _ := c.conn.Read(buf); n > 0 {
50
+			buf = buf[:n]
51
+			if nl := bytes.IndexByte(buf, '\n'); nl != -1 {
52
+				if err := json.Unmarshal(buf[:nl], c.options); err == nil {
53
+					buf = buf[nl+1:]
54
+				}
55
+			}
56
+			c.optionsBuf = &buf
57
+		}
58
+	}
59
+
60
+	return c.options
61
+}
62
+
63
+func (c *DockerTCPConn) Read(b []byte) (int, error) {
64
+	if c.optionsBuf != nil {
65
+		// Consume what we buffered in GetOptions() first:
66
+		optionsBuf := *c.optionsBuf
67
+		optionsBuflen := len(optionsBuf)
68
+		copied := copy(b, optionsBuf)
69
+		if copied < optionsBuflen {
70
+			optionsBuf = optionsBuf[copied:]
71
+			c.optionsBuf = &optionsBuf
72
+			return copied, nil
73
+		}
74
+		c.optionsBuf = nil
75
+		return copied, nil
76
+	}
77
+	return c.conn.Read(b)
78
+}
79
+
80
+func (c *DockerTCPConn) Write(b []byte) (int, error) {
81
+	optionsLen := 0
82
+	if !c.client && !c.handshaked {
83
+		c.handshaked = true
84
+		options, _ := json.Marshal(c.options)
85
+		options = append(options, '\n')
86
+		if optionsLen, err := c.conn.Write(options); err != nil {
87
+			return optionsLen, err
88
+		}
89
+	}
90
+	n, err := c.conn.Write(b)
91
+	return n + optionsLen, err
92
+}
93
+
94
+func (c *DockerTCPConn) Close() error { return c.conn.Close() }
95
+
96
+func (c *DockerTCPConn) CloseWrite() error { return c.conn.CloseWrite() }
97
+
98
+func (c *DockerTCPConn) CloseRead() error { return c.conn.CloseRead() }
99
+
18 100
 // Connect to a remote endpoint using protocol `proto` and address `addr`,
19 101
 // issue a single call, and return the result.
20 102
 // `proto` may be "tcp", "unix", etc. See the `net` package for available protocols.
21
-func Call(proto, addr string, args ...string) (*net.TCPConn, error) {
103
+func Call(proto, addr string, args ...string) (DockerConn, error) {
22 104
 	cmd, err := json.Marshal(args)
23 105
 	if err != nil {
24 106
 		return nil, err
25 107
 	}
26
-	conn, err := net.Dial(proto, addr)
108
+	conn, err := dialDocker(proto, addr)
27 109
 	if err != nil {
28 110
 		return nil, err
29 111
 	}
30 112
 	if _, err := fmt.Fprintln(conn, string(cmd)); err != nil {
31 113
 		return nil, err
32 114
 	}
33
-	return conn.(*net.TCPConn), nil
115
+	return conn, nil
34 116
 }
35 117
 
36 118
 // Listen on `addr`, using protocol `proto`, for incoming rcli calls,
... ...
@@ -46,6 +129,10 @@ func ListenAndServe(proto, addr string, service Service) error {
46 46
 		if conn, err := listener.Accept(); err != nil {
47 47
 			return err
48 48
 		} else {
49
+			conn, err := newDockerServerConn(conn)
50
+			if err != nil {
51
+				return err
52
+			}
49 53
 			go func() {
50 54
 				if DEBUG_FLAG {
51 55
 					CLIENT_SOCKET = conn
... ...
@@ -63,7 +150,7 @@ func ListenAndServe(proto, addr string, service Service) error {
63 63
 
64 64
 // Parse an rcli call on a new connection, and pass it to `service` if it
65 65
 // is valid.
66
-func Serve(conn io.ReadWriter, service Service) error {
66
+func Serve(conn DockerConn, service Service) error {
67 67
 	r := bufio.NewReader(conn)
68 68
 	var args []string
69 69
 	if line, err := r.ReadString('\n'); err != nil {
... ...
@@ -13,10 +13,49 @@ import (
13 13
 	"fmt"
14 14
 	"io"
15 15
 	"log"
16
+	"net"
16 17
 	"reflect"
17 18
 	"strings"
18 19
 )
19 20
 
21
+type DockerConnOptions struct {
22
+	RawTerminal bool
23
+}
24
+
25
+type DockerConn interface {
26
+	io.ReadWriteCloser
27
+	CloseWrite() error
28
+	CloseRead() error
29
+	GetOptions() *DockerConnOptions
30
+	SetOptionRawTerminal()
31
+}
32
+
33
+var UnknownDockerProto = errors.New("Only TCP is actually supported by Docker at the moment")
34
+
35
+func dialDocker(proto string, addr string) (DockerConn, error) {
36
+	conn, err := net.Dial(proto, addr)
37
+	if err != nil {
38
+		return nil, err
39
+	}
40
+	switch i := conn.(type) {
41
+	case *net.TCPConn:
42
+		return NewDockerTCPConn(i, true), nil
43
+	}
44
+	return nil, UnknownDockerProto
45
+}
46
+
47
+func newDockerFromConn(conn net.Conn, client bool) (DockerConn, error) {
48
+	switch i := conn.(type) {
49
+	case *net.TCPConn:
50
+		return NewDockerTCPConn(i, client), nil
51
+	}
52
+	return nil, UnknownDockerProto
53
+}
54
+
55
+func newDockerServerConn(conn net.Conn) (DockerConn, error) {
56
+	return newDockerFromConn(conn, false)
57
+}
58
+
20 59
 type Service interface {
21 60
 	Name() string
22 61
 	Help() string
... ...
@@ -26,11 +65,11 @@ type Cmd func(io.ReadCloser, io.Writer, ...string) error
26 26
 type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error
27 27
 
28 28
 // FIXME: For reverse compatibility
29
-func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error {
29
+func call(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
30 30
 	return LocalCall(service, stdin, stdout, args...)
31 31
 }
32 32
 
33
-func LocalCall(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error {
33
+func LocalCall(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
34 34
 	if len(args) == 0 {
35 35
 		args = []string{"help"}
36 36
 	}