diff --git a/docker/docker.go b/docker/docker.go index fed1211a67..73caf65123 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -2,44 +2,196 @@ package main import ( "io" + "encoding/json" "log" "os" - "net/http" - "net/url" + "net" + "fmt" + "syscall" +"unsafe" ) -// 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" +type Termios struct { + Iflag uintptr + Oflag uintptr + Cflag uintptr + Lflag uintptr + Cc [20]byte + Ispeed uintptr + Ospeed uintptr +} -func CallToURL(host string, cmd string, args []string) *url.URL { - qValues := make(url.Values) - for _, v := range args { - qValues.Add(ARG_URL_KEY, v) - } - return &url.URL{ - Scheme: "http", - Host: host, - Path: "/" + cmd, - RawQuery: qValues.Encode(), - } +const ( + getTermios = syscall.TIOCGETA + setTermios = syscall.TIOCSETA +) + +const ( + // Input flags + inpck = 0x010 + istrip = 0x020 + icrnl = 0x100 + ixon = 0x200 + + // Output flags + opost = 0x1 + + // Control flags + cs8 = 0x300 + + // Local flags + icanon = 0x100 + iexten = 0x400 +) + +const ( + HUPCL = 0x4000 + ICANON = 0x100 + ICRNL = 0x100 + IEXTEN = 0x400 + BRKINT = 0x2 + CFLUSH = 0xf + CLOCAL = 0x8000 + CREAD = 0x800 + CS5 = 0x0 + CS6 = 0x100 + CS7 = 0x200 + CS8 = 0x300 + CSIZE = 0x300 + CSTART = 0x11 + CSTATUS = 0x14 + CSTOP = 0x13 + CSTOPB = 0x400 + CSUSP = 0x1a + IGNBRK = 0x1 + IGNCR = 0x80 + IGNPAR = 0x4 + IMAXBEL = 0x2000 + INLCR = 0x40 + INPCK = 0x10 + ISIG = 0x80 + ISTRIP = 0x20 + IUTF8 = 0x4000 + IXANY = 0x800 + IXOFF = 0x400 + IXON = 0x200 + NOFLSH = 0x80000000 + OCRNL = 0x10 + OFDEL = 0x20000 + OFILL = 0x80 + ONLCR = 0x2 + ONLRET = 0x40 + ONOCR = 0x20 + ONOEOT = 0x8 + OPOST = 0x1 +RENB = 0x1000 + PARMRK = 0x8 + PARODD = 0x2000 + + TOSTOP = 0x400000 + VDISCARD = 0xf + VDSUSP = 0xb + VEOF = 0x0 + VEOL = 0x1 + VEOL2 = 0x2 + VERASE = 0x3 + VINTR = 0x8 + VKILL = 0x5 + VLNEXT = 0xe + VMIN = 0x10 + VQUIT = 0x9 + VREPRINT = 0x6 + VSTART = 0xc + VSTATUS = 0x12 + VSTOP = 0xd + VSUSP = 0xa + VT0 = 0x0 + VT1 = 0x10000 + VTDLY = 0x10000 + VTIME = 0x11 + ECHO = 0x00000008 + + PENDIN = 0x20000000 +) + +type State struct { + termios Termios +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + var termios Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + var oldState State + if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 { + return nil, err + } + + newState := oldState.termios + newState.Iflag &^= istrip | INLCR | ICRNL | IGNCR | IXON | IXOFF + newState.Lflag &^= ECHO | ICANON | ISIG + if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 { + return nil, err + } + + return &oldState, nil +} + + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func Restore(fd int, state *State) error { + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0) + return err +} + +var oldState *State + +func Fatal(err error) { + if oldState != nil { + Restore(0, oldState) + } + log.Fatal(err) } func main() { - var cmd string - var args []string - if len(os.Args) >= 2 { - cmd = os.Args[1] + var err error + if IsTerminal(0) { + oldState, err = MakeRaw(0) + if err != nil { + panic(err) + } + defer Restore(0, oldState) } - if len(os.Args) >= 3 { - args = os.Args[2:] - } - u := CallToURL(os.Getenv("DOCKER"), cmd, args) - resp, err := http.Get(u.String()) + cmd, err := json.Marshal(os.Args[1:]) if err != nil { - log.Fatal(err) + Fatal(err) } - io.Copy(os.Stdout, resp.Body) + conn, err := net.Dial("tcp", os.Getenv("DOCKER")) + if err != nil { + Fatal(err) + } + if _, err := fmt.Fprintln(conn, string(cmd)); err != nil { + Fatal(err) + } + go func() { + if _, err := io.Copy(os.Stdout, conn); err != nil { + Fatal(err) + } + Restore(0, oldState) + os.Exit(0) + }() + if _, err := io.Copy(conn, os.Stdin); err != nil { + Fatal(err) + } + Restore(0, oldState) } diff --git a/dockerd/dockerd.go b/dockerd/dockerd.go index 3ae6b74973..c330dc4b8f 100644 --- a/dockerd/dockerd.go +++ b/dockerd/dockerd.go @@ -1,12 +1,14 @@ package main import ( + "bufio" "errors" "log" "io" "io/ioutil" - "net/http" + "net" "net/url" + "net/http" "os/exec" "flag" "reflect" @@ -22,6 +24,7 @@ import ( "sort" "os" "archive/tar" + "encoding/json" ) func (docker *Docker) CmdHelp(stdin io.ReadCloser, stdout io.Writer, args ...string) error { @@ -393,14 +396,60 @@ func startCommand(cmd *exec.Cmd, interactive bool) (io.WriteCloser, io.ReadClose return stdin, stdout, nil } +func (docker *Docker) ListenAndServeTCP(addr string) error { + listener, err := net.Listen("tcp", addr) + if err != nil { + return err + } + defer listener.Close() + for { + if conn, err := listener.Accept(); err != nil { + return err + } else { + go func() { + if err := docker.serve(conn); err != nil { + log.Printf("Error: " + err.Error() + "\n") + fmt.Fprintf(conn, "Error: " + err.Error() + "\n") + } + conn.Close() + }() + } + } + return nil +} + +func (docker *Docker) ListenAndServeHTTP(addr string) error { + return http.ListenAndServe(addr, docker) +} + + func main() { rand.Seed(time.Now().UTC().UnixNano()) flag.Parse() - if err := http.ListenAndServe(":4242", New()); err != nil { + docker := New() + go func() { + if err := docker.ListenAndServeHTTP(":8080"); err != nil { + log.Fatal(err) + } + }() + if err := docker.ListenAndServeTCP(":4242"); err != nil { log.Fatal(err) } } +func (docker *Docker) serve(conn net.Conn) error { + r := bufio.NewReader(conn) + var args []string + if line, err := r.ReadString('\n'); err != nil { + return err + } else if err := json.Unmarshal([]byte(line), &args); err != nil { + return err + } else { + return docker.Call(ioutil.NopCloser(r), conn, args...) + } + return nil +} + func New() *Docker { return &Docker{ containersByName: make(map[string]*ByDate), @@ -421,30 +470,50 @@ func (w *AutoFlush) Write(data []byte) (int, error) { } func (docker *Docker) ServeHTTP(w http.ResponseWriter, r *http.Request) { - stdout := &AutoFlush{w} - stdin := r.Body + cmd, args := URLToCall(r.URL) + if err := docker.Call(r.Body, &AutoFlush{w}, append([]string{cmd}, args...)...); err != nil { + fmt.Fprintf(w, "Error: " + err.Error() + "\n") + } +} + +func (docker *Docker) Call(stdin io.ReadCloser, stdout io.Writer, args ...string) error { flags := flag.NewFlagSet("docker", flag.ContinueOnError) flags.SetOutput(stdout) flags.Usage = func() { docker.CmdHelp(stdin, stdout) } - cmd, args := URLToCall(r.URL) - if err := flags.Parse(append([]string{cmd}, args...)); err != nil { - return + if err := flags.Parse(args); err != nil { + return err } - log.Printf("%s\n", strings.Join(append(append([]string{"docker"}, cmd), args...), " ")) + cmd := flags.Arg(0) + log.Printf("%s\n", strings.Join(append(append([]string{"docker"}, cmd), args[1:]...), " ")) if cmd == "" { cmd = "help" - } else if cmd == "web" { - w.Header().Set("content-type", "text/html") } method := docker.getMethod(cmd) - if method == nil { - fmt.Fprintf(stdout, "Error: no such command: %s\n", cmd) - } else { - err := method(stdin, stdout, args...) - if err != nil { - fmt.Fprintf(stdout, "Error: %s\n", err) + if method != nil { + return method(stdin, stdout, args[1:]...) + } + return errors.New("No such command: " + cmd) +} + +func (docker *Docker) CmdMirror(stdin io.ReadCloser, stdout io.Writer, args ...string) error { + _, err := io.Copy(stdout, stdin) + return err +} + +func (docker *Docker) CmdDebug(stdin io.ReadCloser, stdout io.Writer, args ...string) error { + for { + if line, err := bufio.NewReader(stdin).ReadString('\n'); err == nil { + fmt.Printf("--- %s", line) + } else if err == io.EOF { + if len(line) > 0 { + fmt.Printf("--- %s\n", line) + } + break + } else { + return err } } + return nil } func (docker *Docker) CmdWeb(stdin io.ReadCloser, stdout io.Writer, args ...string) error { @@ -524,9 +593,8 @@ func (c *Container) Run(command string, args []string, stdin io.ReadCloser, stdo c.stdoutLog.Reset() c.stdinLog.Reset() c.Running = true - defer func() { c.Running = false }() cmd := exec.Command(c.Cmd, c.Args...) - cmd_stdin, cmd_stdout, err := startCommand(cmd, false) + cmd_stdin, cmd_stdout, err := startCommand(cmd, true) // ADD FAKE RANDOM CHANGES c.FilesChanged = uint(rand.Int31n(42)) c.BytesChanged = uint(rand.Int31n(24 * 1024 * 1024)) @@ -537,20 +605,27 @@ func (c *Container) Run(command string, args []string, stdin io.ReadCloser, stdo _, err := io.Copy(io.MultiWriter(stdout, c.stdoutLog), cmd_stdout) return err }) - copy_in := Go(func() error { - //_, err := io.Copy(io.MultiWriter(cmd_stdin, c.stdinLog), stdin) + Go(func() error { + _, err := io.Copy(io.MultiWriter(cmd_stdin, c.stdinLog), stdin) cmd_stdin.Close() stdin.Close() - //return err - return nil + return err }) - if err := cmd.Wait(); err != nil { + wait := Go(func() error { + err := cmd.Wait() + c.Running = false return err - } - if err := <-copy_in; err != nil { - return err - } + }) if err := <-copy_out; err != nil { + if c.Running { + return err + } + } + if err := <-wait; err != nil { + if status, ok := err.(*exec.ExitError); ok { + fmt.Fprintln(stdout, status) + return nil + } return err } return nil diff --git a/dockerd/dockerweb.html b/dockerd/dockerweb.html index e0df9393c5..67cafa4e00 100644 --- a/dockerd/dockerweb.html +++ b/dockerd/dockerweb.html @@ -188,7 +188,7 @@ b)}catch(f){i(f,"onCommandChange");throw f;}t()},commands:x});O.append(b);l.enab if (command !== '') { console.log(command); try { - var url = "http://localhost:4242/" + command.split(" ")[0] + "?" + $.param(command.split(" ").slice(1).map(function(e) { return {name: "q", value: e} })) + var url = "http://localhost:8080/" + command.split(" ")[0] + "?" + $.param(command.split(" ").slice(1).map(function(e) { return {name: "q", value: e} })) console.log(url); $.ajax(url).done(function(data) { term.echo(new String(data));