qemu/qemu-io-cmds.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

2814 lines
73 KiB
C
Raw Permalink Normal View History

/*
* 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 "qemu/osdep.h"
2016-03-14 11:01:28 +03:00
#include "qapi/error.h"
#include "qapi/qmp/qdict.h"
#include "qemu-io.h"
#include "sysemu/block-backend.h"
#include "block/block.h"
#include "block/block_int.h" /* for info_f() */
#include "block/qapi.h"
#include "qemu/error-report.h"
#include "qemu/main-loop.h"
#include "qemu/option.h"
#include "qemu/timer.h"
#include "qemu/cutils.h"
#include "qemu/memalign.h"
#define CMD_NOFILE_OK 0x01
bool qemuio_misalign;
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;
}
if (!(ct->flags & CMD_NOFILE_OK) && !blk) {
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 (!init_check_command(blk, ct)) {
return -EINVAL;
}
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);
}
return -EINVAL;
}
/*
* Request additional permissions if necessary for this command. The caller
* is responsible for restoring the original permissions afterwards if this
* is what it wants.
*
* Coverity thinks that blk may be NULL in the following if condition. It's
* not so: in init_check_command() we fail if blk is NULL for command with
* both CMD_FLAG_GLOBAL and CMD_NOFILE_OK flags unset. And in
* qemuio_add_command() we assert that command with non-zero .perm field
* doesn't set this flags. So, the following assertion is to silence
* Coverity:
*/
assert(blk || !ct->perm);
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);
return ret;
}
}
}
qemu-io: Add generic function for reinitializing optind. On FreeBSD 11.2: $ nbdkit memory size=1M --run './qemu-io -f raw -c "aio_write 0 512" $nbd' Parsing error: non-numeric argument, or extraneous/unrecognized suffix -- aio_write After main option parsing, we reinitialize optind so we can parse each command. However reinitializing optind to 0 does not work on FreeBSD. What happens when you do this is optind remains 0 after the option parsing loop, and the result is we try to parse argv[optind] == argv[0] == "aio_write" as if it was the first parameter. The FreeBSD manual page says: In order to use getopt() to evaluate multiple sets of arguments, or to evaluate a single set of arguments multiple times, the variable optreset must be set to 1 before the second and each additional set of calls to getopt(), and the variable optind must be reinitialized. (From the rest of the man page it is clear that optind must be reinitialized to 1). The glibc man page says: A program that scans multiple argument vectors, or rescans the same vector more than once, and wants to make use of GNU extensions such as '+' and '-' at the start of optstring, or changes the value of POSIXLY_CORRECT between scans, must reinitialize getopt() by resetting optind to 0, rather than the traditional value of 1. (Resetting to 0 forces the invocation of an internal initialization routine that rechecks POSIXLY_CORRECT and checks for GNU extensions in optstring.) This commit introduces an OS-portability function called qemu_reset_optind which provides a way of resetting optind that works on FreeBSD and platforms that use optreset, while keeping it the same as now on other platforms. Note that the qemu codebase sets optind in many other places, but in those other places it's setting a local variable and not using getopt. This change is only needed in places where we are using getopt and the associated global variable optind. Signed-off-by: Richard W.M. Jones <rjones@redhat.com> Message-id: 20190118101114.11759-2-rjones@redhat.com Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Reviewed-by: Eric Blake <eblake@redhat.com> Signed-off-by: Max Reitz <mreitz@redhat.com>
2019-01-18 13:11:14 +03:00
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);
}
}
#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);
} else {
snprintf(ts, size, "%05.2f sec", frac_sec);
}
}
/*
* 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,
bool register_buf)
{
void *buf;
if (qemuio_misalign) {
len += MISALIGN_OFFSET;
}
buf = blk_blockalign(blk, len);
memset(buf, pattern, len);
if (register_buf) {
blk_register_buf(blk, buf, len, &error_abort);
}
if (qemuio_misalign) {
buf += MISALIGN_OFFSET;
}
return buf;
}
static void qemu_io_free(BlockBackend *blk, void *p, size_t len,
bool unregister_buf)
{
if (qemuio_misalign) {
p -= MISALIGN_OFFSET;
len += MISALIGN_OFFSET;
}
if (unregister_buf) {
blk_unregister_buf(blk, p, len);
}
qemu_vfree(p);
}
/*
* 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
* @register_buf - call blk_register_buf()
*
* 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, bool register_buf)
{
size_t alloc_len = len + (qemuio_misalign ? MISALIGN_OFFSET : 0);
char *alloc_buf, *buf, *end;
FILE *f = fopen(file_name, "r");
int pattern_len;
if (!f) {
perror(file_name);
return NULL;
}
alloc_buf = buf = blk_blockalign(blk, alloc_len);
if (qemuio_misalign) {
buf += MISALIGN_OFFSET;
}
pattern_len = fread(buf, 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);
f = NULL;
if (register_buf) {
blk_register_buf(blk, alloc_buf, alloc_len, &error_abort);
}
end = buf + len;
for (char *p = buf + pattern_len; p < end; p += pattern_len) {
memcpy(p, buf, MIN(pattern_len, end - p));
}
return buf;
error:
/*
* This code path is only taken before blk_register_buf() is called, so
* hardcode the qemu_io_free() unregister_buf argument to false.
*/
qemu_io_free(blk, alloc_buf, alloc_len, false);
if (f) {
fclose(f);
}
return NULL;
}
static void dump_buffer(const void *buffer, int64_t offset, int64_t len)
{
uint64_t i;
int j;
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 */
printf("%"PRId64",%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(BlockBackend *blk, QEMUIOVector *qiov, char **argv, int nr_iov,
int pattern, bool register_buf)
{
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) {
print_cvtnum_err(len, arg);
goto fail;
}
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);
buf = p = qemu_io_alloc(blk, count, pattern, register_buf);
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, BdrvRequestFlags flags, int64_t *total)
{
int ret;
if (bytes > INT_MAX) {
return -ERANGE;
}
ret = blk_pread(blk, offset, bytes, (uint8_t *)buf, flags);
if (ret < 0) {
return ret;
}
*total = bytes;
return 1;
}
static int do_pwrite(BlockBackend *blk, char *buf, int64_t offset,
int64_t bytes, BdrvRequestFlags flags, int64_t *total)
{
int ret;
if (bytes > INT_MAX) {
return -ERANGE;
}
ret = blk_pwrite(blk, offset, bytes, (uint8_t *)buf, flags);
if (ret < 0) {
return ret;
}
*total = bytes;
return 1;
}
static int do_pwrite_zeroes(BlockBackend *blk, int64_t offset,
int64_t bytes, BdrvRequestFlags flags,
int64_t *total)
{
int ret = blk_pwrite_zeroes(blk, offset, bytes,
flags | BDRV_REQ_ZERO_WRITE);
if (ret < 0) {
return ret;
}
*total = bytes;
return 1;
}
static int do_write_compressed(BlockBackend *blk, char *buf, int64_t offset,
int64_t bytes, int64_t *total)
{
int ret;
if (bytes > BDRV_REQUEST_MAX_BYTES) {
return -ERANGE;
}
ret = blk_pwrite_compressed(blk, offset, bytes, buf);
if (ret < 0) {
return ret;
}
*total = bytes;
return 1;
}
static int do_load_vmstate(BlockBackend *blk, char *buf, int64_t offset,
int64_t count, int64_t *total)
{
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,
int64_t count, int64_t *total)
{
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, BdrvRequestFlags flags, int *total)
{
int async_ret = NOT_DONE;
blk_aio_preadv(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 int do_aio_writev(BlockBackend *blk, QEMUIOVector *qiov,
int64_t offset, BdrvRequestFlags 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)