Browse code

Merge github.com:dotcloud/docker into 333-redis-documentation

John Costa authored on 2013/04/10 05:07:40
Showing 27 changed files
... ...
@@ -134,6 +134,12 @@ docker pull base
134 134
 docker run -i -t base /bin/bash
135 135
 ```
136 136
 
137
+Detaching from the interactive shell
138
+------------------------------------
139
+```
140
+# In order to detach without killing the shell, you can use the escape sequence Ctrl-p + Ctrl-q
141
+# Note: this works only in tty mode (run with -t option).
142
+```
137 143
 
138 144
 Starting a long-running worker process
139 145
 --------------------------------------
... ...
@@ -183,7 +189,9 @@ JOB=$(docker run -d -p 4444 base /bin/nc -l -p 4444)
183 183
 PORT=$(docker port $JOB 4444)
184 184
 
185 185
 # Connect to the public port via the host's public address
186
-echo hello world | nc $(hostname) $PORT
186
+# Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
187
+IP=$(ifconfig eth0 | perl -n -e 'if (m/inet addr:([\d\.]+)/g) { print $1 }')
188
+echo hello world | nc $IP $PORT
187 189
 
188 190
 # Verify that the network connection worked
189 191
 echo "Daemon received: $(docker logs $JOB)"
... ...
@@ -18,7 +18,7 @@ import (
18 18
 	"unicode"
19 19
 )
20 20
 
21
-const VERSION = "0.1.3"
21
+const VERSION = "0.1.4"
22 22
 
23 23
 var GIT_COMMIT string
24 24
 
... ...
@@ -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
 	//
... ...
@@ -113,6 +113,8 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin
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
... ...
@@ -417,7 +419,8 @@ 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
+	stdout.Flush()
421 422
 	cmd := rcli.Subcmd(stdout, "import", "[OPTIONS] URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball")
422 423
 	var archive io.Reader
423 424
 	var resp *http.Response
... ...
@@ -464,7 +467,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
... ...
@@ -784,7 +787,7 @@ 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 788
 	cmd := rcli.Subcmd(stdout, "attach", "CONTAINER", "Attach to a running container")
789 789
 	if err := cmd.Parse(args); err != nil {
790 790
 		return nil
... ...
@@ -799,6 +802,11 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...stri
799 799
 		return fmt.Errorf("No such container: %s", name)
800 800
 	}
801 801
 
802
+	if container.Config.Tty {
803
+		stdout.SetOptionRawTerminal()
804
+	}
805
+	// Flush the options to make sure the client sets the raw mode
806
+	stdout.Flush()
802 807
 	return <-container.Attach(stdin, nil, stdout, stdout)
803 808
 }
804 809
 
... ...
@@ -870,7 +878,7 @@ 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 874
 	config, err := ParseRun(args, stdout)
875 875
 	if err != nil {
876 876
 		return err
... ...
@@ -884,6 +892,13 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
884 884
 		return fmt.Errorf("Command not specified")
885 885
 	}
886 886
 
887
+	if config.Tty {
888
+		stdout.SetOptionRawTerminal()
889
+	}
890
+	// Flush the options to make sure the client sets the raw mode
891
+	// or tell the client there is no options
892
+	stdout.Flush()
893
+
887 894
 	// Create new container
888 895
 	container, err := srv.runtime.Create(config)
889 896
 	if err != nil {
... ...
@@ -2,8 +2,8 @@ package docker
2 2
 
3 3
 import (
4 4
 	"bufio"
5
-	"bytes"
6 5
 	"fmt"
6
+	"github.com/dotcloud/docker/rcli"
7 7
 	"io"
8 8
 	"io/ioutil"
9 9
 	"strings"
... ...
@@ -69,15 +69,27 @@ func TestRunHostname(t *testing.T) {
69 69
 
70 70
 	srv := &Server{runtime: runtime}
71 71
 
72
-	var stdin, stdout bytes.Buffer
73
-	setTimeout(t, "CmdRun timed out", 2*time.Second, func() {
74
-		if err := srv.CmdRun(ioutil.NopCloser(&stdin), &nopWriteCloser{&stdout}, "-h", "foobar", GetTestImage(runtime).Id, "hostname"); err != nil {
72
+	stdin, _ := io.Pipe()
73
+	stdout, stdoutPipe := io.Pipe()
74
+
75
+	c := make(chan struct{})
76
+	go func() {
77
+		if err := srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-h", "foobar", GetTestImage(runtime).Id, "hostname"); err != nil {
75 78
 			t.Fatal(err)
76 79
 		}
77
-	})
78
-	if output := string(stdout.Bytes()); output != "foobar\n" {
79
-		t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", output)
80
+		close(c)
81
+	}()
82
+	cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
83
+	if err != nil {
84
+		t.Fatal(err)
85
+	}
86
+	if cmdOutput != "foobar\n" {
87
+		t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", cmdOutput)
80 88
 	}
89
+
90
+	setTimeout(t, "CmdRun timed out", 2*time.Second, func() {
91
+		<-c
92
+	})
81 93
 }
82 94
 
83 95
 func TestRunExit(t *testing.T) {
... ...
@@ -93,7 +105,7 @@ func TestRunExit(t *testing.T) {
93 93
 	stdout, stdoutPipe := io.Pipe()
94 94
 	c1 := make(chan struct{})
95 95
 	go func() {
96
-		srv.CmdRun(stdin, stdoutPipe, "-i", GetTestImage(runtime).Id, "/bin/cat")
96
+		srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", GetTestImage(runtime).Id, "/bin/cat")
97 97
 		close(c1)
98 98
 	}()
99 99
 
... ...
@@ -147,7 +159,7 @@ func TestRunDisconnect(t *testing.T) {
147 147
 	go func() {
148 148
 		// We're simulating a disconnect so the return value doesn't matter. What matters is the
149 149
 		// fact that CmdRun returns.
150
-		srv.CmdRun(stdin, stdoutPipe, "-i", GetTestImage(runtime).Id, "/bin/cat")
150
+		srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", GetTestImage(runtime).Id, "/bin/cat")
151 151
 		close(c1)
152 152
 	}()
153 153
 
... ...
@@ -179,10 +191,56 @@ func TestRunDisconnect(t *testing.T) {
179 179
 	})
180 180
 }
181 181
 
182
+// Expected behaviour: the process dies when the client disconnects
183
+func TestRunDisconnectTty(t *testing.T) {
184
+	runtime, err := newTestRuntime()
185
+	if err != nil {
186
+		t.Fatal(err)
187
+	}
188
+	defer nuke(runtime)
189
+
190
+	srv := &Server{runtime: runtime}
191
+
192
+	stdin, stdinPipe := io.Pipe()
193
+	stdout, stdoutPipe := io.Pipe()
194
+	c1 := make(chan struct{})
195
+	go func() {
196
+		// We're simulating a disconnect so the return value doesn't matter. What matters is the
197
+		// fact that CmdRun returns.
198
+		srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", "-t", GetTestImage(runtime).Id, "/bin/cat")
199
+		close(c1)
200
+	}()
201
+
202
+	setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
203
+		if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
204
+			t.Fatal(err)
205
+		}
206
+	})
207
+
208
+	// Close pipes (simulate disconnect)
209
+	if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
210
+		t.Fatal(err)
211
+	}
212
+
213
+	// as the pipes are close, we expect the process to die,
214
+	// therefore CmdRun to unblock. Wait for CmdRun
215
+	setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
216
+		<-c1
217
+	})
218
+
219
+	// Client disconnect after run -i should keep stdin out in TTY mode
220
+	container := runtime.List()[0]
221
+	// Give some time to monitor to do his thing
222
+	container.WaitTimeout(500 * time.Millisecond)
223
+	if !container.State.Running {
224
+		t.Fatalf("/bin/cat should  still be running after closing stdin (tty mode)")
225
+	}
226
+}
227
+
182 228
 // TestAttachStdin checks attaching to stdin without stdout and stderr.
183 229
 // 'docker run -i -a stdin' should sends the client's stdin to the command,
184 230
 // then detach from it and print the container id.
185
-func TestAttachStdin(t *testing.T) {
231
+func TestRunAttachStdin(t *testing.T) {
186 232
 	runtime, err := newTestRuntime()
187 233
 	if err != nil {
188 234
 		t.Fatal(err)
... ...
@@ -190,31 +248,41 @@ func TestAttachStdin(t *testing.T) {
190 190
 	defer nuke(runtime)
191 191
 	srv := &Server{runtime: runtime}
192 192
 
193
-	stdinR, stdinW := io.Pipe()
194
-	var stdout bytes.Buffer
193
+	stdin, stdinPipe := io.Pipe()
194
+	stdout, stdoutPipe := io.Pipe()
195 195
 
196 196
 	ch := make(chan struct{})
197 197
 	go func() {
198
-		srv.CmdRun(stdinR, &stdout, "-i", "-a", "stdin", GetTestImage(runtime).Id, "sh", "-c", "echo hello; cat")
198
+		srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", "-a", "stdin", GetTestImage(runtime).Id, "sh", "-c", "echo hello; cat")
199 199
 		close(ch)
200 200
 	}()
201 201
 
202
-	// Send input to the command, close stdin, wait for CmdRun to return
203
-	setTimeout(t, "Read/Write timed out", 2*time.Second, func() {
204
-		if _, err := stdinW.Write([]byte("hi there\n")); err != nil {
202
+	// Send input to the command, close stdin
203
+	setTimeout(t, "Write timed out", 2*time.Second, func() {
204
+		if _, err := stdinPipe.Write([]byte("hi there\n")); err != nil {
205
+			t.Fatal(err)
206
+		}
207
+		if err := stdinPipe.Close(); err != nil {
205 208
 			t.Fatal(err)
206 209
 		}
207
-		stdinW.Close()
208
-		<-ch
209 210
 	})
210 211
 
211
-	// Check output
212
-	cmdOutput := string(stdout.Bytes())
213 212
 	container := runtime.List()[0]
213
+
214
+	// Check output
215
+	cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
216
+	if err != nil {
217
+		t.Fatal(err)
218
+	}
214 219
 	if cmdOutput != container.ShortId()+"\n" {
215 220
 		t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortId()+"\n", cmdOutput)
216 221
 	}
217 222
 
223
+	// wait for CmdRun to return
224
+	setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
225
+		<-ch
226
+	})
227
+
218 228
 	setTimeout(t, "Waiting for command to exit timed out", 2*time.Second, func() {
219 229
 		container.Wait()
220 230
 	})
... ...
@@ -270,7 +338,7 @@ func TestAttachDisconnect(t *testing.T) {
270 270
 	go func() {
271 271
 		// We're simulating a disconnect so the return value doesn't matter. What matters is the
272 272
 		// fact that CmdAttach returns.
273
-		srv.CmdAttach(stdin, stdoutPipe, container.Id)
273
+		srv.CmdAttach(stdin, rcli.NewDockerLocalConn(stdoutPipe), container.Id)
274 274
 		close(c1)
275 275
 	}()
276 276
 
... ...
@@ -40,11 +40,11 @@ type Container struct {
40 40
 	stdin       io.ReadCloser
41 41
 	stdinPipe   io.WriteCloser
42 42
 
43
-	ptyStdinMaster  io.Closer
44
-	ptyStdoutMaster io.Closer
45
-	ptyStderrMaster io.Closer
43
+	ptyMaster io.Closer
46 44
 
47 45
 	runtime *Runtime
46
+
47
+	waitLock chan struct{}
48 48
 }
49 49
 
50 50
 type Config struct {
... ...
@@ -180,63 +180,37 @@ func (container *Container) generateLXCConfig() error {
180 180
 }
181 181
 
182 182
 func (container *Container) startPty() error {
183
-	stdoutMaster, stdoutSlave, err := pty.Open()
183
+	ptyMaster, ptySlave, err := pty.Open()
184 184
 	if err != nil {
185 185
 		return err
186 186
 	}
187
-	container.ptyStdoutMaster = stdoutMaster
188
-	container.cmd.Stdout = stdoutSlave
189
-
190
-	stderrMaster, stderrSlave, err := pty.Open()
191
-	if err != nil {
192
-		return err
193
-	}
194
-	container.ptyStderrMaster = stderrMaster
195
-	container.cmd.Stderr = stderrSlave
187
+	container.ptyMaster = ptyMaster
188
+	container.cmd.Stdout = ptySlave
189
+	container.cmd.Stderr = ptySlave
196 190
 
197 191
 	// Copy the PTYs to our broadcasters
198 192
 	go func() {
199 193
 		defer container.stdout.CloseWriters()
200 194
 		Debugf("[startPty] Begin of stdout pipe")
201
-		io.Copy(container.stdout, stdoutMaster)
195
+		io.Copy(container.stdout, ptyMaster)
202 196
 		Debugf("[startPty] End of stdout pipe")
203 197
 	}()
204 198
 
205
-	go func() {
206
-		defer container.stderr.CloseWriters()
207
-		Debugf("[startPty] Begin of stderr pipe")
208
-		io.Copy(container.stderr, stderrMaster)
209
-		Debugf("[startPty] End of stderr pipe")
210
-	}()
211
-
212 199
 	// stdin
213
-	var stdinSlave io.ReadCloser
214 200
 	if container.Config.OpenStdin {
215
-		var stdinMaster io.WriteCloser
216
-		stdinMaster, stdinSlave, err = pty.Open()
217
-		if err != nil {
218
-			return err
219
-		}
220
-		container.ptyStdinMaster = stdinMaster
221
-		container.cmd.Stdin = stdinSlave
222
-		// FIXME: The following appears to be broken.
223
-		// "cannot set terminal process group (-1): Inappropriate ioctl for device"
224
-		// container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
201
+		container.cmd.Stdin = ptySlave
202
+		container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
225 203
 		go func() {
226 204
 			defer container.stdin.Close()
227 205
 			Debugf("[startPty] Begin of stdin pipe")
228
-			io.Copy(stdinMaster, container.stdin)
206
+			io.Copy(ptyMaster, container.stdin)
229 207
 			Debugf("[startPty] End of stdin pipe")
230 208
 		}()
231 209
 	}
232 210
 	if err := container.cmd.Start(); err != nil {
233 211
 		return err
234 212
 	}
235
-	stdoutSlave.Close()
236
-	stderrSlave.Close()
237
-	if stdinSlave != nil {
238
-		stdinSlave.Close()
239
-	}
213
+	ptySlave.Close()
240 214
 	return nil
241 215
 }
242 216
 
... ...
@@ -278,10 +252,14 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
278 278
 				if cStderr != nil {
279 279
 					defer cStderr.Close()
280 280
 				}
281
-				if container.Config.StdinOnce {
281
+				if container.Config.StdinOnce && !container.Config.Tty {
282 282
 					defer cStdin.Close()
283 283
 				}
284
-				_, err := io.Copy(cStdin, stdin)
284
+				if container.Config.Tty {
285
+					_, err = CopyEscapable(cStdin, stdin)
286
+				} else {
287
+					_, err = io.Copy(cStdin, stdin)
288
+				}
285 289
 				if err != nil {
286 290
 					Debugf("[error] attach stdin: %s\n", err)
287 291
 				}
... ...
@@ -365,6 +343,9 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
365 365
 }
366 366
 
367 367
 func (container *Container) Start() error {
368
+	container.State.lock()
369
+	defer container.State.unlock()
370
+
368 371
 	if container.State.Running {
369 372
 		return fmt.Errorf("The container %s is already running.", container.Id)
370 373
 	}
... ...
@@ -431,6 +412,9 @@ func (container *Container) Start() error {
431 431
 	// FIXME: save state on disk *first*, then converge
432 432
 	// this way disk state is used as a journal, eg. we can restore after crash etc.
433 433
 	container.State.setRunning(container.cmd.Process.Pid)
434
+
435
+	// Init the lock
436
+	container.waitLock = make(chan struct{})
434 437
 	container.ToDisk()
435 438
 	go container.monitor()
436 439
 	return nil
... ...
@@ -530,19 +514,9 @@ func (container *Container) monitor() {
530 530
 		Debugf("%s: Error close stderr: %s", container.Id, err)
531 531
 	}
532 532
 
533
-	if container.ptyStdinMaster != nil {
534
-		if err := container.ptyStdinMaster.Close(); err != nil {
535
-			Debugf("%s: Error close pty stdin master: %s", container.Id, err)
536
-		}
537
-	}
538
-	if container.ptyStdoutMaster != nil {
539
-		if err := container.ptyStdoutMaster.Close(); err != nil {
540
-			Debugf("%s: Error close pty stdout master: %s", container.Id, err)
541
-		}
542
-	}
543
-	if container.ptyStderrMaster != nil {
544
-		if err := container.ptyStderrMaster.Close(); err != nil {
545
-			Debugf("%s: Error close pty stderr master: %s", container.Id, err)
533
+	if container.ptyMaster != nil {
534
+		if err := container.ptyMaster.Close(); err != nil {
535
+			Debugf("%s: Error closing Pty master: %s", container.Id, err)
546 536
 		}
547 537
 	}
548 538
 
... ...
@@ -557,6 +531,10 @@ func (container *Container) monitor() {
557 557
 
558 558
 	// Report status back
559 559
 	container.State.setStopped(exitCode)
560
+
561
+	// Release the lock
562
+	close(container.waitLock)
563
+
560 564
 	if err := container.ToDisk(); err != nil {
561 565
 		// FIXME: there is a race condition here which causes this to fail during the unit tests.
562 566
 		// If another goroutine was waiting for Wait() to return before removing the container's root
... ...
@@ -569,7 +547,7 @@ func (container *Container) monitor() {
569 569
 }
570 570
 
571 571
 func (container *Container) kill() error {
572
-	if container.cmd == nil {
572
+	if !container.State.Running || container.cmd == nil {
573 573
 		return nil
574 574
 	}
575 575
 	if err := container.cmd.Process.Kill(); err != nil {
... ...
@@ -581,13 +559,14 @@ func (container *Container) kill() error {
581 581
 }
582 582
 
583 583
 func (container *Container) Kill() error {
584
-	if !container.State.Running {
585
-		return nil
586
-	}
584
+	container.State.lock()
585
+	defer container.State.unlock()
587 586
 	return container.kill()
588 587
 }
589 588
 
590 589
 func (container *Container) Stop() error {
590
+	container.State.lock()
591
+	defer container.State.unlock()
591 592
 	if !container.State.Running {
592 593
 		return nil
593 594
 	}
... ...
@@ -596,7 +575,7 @@ func (container *Container) Stop() error {
596 596
 	if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil {
597 597
 		log.Print(string(output))
598 598
 		log.Print("Failed to send SIGTERM to the process, force killing")
599
-		if err := container.Kill(); err != nil {
599
+		if err := container.kill(); err != nil {
600 600
 			return err
601 601
 		}
602 602
 	}
... ...
@@ -604,7 +583,7 @@ func (container *Container) Stop() error {
604 604
 	// 2. Wait for the process to exit on its own
605 605
 	if err := container.WaitTimeout(10 * time.Second); err != nil {
606 606
 		log.Printf("Container %v failed to exit within 10 seconds of SIGTERM - using the force", container.Id)
607
-		if err := container.Kill(); err != nil {
607
+		if err := container.kill(); err != nil {
608 608
 			return err
609 609
 		}
610 610
 	}
... ...
@@ -623,10 +602,7 @@ func (container *Container) Restart() error {
623 623
 
624 624
 // Wait blocks until the container stops running, then returns its exit code.
625 625
 func (container *Container) Wait() int {
626
-
627
-	for container.State.Running {
628
-		container.State.wait()
629
-	}
626
+	<-container.waitLock
630 627
 	return container.State.ExitCode
631 628
 }
632 629
 
... ...
@@ -267,6 +267,7 @@ func TestStart(t *testing.T) {
267 267
 	// Try to avoid the timeoout in destroy. Best effort, don't check error
268 268
 	cStdin, _ := container.StdinPipe()
269 269
 	cStdin.Close()
270
+	container.WaitTimeout(2 * time.Second)
270 271
 }
271 272
 
272 273
 func TestRun(t *testing.T) {
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"io"
9 9
 	"log"
10 10
 	"os"
11
-	"os/signal"
12 11
 )
13 12
 
14 13
 var GIT_COMMIT string
... ...
@@ -57,29 +56,21 @@ func daemon() error {
57 57
 }
58 58
 
59 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
66
-		}
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
-		}()
77
-	}
78 60
 	// FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose
79 61
 	// CloseWrite(), which we need to cleanly signal that stdin is closed without
80 62
 	// closing the connection.
81 63
 	// See http://code.google.com/p/go/issues/detail?id=3345
82 64
 	if conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...); err == nil {
65
+		options := conn.GetOptions()
66
+		if options.RawTerminal &&
67
+			term.IsTerminal(int(os.Stdin.Fd())) &&
68
+			os.Getenv("NORAW") == "" {
69
+			if oldState, err := rcli.SetRawTerminal(); err != nil {
70
+				return err
71
+			} else {
72
+				defer rcli.RestoreTerminal(oldState)
73
+			}
74
+		}
83 75
 		receiveStdout := docker.Go(func() error {
84 76
 			_, err := io.Copy(os.Stdout, conn)
85 77
 			return err
... ...
@@ -104,12 +95,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 := rcli.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
 }
... ...
@@ -39,4 +39,36 @@ Notes
39 39
 * The index.html and gettingstarted.html files are copied from the source dir to the output dir without modification.
40 40
 So changes to those pages should be made directly in html
41 41
 * For the template the css is compiled from less. When changes are needed they can be compiled using
42
-lessc ``lessc main.less`` or watched using watch-lessc ``watch-lessc -i main.less -o main.css``
43 42
\ No newline at end of file
43
+lessc ``lessc main.less`` or watched using watch-lessc ``watch-lessc -i main.less -o main.css``
44
+
45
+
46
+Guides on using sphinx
47
+----------------------
48
+* To make links to certain pages create a link target like so:
49
+
50
+  ```
51
+    .. _hello_world:
52
+
53
+    Hello world
54
+    ===========
55
+
56
+    This is.. (etc.)
57
+  ```
58
+
59
+  The ``_hello_world:`` will make it possible to link to this position (page and marker) from all other pages.
60
+
61
+* Notes, warnings and alarms
62
+
63
+  ```
64
+    # a note (use when something is important)
65
+    .. note::
66
+
67
+    # a warning (orange)
68
+    .. warning::
69
+
70
+    # danger (red, use sparsely)
71
+    .. danger::
72
+
73
+* Code examples
74
+
75
+  Start without $, so it's easy to copy and paste.
44 76
\ No newline at end of file
... ...
@@ -69,7 +69,8 @@ Expose a service on a TCP port
69 69
 
