1 /* ----------------------------------------------------------------------------
2 (c) The University of Glasgow 2004-2020
3
4 Support for System.Process
5 ------------------------------------------------------------------------- */
6
7 #include "runProcess.h"
8 #include "common.h"
9
10 #include <unistd.h>
11 #include <errno.h>
12 #include <sys/wait.h>
13
14 #ifdef HAVE_FCNTL_H
15 #include <fcntl.h>
16 #endif
17
18 #if defined(HAVE_SIGNAL_H)
19 #include <signal.h>
20 #endif
21
22 int
get_max_fd()23 get_max_fd()
24 {
25 static int cache = 0;
26 if (cache == 0) {
27 #if HAVE_SYSCONF
28 cache = sysconf(_SC_OPEN_MAX);
29 if (cache == -1) {
30 cache = 256;
31 }
32 #else
33 cache = 256;
34 #endif
35 }
36 return cache;
37 }
38
39 // If a process was terminated by a signal, the exit status we return
40 // via the System.Process API is (-signum). This encoding avoids collision with
41 // normal process termination status codes. See also #7229.
42 #define TERMSIG_EXITSTATUS(s) (-(WTERMSIG(s)))
43
44 /*
45 * Spawn a new process. We first try posix_spawn but since this isn't supported
46 * on all platforms we fall back to fork/exec in some cases on some platforms.
47 */
48 static ProcHandle
do_spawn(char * const args[],char * workingDirectory,char ** environment,struct std_handle * stdInHdl,struct std_handle * stdOutHdl,struct std_handle * stdErrHdl,gid_t * childGroup,uid_t * childUser,int flags,char ** failed_doing)49 do_spawn (char *const args[],
50 char *workingDirectory, char **environment,
51 struct std_handle *stdInHdl,
52 struct std_handle *stdOutHdl,
53 struct std_handle *stdErrHdl,
54 gid_t *childGroup, uid_t *childUser,
55 int flags,
56 char **failed_doing)
57 {
58 ProcHandle r;
59 r = do_spawn_posix(args,
60 workingDirectory, environment,
61 stdInHdl, stdOutHdl, stdErrHdl,
62 childGroup, childUser,
63 flags,
64 failed_doing);
65 if (r == -2) {
66 // configuration not supported by posix_spawn, fall back to fork/exec
67 } else {
68 return r;
69 }
70
71 r = do_spawn_fork(args,
72 workingDirectory, environment,
73 stdInHdl, stdOutHdl, stdErrHdl,
74 childGroup, childUser,
75 flags,
76 failed_doing);
77 return r;
78 }
79
80 enum pipe_direction {
81 CHILD_READS, CHILD_WRITES
82 };
83
84 /* Initialize a std_handle_behavior from a "pseudo-fd":
85 * - fd == -1 means create a pipe
86 * - fd == -2 means close the handle
87 * - otherwise use fd
88 * Returns 0 on success, -1 otherwise.
89 */
90 static int
init_std_handle(int fd,enum pipe_direction direction,struct std_handle * hdl,char ** failed_doing)91 init_std_handle(int fd, enum pipe_direction direction,
92 /* out */ struct std_handle *hdl,
93 char **failed_doing)
94 {
95 switch (fd) {
96 case -1: {
97 int pipe_fds[2];
98 int r = pipe(pipe_fds);
99 if (r == -1) {
100 *failed_doing = "pipe";
101 return -1;
102 }
103
104 int child_end = direction == CHILD_READS ? 0 : 1;
105 int parent_end = direction == CHILD_READS ? 1 : 0;
106 *hdl = (struct std_handle) {
107 .behavior = STD_HANDLE_USE_PIPE,
108 .use_pipe = { .child_end = pipe_fds[child_end], .parent_end = pipe_fds[parent_end] }
109 };
110 break;
111 }
112 case -2:
113 *hdl = (struct std_handle) {
114 .behavior = STD_HANDLE_CLOSE
115 };
116 break;
117 default:
118 *hdl = (struct std_handle) {
119 .behavior = STD_HANDLE_USE_FD,
120 .use_fd = fd
121 };
122 break;
123 }
124 return 0;
125 }
126
127 ProcHandle
runInteractiveProcess(char * const args[],char * workingDirectory,char ** environment,int fdStdIn,int fdStdOut,int fdStdErr,int * pfdStdInput,int * pfdStdOutput,int * pfdStdError,gid_t * childGroup,uid_t * childUser,int flags,char ** failed_doing)128 runInteractiveProcess (char *const args[],
129 char *workingDirectory, char **environment,
130 // handles to use for the standard handles. -1 indicates
131 // create pipe, -2 indicates close.
132 int fdStdIn, int fdStdOut, int fdStdErr,
133 // output arguments to return created pipe handle to caller
134 int *pfdStdInput, int *pfdStdOutput, int *pfdStdError,
135 gid_t *childGroup, uid_t *childUser,
136 int flags,
137 char **failed_doing)
138 {
139 struct std_handle stdInHdl, stdOutHdl, stdErrHdl;
140 ProcHandle r;
141
142 // A bit of paranoia to ensure that we catch if we fail to set this on
143 // failure.
144 *failed_doing = NULL;
145
146 // Ordering matters here, see below Note [Ordering of handle closing].
147 if (init_std_handle(fdStdIn, CHILD_READS, &stdInHdl, failed_doing) != 0) {
148 goto fail;
149 }
150
151 if (init_std_handle(fdStdOut, CHILD_WRITES, &stdOutHdl, failed_doing) != 0) {
152 goto fail;
153 }
154
155 if (init_std_handle(fdStdErr, CHILD_WRITES, &stdErrHdl, failed_doing) != 0) {
156 goto fail;
157 }
158
159 r = do_spawn(args,
160 workingDirectory, environment,
161 &stdInHdl, &stdOutHdl, &stdErrHdl,
162 childGroup, childUser,
163 flags,
164 failed_doing);
165 if (r == -1) {
166 goto fail;
167 }
168
169 // Close the remote ends of any pipes we created.
170 #define FINALISE_STD_HANDLE(hdl, pfd) \
171 if (hdl.behavior == STD_HANDLE_USE_PIPE) { \
172 close(hdl.use_pipe.child_end); \
173 fcntl(hdl.use_pipe.parent_end, F_SETFD, FD_CLOEXEC); \
174 *pfd = hdl.use_pipe.parent_end; \
175 }
176
177 FINALISE_STD_HANDLE(stdInHdl, pfdStdInput);
178 FINALISE_STD_HANDLE(stdOutHdl, pfdStdOutput);
179 FINALISE_STD_HANDLE(stdErrHdl, pfdStdError);
180 #undef FINALISE_STD_HANDLE
181
182 return r;
183
184 fail:
185 #define CLOSE_PIPE(hdl) \
186 if (hdl.behavior == STD_HANDLE_USE_PIPE) { \
187 close(hdl.use_pipe.child_end); \
188 close(hdl.use_pipe.parent_end); \
189 }
190
191 CLOSE_PIPE(stdInHdl);
192 CLOSE_PIPE(stdOutHdl);
193 CLOSE_PIPE(stdErrHdl);
194 #undef CLOSE_PIPE
195
196 return -1;
197 }
198
199 int
terminateProcess(ProcHandle handle)200 terminateProcess (ProcHandle handle)
201 {
202 return (kill(handle, SIGTERM) == 0);
203 }
204
205 int
getProcessExitCode(ProcHandle handle,int * pExitCode)206 getProcessExitCode (ProcHandle handle, int *pExitCode)
207 {
208 int wstat, res;
209
210 *pExitCode = 0;
211
212 if ((res = waitpid(handle, &wstat, WNOHANG)) > 0) {
213 if (WIFEXITED(wstat)) {
214 *pExitCode = WEXITSTATUS(wstat);
215 return 1;
216 } else {
217 if (WIFSIGNALED(wstat))
218 {
219 *pExitCode = TERMSIG_EXITSTATUS(wstat);
220 return 1;
221 }
222 else
223 {
224 /* This should never happen */
225 }
226 }
227 }
228
229 if (res == 0) return 0;
230
231 if (errno == ECHILD)
232 {
233 *pExitCode = 0;
234 return 1;
235 }
236
237 return -1;
238 }
239
240 int
waitForProcess(ProcHandle handle,int * pret)241 waitForProcess (ProcHandle handle, int *pret)
242 {
243 int wstat;
244
245 if (waitpid(handle, &wstat, 0) < 0)
246 {
247 return -1;
248 }
249
250 if (WIFEXITED(wstat)) {
251 *pret = WEXITSTATUS(wstat);
252 return 0;
253 }
254 else {
255 if (WIFSIGNALED(wstat))
256 {
257 *pret = TERMSIG_EXITSTATUS(wstat);
258 return 0;
259 }
260 else
261 {
262 /* This should never happen */
263 }
264 }
265
266 return -1;
267 }
268