// CopyToPipe connects streamconfig with a libcontainerd.IOPipe func (streamConfig *StreamConfig) CopyToPipe(iop libcontainerd.IOPipe) { copyFunc := func(w io.Writer, r io.Reader) { streamConfig.Add(1) go func() { if _, err := pools.Copy(w, r); err != nil { logrus.Errorf("stream copy error: %+v", err) } streamConfig.Done() }() } if iop.Stdout != nil { copyFunc(streamConfig.Stdout(), iop.Stdout) } if iop.Stderr != nil { copyFunc(streamConfig.Stderr(), iop.Stderr) } if stdin := streamConfig.Stdin(); stdin != nil { if iop.Stdin != nil { go func() { pools.Copy(iop.Stdin, stdin) if err := iop.Stdin.Close(); err != nil { logrus.Errorf("failed to close stdin: %+v", err) } }() } } }
func splitConfigRootFSFromTar(in io.ReadCloser, config *[]byte) io.ReadCloser { pr, pw := io.Pipe() go func() { tarReader := tar.NewReader(in) tarWriter := tar.NewWriter(pw) defer in.Close() hasRootFS := false for { hdr, err := tarReader.Next() if err == io.EOF { if !hasRootFS { pw.CloseWithError(errors.Wrap(err, "no rootfs found")) return } // Signals end of archive. tarWriter.Close() pw.Close() return } if err != nil { pw.CloseWithError(errors.Wrap(err, "failed to read from tar")) return } content := io.Reader(tarReader) name := path.Clean(hdr.Name) if path.IsAbs(name) { name = name[1:] } if name == configFileName { dt, err := ioutil.ReadAll(content) if err != nil { pw.CloseWithError(errors.Wrapf(err, "failed to read %s", configFileName)) return } *config = dt } if parts := strings.Split(name, "/"); len(parts) != 0 && parts[0] == rootFSFileName { hdr.Name = path.Clean(path.Join(parts[1:]...)) if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(strings.ToLower(hdr.Linkname), rootFSFileName+"/") { hdr.Linkname = hdr.Linkname[len(rootFSFileName)+1:] } if err := tarWriter.WriteHeader(hdr); err != nil { pw.CloseWithError(errors.Wrap(err, "error writing tar header")) return } if _, err := pools.Copy(tarWriter, content); err != nil { pw.CloseWithError(errors.Wrap(err, "error copying tar data")) return } hasRootFS = true } else { io.Copy(ioutil.Discard, content) } } }() return pr }
// AttachPipes attaches given pipes to TtyConsole func (t *TtyConsole) AttachPipes(pipes *execdriver.Pipes) error { go func() { if wb, ok := pipes.Stdout.(interface { CloseWriters() error }); ok { defer wb.CloseWriters() } pools.Copy(pipes.Stdout, t.console) }() if pipes.Stdin != nil { go func() { pools.Copy(t.console, pipes.Stdin) pipes.Stdin.Close() }() } return nil }
// AttachPipes attaches given pipes to TtyConsole func (t *TtyConsole) AttachPipes(pipes *execdriver.Pipes, wg *sync.WaitGroup) error { wg.Add(1) go func() { defer wg.Done() if wb, ok := pipes.Stdout.(interface { CloseWriters() error }); ok { defer wb.CloseWriters() } pools.Copy(pipes.Stdout, t.console) }() if pipes.Stdin != nil { go func() { pools.Copy(t.console, pipes.Stdin) pipes.Stdin.Close() }() } return nil }
func copyRegular(srcPath, dstPath string, mode os.FileMode) error { srcFile, err := os.Open(srcPath) if err != nil { return err } defer srcFile.Close() dstFile, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE, mode) if err != nil { return err } defer dstFile.Close() _, err = pools.Copy(dstFile, srcFile) return err }
// ContainerExecStart starts a previously set up exec instance. The // std streams are set up. // If ctx is cancelled, the process is terminated. func (d *Daemon) ContainerExecStart(ctx context.Context, name string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) (err error) { var ( cStdin io.ReadCloser cStdout, cStderr io.Writer ) ec, err := d.getExecConfig(name) if err != nil { return errExecNotFound(name) } ec.Lock() if ec.ExitCode != nil { ec.Unlock() err := fmt.Errorf("Error: Exec command %s has already run", ec.ID) return errors.NewRequestConflictError(err) } if ec.Running { ec.Unlock() return fmt.Errorf("Error: Exec command %s is already running", ec.ID) } ec.Running = true defer func() { if err != nil { ec.Running = false exitCode := 126 ec.ExitCode = &exitCode } }() ec.Unlock() c := d.containers.Get(ec.ContainerID) logrus.Debugf("starting exec command %s in container %s", ec.ID, c.ID) d.LogContainerEvent(c, "exec_start: "+ec.Entrypoint+" "+strings.Join(ec.Args, " ")) if ec.OpenStdin && stdin != nil { r, w := io.Pipe() go func() { defer w.Close() defer logrus.Debug("Closing buffered stdin pipe") pools.Copy(w, stdin) }() cStdin = r } if ec.OpenStdout { cStdout = stdout } if ec.OpenStderr { cStderr = stderr } if ec.OpenStdin { ec.NewInputPipes() } else { ec.NewNopInputPipe() } p := libcontainerd.Process{ Args: append([]string{ec.Entrypoint}, ec.Args...), Terminal: ec.Tty, } if err := execSetPlatformOpt(c, ec, &p); err != nil { return err } attachErr := container.AttachStreams(ctx, ec.StreamConfig, ec.OpenStdin, true, ec.Tty, cStdin, cStdout, cStderr, ec.DetachKeys) if err := d.containerd.AddProcess(ctx, c.ID, name, p); err != nil { return err } select { case <-ctx.Done(): logrus.Debugf("Sending TERM signal to process %v in container %v", name, c.ID) d.containerd.SignalProcess(c.ID, name, int(signal.SignalMap["TERM"])) select { case <-time.After(termProcessTimeout * time.Second): logrus.Infof("Container %v, process %v failed to exit within %d seconds of signal TERM - using the force", c.ID, name, termProcessTimeout) d.containerd.SignalProcess(c.ID, name, int(signal.SignalMap["KILL"])) case <-attachErr: // TERM signal worked } return fmt.Errorf("context cancelled") case err := <-attachErr: if err != nil { if _, ok := err.(container.DetachError); !ok { return fmt.Errorf("exec attach failed with error: %v", err) } d.LogContainerEvent(c, "exec_detach") } } return nil }
// ContainerExecStart starts a previously set up exec instance. The // std streams are set up. func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) error { var ( cStdin io.ReadCloser cStdout, cStderr io.Writer ) ec, err := d.getExecConfig(name) if err != nil { return derr.ErrorCodeNoExecID.WithArgs(name) } ec.Lock() if ec.Running { ec.Unlock() return derr.ErrorCodeExecRunning.WithArgs(ec.ID) } ec.Running = true ec.Unlock() logrus.Debugf("starting exec command %s in container %s", ec.ID, ec.Container.ID) container := ec.Container d.LogContainerEvent(container, "exec_start: "+ec.ProcessConfig.Entrypoint+" "+strings.Join(ec.ProcessConfig.Arguments, " ")) if ec.OpenStdin { r, w := io.Pipe() go func() { defer w.Close() defer logrus.Debugf("Closing buffered stdin pipe") pools.Copy(w, stdin) }() cStdin = r } if ec.OpenStdout { cStdout = stdout } if ec.OpenStderr { cStderr = stderr } ec.streamConfig.stderr = new(broadcaster.Unbuffered) ec.streamConfig.stdout = new(broadcaster.Unbuffered) // Attach to stdin if ec.OpenStdin { ec.streamConfig.stdin, ec.streamConfig.stdinPipe = io.Pipe() } else { ec.streamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin } attachErr := attach(&ec.streamConfig, ec.OpenStdin, true, ec.ProcessConfig.Tty, cStdin, cStdout, cStderr) execErr := make(chan error) // Note, the ExecConfig data will be removed when the container // itself is deleted. This allows us to query it (for things like // the exitStatus) even after the cmd is done running. go func() { execErr <- d.containerExec(container, ec) }() select { case err := <-attachErr: if err != nil { return derr.ErrorCodeExecAttach.WithArgs(err) } return nil case err := <-execErr: if aErr := <-attachErr; aErr != nil && err == nil { return derr.ErrorCodeExecAttach.WithArgs(aErr) } if err == nil { return nil } // Maybe the container stopped while we were trying to exec if !container.IsRunning() { return derr.ErrorCodeExecContainerStopped } return derr.ErrorCodeExecCantRun.WithArgs(ec.ID, container.ID, err) } }
func (d *Daemon) ContainerExecStart(execName string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) error { var ( cStdin io.ReadCloser cStdout, cStderr io.Writer ) execConfig, err := d.getExecConfig(execName) if err != nil { return err } func() { execConfig.Lock() defer execConfig.Unlock() if execConfig.Running { err = fmt.Errorf("Error: Exec command %s is already running", execName) } execConfig.Running = true }() if err != nil { return err } logrus.Debugf("starting exec command %s in container %s", execConfig.ID, execConfig.Container.ID) container := execConfig.Container container.LogEvent("exec_start: " + execConfig.ProcessConfig.Entrypoint + " " + strings.Join(execConfig.ProcessConfig.Arguments, " ")) if execConfig.OpenStdin { r, w := io.Pipe() go func() { defer w.Close() defer logrus.Debugf("Closing buffered stdin pipe") pools.Copy(w, stdin) }() cStdin = r } if execConfig.OpenStdout { cStdout = stdout } if execConfig.OpenStderr { cStderr = stderr } execConfig.StreamConfig.stderr = broadcastwriter.New() execConfig.StreamConfig.stdout = broadcastwriter.New() // Attach to stdin if execConfig.OpenStdin { execConfig.StreamConfig.stdin, execConfig.StreamConfig.stdinPipe = io.Pipe() } else { execConfig.StreamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin } attachErr := attach(&execConfig.StreamConfig, execConfig.OpenStdin, true, execConfig.ProcessConfig.Tty, cStdin, cStdout, cStderr) execErr := make(chan error) // Note, the execConfig data will be removed when the container // itself is deleted. This allows us to query it (for things like // the exitStatus) even after the cmd is done running. go func() { if err := container.Exec(execConfig); err != nil { execErr <- fmt.Errorf("Cannot run exec command %s in container %s: %s", execName, container.ID, err) } }() select { case err := <-attachErr: if err != nil { return fmt.Errorf("attach failed with error: %s", err) } return nil case err := <-execErr: if err == nil { return nil } // Maybe the container stopped while we were trying to exec if !container.IsRunning() { return fmt.Errorf("container stopped while running exec") } return err } }
// ContainerExecStart starts a previously set up exec instance. The // std streams are set up. func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) error { var ( cStdin io.ReadCloser cStdout, cStderr io.Writer ) ec, err := d.getExecConfig(name) if err != nil { return errExecNotFound(name) } ec.Lock() if ec.ExitCode != nil { ec.Unlock() err := fmt.Errorf("Error: Exec command %s has already run", ec.ID) return errors.NewRequestConflictError(err) } if ec.Running { ec.Unlock() return fmt.Errorf("Error: Exec command %s is already running", ec.ID) } ec.Running = true ec.Unlock() c := d.containers.Get(ec.ContainerID) logrus.Debugf("starting exec command %s in container %s", ec.ID, c.ID) d.LogContainerEvent(c, "exec_start: "+ec.ProcessConfig.Entrypoint+" "+strings.Join(ec.ProcessConfig.Arguments, " ")) if ec.OpenStdin && stdin != nil { r, w := io.Pipe() go func() { defer w.Close() defer logrus.Debugf("Closing buffered stdin pipe") pools.Copy(w, stdin) }() cStdin = r } if ec.OpenStdout { cStdout = stdout } if ec.OpenStderr { cStderr = stderr } if ec.OpenStdin { ec.NewInputPipes() } else { ec.NewNopInputPipe() } attachErr := container.AttachStreams(ec.StreamConfig, ec.OpenStdin, true, ec.ProcessConfig.Tty, cStdin, cStdout, cStderr, ec.DetachKeys) execErr := make(chan error) // Note, the ExecConfig data will be removed when the container // itself is deleted. This allows us to query it (for things like // the exitStatus) even after the cmd is done running. go func() { execErr <- d.containerExec(c, ec) }() select { case err := <-attachErr: if err != nil { return fmt.Errorf("attach failed with error: %v", err) } return nil case err := <-execErr: if aErr := <-attachErr; aErr != nil && err == nil { return fmt.Errorf("attach failed with error: %v", aErr) } if err == nil { return nil } // Maybe the container stopped while we were trying to exec if !c.IsRunning() { return fmt.Errorf("container stopped while running exec: %s", c.ID) } return fmt.Errorf("Cannot run exec command %s in container %s: %s", ec.ID, c.ID, err) } }
// ContainerExecStart starts a previously set up exec instance. The // std streams are set up. func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) (err error) { var ( cStdin io.ReadCloser cStdout, cStderr io.Writer ) ec, err := d.getExecConfig(name) if err != nil { return errExecNotFound(name) } ec.Lock() if ec.ExitCode != nil { ec.Unlock() err := fmt.Errorf("Error: Exec command %s has already run", ec.ID) return errors.NewRequestConflictError(err) } if ec.Running { ec.Unlock() return fmt.Errorf("Error: Exec command %s is already running", ec.ID) } ec.Running = true defer func() { if err != nil { ec.Running = false exitCode := 126 ec.ExitCode = &exitCode } }() ec.Unlock() c := d.containers.Get(ec.ContainerID) logrus.Debugf("starting exec command %s in container %s", ec.ID, c.ID) d.LogContainerEvent(c, "exec_start: "+ec.Entrypoint+" "+strings.Join(ec.Args, " ")) if ec.OpenStdin && stdin != nil { r, w := io.Pipe() go func() { defer w.Close() defer logrus.Debugf("Closing buffered stdin pipe") pools.Copy(w, stdin) }() cStdin = r } if ec.OpenStdout { cStdout = stdout } if ec.OpenStderr { cStderr = stderr } if ec.OpenStdin { ec.NewInputPipes() } else { ec.NewNopInputPipe() } p := libcontainerd.Process{ Args: append([]string{ec.Entrypoint}, ec.Args...), Terminal: ec.Tty, } if err := execSetPlatformOpt(c, ec, &p); err != nil { return nil } attachErr := container.AttachStreams(context.Background(), ec.StreamConfig, ec.OpenStdin, true, ec.Tty, cStdin, cStdout, cStderr, ec.DetachKeys) if err := d.containerd.AddProcess(c.ID, name, p); err != nil { return err } err = <-attachErr if err != nil { return fmt.Errorf("attach failed with error: %v", err) } return nil }