blob: eaea02ce9b7bd9c363e1bc51f39e222bde380153 [file] [log] [blame]
// Copyright 2017 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 <dirent.h>
#include <fcntl.h>
#include <fuchsia/paver/llcpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fdio.h>
#include <lib/fzl/resizeable-vmo-mapper.h>
#include <lib/zx/channel.h>
#include <lib/zx/vmo.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <zircon/status.h>
#include <cstdio>
#include <utility>
#include <fbl/algorithm.h>
#include <fbl/string_printf.h>
#include <fbl/unique_fd.h>
#include "payload-streamer.h"
// Print a message to stderr, along with the program name and function name.
#define ERROR(fmt, ...) fprintf(stderr, "disk-pave:[%s] " fmt, __FUNCTION__, ##__VA_ARGS__)
namespace {
void PrintUsage() {
ERROR("install-disk-image <command> [options...]\n");
ERROR("Commands:\n");
ERROR(" install-bootloader : Install a BOOTLOADER partition to the device\n");
ERROR(" install-zircona : Install a ZIRCON-A partition to the device\n");
ERROR(" install-zirconb : Install a ZIRCON-B partition to the device\n");
ERROR(" install-zirconr : Install a ZIRCON-R partition to the device\n");
ERROR(" install-vbmetaa : Install a VBMETA-A partition to the device\n");
ERROR(" install-vbmetab : Install a VBMETA-B partition to the device\n");
ERROR(" install-vbmetar : Install a VBMETA-R partition to the device\n");
ERROR(" install-fvm : Install a sparse FVM to the device\n");
ERROR(" install-data-file : Install a file to DATA (--path required)\n");
ERROR(" wipe : Remove the FVM partition\n");
ERROR(" init-partition-tables : Initialize block device with valid GPT and FVM\n");
ERROR(" wipe-partition-tables : Remove all partitions for partition table\n");
ERROR("Options:\n");
ERROR(" --file <file>: Read from FILE instead of stdin\n");
ERROR(" --force: Install partition even if inappropriate for the device\n");
ERROR(" --path <path>: Install DATA file to path\n");
ERROR(
" --block-device <path>: Block device to operate on. Only applies to wipe, "
"init-partition-tables, and wipe-partition-tables\n");
}
// Refer to //zircon/system/fidl/fuchsia.paver/paver-fidl for a list of what
// these commands translate to.
enum class Command {
kWipe,
kWipePartitionTables,
kInitPartitionTables,
kAsset,
kBootloader,
kDataFile,
kFvm,
};
struct Flags {
Command cmd;
const char* cmd_name = nullptr;
::llcpp::fuchsia::paver::Configuration configuration;
::llcpp::fuchsia::paver::Asset asset;
fbl::unique_fd payload_fd;
const char* path = nullptr;
const char* block_device = nullptr;
};
bool ParseFlags(int argc, char** argv, Flags* flags) {
#define SHIFT_ARGS \
do { \
argc--; \
argv++; \
} while (0)
// Parse command.
if (argc < 2) {
ERROR("install-disk-image needs a command\n");
return false;
}
SHIFT_ARGS;
if (!strcmp(argv[0], "install-bootloader")) {
flags->cmd = Command::kBootloader;
} else if (!strcmp(argv[0], "install-efi")) {
flags->cmd = Command::kBootloader;
} else if (!strcmp(argv[0], "install-kernc")) {
flags->cmd = Command::kAsset;
flags->configuration = ::llcpp::fuchsia::paver::Configuration::A;
flags->asset = ::llcpp::fuchsia::paver::Asset::KERNEL;
} else if (!strcmp(argv[0], "install-zircona")) {
flags->cmd = Command::kAsset;
flags->configuration = ::llcpp::fuchsia::paver::Configuration::A;
flags->asset = ::llcpp::fuchsia::paver::Asset::KERNEL;
} else if (!strcmp(argv[0], "install-zirconb")) {
flags->cmd = Command::kAsset;
flags->configuration = ::llcpp::fuchsia::paver::Configuration::B;
flags->asset = ::llcpp::fuchsia::paver::Asset::KERNEL;
} else if (!strcmp(argv[0], "install-zirconr")) {
flags->cmd = Command::kAsset;
flags->configuration = ::llcpp::fuchsia::paver::Configuration::RECOVERY;
flags->asset = ::llcpp::fuchsia::paver::Asset::KERNEL;
} else if (!strcmp(argv[0], "install-vbmetaa")) {
flags->cmd = Command::kAsset;
flags->configuration = ::llcpp::fuchsia::paver::Configuration::A;
flags->asset = ::llcpp::fuchsia::paver::Asset::VERIFIED_BOOT_METADATA;
} else if (!strcmp(argv[0], "install-vbmetab")) {
flags->cmd = Command::kAsset;
flags->configuration = ::llcpp::fuchsia::paver::Configuration::B;
flags->asset = ::llcpp::fuchsia::paver::Asset::VERIFIED_BOOT_METADATA;
} else if (!strcmp(argv[0], "install-vbmetar")) {
flags->cmd = Command::kAsset;
flags->configuration = ::llcpp::fuchsia::paver::Configuration::RECOVERY;
flags->asset = ::llcpp::fuchsia::paver::Asset::VERIFIED_BOOT_METADATA;
} else if (!strcmp(argv[0], "install-data-file")) {
flags->cmd = Command::kDataFile;
} else if (!strcmp(argv[0], "install-fvm")) {
flags->cmd = Command::kFvm;
} else if (!strcmp(argv[0], "wipe")) {
flags->cmd = Command::kWipe;
} else if (!strcmp(argv[0], "init-partition-tables")) {
flags->cmd = Command::kInitPartitionTables;
} else if (!strcmp(argv[0], "wipe-partition-tables")) {
flags->cmd = Command::kWipePartitionTables;
} else {
ERROR("Invalid command: %s\n", argv[0]);
return false;
}
flags->cmd_name = argv[0];
SHIFT_ARGS;
// Parse options.
flags->payload_fd.reset(STDIN_FILENO);
while (argc > 0) {
if (!strcmp(argv[0], "--file")) {
SHIFT_ARGS;
if (argc < 1) {
ERROR("'--file' argument requires a file\n");
return false;
}
flags->payload_fd.reset(open(argv[0], O_RDONLY));
if (!flags->payload_fd) {
ERROR("Couldn't open supplied file\n");
return false;
}
} else if (!strcmp(argv[0], "--path")) {
SHIFT_ARGS;
if (argc < 1) {
ERROR("'--path' argument requires a path\n");
return false;
}
flags->path = argv[0];
} else if (!strcmp(argv[0], "--block-device")) {
SHIFT_ARGS;
if (argc < 1) {
ERROR("'--block-device' argument requires a path\n");
return false;
}
flags->block_device = argv[0];
} else if (!strcmp(argv[0], "--force")) {
ERROR("Deprecated option \"--force\".");
} else {
return false;
}
SHIFT_ARGS;
}
return true;
#undef SHIFT_ARGS
}
zx_status_t ReadFileToVmo(fbl::unique_fd payload_fd, ::llcpp::fuchsia::mem::Buffer* payload) {
constexpr size_t VmoSize = fbl::round_up(1LU << 20, ZX_PAGE_SIZE);
fzl::ResizeableVmoMapper mapper;
zx_status_t status;
if ((status = mapper.CreateAndMap(VmoSize, "partition-pave")) != ZX_OK) {
ERROR("Failed to create stream VMO\n");
return status;
}
ssize_t r;
size_t vmo_offset = 0;
while ((r = read(payload_fd.get(), &reinterpret_cast<uint8_t*>(mapper.start())[vmo_offset],
mapper.size() - vmo_offset)) > 0) {
vmo_offset += r;
if (mapper.size() - vmo_offset == 0) {
// The buffer is full, let's grow the VMO.
if ((status = mapper.Grow(mapper.size() << 1)) != ZX_OK) {
ERROR("Failed to grow VMO\n");
return status;
}
}
}
if (r < 0) {
ERROR("Error reading partition data\n");
return static_cast<zx_status_t>(r);
}
payload->size = vmo_offset;
payload->vmo = mapper.Release();
return ZX_OK;
}
zx_status_t RealMain(Flags flags) {
zx::channel paver_remote, paver_svc;
auto status = zx::channel::create(0, &paver_svc, &paver_remote);
if (status != ZX_OK) {
ERROR("Unable to create channels.\n");
return status;
}
const auto path = fbl::StringPrintf("/svc/%s", ::llcpp::fuchsia::paver::Paver::Name);
status = fdio_service_connect(path.c_str(), paver_remote.release());
if (status != ZX_OK) {
ERROR("Unable to open /svc/fuchsia.paver.Paver.\n");
return status;
}
::llcpp::fuchsia::paver::Paver::SyncClient paver_client(std::move(paver_svc));
zx::channel data_sink_remote, data_sink_svc;
status = zx::channel::create(0, &data_sink_remote, &data_sink_svc);
if (status != ZX_OK) {
ERROR("Unable to create channels.\n");
return status;
}
switch (flags.cmd) {
case Command::kFvm: {
paver_client.FindDataSink(std::move(data_sink_remote));
::llcpp::fuchsia::paver::DataSink::SyncClient data_sink(std::move(data_sink_svc));
zx::channel client, server;
status = zx::channel::create(0, &client, &server);
if (status) {
return status;
}
// Launch thread which implements interface.
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
disk_pave::PayloadStreamer streamer(std::move(server), std::move(flags.payload_fd));
loop.StartThread("payload-stream");
auto result = data_sink.WriteVolumes(std::move(client));
status = result.ok() ? result.value().status : result.status();
if (status != ZX_OK) {
ERROR("Failed to write volumes: %s\n", zx_status_get_string(status));
return status;
}
return ZX_OK;
}
case Command::kWipe: {
zx::channel block_device, block_device_remote;
if (flags.block_device != nullptr) {
status = zx::channel::create(0, &block_device, &block_device_remote);
if (status != ZX_OK) {
ERROR("Unable create channel: %s\n", zx_status_get_string(status));
return status;
}
status = fdio_service_connect(flags.block_device, block_device_remote.release());
if (status != ZX_OK) {
ERROR("Unable to open block device: %s\n", flags.block_device);
PrintUsage();
block_device.reset();
}
}
if (block_device) {
paver_client.UseBlockDevice(std::move(block_device), std::move(data_sink_remote));
} else {
paver_client.FindDataSink(std::move(data_sink_remote));
}
::llcpp::fuchsia::paver::DataSink::SyncClient data_sink(std::move(data_sink_svc));
auto result = data_sink.WipeVolume();
if (!result.ok()) {
status = result.status();
} else {
status = !result->result.is_response() ? ZX_OK : result->result.err();
}
if (status != ZX_OK) {
ERROR("Failed to wipe block device: %s\n", zx_status_get_string(status));
return status;
}
return ZX_OK;
}
case Command::kInitPartitionTables: {
if (flags.block_device == nullptr) {
ERROR("init-partition-tables requires --block-device\n");
PrintUsage();
return ZX_ERR_INVALID_ARGS;
}
zx::channel block_device, block_device_remote;
status = zx::channel::create(0, &block_device, &block_device_remote);
if (status != ZX_OK) {
ERROR("Unable create channel: %s\n", zx_status_get_string(status));
return status;
}
status = fdio_service_connect(flags.block_device, block_device_remote.release());
if (status != ZX_OK) {
ERROR("Unable to open block device: %s\n", flags.block_device);
PrintUsage();
block_device.reset();
return status;
}
paver_client.UseBlockDevice(std::move(block_device), std::move(data_sink_remote));
::llcpp::fuchsia::paver::DynamicDataSink::SyncClient data_sink(std::move(data_sink_svc));
auto result = data_sink.InitializePartitionTables();
status = result.ok() ? result.value().status : result.status();
if (status != ZX_OK) {
ERROR("Failed to initialize partition tables: %s\n", zx_status_get_string(status));
return status;
}
return ZX_OK;
}
case Command::kWipePartitionTables: {
if (flags.block_device == nullptr) {
ERROR("wipe-partition-tables requires --block-device\n");
PrintUsage();
return ZX_ERR_INVALID_ARGS;
}
zx::channel block_device, block_device_remote;
status = zx::channel::create(0, &block_device, &block_device_remote);
if (status != ZX_OK) {
ERROR("Unable create channel: %s\n", zx_status_get_string(status));
return status;
}
status = fdio_service_connect(flags.block_device, block_device_remote.release());
if (status != ZX_OK) {
ERROR("Unable to open block device: %s\n", flags.block_device);
PrintUsage();
block_device.reset();
}
paver_client.UseBlockDevice(std::move(block_device), std::move(data_sink_remote));
::llcpp::fuchsia::paver::DynamicDataSink::SyncClient data_sink(std::move(data_sink_svc));
auto result = data_sink.WipePartitionTables();
status = result.ok() ? result.value().status : result.status();
if (status != ZX_OK) {
ERROR("Failed to wipe partition tables: %s\n", zx_status_get_string(status));
return status;
}
return ZX_OK;
}
default:
break;
}
::llcpp::fuchsia::mem::Buffer payload;
status = ReadFileToVmo(std::move(flags.payload_fd), &payload);
if (status != ZX_OK) {
return status;
}
paver_client.FindDataSink(std::move(data_sink_remote));
::llcpp::fuchsia::paver::DataSink::SyncClient data_sink(std::move(data_sink_svc));
switch (flags.cmd) {
case Command::kDataFile: {
if (flags.path == nullptr) {
ERROR("install-data-file requires --path\n");
PrintUsage();
return ZX_ERR_INVALID_ARGS;
}
auto result = data_sink.WriteDataFile(fidl::unowned_str(flags.path, strlen(flags.path)),
std::move(payload));
status = result.ok() ? result.value().status : result.status();
if (status != ZX_OK) {
ERROR("install-data-file failed: %s\n", zx_status_get_string(status));
return status;
}
return ZX_OK;
}
case Command::kBootloader: {
auto result = data_sink.WriteBootloader(std::move(payload));
status = result.ok() ? result.value().status : result.status();
if (status != ZX_OK) {
ERROR("Installing bootloader partition failed: %s\n", zx_status_get_string(status));
return status;
}
return ZX_OK;
}
case Command::kAsset: {
auto result = data_sink.WriteAsset(flags.configuration, flags.asset, std::move(payload));
status = result.ok() ? result.value().status : result.status();
if (status != ZX_OK) {
ERROR("Writing asset failed: %s\n", zx_status_get_string(status));
return status;
}
return ZX_OK;
}
default:
return ZX_ERR_INTERNAL;
}
}
} // namespace
int main(int argc, char** argv) {
Flags flags = {};
if (!ParseFlags(argc, argv, &flags)) {
PrintUsage();
return -1;
}
const char* cmd_name = flags.cmd_name;
zx_status_t status = RealMain(std::move(flags));
if (status != ZX_OK) {
return 1;
}
fprintf(stderr, "disk-pave: %s operation succeeded.\n", cmd_name);
return 0;
}