diff options
-rw-r--r-- | libpod/container_api.go | 35 | ||||
-rw-r--r-- | libpod/container_internal.go | 22 | ||||
-rw-r--r-- | libpod/define/exec_codes.go | 6 | ||||
-rw-r--r-- | libpod/oci.go | 9 | ||||
-rw-r--r-- | libpod/oci_attach_linux.go | 4 | ||||
-rw-r--r-- | libpod/oci_conmon_linux.go | 84 | ||||
-rw-r--r-- | libpod/oci_missing.go | 4 |
7 files changed, 65 insertions, 99 deletions
diff --git a/libpod/container_api.go b/libpod/container_api.go index 5e8fcea47..039619ea6 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -282,24 +282,13 @@ func (c *Container) Exec(tty, privileged bool, env map[string]string, cmd []stri opts.Resize = resize opts.DetachKeys = detachKeys - pid := 0 - pipeDataChan, attachChan, err := c.ociRuntime.ExecContainer(c, sessionID, opts) - // if pipeDataChan isn't nil, we should set the err - if pipeDataChan != nil { - pidData := <-pipeDataChan - if pidData.err != nil { - err = pidData.err - } - pid = pidData.data - } + pid, attachChan, err := c.ociRuntime.ExecContainer(c, sessionID, opts) if err != nil { ec := define.ExecErrorCodeGeneric // Conmon will pass a non-zero exit code from the runtime as a pid here. // we differentiate a pid with an exit code by sending it as negative, so reverse // that change and return the exit code the runtime failed with. - // Make sure the value is not ErrorConmonRead, as that is a podman set bogus value - // and not sent by conmon (and thus has no special meaning) - if pid < 0 && pid != define.ErrorConmonRead { + if pid < 0 { ec = -1 * pid } return ec, err @@ -329,24 +318,18 @@ func (c *Container) Exec(tty, privileged bool, env map[string]string, cmd []stri lastErr := <-attachChan - exitCodeData := <-pipeDataChan - if exitCodeData.err != nil { + exitCode, err := c.readExecExitCode(sessionID) + if err != nil { if lastErr != nil { logrus.Errorf(lastErr.Error()) } - lastErr = exitCodeData.err + lastErr = err } - if exitCodeData.data != 0 { + if exitCode != 0 { if lastErr != nil { logrus.Errorf(lastErr.Error()) } - // ErrorConmonRead is a bogus value set by podman to indicate reading a value from - // conmon failed. Since it is specifically not a valid exit code, we should set - // a generic error here - if exitCodeData.data == define.ErrorConmonRead { - exitCodeData.data = define.ExecErrorCodeGeneric - } - lastErr = errors.Wrapf(define.ErrOCIRuntime, "non zero exit code: %d", exitCodeData.data) + lastErr = errors.Wrapf(define.ErrOCIRuntime, "non zero exit code: %d", exitCode) } // Lock again @@ -357,7 +340,7 @@ func (c *Container) Exec(tty, privileged bool, env map[string]string, cmd []stri // Sync the container again to pick up changes in state if err := c.syncContainer(); err != nil { logrus.Errorf("error syncing container %s state to remove exec session %s", c.ID(), sessionID) - return exitCodeData.data, lastErr + return exitCode, lastErr } // Remove the exec session from state @@ -365,7 +348,7 @@ func (c *Container) Exec(tty, privileged bool, env map[string]string, cmd []stri if err := c.save(); err != nil { logrus.Errorf("Error removing exec session %s from container %s state: %v", sessionID, c.ID(), err) } - return exitCodeData.data, lastErr + return exitCode, lastErr } // AttachStreams contains streams that will be attached to the container diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 60b13f125..a0805c1fa 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -206,6 +206,28 @@ func (c *Container) execOCILog(sessionID string) string { return filepath.Join(c.execBundlePath(sessionID), "oci-log") } +// readExecExitCode reads the exit file for an exec session and returns +// the exit code +func (c *Container) readExecExitCode(sessionID string) (int, error) { + exitFile := filepath.Join(c.execExitFileDir(sessionID), c.ID()) + chWait := make(chan error) + defer close(chWait) + + _, err := WaitForFile(exitFile, chWait, time.Second*5) + if err != nil { + return -1, err + } + ec, err := ioutil.ReadFile(exitFile) + if err != nil { + return -1, err + } + ecInt, err := strconv.Atoi(string(ec)) + if err != nil { + return -1, err + } + return ecInt, nil +} + // Wait for the container's exit file to appear. // When it does, update our state based on it. func (c *Container) waitForExitFileAndSync() error { diff --git a/libpod/define/exec_codes.go b/libpod/define/exec_codes.go index c2ec08666..f94616b33 100644 --- a/libpod/define/exec_codes.go +++ b/libpod/define/exec_codes.go @@ -1,7 +1,6 @@ package define import ( - "math" "strings" "github.com/pkg/errors" @@ -18,11 +17,6 @@ const ( ExecErrorCodeCannotInvoke = 126 // ExecErrorCodeNotFound is the error code to return when a command cannot be found ExecErrorCodeNotFound = 127 - // ErrorConmonRead is a bogus value that can neither be a valid PID or exit code. It is - // used because conmon will send a negative value when sending a PID back over a pipe FD - // to signify something went wrong in the runtime. We need to differentiate between that - // value and a failure on the podman side of reading that value. Thus, we use ErrorConmonRead - ErrorConmonRead = math.MinInt32 - 1 ) // TranslateExecErrorToExitCode takes an error and checks whether it diff --git a/libpod/oci.go b/libpod/oci.go index 41d420664..27edebefc 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -70,7 +70,7 @@ type OCIRuntime interface { // ExecContainer executes a command in a running container. // Returns an int (exit code), error channel (errors from attach), and // error (errors that occurred attempting to start the exec session). - ExecContainer(ctr *Container, sessionID string, options *ExecOptions) (chan DataAndErr, chan error, error) + ExecContainer(ctr *Container, sessionID string, options *ExecOptions) (int, chan error, error) // ExecStopContainer stops a given exec session in a running container. // SIGTERM with be sent initially, then SIGKILL after the given timeout. // If timeout is 0, SIGKILL will be sent immediately, and SIGTERM will @@ -159,10 +159,3 @@ type HTTPAttachStreams struct { Stdout bool Stderr bool } - -// DataAndErr is a generic structure for passing around an int and an error -// it is especially useful for getting information from conmon -type DataAndErr struct { - data int - err error -} diff --git a/libpod/oci_attach_linux.go b/libpod/oci_attach_linux.go index 5a8198d05..46c70e7eb 100644 --- a/libpod/oci_attach_linux.go +++ b/libpod/oci_attach_linux.go @@ -119,8 +119,8 @@ func (c *Container) attachToExec(streams *AttachStreams, keys string, resize <-c socketPath := buildSocketPath(sockPath) // 2: read from attachFd that the parent process has set up the console socket - if pipeData := readConmonPipeData(attachFd, ""); pipeData.err != nil { - return pipeData.err + if _, err := readConmonPipeData(attachFd, ""); err != nil { + return err } // 2: then attach diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index f260e3a39..800f89603 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -595,29 +595,31 @@ func (r *ConmonOCIRuntime) AttachResize(ctr *Container, newSize remotecommand.Te // ExecContainer executes a command in a running container // TODO: Split into Create/Start/Attach/Wait -func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options *ExecOptions) (chan DataAndErr, chan error, error) { +func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options *ExecOptions) (int, chan error, error) { if options == nil { - return nil, nil, errors.Wrapf(define.ErrInvalidArg, "must provide an ExecOptions struct to ExecContainer") + return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide an ExecOptions struct to ExecContainer") } if len(options.Cmd) == 0 { - return nil, nil, errors.Wrapf(define.ErrInvalidArg, "must provide a command to execute") + return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide a command to execute") } if sessionID == "" { - return nil, nil, errors.Wrapf(define.ErrEmptyID, "must provide a session ID for exec") + return -1, nil, errors.Wrapf(define.ErrEmptyID, "must provide a session ID for exec") } // create sync pipe to receive the pid parentSyncPipe, childSyncPipe, err := newPipe() if err != nil { - return nil, nil, errors.Wrapf(err, "error creating socket pair") + return -1, nil, errors.Wrapf(err, "error creating socket pair") } + defer errorhandling.CloseQuiet(parentSyncPipe) + // create start pipe to set the cgroup before running // attachToExec is responsible for closing parentStartPipe childStartPipe, parentStartPipe, err := newPipe() if err != nil { - return nil, nil, errors.Wrapf(err, "error creating socket pair") + return -1, nil, errors.Wrapf(err, "error creating socket pair") } // We want to make sure we close the parent{Start,Attach}Pipes if we fail @@ -636,7 +638,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options // attachToExec is responsible for closing parentAttachPipe parentAttachPipe, childAttachPipe, err := newPipe() if err != nil { - return nil, nil, errors.Wrapf(err, "error creating socket pair") + return -1, nil, errors.Wrapf(err, "error creating socket pair") } defer func() { @@ -656,7 +658,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options runtimeDir, err := util.GetRuntimeDir() if err != nil { - return nil, nil, err + return -1, nil, err } finalEnv := make([]string, 0, len(options.Env)) @@ -666,7 +668,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options processFile, err := prepareProcessExec(c, options.Cmd, finalEnv, options.Terminal, options.Cwd, options.User, sessionID) if err != nil { - return nil, nil, err + return -1, nil, err } var ociLog string @@ -715,7 +717,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options conmonEnv, extraFiles, err := r.configureConmonEnv(runtimeDir) if err != nil { - return nil, nil, err + return -1, nil, err } if options.PreserveFDs > 0 { @@ -746,10 +748,10 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options childrenClosed = true if err != nil { - return nil, nil, errors.Wrapf(err, "cannot start container %s", c.ID()) + return -1, nil, errors.Wrapf(err, "cannot start container %s", c.ID()) } if err := r.moveConmonToCgroupAndSignal(c, execCmd, parentStartPipe); err != nil { - return nil, nil, err + return -1, nil, err } if options.PreserveFDs > 0 { @@ -772,16 +774,9 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options }() attachToExecCalled = true - dataChan := make(chan DataAndErr) - go func() { - // read the exec pid - dataChan <- readConmonPipeData(parentSyncPipe, ociLog) - // read the exec exit code - dataChan <- readConmonPipeData(parentSyncPipe, ociLog) - errorhandling.CloseQuiet(parentSyncPipe) - }() + pid, err := readConmonPipeData(parentSyncPipe, ociLog) - return dataChan, attachChan, err + return pid, attachChan, err } // ExecStopContainer stops a given exec session in a running container. @@ -1211,14 +1206,14 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co return err } - pipeData := readConmonPipeData(parentSyncPipe, ociLog) - if pipeData.err != nil { + pid, err := readConmonPipeData(parentSyncPipe, ociLog) + if err != nil { if err2 := r.DeleteContainer(ctr); err2 != nil { logrus.Errorf("Error removing container %s from runtime after creation failed", ctr.ID()) } - return pipeData.err + return err } - ctr.state.PID = pipeData.data + ctr.state.PID = pid conmonPID, err := readConmonPidFile(ctr.config.ConmonPidFile) if err != nil { @@ -1530,7 +1525,7 @@ func readConmonPidFile(pidFile string) (int, error) { } // readConmonPipeData attempts to read a syncInfo struct from the pipe -func readConmonPipeData(pipe *os.File, ociLog string) DataAndErr { +func readConmonPipeData(pipe *os.File, ociLog string) (int, error) { // syncInfo is used to return data from monitor process to daemon type syncInfo struct { Data int `json:"data"` @@ -1557,7 +1552,7 @@ func readConmonPipeData(pipe *os.File, ociLog string) DataAndErr { ch <- syncStruct{si: si} }() - data := define.ErrorConmonRead + data := -1 select { case ss := <-ch: if ss.err != nil { @@ -1566,17 +1561,11 @@ func readConmonPipeData(pipe *os.File, ociLog string) DataAndErr { if err == nil { var ociErr ociError if err := json.Unmarshal(ociLogData, &ociErr); err == nil { - return DataAndErr{ - data: data, - err: getOCIRuntimeError(ociErr.Msg), - } + return -1, getOCIRuntimeError(ociErr.Msg) } } } - return DataAndErr{ - data: data, - err: errors.Wrapf(ss.err, "container create failed (no logs from conmon)"), - } + return -1, errors.Wrapf(ss.err, "container create failed (no logs from conmon)") } logrus.Debugf("Received: %d", ss.si.Data) if ss.si.Data < 0 { @@ -1585,36 +1574,21 @@ func readConmonPipeData(pipe *os.File, ociLog string) DataAndErr { if err == nil { var ociErr ociError if err := json.Unmarshal(ociLogData, &ociErr); err == nil { - return DataAndErr{ - data: ss.si.Data, - err: getOCIRuntimeError(ociErr.Msg), - } + return ss.si.Data, getOCIRuntimeError(ociErr.Msg) } } } // If we failed to parse the JSON errors, then print the output as it is if ss.si.Message != "" { - return DataAndErr{ - data: ss.si.Data, - err: getOCIRuntimeError(ss.si.Message), - } - } - return DataAndErr{ - data: ss.si.Data, - err: errors.Wrapf(define.ErrInternal, "container create failed"), + return ss.si.Data, getOCIRuntimeError(ss.si.Message) } + return ss.si.Data, errors.Wrapf(define.ErrInternal, "container create failed") } data = ss.si.Data case <-time.After(define.ContainerCreateTimeout): - return DataAndErr{ - data: data, - err: errors.Wrapf(define.ErrInternal, "container creation timeout"), - } - } - return DataAndErr{ - data: data, - err: nil, + return -1, errors.Wrapf(define.ErrInternal, "container creation timeout") } + return data, nil } // writeConmonPipeData writes nonse data to a pipe diff --git a/libpod/oci_missing.go b/libpod/oci_missing.go index 1b7c1979d..ff7eea625 100644 --- a/libpod/oci_missing.go +++ b/libpod/oci_missing.go @@ -121,8 +121,8 @@ func (r *MissingRuntime) AttachResize(ctr *Container, newSize remotecommand.Term } // ExecContainer is not available as the runtime is missing -func (r *MissingRuntime) ExecContainer(ctr *Container, sessionID string, options *ExecOptions) (chan DataAndErr, chan error, error) { - return nil, nil, r.printError() +func (r *MissingRuntime) ExecContainer(ctr *Container, sessionID string, options *ExecOptions) (int, chan error, error) { + return -1, nil, r.printError() } // ExecStopContainer is not available as the runtime is missing. |