From d17f37db1213fcd4211ea14cd19bf4099e050a70 Mon Sep 17 00:00:00 2001 From: loki Date: Sat, 14 Dec 2019 23:57:04 +0100 Subject: [PATCH] Groundwork for running different applications --- CMakeLists.txt | 5 +- sunshine/main.cpp | 28 ++++++- sunshine/platform/common.h | 2 + sunshine/platform/linux.cpp | 4 + sunshine/process.cpp | 143 ++++++++++++++++++++++++++++++++++++ sunshine/process.h | 67 +++++++++++++++++ sunshine/thread_safe.h | 14 +--- sunshine/utility.h | 8 ++ 8 files changed, 256 insertions(+), 15 deletions(-) create mode 100644 sunshine/process.cpp create mode 100644 sunshine/process.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 71d92ea1..950a748e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,12 +69,15 @@ set(SUNSHINE_TARGET_FILES sunshine/stream.cpp sunshine/stream.h sunshine/video.cpp - sunshine/video.h sunshine/thread_safe.h + sunshine/video.h + sunshine/thread_safe.h sunshine/input.cpp sunshine/input.h sunshine/audio.cpp sunshine/audio.h sunshine/platform/common.h + sunshine/process.cpp + sunshine/process.h ${PLATFORM_TARGET_FILES}) include_directories( diff --git a/sunshine/main.cpp b/sunshine/main.cpp index 2d86a4c4..12007315 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -14,12 +14,36 @@ extern "C" { } #include "config.h" -#include "platform/common.h" + +#include "process.h" + using namespace std::literals; int main(int argc, char *argv[]) { + std::vector pre_cmds { + { "echo pre-1", "echo post-1" }, + { "echo pre-2", "" }, + { "echo pre-3", "echo post-3" } + }; + + std::unordered_map map { + { "echo", { std::move(pre_cmds), R"(echo \"middle\")", "output.txt" } } + }; + + boost::process::environment env = boost::this_process::environment(); + proc::proc_t proc(std::move(env), std::move(map)); + + proc.execute("echo"s); + + std::this_thread::sleep_for(50ms); + + proc.execute("echo"s); + + std::this_thread::sleep_for(50ms); + return proc.running(); + if(argc > 1) { if(!std::filesystem::exists(argv[1])) { - std::cout << "Error: Couln't find configuration file ["sv << argv[1] << ']' << std::endl; + std::cout << "Error: Couldn't find configuration file ["sv << argv[1] << ']' << std::endl; return 7; } diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index 0de6597a..6ee83282 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -34,6 +34,8 @@ struct gamepad_state_t { std::string get_local_ip(); +void interrupt_process(std::uint64_t handle); + mic_t microphone(); audio_t audio(mic_t &mic, std::uint32_t sample_size); diff --git a/sunshine/platform/linux.cpp b/sunshine/platform/linux.cpp index 343d08e0..fdaa613e 100644 --- a/sunshine/platform/linux.cpp +++ b/sunshine/platform/linux.cpp @@ -84,6 +84,10 @@ std::string get_local_ip(int family) { std::string get_local_ip() { return get_local_ip(AF_INET); } +void interrupt_process(std::uint64_t handle) { + kill((pid_t)handle, SIGTERM); +} + struct display_attr_t { display_attr_t() : display { XOpenDisplay(nullptr) }, window { DefaultRootWindow(display) }, attr {} { refresh(); diff --git a/sunshine/process.cpp b/sunshine/process.cpp new file mode 100644 index 00000000..df05fcb7 --- /dev/null +++ b/sunshine/process.cpp @@ -0,0 +1,143 @@ +// +// Created by loki on 12/14/19. +// + +#include +#include +#include + +#include "process.h" +#include "config.h" +#include "utility.h" +#include "platform/common.h" + +namespace proc { +using namespace std::literals; +namespace bp = boost::process; + +template +void process_end(bp::child &proc, const std::chrono::duration& rel_time) { + if(!proc.running()) { + return; + } + + platf::interrupt_process((std::uint64_t)proc.native_handle()); + + // Force termination if it takes too long + if(!proc.wait_for(rel_time)) { + proc.terminate(); + } +} + +int exe(const std::string &cmd, bp::environment &env, file_t &file, std::error_code &ec) { + if(cmd.empty() || cmd == "null"sv) { + return bp::system(cmd, env, bp::std_out > bp::null, bp::std_err > bp::null, ec); + } + + return bp::system(cmd, env, bp::std_out > file.get(), bp::std_err > file.get(), ec); +} + +int proc_t::execute(const std::string &name) { + auto it = _name_to_proc.find(name); + + // Ensure starting from a clean slate + _undo_pre_cmd(); + + if(it == std::end(_name_to_proc)) { + std::cout << "Error: Couldn't find ["sv << name << ']' << std::endl; + return 404; + } + + auto &proc = it->second; + + _undo_begin = std::begin(proc.pre_cmds); + _undo_it = _undo_begin; + + if(!proc.cmd_output.empty() && proc.cmd_output != "null"sv) { + _pipe.reset(fopen(proc.cmd_output.c_str(), "a")); + } + + std::error_code ec; + //Executed when returning from function + auto fg = util::fail_guard([&]() { + _undo_pre_cmd(); + }); + + for(; _undo_it != std::end(proc.pre_cmds); ++_undo_it) { + auto &cmd = _undo_it->do_cmd; + + std::cout << "Executing: ["sv << cmd << ']' << std::endl; + auto ret = exe(cmd, _env, _pipe, ec); + + if(ec) { + std::cout << "Error: System: "sv << ec.message() << std::endl; + return -1; + } + + if(ret != 0) { + std::cout << "Error: return code ["sv << ret << ']'; + return -1; + } + } + + std::cout << "Starting ["sv << proc.cmd << ']' << std::endl; + if(proc.cmd_output.empty() || proc.cmd_output == "null"sv) { + _process = bp::child(proc.cmd, _env, bp::std_out > bp::null, bp::std_err > bp::null, ec); + } + else { + _process = bp::child(proc.cmd, _env, bp::std_out > proc.cmd_output, bp::std_err > proc.cmd_output, ec); + } + + if(ec) { + std::cout << "Error: System: "sv << ec.message() << std::endl; + return -1; + } + + fg.disable(); + + return 0; +} + +bool proc_t::running() { + return _process.running(); +} + +void proc_t::_undo_pre_cmd() { + std::error_code ec; + + // Ensure child process is terminated + process_end(_process, 10s); + + if(ec) { + std::cout << "FATAL Error: System: "sv << ec.message() << std::endl; + std::abort(); + } + + for(;_undo_it != _undo_begin; --_undo_it) { + auto &cmd = (_undo_it - 1)->undo_cmd; + + if(cmd.empty()) { + continue; + } + + std::cout << "Executing: ["sv << cmd << ']' << std::endl; + + auto ret = exe(cmd, _env, _pipe, ec); + + if(ec) { + std::cout << "FATAL Error: System: "sv << ec.message() << std::endl; + std::abort(); + } + + if(ret != 0) { + std::cout << "FATAL Error: return code ["sv << ret << ']'; + std::abort(); + } + } + + _pipe.reset(); +} +proc_t::~proc_t() { + _undo_pre_cmd(); +} +} \ No newline at end of file diff --git a/sunshine/process.h b/sunshine/process.h new file mode 100644 index 00000000..f0a3f114 --- /dev/null +++ b/sunshine/process.h @@ -0,0 +1,67 @@ +// +// Created by loki on 12/14/19. +// + +#ifndef SUNSHINE_PROCESS_H +#define SUNSHINE_PROCESS_H + +#include + +#include +#include "utility.h" + +namespace proc { +using file_t = util::safe_ptr_v2; + +struct cmd_t { + std::string do_cmd; + + // Executed when proc_t has finished running, meant to reverse 'do_cmd' if applicable + std::string undo_cmd; +}; +/* + * pre_cmds -- guaranteed to be executed unless any of the commands fail. + * cmd -- Runs indefinitely until: + * No session is running and a different set of commands it to be executed + * Command exits + * cmd_output -- + * empty -- The output of the commands are appended to the output of sunshine + * "null" -- The output of the commands are discarded + * filename -- The output of the commands are appended to filename + */ +struct ctx_t { + std::vector pre_cmds; + + std::string cmd; + std::string cmd_output; +}; + +class proc_t { +public: + KITTY_DEFAULT_CONSTR(proc_t) + + proc_t( + boost::process::environment &&env, + std::unordered_map &&name_to_proc) : + _env(std::move(env)), + _name_to_proc(std::move(name_to_proc)) {} + + int execute(const std::string &name); + bool running(); + + ~proc_t(); + +private: + void _undo_pre_cmd(); + + boost::process::environment _env; + std::unordered_map _name_to_proc; + + boost::process::child _process; + file_t _pipe; + std::vector::const_iterator _undo_it; + std::vector::const_iterator _undo_begin; +}; + +} +#endif //SUNSHINE_PROCESS_H diff --git a/sunshine/thread_safe.h b/sunshine/thread_safe.h index 7112339a..68e6d88a 100644 --- a/sunshine/thread_safe.h +++ b/sunshine/thread_safe.h @@ -14,12 +14,7 @@ namespace safe { template class event_t { - using status_t = util::either_t< - (std::is_same_v || - util::instantiation_of_v || - util::instantiation_of_v || - std::is_pointer_v), - T, std::optional>; + using status_t = util::optional_t; public: template @@ -82,12 +77,7 @@ private: template class queue_t { - using status_t = util::either_t< - (std::is_same_v || - util::instantiation_of_v || - util::instantiation_of_v || - std::is_pointer_v), - T, std::optional>; + using status_t = util::optional_t; public: template diff --git a/sunshine/utility.h b/sunshine/utility.h index 9521f8e3..71b95040 100644 --- a/sunshine/utility.h +++ b/sunshine/utility.h @@ -93,6 +93,14 @@ struct __false_v>> { template static constexpr auto false_v = __false_v::value; +template +using optional_t = either_t< + (std::is_same_v || + instantiation_of_v || + instantiation_of_v || + std::is_pointer_v), + T, std::optional>; + template class FailGuard { public: