blob: aa71b4d831db5fac7225f9177ac19f8a9bb6a298 [file] [log] [blame]
// Copyright (c) 2012 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/session_manager_service.h"
#include <dbus/dbus-glib-lowlevel.h>
#include <errno.h>
#include <fcntl.h>
#include <glib.h>
#include <grp.h>
#include <inttypes.h>
#include <secder.h>
#include <signal.h>
#include <stdio.h>
#include <sys/errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <algorithm>
#include <utility>
#include <vector>
#include <base/basictypes.h>
#include <base/bind.h>
#include <base/callback.h>
#include <base/command_line.h>
#include <base/file_path.h>
#include <base/file_util.h>
#include <base/logging.h>
#include <base/memory/scoped_ptr.h>
#include <base/message_loop.h>
#include <base/message_loop_proxy.h>
#include <base/posix/eintr_wrapper.h>
#include <base/run_loop.h>
#include <base/stl_util.h>
#include <base/string_util.h>
#include <base/time.h>
#include <chromeos/dbus/dbus.h>
#include <chromeos/dbus/error_constants.h>
#include <chromeos/dbus/service_constants.h>
#include <chromeos/switches/chrome_switches.h>
#include <chromeos/utility.h>
#include "login_manager/browser_job.h"
#include "login_manager/dbus_glib_shim.h"
#include "login_manager/device_local_account_policy_service.h"
#include "login_manager/device_management_backend.pb.h"
#include "login_manager/generator_job.h"
#include "login_manager/key_generator.h"
#include "login_manager/liveness_checker_impl.h"
#include "login_manager/login_metrics.h"
#include "login_manager/nss_util.h"
#include "login_manager/policy_store.h"
#include "login_manager/regen_mitigator.h"
#include "login_manager/session_manager_impl.h"
#include "login_manager/system_utils.h"
// Forcibly namespace the dbus-bindings generated server bindings instead of
// modifying the files afterward.
namespace login_manager { // NOLINT
namespace gobject { // NOLINT
#include "login_manager/server.h"
} // namespace gobject
} // namespace login_manager
namespace em = enterprise_management;
namespace login_manager {
int g_shutdown_pipe_write_fd = -1;
int g_shutdown_pipe_read_fd = -1;
int g_child_pipe_write_fd = -1;
int g_child_pipe_read_fd = -1;
namespace {
// I need a do-nothing action for SIGALRM, or using alarm() will kill me.
void DoNothing(int signal) {}
// Write |data| to |fd|, retrying on EINTR.
void RetryingWrite(int fd, const char* data, size_t data_length) {
size_t written = 0;
do {
int rv = HANDLE_EINTR(write(fd, data + written, data_length - written));
RAW_CHECK(rv >= 0);
written += rv;
} while (written < data_length);
}
// Common code between SIG{HUP,INT,TERM}Handler.
void GracefulShutdownHandler(int signal) {
RAW_CHECK(g_shutdown_pipe_write_fd != -1);
RAW_CHECK(g_shutdown_pipe_read_fd != -1);
RetryingWrite(g_shutdown_pipe_write_fd,
reinterpret_cast<const char*>(&signal),
sizeof(signal));
RAW_LOG(INFO,
"Successfully wrote to shutdown pipe, resetting signal handler.");
}
void SIGHUPHandler(int signal) {
RAW_CHECK(signal == SIGHUP);
RAW_LOG(INFO, "Handling SIGHUP.");
GracefulShutdownHandler(signal);
}
void SIGINTHandler(int signal) {
RAW_CHECK(signal == SIGINT);
RAW_LOG(INFO, "Handling SIGINT.");
GracefulShutdownHandler(signal);
}
void SIGTERMHandler(int signal) {
RAW_CHECK(signal == SIGTERM);
RAW_LOG(INFO, "Handling SIGTERM.");
GracefulShutdownHandler(signal);
}
void SIGCHLDHandler(int signal, siginfo_t* info, void*) {
RAW_CHECK(signal == SIGCHLD);
RAW_LOG(INFO, "Handling SIGCHLD.");
RAW_CHECK(g_child_pipe_write_fd != -1);
RAW_CHECK(g_child_pipe_read_fd != -1);
RetryingWrite(g_child_pipe_write_fd,
reinterpret_cast<const char*>(info),
sizeof(siginfo_t));
RAW_LOG(INFO, "Successfully wrote to child pipe.");
}
} // anonymous namespace
const char SessionManagerService::kFlagFileDir[] = "/var/run/session_manager";
// TODO(mkrebs): Remove CollectChrome timeout and file when
// crosbug.com/5872 is fixed.
// When crash-reporter based crash reporting of Chrome is enabled
// (which should only be during test runs) we use
// kKillTimeoutCollectChrome instead of the kill timeout specified at
// the command line.
const int SessionManagerService::kKillTimeoutCollectChrome = 60;
const char SessionManagerService::kCollectChromeFile[] =
"/mnt/stateful_partition/etc/collect_chrome_crashes";
namespace {
// Device-local account state directory.
const FilePath::CharType kDeviceLocalAccountStateDir[] =
FILE_PATH_LITERAL("/var/lib/device_local_accounts");
} // namespace
void SessionManagerService::TestApi::ScheduleChildExit(pid_t pid, int status) {
siginfo_t info;
info.si_pid = pid;
if (WIFEXITED(status)) {
info.si_code = CLD_EXITED;
info.si_status = WEXITSTATUS(status);
} else {
info.si_status = WTERMSIG(status);
}
session_manager_service_->loop_proxy_->PostTask(
FROM_HERE,
base::Bind(&SessionManagerService::HandleChildExit,
session_manager_service_, info));
}
SessionManagerService::SessionManagerService(
scoped_ptr<BrowserJobInterface> child_job,
const base::Closure& quit_closure,
int kill_timeout,
bool enable_browser_abort_on_hang,
base::TimeDelta hang_detection_interval,
SystemUtils* utils)
: browser_(child_job.Pass()),
exit_on_child_done_(false),
kill_timeout_(base::TimeDelta::FromSeconds(kill_timeout)),
session_manager_(NULL),
main_loop_(NULL),
loop_proxy_(NULL),
quit_closure_(quit_closure),
system_(utils),
nss_(NssUtil::Create()),
key_gen_(new KeyGenerator(utils, this)),
login_metrics_(NULL),
liveness_checker_(NULL),
enable_browser_abort_on_hang_(enable_browser_abort_on_hang),
liveness_checking_interval_(hang_detection_interval),
set_uid_(false),
shutting_down_(false),
shutdown_already_(false),
exit_code_(SUCCESS) {
int pipefd[2];
PLOG_IF(DFATAL, pipe2(pipefd, O_CLOEXEC) < 0) << "Failed to create pipe";
g_shutdown_pipe_read_fd = pipefd[0];
g_shutdown_pipe_write_fd = pipefd[1];
PLOG_IF(FATAL, pipe2(pipefd,
O_CLOEXEC | O_NONBLOCK) < 0) << "Failed to create pipe";
g_child_pipe_read_fd = pipefd[0];
g_child_pipe_write_fd = pipefd[1];
SetUpHandlers();
}
SessionManagerService::~SessionManagerService() {
if (main_loop_)
g_main_loop_unref(main_loop_);
if (session_manager_)
g_object_unref(session_manager_);
// Remove this in case it was added by StopSession().
g_idle_remove_by_data(this);
RevertHandlers();
}
bool SessionManagerService::Initialize() {
// Install the type-info for the service with dbus.
dbus_g_object_type_install_info(
gobject::session_manager_get_type(),
&gobject::dbus_glib_session_manager_object_info);
dbus_glib_global_set_disable_legacy_property_access();
// Register DBus signals with glib.
// The gobject system seems to need to have glib signals hanging off
// the SessionManager gtype that correspond with the DBus signals we
// wish to emit. These calls create and register them.
// TODO(cmasone): remove these when we migrate away from dbus-glib.
g_signal_new("session_state_changed",
gobject::session_manager_get_type(),
G_SIGNAL_RUN_LAST,
0, // class offset
NULL, NULL, // accumulator and data
// TODO: This is wrong. If you need to use it at some point
// (probably only if you need to listen to this GLib signal
// instead of to the D-Bus signal), you should generate an
// appropriate marshaller that takes two string arguments.
// See e.g. http://goo.gl/vEGT4.
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, // return type
2, // num params
G_TYPE_STRING, // "started", "stopping", or "stopped"
G_TYPE_STRING); // current user
g_signal_new("login_prompt_visible",
gobject::session_manager_get_type(),
G_SIGNAL_RUN_LAST,
0, // class offset
NULL, NULL, // accumulator and data
NULL, // Use default marshaller.
G_TYPE_NONE, // return type
0); // num params
g_signal_new("screen_is_locked",
gobject::session_manager_get_type(),
G_SIGNAL_RUN_LAST,
0, // class offset
NULL, NULL, // accumulator and data
NULL, // Use default marshaller.
G_TYPE_NONE, // return type
0); // num params
g_signal_new("screen_is_unlocked",
gobject::session_manager_get_type(),
G_SIGNAL_RUN_LAST,
0, // class offset
NULL, NULL, // accumulator and data
NULL, // Use default marshaller.
G_TYPE_NONE, // return type
0); // num params
LOG(INFO) << "SessionManagerService starting";
if (!Reset())
return false;
FilePath flag_file_dir(kFlagFileDir);
if (!file_util::CreateDirectory(flag_file_dir)) {
PLOG(ERROR) << "Cannot create flag file directory at " << kFlagFileDir;
return false;
}
login_metrics_.reset(new LoginMetrics(flag_file_dir));
reinterpret_cast<BrowserJob*>(browser_.get())->set_login_metrics(
login_metrics_.get());
// Initially store in derived-type pointer, so that we can initialize
// appropriately below, and also use as delegate for device_policy.
SessionManagerImpl* impl =
new SessionManagerImpl(
scoped_ptr<UpstartSignalEmitter>(new UpstartSignalEmitter),
this, login_metrics_.get(), nss_.get(), system_);
// The below require loop_proxy_, created in Reset(), to be set already.
liveness_checker_.reset(
new LivenessCheckerImpl(this,
system_,
loop_proxy_,
enable_browser_abort_on_hang_,
liveness_checking_interval_));
owner_key_.reset(new PolicyKey(nss_->GetOwnerKeyFilePath(), nss_.get()));
scoped_ptr<OwnerKeyLossMitigator> mitigator(
new RegenMitigator(key_gen_.get(), set_uid_, uid_));
scoped_ptr<DevicePolicyService> device_policy(
DevicePolicyService::Create(login_metrics_.get(),
owner_key_.get(),
mitigator.Pass(),
nss_.get(),
loop_proxy_));
device_policy->set_delegate(impl);
scoped_ptr<UserPolicyServiceFactory> user_policy_factory(
new UserPolicyServiceFactory(getuid(), loop_proxy_, nss_.get(), system_));
scoped_ptr<DeviceLocalAccountPolicyService> device_local_account_policy(
new DeviceLocalAccountPolicyService(FilePath(kDeviceLocalAccountStateDir),
owner_key_.get(),
loop_proxy_));
impl->InjectPolicyServices(device_policy.Pass(),
user_policy_factory.Pass(),
device_local_account_policy.Pass());
impl_.reset(impl);
if (!InitializeImpl())
return false;
// Set any flags that were specified system-wide.
if (device_policy)
browser_->SetExtraArguments(device_policy->GetStartUpFlags());
g_io_add_watch_full(g_io_channel_unix_new(g_shutdown_pipe_read_fd),
G_PRIORITY_HIGH_IDLE,
GIOCondition(G_IO_IN | G_IO_PRI | G_IO_HUP),
HandleKill,
this,
NULL);
g_io_add_watch_full(g_io_channel_unix_new(g_child_pipe_read_fd),
G_PRIORITY_HIGH_IDLE,
GIOCondition(G_IO_IN | G_IO_PRI | G_IO_HUP),
HandleChildrenExiting,
this,
NULL);
// Wire impl to dbus-glib glue.
if (session_manager_)
g_object_unref(session_manager_);
session_manager_ =
reinterpret_cast<gobject::SessionManager*>(
g_object_new(gobject::session_manager_get_type(), NULL));
session_manager_->impl = impl_.get();
return true;
}
bool SessionManagerService::Register(
const chromeos::dbus::BusConnection &connection) {
if (!chromeos::dbus::AbstractDbusService::Register(connection))
return false;
const std::string filter =
StringPrintf("type='method_call', interface='%s'", service_interface());
DBusConnection* conn =
::dbus_g_connection_get_connection(connection.g_connection());
CHECK(conn);
DBusError error;
::dbus_error_init(&error);
::dbus_bus_add_match(conn, filter.c_str(), &error);
if (::dbus_error_is_set(&error)) {
LOG(WARNING) << "Failed to add match to bus: " << error.name << ", message="
<< (error.message ? error.message : "unknown error");
return false;
}
if (!::dbus_connection_add_filter(conn,
&SessionManagerService::FilterMessage,
this,
NULL)) {
LOG(WARNING) << "Failed to add filter to connection";
return false;
}
return true;
}
bool SessionManagerService::Reset() {
if (main_loop_)
g_main_loop_unref(main_loop_);
main_loop_ = g_main_loop_new(NULL, false);
if (!main_loop_) {
LOG(ERROR) << "Failed to create main loop";
return false;
}
loop_proxy_ = MessageLoop::current()->message_loop_proxy();
return true;
}
bool SessionManagerService::Run() {
NOTREACHED();
return false;
}
bool SessionManagerService::Shutdown() {
NOTREACHED();
return false;
}
void SessionManagerService::Finalize() {
LOG(INFO) << "SessionManagerService exiting";
liveness_checker_->Stop();
CleanupChildren(GetKillTimeout());
impl_->Finalize();
impl_->AnnounceSessionStopped();
}
void SessionManagerService::ScheduleShutdown() {
SetExitAndScheduleShutdown(SUCCESS);
}
void SessionManagerService::RunBrowser() {
browser_->RunInBackground();
liveness_checker_->Start();
}
void SessionManagerService::AbortBrowser(int signal,
const std::string& message) {
browser_->KillEverything(signal, message);
}
void SessionManagerService::RestartBrowserWithArgs(
const std::vector<std::string>& args, bool args_are_extra) {
// Waiting for Chrome to shutdown takes too much time.
// We're killing it immediately hoping that data Chrome uses before
// logging in is not corrupted.
// TODO(avayvod): Remove RestartJob when crosbug.com/6924 is fixed.
browser_->KillEverything(SIGKILL, "");
if (args_are_extra)
browser_->SetExtraArguments(args);
else
browser_->SetArguments(args);
// The browser will be restarted in HandleBrowserExit().
}
void SessionManagerService::SetBrowserSessionForUser(
const std::string& username,
const std::string& userhash) {
browser_->StartSession(username, userhash);
}
void SessionManagerService::SetFlagsForUser(
const std::string& username,
const std::vector<std::string>& flags) {
browser_->SetExtraArguments(flags);
}
void SessionManagerService::RunKeyGenerator(const std::string& username) {
if (!key_gen_->Start(username, set_uid_ ? uid_ : 0))
LOG(ERROR) << "Can't fork to run key generator.";
}
void SessionManagerService::AdoptKeyGeneratorJob(
scoped_ptr<GeneratorJobInterface> job,
pid_t pid) {
DLOG(INFO) << "Adopting key generator job with pid " << pid;
generator_.swap(job);
}
void SessionManagerService::AbandonKeyGeneratorJob() {
generator_.reset(NULL);
}
void SessionManagerService::ProcessNewOwnerKey(const std::string& username,
const FilePath& key_file) {
DLOG(INFO) << "Processing generated key at " << key_file.value();
impl_->ImportValidateAndStoreGeneratedKey(username, key_file);
}
bool SessionManagerService::IsGenerator(pid_t pid) {
return (generator_ &&
generator_->CurrentPid() > 0 &&
pid == generator_->CurrentPid());
}
bool SessionManagerService::IsBrowser(pid_t pid) {
return (browser_ &&
browser_->CurrentPid() > 0 &&
pid == browser_->CurrentPid());
}
bool SessionManagerService::IsManagedProcess(pid_t pid) {
return IsBrowser(pid) || IsGenerator(pid);
}
void SessionManagerService::HandleBrowserExit() {
// If I could wait for descendants here, I would. Instead, I kill them.
browser_->KillEverything(SIGKILL, "Session termination");
browser_->ClearPid();
// Do nothing if already shutting down.
if (shutting_down_)
return;
liveness_checker_->Stop();
if (impl_->ScreenIsLocked()) {
LOG(ERROR) << "Screen locked, shutting down";
SetExitAndScheduleShutdown(CRASH_WHILE_SCREEN_LOCKED);
return;
}
DCHECK(browser_.get());
if (browser_->ShouldStop()) {
LOG(WARNING) << "Child stopped, shutting down";
SetExitAndScheduleShutdown(CHILD_EXITING_TOO_FAST);
} else if (browser_->ShouldRunBrowser()) {
// TODO(cmasone): deal with fork failing in RunBrowser()
RunBrowser();
} else {
LOG(INFO) << "Should NOT run " << browser_->GetName() << " again.";
AllowGracefulExitOrRunForever();
}
}
void SessionManagerService::HandleChildExit(const siginfo_t& info) {
CHECK(system_->ChildIsGone(info.si_pid, base::TimeDelta::FromSeconds(5)));
if (!IsManagedProcess(info.si_pid))
return;
if (shutting_down_)
return;
std::string job_name;
if (IsBrowser(info.si_pid))
job_name = browser_->GetName();
else if (IsGenerator(info.si_pid))
job_name = generator_->GetName();
LOG(INFO) << "Handling " << job_name << "(" << info.si_pid << ") exit.";
if (info.si_code == CLD_EXITED) {
LOG_IF(ERROR, info.si_status != 0) << " Exited with exit code "
<< info.si_status;
CHECK(info.si_status != ChildJobInterface::kCantSetUid);
CHECK(info.si_status != ChildJobInterface::kCantExec);
} else {
LOG(ERROR) << " Exited with signal " << info.si_status;
}
if (IsBrowser(info.si_pid))
HandleBrowserExit();
else if (IsGenerator(info.si_pid))
key_gen_->HandleExit(info.si_status == 0);
}
///////////////////////////////////////////////////////////////////////////////
// glib event handlers
// static
gboolean SessionManagerService::HandleChildrenExiting(GIOChannel* source,
GIOCondition condition,
gpointer data) {
SessionManagerService* manager = static_cast<SessionManagerService*>(data);
siginfo_t info;
while (file_util::ReadFromFD(g_io_channel_unix_get_fd(source),
reinterpret_cast<char*>(&info),
sizeof(info))) {
DCHECK(info.si_signo == SIGCHLD) << "Wrong signal!";
manager->HandleChildExit(info);
}
return TRUE; // So that the event source that called this remains installed.
}
gboolean SessionManagerService::HandleKill(GIOChannel* source,
GIOCondition condition,
gpointer data) {
// We only get called if there's data on the pipe. If there's data, we're
// supposed to exit. So, don't even bother to read it.
LOG(INFO) << "HUP, INT, or TERM received; exiting.";
SessionManagerService* manager = static_cast<SessionManagerService*>(data);
manager->ScheduleShutdown();
return FALSE; // So that the event source that called this gets removed.
}
///////////////////////////////////////////////////////////////////////////////
// Utility Methods
// static
DBusHandlerResult SessionManagerService::FilterMessage(DBusConnection* conn,
DBusMessage* message,
void* data) {
SessionManagerService* service = static_cast<SessionManagerService*>(data);
if (::dbus_message_is_method_call(message,
service->service_interface(),
kSessionManagerRestartJob)) {
const char* sender = ::dbus_message_get_sender(message);
if (!sender) {
LOG(ERROR) << "Call to RestartJob has no sender";
return DBUS_HANDLER_RESULT_HANDLED;
}
LOG(INFO) << "Received RestartJob from " << sender;
DBusMessage* get_pid =
::dbus_message_new_method_call("org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
"GetConnectionUnixProcessID");
CHECK(get_pid);
::dbus_message_append_args(get_pid,
DBUS_TYPE_STRING, &sender,
DBUS_TYPE_INVALID);
DBusMessage* got_pid =
::dbus_connection_send_with_reply_and_block(conn, get_pid, -1, NULL);
::dbus_message_unref(get_pid);
if (!got_pid) {
LOG(ERROR) << "Could not look up sender of RestartJob";
return DBUS_HANDLER_RESULT_HANDLED;
}
uint32 pid;
if (!::dbus_message_get_args(got_pid, NULL,
DBUS_TYPE_UINT32, &pid,
DBUS_TYPE_INVALID)) {
::dbus_message_unref(got_pid);
LOG(ERROR) << "Could not extract pid of sender of RestartJob";
return DBUS_HANDLER_RESULT_HANDLED;
}
::dbus_message_unref(got_pid);
if (!service->IsBrowser(pid)) {
LOG(WARNING) << "Sender of RestartJob is no child of mine!";
return DBUS_HANDLER_RESULT_HANDLED;
}
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
void SessionManagerService::SetUpHandlers() {
// I have to ignore SIGUSR1, because Xorg sends it to this process when it's
// got no clients and is ready for new ones. If we don't ignore it, we die.
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = SIG_IGN;
CHECK(sigaction(SIGUSR1, &action, NULL) == 0);
action.sa_handler = DoNothing;
CHECK(sigaction(SIGALRM, &action, NULL) == 0);
// For all termination handlers, we want the default re-installed after
// we get a shot at handling the signal.
action.sa_flags = SA_RESETHAND;
// We need to handle SIGTERM, because that is how many POSIX-based distros ask
// processes to quit gracefully at shutdown time.
action.sa_handler = SIGTERMHandler;
CHECK(sigaction(SIGTERM, &action, NULL) == 0);
// Also handle SIGINT, if session_manager is being run in the foreground.
action.sa_handler = SIGINTHandler;
CHECK(sigaction(SIGINT, &action, NULL) == 0);
// And SIGHUP, for when the terminal disappears. On shutdown, many Linux
// distros send SIGHUP, SIGTERM, and then SIGKILL.
action.sa_handler = SIGHUPHandler;
CHECK(sigaction(SIGHUP, &action, NULL) == 0);
// Manually set an action for SIGCHLD, so that we can convert
// receiving a signal on child exit to file descriptor
// activity. This way we can handle this kind of event in a
// MessageLoop. The flags are set such that the action receives a
// siginfo struct with handy exit status info, but only when the
// child actually exits -- as opposed to also when it gets STOP or CONT.
memset(&action, 0, sizeof(action));
action.sa_flags = (SA_SIGINFO | SA_NOCLDSTOP);
action.sa_sigaction = SIGCHLDHandler;
CHECK(sigaction(SIGCHLD, &action, NULL) == 0);
}
void SessionManagerService::RevertHandlers() {
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = SIG_DFL;
CHECK(sigaction(SIGUSR1, &action, NULL) == 0);
CHECK(sigaction(SIGALRM, &action, NULL) == 0);
CHECK(sigaction(SIGTERM, &action, NULL) == 0);
CHECK(sigaction(SIGINT, &action, NULL) == 0);
CHECK(sigaction(SIGHUP, &action, NULL) == 0);
CHECK(sigaction(SIGCHLD, &action, NULL) == 0);
}
base::TimeDelta SessionManagerService::GetKillTimeout() {
if (file_util::PathExists(FilePath(kCollectChromeFile)))
return base::TimeDelta::FromSeconds(kKillTimeoutCollectChrome);
else
return kill_timeout_;
}
bool SessionManagerService::InitializeImpl() {
if (!impl_->Initialize()) {
LOG(ERROR) << "Policy key is likely corrupt. Initiating device wipe.";
impl_->StartDeviceWipe(NULL, NULL);
impl_->Finalize();
exit_code_ = MUST_WIPE_DEVICE;
return false;
}
return true;
}
void SessionManagerService::AllowGracefulExitOrRunForever() {
if (exit_on_child_done_) {
LOG(INFO) << "SessionManagerService set to exit on child done";
loop_proxy_->PostTask(
FROM_HERE,
base::Bind(base::IgnoreResult(&SessionManagerService::ScheduleShutdown),
this));
} else {
DLOG(INFO) << "Ok, running forever...";
}
}
void SessionManagerService::SetExitAndScheduleShutdown(ExitCode code) {
shutting_down_ = true;
exit_code_ = code;
impl_->AnnounceSessionStoppingIfNeeded();
loop_proxy_->PostTask(FROM_HERE, quit_closure_);
LOG(INFO) << "SessionManagerService quitting run loop";
}
void SessionManagerService::CleanupChildren(base::TimeDelta timeout) {
if (browser_ && browser_->CurrentPid() > 0)
browser_->Kill(SIGTERM, "");
if (generator_ && generator_->CurrentPid() > 0)
generator_->Kill(SIGTERM, "");
if (browser_ && browser_->CurrentPid() > 0)
browser_->WaitAndAbort(timeout);
if (generator_ && generator_->CurrentPid() > 0)
generator_->WaitAndAbort(timeout);
}
// static
std::vector<std::string> SessionManagerService::GetArgList(
const std::vector<std::string>& args) {
std::vector<std::string>::const_iterator start_arg = args.begin();
if (!args.empty() && *start_arg == "--")
++start_arg;
return std::vector<std::string>(start_arg, args.end());
}
} // namespace login_manager