diff options
74 files changed, 8623 insertions, 1902 deletions
@@ -106,6 +106,8 @@ audio/audio.o audio/fmodaudio.o: QEMU_CFLAGS += $(FMOD_CFLAGS) QEMU_CFLAGS+=$(CURL_CFLAGS) +QEMU_CFLAGS+=$(GLIB_CFLAGS) + ui/cocoa.o: ui/cocoa.m ui/sdl.o audio/sdlaudio.o ui/sdl_zoom.o baum.o: QEMU_CFLAGS += $(SDL_CFLAGS) @@ -138,7 +140,7 @@ endif ###################################################################### qemu-img.o: qemu-img-cmds.h -qemu-img.o qemu-tool.o qemu-nbd.o qemu-io.o cmd.o: $(GENERATED_HEADERS) +qemu-img.o qemu-tool.o qemu-nbd.o qemu-io.o cmd.o qemu-ga.o: $(GENERATED_HEADERS) qemu-img$(EXESUF): qemu-img.o qemu-tool.o qemu-error.o $(oslib-obj-y) $(trace-obj-y) $(block-obj-y) $(qobject-obj-y) $(version-obj-y) qemu-timer-common.o @@ -160,19 +162,54 @@ check-qlist: check-qlist.o qlist.o qint.o $(CHECK_PROG_DEPS) check-qfloat: check-qfloat.o qfloat.o $(CHECK_PROG_DEPS) check-qjson: check-qjson.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o qjson.o json-streamer.o json-lexer.o json-parser.o error.o qerror.o qemu-error.o $(CHECK_PROG_DEPS) +$(qapi-obj-y): $(GENERATED_HEADERS) +qapi-dir := qapi-generated +test-visitor.o test-qmp-commands.o qemu-ga$(EXESUF): QEMU_CFLAGS += -I $(qapi-dir) + +$(qapi-dir)/test-qapi-types.c: $(qapi-dir)/test-qapi-types.h +$(qapi-dir)/test-qapi-types.h: $(SRC_PATH)/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-types.py + $(call quiet-command,python $(SRC_PATH)/scripts/qapi-types.py -o "$(qapi-dir)" -p "test-" < $<, " GEN $@") +$(qapi-dir)/test-qapi-visit.c: $(qapi-dir)/test-qapi-visit.h +$(qapi-dir)/test-qapi-visit.h: $(SRC_PATH)/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-visit.py + $(call quiet-command,python $(SRC_PATH)/scripts/qapi-visit.py -o "$(qapi-dir)" -p "test-" < $<, " GEN $@") +$(qapi-dir)/test-qmp-commands.h: $(qapi-dir)/test-qmp-marshal.c +$(qapi-dir)/test-qmp-marshal.c: $(SRC_PATH)/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-commands.py + $(call quiet-command,python $(SRC_PATH)/scripts/qapi-commands.py -o "$(qapi-dir)" -p "test-" < $<, " GEN $@") + +$(qapi-dir)/qga-qapi-types.c: $(qapi-dir)/qga-qapi-types.h +$(qapi-dir)/qga-qapi-types.h: $(SRC_PATH)/qapi-schema-guest.json $(SRC_PATH)/scripts/qapi-types.py + $(call quiet-command,python $(SRC_PATH)/scripts/qapi-types.py -o "$(qapi-dir)" -p "qga-" < $<, " GEN $@") +$(qapi-dir)/qga-qapi-visit.c: $(qapi-dir)/qga-qapi-visit.h +$(qapi-dir)/qga-qapi-visit.h: $(SRC_PATH)/qapi-schema-guest.json $(SRC_PATH)/scripts/qapi-visit.py + $(call quiet-command,python $(SRC_PATH)/scripts/qapi-visit.py -o "$(qapi-dir)" -p "qga-" < $<, " GEN $@") +$(qapi-dir)/qga-qmp-marshal.c: $(SRC_PATH)/qapi-schema-guest.json $(SRC_PATH)/scripts/qapi-commands.py + $(call quiet-command,python $(SRC_PATH)/scripts/qapi-commands.py -o "$(qapi-dir)" -p "qga-" < $<, " GEN $@") + +test-visitor.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h) $(qapi-obj-y) +test-visitor: test-visitor.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o $(qapi-obj-y) error.o osdep.o qemu-malloc.o $(oslib-obj-y) qjson.o json-streamer.o json-lexer.o json-parser.o qerror.o qemu-error.o qemu-tool.o $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-types.o + +test-qmp-commands.o: $(addprefix $(qapi-dir)/, test-qapi-types.c test-qapi-types.h test-qapi-visit.c test-qapi-visit.h test-qmp-marshal.c test-qmp-commands.h) $(qapi-obj-y) +test-qmp-commands: test-qmp-commands.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o $(qapi-obj-y) error.o osdep.o qemu-malloc.o $(oslib-obj-y) qjson.o json-streamer.o json-lexer.o json-parser.o qerror.o qemu-error.o qemu-tool.o $(qapi-dir)/test-qapi-visit.o $(qapi-dir)/test-qapi-types.o $(qapi-dir)/test-qmp-marshal.o module.o + +QGALIB=qga/guest-agent-command-state.o qga/guest-agent-commands.o + +qemu-ga.o: $(addprefix $(qapi-dir)/, qga-qapi-types.c qga-qapi-types.h qga-qapi-visit.c qga-qmp-marshal.c) $(qapi-obj-y) +qemu-ga$(EXESUF): qemu-ga.o $(QGALIB) qemu-tool.o qemu-error.o error.o $(oslib-obj-y) $(trace-obj-y) $(block-obj-y) $(qobject-obj-y) $(version-obj-y) $(qapi-obj-y) qemu-timer-common.o qemu-sockets.o module.o qapi/qmp-dispatch.o qapi/qmp-registry.o $(qapi-dir)/qga-qapi-visit.o $(qapi-dir)/qga-qapi-types.o $(qapi-dir)/qga-qmp-marshal.o + QEMULIBS=libhw32 libhw64 libuser libdis libdis-user clean: # avoid old build problems by removing potentially incorrect old files rm -f config.mak op-i386.h opc-i386.h gen-op-i386.h op-arm.h opc-arm.h gen-op-arm.h rm -f qemu-options.def - rm -f *.o *.d *.a *.lo $(TOOLS) TAGS cscope.* *.pod *~ */*~ + rm -f *.o *.d *.a *.lo $(TOOLS) qemu-ga TAGS cscope.* *.pod *~ */*~ rm -Rf .libs - rm -f slirp/*.o slirp/*.d audio/*.o audio/*.d block/*.o block/*.d net/*.o net/*.d fsdev/*.o fsdev/*.d ui/*.o ui/*.d + rm -f slirp/*.o slirp/*.d audio/*.o audio/*.d block/*.o block/*.d net/*.o net/*.d fsdev/*.o fsdev/*.d ui/*.o ui/*.d qapi/*.o qapi/*.d qga/*.o qga/*.d rm -f qemu-img-cmds.h rm -f trace.c trace.h trace.c-timestamp trace.h-timestamp rm -f trace-dtrace.dtrace trace-dtrace.dtrace-timestamp rm -f trace-dtrace.h trace-dtrace.h-timestamp + rm -rf $(qapi-dir) $(MAKE) -C tests clean for d in $(ALL_SUBDIRS) $(QEMULIBS) libcacard; do \ if test -d $$d; then $(MAKE) -C $$d $@ || exit 1; fi; \ @@ -361,4 +398,4 @@ tarbin: $(mandir)/man8/qemu-nbd.8 # Include automatically generated dependency files --include $(wildcard *.d audio/*.d slirp/*.d block/*.d net/*.d ui/*.d) +-include $(wildcard *.d audio/*.d slirp/*.d block/*.d net/*.d ui/*.d qapi/*.d qga/*.d) diff --git a/Makefile.objs b/Makefile.objs index c43ed05c89..6991a9f52a 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -205,6 +205,7 @@ hw-obj-$(CONFIG_HPET) += hpet.o hw-obj-$(CONFIG_APPLESMC) += applesmc.o hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o ccid-card-passthru.o hw-obj-$(CONFIG_SMARTCARD_NSS) += ccid-card-emulated.o +hw-obj-$(CONFIG_USB_REDIR) += usb-redir.o # PPC devices hw-obj-$(CONFIG_OPENPIC) += openpic.o @@ -372,7 +373,15 @@ endif libcacard-y = cac.o event.o vcard.o vreader.o vcard_emul_nss.o vcard_emul_type.o card_7816.o +###################################################################### +# qapi + +qapi-nested-y = qapi-visit-core.o qmp-input-visitor.o qmp-output-visitor.o qapi-dealloc-visitor.o +qapi-nested-y += qmp-registry.o qmp-dispatch.o +qapi-obj-y = $(addprefix qapi/, $(qapi-nested-y)) + vl.o: QEMU_CFLAGS+=$(GPROF_CFLAGS) vl.o: QEMU_CFLAGS+=$(SDL_CFLAGS) +vl.o: QEMU_CFLAGS+=$(GLIB_CFLAGS) diff --git a/Makefile.target b/Makefile.target index e20a313b9d..cde509ba76 100644 --- a/Makefile.target +++ b/Makefile.target @@ -204,6 +204,7 @@ QEMU_CFLAGS += $(VNC_TLS_CFLAGS) QEMU_CFLAGS += $(VNC_SASL_CFLAGS) QEMU_CFLAGS += $(VNC_JPEG_CFLAGS) QEMU_CFLAGS += $(VNC_PNG_CFLAGS) +QEMU_CFLAGS += $(GLIB_CFLAGS) # xen support obj-$(CONFIG_XEN) += xen-all.o xen_machine_pv.o xen_domainbuild.o xen-mapcache.o @@ -1147,6 +1147,25 @@ int bdrv_truncate(BlockDriverState *bs, int64_t offset) } /** + * Length of a allocated file in bytes. Sparse files are counted by actual + * allocated space. Return < 0 if error or unknown. + */ +int64_t bdrv_get_allocated_file_size(BlockDriverState *bs) +{ + BlockDriver *drv = bs->drv; + if (!drv) { + return -ENOMEDIUM; + } + if (drv->bdrv_get_allocated_file_size) { + return drv->bdrv_get_allocated_file_size(bs); + } + if (bs->file) { + return bdrv_get_allocated_file_size(bs->file); + } + return -ENOTSUP; +} + +/** * Length of a file in bytes. Return < 0 if error or unknown. */ int64_t bdrv_getlength(BlockDriverState *bs) @@ -89,6 +89,7 @@ int bdrv_write_sync(BlockDriverState *bs, int64_t sector_num, const uint8_t *buf, int nb_sectors); int bdrv_truncate(BlockDriverState *bs, int64_t offset); int64_t bdrv_getlength(BlockDriverState *bs); +int64_t bdrv_get_allocated_file_size(BlockDriverState *bs); void bdrv_get_geometry(BlockDriverState *bs, uint64_t *nb_sectors_ptr); void bdrv_guess_geometry(BlockDriverState *bs, int *pcyls, int *pheads, int *psecs); int bdrv_commit(BlockDriverState *bs); diff --git a/block/qcow2-cache.c b/block/qcow2-cache.c index 382473933c..84088477a4 100644 --- a/block/qcow2-cache.c +++ b/block/qcow2-cache.c @@ -312,3 +312,15 @@ found: c->entries[i].dirty = true; } +bool qcow2_cache_set_writethrough(BlockDriverState *bs, Qcow2Cache *c, + bool enable) +{ + bool old = c->writethrough; + + if (!old && enable) { + qcow2_cache_flush(bs, c); + } + + c->writethrough = enable; + return old; +} diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c index ac95b88fe1..14b2f67f14 100644 --- a/block/qcow2-refcount.c +++ b/block/qcow2-refcount.c @@ -705,8 +705,15 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs, BDRVQcowState *s = bs->opaque; uint64_t *l1_table, *l2_table, l2_offset, offset, l1_size2, l1_allocated; int64_t old_offset, old_l2_offset; - int i, j, l1_modified, nb_csectors, refcount; + int i, j, l1_modified = 0, nb_csectors, refcount; int ret; + bool old_l2_writethrough, old_refcount_writethrough; + + /* Switch caches to writeback mode during update */ + old_l2_writethrough = + qcow2_cache_set_writethrough(bs, s->l2_table_cache, false); + old_refcount_writethrough = + qcow2_cache_set_writethrough(bs, s->refcount_block_cache, false); l2_table = NULL; l1_table = NULL; @@ -720,7 +727,11 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs, l1_allocated = 1; if (bdrv_pread(bs->file, l1_table_offset, l1_table, l1_size2) != l1_size2) + { + ret = -EIO; goto fail; + } + for(i = 0;i < l1_size; i++) be64_to_cpus(&l1_table[i]); } else { @@ -729,7 +740,6 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs, l1_allocated = 0; } - l1_modified = 0; for(i = 0; i < l1_size; i++) { l2_offset = l1_table[i]; if (l2_offset) { @@ -773,6 +783,7 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs, } if (refcount < 0) { + ret = -EIO; goto fail; } } @@ -803,6 +814,7 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs, refcount = get_refcount(bs, l2_offset >> s->cluster_bits); } if (refcount < 0) { + ret = -EIO; goto fail; } else if (refcount == 1) { l2_offset |= QCOW_OFLAG_COPIED; @@ -813,6 +825,18 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs, } } } + + ret = 0; +fail: + if (l2_table) { + qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table); + } + + /* Enable writethrough cache mode again */ + qcow2_cache_set_writethrough(bs, s->l2_table_cache, old_l2_writethrough); + qcow2_cache_set_writethrough(bs, s->refcount_block_cache, + old_refcount_writethrough); + if (l1_modified) { for(i = 0; i < l1_size; i++) cpu_to_be64s(&l1_table[i]); @@ -824,15 +848,7 @@ int qcow2_update_snapshot_refcount(BlockDriverState *bs, } if (l1_allocated) qemu_free(l1_table); - return 0; - fail: - if (l2_table) { - qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table); - } - - if (l1_allocated) - qemu_free(l1_table); - return -EIO; + return ret; } diff --git a/block/qcow2.h b/block/qcow2.h index e1ae3e8c2b..6a0a21b694 100644 --- a/block/qcow2.h +++ b/block/qcow2.h @@ -228,6 +228,8 @@ int qcow2_read_snapshots(BlockDriverState *bs); Qcow2Cache *qcow2_cache_create(BlockDriverState *bs, int num_tables, bool writethrough); int qcow2_cache_destroy(BlockDriverState* bs, Qcow2Cache *c); +bool qcow2_cache_set_writethrough(BlockDriverState *bs, Qcow2Cache *c, + bool enable); void qcow2_cache_entry_mark_dirty(Qcow2Cache *c, void *table); int qcow2_cache_flush(BlockDriverState *bs, Qcow2Cache *c); diff --git a/block/raw-posix.c b/block/raw-posix.c index 34b64aa205..cd89c8312a 100644 --- a/block/raw-posix.c +++ b/block/raw-posix.c @@ -793,6 +793,17 @@ static int64_t raw_getlength(BlockDriverState *bs) } #endif +static int64_t raw_get_allocated_file_size(BlockDriverState *bs) +{ + struct stat st; + BDRVRawState *s = bs->opaque; + + if (fstat(s->fd, &st) < 0) { + return -errno; + } + return (int64_t)st.st_blocks * 512; +} + static int raw_create(const char *filename, QEMUOptionParameter *options) { int fd; @@ -888,6 +899,8 @@ static BlockDriver bdrv_file = { .bdrv_truncate = raw_truncate, .bdrv_getlength = raw_getlength, + .bdrv_get_allocated_file_size + = raw_get_allocated_file_size, .create_options = raw_create_options, }; @@ -1156,6 +1169,8 @@ static BlockDriver bdrv_host_device = { .bdrv_read = raw_read, .bdrv_write = raw_write, .bdrv_getlength = raw_getlength, + .bdrv_get_allocated_file_size + = raw_get_allocated_file_size, /* generic scsi device */ #ifdef __linux__ @@ -1277,6 +1292,8 @@ static BlockDriver bdrv_host_floppy = { .bdrv_read = raw_read, .bdrv_write = raw_write, .bdrv_getlength = raw_getlength, + .bdrv_get_allocated_file_size + = raw_get_allocated_file_size, /* removable device support */ .bdrv_is_inserted = floppy_is_inserted, @@ -1380,6 +1397,8 @@ static BlockDriver bdrv_host_cdrom = { .bdrv_read = raw_read, .bdrv_write = raw_write, .bdrv_getlength = raw_getlength, + .bdrv_get_allocated_file_size + = raw_get_allocated_file_size, /* removable device support */ .bdrv_is_inserted = cdrom_is_inserted, @@ -1503,6 +1522,8 @@ static BlockDriver bdrv_host_cdrom = { .bdrv_read = raw_read, .bdrv_write = raw_write, .bdrv_getlength = raw_getlength, + .bdrv_get_allocated_file_size + = raw_get_allocated_file_size, /* removable device support */ .bdrv_is_inserted = cdrom_is_inserted, diff --git a/block/raw-win32.c b/block/raw-win32.c index 56bd7195a1..91067e7595 100644 --- a/block/raw-win32.c +++ b/block/raw-win32.c @@ -213,6 +213,31 @@ static int64_t raw_getlength(BlockDriverState *bs) return l.QuadPart; } +static int64_t raw_get_allocated_file_size(BlockDriverState *bs) +{ + typedef DWORD (WINAPI * get_compressed_t)(const char *filename, + DWORD * high); + get_compressed_t get_compressed; + struct _stati64 st; + const char *filename = bs->filename; + /* WinNT support GetCompressedFileSize to determine allocate size */ + get_compressed = + (get_compressed_t) GetProcAddress(GetModuleHandle("kernel32"), + "GetCompressedFileSizeA"); + if (get_compressed) { + DWORD high, low; + low = get_compressed(filename, &high); + if (low != 0xFFFFFFFFlu || GetLastError() == NO_ERROR) { + return (((int64_t) high) << 32) + low; + } + } + + if (_stati64(filename, &st) < 0) { + return -1; + } + return st.st_size; +} + static int raw_create(const char *filename, QEMUOptionParameter *options) { int fd; @@ -257,6 +282,8 @@ static BlockDriver bdrv_file = { .bdrv_write = raw_write, .bdrv_truncate = raw_truncate, .bdrv_getlength = raw_getlength, + .bdrv_get_allocated_file_size + = raw_get_allocated_file_size, .create_options = raw_create_options, }; @@ -419,6 +446,8 @@ static BlockDriver bdrv_host_device = { .bdrv_read = raw_read, .bdrv_write = raw_write, .bdrv_getlength = raw_getlength, + .bdrv_get_allocated_file_size + = raw_get_allocated_file_size, }; static void bdrv_file_init(void) diff --git a/block/sheepdog.c b/block/sheepdog.c index 80d106c2b2..77a4de5100 100644 --- a/block/sheepdog.c +++ b/block/sheepdog.c @@ -1286,6 +1286,49 @@ static int do_sd_create(char *filename, int64_t vdi_size, return 0; } +static int sd_prealloc(const char *filename) +{ + BlockDriverState *bs = NULL; + uint32_t idx, max_idx; + int64_t vdi_size; + void *buf = qemu_mallocz(SD_DATA_OBJ_SIZE); + int ret; + + ret = bdrv_file_open(&bs, filename, BDRV_O_RDWR); + if (ret < 0) { + goto out; + } + + vdi_size = bdrv_getlength(bs); + if (vdi_size < 0) { + ret = vdi_size; + goto out; + } + max_idx = DIV_ROUND_UP(vdi_size, SD_DATA_OBJ_SIZE); + + for (idx = 0; idx < max_idx; idx++) { + /* + * The created image can be a cloned image, so we need to read + * a data from the source image. + */ + ret = bdrv_pread(bs, idx * SD_DATA_OBJ_SIZE, buf, SD_DATA_OBJ_SIZE); + if (ret < 0) { + goto out; + } + ret = bdrv_pwrite(bs, idx * SD_DATA_OBJ_SIZE, buf, SD_DATA_OBJ_SIZE); + if (ret < 0) { + goto out; + } + } +out: + if (bs) { + bdrv_delete(bs); + } + qemu_free(buf); + + return ret; +} + static int sd_create(const char *filename, QEMUOptionParameter *options) { int ret; @@ -1295,13 +1338,15 @@ static int sd_create(const char *filename, QEMUOptionParameter *options) BDRVSheepdogState s; char vdi[SD_MAX_VDI_LEN], tag[SD_MAX_VDI_TAG_LEN]; uint32_t snapid; + int prealloc = 0; + const char *vdiname; - strstart(filename, "sheepdog:", (const char **)&filename); + strstart(filename, "sheepdog:", &vdiname); memset(&s, 0, sizeof(s)); memset(vdi, 0, sizeof(vdi)); memset(tag, 0, sizeof(tag)); - if (parse_vdiname(&s, filename, vdi, &snapid, tag) < 0) { + if (parse_vdiname(&s, vdiname, vdi, &snapid, tag) < 0) { error_report("invalid filename"); return -EINVAL; } @@ -1311,6 +1356,16 @@ static int sd_create(const char *filename, QEMUOptionParameter *options) vdi_size = options->value.n; } else if (!strcmp(options->name, BLOCK_OPT_BACKING_FILE)) { backing_file = options->value.s; + } else if (!strcmp(options->name, BLOCK_OPT_PREALLOC)) { + if (!options->value.s || !strcmp(options->value.s, "off")) { + prealloc = 0; + } else if (!strcmp(options->value.s, "full")) { + prealloc = 1; + } else { + error_report("Invalid preallocation mode: '%s'", + options->value.s); + return -EINVAL; + } } options++; } @@ -1348,7 +1403,12 @@ static int sd_create(const char *filename, QEMUOptionParameter *options) bdrv_delete(bs); } - return do_sd_create((char *)vdi, vdi_size, base_vid, &vid, 0, s.addr, s.port); + ret = do_sd_create(vdi, vdi_size, base_vid, &vid, 0, s.addr, s.port); + if (!prealloc || ret) { + return ret; + } + + return sd_prealloc(filename); } static void sd_close(BlockDriverState *bs) @@ -1984,6 +2044,11 @@ static QEMUOptionParameter sd_create_options[] = { .type = OPT_STRING, .help = "File name of a base image" }, + { + .name = BLOCK_OPT_PREALLOC, + .type = OPT_STRING, + .help = "Preallocation mode (allowed values: off, full)" + }, { NULL } }; diff --git a/block/vmdk.c b/block/vmdk.c index 922b23d8f5..37478d2553 100644 --- a/block/vmdk.c +++ b/block/vmdk.c @@ -60,7 +60,12 @@ typedef struct { #define L2_CACHE_SIZE 16 -typedef struct BDRVVmdkState { +typedef struct VmdkExtent { + BlockDriverState *file; + bool flat; + int64_t sectors; + int64_t end_sector; + int64_t flat_start_offset; int64_t l1_table_offset; int64_t l1_backup_table_offset; uint32_t *l1_table; @@ -74,7 +79,15 @@ typedef struct BDRVVmdkState { uint32_t l2_cache_counts[L2_CACHE_SIZE]; unsigned int cluster_sectors; +} VmdkExtent; + +typedef struct BDRVVmdkState { + int desc_offset; + bool cid_updated; uint32_t parent_cid; + int num_extents; + /* Extent array with num_extents entries, ascend ordered by address */ + VmdkExtent *extents; } BDRVVmdkState; typedef struct VmdkMetaData { @@ -89,21 +102,77 @@ static int vmdk_probe(const uint8_t *buf, int buf_size, const char *filename) { uint32_t magic; - if (buf_size < 4) + if (buf_size < 4) { return 0; + } magic = be32_to_cpu(*(uint32_t *)buf); if (magic == VMDK3_MAGIC || - magic == VMDK4_MAGIC) + magic == VMDK4_MAGIC) { return 100; - else + } else { + const char *p = (const char *)buf; + const char *end = p + buf_size; + while (p < end) { + if (*p == '#') { + /* skip comment line */ + while (p < end && *p != '\n') { + p++; + } + p++; + continue; + } + if (*p == ' ') { + while (p < end && *p == ' ') { + p++; + } + /* skip '\r' if windows line endings used. */ + if (p < end && *p == '\r') { + p++; + } + /* only accept blank lines before 'version=' line */ + if (p == end || *p != '\n') { + return 0; + } + p++; + continue; + } + if (end - p >= strlen("version=X\n")) { + if (strncmp("version=1\n", p, strlen("version=1\n")) == 0 || + strncmp("version=2\n", p, strlen("version=2\n")) == 0) { + return 100; + } + } + if (end - p >= strlen("version=X\r\n")) { + if (strncmp("version=1\r\n", p, strlen("version=1\r\n")) == 0 || + strncmp("version=2\r\n", p, strlen("version=2\r\n")) == 0) { + return 100; + } + } + return 0; + } return 0; + } } #define CHECK_CID 1 #define SECTOR_SIZE 512 -#define DESC_SIZE 20*SECTOR_SIZE // 20 sectors of 512 bytes each -#define HEADER_SIZE 512 // first sector of 512 bytes +#define DESC_SIZE (20 * SECTOR_SIZE) /* 20 sectors of 512 bytes each */ +#define BUF_SIZE 4096 +#define HEADER_SIZE 512 /* first sector of 512 bytes */ + +static void vmdk_free_extents(BlockDriverState *bs) +{ + int i; + BDRVVmdkState *s = bs->opaque; + + for (i = 0; i < s->num_extents; i++) { + qemu_free(s->extents[i].l1_table); + qemu_free(s->extents[i].l2_cache); + qemu_free(s->extents[i].l1_backup_table); + } + qemu_free(s->extents); +} static uint32_t vmdk_read_cid(BlockDriverState *bs, int parent) { @@ -111,10 +180,11 @@ static uint32_t vmdk_read_cid(BlockDriverState *bs, int parent) uint32_t cid; const char *p_name, *cid_str; size_t cid_str_size; + BDRVVmdkState *s = bs->opaque; - /* the descriptor offset = 0x200 */ - if (bdrv_pread(bs->file, 0x200, desc, DESC_SIZE) != DESC_SIZE) + if (bdrv_pread(bs->file, s->desc_offset, desc, DESC_SIZE) != DESC_SIZE) { return 0; + } if (parent) { cid_str = "parentCID"; @@ -124,9 +194,10 @@ static uint32_t vmdk_read_cid(BlockDriverState *bs, int parent) cid_str_size = sizeof("CID"); } - if ((p_name = strstr(desc,cid_str)) != NULL) { + p_name = strstr(desc, cid_str); + if (p_name != NULL) { p_name += cid_str_size; - sscanf(p_name,"%x",&cid); + sscanf(p_name, "%x", &cid); } return cid; @@ -136,21 +207,25 @@ static int vmdk_write_cid(BlockDriverState *bs, uint32_t cid) { char desc[DESC_SIZE], tmp_desc[DESC_SIZE]; char *p_name, *tmp_str; + BDRVVmdkState *s = bs->opaque; - /* the descriptor offset = 0x200 */ - if (bdrv_pread(bs->file, 0x200, desc, DESC_SIZE) != DESC_SIZE) - return -1; + memset(desc, 0, sizeof(desc)); + if (bdrv_pread(bs->file, s->desc_offset, desc, DESC_SIZE) != DESC_SIZE) { + return -EIO; + } - tmp_str = strstr(desc,"parentCID"); + tmp_str = strstr(desc, "parentCID"); pstrcpy(tmp_desc, sizeof(tmp_desc), tmp_str); - if ((p_name = strstr(desc,"CID")) != NULL) { + p_name = strstr(desc, "CID"); + if (p_name != NULL) { p_name += sizeof("CID"); snprintf(p_name, sizeof(desc) - (p_name - desc), "%x\n", cid); pstrcat(desc, sizeof(desc), tmp_desc); } - if (bdrv_pwrite_sync(bs->file, 0x200, desc, DESC_SIZE) < 0) - return -1; + if (bdrv_pwrite_sync(bs->file, s->desc_offset, desc, DESC_SIZE) < 0) { + return -EIO; + } return 0; } @@ -162,302 +237,387 @@ static int vmdk_is_cid_valid(BlockDriverState *bs) uint32_t cur_pcid; if (p_bs) { - cur_pcid = vmdk_read_cid(p_bs,0); - if (s->parent_cid != cur_pcid) - // CID not valid + cur_pcid = vmdk_read_cid(p_bs, 0); + if (s->parent_cid != cur_pcid) { + /* CID not valid */ return 0; + } } #endif - // CID valid + /* CID valid */ return 1; } -static int vmdk_snapshot_create(const char *filename, const char *backing_file) +static int vmdk_parent_open(BlockDriverState *bs) { - int snp_fd, p_fd; - int ret; - uint32_t p_cid; - char *p_name, *gd_buf, *rgd_buf; - const char *real_filename, *temp_str; - VMDK4Header header; - uint32_t gde_entries, gd_size; - int64_t gd_offset, rgd_offset, capacity, gt_size; - char p_desc[DESC_SIZE], s_desc[DESC_SIZE], hdr[HEADER_SIZE]; - static const char desc_template[] = - "# Disk DescriptorFile\n" - "version=1\n" - "CID=%x\n" - "parentCID=%x\n" - "createType=\"monolithicSparse\"\n" - "parentFileNameHint=\"%s\"\n" - "\n" - "# Extent description\n" - "RW %u SPARSE \"%s\"\n" - "\n" - "# The Disk Data Base \n" - "#DDB\n" - "\n"; - - snp_fd = open(filename, O_RDWR | O_CREAT | O_TRUNC | O_BINARY | O_LARGEFILE, 0644); - if (snp_fd < 0) - return -errno; - p_fd = open(backing_file, O_RDONLY | O_BINARY | O_LARGEFILE); - if (p_fd < 0) { - close(snp_fd); - return -errno; - } + char *p_name; + char desc[DESC_SIZE + 1]; + BDRVVmdkState *s = bs->opaque; - /* read the header */ - if (lseek(p_fd, 0x0, SEEK_SET) == -1) { - ret = -errno; - goto fail; - } - if (read(p_fd, hdr, HEADER_SIZE) != HEADER_SIZE) { - ret = -errno; - goto fail; + desc[DESC_SIZE] = '\0'; + if (bdrv_pread(bs->file, s->desc_offset, desc, DESC_SIZE) != DESC_SIZE) { + return -1; } - /* write the header */ - if (lseek(snp_fd, 0x0, SEEK_SET) == -1) { - ret = -errno; - goto fail; - } - if (write(snp_fd, hdr, HEADER_SIZE) == -1) { - ret = -errno; - goto fail; + p_name = strstr(desc, "parentFileNameHint"); + if (p_name != NULL) { + char *end_name; + + p_name += sizeof("parentFileNameHint") + 1; + end_name = strchr(p_name, '\"'); + if (end_name == NULL) { + return -1; + } + if ((end_name - p_name) > sizeof(bs->backing_file) - 1) { + return -1; + } + + pstrcpy(bs->backing_file, end_name - p_name + 1, p_name); } - memset(&header, 0, sizeof(header)); - memcpy(&header,&hdr[4], sizeof(header)); // skip the VMDK4_MAGIC + return 0; +} - if (ftruncate(snp_fd, header.grain_offset << 9)) { - ret = -errno; - goto fail; +/* Create and append extent to the extent array. Return the added VmdkExtent + * address. return NULL if allocation failed. */ +static VmdkExtent *vmdk_add_extent(BlockDriverState *bs, + BlockDriverState *file, bool flat, int64_t sectors, + int64_t l1_offset, int64_t l1_backup_offset, + uint32_t l1_size, + int l2_size, unsigned int cluster_sectors) +{ + VmdkExtent *extent; + BDRVVmdkState *s = bs->opaque; + + s->extents = qemu_realloc(s->extents, + (s->num_extents + 1) * sizeof(VmdkExtent)); + extent = &s->extents[s->num_extents]; + s->num_extents++; + + memset(extent, 0, sizeof(VmdkExtent)); + extent->file = file; + extent->flat = flat; + extent->sectors = sectors; + extent->l1_table_offset = l1_offset; + extent->l1_backup_table_offset = l1_backup_offset; + extent->l1_size = l1_size; + extent->l1_entry_sectors = l2_size * cluster_sectors; + extent->l2_size = l2_size; + extent->cluster_sectors = cluster_sectors; + + if (s->num_extents > 1) { + extent->end_sector = (*(extent - 1)).end_sector + extent->sectors; + } else { + extent->end_sector = extent->sectors; } - /* the descriptor offset = 0x200 */ - if (lseek(p_fd, 0x200, SEEK_SET) == -1) { - ret = -errno; - goto fail; + bs->total_sectors = extent->end_sector; + return extent; +} + +static int vmdk_init_tables(BlockDriverState *bs, VmdkExtent *extent) +{ + int ret; + int l1_size, i; + + /* read the L1 table */ + l1_size = extent->l1_size * sizeof(uint32_t); + extent->l1_table = qemu_malloc(l1_size); + ret = bdrv_pread(extent->file, + extent->l1_table_offset, + extent->l1_table, + l1_size); + if (ret < 0) { + goto fail_l1; } - if (read(p_fd, p_desc, DESC_SIZE) != DESC_SIZE) { - ret = -errno; - goto fail; + for (i = 0; i < extent->l1_size; i++) { + le32_to_cpus(&extent->l1_table[i]); } - if ((p_name = strstr(p_desc,"CID")) != NULL) { - p_name += sizeof("CID"); - sscanf(p_name,"%x",&p_cid); + if (extent->l1_backup_table_offset) { + extent->l1_backup_table = qemu_malloc(l1_size); + ret = bdrv_pread(extent->file, + extent->l1_backup_table_offset, + extent->l1_backup_table, + l1_size); + if (ret < 0) { + goto fail_l1b; + } + for (i = 0; i < extent->l1_size; i++) { + le32_to_cpus(&extent->l1_backup_table[i]); + } } - real_filename = filename; - if ((temp_str = strrchr(real_filename, '\\')) != NULL) - real_filename = temp_str + 1; - if ((temp_str = strrchr(real_filename, '/')) != NULL) - real_filename = temp_str + 1; - if ((temp_str = strrchr(real_filename, ':')) != NULL) - real_filename = temp_str + 1; + extent->l2_cache = + qemu_malloc(extent->l2_size * L2_CACHE_SIZE * sizeof(uint32_t)); + return 0; + fail_l1b: + qemu_free(extent->l1_backup_table); + fail_l1: + qemu_free(extent->l1_table); + return ret; +} - snprintf(s_desc, sizeof(s_desc), desc_template, p_cid, p_cid, backing_file, - (uint32_t)header.capacity, real_filename); +static int vmdk_open_vmdk3(BlockDriverState *bs, int flags) +{ + int ret; + uint32_t magic; + VMDK3Header header; + BDRVVmdkState *s = bs->opaque; + VmdkExtent *extent; - /* write the descriptor */ - if (lseek(snp_fd, 0x200, SEEK_SET) == -1) { - ret = -errno; + s->desc_offset = 0x200; + ret = bdrv_pread(bs->file, sizeof(magic), &header, sizeof(header)); + if (ret < 0) { goto fail; } - if (write(snp_fd, s_desc, strlen(s_desc)) == -1) { - ret = -errno; + extent = vmdk_add_extent(bs, + bs->file, false, + le32_to_cpu(header.disk_sectors), + le32_to_cpu(header.l1dir_offset) << 9, + 0, 1 << 6, 1 << 9, + le32_to_cpu(header.granularity)); + ret = vmdk_init_tables(bs, extent); + if (ret) { + /* vmdk_init_tables cleans up on fail, so only free allocation of + * vmdk_add_extent here. */ goto fail; } + return 0; + fail: + vmdk_free_extents(bs); + return ret; +} - gd_offset = header.gd_offset * SECTOR_SIZE; // offset of GD table - rgd_offset = header.rgd_offset * SECTOR_SIZE; // offset of RGD table - capacity = header.capacity * SECTOR_SIZE; // Extent size - /* - * Each GDE span 32M disk, means: - * 512 GTE per GT, each GTE points to grain - */ - gt_size = (int64_t)header.num_gtes_per_gte * header.granularity * SECTOR_SIZE; - if (!gt_size) { - ret = -EINVAL; - goto fail; - } - gde_entries = (uint32_t)(capacity / gt_size); // number of gde/rgde - gd_size = gde_entries * sizeof(uint32_t); +static int vmdk_open_vmdk4(BlockDriverState *bs, int flags) +{ + int ret; + uint32_t magic; + uint32_t l1_size, l1_entry_sectors; + VMDK4Header header; + BDRVVmdkState *s = bs->opaque; + VmdkExtent *extent; - /* write RGD */ - rgd_buf = qemu_malloc(gd_size); - if (lseek(p_fd, rgd_offset, SEEK_SET) == -1) { - ret = -errno; - goto fail_rgd; + s->desc_offset = 0x200; + ret = bdrv_pread(bs->file, sizeof(magic), &header, sizeof(header)); + if (ret < 0) { + goto fail; } - if (read(p_fd, rgd_buf, gd_size) != gd_size) { - ret = -errno; - goto fail_rgd; + l1_entry_sectors = le32_to_cpu(header.num_gtes_per_gte) + * le64_to_cpu(header.granularity); + l1_size = (le64_to_cpu(header.capacity) + l1_entry_sectors - 1) + / l1_entry_sectors; + extent = vmdk_add_extent(bs, bs->file, false, + le64_to_cpu(header.capacity), + le64_to_cpu(header.gd_offset) << 9, + le64_to_cpu(header.rgd_offset) << 9, + l1_size, + le32_to_cpu(header.num_gtes_per_gte), + le64_to_cpu(header.granularity)); + if (extent->l1_entry_sectors <= 0) { + ret = -EINVAL; + goto fail; } - if (lseek(snp_fd, rgd_offset, SEEK_SET) == -1) { - ret = -errno; - goto fail_rgd; + /* try to open parent images, if exist */ + ret = vmdk_parent_open(bs); + if (ret) { + goto fail; } - if (write(snp_fd, rgd_buf, gd_size) == -1) { - ret = -errno; - goto fail_rgd; + s->parent_cid = vmdk_read_cid(bs, 1); + ret = vmdk_init_tables(bs, extent); + if (ret) { + goto fail; } + return 0; + fail: + vmdk_free_extents(bs); + return ret; +} - /* write GD */ - gd_buf = qemu_malloc(gd_size); - if (lseek(p_fd, gd_offset, SEEK_SET) == -1) { - ret = -errno; - goto fail_gd; +/* find an option value out of descriptor file */ +static int vmdk_parse_description(const char *desc, const char *opt_name, + char *buf, int buf_size) +{ + char *opt_pos, *opt_end; + const char *end = desc + strlen(desc); + + opt_pos = strstr(desc, opt_name); + if (!opt_pos) { + return -1; } - if (read(p_fd, gd_buf, gd_size) != gd_size) { - ret = -errno; - goto fail_gd; + /* Skip "=\"" following opt_name */ + opt_pos += strlen(opt_name) + 2; + if (opt_pos >= end) { + return -1; } - if (lseek(snp_fd, gd_offset, SEEK_SET) == -1) { - ret = -errno; - goto fail_gd; + opt_end = opt_pos; + while (opt_end < end && *opt_end != '"') { + opt_end++; } - if (write(snp_fd, gd_buf, gd_size) == -1) { - ret = -errno; - goto fail_gd; + if (opt_end == end || buf_size < opt_end - opt_pos + 1) { + return -1; } - ret = 0; - -fail_gd: - qemu_free(gd_buf); -fail_rgd: - qemu_free(rgd_buf); -fail: - close(p_fd); - close(snp_fd); - return ret; + pstrcpy(buf, opt_end - opt_pos + 1, opt_pos); + return 0; } -static int vmdk_parent_open(BlockDriverState *bs) +static int vmdk_parse_extents(const char *desc, BlockDriverState *bs, + const char *desc_file_path) { - char *p_name; - char desc[DESC_SIZE]; + int ret; + char access[11]; + char type[11]; + char fname[512]; + const char *p = desc; + int64_t sectors = 0; + int64_t flat_offset; + + while (*p) { + /* parse extent line: + * RW [size in sectors] FLAT "file-name.vmdk" OFFSET + * or + * RW [size in sectors] SPARSE "file-name.vmdk" + */ + flat_offset = -1; + ret = sscanf(p, "%10s %" SCNd64 " %10s %511s %" SCNd64, + access, §ors, type, fname, &flat_offset); + if (ret < 4 || strcmp(access, "RW")) { + goto next_line; + } else if (!strcmp(type, "FLAT")) { + if (ret != 5 || flat_offset < 0) { + return -EINVAL; + } + } else if (ret != 4) { + return -EINVAL; + } - /* the descriptor offset = 0x200 */ - if (bdrv_pread(bs->file, 0x200, desc, DESC_SIZE) != DESC_SIZE) - return -1; + /* trim the quotation marks around */ + if (fname[0] == '"') { + memmove(fname, fname + 1, strlen(fname)); + if (strlen(fname) <= 1 || fname[strlen(fname) - 1] != '"') { + return -EINVAL; + } + fname[strlen(fname) - 1] = '\0'; + } + if (sectors <= 0 || + (strcmp(type, "FLAT") && strcmp(type, "SPARSE")) || + (strcmp(access, "RW"))) { + goto next_line; + } - if ((p_name = strstr(desc,"parentFileNameHint")) != NULL) { - char *end_name; + /* save to extents array */ + if (!strcmp(type, "FLAT")) { + /* FLAT extent */ + char extent_path[PATH_MAX]; + BlockDriverState *extent_file; + VmdkExtent *extent; + + path_combine(extent_path, sizeof(extent_path), + desc_file_path, fname); + ret = bdrv_file_open(&extent_file, extent_path, bs->open_flags); + if (ret) { + return ret; + } + extent = vmdk_add_extent(bs, extent_file, true, sectors, + 0, 0, 0, 0, sectors); + extent->flat_start_offset = flat_offset; + } else { + /* SPARSE extent, not supported for now */ + fprintf(stderr, + "VMDK: Not supported extent type \"%s\""".\n", type); + return -ENOTSUP; + } +next_line: + /* move to next line */ + while (*p && *p != '\n') { + p++; + } + p++; + } + return 0; +} - p_name += sizeof("parentFileNameHint") + 1; - if ((end_name = strchr(p_name,'\"')) == NULL) - return -1; - if ((end_name - p_name) > sizeof (bs->backing_file) - 1) - return -1; +static int vmdk_open_desc_file(BlockDriverState *bs, int flags) +{ + int ret; + char buf[2048]; + char ct[128]; + BDRVVmdkState *s = bs->opaque; - pstrcpy(bs->backing_file, end_name - p_name + 1, p_name); + ret = bdrv_pread(bs->file, 0, buf, sizeof(buf)); + if (ret < 0) { + return ret; + } + buf[2047] = '\0'; + if (vmdk_parse_description(buf, "createType", ct, sizeof(ct))) { + return -EINVAL; + } + if (strcmp(ct, "monolithicFlat")) { + fprintf(stderr, + "VMDK: Not supported image type \"%s\""".\n", ct); + return -ENOTSUP; + } + s->desc_offset = 0; + ret = vmdk_parse_extents(buf, bs, bs->file->filename); + if (ret) { + return ret; } + /* try to open parent images, if exist */ + if (vmdk_parent_open(bs)) { + qemu_free(s->extents); + return -EINVAL; + } + s->parent_cid = vmdk_read_cid(bs, 1); return 0; } static int vmdk_open(BlockDriverState *bs, int flags) { - BDRVVmdkState *s = bs->opaque; uint32_t magic; - int l1_size, i; - if (bdrv_pread(bs->file, 0, &magic, sizeof(magic)) != sizeof(magic)) - goto fail; + if (bdrv_pread(bs->file, 0, &magic, sizeof(magic)) != sizeof(magic)) { + return -EIO; + } magic = be32_to_cpu(magic); if (magic == VMDK3_MAGIC) { - VMDK3Header header; - - if (bdrv_pread(bs->file, sizeof(magic), &header, sizeof(header)) != sizeof(header)) - goto fail; - s->cluster_sectors = le32_to_cpu(header.granularity); - s->l2_size = 1 << 9; - s->l1_size = 1 << 6; - bs->total_sectors = le32_to_cpu(header.disk_sectors); - s->l1_table_offset = le32_to_cpu(header.l1dir_offset) << 9; - s->l1_backup_table_offset = 0; - s->l1_entry_sectors = s->l2_size * s->cluster_sectors; + return vmdk_open_vmdk3(bs, flags); } else if (magic == VMDK4_MAGIC) { - VMDK4Header header; - - if (bdrv_pread(bs->file, sizeof(magic), &header, sizeof(header)) != sizeof(header)) - goto fail; - bs->total_sectors = le64_to_cpu(header.capacity); - s->cluster_sectors = le64_to_cpu(header.granularity); - s->l2_size = le32_to_cpu(header.num_gtes_per_gte); - s->l1_entry_sectors = s->l2_size * s->cluster_sectors; - if (s->l1_entry_sectors <= 0) - goto fail; - s->l1_size = (bs->total_sectors + s->l1_entry_sectors - 1) - / s->l1_entry_sectors; - s->l1_table_offset = le64_to_cpu(header.rgd_offset) << 9; - s->l1_backup_table_offset = le64_to_cpu(header.gd_offset) << 9; - - // try to open parent images, if exist - if (vmdk_parent_open(bs) != 0) - goto fail; - // write the CID once after the image creation - s->parent_cid = vmdk_read_cid(bs,1); + return vmdk_open_vmdk4(bs, flags); } else { - goto fail; - } - - /* read the L1 table */ - l1_size = s->l1_size * sizeof(uint32_t); - s->l1_table = qemu_malloc(l1_size); - if (bdrv_pread(bs->file, s->l1_table_offset, s->l1_table, l1_size) != l1_size) - goto fail; - for(i = 0; i < s->l1_size; i++) { - le32_to_cpus(&s->l1_table[i]); - } - - if (s->l1_backup_table_offset) { - s->l1_backup_table = qemu_malloc(l1_size); - if (bdrv_pread(bs->file, s->l1_backup_table_offset, s->l1_backup_table, l1_size) != l1_size) - goto fail; - for(i = 0; i < s->l1_size; i++) { - le32_to_cpus(&s->l1_backup_table[i]); - } + return vmdk_open_desc_file(bs, flags); } - - s->l2_cache = qemu_malloc(s->l2_size * L2_CACHE_SIZE * sizeof(uint32_t)); - return 0; - fail: - qemu_free(s->l1_backup_table); - qemu_free(s->l1_table); - qemu_free(s->l2_cache); - return -1; } -static uint64_t get_cluster_offset(BlockDriverState *bs, VmdkMetaData *m_data, - uint64_t offset, int allocate); - -static int get_whole_cluster(BlockDriverState *bs, uint64_t cluster_offset, - uint64_t offset, int allocate) +static int get_whole_cluster(BlockDriverState *bs, + VmdkExtent *extent, + uint64_t cluster_offset, + uint64_t offset, + bool allocate) { - BDRVVmdkState *s = bs->opaque; - uint8_t whole_grain[s->cluster_sectors*512]; // 128 sectors * 512 bytes each = grain size 64KB + /* 128 sectors * 512 bytes each = grain size 64KB */ + uint8_t whole_grain[extent->cluster_sectors * 512]; - // we will be here if it's first write on non-exist grain(cluster). - // try to read from parent image, if exist + /* we will be here if it's first write on non-exist grain(cluster). + * try to read from parent image, if exist */ if (bs->backing_hd) { int ret; - if (!vmdk_is_cid_valid(bs)) + if (!vmdk_is_cid_valid(bs)) { return -1; + } + /* floor offset to cluster */ + offset -= offset % (extent->cluster_sectors * 512); ret = bdrv_read(bs->backing_hd, offset >> 9, whole_grain, - s->cluster_sectors); + extent->cluster_sectors); if (ret < 0) { return -1; } - //Write grain only into the active image - ret = bdrv_write(bs->file, cluster_offset, whole_grain, - s->cluster_sectors); + /* Write grain only into the active image */ + ret = bdrv_write(extent->file, cluster_offset, whole_grain, + extent->cluster_sectors); if (ret < 0) { return -1; } @@ -465,85 +625,112 @@ static int get_whole_cluster(BlockDriverState *bs, uint64_t cluster_offset, return 0; } -static int vmdk_L2update(BlockDriverState *bs, VmdkMetaData *m_data) +static int vmdk_L2update(VmdkExtent *extent, VmdkMetaData *m_data) { - BDRVVmdkState *s = bs->opaque; - /* update L2 table */ - if (bdrv_pwrite_sync(bs->file, ((int64_t)m_data->l2_offset * 512) + (m_data->l2_index * sizeof(m_data->offset)), - &(m_data->offset), sizeof(m_data->offset)) < 0) + if (bdrv_pwrite_sync( + extent->file, + ((int64_t)m_data->l2_offset * 512) + + (m_data->l2_index * sizeof(m_data->offset)), + &(m_data->offset), + sizeof(m_data->offset) + ) < 0) { return -1; + } /* update backup L2 table */ - if (s->l1_backup_table_offset != 0) { - m_data->l2_offset = s->l1_backup_table[m_data->l1_index]; - if (bdrv_pwrite_sync(bs->file, ((int64_t)m_data->l2_offset * 512) + (m_data->l2_index * sizeof(m_data->offset)), - &(m_data->offset), sizeof(m_data->offset)) < 0) + if (extent->l1_backup_table_offset != 0) { + m_data->l2_offset = extent->l1_backup_table[m_data->l1_index]; + if (bdrv_pwrite_sync( + extent->file, + ((int64_t)m_data->l2_offset * 512) + + (m_data->l2_index * sizeof(m_data->offset)), + &(m_data->offset), sizeof(m_data->offset) + ) < 0) { return -1; + } } return 0; } -static uint64_t get_cluster_offset(BlockDriverState *bs, VmdkMetaData *m_data, - uint64_t offset, int allocate) +static int get_cluster_offset(BlockDriverState *bs, + VmdkExtent *extent, + VmdkMetaData *m_data, + uint64_t offset, + int allocate, + uint64_t *cluster_offset) { - BDRVVmdkState *s = bs->opaque; unsigned int l1_index, l2_offset, l2_index; int min_index, i, j; uint32_t min_count, *l2_table, tmp = 0; - uint64_t cluster_offset; - if (m_data) + if (m_data) { m_data->valid = 0; - - l1_index = (offset >> 9) / s->l1_entry_sectors; - if (l1_index >= s->l1_size) - return 0; - l2_offset = s->l1_table[l1_index]; - if (!l2_offset) + } + if (extent->flat) { + *cluster_offset = extent->flat_start_offset; return 0; - for(i = 0; i < L2_CACHE_SIZE; i++) { - if (l2_offset == s->l2_cache_offsets[i]) { + } + + l1_index = (offset >> 9) / extent->l1_entry_sectors; + if (l1_index >= extent->l1_size) { + return -1; + } + l2_offset = extent->l1_table[l1_index]; + if (!l2_offset) { + return -1; + } + for (i = 0; i < L2_CACHE_SIZE; i++) { + if (l2_offset == extent->l2_cache_offsets[i]) { /* increment the hit count */ - if (++s->l2_cache_counts[i] == 0xffffffff) { - for(j = 0; j < L2_CACHE_SIZE; j++) { - s->l2_cache_counts[j] >>= 1; + if (++extent->l2_cache_counts[i] == 0xffffffff) { + for (j = 0; j < L2_CACHE_SIZE; j++) { + extent->l2_cache_counts[j] >>= 1; } } - l2_table = s->l2_cache + (i * s->l2_size); + l2_table = extent->l2_cache + (i * extent->l2_size); goto found; } } /* not found: load a new entry in the least used one */ min_index = 0; min_count = 0xffffffff; - for(i = 0; i < L2_CACHE_SIZE; i++) { - if (s->l2_cache_counts[i] < min_count) { - min_count = s->l2_cache_counts[i]; + for (i = 0; i < L2_CACHE_SIZE; i++) { + if (extent->l2_cache_counts[i] < min_count) { + min_count = extent->l2_cache_counts[i]; min_index = i; } } - l2_table = s->l2_cache + (min_index * s->l2_size); - if (bdrv_pread(bs->file, (int64_t)l2_offset * 512, l2_table, s->l2_size * sizeof(uint32_t)) != - s->l2_size * sizeof(uint32_t)) - return 0; + l2_table = extent->l2_cache + (min_index * extent->l2_size); + if (bdrv_pread( + extent->file, + (int64_t)l2_offset * 512, + l2_table, + extent->l2_size * sizeof(uint32_t) + ) != extent->l2_size * sizeof(uint32_t)) { + return -1; + } - s->l2_cache_offsets[min_index] = l2_offset; - s->l2_cache_counts[min_index] = 1; + extent->l2_cache_offsets[min_index] = l2_offset; + extent->l2_cache_counts[min_index] = 1; found: - l2_index = ((offset >> 9) / s->cluster_sectors) % s->l2_size; - cluster_offset = le32_to_cpu(l2_table[l2_index]); + l2_index = ((offset >> 9) / extent->cluster_sectors) % extent->l2_size; + *cluster_offset = le32_to_cpu(l2_table[l2_index]); - if (!cluster_offset) { - if (!allocate) - return 0; + if (!*cluster_offset) { + if (!allocate) { + return -1; + } - // Avoid the L2 tables update for the images that have snapshots. - cluster_offset = bdrv_getlength(bs->file); - bdrv_truncate(bs->file, cluster_offset + (s->cluster_sectors << 9)); + /* Avoid the L2 tables update for the images that have snapshots. */ + *cluster_offset = bdrv_getlength(extent->file); + bdrv_truncate( + extent->file, + *cluster_offset + (extent->cluster_sectors << 9) + ); - cluster_offset >>= 9; - tmp = cpu_to_le32(cluster_offset); + *cluster_offset >>= 9; + tmp = cpu_to_le32(*cluster_offset); l2_table[l2_index] = tmp; /* First of all we write grain itself, to avoid race condition @@ -551,8 +738,10 @@ static uint64_t get_cluster_offset(BlockDriverState *bs, VmdkMetaData *m_data, * This problem may occur because of insufficient space on host disk * or inappropriate VM shutdown. */ - if (get_whole_cluster(bs, cluster_offset, offset, allocate) == -1) - return 0; + if (get_whole_cluster( + bs, extent, *cluster_offset, offset, allocate) == -1) { + return -1; + } if (m_data) { m_data->offset = tmp; @@ -562,53 +751,95 @@ static uint64_t get_cluster_offset(BlockDriverState *bs, VmdkMetaData *m_data, m_data->valid = 1; } } - cluster_offset <<= 9; - return cluster_offset; + *cluster_offset <<= 9; + return 0; +} + +static VmdkExtent *find_extent(BDRVVmdkState *s, + int64_t sector_num, VmdkExtent *start_hint) +{ + VmdkExtent *extent = start_hint; + + if (!extent) { + extent = &s->extents[0]; + } + while (extent < &s->extents[s->num_extents]) { + if (sector_num < extent->end_sector) { + return extent; + } + extent++; + } + return NULL; } static int vmdk_is_allocated(BlockDriverState *bs, int64_t sector_num, int nb_sectors, int *pnum) { BDRVVmdkState *s = bs->opaque; - int index_in_cluster, n; - uint64_t cluster_offset; + int64_t index_in_cluster, n, ret; + uint64_t offset; + VmdkExtent *extent; - cluster_offset = get_cluster_offset(bs, NULL, sector_num << 9, 0); - index_in_cluster = sector_num % s->cluster_sectors; - n = s->cluster_sectors - index_in_cluster; - if (n > nb_sectors) + extent = find_extent(s, sector_num, NULL); + if (!extent) { + return 0; + } + ret = get_cluster_offset(bs, extent, NULL, + sector_num * 512, 0, &offset); + /* get_cluster_offset returning 0 means success */ + ret = !ret; + + index_in_cluster = sector_num % extent->cluster_sectors; + n = extent->cluster_sectors - index_in_cluster; + if (n > nb_sectors) { n = nb_sectors; + } *pnum = n; - return (cluster_offset != 0); + return ret; } static int vmdk_read(BlockDriverState *bs, int64_t sector_num, uint8_t *buf, int nb_sectors) { BDRVVmdkState *s = bs->opaque; - int index_in_cluster, n, ret; + int ret; + uint64_t n, index_in_cluster; + VmdkExtent *extent = NULL; uint64_t cluster_offset; while (nb_sectors > 0) { - cluster_offset = get_cluster_offset(bs, NULL, sector_num << 9, 0); - index_in_cluster = sector_num % s->cluster_sectors; - n = s->cluster_sectors - index_in_cluster; - if (n > nb_sectors) + extent = find_extent(s, sector_num, extent); + if (!extent) { + return -EIO; + } + ret = get_cluster_offset( + bs, extent, NULL, + sector_num << 9, 0, &cluster_offset); + index_in_cluster = sector_num % extent->cluster_sectors; + n = extent->cluster_sectors - index_in_cluster; + if (n > nb_sectors) { n = nb_sectors; - if (!cluster_offset) { - // try to read from parent image, if exist + } + if (ret) { + /* if not allocated, try to read from parent image, if exist */ if (bs->backing_hd) { - if (!vmdk_is_cid_valid(bs)) - return -1; + if (!vmdk_is_cid_valid(bs)) { + return -EINVAL; + } ret = bdrv_read(bs->backing_hd, sector_num, buf, n); - if (ret < 0) - return -1; + if (ret < 0) { + return ret; + } } else { memset(buf, 0, 512 * n); } } else { - if(bdrv_pread(bs->file, cluster_offset + index_in_cluster * 512, buf, n * 512) != n * 512) - return -1; + ret = bdrv_pread(extent->file, + cluster_offset + index_in_cluster * 512, + buf, n * 512); + if (ret < 0) { + return ret; + } } nb_sectors -= n; sector_num += n; @@ -621,110 +852,101 @@ static int vmdk_write(BlockDriverState *bs, int64_t sector_num, const uint8_t *buf, int nb_sectors) { BDRVVmdkState *s = bs->opaque; - VmdkMetaData m_data; - int index_in_cluster, n; + VmdkExtent *extent = NULL; + int n, ret; + int64_t index_in_cluster; uint64_t cluster_offset; - static int cid_update = 0; + VmdkMetaData m_data; if (sector_num > bs->total_sectors) { fprintf(stderr, "(VMDK) Wrong offset: sector_num=0x%" PRIx64 " total_sectors=0x%" PRIx64 "\n", sector_num, bs->total_sectors); - return -1; + return -EIO; } while (nb_sectors > 0) { - index_in_cluster = sector_num & (s->cluster_sectors - 1); - n = s->cluster_sectors - index_in_cluster; - if (n > nb_sectors) + extent = find_extent(s, sector_num, extent); + if (!extent) { + return -EIO; + } + ret = get_cluster_offset( + bs, + extent, + &m_data, + sector_num << 9, 1, + &cluster_offset); + if (ret) { + return -EINVAL; + } + index_in_cluster = sector_num % extent->cluster_sectors; + n = extent->cluster_sectors - index_in_cluster; + if (n > nb_sectors) { n = nb_sectors; - cluster_offset = get_cluster_offset(bs, &m_data, sector_num << 9, 1); - if (!cluster_offset) - return -1; + } - if (bdrv_pwrite(bs->file, cluster_offset + index_in_cluster * 512, buf, n * 512) != n * 512) - return -1; + ret = bdrv_pwrite(extent->file, + cluster_offset + index_in_cluster * 512, + buf, + n * 512); + if (ret < 0) { + return ret; + } if (m_data.valid) { /* update L2 tables */ - if (vmdk_L2update(bs, &m_data) == -1) - return -1; + if (vmdk_L2update(extent, &m_data) == -1) { + return -EIO; + } } nb_sectors -= n; sector_num += n; buf += n * 512; - // update CID on the first write every time the virtual disk is opened - if (!cid_update) { + /* update CID on the first write every time the virtual disk is + * opened */ + if (!s->cid_updated) { vmdk_write_cid(bs, time(NULL)); - cid_update++; + s->cid_updated = true; } } return 0; } -static int vmdk_create(const char *filename, QEMUOptionParameter *options) + +static int vmdk_create_extent(const char *filename, int64_t filesize, bool flat) { - int fd, i; + int ret, i; + int fd = 0; VMDK4Header header; uint32_t tmp, magic, grains, gd_size, gt_size, gt_count; - static const char desc_template[] = - "# Disk DescriptorFile\n" - "version=1\n" - "CID=%x\n" - "parentCID=ffffffff\n" - "createType=\"monolithicSparse\"\n" - "\n" - "# Extent description\n" - "RW %" PRId64 " SPARSE \"%s\"\n" - "\n" - "# The Disk Data Base \n" - "#DDB\n" - "\n" - "ddb.virtualHWVersion = \"%d\"\n" - "ddb.geometry.cylinders = \"%" PRId64 "\"\n" - "ddb.geometry.heads = \"16\"\n" - "ddb.geometry.sectors = \"63\"\n" - "ddb.adapterType = \"ide\"\n"; - char desc[1024]; - const char *real_filename, *temp_str; - int64_t total_size = 0; - const char *backing_file = NULL; - int flags = 0; - int ret; - // Read out options - while (options && options->name) { - if (!strcmp(options->name, BLOCK_OPT_SIZE)) { - total_size = options->value.n / 512; - } else if (!strcmp(options->name, BLOCK_OPT_BACKING_FILE)) { - backing_file = options->value.s; - } else if (!strcmp(options->name, BLOCK_OPT_COMPAT6)) { - flags |= options->value.n ? BLOCK_FLAG_COMPAT6: 0; - } - options++; + fd = open( + filename, + O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_LARGEFILE, + 0644); + if (fd < 0) { + return -errno; } - - /* XXX: add support for backing file */ - if (backing_file) { - return vmdk_snapshot_create(filename, backing_file); + if (flat) { + ret = ftruncate(fd, filesize); + if (ret < 0) { + ret = -errno; + } + goto exit; } - - fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_LARGEFILE, - 0644); - if (fd < 0) - return -errno; magic = cpu_to_be32(VMDK4_MAGIC); memset(&header, 0, sizeof(header)); header.version = 1; header.flags = 3; /* ?? */ - header.capacity = total_size; + header.capacity = filesize / 512; header.granularity = 128; header.num_gtes_per_gte = 512; - grains = (total_size + header.granularity - 1) / header.granularity; + grains = (filesize / 512 + header.granularity - 1) / header.granularity; gt_size = ((header.num_gtes_per_gte * sizeof(uint32_t)) + 511) >> 9; - gt_count = (grains + header.num_gtes_per_gte - 1) / header.num_gtes_per_gte; + gt_count = + (grains + header.num_gtes_per_gte - 1) / header.num_gtes_per_gte; gd_size = (gt_count * sizeof(uint32_t) + 511) >> 9; header.desc_offset = 1; @@ -735,7 +957,6 @@ static int vmdk_create(const char *filename, QEMUOptionParameter *options) ((header.gd_offset + gd_size + (gt_size * gt_count) + header.granularity - 1) / header.granularity) * header.granularity; - /* swap endianness for all header fields */ header.version = cpu_to_le32(header.version); header.flags = cpu_to_le32(header.flags); @@ -793,27 +1014,255 @@ static int vmdk_create(const char *filename, QEMUOptionParameter *options) } } - /* compose the descriptor */ - real_filename = filename; - if ((temp_str = strrchr(real_filename, '\\')) != NULL) - real_filename = temp_str + 1; - if ((temp_str = strrchr(real_filename, '/')) != NULL) - real_filename = temp_str + 1; - if ((temp_str = strrchr(real_filename, ':')) != NULL) - real_filename = temp_str + 1; - snprintf(desc, sizeof(desc), desc_template, (unsigned int)time(NULL), - total_size, real_filename, - (flags & BLOCK_FLAG_COMPAT6 ? 6 : 4), - total_size / (int64_t)(63 * 16)); + ret = 0; + exit: + close(fd); + return ret; +} + +static int filename_decompose(const char *filename, char *path, char *prefix, + char *postfix, size_t buf_len) +{ + const char *p, *q; + + if (filename == NULL || !strlen(filename)) { + fprintf(stderr, "Vmdk: no filename provided.\n"); + return -1; + } + p = strrchr(filename, '/'); + if (p == NULL) { + p = strrchr(filename, '\\'); + } + if (p == NULL) { + p = strrchr(filename, ':'); + } + if (p != NULL) { + p++; + if (p - filename >= buf_len) { + return -1; + } + pstrcpy(path, p - filename + 1, filename); + } else { + p = filename; + path[0] = '\0'; + } + q = strrchr(p, '.'); + if (q == NULL) { + pstrcpy(prefix, buf_len, p); + postfix[0] = '\0'; + } else { + if (q - p >= buf_len) { + return -1; + } + pstrcpy(prefix, q - p + 1, p); + pstrcpy(postfix, buf_len, q); + } + return 0; +} + +static int relative_path(char *dest, int dest_size, + const char *base, const char *target) +{ + int i = 0; + int n = 0; + const char *p, *q; +#ifdef _WIN32 + const char *sep = "\\"; +#else + const char *sep = "/"; +#endif + + if (!(dest && base && target)) { + return -1; + } + if (path_is_absolute(target)) { + dest[dest_size - 1] = '\0'; + strncpy(dest, target, dest_size - 1); + return 0; + } + while (base[i] == target[i]) { + i++; + } + p = &base[i]; + q = &target[i]; + while (*p) { + if (*p == *sep) { + n++; + } + p++; + } + dest[0] = '\0'; + for (; n; n--) { + pstrcat(dest, dest_size, ".."); + pstrcat(dest, dest_size, sep); + } + pstrcat(dest, dest_size, q); + return 0; +} + +static int vmdk_create(const char *filename, QEMUOptionParameter *options) +{ + int fd, idx = 0; + char desc[BUF_SIZE]; + int64_t total_size = 0, filesize; + const char *backing_file = NULL; + const char *fmt = NULL; + int flags = 0; + int ret = 0; + bool flat, split; + char ext_desc_lines[BUF_SIZE] = ""; + char path[PATH_MAX], prefix[PATH_MAX], postfix[PATH_MAX]; + const int64_t split_size = 0x80000000; /* VMDK has constant split size */ + const char *desc_extent_line; + char parent_desc_line[BUF_SIZE] = ""; + uint32_t parent_cid = 0xffffffff; + const char desc_template[] = + "# Disk DescriptorFile\n" + "version=1\n" + "CID=%x\n" + "parentCID=%x\n" + "createType=\"%s\"\n" + "%s" + "\n" + "# Extent description\n" + "%s" + "\n" + "# The Disk Data Base\n" + "#DDB\n" + "\n" + "ddb.virtualHWVersion = \"%d\"\n" + "ddb.geometry.cylinders = \"%" PRId64 "\"\n" + "ddb.geometry.heads = \"16\"\n" + "ddb.geometry.sectors = \"63\"\n" + "ddb.adapterType = \"ide\"\n"; + + if (filename_decompose(filename, path, prefix, postfix, PATH_MAX)) { + return -EINVAL; + } + /* Read out options */ + while (options && options->name) { + if (!strcmp(options->name, BLOCK_OPT_SIZE)) { + total_size = options->value.n; + } else if (!strcmp(options->name, BLOCK_OPT_BACKING_FILE)) { + backing_file = options->value.s; + } else if (!strcmp(options->name, BLOCK_OPT_COMPAT6)) { + flags |= options->value.n ? BLOCK_FLAG_COMPAT6 : 0; + } else if (!strcmp(options->name, BLOCK_OPT_SUBFMT)) { + fmt = options->value.s; + } + options++; + } + if (!fmt) { + /* Default format to monolithicSparse */ + fmt = "monolithicSparse"; + } else if (strcmp(fmt, "monolithicFlat") && + strcmp(fmt, "monolithicSparse") && + strcmp(fmt, "twoGbMaxExtentSparse") && + strcmp(fmt, "twoGbMaxExtentFlat")) { + fprintf(stderr, "VMDK: Unknown subformat: %s\n", fmt); + return -EINVAL; + } + split = !(strcmp(fmt, "twoGbMaxExtentFlat") && + strcmp(fmt, "twoGbMaxExtentSparse")); + flat = !(strcmp(fmt, "monolithicFlat") && + strcmp(fmt, "twoGbMaxExtentFlat")); + if (flat) { + desc_extent_line = "RW %lld FLAT \"%s\" 0\n"; + } else { + desc_extent_line = "RW %lld SPARSE \"%s\"\n"; + } + if (flat && backing_file) { + /* not supporting backing file for flat image */ + return -ENOTSUP; + } + if (backing_file) { + char parent_filename[PATH_MAX]; + BlockDriverState *bs = bdrv_new(""); + ret = bdrv_open(bs, backing_file, 0, NULL); + if (ret != 0) { + bdrv_delete(bs); + return ret; + } + if (strcmp(bs->drv->format_name, "vmdk")) { + bdrv_delete(bs); + return -EINVAL; + } + filesize = bdrv_getlength(bs); + parent_cid = vmdk_read_cid(bs, 0); + bdrv_delete(bs); + relative_path(parent_filename, sizeof(parent_filename), + filename, backing_file); + snprintf(parent_desc_line, sizeof(parent_desc_line), + "parentFileNameHint=\"%s\"", parent_filename); + } + + /* Create extents */ + filesize = total_size; + while (filesize > 0) { + char desc_line[BUF_SIZE]; + char ext_filename[PATH_MAX]; + char desc_filename[PATH_MAX]; + int64_t size = filesize; - /* write the descriptor */ - lseek(fd, le64_to_cpu(header.desc_offset) << 9, SEEK_SET); + if (split && size > split_size) { + size = split_size; + } + if (split) { + snprintf(desc_filename, sizeof(desc_filename), "%s-%c%03d%s", + prefix, flat ? 'f' : 's', ++idx, postfix); + } else if (flat) { + snprintf(desc_filename, sizeof(desc_filename), "%s-flat%s", + prefix, postfix); + } else { + snprintf(desc_filename, sizeof(desc_filename), "%s%s", + prefix, postfix); + } + snprintf(ext_filename, sizeof(ext_filename), "%s%s", + path, desc_filename); + + if (vmdk_create_extent(ext_filename, size, flat)) { + return -EINVAL; + } + filesize -= size; + + /* Format description line */ + snprintf(desc_line, sizeof(desc_line), + desc_extent_line, size / 512, desc_filename); + pstrcat(ext_desc_lines, sizeof(ext_desc_lines), desc_line); + } + /* generate descriptor file */ + snprintf(desc, sizeof(desc), desc_template, + (unsigned int)time(NULL), + parent_cid, + fmt, + parent_desc_line, + ext_desc_lines, + (flags & BLOCK_FLAG_COMPAT6 ? 6 : 4), + total_size / (int64_t)(63 * 16 * 512)); + if (split || flat) { + fd = open( + filename, + O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_LARGEFILE, + 0644); + } else { + fd = open( + filename, + O_WRONLY | O_BINARY | O_LARGEFILE, + 0644); + } + if (fd < 0) { + return -errno; + } + /* the descriptor offset = 0x200 */ + if (!split && !flat && 0x200 != lseek(fd, 0x200, SEEK_SET)) { + ret = -errno; + goto exit; + } ret = qemu_write_full(fd, desc, strlen(desc)); if (ret != strlen(desc)) { ret = -errno; goto exit; } - ret = 0; exit: close(fd); @@ -822,17 +1271,47 @@ exit: static void vmdk_close(BlockDriverState *bs) { - BDRVVmdkState *s = bs->opaque; - - qemu_free(s->l1_table); - qemu_free(s->l2_cache); + vmdk_free_extents(bs); } static int vmdk_flush(BlockDriverState *bs) { - return bdrv_flush(bs->file); + int i, ret, err; + BDRVVmdkState *s = bs->opaque; + + ret = bdrv_flush(bs->file); + for (i = 0; i < s->num_extents; i++) { + err = bdrv_flush(s->extents[i].file); + if (err < 0) { + ret = err; + } + } + return ret; } +static int64_t vmdk_get_allocated_file_size(BlockDriverState *bs) +{ + int i; + int64_t ret = 0; + int64_t r; + BDRVVmdkState *s = bs->opaque; + + ret = bdrv_get_allocated_file_size(bs->file); + if (ret < 0) { + return ret; + } + for (i = 0; i < s->num_extents; i++) { + if (s->extents[i].file == bs->file) { + continue; + } + r = bdrv_get_allocated_file_size(s->extents[i].file); + if (r < 0) { + return r; + } + ret += r; + } + return ret; +} static QEMUOptionParameter vmdk_create_options[] = { { @@ -850,20 +1329,28 @@ static QEMUOptionParameter vmdk_create_options[] = { .type = OPT_FLAG, .help = "VMDK version 6 image" }, + { + .name = BLOCK_OPT_SUBFMT, + .type = OPT_STRING, + .help = + "VMDK flat extent format, can be one of " + "{monolithicSparse (default) | monolithicFlat | twoGbMaxExtentSparse | twoGbMaxExtentFlat} " + }, { NULL } }; static BlockDriver bdrv_vmdk = { - .format_name = "vmdk", - .instance_size = sizeof(BDRVVmdkState), - .bdrv_probe = vmdk_probe, + .format_name = "vmdk", + .instance_size = sizeof(BDRVVmdkState), + .bdrv_probe = vmdk_probe, .bdrv_open = vmdk_open, - .bdrv_read = vmdk_read, - .bdrv_write = vmdk_write, - .bdrv_close = vmdk_close, - .bdrv_create = vmdk_create, - .bdrv_flush = vmdk_flush, - .bdrv_is_allocated = vmdk_is_allocated, + .bdrv_read = vmdk_read, + .bdrv_write = vmdk_write, + .bdrv_close = vmdk_close, + .bdrv_create = vmdk_create, + .bdrv_flush = vmdk_flush, + .bdrv_is_allocated = vmdk_is_allocated, + .bdrv_get_allocated_file_size = vmdk_get_allocated_file_size, .create_options = vmdk_create_options, }; diff --git a/block_int.h b/block_int.h index 1e265d274d..efb68038c4 100644 --- a/block_int.h +++ b/block_int.h @@ -39,6 +39,7 @@ #define BLOCK_OPT_CLUSTER_SIZE "cluster_size" #define BLOCK_OPT_TABLE_SIZE "table_size" #define BLOCK_OPT_PREALLOC "preallocation" +#define BLOCK_OPT_SUBFMT "subformat" typedef struct AIOPool { void (*cancel)(BlockDriverAIOCB *acb); @@ -85,6 +86,7 @@ struct BlockDriver { const char *protocol_name; int (*bdrv_truncate)(BlockDriverState *bs, int64_t offset); int64_t (*bdrv_getlength)(BlockDriverState *bs); + int64_t (*bdrv_get_allocated_file_size)(BlockDriverState *bs); int (*bdrv_write_compressed)(BlockDriverState *bs, int64_t sector_num, const uint8_t *buf, int nb_sectors); diff --git a/blockdev.c b/blockdev.c index a97a801e95..0b8d3a4f83 100644 --- a/blockdev.c +++ b/blockdev.c @@ -568,7 +568,7 @@ void do_commit(Monitor *mon, const QDict *qdict) int do_snapshot_blkdev(Monitor *mon, const QDict *qdict, QObject **ret_data) { const char *device = qdict_get_str(qdict, "device"); - const char *filename = qdict_get_try_str(qdict, "snapshot_file"); + const char *filename = qdict_get_try_str(qdict, "snapshot-file"); const char *format = qdict_get_try_str(qdict, "format"); BlockDriverState *bs; BlockDriver *drv, *old_drv, *proto_drv; @@ -577,7 +577,7 @@ int do_snapshot_blkdev(Monitor *mon, const QDict *qdict, QObject **ret_data) char old_filename[1024]; if (!filename) { - qerror_report(QERR_MISSING_PARAMETER, "snapshot_file"); + qerror_report(QERR_MISSING_PARAMETER, "snapshot-file"); ret = -1; goto out; } diff --git a/compiler.h b/compiler.h new file mode 100644 index 0000000000..9af5dc6c91 --- /dev/null +++ b/compiler.h @@ -0,0 +1,34 @@ +/* public domain */ + +#ifndef COMPILER_H +#define COMPILER_H + +#include "config-host.h" + +#define QEMU_NORETURN __attribute__ ((__noreturn__)) +#ifdef CONFIG_GCC_ATTRIBUTE_WARN_UNUSED_RESULT +#define QEMU_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define QEMU_WARN_UNUSED_RESULT +#endif + +#define QEMU_BUILD_BUG_ON(x) \ + typedef char qemu_build_bug_on__##__LINE__[(x)?-1:1]; + +#if defined __GNUC__ +# if (__GNUC__ < 4) || \ + defined(__GNUC_MINOR__) && (__GNUC__ == 4) && (__GNUC_MINOR__ < 4) + /* gcc versions before 4.4.x don't support gnu_printf, so use printf. */ +# define GCC_ATTR __attribute__((__unused__, format(printf, 1, 2))) +# define GCC_FMT_ATTR(n, m) __attribute__((format(printf, n, m))) +# else + /* Use gnu_printf when supported (qemu uses standard format strings). */ +# define GCC_ATTR __attribute__((__unused__, format(gnu_printf, 1, 2))) +# define GCC_FMT_ATTR(n, m) __attribute__((format(gnu_printf, n, m))) +# endif +#else +#define GCC_ATTR /**/ +#define GCC_FMT_ATTR(n, m) +#endif + +#endif /* COMPILER_H */ @@ -177,6 +177,7 @@ spice="" rbd="" smartcard="" smartcard_nss="" +usb_redir="" opengl="" # parse CC options first @@ -743,6 +744,10 @@ for opt do ;; --enable-smartcard-nss) smartcard_nss="yes" ;; + --disable-usb-redir) usb_redir="no" + ;; + --enable-usb-redir) usb_redir="yes" + ;; *) echo "ERROR: unknown option $opt"; show_help="yes" ;; esac @@ -1018,6 +1023,8 @@ echo " --disable-smartcard disable smartcard support" echo " --enable-smartcard enable smartcard support" echo " --disable-smartcard-nss disable smartcard nss support" echo " --enable-smartcard-nss enable smartcard nss support" +echo " --disable-usb-redir disable usb network redirection support" +echo " --enable-usb-redir enable usb network redirection support" echo "" echo "NOTE: The object files are built at the place where configure is launched" exit 1 @@ -1803,6 +1810,18 @@ EOF fi ########################################## +# glib support probe +if $pkg_config --modversion gthread-2.0 gio-2.0 > /dev/null 2>&1 ; then + glib_cflags=`$pkg_config --cflags gthread-2.0 gio-2.0 2>/dev/null` + glib_libs=`$pkg_config --libs gthread-2.0 gio-2.0 2>/dev/null` + libs_softmmu="$glib_libs $libs_softmmu" + libs_tools="$glib_libs $libs_tools" +else + echo "glib-2.0 required to compile QEMU" + exit 1 +fi + +########################################## # pthread probe PTHREADLIBS_LIST="-lpthread -lpthreadGC2" @@ -2371,6 +2390,22 @@ if test "$smartcard" = "no" ; then smartcard_nss="no" fi +# check for usbredirparser for usb network redirection support +if test "$usb_redir" != "no" ; then + if $pkg_config libusbredirparser >/dev/null 2>&1 ; then + usb_redir="yes" + usb_redir_cflags=$($pkg_config --cflags libusbredirparser 2>/dev/null) + usb_redir_libs=$($pkg_config --libs libusbredirparser 2>/dev/null) + QEMU_CFLAGS="$QEMU_CFLAGS $usb_redir_cflags" + LIBS="$LIBS $usb_redir_libs" + else + if test "$usb_redir" = "yes"; then + feature_not_found "usb-redir" + fi + usb_redir="no" + fi +fi + ########################################## ########################################## @@ -2520,6 +2555,7 @@ if test "$softmmu" = yes ; then tools="qemu-img\$(EXESUF) qemu-io\$(EXESUF) $tools" if [ "$linux" = "yes" -o "$bsd" = "yes" -o "$solaris" = "yes" ] ; then tools="qemu-nbd\$(EXESUF) $tools" + tools="qemu-ga\$(EXESUF) $tools" if [ "$check_utests" = "yes" ]; then tools="check-qint check-qstring check-qdict check-qlist $tools" tools="check-qfloat check-qjson $tools" @@ -2617,6 +2653,7 @@ echo "spice support $spice" echo "rbd support $rbd" echo "xfsctl support $xfs" echo "nss used $smartcard_nss" +echo "usb net redir $usb_redir" echo "OpenGL support $opengl" if test $sdl_too_old = "yes"; then @@ -2849,6 +2886,7 @@ if test "$bluez" = "yes" ; then echo "CONFIG_BLUEZ=y" >> $config_host_mak echo "BLUEZ_CFLAGS=$bluez_cflags" >> $config_host_mak fi +echo "GLIB_CFLAGS=$glib_cflags" >> $config_host_mak if test "$xen" = "yes" ; then echo "CONFIG_XEN_BACKEND=y" >> $config_host_mak echo "CONFIG_XEN_CTRL_INTERFACE_VERSION=$xen_ctrl_version" >> $config_host_mak @@ -2910,6 +2948,10 @@ if test "$smartcard_nss" = "yes" ; then echo "CONFIG_SMARTCARD_NSS=y" >> $config_host_mak fi +if test "$usb_redir" = "yes" ; then + echo "CONFIG_USB_REDIR=y" >> $config_host_mak +fi + if test "$opengl" = "yes" ; then echo "CONFIG_OPENGL=y" >> $config_host_mak fi @@ -3473,6 +3515,8 @@ DIRS="tests tests/cris slirp audio block net pc-bios/optionrom" DIRS="$DIRS pc-bios/spapr-rtas" DIRS="$DIRS roms/seabios roms/vgabios" DIRS="$DIRS fsdev ui" +DIRS="$DIRS qapi" +DIRS="$DIRS qga" FILES="Makefile tests/Makefile" FILES="$FILES tests/cris/Makefile tests/cris/.gdbinit" FILES="$FILES pc-bios/optionrom/Makefile pc-bios/keymaps" diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt new file mode 100644 index 0000000000..b7befb5e48 --- /dev/null +++ b/docs/qapi-code-gen.txt @@ -0,0 +1,316 @@ += How to use the QAPI code generator = + +* Note: as of this writing, QMP does not use QAPI. Eventually QMP +commands will be converted to use QAPI internally. The following +information describes QMP/QAPI as it will exist after the +conversion. + +QAPI is a native C API within QEMU which provides management-level +functionality to internal/external users. For external +users/processes, this interface is made available by a JSON-based +QEMU Monitor protocol that is provided by the QMP server. + +To map QMP-defined interfaces to the native C QAPI implementations, +a JSON-based schema is used to define types and function +signatures, and a set of scripts is used to generate types/signatures, +and marshaling/dispatch code. The QEMU Guest Agent also uses these +scripts, paired with a seperate schema, to generate +marshaling/dispatch code for the guest agent server running in the +guest. + +This document will describe how the schemas, scripts, and resulting +code is used. + + +== QMP/Guest agent schema == + +This file defines the types, commands, and events used by QMP. It should +fully describe the interface used by QMP. + +This file is designed to be loosely based on JSON although it's technically +executable Python. While dictionaries are used, they are parsed as +OrderedDicts so that ordering is preserved. + +There are two basic syntaxes used, type definitions and command definitions. + +The first syntax defines a type and is represented by a dictionary. There are +two kinds of types that are supported: complex user-defined types, and enums. + +A complex type is a dictionary containing a single key who's value is a +dictionary. This corresponds to a struct in C or an Object in JSON. An +example of a complex type is: + + { 'type': 'MyType', + 'data' { 'member1': 'str', 'member2': 'int', '*member3': 'str } } + +The use of '*' as a prefix to the name means the member is optional. Optional +members should always be added to the end of the dictionary to preserve +backwards compatibility. + +An enumeration type is a dictionary containing a single key who's value is a +list of strings. An example enumeration is: + + { 'enum': 'MyEnum', 'data': [ 'value1', 'value2', 'value3' ] } + +Generally speaking, complex types and enums should always use CamelCase for +the type names. + +Commands are defined by using a list containing three members. The first +member is the command name, the second member is a dictionary containing +arguments, and the third member is the return type. + +An example command is: + + { 'command': 'my-command', + 'data': { 'arg1': 'str', '*arg2': 'str' }, + 'returns': 'str' ] + +Command names should be all lower case with words separated by a hyphen. + + +== Code generation == + +Schemas are fed into 3 scripts to generate all the code/files that, paired +with the core QAPI libraries, comprise everything required to take JSON +commands read in by a QMP/guest agent server, unmarshal the arguments into +the underlying C types, call into the corresponding C function, and map the +response back to a QMP/guest agent response to be returned to the user. + +As an example, we'll use the following schema, which describes a single +complex user-defined type (which will produce a C struct, along with a list +node structure that can be used to chain together a list of such types in +case we want to accept/return a list of this type with a command), and a +command which takes that type as a parameter and returns the same type: + + mdroth@illuin:~/w/qemu2.git$ cat example-schema.json + { 'type': 'UserDefOne', + 'data': { 'integer': 'int', 'string': 'str' } } + + { 'command': 'my-command', + 'data': {'arg1': 'UserDefOne'}, + 'returns': 'UserDefOne' } + mdroth@illuin:~/w/qemu2.git$ + +=== scripts/qapi-types.py === + +Used to generate the C types defined by a schema. The following files are +created: + +$(prefix)qapi-types.h - C types corresponding to types defined in + the schema you pass in +$(prefix)qapi-types.c - Cleanup functions for the above C types + +The $(prefix) is an optional parameter used as a namespace to keep the +generated code from one schema/code-generation separated from others so code +can be generated/used from multiple schemas without clobbering previously +created code. + +Example: + + mdroth@illuin:~/w/qemu2.git$ python scripts/qapi-types.py \ + --output-dir="qapi-generated" --prefix="example-" < example-schema.json + mdroth@illuin:~/w/qemu2.git$ cat qapi-generated/example-qapi-types.c + /* AUTOMATICALLY GENERATED, DO NOT MODIFY */ + + #include "qapi/qapi-dealloc-visitor.h" + #include "example-qapi-types.h" + #include "example-qapi-visit.h" + + void qapi_free_UserDefOne(UserDefOne * obj) + { + QapiDeallocVisitor *md; + Visitor *v; + + if (!obj) { + return; + } + + md = qapi_dealloc_visitor_new(); + v = qapi_dealloc_get_visitor(md); + visit_type_UserDefOne(v, &obj, NULL, NULL); + qapi_dealloc_visitor_cleanup(md); + } + + mdroth@illuin:~/w/qemu2.git$ cat qapi-generated/example-qapi-types.h + /* AUTOMATICALLY GENERATED, DO NOT MODIFY */ + #ifndef QAPI_GENERATED_EXAMPLE_QAPI_TYPES + #define QAPI_GENERATED_EXAMPLE_QAPI_TYPES + + #include "qapi/qapi-types-core.h" + + typedef struct UserDefOne UserDefOne; + + typedef struct UserDefOneList + { + UserDefOne *value; + struct UserDefOneList *next; + } UserDefOneList; + + struct UserDefOne + { + int64_t integer; + char * string; + }; + + void qapi_free_UserDefOne(UserDefOne * obj); + + #endif + + +=== scripts/qapi-visit.py === + +Used to generate the visitor functions used to walk through and convert +a QObject (as provided by QMP) to a native C data structure and +vice-versa, as well as the visitor function used to dealloc a complex +schema-defined C type. + +The following files are generated: + +$(prefix)qapi-visit.c: visitor function for a particular C type, used + to automagically convert QObjects into the + corresponding C type and vice-versa, as well + as for deallocating memory for an existing C + type + +$(prefix)qapi-visit.h: declarations for previously mentioned visitor + functions + +Example: + + mdroth@illuin:~/w/qemu2.git$ python scripts/qapi-visit.py \ + --output-dir="qapi-generated" --prefix="example-" < example-schema.json + mdroth@illuin:~/w/qemu2.git$ cat qapi-generated/example-qapi-visit.c + /* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT MODIFY */ + + #include "example-qapi-visit.h" + + void visit_type_UserDefOne(Visitor *m, UserDefOne ** obj, const char *name, Error **errp) + { + visit_start_struct(m, (void **)obj, "UserDefOne", name, sizeof(UserDefOne), errp); + visit_type_int(m, (obj && *obj) ? &(*obj)->integer : NULL, "integer", errp); + visit_type_str(m, (obj && *obj) ? &(*obj)->string : NULL, "string", errp); + visit_end_struct(m, errp); + } + + void visit_type_UserDefOneList(Visitor *m, UserDefOneList ** obj, const char *name, Error **errp) + { + GenericList *i; + + visit_start_list(m, name, errp); + + for (i = visit_next_list(m, (GenericList **)obj, errp); i; i = visit_next_list(m, &i, errp)) { + UserDefOneList *native_i = (UserDefOneList *)i; + visit_type_UserDefOne(m, &native_i->value, NULL, errp); + } + + visit_end_list(m, errp); + } + mdroth@illuin:~/w/qemu2.git$ cat qapi-generated/example-qapi-visit.h + /* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT MODIFY */ + + #ifndef QAPI_GENERATED_EXAMPLE_QAPI_VISIT + #define QAPI_GENERATED_EXAMPLE_QAPI_VISIT + + #include "qapi/qapi-visit-core.h" + #include "example-qapi-types.h" + + void visit_type_UserDefOne(Visitor *m, UserDefOne ** obj, const char *name, Error **errp); + void visit_type_UserDefOneList(Visitor *m, UserDefOneList ** obj, const char *name, Error **errp); + + #endif + mdroth@illuin:~/w/qemu2.git$ + + +=== scripts/qapi-commands.py === + +Used to generate the marshaling/dispatch functions for the commands defined +in the schema. The following files are generated: + +$(prefix)qmp-marshal.c: command marshal/dispatch functions for each + QMP command defined in the schema. Functions + generated by qapi-visit.py are used to + convert QObjects recieved from the wire into + function parameters, and uses the same + visitor functions to convert native C return + values to QObjects from transmission back + over the wire. + +$(prefix)qmp-commands.h: Function prototypes for the QMP commands + specified in the schema. + +Example: + + mdroth@illuin:~/w/qemu2.git$ cat qapi-generated/example-qmp-marshal.c + /* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT MODIFY */ + + #include "qemu-objects.h" + #include "qapi/qmp-core.h" + #include "qapi/qapi-visit-core.h" + #include "qapi/qmp-output-visitor.h" + #include "qapi/qmp-input-visitor.h" + #include "qapi/qapi-dealloc-visitor.h" + #include "example-qapi-types.h" + #include "example-qapi-visit.h" + + #include "example-qmp-commands.h" + static void qmp_marshal_output_my_command(UserDefOne * ret_in, QObject **ret_out, Error **errp) + { + QapiDeallocVisitor *md = qapi_dealloc_visitor_new(); + QmpOutputVisitor *mo = qmp_output_visitor_new(); + Visitor *v; + + v = qmp_output_get_visitor(mo); + visit_type_UserDefOne(v, &ret_in, "unused", errp); + v = qapi_dealloc_get_visitor(md); + visit_type_UserDefOne(v, &ret_in, "unused", errp); + qapi_dealloc_visitor_cleanup(md); + + + *ret_out = qmp_output_get_qobject(mo); + } + + static void qmp_marshal_input_my_command(QmpState *qmp__sess, QDict *args, QObject **ret, Error **errp) + { + UserDefOne * retval = NULL; + QmpInputVisitor *mi; + QapiDeallocVisitor *md; + Visitor *v; + UserDefOne * arg1 = NULL; + + mi = qmp_input_visitor_new(QOBJECT(args)); + v = qmp_input_get_visitor(mi); + visit_type_UserDefOne(v, &arg1, "arg1", errp); + + if (error_is_set(errp)) { + goto out; + } + retval = qmp_my_command(arg1, errp); + qmp_marshal_output_my_command(retval, ret, errp); + + out: + md = qapi_dealloc_visitor_new(); + v = qapi_dealloc_get_visitor(md); + visit_type_UserDefOne(v, &arg1, "arg1", errp); + qapi_dealloc_visitor_cleanup(md); + return; + } + + static void qmp_init_marshal(void) + { + qmp_register_command("my-command", qmp_marshal_input_my_command); + } + + qapi_init(qmp_init_marshal); + mdroth@illuin:~/w/qemu2.git$ cat qapi-generated/example-qmp-commands.h + /* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT MODIFY */ + + #ifndef QAPI_GENERATED_EXAMPLE_QMP_COMMANDS + #define QAPI_GENERATED_EXAMPLE_QMP_COMMANDS + + #include "example-qapi-types.h" + #include "error.h" + + UserDefOne * qmp_my_command(UserDefOne * arg1, Error **errp); + + #endif + mdroth@illuin:~/w/qemu2.git$ @@ -12,6 +12,7 @@ #ifndef ERROR_H #define ERROR_H +#include "compiler.h" #include <stdbool.h> /** diff --git a/hmp-commands.hx b/hmp-commands.hx index 6ad8806785..c857827618 100644 --- a/hmp-commands.hx +++ b/hmp-commands.hx @@ -840,7 +840,7 @@ ETEXI { .name = "snapshot_blkdev", - .args_type = "device:B,snapshot_file:s?,format:s?", + .args_type = "device:B,snapshot-file:s?,format:s?", .params = "device [new-image-file] [format]", .help = "initiates a live snapshot\n\t\t\t" "of device. If a new image file is specified, the\n\t\t\t" @@ -244,7 +244,7 @@ static void do_busid_cmd(ESPState *s, uint8_t *buf, uint8_t busid) DPRINTF("do_busid_cmd: busid 0x%x\n", busid); lun = busid & 7; - s->current_req = scsi_req_new(s->current_dev, 0, lun); + s->current_req = scsi_req_new(s->current_dev, 0, lun, NULL); datalen = scsi_req_enqueue(s->current_req, buf); s->ti_size = datalen; if (datalen != 0) { diff --git a/hw/lsi53c895a.c b/hw/lsi53c895a.c index 940b43abfd..69eec1d2fe 100644 --- a/hw/lsi53c895a.c +++ b/hw/lsi53c895a.c @@ -661,7 +661,7 @@ static lsi_request *lsi_find_by_tag(LSIState *s, uint32_t tag) static void lsi_request_cancelled(SCSIRequest *req) { LSIState *s = DO_UPCAST(LSIState, dev.qdev, req->bus->qbus.parent); - lsi_request *p; + lsi_request *p = req->hba_private; if (s->current && req == s->current->req) { scsi_req_unref(req); @@ -670,7 +670,6 @@ static void lsi_request_cancelled(SCSIRequest *req) return; } - p = lsi_find_by_tag(s, req->tag); if (p) { QTAILQ_REMOVE(&s->queue, p, next); scsi_req_unref(req); @@ -680,18 +679,12 @@ static void lsi_request_cancelled(SCSIRequest *req) /* Record that data is available for a queued command. Returns zero if the device was reselected, nonzero if the IO is deferred. */ -static int lsi_queue_tag(LSIState *s, uint32_t tag, uint32_t len) +static int lsi_queue_req(LSIState *s, SCSIRequest *req, uint32_t len) { - lsi_request *p; - - p = lsi_find_by_tag(s, tag); - if (!p) { - BADF("IO with unknown tag %d\n", tag); - return 1; - } + lsi_request *p = req->hba_private; if (p->pending) { - BADF("Multiple IO pending for tag %d\n", tag); + BADF("Multiple IO pending for request %p\n", p); } p->pending = len; /* Reselect if waiting for it, or if reselection triggers an IRQ @@ -743,9 +736,9 @@ static void lsi_transfer_data(SCSIRequest *req, uint32_t len) LSIState *s = DO_UPCAST(LSIState, dev.qdev, req->bus->qbus.parent); int out; - if (s->waiting == 1 || !s->current || req->tag != s->current->tag || + if (s->waiting == 1 || !s->current || req->hba_private != s->current || (lsi_irq_on_rsl(s) && !(s->scntl1 & LSI_SCNTL1_CON))) { - if (lsi_queue_tag(s, req->tag, len)) { + if (lsi_queue_req(s, req, len)) { return; } } @@ -789,7 +782,8 @@ static void lsi_do_command(LSIState *s) assert(s->current == NULL); s->current = qemu_mallocz(sizeof(lsi_request)); s->current->tag = s->select_tag; - s->current->req = scsi_req_new(dev, s->current->tag, s->current_lun); + s->current->req = scsi_req_new(dev, s->current->tag, s->current_lun, + s->current); n = scsi_req_enqueue(s->current->req, buf); if (n) { @@ -656,8 +656,8 @@ static void qxl_reset_state(PCIQXLDevice *d) QXLRam *ram = d->ram; QXLRom *rom = d->rom; - assert(SPICE_RING_IS_EMPTY(&ram->cmd_ring)); - assert(SPICE_RING_IS_EMPTY(&ram->cursor_ring)); + assert(!d->ssd.running || SPICE_RING_IS_EMPTY(&ram->cmd_ring)); + assert(!d->ssd.running || SPICE_RING_IS_EMPTY(&ram->cursor_ring)); d->shadow_rom.update_id = cpu_to_le32(0); *rom = d->shadow_rom; qxl_rom_set_dirty(d); diff --git a/hw/scsi-bus.c b/hw/scsi-bus.c index ad6a730be0..8b1a412210 100644 --- a/hw/scsi-bus.c +++ b/hw/scsi-bus.c @@ -131,7 +131,8 @@ int scsi_bus_legacy_handle_cmdline(SCSIBus *bus) return res; } -SCSIRequest *scsi_req_alloc(size_t size, SCSIDevice *d, uint32_t tag, uint32_t lun) +SCSIRequest *scsi_req_alloc(size_t size, SCSIDevice *d, uint32_t tag, + uint32_t lun, void *hba_private) { SCSIRequest *req; @@ -141,14 +142,16 @@ SCSIRequest *scsi_req_alloc(size_t size, SCSIDevice *d, uint32_t tag, uint32_t l req->dev = d; req->tag = tag; req->lun = lun; + req->hba_private = hba_private; req->status = -1; trace_scsi_req_alloc(req->dev->id, req->lun, req->tag); return req; } -SCSIRequest *scsi_req_new(SCSIDevice *d, uint32_t tag, uint32_t lun) +SCSIRequest *scsi_req_new(SCSIDevice *d, uint32_t tag, uint32_t lun, + void *hba_private) { - return d->info->alloc_req(d, tag, lun); + return d->info->alloc_req(d, tag, lun, hba_private); } uint8_t *scsi_req_get_buf(SCSIRequest *req) diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c index a8c7372d3e..05d14ab2fd 100644 --- a/hw/scsi-disk.c +++ b/hw/scsi-disk.c @@ -81,13 +81,13 @@ static int scsi_handle_rw_error(SCSIDiskReq *r, int error, int type); static int scsi_disk_emulate_command(SCSIDiskReq *r, uint8_t *outbuf); static SCSIRequest *scsi_new_request(SCSIDevice *d, uint32_t tag, - uint32_t lun) + uint32_t lun, void *hba_private) { SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d); SCSIRequest *req; SCSIDiskReq *r; - req = scsi_req_alloc(sizeof(SCSIDiskReq), &s->qdev, tag, lun); + req = scsi_req_alloc(sizeof(SCSIDiskReq), &s->qdev, tag, lun, hba_private); r = DO_UPCAST(SCSIDiskReq, req, req); r->iov.iov_base = qemu_blockalign(s->bs, SCSI_DMA_BUF_SIZE); return req; @@ -398,7 +398,8 @@ static int scsi_disk_emulate_inquiry(SCSIRequest *req, uint8_t *outbuf) "buffer size %zd\n", req->cmd.xfer); pages = buflen++; outbuf[buflen++] = 0x00; // list of supported pages (this page) - outbuf[buflen++] = 0x80; // unit serial number + if (s->serial) + outbuf[buflen++] = 0x80; // unit serial number outbuf[buflen++] = 0x83; // device identification if (s->drive_kind == SCSI_HD) { outbuf[buflen++] = 0xb0; // block limits @@ -409,8 +410,14 @@ static int scsi_disk_emulate_inquiry(SCSIRequest *req, uint8_t *outbuf) } case 0x80: /* Device serial number, optional */ { - int l = strlen(s->serial); + int l; + if (!s->serial) { + DPRINTF("Inquiry (EVPD[Serial number] not supported\n"); + return -1; + } + + l = strlen(s->serial); if (l > req->cmd.xfer) l = req->cmd.xfer; if (l > 20) @@ -1007,7 +1014,7 @@ static int32_t scsi_send_command(SCSIRequest *req, uint8_t *buf) command = buf[0]; outbuf = (uint8_t *)r->iov.iov_base; - DPRINTF("Command: lun=%d tag=0x%x data=0x%02x", lun, tag, buf[0]); + DPRINTF("Command: lun=%d tag=0x%x data=0x%02x", req->lun, req->tag, buf[0]); if (scsi_req_parse(&r->req, buf) != 0) { BADF("Unsupported command length, command %x\n", command); @@ -1203,7 +1210,9 @@ static int scsi_initfn(SCSIDevice *dev, SCSIDriveKind kind) if (!s->serial) { /* try to fall back to value set with legacy -drive serial=... */ dinfo = drive_get_by_blockdev(s->bs); - s->serial = qemu_strdup(*dinfo->serial ? dinfo->serial : "0"); + if (*dinfo->serial) { + s->serial = qemu_strdup(dinfo->serial); + } } if (!s->version) { diff --git a/hw/scsi-generic.c b/hw/scsi-generic.c index 8e59c7ee89..90345a714f 100644 --- a/hw/scsi-generic.c +++ b/hw/scsi-generic.c @@ -96,11 +96,12 @@ static int scsi_get_sense(SCSIRequest *req, uint8_t *outbuf, int len) return size; } -static SCSIRequest *scsi_new_request(SCSIDevice *d, uint32_t tag, uint32_t lun) +static SCSIRequest *scsi_new_request(SCSIDevice *d, uint32_t tag, uint32_t lun, + void *hba_private) { SCSIRequest *req; - req = scsi_req_alloc(sizeof(SCSIGenericReq), d, tag, lun); + req = scsi_req_alloc(sizeof(SCSIGenericReq), d, tag, lun, hba_private); return req; } @@ -43,6 +43,7 @@ struct SCSIRequest { } cmd; BlockDriverAIOCB *aiocb; bool enqueued; + void *hba_private; QTAILQ_ENTRY(SCSIRequest) next; }; @@ -67,7 +68,8 @@ struct SCSIDeviceInfo { DeviceInfo qdev; scsi_qdev_initfn init; void (*destroy)(SCSIDevice *s); - SCSIRequest *(*alloc_req)(SCSIDevice *s, uint32_t tag, uint32_t lun); + SCSIRequest *(*alloc_req)(SCSIDevice *s, uint32_t tag, uint32_t lun, + void *hba_private); void (*free_req)(SCSIRequest *req); int32_t (*send_command)(SCSIRequest *req, uint8_t *buf); void (*read_data)(SCSIRequest *req); @@ -138,8 +140,10 @@ extern const struct SCSISense sense_code_LUN_FAILURE; int scsi_build_sense(SCSISense sense, uint8_t *buf, int len, int fixed); int scsi_sense_valid(SCSISense sense); -SCSIRequest *scsi_req_alloc(size_t size, SCSIDevice *d, uint32_t tag, uint32_t lun); -SCSIRequest *scsi_req_new(SCSIDevice *d, uint32_t tag, uint32_t lun); +SCSIRequest *scsi_req_alloc(size_t size, SCSIDevice *d, uint32_t tag, + uint32_t lun, void *hba_private); +SCSIRequest *scsi_req_new(SCSIDevice *d, uint32_t tag, uint32_t lun, + void *hba_private); int32_t scsi_req_enqueue(SCSIRequest *req, uint8_t *buf); void scsi_req_free(SCSIRequest *req); SCSIRequest *scsi_req_ref(SCSIRequest *req); diff --git a/hw/spapr_vscsi.c b/hw/spapr_vscsi.c index 1c901ef6eb..646b1e3caf 100644 --- a/hw/spapr_vscsi.c +++ b/hw/spapr_vscsi.c @@ -121,7 +121,7 @@ static struct vscsi_req *vscsi_get_req(VSCSIState *s) return NULL; } -static void vscsi_put_req(VSCSIState *s, vscsi_req *req) +static void vscsi_put_req(vscsi_req *req) { if (req->sreq != NULL) { scsi_req_unref(req->sreq); @@ -130,15 +130,6 @@ static void vscsi_put_req(VSCSIState *s, vscsi_req *req) req->active = 0; } -static vscsi_req *vscsi_find_req(VSCSIState *s, SCSIRequest *req) -{ - uint32_t tag = req->tag; - if (tag >= VSCSI_REQ_LIMIT || !s->reqs[tag].active) { - return NULL; - } - return &s->reqs[tag]; -} - static void vscsi_decode_id_lun(uint64_t srp_lun, int *id, int *lun) { /* XXX Figure that one out properly ! This is crackpot */ @@ -454,7 +445,7 @@ static void vscsi_send_request_sense(VSCSIState *s, vscsi_req *req) if (n) { req->senselen = n; vscsi_send_rsp(s, req, CHECK_CONDITION, 0, 0); - vscsi_put_req(s, req); + vscsi_put_req(req); return; } @@ -483,7 +474,7 @@ static void vscsi_send_request_sense(VSCSIState *s, vscsi_req *req) static void vscsi_transfer_data(SCSIRequest *sreq, uint32_t len) { VSCSIState *s = DO_UPCAST(VSCSIState, vdev.qdev, sreq->bus->qbus.parent); - vscsi_req *req = vscsi_find_req(s, sreq); + vscsi_req *req = sreq->hba_private; uint8_t *buf; int rc = 0; @@ -531,7 +522,7 @@ static void vscsi_transfer_data(SCSIRequest *sreq, uint32_t len) static void vscsi_command_complete(SCSIRequest *sreq, uint32_t status) { VSCSIState *s = DO_UPCAST(VSCSIState, vdev.qdev, sreq->bus->qbus.parent); - vscsi_req *req = vscsi_find_req(s, sreq); + vscsi_req *req = sreq->hba_private; int32_t res_in = 0, res_out = 0; dprintf("VSCSI: SCSI cmd complete, r=0x%x tag=0x%x status=0x%x, req=%p\n", @@ -563,15 +554,14 @@ static void vscsi_command_complete(SCSIRequest *sreq, uint32_t status) } } vscsi_send_rsp(s, req, 0, res_in, res_out); - vscsi_put_req(s, req); + vscsi_put_req(req); } static void vscsi_request_cancelled(SCSIRequest *sreq) { - VSCSIState *s = DO_UPCAST(VSCSIState, vdev.qdev, sreq->bus->qbus.parent); - vscsi_req *req = vscsi_find_req(s, sreq); + vscsi_req *req = sreq->hba_private; - vscsi_put_req(s, req); + vscsi_put_req(req); } static void vscsi_process_login(VSCSIState *s, vscsi_req *req) @@ -659,7 +649,7 @@ static int vscsi_queue_cmd(VSCSIState *s, vscsi_req *req) } req->lun = lun; - req->sreq = scsi_req_new(sdev, req->qtag, lun); + req->sreq = scsi_req_new(sdev, req->qtag, lun, req); n = scsi_req_enqueue(req->sreq, srp->cmd.cdb); dprintf("VSCSI: Queued command tag 0x%x CMD 0x%x ID %d LUN %d ret: %d\n", @@ -858,7 +848,7 @@ static void vscsi_got_payload(VSCSIState *s, vscsi_crq *crq) } if (done) { - vscsi_put_req(s, req); + vscsi_put_req(req); } } diff --git a/hw/usb-hid.c b/hw/usb-hid.c index d711b5c0be..b812da2a6a 100644 --- a/hw/usb-hid.c +++ b/hw/usb-hid.c @@ -531,18 +531,15 @@ static void usb_keyboard_process_keycode(USBHIDState *hs) case 0xe0: if (s->modifiers & (1 << 9)) { s->modifiers ^= 3 << 8; - usb_hid_changed(hs); return; } case 0xe1 ... 0xe7: if (keycode & (1 << 7)) { s->modifiers &= ~(1 << (hid_code & 0x0f)); - usb_hid_changed(hs); return; } case 0xe8 ... 0xef: s->modifiers |= 1 << (hid_code & 0x0f); - usb_hid_changed(hs); return; } @@ -769,10 +766,12 @@ static int usb_hid_handle_control(USBDevice *dev, USBPacket *p, } break; case GET_REPORT: - if (s->kind == USB_MOUSE || s->kind == USB_TABLET) + if (s->kind == USB_MOUSE || s->kind == USB_TABLET) { ret = usb_pointer_poll(s, data, length); - else if (s->kind == USB_KEYBOARD) + } else if (s->kind == USB_KEYBOARD) { ret = usb_keyboard_poll(s, data, length); + } + s->changed = s->n > 0; break; case SET_REPORT: if (s->kind == USB_KEYBOARD) diff --git a/hw/usb-msd.c b/hw/usb-msd.c index 86582cc723..bfea096893 100644 --- a/hw/usb-msd.c +++ b/hw/usb-msd.c @@ -216,10 +216,6 @@ static void usb_msd_transfer_data(SCSIRequest *req, uint32_t len) MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent); USBPacket *p = s->packet; - if (req->tag != s->tag) { - fprintf(stderr, "usb-msd: Unexpected SCSI Tag 0x%x\n", req->tag); - } - assert((s->mode == USB_MSDM_DATAOUT) == (req->cmd.mode == SCSI_XFER_TO_DEV)); s->scsi_len = len; s->scsi_buf = scsi_req_get_buf(req); @@ -241,9 +237,6 @@ static void usb_msd_command_complete(SCSIRequest *req, uint32_t status) MSDState *s = DO_UPCAST(MSDState, dev.qdev, req->bus->qbus.parent); USBPacket *p = s->packet; - if (req->tag != s->tag) { - fprintf(stderr, "usb-msd: Unexpected SCSI Tag 0x%x\n", req->tag); - } DPRINTF("Command complete %d\n", status); s->residue = s->data_len; s->result = status != 0; @@ -387,7 +380,7 @@ static int usb_msd_handle_data(USBDevice *dev, USBPacket *p) s->tag, cbw.flags, cbw.cmd_len, s->data_len); s->residue = 0; s->scsi_len = 0; - s->req = scsi_req_new(s->scsi_dev, s->tag, 0); + s->req = scsi_req_new(s->scsi_dev, s->tag, 0, NULL); scsi_req_enqueue(s->req, cbw.cmd); /* ??? Should check that USB and SCSI data transfer directions match. */ diff --git a/hw/usb-uhci.c b/hw/usb-uhci.c index 2ef4c5b747..da74c57c62 100644 --- a/hw/usb-uhci.c +++ b/hw/usb-uhci.c @@ -730,6 +730,9 @@ out: td->ctrl |= TD_CTRL_STALL; td->ctrl &= ~TD_CTRL_ACTIVE; s->status |= UHCI_STS_USBERR; + if (td->ctrl & TD_CTRL_IOC) { + *int_mask |= 0x01; + } uhci_update_irq(s); return 1; @@ -737,6 +740,9 @@ out: td->ctrl |= TD_CTRL_BABBLE | TD_CTRL_STALL; td->ctrl &= ~TD_CTRL_ACTIVE; s->status |= UHCI_STS_USBERR; + if (td->ctrl & TD_CTRL_IOC) { + *int_mask |= 0x01; + } uhci_update_irq(s); /* frame interrupted */ return -1; diff --git a/hw/virtio-net.c b/hw/virtio-net.c index 6997e02dcf..a32cc019b0 100644 --- a/hw/virtio-net.c +++ b/hw/virtio-net.c @@ -657,7 +657,7 @@ static ssize_t virtio_net_receive(VLANClientState *nc, const uint8_t *buf, size_ /* copy in packet. ugh */ len = iov_from_buf(sg, elem.in_num, - buf + offset, size - offset); + buf + offset, 0, size - offset); total += len; offset += len; /* If buffers can't be merged, at this point we diff --git a/hw/virtio-serial-bus.c b/hw/virtio-serial-bus.c index 6d73386441..bdc760c36e 100644 --- a/hw/virtio-serial-bus.c +++ b/hw/virtio-serial-bus.c @@ -104,7 +104,7 @@ static size_t write_to_port(VirtIOSerialPort *port, } len = iov_from_buf(elem.in_sg, elem.in_num, - buf + offset, size - offset); + buf + offset, 0, size - offset); offset += len; virtqueue_push(vq, &elem, len); @@ -14,56 +14,61 @@ #include "iov.h" -size_t iov_from_buf(struct iovec *iov, unsigned int iovcnt, - const void *buf, size_t size) +size_t iov_from_buf(struct iovec *iov, unsigned int iov_cnt, + const void *buf, size_t iov_off, size_t size) { - size_t offset; + size_t iovec_off, buf_off; unsigned int i; - offset = 0; - for (i = 0; offset < size && i < iovcnt; i++) { - size_t len; + iovec_off = 0; + buf_off = 0; + for (i = 0; i < iov_cnt && size; i++) { + if (iov_off < (iovec_off + iov[i].iov_len)) { + size_t len = MIN((iovec_off + iov[i].iov_len) - iov_off, size); - len = MIN(iov[i].iov_len, size - offset); + memcpy(iov[i].iov_base + (iov_off - iovec_off), buf + buf_off, len); - memcpy(iov[i].iov_base, buf + offset, len); - offset += len; + buf_off += len; + iov_off += len; + size -= len; + } + iovec_off += iov[i].iov_len; } - return offset; + return buf_off; } -size_t iov_to_buf(const struct iovec *iov, const unsigned int iovcnt, - void *buf, size_t offset, size_t size) +size_t iov_to_buf(const struct iovec *iov, const unsigned int iov_cnt, + void *buf, size_t iov_off, size_t size) { uint8_t *ptr; - size_t iov_off, buf_off; + size_t iovec_off, buf_off; unsigned int i; ptr = buf; - iov_off = 0; + iovec_off = 0; buf_off = 0; - for (i = 0; i < iovcnt && size; i++) { - if (offset < (iov_off + iov[i].iov_len)) { - size_t len = MIN((iov_off + iov[i].iov_len) - offset , size); + for (i = 0; i < iov_cnt && size; i++) { + if (iov_off < (iovec_off + iov[i].iov_len)) { + size_t len = MIN((iovec_off + iov[i].iov_len) - iov_off , size); - memcpy(ptr + buf_off, iov[i].iov_base + (offset - iov_off), len); + memcpy(ptr + buf_off, iov[i].iov_base + (iov_off - iovec_off), len); buf_off += len; - offset += len; + iov_off += len; size -= len; } - iov_off += iov[i].iov_len; + iovec_off += iov[i].iov_len; } return buf_off; } -size_t iov_size(const struct iovec *iov, const unsigned int iovcnt) +size_t iov_size(const struct iovec *iov, const unsigned int iov_cnt) { size_t len; unsigned int i; len = 0; - for (i = 0; i < iovcnt; i++) { + for (i = 0; i < iov_cnt; i++) { len += iov[i].iov_len; } return len; @@ -12,8 +12,8 @@ #include "qemu-common.h" -size_t iov_from_buf(struct iovec *iov, unsigned int iovcnt, - const void *buf, size_t size); -size_t iov_to_buf(const struct iovec *iov, const unsigned int iovcnt, - void *buf, size_t offset, size_t size); -size_t iov_size(const struct iovec *iov, const unsigned int iovcnt); +size_t iov_from_buf(struct iovec *iov, unsigned int iov_cnt, + const void *buf, size_t iov_off, size_t size); +size_t iov_to_buf(const struct iovec *iov, const unsigned int iov_cnt, + void *buf, size_t iov_off, size_t size); +size_t iov_size(const struct iovec *iov, const unsigned int iov_cnt); @@ -24,12 +24,14 @@ typedef enum { MODULE_INIT_BLOCK, MODULE_INIT_DEVICE, MODULE_INIT_MACHINE, + MODULE_INIT_QAPI, MODULE_INIT_MAX } module_init_type; #define block_init(function) module_init(function, MODULE_INIT_BLOCK) #define device_init(function) module_init(function, MODULE_INIT_DEVICE) #define machine_init(function) module_init(function, MODULE_INIT_MACHINE) +#define qapi_init(function) module_init(function, MODULE_INIT_QAPI) void register_module_init(void (*fn)(void), module_init_type type); diff --git a/qapi-schema-guest.json b/qapi-schema-guest.json new file mode 100644 index 0000000000..fde5971e87 --- /dev/null +++ b/qapi-schema-guest.json @@ -0,0 +1,217 @@ +# *-*- Mode: Python -*-* + +## +# @guest-sync: +# +# Echo back a unique integer value +# +# This is used by clients talking to the guest agent over the +# wire to ensure the stream is in sync and doesn't contain stale +# data from previous client. All guest agent responses should be +# ignored until the provided unique integer value is returned, +# and it is up to the client to handle stale whole or +# partially-delivered JSON text in such a way that this response +# can be obtained. +# +# Such clients should also preceed this command +# with a 0xFF byte to make such the guest agent flushes any +# partially read JSON data from a previous session. +# +# @id: randomly generated 64-bit integer +# +# Returns: The unique integer id passed in by the client +# +# Since: 0.15.0 +## +{ 'command': 'guest-sync' + 'data': { 'id': 'int' }, + 'returns': 'int' } + +## +# @guest-ping: +# +# Ping the guest agent, a non-error return implies success +# +# Since: 0.15.0 +## +{ 'command': 'guest-ping' } + +## +# @guest-info: +# +# Get some information about the guest agent. +# +# Since: 0.15.0 +## +{ 'type': 'GuestAgentInfo', 'data': {'version': 'str'} } +{ 'command': 'guest-info', + 'returns': 'GuestAgentInfo' } + +## +# @guest-shutdown: +# +# Initiate guest-activated shutdown. Note: this is an asynchronous +# shutdown request, with no guaruntee of successful shutdown. Errors +# will be logged to guest's syslog. +# +# @mode: #optional "halt", "powerdown" (default), or "reboot" +# +# Returns: Nothing on success +# +# Since: 0.15.0 +## +{ 'command': 'guest-shutdown', 'data': { '*mode': 'str' } } + +## +# @guest-file-open: +# +# Open a file in the guest and retrieve a file handle for it +# +# @filepath: Full path to the file in the guest to open. +# +# @mode: #optional open mode, as per fopen(), "r" is the default. +# +# Returns: Guest file handle on success. +# +# Since: 0.15.0 +## +{ 'command': 'guest-file-open', + 'data': { 'path': 'str', '*mode': 'str' }, + 'returns': 'int' } + +## +# @guest-file-close: +# +# Close an open file in the guest +# +# @handle: filehandle returned by guest-file-open +# +# Returns: Nothing on success. +# +# Since: 0.15.0 +## +{ 'command': 'guest-file-close', + 'data': { 'handle': 'int' } } + +## +# @guest-file-read: +# +# Read from an open file in the guest. Data will be base64-encoded +# +# @handle: filehandle returned by guest-file-open +# +# @count: #optional maximum number of bytes to read (default is 4KB) +# +# Returns: GuestFileRead on success. Note: count is number of bytes read +# *before* base64 encoding bytes read. +# +# Since: 0.15.0 +## +{ 'type': 'GuestFileRead', + 'data': { 'count': 'int', 'buf-b64': 'str', 'eof': 'bool' } } + +{ 'command': 'guest-file-read', + 'data': { 'handle': 'int', '*count': 'int' }, + 'returns': 'GuestFileRead' } + +## +# @guest-file-write: +# +# Write to an open file in the guest. +# +# @handle: filehandle returned by guest-file-open +# +# @buf-b64: base64-encoded string representing data to be written +# +# @count: #optional bytes to write (actual bytes, after base64-decode), +# default is all content in buf-b64 buffer after base64 decoding +# +# Returns: GuestFileWrite on success. Note: count is the number of bytes +# base64-decoded bytes written +# +# Since: 0.15.0 +## +{ 'type': 'GuestFileWrite', + 'data': { 'count': 'int', 'eof': 'bool' } } +{ 'command': 'guest-file-write', + 'data': { 'handle': 'int', 'buf-b64': 'str', '*count': 'int' }, + 'returns': 'GuestFileWrite' } + +## +# @guest-file-seek: +# +# Seek to a position in the file, as with fseek(), and return the +# current file position afterward. Also encapsulates ftell()'s +# functionality, just Set offset=0, whence=SEEK_CUR. +# +# @handle: filehandle returned by guest-file-open +# +# @offset: bytes to skip over in the file stream +# +# @whence: SEEK_SET, SEEK_CUR, or SEEK_END, as with fseek() +# +# Returns: GuestFileSeek on success. +# +# Since: 0.15.0 +## +{ 'type': 'GuestFileSeek', + 'data': { 'position': 'int', 'eof': 'bool' } } + +{ 'command': 'guest-file-seek', + 'data': { 'handle': 'int', 'offset': 'int', 'whence': 'int' }, + 'returns': 'GuestFileSeek' } + +## +# @guest-file-flush: +# +# Write file changes bufferred in userspace to disk/kernel buffers +# +# @handle: filehandle returned by guest-file-open +# +# Returns: Nothing on success. +# +# Since: 0.15.0 +## +{ 'command': 'guest-file-flush', + 'data': { 'handle': 'int' } } + +## +# @guest-fsfreeze-status: +# +# Get guest fsfreeze state. error state indicates failure to thaw 1 or more +# previously frozen filesystems, or failure to open a previously cached +# filesytem (filesystem unmounted/directory changes, etc). +# +# Returns: GuestFsfreezeStatus ("thawed", "frozen", etc., as defined below) +# +# Since: 0.15.0 +## +{ 'enum': 'GuestFsfreezeStatus', + 'data': [ 'thawed', 'frozen', 'error' ] } +{ 'command': 'guest-fsfreeze-status', + 'returns': 'GuestFsfreezeStatus' } + +## +# @guest-fsfreeze-freeze: +# +# Sync and freeze all non-network guest filesystems +# +# Returns: Number of file systems frozen on success +# +# Since: 0.15.0 +## +{ 'command': 'guest-fsfreeze-freeze', + 'returns': 'int' } + +## +# @guest-fsfreeze-thaw: +# +# Unfreeze frozen guest fileystems +# +# Returns: Number of file systems thawed +# If error, -1 (unknown error) or -errno +# +# Since: 0.15.0 +## +{ 'command': 'guest-fsfreeze-thaw', + 'returns': 'int' } diff --git a/qapi-schema-test.json b/qapi-schema-test.json new file mode 100644 index 0000000000..3acedad7ee --- /dev/null +++ b/qapi-schema-test.json @@ -0,0 +1,22 @@ +# *-*- Mode: Python -*-* + +# for testing enums +{ 'enum': 'EnumOne', + 'data': [ 'value1', 'value2', 'value3' ] } +{ 'type': 'NestedEnumsOne', + 'data': { 'enum1': 'EnumOne', '*enum2': 'EnumOne', 'enum3': 'EnumOne', '*enum4': 'EnumOne' } } + +# for testing nested structs +{ 'type': 'UserDefOne', + 'data': { 'integer': 'int', 'string': 'str' } } + +{ 'type': 'UserDefTwo', + 'data': { 'string': 'str', + 'dict': { 'string': 'str', + 'dict': { 'userdef': 'UserDefOne', 'string': 'str' }, + '*dict2': { 'userdef': 'UserDefOne', 'string': 'str' } } } } + +# testing commands +{ 'command': 'user_def_cmd', 'data': {} } +{ 'command': 'user_def_cmd1', 'data': {'ud1a': 'UserDefOne'} } +{ 'command': 'user_def_cmd2', 'data': {'ud1a': 'UserDefOne', 'ud1b': 'UserDefOne'}, 'returns': 'UserDefTwo' } diff --git a/qapi/qapi-dealloc-visitor.c b/qapi/qapi-dealloc-visitor.c new file mode 100644 index 0000000000..8cde4dd0d4 --- /dev/null +++ b/qapi/qapi-dealloc-visitor.c @@ -0,0 +1,147 @@ +/* + * Dealloc Visitor + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Michael Roth <mdroth@linux.vnet.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "qapi-dealloc-visitor.h" +#include "qemu-queue.h" +#include "qemu-common.h" +#include "qemu-objects.h" + +typedef struct StackEntry +{ + void *value; + QTAILQ_ENTRY(StackEntry) node; +} StackEntry; + +struct QapiDeallocVisitor +{ + Visitor visitor; + QTAILQ_HEAD(, StackEntry) stack; +}; + +static QapiDeallocVisitor *to_qov(Visitor *v) +{ + return container_of(v, QapiDeallocVisitor, visitor); +} + +static void qapi_dealloc_push(QapiDeallocVisitor *qov, void *value) +{ + StackEntry *e = qemu_mallocz(sizeof(*e)); + + e->value = value; + QTAILQ_INSERT_HEAD(&qov->stack, e, node); +} + +static void *qapi_dealloc_pop(QapiDeallocVisitor *qov) +{ + StackEntry *e = QTAILQ_FIRST(&qov->stack); + QObject *value; + QTAILQ_REMOVE(&qov->stack, e, node); + value = e->value; + qemu_free(e); + return value; +} + +static void qapi_dealloc_start_struct(Visitor *v, void **obj, const char *kind, + const char *name, size_t unused, + Error **errp) +{ + QapiDeallocVisitor *qov = to_qov(v); + qapi_dealloc_push(qov, obj); +} + +static void qapi_dealloc_end_struct(Visitor *v, Error **errp) +{ + QapiDeallocVisitor *qov = to_qov(v); + void **obj = qapi_dealloc_pop(qov); + if (obj) { + qemu_free(*obj); + } +} + +static void qapi_dealloc_start_list(Visitor *v, const char *name, Error **errp) +{ +} + +static GenericList *qapi_dealloc_next_list(Visitor *v, GenericList **list, + Error **errp) +{ + GenericList *retval = *list; + qemu_free(retval->value); + *list = retval->next; + return retval; +} + +static void qapi_dealloc_end_list(Visitor *v, Error **errp) +{ +} + +static void qapi_dealloc_type_str(Visitor *v, char **obj, const char *name, + Error **errp) +{ + if (obj) { + qemu_free(*obj); + } +} + +static void qapi_dealloc_type_int(Visitor *v, int64_t *obj, const char *name, + Error **errp) +{ +} + +static void qapi_dealloc_type_bool(Visitor *v, bool *obj, const char *name, + Error **errp) +{ +} + +static void qapi_dealloc_type_number(Visitor *v, double *obj, const char *name, + Error **errp) +{ +} + +static void qapi_dealloc_type_enum(Visitor *v, int *obj, const char *strings[], + const char *kind, const char *name, + Error **errp) +{ +} + +Visitor *qapi_dealloc_get_visitor(QapiDeallocVisitor *v) +{ + return &v->visitor; +} + +void qapi_dealloc_visitor_cleanup(QapiDeallocVisitor *v) +{ + qemu_free(v); +} + +QapiDeallocVisitor *qapi_dealloc_visitor_new(void) +{ + QapiDeallocVisitor *v; + + v = qemu_mallocz(sizeof(*v)); + + v->visitor.start_struct = qapi_dealloc_start_struct; + v->visitor.end_struct = qapi_dealloc_end_struct; + v->visitor.start_list = qapi_dealloc_start_list; + v->visitor.next_list = qapi_dealloc_next_list; + v->visitor.end_list = qapi_dealloc_end_list; + v->visitor.type_enum = qapi_dealloc_type_enum; + v->visitor.type_int = qapi_dealloc_type_int; + v->visitor.type_bool = qapi_dealloc_type_bool; + v->visitor.type_str = qapi_dealloc_type_str; + v->visitor.type_number = qapi_dealloc_type_number; + + QTAILQ_INIT(&v->stack); + + return v; +} diff --git a/qapi/qapi-dealloc-visitor.h b/qapi/qapi-dealloc-visitor.h new file mode 100644 index 0000000000..5842bc79bd --- /dev/null +++ b/qapi/qapi-dealloc-visitor.h @@ -0,0 +1,26 @@ +/* + * Dealloc Visitor + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Michael Roth <mdroth@linux.vnet.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#ifndef QAPI_DEALLOC_VISITOR_H +#define QAPI_DEALLOC_VISITOR_H + +#include "qapi-visit-core.h" + +typedef struct QapiDeallocVisitor QapiDeallocVisitor; + +QapiDeallocVisitor *qapi_dealloc_visitor_new(void); +void qapi_dealloc_visitor_cleanup(QapiDeallocVisitor *d); + +Visitor *qapi_dealloc_get_visitor(QapiDeallocVisitor *v); + +#endif diff --git a/qapi/qapi-types-core.h b/qapi/qapi-types-core.h new file mode 100644 index 0000000000..a79bc2b3c3 --- /dev/null +++ b/qapi/qapi-types-core.h @@ -0,0 +1,20 @@ +/* + * Core Definitions for QAPI-generated Types + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#ifndef QAPI_TYPES_CORE_H +#define QAPI_TYPES_CORE_H + +#include "qemu-common.h" +#include "error.h" + +#endif diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c new file mode 100644 index 0000000000..ddef3eda12 --- /dev/null +++ b/qapi/qapi-visit-core.c @@ -0,0 +1,118 @@ +/* + * Core Definitions for QAPI Visitor Classes + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "qapi/qapi-visit-core.h" + +void visit_start_handle(Visitor *v, void **obj, const char *kind, + const char *name, Error **errp) +{ + if (!error_is_set(errp) && v->start_handle) { + v->start_handle(v, obj, kind, name, errp); + } +} + +void visit_end_handle(Visitor *v, Error **errp) +{ + if (!error_is_set(errp) && v->end_handle) { + v->end_handle(v, errp); + } +} + +void visit_start_struct(Visitor *v, void **obj, const char *kind, + const char *name, size_t size, Error **errp) +{ + if (!error_is_set(errp)) { + v->start_struct(v, obj, kind, name, size, errp); + } +} + +void visit_end_struct(Visitor *v, Error **errp) +{ + if (!error_is_set(errp)) { + v->end_struct(v, errp); + } +} + +void visit_start_list(Visitor *v, const char *name, Error **errp) +{ + if (!error_is_set(errp)) { + v->start_list(v, name, errp); + } +} + +GenericList *visit_next_list(Visitor *v, GenericList **list, Error **errp) +{ + if (!error_is_set(errp)) { + return v->next_list(v, list, errp); + } + + return 0; +} + +void visit_end_list(Visitor *v, Error **errp) +{ + if (!error_is_set(errp)) { + v->end_list(v, errp); + } +} + +void visit_start_optional(Visitor *v, bool *present, const char *name, + Error **errp) +{ + if (!error_is_set(errp) && v->start_optional) { + v->start_optional(v, present, name, errp); + } +} + +void visit_end_optional(Visitor *v, Error **errp) +{ + if (!error_is_set(errp) && v->end_optional) { + v->end_optional(v, errp); + } +} + +void visit_type_enum(Visitor *v, int *obj, const char *strings[], + const char *kind, const char *name, Error **errp) +{ + if (!error_is_set(errp)) { + v->type_enum(v, obj, strings, kind, name, errp); + } +} + +void visit_type_int(Visitor *v, int64_t *obj, const char *name, Error **errp) +{ + if (!error_is_set(errp)) { + v->type_int(v, obj, name, errp); + } +} + +void visit_type_bool(Visitor *v, bool *obj, const char *name, Error **errp) +{ + if (!error_is_set(errp)) { + v->type_bool(v, obj, name, errp); + } +} + +void visit_type_str(Visitor *v, char **obj, const char *name, Error **errp) +{ + if (!error_is_set(errp)) { + v->type_str(v, obj, name, errp); + } +} + +void visit_type_number(Visitor *v, double *obj, const char *name, Error **errp) +{ + if (!error_is_set(errp)) { + v->type_number(v, obj, name, errp); + } +} diff --git a/qapi/qapi-visit-core.h b/qapi/qapi-visit-core.h new file mode 100644 index 0000000000..e850746b75 --- /dev/null +++ b/qapi/qapi-visit-core.h @@ -0,0 +1,76 @@ +/* + * Core Definitions for QAPI Visitor Classes + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ +#ifndef QAPI_VISITOR_CORE_H +#define QAPI_VISITOR_CORE_H + +#include "qapi/qapi-types-core.h" +#include <stdlib.h> + +typedef struct GenericList +{ + void *value; + struct GenericList *next; +} GenericList; + +typedef struct Visitor Visitor; + +struct Visitor +{ + /* Must be set */ + void (*start_struct)(Visitor *v, void **obj, const char *kind, + const char *name, size_t size, Error **errp); + void (*end_struct)(Visitor *v, Error **errp); + + void (*start_list)(Visitor *v, const char *name, Error **errp); + GenericList *(*next_list)(Visitor *v, GenericList **list, Error **errp); + void (*end_list)(Visitor *v, Error **errp); + + void (*type_enum)(Visitor *v, int *obj, const char *strings[], + const char *kind, const char *name, Error **errp); + + void (*type_int)(Visitor *v, int64_t *obj, const char *name, Error **errp); + void (*type_bool)(Visitor *v, bool *obj, const char *name, Error **errp); + void (*type_str)(Visitor *v, char **obj, const char *name, Error **errp); + void (*type_number)(Visitor *v, double *obj, const char *name, + Error **errp); + + /* May be NULL */ + void (*start_optional)(Visitor *v, bool *present, const char *name, + Error **errp); + void (*end_optional)(Visitor *v, Error **errp); + + void (*start_handle)(Visitor *v, void **obj, const char *kind, + const char *name, Error **errp); + void (*end_handle)(Visitor *v, Error **errp); +}; + +void visit_start_handle(Visitor *v, void **obj, const char *kind, + const char *name, Error **errp); +void visit_end_handle(Visitor *v, Error **errp); +void visit_start_struct(Visitor *v, void **obj, const char *kind, + const char *name, size_t size, Error **errp); +void visit_end_struct(Visitor *v, Error **errp); +void visit_start_list(Visitor *v, const char *name, Error **errp); +GenericList *visit_next_list(Visitor *v, GenericList **list, Error **errp); +void visit_end_list(Visitor *v, Error **errp); +void visit_start_optional(Visitor *v, bool *present, const char *name, + Error **errp); +void visit_end_optional(Visitor *v, Error **errp); +void visit_type_enum(Visitor *v, int *obj, const char *strings[], + const char *kind, const char *name, Error **errp); +void visit_type_int(Visitor *v, int64_t *obj, const char *name, Error **errp); +void visit_type_bool(Visitor *v, bool *obj, const char *name, Error **errp); +void visit_type_str(Visitor *v, char **obj, const char *name, Error **errp); +void visit_type_number(Visitor *v, double *obj, const char *name, Error **errp); + +#endif diff --git a/qapi/qmp-core.h b/qapi/qmp-core.h new file mode 100644 index 0000000000..f1c26e4b2e --- /dev/null +++ b/qapi/qmp-core.h @@ -0,0 +1,41 @@ +/* + * Core Definitions for QAPI/QMP Dispatch + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#ifndef QMP_CORE_H +#define QMP_CORE_H + +#include "qobject.h" +#include "qdict.h" +#include "error.h" + +typedef void (QmpCommandFunc)(QDict *, QObject **, Error **); + +typedef enum QmpCommandType +{ + QCT_NORMAL, +} QmpCommandType; + +typedef struct QmpCommand +{ + const char *name; + QmpCommandType type; + QmpCommandFunc *fn; + QTAILQ_ENTRY(QmpCommand) node; +} QmpCommand; + +void qmp_register_command(const char *name, QmpCommandFunc *fn); +QmpCommand *qmp_find_command(const char *name); +QObject *qmp_dispatch(QObject *request); + +#endif + diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c new file mode 100644 index 0000000000..558469325c --- /dev/null +++ b/qapi/qmp-dispatch.c @@ -0,0 +1,124 @@ +/* + * Core Definitions for QAPI/QMP Dispatch + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "qemu-objects.h" +#include "qapi/qmp-core.h" +#include "json-parser.h" +#include "error.h" +#include "error_int.h" +#include "qerror.h" + +static QDict *qmp_dispatch_check_obj(const QObject *request, Error **errp) +{ + const QDictEntry *ent; + const char *arg_name; + const QObject *arg_obj; + bool has_exec_key = false; + QDict *dict = NULL; + + if (qobject_type(request) != QTYPE_QDICT) { + error_set(errp, QERR_QMP_BAD_INPUT_OBJECT, + "request is not a dictionary"); + return NULL; + } + + dict = qobject_to_qdict(request); + + for (ent = qdict_first(dict); ent; + ent = qdict_next(dict, ent)) { + arg_name = qdict_entry_key(ent); + arg_obj = qdict_entry_value(ent); + + if (!strcmp(arg_name, "execute")) { + if (qobject_type(arg_obj) != QTYPE_QSTRING) { + error_set(errp, QERR_QMP_BAD_INPUT_OBJECT_MEMBER, "execute", + "string"); + return NULL; + } + has_exec_key = true; + } else if (strcmp(arg_name, "arguments")) { + error_set(errp, QERR_QMP_EXTRA_MEMBER, arg_name); + return NULL; + } + } + + if (!has_exec_key) { + error_set(errp, QERR_QMP_BAD_INPUT_OBJECT, "execute"); + return NULL; + } + + return dict; +} + +static QObject *do_qmp_dispatch(QObject *request, Error **errp) +{ + const char *command; + QDict *args, *dict; + QmpCommand *cmd; + QObject *ret = NULL; + + + dict = qmp_dispatch_check_obj(request, errp); + if (!dict || error_is_set(errp)) { + return NULL; + } + + command = qdict_get_str(dict, "execute"); + cmd = qmp_find_command(command); + if (cmd == NULL) { + error_set(errp, QERR_COMMAND_NOT_FOUND, command); + return NULL; + } + + if (!qdict_haskey(dict, "arguments")) { + args = qdict_new(); + } else { + args = qdict_get_qdict(dict, "arguments"); + QINCREF(args); + } + + switch (cmd->type) { + case QCT_NORMAL: + cmd->fn(args, &ret, errp); + if (!error_is_set(errp) && ret == NULL) { + ret = QOBJECT(qdict_new()); + } + break; + } + + QDECREF(args); + + return ret; +} + +QObject *qmp_dispatch(QObject *request) +{ + Error *err = NULL; + QObject *ret; + QDict *rsp; + + ret = do_qmp_dispatch(request, &err); + + rsp = qdict_new(); + if (err) { + qdict_put_obj(rsp, "error", error_get_qobject(err)); + error_free(err); + } else if (ret) { + qdict_put_obj(rsp, "return", ret); + } else { + QDECREF(rsp); + return NULL; + } + + return QOBJECT(rsp); +} diff --git a/qapi/qmp-input-visitor.c b/qapi/qmp-input-visitor.c new file mode 100644 index 0000000000..6a1adc9fce --- /dev/null +++ b/qapi/qmp-input-visitor.c @@ -0,0 +1,301 @@ +/* + * Input Visitor + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "qmp-input-visitor.h" +#include "qemu-queue.h" +#include "qemu-common.h" +#include "qemu-objects.h" +#include "qerror.h" + +#define QIV_STACK_SIZE 1024 + +typedef struct StackObject +{ + const QObject *obj; + const QListEntry *entry; +} StackObject; + +struct QmpInputVisitor +{ + Visitor visitor; + QObject *obj; + StackObject stack[QIV_STACK_SIZE]; + int nb_stack; +}; + +static QmpInputVisitor *to_qiv(Visitor *v) +{ + return container_of(v, QmpInputVisitor, visitor); +} + +static const QObject *qmp_input_get_object(QmpInputVisitor *qiv, + const char *name) +{ + const QObject *qobj; + + if (qiv->nb_stack == 0) { + qobj = qiv->obj; + } else { + qobj = qiv->stack[qiv->nb_stack - 1].obj; + } + + if (name && qobject_type(qobj) == QTYPE_QDICT) { + return qdict_get(qobject_to_qdict(qobj), name); + } else if (qiv->nb_stack > 0 && qobject_type(qobj) == QTYPE_QLIST) { + return qlist_entry_obj(qiv->stack[qiv->nb_stack - 1].entry); + } + + return qobj; +} + +static void qmp_input_push(QmpInputVisitor *qiv, const QObject *obj, Error **errp) +{ + qiv->stack[qiv->nb_stack].obj = obj; + if (qobject_type(obj) == QTYPE_QLIST) { + qiv->stack[qiv->nb_stack].entry = qlist_first(qobject_to_qlist(obj)); + } + qiv->nb_stack++; + + if (qiv->nb_stack >= QIV_STACK_SIZE) { + error_set(errp, QERR_BUFFER_OVERRUN); + return; + } +} + +static void qmp_input_pop(QmpInputVisitor *qiv, Error **errp) +{ + qiv->nb_stack--; + if (qiv->nb_stack < 0) { + error_set(errp, QERR_BUFFER_OVERRUN); + return; + } +} + +static void qmp_input_start_struct(Visitor *v, void **obj, const char *kind, + const char *name, size_t size, Error **errp) +{ + QmpInputVisitor *qiv = to_qiv(v); + const QObject *qobj = qmp_input_get_object(qiv, name); + + if (!qobj || qobject_type(qobj) != QTYPE_QDICT) { + error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", + "QDict"); + return; + } + + qmp_input_push(qiv, qobj, errp); + if (error_is_set(errp)) { + return; + } + + if (obj) { + *obj = qemu_mallocz(size); + } +} + +static void qmp_input_end_struct(Visitor *v, Error **errp) +{ + QmpInputVisitor *qiv = to_qiv(v); + + qmp_input_pop(qiv, errp); +} + +static void qmp_input_start_list(Visitor *v, const char *name, Error **errp) +{ + QmpInputVisitor *qiv = to_qiv(v); + const QObject *qobj = qmp_input_get_object(qiv, name); + + if (!qobj || qobject_type(qobj) != QTYPE_QLIST) { + error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", + "list"); + return; + } + + qmp_input_push(qiv, qobj, errp); +} + +static GenericList *qmp_input_next_list(Visitor *v, GenericList **list, + Error **errp) +{ + QmpInputVisitor *qiv = to_qiv(v); + GenericList *entry; + StackObject *so = &qiv->stack[qiv->nb_stack - 1]; + + if (so->entry == NULL) { + return NULL; + } + + entry = qemu_mallocz(sizeof(*entry)); + if (*list) { + so->entry = qlist_next(so->entry); + if (so->entry == NULL) { + qemu_free(entry); + return NULL; + } + (*list)->next = entry; + } + *list = entry; + + + return entry; +} + +static void qmp_input_end_list(Visitor *v, Error **errp) +{ + QmpInputVisitor *qiv = to_qiv(v); + + qmp_input_pop(qiv, errp); +} + +static void qmp_input_type_int(Visitor *v, int64_t *obj, const char *name, + Error **errp) +{ + QmpInputVisitor *qiv = to_qiv(v); + const QObject *qobj = qmp_input_get_object(qiv, name); + + if (!qobj || qobject_type(qobj) != QTYPE_QINT) { + error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", + "integer"); + return; + } + + *obj = qint_get_int(qobject_to_qint(qobj)); +} + +static void qmp_input_type_bool(Visitor *v, bool *obj, const char *name, + Error **errp) +{ + QmpInputVisitor *qiv = to_qiv(v); + const QObject *qobj = qmp_input_get_object(qiv, name); + + if (!qobj || qobject_type(qobj) != QTYPE_QBOOL) { + error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", + "boolean"); + return; + } + + *obj = qbool_get_int(qobject_to_qbool(qobj)); +} + +static void qmp_input_type_str(Visitor *v, char **obj, const char *name, + Error **errp) +{ + QmpInputVisitor *qiv = to_qiv(v); + const QObject *qobj = qmp_input_get_object(qiv, name); + + if (!qobj || qobject_type(qobj) != QTYPE_QSTRING) { + error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", + "string"); + return; + } + + *obj = qemu_strdup(qstring_get_str(qobject_to_qstring(qobj))); +} + +static void qmp_input_type_number(Visitor *v, double *obj, const char *name, + Error **errp) +{ + QmpInputVisitor *qiv = to_qiv(v); + const QObject *qobj = qmp_input_get_object(qiv, name); + + if (!qobj || qobject_type(qobj) != QTYPE_QFLOAT) { + error_set(errp, QERR_INVALID_PARAMETER_TYPE, name ? name : "null", + "double"); + return; + } + + *obj = qfloat_get_double(qobject_to_qfloat(qobj)); +} + +static void qmp_input_type_enum(Visitor *v, int *obj, const char *strings[], + const char *kind, const char *name, + Error **errp) +{ + int64_t value = 0; + char *enum_str; + + assert(strings); + + qmp_input_type_str(v, &enum_str, name, errp); + if (error_is_set(errp)) { + return; + } + + while (strings[value] != NULL) { + if (strcmp(strings[value], enum_str) == 0) { + break; + } + value++; + } + + if (strings[value] == NULL) { + error_set(errp, QERR_INVALID_PARAMETER, name ? name : "null"); + return; + } + + *obj = value; +} + +static void qmp_input_start_optional(Visitor *v, bool *present, + const char *name, Error **errp) +{ + QmpInputVisitor *qiv = to_qiv(v); + const QObject *qobj = qmp_input_get_object(qiv, name); + + if (!qobj) { + *present = false; + return; + } + + *present = true; +} + +static void qmp_input_end_optional(Visitor *v, Error **errp) +{ +} + +Visitor *qmp_input_get_visitor(QmpInputVisitor *v) +{ + return &v->visitor; +} + +void qmp_input_visitor_cleanup(QmpInputVisitor *v) +{ + qobject_decref(v->obj); + qemu_free(v); +} + +QmpInputVisitor *qmp_input_visitor_new(QObject *obj) +{ + QmpInputVisitor *v; + + v = qemu_mallocz(sizeof(*v)); + + v->visitor.start_struct = qmp_input_start_struct; + v->visitor.end_struct = qmp_input_end_struct; + v->visitor.start_list = qmp_input_start_list; + v->visitor.next_list = qmp_input_next_list; + v->visitor.end_list = qmp_input_end_list; + v->visitor.type_enum = qmp_input_type_enum; + v->visitor.type_int = qmp_input_type_int; + v->visitor.type_bool = qmp_input_type_bool; + v->visitor.type_str = qmp_input_type_str; + v->visitor.type_number = qmp_input_type_number; + v->visitor.start_optional = qmp_input_start_optional; + v->visitor.end_optional = qmp_input_end_optional; + + v->obj = obj; + qobject_incref(v->obj); + + return v; +} diff --git a/qapi/qmp-input-visitor.h b/qapi/qmp-input-visitor.h new file mode 100644 index 0000000000..3f798f0335 --- /dev/null +++ b/qapi/qmp-input-visitor.h @@ -0,0 +1,27 @@ +/* + * Input Visitor + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#ifndef QMP_INPUT_VISITOR_H +#define QMP_INPUT_VISITOR_H + +#include "qapi-visit-core.h" +#include "qobject.h" + +typedef struct QmpInputVisitor QmpInputVisitor; + +QmpInputVisitor *qmp_input_visitor_new(QObject *obj); +void qmp_input_visitor_cleanup(QmpInputVisitor *v); + +Visitor *qmp_input_get_visitor(QmpInputVisitor *v); + +#endif diff --git a/qapi/qmp-output-visitor.c b/qapi/qmp-output-visitor.c new file mode 100644 index 0000000000..c398cac4f0 --- /dev/null +++ b/qapi/qmp-output-visitor.c @@ -0,0 +1,239 @@ +/* + * Core Definitions for QAPI/QMP Command Registry + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "qmp-output-visitor.h" +#include "qemu-queue.h" +#include "qemu-common.h" +#include "qemu-objects.h" +#include "qerror.h" + +typedef struct QStackEntry +{ + QObject *value; + QTAILQ_ENTRY(QStackEntry) node; +} QStackEntry; + +typedef QTAILQ_HEAD(QStack, QStackEntry) QStack; + +struct QmpOutputVisitor +{ + Visitor visitor; + QStack stack; +}; + +#define qmp_output_add(qov, name, value) \ + qmp_output_add_obj(qov, name, QOBJECT(value)) +#define qmp_output_push(qov, value) qmp_output_push_obj(qov, QOBJECT(value)) + +static QmpOutputVisitor *to_qov(Visitor *v) +{ + return container_of(v, QmpOutputVisitor, visitor); +} + +static void qmp_output_push_obj(QmpOutputVisitor *qov, QObject *value) +{ + QStackEntry *e = qemu_mallocz(sizeof(*e)); + + e->value = value; + QTAILQ_INSERT_HEAD(&qov->stack, e, node); +} + +static QObject *qmp_output_pop(QmpOutputVisitor *qov) +{ + QStackEntry *e = QTAILQ_FIRST(&qov->stack); + QObject *value; + QTAILQ_REMOVE(&qov->stack, e, node); + value = e->value; + qemu_free(e); + return value; +} + +static QObject *qmp_output_first(QmpOutputVisitor *qov) +{ + QStackEntry *e = QTAILQ_LAST(&qov->stack, QStack); + return e->value; +} + +static QObject *qmp_output_last(QmpOutputVisitor *qov) +{ + QStackEntry *e = QTAILQ_FIRST(&qov->stack); + return e->value; +} + +static void qmp_output_add_obj(QmpOutputVisitor *qov, const char *name, + QObject *value) +{ + QObject *cur; + + if (QTAILQ_EMPTY(&qov->stack)) { + qmp_output_push_obj(qov, value); + return; + } + + cur = qmp_output_last(qov); + + switch (qobject_type(cur)) { + case QTYPE_QDICT: + qdict_put_obj(qobject_to_qdict(cur), name, value); + break; + case QTYPE_QLIST: + qlist_append_obj(qobject_to_qlist(cur), value); + break; + default: + qobject_decref(qmp_output_pop(qov)); + qmp_output_push_obj(qov, value); + break; + } +} + +static void qmp_output_start_struct(Visitor *v, void **obj, const char *kind, + const char *name, size_t unused, + Error **errp) +{ + QmpOutputVisitor *qov = to_qov(v); + QDict *dict = qdict_new(); + + qmp_output_add(qov, name, dict); + qmp_output_push(qov, dict); +} + +static void qmp_output_end_struct(Visitor *v, Error **errp) +{ + QmpOutputVisitor *qov = to_qov(v); + qmp_output_pop(qov); +} + +static void qmp_output_start_list(Visitor *v, const char *name, Error **errp) +{ + QmpOutputVisitor *qov = to_qov(v); + QList *list = qlist_new(); + + qmp_output_add(qov, name, list); + qmp_output_push(qov, list); +} + +static GenericList *qmp_output_next_list(Visitor *v, GenericList **list, + Error **errp) +{ + GenericList *retval = *list; + *list = retval->next; + return retval; +} + +static void qmp_output_end_list(Visitor *v, Error **errp) +{ + QmpOutputVisitor *qov = to_qov(v); + qmp_output_pop(qov); +} + +static void qmp_output_type_int(Visitor *v, int64_t *obj, const char *name, + Error **errp) +{ + QmpOutputVisitor *qov = to_qov(v); + qmp_output_add(qov, name, qint_from_int(*obj)); +} + +static void qmp_output_type_bool(Visitor *v, bool *obj, const char *name, + Error **errp) +{ + QmpOutputVisitor *qov = to_qov(v); + qmp_output_add(qov, name, qbool_from_int(*obj)); +} + +static void qmp_output_type_str(Visitor *v, char **obj, const char *name, + Error **errp) +{ + QmpOutputVisitor *qov = to_qov(v); + if (*obj) { + qmp_output_add(qov, name, qstring_from_str(*obj)); + } else { + qmp_output_add(qov, name, qstring_from_str("")); + } +} + +static void qmp_output_type_number(Visitor *v, double *obj, const char *name, + Error **errp) +{ + QmpOutputVisitor *qov = to_qov(v); + qmp_output_add(qov, name, qfloat_from_double(*obj)); +} + +static void qmp_output_type_enum(Visitor *v, int *obj, const char *strings[], + const char *kind, const char *name, + Error **errp) +{ + int i = 0; + int value = *obj; + char *enum_str; + + assert(strings); + while (strings[i++] != NULL); + if (value >= i - 1) { + error_set(errp, QERR_INVALID_PARAMETER, name ? name : "null"); + return; + } + + enum_str = (char *)strings[value]; + qmp_output_type_str(v, &enum_str, name, errp); +} + +QObject *qmp_output_get_qobject(QmpOutputVisitor *qov) +{ + QObject *obj = qmp_output_first(qov); + if (obj) { + qobject_incref(obj); + } + return obj; +} + +Visitor *qmp_output_get_visitor(QmpOutputVisitor *v) +{ + return &v->visitor; +} + +void qmp_output_visitor_cleanup(QmpOutputVisitor *v) +{ + QStackEntry *e, *tmp; + + QTAILQ_FOREACH_SAFE(e, &v->stack, node, tmp) { + QTAILQ_REMOVE(&v->stack, e, node); + if (e->value) { + qobject_decref(e->value); + } + qemu_free(e); + } + + qemu_free(v); +} + +QmpOutputVisitor *qmp_output_visitor_new(void) +{ + QmpOutputVisitor *v; + + v = qemu_mallocz(sizeof(*v)); + + v->visitor.start_struct = qmp_output_start_struct; + v->visitor.end_struct = qmp_output_end_struct; + v->visitor.start_list = qmp_output_start_list; + v->visitor.next_list = qmp_output_next_list; + v->visitor.end_list = qmp_output_end_list; + v->visitor.type_enum = qmp_output_type_enum; + v->visitor.type_int = qmp_output_type_int; + v->visitor.type_bool = qmp_output_type_bool; + v->visitor.type_str = qmp_output_type_str; + v->visitor.type_number = qmp_output_type_number; + + QTAILQ_INIT(&v->stack); + + return v; +} diff --git a/qapi/qmp-output-visitor.h b/qapi/qmp-output-visitor.h new file mode 100644 index 0000000000..4a649c2504 --- /dev/null +++ b/qapi/qmp-output-visitor.h @@ -0,0 +1,28 @@ +/* + * Output Visitor + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#ifndef QMP_OUTPUT_VISITOR_H +#define QMP_OUTPUT_VISITOR_H + +#include "qapi-visit-core.h" +#include "qobject.h" + +typedef struct QmpOutputVisitor QmpOutputVisitor; + +QmpOutputVisitor *qmp_output_visitor_new(void); +void qmp_output_visitor_cleanup(QmpOutputVisitor *v); + +QObject *qmp_output_get_qobject(QmpOutputVisitor *v); +Visitor *qmp_output_get_visitor(QmpOutputVisitor *v); + +#endif diff --git a/qapi/qmp-registry.c b/qapi/qmp-registry.c new file mode 100644 index 0000000000..3fe8866d44 --- /dev/null +++ b/qapi/qmp-registry.c @@ -0,0 +1,40 @@ +/* + * Core Definitions for QAPI/QMP Dispatch + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * Michael Roth <mdroth@us.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "qapi/qmp-core.h" + +static QTAILQ_HEAD(, QmpCommand) qmp_commands = + QTAILQ_HEAD_INITIALIZER(qmp_commands); + +void qmp_register_command(const char *name, QmpCommandFunc *fn) +{ + QmpCommand *cmd = qemu_mallocz(sizeof(*cmd)); + + cmd->name = name; + cmd->type = QCT_NORMAL; + cmd->fn = fn; + QTAILQ_INSERT_TAIL(&qmp_commands, cmd, node); +} + +QmpCommand *qmp_find_command(const char *name) +{ + QmpCommand *i; + + QTAILQ_FOREACH(i, &qmp_commands, node) { + if (strcmp(i->name, name) == 0) { + return i; + } + } + return NULL; +} diff --git a/qemu-common.h b/qemu-common.h index c2b79bd60e..ba55719700 100644 --- a/qemu-common.h +++ b/qemu-common.h @@ -2,16 +2,9 @@ #ifndef QEMU_COMMON_H #define QEMU_COMMON_H +#include "compiler.h" #include "config-host.h" -#define QEMU_NORETURN __attribute__ ((__noreturn__)) -#ifdef CONFIG_GCC_ATTRIBUTE_WARN_UNUSED_RESULT -#define QEMU_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) -#else -#define QEMU_WARN_UNUSED_RESULT -#endif - -#define QEMU_BUILD_BUG_ON(x) typedef char __build_bug_on__##__LINE__[(x)?-1:1]; #define TFR(expr) do { if ((expr) != -1) break; } while (errno == EINTR) typedef struct QEMUTimer QEMUTimer; @@ -82,22 +75,6 @@ struct iovec { #include <sys/uio.h> #endif -#if defined __GNUC__ -# if (__GNUC__ < 4) || \ - defined(__GNUC_MINOR__) && (__GNUC__ == 4) && (__GNUC_MINOR__ < 4) - /* gcc versions before 4.4.x don't support gnu_printf, so use printf. */ -# define GCC_ATTR __attribute__((__unused__, format(printf, 1, 2))) -# define GCC_FMT_ATTR(n, m) __attribute__((format(printf, n, m))) -# else - /* Use gnu_printf when supported (qemu uses standard format strings). */ -# define GCC_ATTR __attribute__((__unused__, format(gnu_printf, 1, 2))) -# define GCC_FMT_ATTR(n, m) __attribute__((format(gnu_printf, n, m))) -# endif -#else -#define GCC_ATTR /**/ -#define GCC_FMT_ATTR(n, m) -#endif - typedef int (*fprintf_function)(FILE *f, const char *fmt, ...) GCC_FMT_ATTR(2, 3); diff --git a/qemu-config.c b/qemu-config.c index c63741c6b1..93d20c6cf8 100644 --- a/qemu-config.c +++ b/qemu-config.c @@ -23,6 +23,7 @@ static QemuOptsList qemu_drive_opts = { },{ .name = "index", .type = QEMU_OPT_NUMBER, + .help = "index number", },{ .name = "cyls", .type = QEMU_OPT_NUMBER, @@ -46,6 +47,7 @@ static QemuOptsList qemu_drive_opts = { },{ .name = "snapshot", .type = QEMU_OPT_BOOL, + .help = "enable/disable snapshot mode", },{ .name = "file", .type = QEMU_OPT_STRING, @@ -65,12 +67,15 @@ static QemuOptsList qemu_drive_opts = { },{ .name = "serial", .type = QEMU_OPT_STRING, + .help = "disk serial number", },{ .name = "rerror", .type = QEMU_OPT_STRING, + .help = "read error action", },{ .name = "werror", .type = QEMU_OPT_STRING, + .help = "write error action", },{ .name = "addr", .type = QEMU_OPT_STRING, @@ -78,6 +83,7 @@ static QemuOptsList qemu_drive_opts = { },{ .name = "readonly", .type = QEMU_OPT_BOOL, + .help = "open drive file as read-only", }, { /* end of list */ } }, diff --git a/qemu-ga.c b/qemu-ga.c new file mode 100644 index 0000000000..6e2f61fe3c --- /dev/null +++ b/qemu-ga.c @@ -0,0 +1,654 @@ +/* + * QEMU Guest Agent + * + * Copyright IBM Corp. 2011 + * + * Authors: + * Adam Litke <aglitke@linux.vnet.ibm.com> + * Michael Roth <mdroth@linux.vnet.ibm.com> + * + * 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 <stdlib.h> +#include <stdio.h> +#include <stdbool.h> +#include <glib.h> +#include <gio/gio.h> +#include <getopt.h> +#include <termios.h> +#include <syslog.h> +#include "qemu_socket.h" +#include "json-streamer.h" +#include "json-parser.h" +#include "qint.h" +#include "qjson.h" +#include "qga/guest-agent-core.h" +#include "module.h" +#include "signal.h" +#include "qerror.h" +#include "error_int.h" + +#define QGA_VIRTIO_PATH_DEFAULT "/dev/virtio-ports/org.qemu.guest_agent.0" +#define QGA_PIDFILE_DEFAULT "/var/run/qemu-ga.pid" +#define QGA_BAUDRATE_DEFAULT B38400 /* for isa-serial channels */ +#define QGA_TIMEOUT_DEFAULT 30*1000 /* ms */ + +struct GAState { + JSONMessageParser parser; + GMainLoop *main_loop; + GSocket *conn_sock; + GIOChannel *conn_channel; + GSocket *listen_sock; + GIOChannel *listen_channel; + const char *path; + const char *method; + bool virtio; /* fastpath to check for virtio to deal with poll() quirks */ + GACommandState *command_state; + GLogLevelFlags log_level; + FILE *log_file; + bool logging_enabled; +}; + +static struct GAState *ga_state; + +static void quit_handler(int sig) +{ + g_debug("recieved signal num %d, quitting", sig); + + if (g_main_loop_is_running(ga_state->main_loop)) { + g_main_loop_quit(ga_state->main_loop); + } +} + +static void register_signal_handlers(void) +{ + struct sigaction sigact; + int ret; + + memset(&sigact, 0, sizeof(struct sigaction)); + sigact.sa_handler = quit_handler; + + ret = sigaction(SIGINT, &sigact, NULL); + if (ret == -1) { + g_error("error configuring signal handler: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + ret = sigaction(SIGTERM, &sigact, NULL); + if (ret == -1) { + g_error("error configuring signal handler: %s", strerror(errno)); + } +} + +static void usage(const char *cmd) +{ + printf( +"Usage: %s -c <channel_opts>\n" +"QEMU Guest Agent %s\n" +"\n" +" -m, --method transport method: one of unix-listen, virtio-serial, or\n" +" isa-serial (virtio-serial is the default)\n" +" -p, --path device/socket path (%s is the default for virtio-serial)\n" +" -l, --logfile set logfile path, logs to stderr by default\n" +" -f, --pidfile specify pidfile (default is %s)\n" +" -v, --verbose log extra debugging information\n" +" -V, --version print version information and exit\n" +" -d, --daemonize become a daemon\n" +" -h, --help display this help and exit\n" +"\n" +"Report bugs to <mdroth@linux.vnet.ibm.com>\n" + , cmd, QGA_VERSION, QGA_VIRTIO_PATH_DEFAULT, QGA_PIDFILE_DEFAULT); +} + +static void conn_channel_close(GAState *s); + +static const char *ga_log_level_str(GLogLevelFlags level) +{ + switch (level & G_LOG_LEVEL_MASK) { + case G_LOG_LEVEL_ERROR: + return "error"; + case G_LOG_LEVEL_CRITICAL: + return "critical"; + case G_LOG_LEVEL_WARNING: + return "warning"; + case G_LOG_LEVEL_MESSAGE: + return "message"; + case G_LOG_LEVEL_INFO: + return "info"; + case G_LOG_LEVEL_DEBUG: + return "debug"; + default: + return "user"; + } +} + +bool ga_logging_enabled(GAState *s) +{ + return s->logging_enabled; +} + +void ga_disable_logging(GAState *s) +{ + s->logging_enabled = false; +} + +void ga_enable_logging(GAState *s) +{ + s->logging_enabled = true; +} + +static void ga_log(const gchar *domain, GLogLevelFlags level, + const gchar *msg, gpointer opaque) +{ + GAState *s = opaque; + GTimeVal time; + const char *level_str = ga_log_level_str(level); + + if (!ga_logging_enabled(s)) { + return; + } + + level &= G_LOG_LEVEL_MASK; + if (g_strcmp0(domain, "syslog") == 0) { + syslog(LOG_INFO, "%s: %s", level_str, msg); + } else if (level & s->log_level) { + g_get_current_time(&time); + fprintf(s->log_file, + "%lu.%lu: %s: %s\n", time.tv_sec, time.tv_usec, level_str, msg); + fflush(s->log_file); + } +} + +static void become_daemon(const char *pidfile) +{ + pid_t pid, sid; + int pidfd; + char *pidstr = NULL; + + pid = fork(); + if (pid < 0) { + exit(EXIT_FAILURE); + } + if (pid > 0) { + exit(EXIT_SUCCESS); + } + + pidfd = open(pidfile, O_CREAT|O_WRONLY|O_EXCL, S_IRUSR|S_IWUSR); + if (pidfd == -1) { + g_critical("Cannot create pid file, %s", strerror(errno)); + exit(EXIT_FAILURE); + } + + if (asprintf(&pidstr, "%d", getpid()) == -1) { + g_critical("Cannot allocate memory"); + goto fail; + } + if (write(pidfd, pidstr, strlen(pidstr)) != strlen(pidstr)) { + free(pidstr); + g_critical("Failed to write pid file"); + goto fail; + } + + umask(0); + sid = setsid(); + if (sid < 0) { + goto fail; + } + if ((chdir("/")) < 0) { + goto fail; + } + + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + free(pidstr); + return; + +fail: + unlink(pidfile); + g_critical("failed to daemonize"); + exit(EXIT_FAILURE); +} + +static int conn_channel_send_buf(GIOChannel *channel, const char *buf, + gsize count) +{ + GError *err = NULL; + gsize written = 0; + GIOStatus status; + + while (count) { + status = g_io_channel_write_chars(channel, buf, count, &written, &err); + g_debug("sending data, count: %d", (int)count); + if (err != NULL) { + g_warning("error sending newline: %s", err->message); + return err->code; + } + if (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) { + return -EPIPE; + } + + if (status == G_IO_STATUS_NORMAL) { + count -= written; + } + } + + return 0; +} + +static int conn_channel_send_payload(GIOChannel *channel, QObject *payload) +{ + int ret = 0; + const char *buf; + QString *payload_qstr; + GError *err = NULL; + + g_assert(payload && channel); + + payload_qstr = qobject_to_json(payload); + if (!payload_qstr) { + return -EINVAL; + } + + qstring_append_chr(payload_qstr, '\n'); + buf = qstring_get_str(payload_qstr); + ret = conn_channel_send_buf(channel, buf, strlen(buf)); + if (ret) { + goto out_free; + } + + g_io_channel_flush(channel, &err); + if (err != NULL) { + g_warning("error flushing payload: %s", err->message); + ret = err->code; + goto out_free; + } + +out_free: + QDECREF(payload_qstr); + if (err) { + g_error_free(err); + } + return ret; +} + +static void process_command(GAState *s, QDict *req) +{ + QObject *rsp = NULL; + int ret; + + g_assert(req); + g_debug("processing command"); + rsp = qmp_dispatch(QOBJECT(req)); + if (rsp) { + ret = conn_channel_send_payload(s->conn_channel, rsp); + if (ret) { + g_warning("error sending payload: %s", strerror(ret)); + } + qobject_decref(rsp); + } else { + g_warning("error getting response"); + } +} + +/* handle requests/control events coming in over the channel */ +static void process_event(JSONMessageParser *parser, QList *tokens) +{ + GAState *s = container_of(parser, GAState, parser); + QObject *obj; + QDict *qdict; + Error *err = NULL; + int ret; + + g_assert(s && parser); + + g_debug("process_event: called"); + obj = json_parser_parse_err(tokens, NULL, &err); + if (err || !obj || qobject_type(obj) != QTYPE_QDICT) { + qobject_decref(obj); + qdict = qdict_new(); + if (!err) { + g_warning("failed to parse event: unknown error"); + error_set(&err, QERR_JSON_PARSING); + } else { + g_warning("failed to parse event: %s", error_get_pretty(err)); + } + qdict_put_obj(qdict, "error", error_get_qobject(err)); + error_free(err); + } else { + qdict = qobject_to_qdict(obj); + } + + g_assert(qdict); + + /* handle host->guest commands */ + if (qdict_haskey(qdict, "execute")) { + process_command(s, qdict); + } else { + if (!qdict_haskey(qdict, "error")) { + QDECREF(qdict); + qdict = qdict_new(); + g_warning("unrecognized payload format"); + error_set(&err, QERR_UNSUPPORTED); + qdict_put_obj(qdict, "error", error_get_qobject(err)); + error_free(err); + } + ret = conn_channel_send_payload(s->conn_channel, QOBJECT(qdict)); + if (ret) { + g_warning("error sending payload: %s", strerror(ret)); + } + } + + QDECREF(qdict); +} + +static gboolean conn_channel_read(GIOChannel *channel, GIOCondition condition, + gpointer data) +{ + GAState *s = data; + gchar buf[1024]; + gsize count; + GError *err = NULL; + memset(buf, 0, 1024); + GIOStatus status = g_io_channel_read_chars(channel, buf, 1024, + &count, &err); + if (err != NULL) { + g_warning("error reading channel: %s", err->message); + conn_channel_close(s); + g_error_free(err); + return false; + } + switch (status) { + case G_IO_STATUS_ERROR: + g_warning("problem"); + return false; + case G_IO_STATUS_NORMAL: + g_debug("read data, count: %d, data: %s", (int)count, buf); + json_message_parser_feed(&s->parser, (char *)buf, (int)count); + case G_IO_STATUS_AGAIN: + /* virtio causes us to spin here when no process is attached to + * host-side chardev. sleep a bit to mitigate this + */ + if (s->virtio) { + usleep(100*1000); + } + return true; + case G_IO_STATUS_EOF: + g_debug("received EOF"); + conn_channel_close(s); + if (s->virtio) { + return true; + } + return false; + default: + g_warning("unknown channel read status, closing"); + conn_channel_close(s); + return false; + } + return true; +} + +static int conn_channel_add(GAState *s, int fd) +{ + GIOChannel *conn_channel; + GError *err = NULL; + + g_assert(s && !s->conn_channel); + conn_channel = g_io_channel_unix_new(fd); + g_assert(conn_channel); + g_io_channel_set_encoding(conn_channel, NULL, &err); + if (err != NULL) { + g_warning("error setting channel encoding to binary"); + g_error_free(err); + return -1; + } + g_io_add_watch(conn_channel, G_IO_IN | G_IO_HUP, + conn_channel_read, s); + s->conn_channel = conn_channel; + return 0; +} + +static gboolean listen_channel_accept(GIOChannel *channel, + GIOCondition condition, gpointer data) +{ + GAState *s = data; + GError *err = NULL; + g_assert(channel != NULL); + int ret; + bool accepted = false; + + s->conn_sock = g_socket_accept(s->listen_sock, NULL, &err); + if (err != NULL) { + g_warning("error converting fd to gsocket: %s", err->message); + g_error_free(err); + goto out; + } + ret = conn_channel_add(s, g_socket_get_fd(s->conn_sock)); + if (ret) { + g_warning("error setting up connection"); + goto out; + } + accepted = true; + +out: + /* only accept 1 connection at a time */ + return !accepted; +} + +/* start polling for readable events on listen fd, new==true + * indicates we should use the existing s->listen_channel + */ +static int listen_channel_add(GAState *s, int listen_fd, bool new) +{ + GError *err = NULL; + + if (new) { + s->listen_channel = g_io_channel_unix_new(listen_fd); + if (s->listen_sock) { + g_object_unref(s->listen_sock); + } + s->listen_sock = g_socket_new_from_fd(listen_fd, &err); + if (err != NULL) { + g_warning("error converting fd to gsocket: %s", err->message); + g_error_free(err); + return -1; + } + } + g_io_add_watch(s->listen_channel, G_IO_IN, + listen_channel_accept, s); + return 0; +} + +/* cleanup state for closed connection/session, start accepting new + * connections if we're in listening mode + */ +static void conn_channel_close(GAState *s) +{ + if (strcmp(s->method, "unix-listen") == 0) { + g_io_channel_shutdown(s->conn_channel, true, NULL); + g_object_unref(s->conn_sock); + s->conn_sock = NULL; + listen_channel_add(s, 0, false); + } else if (strcmp(s->method, "virtio-serial") == 0) { + /* we spin on EOF for virtio-serial, so back off a bit. also, + * dont close the connection in this case, it'll resume normal + * operation when another process connects to host chardev + */ + usleep(100*1000); + goto out_noclose; + } + g_io_channel_unref(s->conn_channel); + s->conn_channel = NULL; +out_noclose: + return; +} + +static void init_guest_agent(GAState *s) +{ + struct termios tio; + int ret, fd; + + if (s->method == NULL) { + /* try virtio-serial as our default */ + s->method = "virtio-serial"; + } + + if (s->path == NULL) { + if (strcmp(s->method, "virtio-serial") != 0) { + g_critical("must specify a path for this channel"); + exit(EXIT_FAILURE); + } + /* try the default path for the virtio-serial port */ + s->path = QGA_VIRTIO_PATH_DEFAULT; + } + + if (strcmp(s->method, "virtio-serial") == 0) { + s->virtio = true; + fd = qemu_open(s->path, O_RDWR | O_NONBLOCK | O_ASYNC); + if (fd == -1) { + g_critical("error opening channel: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + ret = conn_channel_add(s, fd); + if (ret) { + g_critical("error adding channel to main loop"); + exit(EXIT_FAILURE); + } + } else if (strcmp(s->method, "isa-serial") == 0) { + fd = qemu_open(s->path, O_RDWR | O_NOCTTY); + if (fd == -1) { + g_critical("error opening channel: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + tcgetattr(fd, &tio); + /* set up serial port for non-canonical, dumb byte streaming */ + tio.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP | + INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY | + IMAXBEL); + tio.c_oflag = 0; + tio.c_lflag = 0; + tio.c_cflag |= QGA_BAUDRATE_DEFAULT; + /* 1 available byte min or reads will block (we'll set non-blocking + * elsewhere, else we have to deal with read()=0 instead) + */ + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 0; + /* flush everything waiting for read/xmit, it's garbage at this point */ + tcflush(fd, TCIFLUSH); + tcsetattr(fd, TCSANOW, &tio); + ret = conn_channel_add(s, fd); + if (ret) { + g_error("error adding channel to main loop"); + } + } else if (strcmp(s->method, "unix-listen") == 0) { + fd = unix_listen(s->path, NULL, strlen(s->path)); + if (fd == -1) { + g_critical("error opening path: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + ret = listen_channel_add(s, fd, true); + if (ret) { + g_critical("error binding/listening to specified socket"); + exit(EXIT_FAILURE); + } + } else { + g_critical("unsupported channel method/type: %s", s->method); + exit(EXIT_FAILURE); + } + + json_message_parser_init(&s->parser, process_event); + s->main_loop = g_main_loop_new(NULL, false); +} + +int main(int argc, char **argv) +{ + const char *sopt = "hVvdm:p:l:f:"; + const char *method = NULL, *path = NULL, *pidfile = QGA_PIDFILE_DEFAULT; + const struct option lopt[] = { + { "help", 0, NULL, 'h' }, + { "version", 0, NULL, 'V' }, + { "logfile", 0, NULL, 'l' }, + { "pidfile", 0, NULL, 'f' }, + { "verbose", 0, NULL, 'v' }, + { "method", 0, NULL, 'm' }, + { "path", 0, NULL, 'p' }, + { "daemonize", 0, NULL, 'd' }, + { NULL, 0, NULL, 0 } + }; + int opt_ind = 0, ch, daemonize = 0; + GLogLevelFlags log_level = G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL; + FILE *log_file = stderr; + GAState *s; + + while ((ch = getopt_long(argc, argv, sopt, lopt, &opt_ind)) != -1) { + switch (ch) { + case 'm': + method = optarg; + break; + case 'p': + path = optarg; + break; + case 'l': + log_file = fopen(optarg, "a"); + if (!log_file) { + g_critical("unable to open specified log file: %s", + strerror(errno)); + return EXIT_FAILURE; + } + break; + case 'f': + pidfile = optarg; + break; + case 'v': + /* enable all log levels */ + log_level = G_LOG_LEVEL_MASK; + break; + case 'V': + printf("QEMU Guest Agent %s\n", QGA_VERSION); + return 0; + case 'd': + daemonize = 1; + break; + case 'h': + usage(argv[0]); + return 0; + case '?': + g_print("Unknown option, try '%s --help' for more information.\n", + argv[0]); + return EXIT_FAILURE; + } + } + + if (daemonize) { + g_debug("starting daemon"); + become_daemon(pidfile); + } + + g_type_init(); + g_thread_init(NULL); + + s = qemu_mallocz(sizeof(GAState)); + s->conn_channel = NULL; + s->path = path; + s->method = method; + s->log_file = log_file; + s->log_level = log_level; + g_log_set_default_handler(ga_log, s); + g_log_set_fatal_mask(NULL, G_LOG_LEVEL_ERROR); + s->logging_enabled = true; + s->command_state = ga_command_state_new(); + ga_command_state_init(s, s->command_state); + ga_command_state_init_all(s->command_state); + ga_state = s; + + module_call_init(MODULE_INIT_QAPI); + init_guest_agent(ga_state); + register_signal_handlers(); + + g_main_loop_run(ga_state->main_loop); + + ga_command_state_cleanup_all(ga_state->command_state); + unlink(pidfile); + + return 0; +} diff --git a/qemu-img-cmds.hx b/qemu-img-cmds.hx index 2b70618c70..1299e83ef2 100644 --- a/qemu-img-cmds.hx +++ b/qemu-img-cmds.hx @@ -30,7 +30,7 @@ ETEXI DEF("convert", img_convert, "convert [-c] [-p] [-f fmt] [-t cache] [-O output_fmt] [-o options] [-s snapshot_name] filename [filename2 [...]] output_filename") STEXI -@item convert [-c] [-f @var{fmt}] [-O @var{output_fmt}] [-o @var{options}] [-s @var{snapshot_name}] @var{filename} [@var{filename2} [...]] @var{output_filename} +@item convert [-c] [-p] [-f @var{fmt}] [-O @var{output_fmt}] [-o @var{options}] [-s @var{snapshot_name}] @var{filename} [@var{filename2} [...]] @var{output_filename} ETEXI DEF("info", img_info, @@ -48,7 +48,7 @@ ETEXI DEF("rebase", img_rebase, "rebase [-f fmt] [-t cache] [-p] [-u] -b backing_file [-F backing_fmt] filename") STEXI -@item rebase [-f @var{fmt}] [-u] -b @var{backing_file} [-F @var{backing_fmt}] @var{filename} +@item rebase [-f @var{fmt}] [-p] [-u] -b @var{backing_file} [-F @var{backing_fmt}] @var{filename} ETEXI DEF("resize", img_resize, diff --git a/qemu-img.c b/qemu-img.c index 54137a4e92..b205e98dd2 100644 --- a/qemu-img.c +++ b/qemu-img.c @@ -1024,35 +1024,6 @@ out: return 0; } -#ifdef _WIN32 -static int64_t get_allocated_file_size(const char *filename) -{ - typedef DWORD (WINAPI * get_compressed_t)(const char *filename, DWORD *high); - get_compressed_t get_compressed; - struct _stati64 st; - - /* WinNT support GetCompressedFileSize to determine allocate size */ - get_compressed = (get_compressed_t) GetProcAddress(GetModuleHandle("kernel32"), "GetCompressedFileSizeA"); - if (get_compressed) { - DWORD high, low; - low = get_compressed(filename, &high); - if (low != 0xFFFFFFFFlu || GetLastError() == NO_ERROR) - return (((int64_t) high) << 32) + low; - } - - if (_stati64(filename, &st) < 0) - return -1; - return st.st_size; -} -#else -static int64_t get_allocated_file_size(const char *filename) -{ - struct stat st; - if (stat(filename, &st) < 0) - return -1; - return (int64_t)st.st_blocks * 512; -} -#endif static void dump_snapshots(BlockDriverState *bs) { @@ -1112,7 +1083,7 @@ static int img_info(int argc, char **argv) bdrv_get_format(bs, fmt_name, sizeof(fmt_name)); bdrv_get_geometry(bs, &total_sectors); get_human_readable_size(size_buf, sizeof(size_buf), total_sectors * 512); - allocated_size = get_allocated_file_size(filename); + allocated_size = bdrv_get_allocated_file_size(bs); if (allocated_size < 0) { snprintf(dsize_buf, sizeof(dsize_buf), "unavailable"); } else { diff --git a/qemu-img.texi b/qemu-img.texi index 526474c112..495a1b6695 100644 --- a/qemu-img.texi +++ b/qemu-img.texi @@ -38,6 +38,8 @@ by the used format or see the format descriptions below for details. indicates that target image must be compressed (qcow format only) @item -h with or without a command shows help and lists the supported formats +@item -p +display progress bar (convert and rebase commands only) @end table Parameters to snapshot subcommand: @@ -84,7 +86,7 @@ it doesn't need to be specified separately in this case. Commit the changes recorded in @var{filename} in its base image. -@item convert [-c] [-f @var{fmt}] [-O @var{output_fmt}] [-o @var{options}] [-s @var{snapshot_name}] @var{filename} [@var{filename2} [...]] @var{output_filename} +@item convert [-c] [-p] [-f @var{fmt}] [-O @var{output_fmt}] [-o @var{options}] [-s @var{snapshot_name}] @var{filename} [@var{filename2} [...]] @var{output_filename} Convert the disk image @var{filename} or a snapshot @var{snapshot_name} to disk image @var{output_filename} using format @var{output_fmt}. It can be optionally compressed (@code{-c} @@ -114,7 +116,7 @@ they are displayed too. List, apply, create or delete snapshots in image @var{filename}. -@item rebase [-f @var{fmt}] [-u] -b @var{backing_file} [-F @var{backing_fmt}] @var{filename} +@item rebase [-f @var{fmt}] [-p] [-u] -b @var{backing_file} [-F @var{backing_fmt}] @var{filename} Changes the backing file of an image. Only the formats @code{qcow2} and @code{qed} support changing the backing file. @@ -20,7 +20,7 @@ #define VERSION "0.0.1" -#define CMD_NOFILE_OK 0x01 +#define CMD_NOFILE_OK 0x01 char *progname; static BlockDriverState *bs; @@ -35,16 +35,16 @@ static int misalign; */ static int parse_pattern(const char *arg) { - char *endptr = NULL; - long pattern; + 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; - } + 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; + return pattern; } /* @@ -54,70 +54,73 @@ static int parse_pattern(const char *arg) * that is specified on the command line. */ -#define MISALIGN_OFFSET 16 +#define MISALIGN_OFFSET 16 static void *qemu_io_alloc(size_t len, int pattern) { - void *buf; - - if (misalign) - len += MISALIGN_OFFSET; - buf = qemu_blockalign(bs, len); - memset(buf, pattern, len); - if (misalign) - buf += MISALIGN_OFFSET; - return buf; + void *buf; + + if (misalign) { + len += MISALIGN_OFFSET; + } + buf = qemu_blockalign(bs, 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); + if (misalign) { + p -= MISALIGN_OFFSET; + } + qemu_vfree(p); } -static void -dump_buffer(const void *buffer, int64_t offset, int len) +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("%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"); - } + int i, 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 timeval *t, int64_t offset, - int count, int total, int cnt, int Cflag) +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 %" 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("%d,%d,%s,%.3f,%.3f\n", - total, cnt, ts, - tdiv((double)total, *t), - tdiv((double)cnt, *t)); - } + 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 %" 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("%d,%d,%s,%.3f,%.3f\n", + total, cnt, ts, + tdiv((double)total, *t), + tdiv((double)cnt, *t)); + } } /* @@ -127,192 +130,200 @@ print_report(const char *op, struct timeval *t, int64_t offset, 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 = 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) { - printf("non-numeric length argument -- %s\n", arg); - goto fail; - } - - /* should be SIZE_T_MAX, but that doesn't exist */ - if (len > INT_MAX) { - printf("too large length argument -- %s\n", arg); - goto fail; - } - - if (len & 0x1ff) { - printf("length argument %" PRId64 - " is not sector aligned\n", len); - goto fail; - } - - 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]; - } + size_t *sizes = calloc(nr_iov, sizeof(size_t)); + 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) { + printf("non-numeric length argument -- %s\n", arg); + goto fail; + } + + /* should be SIZE_T_MAX, but that doesn't exist */ + if (len > INT_MAX) { + printf("too large length argument -- %s\n", arg); + goto fail; + } + + if (len & 0x1ff) { + printf("length argument %" PRId64 + " is not sector aligned\n", len); + goto fail; + } + + 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]; + } fail: - free(sizes); - return buf; + free(sizes); + return buf; } static int do_read(char *buf, int64_t offset, int count, int *total) { - int ret; + int ret; - ret = bdrv_read(bs, offset >> 9, (uint8_t *)buf, count >> 9); - if (ret < 0) - return ret; - *total = count; - return 1; + 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; + int ret; - ret = bdrv_write(bs, offset >> 9, (uint8_t *)buf, count >> 9); - if (ret < 0) - return ret; - *total = count; - return 1; + 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; + *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; + *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; + *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; + *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; + *(int *)opaque = ret; } static int do_aio_readv(QEMUIOVector *qiov, int64_t offset, int *total) { - BlockDriverAIOCB *acb; - int async_ret = NOT_DONE; + 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(); + 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; + *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; + 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; + 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(); + while (async_ret == NOT_DONE) { + qemu_aio_wait(); + } - *total = qiov->size; - return async_ret < 0 ? async_ret : 1; + *total = qiov->size; + return async_ret < 0 ? async_ret : 1; } struct multiwrite_async_ret { - int num_done; - int error; + int num_done; + int error; }; static void multiwrite_cb(void *opaque, int ret) { - struct multiwrite_async_ret *async_ret = opaque; + struct multiwrite_async_ret *async_ret = opaque; - async_ret->num_done++; - if (ret < 0) { - async_ret->error = ret; - } + async_ret->num_done++; + if (ret < 0) { + async_ret->error = ret; + } } static int do_aio_multiwrite(BlockRequest* reqs, int num_reqs, int *total) { - int i, ret; - struct multiwrite_async_ret async_ret = { - .num_done = 0, - .error = 0, - }; - - *total = 0; - for (i = 0; i < num_reqs; i++) { - reqs[i].cb = multiwrite_cb; - reqs[i].opaque = &async_ret; - *total += reqs[i].qiov->size; - } - - ret = bdrv_aio_multiwrite(bs, reqs, num_reqs); - if (ret < 0) { - return ret; - } - - while (async_ret.num_done < num_reqs) { - qemu_aio_wait(); - } - - return async_ret.error < 0 ? async_ret.error : 1; + int i, ret; + struct multiwrite_async_ret async_ret = { + .num_done = 0, + .error = 0, + }; + + *total = 0; + for (i = 0; i < num_reqs; i++) { + reqs[i].cb = multiwrite_cb; + reqs[i].opaque = &async_ret; + *total += reqs[i].qiov->size; + } + + ret = bdrv_aio_multiwrite(bs, reqs, num_reqs); + if (ret < 0) { + return ret; + } + + while (async_ret.num_done < num_reqs) { + qemu_aio_wait(); + } + + return async_ret.error < 0 ? async_ret.error : 1; } -static void -read_help(void) +static void read_help(void) { - printf( + printf( "\n" " reads a range of bytes from the given offset\n" "\n" @@ -335,94 +346,95 @@ read_help(void) static int read_f(int argc, char **argv); 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, + .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 int -read_f(int argc, char **argv) +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; - } + 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); @@ -437,66 +449,68 @@ read_f(int argc, char **argv) return 0; } - if (!pflag) - if (offset & 0x1ff) { - printf("offset %" PRId64 " is not sector aligned\n", - 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 %" - PRId64 ", %d bytes\n", - 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); + if (!pflag) { + if (offset & 0x1ff) { + printf("offset %" PRId64 " is not sector aligned\n", + 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 %" + PRId64 ", %d bytes\n", + 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); + qemu_io_free(buf); - return 0; + return 0; } -static void -readv_help(void) +static void readv_help(void) { - printf( + printf( "\n" " reads a range of bytes from the given offset into multiple buffers\n" "\n" @@ -516,111 +530,112 @@ readv_help(void) static int readv_f(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, + .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(int argc, char **argv) +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; - /* Some compilers get confused and warn if this is not initialized. */ - int total = 0; - 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 %" PRId64 " is not sector aligned\n", - 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 %" - PRId64 ", %zd bytes\n", - 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); + struct timeval t1, t2; + int Cflag = 0, qflag = 0, vflag = 0; + int c, cnt; + 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; + 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 %" PRId64 " is not sector aligned\n", + 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 %" + PRId64 ", %zd bytes\n", 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; + qemu_io_free(buf); + return 0; } -static void -write_help(void) +static void write_help(void) { - printf( + printf( "\n" " writes a range of bytes from the given offset\n" "\n" @@ -640,121 +655,124 @@ write_help(void) static int write_f(int argc, char **argv); 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, + .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 int -write_f(int argc, char **argv) +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 %" PRId64 " is not sector aligned\n", - 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); + 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 %" PRId64 " is not sector aligned\n", + 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); + qemu_io_free(buf); - return 0; + return 0; } static void writev_help(void) { - printf( + printf( "\n" " writes a range of bytes from the given offset source from multiple buffers\n" "\n" @@ -772,90 +790,91 @@ writev_help(void) static int writev_f(int argc, char **argv); 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, + .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, }; -static int -writev_f(int argc, char **argv) +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; - /* Some compilers get confused and warn if this is not initialized. */ - int total = 0; - 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 %" PRId64 " is not sector aligned\n", - 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); + struct timeval t1, t2; + int Cflag = 0, qflag = 0; + int c, cnt; + char *buf; + int64_t offset; + /* Some compilers get confused and warn if this is not initialized. */ + int total = 0; + 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 %" PRId64 " is not sector aligned\n", + 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; + qemu_io_free(buf); + return 0; } -static void -multiwrite_help(void) +static void multiwrite_help(void) { - printf( + printf( "\n" " writes a range of bytes from the given offset source from multiple buffers,\n" " in a batch of requests that may be merged by qemu\n" @@ -876,217 +895,215 @@ multiwrite_help(void) static int multiwrite_f(int argc, char **argv); static const cmdinfo_t multiwrite_cmd = { - .name = "multiwrite", - .cfunc = multiwrite_f, - .argmin = 2, - .argmax = -1, - .args = "[-Cq] [-P pattern ] off len [len..] [; off len [len..]..]", - .oneline = "issues multiple write requests at once", - .help = multiwrite_help, + .name = "multiwrite", + .cfunc = multiwrite_f, + .argmin = 2, + .argmax = -1, + .args = "[-Cq] [-P pattern ] off len [len..] [; off len [len..]..]", + .oneline = "issues multiple write requests at once", + .help = multiwrite_help, }; -static int -multiwrite_f(int argc, char **argv) +static int multiwrite_f(int argc, char **argv) { - struct timeval t1, t2; - int Cflag = 0, qflag = 0; - int c, cnt; - char **buf; - int64_t offset, first_offset = 0; - /* Some compilers get confused and warn if this is not initialized. */ - int total = 0; - int nr_iov; - int nr_reqs; - int pattern = 0xcd; - QEMUIOVector *qiovs; - int i; - BlockRequest *reqs; - - 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); - - nr_reqs = 1; - for (i = optind; i < argc; i++) { - if (!strcmp(argv[i], ";")) { - nr_reqs++; - } - } - - reqs = qemu_malloc(nr_reqs * sizeof(*reqs)); - buf = qemu_malloc(nr_reqs * sizeof(*buf)); - qiovs = qemu_malloc(nr_reqs * sizeof(*qiovs)); - - for (i = 0; i < nr_reqs; i++) { - int j; - - /* Read the offset of the request */ - offset = cvtnum(argv[optind]); - if (offset < 0) { - printf("non-numeric offset argument -- %s\n", argv[optind]); - return 0; - } - optind++; - - if (offset & 0x1ff) { - printf("offset %lld is not sector aligned\n", - (long long)offset); - return 0; - } + struct timeval t1, t2; + int Cflag = 0, qflag = 0; + int c, cnt; + char **buf; + int64_t offset, first_offset = 0; + /* Some compilers get confused and warn if this is not initialized. */ + int total = 0; + int nr_iov; + int nr_reqs; + int pattern = 0xcd; + QEMUIOVector *qiovs; + int i; + BlockRequest *reqs; + + 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); + } + + nr_reqs = 1; + for (i = optind; i < argc; i++) { + if (!strcmp(argv[i], ";")) { + nr_reqs++; + } + } + + reqs = qemu_malloc(nr_reqs * sizeof(*reqs)); + buf = qemu_malloc(nr_reqs * sizeof(*buf)); + qiovs = qemu_malloc(nr_reqs * sizeof(*qiovs)); + + for (i = 0; i < nr_reqs; i++) { + int j; + + /* Read the offset of the request */ + offset = cvtnum(argv[optind]); + if (offset < 0) { + printf("non-numeric offset argument -- %s\n", argv[optind]); + return 0; + } + optind++; + + if (offset & 0x1ff) { + printf("offset %lld is not sector aligned\n", + (long long)offset); + return 0; + } if (i == 0) { first_offset = offset; } - /* Read lengths for qiov entries */ - for (j = optind; j < argc; j++) { - if (!strcmp(argv[j], ";")) { - break; - } - } + /* Read lengths for qiov entries */ + for (j = optind; j < argc; j++) { + if (!strcmp(argv[j], ";")) { + break; + } + } - nr_iov = j - optind; + nr_iov = j - optind; - /* Build request */ - reqs[i].qiov = &qiovs[i]; - buf[i] = create_iovec(reqs[i].qiov, &argv[optind], nr_iov, pattern); - reqs[i].sector = offset >> 9; - reqs[i].nb_sectors = reqs[i].qiov->size >> 9; + /* Build request */ + reqs[i].qiov = &qiovs[i]; + buf[i] = create_iovec(reqs[i].qiov, &argv[optind], nr_iov, pattern); + reqs[i].sector = offset >> 9; + reqs[i].nb_sectors = reqs[i].qiov->size >> 9; - optind = j + 1; + optind = j + 1; - offset += reqs[i].qiov->size; - pattern++; - } + offset += reqs[i].qiov->size; + pattern++; + } - gettimeofday(&t1, NULL); - cnt = do_aio_multiwrite(reqs, nr_reqs, &total); - gettimeofday(&t2, NULL); + gettimeofday(&t1, NULL); + cnt = do_aio_multiwrite(reqs, nr_reqs, &total); + gettimeofday(&t2, NULL); - if (cnt < 0) { - printf("aio_multiwrite failed: %s\n", strerror(-cnt)); - goto out; - } + if (cnt < 0) { + printf("aio_multiwrite failed: %s\n", strerror(-cnt)); + goto out; + } - if (qflag) - goto out; + if (qflag) { + goto out; + } - /* Finally, report back -- -C gives a parsable format */ - t2 = tsub(t2, t1); - print_report("wrote", &t2, first_offset, total, total, cnt, Cflag); + /* Finally, report back -- -C gives a parsable format */ + t2 = tsub(t2, t1); + print_report("wrote", &t2, first_offset, total, total, cnt, Cflag); out: - for (i = 0; i < nr_reqs; i++) { - qemu_io_free(buf[i]); - qemu_iovec_destroy(&qiovs[i]); - } - qemu_free(buf); - qemu_free(reqs); - qemu_free(qiovs); - return 0; + for (i = 0; i < nr_reqs; i++) { + qemu_io_free(buf[i]); + qemu_iovec_destroy(&qiovs[i]); + } + qemu_free(buf); + qemu_free(reqs); + qemu_free(qiovs); + return 0; } struct aio_ctx { - QEMUIOVector qiov; - int64_t offset; - char *buf; - int qflag; - int vflag; - int Cflag; - int Pflag; - int pattern; - struct timeval t1; + 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) +static void aio_write_done(void *opaque, int ret) { - struct aio_ctx *ctx = opaque; - struct timeval t2; + struct aio_ctx *ctx = opaque; + struct timeval t2; - gettimeofday(&t2, NULL); + gettimeofday(&t2, NULL); - if (ret < 0) { - printf("aio_write failed: %s\n", strerror(-ret)); - goto out; - } + if (ret < 0) { + printf("aio_write failed: %s\n", strerror(-ret)); + goto out; + } - if (ctx->qflag) { - 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); + /* 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); + qemu_io_free(ctx->buf); + free(ctx); } -static void -aio_read_done(void *opaque, int ret) +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 %" - PRId64 ", %zd bytes\n", - 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); + 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 %" + PRId64 ", %zd bytes\n", 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); + qemu_io_free(ctx->buf); + free(ctx); } -static void -aio_read_help(void) +static void aio_read_help(void) { - printf( + printf( "\n" " asynchronously reads a range of bytes from the given offset\n" "\n" @@ -1107,88 +1124,86 @@ aio_read_help(void) static int aio_read_f(int argc, char **argv); 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, + .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 int -aio_read_f(int argc, char **argv) +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) { - free(ctx); - 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 %" PRId64 " is not sector aligned\n", - 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; + 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) { + free(ctx); + 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 %" PRId64 " is not sector aligned\n", + 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 void -aio_write_help(void) +static void aio_write_help(void) { - printf( + printf( "\n" -" asynchronously writes a range of bytes from the given offset source \n" +" asynchronously writes a range of bytes from the given offset source\n" " from multiple buffers\n" "\n" " Example:\n" @@ -1207,199 +1222,196 @@ aio_write_help(void) static int aio_write_f(int argc, char **argv); 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, + .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_write_f(int argc, char **argv) +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 %" PRId64 " is not sector aligned\n", - 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; + 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 %" PRId64 " is not sector aligned\n", + 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 int -aio_flush_f(int argc, char **argv) +static int aio_flush_f(int argc, char **argv) { - qemu_aio_flush(); - return 0; + qemu_aio_flush(); + return 0; } static const cmdinfo_t aio_flush_cmd = { - .name = "aio_flush", - .cfunc = aio_flush_f, - .oneline = "completes all outstanding aio requests" + .name = "aio_flush", + .cfunc = aio_flush_f, + .oneline = "completes all outstanding aio requests" }; -static int -flush_f(int argc, char **argv) +static int flush_f(int argc, char **argv) { - bdrv_flush(bs); - return 0; + 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", + .name = "flush", + .altname = "f", + .cfunc = flush_f, + .oneline = "flush all in-core file state to disk", }; -static int -truncate_f(int argc, char **argv) +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\n", strerror(-ret)); - return 0; - } - - return 0; + 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\n", 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", + .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) +static int length_f(int argc, char **argv) { - int64_t size; - char s1[64]; - - size = bdrv_getlength(bs); - if (size < 0) { - printf("getlength: %s\n", strerror(-size)); - return 0; - } - - cvtstr(size, s1, sizeof(s1)); - printf("%s\n", s1); - return 0; + int64_t size; + char s1[64]; + + size = bdrv_getlength(bs); + if (size < 0) { + printf("getlength: %s\n", 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", + .name = "length", + .altname = "l", + .cfunc = length_f, + .oneline = "gets the length of the current file", }; -static int -info_f(int argc, char **argv) +static int info_f(int argc, char **argv) { - BlockDriverInfo bdi; - char s1[64], s2[64]; - int ret; + 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); + 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; + 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)); + 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); + printf("cluster size: %s\n", s1); + printf("vm state offset: %s\n", s2); - return 0; + return 0; } static const cmdinfo_t info_cmd = { - .name = "info", - .altname = "i", - .cfunc = info_f, - .oneline = "prints information about the current file", + .name = "info", + .altname = "i", + .cfunc = info_f, + .oneline = "prints information about the current file", }; -static void -discard_help(void) +static void discard_help(void) { - printf( + printf( "\n" " discards a range of bytes from the given offset\n" "\n" @@ -1415,148 +1427,147 @@ discard_help(void) static int discard_f(int argc, char **argv); static const cmdinfo_t discard_cmd = { - .name = "discard", - .altname = "d", - .cfunc = discard_f, - .argmin = 2, - .argmax = -1, - .args = "[-Cq] off len", - .oneline = "discards a number of bytes at a specified offset", - .help = discard_help, + .name = "discard", + .altname = "d", + .cfunc = discard_f, + .argmin = 2, + .argmax = -1, + .args = "[-Cq] off len", + .oneline = "discards a number of bytes at a specified offset", + .help = discard_help, }; -static int -discard_f(int argc, char **argv) +static int discard_f(int argc, char **argv) { - struct timeval t1, t2; - int Cflag = 0, qflag = 0; - int c, ret; - int64_t offset; - int count; - - while ((c = getopt(argc, argv, "Cq")) != EOF) { - switch (c) { - case 'C': - Cflag = 1; - break; - case 'q': - qflag = 1; - break; - default: - return command_usage(&discard_cmd); - } - } - - if (optind != argc - 2) { - return command_usage(&discard_cmd); - } - - 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; - } - - gettimeofday(&t1, NULL); - ret = bdrv_discard(bs, offset >> BDRV_SECTOR_BITS, count >> BDRV_SECTOR_BITS); - gettimeofday(&t2, NULL); - - if (ret < 0) { - printf("discard failed: %s\n", strerror(-ret)); - goto out; - } - - /* Finally, report back -- -C gives a parsable format */ - if (!qflag) { - t2 = tsub(t2, t1); - print_report("discard", &t2, offset, count, count, 1, Cflag); - } + struct timeval t1, t2; + int Cflag = 0, qflag = 0; + int c, ret; + int64_t offset; + int count; + + while ((c = getopt(argc, argv, "Cq")) != EOF) { + switch (c) { + case 'C': + Cflag = 1; + break; + case 'q': + qflag = 1; + break; + default: + return command_usage(&discard_cmd); + } + } + + if (optind != argc - 2) { + return command_usage(&discard_cmd); + } + + 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; + } + + gettimeofday(&t1, NULL); + ret = bdrv_discard(bs, offset >> BDRV_SECTOR_BITS, + count >> BDRV_SECTOR_BITS); + gettimeofday(&t2, NULL); + + if (ret < 0) { + printf("discard failed: %s\n", strerror(-ret)); + goto out; + } + + /* Finally, report back -- -C gives a parsable format */ + if (!qflag) { + t2 = tsub(t2, t1); + print_report("discard", &t2, offset, count, count, 1, Cflag); + } out: - return 0; + return 0; } -static int -alloc_f(int argc, char **argv) +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 %" PRId64 " is not sector aligned\n", - 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)); - - printf("%d/%d sectors allocated at offset %s\n", - sum_alloc, nb_sectors, s1); - return 0; + 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 %" PRId64 " is not sector aligned\n", + 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)); + + 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", + .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 -map_f(int argc, char **argv) +static int map_f(int argc, char **argv) { - int64_t offset; - int64_t nb_sectors; - char s1[64]; - int num, num_checked; - int ret; - const char *retstr; - - offset = 0; - nb_sectors = bs->total_sectors; - - do { - num_checked = MIN(nb_sectors, INT_MAX); - ret = bdrv_is_allocated(bs, offset, num_checked, &num); - retstr = ret ? " allocated" : "not allocated"; - cvtstr(offset << 9ULL, s1, sizeof(s1)); - printf("[% 24" PRId64 "] % 8d/% 8d sectors %s at offset %s (%d)\n", - offset << 9ULL, num, num_checked, retstr, s1, ret); - - offset += num; - nb_sectors -= num; - } while(offset < bs->total_sectors); - - return 0; + int64_t offset; + int64_t nb_sectors; + char s1[64]; + int num, num_checked; + int ret; + const char *retstr; + + offset = 0; + nb_sectors = bs->total_sectors; + + do { + num_checked = MIN(nb_sectors, INT_MAX); + ret = bdrv_is_allocated(bs, offset, num_checked, &num); + retstr = ret ? " allocated" : "not allocated"; + cvtstr(offset << 9ULL, s1, sizeof(s1)); + printf("[% 24" PRId64 "] % 8d/% 8d sectors %s at offset %s (%d)\n", + offset << 9ULL, num, num_checked, retstr, s1, ret); + + offset += num; + nb_sectors -= num; + } while (offset < bs->total_sectors); + + return 0; } static const cmdinfo_t map_cmd = { @@ -1569,50 +1580,48 @@ static const cmdinfo_t map_cmd = { }; -static int -close_f(int argc, char **argv) +static int close_f(int argc, char **argv) { - bdrv_close(bs); - bs = NULL; - return 0; + 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", + .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; - } - - if (growable) { - if (bdrv_file_open(&bs, name, flags)) { - fprintf(stderr, "%s: can't open device %s\n", progname, name); - return 1; - } - } else { - bs = bdrv_new("hda"); - - if (bdrv_open(bs, name, flags, NULL) < 0) { - fprintf(stderr, "%s: can't open device %s\n", progname, name); - bs = NULL; - return 1; - } - } - - return 0; + if (bs) { + fprintf(stderr, "file open already, try 'help close'\n"); + return 1; + } + + if (growable) { + if (bdrv_file_open(&bs, name, flags)) { + fprintf(stderr, "%s: can't open device %s\n", progname, name); + return 1; + } + } else { + bs = bdrv_new("hda"); + + if (bdrv_open(bs, name, flags, NULL) < 0) { + fprintf(stderr, "%s: can't open device %s\n", progname, name); + bs = NULL; + return 1; + } + } + + return 0; } -static void -open_help(void) +static void open_help(void) { - printf( + printf( "\n" " opens a new file in the requested mode\n" "\n" @@ -1630,80 +1639,78 @@ open_help(void) static int open_f(int argc, char **argv); 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, + .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 -open_f(int argc, char **argv) +static int open_f(int argc, char **argv) { - int flags = 0; - int readonly = 0; - int growable = 0; - int c; - - while ((c = getopt(argc, argv, "snrg")) != EOF) { - switch (c) { - case 's': - flags |= BDRV_O_SNAPSHOT; - break; - case 'n': - flags |= BDRV_O_NOCACHE | BDRV_O_CACHE_WB; - break; - case 'r': - readonly = 1; - break; - case 'g': - growable = 1; - break; - default: - return command_usage(&open_cmd); - } - } - - if (!readonly) { - flags |= BDRV_O_RDWR; + int flags = 0; + int readonly = 0; + int growable = 0; + int c; + + while ((c = getopt(argc, argv, "snrg")) != EOF) { + switch (c) { + case 's': + flags |= BDRV_O_SNAPSHOT; + break; + case 'n': + flags |= BDRV_O_NOCACHE | BDRV_O_CACHE_WB; + break; + case 'r': + readonly = 1; + break; + case 'g': + growable = 1; + break; + default: + return command_usage(&open_cmd); } + } + + if (!readonly) { + flags |= BDRV_O_RDWR; + } - if (optind != argc - 1) - return command_usage(&open_cmd); + if (optind != argc - 1) { + return command_usage(&open_cmd); + } - return openfile(argv[optind], flags, growable); + return openfile(argv[optind], flags, growable); } -static int -init_args_command( - int index) +static int init_args_command(int index) { - /* only one device allowed so far */ - if (index >= 1) - return 0; - return ++index; + /* only one device allowed so far */ + if (index >= 1) { + return 0; + } + return ++index; } -static int -init_check_command( - const cmdinfo_t *ct) +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; + 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( + printf( "Usage: %s [-h] [-V] [-rsnm] [-c cmd] ... [file]\n" "QEMU Disk exerciser\n" "\n" @@ -1717,115 +1724,117 @@ static void usage(const char *name) " -h, --help display this help and exit\n" " -V, --version output version information and exit\n" "\n", - name); + name); } int main(int argc, char **argv) { - int readonly = 0; - int growable = 0; - const char *sopt = "hVc:rsnmgk"; - const struct option lopt[] = { - { "help", 0, NULL, 'h' }, - { "version", 0, NULL, 'V' }, - { "offset", 1, NULL, 'o' }, - { "cmd", 1, 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 | BDRV_O_CACHE_WB; - break; - case 'c': - add_user_command(optarg); - 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(&multiwrite_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(&discard_cmd); - add_command(&alloc_cmd); - add_command(&map_cmd); - - add_args_command(init_args_command); - add_check_command(init_check_command); - - /* open the device */ - if (!readonly) { - flags |= BDRV_O_RDWR; + int readonly = 0; + int growable = 0; + const char *sopt = "hVc:rsnmgk"; + const struct option lopt[] = { + { "help", 0, NULL, 'h' }, + { "version", 0, NULL, 'V' }, + { "offset", 1, NULL, 'o' }, + { "cmd", 1, 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 | BDRV_O_CACHE_WB; + break; + case 'c': + add_user_command(optarg); + 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); + } - if ((argc - optind) == 1) - openfile(argv[optind], flags, growable); - command_loop(); + 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(&multiwrite_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(&discard_cmd); + add_command(&alloc_cmd); + add_command(&map_cmd); + + add_args_command(init_args_command); + add_check_command(init_check_command); + + /* open the device */ + if (!readonly) { + 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(); + /* + * Make sure all outstanding requests get flushed the program exits. + */ + qemu_aio_flush(); - if (bs) - bdrv_close(bs); - return 0; + if (bs) { + bdrv_close(bs); + } + return 0; } diff --git a/qemu-options.hx b/qemu-options.hx index e6d7adc3af..64114dd448 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -160,6 +160,14 @@ an untrusted format header. This option specifies the serial number to assign to the device. @item addr=@var{addr} Specify the controller's PCI address (if=virtio only). +@item werror=@var{action},rerror=@var{action} +Specify which @var{action} to take on write and read errors. Valid actions are: +"ignore" (ignore the error and try to continue), "stop" (pause QEMU), +"report" (report the error to the guest), "enospc" (pause QEMU only if the +host disk is full; report the error to the guest otherwise). +The default setting is @option{werror=enospc} and @option{rerror=report}. +@item readonly +Open drive @option{file} as read-only. Guest write attempts will fail. @end table By default, writethrough caching is used for all block device. This means that @@ -141,6 +141,11 @@ static const QErrorStringTable qerror_table[] = { .desc = "Invalid JSON syntax", }, { + .error_fmt = QERR_JSON_PARSE_ERROR, + .desc = "JSON parse error, %(message)", + + }, + { .error_fmt = QERR_KVM_MISSING_CAP, .desc = "Using KVM without %(capability), %(feature) unavailable", }, @@ -213,6 +218,14 @@ static const QErrorStringTable qerror_table[] = { .error_fmt = QERR_VNC_SERVER_FAILED, .desc = "Could not start VNC server on %(target)", }, + { + .error_fmt = QERR_QGA_LOGGING_FAILED, + .desc = "Guest agent failed to log non-optional log statement", + }, + { + .error_fmt = QERR_QGA_COMMAND_FAILED, + .desc = "Guest agent command failed, error was '%(message)'", + }, {} }; @@ -124,6 +124,9 @@ QError *qobject_to_qerror(const QObject *obj); #define QERR_JSON_PARSE_ERROR \ "{ 'class': 'JSONParseError', 'data': { 'message': %s } }" +#define QERR_BUFFER_OVERRUN \ + "{ 'class': 'BufferOverrun', 'data': {} }" + #define QERR_KVM_MISSING_CAP \ "{ 'class': 'KVMMissingCap', 'data': { 'capability': %s, 'feature': %s } }" @@ -181,4 +184,10 @@ QError *qobject_to_qerror(const QObject *obj); #define QERR_FEATURE_DISABLED \ "{ 'class': 'FeatureDisabled', 'data': { 'name': %s } }" +#define QERR_QGA_LOGGING_FAILED \ + "{ 'class': 'QgaLoggingFailed', 'data': {} }" + +#define QERR_QGA_COMMAND_FAILED \ + "{ 'class': 'QgaCommandFailed', 'data': { 'message': %s } }" + #endif /* QERROR_H */ diff --git a/qga/guest-agent-command-state.c b/qga/guest-agent-command-state.c new file mode 100644 index 0000000000..bc6e0bd4a8 --- /dev/null +++ b/qga/guest-agent-command-state.c @@ -0,0 +1,73 @@ +/* + * QEMU Guest Agent command state interfaces + * + * Copyright IBM Corp. 2011 + * + * Authors: + * Michael Roth <mdroth@linux.vnet.ibm.com> + * + * 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 <glib.h> +#include "qga/guest-agent-core.h" + +struct GACommandState { + GSList *groups; +}; + +typedef struct GACommandGroup { + void (*init)(void); + void (*cleanup)(void); +} GACommandGroup; + +/* handle init/cleanup for stateful guest commands */ + +void ga_command_state_add(GACommandState *cs, + void (*init)(void), + void (*cleanup)(void)) +{ + GACommandGroup *cg = qemu_mallocz(sizeof(GACommandGroup)); + cg->init = init; + cg->cleanup = cleanup; + cs->groups = g_slist_append(cs->groups, cg); +} + +static void ga_command_group_init(gpointer opaque, gpointer unused) +{ + GACommandGroup *cg = opaque; + + g_assert(cg); + if (cg->init) { + cg->init(); + } +} + +void ga_command_state_init_all(GACommandState *cs) +{ + g_assert(cs); + g_slist_foreach(cs->groups, ga_command_group_init, NULL); +} + +static void ga_command_group_cleanup(gpointer opaque, gpointer unused) +{ + GACommandGroup *cg = opaque; + + g_assert(cg); + if (cg->cleanup) { + cg->cleanup(); + } +} + +void ga_command_state_cleanup_all(GACommandState *cs) +{ + g_assert(cs); + g_slist_foreach(cs->groups, ga_command_group_cleanup, NULL); +} + +GACommandState *ga_command_state_new(void) +{ + GACommandState *cs = qemu_mallocz(sizeof(GACommandState)); + cs->groups = NULL; + return cs; +} diff --git a/qga/guest-agent-commands.c b/qga/guest-agent-commands.c new file mode 100644 index 0000000000..8c0d67ee28 --- /dev/null +++ b/qga/guest-agent-commands.c @@ -0,0 +1,518 @@ +/* + * QEMU Guest Agent commands + * + * Copyright IBM Corp. 2011 + * + * Authors: + * Michael Roth <mdroth@linux.vnet.ibm.com> + * + * 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 <glib.h> +#include <mntent.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <linux/fs.h> +#include "qga/guest-agent-core.h" +#include "qga-qmp-commands.h" +#include "qerror.h" +#include "qemu-queue.h" + +static GAState *ga_state; + +static void disable_logging(void) +{ + ga_disable_logging(ga_state); +} + +static void enable_logging(void) +{ + ga_enable_logging(ga_state); +} + +/* Note: in some situations, like with the fsfreeze, logging may be + * temporarilly disabled. if it is necessary that a command be able + * to log for accounting purposes, check ga_logging_enabled() beforehand, + * and use the QERR_QGA_LOGGING_DISABLED to generate an error + */ +static void slog(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + g_logv("syslog", G_LOG_LEVEL_INFO, fmt, ap); + va_end(ap); +} + +int64_t qmp_guest_sync(int64_t id, Error **errp) +{ + return id; +} + +void qmp_guest_ping(Error **err) +{ + slog("guest-ping called"); +} + +struct GuestAgentInfo *qmp_guest_info(Error **err) +{ + GuestAgentInfo *info = qemu_mallocz(sizeof(GuestAgentInfo)); + + info->version = g_strdup(QGA_VERSION); + + return info; +} + +void qmp_guest_shutdown(bool has_mode, const char *mode, Error **err) +{ + int ret; + const char *shutdown_flag; + + slog("guest-shutdown called, mode: %s", mode); + if (!has_mode || strcmp(mode, "powerdown") == 0) { + shutdown_flag = "-P"; + } else if (strcmp(mode, "halt") == 0) { + shutdown_flag = "-H"; + } else if (strcmp(mode, "reboot") == 0) { + shutdown_flag = "-r"; + } else { + error_set(err, QERR_INVALID_PARAMETER_VALUE, "mode", + "halt|powerdown|reboot"); + return; + } + + ret = fork(); + if (ret == 0) { + /* child, start the shutdown */ + setsid(); + fclose(stdin); + fclose(stdout); + fclose(stderr); + + ret = execl("/sbin/shutdown", "shutdown", shutdown_flag, "+0", + "hypervisor initiated shutdown", (char*)NULL); + if (ret) { + slog("guest-shutdown failed: %s", strerror(errno)); + } + exit(!!ret); + } else if (ret < 0) { + error_set(err, QERR_UNDEFINED_ERROR); + } +} + +typedef struct GuestFileHandle { + uint64_t id; + FILE *fh; + QTAILQ_ENTRY(GuestFileHandle) next; +} GuestFileHandle; + +static struct { + QTAILQ_HEAD(, GuestFileHandle) filehandles; +} guest_file_state; + +static void guest_file_handle_add(FILE *fh) +{ + GuestFileHandle *gfh; + + gfh = qemu_mallocz(sizeof(GuestFileHandle)); + gfh->id = fileno(fh); + gfh->fh = fh; + QTAILQ_INSERT_TAIL(&guest_file_state.filehandles, gfh, next); +} + +static GuestFileHandle *guest_file_handle_find(int64_t id) +{ + GuestFileHandle *gfh; + + QTAILQ_FOREACH(gfh, &guest_file_state.filehandles, next) + { + if (gfh->id == id) { + return gfh; + } + } + + return NULL; +} + +int64_t qmp_guest_file_open(const char *path, bool has_mode, const char *mode, Error **err) +{ + FILE *fh; + int fd; + int64_t ret = -1; + + if (!has_mode) { + mode = "r"; + } + slog("guest-file-open called, filepath: %s, mode: %s", path, mode); + fh = fopen(path, mode); + if (!fh) { + error_set(err, QERR_OPEN_FILE_FAILED, path); + return -1; + } + + /* set fd non-blocking to avoid common use cases (like reading from a + * named pipe) from hanging the agent + */ + fd = fileno(fh); + ret = fcntl(fd, F_GETFL); + ret = fcntl(fd, F_SETFL, ret | O_NONBLOCK); + if (ret == -1) { + error_set(err, QERR_QGA_COMMAND_FAILED, "fcntl() failed"); + fclose(fh); + return -1; + } + + guest_file_handle_add(fh); + slog("guest-file-open, handle: %d", fd); + return fd; +} + +void qmp_guest_file_close(int64_t handle, Error **err) +{ + GuestFileHandle *gfh = guest_file_handle_find(handle); + int ret; + + slog("guest-file-close called, handle: %ld", handle); + if (!gfh) { + error_set(err, QERR_FD_NOT_FOUND, "handle"); + return; + } + + ret = fclose(gfh->fh); + if (ret == -1) { + error_set(err, QERR_QGA_COMMAND_FAILED, "fclose() failed"); + return; + } + + QTAILQ_REMOVE(&guest_file_state.filehandles, gfh, next); + qemu_free(gfh); +} + +struct GuestFileRead *qmp_guest_file_read(int64_t handle, bool has_count, + int64_t count, Error **err) +{ + GuestFileHandle *gfh = guest_file_handle_find(handle); + GuestFileRead *read_data = NULL; + guchar *buf; + FILE *fh; + size_t read_count; + + if (!gfh) { + error_set(err, QERR_FD_NOT_FOUND, "handle"); + return NULL; + } + + if (!has_count) { + count = QGA_READ_COUNT_DEFAULT; + } else if (count < 0) { + error_set(err, QERR_INVALID_PARAMETER, "count"); + return NULL; + } + + fh = gfh->fh; + buf = qemu_mallocz(count+1); + read_count = fread(buf, 1, count, fh); + if (ferror(fh)) { + slog("guest-file-read failed, handle: %ld", handle); + error_set(err, QERR_QGA_COMMAND_FAILED, "fread() failed"); + } else { + buf[read_count] = 0; + read_data = qemu_mallocz(sizeof(GuestFileRead)); + read_data->count = read_count; + read_data->eof = feof(fh); + if (read_count) { + read_data->buf_b64 = g_base64_encode(buf, read_count); + } + } + qemu_free(buf); + clearerr(fh); + + return read_data; +} + +GuestFileWrite *qmp_guest_file_write(int64_t handle, const char *buf_b64, + bool has_count, int64_t count, Error **err) +{ + GuestFileWrite *write_data = NULL; + guchar *buf; + gsize buf_len; + int write_count; + GuestFileHandle *gfh = guest_file_handle_find(handle); + FILE *fh; + + if (!gfh) { + error_set(err, QERR_FD_NOT_FOUND, "handle"); + return NULL; + } + + fh = gfh->fh; + buf = g_base64_decode(buf_b64, &buf_len); + + if (!has_count) { + count = buf_len; + } else if (count < 0 || count > buf_len) { + qemu_free(buf); + error_set(err, QERR_INVALID_PARAMETER, "count"); + return NULL; + } + + write_count = fwrite(buf, 1, count, fh); + if (ferror(fh)) { + slog("guest-file-write failed, handle: %ld", handle); + error_set(err, QERR_QGA_COMMAND_FAILED, "fwrite() error"); + } else { + write_data = qemu_mallocz(sizeof(GuestFileWrite)); + write_data->count = write_count; + write_data->eof = feof(fh); + } + qemu_free(buf); + clearerr(fh); + + return write_data; +} + +struct GuestFileSeek *qmp_guest_file_seek(int64_t handle, int64_t offset, + int64_t whence, Error **err) +{ + GuestFileHandle *gfh = guest_file_handle_find(handle); + GuestFileSeek *seek_data = NULL; + FILE *fh; + int ret; + + if (!gfh) { + error_set(err, QERR_FD_NOT_FOUND, "handle"); + return NULL; + } + + fh = gfh->fh; + ret = fseek(fh, offset, whence); + if (ret == -1) { + error_set(err, QERR_QGA_COMMAND_FAILED, strerror(errno)); + } else { + seek_data = qemu_mallocz(sizeof(GuestFileRead)); + seek_data->position = ftell(fh); + seek_data->eof = feof(fh); + } + clearerr(fh); + + return seek_data; +} + +void qmp_guest_file_flush(int64_t handle, Error **err) +{ + GuestFileHandle *gfh = guest_file_handle_find(handle); + FILE *fh; + int ret; + + if (!gfh) { + error_set(err, QERR_FD_NOT_FOUND, "handle"); + return; + } + + fh = gfh->fh; + ret = fflush(fh); + if (ret == EOF) { + error_set(err, QERR_QGA_COMMAND_FAILED, strerror(errno)); + } +} + +static void guest_file_init(void) +{ + QTAILQ_INIT(&guest_file_state.filehandles); +} + +typedef struct GuestFsfreezeMount { + char *dirname; + char *devtype; + QTAILQ_ENTRY(GuestFsfreezeMount) next; +} GuestFsfreezeMount; + +struct { + GuestFsfreezeStatus status; + QTAILQ_HEAD(, GuestFsfreezeMount) mount_list; +} guest_fsfreeze_state; + +/* + * Walk the mount table and build a list of local file systems + */ +static int guest_fsfreeze_build_mount_list(void) +{ + struct mntent *ment; + GuestFsfreezeMount *mount, *temp; + char const *mtab = MOUNTED; + FILE *fp; + + QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) { + QTAILQ_REMOVE(&guest_fsfreeze_state.mount_list, mount, next); + qemu_free(mount->dirname); + qemu_free(mount->devtype); + qemu_free(mount); + } + + fp = setmntent(mtab, "r"); + if (!fp) { + g_warning("fsfreeze: unable to read mtab"); + return -1; + } + + while ((ment = getmntent(fp))) { + /* + * An entry which device name doesn't start with a '/' is + * either a dummy file system or a network file system. + * Add special handling for smbfs and cifs as is done by + * coreutils as well. + */ + if ((ment->mnt_fsname[0] != '/') || + (strcmp(ment->mnt_type, "smbfs") == 0) || + (strcmp(ment->mnt_type, "cifs") == 0)) { + continue; + } + + mount = qemu_mallocz(sizeof(GuestFsfreezeMount)); + mount->dirname = qemu_strdup(ment->mnt_dir); + mount->devtype = qemu_strdup(ment->mnt_type); + + QTAILQ_INSERT_TAIL(&guest_fsfreeze_state.mount_list, mount, next); + } + + endmntent(fp); + + return 0; +} + +/* + * Return status of freeze/thaw + */ +GuestFsfreezeStatus qmp_guest_fsfreeze_status(Error **err) +{ + return guest_fsfreeze_state.status; +} + +/* + * Walk list of mounted file systems in the guest, and freeze the ones which + * are real local file systems. + */ +int64_t qmp_guest_fsfreeze_freeze(Error **err) +{ + int ret = 0, i = 0; + struct GuestFsfreezeMount *mount, *temp; + int fd; + char err_msg[512]; + + slog("guest-fsfreeze called"); + + if (guest_fsfreeze_state.status == GUEST_FSFREEZE_STATUS_FROZEN) { + return 0; + } + + ret = guest_fsfreeze_build_mount_list(); + if (ret < 0) { + return ret; + } + + /* cannot risk guest agent blocking itself on a write in this state */ + disable_logging(); + + QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) { + fd = qemu_open(mount->dirname, O_RDONLY); + if (fd == -1) { + sprintf(err_msg, "failed to open %s, %s", mount->dirname, strerror(errno)); + error_set(err, QERR_QGA_COMMAND_FAILED, err_msg); + goto error; + } + + /* we try to cull filesytems we know won't work in advance, but other + * filesytems may not implement fsfreeze for less obvious reasons. + * these will report EOPNOTSUPP, so we simply ignore them. when + * thawing, these filesystems will return an EINVAL instead, due to + * not being in a frozen state. Other filesystem-specific + * errors may result in EINVAL, however, so the user should check the + * number * of filesystems returned here against those returned by the + * thaw operation to determine whether everything completed + * successfully + */ + ret = ioctl(fd, FIFREEZE); + if (ret < 0 && errno != EOPNOTSUPP) { + sprintf(err_msg, "failed to freeze %s, %s", mount->dirname, strerror(errno)); + error_set(err, QERR_QGA_COMMAND_FAILED, err_msg); + close(fd); + goto error; + } + close(fd); + + i++; + } + + guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_FROZEN; + return i; + +error: + if (i > 0) { + qmp_guest_fsfreeze_thaw(NULL); + } + return 0; +} + +/* + * Walk list of frozen file systems in the guest, and thaw them. + */ +int64_t qmp_guest_fsfreeze_thaw(Error **err) +{ + int ret; + GuestFsfreezeMount *mount, *temp; + int fd, i = 0; + bool has_error = false; + + QTAILQ_FOREACH_SAFE(mount, &guest_fsfreeze_state.mount_list, next, temp) { + fd = qemu_open(mount->dirname, O_RDONLY); + if (fd == -1) { + has_error = true; + continue; + } + ret = ioctl(fd, FITHAW); + if (ret < 0 && errno != EOPNOTSUPP && errno != EINVAL) { + has_error = true; + close(fd); + continue; + } + close(fd); + i++; + } + + if (has_error) { + guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_ERROR; + } else { + guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED; + } + enable_logging(); + return i; +} + +static void guest_fsfreeze_init(void) +{ + guest_fsfreeze_state.status = GUEST_FSFREEZE_STATUS_THAWED; + QTAILQ_INIT(&guest_fsfreeze_state.mount_list); +} + +static void guest_fsfreeze_cleanup(void) +{ + int64_t ret; + Error *err = NULL; + + if (guest_fsfreeze_state.status == GUEST_FSFREEZE_STATUS_FROZEN) { + ret = qmp_guest_fsfreeze_thaw(&err); + if (ret < 0 || err) { + slog("failed to clean up frozen filesystems"); + } + } +} + +/* register init/cleanup routines for stateful command groups */ +void ga_command_state_init(GAState *s, GACommandState *cs) +{ + ga_state = s; + ga_command_state_add(cs, guest_fsfreeze_init, guest_fsfreeze_cleanup); + ga_command_state_add(cs, guest_file_init, NULL); +} diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h new file mode 100644 index 0000000000..e42b91d364 --- /dev/null +++ b/qga/guest-agent-core.h @@ -0,0 +1,31 @@ +/* + * QEMU Guest Agent core declarations + * + * Copyright IBM Corp. 2011 + * + * Authors: + * Adam Litke <aglitke@linux.vnet.ibm.com> + * Michael Roth <mdroth@linux.vnet.ibm.com> + * + * 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/qmp-core.h" +#include "qemu-common.h" + +#define QGA_VERSION "1.0" +#define QGA_READ_COUNT_DEFAULT 4 << 10 + +typedef struct GAState GAState; +typedef struct GACommandState GACommandState; + +void ga_command_state_init(GAState *s, GACommandState *cs); +void ga_command_state_add(GACommandState *cs, + void (*init)(void), + void (*cleanup)(void)); +void ga_command_state_init_all(GACommandState *cs); +void ga_command_state_cleanup_all(GACommandState *cs); +GACommandState *ga_command_state_new(void); +bool ga_logging_enabled(GAState *s); +void ga_disable_logging(GAState *s); +void ga_enable_logging(GAState *s); @@ -16,6 +16,7 @@ #include "qobject.h" #include "qemu-queue.h" #include "qemu-common.h" +#include "qemu-queue.h" typedef struct QListEntry { QObject *value; @@ -50,4 +51,14 @@ QObject *qlist_peek(QList *qlist); int qlist_empty(const QList *qlist); QList *qobject_to_qlist(const QObject *obj); +static inline const QListEntry *qlist_first(const QList *qlist) +{ + return QTAILQ_FIRST(&qlist->head); +} + +static inline const QListEntry *qlist_next(const QListEntry *entry) +{ + return QTAILQ_NEXT(entry, next); +} + #endif /* QLIST_H */ diff --git a/qmp-commands.hx b/qmp-commands.hx index 92c5c3a318..5d44edf4e7 100644 --- a/qmp-commands.hx +++ b/qmp-commands.hx @@ -694,6 +694,40 @@ Example: EQMP { + .name = "blockdev-snapshot-sync", + .args_type = "device:B,snapshot-file:s?,format:s?", + .params = "device [new-image-file] [format]", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_snapshot_blkdev, + }, + +SQMP +blockdev-snapshot-sync +---------------------- + +Synchronous snapshot of a block device. snapshot-file specifies the +target of the new image. If the file exists, or if it is a device, the +snapshot will be created in the existing file/device. If does not +exist, a new file will be created. format specifies the format of the +snapshot image, default is qcow2. + +Arguments: + +- "device": device name to snapshot (json-string) +- "snapshot-file": name of new image file (json-string) +- "format": format of new image (json-string, optional) + +Example: + +-> { "execute": "blockdev-snapshot", "arguments": { "device": "ide-hd0", + "snapshot-file": + "/some/place/my-image", + "format": "qcow2" } } +<- { "return": {} } + +EQMP + + { .name = "balloon", .args_type = "value:M", .params = "target", diff --git a/scripts/ordereddict.py b/scripts/ordereddict.py new file mode 100644 index 0000000000..7242b5060d --- /dev/null +++ b/scripts/ordereddict.py @@ -0,0 +1,127 @@ +# Copyright (c) 2009 Raymond Hettinger +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +from UserDict import DictMixin + +class OrderedDict(dict, DictMixin): + + def __init__(self, *args, **kwds): + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % len(args)) + try: + self.__end + except AttributeError: + self.clear() + self.update(*args, **kwds) + + def clear(self): + self.__end = end = [] + end += [None, end, end] # sentinel node for doubly linked list + self.__map = {} # key --> [key, prev, next] + dict.clear(self) + + def __setitem__(self, key, value): + if key not in self: + end = self.__end + curr = end[1] + curr[2] = end[1] = self.__map[key] = [key, curr, end] + dict.__setitem__(self, key, value) + + def __delitem__(self, key): + dict.__delitem__(self, key) + key, prev, next = self.__map.pop(key) + prev[2] = next + next[1] = prev + + def __iter__(self): + end = self.__end + curr = end[2] + while curr is not end: + yield curr[0] + curr = curr[2] + + def __reversed__(self): + end = self.__end + curr = end[1] + while curr is not end: + yield curr[0] + curr = curr[1] + + def popitem(self, last=True): + if not self: + raise KeyError('dictionary is empty') + if last: + key = reversed(self).next() + else: + key = iter(self).next() + value = self.pop(key) + return key, value + + def __reduce__(self): + items = [[k, self[k]] for k in self] + tmp = self.__map, self.__end + del self.__map, self.__end + inst_dict = vars(self).copy() + self.__map, self.__end = tmp + if inst_dict: + return (self.__class__, (items,), inst_dict) + return self.__class__, (items,) + + def keys(self): + return list(self) + + setdefault = DictMixin.setdefault + update = DictMixin.update + pop = DictMixin.pop + values = DictMixin.values + items = DictMixin.items + iterkeys = DictMixin.iterkeys + itervalues = DictMixin.itervalues + iteritems = DictMixin.iteritems + + def __repr__(self): + if not self: + return '%s()' % (self.__class__.__name__,) + return '%s(%r)' % (self.__class__.__name__, self.items()) + + def copy(self): + return self.__class__(self) + + @classmethod + def fromkeys(cls, iterable, value=None): + d = cls() + for key in iterable: + d[key] = value + return d + + def __eq__(self, other): + if isinstance(other, OrderedDict): + if len(self) != len(other): + return False + for p, q in zip(self.items(), other.items()): + if p != q: + return False + return True + return dict.__eq__(self, other) + + def __ne__(self, other): + return not self == other diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py new file mode 100644 index 0000000000..9ad4c54991 --- /dev/null +++ b/scripts/qapi-commands.py @@ -0,0 +1,385 @@ +# +# QAPI command marshaller generator +# +# Copyright IBM, Corp. 2011 +# +# Authors: +# Anthony Liguori <aliguori@us.ibm.com> +# Michael Roth <mdroth@linux.vnet.ibm.com> +# +# This work is licensed under the terms of the GNU GPLv2. +# See the COPYING.LIB file in the top-level directory. + +from ordereddict import OrderedDict +from qapi import * +import sys +import os +import getopt +import errno + +def generate_decl_enum(name, members, genlist=True): + return mcgen(''' + +void visit_type_%(name)s(Visitor *m, %(name)s * obj, const char *name, Error **errp); +''', + name=name) + +def generate_command_decl(name, args, ret_type): + arglist="" + for argname, argtype, optional, structured in parse_args(args): + argtype = c_type(argtype) + if argtype == "char *": + argtype = "const char *" + if optional: + arglist += "bool has_%s, " % c_var(argname) + arglist += "%s %s, " % (argtype, c_var(argname)) + return mcgen(''' +%(ret_type)s qmp_%(name)s(%(args)sError **errp); +''', + ret_type=c_type(ret_type), name=c_var(name), args=arglist).strip() + +def gen_sync_call(name, args, ret_type, indent=0): + ret = "" + arglist="" + retval="" + if ret_type: + retval = "retval = " + for argname, argtype, optional, structured in parse_args(args): + if optional: + arglist += "has_%s, " % c_var(argname) + arglist += "%s, " % (c_var(argname)) + push_indent(indent) + ret = mcgen(''' +%(retval)sqmp_%(name)s(%(args)serrp); + +''', + name=c_var(name), args=arglist, retval=retval).rstrip() + if ret_type: + ret += "\n" + mcgen('''' +%(marshal_output_call)s +''', + marshal_output_call=gen_marshal_output_call(name, ret_type)).rstrip() + pop_indent(indent) + return ret.rstrip() + + +def gen_marshal_output_call(name, ret_type): + if not ret_type: + return "" + return "qmp_marshal_output_%s(retval, ret, errp);" % c_var(name) + +def gen_visitor_output_containers_decl(ret_type): + ret = "" + push_indent() + if ret_type: + ret += mcgen(''' +QmpOutputVisitor *mo; +QapiDeallocVisitor *md; +Visitor *v; +''') + pop_indent() + + return ret + +def gen_visitor_input_containers_decl(args): + ret = "" + + push_indent() + if len(args) > 0: + ret += mcgen(''' +QmpInputVisitor *mi; +QapiDeallocVisitor *md; +Visitor *v; +''') + pop_indent() + + return ret.rstrip() + +def gen_visitor_input_vars_decl(args): + ret = "" + push_indent() + for argname, argtype, optional, structured in parse_args(args): + if optional: + ret += mcgen(''' +bool has_%(argname)s = false; +''', + argname=c_var(argname)) + if c_type(argtype).endswith("*"): + ret += mcgen(''' +%(argtype)s %(argname)s = NULL; +''', + argname=c_var(argname), argtype=c_type(argtype)) + else: + ret += mcgen(''' +%(argtype)s %(argname)s; +''', + argname=c_var(argname), argtype=c_type(argtype)) + + pop_indent() + return ret.rstrip() + +def gen_visitor_input_block(args, obj, dealloc=False): + ret = "" + if len(args) == 0: + return ret + + push_indent() + + if dealloc: + ret += mcgen(''' +md = qapi_dealloc_visitor_new(); +v = qapi_dealloc_get_visitor(md); +''') + else: + ret += mcgen(''' +mi = qmp_input_visitor_new(%(obj)s); +v = qmp_input_get_visitor(mi); +''', + obj=obj) + + for argname, argtype, optional, structured in parse_args(args): + if optional: + ret += mcgen(''' +visit_start_optional(v, &has_%(c_name)s, "%(name)s", errp); +if (has_%(c_name)s) { +''', + c_name=c_var(argname), name=argname) + push_indent() + ret += mcgen(''' +visit_type_%(argtype)s(v, &%(c_name)s, "%(name)s", errp); +''', + c_name=c_var(argname), name=argname, argtype=argtype) + if optional: + pop_indent() + ret += mcgen(''' +} +visit_end_optional(v, errp); +''') + + if dealloc: + ret += mcgen(''' +qapi_dealloc_visitor_cleanup(md); +''') + else: + ret += mcgen(''' +qmp_input_visitor_cleanup(mi); +''') + pop_indent() + return ret.rstrip() + +def gen_marshal_output(name, args, ret_type): + if not ret_type: + return "" + ret = mcgen(''' +static void qmp_marshal_output_%(c_name)s(%(c_ret_type)s ret_in, QObject **ret_out, Error **errp) +{ + QapiDeallocVisitor *md = qapi_dealloc_visitor_new(); + QmpOutputVisitor *mo = qmp_output_visitor_new(); + Visitor *v; + + v = qmp_output_get_visitor(mo); + visit_type_%(ret_type)s(v, &ret_in, "unused", errp); + if (!error_is_set(errp)) { + *ret_out = qmp_output_get_qobject(mo); + } + qmp_output_visitor_cleanup(mo); + v = qapi_dealloc_get_visitor(md); + visit_type_%(ret_type)s(v, &ret_in, "unused", errp); + qapi_dealloc_visitor_cleanup(md); +} +''', + c_ret_type=c_type(ret_type), c_name=c_var(name), ret_type=ret_type) + + return ret + +def gen_marshal_input(name, args, ret_type): + ret = mcgen(''' +static void qmp_marshal_input_%(c_name)s(QDict *args, QObject **ret, Error **errp) +{ +''', + c_name=c_var(name)) + + if ret_type: + if c_type(ret_type).endswith("*"): + retval = " %s retval = NULL;" % c_type(ret_type) + else: + retval = " %s retval;" % c_type(ret_type) + ret += mcgen(''' +%(retval)s +''', + retval=retval) + + if len(args) > 0: + ret += mcgen(''' +%(visitor_input_containers_decl)s +%(visitor_input_vars_decl)s + +%(visitor_input_block)s + +''', + visitor_input_containers_decl=gen_visitor_input_containers_decl(args), + visitor_input_vars_decl=gen_visitor_input_vars_decl(args), + visitor_input_block=gen_visitor_input_block(args, "QOBJECT(args)")) + + ret += mcgen(''' + if (error_is_set(errp)) { + goto out; + } +%(sync_call)s +''', + sync_call=gen_sync_call(name, args, ret_type, indent=4)) + ret += mcgen(''' + +out: +''') + ret += mcgen(''' +%(visitor_input_block_cleanup)s + return; +} +''', + visitor_input_block_cleanup=gen_visitor_input_block(args, None, dealloc=True)) + return ret + +def gen_registry(commands): + registry="" + push_indent() + for cmd in commands: + registry += mcgen(''' +qmp_register_command("%(name)s", qmp_marshal_input_%(c_name)s); +''', + name=cmd['command'], c_name=c_var(cmd['command'])) + pop_indent() + ret = mcgen(''' +static void qmp_init_marshal(void) +{ +%(registry)s +} + +qapi_init(qmp_init_marshal); +''', + registry=registry.rstrip()) + return ret + +def gen_command_decl_prologue(header, guard, prefix=""): + ret = mcgen(''' +/* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT MODIFY */ + +/* + * schema-defined QAPI function prototypes + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#ifndef %(guard)s +#define %(guard)s + +#include "%(prefix)sqapi-types.h" +#include "error.h" + +''', + header=basename(h_file), guard=guardname(h_file), prefix=prefix) + return ret + +def gen_command_def_prologue(prefix="", proxy=False): + ret = mcgen(''' +/* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT MODIFY */ + +/* + * schema-defined QMP->QAPI command dispatch + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "qemu-objects.h" +#include "qapi/qmp-core.h" +#include "qapi/qapi-visit-core.h" +#include "qapi/qmp-output-visitor.h" +#include "qapi/qmp-input-visitor.h" +#include "qapi/qapi-dealloc-visitor.h" +#include "%(prefix)sqapi-types.h" +#include "%(prefix)sqapi-visit.h" + +''', + prefix=prefix) + if not proxy: + ret += '#include "%sqmp-commands.h"' % prefix + return ret + "\n" + + +try: + opts, args = getopt.gnu_getopt(sys.argv[1:], "p:o:", ["prefix=", "output-dir=", "type="]) +except getopt.GetoptError, err: + print str(err) + sys.exit(1) + +output_dir = "" +prefix = "" +dispatch_type = "sync" +c_file = 'qmp-marshal.c' +h_file = 'qmp-commands.h' + +for o, a in opts: + if o in ("-p", "--prefix"): + prefix = a + elif o in ("-o", "--output-dir"): + output_dir = a + "/" + elif o in ("-t", "--type"): + dispatch_type = a + +c_file = output_dir + prefix + c_file +h_file = output_dir + prefix + h_file + +try: + os.makedirs(output_dir) +except os.error, e: + if e.errno != errno.EEXIST: + raise + +exprs = parse_schema(sys.stdin) +commands = filter(lambda expr: expr.has_key('command'), exprs) + +if dispatch_type == "sync": + fdecl = open(h_file, 'w') + fdef = open(c_file, 'w') + ret = gen_command_decl_prologue(header=basename(h_file), guard=guardname(h_file), prefix=prefix) + fdecl.write(ret) + ret = gen_command_def_prologue(prefix=prefix) + fdef.write(ret) + + for cmd in commands: + arglist = [] + ret_type = None + if cmd.has_key('data'): + arglist = cmd['data'] + if cmd.has_key('returns'): + ret_type = cmd['returns'] + ret = generate_command_decl(cmd['command'], arglist, ret_type) + "\n" + fdecl.write(ret) + if ret_type: + ret = gen_marshal_output(cmd['command'], arglist, ret_type) + "\n" + fdef.write(ret) + ret = gen_marshal_input(cmd['command'], arglist, ret_type) + "\n" + fdef.write(ret) + + fdecl.write("\n#endif"); + ret = gen_registry(commands) + fdef.write(ret) + + fdef.flush() + fdef.close() + fdecl.flush() + fdecl.close() diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py new file mode 100644 index 0000000000..cece32546a --- /dev/null +++ b/scripts/qapi-types.py @@ -0,0 +1,270 @@ +# +# QAPI types generator +# +# Copyright IBM, Corp. 2011 +# +# Authors: +# Anthony Liguori <aliguori@us.ibm.com> +# +# This work is licensed under the terms of the GNU GPLv2. +# See the COPYING.LIB file in the top-level directory. + +from ordereddict import OrderedDict +from qapi import * +import sys +import os +import getopt +import errno + +def generate_fwd_struct(name, members): + return mcgen(''' +typedef struct %(name)s %(name)s; + +typedef struct %(name)sList +{ + %(name)s *value; + struct %(name)sList *next; +} %(name)sList; +''', + name=name) + +def generate_struct(structname, fieldname, members): + ret = mcgen(''' +struct %(name)s +{ +''', + name=structname) + + for argname, argentry, optional, structured in parse_args(members): + if optional: + ret += mcgen(''' + bool has_%(c_name)s; +''', + c_name=c_var(argname)) + if structured: + push_indent() + ret += generate_struct("", argname, argentry) + pop_indent() + else: + ret += mcgen(''' + %(c_type)s %(c_name)s; +''', + c_type=c_type(argentry), c_name=c_var(argname)) + + if len(fieldname): + fieldname = " " + fieldname + ret += mcgen(''' +}%(field)s; +''', + field=fieldname) + + return ret + +def generate_enum_lookup(name, values): + ret = mcgen(''' +const char *%(name)s_lookup[] = { +''', + name=name) + i = 0 + for value in values: + ret += mcgen(''' + "%(value)s", +''', + value=c_var(value).lower()) + + ret += mcgen(''' + NULL, +}; + +''') + return ret + +def generate_enum(name, values): + lookup_decl = mcgen(''' +extern const char *%(name)s_lookup[]; +''', + name=name) + + enum_decl = mcgen(''' +typedef enum %(name)s +{ +''', + name=name) + + i = 0 + for value in values: + enum_decl += mcgen(''' + %(abbrev)s_%(value)s = %(i)d, +''', + abbrev=de_camel_case(name).upper(), + value=c_var(value).upper(), + i=i) + i += 1 + + enum_decl += mcgen(''' +} %(name)s; +''', + name=name) + + return lookup_decl + enum_decl + +def generate_union(name, typeinfo): + ret = mcgen(''' +struct %(name)s +{ + %(name)sKind kind; + union { +''', + name=name) + + for key in typeinfo: + ret += mcgen(''' + %(c_type)s %(c_name)s; +''', + c_type=c_type(typeinfo[key]), + c_name=c_var(key)) + + ret += mcgen(''' + }; +}; +''') + + return ret + +def generate_type_cleanup_decl(name): + ret = mcgen(''' +void qapi_free_%(type)s(%(c_type)s obj); +''', + c_type=c_type(name),type=name) + return ret + +def generate_type_cleanup(name): + ret = mcgen(''' +void qapi_free_%(type)s(%(c_type)s obj) +{ + QapiDeallocVisitor *md; + Visitor *v; + + if (!obj) { + return; + } + + md = qapi_dealloc_visitor_new(); + v = qapi_dealloc_get_visitor(md); + visit_type_%(type)s(v, &obj, NULL, NULL); + qapi_dealloc_visitor_cleanup(md); +} +''', + c_type=c_type(name),type=name) + return ret + + +try: + opts, args = getopt.gnu_getopt(sys.argv[1:], "p:o:", ["prefix=", "output-dir="]) +except getopt.GetoptError, err: + print str(err) + sys.exit(1) + +output_dir = "" +prefix = "" +c_file = 'qapi-types.c' +h_file = 'qapi-types.h' + +for o, a in opts: + if o in ("-p", "--prefix"): + prefix = a + elif o in ("-o", "--output-dir"): + output_dir = a + "/" + +c_file = output_dir + prefix + c_file +h_file = output_dir + prefix + h_file + +try: + os.makedirs(output_dir) +except os.error, e: + if e.errno != errno.EEXIST: + raise + +fdef = open(c_file, 'w') +fdecl = open(h_file, 'w') + +fdef.write(mcgen(''' +/* AUTOMATICALLY GENERATED, DO NOT MODIFY */ + +/* + * deallocation functions for schema-defined QAPI types + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * Michael Roth <mdroth@linux.vnet.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "qapi/qapi-dealloc-visitor.h" +#include "%(prefix)sqapi-types.h" +#include "%(prefix)sqapi-visit.h" + +''', prefix=prefix)) + +fdecl.write(mcgen(''' +/* AUTOMATICALLY GENERATED, DO NOT MODIFY */ + +/* + * schema-defined QAPI types + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#ifndef %(guard)s +#define %(guard)s + +#include "qapi/qapi-types-core.h" +''', + guard=guardname(h_file))) + +exprs = parse_schema(sys.stdin) + +for expr in exprs: + ret = "\n" + if expr.has_key('type'): + ret += generate_fwd_struct(expr['type'], expr['data']) + elif expr.has_key('enum'): + ret += generate_enum(expr['enum'], expr['data']) + fdef.write(generate_enum_lookup(expr['enum'], expr['data'])) + elif expr.has_key('union'): + ret += generate_fwd_struct(expr['union'], expr['data']) + "\n" + ret += generate_enum('%sKind' % expr['union'], expr['data'].keys()) + else: + continue + fdecl.write(ret) + +for expr in exprs: + ret = "\n" + if expr.has_key('type'): + ret += generate_struct(expr['type'], "", expr['data']) + "\n" + ret += generate_type_cleanup_decl(expr['type']) + fdef.write(generate_type_cleanup(expr['type']) + "\n") + elif expr.has_key('union'): + ret += generate_union(expr['union'], expr['data']) + else: + continue + fdecl.write(ret) + +fdecl.write(''' +#endif +''') + +fdecl.flush() +fdecl.close() diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py new file mode 100644 index 0000000000..252230ef25 --- /dev/null +++ b/scripts/qapi-visit.py @@ -0,0 +1,246 @@ +# +# QAPI visitor generator +# +# Copyright IBM, Corp. 2011 +# +# Authors: +# Anthony Liguori <aliguori@us.ibm.com> +# Michael Roth <mdroth@linux.vnet.ibm.com> +# +# This work is licensed under the terms of the GNU GPLv2. +# See the COPYING.LIB file in the top-level directory. + +from ordereddict import OrderedDict +from qapi import * +import sys +import os +import getopt +import errno + +def generate_visit_struct_body(field_prefix, members): + ret = "" + if len(field_prefix): + field_prefix = field_prefix + "." + for argname, argentry, optional, structured in parse_args(members): + if optional: + ret += mcgen(''' +visit_start_optional(m, (obj && *obj) ? &(*obj)->%(c_prefix)shas_%(c_name)s : NULL, "%(name)s", errp); +if ((*obj)->%(prefix)shas_%(c_name)s) { +''', + c_prefix=c_var(field_prefix), prefix=field_prefix, + c_name=c_var(argname), name=argname) + push_indent() + + if structured: + ret += mcgen(''' +visit_start_struct(m, NULL, "", "%(name)s", 0, errp); +''', + name=argname) + ret += generate_visit_struct_body(field_prefix + argname, argentry) + ret += mcgen(''' +visit_end_struct(m, errp); +''') + else: + ret += mcgen(''' +visit_type_%(type)s(m, (obj && *obj) ? &(*obj)->%(c_prefix)s%(c_name)s : NULL, "%(name)s", errp); +''', + c_prefix=c_var(field_prefix), prefix=field_prefix, + type=type_name(argentry), c_name=c_var(argname), + name=argname) + + if optional: + pop_indent() + ret += mcgen(''' +} +visit_end_optional(m, errp); +''') + return ret + +def generate_visit_struct(name, members): + ret = mcgen(''' + +void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **errp) +{ + visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), errp); +''', + name=name) + push_indent() + ret += generate_visit_struct_body("", members) + pop_indent() + + ret += mcgen(''' + visit_end_struct(m, errp); +} +''') + return ret + +def generate_visit_list(name, members): + return mcgen(''' + +void visit_type_%(name)sList(Visitor *m, %(name)sList ** obj, const char *name, Error **errp) +{ + GenericList *i; + + visit_start_list(m, name, errp); + + for (i = visit_next_list(m, (GenericList **)obj, errp); i; i = visit_next_list(m, &i, errp)) { + %(name)sList *native_i = (%(name)sList *)i; + visit_type_%(name)s(m, &native_i->value, NULL, errp); + } + + visit_end_list(m, errp); +} +''', + name=name) + +def generate_visit_enum(name, members): + return mcgen(''' + +void visit_type_%(name)s(Visitor *m, %(name)s * obj, const char *name, Error **errp) +{ + visit_type_enum(m, (int *)obj, %(name)s_lookup, "%(name)s", name, errp); +} +''', + name=name) + +def generate_visit_union(name, members): + ret = generate_visit_enum('%sKind' % name, members.keys()) + + ret += mcgen(''' + +void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **errp) +{ +} +''', + name=name) + + return ret + +def generate_declaration(name, members, genlist=True): + ret = mcgen(''' + +void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **errp); +''', + name=name) + + if genlist: + ret += mcgen(''' +void visit_type_%(name)sList(Visitor *m, %(name)sList ** obj, const char *name, Error **errp); +''', + name=name) + + return ret + +def generate_decl_enum(name, members, genlist=True): + return mcgen(''' + +void visit_type_%(name)s(Visitor *m, %(name)s * obj, const char *name, Error **errp); +''', + name=name) + +try: + opts, args = getopt.gnu_getopt(sys.argv[1:], "p:o:", ["prefix=", "output-dir="]) +except getopt.GetoptError, err: + print str(err) + sys.exit(1) + +output_dir = "" +prefix = "" +c_file = 'qapi-visit.c' +h_file = 'qapi-visit.h' + +for o, a in opts: + if o in ("-p", "--prefix"): + prefix = a + elif o in ("-o", "--output-dir"): + output_dir = a + "/" + +c_file = output_dir + prefix + c_file +h_file = output_dir + prefix + h_file + +try: + os.makedirs(output_dir) +except os.error, e: + if e.errno != errno.EEXIST: + raise + +fdef = open(c_file, 'w') +fdecl = open(h_file, 'w') + +fdef.write(mcgen(''' +/* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT MODIFY */ + +/* + * schema-defined QAPI visitor functions + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "%(header)s" +''', + header=basename(h_file))) + +fdecl.write(mcgen(''' +/* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT MODIFY */ + +/* + * schema-defined QAPI visitor function + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#ifndef %(guard)s +#define %(guard)s + +#include "qapi/qapi-visit-core.h" +#include "%(prefix)sqapi-types.h" +''', + prefix=prefix, guard=guardname(h_file))) + +exprs = parse_schema(sys.stdin) + +for expr in exprs: + if expr.has_key('type'): + ret = generate_visit_struct(expr['type'], expr['data']) + ret += generate_visit_list(expr['type'], expr['data']) + fdef.write(ret) + + ret = generate_declaration(expr['type'], expr['data']) + fdecl.write(ret) + elif expr.has_key('union'): + ret = generate_visit_union(expr['union'], expr['data']) + fdef.write(ret) + + ret = generate_decl_enum('%sKind' % expr['union'], expr['data'].keys()) + ret += generate_declaration(expr['union'], expr['data']) + fdecl.write(ret) + elif expr.has_key('enum'): + ret = generate_visit_enum(expr['enum'], expr['data']) + fdef.write(ret) + + ret = generate_decl_enum(expr['enum'], expr['data']) + fdecl.write(ret) + +fdecl.write(''' +#endif +''') + +fdecl.flush() +fdecl.close() + +fdef.flush() +fdef.close() diff --git a/scripts/qapi.py b/scripts/qapi.py new file mode 100644 index 0000000000..56af2329bb --- /dev/null +++ b/scripts/qapi.py @@ -0,0 +1,203 @@ +# +# QAPI helper library +# +# Copyright IBM, Corp. 2011 +# +# Authors: +# Anthony Liguori <aliguori@us.ibm.com> +# +# This work is licensed under the terms of the GNU GPLv2. +# See the COPYING.LIB file in the top-level directory. + +from ordereddict import OrderedDict + +def tokenize(data): + while len(data): + if data[0] in ['{', '}', ':', ',', '[', ']']: + yield data[0] + data = data[1:] + elif data[0] in ' \n': + data = data[1:] + elif data[0] == "'": + data = data[1:] + string = '' + while data[0] != "'": + string += data[0] + data = data[1:] + data = data[1:] + yield string + +def parse(tokens): + if tokens[0] == '{': + ret = OrderedDict() + tokens = tokens[1:] + while tokens[0] != '}': + key = tokens[0] + tokens = tokens[1:] + + tokens = tokens[1:] # : + + value, tokens = parse(tokens) + + if tokens[0] == ',': + tokens = tokens[1:] + + ret[key] = value + tokens = tokens[1:] + return ret, tokens + elif tokens[0] == '[': + ret = [] + tokens = tokens[1:] + while tokens[0] != ']': + value, tokens = parse(tokens) + if tokens[0] == ',': + tokens = tokens[1:] + ret.append(value) + tokens = tokens[1:] + return ret, tokens + else: + return tokens[0], tokens[1:] + +def evaluate(string): + return parse(map(lambda x: x, tokenize(string)))[0] + +def parse_schema(fp): + exprs = [] + expr = '' + expr_eval = None + + for line in fp: + if line.startswith('#') or line == '\n': + continue + + if line.startswith(' '): + expr += line + elif expr: + expr_eval = evaluate(expr) + if expr_eval.has_key('enum'): + add_enum(expr_eval['enum']) + elif expr_eval.has_key('union'): + add_enum('%sKind' % expr_eval['union']) + exprs.append(expr_eval) + expr = line + else: + expr += line + + if expr: + expr_eval = evaluate(expr) + if expr_eval.has_key('enum'): + add_enum(expr_eval['enum']) + elif expr_eval.has_key('union'): + add_enum('%sKind' % expr_eval['union']) + exprs.append(expr_eval) + + return exprs + +def parse_args(typeinfo): + for member in typeinfo: + argname = member + argentry = typeinfo[member] + optional = False + structured = False + if member.startswith('*'): + argname = member[1:] + optional = True + if isinstance(argentry, OrderedDict): + structured = True + yield (argname, argentry, optional, structured) + +def de_camel_case(name): + new_name = '' + for ch in name: + if ch.isupper() and new_name: + new_name += '_' + if ch == '-': + new_name += '_' + else: + new_name += ch.lower() + return new_name + +def camel_case(name): + new_name = '' + first = True + for ch in name: + if ch in ['_', '-']: + first = True + elif first: + new_name += ch.upper() + first = False + else: + new_name += ch.lower() + return new_name + +def c_var(name): + return '_'.join(name.split('-')).lstrip("*") + +def c_list_type(name): + return '%sList' % name + +def type_name(name): + if type(name) == list: + return c_list_type(name[0]) + return name + +enum_types = [] + +def add_enum(name): + global enum_types + enum_types.append(name) + +def is_enum(name): + global enum_types + return (name in enum_types) + +def c_type(name): + if name == 'str': + return 'char *' + elif name == 'int': + return 'int64_t' + elif name == 'bool': + return 'bool' + elif name == 'number': + return 'double' + elif type(name) == list: + return '%s *' % c_list_type(name[0]) + elif is_enum(name): + return name + elif name == None or len(name) == 0: + return 'void' + elif name == name.upper(): + return '%sEvent *' % camel_case(name) + else: + return '%s *' % name + +def genindent(count): + ret = "" + for i in range(count): + ret += " " + return ret + +indent_level = 0 + +def push_indent(indent_amount=4): + global indent_level + indent_level += indent_amount + +def pop_indent(indent_amount=4): + global indent_level + indent_level -= indent_amount + +def cgen(code, **kwds): + indent = genindent(indent_level) + lines = code.split('\n') + lines = map(lambda x: indent + x, lines) + return '\n'.join(lines) % kwds + '\n' + +def mcgen(code, **kwds): + return cgen('\n'.join(code.split('\n')[1:-1]), **kwds) + +def basename(filename): + return filename.split("/")[-1] + +def guardname(filename): + return filename.replace("/", "_").replace("-", "_").split(".")[0].upper() diff --git a/test-qmp-commands.c b/test-qmp-commands.c new file mode 100644 index 0000000000..775290439a --- /dev/null +++ b/test-qmp-commands.c @@ -0,0 +1,113 @@ +#include <glib.h> +#include "qemu-objects.h" +#include "test-qmp-commands.h" +#include "qapi/qmp-core.h" +#include "module.h" + +void qmp_user_def_cmd(Error **errp) +{ +} + +void qmp_user_def_cmd1(UserDefOne * ud1, Error **errp) +{ +} + +UserDefTwo * qmp_user_def_cmd2(UserDefOne * ud1a, UserDefOne * ud1b, Error **errp) +{ + UserDefTwo *ret; + UserDefOne *ud1c = qemu_mallocz(sizeof(UserDefOne)); + UserDefOne *ud1d = qemu_mallocz(sizeof(UserDefOne)); + + ud1c->string = strdup(ud1a->string); + ud1c->integer = ud1a->integer; + ud1d->string = strdup(ud1b->string); + ud1d->integer = ud1b->integer; + + ret = qemu_mallocz(sizeof(UserDefTwo)); + ret->string = strdup("blah1"); + ret->dict.string = strdup("blah2"); + ret->dict.dict.userdef = ud1c; + ret->dict.dict.string = strdup("blah3"); + ret->dict.has_dict2 = true; + ret->dict.dict2.userdef = ud1d; + ret->dict.dict2.string = strdup("blah4"); + + return ret; +} + +/* test commands with no input and no return value */ +static void test_dispatch_cmd(void) +{ + QDict *req = qdict_new(); + QObject *resp; + + qdict_put_obj(req, "execute", QOBJECT(qstring_from_str("user_def_cmd"))); + + resp = qmp_dispatch(QOBJECT(req)); + assert(resp != NULL); + assert(!qdict_haskey(qobject_to_qdict(resp), "error")); + g_print("\nresp: %s\n", qstring_get_str(qobject_to_json(resp))); + + qobject_decref(resp); + QDECREF(req); +} + +/* test commands that return an error due to invalid parameters */ +static void test_dispatch_cmd_error(void) +{ + QDict *req = qdict_new(); + QObject *resp; + + qdict_put_obj(req, "execute", QOBJECT(qstring_from_str("user_def_cmd2"))); + + resp = qmp_dispatch(QOBJECT(req)); + assert(resp != NULL); + assert(qdict_haskey(qobject_to_qdict(resp), "error")); + g_print("\nresp: %s\n", qstring_get_str(qobject_to_json_pretty(resp))); + + qobject_decref(resp); + QDECREF(req); +} + +/* test commands that involve both input parameters and return values */ +static void test_dispatch_cmd_io(void) +{ + QDict *req = qdict_new(); + QDict *args = qdict_new(); + QDict *ud1a = qdict_new(); + QDict *ud1b = qdict_new(); + QObject *resp; + + qdict_put_obj(ud1a, "integer", QOBJECT(qint_from_int(42))); + qdict_put_obj(ud1a, "string", QOBJECT(qstring_from_str("hello"))); + qdict_put_obj(ud1b, "integer", QOBJECT(qint_from_int(422))); + qdict_put_obj(ud1b, "string", QOBJECT(qstring_from_str("hello2"))); + qdict_put_obj(args, "ud1a", QOBJECT(ud1a)); + qdict_put_obj(args, "ud1b", QOBJECT(ud1b)); + qdict_put_obj(req, "arguments", QOBJECT(args)); + + qdict_put_obj(req, "execute", QOBJECT(qstring_from_str("user_def_cmd2"))); + + /* TODO: put in full payload and check for errors */ + resp = qmp_dispatch(QOBJECT(req)); + assert(resp != NULL); + assert(!qdict_haskey(qobject_to_qdict(resp), "error")); + g_print("\nresp: %s\n", qstring_get_str(qobject_to_json_pretty(resp))); + + qobject_decref(resp); + QDECREF(req); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + g_test_add_func("/0.15/dispatch_cmd", test_dispatch_cmd); + g_test_add_func("/0.15/dispatch_cmd_error", test_dispatch_cmd_error); + g_test_add_func("/0.15/dispatch_cmd_io", test_dispatch_cmd_io); + + module_call_init(MODULE_INIT_QAPI); + g_test_run(); + + return 0; +} diff --git a/test-visitor.c b/test-visitor.c new file mode 100644 index 0000000000..5133ad6b19 --- /dev/null +++ b/test-visitor.c @@ -0,0 +1,306 @@ +#include <glib.h> +#include "qapi/qmp-output-visitor.h" +#include "qapi/qmp-input-visitor.h" +#include "test-qapi-types.h" +#include "test-qapi-visit.h" +#include "qemu-objects.h" + +typedef struct TestStruct +{ + int64_t x; + int64_t y; +} TestStruct; + +typedef struct TestStructList +{ + TestStruct *value; + struct TestStructList *next; +} TestStructList; + +static void visit_type_TestStruct(Visitor *v, TestStruct **obj, const char *name, Error **errp) +{ + visit_start_struct(v, (void **)obj, "TestStruct", name, sizeof(TestStruct), errp); + visit_type_int(v, &(*obj)->x, "x", errp); + visit_type_int(v, &(*obj)->y, "y", errp); + visit_end_struct(v, errp); +} + +static void visit_type_TestStructList(Visitor *m, TestStructList ** obj, const char *name, Error **errp) +{ + GenericList *i; + + visit_start_list(m, name, errp); + + for (i = visit_next_list(m, (GenericList **)obj, errp); i; i = visit_next_list(m, &i, errp)) { + TestStructList *native_i = (TestStructList *)i; + visit_type_TestStruct(m, &native_i->value, NULL, errp); + } + + visit_end_list(m, errp); +} + +/* test core visitor methods */ +static void test_visitor_core(void) +{ + QmpOutputVisitor *mo; + QmpInputVisitor *mi; + Visitor *v; + TestStruct ts = { 42, 82 }; + TestStruct *pts = &ts; + TestStructList *lts = NULL; + Error *err = NULL; + QObject *obj; + QString *str; + int64_t value = 0; + + mo = qmp_output_visitor_new(); + v = qmp_output_get_visitor(mo); + + visit_type_TestStruct(v, &pts, NULL, &err); + + obj = qmp_output_get_qobject(mo); + + str = qobject_to_json(obj); + + printf("%s\n", qstring_get_str(str)); + + QDECREF(str); + + obj = QOBJECT(qint_from_int(0x42)); + + mi = qmp_input_visitor_new(obj); + v = qmp_input_get_visitor(mi); + + visit_type_int(v, &value, NULL, &err); + if (err) { + g_error("%s", error_get_pretty(err)); + } + + g_assert(value == 0x42); + + qobject_decref(obj); + + obj = qobject_from_json("{'x': 42, 'y': 84}"); + mi = qmp_input_visitor_new(obj); + v = qmp_input_get_visitor(mi); + + pts = NULL; + + visit_type_TestStruct(v, &pts, NULL, &err); + if (err) { + g_error("%s", error_get_pretty(err)); + } + + g_assert(pts != NULL); + g_assert(pts->x == 42); + g_assert(pts->y == 84); + + qobject_decref(obj); + + obj = qobject_from_json("[{'x': 42, 'y': 84}, {'x': 12, 'y': 24}]"); + mi = qmp_input_visitor_new(obj); + v = qmp_input_get_visitor(mi); + + visit_type_TestStructList(v, <s, NULL, &err); + if (err) { + g_error("%s", error_get_pretty(err)); + } + + g_assert(lts != NULL); + g_assert(lts->value->x == 42); + g_assert(lts->value->y == 84); + + lts = lts->next; + g_assert(lts != NULL); + g_assert(lts->value->x == 12); + g_assert(lts->value->y == 24); + + g_assert(lts->next == NULL); + + qobject_decref(obj); +} + +/* test deep nesting with refs to other user-defined types */ +static void test_nested_structs(void) +{ + QmpOutputVisitor *mo; + QmpInputVisitor *mi; + Visitor *v; + UserDefOne ud1; + UserDefOne *ud1_p = &ud1, *ud1c_p = NULL; + UserDefTwo ud2; + UserDefTwo *ud2_p = &ud2, *ud2c_p = NULL; + Error *err = NULL; + QObject *obj; + QString *str; + + ud1.integer = 42; + ud1.string = strdup("fourty two"); + + /* sanity check */ + mo = qmp_output_visitor_new(); + v = qmp_output_get_visitor(mo); + visit_type_UserDefOne(v, &ud1_p, "o_O", &err); + if (err) { + g_error("%s", error_get_pretty(err)); + } + obj = qmp_output_get_qobject(mo); + g_assert(obj); + qobject_decref(obj); + + ud2.string = strdup("fourty three"); + ud2.dict.string = strdup("fourty four"); + ud2.dict.dict.userdef = ud1_p; + ud2.dict.dict.string = strdup("fourty five"); + ud2.dict.has_dict2 = true; + ud2.dict.dict2.userdef = ud1_p; + ud2.dict.dict2.string = strdup("fourty six"); + + /* c type -> qobject */ + mo = qmp_output_visitor_new(); + v = qmp_output_get_visitor(mo); + visit_type_UserDefTwo(v, &ud2_p, "unused", &err); + if (err) { + g_error("%s", error_get_pretty(err)); + } + obj = qmp_output_get_qobject(mo); + g_assert(obj); + str = qobject_to_json_pretty(obj); + g_print("%s\n", qstring_get_str(str)); + QDECREF(str); + + /* qobject -> c type, should match original struct */ + mi = qmp_input_visitor_new(obj); + v = qmp_input_get_visitor(mi); + visit_type_UserDefTwo(v, &ud2c_p, NULL, &err); + if (err) { + g_error("%s", error_get_pretty(err)); + } + + g_assert(!g_strcmp0(ud2c_p->string, ud2.string)); + g_assert(!g_strcmp0(ud2c_p->dict.string, ud2.dict.string)); + + ud1c_p = ud2c_p->dict.dict.userdef; + g_assert(ud1c_p->integer == ud1_p->integer); + g_assert(!g_strcmp0(ud1c_p->string, ud1_p->string)); + + g_assert(!g_strcmp0(ud2c_p->dict.dict.string, ud2.dict.dict.string)); + + ud1c_p = ud2c_p->dict.dict2.userdef; + g_assert(ud1c_p->integer == ud1_p->integer); + g_assert(!g_strcmp0(ud1c_p->string, ud1_p->string)); + + g_assert(!g_strcmp0(ud2c_p->dict.dict2.string, ud2.dict.dict2.string)); + qemu_free(ud1.string); + qemu_free(ud2.string); + qemu_free(ud2.dict.string); + qemu_free(ud2.dict.dict.string); + qemu_free(ud2.dict.dict2.string); + + qapi_free_UserDefTwo(ud2c_p); + + qobject_decref(obj); +} + +/* test enum values */ +static void test_enums(void) +{ + QmpOutputVisitor *mo; + QmpInputVisitor *mi; + Visitor *v; + EnumOne enum1 = ENUM_ONE_VALUE2, enum1_cpy = ENUM_ONE_VALUE1; + Error *err = NULL; + QObject *obj; + QString *str; + + /* C type -> QObject */ + mo = qmp_output_visitor_new(); + v = qmp_output_get_visitor(mo); + visit_type_EnumOne(v, &enum1, "unused", &err); + if (err) { + g_error("%s", error_get_pretty(err)); + } + obj = qmp_output_get_qobject(mo); + g_assert(obj); + str = qobject_to_json_pretty(obj); + g_print("%s\n", qstring_get_str(str)); + QDECREF(str); + g_assert(g_strcmp0(qstring_get_str(qobject_to_qstring(obj)), "value2") == 0); + + /* QObject -> C type */ + mi = qmp_input_visitor_new(obj); + v = qmp_input_get_visitor(mi); + visit_type_EnumOne(v, &enum1_cpy, "unused", &err); + if (err) { + g_error("%s", error_get_pretty(err)); + } + g_debug("enum1_cpy, enum1: %d, %d", enum1_cpy, enum1); + g_assert(enum1_cpy == enum1); + + qobject_decref(obj); +} + +/* test enum values nested in schema-defined structs */ +static void test_nested_enums(void) +{ + QmpOutputVisitor *mo; + QmpInputVisitor *mi; + Visitor *v; + NestedEnumsOne *nested_enums, *nested_enums_cpy = NULL; + Error *err = NULL; + QObject *obj; + QString *str; + + nested_enums = qemu_mallocz(sizeof(NestedEnumsOne)); + nested_enums->enum1 = ENUM_ONE_VALUE1; + nested_enums->enum2 = ENUM_ONE_VALUE2; + nested_enums->enum3 = ENUM_ONE_VALUE3; + nested_enums->enum4 = ENUM_ONE_VALUE3; + nested_enums->has_enum2 = false; + nested_enums->has_enum4 = true; + + /* C type -> QObject */ + mo = qmp_output_visitor_new(); + v = qmp_output_get_visitor(mo); + visit_type_NestedEnumsOne(v, &nested_enums, NULL, &err); + if (err) { + g_error("%s", error_get_pretty(err)); + } + obj = qmp_output_get_qobject(mo); + g_assert(obj); + str = qobject_to_json_pretty(obj); + g_print("%s\n", qstring_get_str(str)); + QDECREF(str); + + /* QObject -> C type */ + mi = qmp_input_visitor_new(obj); + v = qmp_input_get_visitor(mi); + visit_type_NestedEnumsOne(v, &nested_enums_cpy, NULL, &err); + if (err) { + g_error("%s", error_get_pretty(err)); + } + g_assert(nested_enums_cpy); + g_assert(nested_enums_cpy->enum1 == nested_enums->enum1); + g_assert(nested_enums_cpy->enum3 == nested_enums->enum3); + g_assert(nested_enums_cpy->enum4 == nested_enums->enum4); + g_assert(nested_enums_cpy->has_enum2 == false); + g_assert(nested_enums_cpy->has_enum4 == true); + + qobject_decref(obj); + qapi_free_NestedEnumsOne(nested_enums); + qapi_free_NestedEnumsOne(nested_enums_cpy); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + g_test_add_func("/0.15/visitor_core", test_visitor_core); + g_test_add_func("/0.15/nested_structs", test_nested_structs); + g_test_add_func("/0.15/enums", test_enums); + g_test_add_func("/0.15/nested_enums", test_nested_enums); + + g_test_run(); + + return 0; +} diff --git a/trace-events b/trace-events index 99a4a2b144..713f042081 100644 --- a/trace-events +++ b/trace-events @@ -216,13 +216,13 @@ disable usb_ehci_mmio_writel(uint32_t addr, const char *str, uint32_t val) "wr m disable usb_ehci_mmio_change(uint32_t addr, const char *str, uint32_t new, uint32_t old) "ch mmio %04x [%s] = %x (old: %x)" disable usb_ehci_usbsts(const char *sts, int state) "usbsts %s %d" disable usb_ehci_state(const char *schedule, const char *state) "%s schedule %s" -disable usb_ehci_qh_ptrs(void *q, uint32_t addr, uint32_t next, uint32_t c_qtd, uint32_t n_qtd, uint32_t a_qtd) "q %p - QH @ %08x: next %08x qtds %08x,%08x,%08x" +disable usb_ehci_qh_ptrs(void *q, uint32_t addr, uint32_t nxt, uint32_t c_qtd, uint32_t n_qtd, uint32_t a_qtd) "q %p - QH @ %08x: next %08x qtds %08x,%08x,%08x" disable usb_ehci_qh_fields(uint32_t addr, int rl, int mplen, int eps, int ep, int devaddr) "QH @ %08x - rl %d, mplen %d, eps %d, ep %d, dev %d" disable usb_ehci_qh_bits(uint32_t addr, int c, int h, int dtc, int i) "QH @ %08x - c %d, h %d, dtc %d, i %d" -disable usb_ehci_qtd_ptrs(void *q, uint32_t addr, uint32_t next, uint32_t altnext) "q %p - QTD @ %08x: next %08x altnext %08x" +disable usb_ehci_qtd_ptrs(void *q, uint32_t addr, uint32_t nxt, uint32_t altnext) "q %p - QTD @ %08x: next %08x altnext %08x" disable usb_ehci_qtd_fields(uint32_t addr, int tbytes, int cpage, int cerr, int pid) "QTD @ %08x - tbytes %d, cpage %d, cerr %d, pid %d" disable usb_ehci_qtd_bits(uint32_t addr, int ioc, int active, int halt, int babble, int xacterr) "QTD @ %08x - ioc %d, active %d, halt %d, babble %d, xacterr %d" -disable usb_ehci_itd(uint32_t addr, uint32_t next, uint32_t mplen, uint32_t mult, uint32_t ep, uint32_t devaddr) "ITD @ %08x: next %08x - mplen %d, mult %d, ep %d, dev %d" +disable usb_ehci_itd(uint32_t addr, uint32_t nxt, uint32_t mplen, uint32_t mult, uint32_t ep, uint32_t devaddr) "ITD @ %08x: next %08x - mplen %d, mult %d, ep %d, dev %d" disable usb_ehci_port_attach(uint32_t port, const char *device) "attach port #%d - %s" disable usb_ehci_port_detach(uint32_t port) "detach port #%d" disable usb_ehci_port_reset(uint32_t port, int enable) "reset port #%d - %d" diff --git a/ui/spice-core.c b/ui/spice-core.c index e142452bb6..1100417698 100644 --- a/ui/spice-core.c +++ b/ui/spice-core.c @@ -480,7 +480,16 @@ void qemu_spice_init(void) port = qemu_opt_get_number(opts, "port", 0); tls_port = qemu_opt_get_number(opts, "tls-port", 0); if (!port && !tls_port) { - return; + fprintf(stderr, "neither port nor tls-port specified for spice."); + exit(1); + } + if (port < 0 || port > 65535) { + fprintf(stderr, "spice port is out of range"); + exit(1); + } + if (tls_port < 0 || tls_port > 65535) { + fprintf(stderr, "spice tls-port is out of range"); + exit(1); } password = qemu_opt_get(opts, "password"); diff --git a/usb-redir.c b/usb-redir.c new file mode 100644 index 0000000000..e2129931a0 --- /dev/null +++ b/usb-redir.c @@ -0,0 +1,1218 @@ +/* + * USB redirector usb-guest + * + * Copyright (c) 2011 Red Hat, Inc. + * + * Red Hat Authors: + * Hans de Goede <hdegoede@redhat.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu-common.h" +#include "qemu-timer.h" +#include "monitor.h" +#include "sysemu.h" + +#include <dirent.h> +#include <sys/ioctl.h> +#include <signal.h> +#include <usbredirparser.h> + +#include "hw/usb.h" + +#define MAX_ENDPOINTS 32 +#define EP2I(ep_address) (((ep_address & 0x80) >> 3) | (ep_address & 0x0f)) +#define I2EP(i) (((i & 0x10) << 3) | (i & 0x0f)) + +typedef struct AsyncURB AsyncURB; +typedef struct USBRedirDevice USBRedirDevice; + +/* Struct to hold buffered packets (iso or int input packets) */ +struct buf_packet { + uint8_t *data; + int len; + int status; + QTAILQ_ENTRY(buf_packet)next; +}; + +struct endp_data { + uint8_t type; + uint8_t interval; + uint8_t interface; /* bInterfaceNumber this ep belongs to */ + uint8_t iso_started; + uint8_t iso_error; /* For reporting iso errors to the HC */ + uint8_t interrupt_started; + uint8_t interrupt_error; + QTAILQ_HEAD(, buf_packet) bufpq; +}; + +struct USBRedirDevice { + USBDevice dev; + /* Properties */ + CharDriverState *cs; + uint8_t debug; + /* Data passed from chardev the fd_read cb to the usbredirparser read cb */ + const uint8_t *read_buf; + int read_buf_size; + /* For async handling of open/close */ + QEMUBH *open_close_bh; + /* To delay the usb attach in case of quick chardev close + open */ + QEMUTimer *attach_timer; + int64_t next_attach_time; + struct usbredirparser *parser; + struct endp_data endpoint[MAX_ENDPOINTS]; + uint32_t packet_id; + QTAILQ_HEAD(, AsyncURB) asyncq; +}; + +struct AsyncURB { + USBRedirDevice *dev; + USBPacket *packet; + uint32_t packet_id; + int get; + union { + struct usb_redir_control_packet_header control_packet; + struct usb_redir_bulk_packet_header bulk_packet; + struct usb_redir_interrupt_packet_header interrupt_packet; + }; + QTAILQ_ENTRY(AsyncURB)next; +}; + +static void usbredir_device_connect(void *priv, + struct usb_redir_device_connect_header *device_connect); +static void usbredir_device_disconnect(void *priv); +static void usbredir_interface_info(void *priv, + struct usb_redir_interface_info_header *interface_info); +static void usbredir_ep_info(void *priv, + struct usb_redir_ep_info_header *ep_info); +static void usbredir_configuration_status(void *priv, uint32_t id, + struct usb_redir_configuration_status_header *configuration_status); +static void usbredir_alt_setting_status(void *priv, uint32_t id, + struct usb_redir_alt_setting_status_header *alt_setting_status); +static void usbredir_iso_stream_status(void *priv, uint32_t id, + struct usb_redir_iso_stream_status_header *iso_stream_status); +static void usbredir_interrupt_receiving_status(void *priv, uint32_t id, + struct usb_redir_interrupt_receiving_status_header + *interrupt_receiving_status); +static void usbredir_bulk_streams_status(void *priv, uint32_t id, + struct usb_redir_bulk_streams_status_header *bulk_streams_status); +static void usbredir_control_packet(void *priv, uint32_t id, + struct usb_redir_control_packet_header *control_packet, + uint8_t *data, int data_len); +static void usbredir_bulk_packet(void *priv, uint32_t id, + struct usb_redir_bulk_packet_header *bulk_packet, + uint8_t *data, int data_len); +static void usbredir_iso_packet(void *priv, uint32_t id, + struct usb_redir_iso_packet_header *iso_packet, + uint8_t *data, int data_len); +static void usbredir_interrupt_packet(void *priv, uint32_t id, + struct usb_redir_interrupt_packet_header *interrupt_header, + uint8_t *data, int data_len); + +static int usbredir_handle_status(USBRedirDevice *dev, + int status, int actual_len); + +#define VERSION "qemu usb-redir guest " QEMU_VERSION + +/* + * Logging stuff + */ + +#define ERROR(...) \ + do { \ + if (dev->debug >= usbredirparser_error) { \ + error_report("usb-redir error: " __VA_ARGS__); \ + } \ + } while (0) +#define WARNING(...) \ + do { \ + if (dev->debug >= usbredirparser_warning) { \ + error_report("usb-redir warning: " __VA_ARGS__); \ + } \ + } while (0) +#define INFO(...) \ + do { \ + if (dev->debug >= usbredirparser_info) { \ + error_report("usb-redir: " __VA_ARGS__); \ + } \ + } while (0) +#define DPRINTF(...) \ + do { \ + if (dev->debug >= usbredirparser_debug) { \ + error_report("usb-redir: " __VA_ARGS__); \ + } \ + } while (0) +#define DPRINTF2(...) \ + do { \ + if (dev->debug >= usbredirparser_debug_data) { \ + error_report("usb-redir: " __VA_ARGS__); \ + } \ + } while (0) + +static void usbredir_log(void *priv, int level, const char *msg) +{ + USBRedirDevice *dev = priv; + + if (dev->debug < level) { + return; + } + + error_report("%s\n", msg); +} + +static void usbredir_log_data(USBRedirDevice *dev, const char *desc, + const uint8_t *data, int len) +{ + int i, j, n; + + if (dev->debug < usbredirparser_debug_data) { + return; + } + + for (i = 0; i < len; i += j) { + char buf[128]; + + n = sprintf(buf, "%s", desc); + for (j = 0; j < 8 && i + j < len; j++) { + n += sprintf(buf + n, " %02X", data[i + j]); + } + error_report("%s\n", buf); + } +} + +/* + * usbredirparser io functions + */ + +static int usbredir_read(void *priv, uint8_t *data, int count) +{ + USBRedirDevice *dev = priv; + + if (dev->read_buf_size < count) { + count = dev->read_buf_size; + } + + memcpy(data, dev->read_buf, count); + + dev->read_buf_size -= count; + if (dev->read_buf_size) { + dev->read_buf += count; + } else { + dev->read_buf = NULL; + } + + return count; +} + +static int usbredir_write(void *priv, uint8_t *data, int count) +{ + USBRedirDevice *dev = priv; + + return qemu_chr_write(dev->cs, data, count); +} + +/* + * Async and buffered packets helpers + */ + +static AsyncURB *async_alloc(USBRedirDevice *dev, USBPacket *p) +{ + AsyncURB *aurb = (AsyncURB *) qemu_mallocz(sizeof(AsyncURB)); + aurb->dev = dev; + aurb->packet = p; + aurb->packet_id = dev->packet_id; + QTAILQ_INSERT_TAIL(&dev->asyncq, aurb, next); + dev->packet_id++; + + return aurb; +} + +static void async_free(USBRedirDevice *dev, AsyncURB *aurb) +{ + QTAILQ_REMOVE(&dev->asyncq, aurb, next); + qemu_free(aurb); +} + +static AsyncURB *async_find(USBRedirDevice *dev, uint32_t packet_id) +{ + AsyncURB *aurb; + + QTAILQ_FOREACH(aurb, &dev->asyncq, next) { + if (aurb->packet_id == packet_id) { + return aurb; + } + } + ERROR("could not find async urb for packet_id %u\n", packet_id); + return NULL; +} + +static void usbredir_cancel_packet(USBDevice *udev, USBPacket *p) +{ + USBRedirDevice *dev = DO_UPCAST(USBRedirDevice, dev, udev); + AsyncURB *aurb; + + QTAILQ_FOREACH(aurb, &dev->asyncq, next) { + if (p != aurb->packet) { + continue; + } + + DPRINTF("async cancel id %u\n", aurb->packet_id); + usbredirparser_send_cancel_data_packet(dev->parser, aurb->packet_id); + usbredirparser_do_write(dev->parser); + + /* Mark it as dead */ + aurb->packet = NULL; + break; + } +} + +static struct buf_packet *bufp_alloc(USBRedirDevice *dev, + uint8_t *data, int len, int status, uint8_t ep) +{ + struct buf_packet *bufp = qemu_malloc(sizeof(struct buf_packet)); + bufp->data = data; + bufp->len = len; + bufp->status = status; + QTAILQ_INSERT_TAIL(&dev->endpoint[EP2I(ep)].bufpq, bufp, next); + return bufp; +} + +static void bufp_free(USBRedirDevice *dev, struct buf_packet *bufp, + uint8_t ep) +{ + QTAILQ_REMOVE(&dev->endpoint[EP2I(ep)].bufpq, bufp, next); + free(bufp->data); + qemu_free(bufp); +} + +static void usbredir_free_bufpq(USBRedirDevice *dev, uint8_t ep) +{ + struct buf_packet *buf, *buf_next; + + QTAILQ_FOREACH_SAFE(buf, &dev->endpoint[EP2I(ep)].bufpq, next, buf_next) { + bufp_free(dev, buf, ep); + } +} + +/* + * USBDevice callbacks + */ + +static void usbredir_handle_reset(USBDevice *udev) +{ + USBRedirDevice *dev = DO_UPCAST(USBRedirDevice, dev, udev); + + DPRINTF("reset device\n"); + usbredirparser_send_reset(dev->parser); + usbredirparser_do_write(dev->parser); +} + +static int usbredir_handle_iso_data(USBRedirDevice *dev, USBPacket *p, + uint8_t ep) +{ + int status, len; + + if (!dev->endpoint[EP2I(ep)].iso_started && + !dev->endpoint[EP2I(ep)].iso_error) { + struct usb_redir_start_iso_stream_header start_iso = { + .endpoint = ep, + /* TODO maybe do something with these depending on ep interval? */ + .pkts_per_urb = 32, + .no_urbs = 3, + }; + /* No id, we look at the ep when receiving a status back */ + usbredirparser_send_start_iso_stream(dev->parser, 0, &start_iso); + usbredirparser_do_write(dev->parser); + DPRINTF("iso stream started ep %02X\n", ep); + dev->endpoint[EP2I(ep)].iso_started = 1; + } + + if (ep & USB_DIR_IN) { + struct buf_packet *isop; + + isop = QTAILQ_FIRST(&dev->endpoint[EP2I(ep)].bufpq); + if (isop == NULL) { + DPRINTF2("iso-token-in ep %02X, no isop\n", ep); + /* Check iso_error for stream errors, otherwise its an underrun */ + status = dev->endpoint[EP2I(ep)].iso_error; + dev->endpoint[EP2I(ep)].iso_error = 0; + return usbredir_handle_status(dev, status, 0); + } + DPRINTF2("iso-token-in ep %02X status %d len %d\n", ep, isop->status, + isop->len); + + status = isop->status; + if (status != usb_redir_success) { + bufp_free(dev, isop, ep); + return usbredir_handle_status(dev, status, 0); + } + + len = isop->len; + if (len > p->len) { + ERROR("received iso data is larger then packet ep %02X\n", ep); + bufp_free(dev, isop, ep); + return USB_RET_NAK; + } + memcpy(p->data, isop->data, len); + bufp_free(dev, isop, ep); + return len; + } else { + /* If the stream was not started because of a pending error don't + send the packet to the usb-host */ + if (dev->endpoint[EP2I(ep)].iso_started) { + struct usb_redir_iso_packet_header iso_packet = { + .endpoint = ep, + .length = p->len + }; + /* No id, we look at the ep when receiving a status back */ + usbredirparser_send_iso_packet(dev->parser, 0, &iso_packet, + p->data, p->len); + usbredirparser_do_write(dev->parser); + } + status = dev->endpoint[EP2I(ep)].iso_error; + dev->endpoint[EP2I(ep)].iso_error = 0; + DPRINTF2("iso-token-out ep %02X status %d len %d\n", ep, status, + p->len); + return usbredir_handle_status(dev, status, p->len); + } +} + +static void usbredir_stop_iso_stream(USBRedirDevice *dev, uint8_t ep) +{ + struct usb_redir_stop_iso_stream_header stop_iso_stream = { + .endpoint = ep + }; + if (dev->endpoint[EP2I(ep)].iso_started) { + usbredirparser_send_stop_iso_stream(dev->parser, 0, &stop_iso_stream); + DPRINTF("iso stream stopped ep %02X\n", ep); + dev->endpoint[EP2I(ep)].iso_started = 0; + } + usbredir_free_bufpq(dev, ep); +} + +static int usbredir_handle_bulk_data(USBRedirDevice *dev, USBPacket *p, + uint8_t ep) +{ + AsyncURB *aurb = async_alloc(dev, p); + struct usb_redir_bulk_packet_header bulk_packet; + + DPRINTF("bulk-out ep %02X len %d id %u\n", ep, p->len, aurb->packet_id); + + bulk_packet.endpoint = ep; + bulk_packet.length = p->len; + bulk_packet.stream_id = 0; + aurb->bulk_packet = bulk_packet; + + if (ep & USB_DIR_IN) { + usbredirparser_send_bulk_packet(dev->parser, aurb->packet_id, + &bulk_packet, NULL, 0); + } else { + usbredir_log_data(dev, "bulk data out:", p->data, p->len); + usbredirparser_send_bulk_packet(dev->parser, aurb->packet_id, + &bulk_packet, p->data, p->len); + } + usbredirparser_do_write(dev->parser); + return USB_RET_ASYNC; +} + +static int usbredir_handle_interrupt_data(USBRedirDevice *dev, + USBPacket *p, uint8_t ep) +{ + if (ep & USB_DIR_IN) { + /* Input interrupt endpoint, buffered packet input */ + struct buf_packet *intp; + int status, len; + + if (!dev->endpoint[EP2I(ep)].interrupt_started && + !dev->endpoint[EP2I(ep)].interrupt_error) { + struct usb_redir_start_interrupt_receiving_header start_int = { + .endpoint = ep, + }; + /* No id, we look at the ep when receiving a status back */ + usbredirparser_send_start_interrupt_receiving(dev->parser, 0, + &start_int); + usbredirparser_do_write(dev->parser); + DPRINTF("interrupt recv started ep %02X\n", ep); + dev->endpoint[EP2I(ep)].interrupt_started = 1; + } + + intp = QTAILQ_FIRST(&dev->endpoint[EP2I(ep)].bufpq); + if (intp == NULL) { + DPRINTF2("interrupt-token-in ep %02X, no intp\n", ep); + /* Check interrupt_error for stream errors */ + status = dev->endpoint[EP2I(ep)].interrupt_error; + dev->endpoint[EP2I(ep)].interrupt_error = 0; + return usbredir_handle_status(dev, status, 0); + } + DPRINTF("interrupt-token-in ep %02X status %d len %d\n", ep, + intp->status, intp->len); + + status = intp->status; + if (status != usb_redir_success) { + bufp_free(dev, intp, ep); + return usbredir_handle_status(dev, status, 0); + } + + len = intp->len; + if (len > p->len) { + ERROR("received int data is larger then packet ep %02X\n", ep); + bufp_free(dev, intp, ep); + return USB_RET_NAK; + } + memcpy(p->data, intp->data, len); + bufp_free(dev, intp, ep); + return len; + } else { + /* Output interrupt endpoint, normal async operation */ + AsyncURB *aurb = async_alloc(dev, p); + struct usb_redir_interrupt_packet_header interrupt_packet; + + DPRINTF("interrupt-out ep %02X len %d id %u\n", ep, p->len, + aurb->packet_id); + + interrupt_packet.endpoint = ep; + interrupt_packet.length = p->len; + aurb->interrupt_packet = interrupt_packet; + + usbredir_log_data(dev, "interrupt data out:", p->data, p->len); + usbredirparser_send_interrupt_packet(dev->parser, aurb->packet_id, + &interrupt_packet, p->data, p->len); + usbredirparser_do_write(dev->parser); + return USB_RET_ASYNC; + } +} + +static void usbredir_stop_interrupt_receiving(USBRedirDevice *dev, + uint8_t ep) +{ + struct usb_redir_stop_interrupt_receiving_header stop_interrupt_recv = { + .endpoint = ep + }; + if (dev->endpoint[EP2I(ep)].interrupt_started) { + usbredirparser_send_stop_interrupt_receiving(dev->parser, 0, + &stop_interrupt_recv); + DPRINTF("interrupt recv stopped ep %02X\n", ep); + dev->endpoint[EP2I(ep)].interrupt_started = 0; + } + usbredir_free_bufpq(dev, ep); +} + +static int usbredir_handle_data(USBDevice *udev, USBPacket *p) +{ + USBRedirDevice *dev = DO_UPCAST(USBRedirDevice, dev, udev); + uint8_t ep; + + ep = p->devep; + if (p->pid == USB_TOKEN_IN) { + ep |= USB_DIR_IN; + } + + switch (dev->endpoint[EP2I(ep)].type) { + case USB_ENDPOINT_XFER_CONTROL: + ERROR("handle_data called for control transfer on ep %02X\n", ep); + return USB_RET_NAK; + case USB_ENDPOINT_XFER_ISOC: + return usbredir_handle_iso_data(dev, p, ep); + case USB_ENDPOINT_XFER_BULK: + return usbredir_handle_bulk_data(dev, p, ep);; + case USB_ENDPOINT_XFER_INT: + return usbredir_handle_interrupt_data(dev, p, ep);; + default: + ERROR("handle_data ep %02X has unknown type %d\n", ep, + dev->endpoint[EP2I(ep)].type); + return USB_RET_NAK; + } +} + +static int usbredir_set_config(USBRedirDevice *dev, USBPacket *p, + int config) +{ + struct usb_redir_set_configuration_header set_config; + AsyncURB *aurb = async_alloc(dev, p); + int i; + + DPRINTF("set config %d id %u\n", config, aurb->packet_id); + + for (i = 0; i < MAX_ENDPOINTS; i++) { + switch (dev->endpoint[i].type) { + case USB_ENDPOINT_XFER_ISOC: + usbredir_stop_iso_stream(dev, I2EP(i)); + break; + case USB_ENDPOINT_XFER_INT: + if (i & 0x10) { + usbredir_stop_interrupt_receiving(dev, I2EP(i)); + } + break; + } + usbredir_free_bufpq(dev, I2EP(i)); + } + + set_config.configuration = config; + usbredirparser_send_set_configuration(dev->parser, aurb->packet_id, + &set_config); + usbredirparser_do_write(dev->parser); + return USB_RET_ASYNC; +} + +static int usbredir_get_config(USBRedirDevice *dev, USBPacket *p) +{ + AsyncURB *aurb = async_alloc(dev, p); + + DPRINTF("get config id %u\n", aurb->packet_id); + + aurb->get = 1; + usbredirparser_send_get_configuration(dev->parser, aurb->packet_id); + usbredirparser_do_write(dev->parser); + return USB_RET_ASYNC; +} + +static int usbredir_set_interface(USBRedirDevice *dev, USBPacket *p, + int interface, int alt) +{ + struct usb_redir_set_alt_setting_header set_alt; + AsyncURB *aurb = async_alloc(dev, p); + int i; + + DPRINTF("set interface %d alt %d id %u\n", interface, alt, + aurb->packet_id); + + for (i = 0; i < MAX_ENDPOINTS; i++) { + if (dev->endpoint[i].interface == interface) { + switch (dev->endpoint[i].type) { + case USB_ENDPOINT_XFER_ISOC: + usbredir_stop_iso_stream(dev, I2EP(i)); + break; + case USB_ENDPOINT_XFER_INT: + if (i & 0x10) { + usbredir_stop_interrupt_receiving(dev, I2EP(i)); + } + break; + } + usbredir_free_bufpq(dev, I2EP(i)); + } + } + + set_alt.interface = interface; + set_alt.alt = alt; + usbredirparser_send_set_alt_setting(dev->parser, aurb->packet_id, + &set_alt); + usbredirparser_do_write(dev->parser); + return USB_RET_ASYNC; +} + +static int usbredir_get_interface(USBRedirDevice *dev, USBPacket *p, + int interface) +{ + struct usb_redir_get_alt_setting_header get_alt; + AsyncURB *aurb = async_alloc(dev, p); + + DPRINTF("get interface %d id %u\n", interface, aurb->packet_id); + + get_alt.interface = interface; + aurb->get = 1; + usbredirparser_send_get_alt_setting(dev->parser, aurb->packet_id, + &get_alt); + usbredirparser_do_write(dev->parser); + return USB_RET_ASYNC; +} + +static int usbredir_handle_control(USBDevice *udev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + USBRedirDevice *dev = DO_UPCAST(USBRedirDevice, dev, udev); + struct usb_redir_control_packet_header control_packet; + AsyncURB *aurb; + + /* Special cases for certain standard device requests */ + switch (request) { + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + DPRINTF("set address %d\n", value); + dev->dev.addr = value; + return 0; + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + return usbredir_set_config(dev, p, value & 0xff); + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + return usbredir_get_config(dev, p); + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: + return usbredir_set_interface(dev, p, index, value); + case InterfaceRequest | USB_REQ_GET_INTERFACE: + return usbredir_get_interface(dev, p, index); + } + + /* "Normal" ctrl requests */ + aurb = async_alloc(dev, p); + + /* Note request is (bRequestType << 8) | bRequest */ + DPRINTF("ctrl-out type 0x%x req 0x%x val 0x%x index %d len %d id %u\n", + request >> 8, request & 0xff, value, index, length, + aurb->packet_id); + + control_packet.request = request & 0xFF; + control_packet.requesttype = request >> 8; + control_packet.endpoint = control_packet.requesttype & USB_DIR_IN; + control_packet.value = value; + control_packet.index = index; + control_packet.length = length; + aurb->control_packet = control_packet; + + if (control_packet.requesttype & USB_DIR_IN) { + usbredirparser_send_control_packet(dev->parser, aurb->packet_id, + &control_packet, NULL, 0); + } else { + usbredir_log_data(dev, "ctrl data out:", data, length); + usbredirparser_send_control_packet(dev->parser, aurb->packet_id, + &control_packet, data, length); + } + usbredirparser_do_write(dev->parser); + return USB_RET_ASYNC; +} + +/* + * Close events can be triggered by usbredirparser_do_write which gets called + * from within the USBDevice data / control packet callbacks and doing a + * usb_detach from within these callbacks is not a good idea. + * + * So we use a bh handler to take care of close events. We also handle + * open events from this callback to make sure that a close directly followed + * by an open gets handled in the right order. + */ +static void usbredir_open_close_bh(void *opaque) +{ + USBRedirDevice *dev = opaque; + + usbredir_device_disconnect(dev); + + if (dev->parser) { + usbredirparser_destroy(dev->parser); + dev->parser = NULL; + } + + if (dev->cs->opened) { + dev->parser = qemu_oom_check(usbredirparser_create()); + dev->parser->priv = dev; + dev->parser->log_func = usbredir_log; + dev->parser->read_func = usbredir_read; + dev->parser->write_func = usbredir_write; + dev->parser->device_connect_func = usbredir_device_connect; + dev->parser->device_disconnect_func = usbredir_device_disconnect; + dev->parser->interface_info_func = usbredir_interface_info; + dev->parser->ep_info_func = usbredir_ep_info; + dev->parser->configuration_status_func = usbredir_configuration_status; + dev->parser->alt_setting_status_func = usbredir_alt_setting_status; + dev->parser->iso_stream_status_func = usbredir_iso_stream_status; + dev->parser->interrupt_receiving_status_func = + usbredir_interrupt_receiving_status; + dev->parser->bulk_streams_status_func = usbredir_bulk_streams_status; + dev->parser->control_packet_func = usbredir_control_packet; + dev->parser->bulk_packet_func = usbredir_bulk_packet; + dev->parser->iso_packet_func = usbredir_iso_packet; + dev->parser->interrupt_packet_func = usbredir_interrupt_packet; + dev->read_buf = NULL; + dev->read_buf_size = 0; + usbredirparser_init(dev->parser, VERSION, NULL, 0, 0); + usbredirparser_do_write(dev->parser); + } +} + +static void usbredir_do_attach(void *opaque) +{ + USBRedirDevice *dev = opaque; + + usb_device_attach(&dev->dev); +} + +/* + * chardev callbacks + */ + +static int usbredir_chardev_can_read(void *opaque) +{ + USBRedirDevice *dev = opaque; + + if (dev->parser) { + /* usbredir_parser_do_read will consume *all* data we give it */ + return 1024 * 1024; + } else { + /* usbredir_open_close_bh hasn't handled the open event yet */ + return 0; + } +} + +static void usbredir_chardev_read(void *opaque, const uint8_t *buf, int size) +{ + USBRedirDevice *dev = opaque; + + /* No recursion allowed! */ + assert(dev->read_buf == NULL); + + dev->read_buf = buf; + dev->read_buf_size = size; + + usbredirparser_do_read(dev->parser); + /* Send any acks, etc. which may be queued now */ + usbredirparser_do_write(dev->parser); +} + +static void usbredir_chardev_event(void *opaque, int event) +{ + USBRedirDevice *dev = opaque; + + switch (event) { + case CHR_EVENT_OPENED: + case CHR_EVENT_CLOSED: + qemu_bh_schedule(dev->open_close_bh); + break; + } +} + +/* + * init + destroy + */ + +static int usbredir_initfn(USBDevice *udev) +{ + USBRedirDevice *dev = DO_UPCAST(USBRedirDevice, dev, udev); + int i; + + if (dev->cs == NULL) { + qerror_report(QERR_MISSING_PARAMETER, "chardev"); + return -1; + } + + dev->open_close_bh = qemu_bh_new(usbredir_open_close_bh, dev); + dev->attach_timer = qemu_new_timer_ms(vm_clock, usbredir_do_attach, dev); + + QTAILQ_INIT(&dev->asyncq); + for (i = 0; i < MAX_ENDPOINTS; i++) { + QTAILQ_INIT(&dev->endpoint[i].bufpq); + } + + /* We'll do the attach once we receive the speed from the usb-host */ + udev->auto_attach = 0; + + qemu_chr_add_handlers(dev->cs, usbredir_chardev_can_read, + usbredir_chardev_read, usbredir_chardev_event, dev); + + return 0; +} + +static void usbredir_cleanup_device_queues(USBRedirDevice *dev) +{ + AsyncURB *aurb, *next_aurb; + int i; + + QTAILQ_FOREACH_SAFE(aurb, &dev->asyncq, next, next_aurb) { + async_free(dev, aurb); + } + for (i = 0; i < MAX_ENDPOINTS; i++) { + usbredir_free_bufpq(dev, I2EP(i)); + } +} + +static void usbredir_handle_destroy(USBDevice *udev) +{ + USBRedirDevice *dev = DO_UPCAST(USBRedirDevice, dev, udev); + + qemu_chr_close(dev->cs); + /* Note must be done after qemu_chr_close, as that causes a close event */ + qemu_bh_delete(dev->open_close_bh); + + qemu_del_timer(dev->attach_timer); + qemu_free_timer(dev->attach_timer); + + usbredir_cleanup_device_queues(dev); + + if (dev->parser) { + usbredirparser_destroy(dev->parser); + } +} + +/* + * usbredirparser packet complete callbacks + */ + +static int usbredir_handle_status(USBRedirDevice *dev, + int status, int actual_len) +{ + switch (status) { + case usb_redir_success: + return actual_len; + case usb_redir_stall: + return USB_RET_STALL; + case usb_redir_cancelled: + WARNING("returning cancelled packet to HC?\n"); + case usb_redir_inval: + case usb_redir_ioerror: + case usb_redir_timeout: + default: + return USB_RET_NAK; + } +} + +static void usbredir_device_connect(void *priv, + struct usb_redir_device_connect_header *device_connect) +{ + USBRedirDevice *dev = priv; + + switch (device_connect->speed) { + case usb_redir_speed_low: + DPRINTF("attaching low speed device\n"); + dev->dev.speed = USB_SPEED_LOW; + break; + case usb_redir_speed_full: + DPRINTF("attaching full speed device\n"); + dev->dev.speed = USB_SPEED_FULL; + break; + case usb_redir_speed_high: + DPRINTF("attaching high speed device\n"); + dev->dev.speed = USB_SPEED_HIGH; + break; + case usb_redir_speed_super: + DPRINTF("attaching super speed device\n"); + dev->dev.speed = USB_SPEED_SUPER; + break; + default: + DPRINTF("attaching unknown speed device, assuming full speed\n"); + dev->dev.speed = USB_SPEED_FULL; + } + dev->dev.speedmask = (1 << dev->dev.speed); + qemu_mod_timer(dev->attach_timer, dev->next_attach_time); +} + +static void usbredir_device_disconnect(void *priv) +{ + USBRedirDevice *dev = priv; + + /* Stop any pending attaches */ + qemu_del_timer(dev->attach_timer); + + if (dev->dev.attached) { + usb_device_detach(&dev->dev); + usbredir_cleanup_device_queues(dev); + /* + * Delay next usb device attach to give the guest a chance to see + * see the detach / attach in case of quick close / open succession + */ + dev->next_attach_time = qemu_get_clock_ms(vm_clock) + 200; + } +} + +static void usbredir_interface_info(void *priv, + struct usb_redir_interface_info_header *interface_info) +{ + /* The intention is to allow specifying acceptable interface classes + for redirection on the cmdline and in the future verify this here, + and disconnect (or never connect) the device if a not accepted + interface class is detected */ +} + +static void usbredir_ep_info(void *priv, + struct usb_redir_ep_info_header *ep_info) +{ + USBRedirDevice *dev = priv; + int i; + + for (i = 0; i < MAX_ENDPOINTS; i++) { + dev->endpoint[i].type = ep_info->type[i]; + dev->endpoint[i].interval = ep_info->interval[i]; + dev->endpoint[i].interface = ep_info->interface[i]; + if (dev->endpoint[i].type != usb_redir_type_invalid) { + DPRINTF("ep: %02X type: %d interface: %d\n", I2EP(i), + dev->endpoint[i].type, dev->endpoint[i].interface); + } + } +} + +static void usbredir_configuration_status(void *priv, uint32_t id, + struct usb_redir_configuration_status_header *config_status) +{ + USBRedirDevice *dev = priv; + AsyncURB *aurb; + int len = 0; + + DPRINTF("set config status %d config %d id %u\n", config_status->status, + config_status->configuration, id); + + aurb = async_find(dev, id); + if (!aurb) { + return; + } + if (aurb->packet) { + if (aurb->get) { + dev->dev.data_buf[0] = config_status->configuration; + len = 1; + } + aurb->packet->len = + usbredir_handle_status(dev, config_status->status, len); + usb_generic_async_ctrl_complete(&dev->dev, aurb->packet); + } + async_free(dev, aurb); +} + +static void usbredir_alt_setting_status(void *priv, uint32_t id, + struct usb_redir_alt_setting_status_header *alt_setting_status) +{ + USBRedirDevice *dev = priv; + AsyncURB *aurb; + int len = 0; + + DPRINTF("alt status %d intf %d alt %d id: %u\n", + alt_setting_status->status, + alt_setting_status->interface, + alt_setting_status->alt, id); + + aurb = async_find(dev, id); + if (!aurb) { + return; + } + if (aurb->packet) { + if (aurb->get) { + dev->dev.data_buf[0] = alt_setting_status->alt; + len = 1; + } + aurb->packet->len = + usbredir_handle_status(dev, alt_setting_status->status, len); + usb_generic_async_ctrl_complete(&dev->dev, aurb->packet); + } + async_free(dev, aurb); +} + +static void usbredir_iso_stream_status(void *priv, uint32_t id, + struct usb_redir_iso_stream_status_header *iso_stream_status) +{ + USBRedirDevice *dev = priv; + uint8_t ep = iso_stream_status->endpoint; + + DPRINTF("iso status %d ep %02X id %u\n", iso_stream_status->status, + ep, id); + + dev->endpoint[EP2I(ep)].iso_error = iso_stream_status->status; + if (iso_stream_status->status == usb_redir_stall) { + DPRINTF("iso stream stopped by peer ep %02X\n", ep); + dev->endpoint[EP2I(ep)].iso_started = 0; + } +} + +static void usbredir_interrupt_receiving_status(void *priv, uint32_t id, + struct usb_redir_interrupt_receiving_status_header + *interrupt_receiving_status) +{ + USBRedirDevice *dev = priv; + uint8_t ep = interrupt_receiving_status->endpoint; + + DPRINTF("interrupt recv status %d ep %02X id %u\n", + interrupt_receiving_status->status, ep, id); + + dev->endpoint[EP2I(ep)].interrupt_error = + interrupt_receiving_status->status; + if (interrupt_receiving_status->status == usb_redir_stall) { + DPRINTF("interrupt receiving stopped by peer ep %02X\n", ep); + dev->endpoint[EP2I(ep)].interrupt_started = 0; + } +} + +static void usbredir_bulk_streams_status(void *priv, uint32_t id, + struct usb_redir_bulk_streams_status_header *bulk_streams_status) +{ +} + +static void usbredir_control_packet(void *priv, uint32_t id, + struct usb_redir_control_packet_header *control_packet, + uint8_t *data, int data_len) +{ + USBRedirDevice *dev = priv; + int len = control_packet->length; + AsyncURB *aurb; + + DPRINTF("ctrl-in status %d len %d id %u\n", control_packet->status, + len, id); + + aurb = async_find(dev, id); + if (!aurb) { + free(data); + return; + } + + aurb->control_packet.status = control_packet->status; + aurb->control_packet.length = control_packet->length; + if (memcmp(&aurb->control_packet, control_packet, + sizeof(*control_packet))) { + ERROR("return control packet mismatch, please report this!\n"); + len = USB_RET_NAK; + } + + if (aurb->packet) { + len = usbredir_handle_status(dev, control_packet->status, len); + if (len > 0) { + usbredir_log_data(dev, "ctrl data in:", data, data_len); + if (data_len <= sizeof(dev->dev.data_buf)) { + memcpy(dev->dev.data_buf, data, data_len); + } else { + ERROR("ctrl buffer too small (%d > %zu)\n", + data_len, sizeof(dev->dev.data_buf)); + len = USB_RET_STALL; + } + } + aurb->packet->len = len; + usb_generic_async_ctrl_complete(&dev->dev, aurb->packet); + } + async_free(dev, aurb); + free(data); +} + +static void usbredir_bulk_packet(void *priv, uint32_t id, + struct usb_redir_bulk_packet_header *bulk_packet, + uint8_t *data, int data_len) +{ + USBRedirDevice *dev = priv; + uint8_t ep = bulk_packet->endpoint; + int len = bulk_packet->length; + AsyncURB *aurb; + + DPRINTF("bulk-in status %d ep %02X len %d id %u\n", bulk_packet->status, + ep, len, id); + + aurb = async_find(dev, id); + if (!aurb) { + free(data); + return; + } + + if (aurb->bulk_packet.endpoint != bulk_packet->endpoint || + aurb->bulk_packet.stream_id != bulk_packet->stream_id) { + ERROR("return bulk packet mismatch, please report this!\n"); + len = USB_RET_NAK; + } + + if (aurb->packet) { + len = usbredir_handle_status(dev, bulk_packet->status, len); + if (len > 0) { + usbredir_log_data(dev, "bulk data in:", data, data_len); + if (data_len <= aurb->packet->len) { + memcpy(aurb->packet->data, data, data_len); + } else { + ERROR("bulk buffer too small (%d > %d)\n", data_len, + aurb->packet->len); + len = USB_RET_STALL; + } + } + aurb->packet->len = len; + usb_packet_complete(&dev->dev, aurb->packet); + } + async_free(dev, aurb); + free(data); +} + +static void usbredir_iso_packet(void *priv, uint32_t id, + struct usb_redir_iso_packet_header *iso_packet, + uint8_t *data, int data_len) +{ + USBRedirDevice *dev = priv; + uint8_t ep = iso_packet->endpoint; + + DPRINTF2("iso-in status %d ep %02X len %d id %u\n", iso_packet->status, ep, + data_len, id); + + if (dev->endpoint[EP2I(ep)].type != USB_ENDPOINT_XFER_ISOC) { + ERROR("received iso packet for non iso endpoint %02X\n", ep); + free(data); + return; + } + + if (dev->endpoint[EP2I(ep)].iso_started == 0) { + DPRINTF("received iso packet for non started stream ep %02X\n", ep); + free(data); + return; + } + + /* bufp_alloc also adds the packet to the ep queue */ + bufp_alloc(dev, data, data_len, iso_packet->status, ep); +} + +static void usbredir_interrupt_packet(void *priv, uint32_t id, + struct usb_redir_interrupt_packet_header *interrupt_packet, + uint8_t *data, int data_len) +{ + USBRedirDevice *dev = priv; + uint8_t ep = interrupt_packet->endpoint; + + DPRINTF("interrupt-in status %d ep %02X len %d id %u\n", + interrupt_packet->status, ep, data_len, id); + + if (dev->endpoint[EP2I(ep)].type != USB_ENDPOINT_XFER_INT) { + ERROR("received int packet for non interrupt endpoint %02X\n", ep); + free(data); + return; + } + + if (ep & USB_DIR_IN) { + if (dev->endpoint[EP2I(ep)].interrupt_started == 0) { + DPRINTF("received int packet while not started ep %02X\n", ep); + free(data); + return; + } + + /* bufp_alloc also adds the packet to the ep queue */ + bufp_alloc(dev, data, data_len, interrupt_packet->status, ep); + } else { + int len = interrupt_packet->length; + + AsyncURB *aurb = async_find(dev, id); + if (!aurb) { + return; + } + + if (aurb->interrupt_packet.endpoint != interrupt_packet->endpoint) { + ERROR("return int packet mismatch, please report this!\n"); + len = USB_RET_NAK; + } + + if (aurb->packet) { + aurb->packet->len = usbredir_handle_status(dev, + interrupt_packet->status, len); + usb_packet_complete(&dev->dev, aurb->packet); + } + async_free(dev, aurb); + } +} + +static struct USBDeviceInfo usbredir_dev_info = { + .product_desc = "USB Redirection Device", + .qdev.name = "usb-redir", + .qdev.size = sizeof(USBRedirDevice), + .init = usbredir_initfn, + .handle_destroy = usbredir_handle_destroy, + .handle_packet = usb_generic_handle_packet, + .cancel_packet = usbredir_cancel_packet, + .handle_reset = usbredir_handle_reset, + .handle_data = usbredir_handle_data, + .handle_control = usbredir_handle_control, + .qdev.props = (Property[]) { + DEFINE_PROP_CHR("chardev", USBRedirDevice, cs), + DEFINE_PROP_UINT8("debug", USBRedirDevice, debug, 0), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static void usbredir_register_devices(void) +{ + usb_qdev_register(&usbredir_dev_info); +} +device_init(usbredir_register_devices); |