浏览代码

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

John Costa 12 年之前
父节点
当前提交
418ef43fbb

+ 9 - 1
README.md

@@ -134,6 +134,12 @@ docker pull base
 docker run -i -t base /bin/bash
 docker run -i -t base /bin/bash
 ```
 ```
 
 
+Detaching from the interactive shell
+------------------------------------
+```
+# In order to detach without killing the shell, you can use the escape sequence Ctrl-p + Ctrl-q
+# Note: this works only in tty mode (run with -t option).
+```
 
 
 Starting a long-running worker process
 Starting a long-running worker process
 --------------------------------------
 --------------------------------------
@@ -183,7 +189,9 @@ JOB=$(docker run -d -p 4444 base /bin/nc -l -p 4444)
 PORT=$(docker port $JOB 4444)
 PORT=$(docker port $JOB 4444)
 
 
 # Connect to the public port via the host's public address
 # Connect to the public port via the host's public address
-echo hello world | nc $(hostname) $PORT
+# Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
+IP=$(ifconfig eth0 | perl -n -e 'if (m/inet addr:([\d\.]+)/g) { print $1 }')
+echo hello world | nc $IP $PORT
 
 
 # Verify that the network connection worked
 # Verify that the network connection worked
 echo "Daemon received: $(docker logs $JOB)"
 echo "Daemon received: $(docker logs $JOB)"

+ 21 - 6
commands.go

@@ -18,7 +18,7 @@ import (
 	"unicode"
 	"unicode"
 )
 )
 
 
-const VERSION = "0.1.3"
+const VERSION = "0.1.4"
 
 
 var GIT_COMMIT string
 var GIT_COMMIT string
 
 
@@ -62,7 +62,7 @@ func (srv *Server) Help() string {
 }
 }
 
 
 // 'docker login': login / register a user to registry service.
 // '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
 	// Read a line on raw terminal with support for simple backspace
 	// sequences and echo.
 	// sequences and echo.
 	//
 	//
@@ -113,6 +113,8 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin
 		return readStringOnRawTerminal(stdin, stdout, false)
 		return readStringOnRawTerminal(stdin, stdout, false)
 	}
 	}
 
 
+	stdout.SetOptionRawTerminal()
+
 	cmd := rcli.Subcmd(stdout, "login", "", "Register or Login to the docker registry server")
 	cmd := rcli.Subcmd(stdout, "login", "", "Register or Login to the docker registry server")
 	if err := cmd.Parse(args); err != nil {
 	if err := cmd.Parse(args); err != nil {
 		return nil
 		return nil
@@ -417,7 +419,8 @@ func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string
 	return nil
 	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 {
+	stdout.Flush()
 	cmd := rcli.Subcmd(stdout, "import", "[OPTIONS] URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball")
 	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 archive io.Reader
 	var resp *http.Response
 	var resp *http.Response
@@ -464,7 +467,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...stri
 	return nil
 	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")
 	cmd := rcli.Subcmd(stdout, "push", "NAME", "Push an image or a repository to the registry")
 	if err := cmd.Parse(args); err != nil {
 	if err := cmd.Parse(args); err != nil {
 		return nil
 		return nil
@@ -784,7 +787,7 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string
 	return fmt.Errorf("No such container: %s", cmd.Arg(0))
 	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 {
 	cmd := rcli.Subcmd(stdout, "attach", "CONTAINER", "Attach to a running container")
 	cmd := rcli.Subcmd(stdout, "attach", "CONTAINER", "Attach to a running container")
 	if err := cmd.Parse(args); err != nil {
 	if err := cmd.Parse(args); err != nil {
 		return nil
 		return nil
@@ -799,6 +802,11 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...stri
 		return fmt.Errorf("No such container: %s", name)
 		return fmt.Errorf("No such container: %s", name)
 	}
 	}
 
 
+	if container.Config.Tty {
+		stdout.SetOptionRawTerminal()
+	}
+	// Flush the options to make sure the client sets the raw mode
+	stdout.Flush()
 	return <-container.Attach(stdin, nil, stdout, stdout)
 	return <-container.Attach(stdin, nil, stdout, stdout)
 }
 }
 
 
@@ -870,7 +878,7 @@ 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)
 	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 {
 	config, err := ParseRun(args, stdout)
 	config, err := ParseRun(args, stdout)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -884,6 +892,13 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
 		return fmt.Errorf("Command not specified")
 		return fmt.Errorf("Command not specified")
 	}
 	}
 
 
+	if config.Tty {
+		stdout.SetOptionRawTerminal()
+	}
+	// Flush the options to make sure the client sets the raw mode
+	// or tell the client there is no options
+	stdout.Flush()
+
 	// Create new container
 	// Create new container
 	container, err := srv.runtime.Create(config)
 	container, err := srv.runtime.Create(config)
 	if err != nil {
 	if err != nil {

+ 89 - 21
commands_test.go

@@ -2,8 +2,8 @@ package docker
 
 
 import (
 import (
 	"bufio"
 	"bufio"
-	"bytes"
 	"fmt"
 	"fmt"
+	"github.com/dotcloud/docker/rcli"
 	"io"
 	"io"
 	"io/ioutil"
 	"io/ioutil"
 	"strings"
 	"strings"
@@ -69,15 +69,27 @@ func TestRunHostname(t *testing.T) {
 
 
 	srv := &Server{runtime: runtime}
 	srv := &Server{runtime: runtime}
 
 
-	var stdin, stdout bytes.Buffer
-	setTimeout(t, "CmdRun timed out", 2*time.Second, func() {
-		if err := srv.CmdRun(ioutil.NopCloser(&stdin), &nopWriteCloser{&stdout}, "-h", "foobar", GetTestImage(runtime).Id, "hostname"); err != nil {
+	stdin, _ := io.Pipe()
+	stdout, stdoutPipe := io.Pipe()
+
+	c := make(chan struct{})
+	go func() {
+		if err := srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-h", "foobar", GetTestImage(runtime).Id, "hostname"); err != nil {
 			t.Fatal(err)
 			t.Fatal(err)
 		}
 		}
-	})
-	if output := string(stdout.Bytes()); output != "foobar\n" {
-		t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", output)
+		close(c)
+	}()
+	cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
+	if err != nil {
+		t.Fatal(err)
+	}
+	if cmdOutput != "foobar\n" {
+		t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", cmdOutput)
 	}
 	}
+
+	setTimeout(t, "CmdRun timed out", 2*time.Second, func() {
+		<-c
+	})
 }
 }
 
 
 func TestRunExit(t *testing.T) {
 func TestRunExit(t *testing.T) {
@@ -93,7 +105,7 @@ func TestRunExit(t *testing.T) {
 	stdout, stdoutPipe := io.Pipe()
 	stdout, stdoutPipe := io.Pipe()
 	c1 := make(chan struct{})
 	c1 := make(chan struct{})
 	go func() {
 	go func() {
-		srv.CmdRun(stdin, stdoutPipe, "-i", GetTestImage(runtime).Id, "/bin/cat")
+		srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", GetTestImage(runtime).Id, "/bin/cat")
 		close(c1)
 		close(c1)
 	}()
 	}()
 
 
@@ -147,7 +159,7 @@ func TestRunDisconnect(t *testing.T) {
 	go func() {
 	go func() {
 		// We're simulating a disconnect so the return value doesn't matter. What matters is the
 		// We're simulating a disconnect so the return value doesn't matter. What matters is the
 		// fact that CmdRun returns.
 		// fact that CmdRun returns.
-		srv.CmdRun(stdin, stdoutPipe, "-i", GetTestImage(runtime).Id, "/bin/cat")
+		srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", GetTestImage(runtime).Id, "/bin/cat")
 		close(c1)
 		close(c1)
 	}()
 	}()
 
 
@@ -179,10 +191,56 @@ func TestRunDisconnect(t *testing.T) {
 	})
 	})
 }
 }
 
 
+// Expected behaviour: the process dies when the client disconnects
+func TestRunDisconnectTty(t *testing.T) {
+	runtime, err := newTestRuntime()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer nuke(runtime)
+
+	srv := &Server{runtime: runtime}
+
+	stdin, stdinPipe := io.Pipe()
+	stdout, stdoutPipe := io.Pipe()
+	c1 := make(chan struct{})
+	go func() {
+		// We're simulating a disconnect so the return value doesn't matter. What matters is the
+		// fact that CmdRun returns.
+		srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", "-t", GetTestImage(runtime).Id, "/bin/cat")
+		close(c1)
+	}()
+
+	setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
+		if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
+			t.Fatal(err)
+		}
+	})
+
+	// Close pipes (simulate disconnect)
+	if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
+		t.Fatal(err)
+	}
+
+	// as the pipes are close, we expect the process to die,
+	// therefore CmdRun to unblock. Wait for CmdRun
+	setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
+		<-c1
+	})
+
+	// Client disconnect after run -i should keep stdin out in TTY mode
+	container := runtime.List()[0]
+	// Give some time to monitor to do his thing
+	container.WaitTimeout(500 * time.Millisecond)
+	if !container.State.Running {
+		t.Fatalf("/bin/cat should  still be running after closing stdin (tty mode)")
+	}
+}
+
 // TestAttachStdin checks attaching to stdin without stdout and stderr.
 // TestAttachStdin checks attaching to stdin without stdout and stderr.
 // 'docker run -i -a stdin' should sends the client's stdin to the command,
 // 'docker run -i -a stdin' should sends the client's stdin to the command,
 // then detach from it and print the container id.
 // then detach from it and print the container id.
