mirror of
https://github.com/torvalds/linux.git
synced 2024-11-01 13:03:25 +01:00
705c09bb3c
Add non-blocking function to check if a 'struct child_process' has completed. If the process has completed the exit code is stored in the 'struct child_process' so that finish_command() returns it. Signed-off-by: Ian Rogers <irogers@google.com> Cc: Adrian Hunter <adrian.hunter@intel.com> Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com> Cc: Ingo Molnar <mingo@redhat.com> Cc: James Clark <james.clark@arm.com> Cc: Jiri Olsa <jolsa@kernel.org> Cc: Kan Liang <kan.liang@linux.intel.com> Cc: Mark Rutland <mark.rutland@arm.com> Cc: Namhyung Kim <namhyung@kernel.org> Cc: Peter Zijlstra <peterz@infradead.org> Link: https://lore.kernel.org/r/20240405070931.1231245-1-irogers@google.com Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
253 lines
4.9 KiB
C
253 lines
4.9 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <linux/string.h>
|
|
#include <errno.h>
|
|
#include <sys/wait.h>
|
|
#include "subcmd-util.h"
|
|
#include "run-command.h"
|
|
#include "exec-cmd.h"
|
|
|
|
#define STRERR_BUFSIZE 128
|
|
|
|
static inline void close_pair(int fd[2])
|
|
{
|
|
close(fd[0]);
|
|
close(fd[1]);
|
|
}
|
|
|
|
static inline void dup_devnull(int to)
|
|
{
|
|
int fd = open("/dev/null", O_RDWR);
|
|
dup2(fd, to);
|
|
close(fd);
|
|
}
|
|
|
|
int start_command(struct child_process *cmd)
|
|
{
|
|
int need_in, need_out, need_err;
|
|
int fdin[2], fdout[2], fderr[2];
|
|
char sbuf[STRERR_BUFSIZE];
|
|
|
|
/*
|
|
* In case of errors we must keep the promise to close FDs
|
|
* that have been passed in via ->in and ->out.
|
|
*/
|
|
|
|
need_in = !cmd->no_stdin && cmd->in < 0;
|
|
if (need_in) {
|
|
if (pipe(fdin) < 0) {
|
|
if (cmd->out > 0)
|
|
close(cmd->out);
|
|
return -ERR_RUN_COMMAND_PIPE;
|
|
}
|
|
cmd->in = fdin[1];
|
|
}
|
|
|
|
need_out = !cmd->no_stdout
|
|
&& !cmd->stdout_to_stderr
|
|
&& cmd->out < 0;
|
|
if (need_out) {
|
|
if (pipe(fdout) < 0) {
|
|
if (need_in)
|
|
close_pair(fdin);
|
|
else if (cmd->in)
|
|
close(cmd->in);
|
|
return -ERR_RUN_COMMAND_PIPE;
|
|
}
|
|
cmd->out = fdout[0];
|
|
}
|
|
|
|
need_err = !cmd->no_stderr && cmd->err < 0;
|
|
if (need_err) {
|
|
if (pipe(fderr) < 0) {
|
|
if (need_in)
|
|
close_pair(fdin);
|
|
else if (cmd->in)
|
|
close(cmd->in);
|
|
if (need_out)
|
|
close_pair(fdout);
|
|
else if (cmd->out)
|
|
close(cmd->out);
|
|
return -ERR_RUN_COMMAND_PIPE;
|
|
}
|
|
cmd->err = fderr[0];
|
|
}
|
|
|
|
fflush(NULL);
|
|
cmd->pid = fork();
|
|
if (!cmd->pid) {
|
|
if (cmd->no_stdin)
|
|
dup_devnull(0);
|
|
else if (need_in) {
|
|
dup2(fdin[0], 0);
|
|
close_pair(fdin);
|
|
} else if (cmd->in) {
|
|
dup2(cmd->in, 0);
|
|
close(cmd->in);
|
|
}
|
|
|
|
if (cmd->no_stderr)
|
|
dup_devnull(2);
|
|
else if (need_err) {
|
|
dup2(fderr[1], 2);
|
|
close_pair(fderr);
|
|
}
|
|
|
|
if (cmd->no_stdout)
|
|
dup_devnull(1);
|
|
else if (cmd->stdout_to_stderr)
|
|
dup2(2, 1);
|
|
else if (need_out) {
|
|
dup2(fdout[1], 1);
|
|
close_pair(fdout);
|
|
} else if (cmd->out > 1) {
|
|
dup2(cmd->out, 1);
|
|
close(cmd->out);
|
|
}
|
|
|
|
if (cmd->dir && chdir(cmd->dir))
|
|
die("exec %s: cd to %s failed (%s)", cmd->argv[0],
|
|
cmd->dir, str_error_r(errno, sbuf, sizeof(sbuf)));
|
|
if (cmd->env) {
|
|
for (; *cmd->env; cmd->env++) {
|
|
if (strchr(*cmd->env, '='))
|
|
putenv((char*)*cmd->env);
|
|
else
|
|
unsetenv(*cmd->env);
|
|
}
|
|
}
|
|
if (cmd->preexec_cb)
|
|
cmd->preexec_cb();
|
|
if (cmd->no_exec_cmd)
|
|
exit(cmd->no_exec_cmd(cmd));
|
|
if (cmd->exec_cmd) {
|
|
execv_cmd(cmd->argv);
|
|
} else {
|
|
execvp(cmd->argv[0], (char *const*) cmd->argv);
|
|
}
|
|
exit(127);
|
|
}
|
|
|
|
if (cmd->pid < 0) {
|
|
int err = errno;
|
|
if (need_in)
|
|
close_pair(fdin);
|
|
else if (cmd->in)
|
|
close(cmd->in);
|
|
if (need_out)
|
|
close_pair(fdout);
|
|
else if (cmd->out)
|
|
close(cmd->out);
|
|
if (need_err)
|
|
close_pair(fderr);
|
|
return err == ENOENT ?
|
|
-ERR_RUN_COMMAND_EXEC :
|
|
-ERR_RUN_COMMAND_FORK;
|
|
}
|
|
|
|
if (need_in)
|
|
close(fdin[0]);
|
|
else if (cmd->in)
|
|
close(cmd->in);
|
|
|
|
if (need_out)
|
|
close(fdout[1]);
|
|
else if (cmd->out)
|
|
close(cmd->out);
|
|
|
|
if (need_err)
|
|
close(fderr[1]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wait_or_whine(struct child_process *cmd, bool block)
|
|
{
|
|
bool finished = cmd->finished;
|
|
int result = cmd->finish_result;
|
|
|
|
while (!finished) {
|
|
int status, code;
|
|
pid_t waiting = waitpid(cmd->pid, &status, block ? 0 : WNOHANG);
|
|
|
|
if (!block && waiting == 0)
|
|
break;
|
|
|
|
if (waiting < 0 && errno == EINTR)
|
|
continue;
|
|
|
|
finished = true;
|
|
if (waiting < 0) {
|
|
char sbuf[STRERR_BUFSIZE];
|
|
|
|
fprintf(stderr, " Error: waitpid failed (%s)",
|
|
str_error_r(errno, sbuf, sizeof(sbuf)));
|
|
result = -ERR_RUN_COMMAND_WAITPID;
|
|
} else if (waiting != cmd->pid) {
|
|
result = -ERR_RUN_COMMAND_WAITPID_WRONG_PID;
|
|
} else if (WIFSIGNALED(status)) {
|
|
result = -ERR_RUN_COMMAND_WAITPID_SIGNAL;
|
|
} else if (!WIFEXITED(status)) {
|
|
result = -ERR_RUN_COMMAND_WAITPID_NOEXIT;
|
|
} else {
|
|
code = WEXITSTATUS(status);
|
|
switch (code) {
|
|
case 127:
|
|
result = -ERR_RUN_COMMAND_EXEC;
|
|
break;
|
|
case 0:
|
|
result = 0;
|
|
break;
|
|
default:
|
|
result = -code;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (finished) {
|
|
cmd->finished = 1;
|
|
cmd->finish_result = result;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int check_if_command_finished(struct child_process *cmd)
|
|
{
|
|
wait_or_whine(cmd, /*block=*/false);
|
|
return cmd->finished;
|
|
}
|
|
|
|
int finish_command(struct child_process *cmd)
|
|
{
|
|
return wait_or_whine(cmd, /*block=*/true);
|
|
}
|
|
|
|
int run_command(struct child_process *cmd)
|
|
{
|
|
int code = start_command(cmd);
|
|
if (code)
|
|
return code;
|
|
return finish_command(cmd);
|
|
}
|
|
|
|
static void prepare_run_command_v_opt(struct child_process *cmd,
|
|
const char **argv,
|
|
int opt)
|
|
{
|
|
memset(cmd, 0, sizeof(*cmd));
|
|
cmd->argv = argv;
|
|
cmd->no_stdin = opt & RUN_COMMAND_NO_STDIN ? 1 : 0;
|
|
cmd->exec_cmd = opt & RUN_EXEC_CMD ? 1 : 0;
|
|
cmd->stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0;
|
|
}
|
|
|
|
int run_command_v_opt(const char **argv, int opt)
|
|
{
|
|
struct child_process cmd;
|
|
prepare_run_command_v_opt(&cmd, argv, opt);
|
|
return run_command(&cmd);
|
|
}
|