diff --git a/test/main.c b/test/main.c index f6c50cc..4b5bbcd 100644 --- a/test/main.c +++ b/test/main.c @@ -24,5 +24,50 @@ // For more information, please refer to #include "utest.h" +#include "process.h" + +UTEST(utest_cmdline, filter_with_list) { + struct process_s process; + const char *command[3] = {"utest_test", "--list-tests", 0}; + int return_code; + FILE *stdout_file; + size_t index; + +// 64k should be enough for anyone +#define MAX_CHARS (64 * 1024) + char buffer[MAX_CHARS] = {0}; + + ASSERT_EQ(0, process_create(command, process_option_combined_stdout_stderr, + &process)); + + stdout_file = process_stdout(&process); + + for (index = 0; index < utest_state.tests_length; index++) { + if (buffer != fgets(buffer, MAX_CHARS, stdout_file)) { + break; + } + +#if defined(__clang__) +#if __has_warning("-Wdisabled-macro-expansion") +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdisabled-macro-expansion" +#endif +#endif + + ASSERT_EQ(0, strncmp(buffer, utest_state.tests[index].name, + strlen(utest_state.tests[index].name))); + +#if defined(__clang__) +#if __has_warning("-Wdisabled-macro-expansion") +#pragma clang diagnostic pop +#endif +#endif + } + + ASSERT_EQ(0, process_join(&process, &return_code)); + ASSERT_EQ(0, return_code); + + ASSERT_EQ(0, process_destroy(&process)); +} UTEST_MAIN() diff --git a/test/main.c b/test/main.c index f6c50cc..4b5bbcd 100644 --- a/test/main.c +++ b/test/main.c @@ -24,5 +24,50 @@ // For more information, please refer to #include "utest.h" +#include "process.h" + +UTEST(utest_cmdline, filter_with_list) { + struct process_s process; + const char *command[3] = {"utest_test", "--list-tests", 0}; + int return_code; + FILE *stdout_file; + size_t index; + +// 64k should be enough for anyone +#define MAX_CHARS (64 * 1024) + char buffer[MAX_CHARS] = {0}; + + ASSERT_EQ(0, process_create(command, process_option_combined_stdout_stderr, + &process)); + + stdout_file = process_stdout(&process); + + for (index = 0; index < utest_state.tests_length; index++) { + if (buffer != fgets(buffer, MAX_CHARS, stdout_file)) { + break; + } + +#if defined(__clang__) +#if __has_warning("-Wdisabled-macro-expansion") +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdisabled-macro-expansion" +#endif +#endif + + ASSERT_EQ(0, strncmp(buffer, utest_state.tests[index].name, + strlen(utest_state.tests[index].name))); + +#if defined(__clang__) +#if __has_warning("-Wdisabled-macro-expansion") +#pragma clang diagnostic pop +#endif +#endif + } + + ASSERT_EQ(0, process_join(&process, &return_code)); + ASSERT_EQ(0, return_code); + + ASSERT_EQ(0, process_destroy(&process)); +} UTEST_MAIN() diff --git a/test/process.h b/test/process.h new file mode 100644 index 0000000..d3e1a97 --- /dev/null +++ b/test/process.h @@ -0,0 +1,568 @@ +/* + The latest version of this library is available on GitHub; + https://github.com/sheredom/process.h +*/ + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ + +#ifndef SHEREDOM_PROCESS_H_INCLUDED +#define SHEREDOM_PROCESS_H_INCLUDED + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(_MSC_VER) +#pragma warning(push, 1) +#endif + +#include + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if !defined(_MSC_VER) +#include +#include +#include +#include +#endif + +#if defined(_MSC_VER) +#ifdef _WIN64 +typedef __int64 intptr_t; +typedef unsigned __int64 size_t; +#else +typedef int intptr_t; +typedef unsigned int size_t; +#endif + +typedef struct _PROCESS_INFORMATION *LPPROCESS_INFORMATION; +typedef struct _SECURITY_ATTRIBUTES *LPSECURITY_ATTRIBUTES; +typedef struct _STARTUPINFOA *LPSTARTUPINFOA; + +#pragma warning(push, 1) +struct process_process_information_s { + void *hProcess; + void *hThread; + unsigned long dwProcessId; + unsigned long dwThreadId; +}; + +struct process_security_attributes_s { + unsigned long nLength; + void *lpSecurityDescriptor; + int bInheritHandle; +}; + +struct process_startup_info_s { + unsigned long cb; + char *lpReserved; + char *lpDesktop; + char *lpTitle; + unsigned long dwX; + unsigned long dwY; + unsigned long dwXSize; + unsigned long dwYSize; + unsigned long dwXCountChars; + unsigned long dwYCountChars; + unsigned long dwFillAttribute; + unsigned long dwFlags; + unsigned short wShowWindow; + unsigned short cbReserved2; + unsigned char *lpReserved2; + void *hStdInput; + void *hStdOutput; + void *hStdError; +}; +#pragma warning(pop) + +__declspec(dllimport) int __stdcall SetHandleInformation(void *, unsigned long, + unsigned long); +__declspec(dllimport) int __stdcall CreatePipe(void **, void **, + LPSECURITY_ATTRIBUTES, + unsigned long); +__declspec(dllimport) int __stdcall CreateProcessA( + const char *, char *, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, int, + unsigned long, void *, const char *, LPSTARTUPINFOA, LPPROCESS_INFORMATION); +__declspec(dllimport) int __stdcall CloseHandle(void *); +__declspec(dllimport) unsigned long __stdcall WaitForSingleObject( + void *, unsigned long); +__declspec(dllimport) int __stdcall GetExitCodeProcess( + void *, unsigned long *lpExitCode); +__declspec(dllimport) int __cdecl _open_osfhandle(intptr_t, int); +void *__cdecl _alloca(size_t); +#endif + +#if defined(__clang__) || defined(__GNUC__) +#define process_pure __attribute__((pure)) +#define process_weak __attribute__((weak)) +#elif defined(_MSC_VER) +#define process_pure +#define process_weak __inline +#else +#error Non clang, non gcc, non MSVC compiler found! +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif +struct process_s { + FILE *stdin_file; + FILE *stdout_file; + FILE *stderr_file; + +#if defined(_MSC_VER) + void *hProcess; + void *hStdInput; + void *hStdOutput; + void *hStdError; +#else + pid_t child; +#endif +}; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +enum process_option_e { + // stdout and stderr are the same FILE. + process_option_combined_stdout_stderr = 0x1, + + // The child process should inherit the environment variables of the parent. + process_option_inherit_environment = 0x2 +}; + +/// @brief Create a process. +/// @param command_line An array of strings for the command line to execute for +/// this process. The last element must be NULL to signify the end of the array. +/// @param options A bit field of process_option_e's to pass. +/// @param out_process The newly created process. +/// @return On success 0 is returned. +process_weak int process_create(const char *const command_line[], int options, + struct process_s *const out_process); + +/// @brief Get the standard input file for a process. +/// @param process The process to query. +/// @return The file for standard input of the process. +/// +/// The file returned can be written to by the parent process to feed data to +/// the standard input of the process. +process_pure process_weak FILE * +process_stdin(const struct process_s *const process); + +/// @brief Get the standard output file for a process. +/// @param process The process to query. +/// @return The file for standard output of the process. +/// +/// The file returned can be read from by the parent process to read data from +/// the standard output of the child process. +process_pure process_weak FILE * +process_stdout(const struct process_s *const process); + +/// @brief Get the standard error file for a process. +/// @param process The process to query. +/// @return The file for standard error of the process. +/// +/// The file returned can be read from by the parent process to read data from +/// the standard error of the child process. +/// +/// If the process was created with the process_option_combined_stdout_stderr +/// option bit set, this function will return NULL, and the process_stdout +/// function should be used for both the standard output and error combined. +process_pure process_weak FILE * +process_stderr(const struct process_s *const process); + +/// @brief Wait for a process to finish execution. +/// @param process The process to wait for. +/// @param out_return_code The return code of the returned process (can be +/// NULL). +/// @return On success 0 is returned. +process_weak int process_join(struct process_s *const process, + int *const out_return_code); + +/// @brief Destroy a previously created process. +/// @param process The process to destroy. +/// +/// If the process to be destroyed had not finished execution, it may out live +/// the parent process. +process_weak int process_destroy(struct process_s *const process); + +int process_create(const char *const commandLine[], int options, + struct process_s *const out_process) { +#if defined(_MSC_VER) + int fd; + void *rd, *wr; + char *commandLineCombined; + size_t len; + int i, j; + const unsigned long startFUseStdHandles = 0x00000100; + const unsigned long handleFlagInherit = 0x00000001; + struct process_process_information_s processInfo; + struct process_security_attributes_s saAttr = {sizeof(saAttr), 0, 1}; + char *environment = 0; + struct process_startup_info_s startInfo = {0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + startInfo.cb = sizeof(startInfo); + startInfo.dwFlags = startFUseStdHandles; + + if (process_option_inherit_environment != + (options & process_option_inherit_environment)) { + environment = "\0\0"; + } + + if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + return -1; + } + + if (!SetHandleInformation(wr, handleFlagInherit, 0)) { + return -1; + } + + fd = _open_osfhandle((intptr_t)wr, 0); + + if (-1 != fd) { + out_process->stdin_file = _fdopen(fd, "wb"); + + if (0 == out_process->stdin_file) { + return -1; + } + } + + startInfo.hStdInput = rd; + + if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + return -1; + } + + if (!SetHandleInformation(rd, handleFlagInherit, 0)) { + return -1; + } + + fd = _open_osfhandle((intptr_t)rd, 0); + + if (-1 != fd) { + out_process->stdout_file = _fdopen(fd, "rb"); + + if (0 == out_process->stdout_file) { + return -1; + } + } + + startInfo.hStdOutput = wr; + + if (process_option_combined_stdout_stderr == + (options & process_option_combined_stdout_stderr)) { + out_process->stderr_file = out_process->stdout_file; + startInfo.hStdError = startInfo.hStdOutput; + } else { + if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + return -1; + } + + if (!SetHandleInformation(rd, handleFlagInherit, 0)) { + return -1; + } + + fd = _open_osfhandle((intptr_t)rd, 0); + + if (-1 != fd) { + out_process->stderr_file = _fdopen(fd, "rb"); + + if (0 == out_process->stderr_file) { + return -1; + } + } + + startInfo.hStdError = wr; + } + + // Combine commandLine together into a single string + len = 0; + for (i = 0; commandLine[i]; i++) { + // For the ' ' between items and trailing '\0' + len++; + + for (j = 0; '\0' != commandLine[i][j]; j++) { + len++; + } + } + + commandLineCombined = (char *)_alloca(len); + + if (!commandLineCombined) { + return -1; + } + + // Gonna re-use len to store the write index into commandLineCombined + len = 0; + + for (i = 0; commandLine[i]; i++) { + if (0 != i) { + commandLineCombined[len++] = ' '; + } + + for (j = 0; '\0' != commandLine[i][j]; j++) { + commandLineCombined[len++] = commandLine[i][j]; + } + } + + commandLineCombined[len] = '\0'; + + if (!CreateProcessA(NULL, + commandLineCombined, // command line + NULL, // process security attributes + NULL, // primary thread security attributes + 1, // handles are inherited + 0, // creation flags + environment, // use parent's environment + NULL, // use parent's current directory + (LPSTARTUPINFOA)&startInfo, // STARTUPINFO pointer + (LPPROCESS_INFORMATION)&processInfo)) { + return -1; + } + + out_process->hProcess = processInfo.hProcess; + + out_process->hStdInput = startInfo.hStdInput; + out_process->hStdOutput = startInfo.hStdOutput; + out_process->hStdError = startInfo.hStdError; + + // We don't need the handle of the primary thread in the called process. + CloseHandle(processInfo.hThread); + + return 0; +#else + int stdinfd[2]; + int stdoutfd[2]; + int stderrfd[2]; + pid_t child; + + if (0 != pipe(stdinfd)) { + return -1; + } + + if (0 != pipe(stdoutfd)) { + return -1; + } + + if (process_option_combined_stdout_stderr != + (options & process_option_combined_stdout_stderr)) { + if (0 != pipe(stderrfd)) { + return -1; + } + } + + child = fork(); + + if (-1 == child) { + return -1; + } + + if (0 == child) { + // Close the stdin write end + close(stdinfd[1]); + // Map the read end to stdin + dup2(stdinfd[0], STDIN_FILENO); + + // Close the stdout read end + close(stdoutfd[0]); + // Map the write end to stdout + dup2(stdoutfd[1], STDOUT_FILENO); + + if (process_option_combined_stdout_stderr == + (options & process_option_combined_stdout_stderr)) { + dup2(STDOUT_FILENO, STDERR_FILENO); + } else { + // Close the stderr read end + close(stderrfd[0]); + // Map the write end to stdout + dup2(stderrfd[1], STDERR_FILENO); + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-qual" +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif + if (process_option_inherit_environment != + (options & process_option_inherit_environment)) { + char *const environment[1] = {0}; + exit(execve(commandLine[0], (char *const *)commandLine, environment)); + } else { + exit(execvp(commandLine[0], (char *const *)commandLine)); + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } else { + // Close the stdin read end + close(stdinfd[0]); + // Store the stdin write end + out_process->stdin_file = fdopen(stdinfd[1], "wb"); + + // Close the stdout write end + close(stdoutfd[1]); + // Store the stdout read end + out_process->stdout_file = fdopen(stdoutfd[0], "rb"); + + if (process_option_combined_stdout_stderr == + (options & process_option_combined_stdout_stderr)) { + out_process->stderr_file = out_process->stdout_file; + } else { + // Close the stderr write end + close(stderrfd[1]); + // Store the stderr read end + out_process->stderr_file = fdopen(stderrfd[0], "rb"); + } + + // Store the child's pid + out_process->child = child; + + return 0; + } +#endif +} + +FILE *process_stdin(const struct process_s *const process) { + return process->stdin_file; +} + +FILE *process_stdout(const struct process_s *const process) { + return process->stdout_file; +} + +FILE *process_stderr(const struct process_s *const process) { + if (process->stdout_file != process->stderr_file) { + return process->stderr_file; + } else { + return 0; + } +} + +int process_join(struct process_s *const process, int *const out_return_code) { +#if defined(_MSC_VER) + const unsigned long infinite = 0xFFFFFFFF; + + if (0 != process->stdin_file) { + fclose(process->stdin_file); + process->stdin_file = 0; + } + if (0 != process->hStdInput) { + CloseHandle(process->hStdInput); + process->hStdInput = NULL; + } + + WaitForSingleObject(process->hProcess, infinite); + + if (0 != process->hStdOutput) { + CloseHandle(process->hStdOutput); + if (process->hStdError == process->hStdOutput) { + process->hStdError = NULL; + } + process->hStdOutput = NULL; + } + if (0 != process->hStdError) { + CloseHandle(process->hStdError); + process->hStdError = NULL; + } + + if (out_return_code) { + if (!GetExitCodeProcess(process->hProcess, + (unsigned long *)out_return_code)) { + return -1; + } + } + + return 0; +#else + int status; + + if (0 != process->stdin_file) { + fclose(process->stdin_file); + process->stdin_file = 0; + } + + if (process->child != waitpid(process->child, &status, 0)) { + return -1; + } + + if (out_return_code) { + if (WIFEXITED(status)) { + *out_return_code = WEXITSTATUS(status); + } else { + *out_return_code = 0; + } + } + + return 0; +#endif +} + +int process_destroy(struct process_s *const process) { + if (0 != process->stdin_file) { + fclose(process->stdin_file); + } + + fclose(process->stdout_file); + + if (process->stdout_file != process->stderr_file) { + fclose(process->stderr_file); + } + +#if defined(_MSC_VER) + CloseHandle(process->hProcess); + + if (0 != process->hStdInput) { + CloseHandle(process->hStdInput); + process->hStdInput = NULL; + } + if (0 != process->hStdOutput) { + CloseHandle(process->hStdOutput); + if (process->hStdError == process->hStdOutput) { + process->hStdError = NULL; + } + process->hStdOutput = NULL; + } + if (0 != process->hStdError) { + CloseHandle(process->hStdError); + process->hStdError = NULL; + } +#endif + + return 0; +} + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif /* SHEREDOM_PROCESS_H_INCLUDED */ diff --git a/test/main.c b/test/main.c index f6c50cc..4b5bbcd 100644 --- a/test/main.c +++ b/test/main.c @@ -24,5 +24,50 @@ // For more information, please refer to #include "utest.h" +#include "process.h" + +UTEST(utest_cmdline, filter_with_list) { + struct process_s process; + const char *command[3] = {"utest_test", "--list-tests", 0}; + int return_code; + FILE *stdout_file; + size_t index; + +// 64k should be enough for anyone +#define MAX_CHARS (64 * 1024) + char buffer[MAX_CHARS] = {0}; + + ASSERT_EQ(0, process_create(command, process_option_combined_stdout_stderr, + &process)); + + stdout_file = process_stdout(&process); + + for (index = 0; index < utest_state.tests_length; index++) { + if (buffer != fgets(buffer, MAX_CHARS, stdout_file)) { + break; + } + +#if defined(__clang__) +#if __has_warning("-Wdisabled-macro-expansion") +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdisabled-macro-expansion" +#endif +#endif + + ASSERT_EQ(0, strncmp(buffer, utest_state.tests[index].name, + strlen(utest_state.tests[index].name))); + +#if defined(__clang__) +#if __has_warning("-Wdisabled-macro-expansion") +#pragma clang diagnostic pop +#endif +#endif + } + + ASSERT_EQ(0, process_join(&process, &return_code)); + ASSERT_EQ(0, return_code); + + ASSERT_EQ(0, process_destroy(&process)); +} UTEST_MAIN() diff --git a/test/process.h b/test/process.h new file mode 100644 index 0000000..d3e1a97 --- /dev/null +++ b/test/process.h @@ -0,0 +1,568 @@ +/* + The latest version of this library is available on GitHub; + https://github.com/sheredom/process.h +*/ + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ + +#ifndef SHEREDOM_PROCESS_H_INCLUDED +#define SHEREDOM_PROCESS_H_INCLUDED + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(_MSC_VER) +#pragma warning(push, 1) +#endif + +#include + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if !defined(_MSC_VER) +#include +#include +#include +#include +#endif + +#if defined(_MSC_VER) +#ifdef _WIN64 +typedef __int64 intptr_t; +typedef unsigned __int64 size_t; +#else +typedef int intptr_t; +typedef unsigned int size_t; +#endif + +typedef struct _PROCESS_INFORMATION *LPPROCESS_INFORMATION; +typedef struct _SECURITY_ATTRIBUTES *LPSECURITY_ATTRIBUTES; +typedef struct _STARTUPINFOA *LPSTARTUPINFOA; + +#pragma warning(push, 1) +struct process_process_information_s { + void *hProcess; + void *hThread; + unsigned long dwProcessId; + unsigned long dwThreadId; +}; + +struct process_security_attributes_s { + unsigned long nLength; + void *lpSecurityDescriptor; + int bInheritHandle; +}; + +struct process_startup_info_s { + unsigned long cb; + char *lpReserved; + char *lpDesktop; + char *lpTitle; + unsigned long dwX; + unsigned long dwY; + unsigned long dwXSize; + unsigned long dwYSize; + unsigned long dwXCountChars; + unsigned long dwYCountChars; + unsigned long dwFillAttribute; + unsigned long dwFlags; + unsigned short wShowWindow; + unsigned short cbReserved2; + unsigned char *lpReserved2; + void *hStdInput; + void *hStdOutput; + void *hStdError; +}; +#pragma warning(pop) + +__declspec(dllimport) int __stdcall SetHandleInformation(void *, unsigned long, + unsigned long); +__declspec(dllimport) int __stdcall CreatePipe(void **, void **, + LPSECURITY_ATTRIBUTES, + unsigned long); +__declspec(dllimport) int __stdcall CreateProcessA( + const char *, char *, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, int, + unsigned long, void *, const char *, LPSTARTUPINFOA, LPPROCESS_INFORMATION); +__declspec(dllimport) int __stdcall CloseHandle(void *); +__declspec(dllimport) unsigned long __stdcall WaitForSingleObject( + void *, unsigned long); +__declspec(dllimport) int __stdcall GetExitCodeProcess( + void *, unsigned long *lpExitCode); +__declspec(dllimport) int __cdecl _open_osfhandle(intptr_t, int); +void *__cdecl _alloca(size_t); +#endif + +#if defined(__clang__) || defined(__GNUC__) +#define process_pure __attribute__((pure)) +#define process_weak __attribute__((weak)) +#elif defined(_MSC_VER) +#define process_pure +#define process_weak __inline +#else +#error Non clang, non gcc, non MSVC compiler found! +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif +struct process_s { + FILE *stdin_file; + FILE *stdout_file; + FILE *stderr_file; + +#if defined(_MSC_VER) + void *hProcess; + void *hStdInput; + void *hStdOutput; + void *hStdError; +#else + pid_t child; +#endif +}; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +enum process_option_e { + // stdout and stderr are the same FILE. + process_option_combined_stdout_stderr = 0x1, + + // The child process should inherit the environment variables of the parent. + process_option_inherit_environment = 0x2 +}; + +/// @brief Create a process. +/// @param command_line An array of strings for the command line to execute for +/// this process. The last element must be NULL to signify the end of the array. +/// @param options A bit field of process_option_e's to pass. +/// @param out_process The newly created process. +/// @return On success 0 is returned. +process_weak int process_create(const char *const command_line[], int options, + struct process_s *const out_process); + +/// @brief Get the standard input file for a process. +/// @param process The process to query. +/// @return The file for standard input of the process. +/// +/// The file returned can be written to by the parent process to feed data to +/// the standard input of the process. +process_pure process_weak FILE * +process_stdin(const struct process_s *const process); + +/// @brief Get the standard output file for a process. +/// @param process The process to query. +/// @return The file for standard output of the process. +/// +/// The file returned can be read from by the parent process to read data from +/// the standard output of the child process. +process_pure process_weak FILE * +process_stdout(const struct process_s *const process); + +/// @brief Get the standard error file for a process. +/// @param process The process to query. +/// @return The file for standard error of the process. +/// +/// The file returned can be read from by the parent process to read data from +/// the standard error of the child process. +/// +/// If the process was created with the process_option_combined_stdout_stderr +/// option bit set, this function will return NULL, and the process_stdout +/// function should be used for both the standard output and error combined. +process_pure process_weak FILE * +process_stderr(const struct process_s *const process); + +/// @brief Wait for a process to finish execution. +/// @param process The process to wait for. +/// @param out_return_code The return code of the returned process (can be +/// NULL). +/// @return On success 0 is returned. +process_weak int process_join(struct process_s *const process, + int *const out_return_code); + +/// @brief Destroy a previously created process. +/// @param process The process to destroy. +/// +/// If the process to be destroyed had not finished execution, it may out live +/// the parent process. +process_weak int process_destroy(struct process_s *const process); + +int process_create(const char *const commandLine[], int options, + struct process_s *const out_process) { +#if defined(_MSC_VER) + int fd; + void *rd, *wr; + char *commandLineCombined; + size_t len; + int i, j; + const unsigned long startFUseStdHandles = 0x00000100; + const unsigned long handleFlagInherit = 0x00000001; + struct process_process_information_s processInfo; + struct process_security_attributes_s saAttr = {sizeof(saAttr), 0, 1}; + char *environment = 0; + struct process_startup_info_s startInfo = {0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + startInfo.cb = sizeof(startInfo); + startInfo.dwFlags = startFUseStdHandles; + + if (process_option_inherit_environment != + (options & process_option_inherit_environment)) { + environment = "\0\0"; + } + + if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + return -1; + } + + if (!SetHandleInformation(wr, handleFlagInherit, 0)) { + return -1; + } + + fd = _open_osfhandle((intptr_t)wr, 0); + + if (-1 != fd) { + out_process->stdin_file = _fdopen(fd, "wb"); + + if (0 == out_process->stdin_file) { + return -1; + } + } + + startInfo.hStdInput = rd; + + if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + return -1; + } + + if (!SetHandleInformation(rd, handleFlagInherit, 0)) { + return -1; + } + + fd = _open_osfhandle((intptr_t)rd, 0); + + if (-1 != fd) { + out_process->stdout_file = _fdopen(fd, "rb"); + + if (0 == out_process->stdout_file) { + return -1; + } + } + + startInfo.hStdOutput = wr; + + if (process_option_combined_stdout_stderr == + (options & process_option_combined_stdout_stderr)) { + out_process->stderr_file = out_process->stdout_file; + startInfo.hStdError = startInfo.hStdOutput; + } else { + if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + return -1; + } + + if (!SetHandleInformation(rd, handleFlagInherit, 0)) { + return -1; + } + + fd = _open_osfhandle((intptr_t)rd, 0); + + if (-1 != fd) { + out_process->stderr_file = _fdopen(fd, "rb"); + + if (0 == out_process->stderr_file) { + return -1; + } + } + + startInfo.hStdError = wr; + } + + // Combine commandLine together into a single string + len = 0; + for (i = 0; commandLine[i]; i++) { + // For the ' ' between items and trailing '\0' + len++; + + for (j = 0; '\0' != commandLine[i][j]; j++) { + len++; + } + } + + commandLineCombined = (char *)_alloca(len); + + if (!commandLineCombined) { + return -1; + } + + // Gonna re-use len to store the write index into commandLineCombined + len = 0; + + for (i = 0; commandLine[i]; i++) { + if (0 != i) { + commandLineCombined[len++] = ' '; + } + + for (j = 0; '\0' != commandLine[i][j]; j++) { + commandLineCombined[len++] = commandLine[i][j]; + } + } + + commandLineCombined[len] = '\0'; + + if (!CreateProcessA(NULL, + commandLineCombined, // command line + NULL, // process security attributes + NULL, // primary thread security attributes + 1, // handles are inherited + 0, // creation flags + environment, // use parent's environment + NULL, // use parent's current directory + (LPSTARTUPINFOA)&startInfo, // STARTUPINFO pointer + (LPPROCESS_INFORMATION)&processInfo)) { + return -1; + } + + out_process->hProcess = processInfo.hProcess; + + out_process->hStdInput = startInfo.hStdInput; + out_process->hStdOutput = startInfo.hStdOutput; + out_process->hStdError = startInfo.hStdError; + + // We don't need the handle of the primary thread in the called process. + CloseHandle(processInfo.hThread); + + return 0; +#else + int stdinfd[2]; + int stdoutfd[2]; + int stderrfd[2]; + pid_t child; + + if (0 != pipe(stdinfd)) { + return -1; + } + + if (0 != pipe(stdoutfd)) { + return -1; + } + + if (process_option_combined_stdout_stderr != + (options & process_option_combined_stdout_stderr)) { + if (0 != pipe(stderrfd)) { + return -1; + } + } + + child = fork(); + + if (-1 == child) { + return -1; + } + + if (0 == child) { + // Close the stdin write end + close(stdinfd[1]); + // Map the read end to stdin + dup2(stdinfd[0], STDIN_FILENO); + + // Close the stdout read end + close(stdoutfd[0]); + // Map the write end to stdout + dup2(stdoutfd[1], STDOUT_FILENO); + + if (process_option_combined_stdout_stderr == + (options & process_option_combined_stdout_stderr)) { + dup2(STDOUT_FILENO, STDERR_FILENO); + } else { + // Close the stderr read end + close(stderrfd[0]); + // Map the write end to stdout + dup2(stderrfd[1], STDERR_FILENO); + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-qual" +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif + if (process_option_inherit_environment != + (options & process_option_inherit_environment)) { + char *const environment[1] = {0}; + exit(execve(commandLine[0], (char *const *)commandLine, environment)); + } else { + exit(execvp(commandLine[0], (char *const *)commandLine)); + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } else { + // Close the stdin read end + close(stdinfd[0]); + // Store the stdin write end + out_process->stdin_file = fdopen(stdinfd[1], "wb"); + + // Close the stdout write end + close(stdoutfd[1]); + // Store the stdout read end + out_process->stdout_file = fdopen(stdoutfd[0], "rb"); + + if (process_option_combined_stdout_stderr == + (options & process_option_combined_stdout_stderr)) { + out_process->stderr_file = out_process->stdout_file; + } else { + // Close the stderr write end + close(stderrfd[1]); + // Store the stderr read end + out_process->stderr_file = fdopen(stderrfd[0], "rb"); + } + + // Store the child's pid + out_process->child = child; + + return 0; + } +#endif +} + +FILE *process_stdin(const struct process_s *const process) { + return process->stdin_file; +} + +FILE *process_stdout(const struct process_s *const process) { + return process->stdout_file; +} + +FILE *process_stderr(const struct process_s *const process) { + if (process->stdout_file != process->stderr_file) { + return process->stderr_file; + } else { + return 0; + } +} + +int process_join(struct process_s *const process, int *const out_return_code) { +#if defined(_MSC_VER) + const unsigned long infinite = 0xFFFFFFFF; + + if (0 != process->stdin_file) { + fclose(process->stdin_file); + process->stdin_file = 0; + } + if (0 != process->hStdInput) { + CloseHandle(process->hStdInput); + process->hStdInput = NULL; + } + + WaitForSingleObject(process->hProcess, infinite); + + if (0 != process->hStdOutput) { + CloseHandle(process->hStdOutput); + if (process->hStdError == process->hStdOutput) { + process->hStdError = NULL; + } + process->hStdOutput = NULL; + } + if (0 != process->hStdError) { + CloseHandle(process->hStdError); + process->hStdError = NULL; + } + + if (out_return_code) { + if (!GetExitCodeProcess(process->hProcess, + (unsigned long *)out_return_code)) { + return -1; + } + } + + return 0; +#else + int status; + + if (0 != process->stdin_file) { + fclose(process->stdin_file); + process->stdin_file = 0; + } + + if (process->child != waitpid(process->child, &status, 0)) { + return -1; + } + + if (out_return_code) { + if (WIFEXITED(status)) { + *out_return_code = WEXITSTATUS(status); + } else { + *out_return_code = 0; + } + } + + return 0; +#endif +} + +int process_destroy(struct process_s *const process) { + if (0 != process->stdin_file) { + fclose(process->stdin_file); + } + + fclose(process->stdout_file); + + if (process->stdout_file != process->stderr_file) { + fclose(process->stderr_file); + } + +#if defined(_MSC_VER) + CloseHandle(process->hProcess); + + if (0 != process->hStdInput) { + CloseHandle(process->hStdInput); + process->hStdInput = NULL; + } + if (0 != process->hStdOutput) { + CloseHandle(process->hStdOutput); + if (process->hStdError == process->hStdOutput) { + process->hStdError = NULL; + } + process->hStdOutput = NULL; + } + if (0 != process->hStdError) { + CloseHandle(process->hStdError); + process->hStdError = NULL; + } +#endif + + return 0; +} + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif /* SHEREDOM_PROCESS_H_INCLUDED */ diff --git a/test/test.c b/test/test.c index 9426c1e..b85ae16 100644 --- a/test/test.c +++ b/test/test.c @@ -231,74 +231,3 @@ static const int data[4] = {42, 13, 6, -53}; UTEST(c, Array) { EXPECT_NE(data, data + 1); } - -#ifdef __linux__ -#include -/** Check that the output from --list-tests matches our internal view of the - * listed tests. This means calling ourselves recursively and filtering the - * output, so it's hacky. To do this, we need to get the full path to the - * current program, there is a different way to get this information on nearly - * every OS, but the thing we're testing is platform independent, so we should - * be able to get away with just Linux here -*/ -static int testname_cmp(const void *x_, const void *y_) { - const char *x = *(const char *const*)x_; - const char *y = *(const char *const*)y_; - return strcmp(x, y); -} - -/* Some bright spark decided -Werror and -Weverything should be used together, - * which breaks idiomatic code... */ - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wcomma" -#endif -UTEST(utest_cmdline, filter_with_list) { - // 64k should be enough for anyone - char exe_buf[64 * 1024] = {0}, cmd_buf[64 * 1024] = {0}; - ssize_t path_len = readlink("/proc/self/exe", exe_buf, sizeof exe_buf); - int count, ret; - FILE *ps; - const char **test_names; - size_t n = 0, i = 0; - char *lineptr; - ASSERT_NE(path_len, UTEST_CAST(ssize_t, -1)); - count = snprintf(cmd_buf, sizeof cmd_buf, "%s --list-tests", exe_buf); - ASSERT_EQ(errno, 0); - ASSERT_LT(UTEST_CAST(size_t, count), sizeof cmd_buf); - - ps = popen(cmd_buf, "r"); - ASSERT_TRUE(ps); - - test_names = - malloc(utest_state.tests_length * sizeof test_names[0]); - ASSERT_TRUE(test_names); - - for (i = 0; i < utest_state.tests_length; ++i) { - test_names[i] = utest_state.tests[i].name; - } - qsort(test_names, utest_state.tests_length, sizeof test_names[0], - testname_cmp); - - lineptr = malloc(1); - ASSERT_TRUE(lineptr); - for (i = 0, n = 0; getline(&lineptr, &n, ps) != (ssize_t)-1; n = 0, ++i) { - /* remove the terminating newline */ - const char **name; - char *nl = strchr(lineptr, '\n'); - EXPECT_TRUE(nl); - *nl = '\0'; - - name = bsearch(&lineptr, test_names, utest_state.tests_length, - sizeof test_names[0], testname_cmp); - EXPECT_TRUE(name); - EXPECT_STREQ(*name, lineptr); - } - EXPECT_EQ(i, utest_state.tests_length); - free(lineptr); - free(test_names); - ret = pclose(ps); - ASSERT_EQ(ret, 0); -} -#endif diff --git a/test/main.c b/test/main.c index f6c50cc..4b5bbcd 100644 --- a/test/main.c +++ b/test/main.c @@ -24,5 +24,50 @@ // For more information, please refer to #include "utest.h" +#include "process.h" + +UTEST(utest_cmdline, filter_with_list) { + struct process_s process; + const char *command[3] = {"utest_test", "--list-tests", 0}; + int return_code; + FILE *stdout_file; + size_t index; + +// 64k should be enough for anyone +#define MAX_CHARS (64 * 1024) + char buffer[MAX_CHARS] = {0}; + + ASSERT_EQ(0, process_create(command, process_option_combined_stdout_stderr, + &process)); + + stdout_file = process_stdout(&process); + + for (index = 0; index < utest_state.tests_length; index++) { + if (buffer != fgets(buffer, MAX_CHARS, stdout_file)) { + break; + } + +#if defined(__clang__) +#if __has_warning("-Wdisabled-macro-expansion") +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdisabled-macro-expansion" +#endif +#endif + + ASSERT_EQ(0, strncmp(buffer, utest_state.tests[index].name, + strlen(utest_state.tests[index].name))); + +#if defined(__clang__) +#if __has_warning("-Wdisabled-macro-expansion") +#pragma clang diagnostic pop +#endif +#endif + } + + ASSERT_EQ(0, process_join(&process, &return_code)); + ASSERT_EQ(0, return_code); + + ASSERT_EQ(0, process_destroy(&process)); +} UTEST_MAIN() diff --git a/test/process.h b/test/process.h new file mode 100644 index 0000000..d3e1a97 --- /dev/null +++ b/test/process.h @@ -0,0 +1,568 @@ +/* + The latest version of this library is available on GitHub; + https://github.com/sheredom/process.h +*/ + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ + +#ifndef SHEREDOM_PROCESS_H_INCLUDED +#define SHEREDOM_PROCESS_H_INCLUDED + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(_MSC_VER) +#pragma warning(push, 1) +#endif + +#include + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if !defined(_MSC_VER) +#include +#include +#include +#include +#endif + +#if defined(_MSC_VER) +#ifdef _WIN64 +typedef __int64 intptr_t; +typedef unsigned __int64 size_t; +#else +typedef int intptr_t; +typedef unsigned int size_t; +#endif + +typedef struct _PROCESS_INFORMATION *LPPROCESS_INFORMATION; +typedef struct _SECURITY_ATTRIBUTES *LPSECURITY_ATTRIBUTES; +typedef struct _STARTUPINFOA *LPSTARTUPINFOA; + +#pragma warning(push, 1) +struct process_process_information_s { + void *hProcess; + void *hThread; + unsigned long dwProcessId; + unsigned long dwThreadId; +}; + +struct process_security_attributes_s { + unsigned long nLength; + void *lpSecurityDescriptor; + int bInheritHandle; +}; + +struct process_startup_info_s { + unsigned long cb; + char *lpReserved; + char *lpDesktop; + char *lpTitle; + unsigned long dwX; + unsigned long dwY; + unsigned long dwXSize; + unsigned long dwYSize; + unsigned long dwXCountChars; + unsigned long dwYCountChars; + unsigned long dwFillAttribute; + unsigned long dwFlags; + unsigned short wShowWindow; + unsigned short cbReserved2; + unsigned char *lpReserved2; + void *hStdInput; + void *hStdOutput; + void *hStdError; +}; +#pragma warning(pop) + +__declspec(dllimport) int __stdcall SetHandleInformation(void *, unsigned long, + unsigned long); +__declspec(dllimport) int __stdcall CreatePipe(void **, void **, + LPSECURITY_ATTRIBUTES, + unsigned long); +__declspec(dllimport) int __stdcall CreateProcessA( + const char *, char *, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, int, + unsigned long, void *, const char *, LPSTARTUPINFOA, LPPROCESS_INFORMATION); +__declspec(dllimport) int __stdcall CloseHandle(void *); +__declspec(dllimport) unsigned long __stdcall WaitForSingleObject( + void *, unsigned long); +__declspec(dllimport) int __stdcall GetExitCodeProcess( + void *, unsigned long *lpExitCode); +__declspec(dllimport) int __cdecl _open_osfhandle(intptr_t, int); +void *__cdecl _alloca(size_t); +#endif + +#if defined(__clang__) || defined(__GNUC__) +#define process_pure __attribute__((pure)) +#define process_weak __attribute__((weak)) +#elif defined(_MSC_VER) +#define process_pure +#define process_weak __inline +#else +#error Non clang, non gcc, non MSVC compiler found! +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif +struct process_s { + FILE *stdin_file; + FILE *stdout_file; + FILE *stderr_file; + +#if defined(_MSC_VER) + void *hProcess; + void *hStdInput; + void *hStdOutput; + void *hStdError; +#else + pid_t child; +#endif +}; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +enum process_option_e { + // stdout and stderr are the same FILE. + process_option_combined_stdout_stderr = 0x1, + + // The child process should inherit the environment variables of the parent. + process_option_inherit_environment = 0x2 +}; + +/// @brief Create a process. +/// @param command_line An array of strings for the command line to execute for +/// this process. The last element must be NULL to signify the end of the array. +/// @param options A bit field of process_option_e's to pass. +/// @param out_process The newly created process. +/// @return On success 0 is returned. +process_weak int process_create(const char *const command_line[], int options, + struct process_s *const out_process); + +/// @brief Get the standard input file for a process. +/// @param process The process to query. +/// @return The file for standard input of the process. +/// +/// The file returned can be written to by the parent process to feed data to +/// the standard input of the process. +process_pure process_weak FILE * +process_stdin(const struct process_s *const process); + +/// @brief Get the standard output file for a process. +/// @param process The process to query. +/// @return The file for standard output of the process. +/// +/// The file returned can be read from by the parent process to read data from +/// the standard output of the child process. +process_pure process_weak FILE * +process_stdout(const struct process_s *const process); + +/// @brief Get the standard error file for a process. +/// @param process The process to query. +/// @return The file for standard error of the process. +/// +/// The file returned can be read from by the parent process to read data from +/// the standard error of the child process. +/// +/// If the process was created with the process_option_combined_stdout_stderr +/// option bit set, this function will return NULL, and the process_stdout +/// function should be used for both the standard output and error combined. +process_pure process_weak FILE * +process_stderr(const struct process_s *const process); + +/// @brief Wait for a process to finish execution. +/// @param process The process to wait for. +/// @param out_return_code The return code of the returned process (can be +/// NULL). +/// @return On success 0 is returned. +process_weak int process_join(struct process_s *const process, + int *const out_return_code); + +/// @brief Destroy a previously created process. +/// @param process The process to destroy. +/// +/// If the process to be destroyed had not finished execution, it may out live +/// the parent process. +process_weak int process_destroy(struct process_s *const process); + +int process_create(const char *const commandLine[], int options, + struct process_s *const out_process) { +#if defined(_MSC_VER) + int fd; + void *rd, *wr; + char *commandLineCombined; + size_t len; + int i, j; + const unsigned long startFUseStdHandles = 0x00000100; + const unsigned long handleFlagInherit = 0x00000001; + struct process_process_information_s processInfo; + struct process_security_attributes_s saAttr = {sizeof(saAttr), 0, 1}; + char *environment = 0; + struct process_startup_info_s startInfo = {0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + startInfo.cb = sizeof(startInfo); + startInfo.dwFlags = startFUseStdHandles; + + if (process_option_inherit_environment != + (options & process_option_inherit_environment)) { + environment = "\0\0"; + } + + if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + return -1; + } + + if (!SetHandleInformation(wr, handleFlagInherit, 0)) { + return -1; + } + + fd = _open_osfhandle((intptr_t)wr, 0); + + if (-1 != fd) { + out_process->stdin_file = _fdopen(fd, "wb"); + + if (0 == out_process->stdin_file) { + return -1; + } + } + + startInfo.hStdInput = rd; + + if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + return -1; + } + + if (!SetHandleInformation(rd, handleFlagInherit, 0)) { + return -1; + } + + fd = _open_osfhandle((intptr_t)rd, 0); + + if (-1 != fd) { + out_process->stdout_file = _fdopen(fd, "rb"); + + if (0 == out_process->stdout_file) { + return -1; + } + } + + startInfo.hStdOutput = wr; + + if (process_option_combined_stdout_stderr == + (options & process_option_combined_stdout_stderr)) { + out_process->stderr_file = out_process->stdout_file; + startInfo.hStdError = startInfo.hStdOutput; + } else { + if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + return -1; + } + + if (!SetHandleInformation(rd, handleFlagInherit, 0)) { + return -1; + } + + fd = _open_osfhandle((intptr_t)rd, 0); + + if (-1 != fd) { + out_process->stderr_file = _fdopen(fd, "rb"); + + if (0 == out_process->stderr_file) { + return -1; + } + } + + startInfo.hStdError = wr; + } + + // Combine commandLine together into a single string + len = 0; + for (i = 0; commandLine[i]; i++) { + // For the ' ' between items and trailing '\0' + len++; + + for (j = 0; '\0' != commandLine[i][j]; j++) { + len++; + } + } + + commandLineCombined = (char *)_alloca(len); + + if (!commandLineCombined) { + return -1; + } + + // Gonna re-use len to store the write index into commandLineCombined + len = 0; + + for (i = 0; commandLine[i]; i++) { + if (0 != i) { + commandLineCombined[len++] = ' '; + } + + for (j = 0; '\0' != commandLine[i][j]; j++) { + commandLineCombined[len++] = commandLine[i][j]; + } + } + + commandLineCombined[len] = '\0'; + + if (!CreateProcessA(NULL, + commandLineCombined, // command line + NULL, // process security attributes + NULL, // primary thread security attributes + 1, // handles are inherited + 0, // creation flags + environment, // use parent's environment + NULL, // use parent's current directory + (LPSTARTUPINFOA)&startInfo, // STARTUPINFO pointer + (LPPROCESS_INFORMATION)&processInfo)) { + return -1; + } + + out_process->hProcess = processInfo.hProcess; + + out_process->hStdInput = startInfo.hStdInput; + out_process->hStdOutput = startInfo.hStdOutput; + out_process->hStdError = startInfo.hStdError; + + // We don't need the handle of the primary thread in the called process. + CloseHandle(processInfo.hThread); + + return 0; +#else + int stdinfd[2]; + int stdoutfd[2]; + int stderrfd[2]; + pid_t child; + + if (0 != pipe(stdinfd)) { + return -1; + } + + if (0 != pipe(stdoutfd)) { + return -1; + } + + if (process_option_combined_stdout_stderr != + (options & process_option_combined_stdout_stderr)) { + if (0 != pipe(stderrfd)) { + return -1; + } + } + + child = fork(); + + if (-1 == child) { + return -1; + } + + if (0 == child) { + // Close the stdin write end + close(stdinfd[1]); + // Map the read end to stdin + dup2(stdinfd[0], STDIN_FILENO); + + // Close the stdout read end + close(stdoutfd[0]); + // Map the write end to stdout + dup2(stdoutfd[1], STDOUT_FILENO); + + if (process_option_combined_stdout_stderr == + (options & process_option_combined_stdout_stderr)) { + dup2(STDOUT_FILENO, STDERR_FILENO); + } else { + // Close the stderr read end + close(stderrfd[0]); + // Map the write end to stdout + dup2(stderrfd[1], STDERR_FILENO); + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-qual" +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif + if (process_option_inherit_environment != + (options & process_option_inherit_environment)) { + char *const environment[1] = {0}; + exit(execve(commandLine[0], (char *const *)commandLine, environment)); + } else { + exit(execvp(commandLine[0], (char *const *)commandLine)); + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } else { + // Close the stdin read end + close(stdinfd[0]); + // Store the stdin write end + out_process->stdin_file = fdopen(stdinfd[1], "wb"); + + // Close the stdout write end + close(stdoutfd[1]); + // Store the stdout read end + out_process->stdout_file = fdopen(stdoutfd[0], "rb"); + + if (process_option_combined_stdout_stderr == + (options & process_option_combined_stdout_stderr)) { + out_process->stderr_file = out_process->stdout_file; + } else { + // Close the stderr write end + close(stderrfd[1]); + // Store the stderr read end + out_process->stderr_file = fdopen(stderrfd[0], "rb"); + } + + // Store the child's pid + out_process->child = child; + + return 0; + } +#endif +} + +FILE *process_stdin(const struct process_s *const process) { + return process->stdin_file; +} + +FILE *process_stdout(const struct process_s *const process) { + return process->stdout_file; +} + +FILE *process_stderr(const struct process_s *const process) { + if (process->stdout_file != process->stderr_file) { + return process->stderr_file; + } else { + return 0; + } +} + +int process_join(struct process_s *const process, int *const out_return_code) { +#if defined(_MSC_VER) + const unsigned long infinite = 0xFFFFFFFF; + + if (0 != process->stdin_file) { + fclose(process->stdin_file); + process->stdin_file = 0; + } + if (0 != process->hStdInput) { + CloseHandle(process->hStdInput); + process->hStdInput = NULL; + } + + WaitForSingleObject(process->hProcess, infinite); + + if (0 != process->hStdOutput) { + CloseHandle(process->hStdOutput); + if (process->hStdError == process->hStdOutput) { + process->hStdError = NULL; + } + process->hStdOutput = NULL; + } + if (0 != process->hStdError) { + CloseHandle(process->hStdError); + process->hStdError = NULL; + } + + if (out_return_code) { + if (!GetExitCodeProcess(process->hProcess, + (unsigned long *)out_return_code)) { + return -1; + } + } + + return 0; +#else + int status; + + if (0 != process->stdin_file) { + fclose(process->stdin_file); + process->stdin_file = 0; + } + + if (process->child != waitpid(process->child, &status, 0)) { + return -1; + } + + if (out_return_code) { + if (WIFEXITED(status)) { + *out_return_code = WEXITSTATUS(status); + } else { + *out_return_code = 0; + } + } + + return 0; +#endif +} + +int process_destroy(struct process_s *const process) { + if (0 != process->stdin_file) { + fclose(process->stdin_file); + } + + fclose(process->stdout_file); + + if (process->stdout_file != process->stderr_file) { + fclose(process->stderr_file); + } + +#if defined(_MSC_VER) + CloseHandle(process->hProcess); + + if (0 != process->hStdInput) { + CloseHandle(process->hStdInput); + process->hStdInput = NULL; + } + if (0 != process->hStdOutput) { + CloseHandle(process->hStdOutput); + if (process->hStdError == process->hStdOutput) { + process->hStdError = NULL; + } + process->hStdOutput = NULL; + } + if (0 != process->hStdError) { + CloseHandle(process->hStdError); + process->hStdError = NULL; + } +#endif + + return 0; +} + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif /* SHEREDOM_PROCESS_H_INCLUDED */ diff --git a/test/test.c b/test/test.c index 9426c1e..b85ae16 100644 --- a/test/test.c +++ b/test/test.c @@ -231,74 +231,3 @@ static const int data[4] = {42, 13, 6, -53}; UTEST(c, Array) { EXPECT_NE(data, data + 1); } - -#ifdef __linux__ -#include -/** Check that the output from --list-tests matches our internal view of the - * listed tests. This means calling ourselves recursively and filtering the - * output, so it's hacky. To do this, we need to get the full path to the - * current program, there is a different way to get this information on nearly - * every OS, but the thing we're testing is platform independent, so we should - * be able to get away with just Linux here -*/ -static int testname_cmp(const void *x_, const void *y_) { - const char *x = *(const char *const*)x_; - const char *y = *(const char *const*)y_; - return strcmp(x, y); -} - -/* Some bright spark decided -Werror and -Weverything should be used together, - * which breaks idiomatic code... */ - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wcomma" -#endif -UTEST(utest_cmdline, filter_with_list) { - // 64k should be enough for anyone - char exe_buf[64 * 1024] = {0}, cmd_buf[64 * 1024] = {0}; - ssize_t path_len = readlink("/proc/self/exe", exe_buf, sizeof exe_buf); - int count, ret; - FILE *ps; - const char **test_names; - size_t n = 0, i = 0; - char *lineptr; - ASSERT_NE(path_len, UTEST_CAST(ssize_t, -1)); - count = snprintf(cmd_buf, sizeof cmd_buf, "%s --list-tests", exe_buf); - ASSERT_EQ(errno, 0); - ASSERT_LT(UTEST_CAST(size_t, count), sizeof cmd_buf); - - ps = popen(cmd_buf, "r"); - ASSERT_TRUE(ps); - - test_names = - malloc(utest_state.tests_length * sizeof test_names[0]); - ASSERT_TRUE(test_names); - - for (i = 0; i < utest_state.tests_length; ++i) { - test_names[i] = utest_state.tests[i].name; - } - qsort(test_names, utest_state.tests_length, sizeof test_names[0], - testname_cmp); - - lineptr = malloc(1); - ASSERT_TRUE(lineptr); - for (i = 0, n = 0; getline(&lineptr, &n, ps) != (ssize_t)-1; n = 0, ++i) { - /* remove the terminating newline */ - const char **name; - char *nl = strchr(lineptr, '\n'); - EXPECT_TRUE(nl); - *nl = '\0'; - - name = bsearch(&lineptr, test_names, utest_state.tests_length, - sizeof test_names[0], testname_cmp); - EXPECT_TRUE(name); - EXPECT_STREQ(*name, lineptr); - } - EXPECT_EQ(i, utest_state.tests_length); - free(lineptr); - free(test_names); - ret = pclose(ps); - ASSERT_EQ(ret, 0); -} -#endif diff --git a/utest.h b/utest.h index 96c7476..7e7f04f 100644 --- a/utest.h +++ b/utest.h @@ -809,7 +809,7 @@ for (index = 0; index < utest_state.tests_length; index++) { UTEST_PRINTF("%s\n", utest_state.tests[index].name); } - /* When printing the test list, don't actually run the tests */ + /* when printing the test list, don't actually run the tests */ return 0; } }