docker.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. package main
  2. import (
  3. "github.com/dotcloud/docker/rcli"
  4. "github.com/dotcloud/docker/future"
  5. "io"
  6. "io/ioutil"
  7. "log"
  8. "os"
  9. "os/exec"
  10. "syscall"
  11. "unsafe"
  12. "path"
  13. "path/filepath"
  14. "flag"
  15. )
  16. type Termios struct {
  17. Iflag uintptr
  18. Oflag uintptr
  19. Cflag uintptr
  20. Lflag uintptr
  21. Cc [20]byte
  22. Ispeed uintptr
  23. Ospeed uintptr
  24. }
  25. const (
  26. // Input flags
  27. inpck = 0x010
  28. istrip = 0x020
  29. icrnl = 0x100
  30. ixon = 0x200
  31. // Output flags
  32. opost = 0x1
  33. // Control flags
  34. cs8 = 0x300
  35. // Local flags
  36. icanon = 0x100
  37. iexten = 0x400
  38. )
  39. const (
  40. HUPCL = 0x4000
  41. ICANON = 0x100
  42. ICRNL = 0x100
  43. IEXTEN = 0x400
  44. BRKINT = 0x2
  45. CFLUSH = 0xf
  46. CLOCAL = 0x8000
  47. CREAD = 0x800
  48. CS5 = 0x0
  49. CS6 = 0x100
  50. CS7 = 0x200
  51. CS8 = 0x300
  52. CSIZE = 0x300
  53. CSTART = 0x11
  54. CSTATUS = 0x14
  55. CSTOP = 0x13
  56. CSTOPB = 0x400
  57. CSUSP = 0x1a
  58. IGNBRK = 0x1
  59. IGNCR = 0x80
  60. IGNPAR = 0x4
  61. IMAXBEL = 0x2000
  62. INLCR = 0x40
  63. INPCK = 0x10
  64. ISIG = 0x80
  65. ISTRIP = 0x20
  66. IUTF8 = 0x4000
  67. IXANY = 0x800
  68. IXOFF = 0x400
  69. IXON = 0x200
  70. NOFLSH = 0x80000000
  71. OCRNL = 0x10
  72. OFDEL = 0x20000
  73. OFILL = 0x80
  74. ONLCR = 0x2
  75. ONLRET = 0x40
  76. ONOCR = 0x20
  77. ONOEOT = 0x8
  78. OPOST = 0x1
  79. RENB = 0x1000
  80. PARMRK = 0x8
  81. PARODD = 0x2000
  82. TOSTOP = 0x400000
  83. VDISCARD = 0xf
  84. VDSUSP = 0xb
  85. VEOF = 0x0
  86. VEOL = 0x1
  87. VEOL2 = 0x2
  88. VERASE = 0x3
  89. VINTR = 0x8
  90. VKILL = 0x5
  91. VLNEXT = 0xe
  92. VMIN = 0x10
  93. VQUIT = 0x9
  94. VREPRINT = 0x6
  95. VSTART = 0xc
  96. VSTATUS = 0x12
  97. VSTOP = 0xd
  98. VSUSP = 0xa
  99. VT0 = 0x0
  100. VT1 = 0x10000
  101. VTDLY = 0x10000
  102. VTIME = 0x11
  103. ECHO = 0x00000008
  104. PENDIN = 0x20000000
  105. )
  106. type State struct {
  107. termios Termios
  108. }
  109. // IsTerminal returns true if the given file descriptor is a terminal.
  110. func IsTerminal(fd int) bool {
  111. var termios Termios
  112. _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
  113. return err == 0
  114. }
  115. // MakeRaw put the terminal connected to the given file descriptor into raw
  116. // mode and returns the previous state of the terminal so that it can be
  117. // restored.
  118. func MakeRaw(fd int) (*State, error) {
  119. var oldState State
  120. if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
  121. return nil, err
  122. }
  123. newState := oldState.termios
  124. newState.Iflag &^= istrip | INLCR | ICRNL | IGNCR | IXON | IXOFF
  125. newState.Lflag &^= ECHO | ICANON | ISIG
  126. if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
  127. return nil, err
  128. }
  129. return &oldState, nil
  130. }
  131. // Restore restores the terminal connected to the given file descriptor to a
  132. // previous state.
  133. func Restore(fd int, state *State) error {
  134. _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0)
  135. return err
  136. }
  137. var oldState *State
  138. func Fatal(err error) {
  139. if oldState != nil {
  140. Restore(0, oldState)
  141. }
  142. log.Fatal(err)
  143. }
  144. func main() {
  145. if cmd := path.Base(os.Args[0]); cmd == "docker" {
  146. fl_shell := flag.Bool("i", false, "Interactive mode")
  147. flag.Parse()
  148. if *fl_shell {
  149. if err := InteractiveMode(); err != nil {
  150. log.Fatal(err)
  151. }
  152. } else {
  153. SimpleMode(os.Args[1:])
  154. }
  155. } else {
  156. SimpleMode(append([]string{cmd}, os.Args[1:]...))
  157. }
  158. }
  159. // Run docker in "simple mode": run a single command and return.
  160. func SimpleMode(args []string) {
  161. var err error
  162. if IsTerminal(0) && os.Getenv("NORAW") == "" {
  163. oldState, err = MakeRaw(0)
  164. if err != nil {
  165. panic(err)
  166. }
  167. defer Restore(0, oldState)
  168. }
  169. // FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose
  170. // CloseWrite(), which we need to cleanly signal that stdin is closed without
  171. // closing the connection.
  172. // See http://code.google.com/p/go/issues/detail?id=3345
  173. conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...)
  174. if err != nil {
  175. Fatal(err)
  176. }
  177. receive_stdout := future.Go(func() error {
  178. _, err := io.Copy(os.Stdout, conn)
  179. return err
  180. })
  181. send_stdin := future.Go(func() error {
  182. _, err := io.Copy(conn, os.Stdin)
  183. if err := conn.CloseWrite(); err != nil {
  184. log.Printf("Couldn't send EOF: " + err.Error())
  185. }
  186. return err
  187. })
  188. if err := <-receive_stdout; err != nil {
  189. Fatal(err)
  190. }
  191. if oldState != nil {
  192. Restore(0, oldState)
  193. }
  194. if !IsTerminal(0) {
  195. if err := <-send_stdin; err != nil {
  196. Fatal(err)
  197. }
  198. }
  199. }
  200. // Run docker in "interactive mode": run a bash-compatible shell capable of running docker commands.
  201. func InteractiveMode() error {
  202. // Determine path of current docker binary
  203. dockerPath, err := exec.LookPath(os.Args[0])
  204. if err != nil {
  205. return err
  206. }
  207. dockerPath, err = filepath.Abs(dockerPath)
  208. if err != nil {
  209. return err
  210. }
  211. // Create a temp directory
  212. tmp, err := ioutil.TempDir("", "docker-shell")
  213. if err != nil {
  214. return err
  215. }
  216. defer os.RemoveAll(tmp)
  217. // For each command, create an alias in temp directory
  218. // FIXME: generate this list dynamically with introspection of some sort
  219. // It might make sense to merge docker and dockerd to keep that introspection
  220. // within a single binary.
  221. for _, cmd := range []string{
  222. "help",
  223. "run",
  224. "ps",
  225. "pull",
  226. "put",
  227. "rm",
  228. "kill",
  229. "wait",
  230. "stop",
  231. "logs",
  232. "diff",
  233. "commit",
  234. "attach",
  235. "info",
  236. "tar",
  237. "web",
  238. "docker",
  239. } {
  240. if err := os.Symlink(dockerPath, path.Join(tmp, cmd)); err != nil {
  241. return err
  242. }
  243. }
  244. // Run $SHELL with PATH set to temp directory
  245. rcfile, err := ioutil.TempFile("", "docker-shell-rc")
  246. if err != nil {
  247. return err
  248. }
  249. io.WriteString(rcfile, "enable -n help\n")
  250. os.Setenv("PATH", tmp)
  251. os.Setenv("PS1", "\\h docker> ")
  252. shell := exec.Command("/bin/bash", "--rcfile", rcfile.Name())
  253. shell.Stdin = os.Stdin
  254. shell.Stdout = os.Stdout
  255. shell.Stderr = os.Stderr
  256. if err := shell.Run(); err != nil {
  257. return err
  258. }
  259. return nil
  260. }