blob: a9cba20fb49f8095ab62f571e0aa8600ea96add6 [file] [log] [blame]
// Copyright (c) 2014 The Chromium OS 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 "login_manager/child_exit_dispatcher.h"
#include <algorithm>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <base/bind.h>
#include <base/logging.h>
#include <brillo/asynchronous_signal_handler.h>
#include "login_manager/child_exit_handler.h"
#include "login_manager/child_job.h"
namespace login_manager {
ChildExitDispatcher::ChildExitDispatcher(
brillo::AsynchronousSignalHandler* signal_handler,
const std::vector<ChildExitHandler*>& handlers)
: signal_handler_(signal_handler), handlers_(handlers) {
signal_handler_->RegisterHandler(
SIGCHLD,
base::Bind(&ChildExitDispatcher::OnSigChld, base::Unretained(this)));
}
ChildExitDispatcher::~ChildExitDispatcher() {
signal_handler_->UnregisterHandler(SIGCHLD);
}
bool ChildExitDispatcher::OnSigChld(const struct signalfd_siginfo& sig_info) {
DCHECK_EQ(sig_info.ssi_signo, SIGCHLD) << "Wrong signal!";
if (sig_info.ssi_code == CLD_STOPPED || sig_info.ssi_code == CLD_CONTINUED) {
return false;
}
auto ptr = weak_factory_.GetWeakPtr();
siginfo_t info;
// Reap all terminated children.
while (true) {
memset(&info, 0, sizeof(info));
int result = waitid(P_ALL, 0, &info, WEXITED | WNOHANG);
if (result != 0) {
if (errno != ECHILD)
PLOG(FATAL) << "waitid failed";
break;
}
if (info.si_pid == 0)
break;
// Before calling Dispatch(), check if this class is still alive.
// If not, do not call Dispatch() to avoid use-after-free.
// The situation happens when this instance is destroyed in HandleExit().
// Note that this still consumes all pending children even in the case
// for consistent behavior.
// TODO(crbug.com/1053782): Migrate to libbrillo library.
if (ptr)
Dispatch(info);
}
// Continue listening to SIGCHLD
return false;
}
void ChildExitDispatcher::Dispatch(const siginfo_t& info) {
if (info.si_code == CLD_EXITED) {
CHECK_NE(info.si_status, ChildJobInterface::kCantSetUid) << info.si_pid;
CHECK_NE(info.si_status, ChildJobInterface::kCantSetEnv) << info.si_pid;
CHECK_NE(info.si_status, ChildJobInterface::kCantExec) << info.si_pid;
}
for (auto* handler : handlers_) {
if (handler->HandleExit(info)) {
return;
}
}
// No handler handled the exit.
VLOG(1) << "Unmanaged process " << info.si_pid << " exited with "
<< ChildExitHandler::GetExitDescription(info);
}
} // namespace login_manager