TrinityCore
StartProcess.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "StartProcess.h"
19#include "Errors.h"
20#include "Log.h"
21#include "Optional.h"
22
23#include <boost/algorithm/string/join.hpp>
24#include <boost/iostreams/copy.hpp>
25#include <boost/process/args.hpp>
26#include <boost/process/child.hpp>
27#include <boost/process/env.hpp>
28#include <boost/process/exe.hpp>
29#include <boost/process/io.hpp>
30#include <boost/process/pipe.hpp>
31#include <boost/process/search_path.hpp>
32
33using namespace boost::process;
34using namespace boost::iostreams;
35
36namespace Trinity
37{
38
39template<typename T>
41{
43
44public:
45 typedef char char_type;
46 typedef sink_tag category;
47
48 // Requires a callback type which has a void(std::string) signature
49 TCLogSink(T callback)
50 : callback_(std::move(callback)) { }
51
52 std::streamsize write(char const* str, std::streamsize size)
53 {
54 std::string_view consoleStr(str, size);
55 size_t lineEnd = consoleStr.find_first_of("\r\n");
56 std::streamsize processedCharacters = size;
57 if (lineEnd != std::string_view::npos)
58 {
59 consoleStr = consoleStr.substr(0, lineEnd);
60 processedCharacters = lineEnd + 1;
61 }
62
63 if (!consoleStr.empty())
64 callback_(consoleStr);
65
66 return processedCharacters;
67 }
68};
69
70template<typename T>
71auto MakeTCLogSink(T&& callback)
73{
74 return { std::forward<T>(callback) };
75}
76
77template<typename T>
78static int CreateChildProcess(T waiter, std::string const& executable,
79 std::vector<std::string> const& argsVector,
80 std::string const& logger, std::string const& input,
81 bool secure)
82{
83#if TRINITY_COMPILER == TRINITY_COMPILER_MICROSOFT
84#pragma warning(push)
85#pragma warning(disable:4297)
86/*
87 Silence warning with boost 1.83
88
89 boost/process/pipe.hpp(132,5): warning C4297: 'boost::process::basic_pipebuf<char,std::char_traits<char>>::~basic_pipebuf': function assumed not to throw an exception but does
90 boost/process/pipe.hpp(132,5): message : destructor or deallocator has a (possibly implicit) non-throwing exception specification
91 boost/process/pipe.hpp(124,6): message : while compiling class template member function 'boost::process::basic_pipebuf<char,std::char_traits<char>>::~basic_pipebuf(void)'
92 boost/process/pipe.hpp(304,42): message : see reference to class template instantiation 'boost::process::basic_pipebuf<char,std::char_traits<char>>' being compiled
93*/
94#endif
95 ipstream outStream;
96 ipstream errStream;
97#if TRINITY_COMPILER == TRINITY_COMPILER_MICROSOFT
98#pragma warning(pop)
99#endif
100
101 if (!secure)
102 {
103 TC_LOG_TRACE(logger, "Starting process \"{}\" with arguments: \"{}\".",
104 executable, boost::algorithm::join(argsVector, " "));
105 }
106
107 // prepare file with only read permission (boost process opens with read_write)
108 std::shared_ptr<FILE> inputFile(!input.empty() ? fopen(input.c_str(), "rb") : nullptr, [](FILE* ptr)
109 {
110 if (ptr != nullptr)
111 fclose(ptr);
112 });
113
114 // Start the child process
115 child c = [&]()
116 {
117 if (inputFile)
118 {
119 // With binding stdin
120 return child{
121 exe = boost::filesystem::absolute(executable).string(),
122 args = argsVector,
123 env = environment(boost::this_process::environment()),
124 std_in = inputFile.get(),
125 std_out = outStream,
126 std_err = errStream
127 };
128 }
129 else
130 {
131 // Without binding stdin
132 return child{
133 exe = boost::filesystem::absolute(executable).string(),
134 args = argsVector,
135 env = environment(boost::this_process::environment()),
136 std_in = boost::process::close,
137 std_out = outStream,
138 std_err = errStream
139 };
140 }
141 }();
142
143 auto outInfo = MakeTCLogSink([&](std::string_view msg)
144 {
145 TC_LOG_INFO(logger, "{}", msg);
146 });
147
148 auto outError = MakeTCLogSink([&](std::string_view msg)
149 {
150 TC_LOG_ERROR(logger, "{}", msg);
151 });
152
153 copy(outStream, outInfo);
154 copy(errStream, outError);
155
156 // Call the waiter in the current scope to prevent
157 // the streams from closing too early on leaving the scope.
158 int const result = waiter(c);
159
160 if (!secure)
161 {
162 TC_LOG_TRACE(logger, ">> Process \"{}\" finished with return value {}.",
163 executable, result);
164 }
165
166 return result;
167}
168
169int StartProcess(std::string const& executable, std::vector<std::string> const& args,
170 std::string const& logger, std::string input_file, bool secure)
171{
172 return CreateChildProcess([](child& c) -> int
173 {
174 try
175 {
176 c.wait();
177 return c.exit_code();
178 }
179 catch (...)
180 {
181 return EXIT_FAILURE;
182 }
183 }, executable, args, logger, input_file, secure);
184}
185
187 : public AsyncProcessResult
188{
189 std::string const executable;
190 std::vector<std::string> const args;
191 std::string const logger;
192 std::string const input_file;
193 bool const is_secure;
194
195 std::atomic<bool> was_terminated;
196
197 // Workaround for missing move support in boost < 1.57
200
201public:
202 explicit AsyncProcessResultImplementation(std::string executable_, std::vector<std::string> args_,
203 std::string logger_, std::string input_file_,
204 bool secure)
205 : executable(std::move(executable_)), args(std::move(args_)),
206 logger(std::move(logger_)), input_file(input_file_),
207 is_secure(secure), was_terminated(false) { }
208
213
215 {
216 ASSERT(!my_child, "Process started already!");
217
218 return CreateChildProcess([&](child& c) -> int
219 {
220 int result;
221 my_child = std::reference_wrapper<child>(c);
222
223 try
224 {
225 c.wait();
226 result = c.exit_code();
227 }
228 catch (...)
229 {
230 result = EXIT_FAILURE;
231 }
232
233 my_child.reset();
234 return was_terminated ? EXIT_FAILURE : result;
235
237 }
238
239 void SetFuture(std::future<int> result_)
240 {
241 result = std::make_shared<std::future<int>>(std::move(result_));
242 }
243
246 std::future<int>& GetFutureResult() override
247 {
248 ASSERT(*result, "The process wasn't started!");
249 return **result;
250 }
251
253 void Terminate() override
254 {
255 if (my_child)
256 {
257 was_terminated = true;
258 try
259 {
260 my_child->get().terminate();
261 }
262 catch(...)
263 {
264 // Do nothing
265 }
266 }
267 }
268};
269
270std::shared_ptr<AsyncProcessResult>
271 StartAsyncProcess(std::string executable, std::vector<std::string> args,
272 std::string logger, std::string input_file, bool secure)
273{
274 auto handle = std::make_shared<AsyncProcessResultImplementation>(
275 std::move(executable), std::move(args), std::move(logger), std::move(input_file), secure);
276
277 handle->SetFuture(std::async(std::launch::async, [handle] { return handle->StartProcess(); }));
278 return handle;
279}
280
281std::string SearchExecutableInPath(std::string const& filename)
282{
283 try
284 {
285 return search_path(filename).string();
286 }
287 catch (...)
288 {
289 return "";
290 }
291}
292
293} // namespace Trinity
#define ASSERT
Definition: Errors.h:68
#define TC_LOG_TRACE(filterType__,...)
Definition: Log.h:153
#define TC_LOG_ERROR(filterType__,...)
Definition: Log.h:165
#define TC_LOG_INFO(filterType__,...)
Definition: Log.h:159
std::optional< T > Optional
Optional helper class to wrap optional values within.
Definition: Optional.h:25
Optional< std::reference_wrapper< child > > my_child
std::future< int > & GetFutureResult() override
AsyncProcessResultImplementation & operator=(AsyncProcessResultImplementation const &)=delete
std::vector< std::string > const args
Optional< std::shared_ptr< std::future< int > > > result
AsyncProcessResultImplementation(AsyncProcessResultImplementation &&)=delete
AsyncProcessResultImplementation(AsyncProcessResultImplementation const &)=delete
void SetFuture(std::future< int > result_)
void Terminate() override
Tries to terminate the process.
AsyncProcessResultImplementation(std::string executable_, std::vector< std::string > args_, std::string logger_, std::string input_file_, bool secure)
std::streamsize write(char const *str, std::streamsize size)
TCLogSink(T callback)
std::shared_ptr< AsyncProcessResult > StartAsyncProcess(std::string executable, std::vector< std::string > args, std::string logger, std::string input_file, bool secure)
std::string SearchExecutableInPath(std::string const &filename)
static int CreateChildProcess(T waiter, std::string const &executable, std::vector< std::string > const &argsVector, std::string const &logger, std::string const &input, bool secure)
int StartProcess(std::string const &executable, std::vector< std::string > const &args, std::string const &logger, std::string input_file, bool secure)
auto MakeTCLogSink(T &&callback) -> TCLogSink< typename std::decay< T >::type >
constexpr std::size_t size()
Definition: UpdateField.h:796
STL namespace.