blob: 47cf2af0d812d3772745de416c3af66196a45d8b [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 <fuchsia/modular/internal/cpp/fidl.h>
#include <fuchsia/sys/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fdio/directory.h>
#include <lib/fit/result.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/syslog/cpp/log_settings.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/vfs/cpp/pseudo_dir.h>
#include <lib/vfs/cpp/pseudo_file.h>
#include <zircon/errors.h>
#include <iostream>
#include <regex>
#include <src/lib/files/glob.h>
#include <src/lib/fxl/command_line.h>
#include <src/modular/lib/modular_config/modular_config.h>
#include <src/modular/lib/modular_config/modular_config_constants.h>
#include <zxtest/zxtest.h>
constexpr char kBasemgrUrl[] = "fuchsia-pkg://fuchsia.com/basemgr#meta/basemgr.cmx";
constexpr char kBasemgrHubPath[] = "/hub/c/basemgr.cmx/*/out/debug/basemgr";
constexpr char kLaunchCommandString[] = "launch";
constexpr char kShutdownBasemgrCommandString[] = "shutdown";
constexpr char kClearConfigCommandString[] = "delete_config";
constexpr char kDisableRestartAgentOnCrashFlagString[] = "disable_agent_restart_on_crash";
// Returns false if no basemgr debug service can be found, because
// basemgr is not running.
bool FindBasemgrDebugService(std::string* service_path) {
glob_t globbuf;
glob(kBasemgrHubPath, 0, nullptr, &globbuf);
bool found = false;
if (globbuf.gl_pathc > 0) {
*service_path = globbuf.gl_pathv[0];
found = true;
}
globfree(&globbuf);
return found;
}
zx_status_t ShutdownBasemgr(async::Loop* loop) {
// Get a connection to basemgr in order to shut it down.
std::string service_path;
if (!FindBasemgrDebugService(&service_path)) {
// basemgr is not running.
std::cerr << "basemgr does not appear to be running.";
return ZX_OK;
}
fuchsia::modular::internal::BasemgrDebugPtr basemgr_debug;
auto request = basemgr_debug.NewRequest().TakeChannel();
auto service_connect_result = fdio_service_connect(service_path.c_str(), request.get());
if (service_connect_result != ZX_OK) {
std::cerr << "Could not connect to basemgr service at " << service_path << ": "
<< zx_status_get_string(service_connect_result);
return service_connect_result;
}
basemgr_debug->Shutdown();
// Wait for basemgr to shutdown.
zx_status_t channel_close_status;
basemgr_debug.set_error_handler([&channel_close_status, loop](zx_status_t status) {
channel_close_status = status;
loop->Quit();
});
loop->Run();
loop->ResetQuit();
if (channel_close_status != ZX_OK) {
std::cerr << "basemgr did not tear down cleanly. Expected ZX_OK, got "
<< zx_status_get_string(channel_close_status);
}
return ZX_OK;
}
zx_status_t ClearPersistedConfig(async::Loop* loop) {
fuchsia::sys::LaunchInfo launch_info;
launch_info.url = kBasemgrUrl;
launch_info.arguments = {"delete_persistent_config"};
std::unique_ptr<sys::ComponentContext> context =
sys::ComponentContext::CreateAndServeOutgoingDirectory();
fuchsia::sys::LauncherPtr launcher;
fuchsia::sys::ComponentControllerPtr controller;
context->svc()->Connect(launcher.NewRequest());
launcher->CreateComponent(std::move(launch_info), controller.NewRequest());
zx_status_t channel_close_status;
int64_t basemgr_terminate_code = -1;
fuchsia::sys::TerminationReason basemgr_terminate_reason;
controller.events().OnTerminated = [&basemgr_terminate_code, &basemgr_terminate_reason](
int64_t exit_code,
fuchsia::sys::TerminationReason reason) {
basemgr_terminate_reason = reason;
basemgr_terminate_code = exit_code;
};
controller.set_error_handler([&channel_close_status, loop](zx_status_t status) {
channel_close_status = status;
loop->Quit();
});
loop->Run();
loop->ResetQuit();
if (basemgr_terminate_reason != fuchsia::sys::TerminationReason::EXITED ||
basemgr_terminate_code != 0) {
std::cerr << "basemgr delete_peristent_config did not exit cleanly: reason = "
<< static_cast<int>(basemgr_terminate_reason)
<< ", exit code = " << basemgr_terminate_code;
}
return ZX_OK;
}
// Reads and parses a |ModularConfig| from stdin.
fit::result<fuchsia::modular::session::ModularConfig, zx_status_t> ReadConfig() {
// Read the configuration in from stdin.
std::string config_str;
std::string line;
while (getline(std::cin, line)) {
config_str += line;
}
if (config_str.empty()) {
return fit::ok(modular::DefaultConfig());
}
auto parse_result = modular::ParseConfig(config_str);
if (parse_result.is_error()) {
std::cerr << "Could not parse ModularConfig: " << parse_result.error();
return fit::error(ZX_ERR_INVALID_ARGS);
}
return parse_result.take_ok_result();
}
std::unique_ptr<vfs::PseudoDir> CreateConfigPseudoDir(std::string config_str) {
auto dir = std::make_unique<vfs::PseudoDir>();
dir->AddEntry(modular_config::kStartupConfigFilePath,
std::make_unique<vfs::PseudoFile>(
config_str.length(), [config_str = std::move(config_str)](
std::vector<uint8_t>* out, size_t /*unused*/) {
std::copy(config_str.begin(), config_str.end(), std::back_inserter(*out));
return ZX_OK;
}));
return dir;
}
zx_status_t LaunchBasemgr(async::Loop* loop, fuchsia::modular::session::ModularConfig config) {
// If basemgr is already running, shut it down first.
if (files::Glob(kBasemgrHubPath).size() != 0) {
auto result = ShutdownBasemgr(loop);
if (result != ZX_OK) {
std::cerr << "Could not shut down running instance of basemgr: "
<< zx_status_get_string(result);
return result;
}
}
// Create the pseudo directory with our config "file" mapped to kConfigFilename.
auto config_dir = CreateConfigPseudoDir(modular::ConfigToJsonString(config));
fidl::InterfaceHandle<fuchsia::io::Directory> dir_handle;
config_dir->Serve(fuchsia::io::OPEN_RIGHT_READABLE, dir_handle.NewRequest().TakeChannel());
// Build a LaunchInfo with the config directory above mapped to /config_override/data.
fuchsia::sys::LaunchInfo launch_info;
launch_info.url = kBasemgrUrl;
launch_info.flat_namespace = fuchsia::sys::FlatNamespace::New();
launch_info.flat_namespace->paths.push_back(modular_config::kOverriddenConfigDir);
launch_info.flat_namespace->directories.push_back(dir_handle.TakeChannel());
// Quit the loop when basemgr's out directory has been mounted.
fuchsia::sys::ComponentControllerPtr controller;
controller.events().OnDirectoryReady = [&controller, &loop] {
controller->Detach();
loop->Quit();
};
// Launch a basemgr instance with the custom namespace we created above.
std::unique_ptr<sys::ComponentContext> context =
sys::ComponentContext::CreateAndServeOutgoingDirectory();
fuchsia::sys::LauncherPtr launcher;
context->svc()->Connect(launcher.NewRequest());
launcher->CreateComponent(std::move(launch_info), controller.NewRequest());
loop->Run();
return ZX_OK;
}
std::string GetUsage() {
return R"(Control the lifecycle of instances of basemgr.
Usage: basemgr_launcher [<command>] [--disable_agent_restart_on_crash]
<command>
(none) Alias for 'launch'.
launch Launches a new instance of basemgr with a modular JSON configuration
read from stdin.
shutdown Terminates the running instance of basemgr, if found.
delete_config Clears any cached persistent configuration (see below).
--disable_agent_restart_on_crash
Convenience flag for the 'launch' command to set
ModularConfig.sessionmgr_config.disable_agent_restart_on_crash to true.
Equivalent to setting the flag to true in the ModularConfig provided as
input to the 'launch' command.
# Examples (from host machine):
$ cat myconfig.json | fx shell basemgr_launcher
$ fx shell basemgr_launcher shutdown
# Persistent configuration
Persistent configuration can enabled by adding //src/modular/build:allow_persistent_config_override
to a non-production build. When enabled, the configuration provided to basemgr_launcher will
be stored and used when basemgr restarts and across reboots.
This configuration can be deleted by running (from host machine)
$ fx shell basemgr_launcher delete_config
)";
}
int main(int argc, const char** argv) {
syslog::SetTags({"basemgr_launcher"});
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
const auto command_line = fxl::CommandLineFromArgcArgv(argc, argv);
const auto& positional_args = command_line.positional_args();
const auto& cmd = positional_args.empty() ? kLaunchCommandString : positional_args[0];
if (cmd == kShutdownBasemgrCommandString) {
return ShutdownBasemgr(&loop);
}
if (cmd == kClearConfigCommandString) {
return ClearPersistedConfig(&loop);
}
if (cmd == kLaunchCommandString) {
auto config_result = ReadConfig();
if (config_result.is_error()) {
return config_result.take_error();
}
auto config = config_result.take_value();
if (command_line.HasOption(kDisableRestartAgentOnCrashFlagString)) {
config.mutable_sessionmgr_config()->set_disable_agent_restart_on_crash(true);
}
return LaunchBasemgr(&loop, std::move(config));
}
std::cerr << GetUsage() << std::endl;
return ZX_ERR_INVALID_ARGS;
}