diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 7167c09..8972f1d 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -14,7 +14,6 @@ type: [Debug, RelWithDebInfo, MinSizeRel, Release] compiler: [default, clang, gcc] exclude: - - {os: "windows-latest", compiler: "clang"} - {os: "macOS-latest", compiler: "clang"} - {os: "windows-latest", compiler: "gcc"} - {os: "macOS-latest", compiler: "gcc"} @@ -44,12 +43,18 @@ working-directory: ${{github.workspace}}/build run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 - - name: Configure CMake with Clang + - name: Configure CMake with Clang (Ubuntu) shell: bash - if: matrix.compiler == 'clang' + if: (matrix.compiler == 'clang') && startsWith(matrix.os, 'ubuntu') working-directory: ${{github.workspace}}/build run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_C_COMPILER=clang-10 -DCMAKE_CXX_COMPILER=clang++-10 + - name: Configure CMake with Clang (Windows) + shell: bash + if: (matrix.compiler == 'clang') && startsWith(matrix.os, 'windows') + working-directory: ${{github.workspace}}/build + run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -T ClangCL + - name: Build working-directory: ${{github.workspace}}/build shell: bash diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 7167c09..8972f1d 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -14,7 +14,6 @@ type: [Debug, RelWithDebInfo, MinSizeRel, Release] compiler: [default, clang, gcc] exclude: - - {os: "windows-latest", compiler: "clang"} - {os: "macOS-latest", compiler: "clang"} - {os: "windows-latest", compiler: "gcc"} - {os: "macOS-latest", compiler: "gcc"} @@ -44,12 +43,18 @@ working-directory: ${{github.workspace}}/build run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 - - name: Configure CMake with Clang + - name: Configure CMake with Clang (Ubuntu) shell: bash - if: matrix.compiler == 'clang' + if: (matrix.compiler == 'clang') && startsWith(matrix.os, 'ubuntu') working-directory: ${{github.workspace}}/build run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_C_COMPILER=clang-10 -DCMAKE_CXX_COMPILER=clang++-10 + - name: Configure CMake with Clang (Windows) + shell: bash + if: (matrix.compiler == 'clang') && startsWith(matrix.os, 'windows') + working-directory: ${{github.workspace}}/build + run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -T ClangCL + - name: Build working-directory: ${{github.workspace}}/build shell: bash diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 028300f..a0d849f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -24,7 +24,7 @@ # For more information, please refer to project(utest) -cmake_minimum_required(VERSION 3.1.3) +cmake_minimum_required(VERSION 3.15) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../) @@ -33,9 +33,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=gnu89" ) elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(main.c test.c PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu89" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(main.c test.c PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(main.c test.c PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu89" + ) + endif() elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(main.c test.c PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -49,9 +55,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=c99" ) elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(test99.c PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c99" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test99.c PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(test99.c PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c99" + ) + endif() elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test99.c PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -70,9 +82,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=c11" ) elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test11.c PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() set_source_files_properties(test11.c PROPERTIES COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c11" ) + endif() elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test11.c PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -87,9 +105,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=gnu++98" ) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(test.cpp PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu++98" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test.cpp PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(test.cpp PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu++98" + ) + endif() elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test.cpp PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -103,9 +127,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=c++11" ) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(test11.cpp PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c++11 -Wno-c++98-compat" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test11.cpp PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(test11.cpp PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c++11 -Wno-c++98-compat" + ) + endif() elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test11.cpp PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -145,19 +175,23 @@ target_link_options(utest_test_wpo PRIVATE "$<$:/LTCG>") endif() -if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") - add_executable(utest_test_mt - ../utest.h - main.c - test.c - test11.c - test99.c - test.cpp - test11.cpp - stdint_include.c - type_printers.c - type_printers.cpp - ) +add_executable(utest_test_mt + ../utest.h + main.c + test.c + test11.c + test99.c + test.cpp + test11.cpp + stdint_include.c + type_printers.c + type_printers.cpp +) - target_compile_definitions(utest_test_mt PUBLIC "/MT") -endif() +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + target_compile_options(utest_test_mt PUBLIC "/MT") + endif() +elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + target_compile_options(utest_test_mt PUBLIC "/MT") +endif() \ No newline at end of file diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 7167c09..8972f1d 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -14,7 +14,6 @@ type: [Debug, RelWithDebInfo, MinSizeRel, Release] compiler: [default, clang, gcc] exclude: - - {os: "windows-latest", compiler: "clang"} - {os: "macOS-latest", compiler: "clang"} - {os: "windows-latest", compiler: "gcc"} - {os: "macOS-latest", compiler: "gcc"} @@ -44,12 +43,18 @@ working-directory: ${{github.workspace}}/build run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 - - name: Configure CMake with Clang + - name: Configure CMake with Clang (Ubuntu) shell: bash - if: matrix.compiler == 'clang' + if: (matrix.compiler == 'clang') && startsWith(matrix.os, 'ubuntu') working-directory: ${{github.workspace}}/build run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_C_COMPILER=clang-10 -DCMAKE_CXX_COMPILER=clang++-10 + - name: Configure CMake with Clang (Windows) + shell: bash + if: (matrix.compiler == 'clang') && startsWith(matrix.os, 'windows') + working-directory: ${{github.workspace}}/build + run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -T ClangCL + - name: Build working-directory: ${{github.workspace}}/build shell: bash diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 028300f..a0d849f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -24,7 +24,7 @@ # For more information, please refer to project(utest) -cmake_minimum_required(VERSION 3.1.3) +cmake_minimum_required(VERSION 3.15) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../) @@ -33,9 +33,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=gnu89" ) elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(main.c test.c PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu89" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(main.c test.c PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(main.c test.c PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu89" + ) + endif() elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(main.c test.c PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -49,9 +55,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=c99" ) elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(test99.c PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c99" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test99.c PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(test99.c PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c99" + ) + endif() elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test99.c PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -70,9 +82,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=c11" ) elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test11.c PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() set_source_files_properties(test11.c PROPERTIES COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c11" ) + endif() elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test11.c PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -87,9 +105,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=gnu++98" ) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(test.cpp PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu++98" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test.cpp PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(test.cpp PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu++98" + ) + endif() elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test.cpp PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -103,9 +127,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=c++11" ) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(test11.cpp PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c++11 -Wno-c++98-compat" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test11.cpp PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(test11.cpp PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c++11 -Wno-c++98-compat" + ) + endif() elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test11.cpp PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -145,19 +175,23 @@ target_link_options(utest_test_wpo PRIVATE "$<$:/LTCG>") endif() -if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") - add_executable(utest_test_mt - ../utest.h - main.c - test.c - test11.c - test99.c - test.cpp - test11.cpp - stdint_include.c - type_printers.c - type_printers.cpp - ) +add_executable(utest_test_mt + ../utest.h + main.c + test.c + test11.c + test99.c + test.cpp + test11.cpp + stdint_include.c + type_printers.c + type_printers.cpp +) - target_compile_definitions(utest_test_mt PUBLIC "/MT") -endif() +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + target_compile_options(utest_test_mt PUBLIC "/MT") + endif() +elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + target_compile_options(utest_test_mt PUBLIC "/MT") +endif() \ No newline at end of file diff --git a/test/subprocess.h b/test/subprocess.h index 8f63657..b25bc47 100644 --- a/test/subprocess.h +++ b/test/subprocess.h @@ -43,12 +43,12 @@ #pragma warning(pop) #endif -#if defined(__clang__) || defined(__GNUC__) -#define subprocess_pure __attribute__((pure)) -#define subprocess_weak __attribute__((weak)) -#elif defined(_MSC_VER) +#if defined(_MSC_VER) #define subprocess_pure #define subprocess_weak __inline +#elif defined(__clang__) || defined(__GNUC__) +#define subprocess_pure __attribute__((pure)) +#define subprocess_weak __attribute__((weak)) #else #error Non clang, non gcc, non MSVC compiler found! #endif @@ -75,7 +75,7 @@ /// this process. The last element must be NULL to signify the end of the array. /// @param options A bit field of subprocess_option_e's to pass. /// @param out_process The newly created process. -/// @return On success 0 is returned. +/// @return On success zero is returned. subprocess_weak int subprocess_create(const char *const command_line[], int options, struct subprocess_s *const out_process); @@ -115,7 +115,7 @@ /// @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. +/// @return On success zero is returned. /// /// Joining a process will close the stdin pipe to the process. subprocess_weak int subprocess_join(struct subprocess_s *const process, @@ -123,7 +123,7 @@ /// @brief Destroy a previously created process. /// @param process The process to destroy. -/// @return On success 0 is returned. +/// @return On success zero is returned. /// /// If the process to be destroyed had not finished execution, it may out live /// the parent process. @@ -131,7 +131,7 @@ /// @brief Terminate a previously created process. /// @param process The process to terminate. -/// @return On success 0 is returned. +/// @return On success zero is returned. /// /// If the process to be destroyed had not finished execution, it will be /// terminated (i.e killed). @@ -165,21 +165,34 @@ subprocess_read_stderr(struct subprocess_s *const process, char *const buffer, unsigned size); +/// @brief Returns if the subprocess is currently still alive and executing. +/// @param process The process to check. +/// @return If the process is still alive non-zero is returned. +subprocess_weak int subprocess_alive(struct subprocess_s *const process); + #if defined(__cplusplus) #define SUBPROCESS_CAST(type, x) static_cast(x) +#define SUBPROCESS_PTR_CAST(type, x) reinterpret_cast(x) +#define SUBPROCESS_CONST_CAST(type, x) const_cast(x) +#define SUBPROCESS_NULL NULL #else -#define SUBPROCESS_CAST(type, x) ((type)x) +#define SUBPROCESS_CAST(type, x) ((type)(x)) +#define SUBPROCESS_PTR_CAST(type, x) ((type)(x)) +#define SUBPROCESS_CONST_CAST(type, x) ((type)(x)) +#define SUBPROCESS_NULL 0 #endif #if !defined(_MSC_VER) +#include #include #include #include #include -#include #endif #if defined(_MSC_VER) + +#if (_MSC_VER < 1920) #ifdef _WIN64 typedef __int64 subprocess_intptr_t; typedef unsigned __int64 subprocess_size_t; @@ -187,6 +200,12 @@ typedef int subprocess_intptr_t; typedef unsigned int subprocess_size_t; #endif +#else +#include + +typedef intptr_t subprocess_intptr_t; +typedef size_t subprocess_size_t; +#endif typedef struct _PROCESS_INFORMATION *LPPROCESS_INFORMATION; typedef struct _SECURITY_ATTRIBUTES *LPSECURITY_ATTRIBUTES; @@ -272,8 +291,7 @@ void *, unsigned long); __declspec(dllimport) int __stdcall GetExitCodeProcess( void *, unsigned long *lpExitCode); -__declspec(dllimport) int __stdcall TerminateProcess( - void *, unsigned int); +__declspec(dllimport) int __stdcall TerminateProcess(void *, unsigned int); __declspec(dllimport) unsigned long __stdcall WaitForMultipleObjects( unsigned long, void *const *, int, unsigned long); __declspec(dllimport) int __stdcall GetOverlappedResult(void *, LPOVERLAPPED, @@ -290,6 +308,8 @@ SUBPROCESS_DLLIMPORT subprocess_intptr_t __cdecl _get_osfhandle(int); void *__cdecl _alloca(subprocess_size_t); +#else +typedef size_t subprocess_size_t; #endif #ifdef __clang__ @@ -308,7 +328,10 @@ void *hEventError; #else pid_t child; + int return_status; #endif + + subprocess_size_t alive; }; #ifdef __clang__ #pragma clang diagnostic pop @@ -324,8 +347,10 @@ const unsigned long genericWrite = 0x40000000; const unsigned long openExisting = 3; const unsigned long fileAttributeNormal = 0x00000080; - const void *const invalidHandleValue = (void *)~((subprocess_intptr_t)0); - struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), 0, 1}; + const void *const invalidHandleValue = + SUBPROCESS_PTR_CAST(void *, ~(SUBPROCESS_CAST(subprocess_intptr_t, 0))); + struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), + SUBPROCESS_NULL, 1}; char name[256] = {0}; __declspec(thread) static long index = 0; const long unique = index++; @@ -348,16 +373,18 @@ #pragma warning(pop) #endif - *rd = CreateNamedPipeA(name, pipeAccessInbound | fileFlagOverlapped, - pipeTypeByte | pipeWait, 1, 4096, 4096, 0, - (LPSECURITY_ATTRIBUTES)&saAttr); + *rd = + CreateNamedPipeA(name, pipeAccessInbound | fileFlagOverlapped, + pipeTypeByte | pipeWait, 1, 4096, 4096, SUBPROCESS_NULL, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr)); if (invalidHandleValue == rd) { return -1; } - *wr = CreateFileA(name, genericWrite, 0, (LPSECURITY_ATTRIBUTES)&saAttr, - openExisting, fileAttributeNormal, 0); + *wr = CreateFileA(name, genericWrite, SUBPROCESS_NULL, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), + openExisting, fileAttributeNormal, SUBPROCESS_NULL); if (invalidHandleValue == wr) { return -1; @@ -378,20 +405,38 @@ const unsigned long startFUseStdHandles = 0x00000100; const unsigned long handleFlagInherit = 0x00000001; struct subprocess_subprocess_information_s processInfo; - struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), 0, 1}; - char *environment = 0; - struct subprocess_startup_info_s startInfo = {0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0}; + struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), + SUBPROCESS_NULL, 1}; + char *environment = SUBPROCESS_NULL; + struct subprocess_startup_info_s startInfo = {0, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL}; startInfo.cb = sizeof(startInfo); startInfo.dwFlags = startFUseStdHandles; if (subprocess_option_inherit_environment != (options & subprocess_option_inherit_environment)) { - environment = "\0\0"; + environment = SUBPROCESS_CONST_CAST(char *, "\0\0"); } - if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + if (!CreatePipe(&rd, &wr, SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), + 0)) { return -1; } @@ -399,12 +444,12 @@ return -1; } - fd = _open_osfhandle((subprocess_intptr_t)wr, 0); + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, wr), 0); if (-1 != fd) { out_process->stdin_file = _fdopen(fd, "wb"); - if (0 == out_process->stdin_file) { + if (SUBPROCESS_NULL == out_process->stdin_file) { return -1; } } @@ -416,7 +461,8 @@ return -1; } } else { - if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + if (!CreatePipe(&rd, &wr, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) { return -1; } } @@ -425,12 +471,12 @@ return -1; } - fd = _open_osfhandle((subprocess_intptr_t)rd, 0); + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, rd), 0); if (-1 != fd) { out_process->stdout_file = _fdopen(fd, "rb"); - if (0 == out_process->stdout_file) { + if (SUBPROCESS_NULL == out_process->stdout_file) { return -1; } } @@ -447,7 +493,8 @@ return -1; } } else { - if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + if (!CreatePipe(&rd, &wr, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) { return -1; } } @@ -456,12 +503,12 @@ return -1; } - fd = _open_osfhandle((subprocess_intptr_t)rd, 0); + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, rd), 0); if (-1 != fd) { out_process->stderr_file = _fdopen(fd, "rb"); - if (0 == out_process->stderr_file) { + if (SUBPROCESS_NULL == out_process->stderr_file) { return -1; } } @@ -471,26 +518,41 @@ if (options & subprocess_option_enable_async) { out_process->hEventOutput = - CreateEventA((LPSECURITY_ATTRIBUTES)&saAttr, 1, 1, 0); + CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1, + SUBPROCESS_NULL); out_process->hEventError = - CreateEventA((LPSECURITY_ATTRIBUTES)&saAttr, 1, 1, 0); + CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1, + SUBPROCESS_NULL); } else { - out_process->hEventOutput = 0; - out_process->hEventError = 0; + out_process->hEventOutput = SUBPROCESS_NULL; + out_process->hEventError = SUBPROCESS_NULL; } // Combine commandLine together into a single string len = 0; for (i = 0; commandLine[i]; i++) { - // For the ' ' between items and trailing '\0' - len++; + // For the ' ' and two '"' between items and trailing '\0' + len += 3; for (j = 0; '\0' != commandLine[i][j]; j++) { + switch (commandLine[i][j]) { + default: + break; + case '\\': + if (commandLine[i][j + 1] == '"') { + len++; + } + + break; + case '"': + len++; + break; + } len++; } } - commandLineCombined = (char *)_alloca(len); + commandLineCombined = SUBPROCESS_CAST(char *, _alloca(len)); if (!commandLineCombined) { return -1; @@ -503,24 +565,42 @@ if (0 != i) { commandLineCombined[len++] = ' '; } + commandLineCombined[len++] = '"'; for (j = 0; '\0' != commandLine[i][j]; j++) { + switch (commandLine[i][j]) { + default: + break; + case '\\': + if (commandLine[i][j + 1] == '"') { + commandLineCombined[len++] = '\\'; + } + + break; + case '"': + commandLineCombined[len++] = '\\'; + break; + } + commandLineCombined[len++] = commandLine[i][j]; } + commandLineCombined[len++] = '"'; } 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)) { + if (!CreateProcessA( + SUBPROCESS_NULL, + commandLineCombined, // command line + SUBPROCESS_NULL, // process security attributes + SUBPROCESS_NULL, // primary thread security attributes + 1, // handles are inherited + 0, // creation flags + environment, // use parent's environment + SUBPROCESS_NULL, // use parent's current directory + SUBPROCESS_PTR_CAST(LPSTARTUPINFOA, + &startInfo), // STARTUPINFO pointer + SUBPROCESS_PTR_CAST(LPPROCESS_INFORMATION, &processInfo))) { return -1; } @@ -531,7 +611,7 @@ // We don't need the handle of the primary thread in the called process. CloseHandle(processInfo.hThread); - if (0 != startInfo.hStdOutput) { + if (SUBPROCESS_NULL != startInfo.hStdOutput) { CloseHandle(startInfo.hStdOutput); if (startInfo.hStdError != startInfo.hStdOutput) { @@ -539,6 +619,8 @@ } } + out_process->alive = 1; + return 0; #else int stdinfd[2]; @@ -595,7 +677,7 @@ #endif if (subprocess_option_inherit_environment != (options & subprocess_option_inherit_environment)) { - char *const environment[1] = {0}; + char *const environment[1] = {SUBPROCESS_NULL}; exit(execve(commandLine[0], (char *const *)commandLine, environment)); } else { exit(execvp(commandLine[0], (char *const *)commandLine)); @@ -627,6 +709,8 @@ // Store the child's pid out_process->child = child; + out_process->alive = 1; + return 0; } #endif @@ -644,7 +728,7 @@ if (process->stdout_file != process->stderr_file) { return process->stderr_file; } else { - return 0; + return SUBPROCESS_NULL; } } @@ -653,43 +737,55 @@ #if defined(_MSC_VER) const unsigned long infinite = 0xFFFFFFFF; - if (0 != process->stdin_file) { + if (process->stdin_file) { fclose(process->stdin_file); - process->stdin_file = 0; + process->stdin_file = SUBPROCESS_NULL; } - if (0 != process->hStdInput) { + + if (process->hStdInput) { CloseHandle(process->hStdInput); - process->hStdInput = NULL; + process->hStdInput = SUBPROCESS_NULL; } WaitForSingleObject(process->hProcess, infinite); if (out_return_code) { - if (!GetExitCodeProcess(process->hProcess, - (unsigned long *)out_return_code)) { + if (!GetExitCodeProcess( + process->hProcess, + SUBPROCESS_PTR_CAST(unsigned long *, out_return_code))) { return -1; } } + process->alive = 0; + return 0; #else int status; - if (0 != process->stdin_file) { + if (process->stdin_file) { fclose(process->stdin_file); - process->stdin_file = 0; + process->stdin_file = SUBPROCESS_NULL; } - if (process->child != waitpid(process->child, &status, 0)) { - return -1; + if (process->child) { + if (process->child != waitpid(process->child, &status, 0)) { + return -1; + } + + process->child = 0; + + if (WIFEXITED(status)) { + process->return_status = WEXITSTATUS(status); + } else { + process->return_status = EXIT_FAILURE; + } + + process->alive = 0; } if (out_return_code) { - if (WIFEXITED(status)) { - *out_return_code = WEXITSTATUS(status); - } else { - *out_return_code = EXIT_FAILURE; - } + *out_return_code = process->return_status; } return 0; @@ -697,36 +793,36 @@ } int subprocess_destroy(struct subprocess_s *const process) { - if (0 != process->stdin_file) { + if (process->stdin_file) { fclose(process->stdin_file); - process->stdin_file = 0; + process->stdin_file = SUBPROCESS_NULL; } - if (0 != process->stdout_file) { + if (process->stdout_file) { fclose(process->stdout_file); if (process->stdout_file != process->stderr_file) { fclose(process->stderr_file); } - process->stdout_file = 0; - process->stderr_file = 0; + process->stdout_file = SUBPROCESS_NULL; + process->stderr_file = SUBPROCESS_NULL; } #if defined(_MSC_VER) if (process->hProcess) { CloseHandle(process->hProcess); - process->hProcess = 0; + process->hProcess = SUBPROCESS_NULL; - if (0 != process->hStdInput) { + if (process->hStdInput) { CloseHandle(process->hStdInput); } - if (0 != process->hEventOutput) { + if (process->hEventOutput) { CloseHandle(process->hEventOutput); } - if (0 != process->hEventError) { + if (process->hEventError) { CloseHandle(process->hEventError); } } @@ -740,10 +836,11 @@ unsigned int killed_process_exit_code; int success_terminate; int windows_call_result; - + killed_process_exit_code = 99; - windows_call_result = TerminateProcess(process->hProcess, killed_process_exit_code); - success_terminate = (windows_call_result== 0) ? 1 : 0; + windows_call_result = + TerminateProcess(process->hProcess, killed_process_exit_code); + success_terminate = (windows_call_result == 0) ? 1 : 0; return success_terminate; #else int result; @@ -757,19 +854,22 @@ #if defined(_MSC_VER) void *handle; unsigned long bytes_read = 0; - struct subprocess_overlapped_s overlapped = {0}; + struct subprocess_overlapped_s overlapped = {0, 0, {{0, 0}}, SUBPROCESS_NULL}; overlapped.hEvent = process->hEventOutput; - handle = (void *)_get_osfhandle(_fileno(process->stdout_file)); + handle = SUBPROCESS_PTR_CAST(void *, + _get_osfhandle(_fileno(process->stdout_file))); - if (!ReadFile(handle, buffer, size, &bytes_read, (LPOVERLAPPED)&overlapped)) { + if (!ReadFile(handle, buffer, size, &bytes_read, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped))) { const unsigned long errorIoPending = 997; unsigned long error = GetLastError(); // Means we've got an async read! if (error == errorIoPending) { - if (!GetOverlappedResult(handle, (LPOVERLAPPED)&overlapped, &bytes_read, - 1)) { + if (!GetOverlappedResult(handle, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped), + &bytes_read, 1)) { const unsigned long errorIoIncomplete = 996; const unsigned long errorHandleEOF = 38; error = GetLastError(); @@ -781,7 +881,7 @@ } } - return (unsigned)bytes_read; + return SUBPROCESS_CAST(unsigned, bytes_read); #else const int fd = fileno(process->stdout_file); const ssize_t bytes_read = read(fd, buffer, size); @@ -799,19 +899,22 @@ #if defined(_MSC_VER) void *handle; unsigned long bytes_read = 0; - struct subprocess_overlapped_s overlapped = {0}; + struct subprocess_overlapped_s overlapped = {0, 0, {{0, 0}}, SUBPROCESS_NULL}; overlapped.hEvent = process->hEventError; - handle = (void *)_get_osfhandle(_fileno(process->stderr_file)); + handle = SUBPROCESS_PTR_CAST(void *, + _get_osfhandle(_fileno(process->stderr_file))); - if (!ReadFile(handle, buffer, size, &bytes_read, (LPOVERLAPPED)&overlapped)) { + if (!ReadFile(handle, buffer, size, &bytes_read, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped))) { const unsigned long errorIoPending = 997; unsigned long error = GetLastError(); // Means we've got an async read! if (error == errorIoPending) { - if (!GetOverlappedResult(handle, (LPOVERLAPPED)&overlapped, &bytes_read, - 1)) { + if (!GetOverlappedResult(handle, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped), + &bytes_read, 1)) { const unsigned long errorIoIncomplete = 996; const unsigned long errorHandleEOF = 38; error = GetLastError(); @@ -823,7 +926,7 @@ } } - return (unsigned)bytes_read; + return SUBPROCESS_CAST(unsigned, bytes_read); #else const int fd = fileno(process->stderr_file); const ssize_t bytes_read = read(fd, buffer, size); @@ -836,6 +939,50 @@ #endif } +int subprocess_alive(struct subprocess_s *const process) { + int is_alive = SUBPROCESS_CAST(int, process->alive); + + if (!is_alive) { + return 0; + } +#if defined(_MSC_VER) + { + const unsigned long zero = 0x0; + const unsigned long wait_object_0 = 0x00000000L; + + is_alive = wait_object_0 != WaitForSingleObject(process->hProcess, zero); + } +#else + { + int status; + is_alive = 0 == waitpid(process->child, &status, WNOHANG); + + // If the process was successfully waited on we need to cleanup now. + if (!is_alive) { + if (WIFEXITED(status)) { + process->return_status = WEXITSTATUS(status); + } else { + process->return_status = EXIT_FAILURE; + } + + // Since we've already successfully waited on the process, we need to wipe + // the child now. + process->child = 0; + + if (subprocess_join(process, SUBPROCESS_NULL)) { + return -1; + } + } + } +#endif + + if (!is_alive) { + process->alive = 0; + } + + return is_alive; +} + #if defined(__cplusplus) } // extern "C" #endif diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 7167c09..8972f1d 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -14,7 +14,6 @@ type: [Debug, RelWithDebInfo, MinSizeRel, Release] compiler: [default, clang, gcc] exclude: - - {os: "windows-latest", compiler: "clang"} - {os: "macOS-latest", compiler: "clang"} - {os: "windows-latest", compiler: "gcc"} - {os: "macOS-latest", compiler: "gcc"} @@ -44,12 +43,18 @@ working-directory: ${{github.workspace}}/build run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 - - name: Configure CMake with Clang + - name: Configure CMake with Clang (Ubuntu) shell: bash - if: matrix.compiler == 'clang' + if: (matrix.compiler == 'clang') && startsWith(matrix.os, 'ubuntu') working-directory: ${{github.workspace}}/build run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_C_COMPILER=clang-10 -DCMAKE_CXX_COMPILER=clang++-10 + - name: Configure CMake with Clang (Windows) + shell: bash + if: (matrix.compiler == 'clang') && startsWith(matrix.os, 'windows') + working-directory: ${{github.workspace}}/build + run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -T ClangCL + - name: Build working-directory: ${{github.workspace}}/build shell: bash diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 028300f..a0d849f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -24,7 +24,7 @@ # For more information, please refer to project(utest) -cmake_minimum_required(VERSION 3.1.3) +cmake_minimum_required(VERSION 3.15) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../) @@ -33,9 +33,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=gnu89" ) elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(main.c test.c PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu89" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(main.c test.c PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(main.c test.c PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu89" + ) + endif() elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(main.c test.c PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -49,9 +55,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=c99" ) elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(test99.c PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c99" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test99.c PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(test99.c PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c99" + ) + endif() elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test99.c PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -70,9 +82,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=c11" ) elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test11.c PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() set_source_files_properties(test11.c PROPERTIES COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c11" ) + endif() elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test11.c PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -87,9 +105,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=gnu++98" ) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(test.cpp PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu++98" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test.cpp PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(test.cpp PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu++98" + ) + endif() elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test.cpp PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -103,9 +127,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=c++11" ) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(test11.cpp PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c++11 -Wno-c++98-compat" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test11.cpp PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(test11.cpp PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c++11 -Wno-c++98-compat" + ) + endif() elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test11.cpp PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -145,19 +175,23 @@ target_link_options(utest_test_wpo PRIVATE "$<$:/LTCG>") endif() -if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") - add_executable(utest_test_mt - ../utest.h - main.c - test.c - test11.c - test99.c - test.cpp - test11.cpp - stdint_include.c - type_printers.c - type_printers.cpp - ) +add_executable(utest_test_mt + ../utest.h + main.c + test.c + test11.c + test99.c + test.cpp + test11.cpp + stdint_include.c + type_printers.c + type_printers.cpp +) - target_compile_definitions(utest_test_mt PUBLIC "/MT") -endif() +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + target_compile_options(utest_test_mt PUBLIC "/MT") + endif() +elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + target_compile_options(utest_test_mt PUBLIC "/MT") +endif() \ No newline at end of file diff --git a/test/subprocess.h b/test/subprocess.h index 8f63657..b25bc47 100644 --- a/test/subprocess.h +++ b/test/subprocess.h @@ -43,12 +43,12 @@ #pragma warning(pop) #endif -#if defined(__clang__) || defined(__GNUC__) -#define subprocess_pure __attribute__((pure)) -#define subprocess_weak __attribute__((weak)) -#elif defined(_MSC_VER) +#if defined(_MSC_VER) #define subprocess_pure #define subprocess_weak __inline +#elif defined(__clang__) || defined(__GNUC__) +#define subprocess_pure __attribute__((pure)) +#define subprocess_weak __attribute__((weak)) #else #error Non clang, non gcc, non MSVC compiler found! #endif @@ -75,7 +75,7 @@ /// this process. The last element must be NULL to signify the end of the array. /// @param options A bit field of subprocess_option_e's to pass. /// @param out_process The newly created process. -/// @return On success 0 is returned. +/// @return On success zero is returned. subprocess_weak int subprocess_create(const char *const command_line[], int options, struct subprocess_s *const out_process); @@ -115,7 +115,7 @@ /// @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. +/// @return On success zero is returned. /// /// Joining a process will close the stdin pipe to the process. subprocess_weak int subprocess_join(struct subprocess_s *const process, @@ -123,7 +123,7 @@ /// @brief Destroy a previously created process. /// @param process The process to destroy. -/// @return On success 0 is returned. +/// @return On success zero is returned. /// /// If the process to be destroyed had not finished execution, it may out live /// the parent process. @@ -131,7 +131,7 @@ /// @brief Terminate a previously created process. /// @param process The process to terminate. -/// @return On success 0 is returned. +/// @return On success zero is returned. /// /// If the process to be destroyed had not finished execution, it will be /// terminated (i.e killed). @@ -165,21 +165,34 @@ subprocess_read_stderr(struct subprocess_s *const process, char *const buffer, unsigned size); +/// @brief Returns if the subprocess is currently still alive and executing. +/// @param process The process to check. +/// @return If the process is still alive non-zero is returned. +subprocess_weak int subprocess_alive(struct subprocess_s *const process); + #if defined(__cplusplus) #define SUBPROCESS_CAST(type, x) static_cast(x) +#define SUBPROCESS_PTR_CAST(type, x) reinterpret_cast(x) +#define SUBPROCESS_CONST_CAST(type, x) const_cast(x) +#define SUBPROCESS_NULL NULL #else -#define SUBPROCESS_CAST(type, x) ((type)x) +#define SUBPROCESS_CAST(type, x) ((type)(x)) +#define SUBPROCESS_PTR_CAST(type, x) ((type)(x)) +#define SUBPROCESS_CONST_CAST(type, x) ((type)(x)) +#define SUBPROCESS_NULL 0 #endif #if !defined(_MSC_VER) +#include #include #include #include #include -#include #endif #if defined(_MSC_VER) + +#if (_MSC_VER < 1920) #ifdef _WIN64 typedef __int64 subprocess_intptr_t; typedef unsigned __int64 subprocess_size_t; @@ -187,6 +200,12 @@ typedef int subprocess_intptr_t; typedef unsigned int subprocess_size_t; #endif +#else +#include + +typedef intptr_t subprocess_intptr_t; +typedef size_t subprocess_size_t; +#endif typedef struct _PROCESS_INFORMATION *LPPROCESS_INFORMATION; typedef struct _SECURITY_ATTRIBUTES *LPSECURITY_ATTRIBUTES; @@ -272,8 +291,7 @@ void *, unsigned long); __declspec(dllimport) int __stdcall GetExitCodeProcess( void *, unsigned long *lpExitCode); -__declspec(dllimport) int __stdcall TerminateProcess( - void *, unsigned int); +__declspec(dllimport) int __stdcall TerminateProcess(void *, unsigned int); __declspec(dllimport) unsigned long __stdcall WaitForMultipleObjects( unsigned long, void *const *, int, unsigned long); __declspec(dllimport) int __stdcall GetOverlappedResult(void *, LPOVERLAPPED, @@ -290,6 +308,8 @@ SUBPROCESS_DLLIMPORT subprocess_intptr_t __cdecl _get_osfhandle(int); void *__cdecl _alloca(subprocess_size_t); +#else +typedef size_t subprocess_size_t; #endif #ifdef __clang__ @@ -308,7 +328,10 @@ void *hEventError; #else pid_t child; + int return_status; #endif + + subprocess_size_t alive; }; #ifdef __clang__ #pragma clang diagnostic pop @@ -324,8 +347,10 @@ const unsigned long genericWrite = 0x40000000; const unsigned long openExisting = 3; const unsigned long fileAttributeNormal = 0x00000080; - const void *const invalidHandleValue = (void *)~((subprocess_intptr_t)0); - struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), 0, 1}; + const void *const invalidHandleValue = + SUBPROCESS_PTR_CAST(void *, ~(SUBPROCESS_CAST(subprocess_intptr_t, 0))); + struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), + SUBPROCESS_NULL, 1}; char name[256] = {0}; __declspec(thread) static long index = 0; const long unique = index++; @@ -348,16 +373,18 @@ #pragma warning(pop) #endif - *rd = CreateNamedPipeA(name, pipeAccessInbound | fileFlagOverlapped, - pipeTypeByte | pipeWait, 1, 4096, 4096, 0, - (LPSECURITY_ATTRIBUTES)&saAttr); + *rd = + CreateNamedPipeA(name, pipeAccessInbound | fileFlagOverlapped, + pipeTypeByte | pipeWait, 1, 4096, 4096, SUBPROCESS_NULL, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr)); if (invalidHandleValue == rd) { return -1; } - *wr = CreateFileA(name, genericWrite, 0, (LPSECURITY_ATTRIBUTES)&saAttr, - openExisting, fileAttributeNormal, 0); + *wr = CreateFileA(name, genericWrite, SUBPROCESS_NULL, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), + openExisting, fileAttributeNormal, SUBPROCESS_NULL); if (invalidHandleValue == wr) { return -1; @@ -378,20 +405,38 @@ const unsigned long startFUseStdHandles = 0x00000100; const unsigned long handleFlagInherit = 0x00000001; struct subprocess_subprocess_information_s processInfo; - struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), 0, 1}; - char *environment = 0; - struct subprocess_startup_info_s startInfo = {0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0}; + struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), + SUBPROCESS_NULL, 1}; + char *environment = SUBPROCESS_NULL; + struct subprocess_startup_info_s startInfo = {0, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL}; startInfo.cb = sizeof(startInfo); startInfo.dwFlags = startFUseStdHandles; if (subprocess_option_inherit_environment != (options & subprocess_option_inherit_environment)) { - environment = "\0\0"; + environment = SUBPROCESS_CONST_CAST(char *, "\0\0"); } - if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + if (!CreatePipe(&rd, &wr, SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), + 0)) { return -1; } @@ -399,12 +444,12 @@ return -1; } - fd = _open_osfhandle((subprocess_intptr_t)wr, 0); + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, wr), 0); if (-1 != fd) { out_process->stdin_file = _fdopen(fd, "wb"); - if (0 == out_process->stdin_file) { + if (SUBPROCESS_NULL == out_process->stdin_file) { return -1; } } @@ -416,7 +461,8 @@ return -1; } } else { - if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + if (!CreatePipe(&rd, &wr, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) { return -1; } } @@ -425,12 +471,12 @@ return -1; } - fd = _open_osfhandle((subprocess_intptr_t)rd, 0); + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, rd), 0); if (-1 != fd) { out_process->stdout_file = _fdopen(fd, "rb"); - if (0 == out_process->stdout_file) { + if (SUBPROCESS_NULL == out_process->stdout_file) { return -1; } } @@ -447,7 +493,8 @@ return -1; } } else { - if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + if (!CreatePipe(&rd, &wr, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) { return -1; } } @@ -456,12 +503,12 @@ return -1; } - fd = _open_osfhandle((subprocess_intptr_t)rd, 0); + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, rd), 0); if (-1 != fd) { out_process->stderr_file = _fdopen(fd, "rb"); - if (0 == out_process->stderr_file) { + if (SUBPROCESS_NULL == out_process->stderr_file) { return -1; } } @@ -471,26 +518,41 @@ if (options & subprocess_option_enable_async) { out_process->hEventOutput = - CreateEventA((LPSECURITY_ATTRIBUTES)&saAttr, 1, 1, 0); + CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1, + SUBPROCESS_NULL); out_process->hEventError = - CreateEventA((LPSECURITY_ATTRIBUTES)&saAttr, 1, 1, 0); + CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1, + SUBPROCESS_NULL); } else { - out_process->hEventOutput = 0; - out_process->hEventError = 0; + out_process->hEventOutput = SUBPROCESS_NULL; + out_process->hEventError = SUBPROCESS_NULL; } // Combine commandLine together into a single string len = 0; for (i = 0; commandLine[i]; i++) { - // For the ' ' between items and trailing '\0' - len++; + // For the ' ' and two '"' between items and trailing '\0' + len += 3; for (j = 0; '\0' != commandLine[i][j]; j++) { + switch (commandLine[i][j]) { + default: + break; + case '\\': + if (commandLine[i][j + 1] == '"') { + len++; + } + + break; + case '"': + len++; + break; + } len++; } } - commandLineCombined = (char *)_alloca(len); + commandLineCombined = SUBPROCESS_CAST(char *, _alloca(len)); if (!commandLineCombined) { return -1; @@ -503,24 +565,42 @@ if (0 != i) { commandLineCombined[len++] = ' '; } + commandLineCombined[len++] = '"'; for (j = 0; '\0' != commandLine[i][j]; j++) { + switch (commandLine[i][j]) { + default: + break; + case '\\': + if (commandLine[i][j + 1] == '"') { + commandLineCombined[len++] = '\\'; + } + + break; + case '"': + commandLineCombined[len++] = '\\'; + break; + } + commandLineCombined[len++] = commandLine[i][j]; } + commandLineCombined[len++] = '"'; } 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)) { + if (!CreateProcessA( + SUBPROCESS_NULL, + commandLineCombined, // command line + SUBPROCESS_NULL, // process security attributes + SUBPROCESS_NULL, // primary thread security attributes + 1, // handles are inherited + 0, // creation flags + environment, // use parent's environment + SUBPROCESS_NULL, // use parent's current directory + SUBPROCESS_PTR_CAST(LPSTARTUPINFOA, + &startInfo), // STARTUPINFO pointer + SUBPROCESS_PTR_CAST(LPPROCESS_INFORMATION, &processInfo))) { return -1; } @@ -531,7 +611,7 @@ // We don't need the handle of the primary thread in the called process. CloseHandle(processInfo.hThread); - if (0 != startInfo.hStdOutput) { + if (SUBPROCESS_NULL != startInfo.hStdOutput) { CloseHandle(startInfo.hStdOutput); if (startInfo.hStdError != startInfo.hStdOutput) { @@ -539,6 +619,8 @@ } } + out_process->alive = 1; + return 0; #else int stdinfd[2]; @@ -595,7 +677,7 @@ #endif if (subprocess_option_inherit_environment != (options & subprocess_option_inherit_environment)) { - char *const environment[1] = {0}; + char *const environment[1] = {SUBPROCESS_NULL}; exit(execve(commandLine[0], (char *const *)commandLine, environment)); } else { exit(execvp(commandLine[0], (char *const *)commandLine)); @@ -627,6 +709,8 @@ // Store the child's pid out_process->child = child; + out_process->alive = 1; + return 0; } #endif @@ -644,7 +728,7 @@ if (process->stdout_file != process->stderr_file) { return process->stderr_file; } else { - return 0; + return SUBPROCESS_NULL; } } @@ -653,43 +737,55 @@ #if defined(_MSC_VER) const unsigned long infinite = 0xFFFFFFFF; - if (0 != process->stdin_file) { + if (process->stdin_file) { fclose(process->stdin_file); - process->stdin_file = 0; + process->stdin_file = SUBPROCESS_NULL; } - if (0 != process->hStdInput) { + + if (process->hStdInput) { CloseHandle(process->hStdInput); - process->hStdInput = NULL; + process->hStdInput = SUBPROCESS_NULL; } WaitForSingleObject(process->hProcess, infinite); if (out_return_code) { - if (!GetExitCodeProcess(process->hProcess, - (unsigned long *)out_return_code)) { + if (!GetExitCodeProcess( + process->hProcess, + SUBPROCESS_PTR_CAST(unsigned long *, out_return_code))) { return -1; } } + process->alive = 0; + return 0; #else int status; - if (0 != process->stdin_file) { + if (process->stdin_file) { fclose(process->stdin_file); - process->stdin_file = 0; + process->stdin_file = SUBPROCESS_NULL; } - if (process->child != waitpid(process->child, &status, 0)) { - return -1; + if (process->child) { + if (process->child != waitpid(process->child, &status, 0)) { + return -1; + } + + process->child = 0; + + if (WIFEXITED(status)) { + process->return_status = WEXITSTATUS(status); + } else { + process->return_status = EXIT_FAILURE; + } + + process->alive = 0; } if (out_return_code) { - if (WIFEXITED(status)) { - *out_return_code = WEXITSTATUS(status); - } else { - *out_return_code = EXIT_FAILURE; - } + *out_return_code = process->return_status; } return 0; @@ -697,36 +793,36 @@ } int subprocess_destroy(struct subprocess_s *const process) { - if (0 != process->stdin_file) { + if (process->stdin_file) { fclose(process->stdin_file); - process->stdin_file = 0; + process->stdin_file = SUBPROCESS_NULL; } - if (0 != process->stdout_file) { + if (process->stdout_file) { fclose(process->stdout_file); if (process->stdout_file != process->stderr_file) { fclose(process->stderr_file); } - process->stdout_file = 0; - process->stderr_file = 0; + process->stdout_file = SUBPROCESS_NULL; + process->stderr_file = SUBPROCESS_NULL; } #if defined(_MSC_VER) if (process->hProcess) { CloseHandle(process->hProcess); - process->hProcess = 0; + process->hProcess = SUBPROCESS_NULL; - if (0 != process->hStdInput) { + if (process->hStdInput) { CloseHandle(process->hStdInput); } - if (0 != process->hEventOutput) { + if (process->hEventOutput) { CloseHandle(process->hEventOutput); } - if (0 != process->hEventError) { + if (process->hEventError) { CloseHandle(process->hEventError); } } @@ -740,10 +836,11 @@ unsigned int killed_process_exit_code; int success_terminate; int windows_call_result; - + killed_process_exit_code = 99; - windows_call_result = TerminateProcess(process->hProcess, killed_process_exit_code); - success_terminate = (windows_call_result== 0) ? 1 : 0; + windows_call_result = + TerminateProcess(process->hProcess, killed_process_exit_code); + success_terminate = (windows_call_result == 0) ? 1 : 0; return success_terminate; #else int result; @@ -757,19 +854,22 @@ #if defined(_MSC_VER) void *handle; unsigned long bytes_read = 0; - struct subprocess_overlapped_s overlapped = {0}; + struct subprocess_overlapped_s overlapped = {0, 0, {{0, 0}}, SUBPROCESS_NULL}; overlapped.hEvent = process->hEventOutput; - handle = (void *)_get_osfhandle(_fileno(process->stdout_file)); + handle = SUBPROCESS_PTR_CAST(void *, + _get_osfhandle(_fileno(process->stdout_file))); - if (!ReadFile(handle, buffer, size, &bytes_read, (LPOVERLAPPED)&overlapped)) { + if (!ReadFile(handle, buffer, size, &bytes_read, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped))) { const unsigned long errorIoPending = 997; unsigned long error = GetLastError(); // Means we've got an async read! if (error == errorIoPending) { - if (!GetOverlappedResult(handle, (LPOVERLAPPED)&overlapped, &bytes_read, - 1)) { + if (!GetOverlappedResult(handle, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped), + &bytes_read, 1)) { const unsigned long errorIoIncomplete = 996; const unsigned long errorHandleEOF = 38; error = GetLastError(); @@ -781,7 +881,7 @@ } } - return (unsigned)bytes_read; + return SUBPROCESS_CAST(unsigned, bytes_read); #else const int fd = fileno(process->stdout_file); const ssize_t bytes_read = read(fd, buffer, size); @@ -799,19 +899,22 @@ #if defined(_MSC_VER) void *handle; unsigned long bytes_read = 0; - struct subprocess_overlapped_s overlapped = {0}; + struct subprocess_overlapped_s overlapped = {0, 0, {{0, 0}}, SUBPROCESS_NULL}; overlapped.hEvent = process->hEventError; - handle = (void *)_get_osfhandle(_fileno(process->stderr_file)); + handle = SUBPROCESS_PTR_CAST(void *, + _get_osfhandle(_fileno(process->stderr_file))); - if (!ReadFile(handle, buffer, size, &bytes_read, (LPOVERLAPPED)&overlapped)) { + if (!ReadFile(handle, buffer, size, &bytes_read, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped))) { const unsigned long errorIoPending = 997; unsigned long error = GetLastError(); // Means we've got an async read! if (error == errorIoPending) { - if (!GetOverlappedResult(handle, (LPOVERLAPPED)&overlapped, &bytes_read, - 1)) { + if (!GetOverlappedResult(handle, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped), + &bytes_read, 1)) { const unsigned long errorIoIncomplete = 996; const unsigned long errorHandleEOF = 38; error = GetLastError(); @@ -823,7 +926,7 @@ } } - return (unsigned)bytes_read; + return SUBPROCESS_CAST(unsigned, bytes_read); #else const int fd = fileno(process->stderr_file); const ssize_t bytes_read = read(fd, buffer, size); @@ -836,6 +939,50 @@ #endif } +int subprocess_alive(struct subprocess_s *const process) { + int is_alive = SUBPROCESS_CAST(int, process->alive); + + if (!is_alive) { + return 0; + } +#if defined(_MSC_VER) + { + const unsigned long zero = 0x0; + const unsigned long wait_object_0 = 0x00000000L; + + is_alive = wait_object_0 != WaitForSingleObject(process->hProcess, zero); + } +#else + { + int status; + is_alive = 0 == waitpid(process->child, &status, WNOHANG); + + // If the process was successfully waited on we need to cleanup now. + if (!is_alive) { + if (WIFEXITED(status)) { + process->return_status = WEXITSTATUS(status); + } else { + process->return_status = EXIT_FAILURE; + } + + // Since we've already successfully waited on the process, we need to wipe + // the child now. + process->child = 0; + + if (subprocess_join(process, SUBPROCESS_NULL)) { + return -1; + } + } + } +#endif + + if (!is_alive) { + process->alive = 0; + } + + return is_alive; +} + #if defined(__cplusplus) } // extern "C" #endif diff --git a/test/test.c b/test/test.c index ce5e2d4..a880e68 100644 --- a/test/test.c +++ b/test/test.c @@ -31,7 +31,6 @@ /* disable 'conditional expression is constant' - our examples below use this! */ #pragma warning(disable : 4127) -#pragma #endif UTEST(c, ASSERT_TRUE) { ASSERT_TRUE(1); } diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 7167c09..8972f1d 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -14,7 +14,6 @@ type: [Debug, RelWithDebInfo, MinSizeRel, Release] compiler: [default, clang, gcc] exclude: - - {os: "windows-latest", compiler: "clang"} - {os: "macOS-latest", compiler: "clang"} - {os: "windows-latest", compiler: "gcc"} - {os: "macOS-latest", compiler: "gcc"} @@ -44,12 +43,18 @@ working-directory: ${{github.workspace}}/build run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 - - name: Configure CMake with Clang + - name: Configure CMake with Clang (Ubuntu) shell: bash - if: matrix.compiler == 'clang' + if: (matrix.compiler == 'clang') && startsWith(matrix.os, 'ubuntu') working-directory: ${{github.workspace}}/build run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_C_COMPILER=clang-10 -DCMAKE_CXX_COMPILER=clang++-10 + - name: Configure CMake with Clang (Windows) + shell: bash + if: (matrix.compiler == 'clang') && startsWith(matrix.os, 'windows') + working-directory: ${{github.workspace}}/build + run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -T ClangCL + - name: Build working-directory: ${{github.workspace}}/build shell: bash diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 028300f..a0d849f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -24,7 +24,7 @@ # For more information, please refer to project(utest) -cmake_minimum_required(VERSION 3.1.3) +cmake_minimum_required(VERSION 3.15) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../) @@ -33,9 +33,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=gnu89" ) elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(main.c test.c PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu89" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(main.c test.c PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(main.c test.c PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu89" + ) + endif() elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(main.c test.c PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -49,9 +55,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=c99" ) elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(test99.c PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c99" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test99.c PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(test99.c PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c99" + ) + endif() elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test99.c PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -70,9 +82,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=c11" ) elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test11.c PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() set_source_files_properties(test11.c PROPERTIES COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c11" ) + endif() elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test11.c PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -87,9 +105,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=gnu++98" ) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(test.cpp PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu++98" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test.cpp PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(test.cpp PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu++98" + ) + endif() elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test.cpp PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -103,9 +127,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=c++11" ) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(test11.cpp PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c++11 -Wno-c++98-compat" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test11.cpp PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(test11.cpp PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c++11 -Wno-c++98-compat" + ) + endif() elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test11.cpp PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -145,19 +175,23 @@ target_link_options(utest_test_wpo PRIVATE "$<$:/LTCG>") endif() -if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") - add_executable(utest_test_mt - ../utest.h - main.c - test.c - test11.c - test99.c - test.cpp - test11.cpp - stdint_include.c - type_printers.c - type_printers.cpp - ) +add_executable(utest_test_mt + ../utest.h + main.c + test.c + test11.c + test99.c + test.cpp + test11.cpp + stdint_include.c + type_printers.c + type_printers.cpp +) - target_compile_definitions(utest_test_mt PUBLIC "/MT") -endif() +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + target_compile_options(utest_test_mt PUBLIC "/MT") + endif() +elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + target_compile_options(utest_test_mt PUBLIC "/MT") +endif() \ No newline at end of file diff --git a/test/subprocess.h b/test/subprocess.h index 8f63657..b25bc47 100644 --- a/test/subprocess.h +++ b/test/subprocess.h @@ -43,12 +43,12 @@ #pragma warning(pop) #endif -#if defined(__clang__) || defined(__GNUC__) -#define subprocess_pure __attribute__((pure)) -#define subprocess_weak __attribute__((weak)) -#elif defined(_MSC_VER) +#if defined(_MSC_VER) #define subprocess_pure #define subprocess_weak __inline +#elif defined(__clang__) || defined(__GNUC__) +#define subprocess_pure __attribute__((pure)) +#define subprocess_weak __attribute__((weak)) #else #error Non clang, non gcc, non MSVC compiler found! #endif @@ -75,7 +75,7 @@ /// this process. The last element must be NULL to signify the end of the array. /// @param options A bit field of subprocess_option_e's to pass. /// @param out_process The newly created process. -/// @return On success 0 is returned. +/// @return On success zero is returned. subprocess_weak int subprocess_create(const char *const command_line[], int options, struct subprocess_s *const out_process); @@ -115,7 +115,7 @@ /// @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. +/// @return On success zero is returned. /// /// Joining a process will close the stdin pipe to the process. subprocess_weak int subprocess_join(struct subprocess_s *const process, @@ -123,7 +123,7 @@ /// @brief Destroy a previously created process. /// @param process The process to destroy. -/// @return On success 0 is returned. +/// @return On success zero is returned. /// /// If the process to be destroyed had not finished execution, it may out live /// the parent process. @@ -131,7 +131,7 @@ /// @brief Terminate a previously created process. /// @param process The process to terminate. -/// @return On success 0 is returned. +/// @return On success zero is returned. /// /// If the process to be destroyed had not finished execution, it will be /// terminated (i.e killed). @@ -165,21 +165,34 @@ subprocess_read_stderr(struct subprocess_s *const process, char *const buffer, unsigned size); +/// @brief Returns if the subprocess is currently still alive and executing. +/// @param process The process to check. +/// @return If the process is still alive non-zero is returned. +subprocess_weak int subprocess_alive(struct subprocess_s *const process); + #if defined(__cplusplus) #define SUBPROCESS_CAST(type, x) static_cast(x) +#define SUBPROCESS_PTR_CAST(type, x) reinterpret_cast(x) +#define SUBPROCESS_CONST_CAST(type, x) const_cast(x) +#define SUBPROCESS_NULL NULL #else -#define SUBPROCESS_CAST(type, x) ((type)x) +#define SUBPROCESS_CAST(type, x) ((type)(x)) +#define SUBPROCESS_PTR_CAST(type, x) ((type)(x)) +#define SUBPROCESS_CONST_CAST(type, x) ((type)(x)) +#define SUBPROCESS_NULL 0 #endif #if !defined(_MSC_VER) +#include #include #include #include #include -#include #endif #if defined(_MSC_VER) + +#if (_MSC_VER < 1920) #ifdef _WIN64 typedef __int64 subprocess_intptr_t; typedef unsigned __int64 subprocess_size_t; @@ -187,6 +200,12 @@ typedef int subprocess_intptr_t; typedef unsigned int subprocess_size_t; #endif +#else +#include + +typedef intptr_t subprocess_intptr_t; +typedef size_t subprocess_size_t; +#endif typedef struct _PROCESS_INFORMATION *LPPROCESS_INFORMATION; typedef struct _SECURITY_ATTRIBUTES *LPSECURITY_ATTRIBUTES; @@ -272,8 +291,7 @@ void *, unsigned long); __declspec(dllimport) int __stdcall GetExitCodeProcess( void *, unsigned long *lpExitCode); -__declspec(dllimport) int __stdcall TerminateProcess( - void *, unsigned int); +__declspec(dllimport) int __stdcall TerminateProcess(void *, unsigned int); __declspec(dllimport) unsigned long __stdcall WaitForMultipleObjects( unsigned long, void *const *, int, unsigned long); __declspec(dllimport) int __stdcall GetOverlappedResult(void *, LPOVERLAPPED, @@ -290,6 +308,8 @@ SUBPROCESS_DLLIMPORT subprocess_intptr_t __cdecl _get_osfhandle(int); void *__cdecl _alloca(subprocess_size_t); +#else +typedef size_t subprocess_size_t; #endif #ifdef __clang__ @@ -308,7 +328,10 @@ void *hEventError; #else pid_t child; + int return_status; #endif + + subprocess_size_t alive; }; #ifdef __clang__ #pragma clang diagnostic pop @@ -324,8 +347,10 @@ const unsigned long genericWrite = 0x40000000; const unsigned long openExisting = 3; const unsigned long fileAttributeNormal = 0x00000080; - const void *const invalidHandleValue = (void *)~((subprocess_intptr_t)0); - struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), 0, 1}; + const void *const invalidHandleValue = + SUBPROCESS_PTR_CAST(void *, ~(SUBPROCESS_CAST(subprocess_intptr_t, 0))); + struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), + SUBPROCESS_NULL, 1}; char name[256] = {0}; __declspec(thread) static long index = 0; const long unique = index++; @@ -348,16 +373,18 @@ #pragma warning(pop) #endif - *rd = CreateNamedPipeA(name, pipeAccessInbound | fileFlagOverlapped, - pipeTypeByte | pipeWait, 1, 4096, 4096, 0, - (LPSECURITY_ATTRIBUTES)&saAttr); + *rd = + CreateNamedPipeA(name, pipeAccessInbound | fileFlagOverlapped, + pipeTypeByte | pipeWait, 1, 4096, 4096, SUBPROCESS_NULL, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr)); if (invalidHandleValue == rd) { return -1; } - *wr = CreateFileA(name, genericWrite, 0, (LPSECURITY_ATTRIBUTES)&saAttr, - openExisting, fileAttributeNormal, 0); + *wr = CreateFileA(name, genericWrite, SUBPROCESS_NULL, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), + openExisting, fileAttributeNormal, SUBPROCESS_NULL); if (invalidHandleValue == wr) { return -1; @@ -378,20 +405,38 @@ const unsigned long startFUseStdHandles = 0x00000100; const unsigned long handleFlagInherit = 0x00000001; struct subprocess_subprocess_information_s processInfo; - struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), 0, 1}; - char *environment = 0; - struct subprocess_startup_info_s startInfo = {0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0}; + struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), + SUBPROCESS_NULL, 1}; + char *environment = SUBPROCESS_NULL; + struct subprocess_startup_info_s startInfo = {0, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL}; startInfo.cb = sizeof(startInfo); startInfo.dwFlags = startFUseStdHandles; if (subprocess_option_inherit_environment != (options & subprocess_option_inherit_environment)) { - environment = "\0\0"; + environment = SUBPROCESS_CONST_CAST(char *, "\0\0"); } - if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + if (!CreatePipe(&rd, &wr, SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), + 0)) { return -1; } @@ -399,12 +444,12 @@ return -1; } - fd = _open_osfhandle((subprocess_intptr_t)wr, 0); + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, wr), 0); if (-1 != fd) { out_process->stdin_file = _fdopen(fd, "wb"); - if (0 == out_process->stdin_file) { + if (SUBPROCESS_NULL == out_process->stdin_file) { return -1; } } @@ -416,7 +461,8 @@ return -1; } } else { - if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + if (!CreatePipe(&rd, &wr, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) { return -1; } } @@ -425,12 +471,12 @@ return -1; } - fd = _open_osfhandle((subprocess_intptr_t)rd, 0); + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, rd), 0); if (-1 != fd) { out_process->stdout_file = _fdopen(fd, "rb"); - if (0 == out_process->stdout_file) { + if (SUBPROCESS_NULL == out_process->stdout_file) { return -1; } } @@ -447,7 +493,8 @@ return -1; } } else { - if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + if (!CreatePipe(&rd, &wr, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) { return -1; } } @@ -456,12 +503,12 @@ return -1; } - fd = _open_osfhandle((subprocess_intptr_t)rd, 0); + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, rd), 0); if (-1 != fd) { out_process->stderr_file = _fdopen(fd, "rb"); - if (0 == out_process->stderr_file) { + if (SUBPROCESS_NULL == out_process->stderr_file) { return -1; } } @@ -471,26 +518,41 @@ if (options & subprocess_option_enable_async) { out_process->hEventOutput = - CreateEventA((LPSECURITY_ATTRIBUTES)&saAttr, 1, 1, 0); + CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1, + SUBPROCESS_NULL); out_process->hEventError = - CreateEventA((LPSECURITY_ATTRIBUTES)&saAttr, 1, 1, 0); + CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1, + SUBPROCESS_NULL); } else { - out_process->hEventOutput = 0; - out_process->hEventError = 0; + out_process->hEventOutput = SUBPROCESS_NULL; + out_process->hEventError = SUBPROCESS_NULL; } // Combine commandLine together into a single string len = 0; for (i = 0; commandLine[i]; i++) { - // For the ' ' between items and trailing '\0' - len++; + // For the ' ' and two '"' between items and trailing '\0' + len += 3; for (j = 0; '\0' != commandLine[i][j]; j++) { + switch (commandLine[i][j]) { + default: + break; + case '\\': + if (commandLine[i][j + 1] == '"') { + len++; + } + + break; + case '"': + len++; + break; + } len++; } } - commandLineCombined = (char *)_alloca(len); + commandLineCombined = SUBPROCESS_CAST(char *, _alloca(len)); if (!commandLineCombined) { return -1; @@ -503,24 +565,42 @@ if (0 != i) { commandLineCombined[len++] = ' '; } + commandLineCombined[len++] = '"'; for (j = 0; '\0' != commandLine[i][j]; j++) { + switch (commandLine[i][j]) { + default: + break; + case '\\': + if (commandLine[i][j + 1] == '"') { + commandLineCombined[len++] = '\\'; + } + + break; + case '"': + commandLineCombined[len++] = '\\'; + break; + } + commandLineCombined[len++] = commandLine[i][j]; } + commandLineCombined[len++] = '"'; } 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)) { + if (!CreateProcessA( + SUBPROCESS_NULL, + commandLineCombined, // command line + SUBPROCESS_NULL, // process security attributes + SUBPROCESS_NULL, // primary thread security attributes + 1, // handles are inherited + 0, // creation flags + environment, // use parent's environment + SUBPROCESS_NULL, // use parent's current directory + SUBPROCESS_PTR_CAST(LPSTARTUPINFOA, + &startInfo), // STARTUPINFO pointer + SUBPROCESS_PTR_CAST(LPPROCESS_INFORMATION, &processInfo))) { return -1; } @@ -531,7 +611,7 @@ // We don't need the handle of the primary thread in the called process. CloseHandle(processInfo.hThread); - if (0 != startInfo.hStdOutput) { + if (SUBPROCESS_NULL != startInfo.hStdOutput) { CloseHandle(startInfo.hStdOutput); if (startInfo.hStdError != startInfo.hStdOutput) { @@ -539,6 +619,8 @@ } } + out_process->alive = 1; + return 0; #else int stdinfd[2]; @@ -595,7 +677,7 @@ #endif if (subprocess_option_inherit_environment != (options & subprocess_option_inherit_environment)) { - char *const environment[1] = {0}; + char *const environment[1] = {SUBPROCESS_NULL}; exit(execve(commandLine[0], (char *const *)commandLine, environment)); } else { exit(execvp(commandLine[0], (char *const *)commandLine)); @@ -627,6 +709,8 @@ // Store the child's pid out_process->child = child; + out_process->alive = 1; + return 0; } #endif @@ -644,7 +728,7 @@ if (process->stdout_file != process->stderr_file) { return process->stderr_file; } else { - return 0; + return SUBPROCESS_NULL; } } @@ -653,43 +737,55 @@ #if defined(_MSC_VER) const unsigned long infinite = 0xFFFFFFFF; - if (0 != process->stdin_file) { + if (process->stdin_file) { fclose(process->stdin_file); - process->stdin_file = 0; + process->stdin_file = SUBPROCESS_NULL; } - if (0 != process->hStdInput) { + + if (process->hStdInput) { CloseHandle(process->hStdInput); - process->hStdInput = NULL; + process->hStdInput = SUBPROCESS_NULL; } WaitForSingleObject(process->hProcess, infinite); if (out_return_code) { - if (!GetExitCodeProcess(process->hProcess, - (unsigned long *)out_return_code)) { + if (!GetExitCodeProcess( + process->hProcess, + SUBPROCESS_PTR_CAST(unsigned long *, out_return_code))) { return -1; } } + process->alive = 0; + return 0; #else int status; - if (0 != process->stdin_file) { + if (process->stdin_file) { fclose(process->stdin_file); - process->stdin_file = 0; + process->stdin_file = SUBPROCESS_NULL; } - if (process->child != waitpid(process->child, &status, 0)) { - return -1; + if (process->child) { + if (process->child != waitpid(process->child, &status, 0)) { + return -1; + } + + process->child = 0; + + if (WIFEXITED(status)) { + process->return_status = WEXITSTATUS(status); + } else { + process->return_status = EXIT_FAILURE; + } + + process->alive = 0; } if (out_return_code) { - if (WIFEXITED(status)) { - *out_return_code = WEXITSTATUS(status); - } else { - *out_return_code = EXIT_FAILURE; - } + *out_return_code = process->return_status; } return 0; @@ -697,36 +793,36 @@ } int subprocess_destroy(struct subprocess_s *const process) { - if (0 != process->stdin_file) { + if (process->stdin_file) { fclose(process->stdin_file); - process->stdin_file = 0; + process->stdin_file = SUBPROCESS_NULL; } - if (0 != process->stdout_file) { + if (process->stdout_file) { fclose(process->stdout_file); if (process->stdout_file != process->stderr_file) { fclose(process->stderr_file); } - process->stdout_file = 0; - process->stderr_file = 0; + process->stdout_file = SUBPROCESS_NULL; + process->stderr_file = SUBPROCESS_NULL; } #if defined(_MSC_VER) if (process->hProcess) { CloseHandle(process->hProcess); - process->hProcess = 0; + process->hProcess = SUBPROCESS_NULL; - if (0 != process->hStdInput) { + if (process->hStdInput) { CloseHandle(process->hStdInput); } - if (0 != process->hEventOutput) { + if (process->hEventOutput) { CloseHandle(process->hEventOutput); } - if (0 != process->hEventError) { + if (process->hEventError) { CloseHandle(process->hEventError); } } @@ -740,10 +836,11 @@ unsigned int killed_process_exit_code; int success_terminate; int windows_call_result; - + killed_process_exit_code = 99; - windows_call_result = TerminateProcess(process->hProcess, killed_process_exit_code); - success_terminate = (windows_call_result== 0) ? 1 : 0; + windows_call_result = + TerminateProcess(process->hProcess, killed_process_exit_code); + success_terminate = (windows_call_result == 0) ? 1 : 0; return success_terminate; #else int result; @@ -757,19 +854,22 @@ #if defined(_MSC_VER) void *handle; unsigned long bytes_read = 0; - struct subprocess_overlapped_s overlapped = {0}; + struct subprocess_overlapped_s overlapped = {0, 0, {{0, 0}}, SUBPROCESS_NULL}; overlapped.hEvent = process->hEventOutput; - handle = (void *)_get_osfhandle(_fileno(process->stdout_file)); + handle = SUBPROCESS_PTR_CAST(void *, + _get_osfhandle(_fileno(process->stdout_file))); - if (!ReadFile(handle, buffer, size, &bytes_read, (LPOVERLAPPED)&overlapped)) { + if (!ReadFile(handle, buffer, size, &bytes_read, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped))) { const unsigned long errorIoPending = 997; unsigned long error = GetLastError(); // Means we've got an async read! if (error == errorIoPending) { - if (!GetOverlappedResult(handle, (LPOVERLAPPED)&overlapped, &bytes_read, - 1)) { + if (!GetOverlappedResult(handle, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped), + &bytes_read, 1)) { const unsigned long errorIoIncomplete = 996; const unsigned long errorHandleEOF = 38; error = GetLastError(); @@ -781,7 +881,7 @@ } } - return (unsigned)bytes_read; + return SUBPROCESS_CAST(unsigned, bytes_read); #else const int fd = fileno(process->stdout_file); const ssize_t bytes_read = read(fd, buffer, size); @@ -799,19 +899,22 @@ #if defined(_MSC_VER) void *handle; unsigned long bytes_read = 0; - struct subprocess_overlapped_s overlapped = {0}; + struct subprocess_overlapped_s overlapped = {0, 0, {{0, 0}}, SUBPROCESS_NULL}; overlapped.hEvent = process->hEventError; - handle = (void *)_get_osfhandle(_fileno(process->stderr_file)); + handle = SUBPROCESS_PTR_CAST(void *, + _get_osfhandle(_fileno(process->stderr_file))); - if (!ReadFile(handle, buffer, size, &bytes_read, (LPOVERLAPPED)&overlapped)) { + if (!ReadFile(handle, buffer, size, &bytes_read, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped))) { const unsigned long errorIoPending = 997; unsigned long error = GetLastError(); // Means we've got an async read! if (error == errorIoPending) { - if (!GetOverlappedResult(handle, (LPOVERLAPPED)&overlapped, &bytes_read, - 1)) { + if (!GetOverlappedResult(handle, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped), + &bytes_read, 1)) { const unsigned long errorIoIncomplete = 996; const unsigned long errorHandleEOF = 38; error = GetLastError(); @@ -823,7 +926,7 @@ } } - return (unsigned)bytes_read; + return SUBPROCESS_CAST(unsigned, bytes_read); #else const int fd = fileno(process->stderr_file); const ssize_t bytes_read = read(fd, buffer, size); @@ -836,6 +939,50 @@ #endif } +int subprocess_alive(struct subprocess_s *const process) { + int is_alive = SUBPROCESS_CAST(int, process->alive); + + if (!is_alive) { + return 0; + } +#if defined(_MSC_VER) + { + const unsigned long zero = 0x0; + const unsigned long wait_object_0 = 0x00000000L; + + is_alive = wait_object_0 != WaitForSingleObject(process->hProcess, zero); + } +#else + { + int status; + is_alive = 0 == waitpid(process->child, &status, WNOHANG); + + // If the process was successfully waited on we need to cleanup now. + if (!is_alive) { + if (WIFEXITED(status)) { + process->return_status = WEXITSTATUS(status); + } else { + process->return_status = EXIT_FAILURE; + } + + // Since we've already successfully waited on the process, we need to wipe + // the child now. + process->child = 0; + + if (subprocess_join(process, SUBPROCESS_NULL)) { + return -1; + } + } + } +#endif + + if (!is_alive) { + process->alive = 0; + } + + return is_alive; +} + #if defined(__cplusplus) } // extern "C" #endif diff --git a/test/test.c b/test/test.c index ce5e2d4..a880e68 100644 --- a/test/test.c +++ b/test/test.c @@ -31,7 +31,6 @@ /* disable 'conditional expression is constant' - our examples below use this! */ #pragma warning(disable : 4127) -#pragma #endif UTEST(c, ASSERT_TRUE) { ASSERT_TRUE(1); } diff --git a/test/test11.c b/test/test11.c index 4de85c4..b8df343 100644 --- a/test/test11.c +++ b/test/test11.c @@ -31,7 +31,6 @@ /* disable 'conditional expression is constant' - our examples below use this! */ #pragma warning(disable : 4127) -#pragma #endif UTEST(c11, ASSERT_TRUE) { ASSERT_TRUE(1); } diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 7167c09..8972f1d 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -14,7 +14,6 @@ type: [Debug, RelWithDebInfo, MinSizeRel, Release] compiler: [default, clang, gcc] exclude: - - {os: "windows-latest", compiler: "clang"} - {os: "macOS-latest", compiler: "clang"} - {os: "windows-latest", compiler: "gcc"} - {os: "macOS-latest", compiler: "gcc"} @@ -44,12 +43,18 @@ working-directory: ${{github.workspace}}/build run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 - - name: Configure CMake with Clang + - name: Configure CMake with Clang (Ubuntu) shell: bash - if: matrix.compiler == 'clang' + if: (matrix.compiler == 'clang') && startsWith(matrix.os, 'ubuntu') working-directory: ${{github.workspace}}/build run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_C_COMPILER=clang-10 -DCMAKE_CXX_COMPILER=clang++-10 + - name: Configure CMake with Clang (Windows) + shell: bash + if: (matrix.compiler == 'clang') && startsWith(matrix.os, 'windows') + working-directory: ${{github.workspace}}/build + run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -T ClangCL + - name: Build working-directory: ${{github.workspace}}/build shell: bash diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 028300f..a0d849f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -24,7 +24,7 @@ # For more information, please refer to project(utest) -cmake_minimum_required(VERSION 3.1.3) +cmake_minimum_required(VERSION 3.15) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../) @@ -33,9 +33,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=gnu89" ) elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(main.c test.c PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu89" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(main.c test.c PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(main.c test.c PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu89" + ) + endif() elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(main.c test.c PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -49,9 +55,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=c99" ) elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(test99.c PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c99" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test99.c PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(test99.c PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c99" + ) + endif() elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test99.c PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -70,9 +82,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=c11" ) elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test11.c PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() set_source_files_properties(test11.c PROPERTIES COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c11" ) + endif() elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test11.c PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -87,9 +105,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=gnu++98" ) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(test.cpp PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu++98" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test.cpp PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(test.cpp PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu++98" + ) + endif() elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test.cpp PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -103,9 +127,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=c++11" ) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(test11.cpp PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c++11 -Wno-c++98-compat" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test11.cpp PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(test11.cpp PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c++11 -Wno-c++98-compat" + ) + endif() elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test11.cpp PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -145,19 +175,23 @@ target_link_options(utest_test_wpo PRIVATE "$<$:/LTCG>") endif() -if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") - add_executable(utest_test_mt - ../utest.h - main.c - test.c - test11.c - test99.c - test.cpp - test11.cpp - stdint_include.c - type_printers.c - type_printers.cpp - ) +add_executable(utest_test_mt + ../utest.h + main.c + test.c + test11.c + test99.c + test.cpp + test11.cpp + stdint_include.c + type_printers.c + type_printers.cpp +) - target_compile_definitions(utest_test_mt PUBLIC "/MT") -endif() +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + target_compile_options(utest_test_mt PUBLIC "/MT") + endif() +elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + target_compile_options(utest_test_mt PUBLIC "/MT") +endif() \ No newline at end of file diff --git a/test/subprocess.h b/test/subprocess.h index 8f63657..b25bc47 100644 --- a/test/subprocess.h +++ b/test/subprocess.h @@ -43,12 +43,12 @@ #pragma warning(pop) #endif -#if defined(__clang__) || defined(__GNUC__) -#define subprocess_pure __attribute__((pure)) -#define subprocess_weak __attribute__((weak)) -#elif defined(_MSC_VER) +#if defined(_MSC_VER) #define subprocess_pure #define subprocess_weak __inline +#elif defined(__clang__) || defined(__GNUC__) +#define subprocess_pure __attribute__((pure)) +#define subprocess_weak __attribute__((weak)) #else #error Non clang, non gcc, non MSVC compiler found! #endif @@ -75,7 +75,7 @@ /// this process. The last element must be NULL to signify the end of the array. /// @param options A bit field of subprocess_option_e's to pass. /// @param out_process The newly created process. -/// @return On success 0 is returned. +/// @return On success zero is returned. subprocess_weak int subprocess_create(const char *const command_line[], int options, struct subprocess_s *const out_process); @@ -115,7 +115,7 @@ /// @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. +/// @return On success zero is returned. /// /// Joining a process will close the stdin pipe to the process. subprocess_weak int subprocess_join(struct subprocess_s *const process, @@ -123,7 +123,7 @@ /// @brief Destroy a previously created process. /// @param process The process to destroy. -/// @return On success 0 is returned. +/// @return On success zero is returned. /// /// If the process to be destroyed had not finished execution, it may out live /// the parent process. @@ -131,7 +131,7 @@ /// @brief Terminate a previously created process. /// @param process The process to terminate. -/// @return On success 0 is returned. +/// @return On success zero is returned. /// /// If the process to be destroyed had not finished execution, it will be /// terminated (i.e killed). @@ -165,21 +165,34 @@ subprocess_read_stderr(struct subprocess_s *const process, char *const buffer, unsigned size); +/// @brief Returns if the subprocess is currently still alive and executing. +/// @param process The process to check. +/// @return If the process is still alive non-zero is returned. +subprocess_weak int subprocess_alive(struct subprocess_s *const process); + #if defined(__cplusplus) #define SUBPROCESS_CAST(type, x) static_cast(x) +#define SUBPROCESS_PTR_CAST(type, x) reinterpret_cast(x) +#define SUBPROCESS_CONST_CAST(type, x) const_cast(x) +#define SUBPROCESS_NULL NULL #else -#define SUBPROCESS_CAST(type, x) ((type)x) +#define SUBPROCESS_CAST(type, x) ((type)(x)) +#define SUBPROCESS_PTR_CAST(type, x) ((type)(x)) +#define SUBPROCESS_CONST_CAST(type, x) ((type)(x)) +#define SUBPROCESS_NULL 0 #endif #if !defined(_MSC_VER) +#include #include #include #include #include -#include #endif #if defined(_MSC_VER) + +#if (_MSC_VER < 1920) #ifdef _WIN64 typedef __int64 subprocess_intptr_t; typedef unsigned __int64 subprocess_size_t; @@ -187,6 +200,12 @@ typedef int subprocess_intptr_t; typedef unsigned int subprocess_size_t; #endif +#else +#include + +typedef intptr_t subprocess_intptr_t; +typedef size_t subprocess_size_t; +#endif typedef struct _PROCESS_INFORMATION *LPPROCESS_INFORMATION; typedef struct _SECURITY_ATTRIBUTES *LPSECURITY_ATTRIBUTES; @@ -272,8 +291,7 @@ void *, unsigned long); __declspec(dllimport) int __stdcall GetExitCodeProcess( void *, unsigned long *lpExitCode); -__declspec(dllimport) int __stdcall TerminateProcess( - void *, unsigned int); +__declspec(dllimport) int __stdcall TerminateProcess(void *, unsigned int); __declspec(dllimport) unsigned long __stdcall WaitForMultipleObjects( unsigned long, void *const *, int, unsigned long); __declspec(dllimport) int __stdcall GetOverlappedResult(void *, LPOVERLAPPED, @@ -290,6 +308,8 @@ SUBPROCESS_DLLIMPORT subprocess_intptr_t __cdecl _get_osfhandle(int); void *__cdecl _alloca(subprocess_size_t); +#else +typedef size_t subprocess_size_t; #endif #ifdef __clang__ @@ -308,7 +328,10 @@ void *hEventError; #else pid_t child; + int return_status; #endif + + subprocess_size_t alive; }; #ifdef __clang__ #pragma clang diagnostic pop @@ -324,8 +347,10 @@ const unsigned long genericWrite = 0x40000000; const unsigned long openExisting = 3; const unsigned long fileAttributeNormal = 0x00000080; - const void *const invalidHandleValue = (void *)~((subprocess_intptr_t)0); - struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), 0, 1}; + const void *const invalidHandleValue = + SUBPROCESS_PTR_CAST(void *, ~(SUBPROCESS_CAST(subprocess_intptr_t, 0))); + struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), + SUBPROCESS_NULL, 1}; char name[256] = {0}; __declspec(thread) static long index = 0; const long unique = index++; @@ -348,16 +373,18 @@ #pragma warning(pop) #endif - *rd = CreateNamedPipeA(name, pipeAccessInbound | fileFlagOverlapped, - pipeTypeByte | pipeWait, 1, 4096, 4096, 0, - (LPSECURITY_ATTRIBUTES)&saAttr); + *rd = + CreateNamedPipeA(name, pipeAccessInbound | fileFlagOverlapped, + pipeTypeByte | pipeWait, 1, 4096, 4096, SUBPROCESS_NULL, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr)); if (invalidHandleValue == rd) { return -1; } - *wr = CreateFileA(name, genericWrite, 0, (LPSECURITY_ATTRIBUTES)&saAttr, - openExisting, fileAttributeNormal, 0); + *wr = CreateFileA(name, genericWrite, SUBPROCESS_NULL, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), + openExisting, fileAttributeNormal, SUBPROCESS_NULL); if (invalidHandleValue == wr) { return -1; @@ -378,20 +405,38 @@ const unsigned long startFUseStdHandles = 0x00000100; const unsigned long handleFlagInherit = 0x00000001; struct subprocess_subprocess_information_s processInfo; - struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), 0, 1}; - char *environment = 0; - struct subprocess_startup_info_s startInfo = {0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0}; + struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), + SUBPROCESS_NULL, 1}; + char *environment = SUBPROCESS_NULL; + struct subprocess_startup_info_s startInfo = {0, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL}; startInfo.cb = sizeof(startInfo); startInfo.dwFlags = startFUseStdHandles; if (subprocess_option_inherit_environment != (options & subprocess_option_inherit_environment)) { - environment = "\0\0"; + environment = SUBPROCESS_CONST_CAST(char *, "\0\0"); } - if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + if (!CreatePipe(&rd, &wr, SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), + 0)) { return -1; } @@ -399,12 +444,12 @@ return -1; } - fd = _open_osfhandle((subprocess_intptr_t)wr, 0); + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, wr), 0); if (-1 != fd) { out_process->stdin_file = _fdopen(fd, "wb"); - if (0 == out_process->stdin_file) { + if (SUBPROCESS_NULL == out_process->stdin_file) { return -1; } } @@ -416,7 +461,8 @@ return -1; } } else { - if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + if (!CreatePipe(&rd, &wr, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) { return -1; } } @@ -425,12 +471,12 @@ return -1; } - fd = _open_osfhandle((subprocess_intptr_t)rd, 0); + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, rd), 0); if (-1 != fd) { out_process->stdout_file = _fdopen(fd, "rb"); - if (0 == out_process->stdout_file) { + if (SUBPROCESS_NULL == out_process->stdout_file) { return -1; } } @@ -447,7 +493,8 @@ return -1; } } else { - if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + if (!CreatePipe(&rd, &wr, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) { return -1; } } @@ -456,12 +503,12 @@ return -1; } - fd = _open_osfhandle((subprocess_intptr_t)rd, 0); + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, rd), 0); if (-1 != fd) { out_process->stderr_file = _fdopen(fd, "rb"); - if (0 == out_process->stderr_file) { + if (SUBPROCESS_NULL == out_process->stderr_file) { return -1; } } @@ -471,26 +518,41 @@ if (options & subprocess_option_enable_async) { out_process->hEventOutput = - CreateEventA((LPSECURITY_ATTRIBUTES)&saAttr, 1, 1, 0); + CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1, + SUBPROCESS_NULL); out_process->hEventError = - CreateEventA((LPSECURITY_ATTRIBUTES)&saAttr, 1, 1, 0); + CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1, + SUBPROCESS_NULL); } else { - out_process->hEventOutput = 0; - out_process->hEventError = 0; + out_process->hEventOutput = SUBPROCESS_NULL; + out_process->hEventError = SUBPROCESS_NULL; } // Combine commandLine together into a single string len = 0; for (i = 0; commandLine[i]; i++) { - // For the ' ' between items and trailing '\0' - len++; + // For the ' ' and two '"' between items and trailing '\0' + len += 3; for (j = 0; '\0' != commandLine[i][j]; j++) { + switch (commandLine[i][j]) { + default: + break; + case '\\': + if (commandLine[i][j + 1] == '"') { + len++; + } + + break; + case '"': + len++; + break; + } len++; } } - commandLineCombined = (char *)_alloca(len); + commandLineCombined = SUBPROCESS_CAST(char *, _alloca(len)); if (!commandLineCombined) { return -1; @@ -503,24 +565,42 @@ if (0 != i) { commandLineCombined[len++] = ' '; } + commandLineCombined[len++] = '"'; for (j = 0; '\0' != commandLine[i][j]; j++) { + switch (commandLine[i][j]) { + default: + break; + case '\\': + if (commandLine[i][j + 1] == '"') { + commandLineCombined[len++] = '\\'; + } + + break; + case '"': + commandLineCombined[len++] = '\\'; + break; + } + commandLineCombined[len++] = commandLine[i][j]; } + commandLineCombined[len++] = '"'; } 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)) { + if (!CreateProcessA( + SUBPROCESS_NULL, + commandLineCombined, // command line + SUBPROCESS_NULL, // process security attributes + SUBPROCESS_NULL, // primary thread security attributes + 1, // handles are inherited + 0, // creation flags + environment, // use parent's environment + SUBPROCESS_NULL, // use parent's current directory + SUBPROCESS_PTR_CAST(LPSTARTUPINFOA, + &startInfo), // STARTUPINFO pointer + SUBPROCESS_PTR_CAST(LPPROCESS_INFORMATION, &processInfo))) { return -1; } @@ -531,7 +611,7 @@ // We don't need the handle of the primary thread in the called process. CloseHandle(processInfo.hThread); - if (0 != startInfo.hStdOutput) { + if (SUBPROCESS_NULL != startInfo.hStdOutput) { CloseHandle(startInfo.hStdOutput); if (startInfo.hStdError != startInfo.hStdOutput) { @@ -539,6 +619,8 @@ } } + out_process->alive = 1; + return 0; #else int stdinfd[2]; @@ -595,7 +677,7 @@ #endif if (subprocess_option_inherit_environment != (options & subprocess_option_inherit_environment)) { - char *const environment[1] = {0}; + char *const environment[1] = {SUBPROCESS_NULL}; exit(execve(commandLine[0], (char *const *)commandLine, environment)); } else { exit(execvp(commandLine[0], (char *const *)commandLine)); @@ -627,6 +709,8 @@ // Store the child's pid out_process->child = child; + out_process->alive = 1; + return 0; } #endif @@ -644,7 +728,7 @@ if (process->stdout_file != process->stderr_file) { return process->stderr_file; } else { - return 0; + return SUBPROCESS_NULL; } } @@ -653,43 +737,55 @@ #if defined(_MSC_VER) const unsigned long infinite = 0xFFFFFFFF; - if (0 != process->stdin_file) { + if (process->stdin_file) { fclose(process->stdin_file); - process->stdin_file = 0; + process->stdin_file = SUBPROCESS_NULL; } - if (0 != process->hStdInput) { + + if (process->hStdInput) { CloseHandle(process->hStdInput); - process->hStdInput = NULL; + process->hStdInput = SUBPROCESS_NULL; } WaitForSingleObject(process->hProcess, infinite); if (out_return_code) { - if (!GetExitCodeProcess(process->hProcess, - (unsigned long *)out_return_code)) { + if (!GetExitCodeProcess( + process->hProcess, + SUBPROCESS_PTR_CAST(unsigned long *, out_return_code))) { return -1; } } + process->alive = 0; + return 0; #else int status; - if (0 != process->stdin_file) { + if (process->stdin_file) { fclose(process->stdin_file); - process->stdin_file = 0; + process->stdin_file = SUBPROCESS_NULL; } - if (process->child != waitpid(process->child, &status, 0)) { - return -1; + if (process->child) { + if (process->child != waitpid(process->child, &status, 0)) { + return -1; + } + + process->child = 0; + + if (WIFEXITED(status)) { + process->return_status = WEXITSTATUS(status); + } else { + process->return_status = EXIT_FAILURE; + } + + process->alive = 0; } if (out_return_code) { - if (WIFEXITED(status)) { - *out_return_code = WEXITSTATUS(status); - } else { - *out_return_code = EXIT_FAILURE; - } + *out_return_code = process->return_status; } return 0; @@ -697,36 +793,36 @@ } int subprocess_destroy(struct subprocess_s *const process) { - if (0 != process->stdin_file) { + if (process->stdin_file) { fclose(process->stdin_file); - process->stdin_file = 0; + process->stdin_file = SUBPROCESS_NULL; } - if (0 != process->stdout_file) { + if (process->stdout_file) { fclose(process->stdout_file); if (process->stdout_file != process->stderr_file) { fclose(process->stderr_file); } - process->stdout_file = 0; - process->stderr_file = 0; + process->stdout_file = SUBPROCESS_NULL; + process->stderr_file = SUBPROCESS_NULL; } #if defined(_MSC_VER) if (process->hProcess) { CloseHandle(process->hProcess); - process->hProcess = 0; + process->hProcess = SUBPROCESS_NULL; - if (0 != process->hStdInput) { + if (process->hStdInput) { CloseHandle(process->hStdInput); } - if (0 != process->hEventOutput) { + if (process->hEventOutput) { CloseHandle(process->hEventOutput); } - if (0 != process->hEventError) { + if (process->hEventError) { CloseHandle(process->hEventError); } } @@ -740,10 +836,11 @@ unsigned int killed_process_exit_code; int success_terminate; int windows_call_result; - + killed_process_exit_code = 99; - windows_call_result = TerminateProcess(process->hProcess, killed_process_exit_code); - success_terminate = (windows_call_result== 0) ? 1 : 0; + windows_call_result = + TerminateProcess(process->hProcess, killed_process_exit_code); + success_terminate = (windows_call_result == 0) ? 1 : 0; return success_terminate; #else int result; @@ -757,19 +854,22 @@ #if defined(_MSC_VER) void *handle; unsigned long bytes_read = 0; - struct subprocess_overlapped_s overlapped = {0}; + struct subprocess_overlapped_s overlapped = {0, 0, {{0, 0}}, SUBPROCESS_NULL}; overlapped.hEvent = process->hEventOutput; - handle = (void *)_get_osfhandle(_fileno(process->stdout_file)); + handle = SUBPROCESS_PTR_CAST(void *, + _get_osfhandle(_fileno(process->stdout_file))); - if (!ReadFile(handle, buffer, size, &bytes_read, (LPOVERLAPPED)&overlapped)) { + if (!ReadFile(handle, buffer, size, &bytes_read, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped))) { const unsigned long errorIoPending = 997; unsigned long error = GetLastError(); // Means we've got an async read! if (error == errorIoPending) { - if (!GetOverlappedResult(handle, (LPOVERLAPPED)&overlapped, &bytes_read, - 1)) { + if (!GetOverlappedResult(handle, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped), + &bytes_read, 1)) { const unsigned long errorIoIncomplete = 996; const unsigned long errorHandleEOF = 38; error = GetLastError(); @@ -781,7 +881,7 @@ } } - return (unsigned)bytes_read; + return SUBPROCESS_CAST(unsigned, bytes_read); #else const int fd = fileno(process->stdout_file); const ssize_t bytes_read = read(fd, buffer, size); @@ -799,19 +899,22 @@ #if defined(_MSC_VER) void *handle; unsigned long bytes_read = 0; - struct subprocess_overlapped_s overlapped = {0}; + struct subprocess_overlapped_s overlapped = {0, 0, {{0, 0}}, SUBPROCESS_NULL}; overlapped.hEvent = process->hEventError; - handle = (void *)_get_osfhandle(_fileno(process->stderr_file)); + handle = SUBPROCESS_PTR_CAST(void *, + _get_osfhandle(_fileno(process->stderr_file))); - if (!ReadFile(handle, buffer, size, &bytes_read, (LPOVERLAPPED)&overlapped)) { + if (!ReadFile(handle, buffer, size, &bytes_read, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped))) { const unsigned long errorIoPending = 997; unsigned long error = GetLastError(); // Means we've got an async read! if (error == errorIoPending) { - if (!GetOverlappedResult(handle, (LPOVERLAPPED)&overlapped, &bytes_read, - 1)) { + if (!GetOverlappedResult(handle, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped), + &bytes_read, 1)) { const unsigned long errorIoIncomplete = 996; const unsigned long errorHandleEOF = 38; error = GetLastError(); @@ -823,7 +926,7 @@ } } - return (unsigned)bytes_read; + return SUBPROCESS_CAST(unsigned, bytes_read); #else const int fd = fileno(process->stderr_file); const ssize_t bytes_read = read(fd, buffer, size); @@ -836,6 +939,50 @@ #endif } +int subprocess_alive(struct subprocess_s *const process) { + int is_alive = SUBPROCESS_CAST(int, process->alive); + + if (!is_alive) { + return 0; + } +#if defined(_MSC_VER) + { + const unsigned long zero = 0x0; + const unsigned long wait_object_0 = 0x00000000L; + + is_alive = wait_object_0 != WaitForSingleObject(process->hProcess, zero); + } +#else + { + int status; + is_alive = 0 == waitpid(process->child, &status, WNOHANG); + + // If the process was successfully waited on we need to cleanup now. + if (!is_alive) { + if (WIFEXITED(status)) { + process->return_status = WEXITSTATUS(status); + } else { + process->return_status = EXIT_FAILURE; + } + + // Since we've already successfully waited on the process, we need to wipe + // the child now. + process->child = 0; + + if (subprocess_join(process, SUBPROCESS_NULL)) { + return -1; + } + } + } +#endif + + if (!is_alive) { + process->alive = 0; + } + + return is_alive; +} + #if defined(__cplusplus) } // extern "C" #endif diff --git a/test/test.c b/test/test.c index ce5e2d4..a880e68 100644 --- a/test/test.c +++ b/test/test.c @@ -31,7 +31,6 @@ /* disable 'conditional expression is constant' - our examples below use this! */ #pragma warning(disable : 4127) -#pragma #endif UTEST(c, ASSERT_TRUE) { ASSERT_TRUE(1); } diff --git a/test/test11.c b/test/test11.c index 4de85c4..b8df343 100644 --- a/test/test11.c +++ b/test/test11.c @@ -31,7 +31,6 @@ /* disable 'conditional expression is constant' - our examples below use this! */ #pragma warning(disable : 4127) -#pragma #endif UTEST(c11, ASSERT_TRUE) { ASSERT_TRUE(1); } diff --git a/test/test99.c b/test/test99.c index 2531f73..d29c619 100644 --- a/test/test99.c +++ b/test/test99.c @@ -31,7 +31,6 @@ /* disable 'conditional expression is constant' - our examples below use this! */ #pragma warning(disable : 4127) -#pragma #endif UTEST(c99, ASSERT_TRUE) { ASSERT_TRUE(1); } diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 7167c09..8972f1d 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -14,7 +14,6 @@ type: [Debug, RelWithDebInfo, MinSizeRel, Release] compiler: [default, clang, gcc] exclude: - - {os: "windows-latest", compiler: "clang"} - {os: "macOS-latest", compiler: "clang"} - {os: "windows-latest", compiler: "gcc"} - {os: "macOS-latest", compiler: "gcc"} @@ -44,12 +43,18 @@ working-directory: ${{github.workspace}}/build run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 - - name: Configure CMake with Clang + - name: Configure CMake with Clang (Ubuntu) shell: bash - if: matrix.compiler == 'clang' + if: (matrix.compiler == 'clang') && startsWith(matrix.os, 'ubuntu') working-directory: ${{github.workspace}}/build run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_C_COMPILER=clang-10 -DCMAKE_CXX_COMPILER=clang++-10 + - name: Configure CMake with Clang (Windows) + shell: bash + if: (matrix.compiler == 'clang') && startsWith(matrix.os, 'windows') + working-directory: ${{github.workspace}}/build + run: cmake $GITHUB_WORKSPACE/test -DCMAKE_BUILD_TYPE=${{ matrix.type }} -T ClangCL + - name: Build working-directory: ${{github.workspace}}/build shell: bash diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 028300f..a0d849f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -24,7 +24,7 @@ # For more information, please refer to project(utest) -cmake_minimum_required(VERSION 3.1.3) +cmake_minimum_required(VERSION 3.15) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../) @@ -33,9 +33,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=gnu89" ) elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(main.c test.c PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu89" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(main.c test.c PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(main.c test.c PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu89" + ) + endif() elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(main.c test.c PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -49,9 +55,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=c99" ) elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(test99.c PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c99" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test99.c PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(test99.c PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c99" + ) + endif() elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test99.c PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -70,9 +82,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=c11" ) elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") + if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test11.c PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() set_source_files_properties(test11.c PROPERTIES COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c11" ) + endif() elseif("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test11.c PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -87,9 +105,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=gnu++98" ) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(test.cpp PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu++98" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test.cpp PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(test.cpp PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=gnu++98" + ) + endif() elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test.cpp PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -103,9 +127,15 @@ COMPILE_FLAGS "-Wall -Wextra -Werror -std=c++11" ) elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") - set_source_files_properties(test11.cpp PROPERTIES - COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c++11 -Wno-c++98-compat" - ) + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + set_source_files_properties(test11.cpp PROPERTIES + COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" + ) + else() + set_source_files_properties(test11.cpp PROPERTIES + COMPILE_FLAGS "-Wall -Wextra -Weverything -Werror -std=c++11 -Wno-c++98-compat" + ) + endif() elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") set_source_files_properties(test11.cpp PROPERTIES COMPILE_FLAGS "/Wall /WX /wd4514 /wd5045" @@ -145,19 +175,23 @@ target_link_options(utest_test_wpo PRIVATE "$<$:/LTCG>") endif() -if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") - add_executable(utest_test_mt - ../utest.h - main.c - test.c - test11.c - test99.c - test.cpp - test11.cpp - stdint_include.c - type_printers.c - type_printers.cpp - ) +add_executable(utest_test_mt + ../utest.h + main.c + test.c + test11.c + test99.c + test.cpp + test11.cpp + stdint_include.c + type_printers.c + type_printers.cpp +) - target_compile_definitions(utest_test_mt PUBLIC "/MT") -endif() +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + target_compile_options(utest_test_mt PUBLIC "/MT") + endif() +elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + target_compile_options(utest_test_mt PUBLIC "/MT") +endif() \ No newline at end of file diff --git a/test/subprocess.h b/test/subprocess.h index 8f63657..b25bc47 100644 --- a/test/subprocess.h +++ b/test/subprocess.h @@ -43,12 +43,12 @@ #pragma warning(pop) #endif -#if defined(__clang__) || defined(__GNUC__) -#define subprocess_pure __attribute__((pure)) -#define subprocess_weak __attribute__((weak)) -#elif defined(_MSC_VER) +#if defined(_MSC_VER) #define subprocess_pure #define subprocess_weak __inline +#elif defined(__clang__) || defined(__GNUC__) +#define subprocess_pure __attribute__((pure)) +#define subprocess_weak __attribute__((weak)) #else #error Non clang, non gcc, non MSVC compiler found! #endif @@ -75,7 +75,7 @@ /// this process. The last element must be NULL to signify the end of the array. /// @param options A bit field of subprocess_option_e's to pass. /// @param out_process The newly created process. -/// @return On success 0 is returned. +/// @return On success zero is returned. subprocess_weak int subprocess_create(const char *const command_line[], int options, struct subprocess_s *const out_process); @@ -115,7 +115,7 @@ /// @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. +/// @return On success zero is returned. /// /// Joining a process will close the stdin pipe to the process. subprocess_weak int subprocess_join(struct subprocess_s *const process, @@ -123,7 +123,7 @@ /// @brief Destroy a previously created process. /// @param process The process to destroy. -/// @return On success 0 is returned. +/// @return On success zero is returned. /// /// If the process to be destroyed had not finished execution, it may out live /// the parent process. @@ -131,7 +131,7 @@ /// @brief Terminate a previously created process. /// @param process The process to terminate. -/// @return On success 0 is returned. +/// @return On success zero is returned. /// /// If the process to be destroyed had not finished execution, it will be /// terminated (i.e killed). @@ -165,21 +165,34 @@ subprocess_read_stderr(struct subprocess_s *const process, char *const buffer, unsigned size); +/// @brief Returns if the subprocess is currently still alive and executing. +/// @param process The process to check. +/// @return If the process is still alive non-zero is returned. +subprocess_weak int subprocess_alive(struct subprocess_s *const process); + #if defined(__cplusplus) #define SUBPROCESS_CAST(type, x) static_cast(x) +#define SUBPROCESS_PTR_CAST(type, x) reinterpret_cast(x) +#define SUBPROCESS_CONST_CAST(type, x) const_cast(x) +#define SUBPROCESS_NULL NULL #else -#define SUBPROCESS_CAST(type, x) ((type)x) +#define SUBPROCESS_CAST(type, x) ((type)(x)) +#define SUBPROCESS_PTR_CAST(type, x) ((type)(x)) +#define SUBPROCESS_CONST_CAST(type, x) ((type)(x)) +#define SUBPROCESS_NULL 0 #endif #if !defined(_MSC_VER) +#include #include #include #include #include -#include #endif #if defined(_MSC_VER) + +#if (_MSC_VER < 1920) #ifdef _WIN64 typedef __int64 subprocess_intptr_t; typedef unsigned __int64 subprocess_size_t; @@ -187,6 +200,12 @@ typedef int subprocess_intptr_t; typedef unsigned int subprocess_size_t; #endif +#else +#include + +typedef intptr_t subprocess_intptr_t; +typedef size_t subprocess_size_t; +#endif typedef struct _PROCESS_INFORMATION *LPPROCESS_INFORMATION; typedef struct _SECURITY_ATTRIBUTES *LPSECURITY_ATTRIBUTES; @@ -272,8 +291,7 @@ void *, unsigned long); __declspec(dllimport) int __stdcall GetExitCodeProcess( void *, unsigned long *lpExitCode); -__declspec(dllimport) int __stdcall TerminateProcess( - void *, unsigned int); +__declspec(dllimport) int __stdcall TerminateProcess(void *, unsigned int); __declspec(dllimport) unsigned long __stdcall WaitForMultipleObjects( unsigned long, void *const *, int, unsigned long); __declspec(dllimport) int __stdcall GetOverlappedResult(void *, LPOVERLAPPED, @@ -290,6 +308,8 @@ SUBPROCESS_DLLIMPORT subprocess_intptr_t __cdecl _get_osfhandle(int); void *__cdecl _alloca(subprocess_size_t); +#else +typedef size_t subprocess_size_t; #endif #ifdef __clang__ @@ -308,7 +328,10 @@ void *hEventError; #else pid_t child; + int return_status; #endif + + subprocess_size_t alive; }; #ifdef __clang__ #pragma clang diagnostic pop @@ -324,8 +347,10 @@ const unsigned long genericWrite = 0x40000000; const unsigned long openExisting = 3; const unsigned long fileAttributeNormal = 0x00000080; - const void *const invalidHandleValue = (void *)~((subprocess_intptr_t)0); - struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), 0, 1}; + const void *const invalidHandleValue = + SUBPROCESS_PTR_CAST(void *, ~(SUBPROCESS_CAST(subprocess_intptr_t, 0))); + struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), + SUBPROCESS_NULL, 1}; char name[256] = {0}; __declspec(thread) static long index = 0; const long unique = index++; @@ -348,16 +373,18 @@ #pragma warning(pop) #endif - *rd = CreateNamedPipeA(name, pipeAccessInbound | fileFlagOverlapped, - pipeTypeByte | pipeWait, 1, 4096, 4096, 0, - (LPSECURITY_ATTRIBUTES)&saAttr); + *rd = + CreateNamedPipeA(name, pipeAccessInbound | fileFlagOverlapped, + pipeTypeByte | pipeWait, 1, 4096, 4096, SUBPROCESS_NULL, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr)); if (invalidHandleValue == rd) { return -1; } - *wr = CreateFileA(name, genericWrite, 0, (LPSECURITY_ATTRIBUTES)&saAttr, - openExisting, fileAttributeNormal, 0); + *wr = CreateFileA(name, genericWrite, SUBPROCESS_NULL, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), + openExisting, fileAttributeNormal, SUBPROCESS_NULL); if (invalidHandleValue == wr) { return -1; @@ -378,20 +405,38 @@ const unsigned long startFUseStdHandles = 0x00000100; const unsigned long handleFlagInherit = 0x00000001; struct subprocess_subprocess_information_s processInfo; - struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), 0, 1}; - char *environment = 0; - struct subprocess_startup_info_s startInfo = {0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0}; + struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), + SUBPROCESS_NULL, 1}; + char *environment = SUBPROCESS_NULL; + struct subprocess_startup_info_s startInfo = {0, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL}; startInfo.cb = sizeof(startInfo); startInfo.dwFlags = startFUseStdHandles; if (subprocess_option_inherit_environment != (options & subprocess_option_inherit_environment)) { - environment = "\0\0"; + environment = SUBPROCESS_CONST_CAST(char *, "\0\0"); } - if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + if (!CreatePipe(&rd, &wr, SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), + 0)) { return -1; } @@ -399,12 +444,12 @@ return -1; } - fd = _open_osfhandle((subprocess_intptr_t)wr, 0); + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, wr), 0); if (-1 != fd) { out_process->stdin_file = _fdopen(fd, "wb"); - if (0 == out_process->stdin_file) { + if (SUBPROCESS_NULL == out_process->stdin_file) { return -1; } } @@ -416,7 +461,8 @@ return -1; } } else { - if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + if (!CreatePipe(&rd, &wr, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) { return -1; } } @@ -425,12 +471,12 @@ return -1; } - fd = _open_osfhandle((subprocess_intptr_t)rd, 0); + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, rd), 0); if (-1 != fd) { out_process->stdout_file = _fdopen(fd, "rb"); - if (0 == out_process->stdout_file) { + if (SUBPROCESS_NULL == out_process->stdout_file) { return -1; } } @@ -447,7 +493,8 @@ return -1; } } else { - if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) { + if (!CreatePipe(&rd, &wr, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) { return -1; } } @@ -456,12 +503,12 @@ return -1; } - fd = _open_osfhandle((subprocess_intptr_t)rd, 0); + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, rd), 0); if (-1 != fd) { out_process->stderr_file = _fdopen(fd, "rb"); - if (0 == out_process->stderr_file) { + if (SUBPROCESS_NULL == out_process->stderr_file) { return -1; } } @@ -471,26 +518,41 @@ if (options & subprocess_option_enable_async) { out_process->hEventOutput = - CreateEventA((LPSECURITY_ATTRIBUTES)&saAttr, 1, 1, 0); + CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1, + SUBPROCESS_NULL); out_process->hEventError = - CreateEventA((LPSECURITY_ATTRIBUTES)&saAttr, 1, 1, 0); + CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1, + SUBPROCESS_NULL); } else { - out_process->hEventOutput = 0; - out_process->hEventError = 0; + out_process->hEventOutput = SUBPROCESS_NULL; + out_process->hEventError = SUBPROCESS_NULL; } // Combine commandLine together into a single string len = 0; for (i = 0; commandLine[i]; i++) { - // For the ' ' between items and trailing '\0' - len++; + // For the ' ' and two '"' between items and trailing '\0' + len += 3; for (j = 0; '\0' != commandLine[i][j]; j++) { + switch (commandLine[i][j]) { + default: + break; + case '\\': + if (commandLine[i][j + 1] == '"') { + len++; + } + + break; + case '"': + len++; + break; + } len++; } } - commandLineCombined = (char *)_alloca(len); + commandLineCombined = SUBPROCESS_CAST(char *, _alloca(len)); if (!commandLineCombined) { return -1; @@ -503,24 +565,42 @@ if (0 != i) { commandLineCombined[len++] = ' '; } + commandLineCombined[len++] = '"'; for (j = 0; '\0' != commandLine[i][j]; j++) { + switch (commandLine[i][j]) { + default: + break; + case '\\': + if (commandLine[i][j + 1] == '"') { + commandLineCombined[len++] = '\\'; + } + + break; + case '"': + commandLineCombined[len++] = '\\'; + break; + } + commandLineCombined[len++] = commandLine[i][j]; } + commandLineCombined[len++] = '"'; } 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)) { + if (!CreateProcessA( + SUBPROCESS_NULL, + commandLineCombined, // command line + SUBPROCESS_NULL, // process security attributes + SUBPROCESS_NULL, // primary thread security attributes + 1, // handles are inherited + 0, // creation flags + environment, // use parent's environment + SUBPROCESS_NULL, // use parent's current directory + SUBPROCESS_PTR_CAST(LPSTARTUPINFOA, + &startInfo), // STARTUPINFO pointer + SUBPROCESS_PTR_CAST(LPPROCESS_INFORMATION, &processInfo))) { return -1; } @@ -531,7 +611,7 @@ // We don't need the handle of the primary thread in the called process. CloseHandle(processInfo.hThread); - if (0 != startInfo.hStdOutput) { + if (SUBPROCESS_NULL != startInfo.hStdOutput) { CloseHandle(startInfo.hStdOutput); if (startInfo.hStdError != startInfo.hStdOutput) { @@ -539,6 +619,8 @@ } } + out_process->alive = 1; + return 0; #else int stdinfd[2]; @@ -595,7 +677,7 @@ #endif if (subprocess_option_inherit_environment != (options & subprocess_option_inherit_environment)) { - char *const environment[1] = {0}; + char *const environment[1] = {SUBPROCESS_NULL}; exit(execve(commandLine[0], (char *const *)commandLine, environment)); } else { exit(execvp(commandLine[0], (char *const *)commandLine)); @@ -627,6 +709,8 @@ // Store the child's pid out_process->child = child; + out_process->alive = 1; + return 0; } #endif @@ -644,7 +728,7 @@ if (process->stdout_file != process->stderr_file) { return process->stderr_file; } else { - return 0; + return SUBPROCESS_NULL; } } @@ -653,43 +737,55 @@ #if defined(_MSC_VER) const unsigned long infinite = 0xFFFFFFFF; - if (0 != process->stdin_file) { + if (process->stdin_file) { fclose(process->stdin_file); - process->stdin_file = 0; + process->stdin_file = SUBPROCESS_NULL; } - if (0 != process->hStdInput) { + + if (process->hStdInput) { CloseHandle(process->hStdInput); - process->hStdInput = NULL; + process->hStdInput = SUBPROCESS_NULL; } WaitForSingleObject(process->hProcess, infinite); if (out_return_code) { - if (!GetExitCodeProcess(process->hProcess, - (unsigned long *)out_return_code)) { + if (!GetExitCodeProcess( + process->hProcess, + SUBPROCESS_PTR_CAST(unsigned long *, out_return_code))) { return -1; } } + process->alive = 0; + return 0; #else int status; - if (0 != process->stdin_file) { + if (process->stdin_file) { fclose(process->stdin_file); - process->stdin_file = 0; + process->stdin_file = SUBPROCESS_NULL; } - if (process->child != waitpid(process->child, &status, 0)) { - return -1; + if (process->child) { + if (process->child != waitpid(process->child, &status, 0)) { + return -1; + } + + process->child = 0; + + if (WIFEXITED(status)) { + process->return_status = WEXITSTATUS(status); + } else { + process->return_status = EXIT_FAILURE; + } + + process->alive = 0; } if (out_return_code) { - if (WIFEXITED(status)) { - *out_return_code = WEXITSTATUS(status); - } else { - *out_return_code = EXIT_FAILURE; - } + *out_return_code = process->return_status; } return 0; @@ -697,36 +793,36 @@ } int subprocess_destroy(struct subprocess_s *const process) { - if (0 != process->stdin_file) { + if (process->stdin_file) { fclose(process->stdin_file); - process->stdin_file = 0; + process->stdin_file = SUBPROCESS_NULL; } - if (0 != process->stdout_file) { + if (process->stdout_file) { fclose(process->stdout_file); if (process->stdout_file != process->stderr_file) { fclose(process->stderr_file); } - process->stdout_file = 0; - process->stderr_file = 0; + process->stdout_file = SUBPROCESS_NULL; + process->stderr_file = SUBPROCESS_NULL; } #if defined(_MSC_VER) if (process->hProcess) { CloseHandle(process->hProcess); - process->hProcess = 0; + process->hProcess = SUBPROCESS_NULL; - if (0 != process->hStdInput) { + if (process->hStdInput) { CloseHandle(process->hStdInput); } - if (0 != process->hEventOutput) { + if (process->hEventOutput) { CloseHandle(process->hEventOutput); } - if (0 != process->hEventError) { + if (process->hEventError) { CloseHandle(process->hEventError); } } @@ -740,10 +836,11 @@ unsigned int killed_process_exit_code; int success_terminate; int windows_call_result; - + killed_process_exit_code = 99; - windows_call_result = TerminateProcess(process->hProcess, killed_process_exit_code); - success_terminate = (windows_call_result== 0) ? 1 : 0; + windows_call_result = + TerminateProcess(process->hProcess, killed_process_exit_code); + success_terminate = (windows_call_result == 0) ? 1 : 0; return success_terminate; #else int result; @@ -757,19 +854,22 @@ #if defined(_MSC_VER) void *handle; unsigned long bytes_read = 0; - struct subprocess_overlapped_s overlapped = {0}; + struct subprocess_overlapped_s overlapped = {0, 0, {{0, 0}}, SUBPROCESS_NULL}; overlapped.hEvent = process->hEventOutput; - handle = (void *)_get_osfhandle(_fileno(process->stdout_file)); + handle = SUBPROCESS_PTR_CAST(void *, + _get_osfhandle(_fileno(process->stdout_file))); - if (!ReadFile(handle, buffer, size, &bytes_read, (LPOVERLAPPED)&overlapped)) { + if (!ReadFile(handle, buffer, size, &bytes_read, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped))) { const unsigned long errorIoPending = 997; unsigned long error = GetLastError(); // Means we've got an async read! if (error == errorIoPending) { - if (!GetOverlappedResult(handle, (LPOVERLAPPED)&overlapped, &bytes_read, - 1)) { + if (!GetOverlappedResult(handle, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped), + &bytes_read, 1)) { const unsigned long errorIoIncomplete = 996; const unsigned long errorHandleEOF = 38; error = GetLastError(); @@ -781,7 +881,7 @@ } } - return (unsigned)bytes_read; + return SUBPROCESS_CAST(unsigned, bytes_read); #else const int fd = fileno(process->stdout_file); const ssize_t bytes_read = read(fd, buffer, size); @@ -799,19 +899,22 @@ #if defined(_MSC_VER) void *handle; unsigned long bytes_read = 0; - struct subprocess_overlapped_s overlapped = {0}; + struct subprocess_overlapped_s overlapped = {0, 0, {{0, 0}}, SUBPROCESS_NULL}; overlapped.hEvent = process->hEventError; - handle = (void *)_get_osfhandle(_fileno(process->stderr_file)); + handle = SUBPROCESS_PTR_CAST(void *, + _get_osfhandle(_fileno(process->stderr_file))); - if (!ReadFile(handle, buffer, size, &bytes_read, (LPOVERLAPPED)&overlapped)) { + if (!ReadFile(handle, buffer, size, &bytes_read, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped))) { const unsigned long errorIoPending = 997; unsigned long error = GetLastError(); // Means we've got an async read! if (error == errorIoPending) { - if (!GetOverlappedResult(handle, (LPOVERLAPPED)&overlapped, &bytes_read, - 1)) { + if (!GetOverlappedResult(handle, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped), + &bytes_read, 1)) { const unsigned long errorIoIncomplete = 996; const unsigned long errorHandleEOF = 38; error = GetLastError(); @@ -823,7 +926,7 @@ } } - return (unsigned)bytes_read; + return SUBPROCESS_CAST(unsigned, bytes_read); #else const int fd = fileno(process->stderr_file); const ssize_t bytes_read = read(fd, buffer, size); @@ -836,6 +939,50 @@ #endif } +int subprocess_alive(struct subprocess_s *const process) { + int is_alive = SUBPROCESS_CAST(int, process->alive); + + if (!is_alive) { + return 0; + } +#if defined(_MSC_VER) + { + const unsigned long zero = 0x0; + const unsigned long wait_object_0 = 0x00000000L; + + is_alive = wait_object_0 != WaitForSingleObject(process->hProcess, zero); + } +#else + { + int status; + is_alive = 0 == waitpid(process->child, &status, WNOHANG); + + // If the process was successfully waited on we need to cleanup now. + if (!is_alive) { + if (WIFEXITED(status)) { + process->return_status = WEXITSTATUS(status); + } else { + process->return_status = EXIT_FAILURE; + } + + // Since we've already successfully waited on the process, we need to wipe + // the child now. + process->child = 0; + + if (subprocess_join(process, SUBPROCESS_NULL)) { + return -1; + } + } + } +#endif + + if (!is_alive) { + process->alive = 0; + } + + return is_alive; +} + #if defined(__cplusplus) } // extern "C" #endif diff --git a/test/test.c b/test/test.c index ce5e2d4..a880e68 100644 --- a/test/test.c +++ b/test/test.c @@ -31,7 +31,6 @@ /* disable 'conditional expression is constant' - our examples below use this! */ #pragma warning(disable : 4127) -#pragma #endif UTEST(c, ASSERT_TRUE) { ASSERT_TRUE(1); } diff --git a/test/test11.c b/test/test11.c index 4de85c4..b8df343 100644 --- a/test/test11.c +++ b/test/test11.c @@ -31,7 +31,6 @@ /* disable 'conditional expression is constant' - our examples below use this! */ #pragma warning(disable : 4127) -#pragma #endif UTEST(c11, ASSERT_TRUE) { ASSERT_TRUE(1); } diff --git a/test/test99.c b/test/test99.c index 2531f73..d29c619 100644 --- a/test/test99.c +++ b/test/test99.c @@ -31,7 +31,6 @@ /* disable 'conditional expression is constant' - our examples below use this! */ #pragma warning(disable : 4127) -#pragma #endif UTEST(c99, ASSERT_TRUE) { ASSERT_TRUE(1); } diff --git a/utest.h b/utest.h index fdfceae..5ea323b 100644 --- a/utest.h +++ b/utest.h @@ -36,20 +36,17 @@ #ifdef _MSC_VER /* Disable warning about not inlining 'inline' functions. - TODO: We'll fix this later by not using fprintf within our macros, and - instead use snprintf to a realloc'ed buffer. */ #pragma warning(disable : 4710) /* Disable warning about inlining functions that are not marked 'inline'. - TODO: add a UTEST_NOINLINE onto the macro generated functions to fix this. */ #pragma warning(disable : 4711) #pragma warning(push, 1) #endif -#if defined(_MSC_VER) +#if defined(_MSC_VER) && (_MSC_VER < 1920) typedef __int64 utest_int64_t; typedef unsigned __int64 utest_uint64_t; #else @@ -120,9 +117,17 @@ #include #endif -#if defined(_MSC_VER) +#if defined(_MSC_VER) && (_MSC_VER < 1920) #define UTEST_PRId64 "I64d" #define UTEST_PRIu64 "I64u" +#else +#include + +#define UTEST_PRId64 PRId64 +#define UTEST_PRIu64 PRIu64 +#endif + +#if defined(_MSC_VER) #define UTEST_INLINE __forceinline #if defined(_WIN64) @@ -131,12 +136,25 @@ #define UTEST_SYMBOL_PREFIX "_" #endif +#if defined(__clang__) +#define UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wmissing-variable-declarations\"") + +#define UTEST_INITIALIZER_END_DISABLE_WARNINGS _Pragma("clang diagnostic pop") +#else +#define UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS +#define UTEST_INITIALIZER_END_DISABLE_WARNINGS +#endif + #pragma section(".CRT$XCU", read) #define UTEST_INITIALIZER(f) \ static void __cdecl f(void); \ - __pragma(comment(linker, "/include:" UTEST_SYMBOL_PREFIX #f "_")); \ - UTEST_C_FUNC __declspec(allocate(".CRT$XCU")) void(__cdecl * f##_)(void) = \ - f; \ + UTEST_INITIALIZER_BEGIN_DISABLE_WARNINGS \ + __pragma(comment(linker, "/include:" UTEST_SYMBOL_PREFIX #f "_")) \ + UTEST_C_FUNC __declspec(allocate(".CRT$XCU")) void(__cdecl * \ + f##_)(void) = f; \ + UTEST_INITIALIZER_END_DISABLE_WARNINGS \ static void __cdecl f(void) #else #if defined(__linux__) @@ -156,10 +174,6 @@ #endif #endif -#include - -#define UTEST_PRId64 PRId64 -#define UTEST_PRIu64 PRIu64 #define UTEST_INLINE inline #define UTEST_INITIALIZER(f) \ @@ -266,19 +280,21 @@ #pragma clang diagnostic pop #endif -#ifdef _MSC_VER -#define UTEST_SNPRINTF(BUFFER, N, ...) _snprintf_s(BUFFER, N, N, __VA_ARGS__) -#else #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wvariadic-macros" #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" #endif + +#ifdef _MSC_VER +#define UTEST_SNPRINTF(BUFFER, N, ...) _snprintf_s(BUFFER, N, N, __VA_ARGS__) +#else #define UTEST_SNPRINTF(...) snprintf(__VA_ARGS__) +#endif + #ifdef __clang__ #pragma clang diagnostic pop #endif -#endif #if defined(__cplusplus) /* if we are using c++ we can use overloaded methods (its in the language) */ @@ -874,36 +890,21 @@ return 0; } -static UTEST_INLINE int utest_strncmp(const char *a, const char *b, size_t n) { - /* strncmp breaks on Wall / Werror on gcc/clang, so we avoid using it */ - unsigned i; - - for (i = 0; i < n; i++) { - if (a[i] < b[i]) { - return -1; - } else if (a[i] > b[i]) { - return 1; - } - } - - return 0; -} - static UTEST_INLINE FILE *utest_fopen(const char *filename, const char *mode) { #ifdef _MSC_VER FILE *file; if (0 == fopen_s(&file, filename, mode)) { return file; } else { - return 0; + return UTEST_NULL; } #else return fopen(filename, mode); #endif } -UTEST_WEAK int utest_main(int argc, const char *const argv[]); -UTEST_WEAK int utest_main(int argc, const char *const argv[]) { +static UTEST_INLINE int utest_main(int argc, const char *const argv[]); +int utest_main(int argc, const char *const argv[]) { utest_uint64_t failed = 0; size_t index = 0; size_t *failed_testcases = UTEST_NULL; @@ -929,7 +930,7 @@ const char filter_str[] = "--filter="; const char output_str[] = "--output="; - if (0 == utest_strncmp(argv[index], help_str, strlen(help_str))) { + if (0 == UTEST_STRNCMP(argv[index], help_str, strlen(help_str))) { printf("utest.h - the single file unit testing solution for C/C++!\n" "Command line Options:\n" " --help Show this message and exit.\n" @@ -941,13 +942,13 @@ "specified in .\n"); goto cleanup; } else if (0 == - utest_strncmp(argv[index], filter_str, strlen(filter_str))) { + UTEST_STRNCMP(argv[index], filter_str, strlen(filter_str))) { /* user wants to filter what test cases run! */ filter = argv[index] + strlen(filter_str); } else if (0 == - utest_strncmp(argv[index], output_str, strlen(output_str))) { + UTEST_STRNCMP(argv[index], output_str, strlen(output_str))) { utest_state.output = utest_fopen(argv[index] + strlen(output_str), "w+"); - } else if (0 == utest_strncmp(argv[index], list_str, strlen(list_str))) { + } else if (0 == UTEST_STRNCMP(argv[index], list_str, strlen(list_str))) { for (index = 0; index < utest_state.tests_length; index++) { UTEST_PRINTF("%s\n", utest_state.tests[index].name); }