70 70
   # Connect to the public port via the host's public address
71 71
   # Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
72
-  echo hello world | nc $(hostname) $PORT
72
+  IP=$(ifconfig eth0 | perl -n -e 'if (m/inet addr:([\d\.]+)/g) { print $1 }')
73
+  echo hello world | nc $IP $PORT
73 74
 
74 75
   # Verify that the network connection worked
75 76
   echo "Daemon received: $(docker logs $JOB)"
76 77
new file mode 100644
... ...
@@ -0,0 +1,4 @@
0
+
1
+.. note::
2
+    
3
+    This example assumes you have Docker running in daemon mode. For more information please see :ref:`running_examples`
... ...
@@ -6,8 +6,10 @@
6 6
 
7 7
 Hello World
8 8
 ===========
9
-This is the most basic example available for using Docker. The example assumes you have Docker installed.
10 9
 
10
+.. include:: example_header.inc
11
+
12
+This is the most basic example available for using Docker.
11 13
 
12 14
 Download the base container
13 15
 
... ...
@@ -6,6 +6,9 @@
6 6
 
7 7
 Hello World Daemon
8 8
 ==================
9
+
10
+.. include:: example_header.inc
11
+
9 12
 The most boring daemon ever written.
10 13
 
11 14
 This example assumes you have Docker installed and with the base image already imported ``docker pull base``.
