Add ForkAndExecProcess to PAL to support CoreFX
authorStephen Toub <stoub@microsoft.com>
Thu, 19 Mar 2015 15:16:13 +0000 (11:16 -0400)
committerStephen Toub <stoub@microsoft.com>
Fri, 20 Mar 2015 03:09:39 +0000 (23:09 -0400)
System.Diagnostics.Process.Start needs to fork/execve
in order to create the process, but fork is unsafe
in managed code.  This commit pushes the core logic
needed to fork/execve into native code; a separate commit
in CoreFX will then take advantage of this function.

For now, I'm putting this in the runtime.  Longer-term we
can evaluate whether it makes sense to distribute a separate
library with System.Diagnostics.Process just for this function.

13 files changed:
src/dlls/mscoree/coreclr/CMakeLists.txt
src/pal/inc/pal.h
src/pal/inc/pal_corefx.h [new file with mode: 0644]
src/pal/src/CMakeLists.txt
src/pal/src/misc/corefx.cpp [new file with mode: 0644]
src/pal/src/misc/miscpalapi.cpp
src/pal/tests/palsuite/miscellaneous/CMakeLists.txt
src/pal/tests/palsuite/miscellaneous/EnsureOpenSslInitialized/test1/test.c
src/pal/tests/palsuite/miscellaneous/ForkAndExecProcess/CMakeLists.txt [new file with mode: 0644]
src/pal/tests/palsuite/miscellaneous/ForkAndExecProcess/test1/CMakeLists.txt [new file with mode: 0644]
src/pal/tests/palsuite/miscellaneous/ForkAndExecProcess/test1/test.c [new file with mode: 0644]
src/pal/tests/palsuite/miscellaneous/ForkAndExecProcess/test1/testinfo.dat [new file with mode: 0644]
src/pal/tests/palsuite/paltestlist.txt

index c49193d..d26203b 100644 (file)
@@ -22,8 +22,18 @@ if(CMAKE_SYSTEM_NAME STREQUAL Linux)
 # ensure proper resolving of circular references between a subset of the libraries.
     set(START_LIBRARY_GROUP -Wl,--start-group)
     set(END_LIBRARY_GROUP -Wl,--end-group)
+
+# These options are used to force every object to be included even if it's unused.
+    set(START_WHOLE_ARCHIVE -Wl,--whole-archive)
+    set(END_WHOLE_ARCHIVE -Wl,--no-whole-archive) 
 endif(CMAKE_SYSTEM_NAME STREQUAL Linux)
 
+if(CMAKE_SYSTEM_NAME STREQUAL Darwin)
+# These options are used to force every object to be included even if it's unused.
+    set(START_WHOLE_ARCHIVE -force_load)
+    set(END_WHOLE_ARCHIVE )
+endif(CMAKE_SYSTEM_NAME STREQUAL Darwin)
+
 endif (WIN32)
 
 add_definitions(-DFX_VER_INTERNALNAME_STR=CoreCLR.dll)
@@ -84,7 +94,9 @@ if(WIN32)
     )
 else()
     list(APPEND CORECLR_LIBRARIES
+        ${START_WHOLE_ARCHIVE} # force all PAL objects to be included so all exports are available 
         CoreClrPal
+        ${END_WHOLE_ARCHIVE}
         palrt
     )
 endif(WIN32)
