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