summaryrefslogtreecommitdiffstats
path: root/src/exec_nopty.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/exec_nopty.c')
-rw-r--r--src/exec_nopty.c573
1 files changed, 573 insertions, 0 deletions
diff --git a/src/exec_nopty.c b/src/exec_nopty.c
new file mode 100644
index 0000000..0df79f0
--- /dev/null
+++ b/src/exec_nopty.c
@@ -0,0 +1,573 @@
+/*
+ * Copyright (c) 2009-2017 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This is an open source non-commercial project. Dear PVS-Studio, please check it.
+ * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#include "sudo.h"
+#include "sudo_exec.h"
+#include "sudo_event.h"
+#include "sudo_plugin.h"
+#include "sudo_plugin_int.h"
+
+struct exec_closure_nopty {
+ pid_t cmnd_pid;
+ pid_t ppgrp;
+ struct command_status *cstat;
+ struct command_details *details;
+ struct sudo_event_base *evbase;
+ struct sudo_event *errpipe_event;
+ struct sudo_event *sigint_event;
+ struct sudo_event *sigquit_event;
+ struct sudo_event *sigtstp_event;
+ struct sudo_event *sigterm_event;
+ struct sudo_event *sighup_event;
+ struct sudo_event *sigalrm_event;
+ struct sudo_event *sigpipe_event;
+ struct sudo_event *sigusr1_event;
+ struct sudo_event *sigusr2_event;
+ struct sudo_event *sigchld_event;
+ struct sudo_event *sigcont_event;
+ struct sudo_event *siginfo_event;
+};
+
+static void handle_sigchld_nopty(struct exec_closure_nopty *ec);
+
+/* Note: this is basically the same as mon_errpipe_cb() in exec_monitor.c */
+static void
+errpipe_cb(int fd, int what, void *v)
+{
+ struct exec_closure_nopty *ec = v;
+ ssize_t nread;
+ int errval;
+ debug_decl(errpipe_cb, SUDO_DEBUG_EXEC);
+
+ /*
+ * Read errno from child or EOF when command is executed.
+ * Note that the error pipe is *blocking*.
+ */
+ nread = read(fd, &errval, sizeof(errval));
+ switch (nread) {
+ case -1:
+ if (errno != EAGAIN && errno != EINTR) {
+ if (ec->cstat->val == CMD_INVALID) {
+ /* XXX - need a way to distinguish non-exec error. */
+ ec->cstat->type = CMD_ERRNO;
+ ec->cstat->val = errno;
+ }
+ sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO,
+ "%s: failed to read error pipe", __func__);
+ sudo_ev_loopbreak(ec->evbase);
+ }
+ break;
+ default:
+ if (nread == 0) {
+ /* The error pipe closes when the command is executed. */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "EOF on error pipe");
+ } else {
+ /* Errno value when child is unable to execute command. */
+ sudo_debug_printf(SUDO_DEBUG_INFO, "errno from child: %s",
+ strerror(errval));
+ ec->cstat->type = CMD_ERRNO;
+ ec->cstat->val = errval;
+ }
+ sudo_ev_del(ec->evbase, ec->errpipe_event);
+ close(fd);
+ break;
+ }
+ debug_return;
+}
+
+/* Signal callback */
+static void
+signal_cb_nopty(int signo, int what, void *v)
+{
+ struct sudo_ev_siginfo_container *sc = v;
+ struct exec_closure_nopty *ec = sc->closure;
+ char signame[SIG2STR_MAX];
+ debug_decl(signal_cb_nopty, SUDO_DEBUG_EXEC)
+
+ if (ec->cmnd_pid == -1)
+ debug_return;
+
+ if (sig2str(signo, signame) == -1)
+ snprintf(signame, sizeof(signame), "%d", signo);
+ sudo_debug_printf(SUDO_DEBUG_DIAG,
+ "%s: evbase %p, command: %d, signo %s(%d), cstat %p",
+ __func__, ec->evbase, (int)ec->cmnd_pid, signame, signo, ec->cstat);
+
+ switch (signo) {
+ case SIGCHLD:
+ handle_sigchld_nopty(ec);
+ if (ec->cmnd_pid == -1) {
+ /* Command exited or was killed, exit event loop. */
+ sudo_ev_loopexit(ec->evbase);
+ }
+ debug_return;
+#ifdef SIGINFO
+ case SIGINFO:
+#endif
+ case SIGINT:
+ case SIGQUIT:
+ case SIGTSTP:
+ /*
+ * Only forward user-generated signals not sent by a process in
+ * the command's own process group. Signals sent by the kernel
+ * may include SIGTSTP when the user presses ^Z. Curses programs
+ * often trap ^Z and send SIGTSTP to their own pgrp, so we don't
+ * want to send an extra SIGTSTP.
+ */
+ if (!USER_SIGNALED(sc->siginfo))
+ debug_return;
+ if (sc->siginfo->si_pid != 0) {
+ pid_t si_pgrp = getpgid(sc->siginfo->si_pid);
+ if (si_pgrp != -1) {
+ if (si_pgrp == ec->ppgrp || si_pgrp == ec->cmnd_pid)
+ debug_return;
+ } else if (sc->siginfo->si_pid == ec->cmnd_pid) {
+ debug_return;
+ }
+ }
+ break;
+ default:
+ /*
+ * Do not forward signals sent by a process in the command's process
+ * group, as we don't want the command to indirectly kill itself.
+ * For example, this can happen with some versions of reboot that
+ * call kill(-1, SIGTERM) to kill all other processes.
+ */
+ if (USER_SIGNALED(sc->siginfo) && sc->siginfo->si_pid != 0) {
+ pid_t si_pgrp = getpgid(sc->siginfo->si_pid);
+ if (si_pgrp != -1) {
+ if (si_pgrp == ec->ppgrp || si_pgrp == ec->cmnd_pid)
+ debug_return;
+ } else if (sc->siginfo->si_pid == ec->cmnd_pid) {
+ debug_return;
+ }
+ }
+ break;
+ }
+
+ /* Send signal to command. */
+ if (signo == SIGALRM) {
+ terminate_command(ec->cmnd_pid, false);
+ } else if (kill(ec->cmnd_pid, signo) != 0) {
+ sudo_warn("kill(%d, SIG%s)", (int)ec->cmnd_pid, signame);
+ }
+
+ debug_return;
+}
+
+
+/*
+ * Fill in the exec closure and setup initial exec events.
+ * Allocates events for the signal pipe and error pipe.
+ */
+static void
+fill_exec_closure_nopty(struct exec_closure_nopty *ec,
+ struct command_status *cstat, struct command_details *details, int errfd)
+{
+ debug_decl(fill_exec_closure_nopty, SUDO_DEBUG_EXEC)
+
+ /* Fill in the non-event part of the closure. */
+ ec->ppgrp = getpgrp();
+ ec->cstat = cstat;
+ ec->details = details;
+
+ /* Setup event base and events. */
+ ec->evbase = sudo_ev_base_alloc();
+ if (ec->evbase == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+
+ /* Event for command status via errfd. */
+ ec->errpipe_event = sudo_ev_alloc(errfd,
+ SUDO_EV_READ|SUDO_EV_PERSIST, errpipe_cb, ec);
+ if (ec->errpipe_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->errpipe_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+ sudo_debug_printf(SUDO_DEBUG_INFO, "error pipe fd %d\n", errfd);
+
+ /* Events for local signals. */
+ ec->sigint_event = sudo_ev_alloc(SIGINT,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigint_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigint_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigquit_event = sudo_ev_alloc(SIGQUIT,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigquit_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigquit_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigtstp_event = sudo_ev_alloc(SIGTSTP,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigtstp_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigtstp_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigterm_event = sudo_ev_alloc(SIGTERM,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigterm_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigterm_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sighup_event = sudo_ev_alloc(SIGHUP,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sighup_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sighup_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigalrm_event = sudo_ev_alloc(SIGALRM,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigalrm_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigalrm_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigpipe_event = sudo_ev_alloc(SIGPIPE,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigpipe_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigpipe_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigusr1_event = sudo_ev_alloc(SIGUSR1,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigusr1_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigusr1_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigusr2_event = sudo_ev_alloc(SIGUSR2,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigusr2_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigusr2_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigchld_event = sudo_ev_alloc(SIGCHLD,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigchld_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigchld_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+ ec->sigcont_event = sudo_ev_alloc(SIGCONT,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->sigcont_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->sigcont_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+
+#ifdef SIGINFO
+ ec->siginfo_event = sudo_ev_alloc(SIGINFO,
+ SUDO_EV_SIGINFO, signal_cb_nopty, ec);
+ if (ec->siginfo_event == NULL)
+ sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
+ if (sudo_ev_add(ec->evbase, ec->siginfo_event, NULL, false) == -1)
+ sudo_fatal(U_("unable to add event to queue"));
+#endif
+
+ /* Set the default event base. */
+ sudo_ev_base_setdef(ec->evbase);
+
+ debug_return;
+}
+
+/*
+ * Free the dynamically-allocated contents of the exec closure.
+ */
+static void
+free_exec_closure_nopty(struct exec_closure_nopty *ec)
+{
+ debug_decl(free_exec_closure_nopty, SUDO_DEBUG_EXEC)
+
+ sudo_ev_base_free(ec->evbase);
+ sudo_ev_free(ec->errpipe_event);
+ sudo_ev_free(ec->sigint_event);
+ sudo_ev_free(ec->sigquit_event);
+ sudo_ev_free(ec->sigtstp_event);
+ sudo_ev_free(ec->sigterm_event);
+ sudo_ev_free(ec->sighup_event);
+ sudo_ev_free(ec->sigalrm_event);
+ sudo_ev_free(ec->sigpipe_event);
+ sudo_ev_free(ec->sigusr1_event);
+ sudo_ev_free(ec->sigusr2_event);
+ sudo_ev_free(ec->sigchld_event);
+ sudo_ev_free(ec->sigcont_event);
+ sudo_ev_free(ec->siginfo_event);
+
+ debug_return;
+}
+
+/*
+ * Execute a command and wait for it to finish.
+ */
+void
+exec_nopty(struct command_details *details, struct command_status *cstat)
+{
+ struct exec_closure_nopty ec = { 0 };
+ sigset_t set, oset;
+ int errpipe[2];
+ debug_decl(exec_nopty, SUDO_DEBUG_EXEC)
+
+ /*
+ * The policy plugin's session init must be run before we fork
+ * or certain pam modules won't be able to track their state.
+ */
+ if (policy_init_session(details) != true)
+ sudo_fatalx(U_("policy plugin failed session initialization"));
+
+ /*
+ * We use a pipe to get errno if execve(2) fails in the child.
+ */
+ if (pipe2(errpipe, O_CLOEXEC) != 0)
+ sudo_fatal(U_("unable to create pipe"));
+
+ /*
+ * Block signals until we have our handlers setup in the parent so
+ * we don't miss SIGCHLD if the command exits immediately.
+ */
+ sigfillset(&set);
+ sigprocmask(SIG_BLOCK, &set, &oset);
+
+ /* Check for early termination or suspend signals before we fork. */
+ if (sudo_terminated(cstat)) {
+ sigprocmask(SIG_SETMASK, &oset, NULL);
+ debug_return;
+ }
+
+ ec.cmnd_pid = sudo_debug_fork();
+ switch (ec.cmnd_pid) {
+ case -1:
+ sudo_fatal(U_("unable to fork"));
+ break;
+ case 0:
+ /* child */
+ sigprocmask(SIG_SETMASK, &oset, NULL);
+ close(errpipe[0]);
+ exec_cmnd(details, errpipe[1]);
+ while (write(errpipe[1], &errno, sizeof(int)) == -1) {
+ if (errno != EINTR)
+ break;
+ }
+ sudo_debug_exit_int(__func__, __FILE__, __LINE__, sudo_debug_subsys, 1);
+ _exit(1);
+ }
+ sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d", details->command,
+ (int)ec.cmnd_pid);
+ close(errpipe[1]);
+
+ /* No longer need execfd. */
+ if (details->execfd != -1) {
+ close(details->execfd);
+ details->execfd = -1;
+ }
+
+ /* Set command timeout if specified. */
+ if (ISSET(details->flags, CD_SET_TIMEOUT))
+ alarm(details->timeout);
+
+ /*
+ * Fill in exec closure, allocate event base, signal events and
+ * the error pipe event.
+ */
+ fill_exec_closure_nopty(&ec, cstat, details, errpipe[0]);
+
+ /* Restore signal mask now that signal handlers are setup. */
+ sigprocmask(SIG_SETMASK, &oset, NULL);
+
+ /*
+ * Non-pty event loop.
+ * Wait for command to exit, handles signals and the error pipe.
+ */
+ if (sudo_ev_dispatch(ec.evbase) == -1)
+ sudo_warn(U_("error in event loop"));
+ if (sudo_ev_got_break(ec.evbase)) {
+ /* error from callback */
+ sudo_debug_printf(SUDO_DEBUG_ERROR, "event loop exited prematurely");
+ /* kill command */
+ terminate_command(ec.cmnd_pid, true);
+ }
+
+#ifdef HAVE_SELINUX
+ if (ISSET(details->flags, CD_RBAC_ENABLED)) {
+ if (selinux_restore_tty() != 0)
+ sudo_warnx(U_("unable to restore tty label"));
+ }
+#endif
+
+ /* Free things up. */
+ free_exec_closure_nopty(&ec);
+ debug_return;
+}
+
+/*
+ * Wait for command status after receiving SIGCHLD.
+ * If the command exits, fill in cstat and stop the event loop.
+ * If the command stops, save the tty pgrp, suspend sudo, then restore
+ * the tty pgrp when sudo resumes.
+ */
+static void
+handle_sigchld_nopty(struct exec_closure_nopty *ec)
+{
+ pid_t pid;
+ int status;
+ char signame[SIG2STR_MAX];
+ debug_decl(handle_sigchld_nopty, SUDO_DEBUG_EXEC)
+
+ /* Read command status. */
+ do {
+ pid = waitpid(ec->cmnd_pid, &status, WUNTRACED|WNOHANG);
+ } while (pid == -1 && errno == EINTR);
+ switch (pid) {
+ case 0:
+ /* waitpid() will return 0 for SIGCONT, which we don't care about */
+ debug_return;
+ case -1:
+ sudo_warn(U_("%s: %s"), __func__, "waitpid");
+ debug_return;
+ }
+
+ if (WIFSTOPPED(status)) {
+ /*
+ * Save the controlling terminal's process group so we can restore it
+ * after we resume, if needed. Most well-behaved shells change the
+ * pgrp back to its original value before suspending so we must
+ * not try to restore in that case, lest we race with the command upon
+ * resume, potentially stopping sudo with SIGTTOU while the command
+ * continues to run.
+ */
+ struct sigaction sa, osa;
+ pid_t saved_pgrp = -1;
+ int fd, signo = WSTOPSIG(status);
+
+ if (sig2str(signo, signame) == -1)
+ snprintf(signame, sizeof(signame), "%d", signo);
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) stopped, SIG%s",
+ __func__, (int)ec->cmnd_pid, signame);
+
+ fd = open(_PATH_TTY, O_RDWR);
+ if (fd != -1) {
+ saved_pgrp = tcgetpgrp(fd);
+ if (saved_pgrp == -1) {
+ close(fd);
+ fd = -1;
+ }
+ }
+ if (saved_pgrp != -1) {
+ /*
+ * Command was stopped trying to access the controlling terminal.
+ * If the command has a different pgrp and we own the controlling
+ * terminal, give it to the command's pgrp and let it continue.
+ */
+ if (signo == SIGTTOU || signo == SIGTTIN) {
+ if (saved_pgrp == ec->ppgrp) {
+ pid_t cmnd_pgrp = getpgid(ec->cmnd_pid);
+ if (cmnd_pgrp != ec->ppgrp) {
+ if (tcsetpgrp_nobg(fd, cmnd_pgrp) == 0) {
+ if (killpg(cmnd_pgrp, SIGCONT) != 0) {
+ sudo_warn("kill(%d, SIGCONT)",
+ (int)cmnd_pgrp);
+ }
+ close(fd);
+ goto done;
+ }
+ }
+ }
+ }
+ }
+ if (signo == SIGTSTP) {
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = SIG_DFL;
+ if (sudo_sigaction(SIGTSTP, &sa, &osa) != 0) {
+ sudo_warn(U_("unable to set handler for signal %d"),
+ SIGTSTP);
+ }
+ }
+ if (kill(getpid(), signo) != 0)
+ sudo_warn("kill(%d, SIG%s)", (int)getpid(), signame);
+ if (signo == SIGTSTP) {
+ if (sudo_sigaction(SIGTSTP, &osa, NULL) != 0) {
+ sudo_warn(U_("unable to restore handler for signal %d"),
+ SIGTSTP);
+ }
+ }
+ if (saved_pgrp != -1) {
+ /*
+ * On resume, restore foreground process group, if different.
+ * Otherwise, we cannot resume some shells (pdksh).
+ *
+ * It is possible that we are no longer the foreground process so
+ * use tcsetpgrp_nobg() to prevent sudo from receiving SIGTTOU.
+ */
+ if (saved_pgrp != ec->ppgrp)
+ tcsetpgrp_nobg(fd, saved_pgrp);
+ close(fd);
+ }
+ } else {
+ /* Command has exited or been killed, we are done. */
+ if (WIFSIGNALED(status)) {
+ if (sig2str(WTERMSIG(status), signame) == -1)
+ snprintf(signame, sizeof(signame), "%d", WTERMSIG(status));
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) killed, SIG%s",
+ __func__, (int)ec->cmnd_pid, signame);
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_INFO, "%s: command (%d) exited: %d",
+ __func__, (int)ec->cmnd_pid, WEXITSTATUS(status));
+ }
+ /* Don't overwrite execve() failure with command exit status. */
+ if (ec->cstat->type == CMD_INVALID) {
+ ec->cstat->type = CMD_WSTATUS;
+ ec->cstat->val = status;
+ } else {
+ sudo_debug_printf(SUDO_DEBUG_WARN,
+ "%s: not overwriting command status %d,%d with %d,%d",
+ __func__, ec->cstat->type, ec->cstat->val, CMD_WSTATUS, status);
+ }
+ ec->cmnd_pid = -1;
+ }
+done:
+ debug_return;
+}