blob: 6050a05c5f20e41a548904a26c593089bb1826aa [file] [log] [blame]
// Copyright 2020 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 "src/bringup/bin/console-launcher/console_launcher.h"
#include <fcntl.h>
#include <fuchsia/boot/cpp/fidl.h>
#include <fuchsia/hardware/virtioconsole/llcpp/fidl.h>
#include <fuchsia/kernel/cpp/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/namespace.h>
#include <lib/fdio/spawn.h>
#include <lib/fdio/unsafe.h>
#include <lib/fdio/watcher.h>
#include <lib/fit/defer.h>
#include <lib/sys/cpp/service_directory.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/syslog/global.h>
#include <lib/zircon-internal/paths.h>
#include <zircon/compiler.h>
#include <fbl/algorithm.h>
namespace console_launcher {
namespace {
// Get the root job from the root job service.
zx_status_t GetRootJob(zx::job* root_job) {
auto svc_dir = sys::ServiceDirectory::CreateFromNamespace();
fuchsia::kernel::RootJobSyncPtr root_job_ptr;
zx_status_t status = svc_dir->Connect(root_job_ptr.NewRequest());
if (status != ZX_OK) {
return status;
}
return root_job_ptr->Get(root_job);
}
// Wait for the requested file. Its parent directory must exist.
zx_status_t WaitForFile(const char* path, zx::time deadline) {
char path_copy[PATH_MAX];
if (strlen(path) >= PATH_MAX) {
return ZX_ERR_INVALID_ARGS;
}
strcpy(path_copy, path);
char* last_slash = strrchr(path_copy, '/');
// Waiting on the root of the fs or paths with no slashes is not supported by this function
if (last_slash == path_copy || last_slash == nullptr) {
return ZX_ERR_NOT_SUPPORTED;
}
last_slash[0] = 0;
char* dirname = path_copy;
char* basename = last_slash + 1;
auto watch_func = [](int dirfd, int event, const char* fn, void* cookie) -> zx_status_t {
auto basename = static_cast<const char*>(cookie);
if (event != WATCH_EVENT_ADD_FILE) {
return ZX_OK;
}
if (!strcmp(fn, basename)) {
return ZX_ERR_STOP;
}
return ZX_OK;
};
fbl::unique_fd dirfd(open(dirname, O_RDONLY));
if (!dirfd.is_valid()) {
return ZX_ERR_INVALID_ARGS;
}
zx_status_t status = fdio_watch_directory(dirfd.get(), watch_func, deadline.get(),
reinterpret_cast<void*>(basename));
if (status == ZX_ERR_STOP) {
return ZX_OK;
}
return status;
}
} // namespace
zx::status<ConsoleLauncher> ConsoleLauncher::Create() {
ConsoleLauncher launcher;
zx::job root_job;
// TODO(fxbug.dev/33957): Remove all uses of the root job.
zx_status_t status = GetRootJob(&root_job);
if (status != ZX_OK) {
FX_LOGF(ERROR, "Failed to get root job: %s", zx_status_get_string(status));
return zx::error(status);
}
status = zx::job::create(root_job, 0u, &launcher.shell_job_);
if (status != ZX_OK) {
FX_LOGF(ERROR, "Failed to create shell_job: %s", zx_status_get_string(status));
return zx::error(status);
}
status = launcher.shell_job_.set_property(ZX_PROP_NAME, "zircon-shell", 16);
if (status != ZX_OK) {
FX_LOGF(ERROR, "Failed to set shell_job job name: %s", zx_status_get_string(status));
return zx::error(status);
}
return zx::ok(std::move(launcher));
}
std::optional<Arguments> GetArguments(fidl::WireSyncClient<fuchsia_boot::Arguments>* client) {
Arguments ret;
fuchsia_boot::wire::BoolPair bool_keys[]{
{fidl::StringView{"console.shell"}, false},
{fidl::StringView{"kernel.shell"}, false},
{fidl::StringView{"console.is_virtio"}, false},
{fidl::StringView{"devmgr.log-to-debuglog"}, false},
};
auto bool_resp =
client->GetBools(fidl::VectorView<fuchsia_boot::wire::BoolPair>::FromExternal(bool_keys));
if (!bool_resp.ok()) {
printf("console-launcher: failed to get boot bools\n");
return std::nullopt;
}
ret.run_shell = bool_resp->values[0];
// If the kernel console is running a shell we can't launch our own shell.
ret.run_shell = ret.run_shell & !bool_resp->values[1];
ret.is_virtio = bool_resp->values[2];
ret.log_to_debuglog = bool_resp->values[3];
fidl::StringView vars[]{
fidl::StringView{"TERM"},
fidl::StringView{"console.path"},
fidl::StringView{"zircon.autorun.boot"},
fidl::StringView{"zircon.autorun.system"},
};
auto resp = client->GetStrings(fidl::VectorView<fidl::StringView>::FromExternal(vars));
if (!resp.ok()) {
printf("console-launcher: failed to get console path\n");
return std::nullopt;
}
if (resp->values[0].is_null()) {
ret.term += "uart";
} else {
ret.term += std::string{resp->values[0].data(), resp->values[0].size()};
}
if (!resp->values[1].is_null()) {
ret.device = std::string{resp->values[1].data(), resp->values[1].size()};
}
if (!resp->values[2].is_null()) {
ret.autorun_boot = std::string{resp->values[2].data(), resp->values[2].size()};
}
if (!resp->values[3].is_null()) {
ret.autorun_system = std::string{resp->values[3].data(), resp->values[3].size()};
}
return ret;
}
std::optional<fbl::unique_fd> ConsoleLauncher::GetVirtioFd(const Arguments& args,
fbl::unique_fd device_fd) {
zx::channel virtio_channel;
zx_status_t status =
fdio_get_service_handle(device_fd.release(), virtio_channel.reset_and_get_address());
if (status != ZX_OK) {
printf("console-launcher: failed to get console handle '%s'\n", args.device.data());
return std::nullopt;
}
zx::channel local, remote;
status = zx::channel::create(0, &local, &remote);
if (status != ZX_OK) {
printf("console-launcher: failed to create channel for console '%s'\n", args.device.data());
return std::nullopt;
}
fidl::WireSyncClient<fuchsia_hardware_virtioconsole::Device> virtio_client(
std::move(virtio_channel));
virtio_client.GetChannel(std::move(remote));
fdio_t* fdio;
status = fdio_create(local.release(), &fdio);
if (status != ZX_OK) {
printf("console-launcher: failed to setup fdio for console '%s'\n", args.device.data());
return std::nullopt;
}
fbl::unique_fd fd(fdio_bind_to_fd(fdio, -1, 3));
if (!fd.is_valid()) {
fdio_unsafe_release(fdio);
printf("console-launcher: failed to transfer fdio for console '%s'\n", args.device.data());
return std::nullopt;
}
return fd;
}
zx_status_t ConsoleLauncher::LaunchShell(const Arguments& args) {
if (!args.run_shell) {
FX_LOGS(INFO) << "console-launcher: disabled";
return ZX_OK;
}
zx_status_t status = WaitForFile(args.device.data(), zx::time::infinite());
if (status != ZX_OK) {
FX_LOGS(INFO) << "console-launcher: failed to wait for console";
printf("console-launcher: failed to wait for console '%s' (%s)\n", args.device.data(),
zx_status_get_string(status));
return status;
}
fbl::unique_fd fd(open(args.device.data(), O_RDWR));
if (!fd.is_valid()) {
printf("console-launcher: failed to open console '%s'\n", args.device.data());
return ZX_ERR_INVALID_ARGS;
}
// TODO(fxbug.dev/33183): Clean this up once devhost stops speaking fuchsia.io.File
// on behalf of drivers. Once that happens, the virtio-console driver
// should just speak that instead of this shim interface.
if (args.is_virtio) {
std::optional<fbl::unique_fd> result = GetVirtioFd(args, std::move(fd));
if (!result) {
return ZX_ERR_INTERNAL;
}
fd = std::move(*result);
}
const char* argv[] = {ZX_SHELL_DEFAULT, nullptr};
const char* environ[] = {args.term.data(), nullptr};
std::vector<fdio_spawn_action_t> actions;
// Add an action to set the new process name.
{
fdio_spawn_action_t name = {};
name.action = FDIO_SPAWN_ACTION_SET_NAME;
name.name.data = "sh:console";
actions.push_back(std::move(name));
}
// Get our current namespace so we can pass it to the shell process.
fdio_flat_namespace_t* flat = nullptr;
status = fdio_ns_export_root(&flat);
if (status != ZX_OK) {
return status;
}
auto free_flat = fit::defer([&flat]() { fdio_ns_free_flat_ns(flat); });
// Go through each directory in our namespace and copy all of them except /system-delayed.
for (size_t i = 0; i < flat->count; i++) {
if (strcmp(flat->path[i], "/system-delayed") == 0) {
continue;
}
fdio_spawn_action_t add_dir = {};
add_dir.action = FDIO_SPAWN_ACTION_ADD_NS_ENTRY;
add_dir.ns.handle = flat->handle[i];
add_dir.ns.prefix = flat->path[i];
actions.push_back(std::move(add_dir));
}
// Add an action to transfer the STDIO handle.
{
fdio_spawn_action_t stdio = {};
stdio.action = FDIO_SPAWN_ACTION_TRANSFER_FD;
stdio.fd = {.local_fd = fd.release(), .target_fd = FDIO_FLAG_USE_FOR_STDIO};
actions.push_back(std::move(stdio));
}
uint32_t flags = FDIO_SPAWN_CLONE_ALL & ~FDIO_SPAWN_CLONE_STDIO & ~FDIO_SPAWN_CLONE_NAMESPACE;
FX_LOGF(INFO, nullptr, "Launching %s (%s)\n", argv[0], actions[0].name.data);
char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
status = fdio_spawn_etc(shell_job_.get(), flags, argv[0], argv, environ, actions.size(),
actions.data(), shell_process_.reset_and_get_address(), err_msg);
if (status != ZX_OK) {
printf("console-launcher: failed to launch console shell: %s: %d (%s)\n", err_msg, status,
zx_status_get_string(status));
return status;
}
return ZX_OK;
}
zx_status_t ConsoleLauncher::WaitForShellExit() {
zx_status_t status =
shell_process_.wait_one(ZX_PROCESS_TERMINATED, zx::time::infinite(), nullptr);
if (status != ZX_OK) {
printf("console-launcher: failed to wait for console shell termination (%s)\n",
zx_status_get_string(status));
return status;
}
zx_info_process_v2_t proc_info;
status =
shell_process_.get_info(ZX_INFO_PROCESS_V2, &proc_info, sizeof(proc_info), nullptr, nullptr);
if (status != ZX_OK) {
printf("console-launcher: failed to determine console shell termination cause (%s)\n",
zx_status_get_string(status));
return status;
}
printf("console-launcher: console shell exited (started=%d exited=%d, return_code=%ld)\n",
(proc_info.flags & ZX_INFO_PROCESS_FLAG_STARTED) != 0,
(proc_info.flags & ZX_INFO_PROCESS_FLAG_EXITED) != 0, proc_info.return_code);
return ZX_OK;
}
} // namespace console_launcher