On 10/04/2013 07:05 AM, John Ferlan wrote:
On 09/25/2013 03:15 PM, Cole Robinson wrote:
> Again stolen from qemu_driver.c, but dropping all the unneeded bits.
> This aims to copy all the current qemu validation checks since that's
> the most commonly used real driver, but some of the checks are
> completely artificial in the test driver.
>
> This only supports creation of internal snapshots for initial
> simplicity.
> ---
>
> v3:
> Use STRNEQ_NULLABLE for domain_conf.c change
>
> src/conf/domain_conf.c | 2 +-
> src/test/test_driver.c | 504 ++++++++++++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 504 insertions(+), 2 deletions(-)
...
A RESOURCE_LEAK Coverity issue - it takes a bit to set up though...
> +static int
> +testDomainRevertToSnapshot(virDomainSnapshotPtr snapshot,
> + unsigned int flags)
> +{
> + testConnPtr privconn = snapshot->domain->conn->privateData;
> + virDomainObjPtr vm = NULL;
> + virDomainSnapshotObjPtr snap = NULL;
> + virDomainEventPtr event = NULL;
> + virDomainEventPtr event2 = NULL;
> + virDomainDefPtr config = NULL;
> + int ret = -1;
> +
> + virCheckFlags(VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING |
> + VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED |
> + VIR_DOMAIN_SNAPSHOT_REVERT_FORCE, -1);
> +
> + /* We have the following transitions, which create the following events:
> + * 1. inactive -> inactive: none
> + * 2. inactive -> running: EVENT_STARTED
> + * 3. inactive -> paused: EVENT_STARTED, EVENT_PAUSED
> + * 4. running -> inactive: EVENT_STOPPED
> + * 5. running -> running: none
> + * 6. running -> paused: EVENT_PAUSED
> + * 7. paused -> inactive: EVENT_STOPPED
> + * 8. paused -> running: EVENT_RESUMED
> + * 9. paused -> paused: none
> + * Also, several transitions occur even if we fail partway through,
> + * and use of FORCE can cause multiple transitions.
> + */
> +
> + if (!(vm = testDomObjFromSnapshot(snapshot)))
> + return -1;
> +
> + if (!(snap = testSnapObjFromSnapshot(vm, snapshot)))
> + goto cleanup;
> +
> + testDriverLock(privconn);
> +
> + if (!vm->persistent &&
> + snap->def->state != VIR_DOMAIN_RUNNING &&
> + snap->def->state != VIR_DOMAIN_PAUSED &&
> + (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING |
> + VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) == 0) {
> + virReportError(VIR_ERR_OPERATION_INVALID, "%s",
> + _("transient domain needs to request run or pause
"
> + "to revert to inactive snapshot"));
> + goto cleanup;
> + }
> +
> + if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)) {
> + if (!snap->def->dom) {
> + virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY,
> + _("snapshot '%s' lacks domain '%s'
rollback info"),
> + snap->def->name, vm->def->name);
> + goto cleanup;
> + }
> + if (virDomainObjIsActive(vm) &&
> + !(snap->def->state == VIR_DOMAIN_RUNNING
> + || snap->def->state == VIR_DOMAIN_PAUSED) &&
> + (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING |
> + VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED))) {
> + virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY, "%s",
> + _("must respawn guest to start inactive
snapshot"));
> + goto cleanup;
> + }
> + }
> +
> +
> + if (vm->current_snapshot) {
> + vm->current_snapshot->def->current = false;
> + vm->current_snapshot = NULL;
> + }
> +
> + snap->def->current = true;
> + config = virDomainDefCopy(snap->def->dom,
> + privconn->caps, privconn->xmlopt, true);
> + if (!config)
> + goto cleanup;
> +
6953 goto cleanup;
6954
(20) Event cond_false: Condition "snap->def->state ==
VIR_DOMAIN_RUNNING", taking false branch
(21) Event cond_false: Condition "snap->def->state ==
VIR_DOMAIN_PAUSED", taking false branch
6955 if (snap->def->state == VIR_DOMAIN_RUNNING ||
6956 snap->def->state == VIR_DOMAIN_PAUSED) {
> + if (snap->def->state == VIR_DOMAIN_RUNNING ||
> + snap->def->state == VIR_DOMAIN_PAUSED) {
> + /* Transitions 2, 3, 5, 6, 8, 9 */
> + bool was_running = false;
> + bool was_stopped = false;
> +
> + if (virDomainObjIsActive(vm)) {
> + /* Transitions 5, 6, 8, 9 */
> + /* Check for ABI compatibility. */
> + if (!virDomainDefCheckABIStability(vm->def, config)) {
> + virErrorPtr err = virGetLastError();
> +
> + if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_FORCE)) {
> + /* Re-spawn error using correct category. */
> + if (err->code == VIR_ERR_CONFIG_UNSUPPORTED)
> + virReportError(VIR_ERR_SNAPSHOT_REVERT_RISKY,
"%s",
> + err->str2);
> + goto cleanup;
> + }
> +
> + virResetError(err);
> + testDomainShutdownState(snapshot->domain, vm,
> + VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT);
> + event = virDomainEventNewFromObj(vm,
> + VIR_DOMAIN_EVENT_STOPPED,
> + VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT);
> + if (event)
> + testDomainEventQueue(privconn, event);
> + goto load;
> + }
> +
> + if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) {
> + /* Transitions 5, 6 */
> + was_running = true;
> + virDomainObjSetState(vm, VIR_DOMAIN_PAUSED,
> + VIR_DOMAIN_PAUSED_FROM_SNAPSHOT);
> + /* Create an event now in case the restore fails, so
> + * that user will be alerted that they are now paused.
> + * If restore later succeeds, we might replace this. */
> + event = virDomainEventNewFromObj(vm,
> + VIR_DOMAIN_EVENT_SUSPENDED,
> + VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT);
> + }
> + virDomainObjAssignDef(vm, config, false, NULL);
> +
> + } else {
> + /* Transitions 2, 3 */
> + load:
> + was_stopped = true;
> + virDomainObjAssignDef(vm, config, false, NULL);
> + if (testDomainStartState(privconn, vm,
> + VIR_DOMAIN_RUNNING_FROM_SNAPSHOT) < 0)
> + goto cleanup;
> + event = virDomainEventNewFromObj(vm,
> + VIR_DOMAIN_EVENT_STARTED,
> + VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT);
> + }
> +
> + /* Touch up domain state. */
> + if (!(flags & VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING) &&
> + (snap->def->state == VIR_DOMAIN_PAUSED ||
> + (flags & VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED))) {
> + /* Transitions 3, 6, 9 */
> + virDomainObjSetState(vm, VIR_DOMAIN_PAUSED,
> + VIR_DOMAIN_PAUSED_FROM_SNAPSHOT);
> + if (was_stopped) {
> + /* Transition 3, use event as-is and add event2 */
> + event2 = virDomainEventNewFromObj(vm,
> + VIR_DOMAIN_EVENT_SUSPENDED,
> + VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT);
> + } /* else transition 6 and 9 use event as-is */
> + } else {
> + /* Transitions 2, 5, 8 */
> + virDomainEventFree(event);
> + event = NULL;
> +
> + if (was_stopped) {
> + /* Transition 2 */
> + event = virDomainEventNewFromObj(vm,
> + VIR_DOMAIN_EVENT_STARTED,
> + VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT);
> + } else if (was_running) {
> + /* Transition 8 */
> + event = virDomainEventNewFromObj(vm,
> + VIR_DOMAIN_EVENT_RESUMED,
> + VIR_DOMAIN_EVENT_RESUMED);
> + }
> + }
7042 }
(22) Event else_branch: Reached else branch
7043 } else {
> + } else {
> + /* Transitions 1, 4, 7 */
> + virDomainObjAssignDef(vm, config, false, NULL);
> +
(23) Event cond_false:
> + if (virDomainObjIsActive(vm)) {
> + /* Transitions 4, 7 */
> + testDomainShutdownState(snapshot->domain, vm,
> + VIR_DOMAIN_SHUTOFF_FROM_SNAPSHOT);
> + event = virDomainEventNewFromObj(vm,
> + VIR_DOMAIN_EVENT_STOPPED,
> + VIR_DOMAIN_EVENT_STOPPED_FROM_SNAPSHOT);
(24) Event if_end: End of if statement
> + }
> +
(25) Event cond_true: Condition "flags & (3U /*
VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING | VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED */)", taking
true branch
> + if (flags & (VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING |
> + VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED)) {
> + /* Flush first event, now do transition 2 or 3 */
> + bool paused = (flags & VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED) != 0;
> +
(26) Event cond_false: Condition "event", taking false branch
> + if (event)
(27) Event if_end: End of if statement
> + testDomainEventQueue(privconn, event);
> + event = virDomainEventNewFromObj(vm,
> + VIR_DOMAIN_EVENT_STARTED,
> + VIR_DOMAIN_EVENT_STARTED_FROM_SNAPSHOT);
(28) Event cond_true: Condition "paused", taking true branch
> + if (paused) {
(29) Event alloc_fn: Storage is returned from allocation function
"virDomainEventNewFromObj(virDomainObjPtr, int, int)". [details]
(30) Event var_assign: Assigning: "event2" = storage returned from
"virDomainEventNewFromObj(vm, 3, 5)".
Also see events: [leaked_storage]
> + event2 = virDomainEventNewFromObj(vm,
> + VIR_DOMAIN_EVENT_SUSPENDED,
> + VIR_DOMAIN_EVENT_SUSPENDED_FROM_SNAPSHOT);
> + }
> + }
> + }
> +
> + vm->current_snapshot = snap;
> + ret = 0;
> +cleanup:
> + if (event) {
> + testDomainEventQueue(privconn, event);
> + if (event2)
> + testDomainEventQueue(privconn, event2);
> + }
Leads to the following Coverity issue -
7076 cleanup:
(31) Event cond_false: Condition "event", taking false branch
7077 if (event) {
7078 testDomainEventQueue(privconn, event);
7079 if (event2)
7080 testDomainEventQueue(privconn, event2);
(32) Event if_end: End of if statement
7081 }
7082 virObjectUnlock(vm);
7083 testDriverUnlock(privconn);
7084
(33) Event leaked_storage: Variable "event2" going out of scope leaks the
storage it points to.
Also see events: [alloc_fn][var_assign]
7085 return ret;
7086 }
7087
This one is a bit more "difficult" to see, but I think the complaint
is that it's possible that 'event' is NULL at line 7063, but it's never
checked and that 'event2' being allocated is based on 'paused' and not
'event' being non NULL, thus when we come to this point in the code the
event == NULL will cause event2 to be leaked.
Hmm, the logic in this code is pretty intense :) In the followup patch I've added:
@@ -7078,6 +7076,8 @@ cleanup:
testDomainEventQueue(privconn, event);
if (event2)
testDomainEventQueue(privconn, event2);
+ } else {
+ virDomainEventFree(event2);
}
virObjectUnlock(vm);
testDriverUnlock(privconn);
Hopefully that covers it.
Thanks,
Cole