blob: fbf5353951572391583de254ce9c5b44654e0bb3 [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/vfs/cpp/pseudo_dir.h>
#include <lib/vfs/cpp/pseudo_file.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>
#include "src/lib/syslog/cpp/logger.h"
constexpr char kBasemgrUrl[] = "fuchsia-pkg://fuchsia.com/basemgr#meta/basemgr.cmx";
constexpr char kBasemgrHubPath[] = "/hub/c/basemgr.cmx/*/out/debug/basemgr";
constexpr char kShutdownBasemgrCommandString[] = "shutdown";
// 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;
}
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;
}
std::string GetUsage() {
return R"(Control the lifecycle of instances of basemgr.
Usage: basemgr_launcher [<command>]
<command>
(none) Launches a new instance of basemgr with a modular JSON configuration
read from stdin.
shutdown Terminates the running instance of basemgr, if found.
Exmaples (from host machine):
$ cat myconfig.json | fx shell basemgr_launcher
$ fx shell basemgr_launcher shutdown
)";
}
/// Parses configurations from command line into a string. Uses default values if no configuration
/// is provided. Processes arguments if they only contain a base shell url, otherwise returns usage.
fit::result<std::string, std::string> GetConfigFromArgs(fxl::CommandLine command_line) {
auto config_reader = modular::ModularConfigReader(/*config=*/"{}");
fuchsia::modular::session::BasemgrConfig basemgr_config = config_reader.GetBasemgrConfig();
fuchsia::modular::session::SessionmgrConfig sessionmgr_config =
config_reader.GetSessionmgrConfig();
for (auto opt : command_line.options()) {
if (opt.name == modular_config::kBaseShell) {
basemgr_config.mutable_base_shell()->mutable_app_config()->set_url(opt.value);
} else {
return fit::error(GetUsage());
}
}
std::string config_str =
modular::ModularConfigReader::GetConfigAsString(&basemgr_config, &sessionmgr_config);
return fit::ok(config_str);
}
int main(int argc, const char** argv) {
syslog::SetTags({"basemgr_launcher"});
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
std::string config_str = "";
if (argc > 1) {
const auto command_line = fxl::CommandLineFromArgcArgv(argc, argv);
const auto& positional_args = command_line.positional_args();
const auto& cmd = positional_args.empty() ? "" : positional_args[0];
if (cmd.empty()) {
auto config = GetConfigFromArgs(std::move(command_line));
if (config.is_error()) {
std::cerr << config.error().c_str() << std::endl;
return ZX_ERR_INVALID_ARGS;
} else {
config_str = config.value();
}
} else if (cmd == kShutdownBasemgrCommandString) {
return ShutdownBasemgr(&loop);
} else {
std::cerr << GetUsage() << std::endl;
return ZX_ERR_INVALID_ARGS;
}
} else {
// Read the configuration file in from stdin.
std::string line;
while (getline(std::cin, line)) {
config_str += line;
}
}
// 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(config_str);
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;
}