blob: f6e86bc75dfa30f7637e0782677d2e3a21092a92 [file] [log] [blame]
// Copyright 2016 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 <fidl/fuchsia.device/cpp/wire.h>
#include <fidl/fuchsia.hardware.block.partition/cpp/wire.h>
#include <fidl/fuchsia.hardware.block/cpp/wire.h>
#include <fidl/fuchsia.hardware.skipblock/cpp/wire.h>
#include <inttypes.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fit/defer.h>
#include <lib/fzl/owned-vmo-mapper.h>
#include <lib/zx/vmo.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <zircon/process.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <memory>
#include <string>
#include <fbl/unique_fd.h>
#include <gpt/c/gpt.h>
#include <gpt/guid.h>
#include <pretty/hexdump.h>
#include "src/lib/fxl/strings/string_printf.h"
#include "src/storage/lib/storage-metrics/block-metrics.h"
namespace fuchsia_block = fuchsia_hardware_block;
namespace fuchsia_partition = fuchsia_hardware_block_partition;
namespace fuchsia_skipblock = fuchsia_hardware_skipblock;
namespace {
constexpr char DEV_BLOCK[] = "/dev/class/block";
constexpr char DEV_SKIP_BLOCK[] = "/dev/class/skip-block";
char* size_to_cstring(char* str, size_t maxlen, uint64_t size) {
constexpr size_t kibi = 1024;
const char* unit;
uint64_t div;
if (size < kibi) {
unit = "";
div = 1;
} else if (size >= kibi && size < kibi * kibi) {
unit = "K";
div = kibi;
} else if (size >= kibi * kibi && size < kibi * kibi * kibi) {
unit = "M";
div = kibi * kibi;
} else if (size >= kibi * kibi * kibi && size < kibi * kibi * kibi * kibi) {
unit = "G";
div = kibi * kibi * kibi;
} else {
unit = "T";
div = kibi * kibi * kibi * kibi;
}
snprintf(str, maxlen, "%" PRIu64 "%s", size / div, unit);
return str;
}
int cmd_list_blk() {
struct dirent* de;
DIR* dir = opendir(DEV_BLOCK);
if (!dir) {
fprintf(stderr, "Error opening %s\n", DEV_BLOCK);
return -1;
}
auto cleanup = fit::defer([&dir]() { closedir(dir); });
printf("%-3s %-4s %-16s %-20s %-6s %s\n", "ID", "SIZE", "TYPE", "LABEL", "FLAGS", "DEVICE");
while ((de = readdir(dir)) != nullptr) {
if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
continue;
}
std::string device_path = fxl::StringPrintf("%s/%s", DEV_BLOCK, de->d_name);
std::string controller_path = device_path + "/device_controller";
std::string topological_path;
{
zx::result controller = component::Connect<fuchsia_device::Controller>(controller_path);
if (controller.is_error()) {
fprintf(stderr, "Error opening %s: %s\n", controller_path.c_str(),
controller.status_string());
continue;
}
fidl::WireResult result = fidl::WireCall(controller.value())->GetTopologicalPath();
if (!result.ok()) {
fprintf(stderr, "Error getting topological path for %s: %s\n", controller_path.c_str(),
result.status_string());
continue;
}
fit::result response = result.value();
if (response.is_error()) {
fprintf(stderr, "Error getting topological path for %s: %s\n", controller_path.c_str(),
zx_status_get_string(response.error_value()));
continue;
}
topological_path = response.value()->path.get();
}
zx::result device = component::Connect<fuchsia_partition::Partition>(device_path);
if (device.is_error()) {
fprintf(stderr, "Error opening %s: %s\n", device_path.c_str(), device.status_string());
continue;
}
char sizestr[6] = {};
fuchsia_block::wire::BlockInfo block_info;
{
if (const fidl::WireResult result = fidl::WireCall(device.value())->GetInfo(); result.ok()) {
if (const fit::result response = result.value(); response.is_ok()) {
block_info = response.value()->info;
size_to_cstring(sizestr, sizeof(sizestr), block_info.block_size * block_info.block_count);
}
}
}
std::string type;
std::string label;
{
if (const fidl::WireResult result = fidl::WireCall(device.value())->GetTypeGuid();
result.ok()) {
if (const fidl::WireResponse response = result.value(); response.status == ZX_OK) {
type = gpt::KnownGuid::TypeDescription(response.guid->value.data());
}
}
if (const fidl::WireResult result = fidl::WireCall(device.value())->GetName(); result.ok()) {
if (const fidl::WireResponse response = result.value(); response.status == ZX_OK) {
label = response.name.get();
}
}
}
char flags[20] = {0};
if (block_info.flags & fuchsia_block::wire::Flag::kReadonly) {
strlcat(flags, "RO ", sizeof(flags));
}
if (block_info.flags & fuchsia_block::wire::Flag::kRemovable) {
strlcat(flags, "RE ", sizeof(flags));
}
if (block_info.flags & fuchsia_block::wire::Flag::kBootpart) {
strlcat(flags, "BP ", sizeof(flags));
}
printf("%-3s %4s %-16s %-20s %-6s %s\n", de->d_name, sizestr, type.c_str(), label.c_str(),
flags, topological_path.c_str());
}
return 0;
}
int cmd_list_skip_blk() {
struct dirent* de;
DIR* dir = opendir(DEV_SKIP_BLOCK);
if (!dir) {
fprintf(stderr, "Error opening %s\n", DEV_SKIP_BLOCK);
return -1;
}
while ((de = readdir(dir)) != nullptr) {
if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
continue;
}
std::string device_path = fxl::StringPrintf("%s/%s", DEV_SKIP_BLOCK, de->d_name);
std::string controller_path = device_path + "/device_controller";
std::string topological_path;
{
zx::result controller = component::Connect<fuchsia_device::Controller>(controller_path);
if (controller.is_error()) {
fprintf(stderr, "Error opening %s: %s\n", controller_path.c_str(),
controller.status_string());
continue;
}
fidl::WireResult result = fidl::WireCall(controller.value())->GetTopologicalPath();
if (!result.ok()) {
fprintf(stderr, "Error getting topological path for %s: %s\n", controller_path.c_str(),
result.status_string());
continue;
}
fit::result response = result.value();
if (response.is_error()) {
fprintf(stderr, "Error getting topological path for %s: %s\n", controller_path.c_str(),
zx_status_get_string(response.error_value()));
continue;
}
topological_path = response.value()->path.get();
}
std::string type;
{
zx::result device = component::Connect<fuchsia_skipblock::SkipBlock>(device_path);
if (device.is_error()) {
fprintf(stderr, "Error opening %s: %s\n", device_path.c_str(), device.status_string());
continue;
}
if (const fidl::WireResult result = fidl::WireCall(device.value())->GetPartitionInfo();
result.ok()) {
if (const fidl::WireResponse response = result.value(); response.status == ZX_OK) {
type = gpt::KnownGuid::TypeDescription(response.partition_info.partition_guid.data());
}
}
}
printf("%-3s %4sr%-16s %-20s %-6s %s\n", de->d_name, "", type.c_str(), "", "",
topological_path.c_str());
}
closedir(dir);
return 0;
}
int try_read_skip_blk(const fidl::UnownedClientEnd<fuchsia_skipblock::SkipBlock>& skip_block,
off_t offset, size_t count) {
// check that count and offset are aligned to block size
const fidl::WireResult result = fidl::WireCall(skip_block)->GetPartitionInfo();
if (!result.ok()) {
fprintf(stderr, "Failed to get skip block partition info: %s\n",
result.FormatDescription().c_str());
return -1;
}
const fidl::WireResponse response = result.value();
if (zx_status_t status = response.status; status != ZX_OK) {
fprintf(stderr, "Failed to get skip block partition info: %s\n", zx_status_get_string(status));
return -1;
}
uint64_t blksize = response.partition_info.block_size_bytes;
if (count % blksize) {
fprintf(stderr, "Bytes read must be a multiple of blksize=%" PRIu64 "\n", blksize);
return -1;
}
if (offset % blksize) {
fprintf(stderr, "Offset must be a multiple of blksize=%" PRIu64 "\n", blksize);
return -1;
}
// allocate and map a buffer to read into
zx::vmo vmo;
if (zx_status_t status = zx::vmo::create(count, 0, &vmo); status != ZX_OK) {
fprintf(stderr, "Failed to create vmo: %s\n", zx_status_get_string(status));
return -1;
}
fzl::OwnedVmoMapper mapper;
if (zx_status_t status = mapper.Map(std::move(vmo), count); status != ZX_OK) {
fprintf(stderr, "Failed to map vmo: %s\n", zx_status_get_string(status));
return -1;
}
zx::vmo dup;
if (zx_status_t status = mapper.vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &dup); status != ZX_OK) {
fprintf(stderr, "Failed duplicate handle: %s\n", zx_status_get_string(status));
return -1;
}
// read the data
const fidl::WireResult read_result =
fidl::WireCall(skip_block)
->Read({
.vmo = std::move(dup),
.vmo_offset = 0,
.block = static_cast<uint32_t>(offset / blksize),
.block_count = static_cast<uint32_t>(count / blksize),
});
if (!read_result.ok()) {
fprintf(stderr, "Failed to read skip block: %s\n", read_result.FormatDescription().c_str());
return -1;
}
const fidl::WireResponse read_response = read_result.value();
if (zx_status_t status = read_response.status; status != ZX_OK) {
fprintf(stderr, "Failed to read skip block: %s\n", zx_status_get_string(status));
return -1;
}
hexdump8_ex(mapper.start(), count, offset);
return 0;
}
int cmd_read_blk(const char* dev, off_t offset, size_t count) {
zx::result block = component::Connect<fuchsia_block::Block>(dev);
if (block.is_error()) {
fprintf(stderr, "Error connecting to %s: %s\n", dev, block.status_string());
return -1;
}
const fidl::WireResult result = fidl::WireCall(block.value())->GetInfo();
if (!result.ok()) {
fprintf(stderr, "Error getting block size for %s: %s\n", dev,
result.FormatDescription().c_str());
return -1;
}
const fit::result response = result.value();
if (response.is_error()) {
zx::result skip_block = component::Connect<fuchsia_skipblock::SkipBlock>(dev);
if (skip_block.is_error()) {
fprintf(stderr, "Error connecting to %s: %s\n", dev, skip_block.status_string());
return -1;
}
if (try_read_skip_blk(skip_block.value(), offset, count) < 0) {
fprintf(stderr, "Error getting block size for %s\n", dev);
return -1;
}
return 0;
}
// Check that count and offset are aligned to block size.
uint64_t blksize = response.value()->info.block_size;
if (count % blksize) {
fprintf(stderr, "Bytes read must be a multiple of blksize=%" PRIu64 "\n", blksize);
return -1;
}
if (offset % blksize) {
fprintf(stderr, "Offset must be a multiple of blksize=%" PRIu64 "\n", blksize);
return -1;
}
// Create vmo for reading, and handle clone for passing off.
zx::vmo in_vmo;
if (zx_status_t status = zx::vmo::create(count, 0u, &in_vmo); status != ZX_OK) {
fprintf(stderr, "Failed to create read vmo: %s.\n", zx_status_get_string(status));
return -1;
}
zx::vmo out_vmo;
if (zx_status_t status = in_vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &out_vmo); status != ZX_OK) {
fprintf(stderr, "Failed to duplicate vmo handle: %s.\n", zx_status_get_string(status));
return -1;
}
fzl::OwnedVmoMapper mapper;
if (zx_status_t status = mapper.Map(std::move(in_vmo), count); status != ZX_OK) {
fprintf(stderr, "Failed to map vmo: %s\n", zx_status_get_string(status));
return -1;
}
// read the data
const fidl::WireResult read_result =
fidl::WireCall(block.value())->ReadBlocks(std::move(out_vmo), count, offset, 0);
if (!read_result.ok()) {
fprintf(stderr, "FIDL error: %s\n", read_result.FormatDescription().c_str());
return -1;
}
if (read_result.value().is_error()) {
fprintf(stderr, "Error reading blocks from device: %s\n",
zx_status_get_string(read_result.value().error_value()));
return -1;
}
hexdump8_ex(mapper.start(), count, offset);
return 0;
}
int cmd_stats(const char* dev, bool clear) {
zx::result block = component::Connect<fuchsia_block::Block>(dev);
if (block.is_error()) {
fprintf(stderr, "Error connecting to %s: %s\n", dev, block.status_string());
return -1;
}
const fidl::WireResult result = fidl::WireCall(block.value())->GetStats(clear);
if (!result.ok()) {
fprintf(stderr, "Error getting stats for %s: %s\n", dev, result.FormatDescription().c_str());
return -1;
}
const fit::result response = result.value();
if (response.is_error()) {
fprintf(stderr, "Error getting stats for %s: %s\n", dev,
zx_status_get_string(response.error_value()));
return -1;
}
storage_metrics::BlockDeviceMetrics metrics(&response.value()->stats);
metrics.Dump(stdout);
return 0;
}
} // namespace
int main(int argc, const char** argv) {
int rc = 0;
const char* cmd = argc > 1 ? argv[1] : nullptr;
if (cmd) {
if (!strcmp(cmd, "help")) {
goto usage;
} else if (!strcmp(cmd, "read")) {
if (argc < 5)
goto usage;
rc = cmd_read_blk(argv[2], strtoul(argv[3], nullptr, 10), strtoull(argv[4], nullptr, 10));
} else if (!strcmp(cmd, "stats")) {
if (argc < 4)
goto usage;
if (strcmp("true", argv[3]) != 0 && strcmp("false", argv[3]) != 0)
goto usage;
rc = cmd_stats(argv[2], strcmp("true", argv[3]) == 0);
} else {
fprintf(stderr, "Unrecognized command %s!\n", cmd);
goto usage;
}
} else {
rc = cmd_list_blk() || cmd_list_skip_blk();
}
return rc;
usage:
fprintf(stderr, "Usage:\n");
fprintf(stderr, "%s\n", argv[0]);
fprintf(stderr, "%s read <blkdev> <offset> <count>\n", argv[0]);
fprintf(stderr, "%s stats <blkdev> <clear=true|false>\n", argv[0]);
return 0;
}