blob: 799cfcfccba46723821a1ff7ab9cf34affd11df6 [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 <errno.h>
#include <fcntl.h>
#include <fidl/fuchsia.hardware.block/cpp/wire.h>
#include <fidl/fuchsia.hardware.skipblock/cpp/wire.h>
#include <fuchsia/hardware/block/driver/c/banjo.h>
#include <lib/component/incoming/cpp/service.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fzl/owned-vmo-mapper.h>
#include <lib/stdcompat/string_view.h>
#include <lib/zx/result.h>
#include <limits.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <zircon/errors.h>
#include <zircon/process.h>
#include <zircon/rights.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <memory>
#include <fbl/unique_fd.h>
#include <safemath/checked_math.h>
#include <storage/buffer/vmoid_registry.h>
#include "src/storage/lib/block_client/cpp/client.h"
namespace {
int Usage() {
fprintf(stderr, "Usage: dd [OPTIONS]\n");
fprintf(stderr, "dd can be used to convert and copy files\n");
fprintf(stderr, " bs=BYTES : read and write BYTES bytes at a time\n");
fprintf(stderr, " count=N : copy only N input blocks\n");
fprintf(stderr, " ibs=BYTES : read BYTES bytes at a time (default: 512)\n");
fprintf(stderr, " if=FILE : read from FILE instead of stdin\n");
fprintf(stderr, " obs=BYTES : write BYTES bytes at a time (default: 512)\n");
fprintf(stderr, " of=FILE : write to FILE instead of stdout\n");
fprintf(stderr, " seek=N : skip N obs-sized blocks at start of output\n");
fprintf(stderr, " skip=N : skip N ibs-sized blocks at start of input\n");
fprintf(stderr, " conv=sync : pad input to input block size\n");
fprintf(stderr,
" N and BYTES may be followed by the following multiplicitive\n"
" suffixes: c = 1, w = 2, b = 512, kB = 1000, K = 1024,\n"
" MB = 1000 * 1000, M = 1024 * 1024, xM = M,\n"
" GB = 1000 * 1000 * 1000, G = 1024 * 1024 * 1024\n");
fprintf(stderr, " --help : Show this help message\n");
return EXIT_FAILURE;
}
// Returns "true" if the argument matches the prefix.
// In this case, moves the argument past the prefix.
bool PrefixMatch(const char** arg, std::string_view prefix) {
if (cpp20::starts_with(std::string_view(*arg), prefix)) {
*arg += prefix.length();
return true;
}
return false;
}
#define MAYBE_MULTIPLY_SUFFIX(str, out, suffix, value) \
if (std::string_view(str) == (suffix)) { \
(out) *= (value); \
return 0; \
}
// Parse the formatted size string from |s|, and place
// the result in |out|.
//
// Returns 0 on success.
int ParseSize(const char* s, size_t* out) {
char* endptr;
if (*s < '0' || *s > '9') {
goto done;
}
*out = strtol(s, &endptr, 10);
if (*endptr == '\0') {
return 0;
}
MAYBE_MULTIPLY_SUFFIX(endptr, *out, "G", UINT64_C(1) << 30);
MAYBE_MULTIPLY_SUFFIX(endptr, *out, "GB", UINT64_C(1000) * UINT64_C(1000) * UINT64_C(1000));
MAYBE_MULTIPLY_SUFFIX(endptr, *out, "xM", UINT64_C(1) << 20);
MAYBE_MULTIPLY_SUFFIX(endptr, *out, "M", UINT64_C(1) << 20);
MAYBE_MULTIPLY_SUFFIX(endptr, *out, "MB", UINT64_C(1000) * UINT64_C(1000));
MAYBE_MULTIPLY_SUFFIX(endptr, *out, "K", UINT64_C(1) << 10);
MAYBE_MULTIPLY_SUFFIX(endptr, *out, "kB", UINT64_C(1000));
MAYBE_MULTIPLY_SUFFIX(endptr, *out, "b", UINT64_C(512));
MAYBE_MULTIPLY_SUFFIX(endptr, *out, "w", UINT64_C(2));
MAYBE_MULTIPLY_SUFFIX(endptr, *out, "c", UINT64_C(1));
done:
fprintf(stderr, "Couldn't parse size string: %s\n", s);
return -1;
}
struct DdOptions {
bool use_count;
size_t count;
size_t input_bs;
size_t input_skip;
size_t output_bs;
size_t output_seek;
char input[PATH_MAX];
char output[PATH_MAX];
};
int ParseArgs(int argc, const char** argv, DdOptions* options) {
while (argc > 1) {
const char* arg = argv[1];
if (PrefixMatch(&arg, "bs=")) {
size_t size;
if (ParseSize(arg, &size)) {
return Usage();
}
options->input_bs = size;
options->output_bs = size;
} else if (PrefixMatch(&arg, "count=")) {
if (ParseSize(arg, &options->count)) {
return Usage();
}
options->use_count = true;
} else if (PrefixMatch(&arg, "ibs=")) {
if (ParseSize(arg, &options->input_bs)) {
return Usage();
}
} else if (PrefixMatch(&arg, "obs=")) {
if (ParseSize(arg, &options->output_bs)) {
return Usage();
}
} else if (PrefixMatch(&arg, "seek=")) {
if (ParseSize(arg, &options->output_seek)) {
return Usage();
}
} else if (PrefixMatch(&arg, "skip=")) {
if (ParseSize(arg, &options->input_skip)) {
return Usage();
}
} else if (PrefixMatch(&arg, "if=")) {
strncpy(options->input, arg, PATH_MAX);
options->input[PATH_MAX - 1] = '\0';
} else if (PrefixMatch(&arg, "of=")) {
strncpy(options->output, arg, PATH_MAX);
options->output[PATH_MAX - 1] = '\0';
} else if (strcmp(arg, "conv=sync") == 0) {
fprintf(stderr, "TODO: conv=sync and partial reads aren't supported yet\n");
return Usage();
} else {
return Usage();
}
argc--;
argv++;
}
return 0;
}
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#define MAX(x, y) ((x) < (y) ? (y) : (x))
bool IsBlockPath(std::string_view path) {
// E.g. /dev/class/block/000 or /dev/sys/platform/00:2d/ramctl/ramdisk-0/block
return (cpp20::starts_with(path, "/dev") && cpp20::ends_with(path, "/block")) ||
cpp20::starts_with(path, "/dev/class/block");
}
bool IsSkipBlockPath(std::string_view path) {
// E.g. /dev/class/skip-block/000 or
// /dev/sys/platform/05:00:f/aml-raw_nand/nand/zircon-a/skip-block
return (cpp20::starts_with(path, "/dev") && cpp20::ends_with(path, "/skip-block")) ||
cpp20::starts_with(path, "/dev/class/skip-block");
}
zx::result<fidl::ClientEnd<fuchsia_hardware_skipblock::SkipBlock>> GetSkipBlockClient(
const char* path, size_t* block_size) {
zx::result client = component::Connect<fuchsia_hardware_skipblock::SkipBlock>(path);
if (client.is_error()) {
return client.take_error();
}
fidl::WireResult res = fidl::WireCall(*client)->GetPartitionInfo();
if (!res.ok()) {
return zx::error(res.error().status());
}
if (res.value().status != ZX_OK) {
return zx::error(res.value().status);
}
*block_size = res.value().partition_info.block_size_bytes;
return zx::ok(std::move(*client));
}
zx::result<std::unique_ptr<block_client::Client>> GetBlockClient(const char* path,
size_t* block_size) {
zx::result client = component::Connect<fuchsia_hardware_block::Block>(path);
if (client.is_error()) {
return client.take_error();
}
fidl::WireResult info = fidl::WireCall(*client)->GetInfo();
if (!info.ok()) {
return zx::error(info.status());
}
auto [session, server] = fidl::Endpoints<fuchsia_hardware_block::Session>::Create();
if (fidl::OneWayStatus result = fidl::WireCall(*client)->OpenSession(std::move(server));
!result.ok()) {
return zx::error(result.status());
}
fidl::WireResult fifo_result = fidl::WireCall(session)->GetFifo();
if (!fifo_result.ok()) {
return zx::error(fifo_result.status());
}
fit::result fifo_response = fifo_result.value();
if (fifo_response.is_error()) {
return fifo_response.take_error();
}
*block_size = info.value()->info.block_size;
return zx::ok(std::make_unique<block_client::Client>(std::move(session),
std::move(fifo_response.value()->fifo)));
}
class Target {
public:
~Target() {
if (vmoid_.IsAttached()) {
ZX_DEBUG_ASSERT(variant_ == Variant::kBlock && block_);
block_fifo_request_t request;
request.vmoid = vmoid_.TakeId();
request.command = {.opcode = BLOCK_OPCODE_CLOSE_VMO, .flags = 0};
if (zx_status_t status = block_->Transaction(&request, 1); status != ZX_OK) {
fprintf(stderr, "Failed to detach VMO: %s\n", zx_status_get_string(status));
}
}
}
Target(Target&& o) = default;
Target& operator=(Target&& o) = default;
Target(const Target&) = delete;
Target& operator=(const Target&) = delete;
static zx::result<Target> Open(const char* path, size_t target_block_size) {
size_t block_size;
if (IsSkipBlockPath(path)) {
zx::result client = GetSkipBlockClient(path, &block_size);
if (client.is_error()) {
return client.take_error();
}
Target target;
target.variant_ = Variant::kSkipBlock;
target.skip_block_ = std::move(*client);
if (target_block_size % block_size) {
fprintf(stderr, "Block size (%zu) must be a multiple of device's blksize %zu\n",
target_block_size, block_size);
return zx::error(ZX_ERR_INVALID_ARGS);
}
target.block_size_ = block_size;
return zx::ok(std::move(target));
}
if (IsBlockPath(path)) {
zx::result client = GetBlockClient(path, &block_size);
if (client.is_error()) {
return client.take_error();
}
Target target;
target.variant_ = Variant::kBlock;
target.block_ = std::move(*client);
if (target_block_size % block_size) {
fprintf(stderr, "Block size (%zu) must be a multiple of device's blksize %zu\n",
target_block_size, block_size);
return zx::error(ZX_ERR_INVALID_ARGS);
}
target.block_size_ = block_size;
return zx::ok(std::move(target));
}
// Use regular file I/O for the rest.
fbl::unique_fd fd(open(path, O_RDWR | O_CREAT));
if (!fd) {
return zx::error(ZX_ERR_BAD_PATH);
}
Target target;
target.variant_ = Variant::kFile;
target.fd_ = std::move(fd);
target.block_size_ = target_block_size;
return zx::ok(std::move(target));
}
zx::result<> SetBuffer(const fzl::OwnedVmoMapper& vmo) {
buf_size_ = vmo.size();
switch (variant_) {
case Variant::kFile: {
buf_ = vmo.start();
return zx::ok();
}
case Variant::kBlock: {
zx::result vmoid = block_->RegisterVmo(vmo.vmo());
if (vmoid.is_error()) {
return vmoid.take_error();
}
vmoid_ = std::move(*vmoid);
return zx::ok();
}
case Variant::kSkipBlock: {
vmo_ = vmo.vmo().borrow();
return zx::ok();
}
case Variant::kUnknown:
return zx::error(ZX_ERR_BAD_STATE);
}
}
zx::result<> ReadOne() {
auto nblocks = static_cast<uint32_t>(buf_size_ / block_size_);
switch (variant_) {
case Variant::kFile: {
ssize_t res = read(fd_.get(), buf_, buf_size_);
if (res < static_cast<ssize_t>(buf_size_)) {
if (res == 0) {
return zx::error(ZX_ERR_STOP);
}
return zx::error(ZX_ERR_IO);
}
break;
}
case Variant::kBlock: {
block_fifo_request_t request;
request.vmoid = vmoid_.get();
request.command = {.opcode = BLOCK_OPCODE_READ, .flags = 0};
request.length = nblocks;
request.vmo_offset = 0;
request.dev_offset = cur_block_;
if (zx_status_t status = block_->Transaction(&request, 1); status != ZX_OK) {
return zx::error(status);
}
break;
}
case Variant::kSkipBlock: {
zx::vmo vmo;
if (zx_status_t status = vmo_->duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo); status != ZX_OK) {
return zx::error(status);
}
fuchsia_hardware_skipblock::wire::ReadWriteOperation op{
.vmo = std::move(vmo),
.vmo_offset = 0,
.block = static_cast<uint32_t>(cur_block_),
.block_count = static_cast<uint32_t>(nblocks),
};
fidl::WireResult res = fidl::WireCall(skip_block_)->Read(std::move(op));
if (!res.ok()) {
return zx::error(res.status());
}
if (res.value().status != ZX_OK) {
return zx::error(res.value().status);
}
break;
}
case Variant::kUnknown: {
return zx::error(ZX_ERR_BAD_STATE);
}
}
cur_block_ += nblocks;
return zx::ok();
}
zx::result<> WriteOne() {
auto nblocks = static_cast<uint32_t>(buf_size_ / block_size_);
switch (variant_) {
case Variant::kFile: {
ssize_t res = write(fd_.get(), buf_, buf_size_);
if (res < static_cast<ssize_t>(buf_size_)) {
return zx::error(ZX_ERR_IO);
}
break;
}
case Variant::kBlock: {
block_fifo_request_t request;
request.vmoid = vmoid_.get();
request.command = {.opcode = BLOCK_OPCODE_WRITE, .flags = 0};
request.length = nblocks;
request.vmo_offset = 0;
request.dev_offset = cur_block_;
if (zx_status_t status = block_->Transaction(&request, 1); status != ZX_OK) {
return zx::error(status);
}
break;
}
case Variant::kSkipBlock: {
zx::vmo vmo;
if (zx_status_t status = vmo_->duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo); status != ZX_OK) {
return zx::error(status);
}
fuchsia_hardware_skipblock::wire::ReadWriteOperation op{
.vmo = std::move(vmo),
.vmo_offset = 0,
.block = static_cast<uint32_t>(cur_block_),
.block_count = static_cast<uint32_t>(nblocks),
};
fidl::WireResult res = fidl::WireCall(skip_block_)->Write(std::move(op));
if (!res.ok()) {
fprintf(stderr, "Failed to write (FIDL): %s\n", res.status_string());
return zx::error(res.status());
}
if (res.value().status != ZX_OK) {
fprintf(stderr, "Failed to write: %s\n", zx_status_get_string(res.value().status));
return zx::error(res.value().status);
}
break;
}
case Variant::kUnknown: {
return zx::error(ZX_ERR_BAD_STATE);
}
}
cur_block_ += nblocks;
return zx::ok();
}
zx::result<> Seek(uint64_t block) {
cur_block_ = block;
if (variant_ == Variant::kFile) {
off_t seek = safemath::CheckMul(safemath::checked_cast<off_t>(cur_block_), block_size_)
.Cast<off_t>()
.ValueOrDie();
if (lseek(fd_.get(), seek, SEEK_SET) != seek) {
return zx::error(ZX_ERR_IO);
}
}
return zx::ok();
}
private:
Target() = default;
enum class Variant {
kFile,
kBlock,
kSkipBlock,
kUnknown,
} variant_ = Variant::kUnknown;
uint64_t buf_size_ = 0;
uint64_t block_size_ = 0;
uint64_t cur_block_ = 0;
// kFile fields
fbl::unique_fd fd_;
void* buf_ = nullptr;
// kBlock fields
std::unique_ptr<block_client::Client> block_;
storage::OwnedVmoid vmoid_;
// kSkipBlock Fields
fidl::ClientEnd<fuchsia_hardware_skipblock::SkipBlock> skip_block_;
zx::unowned_vmo vmo_;
};
} // namespace
int main(int argc, const char** argv) {
DdOptions options = {};
options.input_bs = 512;
options.output_bs = 512;
if (int r = ParseArgs(argc, argv, &options); r) {
return r;
}
if (options.input_bs == 0 || options.output_bs == 0) {
fprintf(stderr, "block sizes must be greater than zero\n");
return EXIT_FAILURE;
}
if (MAX(options.input_bs, options.output_bs) % MIN(options.input_bs, options.output_bs) != 0) {
// We could implement this case if we cared to do so.
fprintf(stderr, "Input and output block sizes must be multiples\n");
return EXIT_FAILURE;
}
size_t buf_size = MAX(options.output_bs, options.input_bs);
// Buffer to contain partially read data. Both `in` and `out` will share this VMO.
fzl::OwnedVmoMapper vmo;
// Number of full records copied to/from target
size_t records_in = 0;
size_t records_out = 0;
uint64_t sum_bytes_out = 0;
zx_time_t start = 0, stop = 0;
if (*options.input == '\0') {
fprintf(stderr, "Reading from stdin isn't supported.\n");
return Usage();
}
zx::result target = Target::Open(options.input, options.input_bs);
if (target.is_error()) {
fprintf(stderr, "Failed to open %s: %s\n", options.input, target.status_string());
return EXIT_FAILURE;
}
Target in = std::move(*target);
if (*options.output == '\0') {
fprintf(stderr, "Writing to stdout isn't supported.\n");
return Usage();
}
target = Target::Open(options.output, options.output_bs);
if (target.is_error()) {
fprintf(stderr, "Failed to open %s: %s\n", options.output, target.status_string());
return EXIT_FAILURE;
}
Target out = std::move(*target);
if (zx_status_t status = vmo.CreateAndMap(buf_size, "buf"); status != ZX_OK) {
fprintf(stderr, "Failed to create VMO: %s\n", zx_status_get_string(status));
return EXIT_FAILURE;
}
if (zx::result status = in.SetBuffer(vmo); status.is_error()) {
fprintf(stderr, "Failed to set buffer: %s\n", status.status_string());
return EXIT_FAILURE;
}
if (zx::result status = out.SetBuffer(vmo); status.is_error()) {
fprintf(stderr, "Failed to set buffer: %s\n", status.status_string());
return EXIT_FAILURE;
}
if (options.input_skip) {
if (zx::result status = in.Seek(options.input_skip); status.is_error()) {
fprintf(stderr, "Failed to skip: %s\n", status.status_string());
return EXIT_FAILURE;
}
}
if (options.output_seek) {
if (zx::result status = out.Seek(options.output_seek); status.is_error()) {
fprintf(stderr, "Failed to seek: %s\n", status.status_string());
return EXIT_FAILURE;
}
}
start = zx_clock_get_monotonic();
int res = EXIT_FAILURE;
while (true) {
if (options.use_count && !options.count) {
res = EXIT_SUCCESS;
break;
}
--options.count;
if (zx::result status = in.ReadOne(); status.is_error()) {
if (status.status_value() != ZX_ERR_STOP) {
fprintf(stderr, "Failed to read: %s\n", status.status_string());
}
break;
}
records_in++;
if (zx::result status = out.WriteOne(); status.is_error()) {
fprintf(stderr, "Failed to write: %s\n", status.status_string());
break;
}
records_out++;
}
stop = zx_clock_get_monotonic();
printf("%zu+0 records in\n", records_in);
printf("%zu+0 records out\n", records_out);
sum_bytes_out = records_out * options.output_bs;
if ((start != 0) && (stop > start)) {
fprintf(stderr, "%zu bytes copied, %zu bytes/s\n", sum_bytes_out,
sum_bytes_out * ZX_SEC(1) / (stop - start));
} else {
printf("%zu bytes copied\n", sum_bytes_out);
}
return res;
}