func createTty(p *libcontainer.Process, rootuid int, consolePath string) (*tty, error) { if consolePath != "" { if err := p.ConsoleFromPath(consolePath); err != nil { return nil, err } return &tty{}, nil } console, err := p.NewConsole(rootuid) if err != nil { return nil, err } go io.Copy(console, os.Stdin) go io.Copy(os.Stdout, console) state, err := term.SetRawTerminal(os.Stdin.Fd()) if err != nil { return nil, fmt.Errorf("failed to set the terminal from the stdin: %v", err) } t := &tty{ console: console, state: state, closers: []io.Closer{ console, }, } return t, nil }
func readPassword() string { var oldState *term.State var input string oldState, err := term.SetRawTerminal(os.Stdin.Fd()) if err != nil { authLogger.WithField("Error", err).Debug("Unable to Set Raw Terminal") } print("Password: "******"Error", err).Debug("Unable to read password") } if input == "" { authLogger.Println("Password required") os.Exit(1) } print("\n") return input }
// AttachTerminal connects us to container and gives us a terminal func (c *DockerClient) AttachTerminal(containerID string) error { c.logger.Println("Attaching to ", containerID) opts := docker.AttachToContainerOptions{ Container: containerID, Logs: true, Stdin: true, Stdout: true, Stderr: true, Stream: true, InputStream: os.Stdin, ErrorStream: os.Stderr, OutputStream: os.Stdout, RawTerminal: true, } var oldState *term.State oldState, err := term.SetRawTerminal(os.Stdin.Fd()) if err != nil { return err } defer term.RestoreTerminal(os.Stdin.Fd(), oldState) go func() { err := c.AttachToContainer(opts) if err != nil { c.logger.Panicln("attach panic", err) } }() _, err = c.WaitContainer(containerID) return err }
// SetRawTerminal sets raw mode on the input terminal func (i *InStream) SetRawTerminal() (err error) { if os.Getenv("NORAW") != "" || !i.isTerminal { return nil } i.state, err = term.SetRawTerminal(i.fd) return err }
// Run creates, start and attach to the container based on the image name, // the specified configuration. // It will always create a new container. func (c *Container) Run(ctx context.Context, configOverride *config.ServiceConfig) (int, error) { var ( errCh chan error out, stderr io.Writer in io.ReadCloser ) if configOverride.StdinOpen { in = os.Stdin } if configOverride.Tty { out = os.Stdout } if configOverride.Tty { stderr = os.Stderr } options := types.ContainerAttachOptions{ Stream: true, Stdin: configOverride.StdinOpen, Stdout: configOverride.Tty, Stderr: configOverride.Tty, } resp, err := c.client.ContainerAttach(ctx, c.container.ID, options) if err != nil { return -1, err } // set raw terminal inFd, _ := term.GetFdInfo(in) state, err := term.SetRawTerminal(inFd) if err != nil { return -1, err } // restore raw terminal defer term.RestoreTerminal(inFd, state) // holdHijackedConnection (in goroutine) errCh = promise.Go(func() error { return holdHijackedConnection(configOverride.Tty, in, out, stderr, resp) }) if err := c.client.ContainerStart(ctx, c.container.ID, types.ContainerStartOptions{}); err != nil { return -1, err } if err := <-errCh; err != nil { logrus.Debugf("Error hijack: %s", err) return -1, err } exitedContainer, err := c.client.ContainerInspect(ctx, c.container.ID) if err != nil { return -1, err } return exitedContainer.State.ExitCode, nil }
// Run executes a validated remote execution against a pod. func (p *AttachOptions) Run() error { pod, err := p.Client.Pods(p.Namespace).Get(p.PodName) if err != nil { return err } if pod.Status.Phase != api.PodRunning { return fmt.Errorf("pod %s is not running and cannot be attached to; current phase is %s", p.PodName, pod.Status.Phase) } // TODO: refactor with terminal helpers from the edit utility once that is merged var stdin io.Reader tty := p.TTY if p.Stdin { stdin = p.In if tty { if file, ok := stdin.(*os.File); ok { inFd := file.Fd() if term.IsTerminal(inFd) { oldState, err := term.SetRawTerminal(inFd) if err != nil { glog.Fatal(err) } // this handles a clean exit, where the command finished defer term.RestoreTerminal(inFd, oldState) // SIGINT is handled by term.SetRawTerminal (it runs a goroutine that listens // for SIGINT and restores the terminal before exiting) // this handles SIGTERM sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM) go func() { <-sigChan term.RestoreTerminal(inFd, oldState) os.Exit(0) }() } else { fmt.Fprintln(p.Err, "STDIN is not a terminal") } } else { tty = false fmt.Fprintln(p.Err, "Unable to use a TTY - input is not the right kind of file") } } } // TODO: consider abstracting into a client invocation or client helper req := p.Client.RESTClient.Post(). Resource("pods"). Name(pod.Name). Namespace(pod.Namespace). SubResource("attach"). Param("container", p.GetContainerName(pod)) return p.Attach.Attach("POST", req.URL(), p.Config, stdin, p.Out, p.Err, tty) }
func (cli *DockerCli) setRawTerminal() error { if cli.isTerminalIn && os.Getenv("NORAW") == "" { state, err := term.SetRawTerminal(cli.inFd) if err != nil { return err } cli.state = state } return nil }
// Console opens a secure console to a code or database service. For code // services, a command is required. This command is executed as root in the // context of the application root directory. For database services, no command // is needed - instead, the appropriate command for the database type is run. // For example, for a postgres database, psql is run. func Console(serviceLabel string, command string, settings *models.Settings) { helpers.SignIn(settings) service := helpers.RetrieveServiceByLabel(serviceLabel, settings) if service == nil { fmt.Printf("Could not find a service with the label \"%s\"\n", serviceLabel) os.Exit(1) } fmt.Printf("Opening console to %s (%s)\n", serviceLabel, service.ID) task := helpers.RequestConsole(command, service.ID, settings) fmt.Print("Waiting for the console to be ready. This might take a minute.") ch := make(chan string, 1) go helpers.PollConsoleJob(task.ID, service.ID, ch, settings) jobID := <-ch defer helpers.DestroyConsole(jobID, service.ID, settings) creds := helpers.RetrieveConsoleTokens(jobID, service.ID, settings) creds.URL = strings.Replace(creds.URL, "http", "ws", 1) fmt.Println("Connecting...") // BEGIN websocket impl config, _ := websocket.NewConfig(creds.URL, "ws://localhost:9443/") config.TlsConfig = &tls.Config{ MinVersion: tls.VersionTLS12, } config.Header["X-Console-Token"] = []string{creds.Token} ws, err := websocket.DialConfig(config) if err != nil { panic(err) } defer ws.Close() fmt.Println("Connection opened") stdin, stdout, _ := term.StdStreams() fdIn, isTermIn := term.GetFdInfo(stdin) if !isTermIn { panic(errors.New("StdIn is not a terminal")) } oldState, err := term.SetRawTerminal(fdIn) if err != nil { panic(err) } done := make(chan bool) msgCh := make(chan []byte, 2) go webSocketDaemon(ws, &stdout, done, msgCh) signal.Notify(make(chan os.Signal, 1), os.Interrupt) defer term.RestoreTerminal(fdIn, oldState) go termDaemon(&stdin, ws) <-done }
// the process for execing a new process inside an existing container is that we have to exec ourself // with the nsenter argument so that the C code can setns an the namespaces that we require. Then that // code path will drop us into the path that we can do the final setup of the namespace and exec the users // application. func startInExistingContainer(config *libcontainer.Config, state *libcontainer.State, action string, context *cli.Context) (int, error) { var ( master *os.File console string err error sigc = make(chan os.Signal, 10) stdin = os.Stdin stdout = os.Stdout stderr = os.Stderr ) signal.Notify(sigc) if config.Tty && action != "setup" { stdin = nil stdout = nil stderr = nil master, console, err = consolepkg.CreateMasterAndConsole() if err != nil { return -1, err } go io.Copy(master, os.Stdin) go io.Copy(os.Stdout, master) state, err := term.SetRawTerminal(os.Stdin.Fd()) if err != nil { return -1, err } defer term.RestoreTerminal(os.Stdin.Fd(), state) } startCallback := func(cmd *exec.Cmd) { go func() { resizeTty(master) for sig := range sigc { switch sig { case syscall.SIGWINCH: resizeTty(master) default: cmd.Process.Signal(sig) } } }() } return namespaces.ExecIn(config, state, context.Args(), os.Args[0], action, stdin, stdout, stderr, console, startCallback) }
func runIn(container *libcontainer.Config, state *libcontainer.State, args []string) (int, error) { var ( master *os.File console string err error stdin = os.Stdin stdout = os.Stdout stderr = os.Stderr sigc = make(chan os.Signal, 10) ) signal.Notify(sigc) if container.Tty { stdin = nil stdout = nil stderr = nil master, console, err = consolepkg.CreateMasterAndConsole() if err != nil { log.Fatal(err) } go io.Copy(master, os.Stdin) go io.Copy(os.Stdout, master) state, err := term.SetRawTerminal(os.Stdin.Fd()) if err != nil { log.Fatal(err) } defer term.RestoreTerminal(os.Stdin.Fd(), state) } startCallback := func(cmd *exec.Cmd) { go func() { resizeTty(master) for sig := range sigc { switch sig { case syscall.SIGWINCH: resizeTty(master) default: cmd.Process.Signal(sig) } } }() } return namespaces.RunIn(container, state, args, os.Args[0], stdin, stdout, stderr, console, startCallback) }
// AttachInteractive starts an interactive session and runs cmd func (c *DockerClient) AttachInteractive(containerID string, cmd []string, initialStdin []string) error { exec, err := c.CreateExec(docker.CreateExecOptions{ AttachStdin: true, AttachStdout: true, AttachStderr: true, Tty: true, Cmd: cmd, Container: containerID, }) if err != nil { return err } // Dump any initial stdin then go into os.Stdin readers := []io.Reader{} for _, s := range initialStdin { if s != "" { readers = append(readers, strings.NewReader(s+"\n")) } } readers = append(readers, os.Stdin) stdin := io.MultiReader(readers...) // This causes our ctrl-c's to be passed to the stuff in the terminal var oldState *term.State oldState, err = term.SetRawTerminal(os.Stdin.Fd()) if err != nil { return err } defer term.RestoreTerminal(os.Stdin.Fd(), oldState) // Handle resizes sigchan := make(chan os.Signal, 1) signal.Notify(sigchan, dockersignal.SIGWINCH) go func() { for range sigchan { c.ResizeTTY(exec.ID) } }() err = c.StartExec(exec.ID, docker.StartExecOptions{ InputStream: stdin, OutputStream: os.Stdout, ErrorStream: os.Stderr, Tty: true, RawTerminal: true, }) return err }
// Connect func Connect(in io.Reader, out io.Writer) (*term.State, error) { stdInFD, _ := term.GetFdInfo(in) stdOutFD, _ := term.GetFdInfo(out) // handle all incoming os signals and act accordingly; default behavior is to // forward all signals to nanobox server go monitor(stdOutFD) // try to upgrade to a raw terminal; if accessed via a terminal this will upgrade // with no error, if not an error will be returned return term.SetRawTerminal(stdInFD) }
func execInternal(where, params string, in io.Reader, out io.Writer) error { // if we can't connect to the server, lets bail out early conn, err := net.Dial("tcp4", config.ServerURI) if err != nil { return err } defer conn.Close() // get current term info stdInFD, isTerminal := term.GetFdInfo(in) stdOutFD, _ := term.GetFdInfo(out) // terminal.PrintNanoboxHeader(where) // begin watching for changes to the project go func() { if err := notifyutil.Watch(config.CWDir, NotifyServer); err != nil { fmt.Printf(err.Error()) } }() // if we are using a term, lets upgrade it to RawMode if isTerminal { // handle all incoming os signals and act accordingly; default behavior is to // forward all signals to nanobox server go monitorTerminal(stdOutFD) oldState, err := term.SetRawTerminal(stdInFD) // we only use raw mode if it is available. if err == nil { defer term.RestoreTerminal(stdInFD, oldState) } } // make a http request switch where { case "develop": if _, err := fmt.Fprintf(conn, "POST /develop?pid=%d&%v HTTP/1.1\r\n\r\n", os.Getpid(), params); err != nil { return err } default: if _, err := fmt.Fprintf(conn, "POST /exec?pid=%d&%v HTTP/1.1\r\n\r\n", os.Getpid(), params); err != nil { return err } } return pipeToConnection(conn, in, out) }
func (t *tty) attach(process *libcontainer.Process) error { if t.console != nil { go io.Copy(t.console, os.Stdin) go io.Copy(os.Stdout, t.console) state, err := term.SetRawTerminal(os.Stdin.Fd()) if err != nil { return err } t.state = state process.Stderr = nil process.Stdout = nil process.Stdin = nil } return nil }
func (t *Task) runContainer(ctx *context.ExecuteContext) error { interactive := t.config.Interactive name := ContainerName(ctx, t.name.Resource()) container, err := ctx.Client.CreateContainer(t.createOptions(ctx, name)) if err != nil { return fmt.Errorf("failed creating container %q: %s", name, err) } chanSig := t.forwardSignals(ctx.Client, container.ID) defer signal.Stop(chanSig) defer RemoveContainer(t.logger(), ctx.Client, container.ID, true) _, err = ctx.Client.AttachToContainerNonBlocking(docker.AttachToContainerOptions{ Container: container.ID, OutputStream: t.output(), ErrorStream: os.Stderr, InputStream: ioutil.NopCloser(os.Stdin), Stream: true, Stdin: t.config.Interactive, RawTerminal: t.config.Interactive, Stdout: true, Stderr: true, }) if err != nil { return fmt.Errorf("failed attaching to container %q: %s", name, err) } if interactive { inFd, _ := term.GetFdInfo(os.Stdin) state, err := term.SetRawTerminal(inFd) if err != nil { return err } defer func() { if err := term.RestoreTerminal(inFd, state); err != nil { t.logger().Warnf("Failed to restore fd %v: %s", inFd, err) } }() } if err := ctx.Client.StartContainer(container.ID, nil); err != nil { return fmt.Errorf("failed starting container %q: %s", name, err) } return t.wait(ctx.Client, container.ID) }
func attachTty(consolePath *string) error { console, err := libcontainer.NewConsole(os.Getuid(), os.Getgid()) if err != nil { return err } *consolePath = console.Path() stdin = console go func() { io.Copy(os.Stdout, console) console.Close() }() s, err := term.SetRawTerminal(os.Stdin.Fd()) if err != nil { return err } state = s return nil }
func (cli *DockerCli) setRawTerminal() error { if os.Getenv("NORAW") == "" { if cli.isTerminalIn { state, err := term.SetRawTerminal(cli.inFd) if err != nil { return err } cli.inState = state } if cli.isTerminalOut { state, err := term.SetRawTerminalOutput(cli.outFd) if err != nil { return err } cli.outState = state } } return nil }
// Connect func Connect(in io.Reader, out io.Writer) { stdInFD, isTerminal := term.GetFdInfo(in) stdOutFD, _ := term.GetFdInfo(out) // if we are using a term, lets upgrade it to RawMode if isTerminal { // handle all incoming os signals and act accordingly; default behavior is to // forward all signals to nanobox server go monitor(stdOutFD) oldState, err := term.SetRawTerminal(stdInFD) // we only use raw mode if it is available. if err == nil { defer term.RestoreTerminal(stdInFD, oldState) } } }
func (p *process) startInteractive() error { f, err := pty.Start(p.cmd) if err != nil { return err } p.pty = f if p.wire.Input == os.Stdin { // the current terminal shall pass everything to the console, make it ignores ctrl+C etc ... // this is done by making the terminal raw. The state is saved to reset user's terminal settings // when dock exits state, err := term.SetRawTerminal(os.Stdin.Fd()) if err != nil { return err } p.termState = &termState{ state: state, fd: os.Stdin.Fd(), } } else { // wire.Input is a socket (tcp, tls ...). Obvioulsy, we can't set the remote user's terminal in raw mode, however we can at least // disable echo on the console state, err := term.SaveState(p.pty.Fd()) if err != nil { return err } if err := term.DisableEcho(p.pty.Fd(), state); err != nil { return err } p.termState = &termState{ state: state, fd: p.pty.Fd(), } } p.resizePty() go io.Copy(p.wire, f) go io.Copy(f, p.wire) return nil }
func (t *tty) recvtty(process *libcontainer.Process, detach bool) error { console, err := process.GetConsole() if err != nil { return err } if !detach { go io.Copy(console, os.Stdin) t.wg.Add(1) go t.copyIO(os.Stdout, console) state, err := term.SetRawTerminal(os.Stdin.Fd()) if err != nil { return fmt.Errorf("failed to set the terminal from the stdin: %v", err) } t.state = state } t.console = console t.closers = []io.Closer{console} return nil }
func createTty(p *libcontainer.Process, rootuid int) (*tty, error) { console, err := p.NewConsole(rootuid) if err != nil { return nil, err } go io.Copy(console, os.Stdin) go io.Copy(os.Stdout, console) state, err := term.SetRawTerminal(os.Stdin.Fd()) if err != nil { return nil, err } t := &tty{ console: console, state: state, closers: []io.Closer{ console, }, } p.Stderr = nil p.Stdout = nil p.Stdin = nil return t, nil }
func (t *tty) attach(process *libcontainer.Process) error { if t.console != nil { go io.Copy(t.console, os.Stdin) go io.Copy(os.Stdout, t.console) state, err := term.SetRawTerminal(os.Stdin.Fd()) if err != nil { return err } t.state = state process.Stderr = nil process.Stdout = nil process.Stdin = nil } else { // setup standard pipes so that the TTY of the calling nsinit process // is not inherited by the container. r, w, err := os.Pipe() if err != nil { return err } go io.Copy(w, os.Stdin) t.closers = append(t.closers, w) process.Stdin = r if r, w, err = os.Pipe(); err != nil { return err } go io.Copy(os.Stdout, r) process.Stdout = w t.closers = append(t.closers, r) if r, w, err = os.Pipe(); err != nil { return err } go io.Copy(os.Stderr, r) process.Stderr = w t.closers = append(t.closers, r) } return nil }
func cmdRun(c *cli.Context) { if c.Bool("detach") { cmdRunDetached(c) return } fd := os.Stdin.Fd() oldState, err := term.SetRawTerminal(fd) defer term.RestoreTerminal(fd, oldState) _, app, err := stdcli.DirApp(c, ".") if err != nil { stdcli.Error(err) return } if len(c.Args()) < 2 { stdcli.Usage(c, "run") return } ps := c.Args()[0] code, err := rackClient(c).RunProcessAttached(app, ps, strings.Join(c.Args()[1:], " "), os.Stdin, os.Stdout) if err != nil { stdcli.Error(err) return } term.RestoreTerminal(fd, oldState) os.Exit(code) }
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer, data interface{}) error { defer func() { if started != nil { close(started) } }() params, err := cli.encodeData(data) if err != nil { return err } req, err := http.NewRequest(method, fmt.Sprintf("%s/v%s%s", cli.basePath, api.Version, path), params) if err != nil { return err } // Add CLI Config's HTTP Headers BEFORE we set the Docker headers // then the user can't change OUR headers for k, v := range cli.configFile.HTTPHeaders { req.Header.Set(k, v) } req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION+" ("+runtime.GOOS+")") req.Header.Set("Content-Type", "text/plain") req.Header.Set("Connection", "Upgrade") req.Header.Set("Upgrade", "tcp") req.Host = cli.addr dial, err := cli.dial() // When we set up a TCP connection for hijack, there could be long periods // of inactivity (a long running command with no output) that in certain // network setups may cause ECONNTIMEOUT, leaving the client in an unknown // state. Setting TCP KeepAlive on the socket connection will prohibit // ECONNTIMEOUT unless the socket connection truly is broken if tcpConn, ok := dial.(*net.TCPConn); ok { tcpConn.SetKeepAlive(true) tcpConn.SetKeepAlivePeriod(30 * time.Second) } if err != nil { if strings.Contains(err.Error(), "connection refused") { return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?") } return err } clientconn := httputil.NewClientConn(dial, nil) defer clientconn.Close() // Server hijacks the connection, error 'connection closed' expected clientconn.Do(req) rwc, br := clientconn.Hijack() defer rwc.Close() if started != nil { started <- rwc } var receiveStdout chan error var oldState *term.State if in != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" { oldState, err = term.SetRawTerminal(cli.inFd) if err != nil { return err } defer term.RestoreTerminal(cli.inFd, oldState) } if stdout != nil || stderr != nil { receiveStdout = promise.Go(func() (err error) { defer func() { if in != nil { if setRawTerminal && cli.isTerminalIn { term.RestoreTerminal(cli.inFd, oldState) } // For some reason this Close call blocks on darwin.. // As the client exists right after, simply discard the close // until we find a better solution. if runtime.GOOS != "darwin" { in.Close() } } }() // When TTY is ON, use regular copy if setRawTerminal && stdout != nil { _, err = io.Copy(stdout, br) } else { _, err = stdcopy.StdCopy(stdout, stderr, br) } logrus.Debugf("[hijack] End of stdout") return err }) } sendStdin := promise.Go(func() error { if in != nil { io.Copy(rwc, in) logrus.Debugf("[hijack] End of stdin") } if conn, ok := rwc.(interface { CloseWrite() error }); ok { if err := conn.CloseWrite(); err != nil { logrus.Debugf("Couldn't send EOF: %s", err) } } // Discard errors due to pipe interruption return nil }) if stdout != nil || stderr != nil { if err := <-receiveStdout; err != nil { logrus.Debugf("Error receiveStdout: %s", err) return err } } if !cli.isTerminalIn { if err := <-sendStdin; err != nil { logrus.Debugf("Error sendStdin: %s", err) return err } } return nil }
func (t *terminalHelper) SetRawTerminal(fd uintptr) (*term.State, error) { return term.SetRawTerminal(fd) }
func runRun(cmd *Command, args []string) { if len(args) == 0 { cmd.PrintUsage() os.Exit(2) } appname := mustApp() w, err := term.GetWinsize(inFd) if err != nil { // If syscall.TIOCGWINSZ is not supported by the device, we're // probably trying to run tests. Set w to some sensible default. if err.Error() == "operation not supported by device" { w = &term.Winsize{ Height: 20, Width: 80, } } else { printFatal(err.Error()) } } attached := !detachedRun opts := heroku.DynoCreateOpts{Attach: &attached} if attached { env := map[string]string{ "COLUMNS": strconv.Itoa(int(w.Width)), "LINES": strconv.Itoa(int(w.Height)), "TERM": os.Getenv("TERM"), } opts.Env = &env } if dynoSize != "" { if !strings.HasSuffix(dynoSize, "X") { cmd.PrintUsage() os.Exit(2) } opts.Size = &dynoSize } command := strings.Join(args, " ") if detachedRun { dyno, err := client.DynoCreate(appname, command, &opts) must(err) log.Printf("Ran `%s` on %s as %s, detached.", dyno.Command, appname, dyno.Name) return } params := struct { Command string `json:"command"` Attach *bool `json:"attach,omitempty"` Env *map[string]string `json:"env,omitempty"` Size *string `json:"size,omitempty"` }{ Command: command, Attach: opts.Attach, Env: opts.Env, Size: opts.Size, } req, err := client.NewRequest("POST", "/apps/"+appname+"/dynos", params) must(err) u, err := url.Parse(apiURL) must(err) proto, address := dialParams(u) var dial net.Conn if proto == "tls" { dial, err = tlsDial("tcp", address, &tls.Config{}) if err != nil { printFatal(err.Error()) } } else { dial, err = net.Dial(proto, address) if err != nil { printFatal(err.Error()) } } clientconn := httputil.NewClientConn(dial, nil) defer clientconn.Close() _, err = clientconn.Do(req) if err != nil && err != httputil.ErrPersistEOF { printFatal(err.Error()) } rwc, br := clientconn.Hijack() defer rwc.Close() if isTerminalIn && isTerminalOut { state, err := term.SetRawTerminal(inFd) if err != nil { printFatal(err.Error()) } defer term.RestoreTerminal(inFd, state) } errChanOut := make(chan error, 1) errChanIn := make(chan error, 1) exit := make(chan bool) go func() { defer close(exit) defer close(errChanOut) var err error _, err = io.Copy(os.Stdout, br) errChanOut <- err }() go func() { _, err := io.Copy(rwc, os.Stdin) errChanIn <- err rwc.(interface { CloseWrite() error }).CloseWrite() }() <-exit select { case err = <-errChanIn: must(err) case err = <-errChanOut: must(err) } }
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer, data interface{}) error { defer func() { if started != nil { close(started) } }() params, err := cli.encodeData(data) if err != nil { return err } req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params) if err != nil { return err } req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) req.Header.Set("Content-Type", "plain/text") req.Host = cli.addr dial, err := cli.dial() if err != nil { if strings.Contains(err.Error(), "connection refused") { return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") } return err } clientconn := httputil.NewClientConn(dial, nil) defer clientconn.Close() // Server hijacks the connection, error 'connection closed' expected clientconn.Do(req) rwc, br := clientconn.Hijack() defer rwc.Close() if started != nil { started <- rwc } var receiveStdout chan error var oldState *term.State if in != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" { oldState, err = term.SetRawTerminal(cli.inFd) if err != nil { return err } defer term.RestoreTerminal(cli.inFd, oldState) } if stdout != nil || stderr != nil { receiveStdout = utils.Go(func() (err error) { defer func() { if in != nil { if setRawTerminal && cli.isTerminalIn { term.RestoreTerminal(cli.inFd, oldState) } // For some reason this Close call blocks on darwin.. // As the client exists right after, simply discard the close // until we find a better solution. if runtime.GOOS != "darwin" { in.Close() } } }() // When TTY is ON, use regular copy if setRawTerminal && stdout != nil { _, err = io.Copy(stdout, br) } else { _, err = stdcopy.StdCopy(stdout, stderr, br) } log.Debugf("[hijack] End of stdout") return err }) } sendStdin := utils.Go(func() error { if in != nil { io.Copy(rwc, in) log.Debugf("[hijack] End of stdin") } if tcpc, ok := rwc.(*net.TCPConn); ok { if err := tcpc.CloseWrite(); err != nil { log.Debugf("Couldn't send EOF: %s", err) } } else if unixc, ok := rwc.(*net.UnixConn); ok { if err := unixc.CloseWrite(); err != nil { log.Debugf("Couldn't send EOF: %s", err) } } // Discard errors due to pipe interruption return nil }) if stdout != nil || stderr != nil { if err := <-receiveStdout; err != nil { log.Debugf("Error receiveStdout: %s", err) return err } } if !cli.isTerminalIn { if err := <-sendStdin; err != nil { log.Debugf("Error sendStdin: %s", err) return err } } return nil }
// Run executes a validated remote execution against a pod. func (p *ExecOptions) Run() error { pod, err := p.Client.Pods(p.Namespace).Get(p.PodName) if err != nil { return err } if pod.Status.Phase != api.PodRunning { return fmt.Errorf("pod %s is not running and cannot execute commands; current phase is %s", p.PodName, pod.Status.Phase) } containerName := p.ContainerName if len(containerName) == 0 { glog.V(4).Infof("defaulting container name to %s", pod.Spec.Containers[0].Name) containerName = pod.Spec.Containers[0].Name } // TODO: refactor with terminal helpers from the edit utility once that is merged var stdin io.Reader tty := p.TTY if p.Stdin { stdin = p.In if tty { if file, ok := stdin.(*os.File); ok { inFd := file.Fd() if term.IsTerminal(inFd) { oldState, err := term.SetRawTerminal(inFd) if err != nil { glog.Fatal(err) } // this handles a clean exit, where the command finished defer term.RestoreTerminal(inFd, oldState) // SIGINT is handled by term.SetRawTerminal (it runs a goroutine that listens // for SIGINT and restores the terminal before exiting) // this handles SIGTERM sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM) go func() { <-sigChan term.RestoreTerminal(inFd, oldState) os.Exit(0) }() } else { fmt.Fprintln(p.Err, "STDIN is not a terminal") } } else { tty = false fmt.Fprintln(p.Err, "Unable to use a TTY - input is not the right kind of file") } } } // TODO: consider abstracting into a client invocation or client helper req := p.Client.RESTClient.Post(). Resource("pods"). Name(pod.Name). Namespace(pod.Namespace). SubResource("exec"). Param("container", containerName) postErr := p.Executor.Execute(req, p.Config, p.Command, stdin, p.Out, p.Err, tty) // if we don't have an error, return. If we did get an error, try a GET because v3.0.0 shipped with exec running as a GET. if postErr == nil { return nil } // only try the get if the error is either a forbidden or method not supported, otherwise trying with a GET probably won't help if !apierrors.IsForbidden(postErr) && !apierrors.IsMethodNotSupported(postErr) { return postErr } getReq := p.Client.RESTClient.Get(). Resource("pods"). Name(pod.Name). Namespace(pod.Namespace). SubResource("exec"). Param("container", containerName) getErr := p.Executor.Execute(getReq, p.Config, p.Command, stdin, p.Out, p.Err, tty) if getErr == nil { return nil } // if we got a getErr, return the postErr because it's more likely to be correct. GET is legacy return postErr }
// startContainer starts the container. Returns the exit status or -1 and an // error. // // Signals sent to the current process will be forwarded to container. func startContainer(container *libcontainer.Config, dataPath string, args []string) (int, error) { var ( cmd *exec.Cmd sigc = make(chan os.Signal, 10) ) signal.Notify(sigc) createCommand := func(container *libcontainer.Config, console, rootfs, dataPath, init string, pipe *os.File, args []string) *exec.Cmd { cmd = namespaces.DefaultCreateCommand(container, console, rootfs, dataPath, init, pipe, args) if logPath != "" { cmd.Env = append(cmd.Env, fmt.Sprintf("log=%s", logPath)) } return cmd } var ( master *os.File console string err error stdin = os.Stdin stdout = os.Stdout stderr = os.Stderr ) if container.Tty { stdin = nil stdout = nil stderr = nil master, console, err = consolepkg.CreateMasterAndConsole() if err != nil { return -1, err } go io.Copy(master, os.Stdin) go io.Copy(os.Stdout, master) state, err := term.SetRawTerminal(os.Stdin.Fd()) if err != nil { return -1, err } defer term.RestoreTerminal(os.Stdin.Fd(), state) } startCallback := func() { go func() { resizeTty(master) for sig := range sigc { switch sig { case syscall.SIGWINCH: resizeTty(master) default: cmd.Process.Signal(sig) } } }() } return namespaces.Exec(container, stdin, stdout, stderr, console, "", dataPath, args, createCommand, startCallback) }
func (cli *DockerCli) holdHijackedConnection(setRawTerminal bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error { var ( err error oldState *term.State ) if inputStream != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" { oldState, err = term.SetRawTerminal(cli.inFd) if err != nil { return err } defer term.RestoreTerminal(cli.inFd, oldState) } receiveStdout := make(chan error, 1) if outputStream != nil || errorStream != nil { go func() { defer func() { if inputStream != nil { if setRawTerminal && cli.isTerminalIn { term.RestoreTerminal(cli.inFd, oldState) } inputStream.Close() } }() // When TTY is ON, use regular copy if setRawTerminal && outputStream != nil { _, err = io.Copy(outputStream, resp.Reader) } else { _, err = stdcopy.StdCopy(outputStream, errorStream, resp.Reader) } logrus.Debugf("[hijack] End of stdout") receiveStdout <- err }() } stdinDone := make(chan struct{}) go func() { if inputStream != nil { io.Copy(resp.Conn, inputStream) logrus.Debugf("[hijack] End of stdin") } if err := resp.CloseWrite(); err != nil { logrus.Debugf("Couldn't send EOF: %s", err) } close(stdinDone) }() select { case err := <-receiveStdout: if err != nil { logrus.Debugf("Error receiveStdout: %s", err) return err } case <-stdinDone: if outputStream != nil || errorStream != nil { if err := <-receiveStdout; err != nil { logrus.Debugf("Error receiveStdout: %s", err) return err } } } return nil }