-func TestAttachStdin(t *testing.T) {
+func TestRunAttachStdin(t *testing.T) {
 	runtime, err := newTestRuntime()
 	runtime, err := newTestRuntime()
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
@@ -190,31 +248,41 @@ func TestAttachStdin(t *testing.T) {
 	defer nuke(runtime)
 	defer nuke(runtime)
 	srv := &Server{runtime: runtime}
 	srv := &Server{runtime: runtime}
 
 
-	stdinR, stdinW := io.Pipe()
-	var stdout bytes.Buffer
+	stdin, stdinPipe := io.Pipe()
+	stdout, stdoutPipe := io.Pipe()
 
 
 	ch := make(chan struct{})
 	ch := make(chan struct{})
 	go func() {
 	go func() {
-		srv.CmdRun(stdinR, &stdout, "-i", "-a", "stdin", GetTestImage(runtime).Id, "sh", "-c", "echo hello; cat")
+		srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", "-a", "stdin", GetTestImage(runtime).Id, "sh", "-c", "echo hello; cat")
 		close(ch)
 		close(ch)
 	}()
 	}()
 
 
-	// Send input to the command, close stdin, wait for CmdRun to return
-	setTimeout(t, "Read/Write timed out", 2*time.Second, func() {
-		if _, err := stdinW.Write([]byte("hi there\n")); err != nil {
+	// Send input to the command, close stdin
+	setTimeout(t, "Write timed out", 2*time.Second, func() {
+		if _, err := stdinPipe.Write([]byte("hi there\n")); err != nil {
+			t.Fatal(err)
+		}
+		if err := stdinPipe.Close(); err != nil {
 			t.Fatal(err)
 			t.Fatal(err)
 		}
 		}
-		stdinW.Close()
-		<-ch
 	})
 	})
 
 
-	// Check output
-	cmdOutput := string(stdout.Bytes())
 	container := runtime.List()[0]
 	container := runtime.List()[0]
+
+	// Check output
+	cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
+	if err != nil {
+		t.Fatal(err)
+	}
 	if cmdOutput != container.ShortId()+"\n" {
 	if cmdOutput != container.ShortId()+"\n" {
 		t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortId()+"\n", cmdOutput)
 		t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortId()+"\n", cmdOutput)
 	}
 	}
 
 
+	// wait for CmdRun to return
+	setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
+		<-ch
+	})
+
 	setTimeout(t, "Waiting for command to exit timed out", 2*time.Second, func() {
 	setTimeout(t, "Waiting for command to exit timed out", 2*time.Second, func() {
 		container.Wait()
 		container.Wait()
 	})
 	})
