// 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. // 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) (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 }