/* command.c -- utility functions for running commands
*
* Copyright (c) 1994-2012 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, 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.
*/
#include <config.h>
#include <sys/types.h>
#include <syslog.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <errno.h>
#include <sys/wait.h>
#include <unistd.h>
#include "xmalloc.h"
#include "command.h"
#include "signals.h"
#include "strarray.h"
/* generated headers are not necessarily in current directory */
#include "imap/imap_err.h"
static int wait_for_child(const char *argv0, pid_t pid);
EXPORTED int run_command(const char *argv0, ...)
{
va_list va;
const char *p;
strarray_t argv = STRARRAY_INITIALIZER;
pid_t pid;
int r = 0;
strarray_append(&argv, argv0);
va_start(va, argv0);
while ((p = va_arg(va, const char *)))
strarray_append(&argv, p);
va_end(va);
pid = fork();
if (pid < 0) {
syslog(LOG_ERR, "Failed to fork: %m");
r = IMAP_SYS_ERROR;
goto out;
}
if (!pid) {
/* in child */
r = execv(argv0, argv.data);
syslog(LOG_ERR, "Failed to execute %s: %m", argv0);
exit(1);
}
else {
/* in parent */
r = wait_for_child(argv0, pid);
}
out:
strarray_fini(&argv);
return r;
}
#define PIPE_READ 0
#define PIPE_WRITE 1
EXPORTED int command_popen(struct command **cmdp, const char *mode, const char *argv0, ...)
{
va_list va;
const char *p;
strarray_t argv = STRARRAY_INITIALIZER;
pid_t pid;
int r = 0;
struct command *cmd;
int do_stdin = (strchr(mode, 'w') != NULL);
int do_stdout = (strchr(mode, 'r') != NULL);
int stdin_pipe[2] = { -1, -1 };
int stdout_pipe[2] = { -1, -1 };
strarray_append(&argv, argv0);
va_start(va, argv0);
while ((p = va_arg(va, const char *)))
strarray_append(&argv, p);
va_end(va);
if (do_stdin) {
r = pipe(stdin_pipe);
if (r) {
syslog(LOG_ERR, "Failed to pipe(): %m");
r = IMAP_SYS_ERROR;
goto out;
}
}
if (do_stdout) {
r = pipe(stdout_pipe);
if (r) {
syslog(LOG_ERR, "Failed to pipe(): %m");
r = IMAP_SYS_ERROR;
goto out;
}
}
pid = fork();
if (pid < 0) {
syslog(LOG_ERR, "Failed to fork: %m");
r = IMAP_SYS_ERROR;
goto out;
}
if (!pid) {
/* in child */
if (do_stdin) {
close(stdin_pipe[PIPE_WRITE]);
dup2(stdin_pipe[PIPE_READ], STDIN_FILENO);
close(stdin_pipe[PIPE_READ]);
}
if (do_stdout) {
close(stdout_pipe[PIPE_READ]);
dup2(stdout_pipe[PIPE_WRITE], STDOUT_FILENO);
close(stdout_pipe[PIPE_WRITE]);
}
r = execv(argv0, argv.data);
syslog(LOG_ERR, "Failed to execute %s: %m", argv0);
exit(1);
}
/* in parent */
cmd = xzmalloc(sizeof(struct command));
cmd->argv0 = xstrdup(argv0);
cmd->pid = pid;
if (do_stdin)
cmd->stdin_prot = prot_new(stdin_pipe[PIPE_WRITE], /*write*/1);
if (do_stdout)
cmd->stdout_prot = prot_new(stdout_pipe[PIPE_READ], /*write*/0);
*cmdp = cmd;
out:
if (stdin_pipe[PIPE_READ] >= 0) close(stdin_pipe[PIPE_READ]);
if (stdout_pipe[PIPE_WRITE] >= 0) close(stdout_pipe[PIPE_WRITE]);
if (r) {
if (stdin_pipe[PIPE_WRITE] >= 0) close(stdin_pipe[PIPE_WRITE]);
if (stdout_pipe[PIPE_READ] >= 0) close(stdout_pipe[PIPE_READ]);
}
strarray_fini(&argv);
return r;
}
EXPORTED int command_pclose(struct command **cmdp)
{
struct command *cmd = (cmdp ? *cmdp : NULL);
int r;
if (!cmd) return 0;
if (cmd->stdin_prot) {
prot_flush(cmd->stdin_prot);
close(cmd->stdin_prot->fd);
prot_free(cmd->stdin_prot);
}
if (cmd->stdout_prot) {
close(cmd->stdout_prot->fd);
prot_free(cmd->stdout_prot);
}
r = wait_for_child(cmd->argv0, cmd->pid);
free(cmd->argv0);
free(cmd);
*cmdp = NULL;
return r;
}
EXPORTED int command_done_stdin(struct command *cmd)
{
int r = 0;
if (cmd->stdin_prot) {
r = prot_flush(cmd->stdin_prot);
close(cmd->stdin_prot->fd);
prot_free(cmd->stdin_prot);
cmd->stdin_prot = NULL;
}
return r;
}
static int wait_for_child(const char *argv0, pid_t pid)
{
int r = 0;
if (pid) {
for (;;) {
int status;
pid_t pr = waitpid(pid, &status, 0);
if (pr < 0) {
if (errno == EINTR) {
signals_poll();
continue;
}
else if (errno == ECHILD || errno == ESRCH) {
r = 0;
break; /* someone else reaped the child */
}
else {
syslog(LOG_ERR, "waitpid() failed: %m");
r = IMAP_SYS_ERROR;
break;
}
}
if (WIFEXITED(status)) {
r = 0;
if (WEXITSTATUS(status)) {
syslog(LOG_ERR, "Program %s (pid %d) exited with status %d",
argv0, (int)pid, WEXITSTATUS(status));
r = IMAP_SYS_ERROR;
}
break;
}
if (WIFSIGNALED(status)) {
syslog(LOG_ERR, "Program %s (pid %d) died with signal %d",
argv0, (int)pid, WTERMSIG(status));
r = IMAP_SYS_ERROR;
break;
}
}
}
return r;
}