diff options
Diffstat (limited to 'tools/perf/util/run-command.c')
| -rw-r--r-- | tools/perf/util/run-command.c | 395 | 
1 files changed, 395 insertions, 0 deletions
diff --git a/tools/perf/util/run-command.c b/tools/perf/util/run-command.c new file mode 100644 index 000000000000..b2f5e854f40a --- /dev/null +++ b/tools/perf/util/run-command.c @@ -0,0 +1,395 @@ +#include "cache.h" +#include "run-command.h" +#include "exec_cmd.h" + +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]; + +	/* +	 * 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]; +	} + +#ifndef __MINGW32__ +	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, strerror(errno)); +		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->perf_cmd) { +			execv_perf_cmd(cmd->argv); +		} else { +			execvp(cmd->argv[0], (char *const*) cmd->argv); +		} +		exit(127); +	} +#else +	int s0 = -1, s1 = -1, s2 = -1;	/* backups of stdin, stdout, stderr */ +	const char **sargv = cmd->argv; +	char **env = environ; + +	if (cmd->no_stdin) { +		s0 = dup(0); +		dup_devnull(0); +	} else if (need_in) { +		s0 = dup(0); +		dup2(fdin[0], 0); +	} else if (cmd->in) { +		s0 = dup(0); +		dup2(cmd->in, 0); +	} + +	if (cmd->no_stderr) { +		s2 = dup(2); +		dup_devnull(2); +	} else if (need_err) { +		s2 = dup(2); +		dup2(fderr[1], 2); +	} + +	if (cmd->no_stdout) { +		s1 = dup(1); +		dup_devnull(1); +	} else if (cmd->stdout_to_stderr) { +		s1 = dup(1); +		dup2(2, 1); +	} else if (need_out) { +		s1 = dup(1); +		dup2(fdout[1], 1); +	} else if (cmd->out > 1) { +		s1 = dup(1); +		dup2(cmd->out, 1); +	} + +	if (cmd->dir) +		die("chdir in start_command() not implemented"); +	if (cmd->env) { +		env = copy_environ(); +		for (; *cmd->env; cmd->env++) +			env = env_setenv(env, *cmd->env); +	} + +	if (cmd->perf_cmd) { +		cmd->argv = prepare_perf_cmd(cmd->argv); +	} + +	cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env); + +	if (cmd->env) +		free_environ(env); +	if (cmd->perf_cmd) +		free(cmd->argv); + +	cmd->argv = sargv; +	if (s0 >= 0) +		dup2(s0, 0), close(s0); +	if (s1 >= 0) +		dup2(s1, 1), close(s1); +	if (s2 >= 0) +		dup2(s2, 2), close(s2); +#endif + +	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(pid_t pid) +{ +	for (;;) { +		int status, code; +		pid_t waiting = waitpid(pid, &status, 0); + +		if (waiting < 0) { +			if (errno == EINTR) +				continue; +			error("waitpid failed (%s)", strerror(errno)); +			return -ERR_RUN_COMMAND_WAITPID; +		} +		if (waiting != pid) +			return -ERR_RUN_COMMAND_WAITPID_WRONG_PID; +		if (WIFSIGNALED(status)) +			return -ERR_RUN_COMMAND_WAITPID_SIGNAL; + +		if (!WIFEXITED(status)) +			return -ERR_RUN_COMMAND_WAITPID_NOEXIT; +		code = WEXITSTATUS(status); +		switch (code) { +		case 127: +			return -ERR_RUN_COMMAND_EXEC; +		case 0: +			return 0; +		default: +			return -code; +		} +	} +} + +int finish_command(struct child_process *cmd) +{ +	return wait_or_whine(cmd->pid); +} + +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->perf_cmd = opt & RUN_PERF_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); +} + +int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env) +{ +	struct child_process cmd; +	prepare_run_command_v_opt(&cmd, argv, opt); +	cmd.dir = dir; +	cmd.env = env; +	return run_command(&cmd); +} + +#ifdef __MINGW32__ +static __stdcall unsigned run_thread(void *data) +{ +	struct async *async = data; +	return async->proc(async->fd_for_proc, async->data); +} +#endif + +int start_async(struct async *async) +{ +	int pipe_out[2]; + +	if (pipe(pipe_out) < 0) +		return error("cannot create pipe: %s", strerror(errno)); +	async->out = pipe_out[0]; + +#ifndef __MINGW32__ +	/* Flush stdio before fork() to avoid cloning buffers */ +	fflush(NULL); + +	async->pid = fork(); +	if (async->pid < 0) { +		error("fork (async) failed: %s", strerror(errno)); +		close_pair(pipe_out); +		return -1; +	} +	if (!async->pid) { +		close(pipe_out[0]); +		exit(!!async->proc(pipe_out[1], async->data)); +	} +	close(pipe_out[1]); +#else +	async->fd_for_proc = pipe_out[1]; +	async->tid = (HANDLE) _beginthreadex(NULL, 0, run_thread, async, 0, NULL); +	if (!async->tid) { +		error("cannot create thread: %s", strerror(errno)); +		close_pair(pipe_out); +		return -1; +	} +#endif +	return 0; +} + +int finish_async(struct async *async) +{ +#ifndef __MINGW32__ +	int ret = 0; + +	if (wait_or_whine(async->pid)) +		ret = error("waitpid (async) failed"); +#else +	DWORD ret = 0; +	if (WaitForSingleObject(async->tid, INFINITE) != WAIT_OBJECT_0) +		ret = error("waiting for thread failed: %lu", GetLastError()); +	else if (!GetExitCodeThread(async->tid, &ret)) +		ret = error("cannot get thread exit code: %lu", GetLastError()); +	CloseHandle(async->tid); +#endif +	return ret; +} + +int run_hook(const char *index_file, const char *name, ...) +{ +	struct child_process hook; +	const char **argv = NULL, *env[2]; +	char index[PATH_MAX]; +	va_list args; +	int ret; +	size_t i = 0, alloc = 0; + +	if (access(perf_path("hooks/%s", name), X_OK) < 0) +		return 0; + +	va_start(args, name); +	ALLOC_GROW(argv, i + 1, alloc); +	argv[i++] = perf_path("hooks/%s", name); +	while (argv[i-1]) { +		ALLOC_GROW(argv, i + 1, alloc); +		argv[i++] = va_arg(args, const char *); +	} +	va_end(args); + +	memset(&hook, 0, sizeof(hook)); +	hook.argv = argv; +	hook.no_stdin = 1; +	hook.stdout_to_stderr = 1; +	if (index_file) { +		snprintf(index, sizeof(index), "PERF_INDEX_FILE=%s", index_file); +		env[0] = index; +		env[1] = NULL; +		hook.env = env; +	} + +	ret = start_command(&hook); +	free(argv); +	if (ret) { +		warning("Could not spawn %s", argv[0]); +		return ret; +	} +	ret = finish_command(&hook); +	if (ret == -ERR_RUN_COMMAND_WAITPID_SIGNAL) +		warning("%s exited due to uncaught signal", argv[0]); + +	return ret; +}  | 
