diff options
Diffstat (limited to 'base/process')
-rw-r--r-- | base/process/kill.cc | 26 | ||||
-rw-r--r-- | base/process/kill.h | 144 | ||||
-rw-r--r-- | base/process/kill_mac.cc | 173 | ||||
-rw-r--r-- | base/process/kill_posix.cc | 493 | ||||
-rw-r--r-- | base/process/kill_win.cc | 256 |
5 files changed, 1092 insertions, 0 deletions
diff --git a/base/process/kill.cc b/base/process/kill.cc new file mode 100644 index 0000000..caca348 --- /dev/null +++ b/base/process/kill.cc @@ -0,0 +1,26 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/kill.h" + +#include "base/process/process_iterator.h" + +namespace base { + +bool KillProcesses(const FilePath::StringType& executable_name, + int exit_code, + const ProcessFilter* filter) { + bool result = true; + NamedProcessIterator iter(executable_name, filter); + while (const ProcessEntry* entry = iter.NextProcessEntry()) { +#if defined(OS_WIN) + result &= KillProcessById(entry->pid(), exit_code, true); +#else + result &= KillProcess(entry->pid(), exit_code, true); +#endif + } + return result; +} + +} // namespace base diff --git a/base/process/kill.h b/base/process/kill.h new file mode 100644 index 0000000..f7ee14c --- /dev/null +++ b/base/process/kill.h @@ -0,0 +1,144 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains routines to kill processes and get the exit code and +// termination status. + +#ifndef BASE_PROCESS_KILL_H_ +#define BASE_PROCESS_KILL_H_ + +#include "base/files/file_path.h" +#include "base/process.h" +#include "base/time/time.h" + +namespace base { + +class ProcessFilter; + +// Return status values from GetTerminationStatus. Don't use these as +// exit code arguments to KillProcess*(), use platform/application +// specific values instead. +enum TerminationStatus { + TERMINATION_STATUS_NORMAL_TERMINATION, // zero exit status + TERMINATION_STATUS_ABNORMAL_TERMINATION, // non-zero exit status + TERMINATION_STATUS_PROCESS_WAS_KILLED, // e.g. SIGKILL or task manager kill + TERMINATION_STATUS_PROCESS_CRASHED, // e.g. Segmentation fault + TERMINATION_STATUS_STILL_RUNNING, // child hasn't exited yet + TERMINATION_STATUS_MAX_ENUM +}; + +// Attempts to kill all the processes on the current machine that were launched +// from the given executable name, ending them with the given exit code. If +// filter is non-null, then only processes selected by the filter are killed. +// Returns true if all processes were able to be killed off, false if at least +// one couldn't be killed. +BASE_EXPORT bool KillProcesses(const FilePath::StringType& executable_name, + int exit_code, + const ProcessFilter* filter); + +// Attempts to kill the process identified by the given process +// entry structure, giving it the specified exit code. If |wait| is true, wait +// for the process to be actually terminated before returning. +// Returns true if this is successful, false otherwise. +BASE_EXPORT bool KillProcess(ProcessHandle process, int exit_code, bool wait); + +#if defined(OS_POSIX) +// Attempts to kill the process group identified by |process_group_id|. Returns +// true on success. +BASE_EXPORT bool KillProcessGroup(ProcessHandle process_group_id); +#endif // defined(OS_POSIX) + +#if defined(OS_WIN) +BASE_EXPORT bool KillProcessById(ProcessId process_id, + int exit_code, + bool wait); +#endif // defined(OS_WIN) + +// Get the termination status of the process by interpreting the +// circumstances of the child process' death. |exit_code| is set to +// the status returned by waitpid() on POSIX, and from +// GetExitCodeProcess() on Windows. |exit_code| may be NULL if the +// caller is not interested in it. Note that on Linux, this function +// will only return a useful result the first time it is called after +// the child exits (because it will reap the child and the information +// will no longer be available). +BASE_EXPORT TerminationStatus GetTerminationStatus(ProcessHandle handle, + int* exit_code); + +#if defined(OS_POSIX) +// Wait for the process to exit and get the termination status. See +// GetTerminationStatus for more information. On POSIX systems, we can't call +// WaitForExitCode and then GetTerminationStatus as the child will be reaped +// when WaitForExitCode return and this information will be lost. +BASE_EXPORT TerminationStatus WaitForTerminationStatus(ProcessHandle handle, + int* exit_code); +#endif // defined(OS_POSIX) + +// Waits for process to exit. On POSIX systems, if the process hasn't been +// signaled then puts the exit code in |exit_code|; otherwise it's considered +// a failure. On Windows |exit_code| is always filled. Returns true on success, +// and closes |handle| in any case. +BASE_EXPORT bool WaitForExitCode(ProcessHandle handle, int* exit_code); + +// Waits for process to exit. If it did exit within |timeout_milliseconds|, +// then puts the exit code in |exit_code|, and returns true. +// In POSIX systems, if the process has been signaled then |exit_code| is set +// to -1. Returns false on failure (the caller is then responsible for closing +// |handle|). +// The caller is always responsible for closing the |handle|. +BASE_EXPORT bool WaitForExitCodeWithTimeout(ProcessHandle handle, + int* exit_code, + base::TimeDelta timeout); + +// Wait for all the processes based on the named executable to exit. If filter +// is non-null, then only processes selected by the filter are waited on. +// Returns after all processes have exited or wait_milliseconds have expired. +// Returns true if all the processes exited, false otherwise. +BASE_EXPORT bool WaitForProcessesToExit( + const FilePath::StringType& executable_name, + base::TimeDelta wait, + const ProcessFilter* filter); + +// Wait for a single process to exit. Return true if it exited cleanly within +// the given time limit. On Linux |handle| must be a child process, however +// on Mac and Windows it can be any process. +BASE_EXPORT bool WaitForSingleProcess(ProcessHandle handle, + base::TimeDelta wait); + +// Waits a certain amount of time (can be 0) for all the processes with a given +// executable name to exit, then kills off any of them that are still around. +// If filter is non-null, then only processes selected by the filter are waited +// on. Killed processes are ended with the given exit code. Returns false if +// any processes needed to be killed, true if they all exited cleanly within +// the wait_milliseconds delay. +BASE_EXPORT bool CleanupProcesses(const FilePath::StringType& executable_name, + base::TimeDelta wait, + int exit_code, + const ProcessFilter* filter); + +// This method ensures that the specified process eventually terminates, and +// then it closes the given process handle. +// +// It assumes that the process has already been signalled to exit, and it +// begins by waiting a small amount of time for it to exit. If the process +// does not appear to have exited, then this function starts to become +// aggressive about ensuring that the process terminates. +// +// On Linux this method does not block the calling thread. +// On OS X this method may block for up to 2 seconds. +// +// NOTE: The process handle must have been opened with the PROCESS_TERMINATE +// and SYNCHRONIZE permissions. +// +BASE_EXPORT void EnsureProcessTerminated(ProcessHandle process_handle); + +#if defined(OS_POSIX) && !defined(OS_MACOSX) +// The nicer version of EnsureProcessTerminated() that is patient and will +// wait for |process_handle| to finish and then reap it. +BASE_EXPORT void EnsureProcessGetsReaped(ProcessHandle process_handle); +#endif + +} // namespace base + +#endif // BASE_PROCESS_KILL_H_ diff --git a/base/process/kill_mac.cc b/base/process/kill_mac.cc new file mode 100644 index 0000000..5ebca5d --- /dev/null +++ b/base/process/kill_mac.cc @@ -0,0 +1,173 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/kill.h" + +#include <signal.h> +#include <sys/event.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" + +namespace base { + +namespace { + +const int kWaitBeforeKillSeconds = 2; + +// Reap |child| process. This call blocks until completion. +void BlockingReap(pid_t child) { + const pid_t result = HANDLE_EINTR(waitpid(child, NULL, 0)); + if (result == -1) { + DPLOG(ERROR) << "waitpid(" << child << ", NULL, 0)"; + } +} + +// Waits for |timeout| seconds for the given |child| to exit and reap it. If +// the child doesn't exit within the time specified, kills it. +// +// This function takes two approaches: first, it tries to use kqueue to +// observe when the process exits. kevent can monitor a kqueue with a +// timeout, so this method is preferred to wait for a specified period of +// time. Once the kqueue indicates the process has exited, waitpid will reap +// the exited child. If the kqueue doesn't provide an exit event notification, +// before the timeout expires, or if the kqueue fails or misbehaves, the +// process will be mercilessly killed and reaped. +// +// A child process passed to this function may be in one of several states: +// running, terminated and not yet reaped, and (apparently, and unfortunately) +// terminated and already reaped. Normally, a process will at least have been +// asked to exit before this function is called, but this is not required. +// If a process is terminating and unreaped, there may be a window between the +// time that kqueue will no longer recognize it and when it becomes an actual +// zombie that a non-blocking (WNOHANG) waitpid can reap. This condition is +// detected when kqueue indicates that the process is not running and a +// non-blocking waitpid fails to reap the process but indicates that it is +// still running. In this event, a blocking attempt to reap the process +// collects the known-dying child, preventing zombies from congregating. +// +// In the event that the kqueue misbehaves entirely, as it might under a +// EMFILE condition ("too many open files", or out of file descriptors), this +// function will forcibly kill and reap the child without delay. This +// eliminates another potential zombie vector. (If you're out of file +// descriptors, you're probably deep into something else, but that doesn't +// mean that zombies be allowed to kick you while you're down.) +// +// The fact that this function seemingly can be called to wait on a child +// that's not only already terminated but already reaped is a bit of a +// problem: a reaped child's pid can be reclaimed and may refer to a distinct +// process in that case. The fact that this function can seemingly be called +// to wait on a process that's not even a child is also a problem: kqueue will +// work in that case, but waitpid won't, and killing a non-child might not be +// the best approach. +void WaitForChildToDie(pid_t child, int timeout) { + DCHECK(child > 0); + DCHECK(timeout > 0); + + // DON'T ADD ANY EARLY RETURNS TO THIS FUNCTION without ensuring that + // |child| has been reaped. Specifically, even if a kqueue, kevent, or other + // call fails, this function should fall back to the last resort of trying + // to kill and reap the process. Not observing this rule will resurrect + // zombies. + + int result; + + int kq = HANDLE_EINTR(kqueue()); + if (kq == -1) { + DPLOG(ERROR) << "kqueue()"; + } else { + file_util::ScopedFD auto_close_kq(&kq); + + struct kevent change = {0}; + EV_SET(&change, child, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); + result = HANDLE_EINTR(kevent(kq, &change, 1, NULL, 0, NULL)); + + if (result == -1) { + if (errno != ESRCH) { + DPLOG(ERROR) << "kevent (setup " << child << ")"; + } else { + // At this point, one of the following has occurred: + // 1. The process has died but has not yet been reaped. + // 2. The process has died and has already been reaped. + // 3. The process is in the process of dying. It's no longer + // kqueueable, but it may not be waitable yet either. Mark calls + // this case the "zombie death race". + + result = HANDLE_EINTR(waitpid(child, NULL, WNOHANG)); + + if (result != 0) { + // A positive result indicates case 1. waitpid succeeded and reaped + // the child. A result of -1 indicates case 2. The child has already + // been reaped. In both of these cases, no further action is + // necessary. + return; + } + + // |result| is 0, indicating case 3. The process will be waitable in + // short order. Fall back out of the kqueue code to kill it (for good + // measure) and reap it. + } + } else { + // Keep track of the elapsed time to be able to restart kevent if it's + // interrupted. + TimeDelta remaining_delta = TimeDelta::FromSeconds(timeout); + TimeTicks deadline = TimeTicks::Now() + remaining_delta; + result = -1; + struct kevent event = {0}; + while (remaining_delta.InMilliseconds() > 0) { + const struct timespec remaining_timespec = remaining_delta.ToTimeSpec(); + result = kevent(kq, NULL, 0, &event, 1, &remaining_timespec); + if (result == -1 && errno == EINTR) { + remaining_delta = deadline - TimeTicks::Now(); + result = 0; + } else { + break; + } + } + + if (result == -1) { + DPLOG(ERROR) << "kevent (wait " << child << ")"; + } else if (result > 1) { + DLOG(ERROR) << "kevent (wait " << child << "): unexpected result " + << result; + } else if (result == 1) { + if ((event.fflags & NOTE_EXIT) && + (event.ident == static_cast<uintptr_t>(child))) { + // The process is dead or dying. This won't block for long, if at + // all. + BlockingReap(child); + return; + } else { + DLOG(ERROR) << "kevent (wait " << child + << "): unexpected event: fflags=" << event.fflags + << ", ident=" << event.ident; + } + } + } + } + + // The child is still alive, or is very freshly dead. Be sure by sending it + // a signal. This is safe even if it's freshly dead, because it will be a + // zombie (or on the way to zombiedom) and kill will return 0 even if the + // signal is not delivered to a live process. + result = kill(child, SIGKILL); + if (result == -1) { + DPLOG(ERROR) << "kill(" << child << ", SIGKILL)"; + } else { + // The child is definitely on the way out now. BlockingReap won't need to + // wait for long, if at all. + BlockingReap(child); + } +} + +} // namespace + +void EnsureProcessTerminated(ProcessHandle process) { + WaitForChildToDie(process, kWaitBeforeKillSeconds); +} + +} // namespace base diff --git a/base/process/kill_posix.cc b/base/process/kill_posix.cc new file mode 100644 index 0000000..61a6020 --- /dev/null +++ b/base/process/kill_posix.cc @@ -0,0 +1,493 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/kill.h" + +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "base/process/process_iterator.h" +#include "base/process_util.h" +#include "base/synchronization/waitable_event.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/threading/platform_thread.h" + +namespace base { + +namespace { + +int WaitpidWithTimeout(ProcessHandle handle, + int64 wait_milliseconds, + bool* success) { + // This POSIX version of this function only guarantees that we wait no less + // than |wait_milliseconds| for the process to exit. The child process may + // exit sometime before the timeout has ended but we may still block for up + // to 256 milliseconds after the fact. + // + // waitpid() has no direct support on POSIX for specifying a timeout, you can + // either ask it to block indefinitely or return immediately (WNOHANG). + // When a child process terminates a SIGCHLD signal is sent to the parent. + // Catching this signal would involve installing a signal handler which may + // affect other parts of the application and would be difficult to debug. + // + // Our strategy is to call waitpid() once up front to check if the process + // has already exited, otherwise to loop for wait_milliseconds, sleeping for + // at most 256 milliseconds each time using usleep() and then calling + // waitpid(). The amount of time we sleep starts out at 1 milliseconds, and + // we double it every 4 sleep cycles. + // + // usleep() is speced to exit if a signal is received for which a handler + // has been installed. This means that when a SIGCHLD is sent, it will exit + // depending on behavior external to this function. + // + // This function is used primarily for unit tests, if we want to use it in + // the application itself it would probably be best to examine other routes. + int status = -1; + pid_t ret_pid = HANDLE_EINTR(waitpid(handle, &status, WNOHANG)); + static const int64 kMaxSleepInMicroseconds = 1 << 18; // ~256 milliseconds. + int64 max_sleep_time_usecs = 1 << 10; // ~1 milliseconds. + int64 double_sleep_time = 0; + + // If the process hasn't exited yet, then sleep and try again. + TimeTicks wakeup_time = TimeTicks::Now() + + TimeDelta::FromMilliseconds(wait_milliseconds); + while (ret_pid == 0) { + TimeTicks now = TimeTicks::Now(); + if (now > wakeup_time) + break; + // Guaranteed to be non-negative! + int64 sleep_time_usecs = (wakeup_time - now).InMicroseconds(); + // Sleep for a bit while we wait for the process to finish. + if (sleep_time_usecs > max_sleep_time_usecs) + sleep_time_usecs = max_sleep_time_usecs; + + // usleep() will return 0 and set errno to EINTR on receipt of a signal + // such as SIGCHLD. + usleep(sleep_time_usecs); + ret_pid = HANDLE_EINTR(waitpid(handle, &status, WNOHANG)); + + if ((max_sleep_time_usecs < kMaxSleepInMicroseconds) && + (double_sleep_time++ % 4 == 0)) { + max_sleep_time_usecs *= 2; + } + } + + if (success) + *success = (ret_pid != -1); + + return status; +} + +TerminationStatus GetTerminationStatusImpl(ProcessHandle handle, + bool can_block, + int* exit_code) { + int status = 0; + const pid_t result = HANDLE_EINTR(waitpid(handle, &status, + can_block ? 0 : WNOHANG)); + if (result == -1) { + DPLOG(ERROR) << "waitpid(" << handle << ")"; + if (exit_code) + *exit_code = 0; + return TERMINATION_STATUS_NORMAL_TERMINATION; + } else if (result == 0) { + // the child hasn't exited yet. + if (exit_code) + *exit_code = 0; + return TERMINATION_STATUS_STILL_RUNNING; + } + + if (exit_code) + *exit_code = status; + + if (WIFSIGNALED(status)) { + switch (WTERMSIG(status)) { + case SIGABRT: + case SIGBUS: + case SIGFPE: + case SIGILL: + case SIGSEGV: + return TERMINATION_STATUS_PROCESS_CRASHED; + case SIGINT: + case SIGKILL: + case SIGTERM: + return TERMINATION_STATUS_PROCESS_WAS_KILLED; + default: + break; + } + } + + if (WIFEXITED(status) && WEXITSTATUS(status) != 0) + return TERMINATION_STATUS_ABNORMAL_TERMINATION; + + return TERMINATION_STATUS_NORMAL_TERMINATION; +} + +} // namespace + +// Attempts to kill the process identified by the given process +// entry structure. Ignores specified exit_code; posix can't force that. +// Returns true if this is successful, false otherwise. +bool KillProcess(ProcessHandle process_id, int exit_code, bool wait) { + DCHECK_GT(process_id, 1) << " tried to kill invalid process_id"; + if (process_id <= 1) + return false; + bool result = kill(process_id, SIGTERM) == 0; + if (result && wait) { + int tries = 60; + + if (RunningOnValgrind()) { + // Wait for some extra time when running under Valgrind since the child + // processes may take some time doing leak checking. + tries *= 2; + } + + unsigned sleep_ms = 4; + + // The process may not end immediately due to pending I/O + bool exited = false; + while (tries-- > 0) { + pid_t pid = HANDLE_EINTR(waitpid(process_id, NULL, WNOHANG)); + if (pid == process_id) { + exited = true; + break; + } + if (pid == -1) { + if (errno == ECHILD) { + // The wait may fail with ECHILD if another process also waited for + // the same pid, causing the process state to get cleaned up. + exited = true; + break; + } + DPLOG(ERROR) << "Error waiting for process " << process_id; + } + + usleep(sleep_ms * 1000); + const unsigned kMaxSleepMs = 1000; + if (sleep_ms < kMaxSleepMs) + sleep_ms *= 2; + } + + // If we're waiting and the child hasn't died by now, force it + // with a SIGKILL. + if (!exited) + result = kill(process_id, SIGKILL) == 0; + } + + if (!result) + DPLOG(ERROR) << "Unable to terminate process " << process_id; + + return result; +} + +bool KillProcessGroup(ProcessHandle process_group_id) { + bool result = kill(-1 * process_group_id, SIGKILL) == 0; + if (!result) + DPLOG(ERROR) << "Unable to terminate process group " << process_group_id; + return result; +} + +TerminationStatus GetTerminationStatus(ProcessHandle handle, int* exit_code) { + return GetTerminationStatusImpl(handle, false /* can_block */, exit_code); +} + +TerminationStatus WaitForTerminationStatus(ProcessHandle handle, + int* exit_code) { + return GetTerminationStatusImpl(handle, true /* can_block */, exit_code); +} + +bool WaitForExitCode(ProcessHandle handle, int* exit_code) { + int status; + if (HANDLE_EINTR(waitpid(handle, &status, 0)) == -1) { + NOTREACHED(); + return false; + } + + if (WIFEXITED(status)) { + *exit_code = WEXITSTATUS(status); + return true; + } + + // If it didn't exit cleanly, it must have been signaled. + DCHECK(WIFSIGNALED(status)); + return false; +} + +bool WaitForExitCodeWithTimeout(ProcessHandle handle, + int* exit_code, + base::TimeDelta timeout) { + bool waitpid_success = false; + int status = WaitpidWithTimeout(handle, timeout.InMilliseconds(), + &waitpid_success); + if (status == -1) + return false; + if (!waitpid_success) + return false; + if (WIFSIGNALED(status)) { + *exit_code = -1; + return true; + } + if (WIFEXITED(status)) { + *exit_code = WEXITSTATUS(status); + return true; + } + return false; +} + +bool WaitForProcessesToExit(const FilePath::StringType& executable_name, + base::TimeDelta wait, + const ProcessFilter* filter) { + bool result = false; + + // TODO(port): This is inefficient, but works if there are multiple procs. + // TODO(port): use waitpid to avoid leaving zombies around + + base::TimeTicks end_time = base::TimeTicks::Now() + wait; + do { + NamedProcessIterator iter(executable_name, filter); + if (!iter.NextProcessEntry()) { + result = true; + break; + } + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); + } while ((end_time - base::TimeTicks::Now()) > base::TimeDelta()); + + return result; +} + +#if defined(OS_MACOSX) +// Using kqueue on Mac so that we can wait on non-child processes. +// We can't use kqueues on child processes because we need to reap +// our own children using wait. +static bool WaitForSingleNonChildProcess(ProcessHandle handle, + base::TimeDelta wait) { + DCHECK_GT(handle, 0); + DCHECK(wait.InMilliseconds() == base::kNoTimeout || wait > base::TimeDelta()); + + int kq = kqueue(); + if (kq == -1) { + DPLOG(ERROR) << "kqueue"; + return false; + } + file_util::ScopedFD kq_closer(&kq); + + struct kevent change = {0}; + EV_SET(&change, handle, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); + int result = HANDLE_EINTR(kevent(kq, &change, 1, NULL, 0, NULL)); + if (result == -1) { + if (errno == ESRCH) { + // If the process wasn't found, it must be dead. + return true; + } + + DPLOG(ERROR) << "kevent (setup " << handle << ")"; + return false; + } + + // Keep track of the elapsed time to be able to restart kevent if it's + // interrupted. + bool wait_forever = wait.InMilliseconds() == base::kNoTimeout; + base::TimeDelta remaining_delta; + base::TimeTicks deadline; + if (!wait_forever) { + remaining_delta = wait; + deadline = base::TimeTicks::Now() + remaining_delta; + } + + result = -1; + struct kevent event = {0}; + + while (wait_forever || remaining_delta > base::TimeDelta()) { + struct timespec remaining_timespec; + struct timespec* remaining_timespec_ptr; + if (wait_forever) { + remaining_timespec_ptr = NULL; + } else { + remaining_timespec = remaining_delta.ToTimeSpec(); + remaining_timespec_ptr = &remaining_timespec; + } + + result = kevent(kq, NULL, 0, &event, 1, remaining_timespec_ptr); + + if (result == -1 && errno == EINTR) { + if (!wait_forever) { + remaining_delta = deadline - base::TimeTicks::Now(); + } + result = 0; + } else { + break; + } + } + + if (result < 0) { + DPLOG(ERROR) << "kevent (wait " << handle << ")"; + return false; + } else if (result > 1) { + DLOG(ERROR) << "kevent (wait " << handle << "): unexpected result " + << result; + return false; + } else if (result == 0) { + // Timed out. + return false; + } + + DCHECK_EQ(result, 1); + + if (event.filter != EVFILT_PROC || + (event.fflags & NOTE_EXIT) == 0 || + event.ident != static_cast<uintptr_t>(handle)) { + DLOG(ERROR) << "kevent (wait " << handle + << "): unexpected event: filter=" << event.filter + << ", fflags=" << event.fflags + << ", ident=" << event.ident; + return false; + } + + return true; +} +#endif // OS_MACOSX + +bool WaitForSingleProcess(ProcessHandle handle, base::TimeDelta wait) { + ProcessHandle parent_pid = GetParentProcessId(handle); + ProcessHandle our_pid = Process::Current().handle(); + if (parent_pid != our_pid) { +#if defined(OS_MACOSX) + // On Mac we can wait on non child processes. + return WaitForSingleNonChildProcess(handle, wait); +#else + // Currently on Linux we can't handle non child processes. + NOTIMPLEMENTED(); +#endif // OS_MACOSX + } + + bool waitpid_success; + int status = -1; + if (wait.InMilliseconds() == base::kNoTimeout) { + waitpid_success = (HANDLE_EINTR(waitpid(handle, &status, 0)) != -1); + } else { + status = WaitpidWithTimeout( + handle, wait.InMilliseconds(), &waitpid_success); + } + + if (status != -1) { + DCHECK(waitpid_success); + return WIFEXITED(status); + } else { + return false; + } +} + +bool CleanupProcesses(const FilePath::StringType& executable_name, + base::TimeDelta wait, + int exit_code, + const ProcessFilter* filter) { + bool exited_cleanly = WaitForProcessesToExit(executable_name, wait, filter); + if (!exited_cleanly) + KillProcesses(executable_name, exit_code, filter); + return exited_cleanly; +} + +#if !defined(OS_MACOSX) + +namespace { + +// Return true if the given child is dead. This will also reap the process. +// Doesn't block. +static bool IsChildDead(pid_t child) { + const pid_t result = HANDLE_EINTR(waitpid(child, NULL, WNOHANG)); + if (result == -1) { + DPLOG(ERROR) << "waitpid(" << child << ")"; + NOTREACHED(); + } else if (result > 0) { + // The child has died. + return true; + } + + return false; +} + +// A thread class which waits for the given child to exit and reaps it. +// If the child doesn't exit within a couple of seconds, kill it. +class BackgroundReaper : public PlatformThread::Delegate { + public: + BackgroundReaper(pid_t child, unsigned timeout) + : child_(child), + timeout_(timeout) { + } + + // Overridden from PlatformThread::Delegate: + virtual void ThreadMain() OVERRIDE { + WaitForChildToDie(); + delete this; + } + + void WaitForChildToDie() { + // Wait forever case. + if (timeout_ == 0) { + pid_t r = HANDLE_EINTR(waitpid(child_, NULL, 0)); + if (r != child_) { + DPLOG(ERROR) << "While waiting for " << child_ + << " to terminate, we got the following result: " << r; + } + return; + } + + // There's no good way to wait for a specific child to exit in a timed + // fashion. (No kqueue on Linux), so we just loop and sleep. + + // Wait for 2 * timeout_ 500 milliseconds intervals. + for (unsigned i = 0; i < 2 * timeout_; ++i) { + PlatformThread::Sleep(TimeDelta::FromMilliseconds(500)); + if (IsChildDead(child_)) + return; + } + + if (kill(child_, SIGKILL) == 0) { + // SIGKILL is uncatchable. Since the signal was delivered, we can + // just wait for the process to die now in a blocking manner. + if (HANDLE_EINTR(waitpid(child_, NULL, 0)) < 0) + DPLOG(WARNING) << "waitpid"; + } else { + DLOG(ERROR) << "While waiting for " << child_ << " to terminate we" + << " failed to deliver a SIGKILL signal (" << errno << ")."; + } + } + + private: + const pid_t child_; + // Number of seconds to wait, if 0 then wait forever and do not attempt to + // kill |child_|. + const unsigned timeout_; + + DISALLOW_COPY_AND_ASSIGN(BackgroundReaper); +}; + +} // namespace + +void EnsureProcessTerminated(ProcessHandle process) { + // If the child is already dead, then there's nothing to do. + if (IsChildDead(process)) + return; + + const unsigned timeout = 2; // seconds + BackgroundReaper* reaper = new BackgroundReaper(process, timeout); + PlatformThread::CreateNonJoinable(0, reaper); +} + +void EnsureProcessGetsReaped(ProcessHandle process) { + // If the child is already dead, then there's nothing to do. + if (IsChildDead(process)) + return; + + BackgroundReaper* reaper = new BackgroundReaper(process, 0); + PlatformThread::CreateNonJoinable(0, reaper); +} + +#endif // !defined(OS_MACOSX) + +} // namespace base diff --git a/base/process/kill_win.cc b/base/process/kill_win.cc new file mode 100644 index 0000000..929ec4f --- /dev/null +++ b/base/process/kill_win.cc @@ -0,0 +1,256 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/process/kill.h" + +#include <io.h> +#include <windows.h> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/process/process_iterator.h" +#include "base/process_util.h" +#include "base/win/object_watcher.h" + +namespace base { + +namespace { + +// Exit codes with special meanings on Windows. +const DWORD kNormalTerminationExitCode = 0; +const DWORD kDebuggerInactiveExitCode = 0xC0000354; +const DWORD kKeyboardInterruptExitCode = 0xC000013A; +const DWORD kDebuggerTerminatedExitCode = 0x40010004; + +// This exit code is used by the Windows task manager when it kills a +// process. It's value is obviously not that unique, and it's +// surprising to me that the task manager uses this value, but it +// seems to be common practice on Windows to test for it as an +// indication that the task manager has killed something if the +// process goes away. +const DWORD kProcessKilledExitCode = 1; + +// Maximum amount of time (in milliseconds) to wait for the process to exit. +static const int kWaitInterval = 2000; + +class TimerExpiredTask : public win::ObjectWatcher::Delegate { + public: + explicit TimerExpiredTask(ProcessHandle process); + ~TimerExpiredTask(); + + void TimedOut(); + + // MessageLoop::Watcher ----------------------------------------------------- + virtual void OnObjectSignaled(HANDLE object); + + private: + void KillProcess(); + + // The process that we are watching. + ProcessHandle process_; + + win::ObjectWatcher watcher_; + + DISALLOW_COPY_AND_ASSIGN(TimerExpiredTask); +}; + +TimerExpiredTask::TimerExpiredTask(ProcessHandle process) : process_(process) { + watcher_.StartWatching(process_, this); +} + +TimerExpiredTask::~TimerExpiredTask() { + TimedOut(); + DCHECK(!process_) << "Make sure to close the handle."; +} + +void TimerExpiredTask::TimedOut() { + if (process_) + KillProcess(); +} + +void TimerExpiredTask::OnObjectSignaled(HANDLE object) { + CloseHandle(process_); + process_ = NULL; +} + +void TimerExpiredTask::KillProcess() { + // Stop watching the process handle since we're killing it. + watcher_.StopWatching(); + + // OK, time to get frisky. We don't actually care when the process + // terminates. We just care that it eventually terminates, and that's what + // TerminateProcess should do for us. Don't check for the result code since + // it fails quite often. This should be investigated eventually. + base::KillProcess(process_, kProcessKilledExitCode, false); + + // Now, just cleanup as if the process exited normally. + OnObjectSignaled(process_); +} + +} // namespace + +bool KillProcess(ProcessHandle process, int exit_code, bool wait) { + bool result = (TerminateProcess(process, exit_code) != FALSE); + if (result && wait) { + // The process may not end immediately due to pending I/O + if (WAIT_OBJECT_0 != WaitForSingleObject(process, 60 * 1000)) + DLOG_GETLASTERROR(ERROR) << "Error waiting for process exit"; + } else if (!result) { + DLOG_GETLASTERROR(ERROR) << "Unable to terminate process"; + } + return result; +} + +// Attempts to kill the process identified by the given process +// entry structure, giving it the specified exit code. +// Returns true if this is successful, false otherwise. +bool KillProcessById(ProcessId process_id, int exit_code, bool wait) { + HANDLE process = OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE, + FALSE, // Don't inherit handle + process_id); + if (!process) { + DLOG_GETLASTERROR(ERROR) << "Unable to open process " << process_id; + return false; + } + bool ret = KillProcess(process, exit_code, wait); + CloseHandle(process); + return ret; +} + +TerminationStatus GetTerminationStatus(ProcessHandle handle, int* exit_code) { + DWORD tmp_exit_code = 0; + + if (!::GetExitCodeProcess(handle, &tmp_exit_code)) { + DLOG_GETLASTERROR(FATAL) << "GetExitCodeProcess() failed"; + if (exit_code) { + // This really is a random number. We haven't received any + // information about the exit code, presumably because this + // process doesn't have permission to get the exit code, or + // because of some other cause for GetExitCodeProcess to fail + // (MSDN docs don't give the possible failure error codes for + // this function, so it could be anything). But we don't want + // to leave exit_code uninitialized, since that could cause + // random interpretations of the exit code. So we assume it + // terminated "normally" in this case. + *exit_code = kNormalTerminationExitCode; + } + // Assume the child has exited normally if we can't get the exit + // code. + return TERMINATION_STATUS_NORMAL_TERMINATION; + } + if (tmp_exit_code == STILL_ACTIVE) { + DWORD wait_result = WaitForSingleObject(handle, 0); + if (wait_result == WAIT_TIMEOUT) { + if (exit_code) + *exit_code = wait_result; + return TERMINATION_STATUS_STILL_RUNNING; + } + + if (wait_result == WAIT_FAILED) { + DLOG_GETLASTERROR(ERROR) << "WaitForSingleObject() failed"; + } else { + DCHECK_EQ(WAIT_OBJECT_0, wait_result); + + // Strange, the process used 0x103 (STILL_ACTIVE) as exit code. + NOTREACHED(); + } + + return TERMINATION_STATUS_ABNORMAL_TERMINATION; + } + + if (exit_code) + *exit_code = tmp_exit_code; + + switch (tmp_exit_code) { + case kNormalTerminationExitCode: + return TERMINATION_STATUS_NORMAL_TERMINATION; + case kDebuggerInactiveExitCode: // STATUS_DEBUGGER_INACTIVE. + case kKeyboardInterruptExitCode: // Control-C/end session. + case kDebuggerTerminatedExitCode: // Debugger terminated process. + case kProcessKilledExitCode: // Task manager kill. + return TERMINATION_STATUS_PROCESS_WAS_KILLED; + default: + // All other exit codes indicate crashes. + return TERMINATION_STATUS_PROCESS_CRASHED; + } +} + +bool WaitForExitCode(ProcessHandle handle, int* exit_code) { + bool success = WaitForExitCodeWithTimeout( + handle, exit_code, base::TimeDelta::FromMilliseconds(INFINITE)); + CloseProcessHandle(handle); + return success; +} + +bool WaitForExitCodeWithTimeout(ProcessHandle handle, + int* exit_code, + base::TimeDelta timeout) { + if (::WaitForSingleObject(handle, timeout.InMilliseconds()) != WAIT_OBJECT_0) + return false; + DWORD temp_code; // Don't clobber out-parameters in case of failure. + if (!::GetExitCodeProcess(handle, &temp_code)) + return false; + + *exit_code = temp_code; + return true; +} + +bool WaitForProcessesToExit(const FilePath::StringType& executable_name, + base::TimeDelta wait, + const ProcessFilter* filter) { + const ProcessEntry* entry; + bool result = true; + DWORD start_time = GetTickCount(); + + NamedProcessIterator iter(executable_name, filter); + while ((entry = iter.NextProcessEntry())) { + DWORD remaining_wait = std::max<int64>( + 0, wait.InMilliseconds() - (GetTickCount() - start_time)); + HANDLE process = OpenProcess(SYNCHRONIZE, + FALSE, + entry->th32ProcessID); + DWORD wait_result = WaitForSingleObject(process, remaining_wait); + CloseHandle(process); + result = result && (wait_result == WAIT_OBJECT_0); + } + + return result; +} + +bool WaitForSingleProcess(ProcessHandle handle, base::TimeDelta wait) { + int exit_code; + if (!WaitForExitCodeWithTimeout(handle, &exit_code, wait)) + return false; + return exit_code == 0; +} + +bool CleanupProcesses(const FilePath::StringType& executable_name, + base::TimeDelta wait, + int exit_code, + const ProcessFilter* filter) { + bool exited_cleanly = WaitForProcessesToExit(executable_name, wait, filter); + if (!exited_cleanly) + KillProcesses(executable_name, exit_code, filter); + return exited_cleanly; +} + +void EnsureProcessTerminated(ProcessHandle process) { + DCHECK(process != GetCurrentProcess()); + + // If already signaled, then we are done! + if (WaitForSingleObject(process, 0) == WAIT_OBJECT_0) { + CloseHandle(process); + return; + } + + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&TimerExpiredTask::TimedOut, + base::Owned(new TimerExpiredTask(process))), + base::TimeDelta::FromMilliseconds(kWaitInterval)); +} + +} // namespace base |