diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2014-08-18 11:59:26 +0100 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2014-08-18 11:59:27 +0100 |
commit | da398fcc256b226217c92c8a83abf3a6ff247e8b (patch) | |
tree | e9d8ec4f6ff1b43933644fa8d5b422c05f46380e | |
parent | 142f4ac5d5e024670ef4725e8943702b027e4218 (diff) | |
parent | 39ba3bf69c4ef4d8a8b683ee7282efd25b3f01ff (diff) |
Merge remote-tracking branch 'remotes/stefanha/tags/block-pull-request' into staging
Block pull request
# gpg: Signature made Fri 15 Aug 2014 18:04:23 BST using RSA key ID 81AB73C8
# gpg: Good signature from "Stefan Hajnoczi <stefanha@redhat.com>"
# gpg: aka "Stefan Hajnoczi <stefanha@gmail.com>"
* remotes/stefanha/tags/block-pull-request: (55 commits)
qcow2: fix new_blocks double-free in alloc_refcount_block()
image-fuzzer: Reduce number of generator functions in __init__
image-fuzzer: Add generators of L1/L2 tables
image-fuzzer: Add fuzzing functions for L1/L2 table entries
docs: Expand the list of supported image elements with L1/L2 tables
image-fuzzer: Public API for image-fuzzer/runner/runner.py
image-fuzzer: Generator of fuzzed qcow2 images
image-fuzzer: Fuzzing functions for qcow2 images
image-fuzzer: Tool for fuzz tests execution
docs: Specification for the image fuzzer
ide: only constrain read/write requests to drive size, not other types
virtio-blk: Correct bug in support for flexible descriptor layout
libqos: Change free function called in malloc
libqos: Correct mask to align size to PAGE_SIZE in malloc-pc
libqtest: add QTEST_LOG for debugging qtest testcases
ide: Fix segfault when flushing a device that doesn't exist
qemu-options: add missing -drive discard option to cmdline help
parallels: 2TB+ parallels images support
parallels: split check for parallels format in parallels_open
parallels: replace tabs with spaces in block/parallels.c
...
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
33 files changed, 1991 insertions, 238 deletions
diff --git a/block/blkdebug.c b/block/blkdebug.c index f51407de3f..1586ed9664 100644 --- a/block/blkdebug.c +++ b/block/blkdebug.c @@ -522,6 +522,25 @@ static BlockDriverAIOCB *blkdebug_aio_writev(BlockDriverState *bs, return bdrv_aio_writev(bs->file, sector_num, qiov, nb_sectors, cb, opaque); } +static BlockDriverAIOCB *blkdebug_aio_flush(BlockDriverState *bs, + BlockDriverCompletionFunc *cb, void *opaque) +{ + BDRVBlkdebugState *s = bs->opaque; + BlkdebugRule *rule = NULL; + + QSIMPLEQ_FOREACH(rule, &s->active_rules, active_next) { + if (rule->options.inject.sector == -1) { + break; + } + } + + if (rule && rule->options.inject.error) { + return inject_error(bs, cb, opaque, rule); + } + + return bdrv_aio_flush(bs->file, cb, opaque); +} + static void blkdebug_close(BlockDriverState *bs) { @@ -699,6 +718,7 @@ static BlockDriver bdrv_blkdebug = { .bdrv_aio_readv = blkdebug_aio_readv, .bdrv_aio_writev = blkdebug_aio_writev, + .bdrv_aio_flush = blkdebug_aio_flush, .bdrv_debug_event = blkdebug_debug_event, .bdrv_debug_breakpoint = blkdebug_debug_breakpoint, diff --git a/block/parallels.c b/block/parallels.c index 7325678a4d..1774ab8e8e 100644 --- a/block/parallels.c +++ b/block/parallels.c @@ -30,6 +30,7 @@ /**************************************************************/ #define HEADER_MAGIC "WithoutFreeSpace" +#define HEADER_MAGIC2 "WithouFreSpacExt" #define HEADER_VERSION 2 #define HEADER_SIZE 64 @@ -41,8 +42,10 @@ struct parallels_header { uint32_t cylinders; uint32_t tracks; uint32_t catalog_entries; - uint32_t nb_sectors; - char padding[24]; + uint64_t nb_sectors; + uint32_t inuse; + uint32_t data_off; + char padding[12]; } QEMU_PACKED; typedef struct BDRVParallelsState { @@ -52,6 +55,8 @@ typedef struct BDRVParallelsState { unsigned int catalog_size; unsigned int tracks; + + unsigned int off_multiplier; } BDRVParallelsState; static int parallels_probe(const uint8_t *buf, int buf_size, const char *filename) @@ -59,11 +64,12 @@ static int parallels_probe(const uint8_t *buf, int buf_size, const char *filenam const struct parallels_header *ph = (const void *)buf; if (buf_size < HEADER_SIZE) - return 0; + return 0; - if (!memcmp(ph->magic, HEADER_MAGIC, 16) && - (le32_to_cpu(ph->version) == HEADER_VERSION)) - return 100; + if ((!memcmp(ph->magic, HEADER_MAGIC, 16) || + !memcmp(ph->magic, HEADER_MAGIC2, 16)) && + (le32_to_cpu(ph->version) == HEADER_VERSION)) + return 100; return 0; } @@ -83,14 +89,19 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags, goto fail; } - if (memcmp(ph.magic, HEADER_MAGIC, 16) || - (le32_to_cpu(ph.version) != HEADER_VERSION)) { - error_setg(errp, "Image not in Parallels format"); - ret = -EINVAL; - goto fail; - } + bs->total_sectors = le64_to_cpu(ph.nb_sectors); - bs->total_sectors = le32_to_cpu(ph.nb_sectors); + if (le32_to_cpu(ph.version) != HEADER_VERSION) { + goto fail_format; + } + if (!memcmp(ph.magic, HEADER_MAGIC, 16)) { + s->off_multiplier = 1; + bs->total_sectors = 0xffffffff & bs->total_sectors; + } else if (!memcmp(ph.magic, HEADER_MAGIC2, 16)) { + s->off_multiplier = le32_to_cpu(ph.tracks); + } else { + goto fail_format; + } s->tracks = le32_to_cpu(ph.tracks); if (s->tracks == 0) { @@ -98,6 +109,11 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags, ret = -EINVAL; goto fail; } + if (s->tracks > INT32_MAX/513) { + error_setg(errp, "Invalid image: Too big cluster"); + ret = -EFBIG; + goto fail; + } s->catalog_size = le32_to_cpu(ph.catalog_entries); if (s->catalog_size > INT_MAX / 4) { @@ -117,11 +133,14 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags, } for (i = 0; i < s->catalog_size; i++) - le32_to_cpus(&s->catalog_bitmap[i]); + le32_to_cpus(&s->catalog_bitmap[i]); qemu_co_mutex_init(&s->lock); return 0; +fail_format: + error_setg(errp, "Image not in Parallels format"); + ret = -EINVAL; fail: g_free(s->catalog_bitmap); return ret; @@ -137,8 +156,9 @@ static int64_t seek_to_sector(BlockDriverState *bs, int64_t sector_num) /* not allocated */ if ((index > s->catalog_size) || (s->catalog_bitmap[index] == 0)) - return -1; - return (uint64_t)(s->catalog_bitmap[index] + offset) * 512; + return -1; + return + ((uint64_t)s->catalog_bitmap[index] * s->off_multiplier + offset) * 512; } static int parallels_read(BlockDriverState *bs, int64_t sector_num, diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c index d60e2feb10..3b7747048e 100644 --- a/block/qcow2-refcount.c +++ b/block/qcow2-refcount.c @@ -381,6 +381,7 @@ static int alloc_refcount_block(BlockDriverState *bs, ret = bdrv_pwrite_sync(bs->file, meta_offset, new_blocks, blocks_clusters * s->cluster_size); g_free(new_blocks); + new_blocks = NULL; if (ret < 0) { goto fail_table; } diff --git a/docs/image-fuzzer.txt b/docs/image-fuzzer.txt new file mode 100644 index 0000000000..0d0005d34a --- /dev/null +++ b/docs/image-fuzzer.txt @@ -0,0 +1,238 @@ +# Specification for the fuzz testing tool +# +# Copyright (C) 2014 Maria Kustova <maria.k@catit.be> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +Image fuzzer +============ + +Description +----------- + +The goal of the image fuzzer is to catch crashes of qemu-io/qemu-img +by providing to them randomly corrupted images. +Test images are generated from scratch and have valid inner structure with some +elements, e.g. L1/L2 tables, having random invalid values. + + +Test runner +----------- + +The test runner generates test images, executes tests utilizing generated +images, indicates their results and collects all test related artifacts (logs, +core dumps, test images, backing files). +The test means execution of all available commands under test with the same +generated test image. +By default, the test runner generates new tests and executes them until +keyboard interruption. But if a test seed is specified via the '--seed' runner +parameter, then only one test with this seed will be executed, after its finish +the runner will exit. + +The runner uses an external image fuzzer to generate test images. An image +generator should be specified as a mandatory parameter of the test runner. +Details about interactions between the runner and fuzzers see "Module +interfaces". + +The runner activates generation of core dumps during test executions, but it +assumes that core dumps will be generated in the current working directory. +For comprehensive test results, please, set up your test environment +properly. + +Paths to binaries under test (SUTs) qemu-img and qemu-io are retrieved from +environment variables. If the environment check fails the runner will +use SUTs installed in system paths. +qemu-img is required for creation of backing files, so it's mandatory to set +the related environment variable if it's not installed in the system path. +For details about environment variables see qemu-iotests/check. + +The runner accepts a JSON array of fields expected to be fuzzed via the +'--config' argument, e.g. + + '[["feature_name_table"], ["header", "l1_table_offset"]]' + +Each sublist can have one or two strings defining image structure elements. +In the latter case a parent element should be placed on the first position, +and a field name on the second one. + +The runner accepts a list of commands under test as a JSON array via +the '--command' argument. Each command is a list containing a SUT and all its +arguments, e.g. + + runner.py -c '[["qemu-io", "$test_img", "-c", "write $off $len"]]' + /tmp/test ../qcow2 + +For variable arguments next aliases can be used: + - $test_img for a fuzzed img + - $off for an offset in the fuzzed image + - $len for a data size + +Values for last two aliases will be generated based on a size of a virtual +disk of the generated image. +In case when no commands are specified the runner will execute commands from +the default list: + - qemu-img check + - qemu-img info + - qemu-img convert + - qemu-io -c read + - qemu-io -c write + - qemu-io -c aio_read + - qemu-io -c aio_write + - qemu-io -c flush + - qemu-io -c discard + - qemu-io -c truncate + + +Qcow2 image generator +--------------------- + +The 'qcow2' generator is a Python package providing 'create_image' method as +a single public API. See details in 'Test runner/image fuzzer' chapter of +'Module interfaces'. + +Qcow2 contains two submodules: fuzz.py and layout.py. + +'fuzz.py' contains all fuzzing functions, one per image field. It's assumed +that after code analysis every field will have own constraints for its value. +For now only universal potentially dangerous values are used, e.g. type limits +for integers or unsafe symbols as '%s' for strings. For bitmasks random amount +of bits are set to ones. All fuzzed values are checked on non-equality to the +current valid value of the field. In case of equality the value will be +regenerated. + +'layout.py' creates a random valid image, fuzzes a random subset of the image +fields by 'fuzz.py' module and writes a fuzzed image to the file specified. +If a fuzzer configuration is specified, then it has the next interpretation: + + 1. If a list contains a parent image element only, then some random portion + of fields of this element will be fuzzed every test. + The same behavior is applied for the entire image if no configuration is + used. This case is useful for the test specialization. + + 2. If a list contains a parent element and a field name, then a field + will be always fuzzed for every test. This case is useful for regression + testing. + +For now only header fields, header extensions and L1/L2 tables are generated. + +Module interfaces +----------------- + +* Test runner/image fuzzer + +The runner calls an image generator specifying the path to a test image file, +path to a backing file and its format and a fuzzer configuration. +An image generator is expected to provide a + + 'create_image(test_img_path, backing_file_path=None, + backing_file_format=None, fuzz_config=None)' + +method that creates a test image, writes it to the specified file and returns +the size of the virtual disk. +The file should be created if it doesn't exist or overwritten otherwise. +fuzz_config has a form of a list of lists. Every sublist can have one +or two elements: first element is a name of a parent image element, second one +if exists is a name of a field in this element. +Example, + [['header', 'l1_table_offset'], + ['header', 'nb_snapshots'], + ['feature_name_table']] + +Random seed is set by the runner at every test execution for the regression +purpose, so an image generator is not recommended to modify it internally. + + +Overall fuzzer requirements +=========================== + +Input data: +---------- + + - image template (generator) + - work directory + - action vector (optional) + - seed (optional) + - SUT and its arguments (optional) + + +Fuzzer requirements: +------------------- + +1. Should be able to inject random data +2. Should be able to select a random value from the manually pregenerated + vector (boundary values, e.g. max/min cluster size) +3. Image template should describe a general structure invariant for all + test images (image format description) +4. Image template should be autonomous and other fuzzer parts should not + rely on it +5. Image template should contain reference rules (not only block+size + description) +6. Should generate the test image with the correct structure based on an image + template +7. Should accept a seed as an argument (for regression purpose) +8. Should generate a seed if it is not specified as an input parameter. +9. The same seed should generate the same image for the same action vector, + specified or generated. +10. Should accept a vector of actions as an argument (for test reproducing and + for test case specification, e.g. group of tests for header structure, + group of test for snapshots, etc) +11. Action vector should be randomly generated from the pool of available + actions, if it is not specified as an input parameter +12. Pool of actions should be defined automatically based on an image template +13. Should accept a SUT and its call parameters as an argument or select them + randomly otherwise. As far as it's expected to be rarely changed, the list + of all possible test commands can be available in the test runner + internally. +14. Should support an external cancellation of a test run +15. Seed should be logged (for regression purpose) +16. All files related to a test result should be collected: a test image, + SUT logs, fuzzer logs and crash dumps +17. Should be compatible with python version 2.4-2.7 +18. Usage of external libraries should be limited as much as possible. + + +Image formats: +------------- + +Main target image format is qcow2, but support of image templates should +provide an ability to add any other image format. + + +Effectiveness: +------------- + +The fuzzer can be controlled via template, seed and action vector; +it makes the fuzzer itself invariant to an image format and test logic. +It should be able to perform rather complex and precise tests, that can be +specified via an action vector. Otherwise, knowledge about an image structure +allows the fuzzer to generate the pool of all available areas can be fuzzed +and randomly select some of them and so compose its own action vector. +Also complexity of a template defines complexity of the fuzzer, so its +functionality can be varied from simple model-independent fuzzing to smart +model-based one. + + +Glossary: +-------- + +Action vector is a sequence of structure elements retrieved from an image +format, each of them will be fuzzed for the test image. It's a subset of +elements of the action pool. Example: header, refcount table, etc. +Action pool is all available elements of an image structure that generated +automatically from an image template. +Image template is a formal description of an image structure and relations +between image blocks. +Test image is an output image of the fuzzer defined by the current seed and +action vector. diff --git a/hw/block/dataplane/virtio-blk.c b/hw/block/dataplane/virtio-blk.c index d6ba65ca23..24a6b71395 100644 --- a/hw/block/dataplane/virtio-blk.c +++ b/hw/block/dataplane/virtio-blk.c @@ -28,6 +28,7 @@ struct VirtIOBlockDataPlane { bool started; bool starting; bool stopping; + bool disabled; VirtIOBlkConf *blk; @@ -218,8 +219,9 @@ void virtio_blk_data_plane_start(VirtIOBlockDataPlane *s) VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); VirtIOBlock *vblk = VIRTIO_BLK(s->vdev); VirtQueue *vq; + int r; - if (s->started) { + if (s->started || s->disabled) { return; } @@ -231,22 +233,23 @@ void virtio_blk_data_plane_start(VirtIOBlockDataPlane *s) vq = virtio_get_queue(s->vdev, 0); if (!vring_setup(&s->vring, s->vdev, 0)) { - s->starting = false; - return; + goto fail_vring; } /* Set up guest notifier (irq) */ - if (k->set_guest_notifiers(qbus->parent, 1, true) != 0) { - fprintf(stderr, "virtio-blk failed to set guest notifier, " - "ensure -enable-kvm is set\n"); - exit(1); + r = k->set_guest_notifiers(qbus->parent, 1, true); + if (r != 0) { + fprintf(stderr, "virtio-blk failed to set guest notifier (%d), " + "ensure -enable-kvm is set\n", r); + goto fail_guest_notifiers; } s->guest_notifier = virtio_queue_get_guest_notifier(vq); /* Set up virtqueue notify */ - if (k->set_host_notifier(qbus->parent, 0, true) != 0) { - fprintf(stderr, "virtio-blk failed to set host notifier\n"); - exit(1); + r = k->set_host_notifier(qbus->parent, 0, true); + if (r != 0) { + fprintf(stderr, "virtio-blk failed to set host notifier (%d)\n", r); + goto fail_host_notifier; } s->host_notifier = *virtio_queue_get_host_notifier(vq); @@ -266,6 +269,15 @@ void virtio_blk_data_plane_start(VirtIOBlockDataPlane *s) aio_context_acquire(s->ctx); aio_set_event_notifier(s->ctx, &s->host_notifier, handle_notify); aio_context_release(s->ctx); + return; + + fail_host_notifier: + k->set_guest_notifiers(qbus->parent, 1, false); + fail_guest_notifiers: + vring_teardown(&s->vring, s->vdev, 0); + s->disabled = true; + fail_vring: + s->starting = false; } /* Context: QEMU global mutex held */ @@ -274,6 +286,13 @@ void virtio_blk_data_plane_stop(VirtIOBlockDataPlane *s) BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s->vdev))); VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); VirtIOBlock *vblk = VIRTIO_BLK(s->vdev); + + + /* Better luck next time. */ + if (s->disabled) { + s->disabled = false; + return; + } if (!s->started || s->stopping) { return; } diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c index c241c5002b..302c39e2be 100644 --- a/hw/block/virtio-blk.c +++ b/hw/block/virtio-blk.c @@ -404,19 +404,19 @@ void virtio_blk_handle_request(VirtIOBlockReq *req, MultiReqBuffer *mrb) * NB: per existing s/n string convention the string is * terminated by '\0' only when shorter than buffer. */ - strncpy(req->elem.in_sg[0].iov_base, - s->blk.serial ? s->blk.serial : "", - MIN(req->elem.in_sg[0].iov_len, VIRTIO_BLK_ID_BYTES)); + const char *serial = s->blk.serial ? s->blk.serial : ""; + size_t size = MIN(strlen(serial) + 1, + MIN(iov_size(in_iov, in_num), + VIRTIO_BLK_ID_BYTES)); + iov_from_buf(in_iov, in_num, 0, serial, size); virtio_blk_req_complete(req, VIRTIO_BLK_S_OK); virtio_blk_free_request(req); } else if (type & VIRTIO_BLK_T_OUT) { - qemu_iovec_init_external(&req->qiov, &req->elem.out_sg[1], - req->elem.out_num - 1); + qemu_iovec_init_external(&req->qiov, iov, out_num); virtio_blk_handle_write(req, mrb); } else if (type == VIRTIO_BLK_T_IN || type == VIRTIO_BLK_T_BARRIER) { /* VIRTIO_BLK_T_IN is 0, so we can't just & it. */ - qemu_iovec_init_external(&req->qiov, &req->elem.in_sg[0], - req->elem.in_num - 1); + qemu_iovec_init_external(&req->qiov, in_iov, in_num); virtio_blk_handle_read(req); } else { virtio_blk_req_complete(req, VIRTIO_BLK_S_UNSUPP); diff --git a/hw/i386/pc_q35.c b/hw/i386/pc_q35.c index 43350d7bcc..4b5a27404a 100644 --- a/hw/i386/pc_q35.c +++ b/hw/i386/pc_q35.c @@ -234,7 +234,7 @@ static void pc_q35_init(MachineState *machine) gsi_state->i8259_irq[i] = i8259[i]; } if (pci_enabled) { - ioapic_init_gsi(gsi_state, NULL); + ioapic_init_gsi(gsi_state, "q35"); } qdev_init_nofail(icc_bridge); diff --git a/hw/ide/ahci.c b/hw/ide/ahci.c index 604152a823..4cda0d0075 100644 --- a/hw/ide/ahci.c +++ b/hw/ide/ahci.c @@ -584,7 +584,72 @@ static void ahci_write_fis_sdb(AHCIState *s, int port, uint32_t finished) s->dev[port].finished |= finished; *(uint32_t*)(sdb_fis + 4) = cpu_to_le32(s->dev[port].finished); - ahci_trigger_irq(s, &s->dev[port], PORT_IRQ_STAT_SDBS); + ahci_trigger_irq(s, &s->dev[port], PORT_IRQ_SDB_FIS); +} + +static void ahci_write_fis_pio(AHCIDevice *ad, uint16_t len) +{ + AHCIPortRegs *pr = &ad->port_regs; + uint8_t *pio_fis, *cmd_fis; + uint64_t tbl_addr; + dma_addr_t cmd_len = 0x80; + + if (!ad->res_fis || !(pr->cmd & PORT_CMD_FIS_RX)) { + return; + } + + /* map cmd_fis */ + tbl_addr = le64_to_cpu(ad->cur_cmd->tbl_addr); + cmd_fis = dma_memory_map(ad->hba->as, tbl_addr, &cmd_len, + DMA_DIRECTION_TO_DEVICE); + + if (cmd_fis == NULL) { + DPRINTF(ad->port_no, "dma_memory_map failed in ahci_write_fis_pio"); + ahci_trigger_irq(ad->hba, ad, PORT_IRQ_HBUS_ERR); + return; + } + + if (cmd_len != 0x80) { + DPRINTF(ad->port_no, + "dma_memory_map mapped too few bytes in ahci_write_fis_pio"); + dma_memory_unmap(ad->hba->as, cmd_fis, cmd_len, + DMA_DIRECTION_TO_DEVICE, cmd_len); + ahci_trigger_irq(ad->hba, ad, PORT_IRQ_HBUS_ERR); + return; + } + + pio_fis = &ad->res_fis[RES_FIS_PSFIS]; + + pio_fis[0] = 0x5f; + pio_fis[1] = (ad->hba->control_regs.irqstatus ? (1 << 6) : 0); + pio_fis[2] = ad->port.ifs[0].status; + pio_fis[3] = ad->port.ifs[0].error; + + pio_fis[4] = cmd_fis[4]; + pio_fis[5] = cmd_fis[5]; + pio_fis[6] = cmd_fis[6]; + pio_fis[7] = cmd_fis[7]; + pio_fis[8] = cmd_fis[8]; + pio_fis[9] = cmd_fis[9]; + pio_fis[10] = cmd_fis[10]; + pio_fis[11] = cmd_fis[11]; + pio_fis[12] = cmd_fis[12]; + pio_fis[13] = cmd_fis[13]; + pio_fis[14] = 0; + pio_fis[15] = ad->port.ifs[0].status; + pio_fis[16] = len & 255; + pio_fis[17] = len >> 8; + pio_fis[18] = 0; + pio_fis[19] = 0; + + if (pio_fis[2] & ERR_STAT) { + ahci_trigger_irq(ad->hba, ad, PORT_IRQ_TF_ERR); + } + + ahci_trigger_irq(ad->hba, ad, PORT_IRQ_PIOS_FIS); + + dma_memory_unmap(ad->hba->as, cmd_fis, cmd_len, + DMA_DIRECTION_TO_DEVICE, cmd_len); } static void ahci_write_fis_d2h(AHCIDevice *ad, uint8_t *cmd_fis) @@ -629,7 +694,7 @@ static void ahci_write_fis_d2h(AHCIDevice *ad, uint8_t *cmd_fis) } if (d2h_fis[2] & ERR_STAT) { - ahci_trigger_irq(ad->hba, ad, PORT_IRQ_STAT_TFES); + ahci_trigger_irq(ad->hba, ad, PORT_IRQ_TF_ERR); } ahci_trigger_irq(ad->hba, ad, PORT_IRQ_D2H_REG_FIS); @@ -969,11 +1034,6 @@ static int handle_cmd(AHCIState *s, int port, int slot) /* We're ready to process the command in FIS byte 2. */ ide_exec_cmd(&s->dev[port].port, cmd_fis[2]); - - if ((s->dev[port].port.ifs[0].status & (READY_STAT|DRQ_STAT|BUSY_STAT)) == - READY_STAT) { - ahci_write_fis_d2h(&s->dev[port], cmd_fis); - } } out: @@ -991,7 +1051,7 @@ out: } /* DMA dev <-> ram */ -static int ahci_start_transfer(IDEDMA *dma) +static void ahci_start_transfer(IDEDMA *dma) { AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma); IDEState *s = &ad->port.ifs[0]; @@ -1038,11 +1098,9 @@ out: s->end_transfer_func(s); if (!(s->status & DRQ_STAT)) { - /* done with DMA */ - ahci_trigger_irq(ad->hba, ad, PORT_IRQ_STAT_DSS); + /* done with PIO send/receive */ + ahci_write_fis_pio(ad, le32_to_cpu(ad->cur_cmd->status)); } - - return 0; } static void ahci_start_dma(IDEDMA *dma, IDEState *s, @@ -1104,28 +1162,11 @@ static int ahci_dma_set_unit(IDEDMA *dma, int unit) return 0; } -static int ahci_dma_add_status(IDEDMA *dma, int status) -{ - AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma); - DPRINTF(ad->port_no, "set status: %x\n", status); - - if (status & BM_STATUS_INT) { - ahci_trigger_irq(ad->hba, ad, PORT_IRQ_STAT_DSS); - } - - return 0; -} - -static int ahci_dma_set_inactive(IDEDMA *dma) -{ - return 0; -} - -static int ahci_async_cmd_done(IDEDMA *dma) +static void ahci_cmd_done(IDEDMA *dma) { AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma); - DPRINTF(ad->port_no, "async cmd done\n"); + DPRINTF(ad->port_no, "cmd done\n"); /* update d2h status */ ahci_write_fis_d2h(ad, NULL); @@ -1135,8 +1176,6 @@ static int ahci_async_cmd_done(IDEDMA *dma) ad->check_bh = qemu_bh_new(ahci_check_cmd_bh, ad); qemu_bh_schedule(ad->check_bh); } - - return 0; } static void ahci_irq_set(void *opaque, int n, int level) @@ -1147,22 +1186,14 @@ static void ahci_dma_restart_cb(void *opaque, int running, RunState state) { } -static int ahci_dma_reset(IDEDMA *dma) -{ - return 0; -} - static const IDEDMAOps ahci_dma_ops = { .start_dma = ahci_start_dma, .start_transfer = ahci_start_transfer, .prepare_buf = ahci_dma_prepare_buf, .rw_buf = ahci_dma_rw_buf, .set_unit = ahci_dma_set_unit, - .add_status = ahci_dma_add_status, - .set_inactive = ahci_dma_set_inactive, - .async_cmd_done = ahci_async_cmd_done, + .cmd_done = ahci_cmd_done, .restart_cb = ahci_dma_restart_cb, - .reset = ahci_dma_reset, }; void ahci_init(AHCIState *s, DeviceState *qdev, AddressSpace *as, int ports) diff --git a/hw/ide/ahci.h b/hw/ide/ahci.h index f418b30ce7..1543df7b7d 100644 --- a/hw/ide/ahci.h +++ b/hw/ide/ahci.h @@ -132,27 +132,6 @@ #define PORT_CMD_ICC_PARTIAL (0x2 << 28) /* Put i/f in partial state */ #define PORT_CMD_ICC_SLUMBER (0x6 << 28) /* Put i/f in slumber state */ -#define PORT_IRQ_STAT_DHRS (1 << 0) /* Device to Host Register FIS */ -#define PORT_IRQ_STAT_PSS (1 << 1) /* PIO Setup FIS */ -#define PORT_IRQ_STAT_DSS (1 << 2) /* DMA Setup FIS */ -#define PORT_IRQ_STAT_SDBS (1 << 3) /* Set Device Bits */ -#define PORT_IRQ_STAT_UFS (1 << 4) /* Unknown FIS */ -#define PORT_IRQ_STAT_DPS (1 << 5) /* Descriptor Processed */ -#define PORT_IRQ_STAT_PCS (1 << 6) /* Port Connect Change Status */ -#define PORT_IRQ_STAT_DMPS (1 << 7) /* Device Mechanical Presence - Status */ -#define PORT_IRQ_STAT_PRCS (1 << 22) /* File Ready Status */ -#define PORT_IRQ_STAT_IPMS (1 << 23) /* Incorrect Port Multiplier - Status */ -#define PORT_IRQ_STAT_OFS (1 << 24) /* Overflow Status */ -#define PORT_IRQ_STAT_INFS (1 << 26) /* Interface Non-Fatal Error - Status */ -#define PORT_IRQ_STAT_IFS (1 << 27) /* Interface Fatal Error */ -#define PORT_IRQ_STAT_HBDS (1 << 28) /* Host Bus Data Error Status */ -#define PORT_IRQ_STAT_HBFS (1 << 29) /* Host Bus Fatal Error Status */ -#define PORT_IRQ_STAT_TFES (1 << 30) /* Task File Error Status */ -#define PORT_IRQ_STAT_CPDS (1U << 31) /* Code Port Detect Status */ - /* ap->flags bits */ #define AHCI_FLAG_NO_NCQ (1 << 24) #define AHCI_FLAG_IGN_IRQ_IF_ERR (1 << 25) /* ignore IRQ_IF_ERR */ diff --git a/hw/ide/atapi.c b/hw/ide/atapi.c index f7d2009c00..3d92b52dbc 100644 --- a/hw/ide/atapi.c +++ b/hw/ide/atapi.c @@ -174,9 +174,9 @@ void ide_atapi_cmd_reply_end(IDEState *s) #endif if (s->packet_transfer_size <= 0) { /* end of transfer */ - ide_transfer_stop(s); s->status = READY_STAT | SEEK_STAT; s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD; + ide_transfer_stop(s); ide_set_irq(s->bus); #ifdef DEBUG_IDE_ATAPI printf("status=0x%x\n", s->status); @@ -255,8 +255,7 @@ static void ide_atapi_cmd_reply(IDEState *s, int size, int max_size) if (s->atapi_dma) { bdrv_acct_start(s->bs, &s->acct, size, BDRV_ACCT_READ); s->status = READY_STAT | SEEK_STAT | DRQ_STAT; - s->bus->dma->ops->start_dma(s->bus->dma, s, - ide_atapi_cmd_read_dma_cb); + ide_start_dma(s, ide_atapi_cmd_read_dma_cb); } else { s->status = READY_STAT | SEEK_STAT; ide_atapi_cmd_reply_end(s); @@ -356,8 +355,7 @@ static void ide_atapi_cmd_read_dma_cb(void *opaque, int ret) eot: bdrv_acct_done(s->bs, &s->acct); - s->bus->dma->ops->add_status(s->bus->dma, BM_STATUS_INT); - ide_set_inactive(s); + ide_set_inactive(s, false); } /* start a CD-CDROM read command with DMA */ @@ -375,8 +373,7 @@ static void ide_atapi_cmd_read_dma(IDEState *s, int lba, int nb_sectors, /* XXX: check if BUSY_STAT should be set */ s->status = READY_STAT | SEEK_STAT | DRQ_STAT | BUSY_STAT; - s->bus->dma->ops->start_dma(s->bus->dma, s, - ide_atapi_cmd_read_dma_cb); + ide_start_dma(s, ide_atapi_cmd_read_dma_cb); } static void ide_atapi_cmd_read(IDEState *s, int lba, int nb_sectors, diff --git a/hw/ide/cmd646.c b/hw/ide/cmd646.c index a8e35fe38f..74d0deb6dd 100644 --- a/hw/ide/cmd646.c +++ b/hw/ide/cmd646.c @@ -33,6 +33,13 @@ #include <hw/ide/pci.h> /* CMD646 specific */ +#define CFR 0x50 +#define CFR_INTR_CH0 0x04 +#define CNTRL 0x51 +#define CNTRL_EN_CH0 0x04 +#define CNTRL_EN_CH1 0x08 +#define ARTTIM23 0x57 +#define ARTTIM23_INTR_CH1 0x10 #define MRDMODE 0x71 #define MRDMODE_INTR_CH0 0x04 #define MRDMODE_INTR_CH1 0x08 @@ -41,7 +48,7 @@ #define UDIDETCR0 0x73 #define UDIDETCR1 0x7B -static void cmd646_update_irq(PCIIDEState *d); +static void cmd646_update_irq(PCIDevice *pd); static uint64_t cmd646_cmd_read(void *opaque, hwaddr addr, unsigned size) @@ -123,6 +130,38 @@ static void setup_cmd646_bar(PCIIDEState *d, int bus_num) "cmd646-data", 8); } +static void cmd646_update_dma_interrupts(PCIDevice *pd) +{ + /* Sync DMA interrupt status from UDMA interrupt status */ + if (pd->config[MRDMODE] & MRDMODE_INTR_CH0) { + pd->config[CFR] |= CFR_INTR_CH0; + } else { + pd->config[CFR] &= ~CFR_INTR_CH0; + } + + if (pd->config[MRDMODE] & MRDMODE_INTR_CH1) { + pd->config[ARTTIM23] |= ARTTIM23_INTR_CH1; + } else { + pd->config[ARTTIM23] &= ~ARTTIM23_INTR_CH1; + } +} + +static void cmd646_update_udma_interrupts(PCIDevice *pd) +{ + /* Sync UDMA interrupt status from DMA interrupt status */ + if (pd->config[CFR] & CFR_INTR_CH0) { + pd->config[MRDMODE] |= MRDMODE_INTR_CH0; + } else { + pd->config[MRDMODE] &= ~MRDMODE_INTR_CH0; + } + + if (pd->config[ARTTIM23] & ARTTIM23_INTR_CH1) { + pd->config[MRDMODE] |= MRDMODE_INTR_CH1; + } else { + pd->config[MRDMODE] &= ~MRDMODE_INTR_CH1; + } +} + static uint64_t bmdma_read(void *opaque, hwaddr addr, unsigned size) { @@ -181,7 +220,8 @@ static void bmdma_write(void *opaque, hwaddr addr, case 1: pci_dev->config[MRDMODE] = (pci_dev->config[MRDMODE] & ~0x30) | (val & 0x30); - cmd646_update_irq(bm->pci_dev); + cmd646_update_dma_interrupts(pci_dev); + cmd646_update_irq(pci_dev); break; case 2: bm->status = (val & 0x60) | (bm->status & 1) | (bm->status & ~val & 0x06); @@ -219,11 +259,8 @@ static void bmdma_setup_bar(PCIIDEState *d) } } -/* XXX: call it also when the MRDMODE is changed from the PCI config - registers */ -static void cmd646_update_irq(PCIIDEState *d) +static void cmd646_update_irq(PCIDevice *pd) { - PCIDevice *pd = PCI_DEVICE(d); int pci_level; pci_level = ((pd->config[MRDMODE] & MRDMODE_INTR_CH0) && @@ -246,7 +283,8 @@ static void cmd646_set_irq(void *opaque, int channel, int level) } else { pd->config[MRDMODE] &= ~irq_mask; } - cmd646_update_irq(d); + cmd646_update_dma_interrupts(pd); + cmd646_update_irq(pd); } static void cmd646_reset(void *opaque) @@ -259,6 +297,34 @@ static void cmd646_reset(void *opaque) } } +static uint32_t cmd646_pci_config_read(PCIDevice *d, + uint32_t address, int len) +{ + return pci_default_read_config(d, address, len); +} + +static void cmd646_pci_config_write(PCIDevice *d, uint32_t addr, uint32_t val, + int l) +{ + uint32_t i; + + pci_default_write_config(d, addr, val, l); + + for (i = addr; i < addr + l; i++) { + switch (i) { + case CFR: + case ARTTIM23: + cmd646_update_udma_interrupts(d); + break; + case MRDMODE: + cmd646_update_dma_interrupts(d); + break; + } + } + + cmd646_update_irq(d); +} + /* CMD646 PCI IDE controller */ static int pci_cmd646_ide_initfn(PCIDevice *dev) { @@ -269,12 +335,20 @@ static int pci_cmd646_ide_initfn(PCIDevice *dev) pci_conf[PCI_CLASS_PROG] = 0x8f; - pci_conf[0x51] = 0x04; // enable IDE0 + pci_conf[CNTRL] = CNTRL_EN_CH0; // enable IDE0 if (d->secondary) { /* XXX: if not enabled, really disable the seconday IDE controller */ - pci_conf[0x51] |= 0x08; /* enable IDE1 */ + pci_conf[CNTRL] |= CNTRL_EN_CH1; /* enable IDE1 */ } + /* Set write-to-clear interrupt bits */ + dev->wmask[CFR] = 0x0; + dev->w1cmask[CFR] = CFR_INTR_CH0; + dev->wmask[ARTTIM23] = 0x0; + dev->w1cmask[ARTTIM23] = ARTTIM23_INTR_CH1; + dev->wmask[MRDMODE] = 0x0; + dev->w1cmask[MRDMODE] = MRDMODE_INTR_CH0 | MRDMODE_INTR_CH1; + setup_cmd646_bar(d, 0); setup_cmd646_bar(d, 1); pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &d->cmd646_bar[0].data); @@ -347,6 +421,8 @@ static void cmd646_ide_class_init(ObjectClass *klass, void *data) k->device_id = PCI_DEVICE_ID_CMD_646; k->revision = 0x07; k->class_id = PCI_CLASS_STORAGE_IDE; + k->config_read = cmd646_pci_config_read; + k->config_write = cmd646_pci_config_write; dc->props = cmd646_ide_properties; } diff --git a/hw/ide/core.c b/hw/ide/core.c index db191a6c3e..b48127f921 100644 --- a/hw/ide/core.c +++ b/hw/ide/core.c @@ -420,6 +420,7 @@ BlockDriverAIOCB *ide_issue_trim(BlockDriverState *bs, static inline void ide_abort_command(IDEState *s) { + ide_transfer_stop(s); s->status = READY_STAT | ERR_STAT; s->error = ABRT_ERR; } @@ -434,7 +435,16 @@ void ide_transfer_start(IDEState *s, uint8_t *buf, int size, if (!(s->status & ERR_STAT)) { s->status |= DRQ_STAT; } - s->bus->dma->ops->start_transfer(s->bus->dma); + if (s->bus->dma->ops->start_transfer) { + s->bus->dma->ops->start_transfer(s->bus->dma); + } +} + +static void ide_cmd_done(IDEState *s) +{ + if (s->bus->dma->ops->cmd_done) { + s->bus->dma->ops->cmd_done(s->bus->dma); + } } void ide_transfer_stop(IDEState *s) @@ -443,6 +453,7 @@ void ide_transfer_stop(IDEState *s) s->data_ptr = s->io_buffer; s->data_end = s->io_buffer; s->status &= ~DRQ_STAT; + ide_cmd_done(s); } int64_t ide_get_sector(IDEState *s) @@ -521,8 +532,8 @@ static void ide_sector_read_cb(void *opaque, int ret) bdrv_acct_done(s->bs, &s->acct); if (ret != 0) { - if (ide_handle_rw_error(s, -ret, BM_STATUS_PIO_RETRY | - BM_STATUS_RETRY_READ)) { + if (ide_handle_rw_error(s, -ret, IDE_RETRY_PIO | + IDE_RETRY_READ)) { return; } } @@ -585,39 +596,32 @@ static void dma_buf_commit(IDEState *s) qemu_sglist_destroy(&s->sg); } -static void ide_async_cmd_done(IDEState *s) -{ - if (s->bus->dma->ops->async_cmd_done) { - s->bus->dma->ops->async_cmd_done(s->bus->dma); - } -} - -void ide_set_inactive(IDEState *s) +void ide_set_inactive(IDEState *s, bool more) { s->bus->dma->aiocb = NULL; - s->bus->dma->ops->set_inactive(s->bus->dma); - ide_async_cmd_done(s); + if (s->bus->dma->ops->set_inactive) { + s->bus->dma->ops->set_inactive(s->bus->dma, more); + } + ide_cmd_done(s); } void ide_dma_error(IDEState *s) { - ide_transfer_stop(s); - s->error = ABRT_ERR; - s->status = READY_STAT | ERR_STAT; - ide_set_inactive(s); + ide_abort_command(s); + ide_set_inactive(s, false); ide_set_irq(s->bus); } static int ide_handle_rw_error(IDEState *s, int error, int op) { - bool is_read = (op & BM_STATUS_RETRY_READ) != 0; + bool is_read = (op & IDE_RETRY_READ) != 0; BlockErrorAction action = bdrv_get_error_action(s->bs, is_read, error); if (action == BLOCK_ERROR_ACTION_STOP) { s->bus->dma->ops->set_unit(s->bus->dma, s->unit); s->bus->error_status = op; } else if (action == BLOCK_ERROR_ACTION_REPORT) { - if (op & BM_STATUS_DMA_RETRY) { + if (op & IDE_RETRY_DMA) { dma_buf_commit(s); ide_dma_error(s); } else { @@ -636,12 +640,12 @@ void ide_dma_cb(void *opaque, int ret) bool stay_active = false; if (ret < 0) { - int op = BM_STATUS_DMA_RETRY; + int op = IDE_RETRY_DMA; if (s->dma_cmd == IDE_DMA_READ) - op |= BM_STATUS_RETRY_READ; + op |= IDE_RETRY_READ; else if (s->dma_cmd == IDE_DMA_TRIM) - op |= BM_STATUS_RETRY_TRIM; + op |= IDE_RETRY_TRIM; if (ide_handle_rw_error(s, -ret, op)) { return; @@ -688,7 +692,8 @@ void ide_dma_cb(void *opaque, int ret) sector_num, n, s->dma_cmd); #endif - if (!ide_sect_range_ok(s, sector_num, n)) { + if ((s->dma_cmd == IDE_DMA_READ || s->dma_cmd == IDE_DMA_WRITE) && + !ide_sect_range_ok(s, sector_num, n)) { dma_buf_commit(s); ide_dma_error(s); return; @@ -715,10 +720,7 @@ eot: if (s->dma_cmd == IDE_DMA_READ || s->dma_cmd == IDE_DMA_WRITE) { bdrv_acct_done(s->bs, &s->acct); } - ide_set_inactive(s); - if (stay_active) { - s->bus->dma->ops->add_status(s->bus->dma, BM_STATUS_DMAING); - } + ide_set_inactive(s, stay_active); } static void ide_sector_start_dma(IDEState *s, enum ide_dma_cmd dma_cmd) @@ -741,7 +743,14 @@ static void ide_sector_start_dma(IDEState *s, enum ide_dma_cmd dma_cmd) break; } - s->bus->dma->ops->start_dma(s->bus->dma, s, ide_dma_cb); + ide_start_dma(s, ide_dma_cb); +} + +void ide_start_dma(IDEState *s, BlockDriverCompletionFunc *cb) +{ + if (s->bus->dma->ops->start_dma) { + s->bus->dma->ops->start_dma(s->bus->dma, s, cb); + } } static void ide_sector_write_timer_cb(void *opaque) @@ -761,7 +770,7 @@ static void ide_sector_write_cb(void *opaque, int ret) s->status &= ~BUSY_STAT; if (ret != 0) { - if (ide_handle_rw_error(s, -ret, BM_STATUS_PIO_RETRY)) { + if (ide_handle_rw_error(s, -ret, IDE_RETRY_PIO)) { return; } } @@ -831,16 +840,20 @@ static void ide_flush_cb(void *opaque, int ret) { IDEState *s = opaque; + s->pio_aiocb = NULL; + if (ret < 0) { /* XXX: What sector number to set here? */ - if (ide_handle_rw_error(s, -ret, BM_STATUS_RETRY_FLUSH)) { + if (ide_handle_rw_error(s, -ret, IDE_RETRY_FLUSH)) { return; } } - bdrv_acct_done(s->bs, &s->acct); + if (s->bs) { + bdrv_acct_done(s->bs, &s->acct); + } s->status = READY_STAT | SEEK_STAT; - ide_async_cmd_done(s); + ide_cmd_done(s); ide_set_irq(s->bus); } @@ -853,7 +866,7 @@ void ide_flush_cache(IDEState *s) s->status |= BUSY_STAT; bdrv_acct_start(s->bs, &s->acct, 0, BDRV_ACCT_FLUSH); - bdrv_aio_flush(s->bs, ide_flush_cb, s); + s->pio_aiocb = bdrv_aio_flush(s->bs, ide_flush_cb, s); } static void ide_cfata_metadata_inquiry(IDEState *s) @@ -1764,6 +1777,7 @@ void ide_exec_cmd(IDEBus *bus, uint32_t val) s->status |= SEEK_STAT; } + ide_cmd_done(s); ide_set_irq(s->bus); } } @@ -2086,7 +2100,9 @@ void ide_bus_reset(IDEBus *bus) } /* reset dma provider too */ - bus->dma->ops->reset(bus->dma); + if (bus->dma->ops->reset) { + bus->dma->ops->reset(bus->dma); + } } static bool ide_cd_is_tray_open(void *opaque) @@ -2196,16 +2212,6 @@ static void ide_init1(IDEBus *bus, int unit) ide_sector_write_timer_cb, s); } -static void ide_nop_start(IDEDMA *dma, IDEState *s, - BlockDriverCompletionFunc *cb) -{ -} - -static int ide_nop(IDEDMA *dma) -{ - return 0; -} - static int ide_nop_int(IDEDMA *dma, int x) { return 0; @@ -2216,15 +2222,10 @@ static void ide_nop_restart(void *opaque, int x, RunState y) } static const IDEDMAOps ide_dma_nop_ops = { - .start_dma = ide_nop_start, - .start_transfer = ide_nop, .prepare_buf = ide_nop_int, .rw_buf = ide_nop_int, .set_unit = ide_nop_int, - .add_status = ide_nop_int, - .set_inactive = ide_nop, .restart_cb = ide_nop_restart, - .reset = ide_nop, }; static IDEDMA ide_dma_nop = { @@ -2341,7 +2342,7 @@ static bool ide_drive_pio_state_needed(void *opaque) IDEState *s = opaque; return ((s->status & DRQ_STAT) != 0) - || (s->bus->error_status & BM_STATUS_PIO_RETRY); + || (s->bus->error_status & IDE_RETRY_PIO); } static bool ide_tray_state_needed(void *opaque) diff --git a/hw/ide/internal.h b/hw/ide/internal.h index 0567a522f5..5c19f79437 100644 --- a/hw/ide/internal.h +++ b/hw/ide/internal.h @@ -320,8 +320,9 @@ typedef enum { IDE_HD, IDE_CD, IDE_CFATA } IDEDriveKind; typedef void EndTransferFunc(IDEState *); typedef void DMAStartFunc(IDEDMA *, IDEState *, BlockDriverCompletionFunc *); -typedef int DMAFunc(IDEDMA *); +typedef void DMAVoidFunc(IDEDMA *); typedef int DMAIntFunc(IDEDMA *, int); +typedef void DMAStopFunc(IDEDMA *, bool); typedef void DMARestartFunc(void *, int, RunState); struct unreported_events { @@ -427,15 +428,14 @@ struct IDEState { struct IDEDMAOps { DMAStartFunc *start_dma; - DMAFunc *start_transfer; + DMAVoidFunc *start_transfer; DMAIntFunc *prepare_buf; DMAIntFunc *rw_buf; DMAIntFunc *set_unit; - DMAIntFunc *add_status; - DMAFunc *set_inactive; - DMAFunc *async_cmd_done; + DMAStopFunc *set_inactive; + DMAVoidFunc *cmd_done; DMARestartFunc *restart_cb; - DMAFunc *reset; + DMAVoidFunc *reset; }; struct IDEDMA { @@ -484,23 +484,12 @@ struct IDEDevice { uint64_t wwn; }; -#define BM_STATUS_DMAING 0x01 -#define BM_STATUS_ERROR 0x02 -#define BM_STATUS_INT 0x04 - -/* FIXME These are not status register bits */ -#define BM_STATUS_DMA_RETRY 0x08 -#define BM_STATUS_PIO_RETRY 0x10 -#define BM_STATUS_RETRY_READ 0x20 -#define BM_STATUS_RETRY_FLUSH 0x40 -#define BM_STATUS_RETRY_TRIM 0x80 - -#define BM_MIGRATION_COMPAT_STATUS_BITS \ - (BM_STATUS_DMA_RETRY | BM_STATUS_PIO_RETRY | \ - BM_STATUS_RETRY_READ | BM_STATUS_RETRY_FLUSH) - -#define BM_CMD_START 0x01 -#define BM_CMD_READ 0x08 +/* These are used for the error_status field of IDEBus */ +#define IDE_RETRY_DMA 0x08 +#define IDE_RETRY_PIO 0x10 +#define IDE_RETRY_READ 0x20 +#define IDE_RETRY_FLUSH 0x40 +#define IDE_RETRY_TRIM 0x80 static inline IDEState *idebus_active_if(IDEBus *bus) { @@ -532,6 +521,7 @@ void ide_bus_reset(IDEBus *bus); int64_t ide_get_sector(IDEState *s); void ide_set_sector(IDEState *s, int64_t sector_num); +void ide_start_dma(IDEState *s, BlockDriverCompletionFunc *cb); void ide_dma_error(IDEState *s); void ide_atapi_cmd_ok(IDEState *s); @@ -564,7 +554,7 @@ void ide_flush_cache(IDEState *s); void ide_transfer_start(IDEState *s, uint8_t *buf, int size, EndTransferFunc *end_transfer_func); void ide_transfer_stop(IDEState *s); -void ide_set_inactive(IDEState *s); +void ide_set_inactive(IDEState *s, bool more); BlockDriverAIOCB *ide_issue_trim(BlockDriverState *bs, int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, BlockDriverCompletionFunc *cb, void *opaque); diff --git a/hw/ide/macio.c b/hw/ide/macio.c index c14a1ddddb..b0c0d400d9 100644 --- a/hw/ide/macio.c +++ b/hw/ide/macio.c @@ -545,11 +545,6 @@ static void macio_ide_reset(DeviceState *dev) ide_bus_reset(&d->bus); } -static int ide_nop(IDEDMA *dma) -{ - return 0; -} - static int ide_nop_int(IDEDMA *dma, int x) { return 0; @@ -571,14 +566,10 @@ static void ide_dbdma_start(IDEDMA *dma, IDEState *s, static const IDEDMAOps dbdma_ops = { .start_dma = ide_dbdma_start, - .start_transfer = ide_nop, .prepare_buf = ide_nop_int, .rw_buf = ide_nop_int, .set_unit = ide_nop_int, - .add_status = ide_nop_int, - .set_inactive = ide_nop, .restart_cb = ide_nop_restart, - .reset = ide_nop, }; static void macio_ide_realizefn(DeviceState *dev, Error **errp) diff --git a/hw/ide/pci.c b/hw/ide/pci.c index 6257a21ed2..2397f355cc 100644 --- a/hw/ide/pci.c +++ b/hw/ide/pci.c @@ -33,6 +33,10 @@ #define BMDMA_PAGE_SIZE 4096 +#define BM_MIGRATION_COMPAT_STATUS_BITS \ + (IDE_RETRY_DMA | IDE_RETRY_PIO | \ + IDE_RETRY_READ | IDE_RETRY_FLUSH) + static void bmdma_start_dma(IDEDMA *dma, IDEState *s, BlockDriverCompletionFunc *dma_cb) { @@ -152,23 +156,17 @@ static int bmdma_set_unit(IDEDMA *dma, int unit) return 0; } -static int bmdma_add_status(IDEDMA *dma, int status) -{ - BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma); - bm->status |= status; - - return 0; -} - -static int bmdma_set_inactive(IDEDMA *dma) +static void bmdma_set_inactive(IDEDMA *dma, bool more) { BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma); - bm->status &= ~BM_STATUS_DMAING; bm->dma_cb = NULL; bm->unit = -1; - - return 0; + if (more) { + bm->status |= BM_STATUS_DMAING; + } else { + bm->status &= ~BM_STATUS_DMAING; + } } static void bmdma_restart_dma(BMDMAState *bm, enum ide_dma_cmd dma_cmd) @@ -200,7 +198,7 @@ static void bmdma_restart_bh(void *opaque) return; } - is_read = (bus->error_status & BM_STATUS_RETRY_READ) != 0; + is_read = (bus->error_status & IDE_RETRY_READ) != 0; /* The error status must be cleared before resubmitting the request: The * request may fail again, and this case can only be distinguished if the @@ -208,19 +206,19 @@ static void bmdma_restart_bh(void *opaque) error_status = bus->error_status; bus->error_status = 0; - if (error_status & BM_STATUS_DMA_RETRY) { - if (error_status & BM_STATUS_RETRY_TRIM) { + if (error_status & IDE_RETRY_DMA) { + if (error_status & IDE_RETRY_TRIM) { bmdma_restart_dma(bm, IDE_DMA_TRIM); } else { bmdma_restart_dma(bm, is_read ? IDE_DMA_READ : IDE_DMA_WRITE); } - } else if (error_status & BM_STATUS_PIO_RETRY) { + } else if (error_status & IDE_RETRY_PIO) { if (is_read) { ide_sector_read(bmdma_active_if(bm)); } else { ide_sector_write(bmdma_active_if(bm)); } - } else if (error_status & BM_STATUS_RETRY_FLUSH) { + } else if (error_status & IDE_RETRY_FLUSH) { ide_flush_cache(bmdma_active_if(bm)); } } @@ -243,11 +241,11 @@ static void bmdma_cancel(BMDMAState *bm) { if (bm->status & BM_STATUS_DMAING) { /* cancel DMA request */ - bmdma_set_inactive(&bm->dma); + bmdma_set_inactive(&bm->dma, false); } } -static int bmdma_reset(IDEDMA *dma) +static void bmdma_reset(IDEDMA *dma) { BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma); @@ -264,13 +262,6 @@ static int bmdma_reset(IDEDMA *dma) bm->cur_prd_len = 0; bm->sector_num = 0; bm->nsector = 0; - - return 0; -} - -static int bmdma_start_transfer(IDEDMA *dma) -{ - return 0; } static void bmdma_irq(void *opaque, int n, int level) @@ -504,11 +495,9 @@ void pci_ide_create_devs(PCIDevice *dev, DriveInfo **hd_table) static const struct IDEDMAOps bmdma_ops = { .start_dma = bmdma_start_dma, - .start_transfer = bmdma_start_transfer, .prepare_buf = bmdma_prepare_buf, .rw_buf = bmdma_rw_buf, .set_unit = bmdma_set_unit, - .add_status = bmdma_add_status, .set_inactive = bmdma_set_inactive, .restart_cb = bmdma_restart_cb, .reset = bmdma_reset, diff --git a/hw/ide/pci.h b/hw/ide/pci.h index 2428275c8d..517711f913 100644 --- a/hw/ide/pci.h +++ b/hw/ide/pci.h @@ -3,6 +3,13 @@ #include <hw/ide/internal.h> +#define BM_STATUS_DMAING 0x01 +#define BM_STATUS_ERROR 0x02 +#define BM_STATUS_INT 0x04 + +#define BM_CMD_START 0x01 +#define BM_CMD_READ 0x08 + typedef struct BMDMAState { IDEDMA dma; uint8_t cmd; diff --git a/qemu-char.c b/qemu-char.c index 3dcc39adf8..d4f327ab6d 100644 --- a/qemu-char.c +++ b/qemu-char.c @@ -975,7 +975,7 @@ static CharDriverState *qemu_chr_open_fd(int fd_in, int fd_out) s = g_malloc0(sizeof(FDCharDriver)); s->fd_in = io_channel_from_fd(fd_in); s->fd_out = io_channel_from_fd(fd_out); - fcntl(fd_out, F_SETFL, O_NONBLOCK); + qemu_set_nonblock(fd_out); s->chr = chr; chr->opaque = s; chr->chr_add_watch = fd_chr_add_watch; @@ -1062,7 +1062,7 @@ static CharDriverState *qemu_chr_open_stdio(ChardevStdio *opts) } old_fd0_flags = fcntl(0, F_GETFL); tcgetattr (0, &oldtty); - fcntl(0, F_SETFL, O_NONBLOCK); + qemu_set_nonblock(0); atexit(term_exit); chr = qemu_chr_open_fd(0, 1); diff --git a/qemu-options.hx b/qemu-options.hx index 9d743e53da..c573dd8893 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -427,7 +427,7 @@ DEF("drive", HAS_ARG, QEMU_OPTION_drive, " [,serial=s][,addr=A][,rerror=ignore|stop|report]\n" " [,werror=ignore|stop|report|enospc][,id=name][,aio=threads|native]\n" " [,readonly=on|off][,copy-on-read=on|off]\n" - " [,detect-zeroes=on|off|unmap]\n" + " [,discard=ignore|unmap][,detect-zeroes=on|off|unmap]\n" " [[,bps=b]|[[,bps_rd=r][,bps_wr=w]]]\n" " [[,iops=i]|[[,iops_rd=r][,iops_wr=w]]]\n" " [[,bps_max=bm]|[[,bps_rd_max=rm][,bps_wr_max=wm]]]\n" diff --git a/qga/channel-posix.c b/qga/channel-posix.c index e65dda3822..8aad4fee9f 100644 --- a/qga/channel-posix.c +++ b/qga/channel-posix.c @@ -42,7 +42,7 @@ static gboolean ga_channel_listen_accept(GIOChannel *channel, g_warning("error converting fd to gsocket: %s", strerror(errno)); goto out; } - fcntl(client_fd, F_SETFL, O_NONBLOCK); + qemu_set_nonblock(client_fd); ret = ga_channel_client_add(c, client_fd); if (ret) { g_warning("error setting up connection"); diff --git a/tests/ide-test.c b/tests/ide-test.c index 4a0d97f197..ffce6ed669 100644 --- a/tests/ide-test.c +++ b/tests/ide-test.c @@ -106,6 +106,7 @@ static QPCIBus *pcibus = NULL; static QGuestAllocator *guest_malloc; static char tmp_path[] = "/tmp/qtest.XXXXXX"; +static char debug_path[] = "/tmp/qtest-blkdebug.XXXXXX"; static void ide_test_start(const char *cmdline_fmt, ...) { @@ -119,6 +120,8 @@ static void ide_test_start(const char *cmdline_fmt, ...) qtest_start(cmdline); qtest_irq_intercept_in(global_qtest, "ioapic"); guest_malloc = pc_alloc_init(); + + g_free(cmdline); } static void ide_test_quit(void) @@ -145,7 +148,7 @@ static QPCIDevice *get_pci_device(uint16_t *bmdma_base) g_assert(device_id == PCI_DEVICE_ID_INTEL_82371SB_1); /* Map bmdma BAR */ - *bmdma_base = (uint16_t)(uintptr_t) qpci_iomap(dev, 4); + *bmdma_base = (uint16_t)(uintptr_t) qpci_iomap(dev, 4, NULL); qpci_device_enable(dev); @@ -489,6 +492,91 @@ static void test_flush(void) ide_test_quit(); } +static void prepare_blkdebug_script(const char *debug_fn, const char *event) +{ + FILE *debug_file = fopen(debug_fn, "w"); + int ret; + + fprintf(debug_file, "[inject-error]\n"); + fprintf(debug_file, "event = \"%s\"\n", event); + fprintf(debug_file, "errno = \"5\"\n"); + fprintf(debug_file, "state = \"1\"\n"); + fprintf(debug_file, "immediately = \"off\"\n"); + fprintf(debug_file, "once = \"on\"\n"); + + fprintf(debug_file, "[set-state]\n"); + fprintf(debug_file, "event = \"%s\"\n", event); + fprintf(debug_file, "new_state = \"2\"\n"); + fflush(debug_file); + g_assert(!ferror(debug_file)); + + ret = fclose(debug_file); + g_assert(ret == 0); +} + +static void test_retry_flush(void) +{ + uint8_t data; + const char *s; + QDict *response; + + prepare_blkdebug_script(debug_path, "flush_to_disk"); + + ide_test_start( + "-vnc none " + "-drive file=blkdebug:%s:%s,if=ide,cache=writeback,rerror=stop,werror=stop", + debug_path, tmp_path); + + /* FLUSH CACHE command on device 0*/ + outb(IDE_BASE + reg_device, 0); + outb(IDE_BASE + reg_command, CMD_FLUSH_CACHE); + + /* Check status while request is in flight*/ + data = inb(IDE_BASE + reg_status); + assert_bit_set(data, BSY | DRDY); + assert_bit_clear(data, DF | ERR | DRQ); + + for (;; response = NULL) { + response = qmp_receive(); + if ((qdict_haskey(response, "event")) && + (strcmp(qdict_get_str(response, "event"), "STOP") == 0)) { + QDECREF(response); + break; + } + QDECREF(response); + } + + /* Complete the command */ + s = "{'execute':'cont' }"; + qmp_discard_response(s); + + /* Check registers */ + data = inb(IDE_BASE + reg_device); + g_assert_cmpint(data & DEV, ==, 0); + + do { + data = inb(IDE_BASE + reg_status); + } while (data & BSY); + + assert_bit_set(data, DRDY); + assert_bit_clear(data, BSY | DF | ERR | DRQ); + + ide_test_quit(); +} + +static void test_flush_nodev(void) +{ + ide_test_start(""); + + /* FLUSH CACHE command on device 0*/ + outb(IDE_BASE + reg_device, 0); + outb(IDE_BASE + reg_command, CMD_FLUSH_CACHE); + + /* Just testing that qemu doesn't crash... */ + + ide_test_quit(); +} + int main(int argc, char **argv) { const char *arch = qtest_get_arch(); @@ -501,6 +589,11 @@ int main(int argc, char **argv) return 0; } + /* Create temporary blkdebug instructions */ + fd = mkstemp(debug_path); + g_assert(fd >= 0); + close(fd); + /* Create a temporary raw image */ fd = mkstemp(tmp_path); g_assert(fd >= 0); @@ -521,11 +614,15 @@ int main(int argc, char **argv) qtest_add_func("/ide/bmdma/teardown", test_bmdma_teardown); qtest_add_func("/ide/flush", test_flush); + qtest_add_func("/ide/flush_nodev", test_flush_nodev); + + qtest_add_func("/ide/retry/flush", test_retry_flush); ret = g_test_run(); /* Cleanup */ unlink(tmp_path); + unlink(debug_path); return ret; } diff --git a/tests/image-fuzzer/qcow2/__init__.py b/tests/image-fuzzer/qcow2/__init__.py new file mode 100644 index 0000000000..e2ebe19311 --- /dev/null +++ b/tests/image-fuzzer/qcow2/__init__.py @@ -0,0 +1 @@ +from layout import create_image diff --git a/tests/image-fuzzer/qcow2/fuzz.py b/tests/image-fuzzer/qcow2/fuzz.py new file mode 100644 index 0000000000..57527f9b4a --- /dev/null +++ b/tests/image-fuzzer/qcow2/fuzz.py @@ -0,0 +1,355 @@ +# Fuzzing functions for qcow2 fields +# +# Copyright (C) 2014 Maria Kustova <maria.k@catit.be> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import random + + +UINT8 = 0xff +UINT32 = 0xffffffff +UINT64 = 0xffffffffffffffff +# Most significant bit orders +UINT32_M = 31 +UINT64_M = 63 +# Fuzz vectors +UINT8_V = [0, 0x10, UINT8/4, UINT8/2 - 1, UINT8/2, UINT8/2 + 1, UINT8 - 1, + UINT8] +UINT32_V = [0, 0x100, 0x1000, 0x10000, 0x100000, UINT32/4, UINT32/2 - 1, + UINT32/2, UINT32/2 + 1, UINT32 - 1, UINT32] +UINT64_V = UINT32_V + [0x1000000, 0x10000000, 0x100000000, UINT64/4, + UINT64/2 - 1, UINT64/2, UINT64/2 + 1, UINT64 - 1, + UINT64] +STRING_V = ['%s%p%x%d', '.1024d', '%.2049d', '%p%p%p%p', '%x%x%x%x', + '%d%d%d%d', '%s%s%s%s', '%99999999999s', '%08x', '%%20d', '%%20n', + '%%20x', '%%20s', '%s%s%s%s%s%s%s%s%s%s', '%p%p%p%p%p%p%p%p%p%p', + '%#0123456x%08x%x%s%p%d%n%o%u%c%h%l%q%j%z%Z%t%i%e%g%f%a%C%S%08x%%', + '%s x 129', '%x x 257'] + + +def random_from_intervals(intervals): + """Select a random integer number from the list of specified intervals. + + Each interval is a tuple of lower and upper limits of the interval. The + limits are included. Intervals in a list should not overlap. + """ + total = reduce(lambda x, y: x + y[1] - y[0] + 1, intervals, 0) + r = random.randint(0, total - 1) + intervals[0][0] + for x in zip(intervals, intervals[1:]): + r = r + (r > x[0][1]) * (x[1][0] - x[0][1] - 1) + return r + + +def random_bits(bit_ranges): + """Generate random binary mask with ones in the specified bit ranges. + + Each bit_ranges is a list of tuples of lower and upper limits of bit + positions will be fuzzed. The limits are included. Random amount of bits + in range limits will be set to ones. The mask is returned in decimal + integer format. + """ + bit_numbers = [] + # Select random amount of random positions in bit_ranges + for rng in bit_ranges: + bit_numbers += random.sample(range(rng[0], rng[1] + 1), + random.randint(0, rng[1] - rng[0] + 1)) + val = 0 + # Set bits on selected positions to ones + for bit in bit_numbers: + val |= 1 << bit + return val + + +def truncate_string(strings, length): + """Return strings truncated to specified length.""" + if type(strings) == list: + return [s[:length] for s in strings] + else: + return strings[:length] + + +def validator(current, pick, choices): + """Return a value not equal to the current selected by the pick + function from choices. + """ + while True: + val = pick(choices) + if not val == current: + return val + + +def int_validator(current, intervals): + """Return a random value from intervals not equal to the current. + + This function is useful for selection from valid values except current one. + """ + return validator(current, random_from_intervals, intervals) + + +def bit_validator(current, bit_ranges): + """Return a random bit mask not equal to the current. + + This function is useful for selection from valid values except current one. + """ + return validator(current, random_bits, bit_ranges) + + +def string_validator(current, strings): + """Return a random string value from the list not equal to the current. + + This function is useful for selection from valid values except current one. + """ + return validator(current, random.choice, strings) + + +def selector(current, constraints, validate=int_validator): + """Select one value from all defined by constraints. + + Each constraint produces one random value satisfying to it. The function + randomly selects one value satisfying at least one constraint (depending on + constraints overlaps). + """ + def iter_validate(c): + """Apply validate() only to constraints represented as lists. + + This auxiliary function replaces short circuit conditions not supported + in Python 2.4 + """ + if type(c) == list: + return validate(current, c) + else: + return c + + fuzz_values = [iter_validate(c) for c in constraints] + # Remove current for cases it's implicitly specified in constraints + # Duplicate validator functionality to prevent decreasing of probability + # to get one of allowable values + # TODO: remove validators after implementation of intelligent selection + # of fields will be fuzzed + try: + fuzz_values.remove(current) + except ValueError: + pass + return random.choice(fuzz_values) + + +def magic(current): + """Fuzz magic header field. + + The function just returns the current magic value and provides uniformity + of calls for all fuzzing functions. + """ + return current + + +def version(current): + """Fuzz version header field.""" + constraints = UINT32_V + [ + [(2, 3)], # correct values + [(0, 1), (4, UINT32)] + ] + return selector(current, constraints) + + +def backing_file_offset(current): + """Fuzz backing file offset header field.""" + constraints = UINT64_V + return selector(current, constraints) + + +def backing_file_size(current): + """Fuzz backing file size header field.""" + constraints = UINT32_V + return selector(current, constraints) + + +def cluster_bits(current): + """Fuzz cluster bits header field.""" + constraints = UINT32_V + [ + [(9, 20)], # correct values + [(0, 9), (20, UINT32)] + ] + return selector(current, constraints) + + +def size(current): + """Fuzz image size header field.""" + constraints = UINT64_V + return selector(current, constraints) + + +def crypt_method(current): + """Fuzz crypt method header field.""" + constraints = UINT32_V + [ + 1, + [(2, UINT32)] + ] + return selector(current, constraints) + + +def l1_size(current): + """Fuzz L1 table size header field.""" + constraints = UINT32_V + return selector(current, constraints) + + +def l1_table_offset(current): + """Fuzz L1 table offset header field.""" + constraints = UINT64_V + return selector(current, constraints) + + +def refcount_table_offset(current): + """Fuzz refcount table offset header field.""" + constraints = UINT64_V + return selector(current, constraints) + + +def refcount_table_clusters(current): + """Fuzz refcount table clusters header field.""" + constraints = UINT32_V + return selector(current, constraints) + + +def nb_snapshots(current): + """Fuzz number of snapshots header field.""" + constraints = UINT32_V + return selector(current, constraints) + + +def snapshots_offset(current): + """Fuzz snapshots offset header field.""" + constraints = UINT64_V + return selector(current, constraints) + + +def incompatible_features(current): + """Fuzz incompatible features header field.""" + constraints = [ + [(0, 1)], # allowable values + [(0, UINT64_M)] + ] + return selector(current, constraints, bit_validator) + + +def compatible_features(current): + """Fuzz compatible features header field.""" + constraints = [ + [(0, UINT64_M)] + ] + return selector(current, constraints, bit_validator) + + +def autoclear_features(current): + """Fuzz autoclear features header field.""" + constraints = [ + [(0, UINT64_M)] + ] + return selector(current, constraints, bit_validator) + + +def refcount_order(current): + """Fuzz number of refcount order header field.""" + constraints = UINT32_V + return selector(current, constraints) + + +def header_length(current): + """Fuzz number of refcount order header field.""" + constraints = UINT32_V + [ + 72, + 104, + [(0, UINT32)] + ] + return selector(current, constraints) + + +def bf_name(current): + """Fuzz the backing file name.""" + constraints = [ + truncate_string(STRING_V, len(current)) + ] + return selector(current, constraints, string_validator) + + +def ext_magic(current): + """Fuzz magic field of a header extension.""" + constraints = UINT32_V + return selector(current, constraints) + + +def ext_length(current): + """Fuzz length field of a header extension.""" + constraints = UINT32_V + return selector(current, constraints) + + +def bf_format(current): + """Fuzz backing file format in the corresponding header extension.""" + constraints = [ + truncate_string(STRING_V, len(current)), + truncate_string(STRING_V, (len(current) + 7) & ~7) # Fuzz padding + ] + return selector(current, constraints, string_validator) + + +def feature_type(current): + """Fuzz feature type field of a feature name table header extension.""" + constraints = UINT8_V + return selector(current, constraints) + + +def feature_bit_number(current): + """Fuzz bit number field of a feature name table header extension.""" + constraints = UINT8_V + return selector(current, constraints) + + +def feature_name(current): + """Fuzz feature name field of a feature name table header extension.""" + constraints = [ + truncate_string(STRING_V, len(current)), + truncate_string(STRING_V, 46) # Fuzz padding (field length = 46) + ] + return selector(current, constraints, string_validator) + + +def l1_entry(current): + """Fuzz an entry of the L1 table.""" + constraints = UINT64_V + # Reserved bits are ignored + # Added a possibility when only flags are fuzzed + offset = 0x7fffffffffffffff & random.choice([selector(current, + constraints), + current]) + is_cow = random.randint(0, 1) + return offset + (is_cow << UINT64_M) + + +def l2_entry(current): + """Fuzz an entry of an L2 table.""" + constraints = UINT64_V + # Reserved bits are ignored + # Add a possibility when only flags are fuzzed + offset = 0x3ffffffffffffffe & random.choice([selector(current, + constraints), + current]) + is_compressed = random.randint(0, 1) + is_cow = random.randint(0, 1) + is_zero = random.randint(0, 1) + value = offset + (is_cow << UINT64_M) + \ + (is_compressed << UINT64_M - 1) + is_zero + return value diff --git a/tests/image-fuzzer/qcow2/layout.py b/tests/image-fuzzer/qcow2/layout.py new file mode 100644 index 0000000000..730c771d3c --- /dev/null +++ b/tests/image-fuzzer/qcow2/layout.py @@ -0,0 +1,476 @@ +# Generator of fuzzed qcow2 images +# +# Copyright (C) 2014 Maria Kustova <maria.k@catit.be> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import random +import struct +import fuzz +from math import ceil +from os import urandom +from itertools import chain + +MAX_IMAGE_SIZE = 10 * (1 << 20) +# Standard sizes +UINT32_S = 4 +UINT64_S = 8 + + +class Field(object): + + """Atomic image element (field). + + The class represents an image field as quadruple of a data format + of value necessary for its packing to binary form, an offset from + the beginning of the image, a value and a name. + + The field can be iterated as a list [format, offset, value, name]. + """ + + __slots__ = ('fmt', 'offset', 'value', 'name') + + def __init__(self, fmt, offset, val, name): + self.fmt = fmt + self.offset = offset + self.value = val + self.name = name + + def __iter__(self): + return iter([self.fmt, self.offset, self.value, self.name]) + + def __repr__(self): + return "Field(fmt='%s', offset=%d, value=%s, name=%s)" % \ + (self.fmt, self.offset, str(self.value), self.name) + + +class FieldsList(object): + + """List of fields. + + The class allows access to a field in the list by its name. + """ + + def __init__(self, meta_data=None): + if meta_data is None: + self.data = [] + else: + self.data = [Field(*f) + for f in meta_data] + + def __getitem__(self, name): + return [x for x in self.data if x.name == name] + + def __iter__(self): + return iter(self.data) + + def __len__(self): + return len(self.data) + + +class Image(object): + + """ Qcow2 image object. + + This class allows to create qcow2 images with random valid structures and + values, fuzz them via external qcow2.fuzz module and write the result to + a file. + """ + + def __init__(self, backing_file_name=None): + """Create a random valid qcow2 image with the correct header and stored + backing file name. + """ + cluster_bits, self.image_size = self._size_params() + self.cluster_size = 1 << cluster_bits + self.header = FieldsList() + self.backing_file_name = FieldsList() + self.backing_file_format = FieldsList() + self.feature_name_table = FieldsList() + self.end_of_extension_area = FieldsList() + self.l2_tables = FieldsList() + self.l1_table = FieldsList() + self.ext_offset = 0 + self.create_header(cluster_bits, backing_file_name) + self.set_backing_file_name(backing_file_name) + self.data_clusters = self._alloc_data(self.image_size, + self.cluster_size) + # Percentage of fields will be fuzzed + self.bias = random.uniform(0.2, 0.5) + + def __iter__(self): + return chain(self.header, self.backing_file_format, + self.feature_name_table, self.end_of_extension_area, + self.backing_file_name, self.l1_table, self.l2_tables) + + def create_header(self, cluster_bits, backing_file_name=None): + """Generate a random valid header.""" + meta_header = [ + ['>4s', 0, "QFI\xfb", 'magic'], + ['>I', 4, random.randint(2, 3), 'version'], + ['>Q', 8, 0, 'backing_file_offset'], + ['>I', 16, 0, 'backing_file_size'], + ['>I', 20, cluster_bits, 'cluster_bits'], + ['>Q', 24, self.image_size, 'size'], + ['>I', 32, 0, 'crypt_method'], + ['>I', 36, 0, 'l1_size'], + ['>Q', 40, 0, 'l1_table_offset'], + ['>Q', 48, 0, 'refcount_table_offset'], + ['>I', 56, 0, 'refcount_table_clusters'], + ['>I', 60, 0, 'nb_snapshots'], + ['>Q', 64, 0, 'snapshots_offset'], + ['>Q', 72, 0, 'incompatible_features'], + ['>Q', 80, 0, 'compatible_features'], + ['>Q', 88, 0, 'autoclear_features'], + # Only refcount_order = 4 is supported by current (07.2014) + # implementation of QEMU + ['>I', 96, 4, 'refcount_order'], + ['>I', 100, 0, 'header_length'] + ] + self.header = FieldsList(meta_header) + + if self.header['version'][0].value == 2: + self.header['header_length'][0].value = 72 + else: + self.header['incompatible_features'][0].value = \ + random.getrandbits(2) + self.header['compatible_features'][0].value = random.getrandbits(1) + self.header['header_length'][0].value = 104 + # Extensions start at the header last field offset and the field size + self.ext_offset = struct.calcsize( + self.header['header_length'][0].fmt) + \ + self.header['header_length'][0].offset + end_of_extension_area_len = 2 * UINT32_S + free_space = self.cluster_size - self.ext_offset - \ + end_of_extension_area_len + # If the backing file name specified and there is enough space for it + # in the first cluster, then it's placed in the very end of the first + # cluster. + if (backing_file_name is not None) and \ + (free_space >= len(backing_file_name)): + self.header['backing_file_size'][0].value = len(backing_file_name) + self.header['backing_file_offset'][0].value = \ + self.cluster_size - len(backing_file_name) + + def set_backing_file_name(self, backing_file_name=None): + """Add the name of the backing file at the offset specified + in the header. + """ + if (backing_file_name is not None) and \ + (not self.header['backing_file_offset'][0].value == 0): + data_len = len(backing_file_name) + data_fmt = '>' + str(data_len) + 's' + self.backing_file_name = FieldsList([ + [data_fmt, self.header['backing_file_offset'][0].value, + backing_file_name, 'bf_name'] + ]) + + def set_backing_file_format(self, backing_file_fmt=None): + """Generate the header extension for the backing file format.""" + if backing_file_fmt is not None: + # Calculation of the free space available in the first cluster + end_of_extension_area_len = 2 * UINT32_S + high_border = (self.header['backing_file_offset'][0].value or + (self.cluster_size - 1)) - \ + end_of_extension_area_len + free_space = high_border - self.ext_offset + ext_size = 2 * UINT32_S + ((len(backing_file_fmt) + 7) & ~7) + + if free_space >= ext_size: + ext_data_len = len(backing_file_fmt) + ext_data_fmt = '>' + str(ext_data_len) + 's' + ext_padding_len = 7 - (ext_data_len - 1) % 8 + self.backing_file_format = FieldsList([ + ['>I', self.ext_offset, 0xE2792ACA, 'ext_magic'], + ['>I', self.ext_offset + UINT32_S, ext_data_len, + 'ext_length'], + [ext_data_fmt, self.ext_offset + UINT32_S * 2, + backing_file_fmt, 'bf_format'] + ]) + self.ext_offset = \ + struct.calcsize( + self.backing_file_format['bf_format'][0].fmt) + \ + ext_padding_len + \ + self.backing_file_format['bf_format'][0].offset + + def create_feature_name_table(self): + """Generate a random header extension for names of features used in + the image. + """ + def gen_feat_ids(): + """Return random feature type and feature bit.""" + return (random.randint(0, 2), random.randint(0, 63)) + + end_of_extension_area_len = 2 * UINT32_S + high_border = (self.header['backing_file_offset'][0].value or + (self.cluster_size - 1)) - \ + end_of_extension_area_len + free_space = high_border - self.ext_offset + # Sum of sizes of 'magic' and 'length' header extension fields + ext_header_len = 2 * UINT32_S + fnt_entry_size = 6 * UINT64_S + num_fnt_entries = min(10, (free_space - ext_header_len) / + fnt_entry_size) + if not num_fnt_entries == 0: + feature_tables = [] + feature_ids = [] + inner_offset = self.ext_offset + ext_header_len + feat_name = 'some cool feature' + while len(feature_tables) < num_fnt_entries * 3: + feat_type, feat_bit = gen_feat_ids() + # Remove duplicates + while (feat_type, feat_bit) in feature_ids: + feat_type, feat_bit = gen_feat_ids() + feature_ids.append((feat_type, feat_bit)) + feat_fmt = '>' + str(len(feat_name)) + 's' + feature_tables += [['B', inner_offset, + feat_type, 'feature_type'], + ['B', inner_offset + 1, feat_bit, + 'feature_bit_number'], + [feat_fmt, inner_offset + 2, + feat_name, 'feature_name'] + ] + inner_offset += fnt_entry_size + # No padding for the extension is necessary, because + # the extension length is multiple of 8 + self.feature_name_table = FieldsList([ + ['>I', self.ext_offset, 0x6803f857, 'ext_magic'], + # One feature table contains 3 fields and takes 48 bytes + ['>I', self.ext_offset + UINT32_S, + len(feature_tables) / 3 * 48, 'ext_length'] + ] + feature_tables) + self.ext_offset = inner_offset + + def set_end_of_extension_area(self): + """Generate a mandatory header extension marking end of header + extensions. + """ + self.end_of_extension_area = FieldsList([ + ['>I', self.ext_offset, 0, 'ext_magic'], + ['>I', self.ext_offset + UINT32_S, 0, 'ext_length'] + ]) + + def create_l_structures(self): + """Generate random valid L1 and L2 tables.""" + def create_l2_entry(host, guest, l2_cluster): + """Generate one L2 entry.""" + offset = l2_cluster * self.cluster_size + l2_size = self.cluster_size / UINT64_S + entry_offset = offset + UINT64_S * (guest % l2_size) + cluster_descriptor = host * self.cluster_size + if not self.header['version'][0].value == 2: + cluster_descriptor += random.randint(0, 1) + # While snapshots are not supported, bit #63 = 1 + # Compressed clusters are not supported => bit #62 = 0 + entry_val = (1 << 63) + cluster_descriptor + return ['>Q', entry_offset, entry_val, 'l2_entry'] + + def create_l1_entry(l2_cluster, l1_offset, guest): + """Generate one L1 entry.""" + l2_size = self.cluster_size / UINT64_S + entry_offset = l1_offset + UINT64_S * (guest / l2_size) + # While snapshots are not supported bit #63 = 1 + entry_val = (1 << 63) + l2_cluster * self.cluster_size + return ['>Q', entry_offset, entry_val, 'l1_entry'] + + if len(self.data_clusters) == 0: + # All metadata for an empty guest image needs 4 clusters: + # header, rfc table, rfc block, L1 table. + # Header takes cluster #0, other clusters ##1-3 can be used + l1_offset = random.randint(1, 3) * self.cluster_size + l1 = [['>Q', l1_offset, 0, 'l1_entry']] + l2 = [] + else: + meta_data = self._get_metadata() + guest_clusters = random.sample(range(self.image_size / + self.cluster_size), + len(self.data_clusters)) + # Number of entries in a L1/L2 table + l_size = self.cluster_size / UINT64_S + # Number of clusters necessary for L1 table + l1_size = int(ceil((max(guest_clusters) + 1) / float(l_size**2))) + l1_start = self._get_adjacent_clusters(self.data_clusters | + meta_data, l1_size) + meta_data |= set(range(l1_start, l1_start + l1_size)) + l1_offset = l1_start * self.cluster_size + # Indices of L2 tables + l2_ids = [] + # Host clusters allocated for L2 tables + l2_clusters = [] + # L1 entries + l1 = [] + # L2 entries + l2 = [] + for host, guest in zip(self.data_clusters, guest_clusters): + l2_id = guest / l_size + if l2_id not in l2_ids: + l2_ids.append(l2_id) + l2_clusters.append(self._get_adjacent_clusters( + self.data_clusters | meta_data | set(l2_clusters), + 1)) + l1.append(create_l1_entry(l2_clusters[-1], l1_offset, + guest)) + l2.append(create_l2_entry(host, guest, + l2_clusters[l2_ids.index(l2_id)])) + self.l2_tables = FieldsList(l2) + self.l1_table = FieldsList(l1) + self.header['l1_size'][0].value = int(ceil(UINT64_S * self.image_size / + float(self.cluster_size**2))) + self.header['l1_table_offset'][0].value = l1_offset + + def fuzz(self, fields_to_fuzz=None): + """Fuzz an image by corrupting values of a random subset of its fields. + + Without parameters the method fuzzes an entire image. + + If 'fields_to_fuzz' is specified then only fields in this list will be + fuzzed. 'fields_to_fuzz' can contain both individual fields and more + general image elements as a header or tables. + + In the first case the field will be fuzzed always. + In the second a random subset of fields will be selected and fuzzed. + """ + def coin(): + """Return boolean value proportional to a portion of fields to be + fuzzed. + """ + return random.random() < self.bias + + if fields_to_fuzz is None: + for field in self: + if coin(): + field.value = getattr(fuzz, field.name)(field.value) + else: + for item in fields_to_fuzz: + if len(item) == 1: + for field in getattr(self, item[0]): + if coin(): + field.value = getattr(fuzz, + field.name)(field.value) + else: + # If fields with the requested name were not generated + # getattr(self, item[0])[item[1]] returns an empty list + for field in getattr(self, item[0])[item[1]]: + field.value = getattr(fuzz, field.name)(field.value) + + def write(self, filename): + """Write an entire image to the file.""" + image_file = open(filename, 'w') + for field in self: + image_file.seek(field.offset) + image_file.write(struct.pack(field.fmt, field.value)) + + for cluster in sorted(self.data_clusters): + image_file.seek(cluster * self.cluster_size) + image_file.write(urandom(self.cluster_size)) + + # Align the real image size to the cluster size + image_file.seek(0, 2) + size = image_file.tell() + rounded = (size + self.cluster_size - 1) & ~(self.cluster_size - 1) + if rounded > size: + image_file.seek(rounded - 1) + image_file.write("\0") + image_file.close() + + @staticmethod + def _size_params(): + """Generate a random image size aligned to a random correct + cluster size. + """ + cluster_bits = random.randrange(9, 21) + cluster_size = 1 << cluster_bits + img_size = random.randrange(0, MAX_IMAGE_SIZE + 1, cluster_size) + return (cluster_bits, img_size) + + @staticmethod + def _get_available_clusters(used, number): + """Return a set of indices of not allocated clusters. + + 'used' contains indices of currently allocated clusters. + All clusters that cannot be allocated between 'used' clusters will have + indices appended to the end of 'used'. + """ + append_id = max(used) + 1 + free = set(range(1, append_id)) - used + if len(free) >= number: + return set(random.sample(free, number)) + else: + return free | set(range(append_id, append_id + number - len(free))) + + @staticmethod + def _get_adjacent_clusters(used, size): + """Return an index of the first cluster in the sequence of free ones. + + 'used' contains indices of currently allocated clusters. 'size' is the + length of the sequence of free clusters. + If the sequence of 'size' is not available between 'used' clusters, its + first index will be append to the end of 'used'. + """ + def get_cluster_id(lst, length): + """Return the first index of the sequence of the specified length + or None if the sequence cannot be inserted in the list. + """ + if len(lst) != 0: + pairs = [] + pair = (lst[0], 1) + for i in range(1, len(lst)): + if lst[i] == lst[i-1] + 1: + pair = (lst[i], pair[1] + 1) + else: + pairs.append(pair) + pair = (lst[i], 1) + pairs.append(pair) + random.shuffle(pairs) + for x, s in pairs: + if s >= length: + return x - length + 1 + return None + + append_id = max(used) + 1 + free = list(set(range(1, append_id)) - used) + idx = get_cluster_id(free, size) + if idx is None: + return append_id + else: + return idx + + @staticmethod + def _alloc_data(img_size, cluster_size): + """Return a set of random indices of clusters allocated for guest data. + """ + num_of_cls = img_size/cluster_size + return set(random.sample(range(1, num_of_cls + 1), + random.randint(0, num_of_cls))) + + def _get_metadata(self): + """Return indices of clusters allocated for image metadata.""" + ids = set() + for x in self: + ids.add(x.offset/self.cluster_size) + return ids + + +def create_image(test_img_path, backing_file_name=None, backing_file_fmt=None, + fields_to_fuzz=None): + """Create a fuzzed image and write it to the specified file.""" + image = Image(backing_file_name) + image.set_backing_file_format(backing_file_fmt) + image.create_feature_name_table() + image.set_end_of_extension_area() + image.create_l_structures() + image.fuzz(fields_to_fuzz) + image.write(test_img_path) + return image.image_size diff --git a/tests/image-fuzzer/runner.py b/tests/image-fuzzer/runner.py new file mode 100755 index 0000000000..58079d331e --- /dev/null +++ b/tests/image-fuzzer/runner.py @@ -0,0 +1,405 @@ +#!/usr/bin/env python + +# Tool for running fuzz tests +# +# Copyright (C) 2014 Maria Kustova <maria.k@catit.be> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import sys +import os +import signal +import subprocess +import random +import shutil +from itertools import count +import getopt +import StringIO +import resource + +try: + import json +except ImportError: + try: + import simplejson as json + except ImportError: + print >>sys.stderr, \ + "Warning: Module for JSON processing is not found.\n" \ + "'--config' and '--command' options are not supported." + +# Backing file sizes in MB +MAX_BACKING_FILE_SIZE = 10 +MIN_BACKING_FILE_SIZE = 1 + + +def multilog(msg, *output): + """ Write an object to all of specified file descriptors.""" + for fd in output: + fd.write(msg) + fd.flush() + + +def str_signal(sig): + """ Convert a numeric value of a system signal to the string one + defined by the current operational system. + """ + for k, v in signal.__dict__.items(): + if v == sig: + return k + + +def run_app(fd, q_args): + """Start an application with specified arguments and return its exit code + or kill signal depending on the result of execution. + """ + devnull = open('/dev/null', 'r+') + process = subprocess.Popen(q_args, stdin=devnull, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, err = process.communicate() + fd.write(out) + fd.write(err) + return process.returncode + + +class TestException(Exception): + """Exception for errors risen by TestEnv objects.""" + pass + + +class TestEnv(object): + + """Test object. + + The class sets up test environment, generates backing and test images + and executes application under tests with specified arguments and a test + image provided. + + All logs are collected. + + The summary log will contain short descriptions and statuses of tests in + a run. + + The test log will include application (e.g. 'qemu-img') logs besides info + sent to the summary log. + """ + + def __init__(self, test_id, seed, work_dir, run_log, + cleanup=True, log_all=False): + """Set test environment in a specified work directory. + + Path to qemu-img and qemu-io will be retrieved from 'QEMU_IMG' and + 'QEMU_IO' environment variables. + """ + if seed is not None: + self.seed = seed + else: + self.seed = str(random.randint(0, sys.maxint)) + random.seed(self.seed) + + self.init_path = os.getcwd() + self.work_dir = work_dir + self.current_dir = os.path.join(work_dir, 'test-' + test_id) + self.qemu_img = os.environ.get('QEMU_IMG', 'qemu-img')\ + .strip().split(' ') + self.qemu_io = os.environ.get('QEMU_IO', 'qemu-io').strip().split(' ') + self.commands = [['qemu-img', 'check', '-f', 'qcow2', '$test_img'], + ['qemu-img', 'info', '-f', 'qcow2', '$test_img'], + ['qemu-io', '$test_img', '-c', 'read $off $len'], + ['qemu-io', '$test_img', '-c', 'write $off $len'], + ['qemu-io', '$test_img', '-c', + 'aio_read $off $len'], + ['qemu-io', '$test_img', '-c', + 'aio_write $off $len'], + ['qemu-io', '$test_img', '-c', 'flush'], + ['qemu-io', '$test_img', '-c', + 'discard $off $len'], + ['qemu-io', '$test_img', '-c', + 'truncate $off']] + for fmt in ['raw', 'vmdk', 'vdi', 'cow', 'qcow2', 'file', + 'qed', 'vpc']: + self.commands.append( + ['qemu-img', 'convert', '-f', 'qcow2', '-O', fmt, + '$test_img', 'converted_image.' + fmt]) + + try: + os.makedirs(self.current_dir) + except OSError, e: + print >>sys.stderr, \ + "Error: The working directory '%s' cannot be used. Reason: %s"\ + % (self.work_dir, e[1]) + raise TestException + self.log = open(os.path.join(self.current_dir, "test.log"), "w") + self.parent_log = open(run_log, "a") + self.failed = False + self.cleanup = cleanup + self.log_all = log_all + + def _create_backing_file(self): + """Create a backing file in the current directory. + + Return a tuple of a backing file name and format. + + Format of a backing file is randomly chosen from all formats supported + by 'qemu-img create'. + """ + # All formats supported by the 'qemu-img create' command. + backing_file_fmt = random.choice(['raw', 'vmdk', 'vdi', 'cow', 'qcow2', + 'file', 'qed', 'vpc']) + backing_file_name = 'backing_img.' + backing_file_fmt + backing_file_size = random.randint(MIN_BACKING_FILE_SIZE, + MAX_BACKING_FILE_SIZE) * (1 << 20) + cmd = self.qemu_img + ['create', '-f', backing_file_fmt, + backing_file_name, str(backing_file_size)] + temp_log = StringIO.StringIO() + retcode = run_app(temp_log, cmd) + if retcode == 0: + temp_log.close() + return (backing_file_name, backing_file_fmt) + else: + multilog("Warning: The %s backing file was not created.\n\n" + % backing_file_fmt, sys.stderr, self.log, self.parent_log) + self.log.write("Log for the failure:\n" + temp_log.getvalue() + + '\n\n') + temp_log.close() + return (None, None) + + def execute(self, input_commands=None, fuzz_config=None): + """ Execute a test. + + The method creates backing and test images, runs test app and analyzes + its exit status. If the application was killed by a signal, the test + is marked as failed. + """ + if input_commands is None: + commands = self.commands + else: + commands = input_commands + + os.chdir(self.current_dir) + backing_file_name, backing_file_fmt = self._create_backing_file() + img_size = image_generator.create_image('test.img', + backing_file_name, + backing_file_fmt, + fuzz_config) + for item in commands: + shutil.copy('test.img', 'copy.img') + # 'off' and 'len' are multiple of the sector size + sector_size = 512 + start = random.randrange(0, img_size + 1, sector_size) + end = random.randrange(start, img_size + 1, sector_size) + + if item[0] == 'qemu-img': + current_cmd = list(self.qemu_img) + elif item[0] == 'qemu-io': + current_cmd = list(self.qemu_io) + else: + multilog("Warning: test command '%s' is not defined.\n" \ + % item[0], sys.stderr, self.log, self.parent_log) + continue + # Replace all placeholders with their real values + for v in item[1:]: + c = (v + .replace('$test_img', 'copy.img') + .replace('$off', str(start)) + .replace('$len', str(end - start))) + current_cmd.append(c) + + # Log string with the test header + test_summary = "Seed: %s\nCommand: %s\nTest directory: %s\n" \ + "Backing file: %s\n" \ + % (self.seed, " ".join(current_cmd), + self.current_dir, backing_file_name) + + temp_log = StringIO.StringIO() + try: + retcode = run_app(temp_log, current_cmd) + except OSError, e: + multilog(test_summary + "Error: Start of '%s' failed. " \ + "Reason: %s\n\n" % (os.path.basename( + current_cmd[0]), e[1]), + sys.stderr, self.log, self.parent_log) + raise TestException + + if retcode < 0: + self.log.write(temp_log.getvalue()) + multilog(test_summary + "FAIL: Test terminated by signal " + + "%s\n\n" % str_signal(-retcode), sys.stderr, self.log, + self.parent_log) + self.failed = True + else: + if self.log_all: + self.log.write(temp_log.getvalue()) + multilog(test_summary + "PASS: Application exited with" + + " the code '%d'\n\n" % retcode, sys.stdout, + self.log, self.parent_log) + temp_log.close() + os.remove('copy.img') + + def finish(self): + """Restore the test environment after a test execution.""" + self.log.close() + self.parent_log.close() + os.chdir(self.init_path) + if self.cleanup and not self.failed: + shutil.rmtree(self.current_dir) + +if __name__ == '__main__': + + def usage(): + print """ + Usage: runner.py [OPTION...] TEST_DIR IMG_GENERATOR + + Set up test environment in TEST_DIR and run a test in it. A module for + test image generation should be specified via IMG_GENERATOR. + Example: + runner.py -c '[["qemu-img", "info", "$test_img"]]' /tmp/test qcow2 + + Optional arguments: + -h, --help display this help and exit + -c, --command=JSON run tests for all commands specified in + the JSON array + -s, --seed=STRING seed for a test image generation, + by default will be generated randomly + --config=JSON take fuzzer configuration from the JSON + array + -k, --keep_passed don't remove folders of passed tests + -v, --verbose log information about passed tests + + JSON: + + '--command' accepts a JSON array of commands. Each command presents + an application under test with all its paramaters as a list of strings, + e.g. + ["qemu-io", "$test_img", "-c", "write $off $len"] + + Supported application aliases: 'qemu-img' and 'qemu-io'. + Supported argument aliases: $test_img for the fuzzed image, $off + for an offset, $len for length. + + Values for $off and $len will be generated based on the virtual disk + size of the fuzzed image + Paths to 'qemu-img' and 'qemu-io' are retrevied from 'QEMU_IMG' and + 'QEMU_IO' environment variables + + '--config' accepts a JSON array of fields to be fuzzed, e.g. + '[["header"], ["header", "version"]]' + Each of the list elements can consist of a complex image element only + as ["header"] or ["feature_name_table"] or an exact field as + ["header", "version"]. In the first case random portion of the element + fields will be fuzzed, in the second one the specified field will be + fuzzed always. + + If '--config' argument is specified, fields not listed in + the configuration array will not be fuzzed. + """ + + def run_test(test_id, seed, work_dir, run_log, cleanup, log_all, + command, fuzz_config): + """Setup environment for one test and execute this test.""" + try: + test = TestEnv(test_id, seed, work_dir, run_log, cleanup, + log_all) + except TestException: + sys.exit(1) + + # Python 2.4 doesn't support 'finally' and 'except' in the same 'try' + # block + try: + try: + test.execute(command, fuzz_config) + except TestException: + sys.exit(1) + finally: + test.finish() + + try: + opts, args = getopt.gnu_getopt(sys.argv[1:], 'c:hs:kv', + ['command=', 'help', 'seed=', 'config=', + 'keep_passed', 'verbose']) + except getopt.error, e: + print >>sys.stderr, \ + "Error: %s\n\nTry 'runner.py --help' for more information" % e + sys.exit(1) + + command = None + cleanup = True + log_all = False + seed = None + config = None + for opt, arg in opts: + if opt in ('-h', '--help'): + usage() + sys.exit() + elif opt in ('-c', '--command'): + try: + command = json.loads(arg) + except (TypeError, ValueError, NameError), e: + print >>sys.stderr, \ + "Error: JSON array of test commands cannot be loaded.\n" \ + "Reason: %s" % e + sys.exit(1) + elif opt in ('-k', '--keep_passed'): + cleanup = False + elif opt in ('-v', '--verbose'): + log_all = True + elif opt in ('-s', '--seed'): + seed = arg + elif opt == '--config': + try: + config = json.loads(arg) + except (TypeError, ValueError, NameError), e: + print >>sys.stderr, \ + "Error: JSON array with the fuzzer configuration cannot" \ + " be loaded\nReason: %s" % e + sys.exit(1) + + if not len(args) == 2: + print >>sys.stderr, \ + "Expected two parameters\nTry 'runner.py --help'" \ + " for more information." + sys.exit(1) + + work_dir = os.path.realpath(args[0]) + # run_log is created in 'main', because multiple tests are expected to + # log in it + run_log = os.path.join(work_dir, 'run.log') + + # Add the path to the image generator module to sys.path + sys.path.append(os.path.realpath(os.path.dirname(args[1]))) + # Remove a script extension from image generator module if any + generator_name = os.path.splitext(os.path.basename(args[1]))[0] + + try: + image_generator = __import__(generator_name) + except ImportError, e: + print >>sys.stderr, \ + "Error: The image generator '%s' cannot be imported.\n" \ + "Reason: %s" % (generator_name, e) + sys.exit(1) + + # Enable core dumps + resource.setrlimit(resource.RLIMIT_CORE, (-1, -1)) + # If a seed is specified, only one test will be executed. + # Otherwise runner will terminate after a keyboard interruption + for test_id in count(1): + try: + run_test(str(test_id), seed, work_dir, run_log, cleanup, + log_all, command, config) + except (KeyboardInterrupt, SystemExit): + sys.exit(1) + + if seed is not None: + break diff --git a/tests/libqos/malloc-pc.c b/tests/libqos/malloc-pc.c index db1496c667..be1d97f8bb 100644 --- a/tests/libqos/malloc-pc.c +++ b/tests/libqos/malloc-pc.c @@ -36,7 +36,7 @@ static uint64_t pc_alloc(QGuestAllocator *allocator, size_t size) size += (PAGE_SIZE - 1); - size &= PAGE_SIZE; + size &= -PAGE_SIZE; g_assert_cmpint((s->start + size), <=, s->end); @@ -67,5 +67,8 @@ QGuestAllocator *pc_alloc_init(void) /* Respect PCI hole */ s->end = MIN(ram_size, 0xE0000000); + /* clean-up */ + g_free(fw_cfg); + return &s->alloc; } diff --git a/tests/libqos/malloc.h b/tests/libqos/malloc.h index 46f6000763..556538121e 100644 --- a/tests/libqos/malloc.h +++ b/tests/libqos/malloc.h @@ -32,7 +32,7 @@ static inline uint64_t guest_alloc(QGuestAllocator *allocator, size_t size) static inline void guest_free(QGuestAllocator *allocator, uint64_t addr) { - allocator->alloc(allocator, addr); + allocator->free(allocator, addr); } #endif diff --git a/tests/libqos/pci-pc.c b/tests/libqos/pci-pc.c index 4adf4006ae..0609294af0 100644 --- a/tests/libqos/pci-pc.c +++ b/tests/libqos/pci-pc.c @@ -144,7 +144,7 @@ static void qpci_pc_config_writel(QPCIBus *bus, int devfn, uint8_t offset, uint3 outl(0xcfc, value); } -static void *qpci_pc_iomap(QPCIBus *bus, QPCIDevice *dev, int barno) +static void *qpci_pc_iomap(QPCIBus *bus, QPCIDevice *dev, int barno, uint64_t *sizeptr) { QPCIBusPC *s = container_of(bus, QPCIBusPC, bus); static const int bar_reg_map[] = { @@ -173,6 +173,9 @@ static void *qpci_pc_iomap(QPCIBus *bus, QPCIDevice *dev, int barno) if (size == 0) { return NULL; } + if (sizeptr) { + *sizeptr = size; + } if (io_type == PCI_BASE_ADDRESS_SPACE_IO) { uint16_t loc; @@ -237,3 +240,10 @@ QPCIBus *qpci_init_pc(void) return &ret->bus; } + +void qpci_free_pc(QPCIBus *bus) +{ + QPCIBusPC *s = container_of(bus, QPCIBusPC, bus); + + g_free(s); +} diff --git a/tests/libqos/pci-pc.h b/tests/libqos/pci-pc.h index 4f7475f6f7..26211790cd 100644 --- a/tests/libqos/pci-pc.h +++ b/tests/libqos/pci-pc.h @@ -16,5 +16,6 @@ #include "libqos/pci.h" QPCIBus *qpci_init_pc(void); +void qpci_free_pc(QPCIBus *bus); #endif diff --git a/tests/libqos/pci.c b/tests/libqos/pci.c index c9a0b9134a..ce0b308a83 100644 --- a/tests/libqos/pci.c +++ b/tests/libqos/pci.c @@ -138,9 +138,9 @@ void qpci_io_writel(QPCIDevice *dev, void *data, uint32_t value) dev->bus->io_writel(dev->bus, data, value); } -void *qpci_iomap(QPCIDevice *dev, int barno) +void *qpci_iomap(QPCIDevice *dev, int barno, uint64_t *sizeptr) { - return dev->bus->iomap(dev->bus, dev, barno); + return dev->bus->iomap(dev->bus, dev, barno, sizeptr); } void qpci_iounmap(QPCIDevice *dev, void *data) diff --git a/tests/libqos/pci.h b/tests/libqos/pci.h index 3439431540..9ee048b154 100644 --- a/tests/libqos/pci.h +++ b/tests/libqos/pci.h @@ -41,7 +41,7 @@ struct QPCIBus void (*config_writel)(QPCIBus *bus, int devfn, uint8_t offset, uint32_t value); - void *(*iomap)(QPCIBus *bus, QPCIDevice *dev, int barno); + void *(*iomap)(QPCIBus *bus, QPCIDevice *dev, int barno, uint64_t *sizeptr); void (*iounmap)(QPCIBus *bus, void *data); }; @@ -74,7 +74,7 @@ void qpci_io_writeb(QPCIDevice *dev, void *data, uint8_t value); void qpci_io_writew(QPCIDevice *dev, void *data, uint16_t value); void qpci_io_writel(QPCIDevice *dev, void *data, uint32_t value); -void *qpci_iomap(QPCIDevice *dev, int barno); +void *qpci_iomap(QPCIDevice *dev, int barno, uint64_t *sizeptr); void qpci_iounmap(QPCIDevice *dev, void *data); #endif diff --git a/tests/libqtest.c b/tests/libqtest.c index 98e8f4b648..ed55686ce0 100644 --- a/tests/libqtest.c +++ b/tests/libqtest.c @@ -167,11 +167,12 @@ QTestState *qtest_init(const char *extra_args) if (s->qemu_pid == 0) { command = g_strdup_printf("exec %s " "-qtest unix:%s,nowait " - "-qtest-log /dev/null " + "-qtest-log %s " "-qmp unix:%s,nowait " "-machine accel=qtest " "-display none " "%s", qemu_binary, socket_path, + getenv("QTEST_LOG") ? "/dev/fd/2" : "/dev/null", qmp_socket_path, extra_args ?: ""); execlp("/bin/sh", "sh", "-c", command, NULL); @@ -358,6 +359,7 @@ static void qmp_response(JSONMessageParser *parser, QList *tokens) QDict *qtest_qmp_receive(QTestState *s) { QMPResponseParser qmp; + bool log = getenv("QTEST_LOG") != NULL; qmp.response = NULL; json_message_parser_init(&qmp.parser, qmp_response); @@ -375,6 +377,9 @@ QDict *qtest_qmp_receive(QTestState *s) exit(1); } + if (log) { + len = write(2, &c, 1); + } json_message_parser_feed(&qmp.parser, &c, 1); } json_message_parser_destroy(&qmp.parser); @@ -397,10 +402,14 @@ QDict *qtest_qmpv(QTestState *s, const char *fmt, va_list ap) /* No need to send anything for an empty QObject. */ if (qobj) { + int log = getenv("QTEST_LOG") != NULL; QString *qstr = qobject_to_json(qobj); const char *str = qstring_get_str(qstr); size_t size = qstring_get_length(qstr); + if (log) { + fprintf(stderr, "%s", str); + } /* Send QMP request */ socket_send(s->qmp_fd, str, size); @@ -639,6 +648,7 @@ void qtest_add_func(const char *str, void (*fn)) { gchar *path = g_strdup_printf("/%s/%s", qtest_get_arch(), str); g_test_add_func(path, fn); + g_free(path); } void qtest_memwrite(QTestState *s, uint64_t addr, const void *data, size_t size) @@ -654,6 +664,18 @@ void qtest_memwrite(QTestState *s, uint64_t addr, const void *data, size_t size) qtest_rsp(s, 0); } +void qtest_memset(QTestState *s, uint64_t addr, uint8_t pattern, size_t size) +{ + size_t i; + + qtest_sendf(s, "write 0x%" PRIx64 " 0x%zx 0x", addr, size); + for (i = 0; i < size; i++) { + qtest_sendf(s, "%02x", pattern); + } + qtest_sendf(s, "\n"); + qtest_rsp(s, 0); +} + QDict *qmp(const char *fmt, ...) { va_list ap; diff --git a/tests/libqtest.h b/tests/libqtest.h index 8f323c7030..1be0934f07 100644 --- a/tests/libqtest.h +++ b/tests/libqtest.h @@ -283,6 +283,17 @@ void qtest_memread(QTestState *s, uint64_t addr, void *data, size_t size); void qtest_memwrite(QTestState *s, uint64_t addr, const void *data, size_t size); /** + * qtest_memset: + * @s: #QTestState instance to operate on. + * @addr: Guest address to write to. + * @patt: Byte pattern to fill the guest memory region with. + * @size: Number of bytes to write. + * + * Write a pattern to guest memory. + */ +void qtest_memset(QTestState *s, uint64_t addr, uint8_t patt, size_t size); + +/** * qtest_clock_step_next: * @s: #QTestState instance to operate on. * @@ -621,6 +632,19 @@ static inline void memwrite(uint64_t addr, const void *data, size_t size) } /** + * qmemset: + * @addr: Guest address to write to. + * @patt: Byte pattern to fill the guest memory region with. + * @size: Number of bytes to write. + * + * Write a pattern to guest memory. + */ +static inline void qmemset(uint64_t addr, uint8_t patt, size_t size) +{ + qtest_memset(global_qtest, addr, patt, size); +} + +/** * clock_step_next: * * Advance the QEMU_CLOCK_VIRTUAL to the next deadline. diff --git a/tests/usb-hcd-ehci-test.c b/tests/usb-hcd-ehci-test.c index bcdf62fc3b..c990492835 100644 --- a/tests/usb-hcd-ehci-test.c +++ b/tests/usb-hcd-ehci-test.c @@ -34,7 +34,7 @@ static void pci_init_one(struct qhc *hc, uint32_t devfn, int bar) hc->dev = qpci_device_find(pcibus, devfn); g_assert(hc->dev != NULL); qpci_device_enable(hc->dev); - hc->base = qpci_iomap(hc->dev, bar); + hc->base = qpci_iomap(hc->dev, bar, NULL); g_assert(hc->base != NULL); } |