qemu/qemu-io.c
Christoph Hellwig 5c6c3a6c54 raw-posix: add Linux native AIO support
Now that do have a nicer interface to work against we can add Linux native
AIO support.  It's an extremly thing layer just setting up an iocb for
the io_submit system call in the submission path, and registering an
eventfd with the qemu poll handler to do complete the iocbs directly
from there.

This started out based on Anthony's earlier AIO patch, but after
estimated 42,000 rewrites and just as many build system changes
there's not much left of it.

To enable native kernel aio use the aio=native sub-command on the
drive command line.  I have also added an option to qemu-io to
test the aio support without needing a guest.

Signed-off-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
2009-08-27 20:30:22 -05:00

1522 lines
31 KiB
C

/*
* Command line utility to exercise the QEMU I/O path.
*
* Copyright (C) 2009 Red Hat, Inc.
* Copyright (c) 2003-2005 Silicon Graphics, Inc.
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include <sys/types.h>
#include <stdarg.h>
#include <stdio.h>
#include <getopt.h>
#include "qemu-common.h"
#include "block_int.h"
#include "cmd.h"
#define VERSION "0.0.1"
#define CMD_NOFILE_OK 0x01
char *progname;
static BlockDriverState *bs;
static int misalign;
/*
* Parse the pattern argument to various sub-commands.
*
* Because the pattern is used as an argument to memset it must evaluate
* to an unsigned integer that fits into a single byte.
*/
static int parse_pattern(const char *arg)
{
char *endptr = NULL;
long pattern;
pattern = strtol(arg, &endptr, 0);
if (pattern < 0 || pattern > UCHAR_MAX || *endptr != '\0') {
printf("%s is not a valid pattern byte\n", arg);
return -1;
}
return pattern;
}
/*
* Memory allocation helpers.
*
* Make sure memory is aligned by default, or purposefully misaligned if
* that is specified on the command line.
*/
#define MISALIGN_OFFSET 16
static void *qemu_io_alloc(size_t len, int pattern)
{
void *buf;
if (misalign)
len += MISALIGN_OFFSET;
buf = qemu_memalign(512, len);
memset(buf, pattern, len);
if (misalign)
buf += MISALIGN_OFFSET;
return buf;
}
static void qemu_io_free(void *p)
{
if (misalign)
p -= MISALIGN_OFFSET;
qemu_vfree(p);
}
static void
dump_buffer(const void *buffer, int64_t offset, int len)
{
int i, j;
const uint8_t *p;
for (i = 0, p = buffer; i < len; i += 16) {
const uint8_t *s = p;
printf("%08llx: ", (unsigned long long)offset + i);
for (j = 0; j < 16 && i + j < len; j++, p++)
printf("%02x ", *p);
printf(" ");
for (j = 0; j < 16 && i + j < len; j++, s++) {
if (isalnum(*s))
printf("%c", *s);
else
printf(".");
}
printf("\n");
}
}
static void
print_report(const char *op, struct timeval *t, int64_t offset,
int count, int total, int cnt, int Cflag)
{
char s1[64], s2[64], ts[64];
timestr(t, ts, sizeof(ts), Cflag ? VERBOSE_FIXED_TIME : 0);
if (!Cflag) {
cvtstr((double)total, s1, sizeof(s1));
cvtstr(tdiv((double)total, *t), s2, sizeof(s2));
printf("%s %d/%d bytes at offset %lld\n",
op, total, count, (long long)offset);
printf("%s, %d ops; %s (%s/sec and %.4f ops/sec)\n",
s1, cnt, ts, s2, tdiv((double)cnt, *t));
} else {/* bytes,ops,time,bytes/sec,ops/sec */
printf("%d,%d,%s,%.3f,%.3f\n",
total, cnt, ts,
tdiv((double)total, *t),
tdiv((double)cnt, *t));
}
}
/*
* Parse multiple length statements for vectored I/O, and construct an I/O
* vector matching it.
*/
static void *
create_iovec(QEMUIOVector *qiov, char **argv, int nr_iov, int pattern)
{
size_t *sizes = calloc(nr_iov, sizeof(size_t));
size_t count = 0;
void *buf, *p;
int i;
for (i = 0; i < nr_iov; i++) {
char *arg = argv[i];
long long len;
len = cvtnum(arg);
if (len < 0) {
printf("non-numeric length argument -- %s\n", arg);
return NULL;
}
/* should be SIZE_T_MAX, but that doesn't exist */
if (len > UINT_MAX) {
printf("too large length argument -- %s\n", arg);
return NULL;
}
if (len & 0x1ff) {
printf("length argument %lld is not sector aligned\n",
len);
return NULL;
}
sizes[i] = len;
count += len;
}
qemu_iovec_init(qiov, nr_iov);
buf = p = qemu_io_alloc(count, pattern);
for (i = 0; i < nr_iov; i++) {
qemu_iovec_add(qiov, p, sizes[i]);
p += sizes[i];
}
free(sizes);
return buf;
}
static int do_read(char *buf, int64_t offset, int count, int *total)
{
int ret;
ret = bdrv_read(bs, offset >> 9, (uint8_t *)buf, count >> 9);
if (ret < 0)
return ret;
*total = count;
return 1;
}
static int do_write(char *buf, int64_t offset, int count, int *total)
{
int ret;
ret = bdrv_write(bs, offset >> 9, (uint8_t *)buf, count >> 9);
if (ret < 0)
return ret;
*total = count;
return 1;
}
static int do_pread(char *buf, int64_t offset, int count, int *total)
{
*total = bdrv_pread(bs, offset, (uint8_t *)buf, count);
if (*total < 0)
return *total;
return 1;
}
static int do_pwrite(char *buf, int64_t offset, int count, int *total)
{
*total = bdrv_pwrite(bs, offset, (uint8_t *)buf, count);
if (*total < 0)
return *total;
return 1;
}
static int do_load_vmstate(char *buf, int64_t offset, int count, int *total)
{
*total = bdrv_load_vmstate(bs, (uint8_t *)buf, offset, count);
if (*total < 0)
return *total;
return 1;
}
static int do_save_vmstate(char *buf, int64_t offset, int count, int *total)
{
*total = bdrv_save_vmstate(bs, (uint8_t *)buf, offset, count);
if (*total < 0)
return *total;
return 1;
}
#define NOT_DONE 0x7fffffff
static void aio_rw_done(void *opaque, int ret)
{
*(int *)opaque = ret;
}
static int do_aio_readv(QEMUIOVector *qiov, int64_t offset, int *total)
{
BlockDriverAIOCB *acb;
int async_ret = NOT_DONE;
acb = bdrv_aio_readv(bs, offset >> 9, qiov, qiov->size >> 9,
aio_rw_done, &async_ret);
if (!acb)
return -EIO;
while (async_ret == NOT_DONE)
qemu_aio_wait();
*total = qiov->size;
return async_ret < 0 ? async_ret : 1;
}
static int do_aio_writev(QEMUIOVector *qiov, int64_t offset, int *total)
{
BlockDriverAIOCB *acb;
int async_ret = NOT_DONE;
acb = bdrv_aio_writev(bs, offset >> 9, qiov, qiov->size >> 9,
aio_rw_done, &async_ret);
if (!acb)
return -EIO;
while (async_ret == NOT_DONE)
qemu_aio_wait();
*total = qiov->size;
return async_ret < 0 ? async_ret : 1;
}
static const cmdinfo_t read_cmd;
static void
read_help(void)
{
printf(
"\n"
" reads a range of bytes from the given offset\n"
"\n"
" Example:\n"
" 'read -v 512 1k' - dumps 1 kilobyte read from 512 bytes into the file\n"
"\n"
" Reads a segment of the currently open file, optionally dumping it to the\n"
" standard output stream (with -v option) for subsequent inspection.\n"
" -b, -- read from the VM state rather than the virtual disk\n"
" -C, -- report statistics in a machine parsable format\n"
" -l, -- length for pattern verification (only with -P)\n"
" -p, -- use bdrv_pread to read the file\n"
" -P, -- use a pattern to verify read data\n"
" -q, -- quite mode, do not show I/O statistics\n"
" -s, -- start offset for pattern verification (only with -P)\n"
" -v, -- dump buffer to standard output\n"
"\n");
}
static int
read_f(int argc, char **argv)
{
struct timeval t1, t2;
int Cflag = 0, pflag = 0, qflag = 0, vflag = 0;
int Pflag = 0, sflag = 0, lflag = 0, bflag = 0;
int c, cnt;
char *buf;
int64_t offset;
int count;
/* Some compilers get confused and warn if this is not initialized. */
int total = 0;
int pattern = 0, pattern_offset = 0, pattern_count = 0;
while ((c = getopt(argc, argv, "bCl:pP:qs:v")) != EOF) {
switch (c) {
case 'b':
bflag = 1;
break;
case 'C':
Cflag = 1;
break;
case 'l':
lflag = 1;
pattern_count = cvtnum(optarg);
if (pattern_count < 0) {
printf("non-numeric length argument -- %s\n", optarg);
return 0;
}
break;
case 'p':
pflag = 1;
break;
case 'P':
Pflag = 1;
pattern = parse_pattern(optarg);
if (pattern < 0)
return 0;
break;
case 'q':
qflag = 1;
break;
case 's':
sflag = 1;
pattern_offset = cvtnum(optarg);
if (pattern_offset < 0) {
printf("non-numeric length argument -- %s\n", optarg);
return 0;
}
break;
case 'v':
vflag = 1;
break;
default:
return command_usage(&read_cmd);
}
}
if (optind != argc - 2)
return command_usage(&read_cmd);
if (bflag && pflag) {
printf("-b and -p cannot be specified at the same time\n");
return 0;
}
offset = cvtnum(argv[optind]);
if (offset < 0) {
printf("non-numeric length argument -- %s\n", argv[optind]);
return 0;
}
optind++;
count = cvtnum(argv[optind]);
if (count < 0) {
printf("non-numeric length argument -- %s\n", argv[optind]);
return 0;
}
if (!Pflag && (lflag || sflag)) {
return command_usage(&read_cmd);
}
if (!lflag) {
pattern_count = count - pattern_offset;
}
if ((pattern_count < 0) || (pattern_count + pattern_offset > count)) {
printf("pattern verfication range exceeds end of read data\n");
return 0;
}
if (!pflag)
if (offset & 0x1ff) {
printf("offset %lld is not sector aligned\n",
(long long)offset);
return 0;
if (count & 0x1ff) {
printf("count %d is not sector aligned\n",
count);
return 0;
}
}
buf = qemu_io_alloc(count, 0xab);
gettimeofday(&t1, NULL);
if (pflag)
cnt = do_pread(buf, offset, count, &total);
else if (bflag)
cnt = do_load_vmstate(buf, offset, count, &total);
else
cnt = do_read(buf, offset, count, &total);
gettimeofday(&t2, NULL);
if (cnt < 0) {
printf("read failed: %s\n", strerror(-cnt));
goto out;
}
if (Pflag) {
void* cmp_buf = malloc(pattern_count);
memset(cmp_buf, pattern, pattern_count);
if (memcmp(buf + pattern_offset, cmp_buf, pattern_count)) {
printf("Pattern verification failed at offset %lld, "
"%d bytes\n",
(long long) offset + pattern_offset, pattern_count);
}
free(cmp_buf);
}
if (qflag)
goto out;
if (vflag)
dump_buffer(buf, offset, count);
/* Finally, report back -- -C gives a parsable format */
t2 = tsub(t2, t1);
print_report("read", &t2, offset, count, total, cnt, Cflag);
out:
qemu_io_free(buf);
return 0;
}
static const cmdinfo_t read_cmd = {
.name = "read",
.altname = "r",
.cfunc = read_f,
.argmin = 2,
.argmax = -1,
.args = "[-abCpqv] [-P pattern [-s off] [-l len]] off len",
.oneline = "reads a number of bytes at a specified offset",
.help = read_help,
};
static const cmdinfo_t readv_cmd;
static void
readv_help(void)
{
printf(
"\n"
" reads a range of bytes from the given offset into multiple buffers\n"
"\n"
" Example:\n"
" 'readv -v 512 1k 1k ' - dumps 2 kilobytes read from 512 bytes into the file\n"
"\n"
" Reads a segment of the currently open file, optionally dumping it to the\n"
" standard output stream (with -v option) for subsequent inspection.\n"
" Uses multiple iovec buffers if more than one byte range is specified.\n"
" -C, -- report statistics in a machine parsable format\n"
" -P, -- use a pattern to verify read data\n"
" -v, -- dump buffer to standard output\n"
" -q, -- quite mode, do not show I/O statistics\n"
"\n");
}
static int
readv_f(int argc, char **argv)
{
struct timeval t1, t2;
int Cflag = 0, qflag = 0, vflag = 0;
int c, cnt;
char *buf;
int64_t offset;
int total;
int nr_iov;
QEMUIOVector qiov;
int pattern = 0;
int Pflag = 0;
while ((c = getopt(argc, argv, "CP:qv")) != EOF) {
switch (c) {
case 'C':
Cflag = 1;
break;
case 'P':
Pflag = 1;
pattern = parse_pattern(optarg);
if (pattern < 0)
return 0;
break;
case 'q':
qflag = 1;
break;
case 'v':
vflag = 1;
break;
default:
return command_usage(&readv_cmd);
}
}
if (optind > argc - 2)
return command_usage(&readv_cmd);
offset = cvtnum(argv[optind]);
if (offset < 0) {
printf("non-numeric length argument -- %s\n", argv[optind]);
return 0;
}
optind++;
if (offset & 0x1ff) {
printf("offset %lld is not sector aligned\n",
(long long)offset);
return 0;
}
nr_iov = argc - optind;
buf = create_iovec(&qiov, &argv[optind], nr_iov, 0xab);
gettimeofday(&t1, NULL);
cnt = do_aio_readv(&qiov, offset, &total);
gettimeofday(&t2, NULL);
if (cnt < 0) {
printf("readv failed: %s\n", strerror(-cnt));
goto out;
}
if (Pflag) {
void* cmp_buf = malloc(qiov.size);
memset(cmp_buf, pattern, qiov.size);
if (memcmp(buf, cmp_buf, qiov.size)) {
printf("Pattern verification failed at offset %lld, "
"%zd bytes\n",
(long long) offset, qiov.size);
}
free(cmp_buf);
}
if (qflag)
goto out;
if (vflag)
dump_buffer(buf, offset, qiov.size);
/* Finally, report back -- -C gives a parsable format */
t2 = tsub(t2, t1);
print_report("read", &t2, offset, qiov.size, total, cnt, Cflag);
out:
qemu_io_free(buf);
return 0;
}
static const cmdinfo_t readv_cmd = {
.name = "readv",
.cfunc = readv_f,
.argmin = 2,
.argmax = -1,
.args = "[-Cqv] [-P pattern ] off len [len..]",
.oneline = "reads a number of bytes at a specified offset",
.help = readv_help,
};
static const cmdinfo_t write_cmd;
static void
write_help(void)
{
printf(
"\n"
" writes a range of bytes from the given offset\n"
"\n"
" Example:\n"
" 'write 512 1k' - writes 1 kilobyte at 512 bytes into the open file\n"
"\n"
" Writes into a segment of the currently open file, using a buffer\n"
" filled with a set pattern (0xcdcdcdcd).\n"
" -b, -- write to the VM state rather than the virtual disk\n"
" -p, -- use bdrv_pwrite to write the file\n"
" -P, -- use different pattern to fill file\n"
" -C, -- report statistics in a machine parsable format\n"
" -q, -- quite mode, do not show I/O statistics\n"
"\n");
}
static int
write_f(int argc, char **argv)
{
struct timeval t1, t2;
int Cflag = 0, pflag = 0, qflag = 0, bflag = 0;
int c, cnt;
char *buf;
int64_t offset;
int count;
/* Some compilers get confused and warn if this is not initialized. */
int total = 0;
int pattern = 0xcd;
while ((c = getopt(argc, argv, "bCpP:q")) != EOF) {
switch (c) {
case 'b':
bflag = 1;
break;
case 'C':
Cflag = 1;
break;
case 'p':
pflag = 1;
break;
case 'P':
pattern = parse_pattern(optarg);
if (pattern < 0)
return 0;
break;
case 'q':
qflag = 1;
break;
default:
return command_usage(&write_cmd);
}
}
if (optind != argc - 2)
return command_usage(&write_cmd);
if (bflag && pflag) {
printf("-b and -p cannot be specified at the same time\n");
return 0;
}
offset = cvtnum(argv[optind]);
if (offset < 0) {
printf("non-numeric length argument -- %s\n", argv[optind]);
return 0;
}
optind++;
count = cvtnum(argv[optind]);
if (count < 0) {
printf("non-numeric length argument -- %s\n", argv[optind]);
return 0;
}
if (!pflag) {
if (offset & 0x1ff) {
printf("offset %lld is not sector aligned\n",
(long long)offset);
return 0;
}
if (count & 0x1ff) {
printf("count %d is not sector aligned\n",
count);
return 0;
}
}
buf = qemu_io_alloc(count, pattern);
gettimeofday(&t1, NULL);
if (pflag)
cnt = do_pwrite(buf, offset, count, &total);
else if (bflag)
cnt = do_save_vmstate(buf, offset, count, &total);
else
cnt = do_write(buf, offset, count, &total);
gettimeofday(&t2, NULL);
if (cnt < 0) {
printf("write failed: %s\n", strerror(-cnt));
goto out;
}
if (qflag)
goto out;
/* Finally, report back -- -C gives a parsable format */
t2 = tsub(t2, t1);
print_report("wrote", &t2, offset, count, total, cnt, Cflag);
out:
qemu_io_free(buf);
return 0;
}
static const cmdinfo_t write_cmd = {
.name = "write",
.altname = "w",
.cfunc = write_f,
.argmin = 2,
.argmax = -1,
.args = "[-abCpq] [-P pattern ] off len",
.oneline = "writes a number of bytes at a specified offset",
.help = write_help,
};
static const cmdinfo_t writev_cmd;
static void
writev_help(void)
{
printf(
"\n"
" writes a range of bytes from the given offset source from multiple buffers\n"
"\n"
" Example:\n"
" 'write 512 1k 1k' - writes 2 kilobytes at 512 bytes into the open file\n"
"\n"
" Writes into a segment of the currently open file, using a buffer\n"
" filled with a set pattern (0xcdcdcdcd).\n"
" -P, -- use different pattern to fill file\n"
" -C, -- report statistics in a machine parsable format\n"
" -q, -- quite mode, do not show I/O statistics\n"
"\n");
}
static int
writev_f(int argc, char **argv)
{
struct timeval t1, t2;
int Cflag = 0, qflag = 0;
int c, cnt;
char *buf;
int64_t offset;
int total;
int nr_iov;
int pattern = 0xcd;
QEMUIOVector qiov;
while ((c = getopt(argc, argv, "CqP:")) != EOF) {
switch (c) {
case 'C':
Cflag = 1;
break;
case 'q':
qflag = 1;
break;
case 'P':
pattern = parse_pattern(optarg);
if (pattern < 0)
return 0;
break;
default:
return command_usage(&writev_cmd);
}
}
if (optind > argc - 2)
return command_usage(&writev_cmd);
offset = cvtnum(argv[optind]);
if (offset < 0) {
printf("non-numeric length argument -- %s\n", argv[optind]);
return 0;
}
optind++;
if (offset & 0x1ff) {
printf("offset %lld is not sector aligned\n",
(long long)offset);
return 0;
}
nr_iov = argc - optind;
buf = create_iovec(&qiov, &argv[optind], nr_iov, pattern);
gettimeofday(&t1, NULL);
cnt = do_aio_writev(&qiov, offset, &total);
gettimeofday(&t2, NULL);
if (cnt < 0) {
printf("writev failed: %s\n", strerror(-cnt));
goto out;
}
if (qflag)
goto out;
/* Finally, report back -- -C gives a parsable format */
t2 = tsub(t2, t1);
print_report("wrote", &t2, offset, qiov.size, total, cnt, Cflag);
out:
qemu_io_free(buf);
return 0;
}
static const cmdinfo_t writev_cmd = {
.name = "writev",
.cfunc = writev_f,
.argmin = 2,
.argmax = -1,
.args = "[-Cq] [-P pattern ] off len [len..]",
.oneline = "writes a number of bytes at a specified offset",
.help = writev_help,
};
struct aio_ctx {
QEMUIOVector qiov;
int64_t offset;
char *buf;
int qflag;
int vflag;
int Cflag;
int Pflag;
int pattern;
struct timeval t1;
};
static void
aio_write_done(void *opaque, int ret)
{
struct aio_ctx *ctx = opaque;
struct timeval t2;
gettimeofday(&t2, NULL);
if (ret < 0) {
printf("aio_write failed: %s\n", strerror(-ret));
goto out;
}
if (ctx->qflag) {
goto out;
}
/* Finally, report back -- -C gives a parsable format */
t2 = tsub(t2, ctx->t1);
print_report("wrote", &t2, ctx->offset, ctx->qiov.size,
ctx->qiov.size, 1, ctx->Cflag);
out:
qemu_io_free(ctx->buf);
free(ctx);
}
static const cmdinfo_t aio_read_cmd;
static void
aio_read_done(void *opaque, int ret)
{
struct aio_ctx *ctx = opaque;
struct timeval t2;
gettimeofday(&t2, NULL);
if (ret < 0) {
printf("readv failed: %s\n", strerror(-ret));
goto out;
}
if (ctx->Pflag) {
void *cmp_buf = malloc(ctx->qiov.size);
memset(cmp_buf, ctx->pattern, ctx->qiov.size);
if (memcmp(ctx->buf, cmp_buf, ctx->qiov.size)) {
printf("Pattern verification failed at offset %lld, "
"%zd bytes\n",
(long long) ctx->offset, ctx->qiov.size);
}
free(cmp_buf);
}
if (ctx->qflag) {
goto out;
}
if (ctx->vflag) {
dump_buffer(ctx->buf, ctx->offset, ctx->qiov.size);
}
/* Finally, report back -- -C gives a parsable format */
t2 = tsub(t2, ctx->t1);
print_report("read", &t2, ctx->offset, ctx->qiov.size,
ctx->qiov.size, 1, ctx->Cflag);
out:
qemu_io_free(ctx->buf);
free(ctx);
}
static void
aio_read_help(void)
{
printf(
"\n"
" asynchronously reads a range of bytes from the given offset\n"
"\n"
" Example:\n"
" 'aio_read -v 512 1k 1k ' - dumps 2 kilobytes read from 512 bytes into the file\n"
"\n"
" Reads a segment of the currently open file, optionally dumping it to the\n"
" standard output stream (with -v option) for subsequent inspection.\n"
" The read is performed asynchronously and should the aio_flush command \n"
" should be used to ensure all outstanding aio requests have been completed\n"
" -C, -- report statistics in a machine parsable format\n"
" -P, -- use a pattern to verify read data\n"
" -v, -- dump buffer to standard output\n"
" -q, -- quite mode, do not show I/O statistics\n"
"\n");
}
static int
aio_read_f(int argc, char **argv)
{
int nr_iov, c;
struct aio_ctx *ctx = calloc(1, sizeof(struct aio_ctx));
BlockDriverAIOCB *acb;
while ((c = getopt(argc, argv, "CP:qv")) != EOF) {
switch (c) {
case 'C':
ctx->Cflag = 1;
break;
case 'P':
ctx->Pflag = 1;
ctx->pattern = parse_pattern(optarg);
if (ctx->pattern < 0)
return 0;
break;
case 'q':
ctx->qflag = 1;
break;
case 'v':
ctx->vflag = 1;
break;
default:
free(ctx);
return command_usage(&aio_read_cmd);
}
}
if (optind > argc - 2) {
free(ctx);
return command_usage(&aio_read_cmd);
}
ctx->offset = cvtnum(argv[optind]);
if (ctx->offset < 0) {
printf("non-numeric length argument -- %s\n", argv[optind]);
free(ctx);
return 0;
}
optind++;
if (ctx->offset & 0x1ff) {
printf("offset %lld is not sector aligned\n",
(long long)ctx->offset);
free(ctx);
return 0;
}
nr_iov = argc - optind;
ctx->buf = create_iovec(&ctx->qiov, &argv[optind], nr_iov, 0xab);
gettimeofday(&ctx->t1, NULL);
acb = bdrv_aio_readv(bs, ctx->offset >> 9, &ctx->qiov,
ctx->qiov.size >> 9, aio_read_done, ctx);
if (!acb) {
free(ctx->buf);
free(ctx);
return -EIO;
}
return 0;
}
static const cmdinfo_t aio_read_cmd = {
.name = "aio_read",
.cfunc = aio_read_f,
.argmin = 2,
.argmax = -1,
.args = "[-Cqv] [-P pattern ] off len [len..]",
.oneline = "asynchronously reads a number of bytes",
.help = aio_read_help,
};
static const cmdinfo_t aio_write_cmd;
static void
aio_write_help(void)
{
printf(
"\n"
" asynchronously writes a range of bytes from the given offset source \n"
" from multiple buffers\n"
"\n"
" Example:\n"
" 'aio_write 512 1k 1k' - writes 2 kilobytes at 512 bytes into the open file\n"
"\n"
" Writes into a segment of the currently open file, using a buffer\n"
" filled with a set pattern (0xcdcdcdcd).\n"
" The write is performed asynchronously and should the aio_flush command \n"
" should be used to ensure all outstanding aio requests have been completed\n"
" -P, -- use different pattern to fill file\n"
" -C, -- report statistics in a machine parsable format\n"
" -q, -- quite mode, do not show I/O statistics\n"
"\n");
}
static int
aio_write_f(int argc, char **argv)
{
int nr_iov, c;
int pattern = 0xcd;
struct aio_ctx *ctx = calloc(1, sizeof(struct aio_ctx));
BlockDriverAIOCB *acb;
while ((c = getopt(argc, argv, "CqP:")) != EOF) {
switch (c) {
case 'C':
ctx->Cflag = 1;
break;
case 'q':
ctx->qflag = 1;
break;
case 'P':
pattern = parse_pattern(optarg);
if (pattern < 0)
return 0;
break;
default:
free(ctx);
return command_usage(&aio_write_cmd);
}
}
if (optind > argc - 2) {
free(ctx);
return command_usage(&aio_write_cmd);
}
ctx->offset = cvtnum(argv[optind]);
if (ctx->offset < 0) {
printf("non-numeric length argument -- %s\n", argv[optind]);
free(ctx);
return 0;
}
optind++;
if (ctx->offset & 0x1ff) {
printf("offset %lld is not sector aligned\n",
(long long)ctx->offset);
free(ctx);
return 0;
}
nr_iov = argc - optind;
ctx->buf = create_iovec(&ctx->qiov, &argv[optind], nr_iov, pattern);
gettimeofday(&ctx->t1, NULL);
acb = bdrv_aio_writev(bs, ctx->offset >> 9, &ctx->qiov,
ctx->qiov.size >> 9, aio_write_done, ctx);
if (!acb) {
free(ctx->buf);
free(ctx);
return -EIO;
}
return 0;
}
static const cmdinfo_t aio_write_cmd = {
.name = "aio_write",
.cfunc = aio_write_f,
.argmin = 2,
.argmax = -1,
.args = "[-Cq] [-P pattern ] off len [len..]",
.oneline = "asynchronously writes a number of bytes",
.help = aio_write_help,
};
static int
aio_flush_f(int argc, char **argv)
{
qemu_aio_flush();
return 0;
}
static const cmdinfo_t aio_flush_cmd = {
.name = "aio_flush",
.cfunc = aio_flush_f,
.oneline = "completes all outstanding aio requets"
};
static int
flush_f(int argc, char **argv)
{
bdrv_flush(bs);
return 0;
}
static const cmdinfo_t flush_cmd = {
.name = "flush",
.altname = "f",
.cfunc = flush_f,
.oneline = "flush all in-core file state to disk",
};
static int
truncate_f(int argc, char **argv)
{
int64_t offset;
int ret;
offset = cvtnum(argv[1]);
if (offset < 0) {
printf("non-numeric truncate argument -- %s\n", argv[1]);
return 0;
}
ret = bdrv_truncate(bs, offset);
if (ret < 0) {
printf("truncate: %s", strerror(ret));
return 0;
}
return 0;
}
static const cmdinfo_t truncate_cmd = {
.name = "truncate",
.altname = "t",
.cfunc = truncate_f,
.argmin = 1,
.argmax = 1,
.args = "off",
.oneline = "truncates the current file at the given offset",
};
static int
length_f(int argc, char **argv)
{
int64_t size;
char s1[64];
size = bdrv_getlength(bs);
if (size < 0) {
printf("getlength: %s", strerror(size));
return 0;
}
cvtstr(size, s1, sizeof(s1));
printf("%s\n", s1);
return 0;
}
static const cmdinfo_t length_cmd = {
.name = "length",
.altname = "l",
.cfunc = length_f,
.oneline = "gets the length of the current file",
};
static int
info_f(int argc, char **argv)
{
BlockDriverInfo bdi;
char s1[64], s2[64];
int ret;
if (bs->drv && bs->drv->format_name)
printf("format name: %s\n", bs->drv->format_name);
if (bs->drv && bs->drv->protocol_name)
printf("format name: %s\n", bs->drv->protocol_name);
ret = bdrv_get_info(bs, &bdi);
if (ret)
return 0;
cvtstr(bdi.cluster_size, s1, sizeof(s1));
cvtstr(bdi.vm_state_offset, s2, sizeof(s2));
printf("cluster size: %s\n", s1);
printf("vm state offset: %s\n", s2);
return 0;
}
static const cmdinfo_t info_cmd = {
.name = "info",
.altname = "i",
.cfunc = info_f,
.oneline = "prints information about the current file",
};
static int
alloc_f(int argc, char **argv)
{
int64_t offset;
int nb_sectors, remaining;
char s1[64];
int num, sum_alloc;
int ret;
offset = cvtnum(argv[1]);
if (offset & 0x1ff) {
printf("offset %lld is not sector aligned\n",
(long long)offset);
return 0;
}
if (argc == 3)
nb_sectors = cvtnum(argv[2]);
else
nb_sectors = 1;
remaining = nb_sectors;
sum_alloc = 0;
while (remaining) {
ret = bdrv_is_allocated(bs, offset >> 9, nb_sectors, &num);
remaining -= num;
if (ret) {
sum_alloc += num;
}
}
cvtstr(offset, s1, sizeof(s1));
if (nb_sectors == 1)
printf("sector allocated at offset %s\n", s1);
else
printf("%d/%d sectors allocated at offset %s\n",
sum_alloc, nb_sectors, s1);
return 0;
}
static const cmdinfo_t alloc_cmd = {
.name = "alloc",
.altname = "a",
.argmin = 1,
.argmax = 2,
.cfunc = alloc_f,
.args = "off [sectors]",
.oneline = "checks if a sector is present in the file",
};
static int
close_f(int argc, char **argv)
{
bdrv_close(bs);
bs = NULL;
return 0;
}
static const cmdinfo_t close_cmd = {
.name = "close",
.altname = "c",
.cfunc = close_f,
.oneline = "close the current open file",
};
static int openfile(char *name, int flags, int growable)
{
if (bs) {
fprintf(stderr, "file open already, try 'help close'\n");
return 1;
}
bs = bdrv_new("hda");
if (!bs)
return 1;
if (growable) {
flags |= BDRV_O_FILE;
}
if (bdrv_open(bs, name, flags) == -1) {
fprintf(stderr, "%s: can't open device %s\n", progname, name);
bs = NULL;
return 1;
}
if (growable) {
bs->growable = 1;
}
return 0;
}
static void
open_help(void)
{
printf(
"\n"
" opens a new file in the requested mode\n"
"\n"
" Example:\n"
" 'open -Cn /tmp/data' - creates/opens data file read-write and uncached\n"
"\n"
" Opens a file for subsequent use by all of the other qemu-io commands.\n"
" -C, -- create new file if it doesn't exist\n"
" -r, -- open file read-only\n"
" -s, -- use snapshot file\n"
" -n, -- disable host cache\n"
" -g, -- allow file to grow (only applies to protocols)"
"\n");
}
static const cmdinfo_t open_cmd;
static int
open_f(int argc, char **argv)
{
int flags = 0;
int readonly = 0;
int growable = 0;
int c;
while ((c = getopt(argc, argv, "snCrg")) != EOF) {
switch (c) {
case 's':
flags |= BDRV_O_SNAPSHOT;
break;
case 'n':
flags |= BDRV_O_NOCACHE;
break;
case 'C':
flags |= BDRV_O_CREAT;
break;
case 'r':
readonly = 1;
break;
case 'g':
growable = 1;
break;
default:
return command_usage(&open_cmd);
}
}
if (readonly)
flags |= BDRV_O_RDONLY;
else
flags |= BDRV_O_RDWR;
if (optind != argc - 1)
return command_usage(&open_cmd);
return openfile(argv[optind], flags, growable);
}
static const cmdinfo_t open_cmd = {
.name = "open",
.altname = "o",
.cfunc = open_f,
.argmin = 1,
.argmax = -1,
.flags = CMD_NOFILE_OK,
.args = "[-Crsn] [path]",
.oneline = "open the file specified by path",
.help = open_help,
};
static int
init_args_command(
int index)
{
/* only one device allowed so far */
if (index >= 1)
return 0;
return ++index;
}
static int
init_check_command(
const cmdinfo_t *ct)
{
if (ct->flags & CMD_FLAG_GLOBAL)
return 1;
if (!(ct->flags & CMD_NOFILE_OK) && !bs) {
fprintf(stderr, "no file open, try 'help open'\n");
return 0;
}
return 1;
}
static void usage(const char *name)
{
printf(
"Usage: %s [-h] [-V] [-Crsnm] [-c cmd] ... [file]\n"
"QEMU Disk exerciser\n"
"\n"
" -C, --create create new file if it doesn't exist\n"
" -c, --cmd command to execute\n"
" -r, --read-only export read-only\n"
" -s, --snapshot use snapshot file\n"
" -n, --nocache disable host cache\n"
" -g, --growable allow file to grow (only applies to protocols)\n"
" -m, --misalign misalign allocations for O_DIRECT\n"
" -k, --native-aio use kernel AIO implementation (on Linux only)\n"
" -h, --help display this help and exit\n"
" -V, --version output version information and exit\n"
"\n",
name);
}
int main(int argc, char **argv)
{
int readonly = 0;
int growable = 0;
const char *sopt = "hVc:Crsnmgk";
struct option lopt[] = {
{ "help", 0, NULL, 'h' },
{ "version", 0, NULL, 'V' },
{ "offset", 1, NULL, 'o' },
{ "cmd", 1, NULL, 'c' },
{ "create", 0, NULL, 'C' },
{ "read-only", 0, NULL, 'r' },
{ "snapshot", 0, NULL, 's' },
{ "nocache", 0, NULL, 'n' },
{ "misalign", 0, NULL, 'm' },
{ "growable", 0, NULL, 'g' },
{ "native-aio", 0, NULL, 'k' },
{ NULL, 0, NULL, 0 }
};
int c;
int opt_index = 0;
int flags = 0;
progname = basename(argv[0]);
while ((c = getopt_long(argc, argv, sopt, lopt, &opt_index)) != -1) {
switch (c) {
case 's':
flags |= BDRV_O_SNAPSHOT;
break;
case 'n':
flags |= BDRV_O_NOCACHE;
break;
case 'c':
add_user_command(optarg);
break;
case 'C':
flags |= BDRV_O_CREAT;
break;
case 'r':
readonly = 1;
break;
case 'm':
misalign = 1;
break;
case 'g':
growable = 1;
break;
case 'k':
flags |= BDRV_O_NATIVE_AIO;
break;
case 'V':
printf("%s version %s\n", progname, VERSION);
exit(0);
case 'h':
usage(progname);
exit(0);
default:
usage(progname);
exit(1);
}
}
if ((argc - optind) > 1) {
usage(progname);
exit(1);
}
bdrv_init();
/* initialize commands */
quit_init();
help_init();
add_command(&open_cmd);
add_command(&close_cmd);
add_command(&read_cmd);
add_command(&readv_cmd);
add_command(&write_cmd);
add_command(&writev_cmd);
add_command(&aio_read_cmd);
add_command(&aio_write_cmd);
add_command(&aio_flush_cmd);
add_command(&flush_cmd);
add_command(&truncate_cmd);
add_command(&length_cmd);
add_command(&info_cmd);
add_command(&alloc_cmd);
add_args_command(init_args_command);
add_check_command(init_check_command);
/* open the device */
if (readonly)
flags |= BDRV_O_RDONLY;
else
flags |= BDRV_O_RDWR;
if ((argc - optind) == 1)
openfile(argv[optind], flags, growable);
command_loop();
/*
* Make sure all outstanding requests get flushed the program exits.
*/
qemu_aio_flush();
if (bs)
bdrv_close(bs);
return 0;
}