CppCommon  1.0.4.1
C++ Common Library
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 
30 namespace CppCommon {
31 
33 
34 class Process::Impl
35 {
36 public:
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 
469 private:
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 
480 Process::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 
491 Process::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 
502 Process::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 
513 Process::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 
530 Process& Process::operator=(Process&& process) noexcept
531 {
532  Process(std::move(process)).swap(*this);
533  return *this;
534 }
535 
536 uint64_t Process::pid() const noexcept { return impl().pid(); }
537 
538 bool Process::IsRunning() const { return impl().IsRunning(); }
539 
540 void Process::Kill() { return impl().Kill(); }
541 int Process::Wait() { return impl().Wait(); }
542 int Process::WaitFor(const Timespan& timespan) { return impl().WaitFor(timespan); }
543 
544 uint64_t Process::CurrentProcessId() noexcept { return Impl::CurrentProcessId(); }
545 uint64_t Process::ParentProcessId() noexcept { return Impl::ParentProcessId(); }
546 
547 void Process::Exit(int result) { return Impl::Exit(result); }
548 
549 Process 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 
554 void Process::swap(Process& process) noexcept
555 {
556  using std::swap;
557  swap(_storage, process._storage);
558 }
559 
560 } // namespace CppCommon
static std::wstring FromUTF8(std::string_view str)
Convert UTF-8 encoded string to system wide-string.
Definition: encoding.cpp:33
Pipe.
Definition: pipe.h:31
Process abstraction.
Definition: process.h:38
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
static void Yield() noexcept
Yield to other threads.
Definition: thread.cpp:143
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.
Definition: token_bucket.h:15
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.