253 lines · 4.9 KB · C
RawDownload
// 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);}