From 9d6785d0a5237d08e9494dc6dc1bd3c9ad7ddfb8 Mon Sep 17 00:00:00 2001 From: Hudd Date: Wed, 20 Mar 2024 19:07:46 +0400 Subject: [PATCH 1/7] io: rename .cpp to .c++ It is the convention for this project. Should've done it back when I was porting everything to CMake. --- io/CMakeLists.txt | 64 +++++++++---------- io/posix/{file.cpp => file.c++} | 0 io/posix/{mmap.cpp => mmap.c++} | 0 io/win32/{error_codes.cpp => error_codes.c++} | 0 io/win32/{file.cpp => file.c++} | 0 io/win32/{mmap.cpp => mmap.c++} | 0 ...inapi_category.cpp => winapi_category.c++} | 0 7 files changed, 32 insertions(+), 32 deletions(-) rename io/posix/{file.cpp => file.c++} (100%) rename io/posix/{mmap.cpp => mmap.c++} (100%) rename io/win32/{error_codes.cpp => error_codes.c++} (100%) rename io/win32/{file.cpp => file.c++} (100%) rename io/win32/{mmap.cpp => mmap.c++} (100%) rename io/win32/{winapi_category.cpp => winapi_category.c++} (100%) diff --git a/io/CMakeLists.txt b/io/CMakeLists.txt index 708a785d..a48f3c68 100644 --- a/io/CMakeLists.txt +++ b/io/CMakeLists.txt @@ -1,32 +1,32 @@ - -set(WIN32_SOURCES - win32/file.cpp - win32/mmap.cpp - win32/winapi_category.cpp - win32/error_codes.cpp - win32/path.h - win32/winapi_helpers.h) - -set(POSIX_SOURCES - posix/file.cpp - posix/mmap.cpp - posix/helpers.h) - -if (AW_CAPABILITIES_WIN32) - list(APPEND SOURCES ${WIN32_SOURCES}) -endif() - -if (AW_CAPABILITIES_POSIX) - list(APPEND SOURCES ${POSIX_SOURCES}) -endif() - -aw_add_library(awio SHARED - GLOB_HEADERS - SOURCES - ${SOURCES} - EXPORT_NAME - io -) - -target_compile_definitions(awio PRIVATE AW_MODULE_IO) -target_link_libraries(awio awutils) + +set(WIN32_SOURCES + win32/error_codes.c++ + win32/file.c++ + win32/mmap.c++ + win32/path.h + win32/winapi_category.c++ + win32/winapi_helpers.h) + +set(POSIX_SOURCES + posix/file.c++ + posix/helpers.h + posix/mmap.c++) + +if (AW_CAPABILITIES_WIN32) + list(APPEND SOURCES ${WIN32_SOURCES}) +endif() + +if (AW_CAPABILITIES_POSIX) + list(APPEND SOURCES ${POSIX_SOURCES}) +endif() + +aw_add_library(awio SHARED + GLOB_HEADERS + SOURCES + ${SOURCES} + EXPORT_NAME + io +) + +target_compile_definitions(awio PRIVATE AW_MODULE_IO) +target_link_libraries(awio awutils) diff --git a/io/posix/file.cpp b/io/posix/file.c++ similarity index 100% rename from io/posix/file.cpp rename to io/posix/file.c++ diff --git a/io/posix/mmap.cpp b/io/posix/mmap.c++ similarity index 100% rename from io/posix/mmap.cpp rename to io/posix/mmap.c++ diff --git a/io/win32/error_codes.cpp b/io/win32/error_codes.c++ similarity index 100% rename from io/win32/error_codes.cpp rename to io/win32/error_codes.c++ diff --git a/io/win32/file.cpp b/io/win32/file.c++ similarity index 100% rename from io/win32/file.cpp rename to io/win32/file.c++ diff --git a/io/win32/mmap.cpp b/io/win32/mmap.c++ similarity index 100% rename from io/win32/mmap.cpp rename to io/win32/mmap.c++ diff --git a/io/win32/winapi_category.cpp b/io/win32/winapi_category.c++ similarity index 100% rename from io/win32/winapi_category.cpp rename to io/win32/winapi_category.c++ From 9be7e0e5025647d427044357bd618fec82d90fdd Mon Sep 17 00:00:00 2001 From: Hudd Date: Wed, 20 Mar 2024 19:27:50 +0400 Subject: [PATCH 2/7] io: move process from platform to io It's inconvenient to keep two separate libraries and somehow share helpers between them. It makes sense to have "process" in io, so I'm moving it there. --- io/CMakeLists.txt | 3 +- io/include/aw/io/process.h | 61 +++++++++++++++++++++++++++++ io/posix/process.c++ | 78 ++++++++++++++++++++++++++++++++++++++ platform/CMakeLists.txt | 7 ---- 4 files changed, 141 insertions(+), 8 deletions(-) create mode 100644 io/include/aw/io/process.h create mode 100644 io/posix/process.c++ diff --git a/io/CMakeLists.txt b/io/CMakeLists.txt index a48f3c68..28bdccc2 100644 --- a/io/CMakeLists.txt +++ b/io/CMakeLists.txt @@ -10,7 +10,8 @@ set(WIN32_SOURCES set(POSIX_SOURCES posix/file.c++ posix/helpers.h - posix/mmap.c++) + posix/mmap.c++ + posix/process.c++) if (AW_CAPABILITIES_WIN32) list(APPEND SOURCES ${WIN32_SOURCES}) diff --git a/io/include/aw/io/process.h b/io/include/aw/io/process.h new file mode 100644 index 00000000..cd17bdc7 --- /dev/null +++ b/io/include/aw/io/process.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 absurdworlds + * + * License LGPLv3 or later: + * GNU Lesser GPL version 3 + * This is free software: you are free to change and redistribute it. + * There is NO WARRANTY, to the extent permitted by law. + */ +#ifndef aw_platform_demangle_h +#define aw_platform_demangle_h +#include +#include +#include +#include +namespace aw::platform { +#if defined(AW_SUPPORT_PLATFORM_POSIX) +namespace posix { +enum class process_handle : long { + +}; +constexpr auto invalid_process_handle = process_handle(-1L ); + +/*! + * Spawn a child process with specified \a path and argument list \a argv. + * Argument list must end with `nullptr`. + */ +AW_PLATFORM_EXP process_handle spawn(const char* path, aw::array_ref argv, std::error_code& ec) noexcept; +/*! + * Spawn a child process with specified argument list \a argv. `argv[0]` is used as path. + */ +AW_PLATFORM_EXP process_handle spawn(aw::array_ref argv, std::error_code& ec) noexcept; +AW_PLATFORM_EXP int kill(process_handle pid, int signal, std::error_code& ec) noexcept; + +inline process_handle spawn(const char* path, aw::array_ref argv) +{ + std::error_code ec; + return spawn(path, argv, ec); +} +inline process_handle spawn(aw::array_ref argv) +{ + std::error_code ec; + return spawn(argv, ec); +} +AW_PLATFORM_EXP process_handle spawn(std::string path, aw::array_ref argv); + +inline int kill(process_handle pid, int signal) +{ + std::error_code ec; + return kill(pid, signal, ec); +} +} // namespace posix +#endif + +#if (AW_PLATFORM == AW_PLATFORM_POSIX) +using posix::process_handle; +using posix::invalid_process_handle; +using posix::spawn; +using posix::kill; +#endif +} // namespace aw::platform +#endif//aw_platform_demangle_h diff --git a/io/posix/process.c++ b/io/posix/process.c++ new file mode 100644 index 00000000..cba76b5e --- /dev/null +++ b/io/posix/process.c++ @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016-2018 hedede + * + * License LGPLv3 or later: + * GNU Lesser GPL version 3 + * This is free software: you are free to change and redistribute it. + * There is NO WARRANTY, to the extent permitted by law. + */ +#include + +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#ifdef __APPLE__ +extern char** environ; +#endif + +namespace aw::platform::posix { +AW_PLATFORM_EXP +process_handle spawn(const char* path, aw::array_ref argv, std::error_code& ec) noexcept +{ + /*! enforce `nullptr` at the end of `argv` */ + assert( argv.empty() || argv.back() == nullptr ); + + pid_t pid; + int rc = posix_spawn(&pid, path, nullptr, nullptr, argv.data(), environ); + if (rc == 0) + return process_handle( pid ); + + ec.assign( rc, std::generic_category() ); + return invalid_process_handle; +} + +AW_PLATFORM_EXP +process_handle spawn(aw::array_ref argv, std::error_code& ec) noexcept +{ + return spawn( argv[0], argv, ec ); +} + +AW_PLATFORM_EXP +process_handle spawn(std::string path, aw::array_ref argv) +{ + std::error_code ec; + std::vector args; + args.push_back(path.data()); + for (std::string& arg : argv) + args.push_back(arg.data()); + args.push_back(nullptr); + + return spawn(path.data(), args, ec); +} + +AW_PLATFORM_EXP +int kill(process_handle pid, int signal, std::error_code& ec) noexcept +{ + // TODO: don't accept -1 or 0 as pid, add separate functions for that + if( in( pid, process_handle(0), process_handle(-1) ) ) { + ec = make_error_code( std::errc::invalid_argument ); + return -1; + }; + + auto ret = ::kill( pid_t(pid), signal); + if (ret < 0) + ec.assign( errno, std::generic_category() ); + return ret; +} +} // namespace aw::platform::posix diff --git a/platform/CMakeLists.txt b/platform/CMakeLists.txt index b82fdea7..db397545 100644 --- a/platform/CMakeLists.txt +++ b/platform/CMakeLists.txt @@ -1,11 +1,4 @@ set(SOURCES os_version.c++ demangle.c++) -if (AW_CAPABILITIES_WIN32) - # list(APPEND SOURCES win32/process.c++) -endif() - -if (AW_CAPABILITIES_POSIX) - list(APPEND SOURCES posix/process.c++) -endif() aw_add_library(awplatform SHARED GLOB_HEADERS From a8816e4956604ae6bc217014dfdf8e471f51e054 Mon Sep 17 00:00:00 2001 From: Hudd Date: Fri, 22 Mar 2024 13:43:21 +0400 Subject: [PATCH 3/7] test: add test_context Provides additional information about the test. For now it's just the exe_dir to know where the test excutable is located (useful if tests rely on external data that's bundled with the tet executable) --- test/CMakeLists.txt | 1 + test/include/aw/test/main.h | 13 +++++++--- test/include/aw/test/registry.h | 38 +++++++++++++---------------- test/include/aw/test/test.h | 10 +++----- test/include/aw/test/test_case.h | 4 ++- test/include/aw/test/test_context.h | 12 +++++++++ 6 files changed, 47 insertions(+), 31 deletions(-) create mode 100644 test/include/aw/test/test_context.h diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 721cd01b..35370e40 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,6 +6,7 @@ aw_add_library(awtest INTERFACE include/aw/test/print.h include/aw/test/segv_handler.h include/aw/test/test_case.h + include/aw/test/test_context.h include/aw/test/test.h include/aw/test/main.h EXPORT_NAME diff --git a/test/include/aw/test/main.h b/test/include/aw/test/main.h index 3f020c11..6751ed31 100644 --- a/test/include/aw/test/main.h +++ b/test/include/aw/test/main.h @@ -13,12 +13,15 @@ #include #include +#include + namespace aw::test { struct test_config { bool use_junit = false; bool no_exitcode = false; }; + test_config parse_parameters(char** begin, char** end) { using namespace std::string_view_literals; @@ -48,7 +51,7 @@ test_config parse_parameters(char** begin, char** end) return config; } -int registry::run(report* reporter) +int registry::run(std::string_view exe_dir, report* reporter) { auto& vec = static_object<_ctxs>::instance().ctxs; @@ -60,7 +63,7 @@ int registry::run(report* reporter) int res = 0; for (auto& ctx : vec) - res += ctx->run(reporter); + res += ctx->run(exe_dir, reporter); reporter->end_tests(total, res); @@ -73,6 +76,10 @@ int main(int n_param, char** parameters) { using namespace aw::test; + auto dir_name = [] (std::string_view s) { + return std::filesystem::path(s).parent_path().string(); + }; + auto config = parse_parameters(parameters, parameters + n_param); report_classic classic; @@ -80,7 +87,7 @@ int main(int n_param, char** parameters) report* _report = config.use_junit ? (report*)&junit : (report*)&classic; - int fail_count = aw::test::registry::run(_report); + int fail_count = aw::test::registry::run(dir_name(parameters[0]), _report); return config.no_exitcode ? 0 : fail_count; } diff --git a/test/include/aw/test/registry.h b/test/include/aw/test/registry.h index 641c3019..f2fbc429 100644 --- a/test/include/aw/test/registry.h +++ b/test/include/aw/test/registry.h @@ -15,12 +15,12 @@ #include #include +#include #include int main(int,char**); namespace aw::test { - class registry { friend int ::main(int, char**); friend struct context; @@ -37,7 +37,7 @@ class registry { vec.push_back(&ctx); } - inline static int run(report* _report); + inline static int run(std::string_view exe_dir, report* _report); }; struct test_failed : std::exception {}; @@ -60,14 +60,14 @@ struct context { test_case& current() { return *cur; } char const* const filename; - int run(report* _report) + int run(std::string_view exe_dir, report* _report) { install_handler(); _report->begin_suite(filename, tests.size()); for (test_case& test : tests) - run_test_case(test, _report); + run_test_case(test, exe_dir, _report); _report->end_suite(); @@ -123,11 +123,19 @@ struct context { #endif } - inline void run_test_case(test_case& tst, report* _report); + inline void run_test_case(test_case& tst, std::string_view exe_dir, report* _report); - inline void test_failure(report* _report); - inline void test_success(report* _report); + void test_failure(report* _report) + { + ++failed; + _report->test_failure( cur->name, cur->checks, stage_name[size_t(cur->st)] ); + } + + void test_success(report* _report) + { + _report->test_success( cur->name, cur->checks ); + } void report_test_failure(report* _report) { @@ -144,19 +152,7 @@ struct context { bool negative = false; }; -void context::test_failure(report* _report) -{ - ++failed; - - _report->test_failure( cur->name, cur->checks, stage_name[size_t(cur->st)] ); -} - -void context::test_success(report* _report) -{ - _report->test_success( cur->name, cur->checks ); -} - -void context::run_test_case(test_case& test, report* _report) +void context::run_test_case(test_case& test, std::string_view exe_dir, report* _report) { using namespace std::string_literals; @@ -164,7 +160,7 @@ void context::run_test_case(test_case& test, report* _report) try { enter(stage::start); - cur->func(); + cur->func({ exe_dir }); enter(stage::end); return test_success(_report); } diff --git a/test/include/aw/test/test.h b/test/include/aw/test/test.h index a6b5ad2d..320d1880 100644 --- a/test/include/aw/test/test.h +++ b/test/include/aw/test/test.h @@ -26,8 +26,7 @@ #include #endif -namespace aw { -namespace test { +namespace aw::test { namespace { extern context file_context; @@ -172,14 +171,13 @@ struct _catch { std::string _msg; }; } // namespace -} // namespace test -} // namespace aw +} // namespace aw:: #define TestFile(...) namespace aw::test { namespace { context file_context{__VA_ARGS__}; } } #define Test(name) \ - void run_test_##name(); \ + void run_test_##name(::aw::test::test_context); \ const aw::test::register_test add_test_##name{#name, run_test_##name}; \ - void run_test_##name() + void run_test_##name(::aw::test::test_context _context) #define Setup if (aw::test::setup()) #define Preconditions if (aw::test::preconditions()) #define Checks if (aw::test::checks()) diff --git a/test/include/aw/test/test_case.h b/test/include/aw/test/test_case.h index 9f88aaba..09567ebc 100644 --- a/test/include/aw/test/test_case.h +++ b/test/include/aw/test/test_case.h @@ -11,6 +11,8 @@ #include #include +#include "test_context.h" + namespace aw::test { enum class stage : size_t { start, @@ -37,7 +39,7 @@ struct check_report { }; struct test_case { - using test_function = void(); + using test_function = void(test_context exe_dir); test_case(char const* name, test_function* func) : name{name}, func{func} { } diff --git a/test/include/aw/test/test_context.h b/test/include/aw/test/test_context.h new file mode 100644 index 00000000..11f95d87 --- /dev/null +++ b/test/include/aw/test/test_context.h @@ -0,0 +1,12 @@ +#ifndef aw_test_test_context_h +#define aw_test_test_context_h + +#include + +namespace aw::test { + +struct test_context { + std::string_view exe_dir; +}; +} // namespace aw::test +#endif // aw_test_test_context_h From 65784e5172185646255546aed2efe8b78b4a62fd Mon Sep 17 00:00:00 2001 From: Hudd Date: Fri, 22 Mar 2024 13:45:47 +0400 Subject: [PATCH 4/7] io: implement win32 process --- io/CMakeLists.txt | 1 + io/include/aw/io/process.h | 33 +++-- io/include/aw/io/wait_status.h | 15 +++ io/include/aw/io/win32/detail/handle_holder.h | 47 +++++++ io/include/aw/io/win32/process.h | 96 ++++++++++++++ io/tests/CMakeLists.txt | 3 + io/tests/process.c++ | 40 ++++++ io/tests/process/dump_args.c++ | 10 ++ io/win32/error_codes.c++ | 2 +- io/win32/path.h | 3 + io/win32/process.c++ | 122 ++++++++++++++++++ io/win32/winapi_helpers.h | 10 ++ platform/include/aw/platform/process.h | 8 +- 13 files changed, 374 insertions(+), 16 deletions(-) create mode 100644 io/include/aw/io/wait_status.h create mode 100644 io/include/aw/io/win32/detail/handle_holder.h create mode 100644 io/include/aw/io/win32/process.h create mode 100644 io/tests/process.c++ create mode 100644 io/tests/process/dump_args.c++ create mode 100644 io/win32/process.c++ diff --git a/io/CMakeLists.txt b/io/CMakeLists.txt index 28bdccc2..12ae4836 100644 --- a/io/CMakeLists.txt +++ b/io/CMakeLists.txt @@ -4,6 +4,7 @@ set(WIN32_SOURCES win32/file.c++ win32/mmap.c++ win32/path.h + win32/process.c++ win32/winapi_category.c++ win32/winapi_helpers.h) diff --git a/io/include/aw/io/process.h b/io/include/aw/io/process.h index cd17bdc7..f9cbb738 100644 --- a/io/include/aw/io/process.h +++ b/io/include/aw/io/process.h @@ -6,13 +6,16 @@ * This is free software: you are free to change and redistribute it. * There is NO WARRANTY, to the extent permitted by law. */ -#ifndef aw_platform_demangle_h -#define aw_platform_demangle_h -#include +#ifndef aw_platform_process_h +#define aw_platform_process_h +#include #include -#include -#include -namespace aw::platform { + +#if defined(AW_SUPPORT_PLATFORM_WIN32) +#include "win32/process.h" +#endif + +namespace aw::io { #if defined(AW_SUPPORT_PLATFORM_POSIX) namespace posix { enum class process_handle : long { @@ -24,12 +27,12 @@ constexpr auto invalid_process_handle = process_handle(-1L ); * Spawn a child process with specified \a path and argument list \a argv. * Argument list must end with `nullptr`. */ -AW_PLATFORM_EXP process_handle spawn(const char* path, aw::array_ref argv, std::error_code& ec) noexcept; +AW_IO_EXP process_handle spawn(const char* path, aw::array_ref argv, std::error_code& ec) noexcept; /*! * Spawn a child process with specified argument list \a argv. `argv[0]` is used as path. */ -AW_PLATFORM_EXP process_handle spawn(aw::array_ref argv, std::error_code& ec) noexcept; -AW_PLATFORM_EXP int kill(process_handle pid, int signal, std::error_code& ec) noexcept; +AW_IO_EXP process_handle spawn(aw::array_ref argv, std::error_code& ec) noexcept; +AW_IO_EXP int kill(process_handle pid, int signal, std::error_code& ec) noexcept; inline process_handle spawn(const char* path, aw::array_ref argv) { @@ -41,7 +44,7 @@ inline process_handle spawn(aw::array_ref argv) std::error_code ec; return spawn(argv, ec); } -AW_PLATFORM_EXP process_handle spawn(std::string path, aw::array_ref argv); +AW_IO_EXP process_handle spawn(std::string path, aw::array_ref argv); inline int kill(process_handle pid, int signal) { @@ -56,6 +59,14 @@ using posix::process_handle; using posix::invalid_process_handle; using posix::spawn; using posix::kill; +#elif (AW_PLATFORM == AW_PLATFORM_WIN32) +using win32::invalid_process_handle; +using win32::process_handle; +using win32::spawn; +using win32::run; +using win32::kill; +using win32::wait; +using win32::executable_name; #endif } // namespace aw::platform -#endif//aw_platform_demangle_h +#endif//aw_platform_process_h diff --git a/io/include/aw/io/wait_status.h b/io/include/aw/io/wait_status.h new file mode 100644 index 00000000..f7aec9cd --- /dev/null +++ b/io/include/aw/io/wait_status.h @@ -0,0 +1,15 @@ +#ifndef aw_io_wait_status_h +#define aw_io_wait_status_h + +namespace aw::io { + +enum class wait_status { + finished, + timeout, + failed, +}; + +} // namespace aw::io + + +#endif // aw_io_wait_status_h diff --git a/io/include/aw/io/win32/detail/handle_holder.h b/io/include/aw/io/win32/detail/handle_holder.h new file mode 100644 index 00000000..6c10ea78 --- /dev/null +++ b/io/include/aw/io/win32/detail/handle_holder.h @@ -0,0 +1,47 @@ +#ifndef aw_io_handle_holder_h +#define aw_io_handle_holder_h + +#include + +#include + +#include + +namespace aw::io::win32::detail { + +AW_IO_EXP +void close_handle( uintptr_t handle ); + +template +class handle_holder { +public: + handle_holder( Handle handle ) + : handle( handle ) + {} + + handle_holder(const handle_holder& other) = delete; + handle_holder(handle_holder&& other) + : handle(std::exchange(other.handle, Handle(0))) + { + } + + ~handle_holder() { close_handle(handle); } + + handle_holder& operator=(const handle_holder& other) = delete; + handle_holder& operator=(handle_holder&& other) + { + close_handle(handle); + handle = std::exchange(other.handle, Handle(0)); + return *this; + } + + operator Handle() { return static_cast(handle); } + +private: + Handle handle; +}; + + +} // namespace aw::io::win32::detail + +#endif // aw_io_handle_holder_h diff --git a/io/include/aw/io/win32/process.h b/io/include/aw/io/win32/process.h new file mode 100644 index 00000000..c4ee050b --- /dev/null +++ b/io/include/aw/io/win32/process.h @@ -0,0 +1,96 @@ +#ifndef aw_io_win32_process_h +#define aw_io_win32_process_h + +#include "detail/handle_holder.h" + +#include +#include + +#include + +#include +#include +#include + +namespace aw::io::win32 { +enum class process_handle : uintptr_t {}; +constexpr auto invalid_process_handle = process_handle(-1); + +using process_holder = detail::handle_holder; +using timeout_spec_ms = std::optional; + +inline void close_handle(process_handle handle) +{ + detail::close_handle( underlying(handle) ); +} + +/*! + * Spawn a child process with specified \a path and argument list \a argv. + * Argument list must end with `nullptr`. + */ +AW_IO_EXP process_holder spawn(const char* path, aw::array_view argv, std::error_code& ec) noexcept; +/*! + * Spawn a child process with specified argument list \a argv. `argv[0]` is used as path. + */ +AW_IO_EXP process_holder spawn(aw::array_view argv, std::error_code& ec) noexcept; + +inline process_holder spawn(const char* path, aw::array_view argv) +{ + std::error_code ec; + return spawn(path, argv, ec); +} +inline process_holder spawn(aw::array_view argv) +{ + std::error_code ec; + return spawn(argv, ec); +} + +AW_IO_EXP process_holder spawn(std::string path, aw::array_view argv, std::error_code& ec); +inline process_holder spawn(std::string path, aw::array_view argv) +{ + std::error_code ec; + return spawn(path, argv, ec); +} + +AW_IO_EXP wait_status wait(process_handle pid, std::error_code& ec, timeout_spec_ms timeout = {}) noexcept; +inline wait_status wait(process_handle pid, timeout_spec_ms timeout = {}) +{ + std::error_code ec; + return wait(pid, ec, timeout); +} + +AW_IO_EXP int kill(process_handle pid, int signal, std::error_code& ec) noexcept; +inline int kill(process_handle pid, int signal) +{ + std::error_code ec; + return kill(pid, signal, ec); +} + +inline wait_status run( + std::string path, + aw::array_view argv, + std::error_code& ec, + timeout_spec_ms timeout = {}) +{ + auto handle = spawn(path, argv, ec); + if (handle == invalid_process_handle) + return wait_status::failed; + + return wait(handle, ec); +} + +inline wait_status run(std::string path, aw::array_view argv, timeout_spec_ms timeout = {}) +{ + std::error_code ec; + return run(path, argv, ec, timeout); +} + +inline std::string executable_name(std::string path) +{ + if (!path.ends_with(".exe")) + path += ".exe"; + return path; +} +} // namespace aw::io::win32 + +#endif // aw_io_win32_process_h diff --git a/io/tests/CMakeLists.txt b/io/tests/CMakeLists.txt index 4987db3d..e55e4ae7 100644 --- a/io/tests/CMakeLists.txt +++ b/io/tests/CMakeLists.txt @@ -5,6 +5,7 @@ aw_add_test(test_io native_file.cpp mmap_file.cpp file_stream.cpp + process.c++ istream_adapter.cpp) target_link_libraries(test_io @@ -13,3 +14,5 @@ target_link_libraries(test_io awtest awtest_main ) + +add_executable(dump_args process/dump_args.c++) diff --git a/io/tests/process.c++ b/io/tests/process.c++ new file mode 100644 index 00000000..be64f014 --- /dev/null +++ b/io/tests/process.c++ @@ -0,0 +1,40 @@ +#include +#include + +#include +#include + +#include + +#include + +TestFile("Test"); + +namespace aw { +Test(process_basic_test) { + using namespace std::string_literals; + + auto cd_guard = on_scope_exit([cd = fs::current_path()] { fs::current_path(cd); }); + fs::current_path(_context.exe_dir); + + auto path = io::executable_name("dump_args"s); + + auto result = io::run(path, { "a", "b", "c" }); + + TestAssert(result == io::wait_status::finished); + + std::vector args_expect{ { path, "a", "b", "c" } }; + std::vector args; + { + std::ifstream fs("argv.txt"); + std::string str; + while (std::getline(fs, str)) { + const auto pred = [] (char c) { return std::isspace(c); }; + args.push_back(string::rtrimmed(str, pred)); + } + } + + TestEqual(args, args_expect); +} + +} // namespace aw diff --git a/io/tests/process/dump_args.c++ b/io/tests/process/dump_args.c++ new file mode 100644 index 00000000..cdf5d1ca --- /dev/null +++ b/io/tests/process/dump_args.c++ @@ -0,0 +1,10 @@ +#include + +// simple test executable to test process api wrappers +int main(int, char** argv) +{ + // TODO: temp_file + std::ofstream f("argv.txt"); + while (auto str = *argv++) + f << str << std::endl; +} diff --git a/io/win32/error_codes.c++ b/io/win32/error_codes.c++ index 84be1262..7784fca0 100644 --- a/io/win32/error_codes.c++ +++ b/io/win32/error_codes.c++ @@ -1,5 +1,5 @@ #include -#include +#include "winapi_helpers.h" namespace aw { namespace win32 { // based on https://stackoverflow.com/questions/3952342/ diff --git a/io/win32/path.h b/io/win32/path.h index d91fedac..6cd0edae 100644 --- a/io/win32/path.h +++ b/io/win32/path.h @@ -23,6 +23,9 @@ struct winapi_path { winapi_path( fs::path const& path ) : path{ path.wstring() } {} + winapi_path( const std::wstring& path ) + : path{ path } + {} std::wstring path; #endif operator wchar_t*() const { return (wchar_t*)path.data(); } diff --git a/io/win32/process.c++ b/io/win32/process.c++ new file mode 100644 index 00000000..a2bc850c --- /dev/null +++ b/io/win32/process.c++ @@ -0,0 +1,122 @@ +#include "aw/io/win32/process.h" + +#include +#include +#include +#include + +#include "path.h" +#include "winapi_helpers.h" + +#include +#include + +namespace aw::io::win32 { +namespace detail { +void close_handle( uintptr_t handle ) +{ + CloseHandle(HANDLE(handle)); +} +} // namespace detail + +winapi_path format_command_line(const char* path, aw::array_view argv) +{ + bool first = true; + std::wstring result; + for (auto* arg : argv) { + if (first) + first = false; + else + result += L' '; + result += L'"'; + result += aw::unicode::widen(string::escape_quotes(arg)); + result += L'"'; + } + return result; +} + +process_holder spawn(const char* path, aw::array_view argv, std::error_code& ec) noexcept +{ + /*! enforce `nullptr` at the end of `argv` */ + if (!argv.empty()) { + assert( argv.back() == nullptr ); + argv.remove_suffix(1); + } + + STARTUPINFOW startup_info = {}; + PROCESS_INFORMATION process_info = {}; + + GetStartupInfoW(&startup_info); + + auto ret = CreateProcessW( + path ? winapi_path(path) : nullptr, + format_command_line(path, argv), + nullptr, nullptr, false, 0, nullptr, nullptr, + &startup_info, &process_info ); + + set_error_if(!ret, ec); + + const auto handle = process_info.hProcess; + if (handle) + return convert_handle(process_info.hProcess); + + return invalid_process_handle; +} + +process_holder spawn(aw::array_view argv, std::error_code& ec) noexcept +{ + return spawn( nullptr, argv, ec ); +} + +process_holder spawn(std::string path, aw::array_view argv, std::error_code& ec) +{ + std::vector args; + args.push_back(path.data()); + for (const auto& arg : argv) + args.push_back(arg.data()); + args.push_back(nullptr); + + return spawn(path.data(), args, ec); +} + + +wait_status wait(process_handle hprocess, std::error_code& ec, timeout_spec_ms timeout) noexcept +{ + // TODO: add is_handle_valid() + if( in( hprocess, process_handle(0), process_handle(-1) ) ) { + ec = make_error_code( std::errc::invalid_argument ); + return wait_status::failed; + }; + + auto ret = WaitForSingleObject( HANDLE(hprocess), timeout ? timeout->count() : INFINITE ); + + set_error_if(ret == WAIT_FAILED, ec); + + switch (ret) { + using enum wait_status; + default: + case WAIT_OBJECT_0: + case WAIT_ABANDONED: + return finished; + case WAIT_TIMEOUT: + return timeout; + case WAIT_FAILED: + return failed; + } +} + +int kill(process_handle hprocess, int signal, std::error_code& ec) noexcept +{ + // TODO: add is_handle_valid() + if( in( hprocess, process_handle(0), process_handle(-1) ) ) { + ec = make_error_code( std::errc::invalid_argument ); + return -1; + }; + + auto ret = TerminateProcess( HANDLE(hprocess), 1 ); + + set_error_if(!ret, ec); + + return ret; +} +} // namespace aw::platform::win32 diff --git a/io/win32/winapi_helpers.h b/io/win32/winapi_helpers.h index 9b9406d6..01efc9bf 100644 --- a/io/win32/winapi_helpers.h +++ b/io/win32/winapi_helpers.h @@ -8,19 +8,29 @@ */ #ifndef aw_internal_winapi_helpers_h #define aw_internal_winapi_helpers_h +#include #include #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif//WIN32_LEAN_AND_MEAN + #ifndef NOMINMAX #define NOMINMAX #endif//NOMINMAX + #include + namespace aw { AW_IO_EXP std::error_category const& winapi_error_category(); namespace io { namespace win32 { +template +auto convert_handle( HANDLE h ) -> T +{ + return T( reinterpret_cast(h) ); +} + inline void set_error( std::error_code& ec ) { ec.assign( GetLastError(), winapi_error_category() ); diff --git a/platform/include/aw/platform/process.h b/platform/include/aw/platform/process.h index cd17bdc7..0f7cf7b0 100644 --- a/platform/include/aw/platform/process.h +++ b/platform/include/aw/platform/process.h @@ -6,8 +6,8 @@ * This is free software: you are free to change and redistribute it. * There is NO WARRANTY, to the extent permitted by law. */ -#ifndef aw_platform_demangle_h -#define aw_platform_demangle_h +#ifndef aw_platform_process_h +#define aw_platform_process_h #include #include #include @@ -57,5 +57,5 @@ using posix::invalid_process_handle; using posix::spawn; using posix::kill; #endif -} // namespace aw::platform -#endif//aw_platform_demangle_h +} // namespace aw::io +#endif//aw_platform_process_h From 724edb38524ed8729b76ccb264e594b779e5e4a6 Mon Sep 17 00:00:00 2001 From: Hudd Date: Fri, 22 Mar 2024 13:46:39 +0400 Subject: [PATCH 5/7] io: cleanup --- io/include/aw/io/mmap_file.h | 2 +- io/win32/file.c++ | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/io/include/aw/io/mmap_file.h b/io/include/aw/io/mmap_file.h index 99e24a3c..98469732 100644 --- a/io/include/aw/io/mmap_file.h +++ b/io/include/aw/io/mmap_file.h @@ -123,7 +123,7 @@ struct mmap_file { : _file{fd} { if (!ec) - create_mapping( ec, perms ); + create_mapping( ec, perms ); } mmap_file(fs::path const& path, map_perms perms = map_perms::rdwr) diff --git a/io/win32/file.c++ b/io/win32/file.c++ index a316fc9d..03e60efc 100644 --- a/io/win32/file.c++ +++ b/io/win32/file.c++ @@ -7,6 +7,7 @@ * There is NO WARRANTY, to the extent permitted by law. */ #include +#include #include "winapi_helpers.h" #include "path.h" namespace aw { @@ -41,6 +42,8 @@ int get_openmode( file_mode mode ) case fm::exclusive: case fm::none: return OPEN_EXISTING; + default: + assert(!"unreachable"); }; } } // namespace From ed696239fa9ee8c43b93679c32e6444d757abaaa Mon Sep 17 00:00:00 2001 From: Hudd Date: Fri, 22 Mar 2024 14:04:17 +0400 Subject: [PATCH 6/7] io: move posix process into a separate header --- io/include/aw/io/posix/process.h | 47 ++++++++++++++++++++++++++++++++ io/include/aw/io/process.h | 39 ++------------------------ io/posix/process.c++ | 8 +++--- 3 files changed, 53 insertions(+), 41 deletions(-) create mode 100644 io/include/aw/io/posix/process.h diff --git a/io/include/aw/io/posix/process.h b/io/include/aw/io/posix/process.h new file mode 100644 index 00000000..e07ccdee --- /dev/null +++ b/io/include/aw/io/posix/process.h @@ -0,0 +1,47 @@ +#ifndef aw_io_posix_process_h +#define aw_io_posix_process_h + +#include +#include + +#include +#include + +#include +#include + +namespace aw::io::posix { + +enum class process_handle : long {}; +constexpr auto invalid_process_handle = process_handle(-1L ); + +/*! + * Spawn a child process with specified \a path and argument list \a argv. + * Argument list must end with `nullptr`. + */ +AW_IO_EXP process_handle spawn(const char* path, aw::array_ref argv, std::error_code& ec) noexcept; +/*! + * Spawn a child process with specified argument list \a argv. `argv[0]` is used as path. + */ +AW_IO_EXP process_handle spawn(aw::array_ref argv, std::error_code& ec) noexcept; +AW_IO_EXP int kill(process_handle pid, int signal, std::error_code& ec) noexcept; + +inline process_handle spawn(const char* path, aw::array_ref argv) +{ + std::error_code ec; + return spawn(path, argv, ec); +} +inline process_handle spawn(aw::array_ref argv) +{ + std::error_code ec; + return spawn(argv, ec); +} +AW_IO_EXP process_handle spawn(std::string path, aw::array_ref argv); + +inline int kill(process_handle pid, int signal) +{ + std::error_code ec; + return kill(pid, signal, ec); +} +} // namespace aw::io::posix +#endif // aw_io_posix_process_h diff --git a/io/include/aw/io/process.h b/io/include/aw/io/process.h index f9cbb738..1de3674d 100644 --- a/io/include/aw/io/process.h +++ b/io/include/aw/io/process.h @@ -14,46 +14,11 @@ #if defined(AW_SUPPORT_PLATFORM_WIN32) #include "win32/process.h" #endif - -namespace aw::io { #if defined(AW_SUPPORT_PLATFORM_POSIX) -namespace posix { -enum class process_handle : long { - -}; -constexpr auto invalid_process_handle = process_handle(-1L ); - -/*! - * Spawn a child process with specified \a path and argument list \a argv. - * Argument list must end with `nullptr`. - */ -AW_IO_EXP process_handle spawn(const char* path, aw::array_ref argv, std::error_code& ec) noexcept; -/*! - * Spawn a child process with specified argument list \a argv. `argv[0]` is used as path. - */ -AW_IO_EXP process_handle spawn(aw::array_ref argv, std::error_code& ec) noexcept; -AW_IO_EXP int kill(process_handle pid, int signal, std::error_code& ec) noexcept; - -inline process_handle spawn(const char* path, aw::array_ref argv) -{ - std::error_code ec; - return spawn(path, argv, ec); -} -inline process_handle spawn(aw::array_ref argv) -{ - std::error_code ec; - return spawn(argv, ec); -} -AW_IO_EXP process_handle spawn(std::string path, aw::array_ref argv); - -inline int kill(process_handle pid, int signal) -{ - std::error_code ec; - return kill(pid, signal, ec); -} -} // namespace posix +#include "posix/process.h" #endif +namespace aw::io { #if (AW_PLATFORM == AW_PLATFORM_POSIX) using posix::process_handle; using posix::invalid_process_handle; diff --git a/io/posix/process.c++ b/io/posix/process.c++ index cba76b5e..916c3059 100644 --- a/io/posix/process.c++ +++ b/io/posix/process.c++ @@ -27,7 +27,7 @@ extern char** environ; #endif namespace aw::platform::posix { -AW_PLATFORM_EXP +AW_IO_EXP process_handle spawn(const char* path, aw::array_ref argv, std::error_code& ec) noexcept { /*! enforce `nullptr` at the end of `argv` */ @@ -42,13 +42,13 @@ process_handle spawn(const char* path, aw::array_ref argv, std::error_cod return invalid_process_handle; } -AW_PLATFORM_EXP +AW_IO_EXP process_handle spawn(aw::array_ref argv, std::error_code& ec) noexcept { return spawn( argv[0], argv, ec ); } -AW_PLATFORM_EXP +AW_IO_EXP process_handle spawn(std::string path, aw::array_ref argv) { std::error_code ec; @@ -61,7 +61,7 @@ process_handle spawn(std::string path, aw::array_ref argv) return spawn(path.data(), args, ec); } -AW_PLATFORM_EXP +AW_IO_EXP int kill(process_handle pid, int signal, std::error_code& ec) noexcept { // TODO: don't accept -1 or 0 as pid, add separate functions for that From 8274e40426bfef2b14ba5191c5fc3039573fa725 Mon Sep 17 00:00:00 2001 From: Hudd Date: Fri, 22 Mar 2024 14:58:33 +0400 Subject: [PATCH 7/7] io/posix: implement wait() and run() --- io/include/aw/io/posix/process.h | 46 +++++++++++++++++++++++++++----- io/include/aw/io/process.h | 5 +++- io/include/aw/io/win32/process.h | 2 +- io/posix/process.c++ | 36 ++++++++++++++----------- io/tests/process.c++ | 3 ++- 5 files changed, 67 insertions(+), 25 deletions(-) diff --git a/io/include/aw/io/posix/process.h b/io/include/aw/io/posix/process.h index e07ccdee..51c3773f 100644 --- a/io/include/aw/io/posix/process.h +++ b/io/include/aw/io/posix/process.h @@ -19,29 +19,63 @@ constexpr auto invalid_process_handle = process_handle(-1L ); * Spawn a child process with specified \a path and argument list \a argv. * Argument list must end with `nullptr`. */ -AW_IO_EXP process_handle spawn(const char* path, aw::array_ref argv, std::error_code& ec) noexcept; +AW_IO_EXP process_handle spawn(const char* path, aw::array_view argv, std::error_code& ec) noexcept; /*! * Spawn a child process with specified argument list \a argv. `argv[0]` is used as path. */ -AW_IO_EXP process_handle spawn(aw::array_ref argv, std::error_code& ec) noexcept; -AW_IO_EXP int kill(process_handle pid, int signal, std::error_code& ec) noexcept; +AW_IO_EXP process_handle spawn(aw::array_view argv, std::error_code& ec) noexcept; -inline process_handle spawn(const char* path, aw::array_ref argv) +inline process_handle spawn(const char* path, aw::array_view argv) { std::error_code ec; return spawn(path, argv, ec); } -inline process_handle spawn(aw::array_ref argv) +inline process_handle spawn(aw::array_view argv) { std::error_code ec; return spawn(argv, ec); } -AW_IO_EXP process_handle spawn(std::string path, aw::array_ref argv); +AW_IO_EXP process_handle spawn(std::string path, aw::array_ref argv, std::error_code& ec); +AW_IO_EXP process_handle spawn(std::string path, aw::array_ref argv) +{ + std::error_code ec; + return spawn(path, argv, ec); +} + + +AW_IO_EXP wait_status wait(process_handle pid, std::error_code& ec) noexcept; +inline wait_status wait(process_handle pid) +{ + std::error_code ec; + return wait(pid, ec); +} +AW_IO_EXP int kill(process_handle pid, int signal, std::error_code& ec) noexcept; inline int kill(process_handle pid, int signal) { std::error_code ec; return kill(pid, signal, ec); } + + +inline wait_status run(std::string path, aw::array_ref argv, std::error_code& ec) +{ + auto handle = spawn(path, argv, ec); + if (handle == invalid_process_handle) + return wait_status::failed; + + return wait(handle, ec); +} + +inline wait_status run(std::string path, aw::array_ref argv) +{ + std::error_code ec; + return run(path, argv, ec); +} + +inline std::string executable_name(std::string path) +{ + return path; +} } // namespace aw::io::posix #endif // aw_io_posix_process_h diff --git a/io/include/aw/io/process.h b/io/include/aw/io/process.h index 1de3674d..3eefc63a 100644 --- a/io/include/aw/io/process.h +++ b/io/include/aw/io/process.h @@ -24,13 +24,16 @@ using posix::process_handle; using posix::invalid_process_handle; using posix::spawn; using posix::kill; +using posix::wait; +using posix::run; +using posix::executable_name; #elif (AW_PLATFORM == AW_PLATFORM_WIN32) using win32::invalid_process_handle; using win32::process_handle; using win32::spawn; -using win32::run; using win32::kill; using win32::wait; +using win32::run; using win32::executable_name; #endif } // namespace aw::platform diff --git a/io/include/aw/io/win32/process.h b/io/include/aw/io/win32/process.h index c4ee050b..2300befc 100644 --- a/io/include/aw/io/win32/process.h +++ b/io/include/aw/io/win32/process.h @@ -76,7 +76,7 @@ inline wait_status run( if (handle == invalid_process_handle) return wait_status::failed; - return wait(handle, ec); + return wait(handle, ec, timeout); } inline wait_status run(std::string path, aw::array_view argv, timeout_spec_ms timeout = {}) diff --git a/io/posix/process.c++ b/io/posix/process.c++ index 916c3059..dacf900f 100644 --- a/io/posix/process.c++ +++ b/io/posix/process.c++ @@ -1,17 +1,8 @@ -/* - * Copyright (C) 2016-2018 hedede - * - * License LGPLv3 or later: - * GNU Lesser GPL version 3 - * This is free software: you are free to change and redistribute it. - * There is NO WARRANTY, to the extent permitted by law. - */ -#include +#include "aw/io/posix/process.h" #include #include -#include #include #include @@ -26,15 +17,18 @@ extern char** environ; #endif -namespace aw::platform::posix { +namespace aw::io::posix { AW_IO_EXP -process_handle spawn(const char* path, aw::array_ref argv, std::error_code& ec) noexcept +process_handle spawn(const char* path, aw::array_view argv, std::error_code& ec) noexcept { /*! enforce `nullptr` at the end of `argv` */ assert( argv.empty() || argv.back() == nullptr ); pid_t pid; - int rc = posix_spawn(&pid, path, nullptr, nullptr, argv.data(), environ); + int rc = posix_spawnp(&pid, path, nullptr, nullptr, argv.data(), environ); + if (rc == 2) + rc = posix_spawn(&pid, path, nullptr, nullptr, argv.data(), environ); + if (rc == 0) return process_handle( pid ); @@ -43,15 +37,14 @@ process_handle spawn(const char* path, aw::array_ref argv, std::error_cod } AW_IO_EXP -process_handle spawn(aw::array_ref argv, std::error_code& ec) noexcept +process_handle spawn(aw::array_view argv, std::error_code& ec) noexcept { return spawn( argv[0], argv, ec ); } AW_IO_EXP -process_handle spawn(std::string path, aw::array_ref argv) +process_handle spawn(std::string path, aw::array_ref argv, std::error_code& ec) { - std::error_code ec; std::vector args; args.push_back(path.data()); for (std::string& arg : argv) @@ -75,4 +68,15 @@ int kill(process_handle pid, int signal, std::error_code& ec) noexcept ec.assign( errno, std::generic_category() ); return ret; } + +AW_IO_EXP wait_status wait(process_handle pid, std::error_code& ec) noexcept +{ + int status = 0; + pid_t ret = waitpid( pid_t(pid), &status, 0); + if (ret < 0) { + ec.assign( errno, std::generic_category() ); + return wait_status::failed; + } + return wait_status::finished; +} } // namespace aw::platform::posix diff --git a/io/tests/process.c++ b/io/tests/process.c++ index be64f014..62799d84 100644 --- a/io/tests/process.c++ +++ b/io/tests/process.c++ @@ -19,7 +19,8 @@ Test(process_basic_test) { auto path = io::executable_name("dump_args"s); - auto result = io::run(path, { "a", "b", "c" }); + std::vector in_args = { "a", "b", "c" }; + auto result = io::run(path, in_args); TestAssert(result == io::wait_status::finished);