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