/* * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published by the * Free Software Foundation; either version 3 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "StartProcess.h" #include "Errors.h" #include "Log.h" #include "Optional.h" #include "Util.h" #include #include #include "boost/process.hpp" #include using namespace boost::process; using namespace boost::iostreams; namespace Acore { template class ACLogSink { T callback_; public: typedef char char_type; typedef sink_tag category; // Requires a callback type which has a void(std::string) signature ACLogSink(T callback) : callback_(std::move(callback)) { } std::streamsize write(char const* str, std::streamsize size) { std::string consoleStr(str, size); std::string utf8; if (consoleToUtf8(consoleStr, utf8)) callback_(utf8); return size; } }; template auto MakeACLogSink(T&& callback) -> ACLogSink::type> { return { std::forward(callback) }; } template static int CreateChildProcess(T waiter, std::string const& executable, std::vector const& argsVector, std::string const& logger, std::string const& input, bool secure) { #if AC_COMPILER == AC_COMPILER_MICROSOFT #pragma warning(push) #pragma warning(disable:4297) /* Silence warning with boost 1.83 boost/process/pipe.hpp(132,5): warning C4297: 'boost::process::basic_pipebuf>::~basic_pipebuf': function assumed not to throw an exception but does boost/process/pipe.hpp(132,5): message : destructor or deallocator has a (possibly implicit) non-throwing exception specification boost/process/pipe.hpp(124,6): message : while compiling class template member function 'boost::process::basic_pipebuf>::~basic_pipebuf(void)' boost/process/pipe.hpp(304,42): message : see reference to class template instantiation 'boost::process::basic_pipebuf>' being compiled */ #endif ipstream outStream; ipstream errStream; #if AC_COMPILER == AC_COMPILER_MICROSOFT #pragma warning(pop) #endif if (!secure) { LOG_TRACE(logger, "Starting process \"{}\" with arguments: \"{}\".", executable, boost::algorithm::join(argsVector, " ")); } // prepare file with only read permission (boost process opens with read_write) std::shared_ptr inputFile(!input.empty() ? fopen(input.c_str(), "rb") : nullptr, [](FILE* ptr) { if (ptr != nullptr) fclose(ptr); }); // Start the child process child c = [&]() { if (inputFile) { // With binding stdin return child{ exe = std::filesystem::absolute(executable).string(), args = argsVector, env = environment(boost::this_process::environment()), std_in = inputFile.get(), std_out = outStream, std_err = errStream }; } else { // Without binding stdin return child{ exe = std::filesystem::absolute(executable).string(), args = argsVector, env = environment(boost::this_process::environment()), std_in = boost::process::close, std_out = outStream, std_err = errStream }; } }(); auto outInfo = MakeACLogSink([&](std::string const& msg) { LOG_INFO(logger, "{}", msg); }); auto outError = MakeACLogSink([&](std::string const& msg) { LOG_ERROR(logger, "{}", msg); }); copy(outStream, outInfo); copy(errStream, outError); // Call the waiter in the current scope to prevent // the streams from closing too early on leaving the scope. int const result = waiter(c); if (!secure) { LOG_TRACE(logger, ">> Process \"{}\" finished with return value {}.", executable, result); } return result; } int StartProcess(std::string const& executable, std::vector const& args, std::string const& logger, std::string input_file, bool secure) { return CreateChildProcess([](child& c) -> int { try { c.wait(); return c.exit_code(); } catch (...) { return EXIT_FAILURE; } }, executable, args, logger, input_file, secure); } class AsyncProcessResultImplementation : public AsyncProcessResult { std::string const executable; std::vector const args; std::string const logger; std::string const input_file; bool const is_secure; std::atomic was_terminated; Optional>> result; Optional> my_child; public: explicit AsyncProcessResultImplementation(std::string executable_, std::vector args_, std::string logger_, std::string input_file_, bool secure) : executable(std::move(executable_)), args(std::move(args_)), logger(std::move(logger_)), input_file(input_file_), is_secure(secure), was_terminated(false) { } AsyncProcessResultImplementation(AsyncProcessResultImplementation const&) = delete; AsyncProcessResultImplementation& operator= (AsyncProcessResultImplementation const&) = delete; AsyncProcessResultImplementation(AsyncProcessResultImplementation&&) = delete; AsyncProcessResultImplementation& operator= (AsyncProcessResultImplementation&&) = delete; int StartProcess() { ASSERT(!my_child, "Process started already!"); return CreateChildProcess([&](child& c) -> int { int exitCode; my_child = std::reference_wrapper(c); try { c.wait(); exitCode = c.exit_code(); } catch (...) { exitCode = EXIT_FAILURE; } my_child.reset(); return was_terminated ? EXIT_FAILURE : exitCode; }, executable, args, logger, input_file, is_secure); } void SetFuture(std::future result_) { result = std::make_shared>(std::move(result_)); } /// Returns the future which contains the result of the process /// as soon it is finished. std::future& GetFutureResult() override { ASSERT(*result, "The process wasn't started!"); return **result; } /// Tries to terminate the process void Terminate() override { if (my_child) { was_terminated = true; try { my_child->get().terminate(); } catch (...) { // Do nothing } } } }; std::shared_ptr StartAsyncProcess(std::string executable, std::vector args, std::string logger, std::string input_file, bool secure) { auto handle = std::make_shared( std::move(executable), std::move(args), std::move(logger), std::move(input_file), secure); handle->SetFuture(std::async(std::launch::async, [handle] { return handle->StartProcess(); })); return handle; } std::string SearchExecutableInPath(std::string const& filename) { try { return search_path(filename).string(); } catch (...) { return ""; } } } // namespace Acore