blob: cf7c891f34b9b58a4aca91ba9ec3c3232ebd9adb [file] [log] [blame]
// Copyright 2016 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/app/mash/mash_runner.h"
#include <string>
#include "base/at_exit.h"
#include "base/base_paths.h"
#include "base/base_switches.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/debug/debugger.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/i18n/icu_util.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/path_service.h"
#include "base/process/launch.h"
#include "base/process/process.h"
#include "base/run_loop.h"
#include "base/sys_info.h"
#include "base/task_scheduler/task_scheduler.h"
#include "base/threading/sequenced_worker_pool.h"
#include "base/threading/thread.h"
#include "base/trace_event/trace_event.h"
#include "chrome/app/mash/chrome_mash_catalog.h"
#include "components/tracing/common/trace_to_console.h"
#include "components/tracing/common/tracing_switches.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/service_names.mojom.h"
#include "mash/common/config.h"
#include "mash/package/mash_packaged_service.h"
#include "mash/quick_launch/public/interfaces/constants.mojom.h"
#include "mojo/edk/embedder/embedder.h"
#include "mojo/edk/embedder/scoped_ipc_support.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "services/catalog/public/interfaces/catalog.mojom.h"
#include "services/catalog/public/interfaces/constants.mojom.h"
#include "services/service_manager/background/background_service_manager.h"
#include "services/service_manager/public/cpp/connector.h"
#include "services/service_manager/public/cpp/identity.h"
#include "services/service_manager/public/cpp/service.h"
#include "services/service_manager/public/cpp/service_context.h"
#include "services/service_manager/public/cpp/standalone_service/standalone_service.h"
#include "services/service_manager/public/interfaces/service_factory.mojom.h"
#include "services/service_manager/runner/common/client_util.h"
#include "services/service_manager/runner/common/switches.h"
#include "services/service_manager/runner/init.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_paths.h"
#include "ui/base/ui_base_switches.h"
#if defined(OS_CHROMEOS)
#include "base/debug/leak_annotations.h"
#include "chrome/app/mash/mash_crash_reporter_client.h"
#include "components/crash/content/app/breakpad_linux.h" // nogncheck
#endif
using service_manager::mojom::ServiceFactory;
namespace {
// kProcessType used to identify child processes.
const char* kMashChild = "mash-child";
const char kChromeMashServiceName[] = "chrome_mash";
bool IsChild() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kProcessType) &&
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kProcessType) == kMashChild;
}
void InitializeResources() {
ui::RegisterPathProvider();
const std::string locale =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kLang);
// This loads the Chrome's resources (chrome_100_percent.pak etc.)
ui::ResourceBundle::InitSharedInstanceWithLocale(
locale, nullptr, ui::ResourceBundle::LOAD_COMMON_RESOURCES);
}
class ServiceProcessLauncherDelegateImpl
: public service_manager::ServiceProcessLauncher::Delegate {
public:
ServiceProcessLauncherDelegateImpl() {}
~ServiceProcessLauncherDelegateImpl() override {}
private:
// service_manager::ServiceProcessLauncher::Delegate:
void AdjustCommandLineArgumentsForTarget(
const service_manager::Identity& target,
base::CommandLine* command_line) override {
if (target.name() == kChromeMashServiceName ||
target.name() == content::mojom::kBrowserServiceName) {
base::FilePath exe_path;
base::PathService::Get(base::FILE_EXE, &exe_path);
command_line->SetProgram(exe_path);
}
if (target.name() != content::mojom::kBrowserServiceName) {
// If running anything other than the browser process, launch a mash
// child process. The new process will execute MashRunner::RunChild().
command_line->AppendSwitchASCII(switches::kProcessType, kMashChild);
#if defined(OS_WIN)
command_line->AppendArg(switches::kPrefetchArgumentOther);
#endif
return;
}
// When launching the browser process, ensure that we don't inherit the
// --mash flag so it proceeds with the normal content/browser startup path.
// Eliminate all copies in case the developer passed more than one.
base::CommandLine::StringVector new_argv;
for (const base::CommandLine::StringType& arg : command_line->argv()) {
if (arg != FILE_PATH_LITERAL("--mash"))
new_argv.push_back(arg);
}
*command_line = base::CommandLine(new_argv);
}
DISALLOW_COPY_AND_ASSIGN(ServiceProcessLauncherDelegateImpl);
};
#if defined(OS_CHROMEOS)
// Initializes breakpad crash reporting. MashCrashReporterClient handles
// registering crash keys.
void InitializeCrashReporting() {
DCHECK(!breakpad::IsCrashReporterEnabled());
// Intentionally leaked. The crash client needs to outlive all other code.
MashCrashReporterClient* client = new MashCrashReporterClient;
ANNOTATE_LEAKING_OBJECT_PTR(client);
crash_reporter::SetCrashReporterClient(client);
// For now all standalone services act like the browser process and write
// their own in-process crash dumps. When ash and the window server are
// sandboxed we will need to hook up the crash signal file descriptor, make
// the root process handle dumping, and pass a process type here.
const std::string process_type_unused;
breakpad::InitCrashReporter(process_type_unused);
}
#endif // defined(OS_CHROMEOS)
// Quits |run_loop| if the |identity| of the quitting service is critical to the
// system (e.g. the window manager). Used in the main process.
void OnInstanceQuitInMain(base::RunLoop* run_loop,
int* exit_value,
const service_manager::Identity& identity) {
DCHECK(exit_value);
DCHECK(run_loop);
// TODO(jamescook): Also shut down if the window server dies.
if (identity.name() != mash::common::GetWindowManagerServiceName())
return;
if (!run_loop->running())
return;
*exit_value = 1;
run_loop->Quit();
}
} // namespace
MashRunner::MashRunner() {}
MashRunner::~MashRunner() {}
int MashRunner::Run() {
base::TaskScheduler::CreateAndSetSimpleTaskScheduler(
base::SysInfo::NumberOfProcessors());
if (IsChild())
return RunChild();
return RunMain();
}
int MashRunner::RunMain() {
base::SequencedWorkerPool::EnableWithRedirectionToTaskSchedulerForProcess();
mojo::edk::Init();
base::Thread ipc_thread("IPC thread");
ipc_thread.StartWithOptions(
base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
mojo::edk::ScopedIPCSupport ipc_support(
ipc_thread.task_runner(),
mojo::edk::ScopedIPCSupport::ShutdownPolicy::FAST);
int exit_value = RunServiceManagerInMain();
ipc_thread.Stop();
base::TaskScheduler::GetInstance()->Shutdown();
return exit_value;
}
int MashRunner::RunServiceManagerInMain() {
// TODO(sky): refactor BackgroundServiceManager so can supply own context, we
// shouldn't we using context as it has a lot of stuff we don't really want
// in chrome.
ServiceProcessLauncherDelegateImpl service_process_launcher_delegate;
service_manager::BackgroundServiceManager background_service_manager(
&service_process_launcher_delegate, CreateChromeMashCatalog());
service_manager::mojom::ServicePtr service;
context_.reset(new service_manager::ServiceContext(
base::MakeUnique<mash::MashPackagedService>(),
service_manager::mojom::ServiceRequest(&service)));
background_service_manager.RegisterService(
service_manager::Identity(
kChromeMashServiceName, service_manager::mojom::kRootUserID),
std::move(service), nullptr);
// Quit the main process if an important child (e.g. window manager) dies.
// On Chrome OS the OS-level session_manager will restart the main process.
base::RunLoop run_loop;
int exit_value = 0;
background_service_manager.SetInstanceQuitCallback(
base::Bind(&OnInstanceQuitInMain, &run_loop, &exit_value));
// Ping services that we know we want to launch on startup.
// TODO(jamescook): Start the window server / ui service explicitly.
context_->connector()->Connect(mash::common::GetWindowManagerServiceName());
context_->connector()->Connect(mash::quick_launch::mojom::kServiceName);
run_loop.Run();
context_.reset();
return exit_value;
}
int MashRunner::RunChild() {
service_manager::WaitForDebuggerIfNecessary();
base::i18n::InitializeICU();
InitializeResources();
service_manager::RunStandaloneService(
base::Bind(&MashRunner::StartChildApp, base::Unretained(this)));
return 0;
}
void MashRunner::StartChildApp(
service_manager::mojom::ServiceRequest service_request) {
// TODO(sad): Normally, this would be a TYPE_DEFAULT message loop. However,
// TYPE_UI is needed for mojo:ui. But it is not known whether the child app is
// going to be mojo:ui at this point. So always create a TYPE_UI message loop
// for now.
base::MessageLoop message_loop(base::MessageLoop::TYPE_UI);
base::RunLoop run_loop;
context_.reset(new service_manager::ServiceContext(
base::MakeUnique<mash::MashPackagedService>(),
std::move(service_request)));
// Quit the child process if it loses its connection to service manager.
context_->SetConnectionLostClosure(run_loop.QuitClosure());
run_loop.Run();
}
int MashMain() {
#if !defined(OFFICIAL_BUILD) && defined(OS_WIN)
base::RouteStdioToConsole(false);
#endif
// TODO(sky): wire this up correctly.
service_manager::InitializeLogging();
#if defined(OS_LINUX)
base::AtExitManager exit_manager;
#endif
#if !defined(OFFICIAL_BUILD)
// Initialize stack dumping before initializing sandbox to make sure symbol
// names in all loaded libraries will be cached.
base::debug::EnableInProcessStackDumping();
#endif
#if defined(OS_CHROMEOS)
// Breakpad installs signal handlers, so crash reporting must be set up after
// EnableInProcessStackDumping() resets the signal handlers.
InitializeCrashReporting();
#endif
std::unique_ptr<base::MessageLoop> message_loop;
if (!IsChild())
message_loop.reset(new base::MessageLoop(base::MessageLoop::TYPE_UI));
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kTraceToConsole)) {
base::trace_event::TraceConfig trace_config =
tracing::GetConfigForTraceToConsole();
base::trace_event::TraceLog::GetInstance()->SetEnabled(
trace_config,
base::trace_event::TraceLog::RECORDING_MODE);
}
MashRunner mash_runner;
return mash_runner.Run();
}
void WaitForMashDebuggerIfNecessary() {
if (!service_manager::ServiceManagerIsRemote())
return;
const base::CommandLine* command_line =
base::CommandLine::ForCurrentProcess();
const std::string service_name =
command_line->GetSwitchValueASCII(switches::kProcessServiceName);
if (service_name !=
command_line->GetSwitchValueASCII(switches::kWaitForDebugger)) {
return;
}
// Include the pid as logging may not have been initialized yet (the pid
// printed out by logging is wrong).
LOG(WARNING) << "waiting for debugger to attach for service " << service_name
<< " pid=" << base::Process::Current().Pid();
base::debug::WaitForDebugger(120, true);
}