blob: 1cd42b2eae8c377c78e3e4d9fbb26687761310c4 [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 <assert.h>
#include <fcntl.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>
#include <block-client/client.h>
#include <zircon/device/block.h>
#include <zircon/misc/xorshiftrand.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zircon/threads.h>
#define BLOCK_HEADER 0xdeadbeef
static uint64_t base_seed;
static size_t block_size;
static fifo_client_t* client;
static block_info_t info;
static int num_threads;
static uint32_t start_block;
static uint32_t block_count;
static int fd;
static mtx_t lock;
static uint32_t blocks_read;
static bool iochk_failure;
static void generate_block_data(int blk_idx, void* buffer, size_t len) {
// Block size should be a multiple of sizeof(uint64_t), but assert just to be safe
assert(len % sizeof(uint64_t) == 0);
rand64_t seed_gen = RAND63SEED(base_seed + blk_idx);
for (int i = 0; i < 10; i++) {
rand64(&seed_gen);
}
rand64_t data_gen = RAND63SEED(rand64(&seed_gen));
uint64_t* buf = buffer;
int idx = 0;
uint64_t data = BLOCK_HEADER | (((uint64_t) blk_idx) << 32);
while (idx < (int) (len / sizeof(uint64_t))) {
buf[idx] = data;
data = rand64(&data_gen);
idx++;
}
}
static int fill_range(txnid_t* txnid, zx_handle_t vmoid, uint32_t start,
uint32_t count, void* buf) {
zx_status_t st;
for (uint32_t blk_idx = start; blk_idx < count; blk_idx++) {
uint64_t len = (info.block_size * info.block_count) - (blk_idx * block_size);
if (len > block_size) {
len = block_size;
}
generate_block_data(blk_idx, buf, block_size);
block_fifo_request_t request = {
.txnid = *txnid,
.vmoid = vmoid,
.opcode = BLOCKIO_WRITE,
.length = len / info.block_size,
.vmo_offset = 0,
.dev_offset = (blk_idx * block_size) / info.block_size,
};
if ((st = block_fifo_txn(client, &request, 1)) != ZX_OK) {
fprintf(stderr, "error: write block_fifo_txn error %d\n", st);
return -1;
}
}
return 0;
}
static int check_block_data(int blk_idx, void* buffer, int len) {
rand64_t seed_gen = RAND63SEED(base_seed + blk_idx);
for (int i = 0; i < 10; i++) {
rand64(&seed_gen);
}
rand64_t data_gen = RAND63SEED(rand64(&seed_gen));
uint64_t* buf = buffer;
uint64_t expected = BLOCK_HEADER | (((uint64_t) blk_idx) << 32);
int idx = 0;
while (idx < (int) (len / sizeof(uint64_t))) {
if (buf[idx] != expected) {
fprintf(stderr, "error: inital read verification failed: "
"blk_idx=%d offset=%d expected=0x%016lx val=0x%016lx\n",
blk_idx, idx, expected, buf[idx]);
return -1;
}
idx++;
expected = rand64(&data_gen);
}
return 0;
}
static zx_status_t check_range(txnid_t* txnid, zx_handle_t vmoid, uint32_t start,
uint32_t count, void* buf) {
zx_status_t st;
for (uint32_t blk_idx = start; blk_idx < count; blk_idx++) {
uint64_t len = (info.block_size * info.block_count) - (blk_idx * block_size);
if (len > block_size) {
len = block_size;
}
block_fifo_request_t request = {
.txnid = *txnid,
.vmoid = vmoid,
.opcode = BLOCKIO_READ,
.length = len / info.block_size,
.vmo_offset = 0,
.dev_offset = (blk_idx * block_size) / info.block_size,
};
if ((st = block_fifo_txn(client, &request, 1)) != ZX_OK) {
fprintf(stderr, "error: read block_fifo_txn error %d\n", st);
return -1;
}
if (check_block_data(blk_idx, buf, len)) {
return -1;
}
}
return 0;
}
static int init_txn_resources(zx_handle_t* vmo, vmoid_t* vmoid, txnid_t* txnid, void** buf) {
if (zx_vmo_create(block_size, 0, vmo) != ZX_OK) {
fprintf(stderr, "error: out of memory\n");
return -1;
}
if (zx_vmar_map(zx_vmar_root_self(), 0, *vmo, 0, block_size,
ZX_VM_FLAG_PERM_READ | ZX_VM_FLAG_PERM_WRITE, (uintptr_t*) buf) != ZX_OK) {
fprintf(stderr, "error: failed to map vmo\n");
return -1;
}
zx_handle_t dup;
if (zx_handle_duplicate(*vmo, ZX_RIGHT_SAME_RIGHTS, &dup) != ZX_OK) {
fprintf(stderr, "error: cannot duplicate handle\n");
return -1;
}
size_t s;
if ((s = ioctl_block_attach_vmo(fd, &dup, vmoid) != sizeof(vmoid_t))) {
fprintf(stderr, "error: cannot attach vmo for init %lu\n", s);
return -1;
}
if (ioctl_block_alloc_txn(fd, txnid) != sizeof(txnid_t)) {
fprintf(stderr, "error: cannot allocate txn for init\n");
return -1;
}
return 0;
}
static void free_txn_resources(zx_handle_t* vmo, vmoid_t* vmoid, txnid_t* txnid, void** buf) {
ioctl_block_free_txn(fd, txnid);
zx_handle_close(*vmo);
}
static int init_device(void) {
zx_handle_t vmo;
void* buf;
vmoid_t vmoid;
txnid_t txnid;
if (init_txn_resources(&vmo, &vmoid, &txnid, &buf)) {
fprintf(stderr, "Failed to alloc resources to init device\n");
return -1;
}
zx_status_t st;
printf("writing test data to device...");
fflush(stdout);
if ((st = fill_range(&txnid, vmoid, start_block, block_count, buf)) != ZX_OK) {
fprintf(stderr, "failed to write test data\n");
return -1;
}
printf("done\n");
printf("verifying test data...");
fflush(stdout);
if (check_range(&txnid, vmoid, start_block, block_count, buf)) {
fprintf(stderr, "failed to verify test data\n");
return -1;
}
printf("done\n");
free_txn_resources(&vmo, &vmoid, &txnid, &buf);
return 0;
}
static void update_progress(uint32_t was_read) {
uint32_t total_work = ((int) (block_count * log(block_count))) * num_threads;
int old_progress = (int) (100 * blocks_read / total_work);
blocks_read += was_read;
int progress = (int) (100 * blocks_read / total_work);
if (old_progress != progress) {
int ticks = 40;
char str[ticks + 1];
memset(str, ' ', ticks);
memset(str, '=', ticks * progress / 100);
str[ticks] = '\0';
printf("\r[%s] %02d%%", str, progress);
fflush(stdout);
}
if (progress == 100) {
printf("\n");
}
}
static int do_work(void* arg) {
zx_handle_t vmo;
void* buf;
vmoid_t vmoid;
txnid_t txnid;
init_txn_resources(&vmo, &vmoid, &txnid, &buf);
rand32_t seed_gen = RAND32SEED(base_seed + (int) (uintptr_t) arg);
for (int i = 0; i < 20; i++) {
}
rand32_t work_gen = RAND32SEED(rand32(&seed_gen));
// The expected number of random pages we need to hit all of them is
// approx n*log(n) (the coupon collector problem)
uint32_t blocks_left = block_count * log(block_count);
while (blocks_left > 0 && !iochk_failure) {
uint32_t to_read = (rand32(&work_gen) % blocks_left) + 1;
uint32_t work_offset = rand32(&work_gen) % block_count;
if (work_offset + to_read > block_count) {
to_read = block_count - work_offset;
}
int res;
if (rand32(&work_gen) % 2) {
res = check_range(&txnid, vmoid, start_block + work_offset, to_read, buf);
} else {
res = fill_range(&txnid, vmoid, start_block + work_offset, to_read, buf);
}
mtx_lock(&lock);
if (res) {
iochk_failure = true;
} else if (!iochk_failure) {
update_progress(to_read);
blocks_left -= to_read;
}
mtx_unlock(&lock);
}
free_txn_resources(&vmo, &vmoid, &txnid, &buf);
return 0;
}
static uint64_t number(const char* str) {
char* end;
uint64_t n = strtoull(str, &end, 10);
uint64_t m = 1;
switch (*end) {
case 'G':
case 'g':
m = 1024*1024*1024;
break;
case 'M':
case 'm':
m = 1024*1024;
break;
case 'K':
case 'k':
m = 1024;
break;
}
return m * n;
}
static int usage(void) {
fprintf(stderr, "usage: iochk [OPTIONS] <device>\n\n"
" -bs block_size - number of bytes to treat as a unit (default=device block size)\n"
" -t thread# - the number of threads to run (default=1)\n"
" -c block_count - number of blocks to read (default=the whole device)\n"
" -o offset - block-size offset to start reading from (default=0)\n"
" -s seed - the seed to use for pseudorandom testing\n"
" --live-dangerously - skip confirmation prompt\n");
return -1;
}
int iochk(int argc, char** argv) {
const char* device = argv[argc - 1];
fd = open(device, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "error: cannot open '%s'\n", device);
return usage();
}
ioctl_block_get_info(fd, &info);
printf("opened %s - block_size=%u, block_count=%lu\n",
device, info.block_size, info.block_count);
int seed_set = 0;
num_threads = 1;
int i = 1;
bool confirmed = false;
while (i < argc - 1) {
if (strcmp(argv[i], "-t") == 0) {
num_threads = atoi(argv[i + 1]);
i += 2;
} else if (strcmp(argv[i], "-c") == 0) {
block_count = atoi(argv[i + 1]);
i += 2;
} else if (strcmp(argv[i], "-o") == 0) {
start_block = atoi(argv[i + 1]);
i += 2;
} else if (strcmp(argv[i], "-bs") == 0) {
block_size = number(argv[i + 1]);
i += 2;
} else if (strcmp(argv[i], "-s") == 0) {
base_seed = atoll(argv[i + 1]);
seed_set = 1;
i += 2;
} else if (strcmp(argv[i], "--live-dangerously") == 0) {
confirmed = true;
i++;
} else if (strcmp(argv[i], "-h") == 0 ||
strcmp(argv[i], "--help") == 0) {
return usage();
} else {
fprintf(stderr, "Invalid arg %s\n", argv[i]);
return usage();
}
}
if (!confirmed) {
const char* warning = "\033[0;31mWARNING\033[0m";
printf("%s: iochk is a destructive operation.\n", warning);
printf("%s: All data on %s in the given range will be overwritten.\n", warning, device);
printf("%s: Type 'y' to continue, 'n' or ESC to cancel:\n", warning);
for (;;) {
char c;
int r = read(STDIN_FILENO, &c, 1);
if (r < 0) {
fprintf(stderr, "Error reading from stdin\n");
return r;
}
if (c == 'y' || c == 'Y') {
break;
} else if (c == 'n' || c == 'N' || c == 27) {
return 0;
}
}
}
if (!seed_set) {
base_seed = zx_clock_get(ZX_CLOCK_MONOTONIC);
}
printf("seed is %ld\n", base_seed);
if (block_size == 0) {
block_size = info.block_size;
} else if (block_size % info.block_size != 0) {
fprintf(stderr, "error: block-size is not a multiple of device block size\n");
return -1;
}
uint32_t dev_blocks_per_block = block_size / info.block_size;
if (dev_blocks_per_block * start_block >= info.block_count) {
fprintf(stderr, "error: offset past end of device\n");
return -1;
}
if (block_count == 0) {
block_count = (info.block_count + dev_blocks_per_block - 1) / dev_blocks_per_block;
} else if (dev_blocks_per_block * (block_count + start_block)
>= dev_blocks_per_block + info.block_count) {
// Don't allow blocks to start past the end of the device
fprintf(stderr, "error: block_count+offset too large\n");
return -1;
}
if (info.max_transfer_size < block_size) {
fprintf(stderr, "error: block-size is larger than max transfer size (%d)\n",
info.max_transfer_size);
return -1;
}
zx_handle_t fifo;
if (ioctl_block_get_fifos(fd, &fifo) != sizeof(fifo)) {
fprintf(stderr, "error: cannot get fifo for device\n");
return -1;
}
if (block_fifo_create_client(fifo, &client) != ZX_OK) {
fprintf(stderr, "error: cannot create block client for device\n");
return -1;
}
if (init_device()) {
fprintf(stderr, "error: device initialization failed\n");
return -1;
}
printf("starting worker threads...\n");
mtx_init(&lock, mtx_plain);
thrd_t thrds[num_threads];
for (int i = 0; i < num_threads; i++) {
if (thrd_create(thrds + i, do_work, (void*) (uintptr_t) i) != thrd_success) {
fprintf(stderr, "error: thread creation failed\n");
return -1;
}
}
for (int i = 0; i < num_threads; i++) {
thrd_join(thrds[i], NULL);
}
if (!iochk_failure) {
printf("re-verifying device...");
fflush(stdout);
zx_handle_t vmo;
void* buf;
vmoid_t vmoid;
txnid_t txnid;
init_txn_resources(&vmo, &vmoid, &txnid, &buf);
if (check_range(&txnid, vmoid, start_block, block_count, buf)) {
fprintf(stderr, "failed to re-verify test data\n");
iochk_failure = true;
} else {
printf("done\n");
}
free_txn_resources(&vmo, &vmoid, &txnid, &buf);
}
if (!iochk_failure) {
printf("iochk completed successfully\n");
return 0;
} else {
printf("iochk failed (seed was %ld)\n", base_seed);
return -1;
}
}
int main(int argc, char** argv) {
if (argc < 2) {
return usage();
}
fd = -1;
client = NULL;
int res = iochk(argc, argv);
if (client) {
block_fifo_release_client(client);
}
if (fd != -1) {
close(fd);
}
return res;
}