Просмотр исходного кода

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

John Costa 12 лет назад
Родитель
Сommit
418ef43fbb

+ 9 - 1
README.md

@@ -134,6 +134,12 @@ docker pull base
 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
 --------------------------------------
@@ -183,7 +189,9 @@ JOB=$(docker run -d -p 4444 base /bin/nc -l -p 4444)
 PORT=$(docker port $JOB 4444)
 
 # 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
 echo "Daemon received: $(docker logs $JOB)"

+ 21 - 6
commands.go

@@ -18,7 +18,7 @@ import (
 	"unicode"
 )
 
-const VERSION = "0.1.3"
+const VERSION = "0.1.4"
 
 var GIT_COMMIT string
 
@@ -62,7 +62,7 @@ func (srv *Server) Help() string {
 }
 
 // 'docker login': login / register a user to registry service.
-func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	// Read a line on raw terminal with support for simple backspace
 	// sequences and echo.
 	//
@@ -113,6 +113,8 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin
 		return readStringOnRawTerminal(stdin, stdout, false)
 	}
 
+	stdout.SetOptionRawTerminal()
+
 	cmd := rcli.Subcmd(stdout, "login", "", "Register or Login to the docker registry server")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -417,7 +419,8 @@ func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string
 	return nil
 }
 
-func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
+	stdout.Flush()
 	cmd := rcli.Subcmd(stdout, "import", "[OPTIONS] URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball")
 	var archive io.Reader
 	var resp *http.Response
@@ -464,7 +467,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...stri
 	return nil
 }
 
-func (srv *Server) CmdPush(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
 	cmd := rcli.Subcmd(stdout, "push", "NAME", "Push an image or a repository to the registry")
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -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))
 }
 
-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")
 	if err := cmd.Parse(args); err != 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)
 	}
 
+	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)
 }
 
@@ -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)
 }
 
-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)
 	if err != nil {
 		return err
@@ -884,6 +892,13 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
 		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
 	container, err := srv.runtime.Create(config)
 	if err != nil {

+ 89 - 21
commands_test.go

@@ -2,8 +2,8 @@ package docker
 
 import (
 	"bufio"
-	"bytes"
 	"fmt"
+	"github.com/dotcloud/docker/rcli"
 	"io"
 	"io/ioutil"
 	"strings"
@@ -69,15 +69,27 @@ func TestRunHostname(t *testing.T) {
 
 	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)
 		}
-	})
-	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) {
@@ -93,7 +105,7 @@ func TestRunExit(t *testing.T) {
 	stdout, stdoutPipe := io.Pipe()
 	c1 := make(chan struct{})
 	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)
 	}()
 
@@ -147,7 +159,7 @@ func TestRunDisconnect(t *testing.T) {
 	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, stdoutPipe, "-i", GetTestImage(runtime).Id, "/bin/cat")
+		srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", GetTestImage(runtime).Id, "/bin/cat")
 		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.
 // 'docker run -i -a stdin' should sends the client's stdin to the command,
 // then detach from it and print the container id.
-func TestAttachStdin(t *testing.T) {
+func TestRunAttachStdin(t *testing.T) {
 	runtime, err := newTestRuntime()
 	if err != nil {
 		t.Fatal(err)
@@ -190,31 +248,41 @@ func TestAttachStdin(t *testing.T) {
 	defer nuke(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{})
 	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)
 	}()
 
-	// 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)
 		}
-		stdinW.Close()
-		<-ch
 	})
 
-	// Check output
-	cmdOutput := string(stdout.Bytes())
 	container := runtime.List()[0]
+
+	// Check output
+	cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
+	if err != nil {
+		t.Fatal(err)
+	}
 	if cmdOutput != container.ShortId()+"\n" {
 		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() {
 		container.Wait()
 	})
