|
NCBI C++ ToolKit
|
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
1.7.5.1
Modified on Wed May 23 12:53:32 2012 by modify_doxy.py rev. 337098