... ...
@@ -18,7 +21,7 @@ out every second. It will continue to do this until we stop it.
18 18
 
19 19
     CONTAINER_ID=$(docker run -d base /bin/sh -c "while true; do echo hello world; sleep 1; done")
20 20
 
21
-We are going to run a simple hello world daemon in a new container made from the busybox daemon.
21
+We are going to run a simple hello world daemon in a new container made from the base image.
22 22
 
23 23
 - **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon.
24 24
 - **"base"** is the image we want to run the command inside of.
... ...
@@ -12,6 +12,7 @@ Contents:
12 12
 .. toctree::
13 13
    :maxdepth: 1
14 14
 
15
+   running_examples
15 16
    hello_world
16 17
    hello_world_daemon
17 18
    python_web_app
... ...
@@ -6,6 +6,9 @@
6 6
 
7 7
 Building a python web app
8 8
 =========================
9
+
10
+.. include:: example_header.inc
11
+
9 12
 The goal of this example is to show you how you can author your own docker images using a parent image, making changes to it, and then saving the results as a new image. We will do that by making a simple hello flask web application image.
10 13
 
11 14
 **Steps:**
... ...
@@ -45,6 +48,11 @@ Save the changed we just made in the container to a new image called "_/builds/g
45 45
 
46 46
     WEB_WORKER=$(docker run -d -p 5000 $BUILD_IMG /usr/local/bin/runapp)
