diff options
44 files changed, 3342 insertions, 782 deletions
diff --git a/configure b/configure index a3f0522e8f..e8798cec79 100755 --- a/configure +++ b/configure @@ -4915,6 +4915,21 @@ if compile_prog "" "" ; then fi ########################################## +# check for utmpx.h, it is missing e.g. on OpenBSD + +have_utmpx=no +cat > $TMPC << EOF +#include <utmpx.h> +struct utmpx user_info; +int main(void) { + return 0; +} +EOF +if compile_prog "" "" ; then + have_utmpx=yes +fi + +########################################## # End of CC checks # After here, no more $cc or $ld runs @@ -5959,6 +5974,10 @@ if test "$have_static_assert" = "yes" ; then echo "CONFIG_STATIC_ASSERT=y" >> $config_host_mak fi +if test "$have_utmpx" = "yes" ; then + echo "HAVE_UTMPX=y" >> $config_host_mak +fi + # Hold two types of flag: # CONFIG_THREAD_SETNAME_BYTHREAD - we've got a way of setting the name on # a thread we have a handle to diff --git a/docs/devel/bitmaps.md b/docs/devel/bitmaps.md deleted file mode 100644 index a2e8d51163..0000000000 --- a/docs/devel/bitmaps.md +++ /dev/null @@ -1,505 +0,0 @@ -<!-- -Copyright 2015 John Snow <jsnow@redhat.com> and Red Hat, Inc. -All rights reserved. - -This file is licensed via The FreeBSD Documentation License, the full text of -which is included at the end of this document. ---> - -# Dirty Bitmaps and Incremental Backup - -* Dirty Bitmaps are objects that track which data needs to be backed up for the - next incremental backup. - -* Dirty bitmaps can be created at any time and attached to any node - (not just complete drives.) - -## Dirty Bitmap Names - -* A dirty bitmap's name is unique to the node, but bitmaps attached to different - nodes can share the same name. - -* Dirty bitmaps created for internal use by QEMU may be anonymous and have no - name, but any user-created bitmaps may not be. There can be any number of - anonymous bitmaps per node. - -* The name of a user-created bitmap must not be empty (""). - -## Bitmap Modes - -* A Bitmap can be "frozen," which means that it is currently in-use by a backup - operation and cannot be deleted, renamed, written to, reset, - etc. - -* The normal operating mode for a bitmap is "active." - -## Basic QMP Usage - -### Supported Commands ### - -* block-dirty-bitmap-add -* block-dirty-bitmap-remove -* block-dirty-bitmap-clear - -### Creation - -* To create a new bitmap, enabled, on the drive with id=drive0: - -```json -{ "execute": "block-dirty-bitmap-add", - "arguments": { - "node": "drive0", - "name": "bitmap0" - } -} -``` - -* This bitmap will have a default granularity that matches the cluster size of - its associated drive, if available, clamped to between [4KiB, 64KiB]. - The current default for qcow2 is 64KiB. - -* To create a new bitmap that tracks changes in 32KiB segments: - -```json -{ "execute": "block-dirty-bitmap-add", - "arguments": { - "node": "drive0", - "name": "bitmap0", - "granularity": 32768 - } -} -``` - -### Deletion - -* Bitmaps that are frozen cannot be deleted. - -* Deleting the bitmap does not impact any other bitmaps attached to the same - node, nor does it affect any backups already created from this node. - -* Because bitmaps are only unique to the node to which they are attached, - you must specify the node/drive name here, too. - -```json -{ "execute": "block-dirty-bitmap-remove", - "arguments": { - "node": "drive0", - "name": "bitmap0" - } -} -``` - -### Resetting - -* Resetting a bitmap will clear all information it holds. - -* An incremental backup created from an empty bitmap will copy no data, - as if nothing has changed. - -```json -{ "execute": "block-dirty-bitmap-clear", - "arguments": { - "node": "drive0", - "name": "bitmap0" - } -} -``` - -## Transactions - -### Justification - -Bitmaps can be safely modified when the VM is paused or halted by using -the basic QMP commands. For instance, you might perform the following actions: - -1. Boot the VM in a paused state. -2. Create a full drive backup of drive0. -3. Create a new bitmap attached to drive0. -4. Resume execution of the VM. -5. Incremental backups are ready to be created. - -At this point, the bitmap and drive backup would be correctly in sync, -and incremental backups made from this point forward would be correctly aligned -to the full drive backup. - -This is not particularly useful if we decide we want to start incremental -backups after the VM has been running for a while, for which we will need to -perform actions such as the following: - -1. Boot the VM and begin execution. -2. Using a single transaction, perform the following operations: - * Create bitmap0. - * Create a full drive backup of drive0. -3. Incremental backups are now ready to be created. - -### Supported Bitmap Transactions - -* block-dirty-bitmap-add -* block-dirty-bitmap-clear - -The usages are identical to their respective QMP commands, but see below -for examples. - -### Example: New Incremental Backup - -As outlined in the justification, perhaps we want to create a new incremental -backup chain attached to a drive. - -```json -{ "execute": "transaction", - "arguments": { - "actions": [ - {"type": "block-dirty-bitmap-add", - "data": {"node": "drive0", "name": "bitmap0"} }, - {"type": "drive-backup", - "data": {"device": "drive0", "target": "/path/to/full_backup.img", - "sync": "full", "format": "qcow2"} } - ] - } -} -``` - -### Example: New Incremental Backup Anchor Point - -Maybe we just want to create a new full backup with an existing bitmap and -want to reset the bitmap to track the new chain. - -```json -{ "execute": "transaction", - "arguments": { - "actions": [ - {"type": "block-dirty-bitmap-clear", - "data": {"node": "drive0", "name": "bitmap0"} }, - {"type": "drive-backup", - "data": {"device": "drive0", "target": "/path/to/new_full_backup.img", - "sync": "full", "format": "qcow2"} } - ] - } -} -``` - -## Incremental Backups - -The star of the show. - -**Nota Bene!** Only incremental backups of entire drives are supported for now. -So despite the fact that you can attach a bitmap to any arbitrary node, they are -only currently useful when attached to the root node. This is because -drive-backup only supports drives/devices instead of arbitrary nodes. - -### Example: First Incremental Backup - -1. Create a full backup and sync it to the dirty bitmap, as in the transactional -examples above; or with the VM offline, manually create a full copy and then -create a new bitmap before the VM begins execution. - - * Let's assume the full backup is named 'full_backup.img'. - * Let's assume the bitmap you created is 'bitmap0' attached to 'drive0'. - -2. Create a destination image for the incremental backup that utilizes the -full backup as a backing image. - - * Let's assume it is named 'incremental.0.img'. - - ```sh - # qemu-img create -f qcow2 incremental.0.img -b full_backup.img -F qcow2 - ``` - -3. Issue the incremental backup command: - - ```json - { "execute": "drive-backup", - "arguments": { - "device": "drive0", - "bitmap": "bitmap0", - "target": "incremental.0.img", - "format": "qcow2", - "sync": "incremental", - "mode": "existing" - } - } - ``` - -### Example: Second Incremental Backup - -1. Create a new destination image for the incremental backup that points to the - previous one, e.g.: 'incremental.1.img' - - ```sh - # qemu-img create -f qcow2 incremental.1.img -b incremental.0.img -F qcow2 - ``` - -2. Issue a new incremental backup command. The only difference here is that we - have changed the target image below. - - ```json - { "execute": "drive-backup", - "arguments": { - "device": "drive0", - "bitmap": "bitmap0", - "target": "incremental.1.img", - "format": "qcow2", - "sync": "incremental", - "mode": "existing" - } - } - ``` - -## Errors - -* In the event of an error that occurs after a backup job is successfully - launched, either by a direct QMP command or a QMP transaction, the user - will receive a BLOCK_JOB_COMPLETE event with a failure message, accompanied - by a BLOCK_JOB_ERROR event. - -* In the case of an event being cancelled, the user will receive a - BLOCK_JOB_CANCELLED event instead of a pair of COMPLETE and ERROR events. - -* In either case, the incremental backup data contained within the bitmap is - safely rolled back, and the data within the bitmap is not lost. The image - file created for the failed attempt can be safely deleted. - -* Once the underlying problem is fixed (e.g. more storage space is freed up), - you can simply retry the incremental backup command with the same bitmap. - -### Example - -1. Create a target image: - - ```sh - # qemu-img create -f qcow2 incremental.0.img -b full_backup.img -F qcow2 - ``` - -2. Attempt to create an incremental backup via QMP: - - ```json - { "execute": "drive-backup", - "arguments": { - "device": "drive0", - "bitmap": "bitmap0", - "target": "incremental.0.img", - "format": "qcow2", - "sync": "incremental", - "mode": "existing" - } - } - ``` - -3. Receive an event notifying us of failure: - - ```json - { "timestamp": { "seconds": 1424709442, "microseconds": 844524 }, - "data": { "speed": 0, "offset": 0, "len": 67108864, - "error": "No space left on device", - "device": "drive1", "type": "backup" }, - "event": "BLOCK_JOB_COMPLETED" } - ``` - -4. Delete the failed incremental, and re-create the image. - - ```sh - # rm incremental.0.img - # qemu-img create -f qcow2 incremental.0.img -b full_backup.img -F qcow2 - ``` - -5. Retry the command after fixing the underlying problem, - such as freeing up space on the backup volume: - - ```json - { "execute": "drive-backup", - "arguments": { - "device": "drive0", - "bitmap": "bitmap0", - "target": "incremental.0.img", - "format": "qcow2", - "sync": "incremental", - "mode": "existing" - } - } - ``` - -6. Receive confirmation that the job completed successfully: - - ```json - { "timestamp": { "seconds": 1424709668, "microseconds": 526525 }, - "data": { "device": "drive1", "type": "backup", - "speed": 0, "len": 67108864, "offset": 67108864}, - "event": "BLOCK_JOB_COMPLETED" } - ``` - -### Partial Transactional Failures - -* Sometimes, a transaction will succeed in launching and return success, - but then later the backup jobs themselves may fail. It is possible that - a management application may have to deal with a partial backup failure - after a successful transaction. - -* If multiple backup jobs are specified in a single transaction, when one of - them fails, it will not interact with the other backup jobs in any way. - -* The job(s) that succeeded will clear the dirty bitmap associated with the - operation, but the job(s) that failed will not. It is not "safe" to delete - any incremental backups that were created successfully in this scenario, - even though others failed. - -#### Example - -* QMP example highlighting two backup jobs: - - ```json - { "execute": "transaction", - "arguments": { - "actions": [ - { "type": "drive-backup", - "data": { "device": "drive0", "bitmap": "bitmap0", - "format": "qcow2", "mode": "existing", - "sync": "incremental", "target": "d0-incr-1.qcow2" } }, - { "type": "drive-backup", - "data": { "device": "drive1", "bitmap": "bitmap1", - "format": "qcow2", "mode": "existing", - "sync": "incremental", "target": "d1-incr-1.qcow2" } }, - ] - } - } - ``` - -* QMP example response, highlighting one success and one failure: - * Acknowledgement that the Transaction was accepted and jobs were launched: - ```json - { "return": {} } - ``` - - * Later, QEMU sends notice that the first job was completed: - ```json - { "timestamp": { "seconds": 1447192343, "microseconds": 615698 }, - "data": { "device": "drive0", "type": "backup", - "speed": 0, "len": 67108864, "offset": 67108864 }, - "event": "BLOCK_JOB_COMPLETED" - } - ``` - - * Later yet, QEMU sends notice that the second job has failed: - ```json - { "timestamp": { "seconds": 1447192399, "microseconds": 683015 }, - "data": { "device": "drive1", "action": "report", - "operation": "read" }, - "event": "BLOCK_JOB_ERROR" } - ``` - - ```json - { "timestamp": { "seconds": 1447192399, "microseconds": 685853 }, - "data": { "speed": 0, "offset": 0, "len": 67108864, - "error": "Input/output error", - "device": "drive1", "type": "backup" }, - "event": "BLOCK_JOB_COMPLETED" } - -* In the above example, "d0-incr-1.qcow2" is valid and must be kept, - but "d1-incr-1.qcow2" is invalid and should be deleted. If a VM-wide - incremental backup of all drives at a point-in-time is to be made, - new backups for both drives will need to be made, taking into account - that a new incremental backup for drive0 needs to be based on top of - "d0-incr-1.qcow2." - -### Grouped Completion Mode - -* While jobs launched by transactions normally complete or fail on their own, - it is possible to instruct them to complete or fail together as a group. - -* QMP transactions take an optional properties structure that can affect - the semantics of the transaction. - -* The "completion-mode" transaction property can be either "individual" - which is the default, legacy behavior described above, or "grouped," - a new behavior detailed below. - -* Delayed Completion: In grouped completion mode, no jobs will report - success until all jobs are ready to report success. - -* Grouped failure: If any job fails in grouped completion mode, all remaining - jobs will be cancelled. Any incremental backups will restore their dirty - bitmap objects as if no backup command was ever issued. - - * Regardless of if QEMU reports a particular incremental backup job as - CANCELLED or as an ERROR, the in-memory bitmap will be restored. - -#### Example - -* Here's the same example scenario from above with the new property: - - ```json - { "execute": "transaction", - "arguments": { - "actions": [ - { "type": "drive-backup", - "data": { "device": "drive0", "bitmap": "bitmap0", - "format": "qcow2", "mode": "existing", - "sync": "incremental", "target": "d0-incr-1.qcow2" } }, - { "type": "drive-backup", - "data": { "device": "drive1", "bitmap": "bitmap1", - "format": "qcow2", "mode": "existing", - "sync": "incremental", "target": "d1-incr-1.qcow2" } }, - ], - "properties": { - "completion-mode": "grouped" - } - } - } - ``` - -* QMP example response, highlighting a failure for drive2: - * Acknowledgement that the Transaction was accepted and jobs were launched: - ```json - { "return": {} } - ``` - - * Later, QEMU sends notice that the second job has errored out, - but that the first job was also cancelled: - ```json - { "timestamp": { "seconds": 1447193702, "microseconds": 632377 }, - "data": { "device": "drive1", "action": "report", - "operation": "read" }, - "event": "BLOCK_JOB_ERROR" } - ``` - - ```json - { "timestamp": { "seconds": 1447193702, "microseconds": 640074 }, - "data": { "speed": 0, "offset": 0, "len": 67108864, - "error": "Input/output error", - "device": "drive1", "type": "backup" }, - "event": "BLOCK_JOB_COMPLETED" } - ``` - - ```json - { "timestamp": { "seconds": 1447193702, "microseconds": 640163 }, - "data": { "device": "drive0", "type": "backup", "speed": 0, - "len": 67108864, "offset": 16777216 }, - "event": "BLOCK_JOB_CANCELLED" } - ``` - -<!-- -The FreeBSD Documentation License - -Redistribution and use in source (Markdown) and 'compiled' forms (SGML, HTML, -PDF, PostScript, RTF and so forth) with or without modification, are permitted -provided that the following conditions are met: - -Redistributions of source code (Markdown) must retain the above copyright -notice, this list of conditions and the following disclaimer of this file -unmodified. - -Redistributions in compiled form (transformed to other DTDs, converted to PDF, -PostScript, RTF and other formats) must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -THIS DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---> diff --git a/docs/interop/bitmaps.rst b/docs/interop/bitmaps.rst new file mode 100644 index 0000000000..7bcfe7f461 --- /dev/null +++ b/docs/interop/bitmaps.rst @@ -0,0 +1,555 @@ +.. + Copyright 2015 John Snow <jsnow@redhat.com> and Red Hat, Inc. + All rights reserved. + + This file is licensed via The FreeBSD Documentation License, the full + text of which is included at the end of this document. + +==================================== +Dirty Bitmaps and Incremental Backup +==================================== + +- Dirty Bitmaps are objects that track which data needs to be backed up + for the next incremental backup. + +- Dirty bitmaps can be created at any time and attached to any node + (not just complete drives). + +.. contents:: + +Dirty Bitmap Names +------------------ + +- A dirty bitmap's name is unique to the node, but bitmaps attached to + different nodes can share the same name. + +- Dirty bitmaps created for internal use by QEMU may be anonymous and + have no name, but any user-created bitmaps must have a name. There + can be any number of anonymous bitmaps per node. + +- The name of a user-created bitmap must not be empty (""). + +Bitmap Modes +------------ + +- A bitmap can be "frozen," which means that it is currently in-use by + a backup operation and cannot be deleted, renamed, written to, reset, + etc. + +- The normal operating mode for a bitmap is "active." + +Basic QMP Usage +--------------- + +Supported Commands +~~~~~~~~~~~~~~~~~~ + +- ``block-dirty-bitmap-add`` +- ``block-dirty-bitmap-remove`` +- ``block-dirty-bitmap-clear`` + +Creation +~~~~~~~~ + +- To create a new bitmap, enabled, on the drive with id=drive0: + +.. code:: json + + { "execute": "block-dirty-bitmap-add", + "arguments": { + "node": "drive0", + "name": "bitmap0" + } + } + +- This bitmap will have a default granularity that matches the cluster + size of its associated drive, if available, clamped to between [4KiB, + 64KiB]. The current default for qcow2 is 64KiB. + +- To create a new bitmap that tracks changes in 32KiB segments: + +.. code:: json + + { "execute": "block-dirty-bitmap-add", + "arguments": { + "node": "drive0", + "name": "bitmap0", + "granularity": 32768 + } + } + +Deletion +~~~~~~~~ + +- Bitmaps that are frozen cannot be deleted. + +- Deleting the bitmap does not impact any other bitmaps attached to the + same node, nor does it affect any backups already created from this + node. + +- Because bitmaps are only unique to the node to which they are + attached, you must specify the node/drive name here, too. + +.. code:: json + + { "execute": "block-dirty-bitmap-remove", + "arguments": { + "node": "drive0", + "name": "bitmap0" + } + } + +Resetting +~~~~~~~~~ + +- Resetting a bitmap will clear all information it holds. + +- An incremental backup created from an empty bitmap will copy no data, + as if nothing has changed. + +.. code:: json + + { "execute": "block-dirty-bitmap-clear", + "arguments": { + "node": "drive0", + "name": "bitmap0" + } + } + +Transactions +------------ + +Justification +~~~~~~~~~~~~~ + +Bitmaps can be safely modified when the VM is paused or halted by using +the basic QMP commands. For instance, you might perform the following +actions: + +1. Boot the VM in a paused state. +2. Create a full drive backup of drive0. +3. Create a new bitmap attached to drive0. +4. Resume execution of the VM. +5. Incremental backups are ready to be created. + +At this point, the bitmap and drive backup would be correctly in sync, +and incremental backups made from this point forward would be correctly +aligned to the full drive backup. + +This is not particularly useful if we decide we want to start +incremental backups after the VM has been running for a while, for which +we will need to perform actions such as the following: + +1. Boot the VM and begin execution. +2. Using a single transaction, perform the following operations: + + - Create ``bitmap0``. + - Create a full drive backup of ``drive0``. + +3. Incremental backups are now ready to be created. + +Supported Bitmap Transactions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- ``block-dirty-bitmap-add`` +- ``block-dirty-bitmap-clear`` + +The usages are identical to their respective QMP commands, but see below +for examples. + +Example: New Incremental Backup +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +As outlined in the justification, perhaps we want to create a new +incremental backup chain attached to a drive. + +.. code:: json + + { "execute": "transaction", + "arguments": { + "actions": [ + {"type": "block-dirty-bitmap-add", + "data": {"node": "drive0", "name": "bitmap0"} }, + {"type": "drive-backup", + "data": {"device": "drive0", "target": "/path/to/full_backup.img", + "sync": "full", "format": "qcow2"} } + ] + } + } + +Example: New Incremental Backup Anchor Point +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Maybe we just want to create a new full backup with an existing bitmap +and want to reset the bitmap to track the new chain. + +.. code:: json + + { "execute": "transaction", + "arguments": { + "actions": [ + {"type": "block-dirty-bitmap-clear", + "data": {"node": "drive0", "name": "bitmap0"} }, + {"type": "drive-backup", + "data": {"device": "drive0", "target": "/path/to/new_full_backup.img", + "sync": "full", "format": "qcow2"} } + ] + } + } + +Incremental Backups +------------------- + +The star of the show. + +**Nota Bene!** Only incremental backups of entire drives are supported +for now. So despite the fact that you can attach a bitmap to any +arbitrary node, they are only currently useful when attached to the root +node. This is because drive-backup only supports drives/devices instead +of arbitrary nodes. + +Example: First Incremental Backup +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1. Create a full backup and sync it to the dirty bitmap, as in the + transactional examples above; or with the VM offline, manually create + a full copy and then create a new bitmap before the VM begins + execution. + + - Let's assume the full backup is named ``full_backup.img``. + - Let's assume the bitmap you created is ``bitmap0`` attached to + ``drive0``. + +2. Create a destination image for the incremental backup that utilizes + the full backup as a backing image. + + - Let's assume the new incremental image is named + ``incremental.0.img``. + + .. code:: bash + + $ qemu-img create -f qcow2 incremental.0.img -b full_backup.img -F qcow2 + +3. Issue the incremental backup command: + + .. code:: json + + { "execute": "drive-backup", + "arguments": { + "device": "drive0", + "bitmap": "bitmap0", + "target": "incremental.0.img", + "format": "qcow2", + "sync": "incremental", + "mode": "existing" + } + } + +Example: Second Incremental Backup +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1. Create a new destination image for the incremental backup that points + to the previous one, e.g.: ``incremental.1.img`` + + .. code:: bash + + $ qemu-img create -f qcow2 incremental.1.img -b incremental.0.img -F qcow2 + +2. Issue a new incremental backup command. The only difference here is + that we have changed the target image below. + + .. code:: json + + { "execute": "drive-backup", + "arguments": { + "device": "drive0", + "bitmap": "bitmap0", + "target": "incremental.1.img", + "format": "qcow2", + "sync": "incremental", + "mode": "existing" + } + } + +Errors +------ + +- In the event of an error that occurs after a backup job is + successfully launched, either by a direct QMP command or a QMP + transaction, the user will receive a ``BLOCK_JOB_COMPLETE`` event with + a failure message, accompanied by a ``BLOCK_JOB_ERROR`` event. + +- In the case of an event being cancelled, the user will receive a + ``BLOCK_JOB_CANCELLED`` event instead of a pair of COMPLETE and ERROR + events. + +- In either case, the incremental backup data contained within the + bitmap is safely rolled back, and the data within the bitmap is not + lost. The image file created for the failed attempt can be safely + deleted. + +- Once the underlying problem is fixed (e.g. more storage space is + freed up), you can simply retry the incremental backup command with + the same bitmap. + +Example +~~~~~~~ + +1. Create a target image: + + .. code:: bash + + $ qemu-img create -f qcow2 incremental.0.img -b full_backup.img -F qcow2 + +2. Attempt to create an incremental backup via QMP: + + .. code:: json + + { "execute": "drive-backup", + "arguments": { + "device": "drive0", + "bitmap": "bitmap0", + "target": "incremental.0.img", + "format": "qcow2", + "sync": "incremental", + "mode": "existing" + } + } + +3. Receive an event notifying us of failure: + + .. code:: json + + { "timestamp": { "seconds": 1424709442, "microseconds": 844524 }, + "data": { "speed": 0, "offset": 0, "len": 67108864, + "error": "No space left on device", + "device": "drive1", "type": "backup" }, + "event": "BLOCK_JOB_COMPLETED" } + +4. Delete the failed incremental, and re-create the image. + + .. code:: bash + + $ rm incremental.0.img + $ qemu-img create -f qcow2 incremental.0.img -b full_backup.img -F qcow2 + +5. Retry the command after fixing the underlying problem, such as + freeing up space on the backup volume: + + .. code:: json + + { "execute": "drive-backup", + "arguments": { + "device": "drive0", + "bitmap": "bitmap0", + "target": "incremental.0.img", + "format": "qcow2", + "sync": "incremental", + "mode": "existing" + } + } + +6. Receive confirmation that the job completed successfully: + + .. code:: json + + { "timestamp": { "seconds": 1424709668, "microseconds": 526525 }, + "data": { "device": "drive1", "type": "backup", + "speed": 0, "len": 67108864, "offset": 67108864}, + "event": "BLOCK_JOB_COMPLETED" } + +Partial Transactional Failures +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Sometimes, a transaction will succeed in launching and return + success, but then later the backup jobs themselves may fail. It is + possible that a management application may have to deal with a + partial backup failure after a successful transaction. + +- If multiple backup jobs are specified in a single transaction, when + one of them fails, it will not interact with the other backup jobs in + any way. + +- The job(s) that succeeded will clear the dirty bitmap associated with + the operation, but the job(s) that failed will not. It is not "safe" + to delete any incremental backups that were created successfully in + this scenario, even though others failed. + +Example +^^^^^^^ + +- QMP example highlighting two backup jobs: + + .. code:: json + + { "execute": "transaction", + "arguments": { + "actions": [ + { "type": "drive-backup", + "data": { "device": "drive0", "bitmap": "bitmap0", + "format": "qcow2", "mode": "existing", + "sync": "incremental", "target": "d0-incr-1.qcow2" } }, + { "type": "drive-backup", + "data": { "device": "drive1", "bitmap": "bitmap1", + "format": "qcow2", "mode": "existing", + "sync": "incremental", "target": "d1-incr-1.qcow2" } }, + ] + } + } + +- QMP example response, highlighting one success and one failure: + + - Acknowledgement that the Transaction was accepted and jobs were + launched: + + .. code:: json + + { "return": {} } + + - Later, QEMU sends notice that the first job was completed: + + .. code:: json + + { "timestamp": { "seconds": 1447192343, "microseconds": 615698 }, + "data": { "device": "drive0", "type": "backup", + "speed": 0, "len": 67108864, "offset": 67108864 }, + "event": "BLOCK_JOB_COMPLETED" + } + + - Later yet, QEMU sends notice that the second job has failed: + + .. code:: json + + { "timestamp": { "seconds": 1447192399, "microseconds": 683015 }, + "data": { "device": "drive1", "action": "report", + "operation": "read" }, + "event": "BLOCK_JOB_ERROR" } + + .. code:: json + + { "timestamp": { "seconds": 1447192399, "microseconds": + 685853 }, "data": { "speed": 0, "offset": 0, "len": 67108864, + "error": "Input/output error", "device": "drive1", "type": + "backup" }, "event": "BLOCK_JOB_COMPLETED" } + +- In the above example, ``d0-incr-1.qcow2`` is valid and must be kept, + but ``d1-incr-1.qcow2`` is invalid and should be deleted. If a VM-wide + incremental backup of all drives at a point-in-time is to be made, + new backups for both drives will need to be made, taking into account + that a new incremental backup for drive0 needs to be based on top of + ``d0-incr-1.qcow2``. + +Grouped Completion Mode +~~~~~~~~~~~~~~~~~~~~~~~ + +- While jobs launched by transactions normally complete or fail on + their own, it is possible to instruct them to complete or fail + together as a group. + +- QMP transactions take an optional properties structure that can + affect the semantics of the transaction. + +- The "completion-mode" transaction property can be either "individual" + which is the default, legacy behavior described above, or "grouped," + a new behavior detailed below. + +- Delayed Completion: In grouped completion mode, no jobs will report + success until all jobs are ready to report success. + +- Grouped failure: If any job fails in grouped completion mode, all + remaining jobs will be cancelled. Any incremental backups will + restore their dirty bitmap objects as if no backup command was ever + issued. + + - Regardless of if QEMU reports a particular incremental backup job + as CANCELLED or as an ERROR, the in-memory bitmap will be + restored. + +Example +^^^^^^^ + +- Here's the same example scenario from above with the new property: + + .. code:: json + + { "execute": "transaction", + "arguments": { + "actions": [ + { "type": "drive-backup", + "data": { "device": "drive0", "bitmap": "bitmap0", + "format": "qcow2", "mode": "existing", + "sync": "incremental", "target": "d0-incr-1.qcow2" } }, + { "type": "drive-backup", + "data": { "device": "drive1", "bitmap": "bitmap1", + "format": "qcow2", "mode": "existing", + "sync": "incremental", "target": "d1-incr-1.qcow2" } }, + ], + "properties": { + "completion-mode": "grouped" + } + } + } + +- QMP example response, highlighting a failure for ``drive2``: + + - Acknowledgement that the Transaction was accepted and jobs were + launched: + + .. code:: json + + { "return": {} } + + - Later, QEMU sends notice that the second job has errored out, but + that the first job was also cancelled: + + .. code:: json + + { "timestamp": { "seconds": 1447193702, "microseconds": 632377 }, + "data": { "device": "drive1", "action": "report", + "operation": "read" }, + "event": "BLOCK_JOB_ERROR" } + + .. code:: json + + { "timestamp": { "seconds": 1447193702, "microseconds": 640074 }, + "data": { "speed": 0, "offset": 0, "len": 67108864, + "error": "Input/output error", + "device": "drive1", "type": "backup" }, + "event": "BLOCK_JOB_COMPLETED" } + + .. code:: json + + { "timestamp": { "seconds": 1447193702, "microseconds": 640163 }, + "data": { "device": "drive0", "type": "backup", "speed": 0, + "len": 67108864, "offset": 16777216 }, + "event": "BLOCK_JOB_CANCELLED" } + +.. raw:: html + + <!-- + The FreeBSD Documentation License + + Redistribution and use in source (Markdown) and 'compiled' forms (SGML, HTML, + PDF, PostScript, RTF and so forth) with or without modification, are permitted + provided that the following conditions are met: + + Redistributions of source code (Markdown) must retain the above copyright + notice, this list of conditions and the following disclaimer of this file + unmodified. + + Redistributions in compiled form (transformed to other DTDs, converted to PDF, + PostScript, RTF and other formats) must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + + THIS DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + --> diff --git a/docs/interop/live-block-operations.rst b/docs/interop/live-block-operations.rst new file mode 100644 index 0000000000..5f0179749f --- /dev/null +++ b/docs/interop/live-block-operations.rst @@ -0,0 +1,1088 @@ +.. + Copyright (C) 2017 Red Hat Inc. + + This work is licensed under the terms of the GNU GPL, version 2 or + later. See the COPYING file in the top-level directory. + +============================ +Live Block Device Operations +============================ + +QEMU Block Layer currently (as of QEMU 2.9) supports four major kinds of +live block device jobs -- stream, commit, mirror, and backup. These can +be used to manipulate disk image chains to accomplish certain tasks, +namely: live copy data from backing files into overlays; shorten long +disk image chains by merging data from overlays into backing files; live +synchronize data from a disk image chain (including current active disk) +to another target image; and point-in-time (and incremental) backups of +a block device. Below is a description of the said block (QMP) +primitives, and some (non-exhaustive list of) examples to illustrate +their use. + +.. note:: + The file ``qapi/block-core.json`` in the QEMU source tree has the + canonical QEMU API (QAPI) schema documentation for the QMP + primitives discussed here. + +.. todo (kashyapc):: Remove the ".. contents::" directive when Sphinx is + integrated. + +.. contents:: + +Disk image backing chain notation +--------------------------------- + +A simple disk image chain. (This can be created live using QMP +``blockdev-snapshot-sync``, or offline via ``qemu-img``):: + + (Live QEMU) + | + . + V + + [A] <----- [B] + + (backing file) (overlay) + +The arrow can be read as: Image [A] is the backing file of disk image +[B]. And live QEMU is currently writing to image [B], consequently, it +is also referred to as the "active layer". + +There are two kinds of terminology that are common when referring to +files in a disk image backing chain: + +(1) Directional: 'base' and 'top'. Given the simple disk image chain + above, image [A] can be referred to as 'base', and image [B] as + 'top'. (This terminology can be seen in in QAPI schema file, + block-core.json.) + +(2) Relational: 'backing file' and 'overlay'. Again, taking the same + simple disk image chain from the above, disk image [A] is referred + to as the backing file, and image [B] as overlay. + + Throughout this document, we will use the relational terminology. + +.. important:: + The overlay files can generally be any format that supports a + backing file, although QCOW2 is the preferred format and the one + used in this document. + + +Brief overview of live block QMP primitives +------------------------------------------- + +The following are the four different kinds of live block operations that +QEMU block layer supports. + +(1) ``block-stream``: Live copy of data from backing files into overlay + files. + + .. note:: Once the 'stream' operation has finished, three things to + note: + + (a) QEMU rewrites the backing chain to remove + reference to the now-streamed and redundant backing + file; + + (b) the streamed file *itself* won't be removed by QEMU, + and must be explicitly discarded by the user; + + (c) the streamed file remains valid -- i.e. further + overlays can be created based on it. Refer the + ``block-stream`` section further below for more + details. + +(2) ``block-commit``: Live merge of data from overlay files into backing + files (with the optional goal of removing the overlay file from the + chain). Since QEMU 2.0, this includes "active ``block-commit``" + (i.e. merge the current active layer into the base image). + + .. note:: Once the 'commit' operation has finished, there are three + things to note here as well: + + (a) QEMU rewrites the backing chain to remove reference + to now-redundant overlay images that have been + committed into a backing file; + + (b) the committed file *itself* won't be removed by QEMU + -- it ought to be manually removed; + + (c) however, unlike in the case of ``block-stream``, the + intermediate images will be rendered invalid -- i.e. + no more further overlays can be created based on + them. Refer the ``block-commit`` section further + below for more details. + +(3) ``drive-mirror`` (and ``blockdev-mirror``): Synchronize a running + disk to another image. + +(4) ``drive-backup`` (and ``blockdev-backup``): Point-in-time (live) copy + of a block device to a destination. + + +.. _`Interacting with a QEMU instance`: + +Interacting with a QEMU instance +-------------------------------- + +To show some example invocations of command-line, we will use the +following invocation of QEMU, with a QMP server running over UNIX +socket:: + + $ ./x86_64-softmmu/qemu-system-x86_64 -display none -nodefconfig \ + -M q35 -nodefaults -m 512 \ + -blockdev node-name=node-A,driver=qcow2,file.driver=file,file.node-name=file,file.filename=./a.qcow2 \ + -device virtio-blk,drive=node-A,id=virtio0 \ + -monitor stdio -qmp unix:/tmp/qmp-sock,server,nowait + +The ``-blockdev`` command-line option, used above, is available from +QEMU 2.9 onwards. In the above invocation, notice the ``node-name`` +parameter that is used to refer to the disk image a.qcow2 ('node-A') -- +this is a cleaner way to refer to a disk image (as opposed to referring +to it by spelling out file paths). So, we will continue to designate a +``node-name`` to each further disk image created (either via +``blockdev-snapshot-sync``, or ``blockdev-add``) as part of the disk +image chain, and continue to refer to the disks using their +``node-name`` (where possible, because ``block-commit`` does not yet, as +of QEMU 2.9, accept ``node-name`` parameter) when performing various +block operations. + +To interact with the QEMU instance launched above, we will use the +``qmp-shell`` utility (located at: ``qemu/scripts/qmp``, as part of the +QEMU source directory), which takes key-value pairs for QMP commands. +Invoke it as below (which will also print out the complete raw JSON +syntax for reference -- examples in the following sections):: + + $ ./qmp-shell -v -p /tmp/qmp-sock + (QEMU) + +.. note:: + In the event we have to repeat a certain QMP command, we will: for + the first occurrence of it, show the ``qmp-shell`` invocation, *and* + the corresponding raw JSON QMP syntax; but for subsequent + invocations, present just the ``qmp-shell`` syntax, and omit the + equivalent JSON output. + + +Example disk image chain +------------------------ + +We will use the below disk image chain (and occasionally spelling it +out where appropriate) when discussing various primitives:: + + [A] <-- [B] <-- [C] <-- [D] + +Where [A] is the original base image; [B] and [C] are intermediate +overlay images; image [D] is the active layer -- i.e. live QEMU is +writing to it. (The rule of thumb is: live QEMU will always be pointing +to the rightmost image in a disk image chain.) + +The above image chain can be created by invoking +``blockdev-snapshot-sync`` commands as following (which shows the +creation of overlay image [B]) using the ``qmp-shell`` (our invocation +also prints the raw JSON invocation of it):: + + (QEMU) blockdev-snapshot-sync node-name=node-A snapshot-file=b.qcow2 snapshot-node-name=node-B format=qcow2 + { + "execute": "blockdev-snapshot-sync", + "arguments": { + "node-name": "node-A", + "snapshot-file": "b.qcow2", + "format": "qcow2", + "snapshot-node-name": "node-B" + } + } + +Here, "node-A" is the name QEMU internally uses to refer to the base +image [A] -- it is the backing file, based on which the overlay image, +[B], is created. + +To create the rest of the overlay images, [C], and [D] (omitting the raw +JSON output for brevity):: + + (QEMU) blockdev-snapshot-sync node-name=node-B snapshot-file=c.qcow2 snapshot-node-name=node-C format=qcow2 + (QEMU) blockdev-snapshot-sync node-name=node-C snapshot-file=d.qcow2 snapshot-node-name=node-D format=qcow2 + + +A note on points-in-time vs file names +-------------------------------------- + +In our disk image chain:: + + [A] <-- [B] <-- [C] <-- [D] + +We have *three* points in time and an active layer: + +- Point 1: Guest state when [B] was created is contained in file [A] +- Point 2: Guest state when [C] was created is contained in [A] + [B] +- Point 3: Guest state when [D] was created is contained in + [A] + [B] + [C] +- Active layer: Current guest state is contained in [A] + [B] + [C] + + [D] + +Therefore, be aware with naming choices: + +- Naming a file after the time it is created is misleading -- the + guest data for that point in time is *not* contained in that file + (as explained earlier) +- Rather, think of files as a *delta* from the backing file + + +Live block streaming --- ``block-stream`` +----------------------------------------- + +The ``block-stream`` command allows you to do live copy data from backing +files into overlay images. + +Given our original example disk image chain from earlier:: + + [A] <-- [B] <-- [C] <-- [D] + +The disk image chain can be shortened in one of the following different +ways (not an exhaustive list). + +.. _`Case-1`: + +(1) Merge everything into the active layer: I.e. copy all contents from + the base image, [A], and overlay images, [B] and [C], into [D], + *while* the guest is running. The resulting chain will be a + standalone image, [D] -- with contents from [A], [B] and [C] merged + into it (where live QEMU writes go to):: + + [D] + +.. _`Case-2`: + +(2) Taking the same example disk image chain mentioned earlier, merge + only images [B] and [C] into [D], the active layer. The result will + be contents of images [B] and [C] will be copied into [D], and the + backing file pointer of image [D] will be adjusted to point to image + [A]. The resulting chain will be:: + + [A] <-- [D] + +.. _`Case-3`: + +(3) Intermediate streaming (available since QEMU 2.8): Starting afresh + with the original example disk image chain, with a total of four + images, it is possible to copy contents from image [B] into image + [C]. Once the copy is finished, image [B] can now be (optionally) + discarded; and the backing file pointer of image [C] will be + adjusted to point to [A]. I.e. after performing "intermediate + streaming" of [B] into [C], the resulting image chain will be (where + live QEMU is writing to [D]):: + + [A] <-- [C] <-- [D] + + +QMP invocation for ``block-stream`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For `Case-1`_, to merge contents of all the backing files into the +active layer, where 'node-D' is the current active image (by default +``block-stream`` will flatten the entire chain); ``qmp-shell`` (and its +corresponding JSON output):: + + (QEMU) block-stream device=node-D job-id=job0 + { + "execute": "block-stream", + "arguments": { + "device": "node-D", + "job-id": "job0" + } + } + +For `Case-2`_, merge contents of the images [B] and [C] into [D], where +image [D] ends up referring to image [A] as its backing file:: + + (QEMU) block-stream device=node-D base-node=node-A job-id=job0 + +And for `Case-3`_, of "intermediate" streaming", merge contents of +images [B] into [C], where [C] ends up referring to [A] as its backing +image:: + + (QEMU) block-stream device=node-C base-node=node-A job-id=job0 + +Progress of a ``block-stream`` operation can be monitored via the QMP +command:: + + (QEMU) query-block-jobs + { + "execute": "query-block-jobs", + "arguments": {} + } + + +Once the ``block-stream`` operation has completed, QEMU will emit an +event, ``BLOCK_JOB_COMPLETED``. The intermediate overlays remain valid, +and can now be (optionally) discarded, or retained to create further +overlays based on them. Finally, the ``block-stream`` jobs can be +restarted at anytime. + + +Live block commit --- ``block-commit`` +-------------------------------------- + +The ``block-commit`` command lets you merge live data from overlay +images into backing file(s). Since QEMU 2.0, this includes "live active +commit" (i.e. it is possible to merge the "active layer", the right-most +image in a disk image chain where live QEMU will be writing to, into the +base image). This is analogous to ``block-stream``, but in the opposite +direction. + +Again, starting afresh with our example disk image chain, where live +QEMU is writing to the right-most image in the chain, [D]:: + + [A] <-- [B] <-- [C] <-- [D] + +The disk image chain can be shortened in one of the following ways: + +.. _`block-commit_Case-1`: + +(1) Commit content from only image [B] into image [A]. The resulting + chain is the following, where image [C] is adjusted to point at [A] + as its new backing file:: + + [A] <-- [C] <-- [D] + +(2) Commit content from images [B] and [C] into image [A]. The + resulting chain, where image [D] is adjusted to point to image [A] + as its new backing file:: + + [A] <-- [D] + +.. _`block-commit_Case-3`: + +(3) Commit content from images [B], [C], and the active layer [D] into + image [A]. The resulting chain (in this case, a consolidated single + image):: + + [A] + +(4) Commit content from image only image [C] into image [B]. The + resulting chain:: + + [A] <-- [B] <-- [D] + +(5) Commit content from image [C] and the active layer [D] into image + [B]. The resulting chain:: + + [A] <-- [B] + + +QMP invocation for ``block-commit`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For :ref:`Case-1 <block-commit_Case-1>`, to merge contents only from +image [B] into image [A], the invocation is as follows:: + + (QEMU) block-commit device=node-D base=a.qcow2 top=b.qcow2 job-id=job0 + { + "execute": "block-commit", + "arguments": { + "device": "node-D", + "job-id": "job0", + "top": "b.qcow2", + "base": "a.qcow2" + } + } + +Once the above ``block-commit`` operation has completed, a +``BLOCK_JOB_COMPLETED`` event will be issued, and no further action is +required. As the end result, the backing file of image [C] is adjusted +to point to image [A], and the original 4-image chain will end up being +transformed to:: + + [A] <-- [C] <-- [D] + +.. note:: + The intermediate image [B] is invalid (as in: no more further + overlays based on it can be created). + + Reasoning: An intermediate image after a 'stream' operation still + represents that old point-in-time, and may be valid in that context. + However, an intermediate image after a 'commit' operation no longer + represents any point-in-time, and is invalid in any context. + + +However, :ref:`Case-3 <block-commit_Case-3>` (also called: "active +``block-commit``") is a *two-phase* operation: In the first phase, the +content from the active overlay, along with the intermediate overlays, +is copied into the backing file (also called the base image). In the +second phase, adjust the said backing file as the current active image +-- possible via issuing the command ``block-job-complete``. Optionally, +the ``block-commit`` operation can be cancelled by issuing the command +``block-job-cancel``, but be careful when doing this. + +Once the ``block-commit`` operation has completed, the event +``BLOCK_JOB_READY`` will be emitted, signalling that the synchronization +has finished. Now the job can be gracefully completed by issuing the +command ``block-job-complete`` -- until such a command is issued, the +'commit' operation remains active. + +The following is the flow for :ref:`Case-3 <block-commit_Case-3>` to +convert a disk image chain such as this:: + + [A] <-- [B] <-- [C] <-- [D] + +Into:: + + [A] + +Where content from all the subsequent overlays, [B], and [C], including +the active layer, [D], is committed back to [A] -- which is where live +QEMU is performing all its current writes). + +Start the "active ``block-commit``" operation:: + + (QEMU) block-commit device=node-D base=a.qcow2 top=d.qcow2 job-id=job0 + { + "execute": "block-commit", + "arguments": { + "device": "node-D", + "job-id": "job0", + "top": "d.qcow2", + "base": "a.qcow2" + } + } + + +Once the synchronization has completed, the event ``BLOCK_JOB_READY`` will +be emitted. + +Then, optionally query for the status of the active block operations. +We can see the 'commit' job is now ready to be completed, as indicated +by the line *"ready": true*:: + + (QEMU) query-block-jobs + { + "execute": "query-block-jobs", + "arguments": {} + } + { + "return": [ + { + "busy": false, + "type": "commit", + "len": 1376256, + "paused": false, + "ready": true, + "io-status": "ok", + "offset": 1376256, + "device": "job0", + "speed": 0 + } + ] + } + +Gracefully complete the 'commit' block device job:: + + (QEMU) block-job-complete device=job0 + { + "execute": "block-job-complete", + "arguments": { + "device": "job0" + } + } + { + "return": {} + } + +Finally, once the above job is completed, an event +``BLOCK_JOB_COMPLETED`` will be emitted. + +.. note:: + The invocation for rest of the cases (2, 4, and 5), discussed in the + previous section, is omitted for brevity. + + +Live disk synchronization --- ``drive-mirror`` and ``blockdev-mirror`` +---------------------------------------------------------------------- + +Synchronize a running disk image chain (all or part of it) to a target +image. + +Again, given our familiar disk image chain:: + + [A] <-- [B] <-- [C] <-- [D] + +The ``drive-mirror`` (and its newer equivalent ``blockdev-mirror``) allows +you to copy data from the entire chain into a single target image (which +can be located on a different host). + +Once a 'mirror' job has started, there are two possible actions while a +``drive-mirror`` job is active: + +(1) Issuing the command ``block-job-cancel`` after it emits the event + ``BLOCK_JOB_CANCELLED``: will (after completing synchronization of + the content from the disk image chain to the target image, [E]) + create a point-in-time (which is at the time of *triggering* the + cancel command) copy, contained in image [E], of the the entire disk + image chain (or only the top-most image, depending on the ``sync`` + mode). + +(2) Issuing the command ``block-job-complete`` after it emits the event + ``BLOCK_JOB_COMPLETED``: will, after completing synchronization of + the content, adjust the guest device (i.e. live QEMU) to point to + the target image, and, causing all the new writes from this point on + to happen there. One use case for this is live storage migration. + +About synchronization modes: The synchronization mode determines +*which* part of the disk image chain will be copied to the target. +Currently, there are four different kinds: + +(1) ``full`` -- Synchronize the content of entire disk image chain to + the target + +(2) ``top`` -- Synchronize only the contents of the top-most disk image + in the chain to the target + +(3) ``none`` -- Synchronize only the new writes from this point on. + + .. note:: In the case of ``drive-backup`` (or ``blockdev-backup``), + the behavior of ``none`` synchronization mode is different. + Normally, a ``backup`` job consists of two parts: Anything + that is overwritten by the guest is first copied out to + the backup, and in the background the whole image is + copied from start to end. With ``sync=none``, it's only + the first part. + +(4) ``incremental`` -- Synchronize content that is described by the + dirty bitmap + +.. note:: + Refer to the :doc:`bitmaps` document in the QEMU source + tree to learn about the detailed workings of the ``incremental`` + synchronization mode. + + +QMP invocation for ``drive-mirror`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To copy the contents of the entire disk image chain, from [A] all the +way to [D], to a new target (``drive-mirror`` will create the destination +file, if it doesn't already exist), call it [E]:: + + (QEMU) drive-mirror device=node-D target=e.qcow2 sync=full job-id=job0 + { + "execute": "drive-mirror", + "arguments": { + "device": "node-D", + "job-id": "job0", + "target": "e.qcow2", + "sync": "full" + } + } + +The ``"sync": "full"``, from the above, means: copy the *entire* chain +to the destination. + +Following the above, querying for active block jobs will show that a +'mirror' job is "ready" to be completed (and QEMU will also emit an +event, ``BLOCK_JOB_READY``):: + + (QEMU) query-block-jobs + { + "execute": "query-block-jobs", + "arguments": {} + } + { + "return": [ + { + "busy": false, + "type": "mirror", + "len": 21757952, + "paused": false, + "ready": true, + "io-status": "ok", + "offset": 21757952, + "device": "job0", + "speed": 0 + } + ] + } + +And, as noted in the previous section, there are two possible actions +at this point: + +(a) Create a point-in-time snapshot by ending the synchronization. The + point-in-time is at the time of *ending* the sync. (The result of + the following being: the target image, [E], will be populated with + content from the entire chain, [A] to [D]):: + + (QEMU) block-job-cancel device=job0 + { + "execute": "block-job-cancel", + "arguments": { + "device": "job0" + } + } + +(b) Or, complete the operation and pivot the live QEMU to the target + copy:: + + (QEMU) block-job-complete device=job0 + +In either of the above cases, if you once again run the +`query-block-jobs` command, there should not be any active block +operation. + +Comparing 'commit' and 'mirror': In both then cases, the overlay images +can be discarded. However, with 'commit', the *existing* base image +will be modified (by updating it with contents from overlays); while in +the case of 'mirror', a *new* target image is populated with the data +from the disk image chain. + + +QMP invocation for live storage migration with ``drive-mirror`` + NBD +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Live storage migration (without shared storage setup) is one of the most +common use-cases that takes advantage of the ``drive-mirror`` primitive +and QEMU's built-in Network Block Device (NBD) server. Here's a quick +walk-through of this setup. + +Given the disk image chain:: + + [A] <-- [B] <-- [C] <-- [D] + +Instead of copying content from the entire chain, synchronize *only* the +contents of the *top*-most disk image (i.e. the active layer), [D], to a +target, say, [TargetDisk]. + +.. important:: + The destination host must already have the contents of the backing + chain, involving images [A], [B], and [C], visible via other means + -- whether by ``cp``, ``rsync``, or by some storage array-specific + command.) + +Sometimes, this is also referred to as "shallow copy" -- because only +the "active layer", and not the rest of the image chain, is copied to +the destination. + +.. note:: + In this example, for the sake of simplicity, we'll be using the same + ``localhost`` as both source and destination. + +As noted earlier, on the destination host the contents of the backing +chain -- from images [A] to [C] -- are already expected to exist in some +form (e.g. in a file called, ``Contents-of-A-B-C.qcow2``). Now, on the +destination host, let's create a target overlay image (with the image +``Contents-of-A-B-C.qcow2`` as its backing file), to which the contents +of image [D] (from the source QEMU) will be mirrored to:: + + $ qemu-img create -f qcow2 -b ./Contents-of-A-B-C.qcow2 \ + -F qcow2 ./target-disk.qcow2 + +And start the destination QEMU (we already have the source QEMU running +-- discussed in the section: `Interacting with a QEMU instance`_) +instance, with the following invocation. (As noted earlier, for +simplicity's sake, the destination QEMU is started on the same host, but +it could be located elsewhere):: + + $ ./x86_64-softmmu/qemu-system-x86_64 -display none -nodefconfig \ + -M q35 -nodefaults -m 512 \ + -blockdev node-name=node-TargetDisk,driver=qcow2,file.driver=file,file.node-name=file,file.filename=./target-disk.qcow2 \ + -device virtio-blk,drive=node-TargetDisk,id=virtio0 \ + -S -monitor stdio -qmp unix:./qmp-sock2,server,nowait \ + -incoming tcp:localhost:6666 + +Given the disk image chain on source QEMU:: + + [A] <-- [B] <-- [C] <-- [D] + +On the destination host, it is expected that the contents of the chain +``[A] <-- [B] <-- [C]`` are *already* present, and therefore copy *only* +the content of image [D]. + +(1) [On *destination* QEMU] As part of the first step, start the + built-in NBD server on a given host (local host, represented by + ``::``)and port:: + + (QEMU) nbd-server-start addr={"type":"inet","data":{"host":"::","port":"49153"}} + { + "execute": "nbd-server-start", + "arguments": { + "addr": { + "data": { + "host": "::", + "port": "49153" + }, + "type": "inet" + } + } + } + +(2) [On *destination* QEMU] And export the destination disk image using + QEMU's built-in NBD server:: + + (QEMU) nbd-server-add device=node-TargetDisk writable=true + { + "execute": "nbd-server-add", + "arguments": { + "device": "node-TargetDisk" + } + } + +(3) [On *source* QEMU] Then, invoke ``drive-mirror`` (NB: since we're + running ``drive-mirror`` with ``mode=existing`` (meaning: + synchronize to a pre-created file, therefore 'existing', file on the + target host), with the synchronization mode as 'top' (``"sync: + "top"``):: + + (QEMU) drive-mirror device=node-D target=nbd:localhost:49153:exportname=node-TargetDisk sync=top mode=existing job-id=job0 + { + "execute": "drive-mirror", + "arguments": { + "device": "node-D", + "mode": "existing", + "job-id": "job0", + "target": "nbd:localhost:49153:exportname=node-TargetDisk", + "sync": "top" + } + } + +(4) [On *source* QEMU] Once ``drive-mirror`` copies the entire data, and the + event ``BLOCK_JOB_READY`` is emitted, issue ``block-job-cancel`` to + gracefully end the synchronization, from source QEMU:: + + (QEMU) block-job-cancel device=job0 + { + "execute": "block-job-cancel", + "arguments": { + "device": "job0" + } + } + +(5) [On *destination* QEMU] Then, stop the NBD server:: + + (QEMU) nbd-server-stop + { + "execute": "nbd-server-stop", + "arguments": {} + } + +(6) [On *destination* QEMU] Finally, resume the guest vCPUs by issuing the + QMP command `cont`:: + + (QEMU) cont + { + "execute": "cont", + "arguments": {} + } + +.. note:: + Higher-level libraries (e.g. libvirt) automate the entire above + process (although note that libvirt does not allow same-host + migrations to localhost for other reasons). + + +Notes on ``blockdev-mirror`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``blockdev-mirror`` command is equivalent in core functionality to +``drive-mirror``, except that it operates at node-level in a BDS graph. + +Also: for ``blockdev-mirror``, the 'target' image needs to be explicitly +created (using ``qemu-img``) and attach it to live QEMU via +``blockdev-add``, which assigns a name to the to-be created target node. + +E.g. the sequence of actions to create a point-in-time backup of an +entire disk image chain, to a target, using ``blockdev-mirror`` would be: + +(0) Create the QCOW2 overlays, to arrive at a backing chain of desired + depth + +(1) Create the target image (using ``qemu-img``), say, ``e.qcow2`` + +(2) Attach the above created file (``e.qcow2``), run-time, using + ``blockdev-add`` to QEMU + +(3) Perform ``blockdev-mirror`` (use ``"sync": "full"`` to copy the + entire chain to the target). And notice the event + ``BLOCK_JOB_READY`` + +(4) Optionally, query for active block jobs, there should be a 'mirror' + job ready to be completed + +(5) Gracefully complete the 'mirror' block device job, and notice the + the event ``BLOCK_JOB_COMPLETED`` + +(6) Shutdown the guest by issuing the QMP ``quit`` command so that + caches are flushed + +(7) Then, finally, compare the contents of the disk image chain, and + the target copy with ``qemu-img compare``. You should notice: + "Images are identical" + + +QMP invocation for ``blockdev-mirror`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Given the disk image chain:: + + [A] <-- [B] <-- [C] <-- [D] + +To copy the contents of the entire disk image chain, from [A] all the +way to [D], to a new target, call it [E]. The following is the flow. + +Create the overlay images, [B], [C], and [D]:: + + (QEMU) blockdev-snapshot-sync node-name=node-A snapshot-file=b.qcow2 snapshot-node-name=node-B format=qcow2 + (QEMU) blockdev-snapshot-sync node-name=node-B snapshot-file=c.qcow2 snapshot-node-name=node-C format=qcow2 + (QEMU) blockdev-snapshot-sync node-name=node-C snapshot-file=d.qcow2 snapshot-node-name=node-D format=qcow2 + +Create the target image, [E]:: + + $ qemu-img create -f qcow2 e.qcow2 39M + +Add the above created target image to QEMU, via ``blockdev-add``:: + + (QEMU) blockdev-add driver=qcow2 node-name=node-E file={"driver":"file","filename":"e.qcow2"} + { + "execute": "blockdev-add", + "arguments": { + "node-name": "node-E", + "driver": "qcow2", + "file": { + "driver": "file", + "filename": "e.qcow2" + } + } + } + +Perform ``blockdev-mirror``, and notice the event ``BLOCK_JOB_READY``:: + + (QEMU) blockdev-mirror device=node-B target=node-E sync=full job-id=job0 + { + "execute": "blockdev-mirror", + "arguments": { + "device": "node-D", + "job-id": "job0", + "target": "node-E", + "sync": "full" + } + } + +Query for active block jobs, there should be a 'mirror' job ready:: + + (QEMU) query-block-jobs + { + "execute": "query-block-jobs", + "arguments": {} + } + { + "return": [ + { + "busy": false, + "type": "mirror", + "len": 21561344, + "paused": false, + "ready": true, + "io-status": "ok", + "offset": 21561344, + "device": "job0", + "speed": 0 + } + ] + } + +Gracefully complete the block device job operation, and notice the +event ``BLOCK_JOB_COMPLETED``:: + + (QEMU) block-job-complete device=job0 + { + "execute": "block-job-complete", + "arguments": { + "device": "job0" + } + } + { + "return": {} + } + +Shutdown the guest, by issuing the ``quit`` QMP command:: + + (QEMU) quit + { + "execute": "quit", + "arguments": {} + } + + +Live disk backup --- ``drive-backup`` and ``blockdev-backup`` +------------------------------------------------------------- + +The ``drive-backup`` (and its newer equivalent ``blockdev-backup``) allows +you to create a point-in-time snapshot. + +In this case, the point-in-time is when you *start* the ``drive-backup`` +(or its newer equivalent ``blockdev-backup``) command. + + +QMP invocation for ``drive-backup`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Yet again, starting afresh with our example disk image chain:: + + [A] <-- [B] <-- [C] <-- [D] + +To create a target image [E], with content populated from image [A] to +[D], from the above chain, the following is the syntax. (If the target +image does not exist, ``drive-backup`` will create it):: + + (QEMU) drive-backup device=node-D sync=full target=e.qcow2 job-id=job0 + { + "execute": "drive-backup", + "arguments": { + "device": "node-D", + "job-id": "job0", + "sync": "full", + "target": "e.qcow2" + } + } + +Once the above ``drive-backup`` has completed, a ``BLOCK_JOB_COMPLETED`` event +will be issued, indicating the live block device job operation has +completed, and no further action is required. + + +Notes on ``blockdev-backup`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``blockdev-backup`` command is equivalent in functionality to +``drive-backup``, except that it operates at node-level in a Block Driver +State (BDS) graph. + +E.g. the sequence of actions to create a point-in-time backup +of an entire disk image chain, to a target, using ``blockdev-backup`` +would be: + +(0) Create the QCOW2 overlays, to arrive at a backing chain of desired + depth + +(1) Create the target image (using ``qemu-img``), say, ``e.qcow2`` + +(2) Attach the above created file (``e.qcow2``), run-time, using + ``blockdev-add`` to QEMU + +(3) Perform ``blockdev-backup`` (use ``"sync": "full"`` to copy the + entire chain to the target). And notice the event + ``BLOCK_JOB_COMPLETED`` + +(4) Shutdown the guest, by issuing the QMP ``quit`` command, so that + caches are flushed + +(5) Then, finally, compare the contents of the disk image chain, and + the target copy with ``qemu-img compare``. You should notice: + "Images are identical" + +The following section shows an example QMP invocation for +``blockdev-backup``. + +QMP invocation for ``blockdev-backup`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Given a disk image chain of depth 1 where image [B] is the active +overlay (live QEMU is writing to it):: + + [A] <-- [B] + +The following is the procedure to copy the content from the entire chain +to a target image (say, [E]), which has the full content from [A] and +[B]. + +Create the overlay [B]:: + + (QEMU) blockdev-snapshot-sync node-name=node-A snapshot-file=b.qcow2 snapshot-node-name=node-B format=qcow2 + { + "execute": "blockdev-snapshot-sync", + "arguments": { + "node-name": "node-A", + "snapshot-file": "b.qcow2", + "format": "qcow2", + "snapshot-node-name": "node-B" + } + } + + +Create a target image that will contain the copy:: + + $ qemu-img create -f qcow2 e.qcow2 39M + +Then add it to QEMU via ``blockdev-add``:: + + (QEMU) blockdev-add driver=qcow2 node-name=node-E file={"driver":"file","filename":"e.qcow2"} + { + "execute": "blockdev-add", + "arguments": { + "node-name": "node-E", + "driver": "qcow2", + "file": { + "driver": "file", + "filename": "e.qcow2" + } + } + } + +Then invoke ``blockdev-backup`` to copy the contents from the entire +image chain, consisting of images [A] and [B] to the target image +'e.qcow2':: + + (QEMU) blockdev-backup device=node-B target=node-E sync=full job-id=job0 + { + "execute": "blockdev-backup", + "arguments": { + "device": "node-B", + "job-id": "job0", + "target": "node-E", + "sync": "full" + } + } + +Once the above 'backup' operation has completed, the event, +``BLOCK_JOB_COMPLETED`` will be emitted, signalling successful +completion. + +Next, query for any active block device jobs (there should be none):: + + (QEMU) query-block-jobs + { + "execute": "query-block-jobs", + "arguments": {} + } + +Shutdown the guest:: + + (QEMU) quit + { + "execute": "quit", + "arguments": {} + } + "return": {} + } + +.. note:: + The above step is really important; if forgotten, an error, "Failed + to get shared "write" lock on e.qcow2", will be thrown when you do + ``qemu-img compare`` to verify the integrity of the disk image + with the backup content. + + +The end result will be the image 'e.qcow2' containing a +point-in-time backup of the disk image chain -- i.e. contents from +images [A] and [B] at the time the ``blockdev-backup`` command was +initiated. + +One way to confirm the backup disk image contains the identical content +with the disk image chain is to compare the backup and the contents of +the chain, you should see "Images are identical". (NB: this is assuming +QEMU was launched with ``-S`` option, which will not start the CPUs at +guest boot up):: + + $ qemu-img compare b.qcow2 e.qcow2 + Warning: Image size mismatch! + Images are identical. + +NOTE: The "Warning: Image size mismatch!" is expected, as we created the +target image (e.qcow2) with 39M size. diff --git a/docs/live-block-ops.txt b/docs/live-block-ops.txt deleted file mode 100644 index 2211d14428..0000000000 --- a/docs/live-block-ops.txt +++ /dev/null @@ -1,72 +0,0 @@ -LIVE BLOCK OPERATIONS -===================== - -High level description of live block operations. Note these are not -supported for use with the raw format at the moment. - -Note also that this document is incomplete and it currently only -covers the 'stream' operation. Other operations supported by QEMU such -as 'commit', 'mirror' and 'backup' are not described here yet. Please -refer to the qapi/block-core.json file for an overview of those. - -Snapshot live merge -=================== - -Given a snapshot chain, described in this document in the following -format: - -[A] <- [B] <- [C] <- [D] <- [E] - -Where the rightmost object ([E] in the example) described is the current -image which the guest OS has write access to. To the left of it is its base -image, and so on accordingly until the leftmost image, which has no -base. - -The snapshot live merge operation transforms such a chain into a -smaller one with fewer elements, such as this transformation relative -to the first example: - -[A] <- [E] - -Data is copied in the right direction with destination being the -rightmost image, but any other intermediate image can be specified -instead. In this example data is copied from [C] into [D], so [D] can -be backed by [B]: - -[A] <- [B] <- [D] <- [E] - -The operation is implemented in QEMU through image streaming facilities. - -The basic idea is to execute 'block_stream virtio0' while the guest is -running. Progress can be monitored using 'info block-jobs'. When the -streaming operation completes it raises a QMP event. 'block_stream' -copies data from the backing file(s) into the active image. When finished, -it adjusts the backing file pointer. - -The 'base' parameter specifies an image which data need not be -streamed from. This image will be used as the backing file for the -destination image when the operation is finished. - -In the first example above, the command would be: - -(qemu) block_stream virtio0 file-A.img - -In order to specify a destination image different from the active -(rightmost) one we can use its node name instead. - -In the second example above, the command would be: - -(qemu) block_stream node-D file-B.img - -Live block copy -=============== - -To copy an in use image to another destination in the filesystem, one -should create a live snapshot in the desired destination, then stream -into that image. Example: - -(qemu) snapshot_blkdev ide0-hd0 /new-path/disk.img qcow2 - -(qemu) block_stream ide0-hd0 - - diff --git a/hw/core/machine.c b/hw/core/machine.c index dc431fabf5..41b53a17ad 100644 --- a/hw/core/machine.c +++ b/hw/core/machine.c @@ -770,18 +770,11 @@ static void machine_class_finalize(ObjectClass *klass, void *data) g_free(mc->name); } -static void machine_register_compat_for_subclass(ObjectClass *oc, void *opaque) -{ - GlobalProperty *p = opaque; - register_compat_prop(object_class_get_name(oc), p->property, p->value); -} - void machine_register_compat_props(MachineState *machine) { MachineClass *mc = MACHINE_GET_CLASS(machine); int i; GlobalProperty *p; - ObjectClass *oc; if (!mc->compat_props) { return; @@ -789,22 +782,9 @@ void machine_register_compat_props(MachineState *machine) for (i = 0; i < mc->compat_props->len; i++) { p = g_array_index(mc->compat_props, GlobalProperty *, i); - oc = object_class_by_name(p->driver); - if (oc && object_class_is_abstract(oc)) { - /* temporary hack to make sure we do not override - * globals set explicitly on -global: if an abstract class - * is on compat_props, register globals for all its - * non-abstract subtypes instead. - * - * This doesn't solve the problem for cases where - * a non-abstract typename mentioned on compat_props - * has subclasses, like spapr-pci-host-bridge. - */ - object_class_foreach(machine_register_compat_for_subclass, - p->driver, false, p); - } else { - register_compat_prop(p->driver, p->property, p->value); - } + /* Machine compat_props must never cause errors: */ + p->errp = &error_abort; + qdev_prop_register_global(p); } } diff --git a/hw/core/qdev-properties.c b/hw/core/qdev-properties.c index dcecdf03e5..58a8f92d92 100644 --- a/hw/core/qdev-properties.c +++ b/hw/core/qdev-properties.c @@ -1149,8 +1149,7 @@ int qdev_prop_check_globals(void) return ret; } -static void qdev_prop_set_globals_for_type(DeviceState *dev, - const char *typename) +void qdev_prop_set_globals(DeviceState *dev) { GList *l; @@ -1158,7 +1157,7 @@ static void qdev_prop_set_globals_for_type(DeviceState *dev, GlobalProperty *prop = l->data; Error *err = NULL; - if (strcmp(typename, prop->driver) != 0) { + if (object_dynamic_cast(OBJECT(dev), prop->driver) == NULL) { continue; } prop->used = true; @@ -1176,16 +1175,6 @@ static void qdev_prop_set_globals_for_type(DeviceState *dev, } } -void qdev_prop_set_globals(DeviceState *dev) -{ - ObjectClass *class = object_get_class(OBJECT(dev)); - - do { - qdev_prop_set_globals_for_type(dev, object_class_get_name(class)); - class = object_class_get_parent(class); - } while (class); -} - /* --- 64bit unsigned int 'size' type --- */ static void get_size(Object *obj, Visitor *v, const char *name, void *opaque, diff --git a/hw/nvram/fw_cfg.c b/hw/nvram/fw_cfg.c index e881e3b812..5bd904487f 100644 --- a/hw/nvram/fw_cfg.c +++ b/hw/nvram/fw_cfg.c @@ -37,17 +37,6 @@ #define FW_CFG_FILE_SLOTS_DFLT 0x20 -#define FW_CFG_NAME "fw_cfg" -#define FW_CFG_PATH "/machine/" FW_CFG_NAME - -#define TYPE_FW_CFG "fw_cfg" -#define TYPE_FW_CFG_IO "fw_cfg_io" -#define TYPE_FW_CFG_MEM "fw_cfg_mem" - -#define FW_CFG(obj) OBJECT_CHECK(FWCfgState, (obj), TYPE_FW_CFG) -#define FW_CFG_IO(obj) OBJECT_CHECK(FWCfgIoState, (obj), TYPE_FW_CFG_IO) -#define FW_CFG_MEM(obj) OBJECT_CHECK(FWCfgMemState, (obj), TYPE_FW_CFG_MEM) - /* FW_CFG_VERSION bits */ #define FW_CFG_VERSION 0x01 #define FW_CFG_VERSION_DMA 0x02 @@ -61,51 +50,12 @@ #define FW_CFG_DMA_SIGNATURE 0x51454d5520434647ULL /* "QEMU CFG" */ -typedef struct FWCfgEntry { +struct FWCfgEntry { uint32_t len; bool allow_write; uint8_t *data; void *callback_opaque; FWCfgReadCallback read_callback; -} FWCfgEntry; - -struct FWCfgState { - /*< private >*/ - SysBusDevice parent_obj; - /*< public >*/ - - uint16_t file_slots; - FWCfgEntry *entries[2]; - int *entry_order; - FWCfgFiles *files; - uint16_t cur_entry; - uint32_t cur_offset; - Notifier machine_ready; - - int fw_cfg_order_override; - - bool dma_enabled; - dma_addr_t dma_addr; - AddressSpace *dma_as; - MemoryRegion dma_iomem; -}; - -struct FWCfgIoState { - /*< private >*/ - FWCfgState parent_obj; - /*< public >*/ - - MemoryRegion comb_iomem; -}; - -struct FWCfgMemState { - /*< private >*/ - FWCfgState parent_obj; - /*< public >*/ - - MemoryRegion ctl_iomem, data_iomem; - uint32_t data_width; - MemoryRegionOps wide_data_ops; }; #define JPG_FILE 0 @@ -909,17 +859,16 @@ static void fw_cfg_machine_ready(struct Notifier *n, void *data) -static void fw_cfg_init1(DeviceState *dev) +static void fw_cfg_common_realize(DeviceState *dev, Error **errp) { FWCfgState *s = FW_CFG(dev); MachineState *machine = MACHINE(qdev_get_machine()); uint32_t version = FW_CFG_VERSION; - assert(!object_resolve_path(FW_CFG_PATH, NULL)); - - object_property_add_child(OBJECT(machine), FW_CFG_NAME, OBJECT(s), NULL); - - qdev_init_nofail(dev); + if (!fw_cfg_find()) { + error_setg(errp, "at most one %s device is permitted", TYPE_FW_CFG); + return; + } fw_cfg_add_bytes(s, FW_CFG_SIGNATURE, (char *)"QEMU", 4); fw_cfg_add_bytes(s, FW_CFG_UUID, &qemu_uuid, 16); @@ -952,7 +901,9 @@ FWCfgState *fw_cfg_init_io_dma(uint32_t iobase, uint32_t dma_iobase, qdev_prop_set_bit(dev, "dma_enabled", false); } - fw_cfg_init1(dev); + object_property_add_child(OBJECT(qdev_get_machine()), TYPE_FW_CFG, + OBJECT(dev), NULL); + qdev_init_nofail(dev); sbd = SYS_BUS_DEVICE(dev); ios = FW_CFG_IO(dev); @@ -990,7 +941,9 @@ FWCfgState *fw_cfg_init_mem_wide(hwaddr ctl_addr, qdev_prop_set_bit(dev, "dma_enabled", false); } - fw_cfg_init1(dev); + object_property_add_child(OBJECT(qdev_get_machine()), TYPE_FW_CFG, + OBJECT(dev), NULL); + qdev_init_nofail(dev); sbd = SYS_BUS_DEVICE(dev); sysbus_mmio_map(sbd, 0, ctl_addr); @@ -1017,9 +970,11 @@ FWCfgState *fw_cfg_init_mem(hwaddr ctl_addr, hwaddr data_addr) FWCfgState *fw_cfg_find(void) { - return FW_CFG(object_resolve_path(FW_CFG_PATH, NULL)); + /* Returns NULL unless there is exactly one fw_cfg device */ + return FW_CFG(object_resolve_path_type("", TYPE_FW_CFG, NULL)); } + static void fw_cfg_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -1091,6 +1046,8 @@ static void fw_cfg_io_realize(DeviceState *dev, Error **errp) &fw_cfg_dma_mem_ops, FW_CFG(s), "fwcfg.dma", sizeof(dma_addr_t)); } + + fw_cfg_common_realize(dev, errp); } static void fw_cfg_io_class_init(ObjectClass *klass, void *data) @@ -1157,6 +1114,8 @@ static void fw_cfg_mem_realize(DeviceState *dev, Error **errp) sizeof(dma_addr_t)); sysbus_init_mmio(sbd, &FW_CFG(s)->dma_iomem); } + + fw_cfg_common_realize(dev, errp); } static void fw_cfg_mem_class_init(ObjectClass *klass, void *data) diff --git a/hw/vfio/common.c b/hw/vfio/common.c index c1bb6d429a..7b2924c0ef 100644 --- a/hw/vfio/common.c +++ b/hw/vfio/common.c @@ -1109,6 +1109,14 @@ static int vfio_connect_container(VFIOGroup *group, AddressSpace *as, goto free_container_exit; } + vfio_kvm_device_add_group(group); + + QLIST_INIT(&container->group_list); + QLIST_INSERT_HEAD(&space->containers, container, next); + + group->container = container; + QLIST_INSERT_HEAD(&container->group_list, group, container_next); + container->listener = vfio_memory_listener; memory_listener_register(&container->listener, container->space->as); @@ -1122,14 +1130,11 @@ static int vfio_connect_container(VFIOGroup *group, AddressSpace *as, container->initialized = true; - QLIST_INIT(&container->group_list); - QLIST_INSERT_HEAD(&space->containers, container, next); - - group->container = container; - QLIST_INSERT_HEAD(&container->group_list, group, container_next); - return 0; listener_release_exit: + QLIST_REMOVE(group, container_next); + QLIST_REMOVE(container, next); + vfio_kvm_device_del_group(group); vfio_listener_release(container); free_container_exit: @@ -1234,8 +1239,6 @@ VFIOGroup *vfio_get_group(int groupid, AddressSpace *as, Error **errp) QLIST_INSERT_HEAD(&vfio_group_list, group, next); - vfio_kvm_device_add_group(group); - return group; close_fd_exit: diff --git a/include/hw/i386/pc.h b/include/hw/i386/pc.h index f48d167207..d80859bfad 100644 --- a/include/hw/i386/pc.h +++ b/include/hw/i386/pc.h @@ -380,6 +380,11 @@ bool e820_get_entry(int, uint32_t, uint64_t *, uint64_t *); #define PC_COMPAT_2_8 \ HW_COMPAT_2_8 \ {\ + .driver = TYPE_X86_CPU,\ + .property = "tcg-cpuid",\ + .value = "off",\ + },\ + {\ .driver = "kvmclock",\ .property = "x-mach-use-reliable-get-clock",\ .value = "off",\ diff --git a/include/hw/nvram/fw_cfg.h b/include/hw/nvram/fw_cfg.h index b980cbaebf..b77ea48abb 100644 --- a/include/hw/nvram/fw_cfg.h +++ b/include/hw/nvram/fw_cfg.h @@ -1,8 +1,19 @@ #ifndef FW_CFG_H #define FW_CFG_H +#include "qemu/typedefs.h" #include "exec/hwaddr.h" #include "hw/nvram/fw_cfg_keys.h" +#include "hw/sysbus.h" +#include "sysemu/dma.h" + +#define TYPE_FW_CFG "fw_cfg" +#define TYPE_FW_CFG_IO "fw_cfg_io" +#define TYPE_FW_CFG_MEM "fw_cfg_mem" + +#define FW_CFG(obj) OBJECT_CHECK(FWCfgState, (obj), TYPE_FW_CFG) +#define FW_CFG_IO(obj) OBJECT_CHECK(FWCfgIoState, (obj), TYPE_FW_CFG_IO) +#define FW_CFG_MEM(obj) OBJECT_CHECK(FWCfgMemState, (obj), TYPE_FW_CFG_MEM) typedef struct FWCfgFile { uint32_t size; /* file size */ @@ -35,6 +46,45 @@ typedef struct FWCfgDmaAccess { typedef void (*FWCfgReadCallback)(void *opaque); +struct FWCfgState { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + uint16_t file_slots; + FWCfgEntry *entries[2]; + int *entry_order; + FWCfgFiles *files; + uint16_t cur_entry; + uint32_t cur_offset; + Notifier machine_ready; + + int fw_cfg_order_override; + + bool dma_enabled; + dma_addr_t dma_addr; + AddressSpace *dma_as; + MemoryRegion dma_iomem; +}; + +struct FWCfgIoState { + /*< private >*/ + FWCfgState parent_obj; + /*< public >*/ + + MemoryRegion comb_iomem; +}; + +struct FWCfgMemState { + /*< private >*/ + FWCfgState parent_obj; + /*< public >*/ + + MemoryRegion ctl_iomem, data_iomem; + uint32_t data_width; + MemoryRegionOps wide_data_ops; +}; + /** * fw_cfg_add_bytes: * @s: fw_cfg device being modified diff --git a/include/qemu/typedefs.h b/include/qemu/typedefs.h index b19159104c..7b0d4e7e05 100644 --- a/include/qemu/typedefs.h +++ b/include/qemu/typedefs.h @@ -30,6 +30,7 @@ typedef struct DisplaySurface DisplaySurface; typedef struct DriveInfo DriveInfo; typedef struct Error Error; typedef struct EventNotifier EventNotifier; +typedef struct FWCfgEntry FWCfgEntry; typedef struct FWCfgIoState FWCfgIoState; typedef struct FWCfgMemState FWCfgMemState; typedef struct FWCfgState FWCfgState; diff --git a/io/channel.c b/io/channel.c index cdf74540c1..1cfb8b33a2 100644 --- a/io/channel.c +++ b/io/channel.c @@ -279,15 +279,9 @@ static void qio_channel_set_aio_fd_handlers(QIOChannel *ioc) void qio_channel_attach_aio_context(QIOChannel *ioc, AioContext *ctx) { - AioContext *old_ctx; - if (ioc->ctx == ctx) { - return; - } - - old_ctx = ioc->ctx ? ioc->ctx : iohandler_get_aio_context(); - qio_channel_set_aio_fd_handler(ioc, old_ctx, NULL, NULL, NULL); + assert(!ioc->read_coroutine); + assert(!ioc->write_coroutine); ioc->ctx = ctx; - qio_channel_set_aio_fd_handlers(ioc); } void qio_channel_detach_aio_context(QIOChannel *ioc) diff --git a/linux-headers/asm-x86/kvm_para.h b/linux-headers/asm-x86/kvm_para.h index 3a5397988e..cefa127d84 100644 --- a/linux-headers/asm-x86/kvm_para.h +++ b/linux-headers/asm-x86/kvm_para.h @@ -67,6 +67,7 @@ struct kvm_clock_pairing { #define KVM_ASYNC_PF_ENABLED (1 << 0) #define KVM_ASYNC_PF_SEND_ALWAYS (1 << 1) +#define KVM_ASYNC_PF_DELIVERY_AS_PF_VMEXIT (1 << 2) /* Operations for KVM_HC_MMU_OP */ #define KVM_MMU_OP_WRITE_PTE 1 diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h index 43e2d82be1..7971a4f8b5 100644 --- a/linux-headers/linux/kvm.h +++ b/linux-headers/linux/kvm.h @@ -927,6 +927,8 @@ struct kvm_ppc_resize_hpt { #define KVM_CAP_S390_CMMA_MIGRATION 145 #define KVM_CAP_PPC_FWNMI 146 #define KVM_CAP_PPC_SMT_POSSIBLE 147 +#define KVM_CAP_HYPERV_SYNIC2 148 +#define KVM_CAP_HYPERV_VP_INDEX 149 #ifdef KVM_CAP_IRQ_ROUTING @@ -1351,7 +1353,7 @@ struct kvm_s390_ucas_mapping { /* Available with KVM_CAP_X86_SMM */ #define KVM_SMI _IO(KVMIO, 0xb7) /* Available with KVM_CAP_S390_CMMA_MIGRATION */ -#define KVM_S390_GET_CMMA_BITS _IOW(KVMIO, 0xb8, struct kvm_s390_cmma_log) +#define KVM_S390_GET_CMMA_BITS _IOWR(KVMIO, 0xb8, struct kvm_s390_cmma_log) #define KVM_S390_SET_CMMA_BITS _IOW(KVMIO, 0xb9, struct kvm_s390_cmma_log) #define KVM_DEV_ASSIGN_ENABLE_IOMMU (1 << 0) diff --git a/nbd/client.c b/nbd/client.c index c3ee9f36b1..509ed5e4ba 100644 --- a/nbd/client.c +++ b/nbd/client.c @@ -232,8 +232,7 @@ static int nbd_handle_reply_err(QIOChannel *ioc, nbd_opt_reply *reply, break; case NBD_REP_ERR_UNKNOWN: - error_setg(errp, "Requested export not available for option %" PRIx32 - " (%s)", reply->option, nbd_opt_lookup(reply->option)); + error_setg(errp, "Requested export not available"); break; case NBD_REP_ERR_SHUTDOWN: @@ -253,7 +252,7 @@ static int nbd_handle_reply_err(QIOChannel *ioc, nbd_opt_reply *reply, } if (msg) { - error_append_hint(errp, "%s\n", msg); + error_append_hint(errp, "server reported: %s\n", msg); } cleanup: @@ -902,7 +901,8 @@ ssize_t nbd_send_request(QIOChannel *ioc, NBDRequest *request) uint8_t buf[NBD_REQUEST_SIZE]; trace_nbd_send_request(request->from, request->len, request->handle, - request->flags, request->type); + request->flags, request->type, + nbd_cmd_lookup(request->type)); stl_be_p(buf, NBD_REQUEST_MAGIC); stw_be_p(buf + 4, request->flags); diff --git a/nbd/nbd-internal.h b/nbd/nbd-internal.h index 4065bc68ac..396ddb4d3e 100644 --- a/nbd/nbd-internal.h +++ b/nbd/nbd-internal.h @@ -38,9 +38,13 @@ */ /* Size of all NBD_OPT_*, without payload */ -#define NBD_REQUEST_SIZE (4 + 2 + 2 + 8 + 8 + 4) +#define NBD_REQUEST_SIZE (4 + 2 + 2 + 8 + 8 + 4) /* Size of all NBD_REP_* sent in answer to most NBD_OPT_*, without payload */ -#define NBD_REPLY_SIZE (4 + 4 + 8) +#define NBD_REPLY_SIZE (4 + 4 + 8) +/* Size of reply to NBD_OPT_EXPORT_NAME */ +#define NBD_REPLY_EXPORT_NAME_SIZE (8 + 2 + 124) +/* Size of oldstyle negotiation */ +#define NBD_OLDSTYLE_NEGOTIATE_SIZE (8 + 8 + 8 + 4 + 124) #define NBD_REQUEST_MAGIC 0x25609513 #define NBD_REPLY_MAGIC 0x67446698 diff --git a/nbd/server.c b/nbd/server.c index 49ed57455c..82a78bf439 100644 --- a/nbd/server.c +++ b/nbd/server.c @@ -283,12 +283,16 @@ static int nbd_negotiate_handle_export_name(NBDClient *client, uint32_t length, Error **errp) { char name[NBD_MAX_NAME_SIZE + 1]; - char buf[8 + 4 + 124] = ""; + char buf[NBD_REPLY_EXPORT_NAME_SIZE] = ""; size_t len; int ret; /* Client sends: [20 .. xx] export name (length bytes) + Server replies: + [ 0 .. 7] size + [ 8 .. 9] export flags + [10 .. 133] reserved (0) [unless no_zeroes] */ trace_nbd_negotiate_handle_export_name(); if (length >= sizeof(name)) { @@ -800,22 +804,21 @@ static int nbd_negotiate_options(NBDClient *client, uint16_t myflags, */ static coroutine_fn int nbd_negotiate(NBDClient *client, Error **errp) { - char buf[8 + 8 + 8 + 128]; + char buf[NBD_OLDSTYLE_NEGOTIATE_SIZE] = ""; int ret; const uint16_t myflags = (NBD_FLAG_HAS_FLAGS | NBD_FLAG_SEND_TRIM | NBD_FLAG_SEND_FLUSH | NBD_FLAG_SEND_FUA | NBD_FLAG_SEND_WRITE_ZEROES); bool oldStyle; - /* Old style negotiation header without options + /* Old style negotiation header, no room for options [ 0 .. 7] passwd ("NBDMAGIC") [ 8 .. 15] magic (NBD_CLIENT_MAGIC) [16 .. 23] size - [24 .. 25] server flags (0) - [26 .. 27] export flags + [24 .. 27] export flags (zero-extended) [28 .. 151] reserved (0) - New style negotiation header with options + New style negotiation header, client can send options [ 0 .. 7] passwd ("NBDMAGIC") [ 8 .. 15] magic (NBD_OPTS_MAGIC) [16 .. 17] server flags (0) @@ -825,7 +828,6 @@ static coroutine_fn int nbd_negotiate(NBDClient *client, Error **errp) qio_channel_set_blocking(client->ioc, false, NULL); trace_nbd_negotiate_begin(); - memset(buf, 0, sizeof(buf)); memcpy(buf, "NBDMAGIC", 8); oldStyle = client->exp != NULL && !client->tlscreds; @@ -834,7 +836,7 @@ static coroutine_fn int nbd_negotiate(NBDClient *client, Error **errp) client->exp->nbdflags | myflags); stq_be_p(buf + 8, NBD_CLIENT_MAGIC); stq_be_p(buf + 16, client->exp->size); - stw_be_p(buf + 26, client->exp->nbdflags | myflags); + stl_be_p(buf + 24, client->exp->nbdflags | myflags); if (nbd_write(client->ioc, buf, sizeof(buf), errp) < 0) { error_prepend(errp, "write failed: "); diff --git a/nbd/trace-events b/nbd/trace-events index f5024d85a1..d7c7746ea8 100644 --- a/nbd/trace-events +++ b/nbd/trace-events @@ -28,7 +28,7 @@ nbd_client_loop(void) "Doing NBD loop" nbd_client_loop_ret(int ret, const char *error) "NBD loop returned %d: %s" nbd_client_clear_queue(void) "Clearing NBD queue" nbd_client_clear_socket(void) "Clearing NBD socket" -nbd_send_request(uint64_t from, uint32_t len, uint64_t handle, uint16_t flags, uint16_t type) "Sending request to server: { .from = %" PRIu64", .len = %" PRIu32 ", .handle = %" PRIu64 ", .flags = %" PRIx16 ", .type = %" PRIu16 " }" +nbd_send_request(uint64_t from, uint32_t len, uint64_t handle, uint16_t flags, uint16_t type, const char *name) "Sending request to server: { .from = %" PRIu64", .len = %" PRIu32 ", .handle = %" PRIu64 ", .flags = %" PRIx16 ", .type = %" PRIu16 " (%s) }" nbd_receive_reply(uint32_t magic, int32_t error, uint64_t handle) "Got reply: { magic = 0x%" PRIx32 ", .error = % " PRId32 ", handle = %" PRIu64" }" # nbd/server.c diff --git a/qapi-schema.json b/qapi-schema.json index ab438ead70..8b015bee2e 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -3051,10 +3051,15 @@ # # @name: the type name found in the search # +# @abstract: the type is abstract and can't be directly instantiated. +# Omitted if false. (since 2.10) +# +# @parent: Name of parent type, if any (since 2.10) +# # Since: 1.1 ## { 'struct': 'ObjectTypeInfo', - 'data': { 'name': 'str' } } + 'data': { 'name': 'str', '*abstract': 'bool', '*parent': 'str' } } ## # @qom-list-types: diff --git a/qga/commands-posix.c b/qga/commands-posix.c index d8e412275e..ab0c63d931 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -13,9 +13,9 @@ #include "qemu/osdep.h" #include <sys/ioctl.h> +#include <sys/utsname.h> #include <sys/wait.h> #include <dirent.h> -#include <utmpx.h> #include "qga/guest-agent-core.h" #include "qga-qmp-commands.h" #include "qapi/qmp/qerror.h" @@ -25,6 +25,10 @@ #include "qemu/base64.h" #include "qemu/cutils.h" +#ifdef HAVE_UTMPX +#include <utmpx.h> +#endif + #ifndef CONFIG_HAS_ENVIRON #ifdef __APPLE__ #include <crt_externs.h> @@ -2519,6 +2523,8 @@ void ga_command_state_init(GAState *s, GACommandState *cs) #endif } +#ifdef HAVE_UTMPX + #define QGA_MICRO_SECOND_TO_SECOND 1000000 static double ga_get_login_time(struct utmpx *user_info) @@ -2577,3 +2583,152 @@ GuestUserList *qmp_guest_get_users(Error **err) g_hash_table_destroy(cache); return head; } + +#else + +GuestUserList *qmp_guest_get_users(Error **errp) +{ + error_setg(errp, QERR_UNSUPPORTED); + return NULL; +} + +#endif + +/* Replace escaped special characters with theire real values. The replacement + * is done in place -- returned value is in the original string. + */ +static void ga_osrelease_replace_special(gchar *value) +{ + gchar *p, *p2, quote; + + /* Trim the string at first space or semicolon if it is not enclosed in + * single or double quotes. */ + if ((value[0] != '"') || (value[0] == '\'')) { + p = strchr(value, ' '); + if (p != NULL) { + *p = 0; + } + p = strchr(value, ';'); + if (p != NULL) { + *p = 0; + } + return; + } + + quote = value[0]; + p2 = value; + p = value + 1; + while (*p != 0) { + if (*p == '\\') { + p++; + switch (*p) { + case '$': + case '\'': + case '"': + case '\\': + case '`': + break; + default: + /* Keep literal backslash followed by whatever is there */ + p--; + break; + } + } else if (*p == quote) { + *p2 = 0; + break; + } + *(p2++) = *(p++); + } +} + +static GKeyFile *ga_parse_osrelease(const char *fname) +{ + gchar *content = NULL; + gchar *content2 = NULL; + GError *err = NULL; + GKeyFile *keys = g_key_file_new(); + const char *group = "[os-release]\n"; + + if (!g_file_get_contents(fname, &content, NULL, &err)) { + slog("failed to read '%s', error: %s", fname, err->message); + goto fail; + } + + if (!g_utf8_validate(content, -1, NULL)) { + slog("file is not utf-8 encoded: %s", fname); + goto fail; + } + content2 = g_strdup_printf("%s%s", group, content); + + if (!g_key_file_load_from_data(keys, content2, -1, G_KEY_FILE_NONE, + &err)) { + slog("failed to parse file '%s', error: %s", fname, err->message); + goto fail; + } + + g_free(content); + g_free(content2); + return keys; + +fail: + g_error_free(err); + g_free(content); + g_free(content2); + g_key_file_free(keys); + return NULL; +} + +GuestOSInfo *qmp_guest_get_osinfo(Error **errp) +{ + GuestOSInfo *info = NULL; + struct utsname kinfo; + GKeyFile *osrelease = NULL; + const char *qga_os_release = g_getenv("QGA_OS_RELEASE"); + + info = g_new0(GuestOSInfo, 1); + + if (uname(&kinfo) != 0) { + error_setg_errno(errp, errno, "uname failed"); + } else { + info->has_kernel_version = true; + info->kernel_version = g_strdup(kinfo.version); + info->has_kernel_release = true; + info->kernel_release = g_strdup(kinfo.release); + info->has_machine = true; + info->machine = g_strdup(kinfo.machine); + } + + if (qga_os_release != NULL) { + osrelease = ga_parse_osrelease(qga_os_release); + } else { + osrelease = ga_parse_osrelease("/etc/os-release"); + if (osrelease == NULL) { + osrelease = ga_parse_osrelease("/usr/lib/os-release"); + } + } + + if (osrelease != NULL) { + char *value; + +#define GET_FIELD(field, osfield) do { \ + value = g_key_file_get_value(osrelease, "os-release", osfield, NULL); \ + if (value != NULL) { \ + ga_osrelease_replace_special(value); \ + info->has_ ## field = true; \ + info->field = value; \ + } \ +} while (0) + GET_FIELD(id, "ID"); + GET_FIELD(name, "NAME"); + GET_FIELD(pretty_name, "PRETTY_NAME"); + GET_FIELD(version, "VERSION"); + GET_FIELD(version_id, "VERSION_ID"); + GET_FIELD(variant, "VARIANT"); + GET_FIELD(variant_id, "VARIANT_ID"); +#undef GET_FIELD + + g_key_file_free(osrelease); + } + + return info; +} diff --git a/qga/commands-win32.c b/qga/commands-win32.c index 6f1645747b..619dbd2bc2 100644 --- a/qga/commands-win32.c +++ b/qga/commands-win32.c @@ -1642,3 +1642,194 @@ GuestUserList *qmp_guest_get_users(Error **err) return NULL; #endif } + +typedef struct _ga_matrix_lookup_t { + int major; + int minor; + char const *version; + char const *version_id; +} ga_matrix_lookup_t; + +static ga_matrix_lookup_t const WIN_VERSION_MATRIX[2][8] = { + { + /* Desktop editions */ + { 5, 0, "Microsoft Windows 2000", "2000"}, + { 5, 1, "Microsoft Windows XP", "xp"}, + { 6, 0, "Microsoft Windows Vista", "vista"}, + { 6, 1, "Microsoft Windows 7" "7"}, + { 6, 2, "Microsoft Windows 8", "8"}, + { 6, 3, "Microsoft Windows 8.1", "8.1"}, + {10, 0, "Microsoft Windows 10", "10"}, + { 0, 0, 0} + },{ + /* Server editions */ + { 5, 2, "Microsoft Windows Server 2003", "2003"}, + { 6, 0, "Microsoft Windows Server 2008", "2008"}, + { 6, 1, "Microsoft Windows Server 2008 R2", "2008r2"}, + { 6, 2, "Microsoft Windows Server 2012", "2012"}, + { 6, 3, "Microsoft Windows Server 2012 R2", "2012r2"}, + {10, 0, "Microsoft Windows Server 2016", "2016"}, + { 0, 0, 0}, + { 0, 0, 0} + } +}; + +static void ga_get_win_version(RTL_OSVERSIONINFOEXW *info, Error **errp) +{ + typedef NTSTATUS(WINAPI * rtl_get_version_t)( + RTL_OSVERSIONINFOEXW *os_version_info_ex); + + info->dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW); + + HMODULE module = GetModuleHandle("ntdll"); + PVOID fun = GetProcAddress(module, "RtlGetVersion"); + if (fun == NULL) { + error_setg(errp, QERR_QGA_COMMAND_FAILED, + "Failed to get address of RtlGetVersion"); + return; + } + + rtl_get_version_t rtl_get_version = (rtl_get_version_t)fun; + rtl_get_version(info); + return; +} + +static char *ga_get_win_name(OSVERSIONINFOEXW const *os_version, bool id) +{ + DWORD major = os_version->dwMajorVersion; + DWORD minor = os_version->dwMinorVersion; + int tbl_idx = (os_version->wProductType != VER_NT_WORKSTATION); + ga_matrix_lookup_t const *table = WIN_VERSION_MATRIX[tbl_idx]; + while (table->version != NULL) { + if (major == table->major && minor == table->minor) { + if (id) { + return g_strdup(table->version_id); + } else { + return g_strdup(table->version); + } + } + ++table; + } + slog("failed to lookup Windows version: major=%lu, minor=%lu", + major, minor); + return g_strdup("N/A"); +} + +static char *ga_get_win_product_name(Error **errp) +{ + HKEY key = NULL; + DWORD size = 128; + char *result = g_malloc0(size); + LONG err = ERROR_SUCCESS; + + err = RegOpenKeyA(HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + &key); + if (err != ERROR_SUCCESS) { + error_setg_win32(errp, err, "failed to open registry key"); + goto fail; + } + + err = RegQueryValueExA(key, "ProductName", NULL, NULL, + (LPBYTE)result, &size); + if (err == ERROR_MORE_DATA) { + slog("ProductName longer than expected (%lu bytes), retrying", + size); + g_free(result); + result = NULL; + if (size > 0) { + result = g_malloc0(size); + err = RegQueryValueExA(key, "ProductName", NULL, NULL, + (LPBYTE)result, &size); + } + } + if (err != ERROR_SUCCESS) { + error_setg_win32(errp, err, "failed to retrive ProductName"); + goto fail; + } + + return result; + +fail: + g_free(result); + return NULL; +} + +static char *ga_get_current_arch(void) +{ + SYSTEM_INFO info; + GetNativeSystemInfo(&info); + char *result = NULL; + switch (info.wProcessorArchitecture) { + case PROCESSOR_ARCHITECTURE_AMD64: + result = g_strdup("x86_64"); + break; + case PROCESSOR_ARCHITECTURE_ARM: + result = g_strdup("arm"); + break; + case PROCESSOR_ARCHITECTURE_IA64: + result = g_strdup("ia64"); + break; + case PROCESSOR_ARCHITECTURE_INTEL: + result = g_strdup("x86"); + break; + case PROCESSOR_ARCHITECTURE_UNKNOWN: + default: + slog("unknown processor architecture 0x%0x", + info.wProcessorArchitecture); + result = g_strdup("unknown"); + break; + } + return result; +} + +GuestOSInfo *qmp_guest_get_osinfo(Error **errp) +{ + Error *local_err = NULL; + OSVERSIONINFOEXW os_version = {0}; + bool server; + char *product_name; + GuestOSInfo *info; + + ga_get_win_version(&os_version, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return NULL; + } + + server = os_version.wProductType != VER_NT_WORKSTATION; + product_name = ga_get_win_product_name(&local_err); + if (product_name == NULL) { + error_propagate(errp, local_err); + return NULL; + } + + info = g_new0(GuestOSInfo, 1); + + info->has_kernel_version = true; + info->kernel_version = g_strdup_printf("%lu.%lu", + os_version.dwMajorVersion, + os_version.dwMinorVersion); + info->has_kernel_release = true; + info->kernel_release = g_strdup_printf("%lu", + os_version.dwBuildNumber); + info->has_machine = true; + info->machine = ga_get_current_arch(); + + info->has_id = true; + info->id = g_strdup("mswindows"); + info->has_name = true; + info->name = g_strdup("Microsoft Windows"); + info->has_pretty_name = true; + info->pretty_name = product_name; + info->has_version = true; + info->version = ga_get_win_name(&os_version, false); + info->has_version_id = true; + info->version_id = ga_get_win_name(&os_version, true); + info->has_variant = true; + info->variant = g_strdup(server ? "server" : "client"); + info->has_variant_id = true; + info->variant_id = g_strdup(server ? "server" : "client"); + + return info; +} diff --git a/qga/installer/qemu-ga.wxs b/qga/installer/qemu-ga.wxs index fa2260cafa..5af11627f8 100644 --- a/qga/installer/qemu-ga.wxs +++ b/qga/installer/qemu-ga.wxs @@ -125,6 +125,9 @@ <Component Id="libwinpthread" Guid="{6C117C78-0F47-4B07-8F34-6BEE11643829}"> <File Id="libwinpthread_1.dll" Name="libwinpthread-1.dll" Source="$(var.Mingw_bin)/libwinpthread-1.dll" KeyPath="yes" DiskId="1"/> </Component> + <Component Id="libpcre" Guid="{7A86B45E-A009-489A-A849-CE3BACF03CD0}"> + <File Id="libpcre_1.dll" Name="libpcre-1.dll" Source="$(var.Mingw_bin)/libpcre-1.dll" KeyPath="yes" DiskId="1"/> + </Component> <Component Id="registry_entries" Guid="{D075D109-51CA-11E3-9F8B-000C29858960}"> <RegistryKey Root="HKLM" Key="Software\$(env.QEMU_GA_MANUFACTURER)\$(env.QEMU_GA_DISTRO)\Tools\QemuGA"> @@ -173,6 +176,7 @@ <ComponentRef Id="libssp" /> <ComponentRef Id="libwinpthread" /> <ComponentRef Id="registry_entries" /> + <ComponentRef Id="libpcre" /> </Feature> <InstallExecuteSequence> diff --git a/qga/main.c b/qga/main.c index 405c1290f8..1b381d0bf3 100644 --- a/qga/main.c +++ b/qga/main.c @@ -1074,7 +1074,12 @@ static void config_dump(GAConfig *config) g_free(tmp); tmp = g_key_file_to_data(keyfile, NULL, &error); - printf("%s", tmp); + if (error) { + g_critical("Failed to dump keyfile: %s", error->message); + g_clear_error(&error); + } else { + printf("%s", tmp); + } g_free(tmp); g_key_file_free(keyfile); @@ -1314,7 +1319,7 @@ static int run_agent(GAState *s, GAConfig *config, int socket_activation) ga_command_state_init(s, s->command_state); ga_command_state_init_all(s->command_state); json_message_parser_init(&s->parser, process_event); - ga_state = s; + #ifndef _WIN32 if (!register_signal_handlers()) { g_critical("failed to register signal handlers"); diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json index 03743ab905..90a0c8602b 100644 --- a/qga/qapi-schema.json +++ b/qga/qapi-schema.json @@ -1126,3 +1126,68 @@ ## { 'command': 'guest-get-timezone', 'returns': 'GuestTimezone' } + +## +# @GuestOSInfo: +# +# @kernel-release: +# * POSIX: release field returned by uname(2) +# * Windows: version number of the OS +# @kernel-version: +# * POSIX: version field returned by uname(2) +# * Windows: build number of the OS +# @machine: +# * POSIX: machine field returned by uname(2) +# * Windows: one of x86, x86_64, arm, ia64 +# @id: +# * POSIX: as defined by os-release(5) +# * Windows: contains string "mswindows" +# @name: +# * POSIX: as defined by os-release(5) +# * Windows: contains string "Microsoft Windows" +# @pretty-name: +# * POSIX: as defined by os-release(5) +# * Windows: product name, e.g. "Microsoft Windows 10 Enterprise" +# @version: +# * POSIX: as defined by os-release(5) +# * Windows: long version string, e.g. "Microsoft Windows Server 2008" +# @version-id: +# * POSIX: as defined by os-release(5) +# * Windows: short version identifier, e.g. "7" or "20012r2" +# @variant: +# * POSIX: as defined by os-release(5) +# * Windows: contains string "server" or "client" +# @variant-id: +# * POSIX: as defined by os-release(5) +# * Windows: contains string "server" or "client" +# +# Notes: +# +# On POSIX systems the fields @id, @name, @pretty-name, @version, @version-id, +# @variant and @variant-id follow the definition specified in os-release(5). +# Refer to the manual page for exact description of the fields. Their values +# are taken from the os-release file. If the file is not present in the system, +# or the values are not present in the file, the fields are not included. +# +# On Windows the values are filled from information gathered from the system. +# +# Since: 2.10 +## +{ 'struct': 'GuestOSInfo', + 'data': { + '*kernel-release': 'str', '*kernel-version': 'str', + '*machine': 'str', '*id': 'str', '*name': 'str', + '*pretty-name': 'str', '*version': 'str', '*version-id': 'str', + '*variant': 'str', '*variant-id': 'str' } } + +## +# @guest-get-osinfo: +# +# Retrieve guest operating system information +# +# Returns: @GuestOSInfo +# +# Since: 2.10 +## +{ 'command': 'guest-get-osinfo', + 'returns': 'GuestOSInfo' } diff --git a/qga/vss-win32/install.cpp b/qga/vss-win32/install.cpp index f41fcdfdda..ba7c94eb25 100644 --- a/qga/vss-win32/install.cpp +++ b/qga/vss-win32/install.cpp @@ -18,6 +18,9 @@ #include <wbemidl.h> #include <comdef.h> #include <comutil.h> +#include <sddl.h> + +#define BUFFER_SIZE 1024 extern HINSTANCE g_hinstDll; @@ -135,6 +138,27 @@ out: return hr; } +/* Acquire group or user name by SID */ +static HRESULT getNameByStringSID( + const wchar_t *sid, LPWSTR buffer, LPDWORD bufferLen) +{ + HRESULT hr = S_OK; + PSID psid = NULL; + SID_NAME_USE groupType; + DWORD domainNameLen = BUFFER_SIZE; + wchar_t domainName[BUFFER_SIZE]; + + chk(ConvertStringSidToSidW(sid, &psid)); + LookupAccountSidW(NULL, psid, buffer, bufferLen, + domainName, &domainNameLen, &groupType); + hr = HRESULT_FROM_WIN32(GetLastError()); + + LocalFree(psid); + +out: + return hr; +} + /* Find and iterate QGA VSS provider in COM+ Application Catalog */ static HRESULT QGAProviderFind( HRESULT (*found)(ICatalogCollection *, int, void *), void *arg) @@ -216,6 +240,10 @@ STDAPI COMRegister(void) CHAR dllPath[MAX_PATH], tlbPath[MAX_PATH]; bool unregisterOnFailure = false; int count = 0; + DWORD bufferLen = BUFFER_SIZE; + wchar_t buffer[BUFFER_SIZE]; + const wchar_t *administratorsGroupSID = L"S-1-5-32-544"; + const wchar_t *systemUserSID = L"S-1-5-18"; if (!g_hinstDll) { errmsg(E_FAIL, "Failed to initialize DLL"); @@ -284,11 +312,12 @@ STDAPI COMRegister(void) /* Setup roles of the applicaion */ + chk(getNameByStringSID(administratorsGroupSID, buffer, &bufferLen)); chk(pApps->GetCollection(_bstr_t(L"Roles"), key, (IDispatch **)pRoles.replace())); chk(pRoles->Populate()); chk(pRoles->Add((IDispatch **)pObj.replace())); - chk(put_Value(pObj, L"Name", L"Administrators")); + chk(put_Value(pObj, L"Name", buffer)); chk(put_Value(pObj, L"Description", L"Administrators group")); chk(pRoles->SaveChanges(&n)); chk(pObj->get_Key(&key)); @@ -303,8 +332,10 @@ STDAPI COMRegister(void) chk(GetAdminName(&name)); chk(put_Value(pObj, L"User", _bstr_t(".\\") + name)); + bufferLen = BUFFER_SIZE; + chk(getNameByStringSID(systemUserSID, buffer, &bufferLen)); chk(pUsersInRole->Add((IDispatch **)pObj.replace())); - chk(put_Value(pObj, L"User", L"SYSTEM")); + chk(put_Value(pObj, L"User", buffer)); chk(pUsersInRole->SaveChanges(&n)); out: diff --git a/qmp.c b/qmp.c index 2cd40c3080..b86201e349 100644 --- a/qmp.c +++ b/qmp.c @@ -430,9 +430,15 @@ static void qom_list_types_tramp(ObjectClass *klass, void *data) { ObjectTypeInfoList *e, **pret = data; ObjectTypeInfo *info; + ObjectClass *parent = object_class_get_parent(klass); info = g_malloc0(sizeof(*info)); info->name = g_strdup(object_class_get_name(klass)); + info->has_abstract = info->abstract = object_class_is_abstract(klass); + if (parent) { + info->has_parent = true; + info->parent = g_strdup(object_class_get_name(parent)); + } e = g_malloc0(sizeof(*e)); e->value = info; diff --git a/qom/object.c b/qom/object.c index dfdbd50f04..fe6e744b4d 100644 --- a/qom/object.c +++ b/qom/object.c @@ -1712,15 +1712,13 @@ static Object *object_resolve_partial_path(Object *parent, typename, ambiguous); if (found) { if (obj) { - if (ambiguous) { - *ambiguous = true; - } + *ambiguous = true; return NULL; } obj = found; } - if (ambiguous && *ambiguous) { + if (*ambiguous) { return NULL; } } @@ -1729,7 +1727,7 @@ static Object *object_resolve_partial_path(Object *parent, } Object *object_resolve_path_type(const char *path, const char *typename, - bool *ambiguous) + bool *ambiguousp) { Object *obj; gchar **parts; @@ -1738,11 +1736,12 @@ Object *object_resolve_path_type(const char *path, const char *typename, assert(parts); if (parts[0] == NULL || strcmp(parts[0], "") != 0) { - if (ambiguous) { - *ambiguous = false; - } + bool ambiguous = false; obj = object_resolve_partial_path(object_get_root(), parts, - typename, ambiguous); + typename, &ambiguous); + if (ambiguousp) { + *ambiguousp = ambiguous; + } } else { obj = object_resolve_abs_path(object_get_root(), parts, typename, 1); } diff --git a/scripts/device-crash-test b/scripts/device-crash-test index 5f90e9bb54..e77b693eb2 100755 --- a/scripts/device-crash-test +++ b/scripts/device-crash-test @@ -219,7 +219,7 @@ ERROR_WHITELIST = [ {'exitcode':-6, 'log':r"Object .* is not an instance of type spapr-machine", 'loglevel':logging.ERROR}, {'exitcode':-6, 'log':r"Object .* is not an instance of type generic-pc-machine", 'loglevel':logging.ERROR}, {'exitcode':-6, 'log':r"Object .* is not an instance of type e500-ccsr", 'loglevel':logging.ERROR}, - {'exitcode':-6, 'log':r"vmstate_register_with_alias_id: Assertion `!se->compat || se->instance_id == 0' failed", 'loglevel':logging.ERROR}, + {'exitcode':-6, 'log':r"vmstate_register_with_alias_id: Assertion `!se->compat \|\| se->instance_id == 0' failed", 'loglevel':logging.ERROR}, {'exitcode':-11, 'device':'stm32f205-soc', 'loglevel':logging.ERROR, 'expected':True}, {'exitcode':-11, 'device':'xlnx,zynqmp', 'loglevel':logging.ERROR, 'expected':True}, {'exitcode':-11, 'device':'mips-cps', 'loglevel':logging.ERROR, 'expected':True}, diff --git a/target/i386/cpu.c b/target/i386/cpu.c index 4de91d5801..0bbda76323 100644 --- a/target/i386/cpu.c +++ b/target/i386/cpu.c @@ -1331,7 +1331,7 @@ static X86CPUDefinition builtin_x86_defs[] = { CPUID_7_0_EBX_RTM | CPUID_7_0_EBX_RDSEED | CPUID_7_0_EBX_ADX | CPUID_7_0_EBX_SMAP | CPUID_7_0_EBX_MPX, /* Missing: XSAVES (not supported by some Linux versions, - * including v4.1 to v4.6). + * including v4.1 to v4.12). * KVM doesn't yet expose any XSAVES state save component, * and the only one defined in Skylake (processor tracing) * probably will block migration anyway. @@ -1345,6 +1345,54 @@ static X86CPUDefinition builtin_x86_defs[] = { .model_id = "Intel Core Processor (Skylake)", }, { + .name = "Skylake-Server", + .level = 0xd, + .vendor = CPUID_VENDOR_INTEL, + .family = 6, + .model = 85, + .stepping = 4, + .features[FEAT_1_EDX] = + CPUID_VME | CPUID_SSE2 | CPUID_SSE | CPUID_FXSR | CPUID_MMX | + CPUID_CLFLUSH | CPUID_PSE36 | CPUID_PAT | CPUID_CMOV | CPUID_MCA | + CPUID_PGE | CPUID_MTRR | CPUID_SEP | CPUID_APIC | CPUID_CX8 | + CPUID_MCE | CPUID_PAE | CPUID_MSR | CPUID_TSC | CPUID_PSE | + CPUID_DE | CPUID_FP87, + .features[FEAT_1_ECX] = + CPUID_EXT_AVX | CPUID_EXT_XSAVE | CPUID_EXT_AES | + CPUID_EXT_POPCNT | CPUID_EXT_X2APIC | CPUID_EXT_SSE42 | + CPUID_EXT_SSE41 | CPUID_EXT_CX16 | CPUID_EXT_SSSE3 | + CPUID_EXT_PCLMULQDQ | CPUID_EXT_SSE3 | + CPUID_EXT_TSC_DEADLINE_TIMER | CPUID_EXT_FMA | CPUID_EXT_MOVBE | + CPUID_EXT_PCID | CPUID_EXT_F16C | CPUID_EXT_RDRAND, + .features[FEAT_8000_0001_EDX] = + CPUID_EXT2_LM | CPUID_EXT2_PDPE1GB | CPUID_EXT2_RDTSCP | + CPUID_EXT2_NX | CPUID_EXT2_SYSCALL, + .features[FEAT_8000_0001_ECX] = + CPUID_EXT3_ABM | CPUID_EXT3_LAHF_LM | CPUID_EXT3_3DNOWPREFETCH, + .features[FEAT_7_0_EBX] = + CPUID_7_0_EBX_FSGSBASE | CPUID_7_0_EBX_BMI1 | + CPUID_7_0_EBX_HLE | CPUID_7_0_EBX_AVX2 | CPUID_7_0_EBX_SMEP | + CPUID_7_0_EBX_BMI2 | CPUID_7_0_EBX_ERMS | CPUID_7_0_EBX_INVPCID | + CPUID_7_0_EBX_RTM | CPUID_7_0_EBX_RDSEED | CPUID_7_0_EBX_ADX | + CPUID_7_0_EBX_SMAP | CPUID_7_0_EBX_MPX | CPUID_7_0_EBX_CLWB | + CPUID_7_0_EBX_AVX512F | CPUID_7_0_EBX_AVX512DQ | + CPUID_7_0_EBX_AVX512BW | CPUID_7_0_EBX_AVX512CD | + CPUID_7_0_EBX_AVX512VL, + /* Missing: XSAVES (not supported by some Linux versions, + * including v4.1 to v4.12). + * KVM doesn't yet expose any XSAVES state save component, + * and the only one defined in Skylake (processor tracing) + * probably will block migration anyway. + */ + .features[FEAT_XSAVE] = + CPUID_XSAVE_XSAVEOPT | CPUID_XSAVE_XSAVEC | + CPUID_XSAVE_XGETBV1, + .features[FEAT_6_EAX] = + CPUID_6_EAX_ARAT, + .xlevel = 0x80000008, + .model_id = "Intel Xeon Processor (Skylake)", + }, + { .name = "Opteron_G1", .level = 5, .vendor = CPUID_VENDOR_AMD, @@ -2632,12 +2680,15 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, CPUState *cs = CPU(cpu); uint32_t pkg_offset; uint32_t limit; + uint32_t signature[3]; /* Calculate & apply limits for different index ranges */ if (index >= 0xC0000000) { limit = env->cpuid_xlevel2; } else if (index >= 0x80000000) { limit = env->cpuid_xlevel; + } else if (index >= 0x40000000) { + limit = 0x40000001; } else { limit = env->cpuid_level; } @@ -2872,6 +2923,30 @@ void cpu_x86_cpuid(CPUX86State *env, uint32_t index, uint32_t count, } break; } + case 0x40000000: + /* + * CPUID code in kvm_arch_init_vcpu() ignores stuff + * set here, but we restrict to TCG none the less. + */ + if (tcg_enabled() && cpu->expose_tcg) { + memcpy(signature, "TCGTCGTCGTCG", 12); + *eax = 0x40000001; + *ebx = signature[0]; + *ecx = signature[1]; + *edx = signature[2]; + } else { + *eax = 0; + *ebx = 0; + *ecx = 0; + *edx = 0; + } + break; + case 0x40000001: + *eax = 0; + *ebx = 0; + *ecx = 0; + *edx = 0; + break; case 0x80000000: *eax = env->cpuid_xlevel; *ebx = env->cpuid_vendor1; @@ -4018,6 +4093,7 @@ static Property x86_cpu_properties[] = { DEFINE_PROP_BOOL("kvm-no-smi-migration", X86CPU, kvm_no_smi_migration, false), DEFINE_PROP_BOOL("vmware-cpuid-freq", X86CPU, vmware_cpuid_freq, true), + DEFINE_PROP_BOOL("tcg-cpuid", X86CPU, expose_tcg, true), DEFINE_PROP_END_OF_LIST() }; diff --git a/target/i386/cpu.h b/target/i386/cpu.h index 7a228afd04..051867399b 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h @@ -1218,6 +1218,7 @@ struct X86CPU { bool check_cpuid; bool enforce_cpuid; bool expose_kvm; + bool expose_tcg; bool migratable; bool max_features; /* Enable all supported features automatically */ uint32_t apic_id; diff --git a/target/s390x/cpu_models.c b/target/s390x/cpu_models.c index c654279a6c..f4e5bb6611 100644 --- a/target/s390x/cpu_models.c +++ b/target/s390x/cpu_models.c @@ -75,6 +75,7 @@ static S390CPUDef s390_cpu_defs[] = { CPUDEF_INIT(0x2964, 13, 1, 47, 0x08000000U, "z13", "IBM z13 GA1"), CPUDEF_INIT(0x2964, 13, 2, 47, 0x08000000U, "z13.2", "IBM z13 GA2"), CPUDEF_INIT(0x2965, 13, 2, 47, 0x08000000U, "z13s", "IBM z13s GA1"), + CPUDEF_INIT(0x3906, 14, 1, 47, 0x08000000U, "z14", "IBM z14 GA1"), }; void s390_cpudef_featoff(uint8_t gen, uint8_t ec_ga, S390Feat feat) @@ -779,14 +780,19 @@ static void add_qemu_cpu_model_features(S390FeatBitmap fbm) { static const int feats[] = { S390_FEAT_DAT_ENH, + S390_FEAT_IDTE_SEGMENT, S390_FEAT_STFLE, S390_FEAT_EXTENDED_IMMEDIATE, S390_FEAT_EXTENDED_TRANSLATION_2, + S390_FEAT_EXTENDED_TRANSLATION_3, S390_FEAT_LONG_DISPLACEMENT, S390_FEAT_LONG_DISPLACEMENT_FAST, S390_FEAT_ETF2_ENH, S390_FEAT_STORE_CLOCK_FAST, S390_FEAT_MOVE_WITH_OPTIONAL_SPEC, + S390_FEAT_ETF3_ENH, + S390_FEAT_COMPARE_AND_SWAP_AND_STORE, + S390_FEAT_COMPARE_AND_SWAP_AND_STORE_2, S390_FEAT_GENERAL_INSTRUCTIONS_EXT, S390_FEAT_EXECUTE_EXT, S390_FEAT_FLOATING_POINT_SUPPPORT_ENH, diff --git a/target/s390x/gen-features.c b/target/s390x/gen-features.c index af14b11199..cf69157610 100644 --- a/target/s390x/gen-features.c +++ b/target/s390x/gen-features.c @@ -338,6 +338,14 @@ static uint16_t base_GEN13_GA1[] = { #define base_GEN13_GA2 EmptyFeat +static uint16_t base_GEN14_GA1[] = { + S390_FEAT_ENTROPY_ENC_COMP, + S390_FEAT_MISC_INSTRUCTION_EXT, + S390_FEAT_SEMAPHORE_ASSIST, + S390_FEAT_TIME_SLICE_INSTRUMENTATION, + S390_FEAT_ORDER_PRESERVING_COMPRESSION, +}; + /* Full features (in order of release) * Automatically includes corresponding base features. * Full features are all features this hardware supports even if kvm/QEMU do not @@ -442,6 +450,22 @@ static uint16_t full_GEN13_GA1[] = { #define full_GEN13_GA2 EmptyFeat +static uint16_t full_GEN14_GA1[] = { + S390_FEAT_INSTRUCTION_EXEC_PROT, + S390_FEAT_GUARDED_STORAGE, + S390_FEAT_VECTOR_PACKED_DECIMAL, + S390_FEAT_VECTOR_ENH, + S390_FEAT_MULTIPLE_EPOCH, + S390_FEAT_TEST_PENDING_EXT_INTERRUPTION, + S390_FEAT_INSERT_REFERENCE_BITS_MULT, + S390_FEAT_GROUP_MSA_EXT_6, + S390_FEAT_GROUP_MSA_EXT_7, + S390_FEAT_GROUP_MSA_EXT_8, + S390_FEAT_CMM_NT, + S390_FEAT_HPMA2, + S390_FEAT_SIE_KSS, +}; + /* Default features (in order of release) * Automatically includes corresponding base features. * Default features are all features this version of QEMU supports for this @@ -502,6 +526,18 @@ static uint16_t default_GEN13_GA1[] = { #define default_GEN13_GA2 EmptyFeat +static uint16_t default_GEN14_GA1[] = { + S390_FEAT_ADAPTER_INT_SUPPRESSION, + S390_FEAT_INSTRUCTION_EXEC_PROT, + S390_FEAT_GUARDED_STORAGE, + S390_FEAT_VECTOR_PACKED_DECIMAL, + S390_FEAT_VECTOR_ENH, + S390_FEAT_GROUP_MSA_EXT_6, + S390_FEAT_GROUP_MSA_EXT_7, + S390_FEAT_GROUP_MSA_EXT_8, + S390_FEAT_SIE_KSS, +}; + /****** END FEATURE DEFS ******/ #define _YEARS "2016" @@ -559,6 +595,7 @@ static CpuFeatDefSpec CpuFeatDef[] = { CPU_FEAT_INITIALIZER(GEN12_GA2), CPU_FEAT_INITIALIZER(GEN13_GA1), CPU_FEAT_INITIALIZER(GEN13_GA2), + CPU_FEAT_INITIALIZER(GEN14_GA1), }; #define FEAT_GROUP_INITIALIZER(_name) \ diff --git a/target/s390x/helper.h b/target/s390x/helper.h index 964097b2ce..4b0290774e 100644 --- a/target/s390x/helper.h +++ b/target/s390x/helper.h @@ -12,7 +12,8 @@ DEF_HELPER_FLAGS_3(divs32, TCG_CALL_NO_WG, s64, env, s64, s64) DEF_HELPER_FLAGS_3(divu32, TCG_CALL_NO_WG, i64, env, i64, i64) DEF_HELPER_FLAGS_3(divs64, TCG_CALL_NO_WG, s64, env, s64, s64) DEF_HELPER_FLAGS_4(divu64, TCG_CALL_NO_WG, i64, env, i64, i64, i64) -DEF_HELPER_4(srst, i64, env, i64, i64, i64) +DEF_HELPER_3(srst, void, env, i32, i32) +DEF_HELPER_3(srstu, void, env, i32, i32) DEF_HELPER_4(clst, i64, env, i64, i64, i64) DEF_HELPER_FLAGS_4(mvn, TCG_CALL_NO_WG, void, env, i32, i64, i64) DEF_HELPER_FLAGS_4(mvo, TCG_CALL_NO_WG, void, env, i32, i64, i64) @@ -33,6 +34,7 @@ DEF_HELPER_3(celgb, i64, env, i64, i32) DEF_HELPER_3(cdlgb, i64, env, i64, i32) DEF_HELPER_3(cxlgb, i64, env, i64, i32) DEF_HELPER_4(cdsg, void, env, i64, i32, i32) +DEF_HELPER_4(csst, i32, env, i32, i64, i64) DEF_HELPER_FLAGS_3(aeb, TCG_CALL_NO_WG, i64, env, i64, i64) DEF_HELPER_FLAGS_3(adb, TCG_CALL_NO_WG, i64, env, i64, i64) DEF_HELPER_FLAGS_5(axb, TCG_CALL_NO_WG, i64, env, i64, i64, i64, i64) @@ -95,6 +97,7 @@ DEF_HELPER_FLAGS_3(tp, TCG_CALL_NO_WG, i32, env, i64, i32) DEF_HELPER_FLAGS_4(tr, TCG_CALL_NO_WG, void, env, i32, i64, i64) DEF_HELPER_4(tre, i64, env, i64, i64, i64) DEF_HELPER_4(trt, i32, env, i32, i64, i64) +DEF_HELPER_4(trtr, i32, env, i32, i64, i64) DEF_HELPER_5(trXX, i32, env, i32, i32, i32, i32) DEF_HELPER_4(cksm, i64, env, i64, i64, i64) DEF_HELPER_FLAGS_5(calc_cc, TCG_CALL_NO_RWG_SE, i32, env, i32, i64, i64, i64) @@ -106,6 +109,12 @@ DEF_HELPER_2(stfle, i32, env, i64) DEF_HELPER_FLAGS_2(lpq, TCG_CALL_NO_WG, i64, env, i64) DEF_HELPER_FLAGS_4(stpq, TCG_CALL_NO_WG, void, env, i64, i64, i64) DEF_HELPER_4(mvcos, i32, env, i64, i64, i64) +DEF_HELPER_4(cu12, i32, env, i32, i32, i32) +DEF_HELPER_4(cu14, i32, env, i32, i32, i32) +DEF_HELPER_4(cu21, i32, env, i32, i32, i32) +DEF_HELPER_4(cu24, i32, env, i32, i32, i32) +DEF_HELPER_4(cu41, i32, env, i32, i32, i32) +DEF_HELPER_4(cu42, i32, env, i32, i32, i32) #ifndef CONFIG_USER_ONLY DEF_HELPER_3(servc, i32, env, i64, i64) diff --git a/target/s390x/insn-data.def b/target/s390x/insn-data.def index d3bb8516ed..ad84c748e3 100644 --- a/target/s390x/insn-data.def +++ b/target/s390x/insn-data.def @@ -265,6 +265,8 @@ D(0xbb00, CDS, RS_a, Z, r3_D32, r1_D32, new, r1_D32, cs, 0, MO_TEQ) D(0xeb31, CDSY, RSY_a, LD, r3_D32, r1_D32, new, r1_D32, cs, 0, MO_TEQ) C(0xeb3e, CDSG, RSY_a, Z, 0, 0, 0, 0, cdsg, 0) +/* COMPARE AND SWAP AND STORE */ + C(0xc802, CSST, SSF, CASS, la1, a2, 0, 0, csst, 0) /* COMPARE AND TRAP */ D(0xb972, CRT, RRF_c, GIE, r1_32s, r2_32s, 0, 0, ct, 0, 0) @@ -311,6 +313,19 @@ C(0xb3a1, CDLGBR, RRF_e, FPE, 0, r2_o, f1, 0, cdlgb, 0) C(0xb3a2, CXLGBR, RRF_e, FPE, 0, r2_o, x1, 0, cxlgb, 0) +/* CONVERT UTF-8 TO UTF-16 */ + D(0xb2a7, CU12, RRF_c, Z, 0, 0, 0, 0, cuXX, 0, 12) +/* CONVERT UTF-8 TO UTF-32 */ + D(0xb9b0, CU14, RRF_c, ETF3, 0, 0, 0, 0, cuXX, 0, 14) +/* CONVERT UTF-16 to UTF-8 */ + D(0xb2a6, CU21, RRF_c, Z, 0, 0, 0, 0, cuXX, 0, 21) +/* CONVERT UTF-16 to UTF-32 */ + D(0xb9b1, CU24, RRF_c, ETF3, 0, 0, 0, 0, cuXX, 0, 24) +/* CONVERT UTF-32 to UTF-8 */ + D(0xb9b2, CU41, RRF_c, ETF3, 0, 0, 0, 0, cuXX, 0, 41) +/* CONVERT UTF-32 to UTF-16 */ + D(0xb9b3, CU42, RRF_c, ETF3, 0, 0, 0, 0, cuXX, 0, 42) + /* DIVIDE */ C(0x1d00, DR, RR_a, Z, r1_D32, r2_32s, new_P, r1_P32, divs32, 0) C(0x5d00, D, RX_a, Z, r1_D32, m2_32s, new_P, r1_P32, divs32, 0) @@ -721,7 +736,9 @@ C(0xec57, RXSBG, RIE_f, GIE, 0, r2, r1, 0, rosbg, 0) /* SEARCH STRING */ - C(0xb25e, SRST, RRE, Z, r1_o, r2_o, 0, 0, srst, 0) + C(0xb25e, SRST, RRE, Z, 0, 0, 0, 0, srst, 0) +/* SEARCH STRING UNICODE */ + C(0xb9be, SRSTU, RRE, ETF3, 0, 0, 0, 0, srstu, 0) /* SET ACCESS */ C(0xb24e, SAR, RRE, Z, 0, r2_o, 0, 0, sar, 0) @@ -899,6 +916,8 @@ C(0xdc00, TR, SS_a, Z, la1, a2, 0, 0, tr, 0) /* TRANSLATE AND TEST */ C(0xdd00, TRT, SS_a, Z, la1, a2, 0, 0, trt, 0) +/* TRANSLATE AND TEST REVERSE */ + C(0xd000, TRTR, SS_a, ETF3, la1, a2, 0, 0, trtr, 0) /* TRANSLATE EXTENDED */ C(0xb2a5, TRE, RRE, Z, 0, r2, r1_P, 0, tre, 0) diff --git a/target/s390x/mem_helper.c b/target/s390x/mem_helper.c index ede84711d1..cdc78aa3d4 100644 --- a/target/s390x/mem_helper.c +++ b/target/s390x/mem_helper.c @@ -538,18 +538,21 @@ static inline void set_length(CPUS390XState *env, int reg, uint64_t length) } /* search string (c is byte to search, r2 is string, r1 end of string) */ -uint64_t HELPER(srst)(CPUS390XState *env, uint64_t r0, uint64_t end, - uint64_t str) +void HELPER(srst)(CPUS390XState *env, uint32_t r1, uint32_t r2) { uintptr_t ra = GETPC(); + uint64_t end, str; uint32_t len; - uint8_t v, c = r0; + uint8_t v, c = env->regs[0]; - str = wrap_address(env, str); - end = wrap_address(env, end); + /* Bits 32-55 must contain all 0. */ + if (env->regs[0] & 0xffffff00u) { + cpu_restore_state(ENV_GET_CPU(env), ra); + program_interrupt(env, PGM_SPECIFICATION, 6); + } - /* Assume for now that R2 is unmodified. */ - env->retxl = str; + str = get_address(env, r2); + end = get_address(env, r1); /* Lest we fail to service interrupts in a timely manner, limit the amount of work we're willing to do. For now, let's cap at 8k. */ @@ -557,20 +560,61 @@ uint64_t HELPER(srst)(CPUS390XState *env, uint64_t r0, uint64_t end, if (str + len == end) { /* Character not found. R1 & R2 are unmodified. */ env->cc_op = 2; - return end; + return; } v = cpu_ldub_data_ra(env, str + len, ra); if (v == c) { /* Character found. Set R1 to the location; R2 is unmodified. */ env->cc_op = 1; - return str + len; + set_address(env, r1, str + len); + return; + } + } + + /* CPU-determined bytes processed. Advance R2 to next byte to process. */ + env->cc_op = 3; + set_address(env, r2, str + len); +} + +void HELPER(srstu)(CPUS390XState *env, uint32_t r1, uint32_t r2) +{ + uintptr_t ra = GETPC(); + uint32_t len; + uint16_t v, c = env->regs[0]; + uint64_t end, str, adj_end; + + /* Bits 32-47 of R0 must be zero. */ + if (env->regs[0] & 0xffff0000u) { + cpu_restore_state(ENV_GET_CPU(env), ra); + program_interrupt(env, PGM_SPECIFICATION, 6); + } + + str = get_address(env, r2); + end = get_address(env, r1); + + /* If the LSB of the two addresses differ, use one extra byte. */ + adj_end = end + ((str ^ end) & 1); + + /* Lest we fail to service interrupts in a timely manner, limit the + amount of work we're willing to do. For now, let's cap at 8k. */ + for (len = 0; len < 0x2000; len += 2) { + if (str + len == adj_end) { + /* End of input found. */ + env->cc_op = 2; + return; + } + v = cpu_lduw_data_ra(env, str + len, ra); + if (v == c) { + /* Character found. Set R1 to the location; R2 is unmodified. */ + env->cc_op = 1; + set_address(env, r1, str + len); + return; } } /* CPU-determined bytes processed. Advance R2 to next byte to process. */ - env->retxl = str + len; env->cc_op = 3; - return end; + set_address(env, r2, str + len); } /* unsigned string compare (c is string terminator) */ @@ -1233,17 +1277,18 @@ uint64_t HELPER(tre)(CPUS390XState *env, uint64_t array, return array + i; } -static uint32_t do_helper_trt(CPUS390XState *env, uint32_t len, uint64_t array, - uint64_t trans, uintptr_t ra) +static inline uint32_t do_helper_trt(CPUS390XState *env, int len, + uint64_t array, uint64_t trans, + int inc, uintptr_t ra) { - uint32_t i; + int i; for (i = 0; i <= len; i++) { - uint8_t byte = cpu_ldub_data_ra(env, array + i, ra); + uint8_t byte = cpu_ldub_data_ra(env, array + i * inc, ra); uint8_t sbyte = cpu_ldub_data_ra(env, trans + byte, ra); if (sbyte != 0) { - set_address(env, 1, array + i); + set_address(env, 1, array + i * inc); env->regs[2] = deposit64(env->regs[2], 0, 8, sbyte); return (i == len) ? 2 : 1; } @@ -1255,7 +1300,13 @@ static uint32_t do_helper_trt(CPUS390XState *env, uint32_t len, uint64_t array, uint32_t HELPER(trt)(CPUS390XState *env, uint32_t len, uint64_t array, uint64_t trans) { - return do_helper_trt(env, len, array, trans, GETPC()); + return do_helper_trt(env, len, array, trans, 1, GETPC()); +} + +uint32_t HELPER(trtr)(CPUS390XState *env, uint32_t len, uint64_t array, + uint64_t trans) +{ + return do_helper_trt(env, len, array, trans, -1, GETPC()); } /* Translate one/two to one/two */ @@ -1353,6 +1404,195 @@ void HELPER(cdsg)(CPUS390XState *env, uint64_t addr, env->regs[r1 + 1] = int128_getlo(oldv); } +uint32_t HELPER(csst)(CPUS390XState *env, uint32_t r3, uint64_t a1, uint64_t a2) +{ +#if !defined(CONFIG_USER_ONLY) || defined(CONFIG_ATOMIC128) + uint32_t mem_idx = cpu_mmu_index(env, false); +#endif + uintptr_t ra = GETPC(); + uint32_t fc = extract32(env->regs[0], 0, 8); + uint32_t sc = extract32(env->regs[0], 8, 8); + uint64_t pl = get_address(env, 1) & -16; + uint64_t svh, svl; + uint32_t cc; + + /* Sanity check the function code and storage characteristic. */ + if (fc > 1 || sc > 3) { + if (!s390_has_feat(S390_FEAT_COMPARE_AND_SWAP_AND_STORE_2)) { + goto spec_exception; + } + if (fc > 2 || sc > 4 || (fc == 2 && (r3 & 1))) { + goto spec_exception; + } + } + + /* Sanity check the alignments. */ + if (extract32(a1, 0, 4 << fc) || extract32(a2, 0, 1 << sc)) { + goto spec_exception; + } + + /* Sanity check writability of the store address. */ +#ifndef CONFIG_USER_ONLY + probe_write(env, a2, mem_idx, ra); +#endif + + /* Note that the compare-and-swap is atomic, and the store is atomic, but + the complete operation is not. Therefore we do not need to assert serial + context in order to implement this. That said, restart early if we can't + support either operation that is supposed to be atomic. */ + if (parallel_cpus) { + int mask = 0; +#if !defined(CONFIG_ATOMIC64) + mask = -8; +#elif !defined(CONFIG_ATOMIC128) + mask = -16; +#endif + if (((4 << fc) | (1 << sc)) & mask) { + cpu_loop_exit_atomic(ENV_GET_CPU(env), ra); + } + } + + /* All loads happen before all stores. For simplicity, load the entire + store value area from the parameter list. */ + svh = cpu_ldq_data_ra(env, pl + 16, ra); + svl = cpu_ldq_data_ra(env, pl + 24, ra); + + switch (fc) { + case 0: + { + uint32_t nv = cpu_ldl_data_ra(env, pl, ra); + uint32_t cv = env->regs[r3]; + uint32_t ov; + + if (parallel_cpus) { +#ifdef CONFIG_USER_ONLY + uint32_t *haddr = g2h(a1); + ov = atomic_cmpxchg__nocheck(haddr, cv, nv); +#else + TCGMemOpIdx oi = make_memop_idx(MO_TEUL | MO_ALIGN, mem_idx); + ov = helper_atomic_cmpxchgl_be_mmu(env, a1, cv, nv, oi, ra); +#endif + } else { + ov = cpu_ldl_data_ra(env, a1, ra); + cpu_stl_data_ra(env, a1, (ov == cv ? nv : ov), ra); + } + cc = (ov != cv); + env->regs[r3] = deposit64(env->regs[r3], 32, 32, ov); + } + break; + + case 1: + { + uint64_t nv = cpu_ldq_data_ra(env, pl, ra); + uint64_t cv = env->regs[r3]; + uint64_t ov; + + if (parallel_cpus) { +#ifdef CONFIG_ATOMIC64 +# ifdef CONFIG_USER_ONLY + uint64_t *haddr = g2h(a1); + ov = atomic_cmpxchg__nocheck(haddr, cv, nv); +# else + TCGMemOpIdx oi = make_memop_idx(MO_TEQ | MO_ALIGN, mem_idx); + ov = helper_atomic_cmpxchgq_be_mmu(env, a1, cv, nv, oi, ra); +# endif +#else + /* Note that we asserted !parallel_cpus above. */ + g_assert_not_reached(); +#endif + } else { + ov = cpu_ldq_data_ra(env, a1, ra); + cpu_stq_data_ra(env, a1, (ov == cv ? nv : ov), ra); + } + cc = (ov != cv); + env->regs[r3] = ov; + } + break; + + case 2: + { + uint64_t nvh = cpu_ldq_data_ra(env, pl, ra); + uint64_t nvl = cpu_ldq_data_ra(env, pl + 8, ra); + Int128 nv = int128_make128(nvl, nvh); + Int128 cv = int128_make128(env->regs[r3 + 1], env->regs[r3]); + Int128 ov; + + if (parallel_cpus) { +#ifdef CONFIG_ATOMIC128 + TCGMemOpIdx oi = make_memop_idx(MO_TEQ | MO_ALIGN_16, mem_idx); + ov = helper_atomic_cmpxchgo_be_mmu(env, a1, cv, nv, oi, ra); + cc = !int128_eq(ov, cv); +#else + /* Note that we asserted !parallel_cpus above. */ + g_assert_not_reached(); +#endif + } else { + uint64_t oh = cpu_ldq_data_ra(env, a1 + 0, ra); + uint64_t ol = cpu_ldq_data_ra(env, a1 + 8, ra); + + ov = int128_make128(ol, oh); + cc = !int128_eq(ov, cv); + if (cc) { + nv = ov; + } + + cpu_stq_data_ra(env, a1 + 0, int128_gethi(nv), ra); + cpu_stq_data_ra(env, a1 + 8, int128_getlo(nv), ra); + } + + env->regs[r3 + 0] = int128_gethi(ov); + env->regs[r3 + 1] = int128_getlo(ov); + } + break; + + default: + g_assert_not_reached(); + } + + /* Store only if the comparison succeeded. Note that above we use a pair + of 64-bit big-endian loads, so for sc < 3 we must extract the value + from the most-significant bits of svh. */ + if (cc == 0) { + switch (sc) { + case 0: + cpu_stb_data_ra(env, a2, svh >> 56, ra); + break; + case 1: + cpu_stw_data_ra(env, a2, svh >> 48, ra); + break; + case 2: + cpu_stl_data_ra(env, a2, svh >> 32, ra); + break; + case 3: + cpu_stq_data_ra(env, a2, svh, ra); + break; + case 4: + if (parallel_cpus) { +#ifdef CONFIG_ATOMIC128 + TCGMemOpIdx oi = make_memop_idx(MO_TEQ | MO_ALIGN_16, mem_idx); + Int128 sv = int128_make128(svl, svh); + helper_atomic_sto_be_mmu(env, a2, sv, oi, ra); +#else + /* Note that we asserted !parallel_cpus above. */ + g_assert_not_reached(); +#endif + } else { + cpu_stq_data_ra(env, a2 + 0, svh, ra); + cpu_stq_data_ra(env, a2 + 8, svl, ra); + } + default: + g_assert_not_reached(); + } + } + + return cc; + + spec_exception: + cpu_restore_state(ENV_GET_CPU(env), ra); + program_interrupt(env, PGM_SPECIFICATION, 6); + g_assert_not_reached(); +} + #if !defined(CONFIG_USER_ONLY) void HELPER(lctlg)(CPUS390XState *env, uint32_t r1, uint64_t a2, uint32_t r3) { @@ -1886,7 +2126,6 @@ void HELPER(ex)(CPUS390XState *env, uint32_t ilen, uint64_t r1, uint64_t addr) [0x6] = do_helper_oc, [0x7] = do_helper_xc, [0xc] = do_helper_tr, - [0xd] = do_helper_trt, }; dx_helper helper = dx[opc & 0xf]; @@ -2007,3 +2246,313 @@ uint32_t HELPER(mvcos)(CPUS390XState *env, uint64_t dest, uint64_t src, return cc; } + +/* Decode a Unicode character. A return value < 0 indicates success, storing + the UTF-32 result into OCHAR and the input length into OLEN. A return + value >= 0 indicates failure, and the CC value to be returned. */ +typedef int (*decode_unicode_fn)(CPUS390XState *env, uint64_t addr, + uint64_t ilen, bool enh_check, uintptr_t ra, + uint32_t *ochar, uint32_t *olen); + +/* Encode a Unicode character. A return value < 0 indicates success, storing + the bytes into ADDR and the output length into OLEN. A return value >= 0 + indicates failure, and the CC value to be returned. */ +typedef int (*encode_unicode_fn)(CPUS390XState *env, uint64_t addr, + uint64_t ilen, uintptr_t ra, uint32_t c, + uint32_t *olen); + +static int decode_utf8(CPUS390XState *env, uint64_t addr, uint64_t ilen, + bool enh_check, uintptr_t ra, + uint32_t *ochar, uint32_t *olen) +{ + uint8_t s0, s1, s2, s3; + uint32_t c, l; + + if (ilen < 1) { + return 0; + } + s0 = cpu_ldub_data_ra(env, addr, ra); + if (s0 <= 0x7f) { + /* one byte character */ + l = 1; + c = s0; + } else if (s0 <= (enh_check ? 0xc1 : 0xbf)) { + /* invalid character */ + return 2; + } else if (s0 <= 0xdf) { + /* two byte character */ + l = 2; + if (ilen < 2) { + return 0; + } + s1 = cpu_ldub_data_ra(env, addr + 1, ra); + c = s0 & 0x1f; + c = (c << 6) | (s1 & 0x3f); + if (enh_check && (s1 & 0xc0) != 0x80) { + return 2; + } + } else if (s0 <= 0xef) { + /* three byte character */ + l = 3; + if (ilen < 3) { + return 0; + } + s1 = cpu_ldub_data_ra(env, addr + 1, ra); + s2 = cpu_ldub_data_ra(env, addr + 2, ra); + c = s0 & 0x0f; + c = (c << 6) | (s1 & 0x3f); + c = (c << 6) | (s2 & 0x3f); + /* Fold the byte-by-byte range descriptions in the PoO into + tests against the complete value. It disallows encodings + that could be smaller, and the UTF-16 surrogates. */ + if (enh_check + && ((s1 & 0xc0) != 0x80 + || (s2 & 0xc0) != 0x80 + || c < 0x1000 + || (c >= 0xd800 && c <= 0xdfff))) { + return 2; + } + } else if (s0 <= (enh_check ? 0xf4 : 0xf7)) { + /* four byte character */ + l = 4; + if (ilen < 4) { + return 0; + } + s1 = cpu_ldub_data_ra(env, addr + 1, ra); + s2 = cpu_ldub_data_ra(env, addr + 2, ra); + s3 = cpu_ldub_data_ra(env, addr + 3, ra); + c = s0 & 0x07; + c = (c << 6) | (s1 & 0x3f); + c = (c << 6) | (s2 & 0x3f); + c = (c << 6) | (s3 & 0x3f); + /* See above. */ + if (enh_check + && ((s1 & 0xc0) != 0x80 + || (s2 & 0xc0) != 0x80 + || (s3 & 0xc0) != 0x80 + || c < 0x010000 + || c > 0x10ffff)) { + return 2; + } + } else { + /* invalid character */ + return 2; + } + + *ochar = c; + *olen = l; + return -1; +} + +static int decode_utf16(CPUS390XState *env, uint64_t addr, uint64_t ilen, + bool enh_check, uintptr_t ra, + uint32_t *ochar, uint32_t *olen) +{ + uint16_t s0, s1; + uint32_t c, l; + + if (ilen < 2) { + return 0; + } + s0 = cpu_lduw_data_ra(env, addr, ra); + if ((s0 & 0xfc00) != 0xd800) { + /* one word character */ + l = 2; + c = s0; + } else { + /* two word character */ + l = 4; + if (ilen < 4) { + return 0; + } + s1 = cpu_lduw_data_ra(env, addr + 2, ra); + c = extract32(s0, 6, 4) + 1; + c = (c << 6) | (s0 & 0x3f); + c = (c << 10) | (s1 & 0x3ff); + if (enh_check && (s1 & 0xfc00) != 0xdc00) { + /* invalid surrogate character */ + return 2; + } + } + + *ochar = c; + *olen = l; + return -1; +} + +static int decode_utf32(CPUS390XState *env, uint64_t addr, uint64_t ilen, + bool enh_check, uintptr_t ra, + uint32_t *ochar, uint32_t *olen) +{ + uint32_t c; + + if (ilen < 4) { + return 0; + } + c = cpu_ldl_data_ra(env, addr, ra); + if ((c >= 0xd800 && c <= 0xdbff) || c > 0x10ffff) { + /* invalid unicode character */ + return 2; + } + + *ochar = c; + *olen = 4; + return -1; +} + +static int encode_utf8(CPUS390XState *env, uint64_t addr, uint64_t ilen, + uintptr_t ra, uint32_t c, uint32_t *olen) +{ + uint8_t d[4]; + uint32_t l, i; + + if (c <= 0x7f) { + /* one byte character */ + l = 1; + d[0] = c; + } else if (c <= 0x7ff) { + /* two byte character */ + l = 2; + d[1] = 0x80 | extract32(c, 0, 6); + d[0] = 0xc0 | extract32(c, 6, 5); + } else if (c <= 0xffff) { + /* three byte character */ + l = 3; + d[2] = 0x80 | extract32(c, 0, 6); + d[1] = 0x80 | extract32(c, 6, 6); + d[0] = 0xe0 | extract32(c, 12, 4); + } else { + /* four byte character */ + l = 4; + d[3] = 0x80 | extract32(c, 0, 6); + d[2] = 0x80 | extract32(c, 6, 6); + d[1] = 0x80 | extract32(c, 12, 6); + d[0] = 0xf0 | extract32(c, 18, 3); + } + + if (ilen < l) { + return 1; + } + for (i = 0; i < l; ++i) { + cpu_stb_data_ra(env, addr + i, d[i], ra); + } + + *olen = l; + return -1; +} + +static int encode_utf16(CPUS390XState *env, uint64_t addr, uint64_t ilen, + uintptr_t ra, uint32_t c, uint32_t *olen) +{ + uint16_t d0, d1; + + if (c <= 0xffff) { + /* one word character */ + if (ilen < 2) { + return 1; + } + cpu_stw_data_ra(env, addr, c, ra); + *olen = 2; + } else { + /* two word character */ + if (ilen < 4) { + return 1; + } + d1 = 0xdc00 | extract32(c, 0, 10); + d0 = 0xd800 | extract32(c, 10, 6); + d0 = deposit32(d0, 6, 4, extract32(c, 16, 5) - 1); + cpu_stw_data_ra(env, addr + 0, d0, ra); + cpu_stw_data_ra(env, addr + 2, d1, ra); + *olen = 4; + } + + return -1; +} + +static int encode_utf32(CPUS390XState *env, uint64_t addr, uint64_t ilen, + uintptr_t ra, uint32_t c, uint32_t *olen) +{ + if (ilen < 4) { + return 1; + } + cpu_stl_data_ra(env, addr, c, ra); + *olen = 4; + return -1; +} + +static inline uint32_t convert_unicode(CPUS390XState *env, uint32_t r1, + uint32_t r2, uint32_t m3, uintptr_t ra, + decode_unicode_fn decode, + encode_unicode_fn encode) +{ + uint64_t dst = get_address(env, r1); + uint64_t dlen = get_length(env, r1 + 1); + uint64_t src = get_address(env, r2); + uint64_t slen = get_length(env, r2 + 1); + bool enh_check = m3 & 1; + int cc, i; + + /* Lest we fail to service interrupts in a timely manner, limit the + amount of work we're willing to do. For now, let's cap at 256. */ + for (i = 0; i < 256; ++i) { + uint32_t c, ilen, olen; + + cc = decode(env, src, slen, enh_check, ra, &c, &ilen); + if (unlikely(cc >= 0)) { + break; + } + cc = encode(env, dst, dlen, ra, c, &olen); + if (unlikely(cc >= 0)) { + break; + } + + src += ilen; + slen -= ilen; + dst += olen; + dlen -= olen; + cc = 3; + } + + set_address(env, r1, dst); + set_length(env, r1 + 1, dlen); + set_address(env, r2, src); + set_length(env, r2 + 1, slen); + + return cc; +} + +uint32_t HELPER(cu12)(CPUS390XState *env, uint32_t r1, uint32_t r2, uint32_t m3) +{ + return convert_unicode(env, r1, r2, m3, GETPC(), + decode_utf8, encode_utf16); +} + +uint32_t HELPER(cu14)(CPUS390XState *env, uint32_t r1, uint32_t r2, uint32_t m3) +{ + return convert_unicode(env, r1, r2, m3, GETPC(), + decode_utf8, encode_utf32); +} + +uint32_t HELPER(cu21)(CPUS390XState *env, uint32_t r1, uint32_t r2, uint32_t m3) +{ + return convert_unicode(env, r1, r2, m3, GETPC(), + decode_utf16, encode_utf8); +} + +uint32_t HELPER(cu24)(CPUS390XState *env, uint32_t r1, uint32_t r2, uint32_t m3) +{ + return convert_unicode(env, r1, r2, m3, GETPC(), + decode_utf16, encode_utf32); +} + +uint32_t HELPER(cu41)(CPUS390XState *env, uint32_t r1, uint32_t r2, uint32_t m3) +{ + return convert_unicode(env, r1, r2, m3, GETPC(), + decode_utf32, encode_utf8); +} + +uint32_t HELPER(cu42)(CPUS390XState *env, uint32_t r1, uint32_t r2, uint32_t m3) +{ + return convert_unicode(env, r1, r2, m3, GETPC(), + decode_utf32, encode_utf16); +} diff --git a/target/s390x/translate.c b/target/s390x/translate.c index 592d6b0f38..1dffcee884 100644 --- a/target/s390x/translate.c +++ b/target/s390x/translate.c @@ -2033,6 +2033,18 @@ static ExitStatus op_cdsg(DisasContext *s, DisasOps *o) return NO_EXIT; } +static ExitStatus op_csst(DisasContext *s, DisasOps *o) +{ + int r3 = get_field(s->fields, r3); + TCGv_i32 t_r3 = tcg_const_i32(r3); + + gen_helper_csst(cc_op, cpu_env, t_r3, o->in1, o->in2); + tcg_temp_free_i32(t_r3); + + set_cc_static(s); + return NO_EXIT; +} + #ifndef CONFIG_USER_ONLY static ExitStatus op_csp(DisasContext *s, DisasOps *o) { @@ -2110,6 +2122,56 @@ static ExitStatus op_ct(DisasContext *s, DisasOps *o) return NO_EXIT; } +static ExitStatus op_cuXX(DisasContext *s, DisasOps *o) +{ + int m3 = get_field(s->fields, m3); + int r1 = get_field(s->fields, r1); + int r2 = get_field(s->fields, r2); + TCGv_i32 tr1, tr2, chk; + + /* R1 and R2 must both be even. */ + if ((r1 | r2) & 1) { + gen_program_exception(s, PGM_SPECIFICATION); + return EXIT_NORETURN; + } + if (!s390_has_feat(S390_FEAT_ETF3_ENH)) { + m3 = 0; + } + + tr1 = tcg_const_i32(r1); + tr2 = tcg_const_i32(r2); + chk = tcg_const_i32(m3); + + switch (s->insn->data) { + case 12: + gen_helper_cu12(cc_op, cpu_env, tr1, tr2, chk); + break; + case 14: + gen_helper_cu14(cc_op, cpu_env, tr1, tr2, chk); + break; + case 21: + gen_helper_cu21(cc_op, cpu_env, tr1, tr2, chk); + break; + case 24: + gen_helper_cu24(cc_op, cpu_env, tr1, tr2, chk); + break; + case 41: + gen_helper_cu41(cc_op, cpu_env, tr1, tr2, chk); + break; + case 42: + gen_helper_cu42(cc_op, cpu_env, tr1, tr2, chk); + break; + default: + g_assert_not_reached(); + } + + tcg_temp_free_i32(tr1); + tcg_temp_free_i32(tr2); + tcg_temp_free_i32(chk); + set_cc_static(s); + return NO_EXIT; +} + #ifndef CONFIG_USER_ONLY static ExitStatus op_diag(DisasContext *s, DisasOps *o) { @@ -3417,8 +3479,8 @@ static ExitStatus op_risbg(DisasContext *s, DisasOps *o) } /* In some cases we can implement this with extract. */ - if (imask == 0 && pos == 0 && len > 0 && rot + len <= 64) { - tcg_gen_extract_i64(o->out, o->in2, rot, len); + if (imask == 0 && pos == 0 && len > 0 && len <= rot) { + tcg_gen_extract_i64(o->out, o->in2, 64 - rot, len); return NO_EXIT; } @@ -4225,9 +4287,27 @@ static ExitStatus op_stpq(DisasContext *s, DisasOps *o) static ExitStatus op_srst(DisasContext *s, DisasOps *o) { - gen_helper_srst(o->in1, cpu_env, regs[0], o->in1, o->in2); + TCGv_i32 r1 = tcg_const_i32(get_field(s->fields, r1)); + TCGv_i32 r2 = tcg_const_i32(get_field(s->fields, r2)); + + gen_helper_srst(cpu_env, r1, r2); + + tcg_temp_free_i32(r1); + tcg_temp_free_i32(r2); + set_cc_static(s); + return NO_EXIT; +} + +static ExitStatus op_srstu(DisasContext *s, DisasOps *o) +{ + TCGv_i32 r1 = tcg_const_i32(get_field(s->fields, r1)); + TCGv_i32 r2 = tcg_const_i32(get_field(s->fields, r2)); + + gen_helper_srstu(cpu_env, r1, r2); + + tcg_temp_free_i32(r1); + tcg_temp_free_i32(r2); set_cc_static(s); - return_low128(o->in2); return NO_EXIT; } @@ -4367,6 +4447,15 @@ static ExitStatus op_trt(DisasContext *s, DisasOps *o) return NO_EXIT; } +static ExitStatus op_trtr(DisasContext *s, DisasOps *o) +{ + TCGv_i32 l = tcg_const_i32(get_field(s->fields, l1)); + gen_helper_trtr(cc_op, cpu_env, l, o->addr1, o->in2); + tcg_temp_free_i32(l); + set_cc_static(s); + return NO_EXIT; +} + static ExitStatus op_trXX(DisasContext *s, DisasOps *o) { TCGv_i32 r1 = tcg_const_i32(get_field(s->fields, r1)); @@ -5437,7 +5526,6 @@ enum DisasInsnEnum { /* Give smaller names to the various facilities. */ #define FAC_Z S390_FEAT_ZARCH #define FAC_CASS S390_FEAT_COMPARE_AND_SWAP_AND_STORE -#define FAC_CASS2 S390_FEAT_COMPARE_AND_SWAP_AND_STORE_2 #define FAC_DFP S390_FEAT_DFP #define FAC_DFPR S390_FEAT_FLOATING_POINT_SUPPPORT_ENH /* DFP-rounding */ #define FAC_DO S390_FEAT_STFLE_45 /* distinct-operands */ @@ -5466,6 +5554,7 @@ enum DisasInsnEnum { #define FAC_EH S390_FEAT_STFLE_49 /* execution-hint */ #define FAC_PPA S390_FEAT_STFLE_49 /* processor-assist */ #define FAC_LZRB S390_FEAT_STFLE_53 /* load-and-zero-rightmost-byte */ +#define FAC_ETF3 S390_FEAT_EXTENDED_TRANSLATION_3 static const DisasInsn insn_info[] = { #include "insn-data.def" diff --git a/tests/check-qom-proplist.c b/tests/check-qom-proplist.c index 8e432e9ab6..432b66585f 100644 --- a/tests/check-qom-proplist.c +++ b/tests/check-qom-proplist.c @@ -568,6 +568,47 @@ static void test_dummy_delchild(void) object_unparent(OBJECT(dev)); } +static void test_qom_partial_path(void) +{ + Object *root = object_get_objects_root(); + Object *cont1 = container_get(root, "/cont1"); + Object *obj1 = object_new(TYPE_DUMMY); + Object *obj2a = object_new(TYPE_DUMMY); + Object *obj2b = object_new(TYPE_DUMMY); + bool ambiguous; + + /* Objects created: + * /cont1 + * /cont1/obj1 + * /cont1/obj2 (obj2a) + * /obj2 (obj2b) + */ + object_property_add_child(cont1, "obj1", obj1, &error_abort); + object_unref(obj1); + object_property_add_child(cont1, "obj2", obj2a, &error_abort); + object_unref(obj2a); + object_property_add_child(root, "obj2", obj2b, &error_abort); + object_unref(obj2b); + + ambiguous = false; + g_assert(!object_resolve_path_type("", TYPE_DUMMY, &ambiguous)); + g_assert(ambiguous); + g_assert(!object_resolve_path_type("", TYPE_DUMMY, NULL)); + + ambiguous = false; + g_assert(!object_resolve_path("obj2", &ambiguous)); + g_assert(ambiguous); + g_assert(!object_resolve_path("obj2", NULL)); + + ambiguous = false; + g_assert(object_resolve_path("obj1", &ambiguous) == obj1); + g_assert(!ambiguous); + g_assert(object_resolve_path("obj1", NULL) == obj1); + + object_unparent(obj2b); + object_unparent(cont1); +} + int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); @@ -585,6 +626,7 @@ int main(int argc, char **argv) g_test_add_func("/qom/proplist/getenum", test_dummy_getenum); g_test_add_func("/qom/proplist/iterator", test_dummy_iterator); g_test_add_func("/qom/proplist/delchild", test_dummy_delchild); + g_test_add_func("/qom/resolve/partial", test_qom_partial_path); return g_test_run(); } diff --git a/tests/data/test-qga-os-release b/tests/data/test-qga-os-release new file mode 100644 index 0000000000..70664eb6ec --- /dev/null +++ b/tests/data/test-qga-os-release @@ -0,0 +1,7 @@ +ID=qemu-ga-test +NAME=QEMU-GA +PRETTY_NAME="QEMU Guest Agent test" +VERSION="Test 1" +VERSION_ID=1 +VARIANT="Unit test \"\'\$\`\\ and \\\\ etc." +VARIANT_ID=unit-test diff --git a/tests/device-introspect-test.c b/tests/device-introspect-test.c index b1abb2ad89..f7162c023f 100644 --- a/tests/device-introspect-test.c +++ b/tests/device-introspect-test.c @@ -45,6 +45,56 @@ static QList *qom_list_types(const char *implements, bool abstract) return ret; } +/* Build a name -> ObjectTypeInfo index from a ObjectTypeInfo list */ +static QDict *qom_type_index(QList *types) +{ + QDict *index = qdict_new(); + QListEntry *e; + + QLIST_FOREACH_ENTRY(types, e) { + QDict *d = qobject_to_qdict(qlist_entry_obj(e)); + const char *name = qdict_get_str(d, "name"); + QINCREF(d); + qdict_put(index, name, d); + } + return index; +} + +/* Check if @parent is present in the parent chain of @type */ +static bool qom_has_parent(QDict *index, const char *type, const char *parent) +{ + while (type) { + QDict *d = qdict_get_qdict(index, type); + const char *p = d && qdict_haskey(d, "parent") ? + qdict_get_str(d, "parent") : + NULL; + + if (!strcmp(type, parent)) { + return true; + } + + type = p; + } + + return false; +} + +/* Find an entry on a list returned by qom-list-types */ +static QDict *type_list_find(QList *types, const char *name) +{ + QListEntry *e; + + QLIST_FOREACH_ENTRY(types, e) { + QDict *d = qobject_to_qdict(qlist_entry_obj(e)); + const char *ename = qdict_get_str(d, "name"); + if (!strcmp(ename, name)) { + return d; + } + } + + return NULL; +} + static QList *device_type_list(bool abstract) { return qom_list_types("device", abstract); @@ -87,6 +137,61 @@ static void test_device_intro_list(void) qtest_end(); } +/* + * Ensure all entries returned by qom-list-types implements=<parent> + * have <parent> as a parent. + */ +static void test_qom_list_parents(const char *parent) +{ + QList *types; + QListEntry *e; + QDict *index; + + types = qom_list_types(parent, true); + index = qom_type_index(types); + + QLIST_FOREACH_ENTRY(types, e) { + QDict *d = qobject_to_qdict(qlist_entry_obj(e)); + const char *name = qdict_get_str(d, "name"); + + g_assert(qom_has_parent(index, name, parent)); + } + + QDECREF(types); + QDECREF(index); +} + +static void test_qom_list_fields(void) +{ + QList *all_types; + QList *non_abstract; + QListEntry *e; + + qtest_start(common_args); + + all_types = qom_list_types(NULL, true); + non_abstract = qom_list_types(NULL, false); + + QLIST_FOREACH_ENTRY(all_types, e) { + QDict *d = qobject_to_qdict(qlist_entry_obj(e)); + const char *name = qdict_get_str(d, "name"); + bool abstract = qdict_haskey(d, "abstract") ? + qdict_get_bool(d, "abstract") : + false; + bool expected_abstract = !type_list_find(non_abstract, name); + + g_assert(abstract == expected_abstract); + } + + test_qom_list_parents("object"); + test_qom_list_parents("device"); + test_qom_list_parents("sys-bus-device"); + + QDECREF(all_types); + QDECREF(non_abstract); + qtest_end(); +} + static void test_device_intro_none(void) { qtest_start(common_args); @@ -124,42 +229,34 @@ static void test_device_intro_concrete(void) static void test_abstract_interfaces(void) { QList *all_types; - QList *obj_types; - QListEntry *ae; + QListEntry *e; + QDict *index; qtest_start(common_args); - /* qom-list-types implements=interface would return any type - * that implements _any_ interface (not just interface types), - * so use a trick to find the interface type names: - * - list all object types - * - list all types, and look for items that are not - * on the first list - */ - all_types = qom_list_types(NULL, false); - obj_types = qom_list_types("object", false); - - QLIST_FOREACH_ENTRY(all_types, ae) { - QDict *at = qobject_to_qdict(qlist_entry_obj(ae)); - const char *aname = qdict_get_str(at, "name"); - QListEntry *oe; - const char *found = NULL; - - QLIST_FOREACH_ENTRY(obj_types, oe) { - QDict *ot = qobject_to_qdict(qlist_entry_obj(oe)); - const char *oname = qdict_get_str(ot, "name"); - if (!strcmp(aname, oname)) { - found = oname; - break; - } + + all_types = qom_list_types("interface", true); + index = qom_type_index(all_types); + + QLIST_FOREACH_ENTRY(all_types, e) { + QDict *d = qobject_to_qdict(qlist_entry_obj(e)); + const char *name = qdict_get_str(d, "name"); + + /* + * qom-list-types implements=interface returns all types + * that implement _any_ interface (not just interface + * types), so skip the ones that don't have "interface" + * on the parent type chain. + */ + if (!qom_has_parent(index, name, "interface")) { + /* Not an interface type */ + continue; } - /* Using g_assert_cmpstr() will give more useful failure - * messages than g_assert(found) */ - g_assert_cmpstr(aname, ==, found); + g_assert(qdict_haskey(d, "abstract") && qdict_get_bool(d, "abstract")); } QDECREF(all_types); - QDECREF(obj_types); + QDECREF(index); qtest_end(); } @@ -168,6 +265,7 @@ int main(int argc, char **argv) g_test_init(&argc, &argv, NULL); qtest_add_func("device/introspect/list", test_device_intro_list); + qtest_add_func("device/introspect/list-fields", test_qom_list_fields); qtest_add_func("device/introspect/none", test_device_intro_none); qtest_add_func("device/introspect/abstract", test_device_intro_abstract); qtest_add_func("device/introspect/concrete", test_device_intro_concrete); diff --git a/tests/qemu-iotests/140.out b/tests/qemu-iotests/140.out index 0689b2b41c..7295b3d975 100644 --- a/tests/qemu-iotests/140.out +++ b/tests/qemu-iotests/140.out @@ -8,7 +8,8 @@ wrote 65536/65536 bytes at offset 0 read 65536/65536 bytes at offset 0 64 KiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) {"return": {}} -can't open device nbd+unix:///drv?socket=TEST_DIR/nbd: No export with name 'drv' available +can't open device nbd+unix:///drv?socket=TEST_DIR/nbd: Requested export not available +server reported: export 'drv' not present {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} *** done diff --git a/tests/qemu-iotests/143.out b/tests/qemu-iotests/143.out index 0978b8985a..1c7fb45543 100644 --- a/tests/qemu-iotests/143.out +++ b/tests/qemu-iotests/143.out @@ -1,7 +1,8 @@ QA output created by 143 {"return": {}} {"return": {}} -can't open device nbd+unix:///no_such_export?socket=TEST_DIR/nbd: No export with name 'no_such_export' available +can't open device nbd+unix:///no_such_export?socket=TEST_DIR/nbd: Requested export not available +server reported: export 'no_such_export' not present {"return": {}} {"timestamp": {"seconds": TIMESTAMP, "microseconds": TIMESTAMP}, "event": "SHUTDOWN", "data": {"guest": false}} *** done diff --git a/tests/test-qdev-global-props.c b/tests/test-qdev-global-props.c index b25fe892ed..d81b0862d5 100644 --- a/tests/test-qdev-global-props.c +++ b/tests/test-qdev-global-props.c @@ -33,6 +33,8 @@ #define STATIC_TYPE(obj) \ OBJECT_CHECK(MyType, (obj), TYPE_STATIC_PROPS) +#define TYPE_SUBCLASS "static_prop_subtype" + #define PROP_DEFAULT 100 typedef struct MyType { @@ -63,6 +65,11 @@ static const TypeInfo static_prop_type = { .class_init = static_prop_class_init, }; +static const TypeInfo subclass_type = { + .name = TYPE_SUBCLASS, + .parent = TYPE_STATIC_PROPS, +}; + /* Test simple static property setting to default value */ static void test_static_prop_subprocess(void) { @@ -279,12 +286,35 @@ static void test_dynamic_globalprop_nouser(void) g_test_trap_assert_stdout(""); } +/* Test if global props affecting subclasses are applied in the right order */ +static void test_subclass_global_props(void) +{ + MyType *mt; + /* Global properties must be applied in the order they were registered */ + static GlobalProperty props[] = { + { TYPE_STATIC_PROPS, "prop1", "101" }, + { TYPE_SUBCLASS, "prop1", "102" }, + { TYPE_SUBCLASS, "prop2", "103" }, + { TYPE_STATIC_PROPS, "prop2", "104" }, + {} + }; + + qdev_prop_register_global_list(props); + + mt = STATIC_TYPE(object_new(TYPE_SUBCLASS)); + qdev_init_nofail(DEVICE(mt)); + + g_assert_cmpuint(mt->prop1, ==, 102); + g_assert_cmpuint(mt->prop2, ==, 104); +} + int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); module_call_init(MODULE_INIT_QOM); type_register_static(&static_prop_type); + type_register_static(&subclass_type); type_register_static(&dynamic_prop_type); type_register_static(&hotplug_type); type_register_static(&nohotplug_type); @@ -310,6 +340,9 @@ int main(int argc, char **argv) g_test_add_func("/qdev/properties/dynamic/global/nouser", test_dynamic_globalprop_nouser); + g_test_add_func("/qdev/properties/global/subclass", + test_subclass_global_props); + g_test_run(); return 0; diff --git a/tests/test-qga.c b/tests/test-qga.c index c77f241036..06783e7585 100644 --- a/tests/test-qga.c +++ b/tests/test-qga.c @@ -46,7 +46,7 @@ static void qga_watch(GPid pid, gint status, gpointer user_data) } static void -fixture_setup(TestFixture *fixture, gconstpointer data) +fixture_setup(TestFixture *fixture, gconstpointer data, gchar **envp) { const gchar *extra_arg = data; GError *error = NULL; @@ -67,7 +67,7 @@ fixture_setup(TestFixture *fixture, gconstpointer data) g_shell_parse_argv(cmd, NULL, &argv, &error); g_assert_no_error(error); - g_spawn_async(fixture->test_dir, argv, NULL, + g_spawn_async(fixture->test_dir, argv, envp, G_SPAWN_SEARCH_PATH|G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &fixture->pid, &error); g_assert_no_error(error); @@ -707,7 +707,7 @@ static void test_qga_blacklist(gconstpointer data) QDict *ret, *error; const gchar *class, *desc; - fixture_setup(&fix, "-b guest-ping,guest-get-time"); + fixture_setup(&fix, "-b guest-ping,guest-get-time", NULL); /* check blacklist */ ret = qmp_fd(fix.fd, "{'execute': 'guest-ping'}"); @@ -936,6 +936,60 @@ static void test_qga_guest_exec_invalid(gconstpointer fix) QDECREF(ret); } +static void test_qga_guest_get_osinfo(gconstpointer data) +{ + TestFixture fixture; + const gchar *str; + gchar *cwd, *env[2]; + QDict *ret, *val; + + cwd = g_get_current_dir(); + env[0] = g_strdup_printf( + "QGA_OS_RELEASE=%s%ctests%cdata%ctest-qga-os-release", + cwd, G_DIR_SEPARATOR, G_DIR_SEPARATOR, G_DIR_SEPARATOR); + env[1] = NULL; + g_free(cwd); + fixture_setup(&fixture, NULL, env); + + ret = qmp_fd(fixture.fd, "{'execute': 'guest-get-osinfo'}"); + g_assert_nonnull(ret); + qmp_assert_no_error(ret); + + val = qdict_get_qdict(ret, "return"); + + str = qdict_get_try_str(val, "id"); + g_assert_nonnull(str); + g_assert_cmpstr(str, ==, "qemu-ga-test"); + + str = qdict_get_try_str(val, "name"); + g_assert_nonnull(str); + g_assert_cmpstr(str, ==, "QEMU-GA"); + + str = qdict_get_try_str(val, "pretty-name"); + g_assert_nonnull(str); + g_assert_cmpstr(str, ==, "QEMU Guest Agent test"); + + str = qdict_get_try_str(val, "version"); + g_assert_nonnull(str); + g_assert_cmpstr(str, ==, "Test 1"); + + str = qdict_get_try_str(val, "version-id"); + g_assert_nonnull(str); + g_assert_cmpstr(str, ==, "1"); + + str = qdict_get_try_str(val, "variant"); + g_assert_nonnull(str); + g_assert_cmpstr(str, ==, "Unit test \"'$`\\ and \\\\ etc."); + + str = qdict_get_try_str(val, "variant-id"); + g_assert_nonnull(str); + g_assert_cmpstr(str, ==, "unit-test"); + + QDECREF(ret); + g_free(env[0]); + fixture_tear_down(&fixture, NULL); +} + int main(int argc, char **argv) { TestFixture fix; @@ -943,7 +997,7 @@ int main(int argc, char **argv) setlocale (LC_ALL, ""); g_test_init(&argc, &argv, NULL); - fixture_setup(&fix, NULL); + fixture_setup(&fix, NULL, NULL); g_test_add_data_func("/qga/sync-delimited", &fix, test_qga_sync_delimited); g_test_add_data_func("/qga/sync", &fix, test_qga_sync); @@ -972,6 +1026,8 @@ int main(int argc, char **argv) g_test_add_data_func("/qga/guest-exec", &fix, test_qga_guest_exec); g_test_add_data_func("/qga/guest-exec-invalid", &fix, test_qga_guest_exec_invalid); + g_test_add_data_func("/qga/guest-get-osinfo", &fix, + test_qga_guest_get_osinfo); if (g_getenv("QGA_TEST_SIDE_EFFECTING")) { g_test_add_data_func("/qga/fsfreeze-and-thaw", &fix, |