// Copyright (c) 2009 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 "chrome/common/process_watcher.h" #include #include #include #include #include #include "base/eintr_wrapper.h" #include "base/file_util.h" #include "base/time.h" namespace { // 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) { PLOG(ERROR) << "waitpid(" << child << ")"; NOTREACHED(); } } // Waits for |timeout| seconds for the given |child| to exit and reap it. // If the child doesn't exit within a couple of seconds, kill it. void WaitForChildToDie(pid_t child, unsigned timeout) { int kq = HANDLE_EINTR(kqueue()); file_util::ScopedFD auto_close(&kq); if (kq == -1) { PLOG(ERROR) << "Failed to create kqueue"; return; } struct kevent event_to_add = {0}; EV_SET(&event_to_add, child, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL); // Register interest with kqueue. int result = HANDLE_EINTR(kevent(kq, &event_to_add, 1, NULL, 0, NULL)); if (result == -1 && errno == ESRCH) { // A "No Such Process" error is fine, the process may have died already // and been reaped by someone else. But make sure that it was/is reaped. // Don't report an error in case it was already reaped. HANDLE_EINTR(waitpid(child, NULL, WNOHANG)); return; } if (result == -1) { PLOG(ERROR) << "Failed to register event to listen for death of pid " << child; return; } struct kevent event = {0}; DCHECK(timeout != 0); int num_processes_that_died = -1; using base::Time; using base::TimeDelta; // We need to keep track of the elapsed time - if kevent() returns // EINTR in the middle of blocking call we want to make up what's left // of the timeout. TimeDelta time_left = TimeDelta::FromSeconds(timeout); Time wakeup = Time::Now() + time_left; while(time_left.InMilliseconds() > 0) { const struct timespec timeout = time_left.ToTimeSpec(); num_processes_that_died = kevent(kq, NULL, 0, &event, 1, &timeout); if (num_processes_that_died >= 0) break; if (num_processes_that_died == -1 && errno == EINTR) { time_left = wakeup - Time::Now(); continue; } // If we got here, kevent() must have returned -1. PLOG(ERROR) << "kevent() failed"; break; } if (num_processes_that_died == -1) { PLOG(ERROR) << "kevent failed"; return; } if (num_processes_that_died == 1) { if (event.fflags & NOTE_EXIT && event.ident == static_cast(child)) { // Process died, it's safe to make a blocking call here since the // kqueue() notification occurs when the process is already zombified. BlockingReap(child); return; } else { PLOG(ERROR) << "kevent() returned unexpected result - ke.fflags =" << event.fflags << " ke.ident =" << event.ident << " while listening for pid=" << child; } } // If we got here the child is still alive so kill it... 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. BlockingReap(child); } else { PLOG(ERROR) << "While waiting for " << child << " to terminate we" << " failed to deliver a SIGKILL signal"; } } } // namespace void ProcessWatcher::EnsureProcessTerminated(base::ProcessHandle process) { WaitForChildToDie(process, 2); }