summaryrefslogtreecommitdiff
path: root/exec.c
blob: e38d3af6d8fa3371e59aa367bcaa69a6e75d03ed (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/* SPDX-License-Identifier: GPL-2.0 */

/** \file exec.c Helper functions for spawning new processes. */

#include "para.h"
#include "error.h"
#include "fd.h"
#include "string.h"

static void xdup2(int oldfd, int newfd)
{
	while (dup2(oldfd, newfd) == -1) {
		if (errno == EINTR)
			continue;
		PARA_EMERG_LOG("dup2 failed: %s\n", strerror(errno));
		exit(EXIT_FAILURE);
	}
}

/* Spawn a new process, redirect fds, have the child call execvp(2). */
static int fork_and_exec(pid_t *pid, const char *file,
		char *const *const args, int *fds)
{
	int ret, in[2] = {-1, -1}, out[2] = {-1, -1}, err[2] = {-1, -1},
		null = -1, control[2];
	int exec_errno;

	*pid = 0;
	ret = -E_DUP_PIPE;
	if (fds[0] > 0 && pipe(in) < 0)
		goto err_out;
	if (fds[1] > 0 && pipe(out) < 0)
		goto err_out;
	if (fds[2] > 0 && pipe(err) < 0)
		goto err_out;
	if (pipe2(control, O_CLOEXEC) < 0)
		goto err_out;
	if (!fds[0] || !fds[1] || !fds[2]) {
		ret = para_open("/dev/null", O_RDWR, 42);
		if (ret < 0)
			goto err_out;
		null = ret;
	}
	ret = fork();
	if (ret < 0) {
		ret = -ERRNO_TO_PARA_ERROR(errno);
		goto err_out;
	}
	*pid = ret;
	if (!(*pid)) { /* child */
		close(control[0]);
		if (fds[0] == 0)
			xdup2(null, STDIN_FILENO);
		else if (fds[0] > 0) {
			close(in[1]);
			if (in[0] != STDIN_FILENO)
				xdup2(in[0], STDIN_FILENO);
		}
		if (fds[1] == 0)
			xdup2(null, STDOUT_FILENO);
		else if (fds[1] > 0) {
			close(out[0]);
			if (out[1] != STDOUT_FILENO)
				xdup2(out[1], STDOUT_FILENO);
		}
		if (fds[2] == 0)
			xdup2(null, STDERR_FILENO);
		else if (fds[2] > 0) {
			close(err[0]);
			if (err[1] != STDERR_FILENO)
				xdup2(err[1], STDERR_FILENO);
		}
		if (null >= 0)
			close(null);
		/*
		 * If stdin is redirected, the foreground process group can no
		 * longer be signalled with CTRL+C. Set the process group ID of
		 * the child to its PID so that the parent can send a signal to
		 * the process group to kill the child and all its subprocesses.
		 */
		if (fds[0] >= 0)
			setpgid(0, 0);
		execvp(file, args); /* closes control[1] */
		/* exec failed, notify parent */
		xwrite(control[1], &errno, sizeof(int));
		_exit(EXIT_FAILURE);
	}
	close(control[1]);
	do
		ret = read(control[0], &exec_errno, sizeof(int));
	while (ret < 0 && errno == EINTR);
	close(control[0]);
	if (ret > 0) { /* exec failed */
		assert(ret == sizeof(int));
		ret = -ERRNO_TO_PARA_ERROR(exec_errno);
		goto err_out;
	}
	if (fds[0] > 0) {
		close(in[0]);
		*fds = in[1];
	}
	if (fds[1] > 0) {
		close(out[1]);
		*(fds + 1) = out[0];
	}
	if (fds[2] > 0) {
		close(err[1]);
		*(fds + 2) = err[0];
	}
	if (null >= 0)
		close(null);
	return 1;
err_out:
	if (err[0] >= 0)
		close(err[0]);
	if (err[1] >= 0)
		close(err[1]);
	if (out[0] >= 0)
		close(out[0]);
	if (out[1] >= 0)
		close(out[1]);
	if (in[0] >= 0)
		close(in[0]);
	if (in[1] >= 0)
		close(in[1]);
	if (null >= 0)
		close(null);
	return ret;
}

/**
 * Execute a file as a background process, honoring $PATH.
 *
 * \param pid Contains the PID of the child process on return.
 * \param cmdline Path to the file to execute, followed by arguments.
 * \param fds A pointer to a value-result array.
 *
 * Create a new process and return without waiting for the child
 * to terminate. The child replaces itself with the process image that
 * corresponds to the first word of the given command line.
 *
 * The fd pointer must point to an array of three integers. Initially, the
 * integers specify how to deal with fd 0, 1, 2 in the child:
 *
 *	< 0: Leave fd alone.
 *	== 0: Dup fd to /dev/null.
 *	> 0: Create a pipe and dup to one end of that pipe.
 *
 * In the third case, the function sets the corresponding integer of the
 * fd array to one end of the newly created pipe.
 *
 * \return Standard. Regardless of the return value, if pid points to a
 * non-zero value, a child process was created. It is the responsibility of
 * the caller to reap the child.
 *
 * \sa \ref create_argv(), null(4), pipe(2), dup2(2), fork(2), exec(3).
 */
int xexec(pid_t *pid, const char *cmdline, int *fds)
{
	int ret;
	char **argv;

	ret = create_argv(cmdline, " \t", &argv);
	if (ret < 0)
		return ret;
	PARA_INFO_LOG("cmdline: %s\n", cmdline);
	ret = fork_and_exec(pid, argv[0], argv, fds);
	free_argv(argv);
	return ret;
}