static void test_process_start_time(void) { int this_pid, new_pid; time_t this_starttime; this_pid = getpid(); this_starttime = GetProcessStartTime(this_pid); sleep(1); new_pid = fork(); assert_true(new_pid >= 0); if (new_pid == 0) /* child */ { execl("/bin/sleep", "/bin/sleep", "5", NULL); assert_true(false); /* unreachable */ } SPAWNED_PID = new_pid; time_t newproc_starttime = GetProcessStartTime(new_pid); // We might have slipped by a few seconds, but shouldn't be much. assert_true(newproc_starttime >= this_starttime + 1 && newproc_starttime <= this_starttime + 5); kill(new_pid, SIGKILL); wait(NULL); SPAWNED_PID = -1; }
static void test_process_start_time(void) { /* Wait a couple of seconds so that process start time differs. */ printf("Sleeping 2 seconds...\n"); sleep(2); pid_t new_pid = fork(); assert_true(new_pid >= 0); if (new_pid == 0) /* child */ { execl("/bin/sleep", "/bin/sleep", "30", NULL); assert_true(false); /* unreachable */ } SPAWNED_PID = new_pid; time_t newproc_starttime = GetProcessStartTime(new_pid); printf("Spawned a \"sleep\" child with PID %jd and start_time %jd\n", (intmax_t) new_pid, (intmax_t) newproc_starttime); // We might have slipped by a few seconds, but shouldn't be much. assert_int_not_equal(newproc_starttime, PROCESS_START_TIME_UNKNOWN); assert_true(newproc_starttime >= THIS_STARTTIME + 1); assert_true(newproc_starttime <= THIS_STARTTIME + 15); kill(new_pid, SIGKILL); wait(NULL); SPAWNED_PID = 0; }
int main() { PRINT_TEST_BANNER(); /* Don't miss the messages about GetProcessStartTime not implemented. */ LogSetGlobalLevel(LOG_LEVEL_DEBUG); THIS_PID = getpid(); THIS_STARTTIME = GetProcessStartTime(THIS_PID); printf("This parent process has PID %jd and start_time %jd\n", (intmax_t) THIS_PID, (intmax_t) THIS_STARTTIME); const UnitTest tests[] = { unit_test(test_process_start_time), unit_test(test_process_state), unit_test(test_graceful_terminate) }; int ret = run_tests(tests); /* Make sure no child is alive. */ if (SPAWNED_PID > 0) { kill(SPAWNED_PID, SIGKILL); } return ret; }
static void test_graceful_terminate(void) { int ret, state; pid_t new_pid = fork(); assert_true(new_pid >= 0); if (new_pid == 0) /* child */ { execl("/bin/sleep", "/bin/sleep", "30", NULL); assert_true(false); /* unreachable */ } time_t start_time = GetProcessStartTime(new_pid); SPAWNED_PID = new_pid; printf("Spawned a \"sleep\" child with PID %jd and start_time %jd\n", (intmax_t) new_pid, (intmax_t) start_time); state = GetProcessState(new_pid); assert_int_equal(state, PROCESS_STATE_RUNNING); printf("Killing child with wrong start_time, child should not die...\n"); ret = GracefulTerminate(new_pid, 12345); /* fake start time */ assert_false(ret); state = GetProcessState(new_pid); assert_int_equal(state, PROCESS_STATE_RUNNING); printf("Killing child with correct start_time, child should die...\n"); ret = GracefulTerminate(new_pid, start_time); assert_true(ret); state = GetProcessState(new_pid); assert_int_equal(state, PROCESS_STATE_ZOMBIE); wait(NULL); /* reap child */ state = GetProcessState(new_pid); assert_int_equal(state, PROCESS_STATE_DOES_NOT_EXIST); printf("Child Dead!\n"); SPAWNED_PID = 0; printf("Killing ourself, should fail...\n"); ret = GracefulTerminate(THIS_PID, THIS_STARTTIME); assert_false(ret); printf("Killing ourself without specifying starttime, should fail...\n"); ret = GracefulTerminate(THIS_PID, PROCESS_START_TIME_UNKNOWN); assert_false(ret); }
bool ProcessTimes::GetProcessTimes( const Utils::AutoHandleMgr& ahmProcess_i ) { // Get file times for process FILETIME ftStartTime = { 0 }, ftExitTime = { 0 }, ftKernelModeTime = { 0 }, ftUserModeTime = { 0 }; // Get times of process const bool bResult = ::GetProcessTimes( ahmProcess_i, &ftStartTime, &ftExitTime, &ftKernelModeTime, &ftUserModeTime ) != FALSE; // Get formatted Utils::GetFormattedTime( GetProcessStartTime(), ftStartTime, true ); Utils::GetFormattedTime( GetProcessExitTime(), ftExitTime, true ); Utils::GetFormattedTime( GetProcessKernelTime(), ftKernelModeTime, false ); Utils::GetFormattedTime( GetProcessUserTime(), ftUserModeTime, false ); return bResult; }// End GetProcessTimes
static void test_get_start_time_process2(void) { time_t t = GetProcessStartTime(2); assert_int_equal(t, PROCESS_START_TIME_UNKNOWN); }
static void test_get_start_time_process1(void) { time_t t = GetProcessStartTime(1); assert_int_equal(t, 100); }
#endif { return true; } else { return false; } } static bool WriteLockDataCurrent(CF_DB *dbp, const char *lock_id) { LockData lock_data = { .pid = getpid(), .time = time(NULL), .process_start_time = GetProcessStartTime(getpid()), }; return WriteLockData(dbp, lock_id, &lock_data); } /* * Much simpler than AcquireLock. Useful when you just want to check * if a certain amount of time has elapsed for an action since last * time you checked. No need to clean up after calling this * (e.g. like YieldCurrentLock()). * * WARNING: Is prone to race-conditions, both on the thread and * process level. */
/* * Wait until process specified by #pid is stopped due to SIGSTOP signal. * * @returns true if process has come to stop during #timeout_ns nanoseconds, * false if the process cannot be found or failed to stop during #timeout_ns * nanoseconds. * * FIXME: Only timeouts < 1s are supported */ static bool ProcessWaitUntilStopped(pid_t pid, long timeout_ns) { while (timeout_ns > 0) { switch (GetProcessState(pid)) { case PROCESS_STATE_RUNNING: break; /* retry in a while */ case PROCESS_STATE_STOPPED: return true; case PROCESS_STATE_ZOMBIE: /* There is not much we can do by waiting a zombie process. It * will never change to a stopped state. */ return false; case PROCESS_STATE_DOES_NOT_EXIST: return false; } struct timespec ts = { .tv_sec = 0, .tv_nsec = MIN(SLEEP_POLL_TIMEOUT_NS, timeout_ns), }; while (nanosleep(&ts, &ts) < 0) { if (errno != EINTR) { ProgrammingError("Invalid timeout for nanosleep"); } } timeout_ns = MAX(0, timeout_ns - SLEEP_POLL_TIMEOUT_NS); } return false; } /* * Currently only timeouts < 1s are supported */ static bool ProcessWaitUntilExited(pid_t pid, long timeout_ns) { assert(timeout_ns < 1000000000); while (timeout_ns > 0) { switch (GetProcessState(pid)) { case PROCESS_STATE_RUNNING: break; /* retry in a while */ case PROCESS_STATE_DOES_NOT_EXIST: return true; case PROCESS_STATE_ZOMBIE: /* There is not much we can do by waiting a zombie process. It's the responsibility of the caller to reap the child so we're considering it has already exited. */ return true; case PROCESS_STATE_STOPPED: /* Almost the same case with a zombie process, but it will * respond only to signals that can't be caught. */ return false; } struct timespec ts = { .tv_sec = 0, .tv_nsec = MIN(SLEEP_POLL_TIMEOUT_NS, timeout_ns), }; Log(LOG_LEVEL_DEBUG, "PID %jd still alive after signalling, waiting for %lu ms...", (intmax_t) pid, ts.tv_nsec / 1000000); while (nanosleep(&ts, &ts) < 0) { if (errno != EINTR) { ProgrammingError("Invalid timeout for nanosleep"); } } timeout_ns = MAX(0, timeout_ns - SLEEP_POLL_TIMEOUT_NS); } return false; } /* A timeout (in nanoseconds) to wait for process to stop (pause) or exit. * Note that it's important that it does not overflow 32 bits; no more than * nine 9s in a row, i.e. one second. */ #define STOP_WAIT_TIMEOUT 999999999L /* * Safely kill process by checking that the process is the right one by matching * process start time. * * The algorithm: * * 1. Check that the process has the same start time as stored in lock. If it * is not, return, as we know for sure this is a wrong process. (This step * is an optimization to avoid sending SIGSTOP/SIGCONT to wrong processes). * * 2. Send SIGSTOP to the process. * * 3. Poll process state until it is stopped. * * Now the process is stopped, so we may examine it and not be afraid that it * will exit and another one with the same PID will appear. * * 4. Check that the process has the same start time as provided. * If it is, send the signal to the process. * * 5. Send SIGCONT to the process, so it may continue. * * * Returns 0 on success, -1 on error. Error code is signalled through errno. * * ERRORS * * EINVAL An invalid signal was specified. * EPERM The process does not have permission to send the signal. * ESRCH The pid does not exist or its start time does not match expected one. */ static int SafeKill(pid_t pid, time_t expected_start_time, int signal) { /* Preliminary check: in case process start time is different already, we * are sure we don't want to STOP it or kill it. */ time_t pid_start_time = GetProcessStartTime(pid); if (pid_start_time != expected_start_time) { errno = ESRCH; return -1; } /* Now to avoid race conditions we need to stop process so it won't exit * voluntarily while we are working on it */ if (kill(pid, SIGSTOP) < 0) { return -1; } if (!ProcessWaitUntilStopped(pid, STOP_WAIT_TIMEOUT)) { /* Ensure the process is started again in case of timeout or error, so * we don't leave SIGSTOP'ed processes around on overloaded or * misconfigured machine */ kill(pid, SIGCONT); errno = ESRCH; return -1; } /* Here process has stopped, so we may interrogate it without race conditions */ pid_start_time = GetProcessStartTime(pid); if (pid_start_time != expected_start_time) { /* This is a wrong process, let it continue */ kill(pid, SIGCONT); errno = ESRCH; return -1; } /* We've got a right process, signal it and let it continue */ int ret = kill(pid, signal); int saved_errno = errno; /* * We don't check return value of SIGCONT, as the process may have been * terminated already by previous kill. Moreover, what would we do with the * return code? */ kill(pid, SIGCONT); errno = saved_errno; return ret; }
/* * Wait until process specified by #pid is stopped due to SIGSTOP signal. * * @returns true if process has come to stop during #timeout_ns nanoseconds, * false if the process cannot be found or failed to stop during #timeout_ns * nanoseconds. * * FIXME: Only timeouts < 1s are supported */ static bool ProcessWaitUntilStopped(pid_t pid, long timeout_ns) { while (timeout_ns > 0) { switch (GetProcessState(pid)) { case PROCESS_STATE_RUNNING: break; case PROCESS_STATE_STOPPED: return true; case PROCESS_STATE_DOES_NOT_EXIST: return false; default: ProgrammingError("Unexpected value returned from GetProcessState"); } struct timespec ts = { .tv_sec = 0, .tv_nsec = MIN(SLEEP_POLL_TIMEOUT_NS, timeout_ns), }; while (nanosleep(&ts, &ts) < 0) { if (errno != EINTR) { ProgrammingError("Invalid timeout for nanosleep"); } } timeout_ns = MAX(0, timeout_ns - SLEEP_POLL_TIMEOUT_NS); } return false; } /* * FIXME: Only timeouts < 1s are supported */ static bool ProcessWaitUntilExited(pid_t pid, long timeout_ns) { while (timeout_ns > 0) { if (kill(pid, 0) < 0 && errno == ESRCH) { return true; } struct timespec ts = { .tv_sec = 0, .tv_nsec = MIN(SLEEP_POLL_TIMEOUT_NS, timeout_ns), }; while (nanosleep(&ts, &ts) < 0) { if (errno != EINTR) { ProgrammingError("Invalid timeout for nanosleep"); } } timeout_ns = MAX(0, timeout_ns - SLEEP_POLL_TIMEOUT_NS); } return false; } /* A timeout to wait for process to stop (pause) or exit. Note that * it's important that it not over-flow 32 bits; no more than nine 9s * in a row ! */ #define STOP_WAIT_TIMEOUT 999999999L /* * Safely kill process by checking that the process is the right one by matching * process start time. * * The algorithm: * * 1. Check that the process has the same start time as stored in lock. If it * is not, return, as we know for sure this is a wrong process. (This step * is an optimization to avoid sending SIGSTOP/SIGCONT to wrong processes). * * 2. Send SIGSTOP to the process. * * 3. Poll process state until it is stopped. * * Now the process is stopped, so we may examine it and not be afraid that it * will exit and another one with the same PID will appear. * * 4. Check that the process has the same start time as provided. * If it is, send the signal to the process. * * 5. Send SIGCONT to the process, so it may continue. * * * Returns 0 on success, -1 on error. Error code is signalled through errno. * * ERRORS * * EINVAL An invalid signal was specified. * EPERM The process does not have permission to send the signal. * ESRCH The pid does not exist or its start time does not match expected one. */ static int SafeKill(pid_t pid, time_t expected_start_time, int signal) { /* Preliminary check: in case process start time is different already, we * are sure we don't want to STOP it or kill it. */ time_t pid_start_time = GetProcessStartTime(pid); if (pid_start_time != expected_start_time) { errno = ESRCH; return -1; } /* Now to avoid race conditions we need to stop process so it won't exit * voluntarily while we are working on it */ if (kill(pid, SIGSTOP) < 0) { return -1; } if (!ProcessWaitUntilStopped(pid, STOP_WAIT_TIMEOUT)) { /* Ensure the process is started again in case of timeout or error, so * we don't leave SIGSTOP'ed processes around on overloaded or * misconfigured machine */ kill(pid, SIGCONT); errno = ESRCH; return -1; } /* Here process has stopped, so we may interrogate it without race conditions */ pid_start_time = GetProcessStartTime(pid); if (pid_start_time != expected_start_time) { /* This is a wrong process, let it continue */ kill(pid, SIGCONT); errno = ESRCH; return -1; } /* We've got a right process, signal it and let it continue */ int ret = kill(pid, signal); int saved_errno = errno; /* * We don't check return value of SIGCONT, as the proces may have been * terminated already by previous kill. Moreover, what would we do with the * return code? */ kill(pid, SIGCONT); errno = saved_errno; return ret; } static int Kill(pid_t pid, time_t process_start_time, int signal) { if (process_start_time == PROCESS_START_TIME_UNKNOWN) { /* We don't know when the process has started, do a plain kill(2) */ return kill(pid, signal); } else { return SafeKill(pid, process_start_time, signal); } } int GracefulTerminate(pid_t pid, time_t process_start_time) { if (Kill(pid, process_start_time, SIGINT) < 0) { return errno == ESRCH; } if (ProcessWaitUntilExited(pid, STOP_WAIT_TIMEOUT)) { return true; } if (Kill(pid, process_start_time, SIGTERM) < 0) { return errno == ESRCH; } if (ProcessWaitUntilExited(pid, STOP_WAIT_TIMEOUT)) { return true; } if (Kill(pid, process_start_time, SIGKILL) < 0) { return errno == ESRCH; } return true; }
static void test_get_start_time_process2(void) { time_t t2 = GetProcessStartTime(2); assert_int_equal(t2, 347151599); }