1 #include "Process.h"
2
3 #include "Logger.h"
4
5 #include <boost/algorithm/string/trim.hpp>
6
7 #include <stdexcept>
8
9 // assume Linux environment by default
10 #if (!defined(FREEORION_WIN32) && !defined(FREEORION_LINUX) && !defined(FREEORION_MACOSX))
11 #define FREEORION_LINUX
12 #endif
13
14 #ifdef FREEORION_WIN32
15 #include <GG/utf8/checked.h>
16 #define WIN32_LEAN_AND_MEAN
17 #include <windows.h>
18 #endif
19
20 class Process::Impl {
21 public:
22 Impl(const std::string& cmd, const std::vector<std::string>& argv);
23 ~Impl();
24
25 bool SetLowPriority(bool low);
26 bool Terminate();
27 void Kill();
28 void Free();
29
30 private:
31 bool m_free = false;
32 #if defined(FREEORION_WIN32)
33 STARTUPINFOW m_startup_info;
34 PROCESS_INFORMATION m_process_info;
35 #elif defined(FREEORION_LINUX) || defined(FREEORION_MACOSX)
36 pid_t m_process_id;
37 #endif
38 };
39
Process()40 Process::Process() :
41 m_empty(true)
42 {}
43
Process(const std::string & cmd,const std::vector<std::string> & argv)44 Process::Process(const std::string& cmd, const std::vector<std::string>& argv) :
45 m_impl(new Impl(cmd, argv)),
46 m_empty(false)
47 {}
48
SetLowPriority(bool low)49 bool Process::SetLowPriority(bool low) {
50 if (m_empty)
51 return false;
52
53 if (m_low_priority == low)
54 return true;
55
56 if (m_impl->SetLowPriority(low)) {
57 m_low_priority = low;
58 return true;
59 }
60
61 return false;
62 }
63
Kill()64 void Process::Kill() {
65 // Early exit if already killed.
66 if (!m_impl && m_empty && !m_low_priority)
67 return;
68
69 DebugLogger() << "Process::Kill";
70 if (m_impl) {
71 DebugLogger() << "Process::Kill calling m_impl->Kill()";
72 m_impl->Kill();
73 } else {
74 DebugLogger() << "Process::Kill found no m_impl";
75 }
76 DebugLogger() << "Process::Kill calling RequestTermination()";
77 RequestTermination();
78 }
79
Terminate()80 bool Process::Terminate() {
81 // Early exit if already killed.
82 if (!m_impl && m_empty && !m_low_priority)
83 return true;
84
85 bool result = true;
86 DebugLogger() << "Process::Terminate";
87 if (m_impl) {
88 DebugLogger() << "Process::Terminate calling m_impl->Terminate()";
89 result = m_impl->Terminate();
90 } else {
91 DebugLogger() << "Process::Terminate found no m_impl";
92 }
93 DebugLogger() << "Process::Terminate calling RequestTermination()";
94 RequestTermination();
95 return result;
96 }
97
RequestTermination()98 void Process::RequestTermination() {
99 m_impl.reset();
100 m_empty = true;
101 m_low_priority = false;
102 }
103
Free()104 void Process::Free() {
105 if (m_impl)
106 m_impl->Free();
107 }
108
109
110 #if defined(FREEORION_WIN32)
111
Impl(const std::string & cmd,const std::vector<std::string> & argv)112 Process::Impl::Impl(const std::string& cmd, const std::vector<std::string>& argv) {
113 std::wstring wcmd;
114 std::wstring wargs;
115
116 utf8::utf8to16(cmd.begin(), cmd.end(), std::back_inserter(wcmd));
117 for (unsigned int i = 0; i < argv.size(); ++i) {
118 utf8::utf8to16(argv[i].begin(), argv[i].end(), std::back_inserter(wargs));
119 if (i + 1 < argv.size())
120 wargs += ' ';
121 }
122
123 ZeroMemory(&m_startup_info, sizeof(STARTUPINFOW));
124 m_startup_info.cb = sizeof(STARTUPINFOW);
125 ZeroMemory(&m_process_info, sizeof(PROCESS_INFORMATION));
126
127 if (!CreateProcessW(wcmd.c_str(), const_cast<LPWSTR>(wargs.c_str()), 0, 0,
128 false, CREATE_NO_WINDOW, 0, 0, &m_startup_info, &m_process_info)) {
129 std::string err_str;
130 DWORD err = GetLastError();
131 DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM;
132 LPSTR buf;
133 if (FormatMessageA(flags, 0, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buf, 0, 0)) {
134 err_str += buf;
135 LocalFree(buf);
136 }
137 throw std::runtime_error("Process::Process : Failed to create child process. Windows error was: \"" + err_str + "\"");
138 }
139 WaitForInputIdle(m_process_info.hProcess, 1000); // wait for process to finish setting up, or for 1 sec, which ever comes first
140 }
141
~Impl()142 Process::Impl::~Impl()
143 { if (!m_free) Kill(); }
144
SetLowPriority(bool low)145 bool Process::Impl::SetLowPriority(bool low) {
146 if (low)
147 return (SetPriorityClass(m_process_info.hProcess, BELOW_NORMAL_PRIORITY_CLASS) != 0);
148 else
149 return (SetPriorityClass(m_process_info.hProcess, NORMAL_PRIORITY_CLASS) != 0);
150 }
151
Terminate()152 bool Process::Impl::Terminate() {
153 // ToDo: Use actual WinAPI termination.
154 Kill();
155 return true;
156 }
157
Kill()158 void Process::Impl::Kill() {
159 if (m_process_info.hProcess && !TerminateProcess(m_process_info.hProcess, 0)) {
160 std::string err_str;
161 DWORD err = GetLastError();
162 DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM;
163 LPSTR buf;
164 if (FormatMessageA(flags, 0, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buf, 0, 0)) {
165 err_str += buf;
166 LocalFree(buf);
167 }
168 boost::algorithm::trim(err_str);
169 ErrorLogger() << "Process::Impl::Kill : Error terminating process: " << err_str;
170 }
171
172 if (m_process_info.hProcess && !CloseHandle(m_process_info.hProcess)) {
173 std::string err_str;
174 DWORD err = GetLastError();
175 DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM;
176 LPSTR buf;
177 if (FormatMessageA(flags, 0, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buf, 0, 0)) {
178 err_str += buf;
179 LocalFree(buf);
180 }
181 boost::algorithm::trim(err_str);
182 ErrorLogger() << "Process::Impl::Kill : Error closing process handle: " << err_str;
183 }
184
185 if (m_process_info.hThread && !CloseHandle(m_process_info.hThread)) {
186 std::string err_str;
187 DWORD err = GetLastError();
188 DWORD flags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM;
189 LPSTR buf;
190 if (FormatMessageA(flags, 0, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buf, 0, 0)) {
191 err_str += buf;
192 LocalFree(buf);
193 }
194 boost::algorithm::trim(err_str);
195 ErrorLogger() << "Process::Impl::Kill : Error closing thread handle: " << err_str;
196 }
197
198 m_process_info.hProcess = 0;
199 m_process_info.hThread = 0;
200 }
201
202 #elif defined(FREEORION_LINUX) || defined(FREEORION_MACOSX)
203
204 #include <sys/types.h>
205 #include <sys/time.h>
206 #include <sys/resource.h>
207 #include <unistd.h>
208 #include <signal.h>
209 #include <cstdio>
210 #include <sys/wait.h>
211
212
Impl(const std::string & cmd,const std::vector<std::string> & argv)213 Process::Impl::Impl(const std::string& cmd, const std::vector<std::string>& argv) {
214 std::vector<char*> args;
215 for (unsigned int i = 0; i < argv.size(); ++i) {
216 args.push_back(const_cast<char*>(&(const_cast<std::string&>(argv[i])[0])));
217 }
218 args.push_back(nullptr);
219
220 switch (m_process_id = fork()) {
221 case -1: { // error
222 throw std::runtime_error("Process::Process : Failed to fork a new process.");
223 break;
224 }
225
226 case 0: { // child process side of fork
227 execv(cmd.c_str(), &args[0]);
228 perror(("execv failed: " + cmd).c_str());
229 break;
230 }
231
232 default:
233 break;
234 }
235 }
236
~Impl()237 Process::Impl::~Impl()
238 { if (!m_free) Kill(); }
239
SetLowPriority(bool low)240 bool Process::Impl::SetLowPriority(bool low) {
241 if (low)
242 return (setpriority(PRIO_PROCESS, m_process_id, 10) == 0);
243 else
244 return (setpriority(PRIO_PROCESS, m_process_id, 0) == 0);
245 }
246
Terminate()247 bool Process::Impl::Terminate() {
248 if (m_free) {
249 DebugLogger() << "Process::Impl::Terminate called but m_free is true so returning with no action";
250 return true;
251 }
252 int status = -1;
253 DebugLogger() << "Process::Impl::Terminate calling kill(m_process_id, SIGINT)";
254 kill(m_process_id, SIGINT);
255 DebugLogger() << "Process::Impl::Terminate calling waitpid(m_process_id, &status, 0)";
256 waitpid(m_process_id, &status, 0);
257 DebugLogger() << "Process::Impl::Terminate done";
258 if (status != 0) {
259 WarnLogger() << "Process::Impl::Terminate got failure status " << status;
260 return false;
261 }
262 return true;
263 }
264
Kill()265 void Process::Impl::Kill() {
266 if (m_free) {
267 DebugLogger() << "Process::Impl::Kill called but m_free is true so returning with no action";
268 return;
269 }
270 int status;
271 DebugLogger() << "Process::Impl::Kill calling kill(m_process_id, SIGKILL)";
272 kill(m_process_id, SIGKILL);
273 DebugLogger() << "Process::Impl::Kill calling waitpid(m_process_id, &status, 0)";
274 waitpid(m_process_id, &status, 0);
275 DebugLogger() << "Process::Impl::Kill done";
276 }
277
278 #endif
279
Free()280 void Process::Impl::Free()
281 { m_free = true; }
282