1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
|
// 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 <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/posix/eintr_wrapper.h"
#include "base/process/process_iterator.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/platform_thread.h"
#include "build/build_config.h"
namespace base {
namespace {
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:
case SIGTRAP:
case SIGSYS:
return TERMINATION_STATUS_PROCESS_CRASHED;
case SIGKILL:
#if defined(OS_CHROMEOS)
// On ChromeOS, only way a process gets kill by SIGKILL
// is by oom-killer.
return TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM;
#endif
case SIGINT:
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
#if !defined(OS_NACL_NONSFI)
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;
}
#endif // !defined(OS_NACL_NONSFI)
TerminationStatus GetTerminationStatus(ProcessHandle handle, int* exit_code) {
return GetTerminationStatusImpl(handle, false /* can_block */, exit_code);
}
TerminationStatus GetKnownDeadTerminationStatus(ProcessHandle handle,
int* exit_code) {
bool result = kill(handle, SIGKILL) == 0;
if (!result)
DPLOG(ERROR) << "Unable to terminate process " << handle;
return GetTerminationStatusImpl(handle, true /* can_block */, exit_code);
}
#if !defined(OS_NACL_NONSFI)
bool WaitForProcessesToExit(const FilePath::StringType& executable_name,
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
TimeTicks end_time = TimeTicks::Now() + wait;
do {
NamedProcessIterator iter(executable_name, filter);
if (!iter.NextProcessEntry()) {
result = true;
break;
}
PlatformThread::Sleep(TimeDelta::FromMilliseconds(100));
} while ((end_time - TimeTicks::Now()) > TimeDelta());
return result;
}
bool CleanupProcesses(const FilePath::StringType& executable_name,
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:
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(Process process) {
// If the child is already dead, then there's nothing to do.
if (IsChildDead(process.Pid()))
return;
const unsigned timeout = 2; // seconds
BackgroundReaper* reaper = new BackgroundReaper(process.Pid(), timeout);
PlatformThread::CreateNonJoinable(0, reaper);
}
void EnsureProcessGetsReaped(ProcessId pid) {
// If the child is already dead, then there's nothing to do.
if (IsChildDead(pid))
return;
BackgroundReaper* reaper = new BackgroundReaper(pid, 0);
PlatformThread::CreateNonJoinable(0, reaper);
}
#endif // !defined(OS_MACOSX)
#endif // !defined(OS_NACL_NONSFI)
} // namespace base
|