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