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