[project @ 2005-01-31 13:46:24 by simonmar]
[ghc-base.git] / cbits / runProcess.c
1 /* ----------------------------------------------------------------------------\r
2    (c) The University of Glasgow 2004\r
3    \r
4    Support for System.Process\r
5    ------------------------------------------------------------------------- */\r
6 \r
7 #include "HsBase.h"\r
8 \r
9 #if defined(mingw32_HOST_OS)\r
10 #include <windows.h>\r
11 #include <stdlib.h>\r
12 #endif\r
13 \r
14 #ifdef HAVE_VFORK_H\r
15 #include <vfork.h>\r
16 #endif\r
17 \r
18 #ifdef HAVE_VFORK\r
19 #define fork vfork\r
20 #endif\r
21 \r
22 #ifdef HAVE_SIGNAL_H\r
23 #include <signal.h>\r
24 #endif\r
25 \r
26 #if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)\r
27 /* ----------------------------------------------------------------------------\r
28    UNIX versions\r
29    ------------------------------------------------------------------------- */\r
30 \r
31 ProcHandle\r
32 runProcess (char *const args[], char *workingDirectory, char **environment, \r
33             int fdStdInput, int fdStdOutput, int fdStdError)\r
34 {\r
35     int pid;\r
36     struct sigaction dfl;\r
37 \r
38     switch(pid = fork())\r
39     {\r
40     case -1:\r
41         return -1;\r
42         \r
43     case 0:\r
44     {\r
45         pPrPr_disableITimers();\r
46         \r
47         if (workingDirectory) {\r
48             chdir (workingDirectory);\r
49         }\r
50         \r
51         /*\r
52          * Restore SIGINT and SIGQUIT to default actions\r
53          *\r
54          * Glyn Clemments writes:\r
55          * For your purposes, runProcess + waitForProcess is probably\r
56          * the way to go. Except that runProcess appears to be missing\r
57          * the usual signal handling. system() ignores SIGINT and\r
58          * SIGQUIT in the parent, and resets them to their defaults in\r
59          * the child; it also blocks SIGCHLD in the parent. runProcess\r
60          * may need to do something similar; it should probably at\r
61          * least reset SIGINT and SIGQUIT in the child, in case they\r
62          * are ignored in the parent. The parent can set up its own\r
63          * signal handling, but the only place it can control the\r
64          * child's signal handling is between the fork() and the\r
65          * exec(), so if runProcess doesn't do it, it won't get done.\r
66          */\r
67         dfl.sa_handler = SIG_DFL;\r
68         (void)sigemptyset(&dfl.sa_mask);\r
69         dfl.sa_flags = 0;\r
70         (void)sigaction(SIGINT, &dfl, NULL);\r
71         (void)sigaction(SIGQUIT,  &dfl, NULL);\r
72 \r
73         dup2 (fdStdInput,  STDIN_FILENO);\r
74         dup2 (fdStdOutput, STDOUT_FILENO);\r
75         dup2 (fdStdError,  STDERR_FILENO);\r
76         \r
77         if (environment) {\r
78             execvpe(args[0], args, environment);\r
79         } else {\r
80             execvp(args[0], args);\r
81         }\r
82     }\r
83     _exit(127);\r
84     }\r
85     \r
86     return pid;\r
87 }\r
88 \r
89 ProcHandle\r
90 runInteractiveProcess (char *const args[], \r
91                        char *workingDirectory, char **environment,\r
92                        int *pfdStdInput, int *pfdStdOutput, int *pfdStdError)\r
93 {\r
94     int pid;\r
95     int fdStdInput[2], fdStdOutput[2], fdStdError[2];\r
96 \r
97     pipe(fdStdInput);\r
98     pipe(fdStdOutput);\r
99     pipe(fdStdError);\r
100 \r
101     switch(pid = fork())\r
102     {\r
103     case -1:\r
104         close(fdStdInput[0]);\r
105         close(fdStdInput[1]);\r
106         close(fdStdOutput[0]);\r
107         close(fdStdOutput[1]);\r
108         close(fdStdError[0]);\r
109         close(fdStdError[1]);\r
110         return -1;\r
111         \r
112     case 0:\r
113     {\r
114         pPrPr_disableITimers();\r
115         \r
116         if (workingDirectory) {\r
117             chdir (workingDirectory);\r
118         }\r
119         \r
120         dup2 (fdStdInput[0], STDIN_FILENO);\r
121         dup2 (fdStdOutput[1], STDOUT_FILENO);\r
122         dup2 (fdStdError[1], STDERR_FILENO);\r
123         \r
124         close(fdStdInput[0]);\r
125         close(fdStdInput[1]);\r
126         close(fdStdOutput[0]);\r
127         close(fdStdOutput[1]);\r
128         close(fdStdError[0]);\r
129         close(fdStdError[1]);\r
130         \r
131         /* the child */\r
132         if (environment) {\r
133             execvpe(args[0], args, environment);\r
134         } else {\r
135             execvp(args[0], args);\r
136         }\r
137     }\r
138     _exit(127);\r
139     \r
140     default:\r
141         close(fdStdInput[0]);\r
142         close(fdStdOutput[1]);\r
143         close(fdStdError[1]);\r
144         \r
145         *pfdStdInput  = fdStdInput[1];\r
146         *pfdStdOutput = fdStdOutput[0];\r
147         *pfdStdError  = fdStdError[0];\r
148         break;\r
149     }\r
150     \r
151     return pid;\r
152 }\r
153 \r
154 int\r
155 terminateProcess (ProcHandle handle)\r
156 {\r
157     return (kill(handle, SIGTERM) == 0);\r
158 }\r
159 \r
160 int\r
161 getProcessExitCode (ProcHandle handle, int *pExitCode)\r
162 {\r
163     int wstat;\r
164     \r
165     *pExitCode = 0;\r
166     \r
167     if (waitpid(handle, &wstat, WNOHANG) > 0)\r
168     {\r
169         if (WIFEXITED(wstat))\r
170         {\r
171             *pExitCode = WEXITSTATUS(wstat);\r
172             return 1;\r
173         }\r
174         else\r
175             if (WIFSIGNALED(wstat))\r
176             {\r
177                 errno = EINTR;\r
178                 return -1;\r
179             }\r
180             else\r
181             {\r
182                 /* This should never happen */\r
183             }\r
184     }\r
185     \r
186     return 0;\r
187 }\r
188 \r
189 int waitForProcess (ProcHandle handle)\r
190 {\r
191     int wstat;\r
192     \r
193     while (waitpid(handle, &wstat, 0) < 0)\r
194     {\r
195         if (errno != EINTR)\r
196         {\r
197             return -1;\r
198         }\r
199     }\r
200     \r
201     if (WIFEXITED(wstat))\r
202         return WEXITSTATUS(wstat);\r
203     else\r
204         if (WIFSIGNALED(wstat))\r
205         {\r
206             errno = EINTR;\r
207         }\r
208         else\r
209         {\r
210             /* This should never happen */\r
211         }\r
212     \r
213     return -1;\r
214 }\r
215 \r
216 #else\r
217 /* ----------------------------------------------------------------------------\r
218    Win32 versions\r
219    ------------------------------------------------------------------------- */\r
220 \r
221 /* -------------------- WINDOWS VERSION --------------------- */\r
222 \r
223 /* This is the error table that defines the mapping between OS error\r
224    codes and errno values */\r
225 \r
226 struct errentry {\r
227         unsigned long oscode;           /* OS return value */\r
228         int errnocode;  /* System V error code */\r
229 };\r
230 \r
231 static struct errentry errtable[] = {\r
232         {  ERROR_INVALID_FUNCTION,       EINVAL    },  /* 1 */\r
233         {  ERROR_FILE_NOT_FOUND,         ENOENT    },  /* 2 */\r
234         {  ERROR_PATH_NOT_FOUND,         ENOENT    },  /* 3 */\r
235         {  ERROR_TOO_MANY_OPEN_FILES,    EMFILE    },  /* 4 */\r
236         {  ERROR_ACCESS_DENIED,          EACCES    },  /* 5 */\r
237         {  ERROR_INVALID_HANDLE,         EBADF     },  /* 6 */\r
238         {  ERROR_ARENA_TRASHED,          ENOMEM    },  /* 7 */\r
239         {  ERROR_NOT_ENOUGH_MEMORY,      ENOMEM    },  /* 8 */\r
240         {  ERROR_INVALID_BLOCK,          ENOMEM    },  /* 9 */\r
241         {  ERROR_BAD_ENVIRONMENT,        E2BIG     },  /* 10 */\r
242         {  ERROR_BAD_FORMAT,             ENOEXEC   },  /* 11 */\r
243         {  ERROR_INVALID_ACCESS,         EINVAL    },  /* 12 */\r
244         {  ERROR_INVALID_DATA,           EINVAL    },  /* 13 */\r
245         {  ERROR_INVALID_DRIVE,          ENOENT    },  /* 15 */\r
246         {  ERROR_CURRENT_DIRECTORY,      EACCES    },  /* 16 */\r
247         {  ERROR_NOT_SAME_DEVICE,        EXDEV     },  /* 17 */\r
248         {  ERROR_NO_MORE_FILES,          ENOENT    },  /* 18 */\r
249         {  ERROR_LOCK_VIOLATION,         EACCES    },  /* 33 */\r
250         {  ERROR_BAD_NETPATH,            ENOENT    },  /* 53 */\r
251         {  ERROR_NETWORK_ACCESS_DENIED,  EACCES    },  /* 65 */\r
252         {  ERROR_BAD_NET_NAME,           ENOENT    },  /* 67 */\r
253         {  ERROR_FILE_EXISTS,            EEXIST    },  /* 80 */\r
254         {  ERROR_CANNOT_MAKE,            EACCES    },  /* 82 */\r
255         {  ERROR_FAIL_I24,               EACCES    },  /* 83 */\r
256         {  ERROR_INVALID_PARAMETER,      EINVAL    },  /* 87 */\r
257         {  ERROR_NO_PROC_SLOTS,          EAGAIN    },  /* 89 */\r
258         {  ERROR_DRIVE_LOCKED,           EACCES    },  /* 108 */\r
259         {  ERROR_BROKEN_PIPE,            EPIPE     },  /* 109 */\r
260         {  ERROR_DISK_FULL,              ENOSPC    },  /* 112 */\r
261         {  ERROR_INVALID_TARGET_HANDLE,  EBADF     },  /* 114 */\r
262         {  ERROR_INVALID_HANDLE,         EINVAL    },  /* 124 */\r
263         {  ERROR_WAIT_NO_CHILDREN,       ECHILD    },  /* 128 */\r
264         {  ERROR_CHILD_NOT_COMPLETE,     ECHILD    },  /* 129 */\r
265         {  ERROR_DIRECT_ACCESS_HANDLE,   EBADF     },  /* 130 */\r
266         {  ERROR_NEGATIVE_SEEK,          EINVAL    },  /* 131 */\r
267         {  ERROR_SEEK_ON_DEVICE,         EACCES    },  /* 132 */\r
268         {  ERROR_DIR_NOT_EMPTY,          ENOTEMPTY },  /* 145 */\r
269         {  ERROR_NOT_LOCKED,             EACCES    },  /* 158 */\r
270         {  ERROR_BAD_PATHNAME,           ENOENT    },  /* 161 */\r
271         {  ERROR_MAX_THRDS_REACHED,      EAGAIN    },  /* 164 */\r
272         {  ERROR_LOCK_FAILED,            EACCES    },  /* 167 */\r
273         {  ERROR_ALREADY_EXISTS,         EEXIST    },  /* 183 */\r
274         {  ERROR_FILENAME_EXCED_RANGE,   ENOENT    },  /* 206 */\r
275         {  ERROR_NESTING_NOT_ALLOWED,    EAGAIN    },  /* 215 */\r
276         {  ERROR_NOT_ENOUGH_QUOTA,       ENOMEM    }    /* 1816 */\r
277 };\r
278 \r
279 /* size of the table */\r
280 #define ERRTABLESIZE (sizeof(errtable)/sizeof(errtable[0]))\r
281 \r
282 /* The following two constants must be the minimum and maximum\r
283    values in the (contiguous) range of Exec Failure errors. */\r
284 #define MIN_EXEC_ERROR ERROR_INVALID_STARTING_CODESEG\r
285 #define MAX_EXEC_ERROR ERROR_INFLOOP_IN_RELOC_CHAIN\r
286 \r
287 /* These are the low and high value in the range of errors that are\r
288    access violations */\r
289 #define MIN_EACCES_RANGE ERROR_WRITE_PROTECT\r
290 #define MAX_EACCES_RANGE ERROR_SHARING_BUFFER_EXCEEDED\r
291 \r
292 static void maperrno (void)\r
293 {\r
294         int i;\r
295         DWORD dwErrorCode;\r
296 \r
297         dwErrorCode = GetLastError();\r
298 \r
299         /* check the table for the OS error code */\r
300         for (i = 0; i < ERRTABLESIZE; ++i)\r
301         {\r
302                 if (dwErrorCode == errtable[i].oscode)\r
303                 {\r
304                         errno = errtable[i].errnocode;\r
305                         return;\r
306                 }\r
307         }\r
308 \r
309         /* The error code wasn't in the table.  We check for a range of */\r
310         /* EACCES errors or exec failure errors (ENOEXEC).  Otherwise   */\r
311         /* EINVAL is returned.                                          */\r
312 \r
313         if (dwErrorCode >= MIN_EACCES_RANGE && dwErrorCode <= MAX_EACCES_RANGE)\r
314                 errno = EACCES;\r
315         else\r
316                 if (dwErrorCode >= MIN_EXEC_ERROR && dwErrorCode <= MAX_EXEC_ERROR)\r
317                         errno = ENOEXEC;\r
318                 else\r
319                         errno = EINVAL;\r
320 }\r
321 \r
322 /*\r
323  * Function: mkAnonPipe\r
324  *\r
325  * Purpose:  create an anonymous pipe with read and write ends being\r
326  *           optionally (non-)inheritable.\r
327  */\r
328 static BOOL\r
329 mkAnonPipe (HANDLE* pHandleIn, BOOL isInheritableIn, \r
330             HANDLE* pHandleOut, BOOL isInheritableOut)\r
331 {\r
332         HANDLE hTemporaryIn  = NULL;\r
333         HANDLE hTemporaryOut = NULL;\r
334         BOOL status;\r
335         SECURITY_ATTRIBUTES sec_attrs;\r
336 \r
337         /* Create inheritable security attributes */\r
338         sec_attrs.nLength = sizeof(SECURITY_ATTRIBUTES);\r
339         sec_attrs.lpSecurityDescriptor = NULL;\r
340         sec_attrs.bInheritHandle = TRUE;\r
341 \r
342         /* Create the anon pipe with both ends inheritable */\r
343         if (!CreatePipe(&hTemporaryIn, &hTemporaryOut, &sec_attrs, 0))\r
344         {\r
345                 maperrno();\r
346                 *pHandleIn  = NULL;\r
347                 *pHandleOut = NULL;\r
348                 return FALSE;\r
349         }\r
350 \r
351         if (isInheritableIn)\r
352                 *pHandleIn = hTemporaryIn;\r
353         else\r
354         {\r
355                 /* Make the read end non-inheritable */\r
356                 status = DuplicateHandle(GetCurrentProcess(), hTemporaryIn,\r
357                               GetCurrentProcess(), pHandleIn,\r
358                               0,\r
359                               FALSE, /* non-inheritable */\r
360                               DUPLICATE_SAME_ACCESS);\r
361                 CloseHandle(hTemporaryIn);\r
362                 if (!status)\r
363                 {\r
364                         maperrno();\r
365                         *pHandleIn  = NULL;\r
366                         *pHandleOut = NULL;\r
367                         CloseHandle(hTemporaryOut);\r
368                         return FALSE;\r
369                 }\r
370         }\r
371 \r
372         if (isInheritableOut)\r
373                 *pHandleOut = hTemporaryOut;\r
374         else\r
375         {\r
376                 /* Make the write end non-inheritable */\r
377                 status = DuplicateHandle(GetCurrentProcess(), hTemporaryOut,\r
378                               GetCurrentProcess(), pHandleOut,\r
379                               0,\r
380                               FALSE, /* non-inheritable */\r
381                               DUPLICATE_SAME_ACCESS);\r
382                 CloseHandle(hTemporaryOut);\r
383                 if (!status)\r
384                 {\r
385                         maperrno();\r
386                         *pHandleIn  = NULL;\r
387                         *pHandleOut = NULL;\r
388                         CloseHandle(*pHandleIn);\r
389                 return FALSE;\r
390         }\r
391         }\r
392 \r
393         return TRUE;\r
394 }\r
395 \r
396 ProcHandle\r
397 runProcess (char *cmd, char *workingDirectory, void *environment,\r
398             int fdStdInput, int fdStdOutput, int fdStdError)\r
399 {\r
400         STARTUPINFO sInfo;\r
401         PROCESS_INFORMATION pInfo;\r
402         DWORD flags;\r
403 \r
404         ZeroMemory(&sInfo, sizeof(sInfo));\r
405         sInfo.cb = sizeof(sInfo);\r
406         sInfo.dwFlags = STARTF_USESTDHANDLES;\r
407         sInfo.hStdInput = (HANDLE) _get_osfhandle(fdStdInput);\r
408         sInfo.hStdOutput= (HANDLE) _get_osfhandle(fdStdOutput);\r
409         sInfo.hStdError = (HANDLE) _get_osfhandle(fdStdError);\r
410 \r
411         if (sInfo.hStdOutput != GetStdHandle(STD_OUTPUT_HANDLE) &&\r
412             sInfo.hStdError  != GetStdHandle(STD_ERROR_HANDLE))\r
413                 flags = CREATE_NO_WINDOW;   // Run without console window only when both output and error are redirected\r
414         else\r
415                 flags = 0;\r
416 \r
417         if (!CreateProcess(NULL, cmd, NULL, NULL, TRUE, flags, environment, workingDirectory, &sInfo, &pInfo))\r
418         {\r
419                 maperrno();\r
420                 return -1;\r
421         }\r
422 \r
423         CloseHandle(pInfo.hThread);\r
424         return (ProcHandle)pInfo.hProcess;\r
425 }\r
426 \r
427 ProcHandle\r
428 runInteractiveProcess (char *cmd, char *workingDirectory, void *environment,\r
429                        int *pfdStdInput, int *pfdStdOutput, int *pfdStdError)\r
430 {\r
431         STARTUPINFO sInfo;\r
432         PROCESS_INFORMATION pInfo;\r
433         HANDLE hStdInputRead,  hStdInputWrite;\r
434         HANDLE hStdOutputRead, hStdOutputWrite;\r
435         HANDLE hStdErrorRead,  hStdErrorWrite;\r
436 \r
437         if (!mkAnonPipe(&hStdInputRead,  TRUE, &hStdInputWrite,  FALSE))\r
438                 return -1;\r
439 \r
440         if (!mkAnonPipe(&hStdOutputRead, FALSE, &hStdOutputWrite, TRUE))\r
441         {\r
442                 CloseHandle(hStdInputRead);\r
443                 CloseHandle(hStdInputWrite);\r
444                 return -1;\r
445         }\r
446 \r
447         if (!mkAnonPipe(&hStdErrorRead,  FALSE, &hStdErrorWrite,  TRUE))\r
448         {\r
449                 CloseHandle(hStdInputRead);\r
450                 CloseHandle(hStdInputWrite);\r
451                 CloseHandle(hStdOutputRead);\r
452                 CloseHandle(hStdOutputWrite);\r
453                 return -1;\r
454         }\r
455 \r
456         ZeroMemory(&sInfo, sizeof(sInfo));\r
457         sInfo.cb = sizeof(sInfo);\r
458         sInfo.dwFlags = STARTF_USESTDHANDLES;\r
459         sInfo.hStdInput = hStdInputRead;\r
460         sInfo.hStdOutput= hStdOutputWrite;\r
461         sInfo.hStdError = hStdErrorWrite;\r
462 \r
463         if (!CreateProcess(NULL, cmd, NULL, NULL, TRUE, CREATE_NO_WINDOW, environment, workingDirectory, &sInfo, &pInfo))\r
464         {\r
465                 maperrno();\r
466                 CloseHandle(hStdInputRead);\r
467                 CloseHandle(hStdInputWrite);\r
468                 CloseHandle(hStdOutputRead);\r
469                 CloseHandle(hStdOutputWrite);\r
470                 CloseHandle(hStdErrorRead);\r
471                 CloseHandle(hStdErrorWrite);\r
472                 return -1;\r
473         }\r
474         CloseHandle(pInfo.hThread);\r
475 \r
476         // Close the ends of the pipes that were inherited by the\r
477         // child process.  This is important, otherwise we won't see\r
478         // EOF on these pipes when the child process exits.\r
479         CloseHandle(hStdInputRead);\r
480         CloseHandle(hStdOutputWrite);\r
481         CloseHandle(hStdErrorWrite);\r
482 \r
483         *pfdStdInput  = _open_osfhandle((intptr_t) hStdInputWrite, _O_WRONLY);\r
484         *pfdStdOutput = _open_osfhandle((intptr_t) hStdOutputRead, _O_RDONLY);\r
485         *pfdStdError  = _open_osfhandle((intptr_t) hStdErrorRead, _O_RDONLY);\r
486 \r
487         return (int) pInfo.hProcess;\r
488 }\r
489 \r
490 int\r
491 terminateProcess (ProcHandle handle)\r
492 {\r
493     if (!TerminateProcess((HANDLE) handle, 1)) {\r
494         maperrno();\r
495         return -1;\r
496     }\r
497 \r
498     CloseHandle((HANDLE) handle);\r
499     return 0;\r
500 }\r
501 \r
502 int\r
503 getProcessExitCode (ProcHandle handle, int *pExitCode)\r
504 {\r
505     *pExitCode = 0;\r
506 \r
507     if (WaitForSingleObject((HANDLE) handle, 1) == WAIT_OBJECT_0)\r
508     {\r
509         if (GetExitCodeProcess((HANDLE) handle, (DWORD *) pExitCode) == 0)\r
510         {\r
511             maperrno();\r
512             return -1;\r
513         }\r
514         \r
515         CloseHandle((HANDLE) handle);\r
516         return 1;\r
517     }\r
518     \r
519     return 0;\r
520 }\r
521 \r
522 int\r
523 waitForProcess (ProcHandle handle)\r
524 {\r
525     DWORD retCode;\r
526 \r
527     if (WaitForSingleObject((HANDLE) handle, INFINITE) == WAIT_OBJECT_0)\r
528     {\r
529         if (GetExitCodeProcess((HANDLE) handle, &retCode) == 0)\r
530         {\r
531             maperrno();\r
532             return -1;\r
533         }\r
534         \r
535         CloseHandle((HANDLE) handle);\r
536         return retCode;\r
537     }\r
538     \r
539     maperrno();\r
540     return -1;\r
541 }\r
542 \r
543 #endif // Win32\r