123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- package main
- import (
- "github.com/dotcloud/docker/rcli"
- "github.com/dotcloud/docker/future"
- "io"
- "io/ioutil"
- "log"
- "os"
- "os/exec"
- "syscall"
- "unsafe"
- "path"
- "path/filepath"
- "flag"
- )
- type Termios struct {
- Iflag uintptr
- Oflag uintptr
- Cflag uintptr
- Lflag uintptr
- Cc [20]byte
- Ispeed uintptr
- Ospeed uintptr
- }
- 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() {
- if cmd := path.Base(os.Args[0]); cmd == "docker" {
- fl_shell := flag.Bool("i", false, "Interactive mode")
- flag.Parse()
- if *fl_shell {
- if err := InteractiveMode(); err != nil {
- log.Fatal(err)
- }
- } else {
- SimpleMode(os.Args[1:])
- }
- } else {
- SimpleMode(append([]string{cmd}, os.Args[1:]...))
- }
- }
- // Run docker in "simple mode": run a single command and return.
- func SimpleMode(args []string) {
- var err error
- if IsTerminal(0) && os.Getenv("NORAW") == "" {
- oldState, err = MakeRaw(0)
- if err != nil {
- panic(err)
- }
- defer Restore(0, oldState)
- }
- // 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
- conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...)
- if err != nil {
- Fatal(err)
- }
- receive_stdout := future.Go(func() error {
- _, err := io.Copy(os.Stdout, conn)
- return err
- })
- send_stdin := future.Go(func() error {
- _, err := io.Copy(conn, os.Stdin)
- if err := conn.CloseWrite(); err != nil {
- log.Printf("Couldn't send EOF: " + err.Error())
- }
- return err
- })
- if err := <-receive_stdout; err != nil {
- Fatal(err)
- }
- if oldState != nil {
- Restore(0, oldState)
- }
- if !IsTerminal(0) {
- if err := <-send_stdin; err != nil {
- Fatal(err)
- }
- }
- }
- // Run docker in "interactive mode": run a bash-compatible shell capable of running docker commands.
- func InteractiveMode() error {
- // Determine path of current docker binary
- dockerPath, err := exec.LookPath(os.Args[0])
- if err != nil {
- return err
- }
- dockerPath, err = filepath.Abs(dockerPath)
- if err != nil {
- return err
- }
- // Create a temp directory
- tmp, err := ioutil.TempDir("", "docker-shell")
- if err != nil {
- return err
- }
- defer os.RemoveAll(tmp)
- // For each command, create an alias in temp directory
- // FIXME: generate this list dynamically with introspection of some sort
- // It might make sense to merge docker and dockerd to keep that introspection
- // within a single binary.
- for _, cmd := range []string{
- "help",
- "run",
- "ps",
- "pull",
- "put",
- "rm",
- "kill",
- "wait",
- "stop",
- "logs",
- "diff",
- "commit",
- "attach",
- "info",
- "tar",
- "web",
- "docker",
- } {
- if err := os.Symlink(dockerPath, path.Join(tmp, cmd)); err != nil {
- return err
- }
- }
- // Run $SHELL with PATH set to temp directory
- rcfile, err := ioutil.TempFile("", "docker-shell-rc")
- if err != nil {
- return err
- }
- io.WriteString(rcfile, "enable -n help\n")
- os.Setenv("PATH", tmp)
- os.Setenv("PS1", "\\h docker> ")
- shell := exec.Command("/bin/bash", "--rcfile", rcfile.Name())
- shell.Stdin = os.Stdin
- shell.Stdout = os.Stdout
- shell.Stderr = os.Stderr
- if err := shell.Run(); err != nil {
- return err
- }
- return nil
- }
|