[project @ 2004-09-29 15:50:51 by simonmar]
[ghc-base.git] / cbits / runProcess.c
diff --git a/cbits/runProcess.c b/cbits/runProcess.c
new file mode 100644 (file)
index 0000000..efe9ea9
--- /dev/null
@@ -0,0 +1,543 @@
+/* ----------------------------------------------------------------------------\r
+   (c) The University of Glasgow 2004\r
+   \r
+   Support for System.Process\r
+   ------------------------------------------------------------------------- */\r
+\r
+#include "HsBase.h"\r
+\r
+#if defined(mingw32_TARGET_OS)\r
+#include <windows.h>\r
+#include <stdlib.h>\r
+#endif\r
+\r
+#ifdef HAVE_VFORK_H\r
+#include <vfork.h>\r
+#endif\r
+\r
+#ifdef HAVE_VFORK\r
+#define fork vfork\r
+#endif\r
+\r
+#ifdef HAVE_SIGNAL_H\r
+#include <signal.h>\r
+#endif\r
+\r
+#if !defined(mingw32_TARGET_OS) && !defined(__MINGW32__)\r
+/* ----------------------------------------------------------------------------\r
+   UNIX versions\r
+   ------------------------------------------------------------------------- */\r
+\r
+int\r
+runProcess (char *const args[], char *workingDirectory, char **environment, \r
+           int fdStdInput, int fdStdOutput, int fdStdError)\r
+{\r
+    int pid;\r
+    struct sigaction dfl;\r
+\r
+    switch(pid = fork())\r
+    {\r
+    case -1:\r
+       return -1;\r
+       \r
+    case 0:\r
+    {\r
+       pPrPr_disableITimers();\r
+       \r
+       if (workingDirectory) {\r
+           chdir (workingDirectory);\r
+       }\r
+       \r
+       /*\r
+        * Restore SIGINT and SIGQUIT to default actions\r
+        *\r
+        * Glyn Clemments writes:\r
+        * For your purposes, runProcess + waitForProcess is probably\r
+        * the way to go. Except that runProcess appears to be missing\r
+        * the usual signal handling. system() ignores SIGINT and\r
+        * SIGQUIT in the parent, and resets them to their defaults in\r
+        * the child; it also blocks SIGCHLD in the parent. runProcess\r
+        * may need to do something similar; it should probably at\r
+        * least reset SIGINT and SIGQUIT in the child, in case they\r
+        * are ignored in the parent. The parent can set up its own\r
+        * signal handling, but the only place it can control the\r
+        * child's signal handling is between the fork() and the\r
+        * exec(), so if runProcess doesn't do it, it won't get done.\r
+        */\r
+        dfl.sa_handler = SIG_DFL;\r
+        (void)sigemptyset(&dfl.sa_mask);\r
+        dfl.sa_flags = 0;\r
+       (void)sigaction(SIGINT, &dfl, NULL);\r
+       (void)sigaction(SIGQUIT,  &dfl, NULL);\r
+\r
+       dup2 (fdStdInput,  STDIN_FILENO);\r
+       dup2 (fdStdOutput, STDOUT_FILENO);\r
+       dup2 (fdStdError,  STDERR_FILENO);\r
+       \r
+       if (environment) {\r
+           execvpe(args[0], args, environment);\r
+       } else {\r
+           execvp(args[0], args);\r
+       }\r
+    }\r
+    _exit(127);\r
+    }\r
+    \r
+    return pid;\r
+}\r
+\r
+ProcHandle\r
+runInteractiveProcess (char *const args[], \r
+                      char *workingDirectory, char **environment,\r
+                      int *pfdStdInput, int *pfdStdOutput, int *pfdStdError)\r
+{\r
+    int pid;\r
+    int fdStdInput[2], fdStdOutput[2], fdStdError[2];\r
+\r
+    pipe(fdStdInput);\r
+    pipe(fdStdOutput);\r
+    pipe(fdStdError);\r
+\r
+    switch(pid = fork())\r
+    {\r
+    case -1:\r
+       close(fdStdInput[0]);\r
+       close(fdStdInput[1]);\r
+       close(fdStdOutput[0]);\r
+       close(fdStdOutput[1]);\r
+       close(fdStdError[0]);\r
+       close(fdStdError[1]);\r
+       return -1;\r
+       \r
+    case 0:\r
+    {\r
+       pPrPr_disableITimers();\r
+       \r
+       if (workingDirectory) {\r
+           chdir (workingDirectory);\r
+       }\r
+       \r
+       dup2 (fdStdInput[0], STDIN_FILENO);\r
+       dup2 (fdStdOutput[1], STDOUT_FILENO);\r
+       dup2 (fdStdError[1], STDERR_FILENO);\r
+       \r
+       close(fdStdInput[0]);\r
+       close(fdStdInput[1]);\r
+       close(fdStdOutput[0]);\r
+       close(fdStdOutput[1]);\r
+       close(fdStdError[0]);\r
+       close(fdStdError[1]);\r
+       \r
+       /* the child */\r
+       if (environment) {\r
+           execvpe(args[0], args, environment);\r
+       } else {\r
+           execvp(args[0], args);\r
+       }\r
+    }\r
+    _exit(127);\r
+    \r
+    default:\r
+       close(fdStdInput[0]);\r
+       close(fdStdOutput[1]);\r
+       close(fdStdError[1]);\r
+       \r
+       *pfdStdInput  = fdStdInput[1];\r
+       *pfdStdOutput = fdStdOutput[0];\r
+       *pfdStdError  = fdStdError[0];\r
+       break;\r
+    }\r
+    \r
+    return pid;\r
+}\r
+\r
+int\r
+terminateProcess (ProcHandle handle)\r
+{\r
+    return (kill(handle, SIGTERM) == 0);\r
+}\r
+\r
+int\r
+getProcessExitCode (ProcHandle handle, int *pExitCode)\r
+{\r
+    int wstat;\r
+    \r
+    *pExitCode = 0;\r
+    \r
+    if (waitpid(handle, &wstat, WNOHANG) > 0)\r
+    {\r
+       if (WIFEXITED(wstat))\r
+       {\r
+           *pExitCode = WEXITSTATUS(wstat);\r
+           return 1;\r
+       }\r
+       else\r
+           if (WIFSIGNALED(wstat))\r
+           {\r
+               errno = EINTR;\r
+               return -1;\r
+           }\r
+           else\r
+           {\r
+               /* This should never happen */\r
+           }\r
+    }\r
+    \r
+    return 0;\r
+}\r
+\r
+int waitForProcess (ProcHandle handle)\r
+{\r
+    int wstat;\r
+    \r
+    while (waitpid(handle, &wstat, 0) < 0)\r
+    {\r
+       if (errno != EINTR)\r
+       {\r
+           return -1;\r
+       }\r
+    }\r
+    \r
+    if (WIFEXITED(wstat))\r
+       return WEXITSTATUS(wstat);\r
+    else\r
+       if (WIFSIGNALED(wstat))\r
+       {\r
+           errno = EINTR;\r
+       }\r
+       else\r
+       {\r
+           /* This should never happen */\r
+       }\r
+    \r
+    return -1;\r
+}\r
+\r
+#else\r
+/* ----------------------------------------------------------------------------\r
+   Win32 versions\r
+   ------------------------------------------------------------------------- */\r
+\r
+/* -------------------- WINDOWS VERSION --------------------- */\r
+\r
+/* This is the error table that defines the mapping between OS error\r
+   codes and errno values */\r
+\r
+struct errentry {\r
+        unsigned long oscode;           /* OS return value */\r
+        int errnocode;  /* System V error code */\r
+};\r
+\r
+static struct errentry errtable[] = {\r
+        {  ERROR_INVALID_FUNCTION,       EINVAL    },  /* 1 */\r
+        {  ERROR_FILE_NOT_FOUND,         ENOENT    },  /* 2 */\r
+        {  ERROR_PATH_NOT_FOUND,         ENOENT    },  /* 3 */\r
+        {  ERROR_TOO_MANY_OPEN_FILES,    EMFILE    },  /* 4 */\r
+        {  ERROR_ACCESS_DENIED,          EACCES    },  /* 5 */\r
+        {  ERROR_INVALID_HANDLE,         EBADF     },  /* 6 */\r
+        {  ERROR_ARENA_TRASHED,          ENOMEM    },  /* 7 */\r
+        {  ERROR_NOT_ENOUGH_MEMORY,      ENOMEM    },  /* 8 */\r
+        {  ERROR_INVALID_BLOCK,          ENOMEM    },  /* 9 */\r
+        {  ERROR_BAD_ENVIRONMENT,        E2BIG     },  /* 10 */\r
+        {  ERROR_BAD_FORMAT,             ENOEXEC   },  /* 11 */\r
+        {  ERROR_INVALID_ACCESS,         EINVAL    },  /* 12 */\r
+        {  ERROR_INVALID_DATA,           EINVAL    },  /* 13 */\r
+        {  ERROR_INVALID_DRIVE,          ENOENT    },  /* 15 */\r
+        {  ERROR_CURRENT_DIRECTORY,      EACCES    },  /* 16 */\r
+        {  ERROR_NOT_SAME_DEVICE,        EXDEV     },  /* 17 */\r
+        {  ERROR_NO_MORE_FILES,          ENOENT    },  /* 18 */\r
+        {  ERROR_LOCK_VIOLATION,         EACCES    },  /* 33 */\r
+        {  ERROR_BAD_NETPATH,            ENOENT    },  /* 53 */\r
+        {  ERROR_NETWORK_ACCESS_DENIED,  EACCES    },  /* 65 */\r
+        {  ERROR_BAD_NET_NAME,           ENOENT    },  /* 67 */\r
+        {  ERROR_FILE_EXISTS,            EEXIST    },  /* 80 */\r
+        {  ERROR_CANNOT_MAKE,            EACCES    },  /* 82 */\r
+        {  ERROR_FAIL_I24,               EACCES    },  /* 83 */\r
+        {  ERROR_INVALID_PARAMETER,      EINVAL    },  /* 87 */\r
+        {  ERROR_NO_PROC_SLOTS,          EAGAIN    },  /* 89 */\r
+        {  ERROR_DRIVE_LOCKED,           EACCES    },  /* 108 */\r
+        {  ERROR_BROKEN_PIPE,            EPIPE     },  /* 109 */\r
+        {  ERROR_DISK_FULL,              ENOSPC    },  /* 112 */\r
+        {  ERROR_INVALID_TARGET_HANDLE,  EBADF     },  /* 114 */\r
+        {  ERROR_INVALID_HANDLE,         EINVAL    },  /* 124 */\r
+        {  ERROR_WAIT_NO_CHILDREN,       ECHILD    },  /* 128 */\r
+        {  ERROR_CHILD_NOT_COMPLETE,     ECHILD    },  /* 129 */\r
+        {  ERROR_DIRECT_ACCESS_HANDLE,   EBADF     },  /* 130 */\r
+        {  ERROR_NEGATIVE_SEEK,          EINVAL    },  /* 131 */\r
+        {  ERROR_SEEK_ON_DEVICE,         EACCES    },  /* 132 */\r
+        {  ERROR_DIR_NOT_EMPTY,          ENOTEMPTY },  /* 145 */\r
+        {  ERROR_NOT_LOCKED,             EACCES    },  /* 158 */\r
+        {  ERROR_BAD_PATHNAME,           ENOENT    },  /* 161 */\r
+        {  ERROR_MAX_THRDS_REACHED,      EAGAIN    },  /* 164 */\r
+        {  ERROR_LOCK_FAILED,            EACCES    },  /* 167 */\r
+        {  ERROR_ALREADY_EXISTS,         EEXIST    },  /* 183 */\r
+        {  ERROR_FILENAME_EXCED_RANGE,   ENOENT    },  /* 206 */\r
+        {  ERROR_NESTING_NOT_ALLOWED,    EAGAIN    },  /* 215 */\r
+        {  ERROR_NOT_ENOUGH_QUOTA,       ENOMEM    }    /* 1816 */\r
+};\r
+\r
+/* size of the table */\r
+#define ERRTABLESIZE (sizeof(errtable)/sizeof(errtable[0]))\r
+\r
+/* The following two constants must be the minimum and maximum\r
+   values in the (contiguous) range of Exec Failure errors. */\r
+#define MIN_EXEC_ERROR ERROR_INVALID_STARTING_CODESEG\r
+#define MAX_EXEC_ERROR ERROR_INFLOOP_IN_RELOC_CHAIN\r
+\r
+/* These are the low and high value in the range of errors that are\r
+   access violations */\r
+#define MIN_EACCES_RANGE ERROR_WRITE_PROTECT\r
+#define MAX_EACCES_RANGE ERROR_SHARING_BUFFER_EXCEEDED\r
+\r
+static void maperrno (void)\r
+{\r
+       int i;\r
+       DWORD dwErrorCode;\r
+\r
+       dwErrorCode = GetLastError();\r
+\r
+       /* check the table for the OS error code */\r
+       for (i = 0; i < ERRTABLESIZE; ++i)\r
+       {\r
+               if (dwErrorCode == errtable[i].oscode)\r
+               {\r
+                       errno = errtable[i].errnocode;\r
+                       return;\r
+               }\r
+       }\r
+\r
+       /* The error code wasn't in the table.  We check for a range of */\r
+       /* EACCES errors or exec failure errors (ENOEXEC).  Otherwise   */\r
+       /* EINVAL is returned.                                          */\r
+\r
+       if (dwErrorCode >= MIN_EACCES_RANGE && dwErrorCode <= MAX_EACCES_RANGE)\r
+               errno = EACCES;\r
+       else\r
+               if (dwErrorCode >= MIN_EXEC_ERROR && dwErrorCode <= MAX_EXEC_ERROR)\r
+                       errno = ENOEXEC;\r
+               else\r
+                       errno = EINVAL;\r
+}\r
+\r
+/*\r
+ * Function: mkAnonPipe\r
+ *\r
+ * Purpose:  create an anonymous pipe with read and write ends being\r
+ *           optionally (non-)inheritable.\r
+ */\r
+static BOOL\r
+mkAnonPipe (HANDLE* pHandleIn, BOOL isInheritableIn, \r
+           HANDLE* pHandleOut, BOOL isInheritableOut)\r
+{\r
+       HANDLE hTemporaryIn  = NULL;\r
+       HANDLE hTemporaryOut = NULL;\r
+       BOOL status;\r
+       SECURITY_ATTRIBUTES sec_attrs;\r
+\r
+       /* Create inheritable security attributes */\r
+       sec_attrs.nLength = sizeof(SECURITY_ATTRIBUTES);\r
+       sec_attrs.lpSecurityDescriptor = NULL;\r
+       sec_attrs.bInheritHandle = TRUE;\r
+\r
+       /* Create the anon pipe with both ends inheritable */\r
+       if (!CreatePipe(&hTemporaryIn, &hTemporaryOut, &sec_attrs, 0))\r
+       {\r
+               maperrno();\r
+               *pHandleIn  = NULL;\r
+               *pHandleOut = NULL;\r
+               return FALSE;\r
+       }\r
+\r
+       if (isInheritableIn)\r
+               *pHandleIn = hTemporaryIn;\r
+       else\r
+       {\r
+               /* Make the read end non-inheritable */\r
+               status = DuplicateHandle(GetCurrentProcess(), hTemporaryIn,\r
+                             GetCurrentProcess(), pHandleIn,\r
+                             0,\r
+                             FALSE, /* non-inheritable */\r
+                             DUPLICATE_SAME_ACCESS);\r
+               CloseHandle(hTemporaryIn);\r
+               if (!status)\r
+               {\r
+                       maperrno();\r
+                       *pHandleIn  = NULL;\r
+                       *pHandleOut = NULL;\r
+                       CloseHandle(hTemporaryOut);\r
+                       return FALSE;\r
+               }\r
+       }\r
+\r
+       if (isInheritableOut)\r
+               *pHandleOut = hTemporaryOut;\r
+       else\r
+       {\r
+               /* Make the write end non-inheritable */\r
+               status = DuplicateHandle(GetCurrentProcess(), hTemporaryOut,\r
+                             GetCurrentProcess(), pHandleOut,\r
+                             0,\r
+                             FALSE, /* non-inheritable */\r
+                             DUPLICATE_SAME_ACCESS);\r
+               CloseHandle(hTemporaryOut);\r
+               if (!status)\r
+               {\r
+                       maperrno();\r
+                       *pHandleIn  = NULL;\r
+                       *pHandleOut = NULL;\r
+                       CloseHandle(*pHandleIn);\r
+               return FALSE;\r
+       }\r
+       }\r
+\r
+       return TRUE;\r
+}\r
+\r
+ProcHandle\r
+runProcess (char *cmd, char *workingDirectory, void *environment,\r
+           int fdStdInput, int fdStdOutput, int fdStdError)\r
+{\r
+       STARTUPINFO sInfo;\r
+       PROCESS_INFORMATION pInfo;\r
+       DWORD flags;\r
+\r
+       ZeroMemory(&sInfo, sizeof(sInfo));\r
+       sInfo.cb = sizeof(sInfo);\r
+       sInfo.dwFlags = STARTF_USESTDHANDLES;\r
+       sInfo.hStdInput = (HANDLE) _get_osfhandle(fdStdInput);\r
+       sInfo.hStdOutput= (HANDLE) _get_osfhandle(fdStdOutput);\r
+       sInfo.hStdError = (HANDLE) _get_osfhandle(fdStdError);\r
+\r
+       if (sInfo.hStdOutput != GetStdHandle(STD_OUTPUT_HANDLE) &&\r
+           sInfo.hStdError  != GetStdHandle(STD_ERROR_HANDLE))\r
+               flags = CREATE_NO_WINDOW;   // Run without console window only when both output and error are redirected\r
+       else\r
+               flags = 0;\r
+\r
+       if (!CreateProcess(NULL, cmd, NULL, NULL, TRUE, flags, environment, workingDirectory, &sInfo, &pInfo))\r
+       {\r
+               maperrno();\r
+               return -1;\r
+       }\r
+\r
+       CloseHandle(pInfo.hThread);\r
+       return (ProcHandle)pInfo.hProcess;\r
+}\r
+\r
+ProcHandle\r
+runInteractiveProcess (char *cmd, char *workingDirectory, void *environment,\r
+                      int *pfdStdInput, int *pfdStdOutput, int *pfdStdError)\r
+{\r
+       STARTUPINFO sInfo;\r
+       PROCESS_INFORMATION pInfo;\r
+       HANDLE hStdInputRead,  hStdInputWrite;\r
+       HANDLE hStdOutputRead, hStdOutputWrite;\r
+       HANDLE hStdErrorRead,  hStdErrorWrite;\r
+\r
+       if (!mkAnonPipe(&hStdInputRead,  TRUE, &hStdInputWrite,  FALSE))\r
+               return -1;\r
+\r
+       if (!mkAnonPipe(&hStdOutputRead, FALSE, &hStdOutputWrite, TRUE))\r
+       {\r
+               CloseHandle(hStdInputRead);\r
+               CloseHandle(hStdInputWrite);\r
+               return -1;\r
+       }\r
+\r
+       if (!mkAnonPipe(&hStdErrorRead,  FALSE, &hStdErrorWrite,  TRUE))\r
+       {\r
+               CloseHandle(hStdInputRead);\r
+               CloseHandle(hStdInputWrite);\r
+               CloseHandle(hStdOutputRead);\r
+               CloseHandle(hStdOutputWrite);\r
+               return -1;\r
+       }\r
+\r
+       ZeroMemory(&sInfo, sizeof(sInfo));\r
+       sInfo.cb = sizeof(sInfo);\r
+       sInfo.dwFlags = STARTF_USESTDHANDLES;\r
+       sInfo.hStdInput = hStdInputRead;\r
+       sInfo.hStdOutput= hStdOutputWrite;\r
+       sInfo.hStdError = hStdErrorWrite;\r
+\r
+       if (!CreateProcess(NULL, cmd, NULL, NULL, TRUE, CREATE_NO_WINDOW, environment, workingDirectory, &sInfo, &pInfo))\r
+       {\r
+               maperrno();\r
+               CloseHandle(hStdInputRead);\r
+               CloseHandle(hStdInputWrite);\r
+               CloseHandle(hStdOutputRead);\r
+               CloseHandle(hStdOutputWrite);\r
+               CloseHandle(hStdErrorRead);\r
+               CloseHandle(hStdErrorWrite);\r
+               return -1;\r
+       }\r
+       CloseHandle(pInfo.hThread);\r
+\r
+       // Close the ends of the pipes that were inherited by the\r
+       // child process.  This is important, otherwise we won't see\r
+       // EOF on these pipes when the child process exits.\r
+       CloseHandle(hStdInputRead);\r
+       CloseHandle(hStdOutputWrite);\r
+       CloseHandle(hStdErrorWrite);\r
+\r
+       *pfdStdInput  = _open_osfhandle((intptr_t) hStdInputWrite, _O_WRONLY);\r
+       *pfdStdOutput = _open_osfhandle((intptr_t) hStdOutputRead, _O_RDONLY);\r
+       *pfdStdError  = _open_osfhandle((intptr_t) hStdErrorRead, _O_RDONLY);\r
+\r
+       return (int) pInfo.hProcess;\r
+}\r
+\r
+int\r
+terminateProcess (ProcHandle handle)\r
+{\r
+    if (!TerminateProcess((HANDLE) handle, 1)) {\r
+       maperrno();\r
+       return -1;\r
+    }\r
+\r
+    CloseHandle((HANDLE) handle);\r
+    return 0;\r
+}\r
+\r
+int\r
+getProcessExitCode (ProcHandle handle, int *pExitCode)\r
+{\r
+    *pExitCode = 0;\r
+\r
+    if (WaitForSingleObject((HANDLE) handle, 1) == WAIT_OBJECT_0)\r
+    {\r
+       if (GetExitCodeProcess((HANDLE) handle, (DWORD *) pExitCode) == 0)\r
+       {\r
+           maperrno();\r
+           return -1;\r
+       }\r
+       \r
+       CloseHandle((HANDLE) handle);\r
+       return 1;\r
+    }\r
+    \r
+    return 0;\r
+}\r
+\r
+int\r
+waitForProcess (ProcHandle handle)\r
+{\r
+    DWORD retCode;\r
+\r
+    if (WaitForSingleObject((HANDLE) handle, INFINITE) == WAIT_OBJECT_0)\r
+    {\r
+       if (GetExitCodeProcess((HANDLE) handle, &retCode) == 0)\r
+       {\r
+           maperrno();\r
+           return -1;\r
+       }\r
+       \r
+       CloseHandle((HANDLE) handle);\r
+       return retCode;\r
+    }\r
+    \r
+    maperrno();\r
+    return -1;\r
+}\r
+\r
+#endif // Win32\r