blob: 5dc2619720adce4bfed80f2cc51ca388921b3d76 [file] [log] [blame]
// Copyright 2022 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 <fcntl.h>
#include <fidl/fuchsia.buildinfo/cpp/wire.h>
#include <fidl/fuchsia.fshost/cpp/wire.h>
#include <fidl/fuchsia.hardware.power.statecontrol/cpp/natural_ostream.h>
#include <fidl/fuchsia.paver/cpp/wire.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/default.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fastboot/fastboot.h>
#include <lib/fdio/directory.h>
#include <zircon/status.h>
#include <chrono>
#include <optional>
#include <string_view>
#include <thread>
#include <vector>
#include <fbl/unique_fd.h>
#include <sdk/lib/syslog/cpp/macros.h>
#include "lib/zx/result.h"
#include "payload-streamer.h"
#include "sparse_format.h"
#include "src/firmware/lib/fastboot/rust/ffi_c/bindings.h"
#include "src/lib/fxl/strings/split_string.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace fastboot {
namespace {
using fuchsia_hardware_power_statecontrol::wire::ShutdownAction;
using fuchsia_hardware_power_statecontrol::wire::ShutdownOptions;
using fuchsia_hardware_power_statecontrol::wire::ShutdownReason;
constexpr char kFastbootLogTag[] = __FILE__;
constexpr auto kShutdownDelay = std::chrono::seconds(1);
struct FlashPartitionInfo {
std::string_view partition;
std::optional<fuchsia_paver::wire::Configuration> configuration;
};
FlashPartitionInfo GetPartitionInfo(std::string_view partition_label) {
size_t len = partition_label.length();
if (len < 2) {
return {.partition = partition_label, .configuration = std::nullopt};
}
FlashPartitionInfo ret;
ret.partition = partition_label.substr(0, len - 2);
std::string_view slot_suffix = partition_label.substr(len - 2, 2);
// TODO(b/241150035): Some platforms such as x64 still use legacy kernel partition name
// zircon-a/b/r. Hardcode these cases for backward compatibility. Once all products migrate to new
// name. Remove them.
if (slot_suffix == "_a" || partition_label == "zircon-a") {
ret.configuration = fuchsia_paver::wire::Configuration::kA;
} else if (slot_suffix == "_b" || partition_label == "zircon-b") {
ret.configuration = fuchsia_paver::wire::Configuration::kB;
} else if (slot_suffix == "_r" || partition_label == "zircon-r") {
ret.configuration = fuchsia_paver::wire::Configuration::kRecovery;
} else {
ret.partition = partition_label;
}
return ret;
}
bool IsAndroidSparseImage(const void* img, size_t size) {
if (size < sizeof(sparse_header_t)) {
return false;
}
sparse_header_t header;
memcpy(&header, img, sizeof(sparse_header_t));
return header.magic == SPARSE_HEADER_MAGIC;
}
} // namespace
const std::vector<Fastboot::CommandEntry>& Fastboot::GetCommandTable() {
// Using a static pointer and allocate with `new` so that the static instance
// never gets deleted.
static const std::vector<CommandEntry>* kCommandTable = new std::vector<CommandEntry>({
{
.name = "getvar",
.cmd = &Fastboot::GetVar,
},
{
.name = "flash",
.cmd = &Fastboot::Flash,
},
{
.name = "erase",
.cmd = &Fastboot::Erase,
},
{
.name = "set_active",
.cmd = &Fastboot::SetActive,
},
{
.name = "continue",
.cmd = &Fastboot::Continue,
},
{
.name = "reboot",
.cmd = &Fastboot::Reboot,
},
{
.name = "reboot-bootloader",
.cmd = &Fastboot::RebootBootloader,
},
{
.name = "reboot-fastboot",
.cmd = &Fastboot::RebootFastboot,
},
{
.name = "reboot-recovery",
.cmd = &Fastboot::RebootRecovery,
},
{
.name = "oem add-staged-bootloader-file",
.cmd = &Fastboot::OemAddStagedBootloaderFile,
},
{
.name = "oem init-partition-tables",
.cmd = &Fastboot::OemInitPartitionTables,
},
{
.name = "oem install-from-usb",
.cmd = &Fastboot::OemInstallFromUsb,
},
{
.name = "oem wipe-partition-tables",
.cmd = &Fastboot::OemWipePartitionTables,
},
{
.name = "oem install-blob-image",
.cmd = &Fastboot::OemInstallBlobImage,
},
});
return *kCommandTable;
}
const Fastboot::VariableHashTable& Fastboot::GetVariableTable() {
// Using a static pointer and allocate with `new` so that the static instance
// never gets deleted.
static const VariableHashTable* kVariableTable = new VariableHashTable({
{"max-download-size", &Fastboot::GetVarMaxDownloadSize},
{"slot-count", &Fastboot::GetVarSlotCount},
{"is-userspace", &Fastboot::GetVarIsUserspace},
{"hw-revision", &Fastboot::GetVarHwRevision},
{"product", &Fastboot::GetVarHwRevision},
{"version", &Fastboot::GetVarVersion},
{"all", &Fastboot::GetVarAll},
});
return *kVariableTable;
}
Fastboot::Fastboot() {
// Allocate up to 2% of total memory for the fastboot download buffer.
//
// We may need to tweak this further; low-memory devices need to be conservative here to avoid
// OOMing the system, but a smaller buffer results in slower downloads since images need to be
// split into more chunks which creates more overhead.
//
// The VMO will only be instantiated when a download is requested, and will be released when the
// download is finished.
max_download_size_ = zx_system_get_physmem() / 50;
}
Fastboot::Fastboot(size_t max_download_size, fidl::ClientEnd<fuchsia_io::Directory> svc_root)
: max_download_size_(max_download_size), svc_root_(std::move(svc_root)) {}
zx::result<> Fastboot::ProcessCommand(std::string_view command, Transport* transport) {
for (const CommandEntry& cmd : GetCommandTable()) {
if (MatchCommand(command, cmd.name)) {
return (this->*cmd.cmd)(command.data(), transport);
}
}
return SendResponse(ResponseType::kFail, "Unsupported command", transport);
}
void Fastboot::DoClearDownload() { download_vmo_mapper_.Reset(); }
zx::result<void*> Fastboot::GetDownloadBuffer(size_t total_download_size) {
if (zx_status_t ret = download_vmo_mapper_.CreateAndMap(total_download_size, "fastboot download");
ret != ZX_OK) {
return zx::error(ret);
}
if (zx_status_t ret = download_vmo_mapper_.vmo().set_prop_content_size(total_download_size);
ret != ZX_OK) {
return zx::error(ret);
}
return zx::ok(download_vmo_mapper_.start());
}
zx::result<> Fastboot::GetVar(const std::string& command, Transport* transport) {
std::vector<std::string_view> args =
fxl::SplitString(command, ":", fxl::kTrimWhitespace, fxl::kSplitWantNonEmpty);
if (args.size() < 2) {
return SendResponse(ResponseType::kFail, "Not enough arguments", transport);
}
// TODO(https://fxbug.dev/397515768): We should avoid hard-coding partition types/sizes here.
// This is a temporary implementation to unblock fastboot -w support. Hard-coding these here means
// we won't see these variables with `fastboot getvar all`, but the current variable table doesn't
// support a means of querying multi-part variables.
if (args.size() >= 3) {
if (args[1] == "partition-type" && args[2] == "userdata") {
return SendResponse(ResponseType::kOkay, "fxfs-vol", transport);
}
if (args[1] == "partition-size" && args[2] == "userdata") {
return SendResponse(ResponseType::kOkay, "0x00", transport);
}
}
const VariableHashTable& var_table = GetVariableTable();
const VariableHashTable::const_iterator find_res = var_table.find(args[1].data());
if (find_res == var_table.end()) {
return SendResponse(ResponseType::kFail, "Unknown variable", transport);
}
zx::result<std::string> var = (this->*(find_res->second))(args, transport);
if (var.is_error()) {
return SendResponse(ResponseType::kFail, "Failed to get variable", transport,
zx::error(var.status_value()));
}
return SendResponse(ResponseType::kOkay, *var, transport);
}
zx::result<std::string> Fastboot::GetVarVersion(const std::vector<std::string_view>&, Transport*) {
return zx::ok("0.4");
}
zx::result<std::string> Fastboot::GetVarAll(const std::vector<std::string_view>& args,
Transport* transport) {
zx::result<std::string> res(zx::ok("done"));
const VariableHashTable& var_table = GetVariableTable();
for (const auto& [name, func] : var_table) {
if (name == "all") {
continue;
}
std::vector<std::string_view> var_args = {"getvar", name};
zx::result<std::string> var_ret = (this->*(func))(var_args, transport);
if (var_ret.is_error()) {
res = zx::ok("not all variables were retrieved successfully");
}
std::string response = fxl::StringPrintf(
"%s: %s", name.c_str(),
var_ret.value_or(std::string("[error: ") + var_ret.status_string() + "]").c_str());
zx::result<> ret = SendResponse(ResponseType::kInfo, response, transport);
if (ret.is_error()) {
return zx::error(ret.status_value());
}
}
return res;
}
zx::result<std::string> Fastboot::GetVarMaxDownloadSize(const std::vector<std::string_view>&,
Transport*) {
return zx::ok(fxl::StringPrintf("0x%08zx", max_download_size_));
}
zx::result<std::string> Fastboot::GetVarHwRevision(const std::vector<std::string_view>&,
Transport*) {
auto svc_root = GetSvcRoot();
if (svc_root.is_error()) {
return zx::error(svc_root.status_value());
}
auto provider = component::ConnectAt<fuchsia_buildinfo::Provider>(*svc_root);
if (provider.is_error()) {
return zx::error(provider.status_value());
}
auto build_info = fidl::WireCall(*provider)->GetBuildInfo();
if (!build_info.ok()) {
return zx::error(build_info.status());
}
return zx::ok(build_info->build_info.board_config().data());
}
zx::result<std::string> Fastboot::GetVarSlotCount(const std::vector<std::string_view>&,
Transport* transport) {
auto boot_manager = FindBootManager();
if (boot_manager.is_error()) {
auto ret = SendResponse(ResponseType::kFail, "Failed to find boot manager", transport,
zx::error(boot_manager.status_value()));
return zx::error(ret.status_value());
}
// `fastboot set_active` only cares whether the device has >1 slots. Doesn't care how many
// exactly.
return boot_manager->QueryCurrentConfiguration().ok() ? zx::ok("2") : zx::ok("1");
}
zx::result<std::string> Fastboot::GetVarIsUserspace(const std::vector<std::string_view>&,
Transport*) {
return zx::ok("yes");
}
zx::result<fidl::UnownedClientEnd<fuchsia_io::Directory>> Fastboot::GetSvcRoot() {
// If `svc_root_` is not set, use the system svc root.
if (!svc_root_) {
auto svc_root = component::OpenServiceRoot();
if (svc_root.is_error()) {
FX_LOGST(ERROR, kFastbootLogTag)
<< "Failed to connect to svc root " << svc_root.status_string();
return zx::error(ZX_ERR_INTERNAL);
}
svc_root_ = *std::move(svc_root);
}
return zx::ok(fidl::UnownedClientEnd<fuchsia_io::Directory>(svc_root_));
}
zx::result<fidl::WireSyncClient<fuchsia_paver::Paver>> Fastboot::ConnectToPaver() {
// Connect to the paver
auto svc_root = GetSvcRoot();
if (svc_root.is_error()) {
return zx::error(svc_root.status_value());
}
auto paver = component::ConnectAt<fuchsia_paver::Paver>(*svc_root);
if (!paver.is_ok()) {
FX_LOGST(ERROR, kFastbootLogTag) << "Unable to open /svc/fuchsia.paver.Paver";
return zx::error(paver.error_value());
}
return zx::ok(fidl::WireSyncClient(*std::move(paver)));
}
fuchsia_mem::wire::Buffer Fastboot::GetWireBufferFromDownload() {
fuchsia_mem::wire::Buffer buf;
buf.size = download_vmo_mapper_.size();
buf.vmo = download_vmo_mapper_.Release();
return buf;
}
zx::result<> Fastboot::WriteFirmware(fuchsia_paver::wire::Configuration config,
std::string_view firmware_type, Transport* transport,
fidl::WireSyncClient<fuchsia_paver::DataSink>& data_sink) {
auto response = data_sink->WriteFirmware(config, fidl::StringView::FromExternal(firmware_type),
GetWireBufferFromDownload());
if (response.status() != ZX_OK) {
return SendResponse(ResponseType::kFail, "Failed to invoke paver bootloader write", transport,
zx::error(response.status()));
}
const auto& result = response->result;
if (result.is_status() && result.status() != ZX_OK) {
return SendResponse(ResponseType::kFail, "Failed to write bootloader", transport,
zx::error(response->result.status()));
}
if (result.is_unsupported() && result.unsupported()) {
return SendResponse(ResponseType::kFail, "Firmware type is not supported", transport);
}
return SendResponse(ResponseType::kOkay, "", transport);
}
zx::result<> Fastboot::WriteAsset(fuchsia_paver::wire::Configuration config,
fuchsia_paver::wire::Asset asset, Transport* transport,
fidl::WireSyncClient<fuchsia_paver::DataSink>& data_sink) {
auto response = data_sink->WriteAsset(config, asset, GetWireBufferFromDownload());
if (response.status() != ZX_OK) {
return SendResponse(ResponseType::kFail, "Failed to invoke paver data sink write asset",
transport, zx::error(response.status()));
}
if (response->status != ZX_OK) {
return SendResponse(ResponseType::kFail, "Failed to flash asset", transport,
zx::error(response->status));
}
return SendResponse(ResponseType::kOkay, "", transport);
}
zx::result<> Fastboot::Flash(const std::string& command, Transport* transport) {
std::vector<std::string_view> args =
fxl::SplitString(command, ":", fxl::kTrimWhitespace, fxl::kSplitWantNonEmpty);
if (args.size() < 2) {
return SendResponse(ResponseType::kFail, "Not enough arguments", transport);
}
FlashPartitionInfo info = GetPartitionInfo(args[1]);
const bool is_sparse =
IsAndroidSparseImage(download_vmo_mapper_.start(), download_vmo_mapper_.size());
// TODO(https://fxbug.dev/397515768): We should do this when we get a request to flash super
// without the -w flag. To avoid any issues with rollout, for now we use a different name to allow
// for testing the new volume installation feature without affecting existing flashing workflows.
// We only support this on fxblob-based products, so we will also need to figure out how to
// fallback to the legacy paver protocol for non-fxblob products.
if (info.partition == "blob") {
if (!is_sparse) {
return SendResponse(ResponseType::kFail, "blob image must be in Android sparse format.",
transport);
}
auto recovery = ConnectToRecoveryService();
if (recovery.is_error()) {
return SendResponse(ResponseType::kFail, "Failed to connect to recovery", transport,
zx::error(recovery.status_value()));
}
auto response =
fidl::WireCall(*recovery)->WriteSystemBlobImage(GetWireBufferFromDownload().vmo);
if (response.status() != ZX_OK) {
return SendResponse(ResponseType::kFail, "Recovery.WriteSystemBlobImage Failed", transport,
zx::error(response.status()));
}
if (response->is_error()) {
return SendResponse(ResponseType::kFail, "Failed to write blob image", transport,
zx::error(response->error_value()));
}
return SendResponse(ResponseType::kOkay, "", transport);
}
if (info.partition == "super") {
if (!is_sparse) {
return SendResponse(ResponseType::kFail, "super must be in Android sparse format.",
transport);
}
} else if (is_sparse) {
return SendResponse(ResponseType::kFail, "Android sparse image is not supported.", transport);
}
auto paver = ConnectToPaver();
if (paver.is_error()) {
return SendResponse(ResponseType::kFail, "Failed to connect to paver", transport,
zx::error(paver.status_value()));
}
// Connect to the data sink
auto [client, server] = fidl::Endpoints<fuchsia_paver::DataSink>::Create();
// TODO(https://fxbug.dev/42180237) Consider handling the error instead of ignoring it.
(void)paver->FindDataSink(std::move(server));
fidl::WireSyncClient data_sink{std::move(client)};
if (info.partition == "bootloader") {
// If abr suffix is not given, assume that firmware ABR is not supported and just provide a
// A slot configuration. It will be ignored by the paver.
fuchsia_paver::wire::Configuration config =
info.configuration ? *info.configuration : fuchsia_paver::wire::Configuration::kA;
std::string_view firmware_type = args.size() == 3 ? args[2] : "";
return WriteFirmware(config, firmware_type, transport, data_sink);
}
if (info.partition == "fuchsia-esp") {
// x64 platform uses 'fuchsia-esp' for bootloader partition . We should eventually move to use
// "bootloader"
// For legacy `fuchsia-esp` we don't consider firmware ABR or type.
return WriteFirmware(fuchsia_paver::wire::Configuration::kA, "", transport, data_sink);
}
if (info.partition == "zircon" && info.configuration) {
return WriteAsset(*info.configuration, fuchsia_paver::wire::Asset::kKernel, transport,
data_sink);
}
if (info.partition == "vbmeta" && info.configuration) {
return WriteAsset(*info.configuration, fuchsia_paver::wire::Asset::kVerifiedBootMetadata,
transport, data_sink);
}
if (info.partition == "fvm") {
auto response = data_sink->WriteOpaqueVolume(GetWireBufferFromDownload());
if (response.status() != ZX_OK) {
return SendResponse(ResponseType::kFail, "Failed to invoke paver data sink write opaque fvm",
transport, zx::error(response.status()));
}
if (response->is_error()) {
return SendResponse(ResponseType::kFail, "Failed to flash opaque fvm", transport,
zx::error(response->error_value()));
}
return SendResponse(ResponseType::kOkay, "", transport);
}
if (info.partition == "fvm.sparse") {
// Flashing the sparse format FVM image via the paver. Note that at the time this code is
// written, the format of FVM for fuchsia has not reached at a stable point yet. However, the
// implementation of the paver fidl interface `WriteVolumes()` depends on the format of the FVM.
// Therefore, it is important make sure that the device is running the latest version of paver
// before using this fastboot command. This typically means flashing the latest kernel and
// reboot first. Otherwise, if FVM format changes and the currently running paver is not
// up-to-date, the FVM may be flashed wrongly.
auto [client, server] = fidl::Endpoints<fuchsia_paver::PayloadStream>::Create();
// Launch thread which implements interface.
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
internal::PayloadStreamer streamer(std::move(server), download_vmo_mapper_.start(),
download_vmo_mapper_.size());
loop.StartThread("fastboot-payload-stream");
auto response = data_sink->WriteVolumes(std::move(client));
if (response.status() != ZX_OK) {
return SendResponse(ResponseType::kFail, "Failed to invoke paver data sink write volumes",
transport, zx::error(response.status()));
}
if (response->status != ZX_OK) {
return SendResponse(ResponseType::kFail, "Failed to write fvm", transport,
zx::error(response->status));
}
download_vmo_mapper_.Reset();
return SendResponse(ResponseType::kOkay, "", transport);
}
if (info.partition == "gpt-meta") {
// gpt-meta is a pseudo-partition; we don't write the contents directly to the GPT but instead
// it provides some higher-level information about what the GPT should look like and it's up to
// the device implementation to translate that to an actual GPT.
if (info.configuration) {
return SendResponse(ResponseType::kFail, "gpt-meta doesn't support slots", transport,
zx::error(ZX_ERR_INVALID_ARGS));
}
// For now we only support a single input format of a file containing the word "default", which
// means to write the default GPT exactly as `oem init-partition-tables` would. The reason we
// provide this alias is to simplify `ffx flash` by tying into the existing partition flash
// mechanism rather than having to teach it about the `oem init-partition-tables` command.
std::string_view contents(reinterpret_cast<const char*>(download_vmo_mapper_.start()),
download_vmo_mapper_.size());
if (contents != kGptMetaDefault) {
return SendResponse(ResponseType::kFail, "Invalid gpt-meta contents", transport,
zx::error(ZX_ERR_INVALID_ARGS));
}
return OemInitPartitionTables(command, transport);
}
if (info.partition == "super") {
// TODO(https://fxbug.dev/397515768): We don't yet handle the presence or absence of the -w
// flag which is used to indicate if we should wipe userdata or not. For now, this will always
// overwrite userdata regardless of this flag.
auto response = data_sink->WriteSparseVolume(GetWireBufferFromDownload());
if (response.status() != ZX_OK) {
return SendResponse(ResponseType::kFail,
"Failed to invoke fuchsia.paver/DataSink.WriteSparseVolume", transport,
zx::error(response.status()));
}
if (response->is_error()) {
return SendResponse(ResponseType::kFail, "Failed to flash super", transport,
zx::error(response->error_value()));
}
return SendResponse(ResponseType::kOkay, "", transport);
}
return SendResponse(ResponseType::kFail, "Unsupported partition", transport);
}
zx::result<> Fastboot::Erase(const std::string& command, Transport* transport) {
std::vector<std::string_view> args =
fxl::SplitString(command, ":", fxl::kTrimWhitespace, fxl::kSplitWantNonEmpty);
if (args.size() < 2) {
return SendResponse(ResponseType::kFail, "Not enough arguments", transport);
}
auto partition_label = args[1];
// We only support erasing the userdata partition.
if (partition_label != "userdata") {
FX_LOGST(ERROR, kFastbootLogTag) << "Ignoring request to erase partition: " << partition_label;
return SendResponse(ResponseType::kFail, "Unknown partition", transport);
}
auto svc_root = GetSvcRoot();
if (svc_root.is_error()) {
return zx::error(svc_root.status_value());
}
auto fshost_admin = component::ConnectAt<fuchsia_fshost::Admin>(*svc_root);
if (fshost_admin.is_error()) {
return zx::error(fshost_admin.status_value());
}
// Use the fuchsia.fshost/Admin.ShredDataVolume to ensure the userdata partition is shredded on
// the next boot. Note that this does not delete the volume immediately, but it will be deleted
// on the next boot when we fail to unlock the volume.
FX_LOGST(INFO, kFastbootLogTag) << "Shredding data volume.";
auto response = fidl::WireCall(*fshost_admin)->ShredDataVolume();
if (response.status() != ZX_OK) {
return SendResponse(ResponseType::kFail, "Failed to invoke ShredDataVolume", transport,
zx::error(response.status()));
}
if (response->is_error()) {
return SendResponse(ResponseType::kFail, "Failed to shred data volume", transport,
zx::error(response->error_value()));
}
return SendResponse(ResponseType::kOkay, "", transport);
}
zx::result<fidl::WireSyncClient<fuchsia_paver::BootManager>> Fastboot::FindBootManager() {
auto paver = ConnectToPaver();
if (!paver.is_ok()) {
return zx::error(paver.status_value());
}
auto [client, server] = fidl::Endpoints<fuchsia_paver::BootManager>::Create();
auto response = paver->FindBootManager(std::move(server));
if (!response.ok()) {
FX_LOGST(ERROR, kFastbootLogTag) << "Failed to find boot manager";
return zx::error(response.status());
}
return zx::ok(fidl::WireSyncClient(std::move(client)));
}
zx::result<> Fastboot::SetActive(const std::string& command, Transport* transport) {
std::vector<std::string_view> args =
fxl::SplitString(command, ":", fxl::kTrimWhitespace, fxl::kSplitWantNonEmpty);
if (args.size() < 2) {
return SendResponse(ResponseType::kFail, "Not enough arguments", transport);
}
auto boot_manager = FindBootManager();
if (boot_manager.is_error()) {
return SendResponse(ResponseType::kFail, "Failed to find boot manager", transport,
zx::error(boot_manager.status_value()));
}
fuchsia_paver::wire::Configuration config = fuchsia_paver::wire::Configuration::kB;
if (args[1] == "a") {
config = fuchsia_paver::wire::Configuration::kA;
} else if (args[1] != "b") {
return SendResponse(ResponseType::kFail, "Invalid slot", transport);
}
auto response = boot_manager->SetConfigurationActive(config);
if (response.status() != ZX_OK) {
return SendResponse(ResponseType::kFail, "Failed to invoke paver boot manager set active",
transport, zx::error(response.status()));
}
if (response->status != ZX_OK) {
return SendResponse(ResponseType::kFail, "Failed to set configuration active: ", transport,
zx::error(response->status));
}
return SendResponse(ResponseType::kOkay, "", transport);
}
zx::result<fidl::WireSyncClient<fuchsia_hardware_power_statecontrol::Admin>>
Fastboot::ConnectToPowerStateControl() {
auto svc_root = GetSvcRoot();
if (svc_root.is_error()) {
return zx::error(svc_root.status_value());
}
auto admin = component::ConnectAt<fuchsia_hardware_power_statecontrol::Admin>(*svc_root);
if (admin.is_error()) {
return zx::error(admin.status_value());
}
return zx::ok(fidl::WireSyncClient(*std::move(admin)));
}
zx::result<> Fastboot::Continue(const std::string& command, Transport* transport) {
// We cannot continue booting from userspace fastboot, so we issue a regular reboot command
// instead so the device reboots normally.
FX_LOGST(INFO, kFastbootLogTag) << "Userspace fastboot cannot continue, rebooting instead";
return HandleShutdown(transport, ShutdownAction::kReboot);
}
zx::result<> Fastboot::Reboot(const std::string& command, Transport* transport) {
return HandleShutdown(transport, ShutdownAction::kReboot);
}
zx::result<> Fastboot::RebootBootloader(const std::string& command, Transport* transport) {
return HandleShutdown(transport, ShutdownAction::kRebootToBootloader);
}
zx::result<> Fastboot::RebootFastboot(const std::string& command, Transport* transport) {
// Userspace fastboot runs automatically in the Fuchsia recovery image.
return HandleShutdown(transport, ShutdownAction::kRebootToRecovery);
}
zx::result<> Fastboot::RebootRecovery(const std::string& command, Transport* transport) {
return HandleShutdown(transport, ShutdownAction::kRebootToRecovery);
}
zx::result<> Fastboot::HandleShutdown(Transport* transport, ShutdownAction action) {
zx::result admin = ConnectToPowerStateControl();
if (admin.is_error()) {
return SendResponse(ResponseType::kFail,
"Failed to connect to power state control service: ", transport,
zx::error(admin.status_value()));
}
// Send an okay response early, regardless of the result. Once the system reboots or shuts down,
// we have no chance to send the response.
{
zx::result response = SendResponse(ResponseType::kOkay, "", transport);
if (response.is_error()) {
return response;
}
}
// Sleep for a short amount of time to make sure the response is sent over the transport before
// we issue the reboot/shutdown request. This helps to ensure that the host tool receives the
// response, otherwise it will hang waiting for a reply from the device.
FX_LOGST(INFO, kFastbootLogTag) << "Issuing system shutdown in "
<< std::format("{}", kShutdownDelay) << ", action: " << action;
std::this_thread::sleep_for(kShutdownDelay);
// Send the shutdown request.
fidl::Arena arena;
auto builder = ShutdownOptions::Builder(arena);
ShutdownReason reasons[1] = {ShutdownReason::kDeveloperRequest};
auto vector_view = fidl::VectorView<ShutdownReason>::FromExternal(reasons);
builder.reasons(vector_view);
builder.action(action);
auto response = admin->Shutdown(builder.Build());
// We already responded to the command request, so we can't reply with any failures at this point.
// The best we can do is log an error here.
if (!response.ok()) {
FX_LOGST(ERROR, kFastbootLogTag) << "Failed to invoke Shutdown: " << response;
return zx::error(response.error().status());
}
if (response->is_error()) {
zx::result<> ret = zx::error(response->error_value());
FX_LOGST(ERROR, kFastbootLogTag) << "System shutdown failed: " << ret.status_string();
return ret;
}
return zx::ok();
}
zx::result<> Fastboot::OemAddStagedBootloaderFile(const std::string& command,
Transport* transport) {
std::vector<std::string_view> args =
fxl::SplitString(command, " ", fxl::kTrimWhitespace, fxl::kSplitWantNonEmpty);
if (args.size() != 3) {
return SendResponse(ResponseType::kFail, "Invalid number of arguments", transport);
}
if (args[2] != sshd_host::kAuthorizedKeysBootloaderFileName) {
return SendResponse(ResponseType::kFail, "Unsupported file: " + std::string(args[2]),
transport);
}
auto recovery = ConnectToRecoveryService();
if (recovery.is_error()) {
return SendResponse(ResponseType::kFail, "Failed to connect to fuchsia.fshost/Recovery",
transport, zx::error(recovery.status_value()));
}
auto response = fidl::WireCall(*recovery)->WriteDataFile(
fidl::StringView::FromExternal(sshd_host::kAuthorizedKeyPathInData),
download_vmo_mapper_.Release());
if (response.status() != ZX_OK) {
return SendResponse(ResponseType::kFail, "Failed to invoke recovery WriteDataFile", transport,
zx::error(response.status()));
}
if (response->is_error()) {
return SendResponse(ResponseType::kFail, "Failed to write ssh key", transport,
zx::error(response->error_value()));
}
return SendResponse(ResponseType::kOkay, "", transport);
}
zx::result<fidl::WireSyncClient<fuchsia_paver::DynamicDataSink>> Fastboot::ConnectToDynamicDataSink(
Transport* transport) {
auto paver = ConnectToPaver();
if (paver.is_error()) {
return zx::error(SendResponse(ResponseType::kFail, "Failed to connect to paver", transport,
zx::error(paver.status_value()))
.status_value());
}
auto [client, server] = fidl::Endpoints<fuchsia_paver::DynamicDataSink>::Create();
auto response = paver->FindPartitionTableManager(
fidl::ServerEnd<fuchsia_paver::DynamicDataSink>(server.TakeChannel()));
if (!response.ok()) {
return zx::error(SendResponse(ResponseType::kFail, "Failed to find dynamic data sink",
transport, zx::error(response.status()))
.status_value());
}
return zx::ok(fidl::WireSyncClient(std::move(client)));
}
zx::result<> Fastboot::OemInitPartitionTables(const std::string& command, Transport* transport) {
auto data_sink = ConnectToDynamicDataSink(transport);
if (data_sink.is_error()) {
return zx::error(data_sink.status_value());
}
auto response = data_sink->InitializePartitionTables();
if (response.status() != ZX_OK) {
return SendResponse(ResponseType::kFail, "Failed to invoke paver partition table init",
transport, zx::error(response.status()));
}
if (response->status != ZX_OK) {
return SendResponse(ResponseType::kFail, "Failed to init partition table", transport,
zx::error(response->status));
}
return SendResponse(ResponseType::kOkay, "", transport);
}
zx::result<> Fastboot::OemInstallFromUsb(const std::string& command, Transport* transport) {
// Command format: oem install-from-usb [source [destination]].
// Source and/or destination can be the string "default" in order to use the default target.
//
// Note: source/destination aren't really usable at the moment because the path names will
// often be too large to fit in the fastboot packet. Example paths from a NUC11:
// * "/dev/sys/platform/pt/PC00/bus/00:14.0/00:14.0/xhci/usb-bus/001/001/ifc-000/ums/scsi-block-device-0-0/block"
// * "/dev/sys/platform/pt/PC00/bus/01:00.0/01:00.0/nvme/namespace-1/block"
// We'll need to implement some sort of substring matching to make this actually useful.
// Probably also a way to list the current disks so the user doesn't have to magically know this
// entire string.
std::vector<std::string_view> args =
fxl::SplitString(command, " ", fxl::kTrimWhitespace, fxl::kSplitWantNonEmpty);
// Make a copy of any arg we need to pass because they must be null-terminated c-strings.
std::string source, dest;
if (args.size() > 2 && args[2] != "default") {
source = args[2];
}
if (args.size() > 3 && args[3] != "default") {
dest = args[3];
}
zx_status_t status = install_from_usb(source.empty() ? nullptr : source.c_str(),
dest.empty() ? nullptr : dest.c_str());
if (status != ZX_OK) {
return SendResponse(ResponseType::kFail, "Failed to install from USB", transport,
zx::error(status));
}
return SendResponse(ResponseType::kOkay, "", transport);
}
zx::result<> Fastboot::OemWipePartitionTables(const std::string& command, Transport* transport) {
auto data_sink = ConnectToDynamicDataSink(transport);
if (data_sink.is_error()) {
return zx::error(data_sink.status_value());
}
auto response = data_sink->WipePartitionTables();
if (response.status() != ZX_OK) {
return SendResponse(ResponseType::kFail, "Failed to invoke paver partition table wipe",
transport, zx::error(response.status()));
}
if (response->status != ZX_OK) {
return SendResponse(ResponseType::kFail, "Failed to wipe partition table", transport,
zx::error(response->status));
}
return SendResponse(ResponseType::kOkay, "", transport);
}
zx::result<fidl::UnownedClientEnd<fuchsia_fshost::Recovery>> Fastboot::ConnectToRecoveryService() {
if (fshost_recovery_) {
return zx::ok(fshost_recovery_.borrow());
}
auto svc_root = GetSvcRoot();
if (svc_root.is_error()) {
return zx::error(svc_root.status_value());
}
auto fshost_recovery = component::ConnectAt<fuchsia_fshost::Recovery>(*svc_root);
if (fshost_recovery.is_error()) {
return zx::error(fshost_recovery.status_value());
}
fshost_recovery_ = *std::move(fshost_recovery);
return zx::ok(fshost_recovery_.borrow());
}
zx::result<> Fastboot::OemInstallBlobImage(const std::string& command, Transport* transport) {
auto recovery = ConnectToRecoveryService();
if (recovery.is_error()) {
return SendResponse(ResponseType::kFail, "Failed to connect to recovery", transport,
zx::error(recovery.status_value()));
}
auto response = fidl::WireCall(*recovery)->InstallSystemBlobImage();
if (response.status() != ZX_OK) {
return SendResponse(ResponseType::kFail, "Recovery.InstallSystemBlobImage Failed", transport,
zx::error(response.status()));
}
if (response->is_error()) {
return SendResponse(ResponseType::kFail, "Failed to install blob image", transport,
zx::error(response->error_value()));
}
return SendResponse(ResponseType::kOkay, "", transport);
}
} // namespace fastboot