CppCommon 1.0.5.0
C++ Common Library
Loading...
Searching...
No Matches
process.cpp
Go to the documentation of this file.
1
9#include "system/process.h"
10
11#include "errors/fatal.h"
12#include "string/encoding.h"
13#include "string/format.h"
14#include "threads/thread.h"
15#include "utility/resource.h"
17
18#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
19#include <sys/wait.h>
20#include <signal.h>
21#include <stdlib.h>
22#include <unistd.h>
23#elif defined(_WIN32) || defined(_WIN64)
24#include <windows.h>
25#include <tlhelp32.h>
26#undef max
27#undef min
28#endif
29
30namespace CppCommon {
31
33
34class Process::Impl
35{
36public:
37 Impl()
38 {
39#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
40 _pid = (pid_t)-1;
41#elif defined(_WIN32) || defined(_WIN64)
42 _pid = (DWORD)-1;
43 _process = nullptr;
44#endif
45 }
46
47 Impl(uint64_t pid)
48 {
49#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
50 _pid = (pid_t)pid;
51#elif defined(_WIN32) || defined(_WIN64)
52 _pid = (DWORD)pid;
53 _process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE | SYNCHRONIZE, FALSE, _pid);
54 if (_process == nullptr)
55 throwex SystemException(format("Failed to open a process with Id {}!", pid));
56#endif
57 }
58
59 ~Impl()
60 {
61#if defined(_WIN32) || defined(_WIN64)
62 if (_process != nullptr)
63 {
64 if (!CloseHandle(_process))
65 fatality(SystemException(format("Failed to close a process with Id {}!", pid())));
66 _process = nullptr;
67 }
68#endif
69 }
70
71 uint64_t pid() const noexcept
72 {
73 return (uint64_t)_pid;
74 }
75
76 bool IsRunning() const
77 {
78#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
79 return (kill(_pid, 0) == 0);
80#elif defined(_WIN32) || defined(_WIN64)
81 if (_process == nullptr)
82 throwex SystemException(format("Failed to get exit code for a process with Id {}!", pid()));
83
84 DWORD dwExitCode;
85 if (!GetExitCodeProcess(_process, &dwExitCode))
86 throwex SystemException(format("Failed to get exit code for a process with Id {}!", pid()));
87
88 return (dwExitCode == STILL_ACTIVE);
89#endif
90 }
91
92 void Kill()
93 {
94#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
95 int result = kill(_pid, SIGKILL);
96 if (result != 0)
97 throwex SystemException(format("Failed to kill a process with Id {}!", pid()));
98#elif defined(_WIN32) || defined(_WIN64)
99 if (_process == nullptr)
100 throwex SystemException(format("Failed to kill a process with Id {}!", pid()));
101
102 if (!TerminateProcess(_process, 666))
103 throwex SystemException(format("Failed to kill a process with Id {}!", pid()));
104#endif
105 }
106
107 int Wait()
108 {
109#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
110 int status;
111 pid_t result;
112
113 do
114 {
115 result = waitpid(_pid, &status, 0);
116 }
117 while ((result < 0) && (errno == EINTR));
118
119 if (result == -1)
120 throwex SystemException(format("Failed to wait for a process with Id {}!", pid()));
121
122 if (WIFEXITED(status))
123 return WEXITSTATUS(status);
124 else if (WIFSIGNALED(status))
125 throwex SystemException(format("Process with Id {} was killed by signal {}!", pid(), WTERMSIG(status)));
126 else if (WIFSTOPPED(status))
127 throwex SystemException(format("Process with Id {} was stopped by signal {}!", pid(), WSTOPSIG(status)));
128 else if (WIFCONTINUED(status))
129 throwex SystemException(format("Process with Id {} was continued by signal SIGCONT!", pid()));
130 else
131 throwex SystemException(format("Process with Id {} has unknown wait status!", pid()));
132#elif defined(_WIN32) || defined(_WIN64)
133 if (_process == nullptr)
134 throwex SystemException(format("Failed to wait for a process with Id {}!", pid()));
135
136 DWORD result = WaitForSingleObject(_process, INFINITE);
137 if (result != WAIT_OBJECT_0)
138 throwex SystemException(format("Failed to wait for a process with Id {}!", pid()));
139
140 DWORD dwExitCode;
141 if (!GetExitCodeProcess(_process, &dwExitCode))
142 throwex SystemException(format("Failed to get exit code for a process with Id {}!", pid()));
143
144 return (int)dwExitCode;
145#endif
146 }
147
148 int WaitFor(const Timespan& timespan)
149 {
150#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
151 int status;
152 pid_t result;
153
154 do
155 {
156 result = waitpid(_pid, &status, WNOHANG);
157 if (result == 0)
158 Thread::Yield();
159 }
160 while ((result == 0) || ((result < 0) && (errno == EINTR)));
161
162 if (result == -1)
163 throwex SystemException(format("Failed to wait for a process with Id {}!", pid()));
164
165 if (WIFEXITED(status))
166 return WEXITSTATUS(status);
167 else if (WIFSIGNALED(status))
168 throwex SystemException(format("Process with Id {} was killed by signal {}!", pid(), WTERMSIG(status)));
169 else if (WIFSTOPPED(status))
170 throwex SystemException(format("Process with Id {} was stopped by signal {}!", pid(), WSTOPSIG(status)));
171 else if (WIFCONTINUED(status))
172 throwex SystemException(format("Process with Id {} was continued by signal SIGCONT!", pid()));
173 else
174 throwex SystemException(format("Process with Id {} has unknown wait status!", pid()));
175#elif defined(_WIN32) || defined(_WIN64)
176 if (_process == nullptr)
177 throwex SystemException(format("Failed to wait for a process with Id {}!", pid()));
178
179 DWORD result = WaitForSingleObject(_process, std::max((DWORD)1, (DWORD)timespan.milliseconds()));
180 if (result != WAIT_OBJECT_0)
181 return std::numeric_limits<int>::min();
182
183 DWORD dwExitCode;
184 if (!GetExitCodeProcess(_process, &dwExitCode))
185 throwex SystemException(format("Failed to get exit code for a process with Id {}!", pid()));
186
187 return (int)dwExitCode;
188#endif
189 }
190
191 static uint64_t CurrentProcessId() noexcept
192 {
193#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
194 return (uint64_t)getpid();
195#elif defined(_WIN32) || defined(_WIN64)
196 return (uint64_t)GetCurrentProcessId();
197#endif
198 }
199
200 static uint64_t ParentProcessId() noexcept
201 {
202#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
203 return (uint64_t)getppid();
204#elif defined(_WIN32) || defined(_WIN64)
205 DWORD current = GetCurrentProcessId();
206
207 // Takes a snapshot of the specified processes
208 PROCESSENTRY32 pe = { 0 };
209 pe.dwSize = sizeof(PROCESSENTRY32);
210 HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
211 if (hSnapshot == INVALID_HANDLE_VALUE)
212 return (uint64_t)-1;
213
214 // Smart resource cleaner pattern
215 auto snapshot = resource(hSnapshot, [](HANDLE hObject) { CloseHandle(hObject); });
216
217 // Retrieves information about all processes encountered in a system snapshot
218 if (Process32First(snapshot.get(), &pe))
219 {
220 do
221 {
222 if (pe.th32ProcessID == current)
223 return (uint64_t)pe.th32ParentProcessID;
224 } while(Process32Next(snapshot.get(), &pe));
225 }
226
227 return (uint64_t)-1;
228#endif
229 }
230
231 static void Exit(int result)
232 {
233#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
234 exit(result);
235#elif defined(_WIN32) || defined(_WIN64)
236 ExitProcess((UINT)result);
237#endif
238 }
239
240 static Process Execute(const std::string& command, const std::vector<std::string>* arguments, const std::map<std::string, std::string>* envars, const std::string* directory, Pipe* input, Pipe* output, Pipe* error)
241 {
242#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
243 // Prepare arguments
244 size_t index = 0;
245 std::vector<char*> argv(1 + ((arguments != nullptr) ? arguments->size() : 0) + 1);
246 argv[index++] = (char*)command.c_str();
247 if (arguments != nullptr)
248 {
249 for (const auto& argument : *arguments)
250 {
251 argv[index++] = (char*)argument.c_str();
252 }
253 }
254 argv[index++] = nullptr;
255
256 // Prepare environment variables
257 std::vector<char> environment = PrepareEnvars(envars);
258
259 // Fork the current process
260 pid_t pid = fork();
261 if (pid < 0)
262 throwex SystemException("Failed to fork the current process!");
263 else if (pid == 0)
264 {
265 // Set environment variables of the new process
266 if (!environment.empty())
267 {
268 char* envar = environment.data();
269 while (*envar != '\0')
270 {
271 putenv(envar);
272 while (*envar != '\0')
273 ++envar;
274 ++envar;
275 }
276 }
277
278 // Change the current directory of the new process
279 if (directory != nullptr)
280 {
281 int result = chdir(directory->c_str());
282 if (result != 0)
283 _exit(666);
284 }
285
286 // Prepare input communication pipe
287 if (input != nullptr)
288 dup2((int)(size_t)input->reader(), STDIN_FILENO);
289
290 // Prepare output communication pipe
291 if (output != nullptr)
292 dup2((int)(size_t)output->writer(), STDOUT_FILENO);
293
294 // Prepare error communication pipe
295 if (error != nullptr)
296 dup2((int)(size_t)error->writer(), STDERR_FILENO);
297
298 // Close pipes endpoints
299 if (input != nullptr)
300 input->Close();
301 if (output != nullptr)
302 output->Close();
303 if (error != nullptr)
304 error->Close();
305
306 // Close all open file descriptors other than stdin, stdout, stderr
307 for (int i = 3; i < sysconf(_SC_OPEN_MAX); ++i)
308 close(i);
309
310 // Execute a new process image
311 execvp(argv[0], argv.data());
312
313 // Get here only if error occurred during image execution
314 _exit(666);
315 }
316
317 // Close pipes endpoints
318 if (input != nullptr)
319 input->CloseRead();
320 if (output != nullptr)
321 output->CloseWrite();
322 if (error != nullptr)
323 error->CloseWrite();
324
325 // Return result process
326 Process result;
327 result.impl()._pid = pid;
328 return result;
329#elif defined(_WIN32) || defined(_WIN64)
330 BOOL bInheritHandles = FALSE;
331
332 // Prepare command line
333 std::wstring command_line = Encoding::FromUTF8(command);
334 if (arguments != nullptr)
335 {
336 for (const auto& argument : *arguments)
337 {
338 command_line.append(L" ");
339 command_line.append(Encoding::FromUTF8(argument));
340 }
341 }
342
343 // Prepare environment variables
344 std::vector<wchar_t> environment = PrepareEnvars(envars);
345
346 // Fill process startup information
347 STARTUPINFOW si;
348 GetStartupInfoW(&si);
349 si.cb = sizeof(STARTUPINFOW);
350 si.lpReserved = nullptr;
351 si.lpDesktop = nullptr;
352 si.lpTitle = nullptr;
353 si.dwFlags = STARTF_FORCEOFFFEEDBACK;
354 si.cbReserved2 = 0;
355 si.lpReserved2 = nullptr;
356
357 // Get the current process handle
358 HANDLE hCurrentProcess = GetCurrentProcess();
359
360 // Prepare input communication pipe
361 if (input != nullptr)
362 DuplicateHandle(hCurrentProcess, input->reader(), hCurrentProcess, &si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);
363 else
364 {
365 HANDLE hStdHandle = GetStdHandle(STD_INPUT_HANDLE);
366 if (hStdHandle != nullptr)
367 DuplicateHandle(hCurrentProcess, hStdHandle, hCurrentProcess, &si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);
368 else
369 si.hStdInput = nullptr;
370 }
371
372 // Prepare output communication pipe
373 if (output != nullptr)
374 DuplicateHandle(hCurrentProcess, output->writer(), hCurrentProcess, &si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);
375 else
376 {
377 HANDLE hStdHandle = GetStdHandle(STD_OUTPUT_HANDLE);
378 if (hStdHandle != nullptr)
379 DuplicateHandle(hCurrentProcess, hStdHandle, hCurrentProcess, &si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);
380 else
381 si.hStdOutput = nullptr;
382 }
383
384 // Prepare error communication pipe
385 if (error != nullptr)
386 DuplicateHandle(hCurrentProcess, error->writer(), hCurrentProcess, &si.hStdError, 0, TRUE, DUPLICATE_SAME_ACCESS);
387 else
388 {
389 HANDLE hStdHandle = GetStdHandle(STD_ERROR_HANDLE);
390 if (hStdHandle != nullptr)
391 DuplicateHandle(hCurrentProcess, hStdHandle, hCurrentProcess, &si.hStdError, 0, TRUE, DUPLICATE_SAME_ACCESS);
392 else
393 si.hStdError = nullptr;
394 }
395
396 // Close pipes endpoints
397 if (input != nullptr)
398 input->CloseRead();
399 if (output != nullptr)
400 output->CloseWrite();
401 if (error != nullptr)
402 error->CloseWrite();
403
404 // Override standard communication pipes of the process
405 if ((si.hStdInput != nullptr) || (si.hStdOutput != nullptr) || (si.hStdError != nullptr))
406 {
407 bInheritHandles = TRUE;
408 si.dwFlags |= STARTF_USESTDHANDLES;
409 }
410
411 // Create a new process
412 PROCESS_INFORMATION pi;
413 if (!CreateProcessW(nullptr, (wchar_t*)command_line.c_str(), nullptr, nullptr, bInheritHandles, CREATE_UNICODE_ENVIRONMENT, environment.empty() ? nullptr : (LPVOID)environment.data(), (directory == nullptr) ? nullptr : Encoding::FromUTF8(*directory).c_str(), &si, &pi))
414 throwex SystemException(CppCommon::format("Failed to execute a new process with command '{}'!", command));
415
416 // Close standard handles
417 if (si.hStdInput != nullptr)
418 CloseHandle(si.hStdInput);
419 if (si.hStdOutput != nullptr)
420 CloseHandle(si.hStdOutput);
421 if (si.hStdError != nullptr)
422 CloseHandle(si.hStdError);
423
424 // Close thread handle
425 CloseHandle(pi.hThread);
426
427 // Return result process
428 Process result;
429 result.impl()._pid = pi.dwProcessId;
430 result.impl()._process = pi.hProcess;
431 return result;
432#endif
433 }
434
435#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
436 static std::vector<char> PrepareEnvars(const std::map<std::string, std::string>* envars)
437#elif defined(_WIN32) || defined(_WIN64)
438 static std::vector<wchar_t> PrepareEnvars(const std::map<std::string, std::string>* envars)
439#endif
440 {
441#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
442 std::vector<char> result;
443#elif defined(_WIN32) || defined(_WIN64)
444 std::vector<wchar_t> result;
445#endif
446
447 if (envars == nullptr)
448 return result;
449
450 for (const auto& envar : *envars)
451 {
452#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
453 std::string key = envar.first;
454 std::string value = envar.second;
455#elif defined(_WIN32) || defined(_WIN64)
456 std::wstring key = Encoding::FromUTF8(envar.first);
457 std::wstring value = Encoding::FromUTF8(envar.second);
458#endif
459 result.insert(result.end(), key.begin(), key.end());
460 result.insert(result.end(), '=');
461 result.insert(result.end(), value.begin(), value.end());
462 result.insert(result.end(), '\0');
463 }
464 result.insert(result.end(), '\0');
465
466 return result;
467 }
468
469private:
470#if defined(unix) || defined(__unix) || defined(__unix__) || defined(__APPLE__)
471 pid_t _pid;
472#elif defined(_WIN32) || defined(_WIN64)
473 DWORD _pid;
474 HANDLE _process;
475#endif
476};
477
479
480Process::Process()
481{
482 // Check implementation storage parameters
483 [[maybe_unused]] ValidateAlignedStorage<sizeof(Impl), alignof(Impl), StorageSize, StorageAlign> _;
484 static_assert((StorageSize >= sizeof(Impl)), "Process::StorageSize must be increased!");
485 static_assert(((StorageAlign % alignof(Impl)) == 0), "Process::StorageAlign must be adjusted!");
486
487 // Create the implementation instance
488 new(&_storage)Impl();
489}
490
491Process::Process(uint64_t id)
492{
493 // Check implementation storage parameters
494 [[maybe_unused]] ValidateAlignedStorage<sizeof(Impl), alignof(Impl), StorageSize, StorageAlign> _;
495 static_assert((StorageSize >= sizeof(Impl)), "Process::StorageSize must be increased!");
496 static_assert(((StorageAlign % alignof(Impl)) == 0), "Process::StorageAlign must be adjusted!");
497
498 // Create the implementation instance
499 new(&_storage)Impl(id);
500}
501
502Process::Process(const Process& process)
503{
504 // Check implementation storage parameters
505 [[maybe_unused]] ValidateAlignedStorage<sizeof(Impl), alignof(Impl), StorageSize, StorageAlign> _;
506 static_assert((StorageSize >= sizeof(Impl)), "Process::StorageSize must be increased!");
507 static_assert(((StorageAlign % alignof(Impl)) == 0), "Process::StorageAlign must be adjusted!");
508
509 // Create the implementation instance
510 new(&_storage)Impl(process.pid());
511}
512
513Process::Process(Process&& process) noexcept : Process()
514{
515 process.swap(*this);
516}
517
519{
520 // Delete the implementation instance
521 reinterpret_cast<Impl*>(&_storage)->~Impl();
522}
523
525{
526 Process(process).swap(*this);
527 return *this;
528}
529
531{
532 Process(std::move(process)).swap(*this);
533 return *this;
534}
535
536uint64_t Process::pid() const noexcept { return impl().pid(); }
537
538bool Process::IsRunning() const { return impl().IsRunning(); }
539
540void Process::Kill() { return impl().Kill(); }
541int Process::Wait() { return impl().Wait(); }
542int Process::WaitFor(const Timespan& timespan) { return impl().WaitFor(timespan); }
543
544uint64_t Process::CurrentProcessId() noexcept { return Impl::CurrentProcessId(); }
545uint64_t Process::ParentProcessId() noexcept { return Impl::ParentProcessId(); }
546
547void Process::Exit(int result) { return Impl::Exit(result); }
548
549Process Process::Execute(const std::string& command, const std::vector<std::string>* arguments, const std::map<std::string, std::string>* envars, const std::string* directory, Pipe* input, Pipe* output, Pipe* error)
550{
551 return Impl::Execute(command, arguments, envars, directory, input, output, error);
552}
553
554void Process::swap(Process& process) noexcept
555{
556 using std::swap;
557 swap(_storage, process._storage);
558}
559
560} // namespace CppCommon
Pipe.
Definition pipe.h:31
Process abstraction.
Definition process.h:38
friend void swap(Process &process1, Process &process2) noexcept
Definition process.inl:21
static Process Execute(const std::string &command, const std::vector< std::string > *arguments=nullptr, const std::map< std::string, std::string > *envars=nullptr, const std::string *directory=nullptr, Pipe *input=nullptr, Pipe *output=nullptr, Pipe *error=nullptr)
Execute a new process.
Definition process.cpp:549
void Kill()
Kill the process.
Definition process.cpp:540
static uint64_t ParentProcessId() noexcept
Get the parent process Id.
Definition process.cpp:545
int Wait()
Wait the process to exit.
Definition process.cpp:541
bool IsRunning() const
Is the process is running?
Definition process.cpp:538
static uint64_t CurrentProcessId() noexcept
Get the current process Id.
Definition process.cpp:544
void swap(Process &process) noexcept
Swap two instances.
Definition process.cpp:554
Process & operator=(const Process &process)
Definition process.cpp:524
static void Exit(int result)
Exit the current process.
Definition process.cpp:547
uint64_t pid() const noexcept
Get the process Id.
Definition process.cpp:536
int WaitFor(const Timespan &timespan)
Wait the process to exit for the given timespan.
Definition process.cpp:542
Encoding utilities definition.
#define throwex
Throw extended exception macro.
Definition exceptions.h:23
Fatal abort execution definition.
#define fatality(...)
Fatal abort execution extended macro.
Definition fatal.h:22
Format string definition.
C++ Common project definitions.
std::string format(fmt::format_string< T... > pattern, T &&... args)
Format string.
Definition format.inl:12
auto resource(T handle, TCleaner cleaner)
Resource smart cleaner pattern.
Definition resource.h:43
void swap(FileCache &cache1, FileCache &cache2) noexcept
Definition filecache.inl:23
Process definition.
Resource smart cleaner pattern definition.
Thread definition.
Aligned storage validator definition.