47 47
 
48
+- **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon.
49
+  **"-p 5000"* the web app is going to listen on this port, so it must be mapped from the container to the host system.
50
+- **"$BUILD_IMG"** is the image we want to run the command inside of.
51
+- **/usr/local/bin/runapp** is the command which starts the web app.
52
+
48 53
 Use the new image we just created and create a new container with network port 5000, and return the container id and store in the WEB_WORKER variable.
49 54
 
50 55
 .. code-block:: bash
... ...
@@ -54,6 +62,18 @@ Use the new image we just created and create a new container with network port 5
54 54
 
55 55
 view the logs for the new container using the WEB_WORKER variable, and if everything worked as planned you should see the line "Running on http://0.0.0.0:5000/" in the log output.
56 56
 
57
+.. code-block:: bash
58
+
59
+    WEB_PORT=$(docker port $WEB_WORKER 5000)
60
+
61
+lookup the public-facing port which is NAT-ed store the private port used by the container and store it inside of the WEB_PORT variable.
62
+
63
+.. code-block:: bash
64
+
65
+    curl http://`hostname`:$WEB_PORT
66
+      Hello world!
67
+
68
+access the web app using curl. If everything worked as planned you should see the line "Hello world!" inside of your console.
57 69
 
58 70
 **Video:**
