DDS  ver. 3.4
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 #include <boost/asio/deadline_timer.hpp>
39 #pragma clang diagnostic push
40 #pragma clang diagnostic ignored "-Wunused-variable"
41 #include <boost/process.hpp>
42 #pragma clang diagnostic pop
43 
44 namespace bp = boost::process;
45 namespace bio = boost::asio;
46 
47 namespace MiscCommon
48 {
57  inline bool IsProcessRunning(pid_t _PID)
58  {
59  // unlike ::kill we don't except negative or 0 pid.
60  if (_PID <= 0)
61  return false;
62 
63  return !(::kill(_PID, 0) == -1 && errno == ESRCH);
64  }
70  class CPIDFile
71  {
72  public:
73  CPIDFile(const std::string& _FileName, pid_t _PID)
74  : m_FileName(_FileName)
75  {
76  if (!_FileName.empty() && _PID > 0)
77  {
78  // Preventing to start a second "instance" if the pidfile references to the running process
79  const pid_t pid = GetPIDFromFile(m_FileName);
80  if (pid > 0 && IsProcessRunning(pid))
81  {
82  // We don't want to unlink this file
83  m_FileName.clear();
84  throw std::runtime_error("Error creating pidfile. The process corresponding to pidfile \"" +
85  _FileName + "\" is still running");
86  }
87 
88  // Wrtiting new pidfile
89  std::ofstream f(m_FileName.c_str());
90  if (!f.is_open())
91  throw std::runtime_error("can't create PID file: " + m_FileName);
92 
93  f << _PID;
94  }
95  else
96  m_FileName.clear();
97  }
98 
100  {
101  if (!m_FileName.empty())
102  ::unlink(m_FileName.c_str());
103  }
104 
105  static pid_t GetPIDFromFile(const std::string& _FileName)
106  {
107  std::ifstream f(_FileName.c_str());
108  if (!f.is_open())
109  return 0;
110 
111  pid_t pid(0);
112  f >> pid;
113 
114  return pid;
115  }
116 
117  private:
118  std::string m_FileName;
119  };
120 
121 #if defined(__APPLE__)
122 
129  class CFindProcess
130  {
131  public:
132  typedef std::set<pid_t> ProcContainer_t;
133 
134  public:
135  static void getAllPIDsForProcessName(const std::string& _processName,
136  ProcContainer_t* _pidContainer,
137  bool _filterForRealUserID = false)
138  {
139  // Setting up the mib (Management Information Base)
140  // We pass CTL_KERN, KERN_PROC, KERN_PROC_ALL to sysctl as the MIB
141  // to get back a BSD structure with all BSD process information for
142  // all processes in it (including BSD process names)
143  int mib[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
144 
145  size_t buffSize = 0; // set to zero to start with.
146  int error = 0;
147  struct kinfo_proc* BSDProcInfo = NULL;
148 
149  if (_processName.empty())
150  return;
151  if (!_pidContainer)
152  return;
153 
154  _pidContainer->clear();
155 
156  // Getting list of process information for all processes
157  bool done(false);
158  do
159  {
160  error = sysctl(mib, 3, NULL, &buffSize, NULL, 0);
161  if (error != 0)
162  throw system_error("Error occurred while retrieving a running processes list");
163 
164  BSDProcInfo = (struct kinfo_proc*)malloc(buffSize);
165 
166  if (BSDProcInfo == NULL)
167  throw system_error(
168  "Error occurred while retrieving a running processes list. Unable to allocate the buffer.");
169 
170  error = sysctl(mib, 3, BSDProcInfo, &buffSize, NULL, 0);
171 
172  // Here we successfully got the process information.
173  // Thus set the variable to end this sysctl calling loop
174  if (error == 0)
175  {
176  done = true;
177  }
178  else
179  {
180  // failed getting process information we will try again next time around the loop. Note this is
181  // caused
182  // by the fact the process list changed between getting the size of the buffer and actually filling
183  // the buffer (something which will happen from time to time since the process list is dynamic).
184  // Anyways, the attempted sysctl call failed. We will now begin again by freeing up the allocated
185  // buffer and starting again at the beginning of the loop.
186  free(BSDProcInfo);
187  }
188  } while (!done);
189 
190  // Going through process list looking for processes with matching names
191 
192  uid_t userid = getuid();
193 
194  const size_t processCount = buffSize / sizeof(struct kinfo_proc);
195  for (size_t i = 0; i < processCount; ++i)
196  {
197  // Getting PID of process we are examining
198  const pid_t pid = BSDProcInfo[i].kp_proc.p_pid;
199  // Getting name of process we are examining
200  const std::string name = BSDProcInfo[i].kp_proc.p_comm;
201  // Getting real user if of the process
202  const uid_t uid = BSDProcInfo[i].kp_eproc.e_pcred.p_ruid;
203 
204  if ((pid > 0) && (name == _processName))
205  {
206  if (!_filterForRealUserID)
207  _pidContainer->insert(pid);
208  else if (uid == userid)
209  _pidContainer->insert(pid);
210  }
211  }
212 
213  free(BSDProcInfo);
214  }
215 
216  static bool pidExists(int _pid)
217  {
218  int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
219  size_t length = 0;
220  if (sysctl(name, 4, NULL, &length, NULL, 0))
221  throw system_error("Unable to call sysctl in the pidExists function.");
222  kinfo_proc* result = (kinfo_proc*)malloc(length);
223  if (sysctl(name, 4, result, &length, NULL, 0))
224  throw system_error("Unable to call sysctl in the pidExists function.");
225  int i, procCount = length / sizeof(kinfo_proc);
226  for (i = 0; i < procCount; ++i)
227  {
228  kinfo_proc* test = &result[i];
229 
230  if (test->kp_proc.p_pid == _pid)
231  {
232  free(result);
233  return true;
234  }
235  test = NULL;
236  }
237 
238  free(result);
239  return false;
240  }
241  };
242 #endif
243 
255 #if !defined(__APPLE__)
256  class CProcList
257  {
258  public:
259  typedef std::set<pid_t> ProcContainer_t;
260 
261  public:
262  static void GetProcList(ProcContainer_t* _Procs)
263  {
264  if (!_Procs)
265  throw std::invalid_argument("CProcList::GetProcList: Input container is NULL");
266 
267  _Procs->clear();
268 
269  struct dirent** namelist;
270  // scanning the "/proc" filesystem
271  int n = scandir("/proc", &namelist, CheckDigit, alphasort);
272 
273  if (-1 == n)
274  throw system_error("CProcList::GetProcList exception");
275  if (0 == n)
276  return; // there were no files
277 
278  for (int i = 0; i < n; ++i)
279  {
280  std::stringstream ss(namelist[i]->d_name);
281  pid_t pid;
282  ss >> pid;
283  _Procs->insert(pid);
284  free(namelist[i]);
285  }
286 
287  free(namelist);
288  }
289 
290  private:
291  static int CheckDigit(const struct dirent* _d)
292  {
293  const std::string sName(_d->d_name);
294  // Checking whether file name has all digits
295  return (sName.end() == std::find_if(sName.begin(), sName.end(), std::not1(IsDigit())));
296  }
297  };
298 #endif
299 
300 #if !defined(__APPLE__)
301 
316  // TODO: need a new algorithms for a longer app names retrieval
318  {
319  typedef std::shared_ptr<std::ifstream> ifstream_ptr;
320  typedef std::map<std::string, std::string> keyvalue_t;
321 
322  public:
324  {
325  // Preparing regular expression pattern
326  regcomp(&m_re, "(.*):(.*)", REG_EXTENDED);
327  }
329  {
330  regfree(&m_re);
331  }
332  void Open(pid_t _PId)
333  {
334  m_values.clear();
335  if (m_f.get())
336  m_f->close();
337 
338  std::stringstream ss;
339  ss << "/proc/" << _PId << "/status";
340  m_f = ifstream_ptr(new std::ifstream(ss.str().c_str()));
341  // create reader objects
342  // HACK: the extra set of parenthesis (the last argument of vector's ctor) is required (for gcc 4.1+)
343  // StringVector_t vec( custom_istream_iterator<std::string>(*m_f),
344  // (custom_istream_iterator<std::string>()) );
345  // or
346  // custom_istream_iterator<std::string> in_begin(*m_f);
347  // custom_istream_iterator<std::string> in_end;
348  // StringVector_t vec( in_begin, in_end );
349  // the last method for gcc 3.2+
350  // , because
351  // the compiler is very aggressive in identifying function declarations and will identify the
352  // definition of vec as forward declaration of a function accepting two istream_iterator parameters
353  // and returning a vector of integers
356  StringVector_t vec(in_begin, in_end);
357 
358  for_each(vec.begin(), vec.end(), std::bind1st(MiscCommon::stlx::mem_fun(&CProcStatus::_Parser), this));
359  }
360  std::string GetValue(const std::string& _KeyName) const
361  {
362  // We want to be case insensitive
363  std::string sKey(_KeyName);
364  to_lower(sKey);
365 
366  keyvalue_t::const_iterator iter = m_values.find(sKey);
367  return (m_values.end() == iter ? std::string() : iter->second);
368  }
369 
370  private:
371  bool _Parser(const std::string& _sVal)
372  {
373  regmatch_t PMatch[3];
374  if (0 != regexec(&m_re, _sVal.c_str(), 3, PMatch, 0))
375  return false;
376  std::string sKey(_sVal.c_str() + PMatch[1].rm_so, PMatch[1].rm_eo - PMatch[1].rm_so);
377  std::string sValue(_sVal.c_str() + PMatch[2].rm_so, PMatch[2].rm_eo - PMatch[2].rm_so);
378  // We want to be case insensitive
379  to_lower(sKey);
380 
381  trim<std::string>(&sValue, '\t');
382  trim<std::string>(&sValue, ' ');
383  // insert key-value if found
384  m_values.insert(keyvalue_t::value_type(sKey, sValue));
385  return true;
386  }
387 
388  private:
389  ifstream_ptr m_f;
390  regex_t m_re;
391  keyvalue_t m_values;
392  };
393 #endif
394 
398 #if !defined(__APPLE__)
399  struct SFindName : public std::binary_function<CProcList::ProcContainer_t::value_type, std::string, bool>
400  {
401  bool operator()(CProcList::ProcContainer_t::value_type _pid, const std::string& _Name) const
402  {
403  CProcStatus p;
404  p.Open(_pid);
405  return (p.GetValue("Name") == _Name);
406  }
407  };
408 #endif
409 
413  typedef std::vector<pid_t> vectorPid_t;
414 
415  inline vectorPid_t getprocbyname(const std::string& _Srv, bool _filterForRealUserID = false)
416  {
417  vectorPid_t retVal;
418 #if defined(__APPLE__)
419  CFindProcess::ProcContainer_t container;
420  CFindProcess::getAllPIDsForProcessName(_Srv, &container, _filterForRealUserID);
421  copy(container.begin(), container.end(), std::back_inserter(retVal));
422 #else
424  CProcList::GetProcList(&pids);
425 
426  CProcList::ProcContainer_t::const_iterator iter = pids.begin();
427  while (true)
428  {
429  iter = std::find_if(iter, pids.end(), std::bind2nd(SFindName(), _Srv));
430  if (pids.end() == iter)
431  break;
432 
433  retVal.push_back(*iter);
434  ++iter;
435  };
436 #endif
437  return retVal;
438  }
439 
444  inline bool is_status_ok(int status)
445  {
446  return WIFEXITED(status) && WEXITSTATUS(status) == 0;
447  }
448 
453  inline pid_t execute(const std::string& _Command,
454  const std::string& _stdoutFileName,
455  const std::string& _stderrFileName)
456  {
457  try
458  {
459  std::string smartCmd(_Command);
460  MiscCommon::smart_path(&smartCmd);
461 
462  // FIX: A fix for cases when the parent process sets SIG_IGN (if it was created by
463  // bosot::process::spawn). Restore default handler. If we don't do so, we might fail to waitpid our
464  // children. After we started using boost::process we noticed that ::waitpid fails. boost:process either
465  // sets its own handler or there is a call for signal(SIGCHLD, SIG_IGN);
466  std::signal(SIGCHLD, SIG_DFL);
467 
468  // Execute the process
469  bp::child c(smartCmd, bp::std_out > _stdoutFileName, bp::std_err > _stderrFileName);
470  pid_t pid = c.id();
471  c.detach();
472 
473  return pid;
474  }
475  catch (std::exception& _e)
476  {
477  std::stringstream ss;
478  ss << "execute: " << _e.what();
479  throw std::runtime_error(ss.str());
480  }
481  }
482 
484  inline void execute(const std::string& _Command)
485  {
486  try
487  {
488  std::string smartCmd(_Command);
489  MiscCommon::smart_path(&smartCmd);
490  bp::spawn(smartCmd);
491  }
492  catch (std::exception& _e)
493  {
494  std::stringstream ss;
495  ss << "execute: " << _e.what();
496  throw std::runtime_error(ss.str());
497  }
498  }
499 
500  // TODO: Document me!
501  // If _Timeout is 0, then function returns child pid and doesn't wait for the child process to finish. Otherwise
502  // return value is 0.
503  inline pid_t execute(const std::string& _Command,
504  const std::chrono::seconds& _Timeout,
505  std::string* _output = nullptr,
506  std::string* _errout = nullptr,
507  int* _exitCode = nullptr)
508  {
509  try
510  {
511  std::string smartCmd(_Command);
512  MiscCommon::smart_path(&smartCmd);
513 
514  if (std::chrono::seconds(0) == _Timeout)
515  {
516  boost::process::child c(smartCmd);
517  pid_t pid = c.id();
518  c.detach();
519  return pid;
520  }
521 
522  boost::asio::io_service ios;
523  bp::async_pipe outPipe(ios);
524  bp::async_pipe errPipe(ios);
525  boost::asio::streambuf outBuf;
526  boost::asio::streambuf errBuf;
527 
528  boost::asio::deadline_timer watchdog{ ios, boost::posix_time::seconds(_Timeout.count()) };
529 
530  bp::child c(smartCmd,
531  bp::std_in.close(),
532  bp::std_out > outPipe,
533  bp::std_err > errPipe,
534  ios,
535  bp::on_exit([&](int exit, const std::error_code& ec_in) {
536  outPipe.close();
537  errPipe.close();
538  watchdog.cancel();
539  ios.stop();
540  }));
541 
542  if (!c.valid())
543  throw std::runtime_error("Can't execute the given process.");
544 
545  boost::asio::async_read(outPipe, outBuf, [](const boost::system::error_code& ec, std::size_t size) {});
546  boost::asio::async_read(errPipe, errBuf, [](const boost::system::error_code& ec, std::size_t size) {});
547 
548  bool errorFlag(false);
549  watchdog.async_wait([&](boost::system::error_code ec) {
550  // Workaround we can't use boost::process::child::wait_for because it's buged in Boost 1.70 and not yet
551  // fixed. We therefore use a deadline timer.
552  if (!ec)
553  {
554  errorFlag = true;
555  outPipe.close();
556  errPipe.close();
557  ios.stop();
558  // Child didn't yet finish. Terminating it...
559  c.terminate();
560  }
561  });
562 
563  ios.run();
564  // prevent leaving a zombie process
565  c.wait();
566 
567  if (errorFlag)
568  throw std::runtime_error("Timeout has been reached, command execution will be terminated.");
569 
570  if (_exitCode)
571  *_exitCode = c.exit_code();
572 
573  if (_output)
574  _output->assign(std::istreambuf_iterator<char>(&outBuf), std::istreambuf_iterator<char>());
575 
576  if (_errout)
577  _errout->assign(std::istreambuf_iterator<char>(&errBuf), std::istreambuf_iterator<char>());
578 
579  return 0;
580  }
581  catch (std::exception& _e)
582  {
583  std::stringstream ss;
584  ss << "execute: " << _e.what();
585  throw std::runtime_error(ss.str());
586  }
587  }
588 }; // namespace MiscCommon
589 
590 #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:256
~CPIDFile()
Definition: Process.h:99
std::vector< pid_t > vectorPid_t
Definition: Process.h:413
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:249
void Open(pid_t _PId)
Definition: Process.h:332
A PID-file helper.
Definition: Process.h:70
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:323
CPIDFile(const std::string &_FileName, pid_t _PID)
Definition: Process.h:73
bool operator()(CProcList::ProcContainer_t::value_type _pid, const std::string &_Name) const
Definition: Process.h:401
static void GetProcList(ProcContainer_t *_Procs)
Definition: Process.h:262
static pid_t GetPIDFromFile(const std::string &_FileName)
Definition: Process.h:105
~CProcStatus()
Definition: Process.h:328
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:415
std::set< pid_t > ProcContainer_t
Definition: Process.h:259
Definition: Process.h:399
Definition: def.h:152
Definition: MiscUtils.h:219
void smart_path(_T *_Path)
The function extends any environment variable found in the give path to its value.
Definition: SysHelper.h:95
std::string GetValue(const std::string &_KeyName) const
Definition: Process.h:360
bool IsProcessRunning(pid_t _PID)
The function checks, whether the process which corresponds to the given _PID can be found.
Definition: Process.h:57
pid_t execute(const std::string &_Command, const std::string &_stdoutFileName, const std::string &_stderrFileName)
Definition: Process.h:453
bool is_status_ok(int status)
Definition: Process.h:444
Miscellaneous functions and helpers are located here.
Definition: BOOST_FILESYSTEM.h:21
This class helps to retrieve process's information from /proc/<pid>/status.
Definition: Process.h:317