NCBI C++ ToolKit
ncbi_process.cpp
Go to the documentation of this file.

Go to the SVN repository for this file.

1 /* $Id: ncbi_process.cpp 77918 2017-05-15 16:18:35Z grichenk $
2  * ===========================================================================
3  *
4  * PUBLIC DOMAIN NOTICE
5  * National Center for Biotechnology Information
6  *
7  * This software/database is a "United States Government Work" under the
8  * terms of the United States Copyright Act. It was written as part of
9  * the author's official duties as a United States Government employee and
10  * thus cannot be copyrighted. This software/database is freely available
11  * to the public for use. The National Library of Medicine and the U.S.
12  * Government have not placed any restriction on its use or reproduction.
13  *
14  * Although all reasonable efforts have been taken to ensure the accuracy
15  * and reliability of the software and data, the NLM and the U.S.
16  * Government do not and cannot warrant the performance or results that
17  * may be obtained by using this software or data. The NLM and the U.S.
18  * Government disclaim all warranties, express or implied, including
19  * warranties of performance, merchantability or fitness for any particular
20  * purpose.
21  *
22  * Please cite the author in any work or product based on this material.
23  *
24  * ===========================================================================
25  *
26  * Authors: Aaron Ucko, Vladimir Ivanov
27  *
28  */
29 
30 #include <ncbi_pch.hpp>
31 #include <corelib/error_codes.hpp>
32 #include <corelib/ncbidiag.hpp>
33 #include <corelib/ncbifile.hpp>
34 #include <corelib/ncbithr.hpp>
35 #include <corelib/ncbi_process.hpp>
37 #include <corelib/ncbi_system.hpp>
38 #include <corelib/ncbierror.hpp>
39 #include "ncbisys.hpp"
40 
41 #if defined(NCBI_OS_UNIX)
42 # include <errno.h>
43 # include <fcntl.h>
44 # include <signal.h>
45 # include <stdio.h>
46 # include <unistd.h>
47 # include <sys/types.h>
48 # include <sys/wait.h>
49 #elif defined(NCBI_OS_MSWIN)
50 # include <corelib/ncbitime.hpp> // for CStopWatch
51 # include <process.h>
52 # include <tlhelp32.h>
53 # pragma warning (disable : 4191)
54 #endif
55 
56 
57 #define NCBI_USE_ERRCODE_X Corelib_Process
58 
59 
61 
62 
63 /////////////////////////////////////////////////////////////////////////////
64 //
65 // CProcess::CExitInfo
66 //
67 
68 // CExitInfo process state
73 };
74 
75 
76 #define EXIT_INFO_CHECK \
77  if ( !IsPresent() ) { \
78  NCBI_THROW(CCoreException, eCore, \
79  "CProcess::CExitInfo state is unknown. " \
80  "Please check CExitInfo::IsPresent() first."); \
81  }
82 
83 
85 {
87  status = 0;
88 }
89 
90 
92 {
93  return state != eExitInfo_Unknown;
94 }
95 
96 
98 {
100  return state == eExitInfo_Alive;
101 }
102 
103 
105 {
107  if (state != eExitInfo_Terminated) {
108  return false;
109  }
110 #if defined(NCBI_OS_UNIX)
111  return WIFEXITED(status) != 0;
112 #elif defined(NCBI_OS_MSWIN)
113  // The process always terminates with exit code
114  return true;
115 #endif
116 }
117 
118 
120 {
122  if (state != eExitInfo_Terminated) {
123  return false;
124  }
125 #if defined(NCBI_OS_UNIX)
126  return WIFSIGNALED(status) != 0;
127 #elif defined(NCBI_OS_MSWIN)
128  // The process always terminates with exit code
129  return false;
130 #endif
131 }
132 
133 
135 {
136  if ( !IsExited() ) {
137  return -1;
138  }
139 #if defined(NCBI_OS_UNIX)
140  return WEXITSTATUS(status);
141 #elif defined(NCBI_OS_MSWIN)
142  return status;
143 #endif
144 }
145 
146 
148 {
149  if ( !IsSignaled() ) {
150  return -1;
151  }
152 #if defined(NCBI_OS_UNIX)
153  return WTERMSIG(status);
154 #elif defined(NCBI_OS_MSWIN)
155  return -1;
156 #endif
157 }
158 
159 
160 /////////////////////////////////////////////////////////////////////////////
161 //
162 // CProcess
163 //
164 
165 // Predefined timeouts (in milliseconds)
166 const unsigned long kWaitPrecision = 100;
167 const unsigned long CProcess::kDefaultKillTimeout = 1000;
168 
169 
171  : m_Process(process), m_Type(type)
172 {
173  return;
174 }
175 
176 #ifdef NCBI_OS_MSWIN
177 // The helper constructor for MS Windows to avoid cast from
178 // TProcessHandle to TPid
179 CProcess::CProcess(TProcessHandle process, EProcessType type)
180  : m_Process((intptr_t)process), m_Type(type)
181 {
182  return;
183 }
184 #endif //NCBI_OS_MSWIN
185 
186 
187 #ifdef NCBI_THREAD_PID_WORKAROUND
188 # ifndef NCBI_OS_UNIX
189 # error "NCBI_THREAD_PID_WORKAROUND should only be defined on UNIX!"
190 # endif
191 TPid CProcess::sx_GetPid(EGetPidFlag flag)
192 {
193  if ( flag == ePID_GetThread ) {
194  // Return real PID, do not cache it.
195  return getpid();
196  }
197 
198  DEFINE_STATIC_FAST_MUTEX(s_GetPidMutex);
199  static TPid s_CurrentPid = 0;
200  static TPid s_ParentPid = 0;
201 
202  if (CThread::IsMain()) {
203  // For main thread always force caching of PIDs
204  CFastMutexGuard guard(s_GetPidMutex);
205  s_CurrentPid = getpid();
206  s_ParentPid = getppid();
207  }
208  else {
209  // For child threads update cached PIDs only if there was a fork
210  // First call is always from the main thread (explicit or through
211  // CThread::Run()), s_CurrentPid must be != 0 in any child thread.
212  _ASSERT(s_CurrentPid);
213  TPid pid = getpid();
214  TPid thr_pid = CThread::sx_GetThreadPid();
215  if (thr_pid && thr_pid != pid) {
216  // Thread's PID has changed - fork detected.
217  // Use current PID and PPID as globals.
218  CThread::sx_SetThreadPid(pid);
219  CFastMutexGuard guard(s_GetPidMutex);
220  s_CurrentPid = pid;
221  s_ParentPid = getppid();
222  }
223  }
224  return flag == ePID_GetCurrent ? s_CurrentPid : s_ParentPid;
225 }
226 #endif //NCBI_THREAD_PID_WORKAROUND
227 
229 {
230 #if defined(NCBI_OS_MSWIN)
231  return GetCurrentProcess();
232 #elif defined(NCBI_OS_UNIX)
233  return GetCurrentPid();
234 #endif
235 }
236 
237 
239 {
240 #if defined(NCBI_OS_MSWIN)
241  return GetCurrentProcessId();
242 #elif defined NCBI_THREAD_PID_WORKAROUND
243  return sx_GetPid(ePID_GetCurrent);
244 #elif defined(NCBI_OS_UNIX)
245  return getpid();
246 #endif
247 }
248 
249 
251 {
252 #if defined(NCBI_OS_MSWIN)
253  TPid ppid = (TPid)(-1);
254  // Open snapshot handle
255  HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
256 
257  if (hSnapshot != INVALID_HANDLE_VALUE) {
258 
259  PROCESSENTRY32 pe;
260  DWORD pid = GetCurrentProcessId();
261  pe.dwSize = sizeof(PROCESSENTRY32);
262 
263  BOOL retval = Process32First(hSnapshot, &pe);
264  while (retval) {
265  if (pe.th32ProcessID == pid) {
266  ppid = pe.th32ParentProcessID;
267  break;
268  }
269  pe.dwSize = sizeof(PROCESSENTRY32);
270  retval = Process32Next(hSnapshot, &pe);
271  }
272 
273  // close snapshot handle
274  CloseHandle(hSnapshot);
275  }
276  return ppid;
277 #elif defined NCBI_THREAD_PID_WORKAROUND
278  return sx_GetPid(ePID_GetParent);
279 #elif defined(NCBI_OS_UNIX)
280  return getppid();
281 #endif
282 }
283 
284 #ifdef NCBI_OS_UNIX
285 namespace {
286  class CErrnoKeeper
287  {
288  public:
289  CErrnoKeeper() : m_Errno(errno) {}
290  int GetErrno() const {return m_Errno;}
291  ~CErrnoKeeper() {errno = m_Errno;}
292  private:
293  int m_Errno;
294  };
295 }
296 
297 static string s_ErrnoToString()
298 {
299  CErrnoKeeper x_errno;
300  const char* error = strerror(x_errno.GetErrno());
301  return error != NULL && *error != '\0' ? string(error) :
302  ("errno=" + NStr::NumericToString(x_errno.GetErrno()));
303 }
304 #endif
305 
307 {
308 #ifdef NCBI_OS_UNIX
309  TPid pid = ::fork();
310  if (pid == 0)
311  // Only update PID and UID in the child process.
315  else if (pid == (TPid) -1 && (flags & fFF_AllowExceptions) != 0) {
317  "Cannot fork: " << s_ErrnoToString());
318  }
319 
320  return pid;
321 #else
322  NCBI_THROW(CCoreException, eCore,
323  "CProcess::Fork() not implemented on this platform");
324 #endif
325 }
326 
327 #ifdef NCBI_OS_UNIX
328 namespace {
329  class CSafeRedirect
330  {
331  public:
332  CSafeRedirect(int fd, bool* success_flag) :
333  m_OrigFD(fd),
334  m_SuccessFlag(success_flag),
335  m_Redirected(false)
336  {
337  if ((m_DupFD = ::fcntl(fd, F_DUPFD, STDERR_FILENO + 1)) < 0) {
338  NCBI_THROW_FMT(CCoreException, eCore, "[Daemonize] "
339  "Error duplicating file descriptor #" << fd <<
340  ": " << s_ErrnoToString());
341  }
342  }
343  void Redirect(int new_fd)
344  {
345  if (new_fd != m_OrigFD) {
346  int error = ::dup2(new_fd, m_OrigFD);
347  if (error < 0) {
348  CErrnoKeeper x_errno;
349  ::close(new_fd);
350  NCBI_THROW_FMT(CCoreException, eCore, "[Daemonize] "
351  "Error redirecting file descriptor #" << m_OrigFD <<
352  ": " << s_ErrnoToString());
353  }
354  ::close(new_fd);
355  m_Redirected = true;
356  }
357  }
358  ~CSafeRedirect()
359  {
360  CErrnoKeeper x_errno;
361  if (m_Redirected && !*m_SuccessFlag)
362  // Restore the original std I/O stream descriptor.
363  ::dup2(m_DupFD, m_OrigFD);
364  ::close(m_DupFD);
365  }
366 
367  private:
368  int m_OrigFD;
369  int m_DupFD;
370  bool* m_SuccessFlag;
371  bool m_Redirected;
372  };
373 }
374 
376 {
377  if (!(flags & fDF_AllowThreads)) {
378  if (unsigned n = CThread::GetThreadsCount()) {
379  NCBI_THROW_FMT(CCoreException, eCore, "[Daemonize] "
380  "Prohibited, there are already child threads running: " << n);
381  }
382  }
383 
384  bool success_flag = false;
385 
386  CSafeRedirect stdin_redirector(STDIN_FILENO, &success_flag);
387  CSafeRedirect stdout_redirector(STDOUT_FILENO, &success_flag);
388  CSafeRedirect stderr_redirector(STDERR_FILENO, &success_flag);
389 
390  int new_fd;
391 
392  if (flags & fDF_KeepStdin) {
393  if ((new_fd = ::open("/dev/null", O_RDONLY)) < 0) {
394  NCBI_THROW_FMT(CCoreException, eCore, "[Daemonize] "
395  "Error opening /dev/null for reading: " <<
396  s_ErrnoToString());
397  }
398  stdin_redirector.Redirect(new_fd);
399  }
400  if (flags & fDF_KeepStdout) {
401  if ((new_fd = ::open("/dev/null", O_WRONLY)) < 0) {
402  NCBI_THROW_FMT(CCoreException, eCore, "[Daemonize] "
403  "Error opening /dev/null for writing: " <<
404  s_ErrnoToString());
405  }
406  NcbiCout.flush();
407  ::fflush(stdout);
408  stdout_redirector.Redirect(new_fd);
409  }
410  if (logfile) {
411  if (!*logfile) {
412  if ((new_fd = ::open("/dev/null", O_WRONLY | O_APPEND)) < 0) {
413  NCBI_THROW_FMT(CCoreException, eCore, "[Daemonize] "
414  "Error opening /dev/null for appending: " <<
415  s_ErrnoToString());
416  }
417  } else {
418  if ((new_fd = ::open(logfile,
419  O_WRONLY | O_APPEND | O_CREAT, 0666)) < 0) {
420  NCBI_THROW_FMT(CCoreException, eCore, "[Daemonize] "
421  "Unable to open logfile \"" << logfile <<
422  "\": " << s_ErrnoToString());
423  }
424  }
425  NcbiCerr.flush();
426  ::fflush(stderr);
427  stderr_redirector.Redirect(new_fd);
428  }
429  ::fflush(NULL);
431  if (pid) {
432  // Parent process.
433  // No need to set success_flag to true here, because
434  // either this process must be terminated or
435  // the descriptors must be restored.
436  if ((flags & fDF_KeepParent) == 0) {
438  ::_exit(0);
439  }
440  return (TPid) pid/*success*/;
441  }
442  // Child process.
443  success_flag = true;
444  ::setsid();
445  if (flags & fDF_ImmuneTTY) {
446  try {
447  if (Fork() != 0)
448  ::_exit(0); // Exit the second parent process
449  }
450  catch (CCoreException& e) {
451  ERR_POST_X(2, "[Daemonize] Failed to immune from "
452  "TTY accruals: " << e << " ... continuing anyways");
453  }
454  }
455  if (!(flags & fDF_KeepCWD))
456  if (::chdir("/") ) { /*no-op*/ }; // NB: "/" always exists
457  if (!(flags & fDF_KeepStdin))
458  ::fclose(stdin);
459  else
460  ::fflush(stdin); // POSIX requires this
461  if (!(flags & fDF_KeepStdout))
462  ::fclose(stdout);
463  if (!logfile)
464  ::fclose(stderr);
465  return (TPid)(-1)/*success*/;
466 }
467 #endif /* NCBI_OS_UNIX */
468 
470 {
471 #ifdef NCBI_OS_UNIX
472  if (flags & fDF_AllowExceptions)
473  return CProcess::x_DaemonizeEx(logfile, flags);
474  else
475  try {
476  return CProcess::x_DaemonizeEx(logfile, flags);
477  }
478  catch (CException& e) {
479  CErrnoKeeper x_errno;
480  ERR_POST_X(1, e);
481  }
482  catch (exception& e) {
483  CErrnoKeeper x_errno;
484  ERR_POST_X(1, e.what());
485  }
486 #else
487  NCBI_THROW(CCoreException, eCore,
488  "CProcess::Daemonize() not implemented on this platform");
489  /*NOTREACHED*/
490 #endif
491  return (TPid) 0/*failure*/;
492 }
493 
494 
495 bool CProcess::IsAlive(void) const
496 {
497 #if defined(NCBI_OS_UNIX)
498  return kill((TPid)m_Process, 0) == 0 || errno == EPERM;
499 
500 #elif defined(NCBI_OS_MSWIN)
501  HANDLE hProcess = 0;
502  if (m_Type == ePid) {
503  hProcess = OpenProcess(PROCESS_QUERY_INFORMATION,
504  FALSE, (TPid)m_Process);
505  if (!hProcess) {
507  return GetLastError() == ERROR_ACCESS_DENIED;
508  }
509  } else {
510  hProcess = (TProcessHandle)m_Process;
511  }
512  DWORD status = 0;
513  _ASSERT(STILL_ACTIVE != 0);
514  GetExitCodeProcess(hProcess, &status);
515  if (m_Type == ePid) {
516  CloseHandle(hProcess);
517  }
518  return status == STILL_ACTIVE;
519 #endif
520 }
521 
522 
523 bool CProcess::Kill(unsigned long timeout) const
524 {
525 #if defined(NCBI_OS_UNIX)
526 
527  TPid pid = (TPid)m_Process;
528 
529  // Try to kill the process with SIGTERM first
530  if (kill(pid, SIGTERM) < 0 && errno == EPERM) {
532  return false;
533  }
534 
535  // Check process termination within the timeout
536  unsigned long x_timeout = timeout;
537  for (;;) {
538  TPid reap = waitpid(pid, static_cast<int*>(NULL), WNOHANG);
539  if (reap) {
540  if (reap != (TPid)(-1)) {
541  _ASSERT(reap == pid);
542  return true;
543  }
544  if (errno != ECHILD) {
546  return false;
547  }
548  if (kill(pid, 0) < 0)
549  return true;
550  }
551  unsigned long x_sleep = kWaitPrecision;
552  if (x_sleep > x_timeout) {
553  x_sleep = x_timeout;
554  }
555  if ( !x_sleep ) {
556  break;
557  }
558  SleepMilliSec(x_sleep);
559  x_timeout -= x_sleep;
560  }
561  _ASSERT(!x_timeout);
562 
563  // Try harder to kill the stubborn process -- SIGKILL may not be caught!
564  int res = kill(pid, SIGKILL);
565  if ( !timeout ) {
566  return res <= 0;
567  }
568  SleepMilliSec(kWaitPrecision);
569  // Reap the zombie (if child) up from the system
570  waitpid(pid, static_cast<int*>(NULL), WNOHANG);
571  // Check whether the process cannot be killed
572  // (most likely due to a kernel problem)
573  return kill(pid, 0) < 0;
574 
575 #elif defined(NCBI_OS_MSWIN)
576 
577  // Safe process termination
578  bool safe = (timeout > 0);
579 
580  // Try to kill current process?
581  if ( m_Type == ePid && (TPid)m_Process == GetCurrentPid() ) {
582  ExitProcess(-1);
583  // NOTREACHED
584  return false;
585  }
586 
587  HANDLE hProcess = NULL;
588  HANDLE hThread = NULL;
589  bool enable_sync = true;
590 
591  // Get process handle
592  if (m_Type == eHandle) {
593  hProcess = (TProcessHandle)m_Process;
594 
595  } else { // m_Type == ePid
596  hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_TERMINATE |
597  SYNCHRONIZE, FALSE, (TPid)m_Process);
598  if ( !hProcess ) {
599  // Try to open with minimal access right needed
600  // to terminate process.
601  enable_sync = false;
602  hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, (TPid)m_Process);
603  if (!hProcess) {
604  if (GetLastError() != ERROR_ACCESS_DENIED) {
606  return false;
607  }
608  // If we have an administrative rights, that we can try
609  // to terminate the process using SE_DEBUG_NAME privilege,
610  // which system administrators normally have, but it might
611  // be disabled by default. When this privilege is enabled,
612  // the calling thread can open processes with any access
613  // rights regardless of the security descriptor assigned
614  // to the process.
615 
616  // Get current thread token
617  HANDLE hToken;
618  if (!OpenThreadToken(GetCurrentThread(),
619  TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES,
620  FALSE, &hToken)) {
621  if (GetLastError() != ERROR_NO_TOKEN) {
623  return false;
624  }
625  // Rrevert to the process token, if not impersonating
626  if (!OpenProcessToken(GetCurrentProcess(),
627  TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES,
628  &hToken)) {
630  return false;
631  }
632  }
633 
634  // Try to enable the SE_DEBUG_NAME privilege
635 
636  TOKEN_PRIVILEGES tp, tp_prev;
637  DWORD tp_prev_size = sizeof(tp_prev);
638 
639  tp.PrivilegeCount = 1;
640  tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
641  LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
642 
643  if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp),
644  &tp_prev, &tp_prev_size)) {
646  CloseHandle(hToken);
647  return false;
648  }
649  if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) {
650  // The AdjustTokenPrivileges function cannot add new
651  // privileges to the access token. It can only enable or
652  // disable the token's existing privileges.
654  CloseHandle(hToken);
655  return false;
656  }
657 
658  // Try to open process handle again
659  hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, (TPid)m_Process);
660 
661  // Restore original privilege state
662  AdjustTokenPrivileges(hToken, FALSE, &tp_prev, sizeof(tp_prev),
663  NULL, NULL);
664  CloseHandle(hToken);
665  }
666  }
667  }
668 
669  // Check process handle
670  if ( !hProcess || hProcess == INVALID_HANDLE_VALUE ) {
671  return true;
672  }
673  // Terminate process
674  bool terminated = false;
675 
676  CStopWatch timer;
677  if ( safe ) {
678  timer.Start();
679  }
680  // Safe process termination
681  if ( safe && enable_sync ) {
682  // (kernel32.dll loaded at same address in each process)
683  FARPROC exitproc = GetProcAddress(GetModuleHandleA("KERNEL32.DLL"),
684  "ExitProcess");
685  if ( exitproc ) {
686  hThread = CreateRemoteThread(hProcess, NULL, 0,
687  (LPTHREAD_START_ROUTINE)exitproc,
688  0, 0, 0);
689  // Wait until process terminated, or timeout expired
690  if (hThread &&
691  (WaitForSingleObject(hProcess, timeout) == WAIT_OBJECT_0)){
692  terminated = true;
693  }
694  }
695  }
696  // Try harder to kill stubborn process
697  if ( !terminated ) {
698  if ( TerminateProcess(hProcess, -1) != 0 ||
699  GetLastError() == ERROR_INVALID_HANDLE ) {
700  // If process "terminated" succesfuly or error occur but
701  // process handle became invalid -- process has terminated
702  terminated = true;
703  }
704  else {
706  }
707  }
708  if (safe && terminated) {
709  // The process terminating now.
710  // Reset flag, and wait for real process termination.
711 
712  terminated = false;
713  double elapsed = timer.Elapsed() * kMilliSecondsPerSecond;
714  unsigned long linger_timeout = (elapsed < timeout) ?
715  (unsigned long)((double)timeout - elapsed) : 0;
716 
717  for (;;) {
718  if ( !IsAlive() ) {
719  terminated = true;
720  break;
721  }
722  unsigned long x_sleep = kWaitPrecision;
723  if (x_sleep > linger_timeout) {
724  x_sleep = linger_timeout;
725  }
726  if ( !x_sleep ) {
727  break;
728  }
729  SleepMilliSec(x_sleep);
730  linger_timeout -= x_sleep;
731  }
732  }
733  // Close temporary process handle
734  if ( hThread ) {
735  CloseHandle(hThread);
736  }
737  if (m_Type == ePid) {
738  CloseHandle(hProcess);
739  }
740  return terminated;
741 
742 #endif
743 }
744 
745 
746 #ifdef NCBI_OS_MSWIN
747 
748 // MS Windows:
749 // A helper function for terminating all processes
750 // in the tree within specified timeout.
751 
752 // If 'timer' is specified we use safe process termination.
753 static bool s_KillGroup(DWORD pid,
754  CStopWatch *timer, unsigned long &timeout)
755 {
756  // Open snapshot handle.
757  // We cannot use one shapshot for recursive calls,
758  // because it is not reentrant.
759  HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
760  if (hSnapshot == INVALID_HANDLE_VALUE) {
761  return false;
762  }
763  PROCESSENTRY32 pe;
764  pe.dwSize = sizeof(PROCESSENTRY32);
765 
766  // Terminate all children first
767  if (!Process32First(hSnapshot, &pe)) {
768  return false;
769  }
770  do {
771  if (pe.th32ParentProcessID == pid) {
772  // Safe termination -- update timeout
773  if ( timer ) {
774  double elapsed = timer->Elapsed() * kMilliSecondsPerSecond;
775  timeout = (elapsed < timeout) ?
776  (unsigned long)((double)timeout - elapsed) : 0;
777  if ( !timeout ) {
778  CloseHandle(hSnapshot);
779  return false;
780  }
781  }
782  bool res = s_KillGroup(pe.th32ProcessID, timer, timeout);
783  if ( !res ) {
784  CloseHandle(hSnapshot);
785  return false;
786  }
787  }
788  }
789  while (Process32Next(hSnapshot, &pe));
790 
791  // Terminate the specified process
792 
793  // Safe termination -- update timeout
794  if ( timer ) {
795  double elapsed = timer->Elapsed() * kMilliSecondsPerSecond;
796  timeout = (elapsed < timeout) ?
797  (unsigned long)((double)timeout - elapsed) : 0;
798  if ( !timeout ) {
799  CloseHandle(hSnapshot);
800  return false;
801  }
802  }
803  bool res = CProcess(pid, CProcess::ePid).Kill(timeout);
804 
805  // Close snapshot handle
806  CloseHandle(hSnapshot);
807  return res;
808 }
809 
810 #endif //NCBI_OS_MSWIN
811 
812 
813 bool CProcess::KillGroup(unsigned long timeout) const
814 {
815 #if defined(NCBI_OS_UNIX)
816 
817  TPid pgid = getpgid((TPid)m_Process);
818  if (pgid == (TPid)(-1)) {
819  // TRUE if PID does not match any process
820  return errno == ESRCH;
821  }
822  return KillGroupById(pgid, timeout);
823 
824 #elif defined(NCBI_OS_MSWIN)
825 
826  // Convert the process handle to process ID if needed
827  TPid pid = 0;
828  if (m_Type == eHandle) {
829  // Some OS like Windows 2000 and WindowsXP (w/o SP1) don't
830  // have GetProcessId() function. Try to load it directy from
831  // KERNEL32.DLL
832  static bool s_TryGetProcessId = true;
833  typedef DWORD (STDMETHODCALLTYPE FAR* LPFN_GETPROCESSID)(HANDLE process);
834  static LPFN_GETPROCESSID s_GetProcessId = NULL;
835 
836  if ( s_TryGetProcessId && !s_GetProcessId ) {
837  s_GetProcessId = (LPFN_GETPROCESSID)GetProcAddress(
838  GetModuleHandleA("KERNEL32.DLL"),
839  "GetProcessId");
840  s_TryGetProcessId = false;
841  }
842  if ( !s_GetProcessId ) {
843  // GetProcessId() is not available on this platform
844  return false;
845  }
846  pid = s_GetProcessId((TProcessHandle)m_Process);
847 
848  } else { // m_Type == ePid
849  pid = (TPid)m_Process;
850  }
851  if (!pid) {
852  return false;
853  }
854  // Use safe process termination if timeout > 0
855  unsigned long x_timeout = timeout;
856  CStopWatch timer;
857  if ( timeout ) {
858  timer.Start();
859  }
860  // Kill process tree
861  bool result = s_KillGroup(pid, (timeout > 0) ? &timer : 0, x_timeout);
862  return result;
863 
864 #endif
865 }
866 
867 
868 bool CProcess::KillGroupById(TPid pgid, unsigned long timeout)
869 {
870 #if defined(NCBI_OS_UNIX)
871 
872  // Try to kill the process group with SIGTERM first
873  if (kill(-pgid, SIGTERM) < 0 && errno == EPERM) {
875  return false;
876  }
877 
878  // Check process group termination within the timeout
879  unsigned long x_timeout = timeout;
880  for (;;) {
881  // Reap the zombie (if group leader is a child) up from the system
882  TPid reap = waitpid(pgid, static_cast<int*>(NULL), WNOHANG);
883  if (reap) {
884  if (reap != (TPid)(-1)) {
885  _ASSERT(reap == pgid);
886  return true;
887  }
888  if (errno != ECHILD) {
890  return false;
891  }
892  if (kill(-pgid, 0) < 0) {
893  return true;
894  }
895  }
896  unsigned long x_sleep = kWaitPrecision;
897  if (x_sleep > x_timeout) {
898  x_sleep = x_timeout;
899  }
900  if ( !x_sleep ) {
901  break;
902  }
903  SleepMilliSec(x_sleep);
904  x_timeout -= x_sleep;
905  }
906  _ASSERT(!x_timeout);
907 
908  // Try harder to kill the stubborn processes -- SIGKILL may not be caught!
909  int res = kill(-pgid, SIGKILL);
910  if ( !timeout ) {
911  return res <= 0;
912  }
913  SleepMilliSec(kWaitPrecision);
914  // Reap the zombie (if group leader is a child) up from the system
915  waitpid(pgid, static_cast<int*>(NULL), WNOHANG);
916  // Check whether the process cannot be killed
917  // (most likely due to a kernel problem)
918  return kill(-pgid, 0) < 0;
919 
920 #elif defined(NCBI_OS_MSWIN)
921 
922  // Cannot be implemented, use non-static version of KillGroup()
923  // for specified process.
924  return false;
925 
926 #endif
927 }
928 
929 
930 int CProcess::Wait(unsigned long timeout, CExitInfo* info) const
931 {
932  int status;
933 
934  // Reset extended information
935  if (info) {
936  info->state = eExitInfo_Unknown;
937  info->status = 0;
938  }
939 
940 #if defined(NCBI_OS_UNIX)
941 
942  TPid pid = (TPid)m_Process;
943  int options = timeout == kInfiniteTimeoutMs ? 0 : WNOHANG;
944 
945  // Check process termination (with timeout or indefinitely)
946  for (;;) {
947  TPid ws = waitpid(pid, &status, options);
948  if (ws > 0) {
949  // terminated
950  _ASSERT(ws == pid);
951  if (info) {
952  info->state = eExitInfo_Terminated;
953  info->status = status;
954  }
955  return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
956  } else if (ws == 0) {
957  // still running
958  _ASSERT(timeout != kInfiniteTimeoutMs);
959  if ( !timeout ) {
960  if (info) {
961  info->state = eExitInfo_Alive;
962  }
963  break;
964  }
965  unsigned long x_sleep = kWaitPrecision;
966  if (x_sleep > timeout) {
967  x_sleep = timeout;
968  }
969  SleepMilliSec(x_sleep);
970  timeout -= x_sleep;
971  } else if (errno != EINTR) {
973  // error
974  break;
975  }
976  }
977  return -1;
978 
979 #elif defined(NCBI_OS_MSWIN)
980 
981  HANDLE hProcess;
982  bool enable_sync = true;
983 
984  // Get process handle
985  if (m_Type == ePid) {
986  hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE,
987  FALSE, (TPid)m_Process);
988  if ( !hProcess ) {
989  enable_sync = false;
990  hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, (TPid)m_Process);
991  if (!hProcess && GetLastError() == ERROR_ACCESS_DENIED) {
993  return -1;
994  }
995  }
996  } else {
997  hProcess = (TProcessHandle)m_Process;
998  if (!hProcess || hProcess == INVALID_HANDLE_VALUE) {
1000  return -1;
1001  }
1002  }
1003 
1004  status = -1;
1005  DWORD x_status;
1006  // Is process still running?
1007  if (GetExitCodeProcess(hProcess, &x_status)) {
1008  if (x_status == STILL_ACTIVE) {
1009  if (enable_sync && timeout) {
1010  DWORD tv = (timeout == kInfiniteTimeoutMs
1011  ? INFINITE
1012  : (DWORD)timeout);
1013  DWORD ws = WaitForSingleObject(hProcess, tv);
1014  switch(ws) {
1015  case WAIT_TIMEOUT:
1016  // still running
1017  _ASSERT(x_status == STILL_ACTIVE);
1018  break;
1019  case WAIT_OBJECT_0:
1020  if (GetExitCodeProcess(hProcess, &x_status)) {
1021  if (x_status != STILL_ACTIVE) {
1022  // terminated
1023  status = 0;
1024  } // else still running
1025  break;
1026  }
1027  /*FALLTHRU*/
1028  default:
1029  // error
1030  x_status = 0;
1031  break;
1032  }
1033  } // else still running
1034  } else {
1035  // terminated
1036  status = 0;
1037  }
1038  } else {
1039  // error
1040  x_status = 0;
1041  }
1042 
1043  if (status < 0) {
1044  if (info && x_status == STILL_ACTIVE) {
1045  info->state = eExitInfo_Alive;
1046  }
1047  } else {
1048  if (info) {
1049  info->state = eExitInfo_Terminated;
1050  info->status = x_status;
1051  }
1052  status = x_status;
1053  }
1054 
1055  if (m_Type == ePid) {
1056  CloseHandle(hProcess);
1057  }
1058  return status;
1059 
1060 #endif
1061 }
1062 
1063 
1064 
1065 /////////////////////////////////////////////////////////////////////////////
1066 //
1067 // CPIDGuard
1068 //
1069 // NOTE: CPIDGuard know nothing about PID file modification or deletion
1070 // by other processes behind this API. Be aware.
1071 
1072 
1073 CPIDGuard::CPIDGuard(const string& filename)
1074  : m_PID(0)
1075 {
1076  string dir;
1077  CDirEntry::SplitPath(filename, &dir, 0, 0);
1078  if (dir.empty()) {
1080  } else {
1081  m_Path = filename;
1082  }
1083  // Create guard for MT-Safe protect
1084  m_MTGuard.reset(new CInterProcessLock(m_Path + ".guard"));
1085  // Update PID
1086  UpdatePID();
1087 }
1088 
1089 
1090 /// @deprecated
1091 CPIDGuard::CPIDGuard(const string& filename, const string& dir)
1092  : m_PID(0)
1093 {
1094  string real_dir;
1095  CDirEntry::SplitPath(filename, &real_dir, 0, 0);
1096  if (real_dir.empty()) {
1097  if (dir.empty()) {
1098  real_dir = CDir::GetTmpDir();
1099  } else {
1100  real_dir = dir;
1101  }
1102  m_Path = CDirEntry::MakePath(real_dir, filename);
1103  } else {
1104  m_Path = filename;
1105  }
1106  // Create guard for MT-Safe protect
1107  m_MTGuard.reset(new CInterProcessLock(m_Path + ".guard"));
1108  // Update PID
1109  UpdatePID();
1110 }
1111 
1112 
1114 {
1115  Release();
1116  m_MTGuard.reset();
1117  m_PIDGuard.reset();
1118 }
1119 
1120 
1122 {
1123  if ( m_Path.empty() ) {
1124  return;
1125  }
1126  // MT-Safe protect
1128 
1129  // Read info
1130  TPid pid = 0;
1131  unsigned int ref = 0;
1132  CNcbiIfstream in(m_Path.c_str());
1133 
1134  if ( in.good() ) {
1135  in >> pid >> ref;
1136  in.close();
1137  if ( m_PID != pid ) {
1138  // We do not own this file more.
1139  return;
1140  }
1141  if ( ref ) {
1142  ref--;
1143  }
1144  // Check reference counter
1145  if ( ref ) {
1146  // Write updated reference counter into the file
1147  CNcbiOfstream out(m_Path.c_str(),
1149  if ( out.good() ) {
1150  out << pid << endl << ref << endl;
1151  }
1152  if ( !out.good() ) {
1154  "Unable to write into PID file " + m_Path +": "
1156  }
1157  } else {
1158  // Remove the file
1159  CDirEntry(m_Path).Remove();
1160  // Remove modification protect guard
1161  LOCK.Release();
1162  m_MTGuard->Remove();
1163  m_MTGuard.reset();
1164  // PID-file can be reused now
1165  if ( m_PIDGuard.get() ) {
1166  m_PIDGuard->Remove();
1167  m_PIDGuard.reset();
1168  }
1169  }
1170  }
1171  m_Path.erase();
1172 }
1173 
1174 
1176 {
1177  if ( m_Path.empty() ) {
1178  return;
1179  }
1180  // MT-Safe protect
1182 
1183  // Remove the PID file
1184  CDirEntry(m_Path).Remove();
1185  m_Path.erase();
1186  // Remove modification protect guard
1187  m_MTGuard->Remove();
1188  // PID-file can be reused now
1189  if ( m_PIDGuard.get() ) {
1190  m_PIDGuard->Remove();
1191  m_PIDGuard.reset();
1192  }
1193 }
1194 
1195 
1197 {
1198  if (pid == 0) {
1199  pid = CProcess::GetCurrentPid();
1200  }
1201  // MT-Safe protect
1203 
1204  // Check PID from valid PID-files only. The file can be left on
1205  // the file system from previous session. And stored in the file
1206  // PID can be already reused by OS.
1207 
1208  bool valid_file = true;
1209  unsigned int ref = 1;
1210 
1211  // Create guard for PID file
1212  if ( !m_PIDGuard.get() ) {
1213  // first call to Update() ?
1214  m_PIDGuard.reset(new CInterProcessLock(m_Path + ".start.guard"));
1215  // If the guard lock is successfully obtained, the existent PID file
1216  // can be a stale lock or a new unused file, so just reuse it.
1217  valid_file = !m_PIDGuard->TryLock();
1218  }
1219 
1220  if ( valid_file ) {
1221  // Read old PID
1222  CNcbiIfstream in(m_Path.c_str());
1223  if ( in.good() ) {
1224  TPid old_pid;
1225  in >> old_pid >> ref;
1226  if ( old_pid == pid ) {
1227  // Guard the same PID. Just increase the reference counter.
1228  ref++;
1229  } else {
1230  if ( CProcess(old_pid,CProcess::ePid).IsAlive() ) {
1231  NCBI_THROW2(CPIDGuardException, eStillRunning,
1232  "Process is still running", old_pid);
1233  }
1234  ref = 1;
1235  }
1236  }
1237  in.close();
1238  }
1239 
1240  // Write new PID
1242  if ( out.good() ) {
1243  out << pid << endl << ref << endl;
1244  }
1245  if ( !out.good() ) {
1247  "Unable to write into PID file " + m_Path + ": "
1249  }
1250  // Save updated pid
1251  m_PID = pid;
1252 }
1253 
1254 
1256 {
1257  switch (GetErrCode()) {
1258  case eStillRunning: return "eStillRunning";
1259  case eWrite: return "eWrite";
1260  default: return CException::GetErrCodeString();
1261  }
1262 }
1263 
1264 
Do not fail if pre-existing threads are detected.
int close(int fd)
Definition: connection.cpp:45
void Release()
Manually force the resource to be released.
Definition: guard.hpp:167
CStopWatch –.
Definition: ncbitime.hpp:1891
#define NCBI_THROW_FMT(exception_class, err_code, message)
The same as NCBI_THROW but with message processed as output to ostream.
Definition: ncbiexpt.hpp:562
#define NcbiCerr
Definition: ncbistre.hpp:399
void Start(void)
Start the timer.
Definition: ncbitime.hpp:2719
static string NumericToString(TNumeric value, TNumToStringFlags flags=0, int base=10)
Convert numeric value to string.
Definition: ncbistr.hpp:664
static string s_ErrnoToString()
Reset execution timer.
Definition: ncbidiag.hpp:1996
EExitInfoState
void PrintStop(void)
Print exit message.
Definition: ncbidiag.cpp:2005
static void UpdateOnFork(TOnForkFlags flags)
Update diagnostics after fork().
Definition: logging.cpp:824
static TPid GetParentPid(void)
Get process identifier for the parent of the current process.
std::ofstream out("events_result.xml")
main entry point for tests
Throw an exception if fork(2) failed.
static const unsigned long kDefaultKillTimeout
Default wait time (milliseconds) between "soft" and "hard" attempts to terminate a process...
void Remove(void)
Remove the file.
~CPIDGuard(void)
Destructor.
static TPid x_DaemonizeEx(const char *logfile, TDaemonFlags flags)
intptr_t m_Process
Process identifier.
CDirEntry –.
Definition: ncbifile.hpp:263
#define NcbiCout
Definition: ncbistre.hpp:398
TPid TProcessHandle
int GetExitCode(void) const
Get process exit code.
CDiagContext & GetDiagContext(void)
Get diag context instance.
Definition: logging.cpp:818
A real process identifier (pid).
bool IsSignaled(void) const
TRUE if the process terminated by a signal (UNIX only).
string
Definition: cgiapp.hpp:512
#define NCBI_THROW2(exception_class, err_code, message, extra)
Throw exception with extra parameter.
Definition: ncbiexpt.hpp:1623
#define NcbiSys_strerror
Definition: ncbisys.hpp:79
int BOOL
Definition: sybdb.h:150
Unable to write into the PID file.
static TProcessHandle GetCurrentHandle(void)
Get process handle for the current process (esp.
static void SplitPath(const string &path, string *dir=0, string *base=0, string *ext=0)
Split a path string into its basic components.
Definition: ncbifile.cpp:347
static int trunc
Definition: array_out.c:8
#define NULL
Definition: ncbistd.hpp:225
static const unsigned char res[3][32]
Definition: ccm.c:389
#define STDIN_FILENO
Definition: ncbicgi.cpp:60
EProcessType m_Type
Type of process identifier.
Static variables safety - create on demand, destroy on application termination.
CExitInfo(void)
Constructor.
CCoreException –.
Definition: ncbiexpt.hpp:1337
Make daemon immune to re-acquiring a controlling terminal.
#define ERR_POST_X(err_subcode, message)
Error posting with default error code and given error subcode.
Definition: ncbidiag.hpp:544
pid_t TPid
Process identifier (PID) and process handle.
#define _T_CSTRING(x)
Definition: ncbistr.hpp:180
Defines: CTimeFormat - storage class for time format.
void Release(void)
Release PID.
#define DEFINE_STATIC_FAST_MUTEX(id)
Define static fast mutex and initialize it.
Definition: ncbimtx.hpp:481
static TPid GetCurrentPid(void)
Get process identifier for the current process.
#define END_NCBI_SCOPE
End previously defined NCBI scope.
Definition: ncbistl.hpp:101
static bool KillGroupById(TPid pgid, unsigned long timeout=kDefaultKillTimeout)
Terminate a group of processes with specified ID.
unsigned int DWORD
Definition: sqltypes.h:98
Defines NCBI C++ Toolkit portable error codes.
static bool s_KillGroup(DWORD pid, CStopWatch *timer, unsigned long &timeout)
bool KillGroup(unsigned long timeout=kDefaultKillTimeout) const
Terminate a group of processes.
Reset diagnostics timer and log an app-start message in the child process.
Do not exit the parent process but return.
Keep stdout open as "/dev/null" (WO)
virtual bool Remove(TRemoveFlags flags=eRecursive) const
Remove a directory entry.
Definition: ncbifile.cpp:2584
Definition: type.c:5
#define FALSE
bool replacment for C indicating false.
Definition: ncbi_std.h:99
static string GetTmpDir(void)
Get temporary directory.
Definition: ncbifile.cpp:3597
static bool IsMain(void)
Definition: ncbithr.cpp:515
A process handle.
bool IsExited(void) const
TRUE if the process terminated normally.
bool IsAlive(void) const
TRUE if the process is still alive.
char * strerror(int n)
Definition: pcregrep.c:511
static MDB_envinfo info
Definition: mdb_load.c:37
#define FAR
Definition: ncbistd.hpp:278
Throw an exception in case of an error.
CProcess(TPid process, EProcessType type=eHandle)
Constructor.
virtual const char * GetErrCodeString(void) const
#define EXIT_INFO_CHECK
const unsigned long kInfiniteTimeoutMs
Infinite timeout in milliseconds.
void SleepMilliSec(unsigned long ml_sec, EInterruptOnSignal onsignal=eRestartOnSignal)
Extended exit information for waited process.
Multi-threading – classes, functions, and features.
CPIDGuardException –.
CProcess –.
EProcessType
How to interpret the passed process identifier argument.
unsigned TForkFlags
Bit-wise OR of FForkFlags.
#define HANDLE
An abstraction for a file handle.
Definition: mdb.c:375
#define INVALID_HANDLE_VALUE
A value for an invalid file handle.
Definition: mdb.c:381
static TPid Daemonize(const char *logfile=0, TDaemonFlags flags=0)
Go daemon.
int status
Process status information.
#define STDOUT_FILENO
Definition: ncbicgir.cpp:48
Defines classes: CDirEntry, CFile, CDir, CSymLink, CMemoryFile, CFileUtil, CFileLock, CFileIO, CFileReader, CFileWriter, CFileReaderWriter, CFileException.
static int errno
Definition: dblib.c:54
bool Kill(unsigned long timeout=kDefaultKillTimeout) const
Terminate process.
Defines NCBI C++ diagnostic APIs, classes, and macros.
int Wait(unsigned long timeout=kInfiniteTimeoutMs, CExitInfo *info=0) const
Wait until process terminates.
int GetSignal(void) const
Get the signal number that has caused the process to terminate (UNIX only).
void UpdatePID(TPid pid=0)
Update PID in the file.
const unsigned long kWaitPrecision
static uch flags
static unsigned int GetThreadsCount()
Get total amount of threads This amount does not contain main thread.
Definition: ncbithr.hpp:760
double Elapsed(void) const
Return time elapsed since first Start() or last Restart() call (in seconds).
Definition: ncbitime.hpp:2730
string m_Path
int intptr_t
Definition: ncbitype.h:185
unsigned int TDaemonFlags
Bit-wise OR of FDaemonFlags.
Defines a process management classes.
unique_ptr< CInterProcessLock > m_PIDGuard
static string MakePath(const string &dir=kEmptyStr, const string &base=kEmptyStr, const string &ext=kEmptyStr)
Assemble a path from basic components.
Definition: ncbifile.cpp:403
yy_size_t n
static TPid Fork(TForkFlags flags=fFF_UpdateDiag)
Fork the process.
#define _ASSERT
#define NCBI_THROW(exception_class, err_code, message)
Generic macro to throw an exception, given the exception class, error code and message string...
Definition: ncbiexpt.hpp:547
IO_PREFIX::ifstream CNcbiIfstream
Portable alias for ifstream.
Definition: ncbistre.hpp:245
Definition: dbpivot.c:60
unique_ptr< CInterProcessLock > m_MTGuard
std::istream & in(std::istream &in_, double &x_)
bool IsAlive(void) const
Check for process existence.
static void Set(ECode code)
Set last error using native error code enum.
Definition: ncbierror.cpp:166
const long kMilliSecondsPerSecond
Number milliseconds in one second.
Definition: ncbitime.hpp:93
IO_PREFIX::ofstream CNcbiOfstream
Portable alias for ofstream.
Definition: ncbistre.hpp:318
#define BEGIN_NCBI_SCOPE
Define ncbi namespace.
Definition: ncbistl.hpp:98
static void SetFromErrno(void)
Set last error using current "errno" code.
Definition: ncbierror.cpp:226
static int result
Definition: cursor5.c:11
CPIDGuard(const string &filename)
Create/check PID file.
CInterProcessLock –.
Don't change CWD to "/".
int state
Process state (unknown/alive/terminated).
The process listed in the file is still around.
bool IsPresent(void) const
TRUE if the object contains information about the process state.
virtual const char * GetErrCodeString(void) const
Get error code interpreted as text.
Definition: ncbiexpt.cpp:442
static void SetFromWindowsError(void)
Set last error on MS Windows using GetLastError()
Definition: ncbierror.cpp:296
Keep stdin open as "/dev/null" (RO)
Modified on Tue Jan 16 15:33:59 2018 by modify_doxy.py rev. 546573