59 71
 
60 72
new file mode 100644
... ...
@@ -0,0 +1,33 @@
0
+:title: Running the Examples
1
+:description: An overview on how to run the docker examples
2
+:keywords: docker, examples, how to
3
+
4
+.. _running_examples:
5
+
6
+Running The Examples
7
+--------------------
8
+
9
+There are two ways to run docker, daemon mode and standalone mode.
10
+
11
+When you run the docker command it will first check if there is a docker daemon running in the background it can connect to.
12
+
13
+* If it exists it will use that daemon to run all of the commands.
14
+* If it does not exist docker will run in standalone mode (docker will exit after each command).
15
+
16
+Docker needs to be run from a privileged account (root).
17
+
18
+1. The most common (and recommended) way is to run a docker daemon as root in the background, and then connect to it from the docker client from any account.
19
+
20
+   .. code-block:: bash
21
+
22
+      # starting docker daemon in the background
23
+      sudo docker -d &
24
+
25
+      # now you can run docker commands from any account.
26
+      docker <command>
27
+
28
+2. Standalone: You need to run every command as root, or using sudo
29
+
30
+   .. code-block:: bash
31
+
32
+       sudo docker <command>
... ...
@@ -7,7 +7,7 @@
7 7
 Create an ssh daemon service
8 8
 ============================
9 9
 
10
-
10
+.. include:: example_header.inc
11 11
 
12 12
 
13 13
 **Video:**
