blob: 8860ad0706fb02f0e060e9483e90dce78d611422 [file] [log] [blame]
// Copyright 2015 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 "shill/process_manager.h"
#include <signal.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include "shill/event_dispatcher.h"
using base::Closure;
using std::map;
using std::string;
using std::vector;
namespace shill {
namespace {
base::LazyInstance<ProcessManager> g_process_manager =
LAZY_INSTANCE_INITIALIZER;
static const int kTerminationTimeoutSeconds = 2;
bool SetupChild(const map<string, string>& env, bool terminate_with_parent) {
// Setup environment variables.
clearenv();
for (const auto& key_value : env) {
setenv(key_value.first.c_str(), key_value.second.c_str(), 0);
}
if (terminate_with_parent) {
prctl(PR_SET_PDEATHSIG, SIGTERM);
}
return true;
}
} // namespace
ProcessManager::ProcessManager() {}
ProcessManager::~ProcessManager() {}
// static
ProcessManager* ProcessManager::GetInstance() {
return g_process_manager.Pointer();
}
void ProcessManager::Init(EventDispatcher* dispatcher) {
async_signal_handler_.Init();
process_reaper_.Register(&async_signal_handler_);
dispatcher_ = dispatcher;
minijail_ = chromeos::Minijail::GetInstance();
}
pid_t ProcessManager::StartProcess(
const tracked_objects::Location& from_here,
const base::FilePath& program,
const vector<string>& arguments,
const map<string, string>& environment,
bool terminate_with_parent,
const base::Callback<void(int)>& exit_callback) {
// Setup/create child process.
std::unique_ptr<chromeos::Process> process(new chromeos::ProcessImpl());
process->AddArg(program.value());
for (const auto& option : arguments) {
process->AddArg(option);
}
process->SetPreExecCallback(
base::Bind(&SetupChild, environment, terminate_with_parent));
if (!process->Start()) {
LOG(ERROR) << "Failed to start child process for " << program.value();
return -1;
}
// Setup watcher for the child process.
pid_t pid = process->pid();
CHECK(process_reaper_.WatchForChild(
from_here,
pid,
base::Bind(&ProcessManager::OnProcessExited,
weak_factory_.GetWeakPtr(),
pid)));
// Release ownership of the child process from the |process| object, so that
// child process will not get killed on destruction of |process| object.
process->Release();
watched_processes_.emplace(pid, exit_callback);
return pid;
}
pid_t ProcessManager::StartProcessInMinijail(
const tracked_objects::Location& from_here,
const base::FilePath& program,
const std::vector<std::string>& arguments,
const std::string& user,
const std::string& group,
uint64_t capmask,
const base::Callback<void(int)>& exit_callback) {
vector<char*> args;
args.push_back(const_cast<char*>(program.value().c_str()));
for (const auto& arg : arguments) {
args.push_back(const_cast<char*>(arg.c_str()));
}
args.push_back(nullptr);
struct minijail* jail = minijail_->New();
minijail_->DropRoot(jail, user.c_str(), group.c_str());
minijail_->UseCapabilities(jail, capmask);
pid_t pid;
if (!minijail_->RunAndDestroy(jail, args, &pid)) {
LOG(ERROR) << "Unable to spawn " << program.value() << " in a jail.";
return -1;
}
watched_processes_.emplace(pid, exit_callback);
return pid;
}
bool ProcessManager::StopProcess(pid_t pid) {
if (pending_termination_processes_.find(pid) !=
pending_termination_processes_.end()) {
LOG(ERROR) << "Process " << pid << " already being stopped.";
return false;
}
if (watched_processes_.find(pid) == watched_processes_.end()) {
LOG(ERROR) << "Process " << pid << " not being watched";
return false;
}
// Caller not interested in watching this process anymore, since the
// process termination is initiated by the caller.
watched_processes_.erase(pid);
// Attempt to send SIGTERM signal first.
return TerminateProcess(pid, false);
}
void ProcessManager::OnProcessExited(pid_t pid, const siginfo_t& info) {
// Invoke the exit callback if the process is being watched.
auto watched_process = watched_processes_.find(pid);
if (watched_process != watched_processes_.end()) {
base::Callback<void(int)> callback = watched_process->second;
watched_processes_.erase(watched_process);
callback.Run(info.si_status);
return;
}
// Process terminated by us, cancel timeout handler.
auto terminated_process = pending_termination_processes_.find(pid);
if (terminated_process != pending_termination_processes_.end()) {
terminated_process->second->Cancel();
pending_termination_processes_.erase(terminated_process);
return;
}
NOTREACHED() << "Unknown process " << pid << " status " << info.si_status;
}
void ProcessManager::ProcessTerminationTimeoutHandler(pid_t pid,
bool kill_signal) {
CHECK(pending_termination_processes_.find(pid) !=
pending_termination_processes_.end());
pending_termination_processes_.erase(pid);
// Process still not killed after SIGKILL signal.
if (kill_signal) {
LOG(ERROR) << "Timeout waiting for process to be killed.";
return;
}
// Retry using SIGKILL signal.
TerminateProcess(pid, true);
}
bool ProcessManager::TerminateProcess(pid_t pid, bool kill_signal) {
int signal = (kill_signal) ? SIGKILL : SIGTERM;
if (kill(pid, signal) < 0) {
PLOG(ERROR) << "Failed to send " << signal << " signal to process " << pid;
return false;
}
std::unique_ptr<TerminationTimeoutCallback> termination_callback(
new TerminationTimeoutCallback(
base::Bind(&ProcessManager::ProcessTerminationTimeoutHandler,
weak_factory_.GetWeakPtr(),
pid,
kill_signal)));
dispatcher_->PostDelayedTask(termination_callback->callback(),
kTerminationTimeoutSeconds * 1000);
pending_termination_processes_.emplace(pid, std::move(termination_callback));
return true;
}
} // namespace shill