func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.File, out io.Writer) error { req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil) if err != nil { return err } req.Header.Set("User-Agent", "Docker-Client/"+VERSION) req.Header.Set("Content-Type", "plain/text") dial, err := net.Dial(cli.proto, cli.addr) if err != nil { 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() receiveStdout := utils.Go(func() error { _, err := io.Copy(out, br) return err }) if in != nil && setRawTerminal && term.IsTerminal(in.Fd()) && os.Getenv("NORAW") == "" { oldState, err := term.SetRawTerminal() if err != nil { return err } defer term.RestoreTerminal(oldState) } sendStdin := utils.Go(func() error { io.Copy(rwc, in) if err := rwc.(*net.TCPConn).CloseWrite(); err != nil { utils.Debugf("Couldn't send EOF: %s\n", err) } // Discard errors due to pipe interruption return nil }) if err := <-receiveStdout; err != nil { utils.Debugf("Error receiveStdout: %s", err) return err } if !term.IsTerminal(in.Fd()) { if err := <-sendStdin; err != nil { utils.Debugf("Error sendStdin: %s", err) return err } } return nil }
func (c *client) hijack(method, path string, in io.ReadCloser, stdout, stderr io.Writer) error { path = fmt.Sprintf("/%s%s", c.version, path) dial, err := c.transport.Dial("ignored", "ignored") if err != nil { return err } req, err := http.NewRequest(method, path, nil) if err != nil { return err } clientconn := httputil.NewClientConn(dial, nil) defer clientconn.Close() clientconn.Do(req) rwc, br := clientconn.Hijack() defer rwc.Close() receiveStdout := dockerutils.Go(func() (err error) { defer func() { if in != nil { in.Close() } }() _, err = dockerutils.StdCopy(stdout, stderr, br) dockerutils.Debugf("[hijack] End of stdout") return err }) sendStdin := dockerutils.Go(func() error { if in != nil { io.Copy(rwc, in) dockerutils.Debugf("[hijack] End of stdin") } if tcpc, ok := rwc.(*net.TCPConn); ok { if err := tcpc.CloseWrite(); err != nil { dockerutils.Debugf("Couldn't send EOF: %s", err) } } else if unixc, ok := rwc.(*net.UnixConn); ok { if err := unixc.CloseWrite(); err != nil { dockerutils.Debugf("Couldn't send EOF: %s", err) } } // Discard errors due to pipe interruption return nil }) if err := <-receiveStdout; err != nil { dockerutils.Debugf("Error receiveStdout: %s", err) return err } if err := <-sendStdin; err != nil { dockerutils.Debugf("Error sendStdin: %s", err) return err } return nil }
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool) error { req, err := http.NewRequest(method, path, nil) if err != nil { return err } req.Header.Set("Content-Type", "plain/text") dial, err := net.Dial("tcp", fmt.Sprintf("%s:%d", cli.host, cli.port)) if err != nil { return err } clientconn := httputil.NewClientConn(dial, nil) clientconn.Do(req) defer clientconn.Close() rwc, br := clientconn.Hijack() defer rwc.Close() receiveStdout := utils.Go(func() error { _, err := io.Copy(os.Stdout, br) return err }) if setRawTerminal && term.IsTerminal(int(os.Stdin.Fd())) && os.Getenv("NORAW") == "" { if oldState, err := term.SetRawTerminal(); err != nil { return err } else { defer term.RestoreTerminal(oldState) } } sendStdin := utils.Go(func() error { _, err := io.Copy(rwc, os.Stdin) if err := rwc.(*net.TCPConn).CloseWrite(); err != nil { fmt.Fprintf(os.Stderr, "Couldn't send EOF: %s\n", err) } return err }) if err := <-receiveStdout; err != nil { return err } if !term.IsTerminal(int(os.Stdin.Fd())) { if err := <-sendStdin; err != nil { return err } } return nil }
func (b *buildFile) run(c *runtime.Container) error { var errCh chan error if b.verbose { errCh = utils.Go(func() error { return <-c.Attach(nil, nil, b.outStream, b.errStream) }) } //start the container if err := c.Start(); err != nil { return err } if errCh != nil { if err := <-errCh; err != nil { return err } } // Wait for it to finish if ret := c.Wait(); ret != 0 { err := &utils.JSONError{ Message: fmt.Sprintf("The command %v returned a non-zero code: %d", b.config.Cmd, ret), Code: ret, } return err } return nil }
func (container *Container) waitForStart() error { callbackLock := make(chan struct{}) callback := func(command *execdriver.Command) { container.State.SetRunning(command.Pid()) if command.Tty { // The callback is called after the process Start() // so we are in the parent process. In TTY mode, stdin/out/err is the PtySlace // which we close here. if c, ok := command.Stdout.(io.Closer); ok { c.Close() } } if err := container.ToDisk(); err != nil { utils.Debugf("%s", err) } close(callbackLock) } // We use a callback here instead of a goroutine and an chan for // syncronization purposes cErr := utils.Go(func() error { return container.monitor(callback) }) // Start should not return until the process is actually running select { case <-callbackLock: case err := <-cErr: return err } return nil }
func (b *buildFile) run() (string, error) { if b.image == "" { return "", fmt.Errorf("Please provide a source image with `from` prior to run") } b.config.Image = b.image // Create the container and start it c, _, err := b.runtime.Create(b.config, "") if err != nil { return "", err } b.tmpContainers[c.ID] = struct{}{} if b.sf.Used() { b.out.Write(b.sf.FormatStatus("", " ---> Running in %s", utils.TruncateID(c.ID))) } else { fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(c.ID)) } // override the entry point that may have been picked up from the base image c.Path = b.config.Cmd[0] c.Args = b.config.Cmd[1:] var errCh chan error if b.verbose { errCh = utils.Go(func() error { return <-c.Attach(nil, nil, b.out, b.out) }) } //start the container if err := c.Start(); err != nil { return "", err } if errCh != nil { if err := <-errCh; err != nil { return "", err } } // Wait for it to finish if ret := c.Wait(); ret != 0 { err := &utils.JSONError{ Message: fmt.Sprintf("The command %v returned a non-zero code: %d", b.config.Cmd, ret), Code: ret, } return "", err } return c.ID, nil }
// CopyFileWithTar emulates the behavior of the 'cp' command-line // for a single file. It copies a regular file from path `src` to // path `dst`, and preserves all its metadata. // // If `dst` ends with a trailing slash '/', the final destination path // will be `dst/base(src)`. func CopyFileWithTar(src, dst string) (err error) { utils.Debugf("CopyFileWithTar(%s, %s)", src, dst) srcSt, err := os.Stat(src) if err != nil { return err } if srcSt.IsDir() { return fmt.Errorf("Can't copy a directory") } // Clean up the trailing / if dst[len(dst)-1] == '/' { dst = path.Join(dst, filepath.Base(src)) } // Create the holding directory if necessary if err := os.MkdirAll(filepath.Dir(dst), 0700); err != nil && !os.IsExist(err) { return err } r, w := io.Pipe() errC := utils.Go(func() error { defer w.Close() srcF, err := os.Open(src) if err != nil { return err } defer srcF.Close() tw := tar.NewWriter(w) hdr, err := tar.FileInfoHeader(srcSt, "") if err != nil { return err } hdr.Name = filepath.Base(dst) if err := tw.WriteHeader(hdr); err != nil { return err } if _, err := io.Copy(tw, srcF); err != nil { return err } tw.Close() return nil }) defer func() { if er := <-errC; err != nil { err = er } }() return Untar(r, filepath.Dir(dst), nil) }
func (container *Container) Start() (err error) { container.Lock() defer container.Unlock() if container.State.IsRunning() { return fmt.Errorf("The container %s is already running.", container.ID) } defer func() { if err != nil { container.cleanup() } }() if err := container.Mount(); err != nil { return err } if container.runtime.networkManager.disabled { container.Config.NetworkDisabled = true container.buildHostnameAndHostsFiles("127.0.1.1") } else { if err := container.allocateNetwork(); err != nil { return err } container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress) } // Make sure the config is compatible with the current kernel if container.Config.Memory > 0 && !container.runtime.sysInfo.MemoryLimit { log.Printf("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") container.Config.Memory = 0 } if container.Config.Memory > 0 && !container.runtime.sysInfo.SwapLimit { log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") container.Config.MemorySwap = -1 } if container.runtime.sysInfo.IPv4ForwardingDisabled { log.Printf("WARNING: IPv4 forwarding is disabled. Networking will not work") } if container.Volumes == nil || len(container.Volumes) == 0 { container.Volumes = make(map[string]string) container.VolumesRW = make(map[string]bool) } // Apply volumes from another container if requested if err := container.applyExternalVolumes(); err != nil { return err } if err := container.createVolumes(); err != nil { return err } // Setup environment env := []string{ "HOME=/", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "HOSTNAME=" + container.Config.Hostname, } if container.Config.Tty { env = append(env, "TERM=xterm") } // Init any links between the parent and children runtime := container.runtime children, err := runtime.Children(container.Name) if err != nil { return err } if len(children) > 0 { container.activeLinks = make(map[string]*Link, len(children)) // If we encounter an error make sure that we rollback any network // config and ip table changes rollback := func() { for _, link := range container.activeLinks { link.Disable() } container.activeLinks = nil } for p, child := range children { link, err := NewLink(container, child, p, runtime.networkManager.bridgeIface) if err != nil { rollback() return err } container.activeLinks[link.Alias()] = link if err := link.Enable(); err != nil { rollback() return err } for _, envVar := range link.ToEnv() { env = append(env, envVar) } } } for _, elem := range container.Config.Env { env = append(env, elem) } if err := container.generateEnvConfig(env); err != nil { return err } root := container.RootfsPath() if container.Config.WorkingDir != "" { container.Config.WorkingDir = path.Clean(container.Config.WorkingDir) if err := os.MkdirAll(path.Join(root, container.Config.WorkingDir), 0755); err != nil { return nil } } envPath, err := container.EnvConfigPath() if err != nil { return err } // Mount docker specific files into the containers root fs if err := mount.Mount(runtime.sysInitPath, path.Join(root, "/.dockerinit"), "none", "bind,ro"); err != nil { return err } if err := mount.Mount(envPath, path.Join(root, "/.dockerenv"), "none", "bind,ro"); err != nil { return err } if err := mount.Mount(container.ResolvConfPath, path.Join(root, "/etc/resolv.conf"), "none", "bind,ro"); err != nil { return err } if container.HostnamePath != "" && container.HostsPath != "" { if err := mount.Mount(container.HostnamePath, path.Join(root, "/etc/hostname"), "none", "bind,ro"); err != nil { return err } if err := mount.Mount(container.HostsPath, path.Join(root, "/etc/hosts"), "none", "bind,ro"); err != nil { return err } } // Mount user specified volumes for r, v := range container.Volumes { mountAs := "ro" if container.VolumesRW[r] { mountAs = "rw" } r = path.Join(root, r) if p, err := utils.FollowSymlinkInScope(r, root); err != nil { return err } else { r = p } if err := mount.Mount(v, r, "none", fmt.Sprintf("bind,%s", mountAs)); err != nil { return err } } populateCommand(container) // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { return err } if err := container.runtime.LogToDisk(container.stderr, container.logPath("json"), "stderr"); err != nil { return err } container.waitLock = make(chan struct{}) // Setuping pipes and/or Pty var setup func() error if container.Config.Tty { setup = container.setupPty } else { setup = container.setupStd } if err := setup(); err != nil { return err } callbackLock := make(chan struct{}) callback := func(command *execdriver.Command) { container.State.SetRunning(command.Pid()) if command.Tty { // The callback is called after the process Start() // so we are in the parent process. In TTY mode, stdin/out/err is the PtySlace // which we close here. if c, ok := command.Stdout.(io.Closer); ok { c.Close() } } if err := container.ToDisk(); err != nil { utils.Debugf("%s", err) } close(callbackLock) } // We use a callback here instead of a goroutine and an chan for // syncronization purposes cErr := utils.Go(func() error { return container.monitor(callback) }) // Start should not return until the process is actually running select { case <-callbackLock: case err := <-cErr: return err } return nil }
func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error { var cStdout, cStderr io.ReadCloser var nJobs int errors := make(chan error, 3) if stdin != nil && container.Config.OpenStdin { nJobs += 1 if cStdin, err := container.StdinPipe(); err != nil { errors <- err } else { go func() { utils.Debugf("attach: stdin: begin") defer utils.Debugf("attach: stdin: end") // No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr if container.Config.StdinOnce && !container.Config.Tty { defer cStdin.Close() } else { defer func() { if cStdout != nil { cStdout.Close() } if cStderr != nil { cStderr.Close() } }() } if container.Config.Tty { _, err = utils.CopyEscapable(cStdin, stdin) } else { _, err = io.Copy(cStdin, stdin) } if err == io.ErrClosedPipe { err = nil } if err != nil { utils.Errorf("attach: stdin: %s", err) } errors <- err }() } } if stdout != nil { nJobs += 1 if p, err := container.StdoutPipe(); err != nil { errors <- err } else { cStdout = p go func() { utils.Debugf("attach: stdout: begin") defer utils.Debugf("attach: stdout: end") // If we are in StdinOnce mode, then close stdin if container.Config.StdinOnce && stdin != nil { defer stdin.Close() } if stdinCloser != nil { defer stdinCloser.Close() } _, err := io.Copy(stdout, cStdout) if err == io.ErrClosedPipe { err = nil } if err != nil { utils.Errorf("attach: stdout: %s", err) } errors <- err }() } } else { go func() { if stdinCloser != nil { defer stdinCloser.Close() } if cStdout, err := container.StdoutPipe(); err != nil { utils.Errorf("attach: stdout pipe: %s", err) } else { io.Copy(&utils.NopWriter{}, cStdout) } }() } if stderr != nil { nJobs += 1 if p, err := container.StderrPipe(); err != nil { errors <- err } else { cStderr = p go func() { utils.Debugf("attach: stderr: begin") defer utils.Debugf("attach: stderr: end") // If we are in StdinOnce mode, then close stdin if container.Config.StdinOnce && stdin != nil { defer stdin.Close() } if stdinCloser != nil { defer stdinCloser.Close() } _, err := io.Copy(stderr, cStderr) if err == io.ErrClosedPipe { err = nil } if err != nil { utils.Errorf("attach: stderr: %s", err) } errors <- err }() } } else { go func() { if stdinCloser != nil { defer stdinCloser.Close() } if cStderr, err := container.StderrPipe(); err != nil { utils.Errorf("attach: stdout pipe: %s", err) } else { io.Copy(&utils.NopWriter{}, cStderr) } }() } return utils.Go(func() error { defer func() { if cStdout != nil { cStdout.Close() } if cStderr != nil { cStderr.Close() } }() // FIXME: how to clean up the stdin goroutine without the unwanted side effect // of closing the passed stdin? Add an intermediary io.Pipe? for i := 0; i < nJobs; i += 1 { utils.Debugf("attach: waiting for job %d/%d", i+1, nJobs) if err := <-errors; err != nil { utils.Errorf("attach: job %d returned error %s, aborting all jobs", i+1, err) return err } utils.Debugf("attach: job %d completed successfully", i+1) } utils.Debugf("attach: all jobs completed successfully") return nil }) }
func (container *Container) Start() (err error) { container.Lock() defer container.Unlock() if container.State.IsRunning() { return fmt.Errorf("The container %s is already running.", container.ID) } defer func() { if err != nil { container.cleanup() } }() if err := container.Mount(); err != nil { return err } if container.runtime.config.DisableNetwork { container.Config.NetworkDisabled = true container.buildHostnameAndHostsFiles("127.0.1.1") } else { if err := container.allocateNetwork(); err != nil { return err } container.buildHostnameAndHostsFiles(container.NetworkSettings.IPAddress) } // Make sure the config is compatible with the current kernel if container.Config.Memory > 0 && !container.runtime.sysInfo.MemoryLimit { log.Printf("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") container.Config.Memory = 0 } if container.Config.Memory > 0 && !container.runtime.sysInfo.SwapLimit { log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") container.Config.MemorySwap = -1 } if container.runtime.sysInfo.IPv4ForwardingDisabled { log.Printf("WARNING: IPv4 forwarding is disabled. Networking will not work") } if err := prepareVolumesForContainer(container); err != nil { return err } // Setup environment env := []string{ "HOME=/", "PATH=" + DefaultPathEnv, "HOSTNAME=" + container.Config.Hostname, } if container.Config.Tty { env = append(env, "TERM=xterm") } // Init any links between the parent and children runtime := container.runtime children, err := runtime.Children(container.Name) if err != nil { return err } if len(children) > 0 { container.activeLinks = make(map[string]*links.Link, len(children)) // If we encounter an error make sure that we rollback any network // config and ip table changes rollback := func() { for _, link := range container.activeLinks { link.Disable() } container.activeLinks = nil } for linkAlias, child := range children { if !child.State.IsRunning() { return fmt.Errorf("Cannot link to a non running container: %s AS %s", child.Name, linkAlias) } link, err := links.NewLink( container.NetworkSettings.IPAddress, child.NetworkSettings.IPAddress, linkAlias, child.Config.Env, child.Config.ExposedPorts, runtime.eng) if err != nil { rollback() return err } container.activeLinks[link.Alias()] = link if err := link.Enable(); err != nil { rollback() return err } for _, envVar := range link.ToEnv() { env = append(env, envVar) } } } // because the env on the container can override certain default values // we need to replace the 'env' keys where they match and append anything // else. env = utils.ReplaceOrAppendEnvValues(env, container.Config.Env) if err := container.generateEnvConfig(env); err != nil { return err } if container.Config.WorkingDir != "" { container.Config.WorkingDir = path.Clean(container.Config.WorkingDir) pthInfo, err := os.Stat(path.Join(container.basefs, container.Config.WorkingDir)) if err != nil { if !os.IsNotExist(err) { return err } if err := os.MkdirAll(path.Join(container.basefs, container.Config.WorkingDir), 0755); err != nil { return err } } if pthInfo != nil && !pthInfo.IsDir() { return fmt.Errorf("Cannot mkdir: %s is not a directory", container.Config.WorkingDir) } } envPath, err := container.EnvConfigPath() if err != nil { return err } populateCommand(container) container.command.Env = env if err := setupMountsForContainer(container, envPath); err != nil { return err } // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { return err } if err := container.runtime.LogToDisk(container.stderr, container.logPath("json"), "stderr"); err != nil { return err } container.waitLock = make(chan struct{}) callbackLock := make(chan struct{}) callback := func(command *execdriver.Command) { container.State.SetRunning(command.Pid()) if command.Tty { // The callback is called after the process Start() // so we are in the parent process. In TTY mode, stdin/out/err is the PtySlace // which we close here. if c, ok := command.Stdout.(io.Closer); ok { c.Close() } } if err := container.ToDisk(); err != nil { utils.Debugf("%s", err) } close(callbackLock) } // We use a callback here instead of a goroutine and an chan for // syncronization purposes cErr := utils.Go(func() error { return container.monitor(callback) }) // Start should not return until the process is actually running select { case <-callbackLock: case err := <-cErr: return err } return nil }
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, out io.Writer) error { req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil) if err != nil { return err } req.Header.Set("User-Agent", "Docker-Client/"+VERSION) req.Header.Set("Content-Type", "plain/text") req.Host = cli.addr dial, err := net.Dial(cli.proto, cli.addr) if err != nil { if strings.Contains(err.Error(), "connection refused") { return fmt.Errorf("Can't connect to 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() var receiveStdout (chan error) if out != nil { receiveStdout = utils.Go(func() error { _, err := io.Copy(out, br) utils.Debugf("[hijack] End of stdout") return err }) } if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" { oldState, err := term.SetRawTerminal(cli.terminalFd) if err != nil { return err } defer term.RestoreTerminal(cli.terminalFd, oldState) } sendStdin := utils.Go(func() error { if in != nil { io.Copy(rwc, in) utils.Debugf("[hijack] End of stdin") } if tcpc, ok := rwc.(*net.TCPConn); ok { if err := tcpc.CloseWrite(); err != nil { utils.Debugf("Couldn't send EOF: %s\n", err) } } else if unixc, ok := rwc.(*net.UnixConn); ok { if err := unixc.CloseWrite(); err != nil { utils.Debugf("Couldn't send EOF: %s\n", err) } } // Discard errors due to pipe interruption return nil }) if out != nil { if err := <-receiveStdout; err != nil { utils.Debugf("Error receiveStdout: %s", err) return err } } if !cli.isTerminal { if err := <-sendStdin; err != nil { utils.Debugf("Error sendStdin: %s", err) return err } } return nil }
func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error { var cStdout, cStderr io.ReadCloser var nJobs int errors := make(chan error, 3) if stdin != nil && container.Config.OpenStdin { nJobs += 1 if cStdin, err := container.StdinPipe(); err != nil { errors <- err } else { go func() { utils.Debugf("[start] attach stdin\n") defer utils.Debugf("[end] attach stdin\n") // No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr if cStdout != nil { defer cStdout.Close() } if cStderr != nil { defer cStderr.Close() } if container.Config.StdinOnce && !container.Config.Tty { defer cStdin.Close() } if container.Config.Tty { _, err = utils.CopyEscapable(cStdin, stdin) } else { _, err = io.Copy(cStdin, stdin) } if err != nil { utils.Debugf("[error] attach stdin: %s\n", err) } // Discard error, expecting pipe error errors <- nil }() } } if stdout != nil { nJobs += 1 if p, err := container.StdoutPipe(); err != nil { errors <- err } else { cStdout = p go func() { utils.Debugf("[start] attach stdout\n") defer utils.Debugf("[end] attach stdout\n") // If we are in StdinOnce mode, then close stdin if container.Config.StdinOnce { if stdin != nil { defer stdin.Close() } if stdinCloser != nil { defer stdinCloser.Close() } } _, err := io.Copy(stdout, cStdout) if err != nil { utils.Debugf("[error] attach stdout: %s\n", err) } errors <- err }() } } if stderr != nil { nJobs += 1 if p, err := container.StderrPipe(); err != nil { errors <- err } else { cStderr = p go func() { utils.Debugf("[start] attach stderr\n") defer utils.Debugf("[end] attach stderr\n") // If we are in StdinOnce mode, then close stdin if container.Config.StdinOnce { if stdin != nil { defer stdin.Close() } if stdinCloser != nil { defer stdinCloser.Close() } } _, err := io.Copy(stderr, cStderr) if err != nil { utils.Debugf("[error] attach stderr: %s\n", err) } errors <- err }() } } return utils.Go(func() error { if cStdout != nil { defer cStdout.Close() } if cStderr != nil { defer cStderr.Close() } // FIXME: how do clean up the stdin goroutine without the unwanted side effect // of closing the passed stdin? Add an intermediary io.Pipe? for i := 0; i < nJobs; i += 1 { utils.Debugf("Waiting for job %d/%d\n", i+1, nJobs) if err := <-errors; err != nil { utils.Debugf("Job %d returned error %s. Aborting all jobs\n", i+1, err) return err } utils.Debugf("Job %d completed successfully\n", i+1) } utils.Debugf("All jobs completed successfully\n") return nil }) }
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer) error { defer func() { if started != nil { close(started) } }() req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), nil) 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.isTerminal && os.Getenv("NORAW") == "" { oldState, err = term.SetRawTerminal(cli.terminalFd) if err != nil { return err } defer term.RestoreTerminal(cli.terminalFd, oldState) } if stdout != nil || stderr != nil { receiveStdout = utils.Go(func() (err error) { defer func() { if in != nil { if setRawTerminal && cli.isTerminal { term.RestoreTerminal(cli.terminalFd, 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 = utils.StdCopy(stdout, stderr, br) } utils.Debugf("[hijack] End of stdout") return err }) } sendStdin := utils.Go(func() error { if in != nil { io.Copy(rwc, in) utils.Debugf("[hijack] End of stdin") } if tcpc, ok := rwc.(*net.TCPConn); ok { if err := tcpc.CloseWrite(); err != nil { utils.Debugf("Couldn't send EOF: %s\n", err) } } else if unixc, ok := rwc.(*net.UnixConn); ok { if err := unixc.CloseWrite(); err != nil { utils.Debugf("Couldn't send EOF: %s\n", err) } } // Discard errors due to pipe interruption return nil }) if stdout != nil || stderr != nil { if err := <-receiveStdout; err != nil { utils.Debugf("Error receiveStdout: %s", err) return err } } if !cli.isTerminal { if err := <-sendStdin; err != nil { utils.Debugf("Error sendStdin: %s", err) return err } } return nil }