Newer
Older
/*
* Command line utility to exercise the QEMU I/O path.
*
* Copyright (C) 2009-2016 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 "qapi/error.h"
#include "qapi/qmp/qdict.h"
#include "sysemu/block-backend.h"
#include "block/block.h"
#include "block/block_int.h" /* for info_f() */
#include "qemu/option.h"
#define CMD_NOFILE_OK 0x01
static cmdinfo_t *cmdtab;
static int ncmds;
static int compare_cmdname(const void *a, const void *b)
{
return strcmp(((const cmdinfo_t *)a)->name,
((const cmdinfo_t *)b)->name);
}
void qemuio_add_command(const cmdinfo_t *ci)
{
/* ci->perm assumes a file is open, but the GLOBAL and NOFILE_OK
* flags allow it not to be, so that combination is invalid.
* Catch it now rather than letting it manifest as a crash if a
* particular set of command line options are used.
*/
assert(ci->perm == 0 ||
(ci->flags & (CMD_FLAG_GLOBAL | CMD_NOFILE_OK)) == 0);
cmdtab = g_renew(cmdinfo_t, cmdtab, ++ncmds);
cmdtab[ncmds - 1] = *ci;
qsort(cmdtab, ncmds, sizeof(*cmdtab), compare_cmdname);
}
void qemuio_command_usage(const cmdinfo_t *ci)
{
printf("%s %s -- %s\n", ci->name, ci->args, ci->oneline);
}
static int init_check_command(BlockBackend *blk, const cmdinfo_t *ct)
{
if (ct->flags & CMD_FLAG_GLOBAL) {
return 1;
}
fprintf(stderr, "no file open, try 'help open'\n");
return 0;
}
return 1;
}
static int command(BlockBackend *blk, const cmdinfo_t *ct, int argc,
char **argv)
{
char *cmd = argv[0];
}
if (argc - 1 < ct->argmin || (ct->argmax != -1 && argc - 1 > ct->argmax)) {
if (ct->argmax == -1) {
fprintf(stderr,
"bad argument count %d to %s, expected at least %d arguments\n",
argc-1, cmd, ct->argmin);
} else if (ct->argmin == ct->argmax) {
fprintf(stderr,
"bad argument count %d to %s, expected %d arguments\n",
argc-1, cmd, ct->argmin);
} else {
fprintf(stderr,
"bad argument count %d to %s, expected between %d and %d arguments\n",
argc-1, cmd, ct->argmin, ct->argmax);
}
/* Request additional permissions if necessary for this command. The caller
* is responsible for restoring the original permissions afterwards if this
* is what it wants. */
if (ct->perm && blk_is_available(blk)) {
uint64_t orig_perm, orig_shared_perm;
blk_get_perm(blk, &orig_perm, &orig_shared_perm);
if (ct->perm & ~orig_perm) {
uint64_t new_perm;
Error *local_err = NULL;
int ret;
new_perm = orig_perm | ct->perm;
ret = blk_set_perm(blk, new_perm, orig_shared_perm, &local_err);
if (ret < 0) {
error_report_err(local_err);
qemu_reset_optind();
return ct->cfunc(blk, argc, argv);
}
static const cmdinfo_t *find_command(const char *cmd)
{
cmdinfo_t *ct;
for (ct = cmdtab; ct < &cmdtab[ncmds]; ct++) {
if (strcmp(ct->name, cmd) == 0 ||
(ct->altname && strcmp(ct->altname, cmd) == 0))
{
return (const cmdinfo_t *)ct;
}
}
return NULL;
}
/* Invoke fn() for commands with a matching prefix */
void qemuio_complete_command(const char *input,
void (*fn)(const char *cmd, void *opaque),
void *opaque)
{
cmdinfo_t *ct;
size_t input_len = strlen(input);
for (ct = cmdtab; ct < &cmdtab[ncmds]; ct++) {
if (strncmp(input, ct->name, input_len) == 0) {
fn(ct->name, opaque);
}
}
}
static char **breakline(char *input, int *count)
{
int c = 0;
char *p;
char **rval = g_new0(char *, 1);
while (rval && (p = qemu_strsep(&input, " ")) != NULL) {
if (!*p) {
continue;
}
c++;
rval = g_renew(char *, rval, (c + 1));
rval[c - 1] = p;
rval[c] = NULL;
}
*count = c;
return rval;
}
static int64_t cvtnum(const char *s)
{
int err;
uint64_t value;
err = qemu_strtosz(s, NULL, &value);
if (err < 0) {
return err;
}
if (value > INT64_MAX) {
return -ERANGE;
}
return value;
static void print_cvtnum_err(int64_t rc, const char *arg)
{
switch (rc) {
case -EINVAL:
printf("Parsing error: non-numeric argument,"
" or extraneous/unrecognized suffix -- %s\n", arg);
break;
case -ERANGE:
printf("Parsing error: argument too large -- %s\n", arg);
break;
default:
printf("Parsing error: %s\n", arg);
}
}
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
#define EXABYTES(x) ((long long)(x) << 60)
#define PETABYTES(x) ((long long)(x) << 50)
#define TERABYTES(x) ((long long)(x) << 40)
#define GIGABYTES(x) ((long long)(x) << 30)
#define MEGABYTES(x) ((long long)(x) << 20)
#define KILOBYTES(x) ((long long)(x) << 10)
#define TO_EXABYTES(x) ((x) / EXABYTES(1))
#define TO_PETABYTES(x) ((x) / PETABYTES(1))
#define TO_TERABYTES(x) ((x) / TERABYTES(1))
#define TO_GIGABYTES(x) ((x) / GIGABYTES(1))
#define TO_MEGABYTES(x) ((x) / MEGABYTES(1))
#define TO_KILOBYTES(x) ((x) / KILOBYTES(1))
static void cvtstr(double value, char *str, size_t size)
{
char *trim;
const char *suffix;
if (value >= EXABYTES(1)) {
suffix = " EiB";
snprintf(str, size - 4, "%.3f", TO_EXABYTES(value));
} else if (value >= PETABYTES(1)) {
suffix = " PiB";
snprintf(str, size - 4, "%.3f", TO_PETABYTES(value));
} else if (value >= TERABYTES(1)) {
suffix = " TiB";
snprintf(str, size - 4, "%.3f", TO_TERABYTES(value));
} else if (value >= GIGABYTES(1)) {
suffix = " GiB";
snprintf(str, size - 4, "%.3f", TO_GIGABYTES(value));
} else if (value >= MEGABYTES(1)) {
suffix = " MiB";
snprintf(str, size - 4, "%.3f", TO_MEGABYTES(value));
} else if (value >= KILOBYTES(1)) {
suffix = " KiB";
snprintf(str, size - 4, "%.3f", TO_KILOBYTES(value));
} else {
suffix = " bytes";
snprintf(str, size - 6, "%f", value);
}
trim = strstr(str, ".000");
if (trim) {
strcpy(trim, suffix);
} else {
strcat(str, suffix);
}
}
static struct timespec tsub(struct timespec t1, struct timespec t2)
t1.tv_nsec -= t2.tv_nsec;
if (t1.tv_nsec < 0) {
t1.tv_nsec += NANOSECONDS_PER_SECOND;
t1.tv_sec--;
}
t1.tv_sec -= t2.tv_sec;
return t1;
}
static double tdiv(double value, struct timespec tv)
double seconds = tv.tv_sec + (tv.tv_nsec / 1e9);
return value / seconds;
}
#define HOURS(sec) ((sec) / (60 * 60))
#define MINUTES(sec) (((sec) % (60 * 60)) / 60)
#define SECONDS(sec) ((sec) % 60)
enum {
DEFAULT_TIME = 0x0,
TERSE_FIXED_TIME = 0x1,
VERBOSE_FIXED_TIME = 0x2,
};
static void timestr(struct timespec *tv, char *ts, size_t size, int format)
double frac_sec = tv->tv_nsec / 1e9;
if (format & TERSE_FIXED_TIME) {
if (!HOURS(tv->tv_sec)) {
snprintf(ts, size, "%u:%05.2f",
(unsigned int) MINUTES(tv->tv_sec),
SECONDS(tv->tv_sec) + frac_sec);
return;
}
format |= VERBOSE_FIXED_TIME; /* fallback if hours needed */
}
if ((format & VERBOSE_FIXED_TIME) || tv->tv_sec) {
snprintf(ts, size, "%u:%02u:%05.2f",
(unsigned int) HOURS(tv->tv_sec),
(unsigned int) MINUTES(tv->tv_sec),
SECONDS(tv->tv_sec) + frac_sec);
snprintf(ts, size, "%05.2f sec", frac_sec);
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
/*
* 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(BlockBackend *blk, size_t len, int pattern)
{
void *buf;
if (qemuio_misalign) {
len += MISALIGN_OFFSET;
}
memset(buf, pattern, len);
if (qemuio_misalign) {
buf += MISALIGN_OFFSET;
}
return buf;
}
static void qemu_io_free(void *p)
{
if (qemuio_misalign) {
p -= MISALIGN_OFFSET;
}
qemu_vfree(p);
}
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
/*
* qemu_io_alloc_from_file()
*
* Allocates the buffer and populates it with the content of the given file
* up to @len bytes. If the file length is less than @len, then the buffer
* is populated with the file content cyclically.
*
* @blk - the block backend where the buffer content is going to be written to
* @len - the buffer length
* @file_name - the file to read the content from
*
* Returns: the buffer pointer on success
* NULL on error
*/
static void *qemu_io_alloc_from_file(BlockBackend *blk, size_t len,
const char *file_name)
{
char *buf, *buf_origin;
FILE *f = fopen(file_name, "r");
int pattern_len;
if (!f) {
perror(file_name);
return NULL;
}
if (qemuio_misalign) {
len += MISALIGN_OFFSET;
}
buf_origin = buf = blk_blockalign(blk, len);
if (qemuio_misalign) {
buf_origin += MISALIGN_OFFSET;
buf += MISALIGN_OFFSET;
len -= MISALIGN_OFFSET;
}
pattern_len = fread(buf_origin, 1, len, f);
if (ferror(f)) {
perror(file_name);
goto error;
}
if (pattern_len == 0) {
fprintf(stderr, "%s: file is empty\n", file_name);
goto error;
}
fclose(f);
if (len > pattern_len) {
len -= pattern_len;
buf += pattern_len;
while (len > 0) {
size_t len_to_copy = MIN(pattern_len, len);
memcpy(buf, buf_origin, len_to_copy);
len -= len_to_copy;
buf += len_to_copy;
}
}
return buf_origin;
error:
qemu_io_free(buf_origin);
if (f) {
fclose(f);
}
static void dump_buffer(const void *buffer, int64_t offset, int64_t len)
const uint8_t *p;
for (i = 0, p = buffer; i < len; i += 16) {
const uint8_t *s = p;
printf("%08" PRIx64 ": ", 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 timespec *t, int64_t offset,
int64_t count, int64_t total, int cnt, bool 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 %"PRId64"/%"PRId64" bytes at offset %" PRId64 "\n",
op, total, count, 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 */
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(BlockBackend *blk, QEMUIOVector *qiov, char **argv, int nr_iov,
int pattern)
{
size_t *sizes = g_new0(size_t, nr_iov);
size_t count = 0;
void *buf = NULL;
void *p;
int i;
for (i = 0; i < nr_iov; i++) {
char *arg = argv[i];
int64_t len;
len = cvtnum(arg);
if (len < 0) {
if (len > BDRV_REQUEST_MAX_BYTES) {
printf("Argument '%s' exceeds maximum size %" PRIu64 "\n", arg,
(uint64_t)BDRV_REQUEST_MAX_BYTES);
goto fail;
}
if (count > BDRV_REQUEST_MAX_BYTES - len) {
printf("The total number of bytes exceed the maximum size %" PRIu64
"\n", (uint64_t)BDRV_REQUEST_MAX_BYTES);
goto fail;
}
sizes[i] = len;
count += len;
}
qemu_iovec_init(qiov, nr_iov);
for (i = 0; i < nr_iov; i++) {
qemu_iovec_add(qiov, p, sizes[i]);
p += sizes[i];
}
fail:
g_free(sizes);
return buf;
}
static int do_pread(BlockBackend *blk, char *buf, int64_t offset,
int64_t bytes, int64_t *total)
if (bytes > INT_MAX) {
*total = blk_pread(blk, offset, (uint8_t *)buf, bytes);
if (*total < 0) {
return *total;
}
return 1;
}
static int do_pwrite(BlockBackend *blk, char *buf, int64_t offset,
int64_t bytes, int flags, int64_t *total)
if (bytes > INT_MAX) {
*total = blk_pwrite(blk, offset, (uint8_t *)buf, bytes, flags);
if (*total < 0) {
return *total;
}
return 1;
}
typedef struct {
int ret;
bool done;
} CoWriteZeroes;
static void coroutine_fn co_pwrite_zeroes_entry(void *opaque)
{
CoWriteZeroes *data = opaque;
data->ret = blk_co_pwrite_zeroes(data->blk, data->offset, data->bytes,
data->done = true;
if (data->ret < 0) {
*data->total = data->ret;
return;
}
*data->total = data->bytes;
static int do_co_pwrite_zeroes(BlockBackend *blk, int64_t offset,
int64_t bytes, int flags, int64_t *total)
{
Coroutine *co;
CoWriteZeroes data = {
if (bytes > INT_MAX) {
co = qemu_coroutine_create(co_pwrite_zeroes_entry, &data);
bdrv_coroutine_enter(blk_bs(blk), co);
}
if (data.ret < 0) {
return data.ret;
} else {
return 1;
}
}
static int do_write_compressed(BlockBackend *blk, char *buf, int64_t offset,
int64_t bytes, int64_t *total)
if (bytes > BDRV_REQUEST_MAX_BYTES) {
ret = blk_pwrite_compressed(blk, offset, buf, bytes);
if (ret < 0) {
return ret;
}
static int do_load_vmstate(BlockBackend *blk, char *buf, int64_t offset,
if (count > INT_MAX) {
return -ERANGE;
}
*total = blk_load_vmstate(blk, (uint8_t *)buf, offset, count);
if (*total < 0) {
return *total;
}
return 1;
}
static int do_save_vmstate(BlockBackend *blk, char *buf, int64_t offset,
if (count > INT_MAX) {
return -ERANGE;
}
*total = blk_save_vmstate(blk, (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(BlockBackend *blk, QEMUIOVector *qiov,
int64_t offset, int *total)
{
int async_ret = NOT_DONE;
blk_aio_preadv(blk, offset, qiov, 0, aio_rw_done, &async_ret);
while (async_ret == NOT_DONE) {
main_loop_wait(false);
}
*total = qiov->size;
return async_ret < 0 ? async_ret : 1;
}
static int do_aio_writev(BlockBackend *blk, QEMUIOVector *qiov,
int64_t offset, int flags, int *total)
{
int async_ret = NOT_DONE;
blk_aio_pwritev(blk, offset, qiov, flags, aio_rw_done, &async_ret);
while (async_ret == NOT_DONE) {
main_loop_wait(false);
}
*total = qiov->size;
return async_ret < 0 ? async_ret : 1;
}
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, -- ignored for backwards compatibility\n"
" -P, -- use a pattern to verify read data\n"
" -q, -- quiet 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(BlockBackend *blk, int argc, char **argv);
static const cmdinfo_t read_cmd = {
.name = "read",
.altname = "r",
.cfunc = read_f,
.argmin = 2,
.argmax = -1,
.args = "[-abCqv] [-P pattern [-s off] [-l len]] off len",
.oneline = "reads a number of bytes at a specified offset",
.help = read_help,
};
static int read_f(BlockBackend *blk, int argc, char **argv)
bool Cflag = false, qflag = false, vflag = false;
bool Pflag = false, sflag = false, lflag = false, bflag = false;
char *buf;
int64_t offset;
/* Some compilers get confused and warn if this is not initialized. */
int64_t total = 0;
int pattern = 0;
int64_t pattern_offset = 0, pattern_count = 0;
while ((c = getopt(argc, argv, "bCl:pP:qs:v")) != -1) {
pattern_count = cvtnum(optarg);
if (pattern_count < 0) {
/* Ignored for backwards compatibility */
pattern = parse_pattern(optarg);
if (pattern < 0) {
pattern_offset = cvtnum(optarg);
if (pattern_offset < 0) {
qemuio_command_usage(&read_cmd);
}
}
if (optind != argc - 2) {
qemuio_command_usage(&read_cmd);
}
offset = cvtnum(argv[optind]);
if (offset < 0) {
}
optind++;
count = cvtnum(argv[optind]);
if (count < 0) {
} else if (count > BDRV_REQUEST_MAX_BYTES) {
printf("length cannot exceed %" PRIu64 ", given %s\n",
(uint64_t)BDRV_REQUEST_MAX_BYTES, argv[optind]);
}
if (!Pflag && (lflag || sflag)) {
qemuio_command_usage(&read_cmd);
}
if (!lflag) {
pattern_count = count - pattern_offset;
}
if ((pattern_count < 0) || (pattern_count + pattern_offset > count)) {
printf("pattern verification range exceeds end of read data\n");
if (!QEMU_IS_ALIGNED(offset, BDRV_SECTOR_SIZE)) {
printf("%" PRId64 " is not a sector-aligned value for 'offset'\n",
if (!QEMU_IS_ALIGNED(count, BDRV_SECTOR_SIZE)) {
printf("%"PRId64" is not a sector-aligned value for 'count'\n",
clock_gettime(CLOCK_MONOTONIC, &t1);
ret = do_load_vmstate(blk, buf, offset, count, &total);
ret = do_pread(blk, buf, offset, count, &total);
clock_gettime(CLOCK_MONOTONIC, &t2);
if (ret < 0) {
printf("read failed: %s\n", strerror(-ret));
cnt = ret;
ret = 0;
if (Pflag) {
void *cmp_buf = g_malloc(pattern_count);
memset(cmp_buf, pattern, pattern_count);
if (memcmp(buf + pattern_offset, cmp_buf, pattern_count)) {
printf("Pattern verification failed at offset %"
offset + pattern_offset, pattern_count);
}
g_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);
}
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, -- quiet mode, do not show I/O statistics\n"
"\n");
}
static int readv_f(BlockBackend *blk, int argc, char **argv);
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 int readv_f(BlockBackend *blk, int argc, char **argv)
bool Cflag = false, qflag = false, vflag = false;
char *buf;
int64_t offset;
/* Some compilers get confused and warn if this is not initialized. */
int total = 0;
int nr_iov;
QEMUIOVector qiov;
int pattern = 0;
while ((c = getopt(argc, argv, "CP:qv")) != -1) {
pattern = parse_pattern(optarg);
if (pattern < 0) {
qemuio_command_usage(&readv_cmd);
}
}
if (optind > argc - 2) {
qemuio_command_usage(&readv_cmd);
}
offset = cvtnum(argv[optind]);
if (offset < 0) {
}
optind++;
nr_iov = argc - optind;
buf = create_iovec(blk, &qiov, &argv[optind], nr_iov, 0xab);
clock_gettime(CLOCK_MONOTONIC, &t1);
ret = do_aio_readv(blk, &qiov, offset, &total);
clock_gettime(CLOCK_MONOTONIC, &t2);
if (ret < 0) {
printf("readv failed: %s\n", strerror(-ret));
cnt = ret;
ret = 0;
if (Pflag) {
void *cmp_buf = g_malloc(qiov.size);
memset(cmp_buf, pattern, qiov.size);
if (memcmp(buf, cmp_buf, qiov.size)) {
printf("Pattern verification failed at offset %"
PRId64 ", %zu bytes\n", offset, qiov.size);