blob: 736a6c7c88bdbfc2ef4039158fda646ad2a7db2c [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 <fuchsia/hardware/skipblock/c/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <limits.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
int usage(void) {
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 -1;
}
// Returns "true" if the argument matches the prefix.
// In this case, moves the argument past the prefix.
bool prefix_match(const char** arg, const char* prefix) {
if (!strncmp(*arg, prefix, strlen(prefix))) {
*arg += strlen(prefix);
return true;
}
return false;
}
#define MAYBE_MULTIPLY_SUFFIX(str, out, suffix, value) \
if (!strcmp((str), (suffix))) { \
(out) *= (value); \
return 0; \
}
// Parse the formatted size string from |s|, and place
// the result in |out|.
//
// Returns 0 on success.
int parse_size(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", 1UL << 30);
MAYBE_MULTIPLY_SUFFIX(endptr, *out, "GB", 1000 * 1000 * 1000UL);
MAYBE_MULTIPLY_SUFFIX(endptr, *out, "xM", 1UL << 20);
MAYBE_MULTIPLY_SUFFIX(endptr, *out, "M", 1UL << 20);
MAYBE_MULTIPLY_SUFFIX(endptr, *out, "MB", 1000 * 1000UL);
MAYBE_MULTIPLY_SUFFIX(endptr, *out, "K", 1UL << 10);
MAYBE_MULTIPLY_SUFFIX(endptr, *out, "kB", 1000UL);
MAYBE_MULTIPLY_SUFFIX(endptr, *out, "b", 512UL);
MAYBE_MULTIPLY_SUFFIX(endptr, *out, "w", 2UL);
MAYBE_MULTIPLY_SUFFIX(endptr, *out, "c", 1UL);
done:
fprintf(stderr, "Couldn't parse size string: %s\n", s);
return -1;
}
typedef struct {
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];
bool pad;
} dd_options_t;
int parse_args(int argc, const char** argv, dd_options_t* options) {
while (argc > 1) {
const char* arg = argv[1];
if (prefix_match(&arg, "bs=")) {
size_t size;
if (parse_size(arg, &size)) {
return usage();
}
options->input_bs = size;
options->output_bs = size;
} else if (prefix_match(&arg, "count=")) {
if (parse_size(arg, &options->count)) {
return usage();
}
options->use_count = true;
} else if (prefix_match(&arg, "ibs=")) {
if (parse_size(arg, &options->input_bs)) {
return usage();
}
} else if (prefix_match(&arg, "obs=")) {
if (parse_size(arg, &options->output_bs)) {
return usage();
}
} else if (prefix_match(&arg, "seek=")) {
if (parse_size(arg, &options->output_seek)) {
return usage();
}
} else if (prefix_match(&arg, "skip=")) {
if (parse_size(arg, &options->input_skip)) {
return usage();
}
} else if (prefix_match(&arg, "if=")) {
strncpy(options->input, arg, PATH_MAX);
options->input[PATH_MAX - 1] = '\0';
} else if (prefix_match(&arg, "of=")) {
strncpy(options->output, arg, PATH_MAX);
options->output[PATH_MAX - 1] = '\0';
} else if (strcmp(arg, "conv=sync") == 0) {
options->pad = true;
} else {
return usage();
}
argc--;
argv++;
}
return 0;
}
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#define MAX(x, y) ((x) < (y) ? (y) : (x))
bool is_skip_block(const char* path, fuchsia_hardware_skipblock_PartitionInfo* partition_info) {
int fd = open(path, O_RDONLY);
if (fd < 0) {
return false;
}
zx_handle_t channel;
zx_status_t status = fdio_get_service_handle(fd, &channel);
if (status != ZX_OK) {
return false;
}
// |status| is used for the status of the whole FIDL request. We expect
// |status| to be ZX_OK if the channel connects to a skip-block driver.
// |op_status| refers to the status of the underlying read/write operation
// and will be ZX_OK only if the read/write succeeds. It is NOT set if
// the channel is not connected to a skip-block driver.
zx_status_t op_status;
status =
fuchsia_hardware_skipblock_SkipBlockGetPartitionInfo(channel, &op_status, partition_info);
close(fd);
return status == ZX_OK;
}
int main(int argc, const char** argv) {
dd_options_t options;
memset(&options, 0, sizeof(dd_options_t));
options.input_bs = 512;
options.output_bs = 512;
int r;
if ((r = parse_args(argc, argv, &options))) {
return r;
}
if (options.input_bs == 0 || options.output_bs == 0) {
fprintf(stderr, "block sizes must be greater than zero\n");
return -1;
}
options.input_skip *= options.input_bs;
options.output_seek *= options.output_bs;
size_t buf_size = MAX(options.output_bs, options.input_bs);
// Input and output fds
int in = -1;
int out = -1;
// Buffer to contain partially read data
char* buf = NULL;
// Return code
r = -1;
// Number of full records copied to/from target
size_t records_in = 0;
size_t records_out = 0;
// Size of remaining "partial" transfer from input / to output.
size_t record_in_partial = 0;
size_t record_out_partial = 0;
uint64_t sum_bytes_out = 0;
// Logic for skip-block devices.
fuchsia_hardware_skipblock_PartitionInfo partition_info = {};
bool in_is_skip_block = false;
bool out_is_skip_block = false;
zx_handle_t vmo = ZX_HANDLE_INVALID;
zx_handle_t channel_in = ZX_HANDLE_INVALID;
zx_handle_t channel_out = ZX_HANDLE_INVALID;
zx_time_t start = 0, stop = 0;
if (*options.input == '\0') {
in = STDIN_FILENO;
} else {
in = open(options.input, O_RDONLY);
if (in < 0) {
fprintf(stderr, "Couldn't open input file %s : %d\n", options.input, errno);
goto done;
}
}
if (*options.output == '\0') {
out = STDOUT_FILENO;
} else {
out = open(options.output, O_WRONLY | O_CREAT);
if (out < 0) {
fprintf(stderr, "Couldn't open output file %s : %d\n", options.output, errno);
goto done;
}
}
if (is_skip_block(options.input, &partition_info)) {
if (options.input_bs % partition_info.block_size_bytes) {
fprintf(stderr, "BS must be a multiple of %lu\n", partition_info.block_size_bytes);
return false;
}
in_is_skip_block = true;
fdio_get_service_handle(in, &channel_in);
}
if (is_skip_block(options.output, &partition_info)) {
if (options.output_bs % partition_info.block_size_bytes) {
fprintf(stderr, "BS must be a multiple of %lu\n", partition_info.block_size_bytes);
return false;
}
out_is_skip_block = true;
fdio_get_service_handle(out, &channel_out);
}
if (in_is_skip_block || out_is_skip_block) {
if (zx_vmo_create(buf_size, 0, &vmo) != ZX_OK) {
fprintf(stderr, "No memory\n");
goto done;
}
if (zx_vmar_map(zx_vmar_root_self(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, vmo, 0, buf_size,
(zx_vaddr_t*)&buf) != ZX_OK) {
fprintf(stderr, "Failed to map vmo\n");
goto done;
}
} else {
buf = malloc(buf_size);
if (buf == NULL) {
fprintf(stderr, "No memory\n");
goto done;
}
}
if (options.input_skip != 0 && !in_is_skip_block) {
// Try seeking first; if that doesn't work, try reading to an input buffer.
if (lseek(in, options.input_skip, SEEK_SET) != (off_t)options.input_skip) {
while (options.input_skip) {
if (read(in, buf, options.input_bs) != (ssize_t)options.input_bs) {
fprintf(stderr, "Couldn't read from input\n");
goto done;
}
options.input_skip -= options.input_bs;
}
}
}
if (options.output_seek != 0 && !out_is_skip_block) {
if (lseek(out, options.output_seek, SEEK_SET) != (off_t)options.output_seek) {
fprintf(stderr, "Failed to seek on output\n");
goto done;
}
}
if (MAX(options.input_bs, options.output_bs) % MIN(options.input_bs, options.output_bs) != 0) {
// TODO(smklein): Implement this case, rather than returning an error
fprintf(stderr, "Input and output block sizes must be multiples\n");
goto done;
}
bool terminating = false;
size_t rlen = 0;
start = zx_clock_get_monotonic();
while (true) {
if (options.use_count && !options.count) {
r = 0;
goto done;
}
// Read as much as we can (up to input_bs) into our target buffer
if (in_is_skip_block) {
zx_handle_t dup;
if (zx_handle_duplicate(vmo, ZX_RIGHT_SAME_RIGHTS, &dup) != ZX_OK) {
fprintf(stderr, "Cannot duplicate handle\n");
goto done;
}
const uint32_t block_count = (uint32_t)(options.input_bs / partition_info.block_size_bytes);
fuchsia_hardware_skipblock_ReadWriteOperation op = {
.vmo = dup,
.vmo_offset = 0,
.block = (uint32_t)((options.input_skip / partition_info.block_size_bytes) +
(records_in * block_count)),
.block_count = block_count,
};
zx_status_t status;
fuchsia_hardware_skipblock_SkipBlockRead(channel_in, &op, &status);
if (status != ZX_OK) {
fprintf(stderr, "Failed to read\n");
goto done;
}
records_in++;
rlen += options.input_bs;
} else {
ssize_t rout;
if ((rout = read(in, buf, options.input_bs)) != (ssize_t)options.input_bs) {
terminating = true;
}
if (rout == (ssize_t)options.input_bs) {
records_in++;
} else if (rout > 0) {
if (options.pad) {
memset(buf + rout, 0, options.input_bs - rout);
records_in++;
rout = options.input_bs;
} else {
record_in_partial = rout;
}
}
if (rout > 0) {
rlen += rout;
}
}
if (options.use_count) {
--options.count;
if (options.count == 0) {
terminating = true;
}
}
// If we can (or should, due to impending termination), dump the read
// buffer into the output file.
if (rlen >= options.output_bs || terminating) {
if (out_is_skip_block) {
zx_handle_t dup;
if (zx_handle_duplicate(vmo, ZX_RIGHT_SAME_RIGHTS, &dup) != ZX_OK) {
fprintf(stderr, "Cannot duplicate handle\n");
goto done;
}
const uint32_t block_count =
(uint32_t)(options.output_bs / partition_info.block_size_bytes);
fuchsia_hardware_skipblock_ReadWriteOperation op = {
.vmo = dup,
.vmo_offset = 0,
.block = (uint32_t)((options.output_seek / partition_info.block_size_bytes) +
(records_out * block_count)),
.block_count = block_count,
};
zx_status_t status;
bool bad_block_grown;
fuchsia_hardware_skipblock_SkipBlockWrite(channel_out, &op, &status, &bad_block_grown);
if (status != ZX_OK) {
fprintf(stderr, "Failed to write\n");
goto done;
}
records_out++;
} else {
size_t off = 0;
while (off != rlen) {
size_t wlen = MIN(options.output_bs, rlen - off);
if (write(out, buf + off, wlen) != (ssize_t)wlen) {
fprintf(stderr, "Couldn't write %zu bytes to output\n", wlen);
goto done;
}
if (wlen == options.output_bs) {
records_out++;
} else {
record_out_partial = wlen;
}
off += wlen;
}
}
rlen = 0;
}
if (terminating) {
r = 0;
goto done;
}
}
done:
stop = zx_clock_get_monotonic();
printf("%zu+%u records in\n", records_in, record_in_partial ? 1 : 0);
printf("%zu+%u records out\n", records_out, record_out_partial ? 1 : 0);
sum_bytes_out = records_out * options.output_bs + record_out_partial;
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);
}
if (buf) {
if (in_is_skip_block || out_is_skip_block) {
zx_vmar_unmap(zx_vmar_root_self(), (uintptr_t)*buf, buf_size);
} else {
free(buf);
}
}
if (vmo != ZX_HANDLE_INVALID) {
zx_handle_close(vmo);
}
if (in != -1) {
close(in);
}
if (out != -1) {
close(out);
}
return r;
}