... ...
@@ -82,7 +82,7 @@ h4 {
82 82
 .btn-custom {
83 83
   background-color: #292929 !important;
84 84
   background-repeat: repeat-x;
85
-  filter: progid:dximagetransform.microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
85
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
86 86
   background-image: -khtml-gradient(linear, left top, left bottom, from(#515151), to(#282828));
87 87
   background-image: -moz-linear-gradient(top, #515151, #282828);
88 88
   background-image: -ms-linear-gradient(top, #515151, #282828);
... ...
@@ -131,6 +131,27 @@ section.header {
131 131
   margin: 15px 15px 15px 0;
132 132
   border: 2px solid gray;
133 133
 }
134
+.admonition {
135
+  padding: 10px;
136
+  border: 1px solid grey;
137
+  margin-bottom: 10px;
138
+  margin-top: 10px;
139
+  -webkit-border-radius: 4px;
140
+  -moz-border-radius: 4px;
141
+  border-radius: 4px;
142
+}
143
+.admonition .admonition-title {
144
+  font-weight: bold;
145
+}
146
+.admonition.note {
147
+  background-color: #f1ebba;
148
+}
149
+.admonition.warning {
150
+  background-color: #eed9af;
151
+}
152
+.admonition.danger {
153
+  background-color: #e9bcab;
154
+}
134 155
 /* ===================
135 156
 	left navigation
136 157
 ===================== */
... ...
@@ -179,7 +179,33 @@ section.header {
179 179
   border: 2px solid gray;
180 180
 }
181 181
 
182
+.admonition {
183
+  padding: 10px;
184
+  border: 1px solid grey;
185
+
186
+  margin-bottom: 10px;
187
+  margin-top: 10px;
188
+
189
+  -webkit-border-radius: 4px;
190
+  -moz-border-radius: 4px;
191
+  border-radius: 4px;
192
+}
193
+
194
+.admonition .admonition-title {
195
+  font-weight: bold;
196
+}
197
+
198
+.admonition.note {
199
+  background-color: rgb(241, 235, 186);
200
+}
182 201
 
202
+.admonition.warning {
203
+  background-color: rgb(238, 217, 175);
204
+}
205
+
206
+.admonition.danger {
207
+  background-color: rgb(233, 188, 171);
208
+}
183 209
 
184 210
 /* ===================
185 211
 	left navigation
... ...
@@ -111,6 +111,8 @@ func checkRouteOverlaps(dockerNetwork *net.IPNet) error {
111 111
 }
112 112
 
113 113
 func CreateBridgeIface(ifaceName string) error {
114
+	// FIXME: try more IP ranges
115
+	// FIXME: try bigger ranges! /24 is too small.
114 116
 	addrs := []string{"172.16.42.1/24", "10.0.42.1/24", "192.168.42.1/24"}
115 117
 
116 118
 	var ifaceAddr string
... ...
@@ -127,7 +129,7 @@ func CreateBridgeIface(ifaceName string) error {
127 127
 		}
128 128
 	}
129 129
 	if ifaceAddr == "" {
130
-		return fmt.Errorf("Impossible to create a bridge. Please create a bridge manually and restart docker with -br <bridgeName>")
130
+		return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", ifaceName, ifaceName)
131 131
 	} else {
132 132
 		Debugf("Creating bridge %s with network %s", ifaceName, ifaceAddr)
133 133
 	}
134 134
deleted file mode 100644
... ...
@@ -1,38 +0,0 @@
1
-package rcli
2
-
3
-import (
4
-	"fmt"
5
-	"net/http"
6
-	"net/url"
7
-	"path"
8
-)
9
-
10
-// Use this key to encode an RPC call into an URL,
11
-// eg. domain.tld/path/to/method?q=get_user&q=gordon
12
-const ARG_URL_KEY = "q"
13
-
14
-func URLToCall(u *url.URL) (method string, args []string) {
15
-	return path.Base(u.Path), u.Query()[ARG_URL_KEY]
16
-}
17
-
18
-func ListenAndServeHTTP(addr string, service Service) error {
19
-	return http.ListenAndServe(addr, http.HandlerFunc(
20
-		func(w http.ResponseWriter, r *http.Request) {
21
-			cmd, args := URLToCall(r.URL)
22
-			if err := call(service, r.Body, &AutoFlush{w}, append([]string{cmd}, args...)...); err != nil {
23
-				fmt.Fprintln(w, "Error:", err.Error())
24
-			}
25
-		}))
26
-}
27
-
28
-type AutoFlush struct {
29
-	http.ResponseWriter
30
-}
31
-
32
-func (w *AutoFlush) Write(data []byte) (int, error) {
33
-	ret, err := w.ResponseWriter.Write(data)
34
-	if flusher, ok := w.ResponseWriter.(http.Flusher); ok {
35
-		flusher.Flush()
36
-	}
37
-	return ret, err
38
-}
... ...
@@ -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,109 @@ 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) Flush() error {
95
+	_, err := c.Write([]byte{})
96
+	return err
97
+}
98
+
99
+func (c *DockerTCPConn) Close() error { return c.conn.Close() }
100
+
101
+func (c *DockerTCPConn) CloseWrite() error { return c.conn.CloseWrite() }
102
+
103
+func (c *DockerTCPConn) CloseRead() error { return c.conn.CloseRead() }
104
+
18 105
 // Connect to a remote endpoint using protocol `proto` and address `addr`,
19 106
 // issue a single call, and return the result.
20 107
 // `proto` may be "tcp", "unix", etc. See the `net` package for available protocols.
21
-func Call(proto, addr string, args ...string) (*net.TCPConn, error) {
108
+func Call(proto, addr string, args ...string) (DockerConn, error) {
22 109
 	cmd, err := json.Marshal(args)
23 110
 	if err != nil {
24 111
 		return nil, err
25 112
 	}
26
-	conn, err := net.Dial(proto, addr)
113
+	conn, err := dialDocker(proto, addr)
27 114
 	if err != nil {
28 115
 		return nil, err
29 116
 	}
30 117
 	if _, err := fmt.Fprintln(conn, string(cmd)); err != nil {
31 118
 		return nil, err
32 119
 	}
33
-	return conn.(*net.TCPConn), nil
120
+	return conn, nil
34 121
 }
35 122
 
36 123
 // Listen on `addr`, using protocol `proto`, for incoming rcli calls,
... ...
@@ -46,6 +134,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 +155,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 {
... ...
@@ -8,15 +8,99 @@ package rcli
8 8
 // are the usual suspects.
9 9
 
10 10
 import (
11
-	"errors"
12 11
 	"flag"
13 12
 	"fmt"
13
+	"github.com/dotcloud/docker/term"
14 14
 	"io"
15 15
 	"log"
16
+	"net"
17
+	"os"
16 18
 	"reflect"
17 19
 	"strings"
18 20
 )
19 21
 
22
+type DockerConnOptions struct {
23
+	RawTerminal bool
24
+}
25
+
26
+type DockerConn interface {
27
+	io.ReadWriteCloser
28
+	CloseWrite() error
29
+	CloseRead() error
30
+	GetOptions() *DockerConnOptions
31
+	SetOptionRawTerminal()
32
+	Flush() error
33
+}
34
+
35
+type DockerLocalConn struct {
36
+	writer     io.WriteCloser
37
+	savedState *term.State
38
+}
39
+
40
+func NewDockerLocalConn(w io.WriteCloser) *DockerLocalConn {
41
+	return &DockerLocalConn{
42
+		writer: w,
43
+	}
44
+}
45
+
46
+func (c *DockerLocalConn) Read(b []byte) (int, error) {
47
+	return 0, fmt.Errorf("DockerLocalConn does not implement Read()")
48
+}
49
+
50
+func (c *DockerLocalConn) Write(b []byte) (int, error) { return c.writer.Write(b) }
51
+
52
+func (c *DockerLocalConn) Close() error {
53
+	if c.savedState != nil {
54
+		RestoreTerminal(c.savedState)
55
+		c.savedState = nil
56
+	}
57
+	return c.writer.Close()
58
+}
59
+
60
+func (c *DockerLocalConn) Flush() error { return nil }
61
+
62
+func (c *DockerLocalConn) CloseWrite() error { return nil }
63
+
64
+func (c *DockerLocalConn) CloseRead() error { return nil }
65
+
66
+func (c *DockerLocalConn) GetOptions() *DockerConnOptions { return nil }
67
+
68
+func (c *DockerLocalConn) SetOptionRawTerminal() {
69
+	if state, err := SetRawTerminal(); err != nil {
70
+		if os.Getenv("DEBUG") != "" {
71
+			log.Printf("Can't set the terminal in raw mode: %s", err)
72
+		}
73
+	} else {
74
+		c.savedState = state
75
+	}
76
+}
77
+
78
+var UnknownDockerProto = fmt.Errorf("Only TCP is actually supported by Docker at the moment")
79
+
80
+func dialDocker(proto string, addr string) (DockerConn, error) {
81
+	conn, err := net.Dial(proto, addr)
82
+	if err != nil {
83
+		return nil, err
84
+	}
85
+	switch i := conn.(type) {
86
+	case *net.TCPConn:
87
+		return NewDockerTCPConn(i, true), nil
88
+	}
89
+	return nil, UnknownDockerProto
90
+}
91
+
92
+func newDockerFromConn(conn net.Conn, client bool) (DockerConn, error) {
93
+	switch i := conn.(type) {
94
+	case *net.TCPConn:
95
+		return NewDockerTCPConn(i, client), nil
96
+	}
97
+	return nil, UnknownDockerProto
98
+}
99
+
100
+func newDockerServerConn(conn net.Conn) (DockerConn, error) {
101
+	return newDockerFromConn(conn, false)
102
+}
103
+
20 104
 type Service interface {
21 105
 	Name() string
22 106
 	Help() string
... ...
@@ -26,11 +110,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
 	}
... ...
@@ -49,7 +133,7 @@ func LocalCall(service Service, stdin io.ReadCloser, stdout io.Writer, args ...s
49 49
 	if method != nil {
50 50
 		return method(stdin, stdout, flags.Args()[1:]...)
51 51
 	}
52
-	return errors.New("No such command: " + cmd)
52
+	return fmt.Errorf("No such command: %s", cmd)
53 53
 }
54 54
 
55 55
 func getMethod(service Service, name string) Cmd {
... ...
@@ -59,7 +143,7 @@ func getMethod(service Service, name string) Cmd {
59 59
 				stdout.Write([]byte(service.Help()))
60 60
 			} else {
61 61
 				if method := getMethod(service, args[0]); method == nil {
62
-					return errors.New("No such command: " + args[0])
62
+					return fmt.Errorf("No such command: %s", args[0])
63 63
 				} else {
64 64
 					method(stdin, stdout, "--help")
65 65
 				}
66 66
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+package rcli
1
+
2
+import (
3
+	"github.com/dotcloud/docker/term"
4
+	"os"
5
+	"os/signal"
6
+)
7
+
8
+//FIXME: move these function to utils.go (in rcli to avoid import loop)
9
+func SetRawTerminal() (*term.State, error) {
10
+	oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
11
+	if err != nil {
12
+		return nil, err
13
+	}
14
+	c := make(chan os.Signal, 1)
15
+	signal.Notify(c, os.Interrupt)
16
+	go func() {
17
+		_ = <-c
18
+		term.Restore(int(os.Stdin.Fd()), oldState)
19
+		os.Exit(0)
20
+	}()
21
+	return oldState, err
22
+}
23
+
24
+func RestoreTerminal(state *term.State) {
25
+	term.Restore(int(os.Stdin.Fd()), state)
26
+}
... ...
@@ -116,7 +116,6 @@ func (runtime *Runtime) Load(id string) (*Container, error) {
116 116
 	if err := container.FromDisk(); err != nil {
117 117
 		return nil, err
118 118
 	}
119
-	container.State.initLock()
120 119
 	if container.Id != id {
121 120
 		return container, fmt.Errorf("Container %s is stored at %s", container.Id, id)
122 121
 	}
... ...
@@ -136,6 +135,7 @@ func (runtime *Runtime) Register(container *Container) error {
136 136
 	}
137 137
 
138 138
 	// FIXME: if the container is supposed to be running but is not, auto restart it?
139
+	//        if so, then we need to restart monitor and init a new lock
139 140
 	// If the container is supposed to be running, make sure of it
140 141
 	if container.State.Running {
141 142
 		if output, err := exec.Command("lxc-info", "-n", container.Id).CombinedOutput(); err != nil {
... ...
@@ -150,10 +150,10 @@ func (runtime *Runtime) Register(container *Container) error {
150 150
 			}
151 151
 		}
152 152
 	}
153
+	container.State.initLock()
153 154
 
154 155
 	container.runtime = runtime
155
-	// Setup state lock (formerly in newState()
156
-	container.State.initLock()
156
+
157 157
 	// Attach to stdout and stderr
158 158
 	container.stderr = newWriteBroadcaster()
159 159
 	container.stdout = newWriteBroadcaster()
... ...
@@ -1,6 +1,7 @@
1 1
 package docker
2 2
 
3 3
 import (
4
+	"github.com/dotcloud/docker/rcli"
4 5
 	"io"
5 6
 	"io/ioutil"
6 7
 	"os"
... ...
@@ -77,7 +78,7 @@ func init() {
77 77
 		runtime: runtime,
78 78
 	}
79 79
 	// Retrieve the Image
80
-	if err := srv.CmdPull(os.Stdin, os.Stdout, unitTestImageName); err != nil {
80
+	if err := srv.CmdPull(os.Stdin, rcli.NewDockerLocalConn(os.Stdout), unitTestImageName); err != nil {
81 81
 		panic(err)
82 82
 	}
83 83
 }
... ...
@@ -314,7 +315,7 @@ func TestRestore(t *testing.T) {
314 314
 	// Simulate a crash/manual quit of dockerd: process dies, states stays 'Running'
315 315
 	cStdin, _ := container2.StdinPipe()
316 316
 	cStdin.Close()
317
-	if err := container2.WaitTimeout(time.Second); err != nil {
317
+	if err := container2.WaitTimeout(2 * time.Second); err != nil {
318 318
 		t.Fatal(err)
319 319
 	}
320 320
 	container2.State.Running = true
... ...
@@ -358,4 +359,5 @@ func TestRestore(t *testing.T) {
358 358
 	if err := container3.Run(); err != nil {
359 359
 		t.Fatal(err)
360 360
 	}
361
+	container2.State.Running = false
361 362
 }
... ...
@@ -11,9 +11,7 @@ type State struct {
11 11
 	Pid       int
12 12
 	ExitCode  int
13 13
 	StartedAt time.Time
14
-
15
-	stateChangeLock *sync.Mutex
16
-	stateChangeCond *sync.Cond
14
+	l         *sync.Mutex
17 15
 }
18 16
 
19 17
 // String returns a human-readable description of the state
... ...
@@ -29,31 +27,22 @@ func (s *State) setRunning(pid int) {
29 29
 	s.ExitCode = 0
30 30
 	s.Pid = pid
31 31
 	s.StartedAt = time.Now()
32
-	s.broadcast()
33 32
 }
34 33
 
35 34
 func (s *State) setStopped(exitCode int) {
36 35
 	s.Running = false
37 36
 	s.Pid = 0
38 37
 	s.ExitCode = exitCode
39
-	s.broadcast()
40 38
 }
41 39
 
42 40
 func (s *State) initLock() {
43
-	if s.stateChangeLock == nil {
44
-		s.stateChangeLock = &sync.Mutex{}
45
-		s.stateChangeCond = sync.NewCond(s.stateChangeLock)
46
-	}
41
+	s.l = &sync.Mutex{}
47 42
 }
48 43
 
49
-func (s *State) broadcast() {
50
-	s.stateChangeLock.Lock()
51
-	s.stateChangeCond.Broadcast()
52
-	s.stateChangeLock.Unlock()
44
+func (s *State) lock() {
45
+	s.l.Lock()
53 46
 }
54 47
 
55
-func (s *State) wait() {
56
-	s.stateChangeLock.Lock()
57
-	s.stateChangeCond.Wait()
58
-	s.stateChangeLock.Unlock()
48
+func (s *State) unlock() {
49
+	s.l.Unlock()
59 50
 }
... ...
@@ -15,7 +15,8 @@ void MakeRaw(int fd) {
15 15
   ioctl(fd, TCGETS, &t);
16 16
 
17 17
   t.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
18
-  t.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN);
18
+  t.c_oflag &= ~OPOST;
19
+  t.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
19 20
   t.c_cflag &= ~(CSIZE | PARENB);
20 21
   t.c_cflag |= CS8;
21 22
 
... ...
@@ -341,3 +341,46 @@ func TruncateId(id string) string {
341 341
 	}
342 342
 	return id[:shortLen]
343 343
 }
344
+
345
+// Code c/c from io.Copy() modified to handle escape sequence
346
+func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) {
347
+	buf := make([]byte, 32*1024)
348
+	for {
349
+		nr, er := src.Read(buf)
350
+		if nr > 0 {
351
+			// ---- Docker addition
352
+			// char 16 is C-p
353
+			if nr == 1 && buf[0] == 16 {
354
+				nr, er = src.Read(buf)
355
+				// char 17 is C-q
356
+				if nr == 1 && buf[0] == 17 {
357
+					if err := src.Close(); err != nil {
358
+						return 0, err
359
+					}
360
+					return 0, io.EOF
361
+				}
362
+			}
363
+			// ---- End of docker
364
+			nw, ew := dst.Write(buf[0:nr])
365
+			if nw > 0 {
366
+				written += int64(nw)
367
+			}
368
+			if ew != nil {
369
+				err = ew
370
+				break
371
+			}
372
+			if nr != nw {
373
+				err = io.ErrShortWrite
374
+				break
375
+			}
376
+		}
377
+		if er == io.EOF {
378
+			break
379
+		}
380
+		if er != nil {
381
+			err = er
382
+			break
383
+		}
384
+	}
385
+	return written, err
386
+}