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 99381 2023-03-20 13:48:01Z ivanov $
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/time.h> // getrusage(), getrlimit()
49 # include <sys/resource.h> // getrusage(), getrlimit()
50 # include <sys/times.h> // times()
51 # include <sys/wait.h>
52 # if defined(NCBI_OS_BSD) || defined(NCBI_OS_DARWIN)
53 # include <sys/sysctl.h>
54 # endif //NCBI_OS_BSD || NCBI_OS_DARWIN
55 # if defined(NCBI_OS_IRIX)
56 # include <sys/sysmp.h>
57 # endif //NCBI_OS_IRIX
58 # include "ncbi_os_unix_p.hpp"
59 
60 #elif defined(NCBI_OS_MSWIN)
61 # include <corelib/ncbitime.hpp> // CStopWatch
62 # include <corelib/ncbidll.hpp> // CDll
63 # include <process.h>
64 # include "ncbi_os_mswin_p.hpp"
65 # pragma warning (disable : 4191)
66 #endif
67 
68 #ifdef NCBI_OS_DARWIN
69 extern "C" {
70 # include <mach/mach.h>
71 } /* extern "C" */
72 #endif //NCBI_OS_DARWIN
73 
74 
75 #define NCBI_USE_ERRCODE_X Corelib_Process
76 
77 
79 
80 
81 /////////////////////////////////////////////////////////////////////////////
82 //
83 // CProcessBase -- constants initialization
84 //
85 
86 const unsigned long CProcessBase::kDefaultKillTimeout = 1000;
87 const unsigned long CProcessBase::kInfiniteTimeoutMs = kMax_ULong;
88 const unsigned long kWaitPrecisionMs = 100;
89 
90 
91 
92 /////////////////////////////////////////////////////////////////////////////
93 //
94 // CProcess::CExitInfo
95 //
96 
97 // CExitInfo process state
102 };
103 
104 
105 #define EXIT_INFO_CHECK \
106  if ( !IsPresent() ) { \
107  NCBI_THROW(CCoreException, eCore, \
108  "CProcess::CExitInfo state is unknown. " \
109  "Please check CExitInfo::IsPresent() first."); \
110  }
111 
112 
114 {
116  status = 0;
117 }
118 
119 
121 {
122  return state != eExitInfo_Unknown;
123 }
124 
125 
127 {
129  return state == eExitInfo_Alive;
130 }
131 
132 
134 {
136  if (state != eExitInfo_Terminated) {
137  return false;
138  }
139 #if defined(NCBI_OS_UNIX)
140  return WIFEXITED(status) != 0;
141 #elif defined(NCBI_OS_MSWIN)
142  // The process always terminates with exit code
143  return true;
144 #endif
145 }
146 
147 
149 {
151  if (state != eExitInfo_Terminated) {
152  return false;
153  }
154 #if defined(NCBI_OS_UNIX)
155  return WIFSIGNALED(status) != 0;
156 #elif defined(NCBI_OS_MSWIN)
157  // The process always terminates with exit code
158  return false;
159 #endif
160 }
161 
162 
164 {
165  if ( !IsExited() ) {
166  return -1;
167  }
168 #if defined(NCBI_OS_UNIX)
169  return WEXITSTATUS(status);
170 #elif defined(NCBI_OS_MSWIN)
171  return status;
172 #endif
173 }
174 
175 
177 {
178  if ( !IsSignaled() ) {
179  return -1;
180  }
181 #if defined(NCBI_OS_UNIX)
182  return WTERMSIG(status);
183 #elif defined(NCBI_OS_MSWIN)
184  return -1;
185 #endif
186 }
187 
188 
189 
190 /////////////////////////////////////////////////////////////////////////////
191 //
192 // CCurrentProcess
193 //
194 
195 #ifdef NCBI_THREAD_PID_WORKAROUND
196 # ifndef NCBI_OS_UNIX
197 # error "NCBI_THREAD_PID_WORKAROUND should only be defined on UNIX!"
198 # endif
199 TPid CCurrentProcess::sx_GetPid(EGetPidFlag flag)
200 {
201  if ( flag == ePID_GetThread ) {
202  // Return real PID, do not cache it.
203  return getpid();
204  }
205 
206  DEFINE_STATIC_FAST_MUTEX(s_GetPidMutex);
207  static TPid s_CurrentPid = 0;
208  static TPid s_ParentPid = 0;
209 
210  if (CThread::IsMain()) {
211  // For main thread always force caching of PIDs
212  CFastMutexGuard guard(s_GetPidMutex);
213  s_CurrentPid = getpid();
214  s_ParentPid = getppid();
215  }
216  else {
217  // For child threads update cached PIDs only if there was a fork
218  // First call is always from the main thread (explicit or through
219  // CThread::Run()), s_CurrentPid must be != 0 in any child thread.
220  _ASSERT(s_CurrentPid);
221  TPid pid = getpid();
222  TPid thr_pid = CThread::sx_GetThreadPid();
223  if (thr_pid && thr_pid != pid) {
224  // Thread's PID has changed - fork detected.
225  // Use current PID and PPID as globals.
226  CThread::sx_SetThreadPid(pid);
227  CFastMutexGuard guard(s_GetPidMutex);
228  s_CurrentPid = pid;
229  s_ParentPid = getppid();
230  }
231  }
232  return flag == ePID_GetCurrent ? s_CurrentPid : s_ParentPid;
233 }
234 #endif //NCBI_THREAD_PID_WORKAROUND
235 
236 
238 {
239 #if defined(NCBI_OS_MSWIN)
240  return ::GetCurrentProcess();
241 #elif defined(NCBI_OS_UNIX)
242  return GetPid();
243 #endif
244 }
245 
246 
248 {
249 #if defined(NCBI_OS_MSWIN)
250  return ::GetCurrentProcessId();
251 #elif defined NCBI_THREAD_PID_WORKAROUND
252  return sx_GetPid(ePID_GetCurrent);
253 #elif defined(NCBI_OS_UNIX)
254  return getpid();
255 #endif
256 }
257 
258 
260 {
261 #if defined(NCBI_OS_MSWIN)
262  PROCESSENTRY32 entry;
263  if (CWinFeature::FindProcessEntry(::GetCurrentProcessId(), entry)) {
264  return entry.th32ParentProcessID;
265  }
267  return 0;
268 
269 #elif defined NCBI_THREAD_PID_WORKAROUND
270  return sx_GetPid(ePID_GetParent);
271 
272 #elif defined(NCBI_OS_UNIX)
273  return getppid();
274 #endif
275 }
276 
277 
278 
279 /////////////////////////////////////////////////////////////////////////////
280 //
281 // CCurrentProcess - Forks & Daemons
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') ?
302  string(error) :
303  ("errno=" + NStr::NumericToString(x_errno.GetErrno()));
304 }
305 #endif
306 
307 
309 {
310 #ifdef NCBI_OS_UNIX
311 
312  // Issue a warning if not a single thread is used that
313  // can lead to a not-async safe calls (and potential problems)
314  bool use_async_safe = CCurrentProcess::GetThreadCount() > 1;
315  if (!(flags & fFF_Exec) && use_async_safe) {
316  ERR_POST_X(3, Warning << "It is not safe to call Fork() from a multithreaded program");
317  }
318  TPid pid = ::fork();
319  if (pid == 0) {
320  // child process:
321  // Update Diag API after fork, except fFF_Exec cases
322  if (!(flags & fFF_Exec)) {
324  if (flags & fFF_UpdateDiag) {
326  }
327  if (use_async_safe) {
329  }
330  // Update PID and UID in the child process
332  }
333  }
334  else if (pid == (TPid)(-1) && (flags & fFF_AllowExceptions) != 0) {
336  "CCurrentProcess::Fork(): Cannot fork: " << s_ErrnoToString());
337  }
338  return pid;
339 
340 #else
341  NCBI_THROW(CCoreException, eCore,
342  "CCurrentProcess::Fork() not implemented on this platform");
343 #endif
344 }
345 
346 
347 #ifdef NCBI_OS_UNIX
348 namespace {
349  class CSafeRedirect
350  {
351  public:
352  CSafeRedirect(int fd, bool* success_flag) :
353  m_OrigFD(fd), m_SuccessFlag(success_flag), m_Redirected(false)
354  {
355  if ((m_DupFD = ::fcntl(fd, F_DUPFD, STDERR_FILENO + 1)) < 0) {
356  NCBI_THROW_FMT(CCoreException, eCore, "[Daemonize] "
357  "Error duplicating file descriptor #" << fd <<
358  ": " << s_ErrnoToString());
359  }
360  }
361  void Redirect(int new_fd)
362  {
363  if (new_fd != m_OrigFD) {
364  int error = ::dup2(new_fd, m_OrigFD);
365  if (error < 0) {
366  CErrnoKeeper x_errno;
367  ::close(new_fd);
368  NCBI_THROW_FMT(CCoreException, eCore, "[Daemonize] "
369  "Error redirecting file descriptor #" << m_OrigFD <<
370  ": " << s_ErrnoToString());
371  }
372  ::close(new_fd);
373  m_Redirected = true;
374  }
375  }
376  ~CSafeRedirect()
377  {
378  CErrnoKeeper x_errno;
379  if (m_Redirected && !*m_SuccessFlag) {
380  // Restore the original std I/O stream descriptor.
381  ::dup2(m_DupFD, m_OrigFD);
382  }
383  ::close(m_DupFD);
384  }
385 
386  private:
387  int m_OrigFD;
388  int m_DupFD;
389  bool* m_SuccessFlag;
390  bool m_Redirected;
391  };
392 }
393 
395 {
397  if (unsigned n = CThread::GetThreadsCount()) {
398  NCBI_THROW_FMT(CCoreException, eCore, "[Daemonize] "
399  "Prohibited, there are already child threads running: " << n);
400  }
401  }
402  bool success_flag = false;
403 
404  CSafeRedirect stdin_redirector (STDIN_FILENO, &success_flag);
405  CSafeRedirect stdout_redirector(STDOUT_FILENO, &success_flag);
406  CSafeRedirect stderr_redirector(STDERR_FILENO, &success_flag);
407 
408  int new_fd;
409 
411  if ((new_fd = ::open("/dev/null", O_RDONLY)) < 0) {
412  NCBI_THROW_FMT(CCoreException, eCore, "[Daemonize] "
413  "Error opening /dev/null for reading: " << s_ErrnoToString());
414  }
415  stdin_redirector.Redirect(new_fd);
416  }
418  if ((new_fd = ::open("/dev/null", O_WRONLY)) < 0) {
419  NCBI_THROW_FMT(CCoreException, eCore, "[Daemonize] "
420  "Error opening /dev/null for writing: " << s_ErrnoToString());
421  }
422  NcbiCout.flush();
423  ::fflush(stdout);
424  stdout_redirector.Redirect(new_fd);
425  }
426  if (logfile) {
427  if (!*logfile) {
428  if ((new_fd = ::open("/dev/null", O_WRONLY | O_APPEND)) < 0) {
429  NCBI_THROW_FMT(CCoreException, eCore, "[Daemonize] "
430  "Error opening /dev/null for appending: " << s_ErrnoToString());
431  }
432  } else {
433  if ((new_fd = ::open(logfile, O_WRONLY | O_APPEND | O_CREAT, 0666)) < 0) {
434  NCBI_THROW_FMT(CCoreException, eCore, "[Daemonize] "
435  "Unable to open logfile \"" << logfile << "\": " << s_ErrnoToString());
436  }
437  }
438  NcbiCerr.flush();
439  ::fflush(stderr);
440  stderr_redirector.Redirect(new_fd);
441  }
442  ::fflush(NULL);
445  if (pid) {
446  // Parent process.
447  // No need to set success_flag to true here, because
448  // either this process must be terminated or
449  // the descriptors must be restored.
450  if ((flags & CCurrentProcess::fDF_KeepParent) == 0) {
452  ::_exit(0);
453  }
454  return pid; /*success*/
455  }
456  // Child process.
457  success_flag = true;
458  ::setsid();
460  try {
461  if (CCurrentProcess::Fork() != 0) {
462  ::_exit(0); // Exit the second parent process
463  }
464  }
465  catch (CCoreException& e) {
466  ERR_POST_X(2, "[Daemonize] Failed to immune from "
467  "TTY accruals: " << e << " ... continuing anyways");
468  }
469  }
471  if (::chdir("/") ) { /*no-op*/ }; // "/" always exists
473  ::fclose(stdin);
474  else
475  ::fflush(stdin); // POSIX requires this
477  ::fclose(stdout);
478  if (!logfile)
479  ::fclose(stderr);
480  return (TPid)(-1); /*success*/
481 }
482 #endif /* NCBI_OS_UNIX */
483 
484 
486 {
487 #ifdef NCBI_OS_UNIX
489  return s_Daemonize(logfile, flags);
490  else
491  try {
492  return s_Daemonize(logfile, flags);
493  }
494  catch (CException& e) {
495  CErrnoKeeper x_errno;
496  ERR_POST_X(1, e);
497  }
498  catch (exception& e) {
499  CErrnoKeeper x_errno;
500  ERR_POST_X(1, e.what());
501  }
502 #else
503  NCBI_THROW(CCoreException, eCore,
504  "CCurrentProcess::Daemonize() not implemented on this platform");
505  /*NOTREACHED*/
506 #endif
507  return (TPid) 0; /*failure*/
508 }
509 
510 
511 /////////////////////////////////////////////////////////////////////////////
512 //
513 // CProcess
514 //
515 
518  m_Type(ePid),
520 {
521  return;
522 }
523 
524 
526  : m_Process(process),
527  m_Type(type),
528  m_IsCurrent(eTriState_Unknown)
529 {
530  // We don't try to determine that passed "process" represent
531  // the current process here, probably this information will not be needed.
532  // Otherwise, IsCurrent() will take care about this.
533  return;
534 }
535 
536 #ifdef NCBI_OS_MSWIN
537 
538 // The helper constructor for MS Windows to avoid cast from
539 // TProcessHandle to TPid
540 CProcess::CProcess(TProcessHandle process, EType type)
541  : m_Process((intptr_t)process),
542  m_Type(type),
543  m_IsCurrent(eTriState_Unknown)
544 {
545  return;
546 }
547 
548 TProcessHandle CProcess::x_GetHandle(DWORD desired_access, DWORD* errcode) const
549 {
550  TProcessHandle hProcess = NULL;
551  if (GetType() == eHandle) {
552  hProcess = GetHandle();
553  if (!hProcess || hProcess == INVALID_HANDLE_VALUE) {
555  return NULL;
556  }
557  } else {
558  hProcess = ::OpenProcess(desired_access, FALSE, GetPid());
559  if (!hProcess) {
560  if (errcode) {
561  *errcode = ::GetLastError();
562  CNcbiError::SetWindowsError(*errcode);
563  } else {
565  }
566  }
567  }
568  return hProcess;
569 }
570 
572 {
573  if (GetType() == ePid) {
574  ::CloseHandle(handle);
575  }
576 }
577 
579 {
580  if (GetType() == eHandle) {
581  return ::GetProcessId(GetHandle());
582  }
583  return GetPid();
584 }
585 
586 #endif //NCBI_OS_MSWIN
587 
588 
590 {
591  if (m_IsCurrent == eTriState_True) {
592  return true;
593  }
594  bool current = false;
595  if (GetType() == ePid && GetPid() == CCurrentProcess::GetPid()) {
596  current = true;
597  }
598 #if defined(NCBI_OS_MSWIN)
599  else
601  current = true;
602  }
603 #endif
605  return current;
606 }
607 
608 
609 bool CProcess::IsAlive(void) const
610 {
611 #if defined(NCBI_OS_UNIX)
612  return kill(GetPid(), 0) == 0 || errno == EPERM;
613 
614 #elif defined(NCBI_OS_MSWIN)
615  DWORD status;
616  HANDLE hProcess = x_GetHandle(PROCESS_QUERY_INFORMATION, &status);
617  if (!hProcess) {
618  return status == ERROR_ACCESS_DENIED;
619  }
620  _ASSERT(STILL_ACTIVE != 0);
621  ::GetExitCodeProcess(hProcess, &status);
622  x_CloseHandle(hProcess);
623  return status == STILL_ACTIVE;
624 #endif
625 }
626 
627 
628 bool CProcess::Kill(unsigned long timeout)
629 {
630 #if defined(NCBI_OS_UNIX)
631 
632  TPid pid = GetPid();
633 
634  // Try to kill the process with SIGTERM first
635  if (kill(pid, SIGTERM) < 0 && errno == EPERM) {
637  return false;
638  }
639 
640  // Check process termination within the timeout
641  unsigned long x_timeout = timeout;
642  for (;;) {
643  TPid reap = waitpid(pid, 0, WNOHANG);
644  if (reap) {
645  if (reap != (TPid)(-1)) {
646  _ASSERT(reap == pid);
647  return true;
648  }
649  if (errno != ECHILD) {
651  return false;
652  }
653  if (kill(pid, 0) < 0)
654  return true;
655  }
656  unsigned long x_sleep = kWaitPrecisionMs;
657  if (x_sleep > x_timeout) {
658  x_sleep = x_timeout;
659  }
660  if ( !x_sleep ) {
661  break;
662  }
663  SleepMilliSec(x_sleep);
664  x_timeout -= x_sleep;
665  }
666  _ASSERT(!x_timeout);
667 
668  // Try harder to kill the stubborn process -- SIGKILL may not be caught!
669  int res = kill(pid, SIGKILL);
670  if ( !timeout ) {
671  return res <= 0;
672  }
674  // Reap the zombie (if child) up from the system
675  waitpid(pid, 0, WNOHANG);
676 
677  // Check whether the process cannot be killed
678  // (most likely due to a kernel problem)
679  return kill(pid, 0) < 0;
680 
681 #elif defined(NCBI_OS_MSWIN)
682 
683  // Safe process termination
684  bool safe = (timeout > 0);
685 
686  // Try to kill current process?
687  if ( IsCurrent() ) {
688  // Just exit
689  ::ExitProcess(-1);
690  // NOTREACHED
691  return false;
692  }
693 
694  HANDLE hProcess = NULL;
695  HANDLE hThread = NULL;
696  bool allow_wait = true;
697 
698  // Get process handle
699 
700  hProcess = x_GetHandle(PROCESS_CREATE_THREAD | PROCESS_TERMINATE | SYNCHRONIZE);
701  if (!hProcess) {
702  if (GetType() != ePid) {
703  return false;
704  }
705  // SYNCHRONIZE access right is required to wait for the process to terminate
706  allow_wait = false;
707  // Try to open with minimal access right needed to terminate process.
708  DWORD err;
709  hProcess = x_GetHandle(PROCESS_TERMINATE, &err);
710  if (!hProcess) {
711  if (err != ERROR_ACCESS_DENIED) {
712  return false;
713  }
714  // If we have an administrative rights, that we can try
715  // to terminate the process using SE_DEBUG_NAME privilege,
716  // which system administrators normally have, but it might
717  // be disabled by default. When this privilege is enabled,
718  // the calling thread can open processes with any access
719  // rights regardless of the security descriptor assigned
720  // to the process.
721 
722  // TODO: Use CWinSecurity to adjust privileges
723 
724  // Get current thread token
725  HANDLE hToken;
726  if (!::OpenThreadToken(::GetCurrentThread(),
727  TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, FALSE, &hToken)) {
728  err = ::GetLastError();
729  if (err != ERROR_NO_TOKEN) {
731  return false;
732  }
733  // Revert to the process token, if not impersonating
734  if (!::OpenProcessToken(::GetCurrentProcess(),
735  TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken)) {
737  return false;
738  }
739  }
740 
741  // Try to enable the SE_DEBUG_NAME privilege
742 
743  TOKEN_PRIVILEGES tp, tp_prev;
744  DWORD tp_prev_size = sizeof(tp_prev);
745 
746  tp.PrivilegeCount = 1;
747  tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
748  ::LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
749 
750  if (!::AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), &tp_prev, &tp_prev_size)) {
752  ::CloseHandle(hToken);
753  return false;
754  }
755  // To determine that successful call to the AdjustTokenPrivileges()
756  // adjusted all of the specified privileges, check GetLastError():
757  err = ::GetLastError();
758  if (err == ERROR_NOT_ALL_ASSIGNED) {
759  // The AdjustTokenPrivileges function cannot add new
760  // privileges to the access token. It can only enable or
761  // disable the token's existing privileges.
763  ::CloseHandle(hToken);
764  return false;
765  }
766 
767  // Try to open process handle again
768  hProcess = ::OpenProcess(PROCESS_TERMINATE, FALSE, GetPid());
769 
770  // Restore original privilege state
771  ::AdjustTokenPrivileges(hToken, FALSE, &tp_prev, sizeof(tp_prev), NULL, NULL);
772  ::CloseHandle(hToken);
773  }
774  }
775 
776  // Check process handle
777  if ( !hProcess || hProcess == INVALID_HANDLE_VALUE ) {
778  return true;
779  }
780  // Terminate process
781  bool terminated = false;
782 
783  CStopWatch timer;
784  if ( safe ) {
785  timer.Start();
786  }
787  // Safe process termination
788  if ( safe && allow_wait ) {
789  // kernel32.dll loaded at same address in each process,
790  // so call ::ExitProcess() there.
791  FARPROC exitproc = ::GetProcAddress(::GetModuleHandleA("KERNEL32.DLL"), "ExitProcess");
792  if ( exitproc ) {
793  hThread = ::CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)exitproc, 0, 0, 0);
794  // Wait until process terminated, or timeout expired
795  if (hThread &&
796  (::WaitForSingleObject(hProcess, timeout) == WAIT_OBJECT_0)){
797  terminated = true;
798  }
799  }
800  }
801  // Try harder to kill stubborn process
802  if ( !terminated ) {
803  if ( ::TerminateProcess(hProcess, -1) != 0 ) {
804  DWORD err = ::GetLastError();
805  if (err == ERROR_INVALID_HANDLE) {
806  // If process "terminated" successfully or error occur but
807  // process handle became invalid -- process has terminated
808  terminated = true;
809  }
810  else {
812  }
813  }
814  else {
816  }
817  }
818  if (safe && terminated) {
819  // The process terminating now.
820  // Reset flag, and wait for real process termination.
821 
822  terminated = false;
823  double elapsed = timer.Elapsed() * kMilliSecondsPerSecond;
824  unsigned long linger_timeout = (elapsed < timeout) ?
825  (unsigned long)((double)timeout - elapsed) : 0;
826 
827  for (;;) {
828  if ( !IsAlive() ) {
829  terminated = true;
830  break;
831  }
832  unsigned long x_sleep = kWaitPrecisionMs;
833  if (x_sleep > linger_timeout) {
834  x_sleep = linger_timeout;
835  }
836  if ( !x_sleep ) {
837  break;
838  }
839  SleepMilliSec(x_sleep);
840  linger_timeout -= x_sleep;
841  }
842  }
843  // Close temporary process handle
844  if ( hThread ) {
845  ::CloseHandle(hThread);
846  }
847  x_CloseHandle(hProcess);
848  return terminated;
849 
850 #endif
851 }
852 
853 
854 #ifdef NCBI_OS_MSWIN
855 
856 // MS Windows:
857 // A helper function for terminating all processes
858 // in the tree within specified timeout.
859 // If 'timer' is specified, use safe process termination.
860 
861 static bool s_Win_KillGroup(DWORD pid, CStopWatch *timer, unsigned long &timeout)
862 {
863  // Open snapshot handle.
864  // We cannot use one snapshot for recursive calls,
865  // because it is not reentrant.
866  HANDLE const snapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
867  if (snapshot == INVALID_HANDLE_VALUE) {
868  return false;
869  }
870  PROCESSENTRY32 entry;
871  entry.dwSize = sizeof(PROCESSENTRY32);
872 
873  // Terminate all children first
874  if (!::Process32First(snapshot, &entry)) {
875  ::CloseHandle(snapshot);
876  return false;
877  }
878  do {
879  if (entry.th32ParentProcessID == pid) {
880  // Safe termination -- update timeout
881  if ( timer ) {
882  double elapsed = timer->Elapsed() * kMilliSecondsPerSecond;
883  timeout = (elapsed < timeout) ? (unsigned long)((double)timeout - elapsed) : 0;
884  if ( !timeout ) {
885  ::CloseHandle(snapshot);
886  return false;
887  }
888  }
889  bool res = s_Win_KillGroup(entry.th32ProcessID, timer, timeout);
890  if ( !res ) {
891  ::CloseHandle(snapshot);
892  return false;
893  }
894  }
895  }
896  while (::Process32Next(snapshot, &entry));
897 
898  // Terminate the specified process
899 
900  // Safe termination -- update timeout
901  if ( timer ) {
902  double elapsed = timer->Elapsed() * kMilliSecondsPerSecond;
903  timeout = (elapsed < timeout) ? (unsigned long)((double)timeout - elapsed) : 0;
904  if ( !timeout ) {
905  ::CloseHandle(snapshot);
906  return false;
907  }
908  }
909  bool res = CProcess(pid, CProcess::ePid).Kill(timeout);
910 
911  // Close snapshot handle
912  ::CloseHandle(snapshot);
913  return res;
914 }
915 
916 #endif //NCBI_OS_MSWIN
917 
918 
919 bool CProcess::KillGroup(unsigned long timeout) const
920 {
921 #if defined(NCBI_OS_UNIX)
922 
923  TPid pgid = getpgid(GetPid());
924  if (pgid == (TPid)(-1)) {
926  // TRUE if PID does not match any process
927  return errno == ESRCH;
928  }
929  return KillGroupById(pgid, timeout);
930 
931 #elif defined(NCBI_OS_MSWIN)
932 
933  TPid pid = x_GetPid();
934  if (!pid) {
935  return false;
936  }
937  // Use safe process termination if timeout > 0
938  unsigned long x_timeout = timeout;
939  CStopWatch timer;
940  if ( timeout ) {
941  timer.Start();
942  }
943  // Kill process tree
944  bool result = s_Win_KillGroup(pid, (timeout > 0) ? &timer : 0, x_timeout);
945  return result;
946 
947 #endif
948 }
949 
950 
951 bool CProcess::KillGroupById(TPid pgid, unsigned long timeout)
952 {
953 #if defined(NCBI_OS_UNIX)
954 
955  // Try to kill the process group with SIGTERM first
956  if (kill(-pgid, SIGTERM) < 0 && errno == EPERM) {
958  return false;
959  }
960 
961  // Check process group termination within the timeout
962  unsigned long x_timeout = timeout;
963  for (;;) {
964  // Reap the zombie (if group leader is a child) up from the system
965  TPid reap = waitpid(pgid, 0, WNOHANG);
966  if (reap) {
967  if (reap != (TPid)(-1)) {
968  _ASSERT(reap == pgid);
969  return true;
970  }
971  if (errno != ECHILD) {
973  return false;
974  }
975  if (kill(-pgid, 0) < 0) {
976  return true;
977  }
978  }
979  unsigned long x_sleep = kWaitPrecisionMs;
980  if (x_sleep > x_timeout) {
981  x_sleep = x_timeout;
982  }
983  if ( !x_sleep ) {
984  break;
985  }
986  SleepMilliSec(x_sleep);
987  x_timeout -= x_sleep;
988  }
989  _ASSERT(!x_timeout);
990 
991  // Try harder to kill the stubborn processes -- SIGKILL may not be caught!
992  int res = kill(-pgid, SIGKILL);
993  if ( !timeout ) {
994  return res <= 0;
995  }
997  // Reap the zombie (if group leader is a child) up from the system
998  waitpid(pgid, 0, WNOHANG);
999  // Check whether the process cannot be killed
1000  // (most likely due to a kernel problem)
1001  return kill(-pgid, 0) < 0;
1002 
1003 #else
1004  // Cannot be implemented, use non-static version of KillGroup()
1005  // for specified process.
1007  return false;
1008 
1009 #endif
1010 }
1011 
1012 
1013 int CProcess::Wait(unsigned long timeout, CExitInfo* info) const
1014 {
1015  int status;
1016 
1017  // Reset extended information
1018  if (info) {
1019  info->state = eExitInfo_Unknown;
1020  info->status = 0;
1021  }
1022 
1023 #if defined(NCBI_OS_UNIX)
1024 
1025  TPid pid = GetPid();
1026  int options = timeout == kInfiniteTimeoutMs ? 0 : WNOHANG;
1027 
1028  // Check process termination (with timeout or indefinitely)
1029  for (;;) {
1030  TPid ws = waitpid(pid, &status, options);
1031  if (ws > 0) {
1032  // terminated
1033  _ASSERT(ws == pid);
1034  if (info) {
1035  info->state = eExitInfo_Terminated;
1036  info->status = status;
1037  }
1038  return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
1039  } else if (ws == 0) {
1040  // still running
1041  _ASSERT(timeout != kInfiniteTimeoutMs);
1042  if ( !timeout ) {
1043  if (info) {
1044  info->state = eExitInfo_Alive;
1045  }
1046  break;
1047  }
1048  unsigned long x_sleep = kWaitPrecisionMs;
1049  if (x_sleep > timeout) {
1050  x_sleep = timeout;
1051  }
1052  SleepMilliSec(x_sleep);
1053  timeout -= x_sleep;
1054  } else if (errno != EINTR) {
1056  // error
1057  break;
1058  }
1059  }
1060  return -1;
1061 
1062 #elif defined(NCBI_OS_MSWIN)
1063 
1064  bool allow_wait = true;
1065 
1066  // Get process handle
1067  HANDLE hProcess = x_GetHandle(PROCESS_QUERY_INFORMATION | SYNCHRONIZE);
1068  if (!hProcess) {
1069  // SYNCHRONIZE access right is required to wait for the process to terminate
1070  allow_wait = false;
1071  // Try to open with minimal access right needed to terminate process.
1072  hProcess = x_GetHandle(PROCESS_TERMINATE);
1073  if (!hProcess) {
1074  return -1;
1075  }
1076  }
1077  status = -1;
1078  DWORD x_status;
1079  // Is process still running?
1080  if (::GetExitCodeProcess(hProcess, &x_status)) {
1081  if (x_status == STILL_ACTIVE) {
1082  if (allow_wait && timeout) {
1083  DWORD tv = (timeout == kInfiniteTimeoutMs
1084  ? INFINITE : (DWORD) timeout);
1085  DWORD ws = ::WaitForSingleObject(hProcess, tv);
1086  switch(ws) {
1087  case WAIT_TIMEOUT:
1088  // still running
1089  _ASSERT(x_status == STILL_ACTIVE);
1090  break;
1091  case WAIT_OBJECT_0:
1092  if (::GetExitCodeProcess(hProcess, &x_status)) {
1093  if (x_status != STILL_ACTIVE) {
1094  // terminated
1095  status = 0;
1096  } // else still running
1097  break;
1098  }
1099  /*FALLTHRU*/
1100  default:
1101  // error
1102  x_status = 0;
1103  break;
1104  }
1105  } // else still running
1106  } else {
1107  // terminated
1108  status = 0;
1109  }
1110  } else {
1111  // error
1112  x_status = 0;
1113  }
1114 
1115  if (status < 0) {
1116  if (info && x_status == STILL_ACTIVE) {
1117  info->state = eExitInfo_Alive;
1118  }
1119  } else {
1120  if (info) {
1121  info->state = eExitInfo_Terminated;
1122  info->status = x_status;
1123  }
1124  status = x_status;
1125  }
1126  x_CloseHandle(hProcess);
1127 
1128  return status;
1129 
1130 #endif
1131 }
1132 
1133 
1134 #if defined(NCBI_OS_MSWIN)
1135 bool s_Win_GetHandleTimes(HANDLE handle, double* real, double* user, double* sys, CProcess::EWhat what)
1136 {
1137  // Each FILETIME structure contains the number of 100-nanosecond time units
1138  FILETIME ft_creation, ft_exit, ft_kernel, ft_user;
1139  BOOL res = FALSE;
1140 
1141  if (what == CProcess::eProcess) {
1142  res = ::GetProcessTimes(handle, &ft_creation, &ft_exit, &ft_kernel, &ft_user);
1143  } else
1144  if (what == CProcess::eThread) {
1145  res = ::GetThreadTimes(handle, &ft_creation, &ft_exit, &ft_kernel, &ft_user);
1146  }
1147  if (!res) {
1149  return false;
1150  }
1151  if ( real ) {
1152  // Calculate real execution time using handle creation time.
1153  // clock() is less accurate on Windows, so use GetSystemTime*().
1154  // <real time> = <system time> - <creation time>
1155 
1156  FILETIME ft_sys;
1157  ::GetSystemTimeAsFileTime(&ft_sys);
1158  // Calculate a difference between FILETIME variables
1159  ULARGE_INTEGER i;
1160  __int64 v2, v1, v_diff;
1161  i.LowPart = ft_creation.dwLowDateTime;
1162  i.HighPart = ft_creation.dwHighDateTime;
1163  v1 = i.QuadPart;
1164  i.LowPart = ft_sys.dwLowDateTime;
1165  i.HighPart = ft_sys.dwHighDateTime;
1166  v2 = i.QuadPart;
1167  v_diff = v2 - v1;
1168  i.QuadPart = v_diff;
1169  *real = (i.LowPart + ((Uint8)i.HighPart << 32)) * 1.0e-7;
1170  }
1171  if ( sys ) {
1172  *sys = (ft_kernel.dwLowDateTime + ((Uint8) ft_kernel.dwHighDateTime << 32)) * 1e-7;
1173  }
1174  if ( user ) {
1175  *user = (ft_user.dwLowDateTime + ((Uint8) ft_user.dwHighDateTime << 32)) * 1e-7;
1176  }
1177  return true;
1178 }
1179 #endif
1180 
1181 
1182 #if defined(NCBI_OS_LINUX)
1183 bool s_Linux_GetTimes_ProcStat(TPid pid, double* real, double* user, double* sys, CProcess::EWhat what)
1184 {
1185  // No need to set errno in this function, it have a backup that overwrite it
1186  if (what == CProcess::eThread) {
1187  return false;
1188  }
1189  clock_t tick = CSystemInfo::GetClockTicksPerSecond();
1190  if ( !tick ) {
1191  return false;
1192  }
1193 
1194  // Execution times can be extracted from /proc/<pid>/stat,
1195  CLinuxFeature::CProcStat ps(pid);
1196 
1197  // Fields numbers in that file, for process itself and children.
1198  // Note, all values can be zero.
1199  int fu = 14, fs = 15;
1200  if (what == CProcess::eChildren) {
1201  fu = 16;
1202  fs = 17;
1203  }
1204  if ( real && (what == CProcess::eProcess) ) {
1205  // Real execution time can be calculated for the process only.
1206  // field 22, in ticks per second.
1207  Uint8 start = NStr::StringToUInt8(ps.at(22), NStr::fConvErr_NoThrow);
1208  double uptime = CSystemInfo::GetUptime();
1209  if (start > 0 && uptime > 0) {
1210  *real = uptime - (double)start / (double)tick;
1211  }
1212  }
1213  if ( user ) {
1214  *user = (double)NStr::StringToUInt8(ps.at(fu), NStr::fConvErr_NoThrow) / (double)tick;
1215  }
1216  if ( sys ) {
1217  *sys = (double)NStr::StringToUInt8(ps.at(fs), NStr::fConvErr_NoThrow) / (double)tick;
1218  }
1219  return true;
1220 
1221 }
1222 #endif
1223 
1224 
1225 bool CProcess::GetTimes(double* real, double* user, double* sys, CProcess::EWhat what)
1226 {
1227  // Use CCurrentProcess::GetProcessTimes() for the current process,
1228  // it can give better results than "generic" version.
1229  if ( IsCurrent() ) {
1230  return CCurrentProcess::GetTimes(real, user, sys, what);
1231  }
1232 
1233  const double kUndefined = -1.0;
1234  if ( real ) { *real = kUndefined; }
1235  if ( user ) { *user = kUndefined; }
1236  if ( sys ) { *sys = kUndefined; }
1237 
1238  if (what == eThread) {
1239  // We don't have information about thread times for some arbitrary process.
1241  return false;
1242  }
1243 
1244 #if defined(NCBI_OS_MSWIN)
1245 
1246  if (what == eChildren) {
1247  // On Windows we have information for the process only.
1248  // TODO: Try to implemented this for children.
1250  return false;
1251  }
1252  // Get process handle
1253  HANDLE hProcess = x_GetHandle(PROCESS_QUERY_INFORMATION | SYNCHRONIZE);
1254  if (!hProcess) {
1255  return false;
1256  }
1257  bool res = s_Win_GetHandleTimes(hProcess, real, user, sys, CProcess::eProcess);
1258  x_CloseHandle(hProcess);
1259  return res;
1260 
1261 #elif defined(NCBI_OS_UNIX)
1262 
1263 # if defined(NCBI_OS_LINUX)
1264  return s_Linux_GetTimes_ProcStat(GetPid(), real, user, sys, what);
1265 # else
1266  // TODO: Investigate how to determine this on other Unix flavors
1267 # endif //NCBI_OS_LINUX
1268 
1269 #endif //NCBI_OS_UNIX
1270 
1272  return false;
1273 }
1274 
1275 
1276 bool CCurrentProcess::GetTimes(double* real, double* user, double* sys, EWhat what)
1277 {
1278  const double kUndefined = -1.0;
1279  if ( real ) { *real = kUndefined; }
1280  if ( user ) { *user = kUndefined; }
1281  if ( sys ) { *sys = kUndefined; }
1282 
1283 #if defined(NCBI_OS_MSWIN)
1284 
1285  if (what == eChildren) {
1286  // We have information for the process/thread only.
1287  // TODO: Try to implemented this for children.
1289  return false;
1290  }
1291  bool res;
1292  if (what == eProcess) {
1293  res = s_Win_GetHandleTimes(::GetCurrentProcess(), real, user, sys, CProcess::eProcess);
1294  } else {
1295  res = s_Win_GetHandleTimes(::GetCurrentThread(), real, user, sys, CProcess::eThread);
1296  }
1297  return res;
1298 
1299 #elif defined(NCBI_OS_UNIX)
1300 
1301  // UNIX: Real execution time can be calculated on Linux and for the process only.
1302  // TODO: Investigate how to determine this on other Unix flavors
1303 
1304 # if defined(NCBI_OS_LINUX)
1305  // Try Linux specific first, before going to more generic methods
1306  // For process and cildren only.
1307  if (what != eThread) {
1308  if ( s_Linux_GetTimes_ProcStat(0 /*current pid*/, real, user, sys, what) ) {
1309  return true;
1310  }
1311  }
1312 # endif
1313 # if defined(HAVE_GETRUSAGE)
1314  int who = RUSAGE_SELF;
1315  switch ( what ) {
1316  case eProcess:
1317  // who = RUSAGE_SELF
1318  break;
1319  case eChildren:
1320  who = RUSAGE_CHILDREN;
1321  break;
1322  case eThread:
1323  #ifdef RUSAGE_THREAD
1324  who = RUSAGE_THREAD;
1325  break;
1326  #else
1328  return false;
1329  #endif
1330  default:
1331  _TROUBLE;
1332  }
1333  struct rusage ru;
1334  memset(&ru, '\0', sizeof(ru));
1335  if (getrusage(who, &ru) != 0) {
1337  return false;
1338  }
1339  if ( user ) {
1340  *user = double(ru.ru_utime.tv_sec) + double(ru.ru_utime.tv_usec) / 1e6;
1341  }
1342  if ( sys ) {
1343  *sys = double(ru.ru_stime.tv_sec) + double(ru.ru_stime.tv_usec) / 1e6;
1344  }
1345  return true;
1346 
1347 # else
1348  // Backup to times()
1349  if (what != eProcess) {
1350  // We have information for the current process only
1352  return false;
1353  }
1354  tms buf;
1355  clock_t t = times(&buf);
1356  if ( t == (clock_t)(-1) ) {
1358  return false;
1359  }
1360  clock_t tick = CSystemInfo::GetClockTicksPerSecond();
1361  if ( !tick ) {
1362  // GetClockTicksPerSecond() already set CNcbiError
1363  return false;
1364  }
1365  if ( user ) {
1366  *user = (double)buf.tms_utime / (double)tick;
1367  }
1368  if ( sys ) {
1369  *sys = (double)buf.tms_stime / (double)tick;
1370  }
1371  return true;
1372 
1373 # endif //HAVE_GETRUSAGE
1374 #endif //NCBI_OS_UNIX
1375 
1377  return false;
1378 }
1379 
1380 
1381 #if defined(NCBI_OS_MSWIN)
1382 
1383 // PSAPI structure PROCESS_MEMORY_COUNTERS
1393  SIZE_T PagefileUsage; // same as PrivateUsage in PROCESS_MEMORY_COUNTERS_EX
1395 };
1396 
1398 {
1399  try {
1400  // Use GetProcessMemoryInfo() from psapi.dll.
1401  // Headers/libs to use this API directly may be missed, so we don't use it, but DLL exists usually.
1402  CDll psapi_dll("psapi.dll", CDll::eLoadNow, CDll::eAutoUnload);
1403  BOOL (STDMETHODCALLTYPE FAR * dllGetProcessMemoryInfo)
1404  (HANDLE process, SProcessMemoryCounters& counters, DWORD size) = 0;
1405  dllGetProcessMemoryInfo = psapi_dll.GetEntryPoint_Func("GetProcessMemoryInfo", &dllGetProcessMemoryInfo);
1406  if (dllGetProcessMemoryInfo) {
1407  if ( !dllGetProcessMemoryInfo(handle, memcounters, sizeof(memcounters)) ) {
1409  return false;
1410  }
1411  return true;
1412  }
1413  } catch (CException) {
1414  // Just catch all exceptions from CDll
1415  }
1416  // Cannot get entry point for GetProcessMemoryInfo()
1418  return false;
1419 }
1420 
1421 // On Windows we count the "working set" only.
1422 // The "working set" of a process is the set of memory pages currently visible
1423 // to the process in physical RAM memory. These pages are resident and available
1424 // for an application to use without triggering a page fault.
1425 //
1426 // https://docs.microsoft.com/en-us/windows/desktop/api/psapi/ns-psapi-_process_memory_counters
1427 // https://docs.microsoft.com/en-us/windows/desktop/Memory/working-set
1428 
1430 {
1432  if (!s_Win_GetMemoryCounters(handle, mc)) {
1433  return false;
1434  }
1435  usage.total = mc.WorkingSetSize + mc.PagefileUsage;
1436  usage.total_peak = mc.PeakWorkingSetSize + mc.PeakPagefileUsage;
1437  usage.resident = mc.WorkingSetSize;
1438  usage.resident_peak = mc.PeakWorkingSetSize;
1439  usage.swap = mc.PagefileUsage;
1440  return true;
1441 }
1442 #endif
1443 
1444 
1446 {
1447  // Use CCurrentProcess::GetMemoryUsage() for the current process,
1448  // it can give better results than "generic" version.
1449  if ( IsCurrent() ) {
1451  }
1452  memset(&usage, 0, sizeof(usage));
1453 
1454 #if defined(NCBI_OS_MSWIN)
1455  // Get process handle
1456  HANDLE hProcess = x_GetHandle(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ);
1457  if (!hProcess) {
1458  return false;
1459  }
1460  bool res = s_Win_GetMemoryUsage(hProcess, usage);
1461  x_CloseHandle(hProcess);
1462  return res;
1463 
1464 #elif defined(NCBI_OS_LINUX)
1466 
1467 #else
1469 #endif
1470  return false;
1471 }
1472 
1473 
1475 {
1476  memset(&usage, 0, sizeof(usage));
1477 
1478 #if defined(NCBI_OS_MSWIN)
1479  return s_Win_GetMemoryUsage(::GetCurrentProcess(), usage);
1480 
1481 #elif defined(NCBI_OS_LINUX)
1482  // Many getrusage() memory related info fields are unmaintained on Linux,
1483  // so read it directly from procfs.
1484  return CLinuxFeature::GetMemoryUsage(0 /*current pid*/, usage);
1485 
1486 #elif defined(NCBI_OS_SOLARIS)
1487  Int8 len = CFile("/proc/self/as").GetLength();
1488  if (len > 0) {
1489  usage.total = (size_t) len;
1490  usage.resident = (size_t) len; // conservative estimate
1491  return true;
1492  }
1494 
1495 #elif defined(HAVE_GETRUSAGE)
1496  //
1497  // BIG FAT NOTE !!!:
1498  //
1499  // We will use peak RSS value only (ru_maxrss) here. And even it can
1500  // use different size units depends on OS. Seems it is in KB usually,
1501  // but on Darwin it measured in bytes. All other memory related fields
1502  // of rusage structure are an "integral" values, measured in
1503  // "kilobytes * ticks-of-execution", whatever that means. Seems no one
1504  // know how to use this. Probably it is good for measure relative memory
1505  // usage, measure what happens between 2 getrusage() calls, but it is
1506  // useless to get "current" memory usage. Most platforms don't maintain
1507  // these values at all and all fields are set 0, other like BSD, shows
1508  // inconsistent results in tests, and it is impossible to make a relation
1509  // between its values and real memory usage.
1510  //
1511  // Interesting related article to read:
1512  // https://www.perlmonks.org/?node_id=626693
1513 
1514  #if defined(NCBI_OS_LINUX_)
1515  // NOTE: NCBI_OS_LINUX should go first to get more reliable results on Linux
1516  // We process Linux separately, so ignore it here.
1517  // JFY, on Linux:
1518  // ru_maxrss - are in kilobytes
1519  // other - unknown, values are unmaintained and docs says nothing about unit size.
1520  _TROUBLE;
1521  #endif
1522 
1523  struct rusage ru;
1524  memset(&ru, '\0', sizeof(ru));
1525  if (getrusage(RUSAGE_SELF, &ru) == 0) {
1526 
1527  #if defined(NCBI_OS_DARWIN)
1528  // ru_maxrss - in bytes
1529  usage.total_peak = ru.ru_maxrss;
1530  usage.resident_peak = ru.ru_maxrss;
1531  #else
1532  // Use BSD rules, ru_maxrss - in kilobytes
1533  usage.resident_peak = ru.ru_maxrss * 1024;
1534  usage.total_peak = ru.ru_maxrss * 1024;
1535  #endif
1536  #if !defined(NCBI_OS_DARWIN)
1537  // We have workaround on Darwin, see below.
1538  // For other -- exist
1539  return true;
1540  #endif
1541 
1542  #if 0 /* disabled for now, probably need to revisit later */
1543  // calculates "kilobytes * ticks-of-execution"
1544  #define KBT(v) ((v) * 1024 / ticks)
1545  usage.total = KBT(ru.ru_ixrss + ru.ru_idrss + ru.ru_isrss);
1546  if (usage.total > 0) {
1547  // If total is 0, all other walues doesn't matter.
1548  usage.total_peak = KBT(ru.ru_ixrss + ru.ru_isrss) + maxrss;
1549  usage.resident = KBT(ru.ru_idrss);
1550  usage.resident_peak = maxrss;
1551  usage.shared = KBT(ru.ru_ixrss);
1552  usage.data = KBT(ru.ru_idrss);
1553  usage.stack = KBT(ru.ru_isrss);
1554  } else {
1555  // Use peak values only
1556  usage.total_peak = maxrss;
1557  usage.resident_peak = maxrss;
1558  }
1559  #undef KBT
1560  #if defined(NCBI_OS_DARWIN)
1561  if (usage.total > 0) {
1562  return true;
1563  }
1564  #else
1565  return true;
1566  #endif
1567  #endif // 0
1568  } // getrusage
1569 
1570  #if defined(NCBI_OS_DARWIN)
1571  // Alternative workaround for DARWIN if no getrusage()
1572  #ifdef MACH_TASK_BASIC_INFO
1573  task_flavor_t flavor = MACH_TASK_BASIC_INFO;
1574  struct mach_task_basic_info t_info;
1575  mach_msg_type_number_t t_info_count = MACH_TASK_BASIC_INFO_COUNT;
1576  #else
1577  task_flavor_t flavor = TASK_BASIC_INFO;
1578  struct task_basic_info t_info;
1579  mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;
1580  #endif
1581  if (task_info(mach_task_self(), flavor, (task_info_t)&t_info, &t_info_count) == KERN_SUCCESS) {
1582  usage.total = t_info.resident_size;
1583  usage.resident = t_info.resident_size;
1584  if (usage.total_peak < usage.total) {
1585  usage.total_peak = usage.total;
1586  }
1587  if (usage.resident_peak < usage.resident) {
1588  usage.resident_peak = usage.resident;
1589  }
1590  // shared memory and etc are unavailable, even with mach_task_basic_info
1591  return true;
1592  }
1594  #else
1596  #endif
1597 
1598 #else
1600 #endif
1601  return false;
1602 }
1603 
1604 
1606 {
1607 #if defined(NCBI_OS_MSWIN)
1608  PROCESSENTRY32 entry;
1609  if (CWinFeature::FindProcessEntry(x_GetPid(), entry)) {
1610  return entry.cntThreads;
1611  }
1613 
1614 #elif defined(NCBI_OS_LINUX)
1615  return CLinuxFeature::GetThreadCount(GetPid());
1616 
1617 #else
1619 #endif
1620  return -1;
1621 }
1622 
1623 
1625 {
1626 #if defined(NCBI_OS_MSWIN)
1627  PROCESSENTRY32 entry;
1628  if (CWinFeature::FindProcessEntry(::GetCurrentProcessId(), entry)) {
1629  return entry.cntThreads;
1630  }
1632 
1633 #elif defined(NCBI_OS_LINUX)
1634  return CLinuxFeature::GetThreadCount();
1635 
1636 #else
1638 #endif
1639  return -1;
1640 }
1641 
1642 
1644 {
1645  // Use CCurrentProcess::GetFileDescriptorsCount() for the current process,
1646  // it can give better results than "generic" version.
1647  if ( IsCurrent() ) {
1649  }
1650 #if defined(NCBI_OS_LINUX)
1651  return CLinuxFeature::GetFileDescriptorsCount(GetPid());
1652 #else
1654 #endif
1655  return -1;
1656 }
1657 
1658 
1659 int CCurrentProcess::GetFileDescriptorsCount(int* soft_limit, int* hard_limit)
1660 {
1661 #if defined(NCBI_OS_UNIX)
1662  int n = -1;
1663 
1664  // Get limits first
1665  struct rlimit rlim;
1666  rlim_t cur_limit = -1;
1667  rlim_t max_limit = -1;
1668  if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) {
1669  cur_limit = rlim.rlim_cur;
1670  max_limit = rlim.rlim_max;
1671  } else {
1672  // fallback to sysconf()
1673  cur_limit = static_cast<rlim_t>(sysconf(_SC_OPEN_MAX));
1674  }
1675  if (soft_limit) {
1676  *soft_limit = (cur_limit > INT_MAX) ? INT_MAX : static_cast<int>(cur_limit);
1677  }
1678  if (hard_limit) {
1679  *hard_limit = (max_limit > INT_MAX) ? INT_MAX : static_cast<int>(max_limit);
1680  }
1681 
1682  #if defined(NCBI_OS_LINUX)
1683  n = CLinuxFeature::GetFileDescriptorsCount(GetPid());
1684  #endif
1685  // Fallback to analysis via looping over all fds
1686  if (n < 0 && cur_limit > 0) {
1687  int max_fd = (cur_limit > INT_MAX) ? INT_MAX : static_cast<int>(cur_limit);
1688  for (int fd = 0; fd < max_fd; ++fd) {
1689  if (fcntl(fd, F_GETFD, 0) == -1) {
1690  if (errno == EBADF) {
1691  continue;
1692  }
1693  }
1694  ++n;
1695  }
1696  }
1697  if (n >= 0) {
1698  return n;
1699  }
1701 
1702 #else
1703  if ( soft_limit ) { *soft_limit = -1; }
1704  if ( hard_limit ) { *hard_limit = -1; }
1706 #endif
1707 
1708  return -1;
1709 }
1710 
1711 
1712 /////////////////////////////////////////////////////////////////////////////
1713 //
1714 // CPIDGuard
1715 //
1716 // NOTE: CPIDGuard know nothing about PID file modification or deletion
1717 // by other processes behind this API. Be aware.
1718 
1719 
1720 CPIDGuard::CPIDGuard(const string& filename)
1721  : m_PID(0)
1722 {
1723  string dir;
1724  CDirEntry::SplitPath(filename, &dir, 0, 0);
1725  if (dir.empty()) {
1727  } else {
1728  m_Path = filename;
1729  }
1730  // Create guard for MT-Safe protect
1731  m_MTGuard.reset(new CInterProcessLock(m_Path + ".guard"));
1732  // Update PID
1733  UpdatePID();
1734 }
1735 
1736 
1737 /// @deprecated
1738 CPIDGuard::CPIDGuard(const string& filename, const string& dir)
1739  : m_PID(0)
1740 {
1741  string real_dir;
1742  CDirEntry::SplitPath(filename, &real_dir, 0, 0);
1743  if (real_dir.empty()) {
1744  if (dir.empty()) {
1745  real_dir = CDir::GetTmpDir();
1746  } else {
1747  real_dir = dir;
1748  }
1749  m_Path = CDirEntry::MakePath(real_dir, filename);
1750  } else {
1751  m_Path = filename;
1752  }
1753  // Create guard for MT-Safe protect
1754  m_MTGuard.reset(new CInterProcessLock(m_Path + ".guard"));
1755  // Update PID
1756  UpdatePID();
1757 }
1758 
1759 
1761 {
1762  Release();
1763  m_MTGuard.reset();
1764  m_PIDGuard.reset();
1765 }
1766 
1767 
1769 {
1770  if ( m_Path.empty() ) {
1771  return;
1772  }
1773  // MT-Safe protect
1775 
1776  // Read info
1777  TPid pid = 0;
1778  unsigned int ref = 0;
1779  CNcbiIfstream in(m_Path.c_str());
1780 
1781  if ( in.good() ) {
1782  in >> pid >> ref;
1783  in.close();
1784  if ( m_PID != pid ) {
1785  // We do not own this file more.
1786  return;
1787  }
1788  if ( ref ) {
1789  ref--;
1790  }
1791  // Check reference counter
1792  if ( ref ) {
1793  // Write updated reference counter into the file
1795  if ( out.good() ) {
1796  out << pid << endl << ref << endl;
1797  }
1798  if ( !out.good() ) {
1800  "Unable to write into PID file " + m_Path +": "
1801  + _T_CSTRING(NcbiSys_strerror(errno)));
1802  }
1803  } else {
1804  // Remove the file
1805  CDirEntry(m_Path).Remove();
1806  // Remove modification protect guard
1807  LOCK.Release();
1808  m_MTGuard->Remove();
1809  m_MTGuard.reset();
1810  // PID-file can be reused now
1811  if ( m_PIDGuard.get() ) {
1812  m_PIDGuard->Remove();
1813  m_PIDGuard.reset();
1814  }
1815  }
1816  }
1817  m_Path.erase();
1818 }
1819 
1820 
1822 {
1823  if ( m_Path.empty() ) {
1824  return;
1825  }
1826  // MT-Safe protect
1828 
1829  // Remove the PID file
1830  CDirEntry(m_Path).Remove();
1831  m_Path.erase();
1832  // Remove modification protect guard
1833  m_MTGuard->Remove();
1834  // PID-file can be reused now
1835  if ( m_PIDGuard.get() ) {
1836  m_PIDGuard->Remove();
1837  m_PIDGuard.reset();
1838  }
1839 }
1840 
1841 
1843 {
1844  if (pid == 0) {
1845  pid = CCurrentProcess::GetPid();
1846  }
1847  // MT-Safe protect
1849 
1850  // Check PID from valid PID-files only. The file can be left on
1851  // the file system from previous session. And stored in the file
1852  // PID can be already reused by OS.
1853 
1854  bool valid_file = true;
1855  unsigned int ref = 1;
1856 
1857  // Create guard for PID file
1858  if ( !m_PIDGuard.get() ) {
1859  // first call to Update() ?
1860  m_PIDGuard.reset(new CInterProcessLock(m_Path + ".start.guard"));
1861  // If the guard lock is successfully obtained, the existent PID file
1862  // can be a stale lock or a new unused file, so just reuse it.
1863  valid_file = !m_PIDGuard->TryLock();
1864  }
1865 
1866  if ( valid_file ) {
1867  // Read old PID
1868  CNcbiIfstream in(m_Path.c_str());
1869  if ( in.good() ) {
1870  TPid old_pid;
1871  in >> old_pid >> ref;
1872  if ( old_pid == pid ) {
1873  // Guard the same PID. Just increase the reference counter.
1874  ref++;
1875  } else {
1876  if ( CProcess(old_pid,CProcess::ePid).IsAlive() ) {
1877  NCBI_THROW2(CPIDGuardException, eStillRunning,
1878  "Process is still running", old_pid);
1879  }
1880  ref = 1;
1881  }
1882  }
1883  in.close();
1884  }
1885 
1886  // Write new PID
1888  if ( out.good() ) {
1889  out << pid << endl << ref << endl;
1890  }
1891  if ( !out.good() ) {
1893  "Unable to write into PID file " + m_Path + ": "
1894  + _T_CSTRING(NcbiSys_strerror(errno)));
1895  }
1896  // Save updated pid
1897  m_PID = pid;
1898 }
1899 
1900 
1902 {
1903  switch (GetErrCode()) {
1904  case eStillRunning: return "eStillRunning";
1905  case eWrite: return "eWrite";
1906  default: return CException::GetErrCodeString();
1907  }
1908 }
1909 
1910 
1911 
const char * usage
static int trunc
Definition: array_out.c:8
#define false
Definition: bool.h:36
CCoreException –.
Definition: ncbiexpt.hpp:1476
CCurrentProcess –.
CDirEntry –.
Definition: ncbifile.hpp:262
CDll –.
Definition: ncbidll.hpp:107
CFile –.
Definition: ncbifile.hpp:1604
void Release()
Manually force the resource to be released.
Definition: guard.hpp:166
CInterProcessLock –.
CPIDGuardException –.
Extended exit information for waited process.
CProcess –.
CStopWatch –.
Definition: ncbitime.hpp:1938
static clock_t GetClockTicksPerSecond(void)
Get number of (statistics) clock ticks per second.
static double GetUptime(void)
Get system uptime in seconds.
static bool FindProcessEntry(DWORD id, PROCESSENTRY32 &entry)
Find process entry information by process identifier (pid).
static uch flags
int close(int fd)
Definition: connection.cpp:45
std::ofstream out("events_result.xml")
main entry point for tests
@ eTriState_False
The value is equivalent to false/no.
Definition: ncbimisc.hpp:130
@ eTriState_True
The value is equivalent to true/yes.
Definition: ncbimisc.hpp:131
@ eTriState_Unknown
The value is indeterminate.
Definition: ncbimisc.hpp:129
string
Definition: cgiapp.hpp:687
#define NULL
Definition: ncbistd.hpp:225
#define FAR
Definition: ncbistd.hpp:278
CDiagContext & GetDiagContext(void)
Get diag context instance.
Definition: logging.cpp:818
#define ERR_POST_X(err_subcode, message)
Error posting with default error code and given error subcode.
Definition: ncbidiag.hpp:550
static void UpdateOnFork(TOnForkFlags flags)
Update diagnostics after fork().
Definition: logging.cpp:824
void PrintStop(void)
Print exit message.
Definition: ncbidiag.cpp:2139
@ fOnFork_ResetTimer
Reset execution timer.
Definition: ncbidiag.hpp:1946
@ fOnFork_AsyncSafe
After a fork() in a multithreaded program, the child can safely call only async-signal-safe functions...
Definition: ncbidiag.hpp:1947
@ fOnFork_PrintStart
Log app-start.
Definition: ncbidiag.hpp:1945
TFunc GetEntryPoint_Func(const string &name, TFunc *func)
Get DLLs entry point (function).
Definition: ncbidll.hpp:273
@ eAutoUnload
Definition: ncbidll.hpp:144
@ eLoadNow
Definition: ncbidll.hpp:138
static void SetFromWindowsError(void)
Set last error on MS Windows using GetLastError()
Definition: ncbierror.cpp:290
#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:704
static void SetFromErrno(void)
Set last error using current "errno" code.
Definition: ncbierror.cpp:220
void Warning(CExceptionArgs_Base &args)
Definition: ncbiexpt.hpp:1191
static void Set(ECode code)
Set last error using native error code enum.
Definition: ncbierror.cpp:160
#define NCBI_THROW2(exception_class, err_code, message, extra)
Throw exception with extra parameter.
Definition: ncbiexpt.hpp:1754
#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:719
static void SetWindowsError(int native_err_code)
Set last error using Windows-specific error code.
Definition: ncbierror.cpp:260
virtual const char * GetErrCodeString(void) const
Get error code interpreted as text.
Definition: ncbiexpt.cpp:444
virtual const char * what(void) const noexcept
Standard report (includes full backlog).
Definition: ncbiexpt.cpp:342
@ eAddressNotAvailable
Definition: ncbierror.hpp:60
@ eUnknown
Unknown error.
Definition: ncbierror.hpp:138
static string GetTmpDir(void)
Get temporary directory.
Definition: ncbifile.cpp:3660
Int8 GetLength(void) const
Get size of file.
Definition: ncbifile.cpp:3204
virtual bool Remove(TRemoveFlags flags=eRecursive) const
Remove a directory entry.
Definition: ncbifile.cpp:2595
static string MakePath(const string &dir=kEmptyStr, const string &base=kEmptyStr, const string &ext=kEmptyStr)
Assemble a path from basic components.
Definition: ncbifile.cpp:413
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:358
const CVect2< U > & v2
Definition: globals.hpp:440
int intptr_t
Definition: ncbitype.h:185
int64_t Int8
8-byte (64-bit) signed integer
Definition: ncbitype.h:104
uint64_t Uint8
8-byte (64-bit) unsigned integer
Definition: ncbitype.h:105
virtual const char * GetErrCodeString(void) const override
bool GetTimes(double *real, double *user, double *sys, EWhat what=eProcess)
Get process execution times.
static int GetThreadCount(void)
Get the number of threads in the current process.
TPid x_GetPid(void) const
~CPIDGuard(void)
Destructor.
CPIDGuard(const string &filename)
Create/check PID file.
TProcessHandle x_GetHandle(DWORD desired_access, DWORD *errcode=0) const
static TPid GetParentPid(void)
Get process identifier (pid) for the parent of the current process.
bool IsPresent(void) const
TRUE if the object contains information about the process state.
EType
How to interpret the used process identifier.
TProcessHandle GetHandle(void) const
Get stored process handle.
static bool KillGroupById(TPid pgid, unsigned long timeout=kDefaultKillTimeout)
Terminate a group of processes with specified ID.
bool KillGroup(unsigned long timeout=kDefaultKillTimeout) const
Terminate a group of processes.
static TPid GetPid(void)
Get process identifier (pid) for the current process.
int status
Process status information.
unsigned int TDaemonFlags
Bit-wise OR of FDaemonFlags.
static int GetFileDescriptorsCount(int *soft_limit=NULL, int *hard_limit=NULL)
Get the number of file descriptors consumed by the current process, and optional system wide file des...
TPid TProcessHandle
EType m_Type
Type of process identifier.
EWhat
Process information "target".
unique_ptr< CInterProcessLock > m_MTGuard
bool IsExited(void) const
TRUE if the process terminated normally.
string m_Path
bool GetMemoryUsage(SMemoryUsage &usage)
Get process memory usage.
int GetSignal(void) const
Get the signal number that has caused the process to terminate (UNIX only).
bool Kill(unsigned long timeout=kDefaultKillTimeout)
Terminate process.
static bool GetMemoryUsage(SMemoryUsage &usage)
Get current process memory usage.
ETriState m_IsCurrent
Status that m_Process represent the current process.
static TPid Daemonize(const char *logfile=0, TDaemonFlags flags=0)
Go daemon.
static TPid Fork(TForkFlags flags=fFF_UpdateDiag)
Fork the process.
unique_ptr< CInterProcessLock > m_PIDGuard
static const unsigned long kInfiniteTimeoutMs
Infinite timeout (milliseconds).
CExitInfo(void)
Constructor.
intptr_t m_Process
Process identifier.
bool IsCurrent(void)
Checks that stored process identifier (pid) represent the current process.
int Wait(unsigned long timeout=kInfiniteTimeoutMs, CExitInfo *info=0) const
Wait until process terminates.
void x_CloseHandle(TProcessHandle handle) const
int GetThreadCount(void)
Get the number of threads in the process.
static const unsigned long kDefaultKillTimeout
Default wait time (milliseconds) between "soft" and "hard" attempts to terminate a process.
unsigned TForkFlags
Bit-wise OR of FForkFlags.
pid_t TPid
Process identifier (PID) and process handle.
bool IsAlive(void) const
TRUE if the process is still alive.
void Release(void)
Release PID.
bool IsSignaled(void) const
TRUE if the process terminated by a signal (UNIX only).
CProcess(void)
Default constructor. Uses the current process pid.
TPid GetPid(void) const
Get stored process identifier (pid).
static bool GetTimes(double *real, double *user, double *sys, EWhat what=eProcess)
Get current process execution times.
bool IsAlive(void) const
Check for process existence.
static TProcessHandle GetHandle(void)
Get process handle for the current process (esp.
int GetFileDescriptorsCount(void)
Get the number of file descriptors consumed by the current process.
EType GetType(void) const
Get type of stored process identifier.
void UpdatePID(TPid pid=0)
Update PID in the file.
void Remove(void)
Remove the file.
int GetExitCode(void) const
Get process exit code.
@ fFF_AllowExceptions
@ ePid
A real process identifier (pid).
@ eHandle
A process handle (MS Windows).
@ eWrite
Unable to write into the PID file.
@ eStillRunning
The process listed in the file is still around.
@ eChildren
All children of the calling process.
@ eThread
Current thread.
@ eProcess
Current process.
@ fFF_UpdateDiag
Reset diagnostics timer and log an app-start message in the child process.
@ fFF_AllowExceptions
Throw an exception on error.
@ fDF_KeepCWD
Don't change CWD to "/".
@ fDF_KeepParent
Do not exit the parent process but return.
@ fDF_KeepStdout
Keep stdout open as "/dev/null" (WO)
@ fDF_AllowExceptions
Throw an exception in case of an error.
@ fDF_KeepStdin
Keep stdin open as "/dev/null" (RO)
@ fDF_AllowThreads
Do not fail if pre-existing threads are detected.
@ fDF_ImmuneTTY
Make daemon immune to re-acquiring a controlling terminal.
#define END_NCBI_SCOPE
End previously defined NCBI scope.
Definition: ncbistl.hpp:103
#define BEGIN_NCBI_SCOPE
Define ncbi namespace.
Definition: ncbistl.hpp:100
IO_PREFIX::ofstream CNcbiOfstream
Portable alias for ofstream.
Definition: ncbistre.hpp:500
#define NcbiCout
Definition: ncbistre.hpp:543
#define NcbiCerr
Definition: ncbistre.hpp:544
IO_PREFIX::ifstream CNcbiIfstream
Portable alias for ifstream.
Definition: ncbistre.hpp:439
#define _T_CSTRING(x)
Definition: ncbistr.hpp:182
static Uint8 StringToUInt8(const CTempString str, TStringToNumFlags flags=0, int base=10)
Convert string to Uint8.
Definition: ncbistr.cpp:873
static enable_if< is_arithmetic< TNumeric >::value||is_convertible< TNumeric, Int8 >::value, string >::type NumericToString(TNumeric value, TNumToStringFlags flags=0, int base=10)
Convert numeric value to string.
Definition: ncbistr.hpp:673
@ fConvErr_NoThrow
Do not throw an exception on error.
Definition: ncbistr.hpp:285
static unsigned int GetThreadsCount()
Get total amount of threads This amount does not contain main thread.
Definition: ncbithr.hpp:785
static bool IsMain(void)
Definition: ncbithr.cpp:537
#define DEFINE_STATIC_FAST_MUTEX(id)
Define static fast mutex and initialize it.
Definition: ncbimtx.hpp:496
double Elapsed(void) const
Return time elapsed since first Start() or last Restart() call (in seconds).
Definition: ncbitime.hpp:2776
const long kMilliSecondsPerSecond
Number milliseconds in one second.
Definition: ncbitime.hpp:96
void Start(void)
Start the timer.
Definition: ncbitime.hpp:2765
#define INVALID_HANDLE_VALUE
A value for an invalid file handle.
Definition: mdb.c:389
#define HANDLE
An abstraction for a file handle.
Definition: mdb.c:383
Definition of all error codes used in corelib (xncbi.lib).
char * buf
int i
yy_size_t n
int len
static MDB_envinfo info
Definition: mdb_load.c:37
const struct ncbi::grid::netcache::search::fields::SIZE size
EIPRangeType t
Definition: ncbi_localip.c:101
Defines MS Windows specific private functions and classes.
Private UNIX specific features.
bool s_Win_GetMemoryUsage(HANDLE handle, CProcess::SMemoryUsage &usage)
TPid s_Daemonize(const char *logfile, CCurrentProcess::TDaemonFlags flags)
bool s_Win_GetHandleTimes(HANDLE handle, double *real, double *user, double *sys, CProcess::EWhat what)
#define EXIT_INFO_CHECK
static bool s_Win_KillGroup(DWORD pid, CStopWatch *timer, unsigned long &timeout)
bool s_Win_GetMemoryCounters(HANDLE handle, SProcessMemoryCounters &memcounters)
const unsigned long kWaitPrecisionMs
EExitInfoState
@ eExitInfo_Alive
@ eExitInfo_Terminated
@ eExitInfo_Unknown
static string s_ErrnoToString()
Defines process management classes.
Static variables safety - create on demand, destroy on application termination.
void SleepMilliSec(unsigned long ml_sec, EInterruptOnSignal onsignal=eRestartOnSignal)
bool GetMemoryUsage(size_t *total, size_t *resident, size_t *shared)
#define STDIN_FILENO
Definition: ncbicgi.cpp:61
#define STDOUT_FILENO
Definition: ncbicgir.cpp:48
Defines NCBI C++ diagnostic APIs, classes, and macros.
Define class Dll and for Portable DLL handling.
Defines NCBI C++ Toolkit portable error codes.
Defines classes: CDirEntry, CFile, CDir, CSymLink, CMemoryFile, CFileUtil, CFileLock,...
Multi-threading – classes, functions, and features.
Defines: CTimeFormat - storage class for time format.
std::istream & in(std::istream &in_, double &x_)
double f(double x_, const double &y_)
Definition: njn_root.hpp:188
#define BOOL
Definition: odbcinst.h:18
char * strerror(int n)
Definition: pcregrep.c:835
static const TModelUnit kUndefined
Definition: seq_glyph.cpp:44
unsigned int DWORD
Definition: sqltypes.h:98
#define NcbiSys_strerror
Definition: ncbisys.hpp:105
#define __int64
Definition: sse2neon.h:208
Process memory usage information, in bytes.
Definition: type.c:6
int BOOL
Definition: sybdb.h:150
#define _TROUBLE
#define _ASSERT
@ FALSE
Definition: testodbc.c:27
else result
Definition: token2.c:20
Modified on Tue Dec 05 02:03:03 2023 by modify_doxy.py rev. 669887