NCBI C++ ToolKit
ncbi_process.cpp
Go to the documentation of this file.
00001 /* $Id: ncbi_process.cpp 52945 2012-02-09 15:36:24Z ucko $
00002  * ===========================================================================
00003  *
00004  *                            PUBLIC DOMAIN NOTICE
00005  *               National Center for Biotechnology Information
00006  *
00007  *  This software/database is a "United States Government Work" under the
00008  *  terms of the United States Copyright Act.  It was written as part of
00009  *  the author's official duties as a United States Government employee and
00010  *  thus cannot be copyrighted.  This software/database is freely available
00011  *  to the public for use. The National Library of Medicine and the U.S.
00012  *  Government have not placed any restriction on its use or reproduction.
00013  *
00014  *  Although all reasonable efforts have been taken to ensure the accuracy
00015  *  and reliability of the software and data, the NLM and the U.S.
00016  *  Government do not and cannot warrant the performance or results that
00017  *  may be obtained by using this software or data. The NLM and the U.S.
00018  *  Government disclaim all warranties, express or implied, including
00019  *  warranties of performance, merchantability or fitness for any particular
00020  *  purpose.
00021  *
00022  *  Please cite the author in any work or product based on this material.
00023  *
00024  * ===========================================================================
00025  *
00026  * Authors:  Aaron Ucko, Vladimir Ivanov
00027  *
00028  */
00029 
00030 #include <ncbi_pch.hpp>
00031 #include <corelib/error_codes.hpp>
00032 #include <corelib/ncbidiag.hpp>
00033 #include <corelib/ncbifile.hpp>
00034 #include <corelib/ncbithr.hpp>
00035 #include <corelib/ncbi_process.hpp>
00036 #include <corelib/ncbi_safe_static.hpp>
00037 #include <corelib/ncbi_system.hpp>
00038 #include "ncbisys.hpp"
00039 
00040 #if   defined(NCBI_OS_UNIX)
00041 #  include <errno.h>
00042 #  include <fcntl.h>
00043 #  include <signal.h>
00044 #  include <stdio.h>
00045 #  include <unistd.h>
00046 #  include <sys/types.h>
00047 #  include <sys/wait.h>
00048 #elif defined(NCBI_OS_MSWIN)
00049 #  include <corelib/ncbitime.hpp>  // for CStopWatch
00050 #  include <process.h>
00051 #  include <tlhelp32.h>
00052 #  pragma warning (disable : 4191)
00053 #endif
00054 
00055 
00056 #define NCBI_USE_ERRCODE_X   Corelib_Process
00057 
00058 
00059 BEGIN_NCBI_SCOPE
00060 
00061 
00062 /////////////////////////////////////////////////////////////////////////////
00063 //
00064 // CProcess::CExitInfo
00065 //
00066 
00067 // CExitInfo process state
00068 enum EExitInfoState {
00069     eExitInfo_Unknown = 0,
00070     eExitInfo_Alive,
00071     eExitInfo_Terminated
00072 };
00073 
00074 
00075 #define EXIT_INFO_CHECK                                         \
00076   if ( !IsPresent() ) {                                         \
00077       NCBI_THROW(CCoreException, eCore,                         \
00078                  "CProcess::CExitInfo state is unknown. "       \
00079                  "Please check CExitInfo::IsPresent() first."); \
00080   }
00081 
00082 
00083 CProcess::CExitInfo::CExitInfo(void)
00084 {
00085     state  = eExitInfo_Unknown;
00086     status = 0;
00087 }
00088 
00089 
00090 bool CProcess::CExitInfo::IsPresent(void) const
00091 {
00092     return state != eExitInfo_Unknown;
00093 }
00094 
00095 
00096 bool CProcess::CExitInfo::IsAlive(void) const
00097 {
00098     EXIT_INFO_CHECK;
00099     return state == eExitInfo_Alive;
00100 }
00101 
00102 
00103 bool CProcess::CExitInfo::IsExited(void) const
00104 {
00105     EXIT_INFO_CHECK;
00106     if (state != eExitInfo_Terminated) {
00107         return false;
00108     }
00109 #if   defined(NCBI_OS_UNIX)
00110     return WIFEXITED(status) != 0;
00111 #elif defined(NCBI_OS_MSWIN)
00112     // The process always terminates with exit code
00113     return true;
00114 #endif
00115 }
00116 
00117 
00118 bool CProcess::CExitInfo::IsSignaled(void) const
00119 {
00120     EXIT_INFO_CHECK;
00121     if (state != eExitInfo_Terminated) {
00122         return false;
00123     }
00124 #if   defined(NCBI_OS_UNIX)
00125     return WIFSIGNALED(status) != 0;
00126 #elif defined(NCBI_OS_MSWIN)
00127     // The process always terminates with exit code
00128     return false;
00129 #endif
00130 }
00131 
00132 
00133 int CProcess::CExitInfo::GetExitCode(void) const
00134 {
00135     if ( !IsExited() ) {
00136         return -1;
00137     }
00138 #if   defined(NCBI_OS_UNIX)
00139     return WEXITSTATUS(status);
00140 #elif defined(NCBI_OS_MSWIN)
00141     return status;
00142 #endif
00143 }
00144 
00145 
00146 int CProcess::CExitInfo::GetSignal(void) const
00147 {
00148     if ( !IsSignaled() ) {
00149         return -1;
00150     }
00151 #if   defined(NCBI_OS_UNIX)
00152     return WTERMSIG(status);
00153 #elif defined(NCBI_OS_MSWIN)
00154     return -1;
00155 #endif
00156 }
00157 
00158 
00159 /////////////////////////////////////////////////////////////////////////////
00160 //
00161 // CProcess 
00162 //
00163 
00164 // Predefined timeouts (in milliseconds)
00165 const unsigned long           kWaitPrecision        = 100;
00166 const unsigned long CProcess::kDefaultKillTimeout   = 1000;
00167 
00168 
00169 CProcess::CProcess(TPid process, EProcessType type)
00170     : m_Process(process), m_Type(type)
00171 {
00172     return;
00173 }
00174 
00175 #ifdef NCBI_OS_MSWIN
00176 // The helper constructor for MS Windows to avoid cast from
00177 // TProcessHandle to TPid
00178 CProcess::CProcess(TProcessHandle process, EProcessType type)
00179     : m_Process((intptr_t)process), m_Type(type)
00180 {
00181     return;
00182 }
00183 #endif //NCBI_OS_MSWIN
00184 
00185 
00186 #ifdef NCBI_THREAD_PID_WORKAROUND
00187 #  ifndef NCBI_OS_UNIX
00188 #    error "NCBI_THREAD_PID_WORKAROUND should only be defined on UNIX!"
00189 #  endif
00190 TPid CProcess::sx_GetPid(EGetPidFlag flag)
00191 {
00192     if ( flag == ePID_GetThread ) {
00193         // Return real PID, do not cache it.
00194         return getpid();
00195     }
00196 
00197     DEFINE_STATIC_FAST_MUTEX(s_GetPidMutex);
00198     static TPid s_CurrentPid = 0;
00199     static TPid s_ParentPid = 0;
00200 
00201     if (CThread::GetSelf() == 0) {
00202         // For main thread always force caching of PIDs
00203         CFastMutexGuard guard(s_GetPidMutex);
00204         s_CurrentPid = getpid();
00205         s_ParentPid = getppid();
00206     }
00207     else {
00208         // For child threads update cached PIDs only if there was a fork
00209         // First call is always from the main thread (explicit or through
00210         // CThread::Run()), s_CurrentPid must be != 0 in any child thread.
00211         _ASSERT(s_CurrentPid);
00212         TPid pid = getpid();
00213         TPid thr_pid = CThread::sx_GetThreadPid();
00214         if (thr_pid  &&  thr_pid != pid) {
00215             // Thread's PID has changed - fork detected.
00216             // Use current PID and PPID as globals.
00217             CThread::sx_SetThreadPid(pid);
00218             CFastMutexGuard guard(s_GetPidMutex);
00219             s_CurrentPid = pid;
00220             s_ParentPid = getppid();
00221         }
00222     }
00223     return flag == ePID_GetCurrent ? s_CurrentPid : s_ParentPid;
00224 }
00225 #endif //NCBI_THREAD_PID_WORKAROUND
00226 
00227 TProcessHandle CProcess::GetCurrentHandle(void)
00228 {
00229 #if   defined(NCBI_OS_MSWIN)
00230     return GetCurrentProcess();
00231 #elif defined(NCBI_OS_UNIX)
00232     return GetCurrentPid();
00233 #endif
00234 }
00235 
00236 
00237 TPid CProcess::GetCurrentPid(void)
00238 {
00239 #if   defined(NCBI_OS_MSWIN)
00240     return GetCurrentProcessId();
00241 #elif defined NCBI_THREAD_PID_WORKAROUND
00242     return sx_GetPid(ePID_GetCurrent);
00243 #elif defined(NCBI_OS_UNIX)
00244     return getpid();
00245 #endif
00246 }
00247 
00248 
00249 TPid CProcess::GetParentPid(void)
00250 {
00251 #if   defined(NCBI_OS_MSWIN)
00252     TPid ppid = (TPid)(-1);
00253     // Open snapshot handle
00254     HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
00255 
00256     if (hSnapshot != INVALID_HANDLE_VALUE) {
00257 
00258         PROCESSENTRY32 pe;
00259         DWORD pid = GetCurrentProcessId();
00260         pe.dwSize = sizeof(PROCESSENTRY32);
00261 
00262         BOOL retval = Process32First(hSnapshot, &pe);
00263         while (retval) {
00264             if (pe.th32ProcessID == pid) {
00265                 ppid = pe.th32ParentProcessID;
00266                 break;
00267             }
00268             pe.dwSize = sizeof(PROCESSENTRY32);
00269             retval = Process32Next(hSnapshot, &pe);
00270         }
00271 
00272         // close snapshot handle
00273         CloseHandle(hSnapshot);
00274     }
00275     return ppid;
00276 #elif defined NCBI_THREAD_PID_WORKAROUND
00277     return sx_GetPid(ePID_GetParent);
00278 #elif defined(NCBI_OS_UNIX)
00279     return getppid();
00280 #endif
00281 }
00282 
00283 
00284 TPid CProcess::Fork(void)
00285 {
00286 #ifdef NCBI_OS_UNIX
00287     TPid pid = ::fork();
00288     CDiagContext::UpdatePID();
00289     return pid;
00290 #else
00291     NCBI_THROW(CCoreException, eCore,
00292                "CProcess::Fork() not implemented on this platform");
00293 #endif
00294 }
00295 
00296 
00297 TPid CProcess::Daemonize(const char* logfile, CProcess::TDaemonFlags flags)
00298 {
00299 #ifdef NCBI_OS_UNIX
00300     int fdin  = ::fcntl(STDIN_FILENO,  F_DUPFD, STDERR_FILENO + 1);
00301     int fdout = ::fcntl(STDOUT_FILENO, F_DUPFD, STDERR_FILENO + 1);
00302     int fderr = ::fcntl(STDERR_FILENO, F_DUPFD, STDERR_FILENO + 1);
00303 
00304     try {
00305         if (flags & fKeepStdin) {
00306             int nullr = ::open("/dev/null", O_RDONLY);
00307             if (nullr < 0)
00308                 throw string("Error opening /dev/null for reading");
00309             if (nullr != STDIN_FILENO) {
00310                 int error = ::dup2(nullr, STDIN_FILENO);
00311                 int x_errno = errno;
00312                 ::close(nullr);
00313                 if (error < 0) {
00314                     errno = x_errno;
00315                     throw string("Error redirecting stdin");
00316                 }
00317             }
00318         }
00319         if (flags & fKeepStdout) {
00320             int nullw = ::open("/dev/null", O_WRONLY);
00321             if (nullw < 0)
00322                 throw string("Error opening /dev/null for writing");
00323             NcbiCout.flush();
00324             ::fflush(stdout);
00325             if (nullw != STDOUT_FILENO) {
00326                 int error = ::dup2(nullw, STDOUT_FILENO);
00327                 int x_errno = errno;
00328                 ::close(nullw);
00329                 if (error < 0) {
00330                     ::dup2(fdin, STDIN_FILENO);
00331                     errno = x_errno;
00332                     throw string("Error redirecting stdout");
00333                 }
00334             }
00335         }
00336         if (logfile) {
00337             int fd = (!*logfile ? ::open("/dev/null", O_WRONLY | O_APPEND) :
00338                       ::open(logfile, O_WRONLY | O_APPEND | O_CREAT, 0666));
00339             if (fd < 0) {
00340                 if (!*logfile)
00341                     throw string("Error opening /dev/null for appending");
00342                 throw "Unable to open logfile \"" + string(logfile) + '"';
00343             }
00344             NcbiCerr.flush();
00345             ::fflush(stderr);
00346             if (fd != STDERR_FILENO) {
00347                 int error = ::dup2(fd, STDERR_FILENO);
00348                 int x_errno = errno;
00349                 ::close(fd);
00350                 if (error < 0) {
00351                     ::dup2(fdin,  STDIN_FILENO);
00352                     ::dup2(fdout, STDOUT_FILENO);
00353                     errno = x_errno;
00354                     throw string("Error redirecting stderr");
00355                 }
00356             }
00357         }
00358         TPid pid = Fork();
00359         if (pid) {
00360             // Parent thread (including fork error)
00361             int x_errno = errno;
00362             if (pid == (TPid)(-1)  ||  (flags & fKeepParent)) {
00363                 ::dup2(fdin,  STDIN_FILENO);
00364                 ::dup2(fdout, STDOUT_FILENO);
00365                 ::dup2(fderr, STDERR_FILENO);
00366             }
00367             if (pid == (TPid)(-1)) {
00368                 errno = x_errno;
00369                 throw string("Cannot fork");
00370             }
00371             if (!(flags & fKeepParent)) {
00372                 ::_exit(0);
00373             }
00374             ::close(fdin);
00375             ::close(fdout);
00376             ::close(fderr);
00377             return (TPid) pid/*success*/;
00378         }
00379         // Child thread only
00380         ::setsid();
00381         if (flags & fImmuneTTY) {
00382             pid = Fork();
00383             if (pid == (TPid)(-1)) {
00384                 const char* error = strerror(errno);
00385                 if (!error  ||  !*error)
00386                     error = "Unknown error";
00387                 ERR_POST_X(2, "[Daemonize]  Failed to immune from TTY accruals"
00388                            " (" + string(error) + "), continuing anyways");
00389             } else if (pid) {
00390                 ::_exit(0);
00391             }
00392         }
00393         if (!(flags & fDontChroot))
00394             if (::chdir("/") ) { /*dummy*/ };  // NB: "/" always exists
00395         if (!(flags & fKeepStdin))
00396             ::fclose(stdin);
00397         ::close(fdin);
00398         if (!(flags & fKeepStdout))
00399             ::fclose(stdout);
00400         ::close(fdout);
00401         if (!logfile)
00402             ::fclose(stderr);
00403         ::close(fderr);
00404         return (TPid)(-1)/*success*/;
00405     }
00406     catch (const string& what) {
00407         int x_errno = errno;
00408         const char* error = x_errno ? strerror(x_errno) : 0;
00409         ERR_POST_X(1, "[Daemonize]  " + what
00410                    + (error  &&  *error ? string(": ") + error : kEmptyStr));
00411         ::close(fdin);
00412         ::close(fdout);
00413         ::close(fderr);
00414         errno = x_errno;
00415     }
00416     /* caution: stream exceptions (if any) let through */
00417 #else
00418     NCBI_THROW(CCoreException, eCore,
00419                "CProcess::Daemonize() not implemented on this platform");
00420     /*NOTREACHED*/
00421 #endif
00422     return (TPid) 0/*failure*/;
00423 }
00424 
00425 
00426 bool CProcess::IsAlive(void) const
00427 {
00428 #if   defined(NCBI_OS_UNIX)
00429     return kill((TPid)m_Process, 0) == 0  ||  errno == EPERM;
00430 
00431 #elif defined(NCBI_OS_MSWIN)
00432     HANDLE hProcess = 0;
00433     if (m_Type == ePid) {
00434         hProcess = OpenProcess(PROCESS_QUERY_INFORMATION,
00435                                FALSE, (TPid)m_Process);
00436         if (!hProcess) {
00437             return GetLastError() == ERROR_ACCESS_DENIED;
00438         }
00439     } else {
00440         hProcess = (TProcessHandle)m_Process;
00441     }
00442     DWORD status = 0;
00443     _ASSERT(STILL_ACTIVE != 0);
00444     GetExitCodeProcess(hProcess, &status);
00445     if (m_Type == ePid) {
00446         CloseHandle(hProcess);
00447     }
00448     return status == STILL_ACTIVE;
00449 #endif
00450 }
00451 
00452 
00453 bool CProcess::Kill(unsigned long timeout) const
00454 {
00455 #if   defined(NCBI_OS_UNIX)
00456 
00457     TPid pid = (TPid)m_Process;
00458 
00459     // Try to kill the process with SIGTERM first
00460     if (kill(pid, SIGTERM) < 0  &&  errno == EPERM) {
00461         return false;
00462     }
00463 
00464     // Check process termination within the timeout
00465     unsigned long x_timeout = timeout;
00466     for (;;) {
00467         TPid reap = waitpid(pid, static_cast<int*>(NULL), WNOHANG);
00468         if (reap) {
00469             if (reap != (TPid)(-1)) {
00470                 _ASSERT(reap == pid);
00471                 return true;
00472             }
00473             if (errno != ECHILD)
00474                 return false;
00475             if (kill(pid, 0) < 0)
00476                 return true;
00477         }
00478         unsigned long x_sleep = kWaitPrecision;
00479         if (x_sleep > x_timeout) {
00480             x_sleep = x_timeout;
00481         }
00482         if ( !x_sleep ) {
00483              break;
00484         }
00485         SleepMilliSec(x_sleep);
00486         x_timeout  -= x_sleep;
00487     }
00488     _ASSERT(!x_timeout);
00489 
00490     // Try harder to kill the stubborn process -- SIGKILL may not be caught!
00491     int res = kill(pid, SIGKILL);
00492     if ( !timeout ) {
00493         return res <= 0;
00494     }
00495     SleepMilliSec(kWaitPrecision);
00496     // Reap the zombie (if child) up from the system
00497     waitpid(pid, static_cast<int*>(NULL), WNOHANG);
00498     // Check whether the process cannot be killed
00499     // (most likely due to a kernel problem)
00500     return kill(pid, 0) < 0;
00501 
00502 #elif defined(NCBI_OS_MSWIN)
00503 
00504     // Safe process termination
00505     bool safe = (timeout > 0);
00506 
00507     // Try to kill current process?
00508     if ( m_Type == ePid  &&  (TPid)m_Process == GetCurrentPid() ) {
00509         ExitProcess(-1);
00510         // NOTREACHED
00511         return false;
00512     }
00513 
00514     HANDLE hProcess    = NULL;
00515     HANDLE hThread     = NULL;
00516     bool   enable_sync = true;
00517 
00518     // Get process handle
00519     if (m_Type == eHandle) {
00520         hProcess = (TProcessHandle)m_Process;
00521 
00522     } else {  // m_Type == ePid
00523         hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_TERMINATE |
00524                                SYNCHRONIZE, FALSE, (TPid)m_Process);
00525         if ( !hProcess ) {
00526             // Try to open with minimal access right needed
00527             // to terminate process.
00528             enable_sync = false;
00529             hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, (TPid)m_Process);
00530             if (!hProcess) {
00531                 if (GetLastError() != ERROR_ACCESS_DENIED) {
00532                     return false;
00533                 }
00534                 // If we have an administrative rights, that we can try
00535                 // to terminate the process using SE_DEBUG_NAME privilege,
00536                 // which system administrators normally have, but it might
00537                 // be disabled by default. When this privilege is enabled,
00538                 // the calling thread can open processes with any access
00539                 // rights regardless of the security descriptor assigned
00540                 // to the process.
00541 
00542                 // Determine OS version
00543                 OSVERSIONINFO vi;
00544                 vi.dwOSVersionInfoSize = sizeof(vi);
00545                 GetVersionEx(&vi);
00546                 if (vi.dwPlatformId != VER_PLATFORM_WIN32_NT) {
00547                     return false;
00548                 }
00549 
00550                 // Get current thread token 
00551                 HANDLE hToken;
00552                 if (!OpenThreadToken(GetCurrentThread(), 
00553                                      TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES,
00554                                      FALSE, &hToken)) {
00555                     if (GetLastError() != ERROR_NO_TOKEN) {
00556                         return false;
00557                     }
00558                     // Rrevert to the process token, if not impersonating
00559                     if (!OpenProcessToken(GetCurrentProcess(),
00560                                           TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES,
00561                                           &hToken)) {
00562                         return false;
00563                     }
00564                 }
00565 
00566                 // Try to enable the SE_DEBUG_NAME privilege
00567 
00568                 TOKEN_PRIVILEGES tp, tp_prev;
00569                 DWORD            tp_prev_size = sizeof(tp_prev);
00570 
00571                 tp.PrivilegeCount = 1;
00572                 tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
00573                 LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
00574 
00575                 if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp),
00576                                            &tp_prev, &tp_prev_size)) {
00577                     CloseHandle(hToken);
00578                     return false;
00579                 }
00580                 if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) {
00581                     // The AdjustTokenPrivileges function cannot add new
00582                     // privileges to the access token. It can only enable or
00583                     // disable the token's existing privileges.
00584                     CloseHandle(hToken);
00585                     return false;
00586                 }
00587 
00588                 // Try to open process handle again
00589                 hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, (TPid)m_Process);
00590                 
00591                 // Restore original privilege state
00592                 AdjustTokenPrivileges(hToken, FALSE, &tp_prev, sizeof(tp_prev),
00593                                       NULL, NULL);
00594                 CloseHandle(hToken);
00595             }
00596         }
00597     }
00598 
00599     // Check process handle
00600     if ( !hProcess  ||  hProcess == INVALID_HANDLE_VALUE ) {
00601         return true;
00602     }
00603     // Terminate process
00604     bool terminated = false;
00605 
00606     CStopWatch timer;
00607     if ( safe ) {
00608         timer.Start();
00609     }
00610     // Safe process termination
00611     if ( safe  &&  enable_sync ) {
00612         // (kernel32.dll loaded at same address in each process)
00613         FARPROC exitproc = GetProcAddress(GetModuleHandleA("KERNEL32.DLL"),
00614                                           "ExitProcess");
00615         if ( exitproc ) {
00616             hThread = CreateRemoteThread(hProcess, NULL, 0,
00617                                         (LPTHREAD_START_ROUTINE)exitproc,
00618                                         0, 0, 0);
00619             // Wait until process terminated, or timeout expired
00620             if (hThread   &&
00621                 (WaitForSingleObject(hProcess, timeout) == WAIT_OBJECT_0)){
00622                 terminated = true;
00623             }
00624         }
00625     }
00626     // Try harder to kill stubborn process
00627     if ( !terminated ) {
00628         if ( TerminateProcess(hProcess, -1) != 0  ||
00629             GetLastError() == ERROR_INVALID_HANDLE ) {
00630             // If process "terminated" succesfuly or error occur but
00631             // process handle became invalid -- process has terminated
00632             terminated = true;
00633         }
00634     }
00635     if (safe  &&  terminated) {
00636         // The process terminating now.
00637         // Reset flag, and wait for real process termination.
00638 
00639         terminated = false;
00640         double elapsed = timer.Elapsed() * kMilliSecondsPerSecond;
00641         unsigned long linger_timeout = (elapsed < timeout) ? 
00642             (unsigned long)((double)timeout - elapsed) : 0;
00643 
00644         for (;;) {
00645             if ( !IsAlive() ) {
00646                 terminated = true;
00647                 break;
00648             }
00649             unsigned long x_sleep = kWaitPrecision;
00650             if (x_sleep > linger_timeout) {
00651                 x_sleep = linger_timeout;
00652             }
00653             if ( !x_sleep ) {
00654                 break;
00655             }
00656             SleepMilliSec(x_sleep);
00657             linger_timeout -= x_sleep;
00658         }
00659     }
00660     // Close temporary process handle
00661     if ( hThread ) {
00662         CloseHandle(hThread);
00663     }
00664     if (m_Type == ePid) {
00665         CloseHandle(hProcess);
00666     }
00667     return terminated;
00668 
00669 #endif
00670 }
00671 
00672 
00673 #ifdef NCBI_OS_MSWIN
00674 
00675 // MS Windows:
00676 // A helper function for terminating all processes
00677 // in the tree within specified timeout.
00678 
00679 // If 'timer' is specified we use safe process termination.
00680 static bool s_KillGroup(DWORD pid,
00681                         CStopWatch *timer, unsigned long &timeout)
00682 {
00683     // Open snapshot handle.
00684     // We cannot use one shapshot for recursive calls, 
00685     // because it is not reentrant.
00686     HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
00687     if (hSnapshot == INVALID_HANDLE_VALUE) {
00688         return false;
00689     }
00690     PROCESSENTRY32 pe;
00691     pe.dwSize = sizeof(PROCESSENTRY32);
00692 
00693     // Terminate all children first
00694     if (!Process32First(hSnapshot, &pe)) {
00695         return false;
00696     }
00697     do {
00698         if (pe.th32ParentProcessID == pid) {
00699             // Safe termination -- update timeout
00700             if ( timer ) {
00701                 double elapsed = timer->Elapsed() * kMilliSecondsPerSecond;
00702                 timeout = (elapsed < timeout) ?
00703                     (unsigned long)((double)timeout - elapsed) : 0;
00704                 if ( !timeout ) {
00705                     CloseHandle(hSnapshot);
00706                     return false;
00707                 }
00708             }
00709             bool res = s_KillGroup(pe.th32ProcessID, timer, timeout);
00710             if ( !res ) {
00711                 CloseHandle(hSnapshot);
00712                 return false;
00713             }
00714         }
00715     }
00716     while (Process32Next(hSnapshot, &pe)); 
00717 
00718     // Terminate the specified process
00719 
00720     // Safe termination -- update timeout
00721     if ( timer ) {
00722         double elapsed = timer->Elapsed() * kMilliSecondsPerSecond;
00723         timeout = (elapsed < timeout) ?
00724             (unsigned long)((double)timeout - elapsed) : 0;
00725         if ( !timeout ) {
00726             CloseHandle(hSnapshot);
00727             return false;
00728         }
00729     }
00730     bool res = CProcess(pid, CProcess::ePid).Kill(timeout);
00731 
00732     // Close snapshot handle
00733     CloseHandle(hSnapshot);
00734     return res;
00735 }
00736 
00737 #endif //NCBI_OS_MSWIN
00738 
00739 
00740 bool CProcess::KillGroup(unsigned long timeout) const
00741 {
00742 #if   defined(NCBI_OS_UNIX)
00743 
00744     TPid pgid = getpgid((TPid)m_Process);
00745     if (pgid == (TPid)(-1)) {
00746         // TRUE if PID does not match any process
00747         return errno == ESRCH;
00748     }
00749     return KillGroupById(pgid, timeout);
00750 
00751 #elif defined(NCBI_OS_MSWIN)
00752 
00753     // Convert the process handle to process ID if needed
00754     TPid pid = 0;
00755     if (m_Type == eHandle) {
00756         // Some OS like Windows 2000 and WindowsXP (w/o SP1) don't
00757         // have GetProcessId() function. Try to load it directy from 
00758         // KERNEL32.DLL
00759         static bool  s_TryGetProcessId = true;
00760         typedef DWORD (STDMETHODCALLTYPE FAR* LPFN_GETPROCESSID)(HANDLE process);
00761         static LPFN_GETPROCESSID s_GetProcessId = NULL;
00762 
00763         if ( s_TryGetProcessId  &&  !s_GetProcessId ) {
00764             s_GetProcessId  = (LPFN_GETPROCESSID)GetProcAddress(
00765                                     GetModuleHandleA("KERNEL32.DLL"),
00766                                     "GetProcessId");
00767             s_TryGetProcessId = false;
00768         }
00769         if ( !s_GetProcessId ) {
00770             // GetProcessId() is not available on this platform
00771             return false;
00772         }
00773         pid = s_GetProcessId((TProcessHandle)m_Process);
00774 
00775     } else {  // m_Type == ePid
00776         pid = (TPid)m_Process;
00777     }
00778     if (!pid) {
00779         return false;
00780     }
00781     // Use safe process termination if timeout > 0
00782     unsigned long x_timeout = timeout;
00783     CStopWatch timer;
00784     if ( timeout ) {
00785         timer.Start();
00786     }
00787     // Kill process tree
00788     bool result = s_KillGroup(pid, (timeout > 0) ? &timer : 0, x_timeout);
00789     return result;
00790 
00791 #endif
00792 }
00793 
00794 
00795 bool CProcess::KillGroupById(TPid pgid, unsigned long timeout)
00796 {
00797 #if   defined(NCBI_OS_UNIX)
00798 
00799     // Try to kill the process group with SIGTERM first
00800     if (kill(-pgid, SIGTERM) < 0  &&  errno == EPERM) {
00801         return false;
00802     }
00803 
00804     // Check process group termination within the timeout 
00805     unsigned long x_timeout = timeout;
00806     for (;;) {
00807         // Reap the zombie (if group leader is a child) up from the system
00808         TPid reap = waitpid(pgid, static_cast<int*>(NULL), WNOHANG);
00809         if (reap) {
00810             if (reap != (TPid)(-1)) {
00811                 _ASSERT(reap == pgid);
00812                 return true;
00813             }
00814             if (errno != ECHILD)
00815                 return false;
00816             if (kill(-pgid, 0) < 0) {
00817                 return true;
00818             }
00819         }
00820         unsigned long x_sleep = kWaitPrecision;
00821         if (x_sleep > x_timeout) {
00822             x_sleep = x_timeout;
00823         }
00824         if ( !x_sleep ) {
00825              break;
00826         }
00827         SleepMilliSec(x_sleep);
00828         x_timeout  -= x_sleep;
00829     }
00830     _ASSERT(!x_timeout);
00831 
00832     // Try harder to kill the stubborn processes -- SIGKILL may not be caught!
00833     int res = kill(-pgid, SIGKILL);
00834     if ( !timeout ) {
00835         return res <= 0;
00836     }
00837     SleepMilliSec(kWaitPrecision);
00838     // Reap the zombie (if group leader is a child) up from the system
00839     waitpid(pgid, static_cast<int*>(NULL), WNOHANG);
00840     // Check whether the process cannot be killed
00841     // (most likely due to a kernel problem)
00842     return kill(-pgid, 0) < 0;
00843 
00844 #elif defined(NCBI_OS_MSWIN)
00845 
00846     // Cannot be implemented, use non-static version of KillGroup()
00847     // for specified process.
00848     return false;
00849 
00850 #endif
00851 }
00852 
00853 
00854 int CProcess::Wait(unsigned long timeout, CExitInfo* info) const
00855 {
00856     int  status;
00857 
00858     // Reset extended information
00859     if (info) {
00860         info->state  = eExitInfo_Unknown;
00861         info->status = 0;
00862     }
00863 
00864 #if   defined(NCBI_OS_UNIX)
00865 
00866     TPid pid     = (TPid)m_Process;
00867     int  options = timeout == kInfiniteTimeoutMs ? 0 : WNOHANG;
00868 
00869     // Check process termination (with timeout or indefinitely)
00870     for (;;) {
00871         TPid ws = waitpid(pid, &status, options);
00872         if (ws > 0) {
00873             // terminated
00874             _ASSERT(ws == pid);
00875             if (info) {
00876                 info->state  = eExitInfo_Terminated;
00877                 info->status = status;
00878             }
00879             return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
00880         } else if (ws == 0) {
00881             // still running
00882             _ASSERT(timeout != kInfiniteTimeoutMs);
00883             if ( !timeout ) {
00884                 if (info) {
00885                     info->state = eExitInfo_Alive;
00886                 }
00887                 break;
00888             }
00889             unsigned long x_sleep = kWaitPrecision;
00890             if (x_sleep > timeout) {
00891                 x_sleep = timeout;
00892             }
00893             SleepMilliSec(x_sleep);
00894             timeout    -= x_sleep;
00895         } else if (errno != EINTR) {
00896             // error
00897             break;
00898         }
00899     }
00900     return -1;
00901 
00902 #elif defined(NCBI_OS_MSWIN)
00903 
00904     HANDLE hProcess;
00905     bool   enable_sync = true;
00906 
00907     // Get process handle
00908     if (m_Type == ePid) {
00909         hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE,
00910                                FALSE, (TPid)m_Process);
00911         if ( !hProcess ) {
00912             enable_sync = false;
00913             hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, (TPid)m_Process);
00914             if (!hProcess   &&  GetLastError() == ERROR_ACCESS_DENIED) {
00915                 return -1;
00916             }
00917         }
00918     } else {
00919         hProcess = (TProcessHandle)m_Process;
00920         if (!hProcess  ||  hProcess == INVALID_HANDLE_VALUE) {
00921             return -1;
00922         }
00923     }
00924 
00925     status = -1;
00926     DWORD x_status;
00927     // Is process still running?
00928     if (GetExitCodeProcess(hProcess, &x_status)) {
00929         if (x_status == STILL_ACTIVE) {
00930             if (enable_sync  &&  timeout) {
00931                 DWORD tv = (timeout == kInfiniteTimeoutMs
00932                             ? INFINITE
00933                             : (DWORD)timeout);
00934                 DWORD ws = WaitForSingleObject(hProcess, tv);
00935                 switch(ws) {
00936                 case WAIT_TIMEOUT:
00937                     // still running
00938                     _ASSERT(x_status == STILL_ACTIVE);
00939                     break;
00940                 case WAIT_OBJECT_0:
00941                     if (GetExitCodeProcess(hProcess, &x_status)) {
00942                         if (x_status != STILL_ACTIVE) {
00943                             // terminated
00944                             status = 0;
00945                         } // else still running
00946                         break;
00947                     }
00948                     /*FALLTHRU*/
00949                 default:
00950                     // error
00951                     x_status = 0;
00952                     break;
00953                 }
00954             } // else still running
00955         } else {
00956             // terminated
00957             status = 0;
00958         }
00959     } else {
00960         // error
00961         x_status = 0;
00962     }
00963 
00964     if (status < 0) {
00965         if (info  &&  x_status == STILL_ACTIVE) {
00966             info->state  = eExitInfo_Alive;
00967         }
00968     } else {
00969         if (info) {
00970             info->state  = eExitInfo_Terminated;
00971             info->status = x_status;
00972         }
00973         status = x_status;
00974     }
00975 
00976     if (m_Type == ePid) {
00977         CloseHandle(hProcess);
00978     }
00979     return status;
00980 
00981 #endif
00982 }
00983 
00984 
00985 
00986 /////////////////////////////////////////////////////////////////////////////
00987 //
00988 // CPIDGuard
00989 //
00990 
00991 // Protective mutex
00992 DEFINE_STATIC_FAST_MUTEX(s_PidGuardMutex);
00993 
00994 // NOTE: This method to protect PID file works only within one process.
00995 //       CPIDGuard know nothing about PID file modification or deletion 
00996 //       by other processes. Be aware.
00997 
00998 
00999 CPIDGuard::CPIDGuard(const string& filename, const string& dir)
01000     : m_OldPID(0), m_NewPID(0)
01001 {
01002     string real_dir;
01003     CDirEntry::SplitPath(filename, &real_dir, 0, 0);
01004     if (real_dir.empty()) {
01005         if (dir.empty()) {
01006             real_dir = CDir::GetTmpDir();
01007         } else {
01008             real_dir = dir;
01009         }
01010         m_Path = CDirEntry::MakePath(real_dir, filename);
01011     } else {
01012         m_Path = filename;
01013     }
01014     UpdatePID();
01015 }
01016 
01017 
01018 CPIDGuard::~CPIDGuard(void)
01019 {
01020     Release();
01021 }
01022 
01023 
01024 void CPIDGuard::Release(void)
01025 {
01026     if ( !m_Path.empty() ) {
01027         // MT-Safe protect
01028         CFastMutexGuard LOCK(s_PidGuardMutex);
01029 
01030         // Read info
01031         TPid pid = 0;
01032         unsigned int ref = 0;
01033         CNcbiIfstream in(m_Path.c_str());
01034         if ( in.good() ) {
01035             in >> pid >> ref;
01036             in.close();
01037             if ( m_NewPID != pid ) {
01038                 // We do not own this file more
01039                 return;
01040             }
01041             if ( ref ) {
01042                 ref--;
01043             }
01044             // Check reference counter
01045             if ( ref ) {
01046                 // Write updated reference counter into the file
01047                 CNcbiOfstream out(m_Path.c_str(),
01048                                   IOS_BASE::out | IOS_BASE::trunc);
01049                 if ( out.good() ) {
01050                     out << pid << endl << ref << endl;
01051                 }
01052                 if ( !out.good() ) {
01053                     NCBI_THROW(CPIDGuardException, eWrite,
01054                                "Unable to write into PID file " + m_Path +": "
01055                                + _T_CSTRING(NcbiSys_strerror(errno)));
01056                 }
01057             } else {
01058                 // Remove the file
01059                 CDirEntry(m_Path).Remove();
01060             }
01061         }
01062         m_Path.erase();
01063     }
01064 }
01065 
01066 
01067 void CPIDGuard::Remove(void)
01068 {
01069     if ( !m_Path.empty() ) {
01070         // MT-Safe protect
01071         CFastMutexGuard LOCK(s_PidGuardMutex);
01072         // Remove the file
01073         CDirEntry(m_Path).Remove();
01074         m_Path.erase();
01075     }
01076 }
01077 
01078 
01079 void CPIDGuard::UpdatePID(TPid pid)
01080 {
01081     if (pid == 0) {
01082         pid = CProcess::GetCurrentPid();
01083     }
01084 
01085     // MT-Safe protect
01086     CFastMutexGuard LOCK(s_PidGuardMutex);
01087 
01088     // Read old PID
01089     unsigned int ref = 1;
01090     CNcbiIfstream in(m_Path.c_str());
01091     if ( in.good() ) {
01092         in >> m_OldPID >> ref;
01093         if ( m_OldPID == pid ) {
01094             // Guard the same PID. Just increase the reference counter.
01095             ref++;
01096         } else {
01097             if ( CProcess(m_OldPID,CProcess::ePid).IsAlive() ) {
01098                 NCBI_THROW2(CPIDGuardException, eStillRunning,
01099                             "Process is still running", m_OldPID);
01100             }
01101             ref = 1;
01102         }
01103     }
01104     in.close();
01105 
01106     // Write new PID
01107     CNcbiOfstream out(m_Path.c_str(), IOS_BASE::out | IOS_BASE::trunc);
01108     if ( out.good() ) {
01109         out << pid << endl << ref << endl;
01110     }
01111     if ( !out.good() ) {
01112         NCBI_THROW(CPIDGuardException, eWrite,
01113                    "Unable to write into PID file " + m_Path + ": "
01114                    + _T_CSTRING(NcbiSys_strerror(errno)));
01115     }
01116     // Save updated pid
01117     m_NewPID = pid;
01118 }
01119 
01120 const char* CPIDGuardException::GetErrCodeString(void) const
01121 {
01122     switch (GetErrCode()) {
01123     case eStillRunning: return "eStillRunning";
01124     case eWrite:        return "eWrite";
01125     default:            return CException::GetErrCodeString();
01126     }
01127 }
01128 
01129 
01130 END_NCBI_SCOPE
Modified on Wed May 23 12:53:32 2012 by modify_doxy.py rev. 337098