DDS  ver. 2.0
Process.h
Go to the documentation of this file.
1 // Copyright 2014 GSI, Inc. All rights reserved.
2 //
3 // This header contains a subset of helpers for Process, Daemon and PID file operations.
4 //
5 #ifndef PROCESS_H_
6 #define PROCESS_H_
7 
8 // API
9 #include <dirent.h>
10 #include <fcntl.h>
11 #include <signal.h>
12 #include <sys/stat.h>
13 #include <sys/types.h>
14 #include <sys/wait.h>
15 #include <unistd.h>
16 #include <wordexp.h>
17 
18 #if defined(__APPLE__)
19 #include <sys/sysctl.h>
20 #endif
21 
22 // STD
23 #include <fstream>
24 #include <iterator>
25 #include <memory>
26 #include <stdexcept>
27 // POSIX regexp
28 #include <regex.h>
29 // MiscCommon
30 #include "CustomIterator.h"
31 #include "ErrorCode.h"
32 #include "MiscUtils.h"
33 #include "SysHelper.h"
34 #include "def.h"
35 #include "stlx.h"
36 // BOOST
37 #include <boost/asio.hpp>
38 #pragma clang diagnostic push
39 #pragma clang diagnostic ignored "-Wunused-variable"
40 #include <boost/process.hpp>
41 #pragma clang diagnostic pop
42 
43 namespace bp = boost::process;
44 namespace bio = boost::asio;
45 
46 namespace MiscCommon
47 {
56  inline bool IsProcessRunning(pid_t _PID)
57  {
58  return !(::kill(_PID, 0) == -1 && errno == ESRCH);
59  }
65  class CPIDFile
66  {
67  public:
68  CPIDFile(const std::string& _FileName, pid_t _PID)
69  : m_FileName(_FileName)
70  {
71  if (!_FileName.empty() && _PID > 0)
72  {
73  // Preventing to start a second "instance" if the pidfile references to the running process
74  const pid_t pid = GetPIDFromFile(m_FileName);
75  if (pid > 0 && IsProcessRunning(pid))
76  {
77  // We don't want to unlink this file
78  m_FileName.clear();
79  throw std::runtime_error("Error creating pidfile. The process corresponding to pidfile \"" +
80  _FileName + "\" is still running");
81  }
82 
83  // Wrtiting new pidfile
84  std::ofstream f(m_FileName.c_str());
85  if (!f.is_open())
86  throw std::runtime_error("can't create PID file: " + m_FileName);
87 
88  f << _PID;
89  }
90  else
91  m_FileName.clear();
92  }
93 
95  {
96  if (!m_FileName.empty())
97  ::unlink(m_FileName.c_str());
98  }
99 
100  static pid_t GetPIDFromFile(const std::string& _FileName)
101  {
102  std::ifstream f(_FileName.c_str());
103  if (!f.is_open())
104  return 0;
105 
106  pid_t pid(0);
107  f >> pid;
108 
109  return pid;
110  }
111 
112  private:
113  std::string m_FileName;
114  };
115 
116 #if defined(__APPLE__)
117 
124  class CFindProcess
125  {
126  public:
127  typedef std::set<pid_t> ProcContainer_t;
128 
129  public:
130  static void getAllPIDsForProcessName(const std::string& _processName,
131  ProcContainer_t* _pidContainer,
132  bool _filterForRealUserID = false)
133  {
134  // Setting up the mib (Management Information Base)
135  // We pass CTL_KERN, KERN_PROC, KERN_PROC_ALL to sysctl as the MIB
136  // to get back a BSD structure with all BSD process information for
137  // all processes in it (including BSD process names)
138  int mib[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
139 
140  size_t buffSize = 0; // set to zero to start with.
141  int error = 0;
142  struct kinfo_proc* BSDProcInfo = NULL;
143 
144  if (_processName.empty())
145  return;
146  if (!_pidContainer)
147  return;
148 
149  _pidContainer->clear();
150 
151  // Getting list of process information for all processes
152  bool done(false);
153  do
154  {
155  error = sysctl(mib, 3, NULL, &buffSize, NULL, 0);
156  if (error != 0)
157  throw system_error("Error occurred while retrieving a running processes list");
158 
159  BSDProcInfo = (struct kinfo_proc*)malloc(buffSize);
160 
161  if (BSDProcInfo == NULL)
162  throw system_error(
163  "Error occurred while retrieving a running processes list. Unable to allocate the buffer.");
164 
165  error = sysctl(mib, 3, BSDProcInfo, &buffSize, NULL, 0);
166 
167  // Here we successfully got the process information.
168  // Thus set the variable to end this sysctl calling loop
169  if (error == 0)
170  {
171  done = true;
172  }
173  else
174  {
175  // failed getting process information we will try again next time around the loop. Note this is
176  // caused
177  // by the fact the process list changed between getting the size of the buffer and actually filling
178  // the buffer (something which will happen from time to time since the process list is dynamic).
179  // Anyways, the attempted sysctl call failed. We will now begin again by freeing up the allocated
180  // buffer and starting again at the beginning of the loop.
181  free(BSDProcInfo);
182  }
183  } while (!done);
184 
185  // Going through process list looking for processes with matching names
186 
187  uid_t userid = getuid();
188 
189  const size_t processCount = buffSize / sizeof(struct kinfo_proc);
190  for (size_t i = 0; i < processCount; ++i)
191  {
192  // Getting PID of process we are examining
193  const pid_t pid = BSDProcInfo[i].kp_proc.p_pid;
194  // Getting name of process we are examining
195  const std::string name = BSDProcInfo[i].kp_proc.p_comm;
196  // Getting real user if of the process
197  const uid_t uid = BSDProcInfo[i].kp_eproc.e_pcred.p_ruid;
198 
199  if ((pid > 0) && (name == _processName))
200  {
201  if (!_filterForRealUserID)
202  _pidContainer->insert(pid);
203  else if (uid == userid)
204  _pidContainer->insert(pid);
205  }
206  }
207 
208  free(BSDProcInfo);
209  }
210 
211  static bool pidExists(int _pid)
212  {
213  int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
214  size_t length = 0;
215  if (sysctl(name, 4, NULL, &length, NULL, 0))
216  throw system_error("Unable to call sysctl in the pidExists function.");
217  kinfo_proc* result = (kinfo_proc*)malloc(length);
218  if (sysctl(name, 4, result, &length, NULL, 0))
219  throw system_error("Unable to call sysctl in the pidExists function.");
220  int i, procCount = length / sizeof(kinfo_proc);
221  for (i = 0; i < procCount; ++i)
222  {
223  kinfo_proc* test = &result[i];
224 
225  if (test->kp_proc.p_pid == _pid)
226  {
227  free(result);
228  return true;
229  }
230  test = NULL;
231  }
232 
233  free(result);
234  return false;
235  }
236  };
237 #endif
238 
250 #if !defined(__APPLE__)
251  class CProcList
252  {
253  public:
254  typedef std::set<pid_t> ProcContainer_t;
255 
256  public:
257  static void GetProcList(ProcContainer_t* _Procs)
258  {
259  if (!_Procs)
260  throw std::invalid_argument("CProcList::GetProcList: Input container is NULL");
261 
262  _Procs->clear();
263 
264  struct dirent** namelist;
265  // scanning the "/proc" filesystem
266  int n = scandir("/proc", &namelist, CheckDigit, alphasort);
267 
268  if (-1 == n)
269  throw system_error("CProcList::GetProcList exception");
270  if (0 == n)
271  return; // there were no files
272 
273  for (int i = 0; i < n; ++i)
274  {
275  std::stringstream ss(namelist[i]->d_name);
276  pid_t pid;
277  ss >> pid;
278  _Procs->insert(pid);
279  free(namelist[i]);
280  }
281 
282  free(namelist);
283  }
284 
285  private:
286  static int CheckDigit(const struct dirent* _d)
287  {
288  const std::string sName(_d->d_name);
289  // Checking whether file name has all digits
290  return (sName.end() == std::find_if(sName.begin(), sName.end(), std::not1(IsDigit())));
291  }
292  };
293 #endif
294 
295 #if !defined(__APPLE__)
296 
311  // TODO: need a new algorithms for a longer app names retrieval
313  {
314  typedef std::shared_ptr<std::ifstream> ifstream_ptr;
315  typedef std::map<std::string, std::string> keyvalue_t;
316 
317  public:
319  {
320  // Preparing regular expression pattern
321  regcomp(&m_re, "(.*):(.*)", REG_EXTENDED);
322  }
324  {
325  regfree(&m_re);
326  }
327  void Open(pid_t _PId)
328  {
329  m_values.clear();
330  if (m_f.get())
331  m_f->close();
332 
333  std::stringstream ss;
334  ss << "/proc/" << _PId << "/status";
335  m_f = ifstream_ptr(new std::ifstream(ss.str().c_str()));
336  // create reader objects
337  // HACK: the extra set of parenthesis (the last argument of vector's ctor) is required (for gcc 4.1+)
338  // StringVector_t vec( custom_istream_iterator<std::string>(*m_f),
339  // (custom_istream_iterator<std::string>()) );
340  // or
341  // custom_istream_iterator<std::string> in_begin(*m_f);
342  // custom_istream_iterator<std::string> in_end;
343  // StringVector_t vec( in_begin, in_end );
344  // the last method for gcc 3.2+
345  // , because
346  // the compiler is very aggressive in identifying function declarations and will identify the
347  // definition of vec as forward declaration of a function accepting two istream_iterator parameters
348  // and returning a vector of integers
351  StringVector_t vec(in_begin, in_end);
352 
353  for_each(vec.begin(), vec.end(), std::bind1st(MiscCommon::stlx::mem_fun(&CProcStatus::_Parser), this));
354  }
355  std::string GetValue(const std::string& _KeyName) const
356  {
357  // We want to be case insensitive
358  std::string sKey(_KeyName);
359  to_lower(sKey);
360 
361  keyvalue_t::const_iterator iter = m_values.find(sKey);
362  return (m_values.end() == iter ? std::string() : iter->second);
363  }
364 
365  private:
366  bool _Parser(const std::string& _sVal)
367  {
368  regmatch_t PMatch[3];
369  if (0 != regexec(&m_re, _sVal.c_str(), 3, PMatch, 0))
370  return false;
371  std::string sKey(_sVal.c_str() + PMatch[1].rm_so, PMatch[1].rm_eo - PMatch[1].rm_so);
372  std::string sValue(_sVal.c_str() + PMatch[2].rm_so, PMatch[2].rm_eo - PMatch[2].rm_so);
373  // We want to be case insensitive
374  to_lower(sKey);
375 
376  trim<std::string>(&sValue, '\t');
377  trim<std::string>(&sValue, ' ');
378  // insert key-value if found
379  m_values.insert(keyvalue_t::value_type(sKey, sValue));
380  return true;
381  }
382 
383  private:
384  ifstream_ptr m_f;
385  regex_t m_re;
386  keyvalue_t m_values;
387  };
388 #endif
389 
393 #if !defined(__APPLE__)
394  struct SFindName : public std::binary_function<CProcList::ProcContainer_t::value_type, std::string, bool>
395  {
396  bool operator()(CProcList::ProcContainer_t::value_type _pid, const std::string& _Name) const
397  {
398  CProcStatus p;
399  p.Open(_pid);
400  return (p.GetValue("Name") == _Name);
401  }
402  };
403 #endif
404 
408  typedef std::vector<pid_t> vectorPid_t;
409 
410  inline vectorPid_t getprocbyname(const std::string& _Srv, bool _filterForRealUserID = false)
411  {
412  vectorPid_t retVal;
413 #if defined(__APPLE__)
414  CFindProcess::ProcContainer_t container;
415  CFindProcess::getAllPIDsForProcessName(_Srv, &container, _filterForRealUserID);
416  copy(container.begin(), container.end(), std::back_inserter(retVal));
417 #else
419  CProcList::GetProcList(&pids);
420 
421  CProcList::ProcContainer_t::const_iterator iter = pids.begin();
422  while (true)
423  {
424  iter = std::find_if(iter, pids.end(), std::bind2nd(SFindName(), _Srv));
425  if (pids.end() == iter)
426  break;
427 
428  retVal.push_back(*iter);
429  ++iter;
430  };
431 #endif
432  return retVal;
433  }
434 
439  inline bool is_status_ok(int status)
440  {
441  return WIFEXITED(status) && WEXITSTATUS(status) == 0;
442  }
443 
448  inline pid_t execute(const std::string& _Command,
449  const std::string& _stdoutFileName,
450  const std::string& _stderrFileName)
451  {
452  try
453  {
454  std::string smartCmd(_Command);
455  MiscCommon::smart_path(&smartCmd);
456 
457  // Restore default handler. If we fail to do so, we might fail to waitpid our children.
458  // After we started using boost::process we noticed that ::waitpid fails.
459  // boost:process either sets its own handler or there is a call for signal(SIGCHLD, SIG_IGN);
460  signal(SIGCHLD, SIG_DFL);
461  // Execute the process
462  bp::child c(smartCmd, bp::std_out > _stdoutFileName, bp::std_err > _stderrFileName);
463  pid_t pid = c.id();
464  c.detach();
465  return pid;
466  }
467  catch (std::exception& _e)
468  {
469  std::stringstream ss;
470  ss << "execute: " << _e.what();
471  throw std::runtime_error(ss.str());
472  }
473  }
474 
476  inline void execute(const std::string& _Command)
477  {
478  try
479  {
480  std::string smartCmd(_Command);
481  MiscCommon::smart_path(&smartCmd);
482  bp::spawn(smartCmd);
483  }
484  catch (std::exception& _e)
485  {
486  std::stringstream ss;
487  ss << "execute: " << _e.what();
488  throw std::runtime_error(ss.str());
489  }
490  }
491 
492  // TODO: Document me!
493  // If _Timeout is 0, then function returns child pid and doesn't wait for the child process to finish. Otherwise
494  // return value is 0.
495  inline pid_t execute(const std::string& _Command,
496  const std::chrono::seconds& _Timeout,
497  std::string* _output = nullptr,
498  std::string* _errout = nullptr,
499  int* _exitCode = nullptr)
500  {
501  try
502  {
503  bio::io_service ios;
504  std::future<std::string> out_data;
505  std::future<std::string> err_data;
506 
507  std::string smartCmd(_Command);
508  MiscCommon::smart_path(&smartCmd);
509 
510  if (std::chrono::seconds(0) == _Timeout)
511  {
512  boost::process::child c(smartCmd);
513  pid_t pid = c.id();
514  c.detach();
515  return pid;
516  }
517 
518  bp::child c(smartCmd, bp::std_in.close(), bp::std_out > out_data, bp::std_err > err_data, ios);
519 
520  while (c.valid() && !c.running())
521  ;
522 
523  if (!c.valid())
524  throw std::runtime_error("Can't execute the given process.");
525 
526  // since we use async io to be able to read both stdout and stderr and have a timeout on process execution,
527  // we need to have a worker thread for asio service to prevent blocking of the main thread.
528  std::thread asioWorker{ [&ios]() {
529  try
530  {
531  ios.run();
532  }
533  catch (std::exception& _e)
534  {
535  }
536  } };
537 
538  // TODO: The process library has a bug in BOOST 1.64, which can cause wait_for not atually to wait in some
539  // cases. We therefore use our own implemention of wait. Replace it with official wait_for when DDS switvjed
540  // to BOOST 1.66 or higher.
541 
542  /* if (!c.wait_for(_Timeout))
543  {
544  // Child didn't yet finish. Terminating it...
545  c.terminate();
546  // prevent leaving a zombie process
547  c.wait();
548  if(asioWorker.joinable())
549  asioWorker.join();
550  throw std::runtime_error("Timeout has been reached, command execution will be terminated.");
551  }*/
553  pid_t _pid = c.id();
554  int status;
555 
556  auto start = std::chrono::steady_clock::now();
557  while (true)
558  {
559  pid_t ret = ::waitpid(_pid, &status, WNOHANG | WUNTRACED);
560  if (ret < 0 || ret == _pid)
561  break;
562 
563  if ((std::chrono::steady_clock::now() - start) > _Timeout)
564  {
565  // Child didn't yet finish. Terminating it...
566  c.terminate();
567  // prevent leaving a zombie process
568  c.wait();
569  if (asioWorker.joinable())
570  asioWorker.join();
571  throw std::runtime_error("Timeout has been reached, command execution will be terminated.");
572  }
573  }
575 
576  if (asioWorker.joinable())
577  asioWorker.join();
578 
579  if (_output)
580  *_output = out_data.get();
581 
582  if (_errout)
583  *_errout = err_data.get();
584 
585  if (_exitCode)
586  *_exitCode = c.exit_code();
587 
588  return 0;
589  }
590  catch (std::exception& _e)
591  {
592  std::stringstream ss;
593  ss << "execute: " << _e.what();
594  throw std::runtime_error(ss.str());
595  }
596  }
597 };
598 
599 #endif /*PROCESS_H_*/
std::vector< std::string > StringVector_t
An STL vector of strings.
Definition: def.h:115
This class is used to quarry a list of currently running processes.
Definition: Process.h:251
~CPIDFile()
Definition: Process.h:94
std::vector< pid_t > vectorPid_t
Definition: Process.h:408
The system_error exception class retrieves a string, which represent the last error.
Definition: ErrorCode.h:77
_T & to_lower(_T &_str)
convert string to lower case.
Definition: MiscUtils.h:245
void Open(pid_t _PId)
Definition: Process.h:327
A PID-file helper.
Definition: Process.h:65
mem_fun1_t< _Result, _Class, _Argument > mem_fun(_Result(_Class::*member)(_Argument))
The mem_fun() template is a custom mem_fun adapter, which extends std::mem_fun.
Definition: stlx.h:55
CProcStatus()
Definition: Process.h:318
CPIDFile(const std::string &_FileName, pid_t _PID)
Definition: Process.h:68
bool operator()(CProcList::ProcContainer_t::value_type _pid, const std::string &_Name) const
Definition: Process.h:396
static void GetProcList(ProcContainer_t *_Procs)
Definition: Process.h:257
static pid_t GetPIDFromFile(const std::string &_FileName)
Definition: Process.h:100
~CProcStatus()
Definition: Process.h:323
This custom istream iterator helps to read input line by line without breaking lines after whitespace...
Definition: CustomIterator.h:24
vectorPid_t getprocbyname(const std::string &_Srv, bool _filterForRealUserID=false)
Definition: Process.h:410
std::set< pid_t > ProcContainer_t
Definition: Process.h:254
Definition: Process.h:394
Definition: def.h:152
Definition: MiscUtils.h:215
void smart_path(_T *_Path)
The function extends any environment variable found in the give path to its value.
Definition: SysHelper.h:93
std::string GetValue(const std::string &_KeyName) const
Definition: Process.h:355
bool IsProcessRunning(pid_t _PID)
The function checks, whether the process which corresponds to the given _PID can be found...
Definition: Process.h:56
pid_t execute(const std::string &_Command, const std::string &_stdoutFileName, const std::string &_stderrFileName)
Definition: Process.h:448
bool is_status_ok(int status)
Definition: Process.h:439
Miscellaneous functions and helpers are located here.
Definition: BOOST_FILESYSTEM.h:21
This class helps to retrieve process&#39;s information from /proc/<pid>/status.
Definition: Process.h:312