@@ -270,7 +338,7 @@ func TestAttachDisconnect(t *testing.T) {
 	go func() {
 	go func() {
 		// We're simulating a disconnect so the return value doesn't matter. What matters is the
 		// We're simulating a disconnect so the return value doesn't matter. What matters is the
 		// fact that CmdAttach returns.
 		// fact that CmdAttach returns.
-		srv.CmdAttach(stdin, stdoutPipe, container.Id)
+		srv.CmdAttach(stdin, rcli.NewDockerLocalConn(stdoutPipe), container.Id)
 		close(c1)
 		close(c1)
 	}()
 	}()
 
 

+ 39 - 63
container.go

@@ -40,11 +40,11 @@ type Container struct {
 	stdin       io.ReadCloser
 	stdin       io.ReadCloser
 	stdinPipe   io.WriteCloser
 	stdinPipe   io.WriteCloser
 
 
-	ptyStdinMaster  io.Closer
-	ptyStdoutMaster io.Closer
-	ptyStderrMaster io.Closer
+	ptyMaster io.Closer
 
 
 	runtime *Runtime
 	runtime *Runtime
+
+	waitLock chan struct{}
 }
 }
 
 
 type Config struct {
 type Config struct {
@@ -180,63 +180,37 @@ func (container *Container) generateLXCConfig() error {
 }
 }
 
 
 func (container *Container) startPty() error {
 func (container *Container) startPty() error {
-	stdoutMaster, stdoutSlave, err := pty.Open()
+	ptyMaster, ptySlave, err := pty.Open()
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	container.ptyStdoutMaster = stdoutMaster
-	container.cmd.Stdout = stdoutSlave
-
-	stderrMaster, stderrSlave, err := pty.Open()
-	if err != nil {
-		return err
-	}
-	container.ptyStderrMaster = stderrMaster
-	container.cmd.Stderr = stderrSlave
+	container.ptyMaster = ptyMaster
+	container.cmd.Stdout = ptySlave
+	container.cmd.Stderr = ptySlave
 
 
 	// Copy the PTYs to our broadcasters
 	// Copy the PTYs to our broadcasters
 	go func() {
 	go func() {
 		defer container.stdout.CloseWriters()
 		defer container.stdout.CloseWriters()
 		Debugf("[startPty] Begin of stdout pipe")
 		Debugf("[startPty] Begin of stdout pipe")
-		io.Copy(container.stdout, stdoutMaster)
+		io.Copy(container.stdout, ptyMaster)
 		Debugf("[startPty] End of stdout pipe")
 		Debugf("[startPty] End of stdout pipe")
 	}()
 	}()
 
 
-	go func() {
-		defer container.stderr.CloseWriters()
-		Debugf("[startPty] Begin of stderr pipe")
-		io.Copy(container.stderr, stderrMaster)
-		Debugf("[startPty] End of stderr pipe")
-	}()
-
 	// stdin
 	// stdin
-	var stdinSlave io.ReadCloser
 	if container.Config.OpenStdin {
 	if container.Config.OpenStdin {
-		var stdinMaster io.WriteCloser
-		stdinMaster, stdinSlave, err = pty.Open()
-		if err != nil {
-			return err
-		}
-		container.ptyStdinMaster = stdinMaster
-		container.cmd.Stdin = stdinSlave
-		// FIXME: The following appears to be broken.
-		// "cannot set terminal process group (-1): Inappropriate ioctl for device"
-		// container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
+		container.cmd.Stdin = ptySlave
+		container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
 		go func() {
 		go func() {
 			defer container.stdin.Close()
 			defer container.stdin.Close()
 			Debugf("[startPty] Begin of stdin pipe")
 			Debugf("[startPty] Begin of stdin pipe")
-			io.Copy(stdinMaster, container.stdin)
+			io.Copy(ptyMaster, container.stdin)
 			Debugf("[startPty] End of stdin pipe")
 			Debugf("[startPty] End of stdin pipe")
 		}()
 		}()
 	}
 	}
 	if err := container.cmd.Start(); err != nil {
 	if err := container.cmd.Start(); err != nil {
 		return err
 		return err
 	}
 	}
-	stdoutSlave.Close()
-	stderrSlave.Close()
-	if stdinSlave != nil {
-		stdinSlave.Close()
-	}
+	ptySlave.Close()
 	return nil
 	return nil
 }
 }
 
 
@@ -278,10 +252,14 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
 				if cStderr != nil {
 				if cStderr != nil {
 					defer cStderr.Close()
 					defer cStderr.Close()
 				}
 				}
-				if container.Config.StdinOnce {
+				if container.Config.StdinOnce && !container.Config.Tty {
 					defer cStdin.Close()
 					defer cStdin.Close()
 				}
 				}
-				_, err := io.Copy(cStdin, stdin)
+				if container.Config.Tty {
+					_, err = CopyEscapable(cStdin, stdin)
+				} else {
+					_, err = io.Copy(cStdin, stdin)
+				}
 				if err != nil {
 				if err != nil {
 					Debugf("[error] attach stdin: %s\n", err)
 					Debugf("[error] attach stdin: %s\n", err)
 				}
 				}
@@ -365,6 +343,9 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
 }
 }
 
 
 func (container *Container) Start() error {
 func (container *Container) Start() error {
+	container.State.lock()
+	defer container.State.unlock()
+
 	if container.State.Running {
 	if container.State.Running {
 		return fmt.Errorf("The container %s is already running.", container.Id)
 		return fmt.Errorf("The container %s is already running.", container.Id)
 	}
 	}
@@ -431,6 +412,9 @@ func (container *Container) Start() error {
 	// FIXME: save state on disk *first*, then converge
 	// FIXME: save state on disk *first*, then converge
 	// this way disk state is used as a journal, eg. we can restore after crash etc.
 	// this way disk state is used as a journal, eg. we can restore after crash etc.
 	container.State.setRunning(container.cmd.Process.Pid)
 	container.State.setRunning(container.cmd.Process.Pid)
+
+	// Init the lock
+	container.waitLock = make(chan struct{})
 	container.ToDisk()
 	container.ToDisk()
 	go container.monitor()
 	go container.monitor()
 	return nil
 	return nil
@@ -530,19 +514,9 @@ func (container *Container) monitor() {
 		Debugf("%s: Error close stderr: %s", container.Id, err)
 		Debugf("%s: Error close stderr: %s", container.Id, err)
 	}
 	}
 
 
-	if container.ptyStdinMaster != nil {
-		if err := container.ptyStdinMaster.Close(); err != nil {
-			Debugf("%s: Error close pty stdin master: %s", container.Id, err)
-		}
-	}
-	if container.ptyStdoutMaster != nil {
-		if err := container.ptyStdoutMaster.Close(); err != nil {
-			Debugf("%s: Error close pty stdout master: %s", container.Id, err)
-		}
-	}
-	if container.ptyStderrMaster != nil {
-		if err := container.ptyStderrMaster.Close(); err != nil {
-			Debugf("%s: Error close pty stderr master: %s", container.Id, err)
+	if container.ptyMaster != nil {
+		if err := container.ptyMaster.Close(); err != nil {
+			Debugf("%s: Error closing Pty master: %s", container.Id, err)
 		}
 		}
 	}
 	}
 
 
@@ -557,6 +531,10 @@ func (container *Container) monitor() {
 
 
 	// Report status back
 	// Report status back
 	container.State.setStopped(exitCode)
 	container.State.setStopped(exitCode)
+
+	// Release the lock
+	close(container.waitLock)
+
 	if err := container.ToDisk(); err != nil {
 	if err := container.ToDisk(); err != nil {
 		// FIXME: there is a race condition here which causes this to fail during the unit tests.
 		// FIXME: there is a race condition here which causes this to fail during the unit tests.
 		// If another goroutine was waiting for Wait() to return before removing the container's root
 		// If another goroutine was waiting for Wait() to return before removing the container's root
@@ -569,7 +547,7 @@ func (container *Container) monitor() {
 }
 }
 
 
 func (container *Container) kill() error {
 func (container *Container) kill() error {
-	if container.cmd == nil {
+	if !container.State.Running || container.cmd == nil {
 		return nil
 		return nil
 	}
 	}
 	if err := container.cmd.Process.Kill(); err != nil {
 	if err := container.cmd.Process.Kill(); err != nil {
@@ -581,13 +559,14 @@ func (container *Container) kill() error {
 }
 }
 
 
 func (container *Container) Kill() error {
 func (container *Container) Kill() error {
-	if !container.State.Running {
-		return nil
-	}
+	container.State.lock()
+	defer container.State.unlock()
 	return container.kill()
 	return container.kill()
 }
 }
 
 
 func (container *Container) Stop() error {
 func (container *Container) Stop() error {
+	container.State.lock()
+	defer container.State.unlock()
 	if !container.State.Running {
 	if !container.State.Running {
 		return nil
 		return nil
 	}
 	}
@@ -596,7 +575,7 @@ func (container *Container) Stop() error {
 	if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil {
 	if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil {
 		log.Print(string(output))
 		log.Print(string(output))
 		log.Print("Failed to send SIGTERM to the process, force killing")
 		log.Print("Failed to send SIGTERM to the process, force killing")
-		if err := container.Kill(); err != nil {
+		if err := container.kill(); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
@@ -604,7 +583,7 @@ func (container *Container) Stop() error {
 	// 2. Wait for the process to exit on its own
 	// 2. Wait for the process to exit on its own
 	if err := container.WaitTimeout(10 * time.Second); err != nil {
 	if err := container.WaitTimeout(10 * time.Second); err != nil {
 		log.Printf("Container %v failed to exit within 10 seconds of SIGTERM - using the force", container.Id)
 		log.Printf("Container %v failed to exit within 10 seconds of SIGTERM - using the force", container.Id)
-		if err := container.Kill(); err != nil {
+		if err := container.kill(); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
@@ -623,10 +602,7 @@ func (container *Container) Restart() error {
 
 
 // Wait blocks until the container stops running, then returns its exit code.
 // Wait blocks until the container stops running, then returns its exit code.
 func (container *Container) Wait() int {
 func (container *Container) Wait() int {
-
-	for container.State.Running {
-		container.State.wait()
-	}
+	<-container.waitLock
 	return container.State.ExitCode
 	return container.State.ExitCode
 }
 }
 
 

+ 1 - 0
container_test.go

@@ -267,6 +267,7 @@ func TestStart(t *testing.T) {
 	// Try to avoid the timeoout in destroy. Best effort, don't check error
 	// Try to avoid the timeoout in destroy. Best effort, don't check error
 	cStdin, _ := container.StdinPipe()
 	cStdin, _ := container.StdinPipe()
 	cStdin.Close()
 	cStdin.Close()
+	container.WaitTimeout(2 * time.Second)
 }
 }
 
 
 func TestRun(t *testing.T) {
 func TestRun(t *testing.T) {

+ 13 - 23
docker/docker.go

@@ -8,7 +8,6 @@ import (
 	"io"
 	"io"
 	"log"
 	"log"
 	"os"
 	"os"
-	"os/signal"
 )
 )
 
 
 var GIT_COMMIT string
 var GIT_COMMIT string
@@ -57,29 +56,21 @@ func daemon() error {
 }
 }
 
 
 func runCommand(args []string) error {
 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
-		}
-		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)
-			}
-		}()
-	}
 	// FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose
 	// 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
 	// CloseWrite(), which we need to cleanly signal that stdin is closed without
 	// closing the connection.
 	// closing the connection.
 	// See http://code.google.com/p/go/issues/detail?id=3345
 	// 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 {
 	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 := rcli.SetRawTerminal(); err != nil {
+				return err
+			} else {
+				defer rcli.RestoreTerminal(oldState)
+			}
+		}
 		receiveStdout := docker.Go(func() error {
 		receiveStdout := docker.Go(func() error {
 			_, err := io.Copy(os.Stdout, conn)
 			_, err := io.Copy(os.Stdout, conn)
 			return err
 			return err
@@ -104,12 +95,11 @@ func runCommand(args []string) error {
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
-		if err := rcli.LocalCall(service, os.Stdin, os.Stdout, args...); err != nil {
+		dockerConn := rcli.NewDockerLocalConn(os.Stdout)
+		defer dockerConn.Close()
+		if err := rcli.LocalCall(service, os.Stdin, dockerConn, args...); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
-	if oldState != nil {
-		term.Restore(int(os.Stdin.Fd()), oldState)
-	}
 	return nil
 	return nil
 }
 }

+ 33 - 1
docs/README.md

@@ -39,4 +39,36 @@ Notes
 * The index.html and gettingstarted.html files are copied from the source dir to the output dir without modification.
 * The index.html and gettingstarted.html files are copied from the source dir to the output dir without modification.
 So changes to those pages should be made directly in html
 So changes to those pages should be made directly in html
 * For the template the css is compiled from less. When changes are needed they can be compiled using
 * For the template the css is compiled from less. When changes are needed they can be compiled using
-lessc ``lessc main.less`` or watched using watch-lessc ``watch-lessc -i main.less -o main.css``
+lessc ``lessc main.less`` or watched using watch-lessc ``watch-lessc -i main.less -o main.css``
+
+
+Guides on using sphinx
+----------------------
+* To make links to certain pages create a link target like so:
+
+  ```
+    .. _hello_world:
+
+    Hello world
+    ===========
+
+    This is.. (etc.)
+  ```
+
+  The ``_hello_world:`` will make it possible to link to this position (page and marker) from all other pages.
+
+* Notes, warnings and alarms
+
+  ```
+    # a note (use when something is important)
+    .. note::
+
+    # a warning (orange)
+    .. warning::
+
+    # danger (red, use sparsely)
+    .. danger::
+
+* Code examples
+
+  Start without $, so it's easy to copy and paste.

+ 2 - 1
docs/sources/commandline/basics.rst

@@ -69,7 +69,8 @@ Expose a service on a TCP port
 
 
   # Connect to the public port via the host's public address
   # Connect to the public port via the host's public address
   # Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
   # Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
-  echo hello world | nc $(hostname) $PORT
+  IP=$(ifconfig eth0 | perl -n -e 'if (m/inet addr:([\d\.]+)/g) { print $1 }')
+  echo hello world | nc $IP $PORT
 
 
   # Verify that the network connection worked
   # Verify that the network connection worked
   echo "Daemon received: $(docker logs $JOB)"
   echo "Daemon received: $(docker logs $JOB)"

+ 4 - 0
docs/sources/examples/example_header.inc

@@ -0,0 +1,4 @@
+
+.. note::
+    
+    This example assumes you have Docker running in daemon mode. For more information please see :ref:`running_examples`

+ 3 - 1
docs/sources/examples/hello_world.rst

@@ -6,8 +6,10 @@
 
 
 Hello World
 Hello World
 ===========
 ===========
-This is the most basic example available for using Docker. The example assumes you have Docker installed.
 
 
+.. include:: example_header.inc
+
+This is the most basic example available for using Docker.
 
 
 Download the base container
 Download the base container
 
 

+ 4 - 1
docs/sources/examples/hello_world_daemon.rst

@@ -6,6 +6,9 @@
 
 
 Hello World Daemon
 Hello World Daemon
 ==================
 ==================
+
+.. include:: example_header.inc
+
 The most boring daemon ever written.
 The most boring daemon ever written.
 
 
 This example assumes you have Docker installed and with the base image already imported ``docker pull base``.
 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.
 
 
     CONTAINER_ID=$(docker run -d base /bin/sh -c "while true; do echo hello world; sleep 1; done")
     CONTAINER_ID=$(docker run -d base /bin/sh -c "while true; do echo hello world; sleep 1; done")
 
 
-We are going to run a simple hello world daemon in a new container made from the busybox daemon.
+We are going to run a simple hello world daemon in a new container made from the base image.
 
 
 - **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon.
 - **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon.
 - **"base"** is the image we want to run the command inside of.
 - **"base"** is the image we want to run the command inside of.

+ 1 - 0
docs/sources/examples/index.rst

@@ -12,6 +12,7 @@ Contents:
 .. toctree::
 .. toctree::
    :maxdepth: 1
    :maxdepth: 1
 
 
+   running_examples
    hello_world
    hello_world
    hello_world_daemon
    hello_world_daemon
    python_web_app
    python_web_app

+ 20 - 0
docs/sources/examples/python_web_app.rst

@@ -6,6 +6,9 @@
 
 
 Building a python web app
 Building a python web app
 =========================
 =========================
+
+.. include:: example_header.inc
+
 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.
 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.
 
 
 **Steps:**
 **Steps:**
@@ -45,6 +48,11 @@ Save the changed we just made in the container to a new image called "_/builds/g
 
 
     WEB_WORKER=$(docker run -d -p 5000 $BUILD_IMG /usr/local/bin/runapp)
     WEB_WORKER=$(docker run -d -p 5000 $BUILD_IMG /usr/local/bin/runapp)
 
 
+- **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon.
+  **"-p 5000"* the web app is going to listen on this port, so it must be mapped from the container to the host system.
+- **"$BUILD_IMG"** is the image we want to run the command inside of.
+- **/usr/local/bin/runapp** is the command which starts the web app.
+
 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.
 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.
 
 
 .. code-block:: bash
 .. code-block:: bash
@@ -54,6 +62,18 @@ Use the new image we just created and create a new container with network port 5
 
 
 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.
 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.
 
 
+.. code-block:: bash
+
+    WEB_PORT=$(docker port $WEB_WORKER 5000)
+
+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.
+
+.. code-block:: bash
+
+    curl http://`hostname`:$WEB_PORT
+      Hello world!
+
+access the web app using curl. If everything worked as planned you should see the line "Hello world!" inside of your console.
 
 
 **Video:**
 **Video:**
 
 

+ 33 - 0
docs/sources/examples/running_examples.rst

@@ -0,0 +1,33 @@
+:title: Running the Examples
+:description: An overview on how to run the docker examples
+:keywords: docker, examples, how to
+
+.. _running_examples:
+
+Running The Examples
+--------------------
+
+There are two ways to run docker, daemon mode and standalone mode.
+
+When you run the docker command it will first check if there is a docker daemon running in the background it can connect to.
+
+* If it exists it will use that daemon to run all of the commands.
+* If it does not exist docker will run in standalone mode (docker will exit after each command).
+
+Docker needs to be run from a privileged account (root).
+
+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.
+
+   .. code-block:: bash
+
+      # starting docker daemon in the background
+      sudo docker -d &
+
+      # now you can run docker commands from any account.
+      docker <command>
+
+2. Standalone: You need to run every command as root, or using sudo
+
+   .. code-block:: bash
+
+       sudo docker <command>

+ 1 - 1
docs/sources/examples/running_ssh_service.rst

@@ -7,7 +7,7 @@
 Create an ssh daemon service
 Create an ssh daemon service
 ============================
 ============================
 
 
-
+.. include:: example_header.inc
 
 
 
 
 **Video:**
 **Video:**

+ 22 - 1
docs/theme/docker/static/css/main.css

@@ -82,7 +82,7 @@ h4 {
 .btn-custom {
 .btn-custom {
   background-color: #292929 !important;
   background-color: #292929 !important;
   background-repeat: repeat-x;
   background-repeat: repeat-x;
-  filter: progid:dximagetransform.microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
   background-image: -khtml-gradient(linear, left top, left bottom, from(#515151), to(#282828));
   background-image: -khtml-gradient(linear, left top, left bottom, from(#515151), to(#282828));
   background-image: -moz-linear-gradient(top, #515151, #282828);
   background-image: -moz-linear-gradient(top, #515151, #282828);
   background-image: -ms-linear-gradient(top, #515151, #282828);
   background-image: -ms-linear-gradient(top, #515151, #282828);
@@ -131,6 +131,27 @@ section.header {
   margin: 15px 15px 15px 0;
   margin: 15px 15px 15px 0;
   border: 2px solid gray;
   border: 2px solid gray;
 }
 }
+.admonition {
+  padding: 10px;
+  border: 1px solid grey;
+  margin-bottom: 10px;
+  margin-top: 10px;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.admonition .admonition-title {
+  font-weight: bold;
+}
+.admonition.note {
+  background-color: #f1ebba;
+}
+.admonition.warning {
+  background-color: #eed9af;
+}
+.admonition.danger {
+  background-color: #e9bcab;
+}
 /* ===================
 /* ===================
 	left navigation
 	left navigation
 ===================== */
 ===================== */

+ 26 - 0
docs/theme/docker/static/css/main.less

@@ -179,7 +179,33 @@ section.header {
   border: 2px solid gray;
   border: 2px solid gray;
 }
 }
 
 
+.admonition {
+  padding: 10px;
+  border: 1px solid grey;
+
+  margin-bottom: 10px;
+  margin-top: 10px;
+
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+
+.admonition .admonition-title {
+  font-weight: bold;
+}
+
+.admonition.note {
+  background-color: rgb(241, 235, 186);
+}
 
 
+.admonition.warning {
+  background-color: rgb(238, 217, 175);
+}
+
+.admonition.danger {
+  background-color: rgb(233, 188, 171);
+}
 
 
 /* ===================
 /* ===================
 	left navigation
 	left navigation

+ 3 - 1
network.go

@@ -111,6 +111,8 @@ func checkRouteOverlaps(dockerNetwork *net.IPNet) error {
 }
 }
 
 
 func CreateBridgeIface(ifaceName string) error {
 func CreateBridgeIface(ifaceName string) error {
+	// FIXME: try more IP ranges
+	// FIXME: try bigger ranges! /24 is too small.
 	addrs := []string{"172.16.42.1/24", "10.0.42.1/24", "192.168.42.1/24"}
 	addrs := []string{"172.16.42.1/24", "10.0.42.1/24", "192.168.42.1/24"}
 
 
 	var ifaceAddr string
 	var ifaceAddr string
@@ -127,7 +129,7 @@ func CreateBridgeIface(ifaceName string) error {
 		}
 		}
 	}
 	}
 	if ifaceAddr == "" {
 	if ifaceAddr == "" {
-		return fmt.Errorf("Impossible to create a bridge. Please create a bridge manually and restart docker with -br <bridgeName>")
+		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)
 	} else {
 	} else {
 		Debugf("Creating bridge %s with network %s", ifaceName, ifaceAddr)
 		Debugf("Creating bridge %s with network %s", ifaceName, ifaceAddr)
 	}
 	}

+ 0 - 38
rcli/http.go

@@ -1,38 +0,0 @@
-package rcli
-
-import (
-	"fmt"
-	"net/http"
-	"net/url"
-	"path"
-)
-
-// Use this key to encode an RPC call into an URL,
-// eg. domain.tld/path/to/method?q=get_user&q=gordon
-const ARG_URL_KEY = "q"
-
-func URLToCall(u *url.URL) (method string, args []string) {
-	return path.Base(u.Path), u.Query()[ARG_URL_KEY]
-}
-
-func ListenAndServeHTTP(addr string, service Service) error {
-	return http.ListenAndServe(addr, http.HandlerFunc(
-		func(w http.ResponseWriter, r *http.Request) {
-			cmd, args := URLToCall(r.URL)
-			if err := call(service, r.Body, &AutoFlush{w}, append([]string{cmd}, args...)...); err != nil {
-				fmt.Fprintln(w, "Error:", err.Error())
-			}
-		}))
-}
-
-type AutoFlush struct {
-	http.ResponseWriter
-}
-
-func (w *AutoFlush) Write(data []byte) (int, error) {
-	ret, err := w.ResponseWriter.Write(data)
-	if flusher, ok := w.ResponseWriter.(http.Flusher); ok {
-		flusher.Flush()
-	}
-	return ret, err
-}

+ 96 - 4
rcli/tcp.go

@@ -2,6 +2,7 @@ package rcli
 
 
 import (
 import (
 	"bufio"
 	"bufio"
+	"bytes"
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
@@ -15,22 +16,109 @@ import (
 var DEBUG_FLAG bool = false
 var DEBUG_FLAG bool = false
 var CLIENT_SOCKET io.Writer = nil
 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) Flush() error {
+	_, err := c.Write([]byte{})
+	return 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`,
 // Connect to a remote endpoint using protocol `proto` and address `addr`,
 // issue a single call, and return the result.
 // issue a single call, and return the result.
 // `proto` may be "tcp", "unix", etc. See the `net` package for available protocols.
 // `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)
 	cmd, err := json.Marshal(args)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	conn, err := net.Dial(proto, addr)
+	conn, err := dialDocker(proto, addr)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 	if _, err := fmt.Fprintln(conn, string(cmd)); err != nil {
 	if _, err := fmt.Fprintln(conn, string(cmd)); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	return conn.(*net.TCPConn), nil
+	return conn, nil
 }
 }
 
 
 // Listen on `addr`, using protocol `proto`, for incoming rcli calls,
 // Listen on `addr`, using protocol `proto`, for incoming rcli calls,
@@ -46,6 +134,10 @@ func ListenAndServe(proto, addr string, service Service) error {
 		if conn, err := listener.Accept(); err != nil {
 		if conn, err := listener.Accept(); err != nil {
 			return err
 			return err
 		} else {
 		} else {
+			conn, err := newDockerServerConn(conn)
+			if err != nil {
+				return err
+			}
 			go func() {
 			go func() {
 				if DEBUG_FLAG {
 				if DEBUG_FLAG {
 					CLIENT_SOCKET = conn
 					CLIENT_SOCKET = conn
@@ -63,7 +155,7 @@ func ListenAndServe(proto, addr string, service Service) error {
 
 
 // Parse an rcli call on a new connection, and pass it to `service` if it
 // Parse an rcli call on a new connection, and pass it to `service` if it
 // is valid.
 // is valid.
-func Serve(conn io.ReadWriter, service Service) error {
+func Serve(conn DockerConn, service Service) error {
 	r := bufio.NewReader(conn)
 	r := bufio.NewReader(conn)
 	var args []string
 	var args []string
 	if line, err := r.ReadString('\n'); err != nil {
 	if line, err := r.ReadString('\n'); err != nil {

+ 89 - 5
rcli/types.go

@@ -8,15 +8,99 @@ package rcli
 // are the usual suspects.
 // are the usual suspects.
 
 
 import (
 import (
-	"errors"
 	"flag"
 	"flag"
 	"fmt"
 	"fmt"
+	"github.com/dotcloud/docker/term"
 	"io"
 	"io"
 	"log"
 	"log"
+	"net"
+	"os"
 	"reflect"
 	"reflect"
 	"strings"
 	"strings"
 )
 )
 
 
+type DockerConnOptions struct {
+	RawTerminal bool
+}
+
+type DockerConn interface {
+	io.ReadWriteCloser
+	CloseWrite() error
+	CloseRead() error
+	GetOptions() *DockerConnOptions
+	SetOptionRawTerminal()
+	Flush() error
+}
+
+type DockerLocalConn struct {
+	writer     io.WriteCloser
+	savedState *term.State
+}
+
+func NewDockerLocalConn(w io.WriteCloser) *DockerLocalConn {
+	return &DockerLocalConn{
+		writer: w,
+	}
+}
+
+func (c *DockerLocalConn) Read(b []byte) (int, error) {
+	return 0, fmt.Errorf("DockerLocalConn does not implement Read()")
+}
+
+func (c *DockerLocalConn) Write(b []byte) (int, error) { return c.writer.Write(b) }
+
+func (c *DockerLocalConn) Close() error {
+	if c.savedState != nil {
+		RestoreTerminal(c.savedState)
+		c.savedState = nil
+	}
+	return c.writer.Close()
+}
+
+func (c *DockerLocalConn) Flush() error { return nil }
+
+func (c *DockerLocalConn) CloseWrite() error { return nil }
+
+func (c *DockerLocalConn) CloseRead() error { return nil }
+
+func (c *DockerLocalConn) GetOptions() *DockerConnOptions { return nil }
+
+func (c *DockerLocalConn) SetOptionRawTerminal() {
+	if state, err := SetRawTerminal(); err != nil {
+		if os.Getenv("DEBUG") != "" {
+			log.Printf("Can't set the terminal in raw mode: %s", err)
+		}
+	} else {
+		c.savedState = state
+	}
+}
+
+var UnknownDockerProto = fmt.Errorf("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 {
 type Service interface {
 	Name() string
 	Name() string
 	Help() string
 	Help() string
@@ -26,11 +110,11 @@ type Cmd func(io.ReadCloser, io.Writer, ...string) error
 type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error
 type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error
 
 
 // FIXME: For reverse compatibility
 // 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...)
 	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 {
 	if len(args) == 0 {
 		args = []string{"help"}
 		args = []string{"help"}
 	}
 	}
@@ -49,7 +133,7 @@ func LocalCall(service Service, stdin io.ReadCloser, stdout io.Writer, args ...s
 	if method != nil {
 	if method != nil {
 		return method(stdin, stdout, flags.Args()[1:]...)
 		return method(stdin, stdout, flags.Args()[1:]...)
 	}
 	}
-	return errors.New("No such command: " + cmd)
+	return fmt.Errorf("No such command: %s", cmd)
 }
 }
 
 
 func getMethod(service Service, name string) Cmd {
 func getMethod(service Service, name string) Cmd {
@@ -59,7 +143,7 @@ func getMethod(service Service, name string) Cmd {
 				stdout.Write([]byte(service.Help()))
 				stdout.Write([]byte(service.Help()))
 			} else {
 			} else {
 				if method := getMethod(service, args[0]); method == nil {
 				if method := getMethod(service, args[0]); method == nil {
-					return errors.New("No such command: " + args[0])
+					return fmt.Errorf("No such command: %s", args[0])
 				} else {
 				} else {
 					method(stdin, stdout, "--help")
 					method(stdin, stdout, "--help")
 				}
 				}

+ 27 - 0
rcli/utils.go

@@ -0,0 +1,27 @@
+package rcli
+
+import (
+	"github.com/dotcloud/docker/term"
+	"os"
+	"os/signal"
+)
+
+//FIXME: move these function to utils.go (in rcli to avoid import loop)
+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() {
+		_ = <-c
+		term.Restore(int(os.Stdin.Fd()), oldState)
+		os.Exit(0)
+	}()
+	return oldState, err
+}
+
+func RestoreTerminal(state *term.State) {
+	term.Restore(int(os.Stdin.Fd()), state)
+}

+ 3 - 3
runtime.go

@@ -116,7 +116,6 @@ func (runtime *Runtime) Load(id string) (*Container, error) {
 	if err := container.FromDisk(); err != nil {
 	if err := container.FromDisk(); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	container.State.initLock()
 	if container.Id != id {
 	if container.Id != id {
 		return container, fmt.Errorf("Container %s is stored at %s", container.Id, id)
 		return container, fmt.Errorf("Container %s is stored at %s", container.Id, id)
 	}
 	}
@@ -136,6 +135,7 @@ func (runtime *Runtime) Register(container *Container) error {
 	}
 	}
 
 
 	// FIXME: if the container is supposed to be running but is not, auto restart it?
 	// FIXME: if the container is supposed to be running but is not, auto restart it?
+	//        if so, then we need to restart monitor and init a new lock
 	// If the container is supposed to be running, make sure of it
 	// If the container is supposed to be running, make sure of it
 	if container.State.Running {
 	if container.State.Running {
 		if output, err := exec.Command("lxc-info", "-n", container.Id).CombinedOutput(); err != nil {
 		if output, err := exec.Command("lxc-info", "-n", container.Id).CombinedOutput(); err != nil {
@@ -150,10 +150,10 @@ func (runtime *Runtime) Register(container *Container) error {
 			}
 			}
 		}
 		}
 	}
 	}
+	container.State.initLock()
 
 
 	container.runtime = runtime
 	container.runtime = runtime
-	// Setup state lock (formerly in newState()
-	container.State.initLock()
+
 	// Attach to stdout and stderr
 	// Attach to stdout and stderr
 	container.stderr = newWriteBroadcaster()
 	container.stderr = newWriteBroadcaster()
 	container.stdout = newWriteBroadcaster()
 	container.stdout = newWriteBroadcaster()

+ 4 - 2
runtime_test.go

@@ -1,6 +1,7 @@
 package docker
 package docker
 
 
 import (
 import (
+	"github.com/dotcloud/docker/rcli"
 	"io"
 	"io"
 	"io/ioutil"
 	"io/ioutil"
 	"os"
 	"os"
@@ -77,7 +78,7 @@ func init() {
 		runtime: runtime,
 		runtime: runtime,
 	}
 	}
 	// Retrieve the Image
 	// Retrieve the Image
-	if err := srv.CmdPull(os.Stdin, os.Stdout, unitTestImageName); err != nil {
+	if err := srv.CmdPull(os.Stdin, rcli.NewDockerLocalConn(os.Stdout), unitTestImageName); err != nil {
 		panic(err)
 		panic(err)
 	}
 	}
 }
 }
@@ -314,7 +315,7 @@ func TestRestore(t *testing.T) {
 	// Simulate a crash/manual quit of dockerd: process dies, states stays 'Running'
 	// Simulate a crash/manual quit of dockerd: process dies, states stays 'Running'
 	cStdin, _ := container2.StdinPipe()
 	cStdin, _ := container2.StdinPipe()
 	cStdin.Close()
 	cStdin.Close()
-	if err := container2.WaitTimeout(time.Second); err != nil {
+	if err := container2.WaitTimeout(2 * time.Second); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	container2.State.Running = true
 	container2.State.Running = true
@@ -358,4 +359,5 @@ func TestRestore(t *testing.T) {
 	if err := container3.Run(); err != nil {
 	if err := container3.Run(); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
+	container2.State.Running = false
 }
 }

+ 6 - 17
state.go

@@ -11,9 +11,7 @@ type State struct {
 	Pid       int
 	Pid       int
 	ExitCode  int
 	ExitCode  int
 	StartedAt time.Time
 	StartedAt time.Time
-
-	stateChangeLock *sync.Mutex
-	stateChangeCond *sync.Cond
+	l         *sync.Mutex
 }
 }
 
 
 // String returns a human-readable description of the state
 // String returns a human-readable description of the state
@@ -29,31 +27,22 @@ func (s *State) setRunning(pid int) {
 	s.ExitCode = 0
 	s.ExitCode = 0
 	s.Pid = pid
 	s.Pid = pid
 	s.StartedAt = time.Now()
 	s.StartedAt = time.Now()
-	s.broadcast()
 }
 }
 
 
 func (s *State) setStopped(exitCode int) {
 func (s *State) setStopped(exitCode int) {
 	s.Running = false
 	s.Running = false
 	s.Pid = 0
 	s.Pid = 0
 	s.ExitCode = exitCode
 	s.ExitCode = exitCode
-	s.broadcast()
 }
 }
 
 
 func (s *State) initLock() {
 func (s *State) initLock() {
-	if s.stateChangeLock == nil {
-		s.stateChangeLock = &sync.Mutex{}
-		s.stateChangeCond = sync.NewCond(s.stateChangeLock)
-	}
+	s.l = &sync.Mutex{}
 }
 }
 
 
-func (s *State) broadcast() {
-	s.stateChangeLock.Lock()
-	s.stateChangeCond.Broadcast()
-	s.stateChangeLock.Unlock()
+func (s *State) lock() {
+	s.l.Lock()
 }
 }
 
 
-func (s *State) wait() {
-	s.stateChangeLock.Lock()
-	s.stateChangeCond.Wait()
-	s.stateChangeLock.Unlock()
+func (s *State) unlock() {
+	s.l.Unlock()
 }
 }

+ 2 - 1
term/termios_linux.go

@@ -15,7 +15,8 @@ void MakeRaw(int fd) {
   ioctl(fd, TCGETS, &t);
   ioctl(fd, TCGETS, &t);
 
 
   t.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
   t.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
-  t.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN);
+  t.c_oflag &= ~OPOST;
+  t.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
   t.c_cflag &= ~(CSIZE | PARENB);
   t.c_cflag &= ~(CSIZE | PARENB);
   t.c_cflag |= CS8;
   t.c_cflag |= CS8;
 
 

+ 43 - 0
utils.go

@@ -341,3 +341,46 @@ func TruncateId(id string) string {
 	}
 	}
 	return id[:shortLen]
 	return id[:shortLen]
 }
 }
+
+// Code c/c from io.Copy() modified to handle escape sequence
+func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) {
+	buf := make([]byte, 32*1024)
+	for {
+		nr, er := src.Read(buf)
+		if nr > 0 {
+			// ---- Docker addition
+			// char 16 is C-p
+			if nr == 1 && buf[0] == 16 {
+				nr, er = src.Read(buf)
+				// char 17 is C-q
+				if nr == 1 && buf[0] == 17 {
+					if err := src.Close(); err != nil {
+						return 0, err
+					}
+					return 0, io.EOF
+				}
+			}
+			// ---- End of docker
+			nw, ew := dst.Write(buf[0:nr])
+			if nw > 0 {
+				written += int64(nw)
+			}
+			if ew != nil {
+				err = ew
+				break
+			}
+			if nr != nw {
+				err = io.ErrShortWrite
+				break
+			}
+		}
+		if er == io.EOF {
+			break
+		}
+		if er != nil {
+			err = er
+			break
+		}
+	}
+	return written, err
+}