blob: a50a8079ce1e4f2fead1f2bd1fb1f82e1be242a3 [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/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 "chrome/common/chrome_switches.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 "services/ui/public/interfaces/constants.mojom.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_POSIX)
#include <signal.h>
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/app/shutdown_signal_handlers_posix.h"
#endif // defined(OS_POSIX)
#if defined(OS_CHROMEOS)
#include "chrome/app/mash/chrome_mus_catalog.h"
#endif
using service_manager::mojom::ServiceFactory;
namespace {
// kProcessType used to identify child processes.
const char* kMashChild = "mash-child";
const char kChromeMashServiceName[] = "chrome_mash";
// Name used for --mus. This is only applicable to ChromeOS. it is placed
// outside of ifdefs to make code slightly more readable.
const char kChromeMusServiceName[] = "chrome_mus";
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() == kChromeMusServiceName ||
target.name() == content::mojom::kPackagedServicesServiceName) {
base::FilePath exe_path;
base::PathService::Get(base::FILE_EXE, &exe_path);
command_line->SetProgram(exe_path);
}
if (target.name() != content::mojom::kPackagedServicesServiceName) {
// 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
// the mash/mus flag so it proceeds with the normal content/browser startup
// path.
const bool is_mash = command_line->HasSwitch(switches::kMash);
base::CommandLine::SwitchMap new_switches = command_line->GetSwitches();
new_switches.erase(switches::kMash);
new_switches.erase(switches::kMus);
*command_line = base::CommandLine(command_line->GetProgram());
for (const std::pair<std::string, base::CommandLine::StringType>& sw :
new_switches) {
command_line->AppendSwitchNative(sw.first, sw.second);
}
// Add kMusConfig so that launched processes know what config they are
// running in.
command_line->AppendSwitchASCII(switches::kMusConfig,
is_mash ? switches::kMash : switches::kMus);
}
DISALLOW_COPY_AND_ASSIGN(ServiceProcessLauncherDelegateImpl);
};
// 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);
if (identity.name() != mash::common::GetWindowManagerServiceName() &&
identity.name() != ui::mojom::kServiceName &&
identity.name() != content::mojom::kPackagedServicesServiceName) {
return;
}
LOG(ERROR) << "Main process exiting because service " << identity.name()
<< " quit unexpectedly.";
*exit_value = 1;
run_loop->Quit();
}
} // namespace
MashRunner::MashRunner() {}
MashRunner::~MashRunner() {}
int MashRunner::Run() {
base::TaskScheduler::CreateAndSetSimpleTaskScheduler("MashRunner");
if (IsChild())
return RunChild();
return RunMain();
}
int MashRunner::RunMain() {
base::MessageLoop message_loop(base::MessageLoop::TYPE_UI);
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;
const bool is_mash =
base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kMash);
#if defined(OS_CHROMEOS)
service_manager::BackgroundServiceManager background_service_manager(
&service_process_launcher_delegate,
is_mash ? CreateChromeMashCatalog() : CreateChromeMusCatalog());
#else
service_manager::BackgroundServiceManager background_service_manager(
&service_process_launcher_delegate, CreateChromeMashCatalog());
#endif
service_manager::mojom::ServicePtr service;
service_manager::ServiceContext context(
base::MakeUnique<mash::MashPackagedService>(),
service_manager::mojom::ServiceRequest(&service));
background_service_manager.RegisterService(
service_manager::Identity(
is_mash ? kChromeMashServiceName : kChromeMusServiceName,
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));
#if defined(OS_POSIX)
// Quit the main process in response to shutdown signals (like SIGTERM).
// These signals are used by Linux distributions to request clean shutdown.
// On Chrome OS the SIGTERM signal is sent by session_manager.
InstallShutdownSignalHandlers(run_loop.QuitClosure(),
base::ThreadTaskRunnerHandle::Get());
#endif
// Ping services that we know we want to launch on startup (UI service,
// window manager, quick launch app).
context.connector()->StartService(ui::mojom::kServiceName);
context.connector()->StartService(
content::mojom::kPackagedServicesServiceName);
if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kMash)) {
context.connector()->StartService(
mash::common::GetWindowManagerServiceName());
context.connector()->StartService(mash::quick_launch::mojom::kServiceName);
}
run_loop.Run();
// |context| must be destroyed before the message loop.
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;
service_manager::ServiceContext context(
base::MakeUnique<mash::MashPackagedService>(),
std::move(service_request));
// Quit the child process when the service quits.
context.SetQuitClosure(run_loop.QuitClosure());
run_loop.Run();
// |context| must be destroyed before |message_loop|.
}
int MashMain() {
#if !defined(OFFICIAL_BUILD) && defined(OS_WIN)
base::RouteStdioToConsole(false);
#endif
#if defined(OS_POSIX)
// We inherit the signal mask of our parent process, which might block signals
// like SIGTERM that we need in order to cleanly shut down. Reset the signal
// mask to unblock all signals. http://crbug.com/699777
sigset_t empty_signal_set;
CHECK_EQ(0, sigemptyset(&empty_signal_set));
CHECK_EQ(0, sigprocmask(SIG_SETMASK, &empty_signal_set, nullptr));
#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.
// NOTE: On Chrome OS, crash reporting for the root process and non-browser
// service processes is handled by the OS-level crash_reporter.
base::debug::EnableInProcessStackDumping();
#endif
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);
}