index 7adfa10..0c12e9b 100644 (file)
@@ -5227,14 +5227,6 @@ ReportEventW (
 #endif // !UNICODE
 
 
-/******************* CoreFX Entrypoints *******************************/
-
-PALIMPORT
-DWORD
-PALAPI
-EnsureOpenSslInitialized();
-
-
 /******************* C Runtime Entrypoints *******************************/
 
 #ifdef PLATFORM_UNIX
diff --git a/src/pal/inc/pal_corefx.h b/src/pal/inc/pal_corefx.h
new file mode 100644 (file)
index 0000000..80383f2
--- /dev/null
@@ -0,0 +1,53 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information. 
+//
+
+/*++
+
+Module Name:
+
+    pal_corefx.h
+
+Abstract:
+
+    Header file for functions meant to be consumed by the CoreFX libraries.
+
+--*/
+
+#include "pal.h"
+
+#ifndef __PAL_COREFX_H__
+#define __PAL_COREFX_H__
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+PALIMPORT
+int
+PALAPI
+EnsureOpenSslInitialized();
+
+PALIMPORT
+int
+PALAPI
+ForkAndExecProcess(
+           const char* filename,
+           char* const argv[],
+           char* const envp[],
+           const char* cwd,
+           int redirectStdin,
+           int redirectStdout,
+           int redirectStderr,
+           int* childPid,
+           int* stdinFd,
+           int* stdoutFd,
+           int* stderrFd);
+
+#ifdef  __cplusplus
+} // extern "C"
+#endif
+
+#endif // __PAL_COREFX_H__
+
index d65233e..33227d3 100644 (file)
@@ -83,6 +83,7 @@ set(SOURCES
   map/virtual.cpp
   memory/heap.cpp
   memory/local.cpp
+  misc/corefx.cpp
   misc/dbgmsg.cpp
   misc/environ.cpp
   misc/error.cpp
diff --git a/src/pal/src/misc/corefx.cpp b/src/pal/src/misc/corefx.cpp
new file mode 100644 (file)
index 0000000..d130eb8
--- /dev/null
@@ -0,0 +1,348 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information. 
+//
+
+/*++
+
+Module Name:
+
+    corefx.cpp
+
+Abstract:
+
+    Implementation of PAL APIs meant to be consumed by CoreFX libraries
+
+--*/
+
+#include "pal/palinternal.h"
+#include "pal/dbgmsg.h"
+#include "pal/module.h"
+#include <pal_corefx.h>
+
+#include <errno.h>
+#include <unistd.h> 
+#include <dlfcn.h>
+
+#ifdef __APPLE__
+#include <mach-o/dyld.h>
+#endif // __APPLE__
+
+SET_DEFAULT_DEBUG_CHANNEL(MISC);
+
+/*++
+Function:
+  EnsureOpenSslInitialized
+
+  Used by cryptographic libraries in CoreFX to initialize
+  threading support in OpenSSL.
+
+  --*/
+
+static const char * const libcryptoName = "libcrypto" PAL_SHLIB_SUFFIX;
+
+static void* g_OpenSslLib;
+static pthread_mutex_t g_OpenSslInitLock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t *g_OpenSslLocks;
+
+#define CRYPTO_LOCK 1
+typedef void(*locking_function)(int mode, int n, char* file, int line);
+typedef int(*CRYPTO_num_locks)(void);
+typedef void(*CRYPTO_set_locking_callback)(locking_function callback);
+
+static void LockingCallback(int mode, int n, char* file, int line)
+{
+    int result;
+    if (mode & CRYPTO_LOCK)
+    {
+        result = pthread_mutex_lock(&g_OpenSslLocks[n]);
+    }
+    else
+    {
+        result = pthread_mutex_unlock(&g_OpenSslLocks[n]);
+    }
+
+    if (result != 0)
+    {
+        ASSERT("LockingCallback(%d, %d, %s, %d) failed with error %d \n",
+            mode, n, file, line, result);
+    }
+}
+
+int
+PALAPI
+EnsureOpenSslInitialized()
+{
+    int ret = 0;
+    int numLocks;
+    CRYPTO_num_locks numLocksFunc;
+    CRYPTO_set_locking_callback setCallbackFunc;
+    int locksInitialized = 0;
+
+    PERF_ENTRY(EnsureOpenSslInitialized);
+    ENTRY("EnsureOpenSslInitialized()\n");
+
+    pthread_mutex_lock(&g_OpenSslInitLock);
+
+    if (g_OpenSslLocks != NULL)
+    {
+        // Already initialized; nothing more to do.
+        goto done;
+    }
+
+    // Open the libcrypto library
+    g_OpenSslLib = dlopen(libcryptoName, RTLD_NOW);
+    if (g_OpenSslLib == NULL)
+    {
+        // CoreCLR does not require libcrypto as a dependency,
+        // even though various libraries might.
+        ret = 1;
+        goto done;
+    }
+
+    // Get the functions we need from OpenSSL
+    numLocksFunc = (CRYPTO_num_locks) dlsym(g_OpenSslLib, "CRYPTO_num_locks");
+    setCallbackFunc = (CRYPTO_set_locking_callback) dlsym(g_OpenSslLib, "CRYPTO_set_locking_callback");
+    if (numLocksFunc == NULL || setCallbackFunc == NULL)
+    {
+        ASSERT("Unable to find CRYPTO_num_locks or CRYPTO_set_locking_callback\n");
+        ret = 2;
+        goto done;
+    }
+
+    // Determine how many locks are needed
+    numLocks = numLocksFunc();
+    if (numLocks <= 0)
+    {
+        ASSERT("CRYPTO_num_locks returned invalid value: %d\n", numLocks);
+        ret = 3;
+        goto done;
+    }
+
+    // Create the locks array
+    g_OpenSslLocks = (pthread_mutex_t*) PAL_malloc(sizeof(pthread_mutex_t) * numLocks);
+    if (g_OpenSslLocks == NULL)
+    {
+        ASSERT("PAL_malloc failed\n");
+        ret = 4;
+        goto done;
+    }
+
+    // Initialize each of the locks
+    for (locksInitialized = 0; locksInitialized < numLocks; locksInitialized++)
+    {
+        if (pthread_mutex_init(&g_OpenSslLocks[locksInitialized], NULL) != 0)
+        {
+            ASSERT("pthread_mutex_init failed\n");
+            ret = 5;
+            goto done;
+        }
+    }
+
+    // Initialize the callback
+    setCallbackFunc((locking_function) LockingCallback);
+
+done:
+    if (ret != 0)
+    {
+        // Cleanup on failure
+
+        if (g_OpenSslLocks != NULL)
+        {
+            for (int i = locksInitialized - 1; i >= 0; i--)
+            {
+                if (pthread_mutex_destroy(&g_OpenSslLocks[i]) != 0)
+                {
+                    ASSERT("Unable to pthread_mutex_destroy while cleaning up\n");
+                }
+            }
+            PAL_free(g_OpenSslLocks);
+            g_OpenSslLocks = NULL;
+        }
+
+        if (g_OpenSslLib != NULL)
+        {
+            if (dlclose(g_OpenSslLib) != 0)
+            {
+                ASSERT("Unable to close OpenSSL with dlerror \"%s\" \n", dlerror());
+            }
+            g_OpenSslLib = NULL;
+        }
+    }
+
+    pthread_mutex_unlock(&g_OpenSslInitLock);
+
+    // If successful, keep OpenSSL library open and initialized
+
+    LOGEXIT("EnsureOpenSslInitialized returns %u\n", ret);
+    PERF_EXIT(EnsureOpenSslInitialized);
+    return ret;
+}
+
+/*++
+Function:
+ForkAndExecProcess
+
+Used by System.Diagnostics.Process.Start to fork/exec a new process.
+
+This function takes the place of directly using fork and execve from managed code,
+in order to avoid executing managed code in the child process in the window between
+fork and execve, which is not safe.  
+
+As would have been the case with fork/execve, a return value of 0 is success and -1
+is failure; if failure, error information is provided in errno.
+
+--*/
+
+#define READ_END_OF_PIPE  0
+#define WRITE_END_OF_PIPE 1
+
+static void closeIfOpen(int fd)
+{
+    if (fd >= 0)
+        close(fd);
+}
+
+int
+PALAPI
+ForkAndExecProcess(
+           const char* filename, // filename argument to execve
+           char* const argv[],   // argv argument to execve
+           char* const envp[],   // envp argument to execve
+           const char* cwd,      // path passed to chdir in child process
+           int redirectStdin,    // whether to redirect standard input from the parent
+           int redirectStdout,   // whether to redirect standard output to the parent
+           int redirectStderr,   // whether to redirect standard error to the parent
+           int* childPid,        // the child process' id
+           int* stdinFd,         // if bRedirectStdin, the parent's fd for the child's stdin
+           int* stdoutFd,        // if bRedirectStdout, the parent's fd for the child's stdout
+           int* stderrFd)        // if bRedirectStderr, the parent's fd for the child's stderr
+{
+    int success = TRUE;
+    int stdinFds[2] = { -1, -1 }, stdoutFds[2] = { -1, -1 }, stderrFds[2] = { -1, -1 };
+    int processId = -1;
+
+    PERF_ENTRY(ForkAndExecProcess);
+    ENTRY("ForkAndExecProcess(filename=%p (%s), argv=%p, envp=%p, cwd=%p (%s), "
+           "redirectStdin=%d, redirectStdout=%d, redirectStderr=%d, "
+           "childPid=%p, stdinFd=%p, stdoutFd=%p, stderrFd=%p)\n",
+           filename, filename ? filename : "NULL", 
+           argv, envp,
+           cwd, cwd ? cwd : "NULL",
+           redirectStdin, redirectStdout, redirectStderr,
+           childPid, stdinFd, stdoutFd, stderrFd);
+
+    // Validate arguments
+    if (NULL == filename || NULL == argv || NULL == envp ||
+        NULL == stdinFd || NULL == stdoutFd || NULL == stderrFd ||
+        NULL == childPid)
+    {
+        ASSERT("%s should not be NULL\n", 
+            filename == NULL ? "filename" : 
+            argv == NULL ? "argv" : 
+            envp == NULL ? "envp" :
+            stdinFd == NULL ? "stdinFd" : 
+            stdoutFd == NULL ? "stdoutFd" : 
+            stderrFd == NULL ? "stderrFd" :
+            "childPid");
+        errno = EINVAL;
+        success = FALSE;
+        goto done;
+    }
+    if ((redirectStdin  & ~1) != 0 || 
+        (redirectStdout & ~1) != 0 ||
+        (redirectStderr & ~1) != 0)
+    {
+        ASSERT("Boolean redirect* inputs must be 0 or 1. "
+               "redirectStdin=%d redirectStdout=%d redirectStderr=%d ",
+               redirectStdin, redirectStdout, redirectStderr);
+        errno = EINVAL;
+        success = FALSE;
+        goto done;
+    }
+
+    // Open pipes for any requests to redirect stdin/stdout/stderr
+    if ((redirectStdin  && pipe(stdinFds)  != 0) ||
+        (redirectStdout && pipe(stdoutFds) != 0) ||
+        (redirectStderr && pipe(stderrFds) != 0))
+    {
+        ASSERT("pipe() failed with error %d (%s)\n", errno, strerror(errno));
+        success = FALSE;
+        goto done;
+    }
+
+    // Fork the child process
+    if ((processId = fork()) == -1)
+    {
+        ASSERT("fork() failed with error %d (%s)\n", errno, strerror(errno));
+        success = FALSE;
+        goto done;
+    }
+
+    /* From the time the child process (processId == 0) begins running from fork to when 
+     * it reaches execve, the child process must not touch anything in the PAL.  Doing so 
+     * is not safe. The parent process (processId >= 0) may continue to use the PAL.
+     */
+
+    if (processId == 0) // processId == 0 if this is child process
+    {
+        // Close the parent end of any open pipes
+        closeIfOpen(stdinFds[WRITE_END_OF_PIPE]);
+        closeIfOpen(stdoutFds[READ_END_OF_PIPE]);
+        closeIfOpen(stderrFds[READ_END_OF_PIPE]);
+
+        // For any redirections that should happen, dup the pipe descriptors onto stdin/out/err.
+        // Then close out the old pipe descriptrs, which we no longer need.
+        if ((redirectStdin  && dup2(stdinFds[READ_END_OF_PIPE],   STDIN_FILENO)  == -1) ||
+            (redirectStdout && dup2(stdoutFds[WRITE_END_OF_PIPE], STDOUT_FILENO) == -1) ||
+            (redirectStderr && dup2(stderrFds[WRITE_END_OF_PIPE], STDERR_FILENO) == -1))
+        {
+            _exit(errno != 0 ? errno : EXIT_FAILURE);
+        }
+        closeIfOpen(stdinFds[READ_END_OF_PIPE]);
+        closeIfOpen(stdoutFds[WRITE_END_OF_PIPE]);
+        closeIfOpen(stderrFds[WRITE_END_OF_PIPE]);
+
+        // Change to the designated working directory, if one was specified
+        if (NULL != cwd && chdir(cwd) == -1)
+        {
+            _exit(errno != 0 ? errno : EXIT_FAILURE);
+        }
+
+        // Finally, execute the new process.  execve will not return if it's successful.
+        execve(filename, (char**)argv, (char**)envp);
+        _exit(errno != 0 ? errno : EXIT_FAILURE); // execve failed
+    }
+
+    // This is the parent process. processId == pid of the child
+    *childPid = processId;
+    *stdinFd = stdinFds[WRITE_END_OF_PIPE];
+    *stdoutFd = stdoutFds[READ_END_OF_PIPE];
+    *stderrFd = stderrFds[READ_END_OF_PIPE];
+
+done:
+    // Regardless of success or failure, close the parent's copy of the child's end of
+    // any opened pipes.  The parent doesn't need them anymore.
+    closeIfOpen(stdinFds[READ_END_OF_PIPE]);
+    closeIfOpen(stdoutFds[WRITE_END_OF_PIPE]);
+    closeIfOpen(stderrFds[WRITE_END_OF_PIPE]);
+
+    // If we failed, close everything else and give back error values in all out arguments.
+    if (!success)
+    {
+        closeIfOpen(stdinFds[WRITE_END_OF_PIPE]);
+        closeIfOpen(stdoutFds[READ_END_OF_PIPE]);
+        closeIfOpen(stderrFds[READ_END_OF_PIPE]);
+
+        *stdinFd  = -1;
+        *stdoutFd = -1;
+        *stderrFd = -1;
+        *childPid = -1;
+    }
+
+    LOGEXIT("ForkAndExecProcess returns BOOL %d with error %d\n", success, success ? 0 : errno);
+    PERF_EXIT(ForkAndExecProcess);
+
+    return success ? 0 : -1;
+}
+
index 5a293ad..a9d11c6 100644 (file)
@@ -302,155 +302,3 @@ PAL_Random(
     return bRet;
 }
 
-
-// Support for EnsureOpenSslInitialized
-
-static const char * const libcryptoName = "libcrypto" PAL_SHLIB_SUFFIX;
-
-static void* g_OpenSslLib;
-static pthread_mutex_t g_OpenSslInitLock = PTHREAD_MUTEX_INITIALIZER;
-static pthread_mutex_t *g_OpenSslLocks;
-
-#define CRYPTO_LOCK 1
-typedef void(*locking_function)(int mode, int n, char* file, int line);
-typedef int(*CRYPTO_num_locks)(void);
-typedef void(*CRYPTO_set_locking_callback)(locking_function callback);
-
-void LockingCallback(int mode, int n, char* file, int line)
-{
-    int result;
-    if (mode & CRYPTO_LOCK)
-    {
-        result = pthread_mutex_lock(&g_OpenSslLocks[n]);
-    }
-    else
-    {
-        result = pthread_mutex_unlock(&g_OpenSslLocks[n]);
-    }
-
-    if (result != 0)
-    {
-        ASSERT("LockingCallback(%d, %d, %s, %d) failed with error %d \n",
-            mode, n, file, line, result);
-    }
-}
-
-/*++
-Function:
-  EnsureOpenSslInitialized
-
-  Used by cryptographic libraries in CoreFX to initialize
-  threading support in OpenSSL.
-
-  --*/
-
-DWORD
-PALAPI
-EnsureOpenSslInitialized()
-{
-    DWORD dwRet = 0;
-    int numLocks;
-    CRYPTO_num_locks numLocksFunc;
-    CRYPTO_set_locking_callback setCallbackFunc;
-    int locksInitialized = 0;
-
-    PERF_ENTRY(EnsureOpenSslInitialized);
-    ENTRY("EnsureOpenSslInitialized()\n");
-
-    pthread_mutex_lock(&g_OpenSslInitLock);
-
-    if (g_OpenSslLocks != NULL)
-    {
-        // Already initialized; nothing more to do.
-        goto done;
-    }
-
-    // Open the libcrypto library
-    g_OpenSslLib = dlopen(libcryptoName, RTLD_NOW);
-    if (g_OpenSslLib == NULL)
-    {
-        // CoreCLR does not require libcrypto as a dependency,
-        // even though various libraries might.
-        dwRet = 1;
-        goto done;
-    }
-
-    // Get the functions we need from OpenSSL
-    numLocksFunc = (CRYPTO_num_locks) dlsym(g_OpenSslLib, "CRYPTO_num_locks");
-    setCallbackFunc = (CRYPTO_set_locking_callback) dlsym(g_OpenSslLib, "CRYPTO_set_locking_callback");
-    if (numLocksFunc == NULL || setCallbackFunc == NULL)
-    {
-        ASSERT("Unable to find CRYPTO_num_locks or CRYPTO_set_locking_callback\n");
-        dwRet = 2;
-        goto done;
-    }
-
-    // Determine how many locks are needed
-    numLocks = numLocksFunc();
-    if (numLocks <= 0)
-    {
-        ASSERT("CRYPTO_num_locks returned invalid value: %d\n", numLocks);
-        dwRet = 3;
-        goto done;
-    }
-
-    // Create the locks array
-    g_OpenSslLocks = (pthread_mutex_t*) PAL_malloc(sizeof(pthread_mutex_t) * numLocks);
-    if (g_OpenSslLocks == NULL)
-    {
-        ASSERT("PAL_malloc failed\n");
-        dwRet = 4;
-        goto done;
-    }
-
-    // Initialize each of the locks
-    for (locksInitialized = 0; locksInitialized < numLocks; locksInitialized++)
-    {
-        if (pthread_mutex_init(&g_OpenSslLocks[locksInitialized], NULL) != 0)
-        {
-            ASSERT("pthread_mutex_init failed\n");
-            dwRet = 5;
-            goto done;
-        }
-    }
-
-    // Initialize the callback
-    setCallbackFunc((locking_function) LockingCallback);
-
-done:
-    if (dwRet != 0)
-    {
-        // Cleanup on failure
-
-        if (g_OpenSslLocks != NULL)
-        {
-            for (int i = locksInitialized - 1; i >= 0; i--)
-            {
-                if (pthread_mutex_destroy(&g_OpenSslLocks[i]) != 0)
-                {
-                    ASSERT("Unable to pthread_mutex_destroy while cleaning up\n");
-                }
-            }
-            PAL_free(g_OpenSslLocks);
-            g_OpenSslLocks = NULL;
-        }
-
-        if (g_OpenSslLib != NULL)
-        {
-            if (dlclose(g_OpenSslLib) != 0)
-            {
-                ASSERT("Unable to close OpenSSL with dlerror \"%s\" \n", dlerror());
-            }
-            g_OpenSslLib = NULL;
-        }
-    }
-
-    pthread_mutex_unlock(&g_OpenSslInitLock);
-
-    // If successful, keep OpenSSL library open and initialized
-
-    LOGEXIT("EnsureOpenSslInitialized returns DWORD %u\n", dwRet);
-    PERF_EXIT(EnsureOpenSslInitialized);
-    return dwRet;
-}
-
index afd7628..657f0ca 100644 (file)
@@ -6,6 +6,7 @@ add_subdirectory(CloseHandle)
 add_subdirectory(CreatePipe)
 add_subdirectory(EnsureOpenSslInitialized)
 add_subdirectory(FlushInstructionCache)
+add_subdirectory(ForkAndExecProcess)
 add_subdirectory(FormatMessageW)
 add_subdirectory(FreeEnvironmentStringsW)
 add_subdirectory(GetCommandLineW)
index f6ebb69..c4155d2 100644 (file)
@@ -15,6 +15,7 @@
 **=========================================================*/
 
 #include <palsuite.h>
+#include <pal_corefx.h>
 #include <dlfcn.h>
 
 typedef void* (*CRYPTO_get_locking_callback)();
diff --git a/src/pal/tests/palsuite/miscellaneous/ForkAndExecProcess/CMakeLists.txt b/src/pal/tests/palsuite/miscellaneous/ForkAndExecProcess/CMakeLists.txt
new file mode 100644 (file)
index 0000000..f6aa0cb
--- /dev/null
@@ -0,0 +1,4 @@
+cmake_minimum_required(VERSION 2.8.12.2)
+
+add_subdirectory(test1)
+
diff --git a/src/pal/tests/palsuite/miscellaneous/ForkAndExecProcess/test1/CMakeLists.txt b/src/pal/tests/palsuite/miscellaneous/ForkAndExecProcess/test1/CMakeLists.txt
new file mode 100644 (file)
index 0000000..7134827
--- /dev/null
@@ -0,0 +1,19 @@
+cmake_minimum_required(VERSION 2.8.12.2)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(SOURCES
+  test.c
+)
+
+add_executable(paltest_forkandexecprocess_test1
+  ${SOURCES}
+)
+
+add_dependencies(paltest_forkandexecprocess_test1 CoreClrPal)
+
+target_link_libraries(paltest_forkandexecprocess_test1
+  pthread
+  m
+  CoreClrPal
+)
diff --git a/src/pal/tests/palsuite/miscellaneous/ForkAndExecProcess/test1/test.c b/src/pal/tests/palsuite/miscellaneous/ForkAndExecProcess/test1/test.c
new file mode 100644 (file)
index 0000000..94a8890
--- /dev/null
@@ -0,0 +1,96 @@
+//
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information. 
+//
+
+/*============================================================
+**
+** Source:  
+**
+** Source : test1.c
+**
+** Purpose: Test for ForkAndExecProcess function
+**
+**
+**=========================================================*/
+
+#include <palsuite.h>
+#include <pal_corefx.h>
+#include "string.h"
+
+extern char** environ;
+
+int __cdecl main(int argc, char *argv[]) {
+
+    int childPid = -1, childStdinFd = -1, childStdoutFd = -1, childStderrFd = -1;
+    FILE *childStdin = NULL, *childStdout = NULL, *childStderr = NULL;
+    char* childArgv[3] = { argv[0], "child", NULL };
+    int c = 0;
+
+    // Initialize the PAL and return FAILURE if this fails
+    if ((PAL_Initialize(argc, argv)) != 0)
+    {
+        return FAIL;
+    }
+
+    // If this is the child process, it'll have an argument.
+    if (argc > 1)
+    {
+        // This is the child.  Receive 'a' from the parent,
+        // then send back 'b' on stdout and 'c' on stderr.
+        if ((c = getc(stdin)) == EOF ||
+            c != 'a' ||
+            fputc('b', stdout) == EOF ||
+            fflush(stdout) != 0 ||
+            fputc('c', stderr) == EOF ||
+            fflush(stdout) != 0)
+        {
+            Fail("Error: Child process failed");
+        }
+        goto done;
+    }
+
+    // Now fork/exec the child process, with the same executable but an extra argument
+    if (ForkAndExecProcess(argv[0], childArgv, environ, NULL,
+                           1, 1, 1,
+                           &childPid, &childStdinFd, &childStdoutFd, &childStderrFd) != 0)
+    {
+        Fail("Error: ForkAndExecProces failed with errno %d (%s)\n", errno, strerror(errno));
+    }
+    if (childPid < 0 || childStdinFd < 0 || childStdoutFd < 0 || childStderrFd < 0)
+    {
+        Fail("Error: ForkAndExecProcess returned childpid=%d, stdinFd=%d, stdoutFd=%d, stderrFd=%d", 
+            childPid, childStdinFd, childStdoutFd, childStderrFd);
+    }
+
+    // Open files for the child's redirected stdin, stdout, and stderr
+    if ((childStdin = _fdopen(childStdinFd, "w")) == NULL ||
+        (childStdout = _fdopen(childStdoutFd, "r")) == NULL ||
+        (childStderr = _fdopen(childStderrFd, "r")) == NULL)
+    {
+        Fail("Error: Opening FILE* for stdin, stdout, or stderr resulted in errno %d (%s)", 
+            errno, strerror(errno));
+    }
+
+    // Send 'a' to the child
+    if (fputc('a', childStdin) == EOF ||
+        fflush(childStdin) != 0)
+    {
+        Fail("Writing to the child process failed with errno %d (%s)", errno, strerror(errno));
+    }
+
+    // Then receive 'b' from the child's stdout, then 'c' from stderr
+    if ((c = getc(childStdout)) != 'b')
+    {
+        Fail("Received '%c' from child's stdout; expected 'b'", c);
+    }
+    if ((c = getc(childStderr)) != 'c')
+    {
+        Fail("Received '%c' from child's stderr; expected 'c'", c);
+    }
+
+done:
+    PAL_Terminate();
+    return PASS;
+}
+
diff --git a/src/pal/tests/palsuite/miscellaneous/ForkAndExecProcess/test1/testinfo.dat b/src/pal/tests/palsuite/miscellaneous/ForkAndExecProcess/test1/testinfo.dat
new file mode 100644 (file)
index 0000000..ae31bbc
--- /dev/null
@@ -0,0 +1,18 @@
+#
+# Copyright (c) Microsoft Corporation.  All rights reserved.
+#
+
+Version = 1.0
+Section = Miscellaneous
+Function = ForkAndExecProcess
+Name = Positive Test for ForkAndExecProcess
+TYPE = DEFAULT
+EXE1 = test
+Description
+= Validates that ForkAndExecProcess appropriately
+= creates a child process after opening any necessary
+= pipes for redirects.  The test process creates
+= a child process, sends it a value on its stdin,
+= and then expects to receive back a value on its
+= stdout and stderr.
+
index 82288d7..6b47fd5 100644 (file)
@@ -645,6 +645,7 @@ miscellaneous/CloseHandle/test2/paltest_closehandle_test2
 miscellaneous/CreatePipe/test1/paltest_createpipe_test1
 miscellaneous/EnsureOpenSslInitialized/test1/paltest_ensureopensslinitialized_test1
 miscellaneous/FlushInstructionCache/test1/paltest_flushinstructioncache_test1
+miscellaneous/ForkAndExecProcess/test1/paltest_forkandexecprocess_test1
 miscellaneous/FormatMessageW/test1/paltest_formatmessagew_test1
 miscellaneous/FormatMessageW/test2/paltest_formatmessagew_test2
 miscellaneous/FormatMessageW/test3/paltest_formatmessagew_test3