@@ -270,7 +338,7 @@ func TestAttachDisconnect(t *testing.T) {
 	go func() {
 		// We're simulating a disconnect so the return value doesn't matter. What matters is the
 		// fact that CmdAttach returns.
-		srv.CmdAttach(stdin, stdoutPipe, container.Id)
+		srv.CmdAttach(stdin, rcli.NewDockerLocalConn(stdoutPipe), container.Id)
 		close(c1)
 	}()
 

+ 39 - 63
container.go

@@ -40,11 +40,11 @@ type Container struct {
 	stdin       io.ReadCloser
 	stdinPipe   io.WriteCloser
 
-	ptyStdinMaster  io.Closer
-	ptyStdoutMaster io.Closer
-	ptyStderrMaster io.Closer
+	ptyMaster io.Closer
 
 	runtime *Runtime
+
+	waitLock chan struct{}
 }
 
 type Config struct {
@@ -180,63 +180,37 @@ func (container *Container) generateLXCConfig() error {
 }
 
 func (container *Container) startPty() error {
-	stdoutMaster, stdoutSlave, err := pty.Open()
+	ptyMaster, ptySlave, err := pty.Open()
 	if err != nil {
 		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
 	go func() {
 		defer container.stdout.CloseWriters()
 		Debugf("[startPty] Begin of stdout pipe")
-		io.Copy(container.stdout, stdoutMaster)
+		io.Copy(container.stdout, ptyMaster)
 		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
-	var stdinSlave io.ReadCloser
 	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() {
 			defer container.stdin.Close()
 			Debugf("[startPty] Begin of stdin pipe")
-			io.Copy(stdinMaster, container.stdin)
+			io.Copy(ptyMaster, container.stdin)
 			Debugf("[startPty] End of stdin pipe")
 		}()
 	}
 	if err := container.cmd.Start(); err != nil {
 		return err
 	}
-	stdoutSlave.Close()
-	stderrSlave.Close()
-	if stdinSlave != nil {
-		stdinSlave.Close()
-	}
+	ptySlave.Close()
 	return nil
 }
 
@@ -278,10 +252,14 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
 				if cStderr != nil {
 					defer cStderr.Close()
 				}
-				if container.Config.StdinOnce {
+				if container.Config.StdinOnce && !container.Config.Tty {
 					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 {
 					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 {
+	container.State.lock()
+	defer container.State.unlock()
+
 	if container.State.Running {
 		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
 	// this way disk state is used as a journal, eg. we can restore after crash etc.
 	container.State.setRunning(container.cmd.Process.Pid)
+
+	// Init the lock
+	container.waitLock = make(chan struct{})
 	container.ToDisk()
 	go container.monitor()
 	return nil
@@ -530,19 +514,9 @@ func (container *Container) monitor() {
 		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
 	container.State.setStopped(exitCode)
+
+	// Release the lock
+	close(container.waitLock)
+
 	if err := container.ToDisk(); err != nil {
 		// 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
@@ -569,7 +547,7 @@ func (container *Container) monitor() {
 }
 
 func (container *Container) kill() error {
-	if container.cmd == nil {
+	if !container.State.Running || container.cmd == nil {
 		return nil
 	}
 	if err := container.cmd.Process.Kill(); err != nil {
@@ -581,13 +559,14 @@ 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()
 }
 
 func (container *Container) Stop() error {
+	container.State.lock()
+	defer container.State.unlock()
 	if !container.State.Running {
 		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 {
 		log.Print(string(output))
 		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
 		}
 	}
@@ -604,7 +583,7 @@ func (container *Container) Stop() error {
 	// 2. Wait for the process to exit on its own
 	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)
-		if err := container.Kill(); err != nil {
+		if err := container.kill(); err != nil {
 			return err
 		}
 	}
@@ -623,10 +602,7 @@ func (container *Container) Restart() error {
 
 // Wait blocks until the container stops running, then returns its exit code.
 func (container *Container) Wait() int {
-
-	for container.State.Running {
-		container.State.wait()
-	}
+	<-container.waitLock
 	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
 	cStdin, _ := container.StdinPipe()
 	cStdin.Close()
+	container.WaitTimeout(2 * time.Second)
 }
 
 func TestRun(t *testing.T) {

+ 13 - 23
docker/docker.go

@@ -8,7 +8,6 @@ import (
 	"io"
 	"log"
 	"os"
-	"os/signal"
 )
 
 var GIT_COMMIT string
@@ -57,29 +56,21 @@ func daemon() 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
 	// CloseWrite(), which we need to cleanly signal that stdin is closed without
 	// closing the connection.
 	// See http://code.google.com/p/go/issues/detail?id=3345
 	if conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...); err == nil {
+		options := conn.GetOptions()
+		if options.RawTerminal &&
+			term.IsTerminal(int(os.Stdin.Fd())) &&
+			os.Getenv("NORAW") == "" {
+			if oldState, err := rcli.SetRawTerminal(); err != nil {
+				return err
+			} else {
+				defer rcli.RestoreTerminal(oldState)
+			}
+		}
 		receiveStdout := docker.Go(func() error {
 			_, err := io.Copy(os.Stdout, conn)
 			return err
@@ -104,12 +95,11 @@ func runCommand(args []string) error {
 		if err != nil {
 			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
 		}
 	}
-	if oldState != nil {
-		term.Restore(int(os.Stdin.Fd()), oldState)
-	}
 	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.
 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
-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
   # 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
   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
 ===========
-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
 

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

@@ -6,6 +6,9 @@
 
 Hello World Daemon
 ==================
+
+.. include:: example_header.inc
+
 The most boring daemon ever written.
 
 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")
 
-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.
 - **"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::
    :maxdepth: 1
 
+   running_examples
    hello_world
    hello_world_daemon
    python_web_app

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

@@ -6,6 +6,9 @@
 
 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.
 
 **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)
 
+- **"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.
 
 .. 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.
 
+.. 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:**
 

+ 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
 ============================
 
-
+.. include:: example_header.inc
 
 
 **Video:**

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

@@ -82,7 +82,7 @@ h4 {
 .btn-custom {
   background-color: #292929 !important;
   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: -moz-linear-gradient(top, #515151, #282828);
   background-image: -ms-linear-gradient(top, #515151, #282828);
@@ -131,6 +131,27 @@ section.header {
   margin: 15px 15px 15px 0;
   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
 ===================== */

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

@@ -179,7 +179,33 @@ section.header {
   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

+ 3 - 1
network.go

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

+ 89 - 5
rcli/types.go

@@ -8,15 +8,99 @@ package rcli
 // are the usual suspects.
 
 import (
-	"errors"
 	"flag"
 	"fmt"
+	"github.com/dotcloud/docker/term"
 	"io"
 	"log"
+	"net"
+	"os"
 	"reflect"
 	"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 {
 	Name() 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
 
 // FIXME: For reverse compatibility
-func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func call(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
 	return LocalCall(service, stdin, stdout, args...)
 }
 
-func LocalCall(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func LocalCall(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
 	if len(args) == 0 {
 		args = []string{"help"}
 	}
@@ -49,7 +133,7 @@ func LocalCall(service Service, stdin io.ReadCloser, stdout io.Writer, args ...s
 	if method != nil {
 		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 {
@@ -59,7 +143,7 @@ func getMethod(service Service, name string) Cmd {
 				stdout.Write([]byte(service.Help()))
 			} else {
 				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 {
 					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 {
 		return nil, err
 	}
-	container.State.initLock()
 	if 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?
+	//        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 container.State.Running {
 		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
-	// Setup state lock (formerly in newState()
-	container.State.initLock()
+
 	// Attach to stdout and stderr
 	container.stderr = newWriteBroadcaster()
 	container.stdout = newWriteBroadcaster()

+ 4 - 2
runtime_test.go

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

+ 6 - 17
state.go

@@ -11,9 +11,7 @@ type State struct {
 	Pid       int
 	ExitCode  int
 	StartedAt time.Time
-
-	stateChangeLock *sync.Mutex
-	stateChangeCond *sync.Cond
+	l         *sync.Mutex
 }
 
 // String returns a human-readable description of the state
@@ -29,31 +27,22 @@ func (s *State) setRunning(pid int) {
 	s.ExitCode = 0
 	s.Pid = pid
 	s.StartedAt = time.Now()
-	s.broadcast()
 }
 
 func (s *State) setStopped(exitCode int) {
 	s.Running = false
 	s.Pid = 0
 	s.ExitCode = exitCode
-	s.broadcast()
 }
 
 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);
 
   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 |= CS8;
 

+ 43 - 0
utils.go

@@ -341,3 +341,46 @@ func TruncateId(id string) string {
 	}
 	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
+}