/* * qemu_process.c: QEMU process management * * Copyright (C) 2006-2016 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . * */ #include #include #include #include #include #if defined(__linux__) # include #elif defined(__FreeBSD__) # include # include #endif #include "qemu_process.h" #include "qemu_processpriv.h" #include "qemu_alias.h" #include "qemu_domain.h" #include "qemu_domain_address.h" #include "qemu_cgroup.h" #include "qemu_capabilities.h" #include "qemu_monitor.h" #include "qemu_command.h" #include "qemu_hostdev.h" #include "qemu_hotplug.h" #include "qemu_migration.h" #include "qemu_interface.h" #include "qemu_security.h" #include "cpu/cpu.h" #include "datatypes.h" #include "virlog.h" #include "virerror.h" #include "viralloc.h" #include "virhook.h" #include "virfile.h" #include "virpidfile.h" #include "virhostcpu.h" #include "domain_audit.h" #include "domain_nwfilter.h" #include "locking/domain_lock.h" #include "network/bridge_driver.h" #include "viruuid.h" #include "virprocess.h" #include "virtime.h" #include "virnetdevtap.h" #include "virnetdevopenvswitch.h" #include "virnetdevmidonet.h" #include "virbitmap.h" #include "viratomic.h" #include "virnuma.h" #include "virstring.h" #include "virhostdev.h" #include "secret_util.h" #include "storage/storage_driver.h" #include "configmake.h" #include "nwfilter_conf.h" #include "netdev_bandwidth_conf.h" #define VIR_FROM_THIS VIR_FROM_QEMU VIR_LOG_INIT("qemu.qemu_process"); /** * qemuProcessRemoveDomainStatus * * remove all state files of a domain from statedir * * Returns 0 on success */ static int qemuProcessRemoveDomainStatus(virQEMUDriverPtr driver, virDomainObjPtr vm) { char ebuf[1024]; char *file = NULL; qemuDomainObjPrivatePtr priv = vm->privateData; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); int ret = -1; if (virAsprintf(&file, "%s/%s.xml", cfg->stateDir, vm->def->name) < 0) goto cleanup; if (unlink(file) < 0 && errno != ENOENT && errno != ENOTDIR) VIR_WARN("Failed to remove domain XML for %s: %s", vm->def->name, virStrerror(errno, ebuf, sizeof(ebuf))); VIR_FREE(file); if (priv->pidfile && unlink(priv->pidfile) < 0 && errno != ENOENT) VIR_WARN("Failed to remove PID file for %s: %s", vm->def->name, virStrerror(errno, ebuf, sizeof(ebuf))); ret = 0; cleanup: virObjectUnref(cfg); return ret; } /* XXX figure out how to remove this */ extern virQEMUDriverPtr qemu_driver; /* * This is a callback registered with a qemuAgentPtr instance, * and to be invoked when the agent console hits an end of file * condition, or error, thus indicating VM shutdown should be * performed */ static void qemuProcessHandleAgentEOF(qemuAgentPtr agent, virDomainObjPtr vm) { qemuDomainObjPrivatePtr priv; VIR_DEBUG("Received EOF from agent on %p '%s'", vm, vm->def->name); virObjectLock(vm); priv = vm->privateData; if (!priv->agent) { VIR_DEBUG("Agent freed already"); goto unlock; } if (priv->beingDestroyed) { VIR_DEBUG("Domain is being destroyed, agent EOF is expected"); goto unlock; } qemuAgentClose(agent); priv->agent = NULL; priv->agentError = false; virObjectUnlock(vm); return; unlock: virObjectUnlock(vm); return; } /* * This is invoked when there is some kind of error * parsing data to/from the agent. The VM can continue * to run, but no further agent commands will be * allowed */ static void qemuProcessHandleAgentError(qemuAgentPtr agent ATTRIBUTE_UNUSED, virDomainObjPtr vm) { qemuDomainObjPrivatePtr priv; VIR_DEBUG("Received error from agent on %p '%s'", vm, vm->def->name); virObjectLock(vm); priv = vm->privateData; priv->agentError = true; virObjectUnlock(vm); } static void qemuProcessHandleAgentDestroy(qemuAgentPtr agent, virDomainObjPtr vm) { VIR_DEBUG("Received destroy agent=%p vm=%p", agent, vm); virObjectUnref(vm); } static qemuAgentCallbacks agentCallbacks = { .destroy = qemuProcessHandleAgentDestroy, .eofNotify = qemuProcessHandleAgentEOF, .errorNotify = qemuProcessHandleAgentError, }; int qemuConnectAgent(virQEMUDriverPtr driver, virDomainObjPtr vm) { qemuDomainObjPrivatePtr priv = vm->privateData; qemuAgentPtr agent = NULL; virDomainChrDefPtr config = qemuFindAgentConfig(vm->def); if (!config) return 0; if (priv->agent) return 0; if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_VSERPORT_CHANGE) && config->state != VIR_DOMAIN_CHR_DEVICE_STATE_CONNECTED) { VIR_DEBUG("Deferring connecting to guest agent"); return 0; } if (virSecurityManagerSetDaemonSocketLabel(driver->securityManager, vm->def) < 0) { VIR_ERROR(_("Failed to set security context for agent for %s"), vm->def->name); goto cleanup; } /* Hold an extra reference because we can't allow 'vm' to be * deleted while the agent is active */ virObjectRef(vm); virObjectUnlock(vm); agent = qemuAgentOpen(vm, config->source, &agentCallbacks); virObjectLock(vm); if (agent == NULL) virObjectUnref(vm); if (!virDomainObjIsActive(vm)) { qemuAgentClose(agent); virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("guest crashed while connecting to the guest agent")); return -1; } if (virSecurityManagerClearSocketLabel(driver->securityManager, vm->def) < 0) { VIR_ERROR(_("Failed to clear security context for agent for %s"), vm->def->name); qemuAgentClose(agent); goto cleanup; } priv->agent = agent; if (!priv->agent) VIR_INFO("Failed to connect agent for %s", vm->def->name); cleanup: if (!priv->agent) { VIR_WARN("Cannot connect to QEMU guest agent for %s", vm->def->name); priv->agentError = true; virResetLastError(); } return 0; } /* * This is a callback registered with a qemuMonitorPtr instance, * and to be invoked when the monitor console hits an end of file * condition, or error, thus indicating VM shutdown should be * performed */ static void qemuProcessHandleMonitorEOF(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, void *opaque) { virQEMUDriverPtr driver = opaque; qemuDomainObjPrivatePtr priv; struct qemuProcessEvent *processEvent; virObjectLock(vm); VIR_DEBUG("Received EOF on %p '%s'", vm, vm->def->name); priv = vm->privateData; if (priv->beingDestroyed) { VIR_DEBUG("Domain is being destroyed, EOF is expected"); goto cleanup; } if (VIR_ALLOC(processEvent) < 0) goto cleanup; processEvent->eventType = QEMU_PROCESS_EVENT_MONITOR_EOF; processEvent->vm = vm; virObjectRef(vm); if (virThreadPoolSendJob(driver->workerPool, 0, processEvent) < 0) { ignore_value(virObjectUnref(vm)); VIR_FREE(processEvent); goto cleanup; } /* We don't want this EOF handler to be called over and over while the * thread is waiting for a job. */ qemuMonitorUnregister(mon); cleanup: virObjectUnlock(vm); } /* * This is invoked when there is some kind of error * parsing data to/from the monitor. The VM can continue * to run, but no further monitor commands will be * allowed */ static void qemuProcessHandleMonitorError(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, void *opaque) { virQEMUDriverPtr driver = opaque; virObjectEventPtr event = NULL; VIR_DEBUG("Received error on %p '%s'", vm, vm->def->name); virObjectLock(vm); ((qemuDomainObjPrivatePtr) vm->privateData)->monError = true; event = virDomainEventControlErrorNewFromObj(vm); qemuDomainEventQueue(driver, event); virObjectUnlock(vm); } virDomainDiskDefPtr qemuProcessFindDomainDiskByAlias(virDomainObjPtr vm, const char *alias) { size_t i; alias = qemuAliasDiskDriveSkipPrefix(alias); for (i = 0; i < vm->def->ndisks; i++) { virDomainDiskDefPtr disk; disk = vm->def->disks[i]; if (disk->info.alias != NULL && STREQ(disk->info.alias, alias)) return disk; } virReportError(VIR_ERR_INTERNAL_ERROR, _("no disk found with alias %s"), alias); return NULL; } static int qemuProcessGetVolumeQcowPassphrase(virConnectPtr conn, virDomainDiskDefPtr disk, char **secretRet, size_t *secretLen) { char *passphrase; unsigned char *data; size_t size; int ret = -1; virStorageEncryptionPtr enc; if (!disk->src->encryption) { virReportError(VIR_ERR_INTERNAL_ERROR, _("disk %s does not have any encryption information"), disk->src->path); return -1; } enc = disk->src->encryption; if (!conn) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("cannot find secrets without a connection")); goto cleanup; } if (conn->secretDriver == NULL || conn->secretDriver->secretLookupByUUID == NULL || conn->secretDriver->secretGetValue == NULL) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("secret storage not supported")); goto cleanup; } if (enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_QCOW || enc->nsecrets != 1 || enc->secrets[0]->type != VIR_STORAGE_ENCRYPTION_SECRET_TYPE_PASSPHRASE) { virReportError(VIR_ERR_XML_ERROR, _("invalid for volume %s"), virDomainDiskGetSource(disk)); goto cleanup; } if (virSecretGetSecretString(conn, &enc->secrets[0]->seclookupdef, VIR_SECRET_USAGE_TYPE_VOLUME, &data, &size) < 0) goto cleanup; if (memchr(data, '\0', size) != NULL) { memset(data, 0, size); VIR_FREE(data); virReportError(VIR_ERR_XML_ERROR, _("format='qcow' passphrase for %s must not contain a " "'\\0'"), virDomainDiskGetSource(disk)); goto cleanup; } if (VIR_ALLOC_N(passphrase, size + 1) < 0) { memset(data, 0, size); VIR_FREE(data); goto cleanup; } memcpy(passphrase, data, size); passphrase[size] = '\0'; memset(data, 0, size); VIR_FREE(data); *secretRet = passphrase; *secretLen = size; ret = 0; cleanup: return ret; } static int qemuProcessFindVolumeQcowPassphrase(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virConnectPtr conn, virDomainObjPtr vm, const char *path, char **secretRet, size_t *secretLen, void *opaque ATTRIBUTE_UNUSED) { virDomainDiskDefPtr disk; int ret = -1; virObjectLock(vm); if (!(disk = virDomainDiskByName(vm->def, path, true))) { virReportError(VIR_ERR_INTERNAL_ERROR, _("no disk found with path %s"), path); goto cleanup; } ret = qemuProcessGetVolumeQcowPassphrase(conn, disk, secretRet, secretLen); cleanup: virObjectUnlock(vm); return ret; } static int qemuProcessHandleReset(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, void *opaque) { virQEMUDriverPtr driver = opaque; virObjectEventPtr event; qemuDomainObjPrivatePtr priv; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); virObjectLock(vm); event = virDomainEventRebootNewFromObj(vm); priv = vm->privateData; if (priv->agent) qemuAgentNotifyEvent(priv->agent, QEMU_AGENT_EVENT_RESET); if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm, driver->caps) < 0) VIR_WARN("Failed to save status on vm %s", vm->def->name); virObjectUnlock(vm); qemuDomainEventQueue(driver, event); virObjectUnref(cfg); return 0; } /* * Since we have the '-no-shutdown' flag set, the * QEMU process will currently have guest OS shutdown * and the CPUS stopped. To fake the reboot, we thus * want todo a reset of the virtual hardware, followed * by restart of the CPUs. This should result in the * guest OS booting up again */ static void qemuProcessFakeReboot(void *opaque) { virQEMUDriverPtr driver = qemu_driver; virDomainObjPtr vm = opaque; qemuDomainObjPrivatePtr priv = vm->privateData; virObjectEventPtr event = NULL; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); virDomainRunningReason reason = VIR_DOMAIN_RUNNING_BOOTED; int ret = -1, rc; VIR_DEBUG("vm=%p", vm); virObjectLock(vm); if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0) goto cleanup; if (!virDomainObjIsActive(vm)) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("guest unexpectedly quit")); goto endjob; } qemuDomainObjEnterMonitor(driver, vm); rc = qemuMonitorSystemReset(priv->mon); if (qemuDomainObjExitMonitor(driver, vm) < 0) goto endjob; if (rc < 0) goto endjob; if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_CRASHED) reason = VIR_DOMAIN_RUNNING_CRASHED; if (qemuProcessStartCPUs(driver, vm, NULL, reason, QEMU_ASYNC_JOB_NONE) < 0) { if (virGetLastError() == NULL) virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("resume operation failed")); goto endjob; } priv->gotShutdown = false; event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_RESUMED, VIR_DOMAIN_EVENT_RESUMED_UNPAUSED); if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm, driver->caps) < 0) { VIR_WARN("Unable to save status on vm %s after state change", vm->def->name); } ret = 0; endjob: qemuDomainObjEndJob(driver, vm); cleanup: if (ret == -1) ignore_value(qemuProcessKill(vm, VIR_QEMU_PROCESS_KILL_FORCE)); virDomainObjEndAPI(&vm); qemuDomainEventQueue(driver, event); virObjectUnref(cfg); } void qemuProcessShutdownOrReboot(virQEMUDriverPtr driver, virDomainObjPtr vm) { qemuDomainObjPrivatePtr priv = vm->privateData; if (priv->fakeReboot) { qemuDomainSetFakeReboot(driver, vm, false); virObjectRef(vm); virThread th; if (virThreadCreate(&th, false, qemuProcessFakeReboot, vm) < 0) { VIR_ERROR(_("Failed to create reboot thread, killing domain")); ignore_value(qemuProcessKill(vm, VIR_QEMU_PROCESS_KILL_NOWAIT)); virObjectUnref(vm); } } else { ignore_value(qemuProcessKill(vm, VIR_QEMU_PROCESS_KILL_NOWAIT)); } } static int qemuProcessHandleEvent(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, const char *eventName, long long seconds, unsigned int micros, const char *details, void *opaque) { virQEMUDriverPtr driver = opaque; virObjectEventPtr event = NULL; VIR_DEBUG("vm=%p", vm); virObjectLock(vm); event = virDomainQemuMonitorEventNew(vm->def->id, vm->def->name, vm->def->uuid, eventName, seconds, micros, details); virObjectUnlock(vm); qemuDomainEventQueue(driver, event); return 0; } static int qemuProcessHandleShutdown(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, void *opaque) { virQEMUDriverPtr driver = opaque; qemuDomainObjPrivatePtr priv; virObjectEventPtr event = NULL; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); VIR_DEBUG("vm=%p", vm); virObjectLock(vm); priv = vm->privateData; if (priv->gotShutdown) { VIR_DEBUG("Ignoring repeated SHUTDOWN event from domain %s", vm->def->name); goto unlock; } else if (!virDomainObjIsActive(vm)) { VIR_DEBUG("Ignoring SHUTDOWN event from inactive domain %s", vm->def->name); goto unlock; } priv->gotShutdown = true; VIR_DEBUG("Transitioned guest %s to shutdown state", vm->def->name); virDomainObjSetState(vm, VIR_DOMAIN_SHUTDOWN, VIR_DOMAIN_SHUTDOWN_UNKNOWN); event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_SHUTDOWN, VIR_DOMAIN_EVENT_SHUTDOWN_FINISHED); if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm, driver->caps) < 0) { VIR_WARN("Unable to save status on vm %s after state change", vm->def->name); } if (priv->agent) qemuAgentNotifyEvent(priv->agent, QEMU_AGENT_EVENT_SHUTDOWN); qemuProcessShutdownOrReboot(driver, vm); unlock: virObjectUnlock(vm); qemuDomainEventQueue(driver, event); virObjectUnref(cfg); return 0; } static int qemuProcessHandleStop(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, void *opaque) { virQEMUDriverPtr driver = opaque; virObjectEventPtr event = NULL; virDomainPausedReason reason = VIR_DOMAIN_PAUSED_UNKNOWN; virDomainEventSuspendedDetailType detail = VIR_DOMAIN_EVENT_SUSPENDED_PAUSED; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); virObjectLock(vm); if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { qemuDomainObjPrivatePtr priv = vm->privateData; if (priv->gotShutdown) { VIR_DEBUG("Ignoring STOP event after SHUTDOWN"); goto unlock; } if (priv->job.asyncJob == QEMU_ASYNC_JOB_MIGRATION_OUT) { if (priv->job.current->stats.status == QEMU_MONITOR_MIGRATION_STATUS_POSTCOPY) { reason = VIR_DOMAIN_PAUSED_POSTCOPY; detail = VIR_DOMAIN_EVENT_SUSPENDED_POSTCOPY; } else { reason = VIR_DOMAIN_PAUSED_MIGRATION; detail = VIR_DOMAIN_EVENT_SUSPENDED_MIGRATED; } } VIR_DEBUG("Transitioned guest %s to paused state, reason %s", vm->def->name, virDomainPausedReasonTypeToString(reason)); if (priv->job.current) ignore_value(virTimeMillisNow(&priv->job.current->stopped)); if (priv->signalStop) virDomainObjBroadcast(vm); virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, reason); event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_SUSPENDED, detail); VIR_FREE(priv->lockState); if (virDomainLockProcessPause(driver->lockManager, vm, &priv->lockState) < 0) VIR_WARN("Unable to release lease on %s", vm->def->name); VIR_DEBUG("Preserving lock state '%s'", NULLSTR(priv->lockState)); if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm, driver->caps) < 0) { VIR_WARN("Unable to save status on vm %s after state change", vm->def->name); } } unlock: virObjectUnlock(vm); qemuDomainEventQueue(driver, event); virObjectUnref(cfg); return 0; } static int qemuProcessHandleResume(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, void *opaque) { virQEMUDriverPtr driver = opaque; virObjectEventPtr event = NULL; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); virObjectLock(vm); if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_PAUSED) { qemuDomainObjPrivatePtr priv = vm->privateData; if (priv->gotShutdown) { VIR_DEBUG("Ignoring RESUME event after SHUTDOWN"); goto unlock; } VIR_DEBUG("Transitioned guest %s out of paused into resumed state", vm->def->name); virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, VIR_DOMAIN_RUNNING_UNPAUSED); event = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_RESUMED, VIR_DOMAIN_EVENT_RESUMED_UNPAUSED); if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm, driver->caps) < 0) { VIR_WARN("Unable to save status on vm %s after state change", vm->def->name); } } unlock: virObjectUnlock(vm); qemuDomainEventQueue(driver, event); virObjectUnref(cfg); return 0; } static int qemuProcessHandleRTCChange(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, long long offset, void *opaque) { virQEMUDriverPtr driver = opaque; virObjectEventPtr event = NULL; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); virObjectLock(vm); if (vm->def->clock.offset == VIR_DOMAIN_CLOCK_OFFSET_VARIABLE) { /* when a basedate is manually given on the qemu commandline * rather than simply "-rtc base=utc", the offset sent by qemu * in this event is *not* the new offset from UTC, but is * instead the new offset from the *original basedate* + * uptime. For example, if the original offset was 3600 and * the guest clock has been advanced by 10 seconds, qemu will * send "10" in the event - this means that the new offset * from UTC is 3610, *not* 10. If the guest clock is advanced * by another 10 seconds, qemu will now send "20" - i.e. each * event is the sum of the most recent change and all previous * changes since the domain was started. Fortunately, we have * saved the initial offset in "adjustment0", so to arrive at * the proper new "adjustment", we just add the most recent * offset to adjustment0. */ offset += vm->def->clock.data.variable.adjustment0; vm->def->clock.data.variable.adjustment = offset; if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm, driver->caps) < 0) VIR_WARN("unable to save domain status with RTC change"); } event = virDomainEventRTCChangeNewFromObj(vm, offset); virObjectUnlock(vm); qemuDomainEventQueue(driver, event); virObjectUnref(cfg); return 0; } static int qemuProcessHandleWatchdog(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, int action, void *opaque) { virQEMUDriverPtr driver = opaque; virObjectEventPtr watchdogEvent = NULL; virObjectEventPtr lifecycleEvent = NULL; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); virObjectLock(vm); watchdogEvent = virDomainEventWatchdogNewFromObj(vm, action); if (action == VIR_DOMAIN_EVENT_WATCHDOG_PAUSE && virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { qemuDomainObjPrivatePtr priv = vm->privateData; VIR_DEBUG("Transitioned guest %s to paused state due to watchdog", vm->def->name); virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_WATCHDOG); lifecycleEvent = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_SUSPENDED, VIR_DOMAIN_EVENT_SUSPENDED_WATCHDOG); VIR_FREE(priv->lockState); if (virDomainLockProcessPause(driver->lockManager, vm, &priv->lockState) < 0) VIR_WARN("Unable to release lease on %s", vm->def->name); VIR_DEBUG("Preserving lock state '%s'", NULLSTR(priv->lockState)); if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm, driver->caps) < 0) { VIR_WARN("Unable to save status on vm %s after watchdog event", vm->def->name); } } if (vm->def->watchdog->action == VIR_DOMAIN_WATCHDOG_ACTION_DUMP) { struct qemuProcessEvent *processEvent; if (VIR_ALLOC(processEvent) == 0) { processEvent->eventType = QEMU_PROCESS_EVENT_WATCHDOG; processEvent->action = VIR_DOMAIN_WATCHDOG_ACTION_DUMP; processEvent->vm = vm; /* Hold an extra reference because we can't allow 'vm' to be * deleted before handling watchdog event is finished. */ virObjectRef(vm); if (virThreadPoolSendJob(driver->workerPool, 0, processEvent) < 0) { if (!virObjectUnref(vm)) vm = NULL; VIR_FREE(processEvent); } } } if (vm) virObjectUnlock(vm); qemuDomainEventQueue(driver, watchdogEvent); qemuDomainEventQueue(driver, lifecycleEvent); virObjectUnref(cfg); return 0; } static int qemuProcessHandleIOError(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, const char *diskAlias, int action, const char *reason, void *opaque) { virQEMUDriverPtr driver = opaque; virObjectEventPtr ioErrorEvent = NULL; virObjectEventPtr ioErrorEvent2 = NULL; virObjectEventPtr lifecycleEvent = NULL; const char *srcPath; const char *devAlias; virDomainDiskDefPtr disk; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); virObjectLock(vm); disk = qemuProcessFindDomainDiskByAlias(vm, diskAlias); if (disk) { srcPath = virDomainDiskGetSource(disk); devAlias = disk->info.alias; } else { srcPath = ""; devAlias = ""; } ioErrorEvent = virDomainEventIOErrorNewFromObj(vm, srcPath, devAlias, action); ioErrorEvent2 = virDomainEventIOErrorReasonNewFromObj(vm, srcPath, devAlias, action, reason); if (action == VIR_DOMAIN_EVENT_IO_ERROR_PAUSE && virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { qemuDomainObjPrivatePtr priv = vm->privateData; VIR_DEBUG("Transitioned guest %s to paused state due to IO error", vm->def->name); if (priv->signalIOError) virDomainObjBroadcast(vm); virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_IOERROR); lifecycleEvent = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_SUSPENDED, VIR_DOMAIN_EVENT_SUSPENDED_IOERROR); VIR_FREE(priv->lockState); if (virDomainLockProcessPause(driver->lockManager, vm, &priv->lockState) < 0) VIR_WARN("Unable to release lease on %s", vm->def->name); VIR_DEBUG("Preserving lock state '%s'", NULLSTR(priv->lockState)); if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm, driver->caps) < 0) VIR_WARN("Unable to save status on vm %s after IO error", vm->def->name); } virObjectUnlock(vm); qemuDomainEventQueue(driver, ioErrorEvent); qemuDomainEventQueue(driver, ioErrorEvent2); qemuDomainEventQueue(driver, lifecycleEvent); virObjectUnref(cfg); return 0; } static int qemuProcessHandleBlockJob(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, const char *diskAlias, int type, int status, void *opaque) { virQEMUDriverPtr driver = opaque; struct qemuProcessEvent *processEvent = NULL; virDomainDiskDefPtr disk; qemuDomainDiskPrivatePtr diskPriv; char *data = NULL; virObjectLock(vm); VIR_DEBUG("Block job for device %s (domain: %p,%s) type %d status %d", diskAlias, vm, vm->def->name, type, status); if (!(disk = qemuProcessFindDomainDiskByAlias(vm, diskAlias))) goto error; diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); if (diskPriv->blockJobSync) { /* We have a SYNC API waiting for this event, dispatch it back */ diskPriv->blockJobType = type; diskPriv->blockJobStatus = status; virDomainObjBroadcast(vm); } else { /* there is no waiting SYNC API, dispatch the update to a thread */ if (VIR_ALLOC(processEvent) < 0) goto error; processEvent->eventType = QEMU_PROCESS_EVENT_BLOCK_JOB; if (VIR_STRDUP(data, diskAlias) < 0) goto error; processEvent->data = data; processEvent->vm = vm; processEvent->action = type; processEvent->status = status; virObjectRef(vm); if (virThreadPoolSendJob(driver->workerPool, 0, processEvent) < 0) { ignore_value(virObjectUnref(vm)); goto error; } } cleanup: virObjectUnlock(vm); return 0; error: if (processEvent) VIR_FREE(processEvent->data); VIR_FREE(processEvent); goto cleanup; } static int qemuProcessHandleGraphics(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, int phase, int localFamily, const char *localNode, const char *localService, int remoteFamily, const char *remoteNode, const char *remoteService, const char *authScheme, const char *x509dname, const char *saslUsername, void *opaque) { virQEMUDriverPtr driver = opaque; virObjectEventPtr event; virDomainEventGraphicsAddressPtr localAddr = NULL; virDomainEventGraphicsAddressPtr remoteAddr = NULL; virDomainEventGraphicsSubjectPtr subject = NULL; size_t i; if (VIR_ALLOC(localAddr) < 0) goto error; localAddr->family = localFamily; if (VIR_STRDUP(localAddr->service, localService) < 0 || VIR_STRDUP(localAddr->node, localNode) < 0) goto error; if (VIR_ALLOC(remoteAddr) < 0) goto error; remoteAddr->family = remoteFamily; if (VIR_STRDUP(remoteAddr->service, remoteService) < 0 || VIR_STRDUP(remoteAddr->node, remoteNode) < 0) goto error; if (VIR_ALLOC(subject) < 0) goto error; if (x509dname) { if (VIR_REALLOC_N(subject->identities, subject->nidentity+1) < 0) goto error; subject->nidentity++; if (VIR_STRDUP(subject->identities[subject->nidentity-1].type, "x509dname") < 0 || VIR_STRDUP(subject->identities[subject->nidentity-1].name, x509dname) < 0) goto error; } if (saslUsername) { if (VIR_REALLOC_N(subject->identities, subject->nidentity+1) < 0) goto error; subject->nidentity++; if (VIR_STRDUP(subject->identities[subject->nidentity-1].type, "saslUsername") < 0 || VIR_STRDUP(subject->identities[subject->nidentity-1].name, saslUsername) < 0) goto error; } virObjectLock(vm); event = virDomainEventGraphicsNewFromObj(vm, phase, localAddr, remoteAddr, authScheme, subject); virObjectUnlock(vm); qemuDomainEventQueue(driver, event); return 0; error: if (localAddr) { VIR_FREE(localAddr->service); VIR_FREE(localAddr->node); VIR_FREE(localAddr); } if (remoteAddr) { VIR_FREE(remoteAddr->service); VIR_FREE(remoteAddr->node); VIR_FREE(remoteAddr); } if (subject) { for (i = 0; i < subject->nidentity; i++) { VIR_FREE(subject->identities[i].type); VIR_FREE(subject->identities[i].name); } VIR_FREE(subject->identities); VIR_FREE(subject); } return -1; } static int qemuProcessHandleTrayChange(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, const char *devAlias, int reason, void *opaque) { virQEMUDriverPtr driver = opaque; virObjectEventPtr event = NULL; virDomainDiskDefPtr disk; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); virObjectLock(vm); disk = qemuProcessFindDomainDiskByAlias(vm, devAlias); if (disk) { event = virDomainEventTrayChangeNewFromObj(vm, devAlias, reason); /* Update disk tray status */ if (reason == VIR_DOMAIN_EVENT_TRAY_CHANGE_OPEN) disk->tray_status = VIR_DOMAIN_DISK_TRAY_OPEN; else if (reason == VIR_DOMAIN_EVENT_TRAY_CHANGE_CLOSE) disk->tray_status = VIR_DOMAIN_DISK_TRAY_CLOSED; if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm, driver->caps) < 0) { VIR_WARN("Unable to save status on vm %s after tray moved event", vm->def->name); } virDomainObjBroadcast(vm); } virObjectUnlock(vm); qemuDomainEventQueue(driver, event); virObjectUnref(cfg); return 0; } static int qemuProcessHandlePMWakeup(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, void *opaque) { virQEMUDriverPtr driver = opaque; virObjectEventPtr event = NULL; virObjectEventPtr lifecycleEvent = NULL; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); virObjectLock(vm); event = virDomainEventPMWakeupNewFromObj(vm); /* Don't set domain status back to running if it wasn't paused * from guest side, otherwise it can just cause confusion. */ if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_PMSUSPENDED) { VIR_DEBUG("Transitioned guest %s from pmsuspended to running " "state due to QMP wakeup event", vm->def->name); virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, VIR_DOMAIN_RUNNING_WAKEUP); lifecycleEvent = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_STARTED, VIR_DOMAIN_EVENT_STARTED_WAKEUP); if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm, driver->caps) < 0) { VIR_WARN("Unable to save status on vm %s after wakeup event", vm->def->name); } } virObjectUnlock(vm); qemuDomainEventQueue(driver, event); qemuDomainEventQueue(driver, lifecycleEvent); virObjectUnref(cfg); return 0; } static int qemuProcessHandlePMSuspend(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, void *opaque) { virQEMUDriverPtr driver = opaque; virObjectEventPtr event = NULL; virObjectEventPtr lifecycleEvent = NULL; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); virObjectLock(vm); event = virDomainEventPMSuspendNewFromObj(vm); if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { qemuDomainObjPrivatePtr priv = vm->privateData; VIR_DEBUG("Transitioned guest %s to pmsuspended state due to " "QMP suspend event", vm->def->name); virDomainObjSetState(vm, VIR_DOMAIN_PMSUSPENDED, VIR_DOMAIN_PMSUSPENDED_UNKNOWN); lifecycleEvent = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_PMSUSPENDED, VIR_DOMAIN_EVENT_PMSUSPENDED_MEMORY); if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm, driver->caps) < 0) { VIR_WARN("Unable to save status on vm %s after suspend event", vm->def->name); } if (priv->agent) qemuAgentNotifyEvent(priv->agent, QEMU_AGENT_EVENT_SUSPEND); } virObjectUnlock(vm); qemuDomainEventQueue(driver, event); qemuDomainEventQueue(driver, lifecycleEvent); virObjectUnref(cfg); return 0; } static int qemuProcessHandleBalloonChange(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, unsigned long long actual, void *opaque) { virQEMUDriverPtr driver = opaque; virObjectEventPtr event = NULL; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); virObjectLock(vm); event = virDomainEventBalloonChangeNewFromObj(vm, actual); VIR_DEBUG("Updating balloon from %lld to %lld kb", vm->def->mem.cur_balloon, actual); vm->def->mem.cur_balloon = actual; if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm, driver->caps) < 0) VIR_WARN("unable to save domain status with balloon change"); virObjectUnlock(vm); qemuDomainEventQueue(driver, event); virObjectUnref(cfg); return 0; } static int qemuProcessHandlePMSuspendDisk(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, void *opaque) { virQEMUDriverPtr driver = opaque; virObjectEventPtr event = NULL; virObjectEventPtr lifecycleEvent = NULL; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); virObjectLock(vm); event = virDomainEventPMSuspendDiskNewFromObj(vm); if (virDomainObjGetState(vm, NULL) == VIR_DOMAIN_RUNNING) { qemuDomainObjPrivatePtr priv = vm->privateData; VIR_DEBUG("Transitioned guest %s to pmsuspended state due to " "QMP suspend_disk event", vm->def->name); virDomainObjSetState(vm, VIR_DOMAIN_PMSUSPENDED, VIR_DOMAIN_PMSUSPENDED_UNKNOWN); lifecycleEvent = virDomainEventLifecycleNewFromObj(vm, VIR_DOMAIN_EVENT_PMSUSPENDED, VIR_DOMAIN_EVENT_PMSUSPENDED_DISK); if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm, driver->caps) < 0) { VIR_WARN("Unable to save status on vm %s after suspend event", vm->def->name); } if (priv->agent) qemuAgentNotifyEvent(priv->agent, QEMU_AGENT_EVENT_SUSPEND); } virObjectUnlock(vm); qemuDomainEventQueue(driver, event); qemuDomainEventQueue(driver, lifecycleEvent); virObjectUnref(cfg); return 0; } static int qemuProcessHandleGuestPanic(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, void *opaque) { virQEMUDriverPtr driver = opaque; struct qemuProcessEvent *processEvent; virObjectLock(vm); if (VIR_ALLOC(processEvent) < 0) goto cleanup; processEvent->eventType = QEMU_PROCESS_EVENT_GUESTPANIC; processEvent->action = vm->def->onCrash; processEvent->vm = vm; /* Hold an extra reference because we can't allow 'vm' to be * deleted before handling guest panic event is finished. */ virObjectRef(vm); if (virThreadPoolSendJob(driver->workerPool, 0, processEvent) < 0) { if (!virObjectUnref(vm)) vm = NULL; VIR_FREE(processEvent); } cleanup: if (vm) virObjectUnlock(vm); return 0; } int qemuProcessHandleDeviceDeleted(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, const char *devAlias, void *opaque) { virQEMUDriverPtr driver = opaque; struct qemuProcessEvent *processEvent = NULL; char *data; virObjectLock(vm); VIR_DEBUG("Device %s removed from domain %p %s", devAlias, vm, vm->def->name); if (qemuDomainSignalDeviceRemoval(vm, devAlias, QEMU_DOMAIN_UNPLUGGING_DEVICE_STATUS_OK)) goto cleanup; if (VIR_ALLOC(processEvent) < 0) goto error; processEvent->eventType = QEMU_PROCESS_EVENT_DEVICE_DELETED; if (VIR_STRDUP(data, devAlias) < 0) goto error; processEvent->data = data; processEvent->vm = vm; virObjectRef(vm); if (virThreadPoolSendJob(driver->workerPool, 0, processEvent) < 0) { ignore_value(virObjectUnref(vm)); goto error; } cleanup: virObjectUnlock(vm); return 0; error: if (processEvent) VIR_FREE(processEvent->data); VIR_FREE(processEvent); goto cleanup; } /** * * Meaning of fields reported by the event according to the ACPI standard: * @source: * 0x00 - 0xff: Notification values, as passed at the request time * 0x100: Operating System Shutdown Processing * 0x103: Ejection processing * 0x200: Insertion processing * other values are reserved * * @status: * general values * 0x00: success * 0x01: non-specific failure * 0x02: unrecognized notify code * 0x03 - 0x7f: reserved * other values are specific to the notification type * * for the 0x100 source the following additional codes are standardized * 0x80: OS Shutdown request denied * 0x81: OS Shutdown in progress * 0x82: OS Shutdown completed * 0x83: OS Graceful shutdown not supported * other values are reserved * * Other fields and semantics are specific to the qemu handling of the event. * - @alias may be NULL for successful unplug operations * - @slotType describes the device type a bit more closely, currently the * only known value is 'DIMM' * - @slot describes the specific device * * Note that qemu does not emit the event for all the documented sources or * devices. */ static int qemuProcessHandleAcpiOstInfo(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, const char *alias, const char *slotType, const char *slot, unsigned int source, unsigned int status, void *opaque) { virQEMUDriverPtr driver = opaque; virObjectEventPtr event = NULL; virObjectLock(vm); VIR_DEBUG("ACPI OST info for device %s domain %p %s. " "slotType='%s' slot='%s' source=%u status=%u", NULLSTR(alias), vm, vm->def->name, slotType, slot, source, status); /* handle memory unplug failure */ if (STREQ(slotType, "DIMM") && alias && status == 1) { qemuDomainSignalDeviceRemoval(vm, alias, QEMU_DOMAIN_UNPLUGGING_DEVICE_STATUS_GUEST_REJECTED); event = virDomainEventDeviceRemovalFailedNewFromObj(vm, alias); } virObjectUnlock(vm); qemuDomainEventQueue(driver, event); return 0; } static int qemuProcessHandleNicRxFilterChanged(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, const char *devAlias, void *opaque) { virQEMUDriverPtr driver = opaque; struct qemuProcessEvent *processEvent = NULL; char *data; virObjectLock(vm); VIR_DEBUG("Device %s RX Filter changed in domain %p %s", devAlias, vm, vm->def->name); if (VIR_ALLOC(processEvent) < 0) goto error; processEvent->eventType = QEMU_PROCESS_EVENT_NIC_RX_FILTER_CHANGED; if (VIR_STRDUP(data, devAlias) < 0) goto error; processEvent->data = data; processEvent->vm = vm; virObjectRef(vm); if (virThreadPoolSendJob(driver->workerPool, 0, processEvent) < 0) { ignore_value(virObjectUnref(vm)); goto error; } cleanup: virObjectUnlock(vm); return 0; error: if (processEvent) VIR_FREE(processEvent->data); VIR_FREE(processEvent); goto cleanup; } static int qemuProcessHandleSerialChanged(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, const char *devAlias, bool connected, void *opaque) { virQEMUDriverPtr driver = opaque; struct qemuProcessEvent *processEvent = NULL; char *data; virObjectLock(vm); VIR_DEBUG("Serial port %s state changed to '%d' in domain %p %s", devAlias, connected, vm, vm->def->name); if (VIR_ALLOC(processEvent) < 0) goto error; processEvent->eventType = QEMU_PROCESS_EVENT_SERIAL_CHANGED; if (VIR_STRDUP(data, devAlias) < 0) goto error; processEvent->data = data; processEvent->action = connected; processEvent->vm = vm; virObjectRef(vm); if (virThreadPoolSendJob(driver->workerPool, 0, processEvent) < 0) { ignore_value(virObjectUnref(vm)); goto error; } cleanup: virObjectUnlock(vm); return 0; error: if (processEvent) VIR_FREE(processEvent->data); VIR_FREE(processEvent); goto cleanup; } static int qemuProcessHandleSpiceMigrated(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, void *opaque ATTRIBUTE_UNUSED) { qemuDomainObjPrivatePtr priv; virObjectLock(vm); VIR_DEBUG("Spice migration completed for domain %p %s", vm, vm->def->name); priv = vm->privateData; if (priv->job.asyncJob != QEMU_ASYNC_JOB_MIGRATION_OUT) { VIR_DEBUG("got SPICE_MIGRATE_COMPLETED event without a migration job"); goto cleanup; } priv->job.spiceMigrated = true; virDomainObjBroadcast(vm); cleanup: virObjectUnlock(vm); return 0; } static int qemuProcessHandleMigrationStatus(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, int status, void *opaque ATTRIBUTE_UNUSED) { qemuDomainObjPrivatePtr priv; virObjectLock(vm); VIR_DEBUG("Migration of domain %p %s changed state to %s", vm, vm->def->name, qemuMonitorMigrationStatusTypeToString(status)); priv = vm->privateData; if (priv->job.asyncJob == QEMU_ASYNC_JOB_NONE) { VIR_DEBUG("got MIGRATION event without a migration job"); goto cleanup; } priv->job.current->stats.status = status; virDomainObjBroadcast(vm); cleanup: virObjectUnlock(vm); return 0; } static int qemuProcessHandleMigrationPass(qemuMonitorPtr mon ATTRIBUTE_UNUSED, virDomainObjPtr vm, int pass, void *opaque) { virQEMUDriverPtr driver = opaque; qemuDomainObjPrivatePtr priv; virObjectLock(vm); VIR_DEBUG("Migrating domain %p %s, iteration %d", vm, vm->def->name, pass); priv = vm->privateData; if (priv->job.asyncJob == QEMU_ASYNC_JOB_NONE) { VIR_DEBUG("got MIGRATION_PASS event without a migration job"); goto cleanup; } qemuDomainEventQueue(driver, virDomainEventMigrationIterationNewFromObj(vm, pass)); cleanup: virObjectUnlock(vm); return 0; } static qemuMonitorCallbacks monitorCallbacks = { .eofNotify = qemuProcessHandleMonitorEOF, .errorNotify = qemuProcessHandleMonitorError, .diskSecretLookup = qemuProcessFindVolumeQcowPassphrase, .domainEvent = qemuProcessHandleEvent, .domainShutdown = qemuProcessHandleShutdown, .domainStop = qemuProcessHandleStop, .domainResume = qemuProcessHandleResume, .domainReset = qemuProcessHandleReset, .domainRTCChange = qemuProcessHandleRTCChange, .domainWatchdog = qemuProcessHandleWatchdog, .domainIOError = qemuProcessHandleIOError, .domainGraphics = qemuProcessHandleGraphics, .domainBlockJob = qemuProcessHandleBlockJob, .domainTrayChange = qemuProcessHandleTrayChange, .domainPMWakeup = qemuProcessHandlePMWakeup, .domainPMSuspend = qemuProcessHandlePMSuspend, .domainBalloonChange = qemuProcessHandleBalloonChange, .domainPMSuspendDisk = qemuProcessHandlePMSuspendDisk, .domainGuestPanic = qemuProcessHandleGuestPanic, .domainDeviceDeleted = qemuProcessHandleDeviceDeleted, .domainNicRxFilterChanged = qemuProcessHandleNicRxFilterChanged, .domainSerialChange = qemuProcessHandleSerialChanged, .domainSpiceMigrated = qemuProcessHandleSpiceMigrated, .domainMigrationStatus = qemuProcessHandleMigrationStatus, .domainMigrationPass = qemuProcessHandleMigrationPass, .domainAcpiOstInfo = qemuProcessHandleAcpiOstInfo, }; static void qemuProcessMonitorReportLogError(qemuMonitorPtr mon, const char *msg, void *opaque); static void qemuProcessMonitorLogFree(void *opaque) { qemuDomainLogContextPtr logCtxt = opaque; qemuDomainLogContextFree(logCtxt); } static int qemuConnectMonitor(virQEMUDriverPtr driver, virDomainObjPtr vm, int asyncJob, qemuDomainLogContextPtr logCtxt) { qemuDomainObjPrivatePtr priv = vm->privateData; int ret = -1; qemuMonitorPtr mon = NULL; if (virSecurityManagerSetDaemonSocketLabel(driver->securityManager, vm->def) < 0) { VIR_ERROR(_("Failed to set security context for monitor for %s"), vm->def->name); return -1; } /* Hold an extra reference because we can't allow 'vm' to be * deleted until the monitor gets its own reference. */ virObjectRef(vm); ignore_value(virTimeMillisNow(&priv->monStart)); virObjectUnlock(vm); mon = qemuMonitorOpen(vm, priv->monConfig, priv->monJSON, &monitorCallbacks, driver); if (mon && logCtxt) { qemuDomainLogContextRef(logCtxt); qemuMonitorSetDomainLog(mon, qemuProcessMonitorReportLogError, logCtxt, qemuProcessMonitorLogFree); } virObjectLock(vm); virObjectUnref(vm); priv->monStart = 0; if (!virDomainObjIsActive(vm)) { qemuMonitorClose(mon); mon = NULL; } priv->mon = mon; if (virSecurityManagerClearSocketLabel(driver->securityManager, vm->def) < 0) { VIR_ERROR(_("Failed to clear security context for monitor for %s"), vm->def->name); return -1; } if (priv->mon == NULL) { VIR_INFO("Failed to connect monitor for %s", vm->def->name); return -1; } if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) return -1; if (qemuMonitorSetCapabilities(priv->mon) < 0) goto cleanup; if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_MIGRATION_EVENT) && qemuMonitorSetMigrationCapability(priv->mon, QEMU_MONITOR_MIGRATION_CAPS_EVENTS, true) < 0) { VIR_DEBUG("Cannot enable migration events; clearing capability"); virQEMUCapsClear(priv->qemuCaps, QEMU_CAPS_MIGRATION_EVENT); } ret = 0; cleanup: if (qemuDomainObjExitMonitor(driver, vm) < 0) ret = -1; return ret; } /** * qemuProcessReadLog: Read log file of a qemu VM * @logCtxt: the domain log context * @msg: pointer to buffer to store the read messages in * * Reads log of a qemu VM. Skips messages not produced by qemu or irrelevant * messages. Returns returns 0 on success or -1 on error */ static int qemuProcessReadLog(qemuDomainLogContextPtr logCtxt, char **msg) { char *buf; ssize_t got; char *eol; char *filter_next; if ((got = qemuDomainLogContextRead(logCtxt, &buf)) < 0) return -1; /* Filter out debug messages from intermediate libvirt process */ filter_next = buf; while ((eol = strchr(filter_next, '\n'))) { *eol = '\0'; if (virLogProbablyLogMessage(filter_next) || STRPREFIX(filter_next, "char device redirected to")) { size_t skip = (eol + 1) - filter_next; memmove(filter_next, eol + 1, buf + got - eol); got -= skip; } else { filter_next = eol + 1; *eol = '\n'; } } filter_next = NULL; /* silence false coverity warning */ if (got > 0 && buf[got - 1] == '\n') { buf[got - 1] = '\0'; got--; } ignore_value(VIR_REALLOC_N_QUIET(buf, got + 1)); *msg = buf; return 0; } static int qemuProcessReportLogError(qemuDomainLogContextPtr logCtxt, const char *msgprefix) { char *logmsg = NULL; if (qemuProcessReadLog(logCtxt, &logmsg) < 0) return -1; virResetLastError(); if (virStringIsEmpty(logmsg)) virReportError(VIR_ERR_INTERNAL_ERROR, "%s", msgprefix); else virReportError(VIR_ERR_INTERNAL_ERROR, _("%s: %s"), msgprefix, logmsg); VIR_FREE(logmsg); return 0; } static void qemuProcessMonitorReportLogError(qemuMonitorPtr mon ATTRIBUTE_UNUSED, const char *msg, void *opaque) { qemuDomainLogContextPtr logCtxt = opaque; qemuProcessReportLogError(logCtxt, msg); } static int qemuProcessLookupPTYs(virDomainDefPtr def, virQEMUCapsPtr qemuCaps, virDomainChrDefPtr *devices, int count, virHashTablePtr info) { size_t i; for (i = 0; i < count; i++) { virDomainChrDefPtr chr = devices[i]; bool chardevfmt = virQEMUCapsSupportsChardev(def, qemuCaps, chr); if (chr->source->type == VIR_DOMAIN_CHR_TYPE_PTY) { char id[32]; qemuMonitorChardevInfoPtr entry; if (snprintf(id, sizeof(id), "%s%s", chardevfmt ? "char" : "", chr->info.alias) >= sizeof(id)) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("failed to format device alias " "for PTY retrieval")); return -1; } entry = virHashLookup(info, id); if (!entry || !entry->ptyPath) { if (chr->source->data.file.path == NULL) { /* neither the log output nor 'info chardev' had a * pty path for this chardev, report an error */ virReportError(VIR_ERR_INTERNAL_ERROR, _("no assigned pty for device %s"), id); return -1; } else { /* 'info chardev' had no pty path for this chardev, * but the log output had, so we're fine */ continue; } } VIR_FREE(chr->source->data.file.path); if (VIR_STRDUP(chr->source->data.file.path, entry->ptyPath) < 0) return -1; } } return 0; } static int qemuProcessFindCharDevicePTYsMonitor(virDomainObjPtr vm, virQEMUCapsPtr qemuCaps, virHashTablePtr info) { size_t i = 0; if (qemuProcessLookupPTYs(vm->def, qemuCaps, vm->def->serials, vm->def->nserials, info) < 0) return -1; if (qemuProcessLookupPTYs(vm->def, qemuCaps, vm->def->parallels, vm->def->nparallels, info) < 0) return -1; if (qemuProcessLookupPTYs(vm->def, qemuCaps, vm->def->channels, vm->def->nchannels, info) < 0) return -1; /* For historical reasons, console[0] can be just an alias * for serial[0]. That's why we need to update it as well. */ if (vm->def->nconsoles) { virDomainChrDefPtr chr = vm->def->consoles[0]; if (vm->def->nserials && chr->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE && chr->targetType == VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL) { /* yes, the first console is just an alias for serials[0] */ i = 1; if (virDomainChrSourceDefCopy(chr->source, ((vm->def->serials[0])->source)) < 0) return -1; } } if (qemuProcessLookupPTYs(vm->def, qemuCaps, vm->def->consoles + i, vm->def->nconsoles - i, info) < 0) return -1; return 0; } static int qemuProcessRefreshChannelVirtioState(virQEMUDriverPtr driver, virDomainObjPtr vm, virHashTablePtr info, int booted) { size_t i; int agentReason = VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_CHANNEL; qemuMonitorChardevInfoPtr entry; virObjectEventPtr event = NULL; char id[32]; if (booted) agentReason = VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_DOMAIN_STARTED; for (i = 0; i < vm->def->nchannels; i++) { virDomainChrDefPtr chr = vm->def->channels[i]; if (chr->targetType == VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO) { if (snprintf(id, sizeof(id), "char%s", chr->info.alias) >= sizeof(id)) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("failed to format device alias " "for PTY retrieval")); return -1; } /* port state not reported */ if (!(entry = virHashLookup(info, id)) || !entry->state) continue; if (entry->state != VIR_DOMAIN_CHR_DEVICE_STATE_DEFAULT && STREQ_NULLABLE(chr->target.name, "org.qemu.guest_agent.0") && (event = virDomainEventAgentLifecycleNewFromObj(vm, entry->state, agentReason))) qemuDomainEventQueue(driver, event); chr->state = entry->state; } } return 0; } int qemuRefreshVirtioChannelState(virQEMUDriverPtr driver, virDomainObjPtr vm, qemuDomainAsyncJob asyncJob) { qemuDomainObjPrivatePtr priv = vm->privateData; virHashTablePtr info = NULL; int ret = -1; if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) goto cleanup; ret = qemuMonitorGetChardevInfo(priv->mon, &info); if (qemuDomainObjExitMonitor(driver, vm) < 0) ret = -1; if (ret < 0) goto cleanup; ret = qemuProcessRefreshChannelVirtioState(driver, vm, info, false); cleanup: virHashFree(info); return ret; } static void qemuRefreshRTC(virQEMUDriverPtr driver, virDomainObjPtr vm) { qemuDomainObjPrivatePtr priv = vm->privateData; time_t now, then; struct tm thenbits; long localOffset; int rv; if (vm->def->clock.offset != VIR_DOMAIN_CLOCK_OFFSET_VARIABLE) return; memset(&thenbits, 0, sizeof(thenbits)); qemuDomainObjEnterMonitor(driver, vm); now = time(NULL); rv = qemuMonitorGetRTCTime(priv->mon, &thenbits); if (qemuDomainObjExitMonitor(driver, vm) < 0) rv = -1; if (rv < 0) return; thenbits.tm_isdst = -1; if ((then = mktime(&thenbits)) == (time_t) -1) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Unable to convert time")); return; } /* Thing is, @now is in local TZ but @then in UTC. */ if (virTimeLocalOffsetFromUTC(&localOffset) < 0) return; vm->def->clock.data.variable.adjustment = then - now + localOffset; } int qemuProcessRefreshBalloonState(virQEMUDriverPtr driver, virDomainObjPtr vm, int asyncJob) { unsigned long long balloon; int rc; /* if no ballooning is available, the current size equals to the current * full memory size */ if (!virDomainDefHasMemballoon(vm->def)) { vm->def->mem.cur_balloon = virDomainDefGetMemoryTotal(vm->def); return 0; } if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) return -1; rc = qemuMonitorGetBalloonInfo(qemuDomainGetMonitor(vm), &balloon); if (qemuDomainObjExitMonitor(driver, vm) < 0 || rc < 0) return -1; vm->def->mem.cur_balloon = balloon; return 0; } static int qemuProcessWaitForMonitor(virQEMUDriverPtr driver, virDomainObjPtr vm, int asyncJob, virQEMUCapsPtr qemuCaps, qemuDomainLogContextPtr logCtxt) { int ret = -1; virHashTablePtr info = NULL; qemuDomainObjPrivatePtr priv; VIR_DEBUG("Connect monitor to %p '%s'", vm, vm->def->name); if (qemuConnectMonitor(driver, vm, asyncJob, logCtxt) < 0) goto cleanup; /* Try to get the pty path mappings again via the monitor. This is much more * reliable if it's available. * Note that the monitor itself can be on a pty, so we still need to try the * log output method. */ priv = vm->privateData; if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) goto cleanup; ret = qemuMonitorGetChardevInfo(priv->mon, &info); VIR_DEBUG("qemuMonitorGetChardevInfo returned %i", ret); if (qemuDomainObjExitMonitor(driver, vm) < 0) ret = -1; if (ret == 0) { if ((ret = qemuProcessFindCharDevicePTYsMonitor(vm, qemuCaps, info)) < 0) goto cleanup; if ((ret = qemuProcessRefreshChannelVirtioState(driver, vm, info, true)) < 0) goto cleanup; } cleanup: virHashFree(info); if (logCtxt && kill(vm->pid, 0) == -1 && errno == ESRCH) { qemuProcessReportLogError(logCtxt, _("process exited while connecting to monitor")); ret = -1; } return ret; } static int qemuProcessDetectIOThreadPIDs(virQEMUDriverPtr driver, virDomainObjPtr vm, int asyncJob) { qemuDomainObjPrivatePtr priv = vm->privateData; qemuMonitorIOThreadInfoPtr *iothreads = NULL; int niothreads = 0; int ret = -1; size_t i; if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_OBJECT_IOTHREAD)) { /* The following check is because at one time a domain could * define iothreadids and start the domain - only failing the * capability check when attempting to add a disk. Because the * iothreads and [n]iothreadids were left untouched other code * assumed it could use the ->thread_id value to make thread_id * based adjustments (e.g. pinning, scheduling) which while * succeeding would execute on the calling thread. */ if (vm->def->niothreadids) { for (i = 0; i < vm->def->niothreadids; i++) { /* Check if the domain had defined any iothreadid elements * and supply a VIR_INFO indicating that it's being removed. */ if (!vm->def->iothreadids[i]->autofill) VIR_INFO("IOThreads not supported, remove iothread id '%u'", vm->def->iothreadids[i]->iothread_id); virDomainIOThreadIDDefFree(vm->def->iothreadids[i]); } /* Remove any trace */ VIR_FREE(vm->def->iothreadids); vm->def->niothreadids = 0; vm->def->iothreads = 0; } return 0; } /* Get the list of IOThreads from qemu */ if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) goto cleanup; niothreads = qemuMonitorGetIOThreads(priv->mon, &iothreads); if (qemuDomainObjExitMonitor(driver, vm) < 0) goto cleanup; if (niothreads < 0) goto cleanup; if (niothreads != vm->def->niothreadids) { virReportError(VIR_ERR_INTERNAL_ERROR, _("got wrong number of IOThread pids from QEMU monitor. " "got %d, wanted %zu"), niothreads, vm->def->niothreadids); goto cleanup; } /* Nothing to do */ if (niothreads == 0) { ret = 0; goto cleanup; } for (i = 0; i < niothreads; i++) { virDomainIOThreadIDDefPtr iothrid; if (!(iothrid = virDomainIOThreadIDFind(vm->def, iothreads[i]->iothread_id))) { virReportError(VIR_ERR_INTERNAL_ERROR, _("iothread %d not found"), iothreads[i]->iothread_id); goto cleanup; } iothrid->thread_id = iothreads[i]->thread_id; } ret = 0; cleanup: if (iothreads) { for (i = 0; i < niothreads; i++) VIR_FREE(iothreads[i]); VIR_FREE(iothreads); } return ret; } /* * To be run between fork/exec of QEMU only */ static int qemuProcessInitCpuAffinity(virDomainObjPtr vm) { int ret = -1; virBitmapPtr cpumap = NULL; virBitmapPtr cpumapToSet = NULL; virBitmapPtr hostcpumap = NULL; qemuDomainObjPrivatePtr priv = vm->privateData; if (!vm->pid) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Cannot setup CPU affinity until process is started")); return -1; } if (vm->def->placement_mode == VIR_DOMAIN_CPU_PLACEMENT_MODE_AUTO) { VIR_DEBUG("Set CPU affinity with advisory nodeset from numad"); cpumapToSet = priv->autoCpuset; } else { VIR_DEBUG("Set CPU affinity with specified cpuset"); if (vm->def->cpumask) { cpumapToSet = vm->def->cpumask; } else { /* You may think this is redundant, but we can't assume libvirtd * itself is running on all pCPUs, so we need to explicitly set * the spawned QEMU instance to all pCPUs if no map is given in * its config file */ int hostcpus; if (virHostCPUHasBitmap()) { hostcpumap = virHostCPUGetOnlineBitmap(); cpumap = virProcessGetAffinity(vm->pid); } if (hostcpumap && cpumap && virBitmapEqual(hostcpumap, cpumap)) { /* we're using all available CPUs, no reason to set * mask. If libvirtd is running without explicit * affinity, we can use hotplugged CPUs for this VM */ ret = 0; goto cleanup; } else { /* setaffinity fails if you set bits for CPUs which * aren't present, so we have to limit ourselves */ if ((hostcpus = virHostCPUGetCount()) < 0) goto cleanup; if (hostcpus > QEMUD_CPUMASK_LEN) hostcpus = QEMUD_CPUMASK_LEN; virBitmapFree(cpumap); if (!(cpumap = virBitmapNew(hostcpus))) goto cleanup; virBitmapSetAll(cpumap); cpumapToSet = cpumap; } } } if (virProcessSetAffinity(vm->pid, cpumapToSet) < 0) goto cleanup; ret = 0; cleanup: virBitmapFree(cpumap); virBitmapFree(hostcpumap); return ret; } /* set link states to down on interfaces at qemu start */ static int qemuProcessSetLinkStates(virQEMUDriverPtr driver, virDomainObjPtr vm, qemuDomainAsyncJob asyncJob) { qemuDomainObjPrivatePtr priv = vm->privateData; virDomainDefPtr def = vm->def; size_t i; int ret = -1; int rv; if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) return -1; for (i = 0; i < def->nnets; i++) { if (def->nets[i]->linkstate == VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DOWN) { if (!def->nets[i]->info.alias) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("missing alias for network device")); goto cleanup; } VIR_DEBUG("Setting link state: %s", def->nets[i]->info.alias); if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_NETDEV)) { virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s", _("Setting of link state is not supported by this qemu")); goto cleanup; } rv = qemuMonitorSetLink(priv->mon, def->nets[i]->info.alias, VIR_DOMAIN_NET_INTERFACE_LINK_STATE_DOWN); if (rv < 0) { virReportError(VIR_ERR_OPERATION_FAILED, _("Couldn't set link state on interface: %s"), def->nets[i]->info.alias); goto cleanup; } } } ret = 0; cleanup: if (qemuDomainObjExitMonitor(driver, vm) < 0) ret = -1; return ret; } /** * qemuProcessSetupPid: * * This function sets resource properities (affinity, cgroups, * scheduler) for any PID associated with a domain. It should be used * to set up emulator PIDs as well as vCPU and I/O thread pids to * ensure they are all handled the same way. * * Returns 0 on success, -1 on error. */ static int qemuProcessSetupPid(virDomainObjPtr vm, pid_t pid, virCgroupThreadName nameval, int id, virBitmapPtr cpumask, unsigned long long period, long long quota, virDomainThreadSchedParamPtr sched) { qemuDomainObjPrivatePtr priv = vm->privateData; virDomainNumatuneMemMode mem_mode; virCgroupPtr cgroup = NULL; virBitmapPtr use_cpumask; char *mem_mask = NULL; int ret = -1; if ((period || quota) && !virCgroupHasController(priv->cgroup, VIR_CGROUP_CONTROLLER_CPU)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("cgroup cpu is required for scheduler tuning")); goto cleanup; } /* Infer which cpumask shall be used. */ if (cpumask) use_cpumask = cpumask; else if (vm->def->placement_mode == VIR_DOMAIN_CPU_PLACEMENT_MODE_AUTO) use_cpumask = priv->autoCpuset; else use_cpumask = vm->def->cpumask; /* * If CPU cgroup controller is not initialized here, then we need * neither period nor quota settings. And if CPUSET controller is * not initialized either, then there's nothing to do anyway. */ if (virCgroupHasController(priv->cgroup, VIR_CGROUP_CONTROLLER_CPU) || virCgroupHasController(priv->cgroup, VIR_CGROUP_CONTROLLER_CPUSET)) { if (virDomainNumatuneGetMode(vm->def->numa, -1, &mem_mode) == 0 && mem_mode == VIR_DOMAIN_NUMATUNE_MEM_STRICT && virDomainNumatuneMaybeFormatNodeset(vm->def->numa, priv->autoNodeset, &mem_mask, -1) < 0) goto cleanup; if (virCgroupNewThread(priv->cgroup, nameval, id, true, &cgroup) < 0) goto cleanup; if (virCgroupHasController(priv->cgroup, VIR_CGROUP_CONTROLLER_CPUSET)) { if (use_cpumask && qemuSetupCgroupCpusetCpus(cgroup, use_cpumask) < 0) goto cleanup; /* * Don't setup cpuset.mems for the emulator, they need to * be set up after initialization in order for kvm * allocations to succeed. */ if (nameval != VIR_CGROUP_THREAD_EMULATOR && mem_mask && virCgroupSetCpusetMems(cgroup, mem_mask) < 0) goto cleanup; } if ((period || quota) && qemuSetupCgroupVcpuBW(cgroup, period, quota) < 0) goto cleanup; /* Move the thread to the sub dir */ if (virCgroupAddTask(cgroup, pid) < 0) goto cleanup; } /* Setup legacy affinity. */ if (use_cpumask && virProcessSetAffinity(pid, use_cpumask) < 0) goto cleanup; /* Set scheduler type and priority. */ if (sched && virProcessSetScheduler(pid, sched->policy, sched->priority) < 0) goto cleanup; ret = 0; cleanup: VIR_FREE(mem_mask); if (cgroup) { if (ret < 0) virCgroupRemove(cgroup); virCgroupFree(&cgroup); } return ret; } static int qemuProcessSetupEmulator(virDomainObjPtr vm) { return qemuProcessSetupPid(vm, vm->pid, VIR_CGROUP_THREAD_EMULATOR, 0, vm->def->cputune.emulatorpin, vm->def->cputune.emulator_period, vm->def->cputune.emulator_quota, NULL); } static int qemuProcessInitPasswords(virConnectPtr conn, virQEMUDriverPtr driver, virDomainObjPtr vm, int asyncJob) { int ret = 0; qemuDomainObjPrivatePtr priv = vm->privateData; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); size_t i; char *alias = NULL; char *secret = NULL; for (i = 0; i < vm->def->ngraphics; ++i) { virDomainGraphicsDefPtr graphics = vm->def->graphics[i]; if (graphics->type == VIR_DOMAIN_GRAPHICS_TYPE_VNC) { ret = qemuDomainChangeGraphicsPasswords(driver, vm, VIR_DOMAIN_GRAPHICS_TYPE_VNC, &graphics->data.vnc.auth, cfg->vncPassword, asyncJob); } else if (graphics->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE) { ret = qemuDomainChangeGraphicsPasswords(driver, vm, VIR_DOMAIN_GRAPHICS_TYPE_SPICE, &graphics->data.spice.auth, cfg->spicePassword, asyncJob); } if (ret < 0) goto cleanup; } for (i = 0; i < vm->def->ndisks; i++) { size_t secretLen; if (!vm->def->disks[i]->src->encryption || !virDomainDiskGetSource(vm->def->disks[i])) continue; if (vm->def->disks[i]->src->encryption->format != VIR_STORAGE_ENCRYPTION_FORMAT_DEFAULT && vm->def->disks[i]->src->encryption->format != VIR_STORAGE_ENCRYPTION_FORMAT_QCOW) continue; VIR_FREE(secret); if (qemuProcessGetVolumeQcowPassphrase(conn, vm->def->disks[i], &secret, &secretLen) < 0) goto cleanup; VIR_FREE(alias); if (!(alias = qemuAliasFromDisk(vm->def->disks[i]))) goto cleanup; if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) goto cleanup; ret = qemuMonitorSetDrivePassphrase(priv->mon, alias, secret); if (qemuDomainObjExitMonitor(driver, vm) < 0) ret = -1; if (ret < 0) goto cleanup; } cleanup: VIR_FREE(alias); VIR_FREE(secret); virObjectUnref(cfg); return ret; } static int qemuProcessPrepareChardevDevice(virDomainDefPtr def ATTRIBUTE_UNUSED, virDomainChrDefPtr dev, void *opaque ATTRIBUTE_UNUSED) { int fd; if (dev->source->type != VIR_DOMAIN_CHR_TYPE_FILE) return 0; if ((fd = open(dev->source->data.file.path, O_CREAT | O_APPEND, S_IRUSR|S_IWUSR)) < 0) { virReportSystemError(errno, _("Unable to pre-create chardev file '%s'"), dev->source->data.file.path); return -1; } VIR_FORCE_CLOSE(fd); return 0; } static int qemuProcessCleanupChardevDevice(virDomainDefPtr def ATTRIBUTE_UNUSED, virDomainChrDefPtr dev, void *opaque ATTRIBUTE_UNUSED) { if (dev->source->type == VIR_DOMAIN_CHR_TYPE_UNIX && dev->source->data.nix.listen && dev->source->data.nix.path) unlink(dev->source->data.nix.path); return 0; } /** * Loads and update video memory size for video devices according to QEMU * process as the QEMU will silently update the values that we pass to QEMU * through command line. We need to load these updated values and store them * into the status XML. * * We will fail if for some reason the values cannot be loaded from QEMU because * its mandatory to get the correct video memory size to status XML to not break * migration. */ static int qemuProcessUpdateVideoRamSize(virQEMUDriverPtr driver, virDomainObjPtr vm, int asyncJob) { int ret = -1; ssize_t i; qemuDomainObjPrivatePtr priv = vm->privateData; virDomainVideoDefPtr video = NULL; virQEMUDriverConfigPtr cfg = NULL; if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) return -1; for (i = 0; i < vm->def->nvideos; i++) { video = vm->def->videos[i]; switch (video->type) { case VIR_DOMAIN_VIDEO_TYPE_VGA: if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_VGA_VGAMEM)) { if (qemuMonitorUpdateVideoMemorySize(priv->mon, video, "VGA") < 0) goto error; } break; case VIR_DOMAIN_VIDEO_TYPE_QXL: if (i == 0) { if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_QXL_VGAMEM) && qemuMonitorUpdateVideoMemorySize(priv->mon, video, "qxl-vga") < 0) goto error; if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_QXL_VRAM64) && qemuMonitorUpdateVideoVram64Size(priv->mon, video, "qxl-vga") < 0) goto error; } else { if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_QXL_VGAMEM) && qemuMonitorUpdateVideoMemorySize(priv->mon, video, "qxl") < 0) goto error; if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_QXL_VRAM64) && qemuMonitorUpdateVideoVram64Size(priv->mon, video, "qxl") < 0) goto error; } break; case VIR_DOMAIN_VIDEO_TYPE_VMVGA: if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_VMWARE_SVGA_VGAMEM)) { if (qemuMonitorUpdateVideoMemorySize(priv->mon, video, "vmware-svga") < 0) goto error; } break; case VIR_DOMAIN_VIDEO_TYPE_CIRRUS: case VIR_DOMAIN_VIDEO_TYPE_XEN: case VIR_DOMAIN_VIDEO_TYPE_VBOX: case VIR_DOMAIN_VIDEO_TYPE_LAST: break; } } if (qemuDomainObjExitMonitor(driver, vm) < 0) return -1; cfg = virQEMUDriverGetConfig(driver); ret = virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm, driver->caps); virObjectUnref(cfg); return ret; error: ignore_value(qemuDomainObjExitMonitor(driver, vm)); return -1; } struct qemuProcessHookData { virConnectPtr conn; virDomainObjPtr vm; virQEMUDriverPtr driver; virQEMUDriverConfigPtr cfg; }; static int qemuProcessHook(void *data) { struct qemuProcessHookData *h = data; qemuDomainObjPrivatePtr priv = h->vm->privateData; int ret = -1; int fd; virBitmapPtr nodeset = NULL; virDomainNumatuneMemMode mode; /* This method cannot use any mutexes, which are not * protected across fork() */ virSecurityManagerPostFork(h->driver->securityManager); /* Some later calls want pid present */ h->vm->pid = getpid(); VIR_DEBUG("Obtaining domain lock"); /* * Since we're going to leak the returned FD to QEMU, * we need to make sure it gets a sensible label. * This mildly sucks, because there could be other * sockets the lock driver opens that we don't want * labelled. So far we're ok though. */ if (virSecurityManagerSetSocketLabel(h->driver->securityManager, h->vm->def) < 0) goto cleanup; if (virDomainLockProcessStart(h->driver->lockManager, h->cfg->uri, h->vm, /* QEMU is always paused initially */ true, &fd) < 0) goto cleanup; if (virSecurityManagerClearSocketLabel(h->driver->securityManager, h->vm->def) < 0) goto cleanup; if (qemuDomainBuildNamespace(h->driver, h->vm) < 0) goto cleanup; if (virDomainNumatuneGetMode(h->vm->def->numa, -1, &mode) == 0) { if (mode == VIR_DOMAIN_NUMATUNE_MEM_STRICT && h->cfg->cgroupControllers & (1 << VIR_CGROUP_CONTROLLER_CPUSET) && virCgroupControllerAvailable(VIR_CGROUP_CONTROLLER_CPUSET)) { /* Use virNuma* API iff necessary. Once set and child is exec()-ed, * there's no way for us to change it. Rely on cgroups (if available * and enabled in the config) rather than virNuma*. */ VIR_DEBUG("Relying on CGroups for memory binding"); } else { nodeset = virDomainNumatuneGetNodeset(h->vm->def->numa, priv->autoNodeset, -1); if (virNumaSetupMemoryPolicy(mode, nodeset) < 0) goto cleanup; } } ret = 0; cleanup: virObjectUnref(h->cfg); VIR_DEBUG("Hook complete ret=%d", ret); return ret; } int qemuProcessPrepareMonitorChr(virDomainChrSourceDefPtr monConfig, const char *domainDir) { monConfig->type = VIR_DOMAIN_CHR_TYPE_UNIX; monConfig->data.nix.listen = true; if (virAsprintf(&monConfig->data.nix.path, "%s/monitor.sock", domainDir) < 0) return -1; return 0; } /* * Precondition: vm must be locked, and a job must be active. * This method will call {Enter,Exit}Monitor */ int qemuProcessStartCPUs(virQEMUDriverPtr driver, virDomainObjPtr vm, virConnectPtr conn, virDomainRunningReason reason, qemuDomainAsyncJob asyncJob) { int ret = -1; qemuDomainObjPrivatePtr priv = vm->privateData; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); /* Bring up netdevs before starting CPUs */ if (qemuInterfaceStartDevices(vm->def) < 0) goto cleanup; VIR_DEBUG("Using lock state '%s'", NULLSTR(priv->lockState)); if (virDomainLockProcessResume(driver->lockManager, cfg->uri, vm, priv->lockState) < 0) { /* Don't free priv->lockState on error, because we need * to make sure we have state still present if the user * tries to resume again */ goto cleanup; } VIR_FREE(priv->lockState); if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) goto release; ret = qemuMonitorStartCPUs(priv->mon, conn); if (qemuDomainObjExitMonitor(driver, vm) < 0) ret = -1; if (ret < 0) goto release; virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, reason); cleanup: virObjectUnref(cfg); return ret; release: if (virDomainLockProcessPause(driver->lockManager, vm, &priv->lockState) < 0) VIR_WARN("Unable to release lease on %s", vm->def->name); VIR_DEBUG("Preserving lock state '%s'", NULLSTR(priv->lockState)); goto cleanup; } int qemuProcessStopCPUs(virQEMUDriverPtr driver, virDomainObjPtr vm, virDomainPausedReason reason, qemuDomainAsyncJob asyncJob) { int ret = -1; qemuDomainObjPrivatePtr priv = vm->privateData; VIR_FREE(priv->lockState); if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) goto cleanup; ret = qemuMonitorStopCPUs(priv->mon); if (qemuDomainObjExitMonitor(driver, vm) < 0) ret = -1; if (ret < 0) goto cleanup; /* de-activate netdevs after stopping CPUs */ ignore_value(qemuInterfaceStopDevices(vm->def)); if (priv->job.current) ignore_value(virTimeMillisNow(&priv->job.current->stopped)); virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, reason); if (virDomainLockProcessPause(driver->lockManager, vm, &priv->lockState) < 0) VIR_WARN("Unable to release lease on %s", vm->def->name); VIR_DEBUG("Preserving lock state '%s'", NULLSTR(priv->lockState)); cleanup: return ret; } static int qemuProcessNotifyNets(virDomainDefPtr def) { size_t i; for (i = 0; i < def->nnets; i++) { virDomainNetDefPtr net = def->nets[i]; /* keep others from trying to use the macvtap device name, but * don't return error if this happens, since that causes the * domain to be unceremoniously killed, which would be *very* * impolite. */ if (virDomainNetGetActualType(net) == VIR_DOMAIN_NET_TYPE_DIRECT) ignore_value(virNetDevMacVLanReserveName(net->ifname, false)); if (networkNotifyActualDevice(def, net) < 0) return -1; } return 0; } static int qemuProcessFiltersInstantiate(virDomainDefPtr def) { size_t i; for (i = 0; i < def->nnets; i++) { virDomainNetDefPtr net = def->nets[i]; if ((net->filter) && (net->ifname)) { if (virDomainConfNWFilterInstantiate(def->uuid, net) < 0) return 1; } } return 0; } static int qemuProcessUpdateState(virQEMUDriverPtr driver, virDomainObjPtr vm) { qemuDomainObjPrivatePtr priv = vm->privateData; virDomainState state; virDomainPausedReason reason; virDomainState newState = VIR_DOMAIN_NOSTATE; int oldReason; int newReason; bool running; char *msg = NULL; int ret; qemuDomainObjEnterMonitor(driver, vm); ret = qemuMonitorGetStatus(priv->mon, &running, &reason); if (qemuDomainObjExitMonitor(driver, vm) < 0) return -1; if (ret < 0) return -1; state = virDomainObjGetState(vm, &oldReason); if (running && (state == VIR_DOMAIN_SHUTOFF || (state == VIR_DOMAIN_PAUSED && oldReason == VIR_DOMAIN_PAUSED_STARTING_UP))) { newState = VIR_DOMAIN_RUNNING; newReason = VIR_DOMAIN_RUNNING_BOOTED; ignore_value(VIR_STRDUP_QUIET(msg, "finished booting")); } else if (state == VIR_DOMAIN_PAUSED && running) { newState = VIR_DOMAIN_RUNNING; newReason = VIR_DOMAIN_RUNNING_UNPAUSED; ignore_value(VIR_STRDUP_QUIET(msg, "was unpaused")); } else if (state == VIR_DOMAIN_RUNNING && !running) { if (reason == VIR_DOMAIN_PAUSED_SHUTTING_DOWN) { newState = VIR_DOMAIN_SHUTDOWN; newReason = VIR_DOMAIN_SHUTDOWN_UNKNOWN; ignore_value(VIR_STRDUP_QUIET(msg, "shutdown")); } else if (reason == VIR_DOMAIN_PAUSED_CRASHED) { newState = VIR_DOMAIN_CRASHED; newReason = VIR_DOMAIN_CRASHED_PANICKED; ignore_value(VIR_STRDUP_QUIET(msg, "crashed")); } else { newState = VIR_DOMAIN_PAUSED; newReason = reason; ignore_value(virAsprintf(&msg, "was paused (%s)", virDomainPausedReasonTypeToString(reason))); } } if (newState != VIR_DOMAIN_NOSTATE) { VIR_DEBUG("Domain %s %s while its monitor was disconnected;" " changing state to %s (%s)", vm->def->name, NULLSTR(msg), virDomainStateTypeToString(newState), virDomainStateReasonToString(newState, newReason)); VIR_FREE(msg); virDomainObjSetState(vm, newState, newReason); } return 0; } static int qemuProcessRecoverMigrationIn(virQEMUDriverPtr driver, virDomainObjPtr vm, virConnectPtr conn, qemuMigrationJobPhase phase, virDomainState state, int reason) { bool postcopy = (state == VIR_DOMAIN_PAUSED && reason == VIR_DOMAIN_PAUSED_POSTCOPY_FAILED) || (state == VIR_DOMAIN_RUNNING && reason == VIR_DOMAIN_RUNNING_POSTCOPY); switch (phase) { case QEMU_MIGRATION_PHASE_NONE: case QEMU_MIGRATION_PHASE_PERFORM2: case QEMU_MIGRATION_PHASE_BEGIN3: case QEMU_MIGRATION_PHASE_PERFORM3: case QEMU_MIGRATION_PHASE_PERFORM3_DONE: case QEMU_MIGRATION_PHASE_CONFIRM3_CANCELLED: case QEMU_MIGRATION_PHASE_CONFIRM3: case QEMU_MIGRATION_PHASE_LAST: /* N/A for incoming migration */ break; case QEMU_MIGRATION_PHASE_PREPARE: VIR_DEBUG("Killing unfinished incoming migration for domain %s", vm->def->name); return -1; case QEMU_MIGRATION_PHASE_FINISH2: /* source domain is already killed so let's just resume the domain * and hope we are all set */ VIR_DEBUG("Incoming migration finished, resuming domain %s", vm->def->name); if (qemuProcessStartCPUs(driver, vm, conn, VIR_DOMAIN_RUNNING_UNPAUSED, QEMU_ASYNC_JOB_NONE) < 0) { VIR_WARN("Could not resume domain %s", vm->def->name); } break; case QEMU_MIGRATION_PHASE_FINISH3: /* migration finished, we started resuming the domain but didn't * confirm success or failure yet; killing it seems safest unless * we already started guest CPUs or we were in post-copy mode */ if (postcopy) { qemuMigrationPostcopyFailed(driver, vm); } else if (state != VIR_DOMAIN_RUNNING) { VIR_DEBUG("Killing migrated domain %s", vm->def->name); return -1; } break; } return 0; } static int qemuProcessRecoverMigrationOut(virQEMUDriverPtr driver, virDomainObjPtr vm, virConnectPtr conn, qemuMigrationJobPhase phase, virDomainState state, int reason, unsigned int *stopFlags) { bool postcopy = state == VIR_DOMAIN_PAUSED && (reason == VIR_DOMAIN_PAUSED_POSTCOPY || reason == VIR_DOMAIN_PAUSED_POSTCOPY_FAILED); switch (phase) { case QEMU_MIGRATION_PHASE_NONE: case QEMU_MIGRATION_PHASE_PREPARE: case QEMU_MIGRATION_PHASE_FINISH2: case QEMU_MIGRATION_PHASE_FINISH3: case QEMU_MIGRATION_PHASE_LAST: /* N/A for outgoing migration */ break; case QEMU_MIGRATION_PHASE_BEGIN3: /* nothing happened so far, just forget we were about to migrate the * domain */ break; case QEMU_MIGRATION_PHASE_PERFORM2: case QEMU_MIGRATION_PHASE_PERFORM3: /* migration is still in progress, let's cancel it and resume the * domain; however we can only do that before migration enters * post-copy mode */ if (postcopy) { qemuMigrationPostcopyFailed(driver, vm); } else { VIR_DEBUG("Cancelling unfinished migration of domain %s", vm->def->name); if (qemuMigrationCancel(driver, vm) < 0) { VIR_WARN("Could not cancel ongoing migration of domain %s", vm->def->name); } goto resume; } break; case QEMU_MIGRATION_PHASE_PERFORM3_DONE: /* migration finished but we didn't have a chance to get the result * of Finish3 step; third party needs to check what to do next; in * post-copy mode we can use PAUSED_POSTCOPY_FAILED state for this */ if (postcopy) qemuMigrationPostcopyFailed(driver, vm); break; case QEMU_MIGRATION_PHASE_CONFIRM3_CANCELLED: /* Finish3 failed, we need to resume the domain, but once we enter * post-copy mode there's no way back, so let's just mark the domain * as broken in that case */ if (postcopy) { qemuMigrationPostcopyFailed(driver, vm); } else { VIR_DEBUG("Resuming domain %s after failed migration", vm->def->name); goto resume; } break; case QEMU_MIGRATION_PHASE_CONFIRM3: /* migration completed, we need to kill the domain here */ *stopFlags |= VIR_QEMU_PROCESS_STOP_MIGRATED; return -1; } return 0; resume: /* resume the domain but only if it was paused as a result of * migration */ if (state == VIR_DOMAIN_PAUSED && (reason == VIR_DOMAIN_PAUSED_MIGRATION || reason == VIR_DOMAIN_PAUSED_UNKNOWN)) { if (qemuProcessStartCPUs(driver, vm, conn, VIR_DOMAIN_RUNNING_UNPAUSED, QEMU_ASYNC_JOB_NONE) < 0) { VIR_WARN("Could not resume domain %s", vm->def->name); } } return 0; } static int qemuProcessRecoverJob(virQEMUDriverPtr driver, virDomainObjPtr vm, virConnectPtr conn, const struct qemuDomainJobObj *job, unsigned int *stopFlags) { qemuDomainObjPrivatePtr priv = vm->privateData; virDomainState state; int reason; state = virDomainObjGetState(vm, &reason); switch (job->asyncJob) { case QEMU_ASYNC_JOB_MIGRATION_OUT: if (qemuProcessRecoverMigrationOut(driver, vm, conn, job->phase, state, reason, stopFlags) < 0) return -1; break; case QEMU_ASYNC_JOB_MIGRATION_IN: if (qemuProcessRecoverMigrationIn(driver, vm, conn, job->phase, state, reason) < 0) return -1; break; case QEMU_ASYNC_JOB_SAVE: case QEMU_ASYNC_JOB_DUMP: case QEMU_ASYNC_JOB_SNAPSHOT: qemuDomainObjEnterMonitor(driver, vm); ignore_value(qemuMonitorMigrateCancel(priv->mon)); if (qemuDomainObjExitMonitor(driver, vm) < 0) return -1; /* resume the domain but only if it was paused as a result of * running a migration-to-file operation. Although we are * recovering an async job, this function is run at startup * and must resume things using sync monitor connections. */ if (state == VIR_DOMAIN_PAUSED && ((job->asyncJob == QEMU_ASYNC_JOB_DUMP && reason == VIR_DOMAIN_PAUSED_DUMP) || (job->asyncJob == QEMU_ASYNC_JOB_SAVE && reason == VIR_DOMAIN_PAUSED_SAVE) || (job->asyncJob == QEMU_ASYNC_JOB_SNAPSHOT && (reason == VIR_DOMAIN_PAUSED_SNAPSHOT || reason == VIR_DOMAIN_PAUSED_MIGRATION)) || reason == VIR_DOMAIN_PAUSED_UNKNOWN)) { if (qemuProcessStartCPUs(driver, vm, conn, VIR_DOMAIN_RUNNING_UNPAUSED, QEMU_ASYNC_JOB_NONE) < 0) { VIR_WARN("Could not resume domain '%s' after migration to file", vm->def->name); } } break; case QEMU_ASYNC_JOB_START: /* Already handled in VIR_DOMAIN_PAUSED_STARTING_UP check. */ break; case QEMU_ASYNC_JOB_NONE: case QEMU_ASYNC_JOB_LAST: break; } if (!virDomainObjIsActive(vm)) return -1; /* In case any special handling is added for job type that has been ignored * before, QEMU_DOMAIN_TRACK_JOBS (from qemu_domain.h) needs to be updated * for the job to be properly tracked in domain state XML. */ switch (job->active) { case QEMU_JOB_QUERY: /* harmless */ break; case QEMU_JOB_DESTROY: VIR_DEBUG("Domain %s should have already been destroyed", vm->def->name); return -1; case QEMU_JOB_SUSPEND: /* mostly harmless */ break; case QEMU_JOB_MODIFY: /* XXX depending on the command we may be in an inconsistent state and * we should probably fall back to "monitor error" state and refuse to */ break; case QEMU_JOB_MIGRATION_OP: case QEMU_JOB_ABORT: case QEMU_JOB_ASYNC: case QEMU_JOB_ASYNC_NESTED: /* async job was already handled above */ case QEMU_JOB_NONE: case QEMU_JOB_LAST: break; } return 0; } static int qemuProcessUpdateDevices(virQEMUDriverPtr driver, virDomainObjPtr vm) { qemuDomainObjPrivatePtr priv = vm->privateData; virDomainDeviceDef dev; const char **qemuDevices; char **old; char **tmp; int ret = -1; if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_DEVICE_DEL_EVENT)) return 0; old = priv->qemuDevices; priv->qemuDevices = NULL; if (qemuDomainUpdateDeviceList(driver, vm, QEMU_ASYNC_JOB_NONE) < 0) goto cleanup; qemuDevices = (const char **) priv->qemuDevices; if ((tmp = old)) { while (*tmp) { if (!virStringListHasString(qemuDevices, *tmp) && virDomainDefFindDevice(vm->def, *tmp, &dev, false) == 0 && qemuDomainRemoveDevice(driver, vm, &dev) < 0) { goto cleanup; } tmp++; } } ret = 0; cleanup: virStringListFree(old); return ret; } static int qemuDomainPerfRestart(virDomainObjPtr vm) { size_t i; virDomainDefPtr def = vm->def; qemuDomainObjPrivatePtr priv = vm->privateData; if (!(priv->perf = virPerfNew())) return -1; for (i = 0; i < VIR_PERF_EVENT_LAST; i++) { if (def->perf.events[i] && def->perf.events[i] == VIR_TRISTATE_BOOL_YES) { /* Failure to re-enable the perf event should not be fatal */ if (virPerfEventEnable(priv->perf, i, vm->pid) < 0) def->perf.events[i] = VIR_TRISTATE_BOOL_NO; } } return 0; } static void qemuProcessReconnectCheckMemAliasOrderMismatch(virDomainObjPtr vm) { size_t i; int aliasidx; virDomainDefPtr def = vm->def; qemuDomainObjPrivatePtr priv = vm->privateData; if (!virDomainDefHasMemoryHotplug(def) || def->nmems == 0) return; for (i = 0; i < def->nmems; i++) { aliasidx = qemuDomainDeviceAliasIndex(&def->mems[i]->info, "dimm"); if (def->mems[i]->info.addr.dimm.slot != aliasidx) { priv->memAliasOrderMismatch = true; break; } } } static int qemuProcessBuildDestroyHugepagesPath(virQEMUDriverPtr driver, virDomainObjPtr vm, bool build) { virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); char *hugepagePath = NULL; size_t i; int ret = -1; if (vm->def->mem.nhugepages) { for (i = 0; i < cfg->nhugetlbfs; i++) { VIR_FREE(hugepagePath); hugepagePath = qemuGetDomainHugepagePath(vm->def, &cfg->hugetlbfs[i]); if (!hugepagePath) goto cleanup; if (build) { if (virFileMakePathWithMode(hugepagePath, 0700) < 0) { virReportSystemError(errno, _("Unable to create %s"), hugepagePath); goto cleanup; } if (virSecurityManagerDomainSetPathLabel(driver->securityManager, vm->def, hugepagePath) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Unable to set huge path in security driver")); goto cleanup; } } else { if (rmdir(hugepagePath) < 0) VIR_WARN("Unable to remove hugepage path: %s (errno=%d)", hugepagePath, errno); } } } ret = 0; cleanup: VIR_FREE(hugepagePath); virObjectUnref(cfg); return ret; } struct qemuProcessReconnectData { virConnectPtr conn; virQEMUDriverPtr driver; virDomainObjPtr obj; }; /* * Open an existing VM's monitor, re-detect VCPU threads * and re-reserve the security labels in use * * We own the virConnectPtr we are passed here - whoever started * this thread function has increased the reference counter to it * so that we now have to close it. * * This function also inherits a locked and ref'd domain object. * * This function needs to: * 1. Enter job * 1. just before monitor reconnect do lightweight MonitorEnter * (increase VM refcount and unlock VM) * 2. reconnect to monitor * 3. do lightweight MonitorExit (lock VM) * 4. continue reconnect process * 5. EndJob * * We can't do normal MonitorEnter & MonitorExit because these two lock the * monitor lock, which does not exists in this early phase. */ static void qemuProcessReconnect(void *opaque) { struct qemuProcessReconnectData *data = opaque; virQEMUDriverPtr driver = data->driver; virDomainObjPtr obj = data->obj; qemuDomainObjPrivatePtr priv; virConnectPtr conn = data->conn; struct qemuDomainJobObj oldjob; int state; int reason; virQEMUDriverConfigPtr cfg; size_t i; unsigned int stopFlags = 0; bool jobStarted = false; virCapsPtr caps = NULL; VIR_FREE(data); qemuDomainObjRestoreJob(obj, &oldjob); if (oldjob.asyncJob == QEMU_ASYNC_JOB_MIGRATION_IN) stopFlags |= VIR_QEMU_PROCESS_STOP_MIGRATED; cfg = virQEMUDriverGetConfig(driver); priv = obj->privateData; if (!(caps = virQEMUDriverGetCapabilities(driver, false))) goto error; if (qemuDomainObjBeginJob(driver, obj, QEMU_JOB_MODIFY) < 0) goto error; jobStarted = true; /* XXX If we ever gonna change pid file pattern, come up with * some intelligence here to deal with old paths. */ if (!(priv->pidfile = virPidFileBuildPath(cfg->stateDir, obj->def->name))) goto error; /* Restore the masterKey */ if (qemuDomainMasterKeyReadFile(priv) < 0) goto error; virNWFilterReadLockFilterUpdates(); VIR_DEBUG("Reconnect monitor to %p '%s'", obj, obj->def->name); /* XXX check PID liveliness & EXE path */ if (qemuConnectMonitor(driver, obj, QEMU_ASYNC_JOB_NONE, NULL) < 0) goto error; if (qemuHostdevUpdateActiveDomainDevices(driver, obj->def) < 0) goto error; if (qemuConnectCgroup(driver, obj) < 0) goto error; if (qemuDomainPerfRestart(obj) < 0) goto error; /* XXX: Need to change as long as lock is introduced for * qemu_driver->sharedDevices. */ for (i = 0; i < obj->def->ndisks; i++) { virDomainDeviceDef dev; if (virStorageTranslateDiskSourcePool(conn, obj->def->disks[i]) < 0) goto error; /* XXX we should be able to restore all data from XML in the future. * This should be the only place that calls qemuDomainDetermineDiskChain * with @report_broken == false to guarantee best-effort domain * reconnect */ if (qemuDomainDetermineDiskChain(driver, obj, obj->def->disks[i], true, false) < 0) goto error; dev.type = VIR_DOMAIN_DEVICE_DISK; dev.data.disk = obj->def->disks[i]; if (qemuAddSharedDevice(driver, &dev, obj->def->name) < 0) goto error; } if (qemuProcessUpdateState(driver, obj) < 0) goto error; state = virDomainObjGetState(obj, &reason); if (state == VIR_DOMAIN_SHUTOFF || (state == VIR_DOMAIN_PAUSED && reason == VIR_DOMAIN_PAUSED_STARTING_UP)) { VIR_DEBUG("Domain '%s' wasn't fully started yet, killing it", obj->def->name); goto error; } /* If upgrading from old libvirtd we won't have found any * caps in the domain status, so re-query them */ if (!priv->qemuCaps && !(priv->qemuCaps = virQEMUCapsCacheLookupCopy(caps, driver->qemuCapsCache, obj->def->emulator, obj->def->os.machine))) goto error; /* In case the domain shutdown while we were not running, * we need to finish the shutdown process. And we need to do it after * we have virQEMUCaps filled in. */ if (state == VIR_DOMAIN_SHUTDOWN || (state == VIR_DOMAIN_PAUSED && reason == VIR_DOMAIN_PAUSED_SHUTTING_DOWN)) { VIR_DEBUG("Finishing shutdown sequence for domain %s", obj->def->name); qemuProcessShutdownOrReboot(driver, obj); goto cleanup; } if (qemuProcessBuildDestroyHugepagesPath(driver, obj, true) < 0) goto error; if ((qemuDomainAssignAddresses(obj->def, priv->qemuCaps, driver, obj, false)) < 0) { goto error; } /* if domain requests security driver we haven't loaded, report error, but * do not kill the domain */ ignore_value(virSecurityManagerCheckAllLabel(driver->securityManager, obj->def)); if (qemuDomainRefreshVcpuInfo(driver, obj, QEMU_ASYNC_JOB_NONE, true) < 0) goto error; if (virSecurityManagerReserveLabel(driver->securityManager, obj->def, obj->pid) < 0) goto error; if (qemuProcessNotifyNets(obj->def) < 0) goto error; if (qemuProcessFiltersInstantiate(obj->def)) goto error; if (qemuProcessRefreshDisks(driver, obj, QEMU_ASYNC_JOB_NONE) < 0) goto error; if (qemuRefreshVirtioChannelState(driver, obj, QEMU_ASYNC_JOB_NONE) < 0) goto error; /* If querying of guest's RTC failed, report error, but do not kill the domain. */ qemuRefreshRTC(driver, obj); if (qemuProcessRefreshBalloonState(driver, obj, QEMU_ASYNC_JOB_NONE) < 0) goto error; if (qemuProcessRecoverJob(driver, obj, conn, &oldjob, &stopFlags) < 0) goto error; if (qemuProcessUpdateDevices(driver, obj) < 0) goto error; qemuProcessReconnectCheckMemAliasOrderMismatch(obj); if (qemuConnectAgent(driver, obj) < 0) goto error; /* update domain state XML with possibly updated state in virDomainObj */ if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, obj, driver->caps) < 0) goto error; /* Run an hook to allow admins to do some magic */ if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) { char *xml = qemuDomainDefFormatXML(driver, obj->def, 0); int hookret; hookret = virHookCall(VIR_HOOK_DRIVER_QEMU, obj->def->name, VIR_HOOK_QEMU_OP_RECONNECT, VIR_HOOK_SUBOP_BEGIN, NULL, xml, NULL); VIR_FREE(xml); /* * If the script raised an error abort the launch */ if (hookret < 0) goto error; } if (virAtomicIntInc(&driver->nactive) == 1 && driver->inhibitCallback) driver->inhibitCallback(true, driver->inhibitOpaque); cleanup: if (jobStarted) qemuDomainObjEndJob(driver, obj); if (!virDomainObjIsActive(obj)) qemuDomainRemoveInactive(driver, obj); virDomainObjEndAPI(&obj); virObjectUnref(conn); virObjectUnref(cfg); virObjectUnref(caps); virNWFilterUnlockFilterUpdates(); return; error: if (virDomainObjIsActive(obj)) { /* We can't get the monitor back, so must kill the VM * to remove danger of it ending up running twice if * user tries to start it again later */ if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_NO_SHUTDOWN)) { /* If we couldn't get the monitor and qemu supports * no-shutdown, we can safely say that the domain * crashed ... */ state = VIR_DOMAIN_SHUTOFF_CRASHED; } else { /* ... but if it doesn't we can't say what the state * really is and FAILED means "failed to start" */ state = VIR_DOMAIN_SHUTOFF_UNKNOWN; } /* If BeginJob failed, we jumped here without a job, let's hope another * thread didn't have a chance to start playing with the domain yet * (it's all we can do anyway). */ qemuProcessStop(driver, obj, state, QEMU_ASYNC_JOB_NONE, stopFlags); } goto cleanup; } static int qemuProcessReconnectHelper(virDomainObjPtr obj, void *opaque) { virThread thread; struct qemuProcessReconnectData *src = opaque; struct qemuProcessReconnectData *data; /* If the VM was inactive, we don't need to reconnect */ if (!obj->pid) return 0; if (VIR_ALLOC(data) < 0) return -1; memcpy(data, src, sizeof(*data)); data->obj = obj; /* this lock and reference will be eventually transferred to the thread * that handles the reconnect */ virObjectLock(obj); virObjectRef(obj); /* Since we close the connection later on, we have to make sure that the * threads we start see a valid connection throughout their lifetime. We * simply increase the reference counter here. */ virObjectRef(data->conn); if (virThreadCreate(&thread, false, qemuProcessReconnect, data) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Could not create thread. QEMU initialization " "might be incomplete")); /* We can't spawn a thread and thus connect to monitor. Kill qemu. * It's safe to call qemuProcessStop without a job here since there * is no thread that could be doing anything else with the same domain * object. */ qemuProcessStop(src->driver, obj, VIR_DOMAIN_SHUTOFF_FAILED, QEMU_ASYNC_JOB_NONE, 0); qemuDomainRemoveInactive(src->driver, obj); virDomainObjEndAPI(&obj); virObjectUnref(data->conn); VIR_FREE(data); return -1; } return 0; } /** * qemuProcessReconnectAll * * Try to re-open the resources for live VMs that we care * about. */ void qemuProcessReconnectAll(virConnectPtr conn, virQEMUDriverPtr driver) { struct qemuProcessReconnectData data = {.conn = conn, .driver = driver}; virDomainObjListForEach(driver->domains, qemuProcessReconnectHelper, &data); } static int qemuProcessVNCAllocatePorts(virQEMUDriverPtr driver, virDomainGraphicsDefPtr graphics, bool allocate) { unsigned short port; if (!allocate) { if (graphics->data.vnc.autoport) graphics->data.vnc.port = 5900; return 0; } if (graphics->data.vnc.autoport) { if (virPortAllocatorAcquire(driver->remotePorts, &port) < 0) return -1; graphics->data.vnc.port = port; } if (graphics->data.vnc.websocket == -1) { if (virPortAllocatorAcquire(driver->webSocketPorts, &port) < 0) return -1; graphics->data.vnc.websocket = port; graphics->data.vnc.websocketGenerated = true; } return 0; } static int qemuProcessSPICEAllocatePorts(virQEMUDriverPtr driver, virDomainGraphicsDefPtr graphics, bool allocate) { virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); unsigned short port = 0; unsigned short tlsPort; size_t i; int defaultMode = graphics->data.spice.defaultMode; int ret = -1; bool needTLSPort = false; bool needPort = false; if (graphics->data.spice.autoport) { /* check if tlsPort or port need allocation */ for (i = 0; i < VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_LAST; i++) { switch (graphics->data.spice.channels[i]) { case VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_MODE_SECURE: needTLSPort = true; break; case VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_MODE_INSECURE: needPort = true; break; case VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_MODE_ANY: /* default mode will be used */ break; } } switch (defaultMode) { case VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_MODE_SECURE: needTLSPort = true; break; case VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_MODE_INSECURE: needPort = true; break; case VIR_DOMAIN_GRAPHICS_SPICE_CHANNEL_MODE_ANY: if (cfg->spiceTLS) needTLSPort = true; needPort = true; break; } } if (!allocate) { if (needPort || graphics->data.spice.port == -1) graphics->data.spice.port = 5901; if (needTLSPort || graphics->data.spice.tlsPort == -1) graphics->data.spice.tlsPort = 5902; ret = 0; goto cleanup; } if (needPort || graphics->data.spice.port == -1) { if (virPortAllocatorAcquire(driver->remotePorts, &port) < 0) goto cleanup; graphics->data.spice.port = port; if (!graphics->data.spice.autoport) graphics->data.spice.portReserved = true; } if (needTLSPort || graphics->data.spice.tlsPort == -1) { if (!cfg->spiceTLS) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Auto allocation of spice TLS port requested " "but spice TLS is disabled in qemu.conf")); goto cleanup; } if (virPortAllocatorAcquire(driver->remotePorts, &tlsPort) < 0) goto cleanup; graphics->data.spice.tlsPort = tlsPort; if (!graphics->data.spice.autoport) graphics->data.spice.tlsPortReserved = true; } ret = 0; cleanup: virPortAllocatorRelease(driver->remotePorts, port); virObjectUnref(cfg); return ret; } static int qemuValidateCpuCount(virDomainDefPtr def, virQEMUCapsPtr qemuCaps) { unsigned int maxCpus = virQEMUCapsGetMachineMaxCpus(qemuCaps, def->os.machine); if (virDomainDefGetVcpus(def) == 0) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Domain requires at least 1 vCPU")); return -1; } if (maxCpus > 0 && virDomainDefGetVcpusMax(def) > maxCpus) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Maximum CPUs greater than specified machine type limit")); return -1; } return 0; } static bool qemuProcessVerifyGuestCPU(virQEMUDriverPtr driver, virDomainObjPtr vm, int asyncJob) { virDomainDefPtr def = vm->def; virArch arch = def->os.arch; virCPUDataPtr guestcpu = NULL; qemuDomainObjPrivatePtr priv = vm->privateData; int rc; bool ret = false; size_t i; switch (arch) { case VIR_ARCH_I686: case VIR_ARCH_X86_64: if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) return false; rc = qemuMonitorGetGuestCPU(priv->mon, arch, &guestcpu); if (qemuDomainObjExitMonitor(driver, vm) < 0) return false; if (rc < 0) { if (rc == -2) break; goto cleanup; } if (def->features[VIR_DOMAIN_FEATURE_PVSPINLOCK] == VIR_TRISTATE_SWITCH_ON) { if (!virCPUDataCheckFeature(guestcpu, VIR_CPU_x86_KVM_PV_UNHALT)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("host doesn't support paravirtual spinlocks")); goto cleanup; } } for (i = 0; i < VIR_DOMAIN_HYPERV_LAST; i++) { if (def->hyperv_features[i] == VIR_TRISTATE_SWITCH_ON) { char *cpuFeature; if (virAsprintf(&cpuFeature, "__kvm_hv_%s", virDomainHypervTypeToString(i)) < 0) goto cleanup; if (!virCPUDataCheckFeature(guestcpu, cpuFeature)) { switch ((virDomainHyperv) i) { case VIR_DOMAIN_HYPERV_RELAXED: case VIR_DOMAIN_HYPERV_VAPIC: case VIR_DOMAIN_HYPERV_SPINLOCKS: VIR_WARN("host doesn't support hyperv '%s' feature", virDomainHypervTypeToString(i)); break; case VIR_DOMAIN_HYPERV_VPINDEX: case VIR_DOMAIN_HYPERV_RUNTIME: case VIR_DOMAIN_HYPERV_SYNIC: case VIR_DOMAIN_HYPERV_STIMER: case VIR_DOMAIN_HYPERV_RESET: case VIR_DOMAIN_HYPERV_VENDOR_ID: virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("host doesn't support hyperv '%s' feature"), virDomainHypervTypeToString(i)); goto cleanup; break; /* coverity[dead_error_begin] */ case VIR_DOMAIN_HYPERV_LAST: break; } } } } if (def->cpu && def->cpu->mode != VIR_CPU_MODE_HOST_PASSTHROUGH) { for (i = 0; i < def->cpu->nfeatures; i++) { virCPUFeatureDefPtr feature = &def->cpu->features[i]; if (feature->policy != VIR_CPU_FEATURE_REQUIRE) continue; if (STREQ(feature->name, "invtsc") && !virCPUDataCheckFeature(guestcpu, feature->name)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("host doesn't support invariant TSC")); goto cleanup; } } } break; default: break; } ret = true; cleanup: cpuDataFree(guestcpu); return ret; } static int qemuPrepareNVRAM(virQEMUDriverConfigPtr cfg, virDomainObjPtr vm) { int ret = -1; int srcFD = -1; int dstFD = -1; virDomainLoaderDefPtr loader = vm->def->os.loader; bool created = false; const char *master_nvram_path; ssize_t r; if (!loader || !loader->nvram || virFileExists(loader->nvram)) return 0; master_nvram_path = loader->templt; if (!loader->templt) { size_t i; for (i = 0; i < cfg->nfirmwares; i++) { if (STREQ(cfg->firmwares[i]->name, loader->path)) { master_nvram_path = cfg->firmwares[i]->nvram; break; } } } if (!master_nvram_path) { virReportError(VIR_ERR_OPERATION_FAILED, _("unable to find any master var store for " "loader: %s"), loader->path); goto cleanup; } if ((srcFD = virFileOpenAs(master_nvram_path, O_RDONLY, 0, -1, -1, 0)) < 0) { virReportSystemError(-srcFD, _("Failed to open file '%s'"), master_nvram_path); goto cleanup; } if ((dstFD = virFileOpenAs(loader->nvram, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR, cfg->user, cfg->group, 0)) < 0) { virReportSystemError(-dstFD, _("Failed to create file '%s'"), loader->nvram); goto cleanup; } created = true; do { char buf[1024]; if ((r = saferead(srcFD, buf, sizeof(buf))) < 0) { virReportSystemError(errno, _("Unable to read from file '%s'"), master_nvram_path); goto cleanup; } if (safewrite(dstFD, buf, r) < 0) { virReportSystemError(errno, _("Unable to write to file '%s'"), loader->nvram); goto cleanup; } } while (r); if (VIR_CLOSE(srcFD) < 0) { virReportSystemError(errno, _("Unable to close file '%s'"), master_nvram_path); goto cleanup; } if (VIR_CLOSE(dstFD) < 0) { virReportSystemError(errno, _("Unable to close file '%s'"), loader->nvram); goto cleanup; } ret = 0; cleanup: /* We successfully generated the nvram path, but failed to * copy the file content. Roll back. */ if (ret < 0) { if (created) unlink(loader->nvram); } VIR_FORCE_CLOSE(srcFD); VIR_FORCE_CLOSE(dstFD); return ret; } static void qemuLogOperation(virDomainObjPtr vm, const char *msg, virCommandPtr cmd, qemuDomainLogContextPtr logCtxt) { char *timestamp; qemuDomainObjPrivatePtr priv = vm->privateData; int qemuVersion = virQEMUCapsGetVersion(priv->qemuCaps); const char *package = virQEMUCapsGetPackage(priv->qemuCaps); char *hostname = virGetHostname(); if ((timestamp = virTimeStringNow()) == NULL) goto cleanup; if (qemuDomainLogContextWrite(logCtxt, "%s: %s %s, qemu version: %d.%d.%d%s, hostname: %s\n", timestamp, msg, VIR_LOG_VERSION_STRING, (qemuVersion / 1000000) % 1000, (qemuVersion / 1000) % 1000, qemuVersion % 1000, package ? package : "", hostname ? hostname : "") < 0) goto cleanup; if (cmd) { char *args = virCommandToString(cmd); qemuDomainLogContextWrite(logCtxt, "%s\n", args); VIR_FREE(args); } cleanup: VIR_FREE(hostname); VIR_FREE(timestamp); } void qemuProcessIncomingDefFree(qemuProcessIncomingDefPtr inc) { if (!inc) return; VIR_FREE(inc->address); VIR_FREE(inc->launchURI); VIR_FREE(inc->deferredURI); VIR_FREE(inc); } /* * This function does not copy @path, the caller is responsible for keeping * the @path pointer valid during the lifetime of the allocated * qemuProcessIncomingDef structure. */ qemuProcessIncomingDefPtr qemuProcessIncomingDefNew(virQEMUCapsPtr qemuCaps, const char *listenAddress, const char *migrateFrom, int fd, const char *path) { qemuProcessIncomingDefPtr inc = NULL; if (qemuMigrationCheckIncoming(qemuCaps, migrateFrom) < 0) return NULL; if (VIR_ALLOC(inc) < 0) return NULL; if (VIR_STRDUP(inc->address, listenAddress) < 0) goto error; inc->launchURI = qemuMigrationIncomingURI(migrateFrom, fd); if (!inc->launchURI) goto error; if (virQEMUCapsGet(qemuCaps, QEMU_CAPS_INCOMING_DEFER)) { inc->deferredURI = inc->launchURI; if (VIR_STRDUP(inc->launchURI, "defer") < 0) goto error; } inc->fd = fd; inc->path = path; return inc; error: qemuProcessIncomingDefFree(inc); return NULL; } /* * This function starts a new QEMU_ASYNC_JOB_START async job. The user is * responsible for calling qemuProcessEndJob to stop this job and for passing * QEMU_ASYNC_JOB_START as @asyncJob argument to any function requiring this * parameter between qemuProcessBeginJob and qemuProcessEndJob. */ int qemuProcessBeginJob(virQEMUDriverPtr driver, virDomainObjPtr vm) { qemuDomainObjPrivatePtr priv = vm->privateData; if (qemuDomainObjBeginAsyncJob(driver, vm, QEMU_ASYNC_JOB_START) < 0) return -1; qemuDomainObjSetAsyncJobMask(vm, QEMU_JOB_NONE); priv->job.current->type = VIR_DOMAIN_JOB_UNBOUNDED; return 0; } void qemuProcessEndJob(virQEMUDriverPtr driver, virDomainObjPtr vm) { qemuDomainObjEndAsyncJob(driver, vm); } static int qemuProcessStartHook(virQEMUDriverPtr driver, virDomainObjPtr vm, virHookQemuOpType op, virHookSubopType subop) { char *xml; int ret; if (!virHookPresent(VIR_HOOK_DRIVER_QEMU)) return 0; if (!(xml = qemuDomainDefFormatXML(driver, vm->def, 0))) return -1; ret = virHookCall(VIR_HOOK_DRIVER_QEMU, vm->def->name, op, subop, NULL, xml, NULL); VIR_FREE(xml); return ret; } static int qemuProcessGraphicsReservePorts(virQEMUDriverPtr driver, virDomainGraphicsDefPtr graphics) { virDomainGraphicsListenDefPtr glisten; if (graphics->nListens <= 0) return 0; glisten = &graphics->listens[0]; if (glisten->type != VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS && glisten->type != VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NETWORK) return 0; switch (graphics->type) { case VIR_DOMAIN_GRAPHICS_TYPE_VNC: if (!graphics->data.vnc.autoport) { if (virPortAllocatorSetUsed(driver->remotePorts, graphics->data.vnc.port, true) < 0) return -1; graphics->data.vnc.portReserved = true; } if (graphics->data.vnc.websocket > 0 && virPortAllocatorSetUsed(driver->remotePorts, graphics->data.vnc.websocket, true) < 0) return -1; break; case VIR_DOMAIN_GRAPHICS_TYPE_SPICE: if (graphics->data.spice.autoport) return 0; if (graphics->data.spice.port > 0) { if (virPortAllocatorSetUsed(driver->remotePorts, graphics->data.spice.port, true) < 0) return -1; graphics->data.spice.portReserved = true; } if (graphics->data.spice.tlsPort > 0) { if (virPortAllocatorSetUsed(driver->remotePorts, graphics->data.spice.tlsPort, true) < 0) return -1; graphics->data.spice.tlsPortReserved = true; } break; case VIR_DOMAIN_GRAPHICS_TYPE_SDL: case VIR_DOMAIN_GRAPHICS_TYPE_RDP: case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP: case VIR_DOMAIN_GRAPHICS_TYPE_LAST: break; } return 0; } static int qemuProcessGraphicsAllocatePorts(virQEMUDriverPtr driver, virDomainGraphicsDefPtr graphics, bool allocate) { virDomainGraphicsListenDefPtr glisten; if (graphics->nListens <= 0) return 0; glisten = &graphics->listens[0]; if (glisten->type != VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS && glisten->type != VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NETWORK) return 0; switch (graphics->type) { case VIR_DOMAIN_GRAPHICS_TYPE_VNC: if (qemuProcessVNCAllocatePorts(driver, graphics, allocate) < 0) return -1; break; case VIR_DOMAIN_GRAPHICS_TYPE_SPICE: if (qemuProcessSPICEAllocatePorts(driver, graphics, allocate) < 0) return -1; break; case VIR_DOMAIN_GRAPHICS_TYPE_SDL: case VIR_DOMAIN_GRAPHICS_TYPE_RDP: case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP: case VIR_DOMAIN_GRAPHICS_TYPE_LAST: break; } return 0; } static int qemuProcessGraphicsSetupNetworkAddress(virDomainGraphicsListenDefPtr glisten, const char *listenAddr) { int rc; /* TODO: reject configuration without network specified for network listen */ if (!glisten->network) { if (VIR_STRDUP(glisten->address, listenAddr) < 0) return -1; return 0; } rc = networkGetNetworkAddress(glisten->network, &glisten->address); if (rc <= -2) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("network-based listen isn't possible, " "network driver isn't present")); return -1; } if (rc < 0) return -1; return 0; } static int qemuProcessGraphicsSetupListen(virQEMUDriverPtr driver, virDomainGraphicsDefPtr graphics, virDomainObjPtr vm) { qemuDomainObjPrivatePtr priv = vm->privateData; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); const char *type = virDomainGraphicsTypeToString(graphics->type); char *listenAddr = NULL; bool useSocket = false; size_t i; int ret = -1; switch (graphics->type) { case VIR_DOMAIN_GRAPHICS_TYPE_VNC: useSocket = cfg->vncAutoUnixSocket; listenAddr = cfg->vncListen; break; case VIR_DOMAIN_GRAPHICS_TYPE_SPICE: useSocket = cfg->spiceAutoUnixSocket; listenAddr = cfg->spiceListen; break; case VIR_DOMAIN_GRAPHICS_TYPE_SDL: case VIR_DOMAIN_GRAPHICS_TYPE_RDP: case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP: case VIR_DOMAIN_GRAPHICS_TYPE_LAST: break; } for (i = 0; i < graphics->nListens; i++) { virDomainGraphicsListenDefPtr glisten = &graphics->listens[i]; switch (glisten->type) { case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_ADDRESS: if (!glisten->address) { /* If there is no address specified and qemu.conf has * *_auto_unix_socket set we should use unix socket as * default instead of tcp listen. */ if (useSocket) { memset(glisten, 0, sizeof(virDomainGraphicsListenDef)); if (virAsprintf(&glisten->socket, "%s/%s.sock", priv->libDir, type) < 0) goto cleanup; glisten->fromConfig = true; glisten->type = VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_SOCKET; } else if (listenAddr) { if (VIR_STRDUP(glisten->address, listenAddr) < 0) goto cleanup; glisten->fromConfig = true; } } break; case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NETWORK: if (glisten->address || !listenAddr) continue; if (qemuProcessGraphicsSetupNetworkAddress(glisten, listenAddr) < 0) goto cleanup; break; case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_SOCKET: if (!glisten->socket) { if (virAsprintf(&glisten->socket, "%s/%s.sock", priv->libDir, type) < 0) goto cleanup; glisten->autoGenerated = true; } break; case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_NONE: case VIR_DOMAIN_GRAPHICS_LISTEN_TYPE_LAST: break; } } ret = 0; cleanup: virObjectUnref(cfg); return ret; } static int qemuProcessSetupGraphics(virQEMUDriverPtr driver, virDomainObjPtr vm, unsigned int flags) { virDomainGraphicsDefPtr graphics; bool allocate = !(flags & VIR_QEMU_PROCESS_START_PRETEND); size_t i; int ret = -1; for (i = 0; i < vm->def->ngraphics; i++) { graphics = vm->def->graphics[i]; if (qemuProcessGraphicsSetupListen(driver, graphics, vm) < 0) goto cleanup; } if (allocate) { for (i = 0; i < vm->def->ngraphics; i++) { graphics = vm->def->graphics[i]; if (qemuProcessGraphicsReservePorts(driver, graphics) < 0) goto cleanup; } } for (i = 0; i < vm->def->ngraphics; ++i) { graphics = vm->def->graphics[i]; if (qemuProcessGraphicsAllocatePorts(driver, graphics, allocate) < 0) goto cleanup; } ret = 0; cleanup: return ret; } static int qemuProcessSetupRawIO(virQEMUDriverPtr driver, virDomainObjPtr vm, virCommandPtr cmd ATTRIBUTE_UNUSED) { bool rawio = false; size_t i; int ret = -1; /* in case a certain disk is desirous of CAP_SYS_RAWIO, add this */ for (i = 0; i < vm->def->ndisks; i++) { virDomainDeviceDef dev; virDomainDiskDefPtr disk = vm->def->disks[i]; if (disk->rawio == VIR_TRISTATE_BOOL_YES) { rawio = true; #ifndef CAP_SYS_RAWIO break; #endif } dev.type = VIR_DOMAIN_DEVICE_DISK; dev.data.disk = disk; if (qemuAddSharedDevice(driver, &dev, vm->def->name) < 0) goto cleanup; if (qemuSetUnprivSGIO(&dev) < 0) goto cleanup; } /* If rawio not already set, check hostdevs as well */ if (!rawio) { for (i = 0; i < vm->def->nhostdevs; i++) { if (!virHostdevIsSCSIDevice(vm->def->hostdevs[i])) continue; virDomainHostdevSubsysSCSIPtr scsisrc = &vm->def->hostdevs[i]->source.subsys.u.scsi; if (scsisrc->rawio == VIR_TRISTATE_BOOL_YES) { rawio = true; break; } } } ret = 0; cleanup: if (rawio) { #ifdef CAP_SYS_RAWIO if (ret == 0) virCommandAllowCap(cmd, CAP_SYS_RAWIO); #else virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Raw I/O is not supported on this platform")); ret = -1; #endif } return ret; } static int qemuProcessSetupBalloon(virQEMUDriverPtr driver, virDomainObjPtr vm, qemuDomainAsyncJob asyncJob) { unsigned long long balloon = vm->def->mem.cur_balloon; qemuDomainObjPrivatePtr priv = vm->privateData; int ret = -1; if (!virDomainDefHasMemballoon(vm->def)) return 0; if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) goto cleanup; if (vm->def->memballoon->period) qemuMonitorSetMemoryStatsPeriod(priv->mon, vm->def->memballoon, vm->def->memballoon->period); if (qemuMonitorSetBalloon(priv->mon, balloon) < 0) goto cleanup; ret = 0; cleanup: if (qemuDomainObjExitMonitor(driver, vm) < 0) ret = -1; return ret; } static int qemuProcessMakeDir(virQEMUDriverPtr driver, virDomainObjPtr vm, const char *path) { int ret = -1; if (virFileMakePathWithMode(path, 0750) < 0) { virReportSystemError(errno, _("Cannot create directory '%s'"), path); goto cleanup; } if (virSecurityManagerDomainSetPathLabel(driver->securityManager, vm->def, path) < 0) goto cleanup; ret = 0; cleanup: return ret; } static void qemuProcessStartWarnShmem(virDomainObjPtr vm) { size_t i; bool check_shmem = false; bool shmem = vm->def->nshmems; /* * For vhost-user to work, the domain has to have some type of * shared memory configured. We're not the proper ones to judge * whether shared hugepages or shm are enough and will be in the * future, so we'll just warn in case neither is configured. * Moreover failing would give the false illusion that libvirt is * really checking that everything works before running the domain * and not only we are unable to do that, but it's also not our * aim to do so. */ for (i = 0; i < vm->def->nnets; i++) { if (virDomainNetGetActualType(vm->def->nets[i]) == VIR_DOMAIN_NET_TYPE_VHOSTUSER) { check_shmem = true; break; } } if (!check_shmem) return; /* * This check is by no means complete. We merely check * whether there are *some* hugepages enabled and *some* NUMA * nodes with shared memory access. */ if (!shmem && vm->def->mem.nhugepages) { for (i = 0; i < virDomainNumaGetNodeCount(vm->def->numa); i++) { if (virDomainNumaGetNodeMemoryAccessMode(vm->def->numa, i) == VIR_NUMA_MEM_ACCESS_SHARED) { shmem = true; break; } } } if (!shmem) { VIR_WARN("Detected vhost-user interface without any shared memory, " "the interface might not be operational"); } } static int qemuProcessStartValidateGraphics(virDomainObjPtr vm) { size_t i; for (i = 0; i < vm->def->ngraphics; i++) { virDomainGraphicsDefPtr graphics = vm->def->graphics[i]; switch (graphics->type) { case VIR_DOMAIN_GRAPHICS_TYPE_VNC: case VIR_DOMAIN_GRAPHICS_TYPE_SPICE: if (graphics->nListens > 1) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("QEMU does not support multiple listens for " "one graphics device.")); return -1; } break; case VIR_DOMAIN_GRAPHICS_TYPE_SDL: case VIR_DOMAIN_GRAPHICS_TYPE_RDP: case VIR_DOMAIN_GRAPHICS_TYPE_DESKTOP: case VIR_DOMAIN_GRAPHICS_TYPE_LAST: break; } } return 0; } static int qemuProcessStartValidateVideo(virDomainObjPtr vm, virQEMUCapsPtr qemuCaps) { size_t i; virDomainVideoDefPtr video; for (i = 0; i < vm->def->nvideos; i++) { video = vm->def->videos[i]; if ((video->type == VIR_DOMAIN_VIDEO_TYPE_VGA && !virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VGA)) || (video->type == VIR_DOMAIN_VIDEO_TYPE_CIRRUS && !virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_CIRRUS_VGA)) || (video->type == VIR_DOMAIN_VIDEO_TYPE_VMVGA && !virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VMWARE_SVGA)) || (video->type == VIR_DOMAIN_VIDEO_TYPE_QXL && !virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_QXL)) || (video->type == VIR_DOMAIN_VIDEO_TYPE_VIRTIO && !virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_VIRTIO_GPU))) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("this QEMU does not support '%s' video device"), virDomainVideoTypeToString(video->type)); return -1; } if (video->accel) { if (video->accel->accel3d == VIR_TRISTATE_SWITCH_ON && (video->type != VIR_DOMAIN_VIDEO_TYPE_VIRTIO || !virQEMUCapsGet(qemuCaps, QEMU_CAPS_VIRTIO_GPU_VIRGL))) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("%s 3d acceleration is not supported"), virDomainVideoTypeToString(video->type)); return -1; } } } return 0; } static int qemuProcessStartValidateXML(virQEMUDriverPtr driver, virDomainObjPtr vm, virQEMUCapsPtr qemuCaps, virCapsPtr caps, unsigned int flags) { /* The bits we validate here are XML configs that we previously * accepted. We reject them at VM startup time rather than parse * time so that pre-existing VMs aren't rejected and dropped from * the VM list when libvirt is updated. * * If back compat isn't a concern, XML validation should probably * be done at parse time. */ if (qemuValidateCpuCount(vm->def, qemuCaps) < 0) return -1; /* checks below should not be executed when starting a qemu process for a * VM that was running before (migration, snapshots, save). It's more * important to start such VM than keep the configuration clean */ if ((flags & VIR_QEMU_PROCESS_START_NEW) && virDomainDefValidate(vm->def, caps, 0, driver->xmlopt) < 0) return -1; return 0; } /** * qemuProcessStartValidate: * @vm: domain object * @qemuCaps: emulator capabilities * @migration: restoration of existing state * * This function aggregates checks done prior to start of a VM. * * Flag VIR_QEMU_PROCESS_START_PRETEND tells, that we don't want to actually * start the domain but create a valid qemu command. If some code shouldn't be * executed in this case, make sure to check this flag. */ static int qemuProcessStartValidate(virQEMUDriverPtr driver, virDomainObjPtr vm, virQEMUCapsPtr qemuCaps, virCapsPtr caps, unsigned int flags) { if (!(flags & VIR_QEMU_PROCESS_START_PRETEND)) { if (vm->def->virtType == VIR_DOMAIN_VIRT_KVM) { VIR_DEBUG("Checking for KVM availability"); if (!virFileExists("/dev/kvm")) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("Domain requires KVM, but it is not available. " "Check that virtualization is enabled in the " "host BIOS, and host configuration is setup to " "load the kvm modules.")); return -1; } } VIR_DEBUG("Checking domain and device security labels"); if (virSecurityManagerCheckAllLabel(driver->securityManager, vm->def) < 0) return -1; } if (qemuProcessStartValidateXML(driver, vm, qemuCaps, caps, flags) < 0) return -1; if (qemuProcessStartValidateGraphics(vm) < 0) return -1; if (qemuProcessStartValidateVideo(vm, qemuCaps) < 0) return -1; VIR_DEBUG("Checking for any possible (non-fatal) issues"); qemuProcessStartWarnShmem(vm); return 0; } /** * qemuProcessInit: * * Prepares the domain up to the point when priv->qemuCaps is initialized. The * function calls qemuProcessStop when needed. * * Flag VIR_QEMU_PROCESS_START_PRETEND tells, that we don't want to actually * start the domain but create a valid qemu command. If some code shouldn't be * executed in this case, make sure to check this flag. * * Returns 0 on success, -1 on error. */ int qemuProcessInit(virQEMUDriverPtr driver, virDomainObjPtr vm, qemuDomainAsyncJob asyncJob, bool migration, unsigned int flags) { virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); virCapsPtr caps = NULL; qemuDomainObjPrivatePtr priv = vm->privateData; int stopFlags; int ret = -1; VIR_DEBUG("vm=%p name=%s id=%d migration=%d", vm, vm->def->name, vm->def->id, migration); VIR_DEBUG("Beginning VM startup process"); if (virDomainObjIsActive(vm)) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("VM is already active")); goto cleanup; } if (!(caps = virQEMUDriverGetCapabilities(driver, false))) goto cleanup; VIR_DEBUG("Determining emulator version"); virObjectUnref(priv->qemuCaps); if (!(priv->qemuCaps = virQEMUCapsCacheLookupCopy(caps, driver->qemuCapsCache, vm->def->emulator, vm->def->os.machine))) goto cleanup; if (qemuProcessStartValidate(driver, vm, priv->qemuCaps, caps, flags) < 0) goto cleanup; /* Do this upfront, so any part of the startup process can add * runtime state to vm->def that won't be persisted. This let's us * report implicit runtime defaults in the XML, like vnc listen/socket */ VIR_DEBUG("Setting current domain def as transient"); if (virDomainObjSetDefTransient(caps, driver->xmlopt, vm) < 0) goto stop; if (!(flags & VIR_QEMU_PROCESS_START_PRETEND)) { vm->def->id = qemuDriverAllocateID(driver); qemuDomainSetFakeReboot(driver, vm, false); virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_STARTING_UP); if (virAtomicIntInc(&driver->nactive) == 1 && driver->inhibitCallback) driver->inhibitCallback(true, driver->inhibitOpaque); /* Run an early hook to set-up missing devices */ if (qemuProcessStartHook(driver, vm, VIR_HOOK_QEMU_OP_PREPARE, VIR_HOOK_SUBOP_BEGIN) < 0) goto stop; } if (qemuDomainSetPrivatePaths(driver, vm) < 0) goto cleanup; ret = 0; cleanup: virObjectUnref(cfg); virObjectUnref(caps); return ret; stop: stopFlags = VIR_QEMU_PROCESS_STOP_NO_RELABEL; if (migration) stopFlags |= VIR_QEMU_PROCESS_STOP_MIGRATED; qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED, asyncJob, stopFlags); goto cleanup; } /** * qemuProcessNetworkPrepareDevices */ static int qemuProcessNetworkPrepareDevices(virDomainDefPtr def) { int ret = -1; size_t i; for (i = 0; i < def->nnets; i++) { virDomainNetDefPtr net = def->nets[i]; virDomainNetType actualType; /* If appropriate, grab a physical device from the configured * network's pool of devices, or resolve bridge device name * to the one defined in the network definition. */ if (networkAllocateActualDevice(def, net) < 0) goto cleanup; actualType = virDomainNetGetActualType(net); if (actualType == VIR_DOMAIN_NET_TYPE_HOSTDEV && net->type == VIR_DOMAIN_NET_TYPE_NETWORK) { /* Each type='hostdev' network device must also have a * corresponding entry in the hostdevs array. For netdevs * that are hardcoded as type='hostdev', this is already * done by the parser, but for those allocated from a * network / determined at runtime, we need to do it * separately. */ virDomainHostdevDefPtr hostdev = virDomainNetGetActualHostdev(net); virDomainHostdevSubsysPCIPtr pcisrc = &hostdev->source.subsys.u.pci; if (virDomainHostdevFind(def, hostdev, NULL) >= 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("PCI device %04x:%02x:%02x.%x " "allocated from network %s is already " "in use by domain %s"), pcisrc->addr.domain, pcisrc->addr.bus, pcisrc->addr.slot, pcisrc->addr.function, net->data.network.name, def->name); goto cleanup; } if (virDomainHostdevInsert(def, hostdev) < 0) goto cleanup; } } ret = 0; cleanup: return ret; } /** * qemuProcessSetupVcpu: * @vm: domain object * @vcpuid: id of VCPU to set defaults * * This function sets resource properties (cgroups, affinity, scheduler) for a * vCPU. This function expects that the vCPU is online and the vCPU pids were * correctly detected at the point when it's called. * * Returns 0 on success, -1 on error. */ int qemuProcessSetupVcpu(virDomainObjPtr vm, unsigned int vcpuid) { pid_t vcpupid = qemuDomainGetVcpuPid(vm, vcpuid); virDomainVcpuDefPtr vcpu = virDomainDefGetVcpu(vm->def, vcpuid); return qemuProcessSetupPid(vm, vcpupid, VIR_CGROUP_THREAD_VCPU, vcpuid, vcpu->cpumask, vm->def->cputune.period, vm->def->cputune.quota, &vcpu->sched); } static int qemuProcessSetupVcpus(virDomainObjPtr vm) { virDomainVcpuDefPtr vcpu; unsigned int maxvcpus = virDomainDefGetVcpusMax(vm->def); size_t i; if ((vm->def->cputune.period || vm->def->cputune.quota) && !virCgroupHasController(((qemuDomainObjPrivatePtr) vm->privateData)->cgroup, VIR_CGROUP_CONTROLLER_CPU)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("cgroup cpu is required for scheduler tuning")); return -1; } if (!qemuDomainHasVcpuPids(vm)) { /* If any CPU has custom affinity that differs from the * VM default affinity, we must reject it */ for (i = 0; i < maxvcpus; i++) { vcpu = virDomainDefGetVcpu(vm->def, i); if (!vcpu->online) continue; if (vcpu->cpumask && !virBitmapEqual(vm->def->cpumask, vcpu->cpumask)) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("cpu affinity is not supported")); return -1; } } return 0; } for (i = 0; i < maxvcpus; i++) { vcpu = virDomainDefGetVcpu(vm->def, i); if (!vcpu->online) continue; if (qemuProcessSetupVcpu(vm, i) < 0) return -1; } return 0; } int qemuProcessSetupIOThread(virDomainObjPtr vm, virDomainIOThreadIDDefPtr iothread) { return qemuProcessSetupPid(vm, iothread->thread_id, VIR_CGROUP_THREAD_IOTHREAD, iothread->iothread_id, iothread->cpumask, vm->def->cputune.iothread_period, vm->def->cputune.iothread_quota, &iothread->sched); } static int qemuProcessSetupIOThreads(virDomainObjPtr vm) { size_t i; for (i = 0; i < vm->def->niothreadids; i++) { virDomainIOThreadIDDefPtr info = vm->def->iothreadids[i]; if (qemuProcessSetupIOThread(vm, info) < 0) return -1; } return 0; } static int qemuProcessValidateHotpluggableVcpus(virDomainDefPtr def) { virDomainVcpuDefPtr vcpu; virDomainVcpuDefPtr subvcpu; qemuDomainVcpuPrivatePtr vcpupriv; unsigned int maxvcpus = virDomainDefGetVcpusMax(def); size_t i = 0; size_t j; virBitmapPtr ordermap = NULL; int ret = -1; if (!(ordermap = virBitmapNew(maxvcpus + 1))) goto cleanup; /* validate: * - all hotpluggable entities to be hotplugged have the correct data * - vcpus belonging to a hotpluggable entity share configuration * - order of the hotpluggable entities is unique */ for (i = 0; i < maxvcpus; i++) { vcpu = virDomainDefGetVcpu(def, i); vcpupriv = QEMU_DOMAIN_VCPU_PRIVATE(vcpu); /* skip over hotpluggable entities */ if (vcpupriv->vcpus == 0) continue; if (vcpu->order != 0) { if (virBitmapIsBitSet(ordermap, vcpu->order)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("duplicate vcpu order '%u'"), vcpu->order); goto cleanup; } if (virBitmapSetBit(ordermap, vcpu->order)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("vcpu order '%u' exceeds vcpu count"), vcpu->order); goto cleanup; } } for (j = i + 1; j < (i + vcpupriv->vcpus); j++) { subvcpu = virDomainDefGetVcpu(def, j); if (subvcpu->hotpluggable != vcpu->hotpluggable || subvcpu->online != vcpu->online || subvcpu->order != vcpu->order) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("vcpus '%zu' and '%zu' are in the same hotplug " "group but differ in configuration"), i, j); goto cleanup; } } if (vcpu->online && vcpu->hotpluggable == VIR_TRISTATE_BOOL_YES) { if ((vcpupriv->socket_id == -1 && vcpupriv->core_id == -1 && vcpupriv->thread_id == -1) || !vcpupriv->type) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("vcpu '%zu' is missing hotplug data"), i); goto cleanup; } } } ret = 0; cleanup: virBitmapFree(ordermap); return ret; } static int qemuDomainHasHotpluggableStartupVcpus(virDomainDefPtr def) { size_t maxvcpus = virDomainDefGetVcpusMax(def); virDomainVcpuDefPtr vcpu; size_t i; for (i = 0; i < maxvcpus; i++) { vcpu = virDomainDefGetVcpu(def, i); if (vcpu->online && vcpu->hotpluggable == VIR_TRISTATE_BOOL_YES) return true; } return false; } static int qemuProcessVcpusSortOrder(const void *a, const void *b) { virDomainVcpuDefPtr vcpua = *((virDomainVcpuDefPtr *)a); virDomainVcpuDefPtr vcpub = *((virDomainVcpuDefPtr *)b); return vcpua->order - vcpub->order; } static int qemuProcessSetupHotpluggableVcpus(virQEMUDriverPtr driver, virDomainObjPtr vm, qemuDomainAsyncJob asyncJob) { unsigned int maxvcpus = virDomainDefGetVcpusMax(vm->def); qemuDomainObjPrivatePtr priv = vm->privateData; qemuCgroupEmulatorAllNodesDataPtr emulatorCgroup = NULL; virDomainVcpuDefPtr vcpu; qemuDomainVcpuPrivatePtr vcpupriv; virJSONValuePtr vcpuprops = NULL; size_t i; int ret = -1; int rc; virDomainVcpuDefPtr *bootHotplug = NULL; size_t nbootHotplug = 0; for (i = 0; i < maxvcpus; i++) { vcpu = virDomainDefGetVcpu(vm->def, i); vcpupriv = QEMU_DOMAIN_VCPU_PRIVATE(vcpu); if (vcpu->hotpluggable == VIR_TRISTATE_BOOL_YES && vcpu->online && vcpupriv->vcpus != 0) { if (virAsprintf(&vcpupriv->alias, "vcpu%zu", i) < 0) goto cleanup; if (VIR_APPEND_ELEMENT(bootHotplug, nbootHotplug, vcpu) < 0) goto cleanup; } } if (nbootHotplug == 0) { ret = 0; goto cleanup; } qsort(bootHotplug, nbootHotplug, sizeof(*bootHotplug), qemuProcessVcpusSortOrder); if (qemuCgroupEmulatorAllNodesAllow(priv->cgroup, &emulatorCgroup) < 0) goto cleanup; for (i = 0; i < nbootHotplug; i++) { vcpu = bootHotplug[i]; if (!(vcpuprops = qemuBuildHotpluggableCPUProps(vcpu))) goto cleanup; if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) goto cleanup; rc = qemuMonitorAddDeviceArgs(qemuDomainGetMonitor(vm), vcpuprops); vcpuprops = NULL; if (qemuDomainObjExitMonitor(driver, vm) < 0) goto cleanup; if (rc < 0) goto cleanup; virJSONValueFree(vcpuprops); } ret = 0; cleanup: qemuCgroupEmulatorAllNodesRestore(emulatorCgroup); VIR_FREE(bootHotplug); virJSONValueFree(vcpuprops); return ret; } static int qemuProcessUpdateGuestCPU(virDomainDefPtr def, virQEMUCapsPtr qemuCaps, virCapsPtr caps, unsigned int flags) { int ret = -1; size_t nmodels = 0; char **models = NULL; if (!def->cpu) return 0; /* nothing to do if only topology part of CPU def is used */ if (def->cpu->mode == VIR_CPU_MODE_CUSTOM && !def->cpu->model) return 0; /* Old libvirt added host CPU model to host-model CPUs for migrations, * while new libvirt just turns host-model into custom mode. We need * to fix the mode to maintain backward compatibility and to avoid * the CPU model to be replaced in virCPUUpdate. */ if (!(flags & VIR_QEMU_PROCESS_START_NEW) && ARCH_IS_X86(def->os.arch) && def->cpu->mode == VIR_CPU_MODE_HOST_MODEL && def->cpu->model) { def->cpu->mode = VIR_CPU_MODE_CUSTOM; } if (!virQEMUCapsIsCPUModeSupported(qemuCaps, caps, def->virtType, def->cpu->mode)) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("CPU mode '%s' for %s %s domain on %s host is not " "supported by hypervisor"), virCPUModeTypeToString(def->cpu->mode), virArchToString(def->os.arch), virDomainVirtTypeToString(def->virtType), virArchToString(caps->host.arch)); return -1; } if (virCPUConvertLegacy(caps->host.arch, def->cpu) < 0) return -1; /* nothing to update for host-passthrough */ if (def->cpu->mode == VIR_CPU_MODE_HOST_PASSTHROUGH) return 0; /* custom CPUs in TCG mode don't need to be compared to host CPU */ if (def->virtType != VIR_DOMAIN_VIRT_QEMU || def->cpu->mode != VIR_CPU_MODE_CUSTOM) { if (virCPUCompare(caps->host.arch, virQEMUCapsGetHostModel(qemuCaps), def->cpu, true) < 0) return -1; } if (virCPUUpdate(def->os.arch, def->cpu, virQEMUCapsGetHostModel(qemuCaps)) < 0) goto cleanup; if (virQEMUCapsGetCPUDefinitions(qemuCaps, def->virtType, &models, &nmodels) < 0 || virCPUTranslate(def->os.arch, def->cpu, (const char **) models, nmodels) < 0) goto cleanup; def->cpu->fallback = VIR_CPU_FALLBACK_FORBID; ret = 0; cleanup: virStringListFreeCount(models, nmodels); return ret; } /** * qemuProcessPrepareDomain * * This function groups all code that modifies only live XML of a domain which * is about to start and it's the only place to do those modifications. * * Flag VIR_QEMU_PROCESS_START_PRETEND tells, that we don't want to actually * start the domain but create a valid qemu command. If some code shouldn't be * executed in this case, make sure to check this flag. * * TODO: move all XML modification from qemuBuildCommandLine into this function */ int qemuProcessPrepareDomain(virConnectPtr conn, virQEMUDriverPtr driver, virDomainObjPtr vm, unsigned int flags) { int ret = -1; size_t i; char *nodeset = NULL; qemuDomainObjPrivatePtr priv = vm->privateData; virCapsPtr caps; if (!(caps = virQEMUDriverGetCapabilities(driver, false))) goto cleanup; if (!(flags & VIR_QEMU_PROCESS_START_PRETEND)) { /* If you are using a SecurityDriver with dynamic labelling, then generate a security label for isolation */ VIR_DEBUG("Generating domain security label (if required)"); if (virSecurityManagerGenLabel(driver->securityManager, vm->def) < 0) { virDomainAuditSecurityLabel(vm, false); goto cleanup; } virDomainAuditSecurityLabel(vm, true); /* Get the advisory nodeset from numad if 'placement' of * either or is 'auto'. */ if (virDomainDefNeedsPlacementAdvice(vm->def)) { nodeset = virNumaGetAutoPlacementAdvice(virDomainDefGetVcpus(vm->def), virDomainDefGetMemoryTotal(vm->def)); if (!nodeset) goto cleanup; VIR_DEBUG("Nodeset returned from numad: %s", nodeset); if (virBitmapParse(nodeset, &priv->autoNodeset, VIR_DOMAIN_CPUMASK_LEN) < 0) goto cleanup; if (!(priv->autoCpuset = virCapabilitiesGetCpusForNodemask(caps, priv->autoNodeset))) goto cleanup; } } /* * Normally PCI addresses are assigned in the virDomainCreate * or virDomainDefine methods. We might still need to assign * some here to cope with the question of upgrades. Regardless * we also need to populate the PCI address set cache for later * use in hotplug */ VIR_DEBUG("Assigning domain PCI addresses"); if ((qemuDomainAssignAddresses(vm->def, priv->qemuCaps, driver, vm, !!(flags & VIR_QEMU_PROCESS_START_NEW))) < 0) { goto cleanup; } if (qemuAssignDeviceAliases(vm->def, priv->qemuCaps) < 0) goto cleanup; VIR_DEBUG("Setting graphics devices"); if (qemuProcessSetupGraphics(driver, vm, flags) < 0) goto cleanup; /* Drop possibly missing disks from the definition. This function * also resolves source pool/volume into a path and it needs to * happen after the def is copied and aliases are set. */ if (qemuDomainCheckDiskPresence(conn, driver, vm, flags) < 0) goto cleanup; VIR_DEBUG("Create domain masterKey"); if (qemuDomainMasterKeyCreate(vm) < 0) goto cleanup; VIR_DEBUG("Prepare chardev source backends for TLS"); qemuDomainPrepareChardevSource(vm->def, driver); VIR_DEBUG("Add secrets to disks, hostdevs, and chardevs"); if (qemuDomainSecretPrepare(conn, driver, vm) < 0) goto cleanup; for (i = 0; i < vm->def->nchannels; i++) { if (qemuDomainPrepareChannel(vm->def->channels[i], priv->channelTargetDir) < 0) goto cleanup; } if (VIR_ALLOC(priv->monConfig) < 0) goto cleanup; VIR_DEBUG("Preparing monitor state"); if (qemuProcessPrepareMonitorChr(priv->monConfig, priv->libDir) < 0) goto cleanup; priv->monJSON = virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_MONITOR_JSON); priv->monError = false; priv->monStart = 0; priv->gotShutdown = false; VIR_DEBUG("Updating guest CPU definition"); if (qemuProcessUpdateGuestCPU(vm->def, priv->qemuCaps, caps, flags) < 0) goto cleanup; ret = 0; cleanup: VIR_FREE(nodeset); virObjectUnref(caps); return ret; } /** * qemuProcessPrepareHost * * This function groups all code that modifies host system (which also may * update live XML) to prepare environment for a domain which is about to start * and it's the only place to do those modifications. * * TODO: move all host modification from qemuBuildCommandLine into this function */ int qemuProcessPrepareHost(virQEMUDriverPtr driver, virDomainObjPtr vm, bool incoming) { int ret = -1; unsigned int hostdev_flags = 0; qemuDomainObjPrivatePtr priv = vm->privateData; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); if (qemuPrepareNVRAM(cfg, vm) < 0) goto cleanup; /* network devices must be "prepared" before hostdevs, because * setting up a network device might create a new hostdev that * will need to be setup. */ VIR_DEBUG("Preparing network devices"); if (qemuProcessNetworkPrepareDevices(vm->def) < 0) goto cleanup; /* Must be run before security labelling */ VIR_DEBUG("Preparing host devices"); if (!cfg->relaxedACS) hostdev_flags |= VIR_HOSTDEV_STRICT_ACS_CHECK; if (!incoming) hostdev_flags |= VIR_HOSTDEV_COLD_BOOT; if (qemuHostdevPrepareDomainDevices(driver, vm->def, priv->qemuCaps, hostdev_flags) < 0) goto cleanup; VIR_DEBUG("Preparing chr devices"); if (virDomainChrDefForeach(vm->def, true, qemuProcessPrepareChardevDevice, NULL) < 0) goto cleanup; if (qemuProcessBuildDestroyHugepagesPath(driver, vm, true) < 0) goto cleanup; /* Ensure no historical cgroup for this VM is lying around bogus * settings */ VIR_DEBUG("Ensuring no historical cgroup is lying around"); qemuRemoveCgroup(vm); if (virFileMakePath(cfg->logDir) < 0) { virReportSystemError(errno, _("cannot create log directory %s"), cfg->logDir); goto cleanup; } VIR_FREE(priv->pidfile); if (!(priv->pidfile = virPidFileBuildPath(cfg->stateDir, vm->def->name))) { virReportSystemError(errno, "%s", _("Failed to build pidfile path.")); goto cleanup; } if (unlink(priv->pidfile) < 0 && errno != ENOENT) { virReportSystemError(errno, _("Cannot remove stale PID file %s"), priv->pidfile); goto cleanup; } /* * Create all per-domain directories in order to make sure domain * with any possible seclabels can access it. */ if (qemuProcessMakeDir(driver, vm, priv->libDir) < 0 || qemuProcessMakeDir(driver, vm, priv->channelTargetDir) < 0) goto cleanup; VIR_DEBUG("Write domain masterKey"); if (qemuDomainWriteMasterKeyFile(driver, vm) < 0) goto cleanup; ret = 0; cleanup: virObjectUnref(cfg); return ret; } /** * qemuProcessLaunch: * * Launch a new QEMU process with stopped virtual CPUs. * * The caller is supposed to call qemuProcessStop with appropriate * flags in case of failure. * * Returns 0 on success, * -1 on error which happened before devices were labeled and thus * there is no need to restore them, * -2 on error requesting security labels to be restored. */ int qemuProcessLaunch(virConnectPtr conn, virQEMUDriverPtr driver, virDomainObjPtr vm, qemuDomainAsyncJob asyncJob, qemuProcessIncomingDefPtr incoming, virDomainSnapshotObjPtr snapshot, virNetDevVPortProfileOp vmop, unsigned int flags) { int ret = -1; int rv; int logfile = -1; qemuDomainLogContextPtr logCtxt = NULL; qemuDomainObjPrivatePtr priv = vm->privateData; virCommandPtr cmd = NULL; struct qemuProcessHookData hookData; virQEMUDriverConfigPtr cfg; virCapsPtr caps = NULL; size_t nnicindexes = 0; int *nicindexes = NULL; size_t i; VIR_DEBUG("vm=%p name=%s id=%d asyncJob=%d " "incoming.launchURI=%s incoming.deferredURI=%s " "incoming.fd=%d incoming.path=%s " "snapshot=%p vmop=%d flags=0x%x", vm, vm->def->name, vm->def->id, asyncJob, NULLSTR(incoming ? incoming->launchURI : NULL), NULLSTR(incoming ? incoming->deferredURI : NULL), incoming ? incoming->fd : -1, NULLSTR(incoming ? incoming->path : NULL), snapshot, vmop, flags); /* Okay, these are just internal flags, * but doesn't hurt to check */ virCheckFlags(VIR_QEMU_PROCESS_START_COLD | VIR_QEMU_PROCESS_START_PAUSED | VIR_QEMU_PROCESS_START_AUTODESTROY | VIR_QEMU_PROCESS_START_NEW, -1); cfg = virQEMUDriverGetConfig(driver); hookData.conn = conn; hookData.vm = vm; hookData.driver = driver; /* We don't increase cfg's reference counter here. */ hookData.cfg = cfg; if (!(caps = virQEMUDriverGetCapabilities(driver, false))) goto cleanup; VIR_DEBUG("Creating domain log file"); if (!(logCtxt = qemuDomainLogContextNew(driver, vm, QEMU_DOMAIN_LOG_CONTEXT_MODE_START))) goto cleanup; logfile = qemuDomainLogContextGetWriteFD(logCtxt); VIR_DEBUG("Building emulator command line"); if (!(cmd = qemuBuildCommandLine(driver, qemuDomainLogContextGetManager(logCtxt), vm->def, priv->monConfig, priv->monJSON, priv->qemuCaps, incoming ? incoming->launchURI : NULL, snapshot, vmop, false, qemuCheckFips(), priv->autoNodeset, &nnicindexes, &nicindexes, priv->libDir))) goto cleanup; if (incoming && incoming->fd != -1) virCommandPassFD(cmd, incoming->fd, 0); /* now that we know it is about to start call the hook if present */ if (qemuProcessStartHook(driver, vm, VIR_HOOK_QEMU_OP_START, VIR_HOOK_SUBOP_BEGIN) < 0) goto cleanup; qemuLogOperation(vm, "starting up", cmd, logCtxt); qemuDomainObjCheckTaint(driver, vm, logCtxt); qemuDomainLogContextMarkPosition(logCtxt); VIR_DEBUG("Building mount namespace"); if (qemuDomainCreateNamespace(driver, vm) < 0) goto cleanup; VIR_DEBUG("Clear emulator capabilities: %d", cfg->clearEmulatorCapabilities); if (cfg->clearEmulatorCapabilities) virCommandClearCaps(cmd); VIR_DEBUG("Setting up raw IO"); if (qemuProcessSetupRawIO(driver, vm, cmd) < 0) goto cleanup; virCommandSetPreExecHook(cmd, qemuProcessHook, &hookData); virCommandSetMaxProcesses(cmd, cfg->maxProcesses); virCommandSetMaxFiles(cmd, cfg->maxFiles); virCommandSetMaxCoreSize(cmd, cfg->maxCore); virCommandSetUmask(cmd, 0x002); VIR_DEBUG("Setting up security labelling"); if (virSecurityManagerSetChildProcessLabel(driver->securityManager, vm->def, cmd) < 0) goto cleanup; virCommandSetOutputFD(cmd, &logfile); virCommandSetErrorFD(cmd, &logfile); virCommandNonblockingFDs(cmd); virCommandSetPidFile(cmd, priv->pidfile); virCommandDaemonize(cmd); virCommandRequireHandshake(cmd); if (virSecurityManagerPreFork(driver->securityManager) < 0) goto cleanup; rv = virCommandRun(cmd, NULL); virSecurityManagerPostFork(driver->securityManager); /* wait for qemu process to show up */ if (rv == 0) { if (virPidFileReadPath(priv->pidfile, &vm->pid) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Domain %s didn't show up"), vm->def->name); rv = -1; } VIR_DEBUG("QEMU vm=%p name=%s running with pid=%lld", vm, vm->def->name, (long long) vm->pid); } else { VIR_DEBUG("QEMU vm=%p name=%s failed to spawn", vm, vm->def->name); } VIR_DEBUG("Writing early domain status to disk"); if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm, driver->caps) < 0) goto cleanup; VIR_DEBUG("Waiting for handshake from child"); if (virCommandHandshakeWait(cmd) < 0) { /* Read errors from child that occurred between fork and exec. */ qemuProcessReportLogError(logCtxt, _("Process exited prior to exec")); goto cleanup; } VIR_DEBUG("Setting up domain cgroup (if required)"); if (qemuSetupCgroup(driver, vm, nnicindexes, nicindexes) < 0) goto cleanup; if (!(priv->perf = virPerfNew())) goto cleanup; for (i = 0; i < VIR_PERF_EVENT_LAST; i++) { if (vm->def->perf.events[i] == VIR_TRISTATE_BOOL_YES && virPerfEventEnable(priv->perf, i, vm->pid) < 0) goto cleanup; } /* This must be done after cgroup placement to avoid resetting CPU * affinity */ if (!vm->def->cputune.emulatorpin && qemuProcessInitCpuAffinity(vm) < 0) goto cleanup; VIR_DEBUG("Setting emulator tuning/settings"); if (qemuProcessSetupEmulator(vm) < 0) goto cleanup; VIR_DEBUG("Setting domain security labels"); if (qemuSecuritySetAllLabel(driver, vm, incoming ? incoming->path : NULL) < 0) goto cleanup; /* Security manager labeled all devices, therefore * if any operation from now on fails, we need to ask the caller to * restore labels. */ ret = -2; if (incoming && incoming->fd != -1) { /* if there's an fd to migrate from, and it's a pipe, put the * proper security label on it */ struct stat stdin_sb; VIR_DEBUG("setting security label on pipe used for migration"); if (fstat(incoming->fd, &stdin_sb) < 0) { virReportSystemError(errno, _("cannot stat fd %d"), incoming->fd); goto cleanup; } if (S_ISFIFO(stdin_sb.st_mode) && virSecurityManagerSetImageFDLabel(driver->securityManager, vm->def, incoming->fd) < 0) goto cleanup; } VIR_DEBUG("Labelling done, completing handshake to child"); if (virCommandHandshakeNotify(cmd) < 0) goto cleanup; VIR_DEBUG("Handshake complete, child running"); if (rv == -1) /* The VM failed to start; tear filters before taps */ virDomainConfVMNWFilterTeardown(vm); if (rv == -1) /* The VM failed to start */ goto cleanup; VIR_DEBUG("Waiting for monitor to show up"); if (qemuProcessWaitForMonitor(driver, vm, asyncJob, priv->qemuCaps, logCtxt) < 0) goto cleanup; if (qemuConnectAgent(driver, vm) < 0) goto cleanup; VIR_DEBUG("Detecting if required emulator features are present"); if (!qemuProcessVerifyGuestCPU(driver, vm, asyncJob)) goto cleanup; VIR_DEBUG("Setting up post-init cgroup restrictions"); if (qemuSetupCpusetMems(vm) < 0) goto cleanup; VIR_DEBUG("setting up hotpluggable cpus"); if (qemuDomainHasHotpluggableStartupVcpus(vm->def)) { if (qemuDomainRefreshVcpuInfo(driver, vm, asyncJob, false) < 0) goto cleanup; if (qemuProcessValidateHotpluggableVcpus(vm->def) < 0) goto cleanup; if (qemuProcessSetupHotpluggableVcpus(driver, vm, asyncJob) < 0) goto cleanup; } VIR_DEBUG("Refreshing VCPU info"); if (qemuDomainRefreshVcpuInfo(driver, vm, asyncJob, false) < 0) goto cleanup; if (qemuDomainValidateVcpuInfo(vm) < 0) goto cleanup; qemuDomainVcpuPersistOrder(vm->def); VIR_DEBUG("Detecting IOThread PIDs"); if (qemuProcessDetectIOThreadPIDs(driver, vm, asyncJob) < 0) goto cleanup; VIR_DEBUG("Setting global CPU cgroup (if required)"); if (qemuSetupGlobalCpuCgroup(vm) < 0) goto cleanup; VIR_DEBUG("Setting vCPU tuning/settings"); if (qemuProcessSetupVcpus(vm) < 0) goto cleanup; VIR_DEBUG("Setting IOThread tuning/settings"); if (qemuProcessSetupIOThreads(vm) < 0) goto cleanup; VIR_DEBUG("Setting any required VM passwords"); if (qemuProcessInitPasswords(conn, driver, vm, asyncJob) < 0) goto cleanup; /* set default link states */ /* qemu doesn't support setting this on the command line, so * enter the monitor */ VIR_DEBUG("Setting network link states"); if (qemuProcessSetLinkStates(driver, vm, asyncJob) < 0) goto cleanup; VIR_DEBUG("Fetching list of active devices"); if (qemuDomainUpdateDeviceList(driver, vm, asyncJob) < 0) goto cleanup; VIR_DEBUG("Updating info of memory devices"); if (qemuDomainUpdateMemoryDeviceInfo(driver, vm, asyncJob) < 0) goto cleanup; VIR_DEBUG("Setting initial memory amount"); if (qemuProcessSetupBalloon(driver, vm, asyncJob) < 0) goto cleanup; /* Since CPUs were not started yet, the balloon could not return the memory * to the host and thus cur_balloon needs to be updated so that GetXMLdesc * and friends return the correct size in case they can't grab the job */ if (!incoming && !snapshot && qemuProcessRefreshBalloonState(driver, vm, asyncJob) < 0) goto cleanup; VIR_DEBUG("Detecting actual memory size for video device"); if (qemuProcessUpdateVideoRamSize(driver, vm, asyncJob) < 0) goto cleanup; VIR_DEBUG("Updating disk data"); if (qemuProcessRefreshDisks(driver, vm, asyncJob) < 0) goto cleanup; if (flags & VIR_QEMU_PROCESS_START_AUTODESTROY && qemuProcessAutoDestroyAdd(driver, vm, conn) < 0) goto cleanup; ret = 0; cleanup: qemuDomainSecretDestroy(vm); virCommandFree(cmd); qemuDomainLogContextFree(logCtxt); virObjectUnref(cfg); virObjectUnref(caps); VIR_FREE(nicindexes); return ret; } /** * qemuProcessFinishStartup: * * Finish starting a new domain. */ int qemuProcessFinishStartup(virConnectPtr conn, virQEMUDriverPtr driver, virDomainObjPtr vm, qemuDomainAsyncJob asyncJob, bool startCPUs, virDomainPausedReason pausedReason) { virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); int ret = -1; if (startCPUs) { VIR_DEBUG("Starting domain CPUs"); if (qemuProcessStartCPUs(driver, vm, conn, VIR_DOMAIN_RUNNING_BOOTED, asyncJob) < 0) { if (!virGetLastError()) virReportError(VIR_ERR_OPERATION_FAILED, "%s", _("resume operation failed")); goto cleanup; } } else { virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, pausedReason); } VIR_DEBUG("Writing domain status to disk"); if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm, driver->caps) < 0) goto cleanup; if (qemuProcessStartHook(driver, vm, VIR_HOOK_QEMU_OP_STARTED, VIR_HOOK_SUBOP_BEGIN) < 0) goto cleanup; ret = 0; cleanup: virObjectUnref(cfg); return ret; } int qemuProcessStart(virConnectPtr conn, virQEMUDriverPtr driver, virDomainObjPtr vm, qemuDomainAsyncJob asyncJob, const char *migrateFrom, int migrateFd, const char *migratePath, virDomainSnapshotObjPtr snapshot, virNetDevVPortProfileOp vmop, unsigned int flags) { qemuDomainObjPrivatePtr priv = vm->privateData; qemuProcessIncomingDefPtr incoming = NULL; unsigned int stopFlags; bool relabel = false; int ret = -1; int rv; VIR_DEBUG("conn=%p driver=%p vm=%p name=%s id=%d asyncJob=%s " "migrateFrom=%s migrateFd=%d migratePath=%s " "snapshot=%p vmop=%d flags=0x%x", conn, driver, vm, vm->def->name, vm->def->id, qemuDomainAsyncJobTypeToString(asyncJob), NULLSTR(migrateFrom), migrateFd, NULLSTR(migratePath), snapshot, vmop, flags); virCheckFlagsGoto(VIR_QEMU_PROCESS_START_COLD | VIR_QEMU_PROCESS_START_PAUSED | VIR_QEMU_PROCESS_START_AUTODESTROY, cleanup); if (!migrateFrom && !snapshot) flags |= VIR_QEMU_PROCESS_START_NEW; if (qemuProcessInit(driver, vm, asyncJob, !!migrateFrom, flags) < 0) goto cleanup; if (migrateFrom) { incoming = qemuProcessIncomingDefNew(priv->qemuCaps, NULL, migrateFrom, migrateFd, migratePath); if (!incoming) goto stop; } if (qemuProcessPrepareDomain(conn, driver, vm, flags) < 0) goto stop; if (qemuProcessPrepareHost(driver, vm, !!incoming) < 0) goto stop; if ((rv = qemuProcessLaunch(conn, driver, vm, asyncJob, incoming, snapshot, vmop, flags)) < 0) { if (rv == -2) relabel = true; goto stop; } relabel = true; if (incoming && incoming->deferredURI && qemuMigrationRunIncoming(driver, vm, incoming->deferredURI, asyncJob) < 0) goto stop; if (qemuProcessFinishStartup(conn, driver, vm, asyncJob, !(flags & VIR_QEMU_PROCESS_START_PAUSED), incoming ? VIR_DOMAIN_PAUSED_MIGRATION : VIR_DOMAIN_PAUSED_USER) < 0) goto stop; /* Keep watching qemu log for errors during incoming migration, otherwise * unset reporting errors from qemu log. */ if (!incoming) qemuMonitorSetDomainLog(priv->mon, NULL, NULL, NULL); ret = 0; cleanup: qemuProcessIncomingDefFree(incoming); return ret; stop: stopFlags = 0; if (!relabel) stopFlags |= VIR_QEMU_PROCESS_STOP_NO_RELABEL; if (migrateFrom) stopFlags |= VIR_QEMU_PROCESS_STOP_MIGRATED; if (priv->mon) qemuMonitorSetDomainLog(priv->mon, NULL, NULL, NULL); qemuProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_FAILED, asyncJob, stopFlags); goto cleanup; } virCommandPtr qemuProcessCreatePretendCmd(virConnectPtr conn, virQEMUDriverPtr driver, virDomainObjPtr vm, const char *migrateURI, bool enableFips, bool standalone, unsigned int flags) { qemuDomainObjPrivatePtr priv = vm->privateData; virCommandPtr cmd = NULL; virCheckFlagsGoto(VIR_QEMU_PROCESS_START_COLD | VIR_QEMU_PROCESS_START_PAUSED | VIR_QEMU_PROCESS_START_AUTODESTROY, cleanup); flags |= VIR_QEMU_PROCESS_START_PRETEND; flags |= VIR_QEMU_PROCESS_START_NEW; if (qemuProcessInit(driver, vm, QEMU_ASYNC_JOB_NONE, !!migrateURI, flags) < 0) goto cleanup; if (qemuProcessPrepareDomain(conn, driver, vm, flags) < 0) goto cleanup; VIR_DEBUG("Building emulator command line"); cmd = qemuBuildCommandLine(driver, NULL, vm->def, priv->monConfig, priv->monJSON, priv->qemuCaps, migrateURI, NULL, VIR_NETDEV_VPORT_PROFILE_OP_NO_OP, standalone, enableFips, priv->autoNodeset, NULL, NULL, priv->libDir); cleanup: return cmd; } int qemuProcessKill(virDomainObjPtr vm, unsigned int flags) { int ret; VIR_DEBUG("vm=%p name=%s pid=%lld flags=%x", vm, vm->def->name, (long long) vm->pid, flags); if (!(flags & VIR_QEMU_PROCESS_KILL_NOCHECK)) { if (!virDomainObjIsActive(vm)) { VIR_DEBUG("VM '%s' not active", vm->def->name); return 0; } } if (flags & VIR_QEMU_PROCESS_KILL_NOWAIT) { virProcessKill(vm->pid, (flags & VIR_QEMU_PROCESS_KILL_FORCE) ? SIGKILL : SIGTERM); return 0; } ret = virProcessKillPainfully(vm->pid, !!(flags & VIR_QEMU_PROCESS_KILL_FORCE)); return ret; } /** * qemuProcessBeginStopJob: * * Stop all current jobs by killing the domain and start a new one for * qemuProcessStop. */ int qemuProcessBeginStopJob(virQEMUDriverPtr driver, virDomainObjPtr vm, qemuDomainJob job, bool forceKill) { qemuDomainObjPrivatePtr priv = vm->privateData; unsigned int killFlags = forceKill ? VIR_QEMU_PROCESS_KILL_FORCE : 0; int ret = -1; /* We need to prevent monitor EOF callback from doing our work (and * sending misleading events) while the vm is unlocked inside * BeginJob/ProcessKill API */ priv->beingDestroyed = true; if (qemuProcessKill(vm, killFlags) < 0) goto cleanup; /* Wake up anything waiting on domain condition */ virDomainObjBroadcast(vm); if (qemuDomainObjBeginJob(driver, vm, job) < 0) goto cleanup; ret = 0; cleanup: priv->beingDestroyed = false; return ret; } void qemuProcessStop(virQEMUDriverPtr driver, virDomainObjPtr vm, virDomainShutoffReason reason, qemuDomainAsyncJob asyncJob, unsigned int flags) { int ret; int retries = 0; qemuDomainObjPrivatePtr priv = vm->privateData; virErrorPtr orig_err; virDomainDefPtr def; virNetDevVPortProfilePtr vport = NULL; size_t i; char *timestamp; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); VIR_DEBUG("Shutting down vm=%p name=%s id=%d pid=%lld, " "reason=%s, asyncJob=%s, flags=%x", vm, vm->def->name, vm->def->id, (long long) vm->pid, virDomainShutoffReasonTypeToString(reason), qemuDomainAsyncJobTypeToString(asyncJob), flags); /* This method is routinely used in clean up paths. Disable error * reporting so we don't squash a legit error. */ orig_err = virSaveLastError(); if (asyncJob != QEMU_ASYNC_JOB_NONE) { if (qemuDomainObjBeginNestedJob(driver, vm, asyncJob) < 0) goto cleanup; } else if (priv->job.asyncJob != QEMU_ASYNC_JOB_NONE && priv->job.asyncOwner == virThreadSelfID() && priv->job.active != QEMU_JOB_ASYNC_NESTED) { VIR_WARN("qemuProcessStop called without a nested job (async=%s)", qemuDomainAsyncJobTypeToString(asyncJob)); } if (!virDomainObjIsActive(vm)) { VIR_DEBUG("VM '%s' not active", vm->def->name); goto endjob; } qemuProcessBuildDestroyHugepagesPath(driver, vm, false); vm->def->id = -1; if (virAtomicIntDecAndTest(&driver->nactive) && driver->inhibitCallback) driver->inhibitCallback(false, driver->inhibitOpaque); /* Wake up anything waiting on domain condition */ virDomainObjBroadcast(vm); if ((timestamp = virTimeStringNow()) != NULL) { qemuDomainLogAppendMessage(driver, vm, "%s: shutting down, reason=%s\n", timestamp, virDomainShutoffReasonTypeToString(reason)); VIR_FREE(timestamp); } /* Clear network bandwidth */ virDomainClearNetBandwidth(vm); virDomainConfVMNWFilterTeardown(vm); if (cfg->macFilter) { def = vm->def; for (i = 0; i < def->nnets; i++) { virDomainNetDefPtr net = def->nets[i]; if (net->ifname == NULL) continue; ignore_value(ebtablesRemoveForwardAllowIn(driver->ebtables, net->ifname, &net->mac)); } } virPortAllocatorRelease(driver->migrationPorts, priv->nbdPort); priv->nbdPort = 0; if (priv->agent) { qemuAgentClose(priv->agent); priv->agent = NULL; } priv->agentError = false; if (priv->mon) { qemuMonitorClose(priv->mon); priv->mon = NULL; } if (priv->monConfig) { if (priv->monConfig->type == VIR_DOMAIN_CHR_TYPE_UNIX) unlink(priv->monConfig->data.nix.path); virDomainChrSourceDefFree(priv->monConfig); priv->monConfig = NULL; } /* Remove the master key */ qemuDomainMasterKeyRemove(priv); virFileDeleteTree(priv->libDir); virFileDeleteTree(priv->channelTargetDir); qemuDomainClearPrivatePaths(vm); ignore_value(virDomainChrDefForeach(vm->def, false, qemuProcessCleanupChardevDevice, NULL)); /* shut it off for sure */ ignore_value(qemuProcessKill(vm, VIR_QEMU_PROCESS_KILL_FORCE| VIR_QEMU_PROCESS_KILL_NOCHECK)); qemuDomainCleanupRun(driver, vm); /* Stop autodestroy in case guest is restarted */ qemuProcessAutoDestroyRemove(driver, vm); /* now that we know it's stopped call the hook if present */ if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) { char *xml = qemuDomainDefFormatXML(driver, vm->def, 0); /* we can't stop the operation even if the script raised an error */ ignore_value(virHookCall(VIR_HOOK_DRIVER_QEMU, vm->def->name, VIR_HOOK_QEMU_OP_STOPPED, VIR_HOOK_SUBOP_END, NULL, xml, NULL)); VIR_FREE(xml); } /* Reset Security Labels unless caller don't want us to */ if (!(flags & VIR_QEMU_PROCESS_STOP_NO_RELABEL)) qemuSecurityRestoreAllLabel(driver, vm, !!(flags & VIR_QEMU_PROCESS_STOP_MIGRATED)); virSecurityManagerReleaseLabel(driver->securityManager, vm->def); for (i = 0; i < vm->def->ndisks; i++) { virDomainDeviceDef dev; virDomainDiskDefPtr disk = vm->def->disks[i]; dev.type = VIR_DOMAIN_DEVICE_DISK; dev.data.disk = disk; ignore_value(qemuRemoveSharedDevice(driver, &dev, vm->def->name)); } /* Clear out dynamically assigned labels */ for (i = 0; i < vm->def->nseclabels; i++) { if (vm->def->seclabels[i]->type == VIR_DOMAIN_SECLABEL_DYNAMIC) VIR_FREE(vm->def->seclabels[i]->label); VIR_FREE(vm->def->seclabels[i]->imagelabel); } virStringListFree(priv->qemuDevices); priv->qemuDevices = NULL; qemuHostdevReAttachDomainDevices(driver, vm->def); def = vm->def; for (i = 0; i < def->nnets; i++) { virDomainNetDefPtr net = def->nets[i]; vport = virDomainNetGetActualVirtPortProfile(net); switch (virDomainNetGetActualType(net)) { case VIR_DOMAIN_NET_TYPE_DIRECT: ignore_value(virNetDevMacVLanDeleteWithVPortProfile( net->ifname, &net->mac, virDomainNetGetActualDirectDev(net), virDomainNetGetActualDirectMode(net), virDomainNetGetActualVirtPortProfile(net), cfg->stateDir)); break; case VIR_DOMAIN_NET_TYPE_ETHERNET: if (net->ifname) { ignore_value(virNetDevTapDelete(net->ifname, net->backend.tap)); VIR_FREE(net->ifname); } break; case VIR_DOMAIN_NET_TYPE_BRIDGE: case VIR_DOMAIN_NET_TYPE_NETWORK: #ifdef VIR_NETDEV_TAP_REQUIRE_MANUAL_CLEANUP if (!(vport && vport->virtPortType == VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH)) ignore_value(virNetDevTapDelete(net->ifname, net->backend.tap)); #endif break; case VIR_DOMAIN_NET_TYPE_USER: case VIR_DOMAIN_NET_TYPE_VHOSTUSER: case VIR_DOMAIN_NET_TYPE_SERVER: case VIR_DOMAIN_NET_TYPE_CLIENT: case VIR_DOMAIN_NET_TYPE_MCAST: case VIR_DOMAIN_NET_TYPE_INTERNAL: case VIR_DOMAIN_NET_TYPE_HOSTDEV: case VIR_DOMAIN_NET_TYPE_UDP: case VIR_DOMAIN_NET_TYPE_LAST: /* No special cleanup procedure for these types. */ break; } /* release the physical device (or any other resources used by * this interface in the network driver */ if (vport) { if (vport->virtPortType == VIR_NETDEV_VPORT_PROFILE_MIDONET) { ignore_value(virNetDevMidonetUnbindPort(vport)); } else if (vport->virtPortType == VIR_NETDEV_VPORT_PROFILE_OPENVSWITCH) { ignore_value(virNetDevOpenvswitchRemovePort( virDomainNetGetActualBridgeName(net), net->ifname)); } } /* kick the device out of the hostdev list too */ virDomainNetRemoveHostdev(def, net); networkReleaseActualDevice(vm->def, net); } retry: if ((ret = qemuRemoveCgroup(vm)) < 0) { if (ret == -EBUSY && (retries++ < 5)) { usleep(200*1000); goto retry; } VIR_WARN("Failed to remove cgroup for %s", vm->def->name); } virCgroupFree(&priv->cgroup); virPerfFree(priv->perf); priv->perf = NULL; qemuProcessRemoveDomainStatus(driver, vm); /* Remove VNC and Spice ports from port reservation bitmap, but only if they were reserved by the driver (autoport=yes) */ for (i = 0; i < vm->def->ngraphics; ++i) { virDomainGraphicsDefPtr graphics = vm->def->graphics[i]; if (graphics->type == VIR_DOMAIN_GRAPHICS_TYPE_VNC) { if (graphics->data.vnc.autoport) { virPortAllocatorRelease(driver->remotePorts, graphics->data.vnc.port); } else if (graphics->data.vnc.portReserved) { virPortAllocatorSetUsed(driver->remotePorts, graphics->data.spice.port, false); graphics->data.vnc.portReserved = false; } if (graphics->data.vnc.websocketGenerated) { virPortAllocatorRelease(driver->webSocketPorts, graphics->data.vnc.websocket); graphics->data.vnc.websocketGenerated = false; graphics->data.vnc.websocket = -1; } else if (graphics->data.vnc.websocket) { virPortAllocatorSetUsed(driver->remotePorts, graphics->data.vnc.websocket, false); } } if (graphics->type == VIR_DOMAIN_GRAPHICS_TYPE_SPICE) { if (graphics->data.spice.autoport) { virPortAllocatorRelease(driver->remotePorts, graphics->data.spice.port); virPortAllocatorRelease(driver->remotePorts, graphics->data.spice.tlsPort); } else { if (graphics->data.spice.portReserved) { virPortAllocatorSetUsed(driver->remotePorts, graphics->data.spice.port, false); graphics->data.spice.portReserved = false; } if (graphics->data.spice.tlsPortReserved) { virPortAllocatorSetUsed(driver->remotePorts, graphics->data.spice.tlsPort, false); graphics->data.spice.tlsPortReserved = false; } } } } vm->taint = 0; vm->pid = -1; virDomainObjSetState(vm, VIR_DOMAIN_SHUTOFF, reason); for (i = 0; i < vm->def->niothreadids; i++) vm->def->iothreadids[i]->thread_id = 0; virObjectUnref(priv->qemuCaps); priv->qemuCaps = NULL; VIR_FREE(priv->pidfile); /* The "release" hook cleans up additional resources */ if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) { char *xml = qemuDomainDefFormatXML(driver, vm->def, 0); /* we can't stop the operation even if the script raised an error */ virHookCall(VIR_HOOK_DRIVER_QEMU, vm->def->name, VIR_HOOK_QEMU_OP_RELEASE, VIR_HOOK_SUBOP_END, NULL, xml, NULL); VIR_FREE(xml); } virDomainObjRemoveTransientDef(vm); endjob: if (asyncJob != QEMU_ASYNC_JOB_NONE) qemuDomainObjEndJob(driver, vm); cleanup: if (orig_err) { virSetError(orig_err); virFreeError(orig_err); } virObjectUnref(cfg); } int qemuProcessAttach(virConnectPtr conn ATTRIBUTE_UNUSED, virQEMUDriverPtr driver, virDomainObjPtr vm, pid_t pid, const char *pidfile, virDomainChrSourceDefPtr monConfig, bool monJSON) { size_t i; qemuDomainLogContextPtr logCtxt = NULL; char *timestamp; qemuDomainObjPrivatePtr priv = vm->privateData; bool running = true; virDomainPausedReason reason; virSecurityLabelPtr seclabel = NULL; virSecurityLabelDefPtr seclabeldef = NULL; bool seclabelgen = false; virSecurityManagerPtr* sec_managers = NULL; const char *model; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); virCapsPtr caps = NULL; bool active = false; VIR_DEBUG("Beginning VM attach process"); if (virDomainObjIsActive(vm)) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("VM is already active")); virObjectUnref(cfg); return -1; } if (!(caps = virQEMUDriverGetCapabilities(driver, false))) goto error; /* Do this upfront, so any part of the startup process can add * runtime state to vm->def that won't be persisted. This let's us * report implicit runtime defaults in the XML, like vnc listen/socket */ VIR_DEBUG("Setting current domain def as transient"); if (virDomainObjSetDefTransient(caps, driver->xmlopt, vm) < 0) goto error; vm->def->id = qemuDriverAllocateID(driver); if (virAtomicIntInc(&driver->nactive) == 1 && driver->inhibitCallback) driver->inhibitCallback(true, driver->inhibitOpaque); active = true; if (virFileMakePath(cfg->logDir) < 0) { virReportSystemError(errno, _("cannot create log directory %s"), cfg->logDir); goto error; } VIR_FREE(priv->pidfile); if (VIR_STRDUP(priv->pidfile, pidfile) < 0) goto error; vm->pid = pid; VIR_DEBUG("Detect security driver config"); sec_managers = virSecurityManagerGetNested(driver->securityManager); if (sec_managers == NULL) goto error; for (i = 0; sec_managers[i]; i++) { seclabelgen = false; model = virSecurityManagerGetModel(sec_managers[i]); seclabeldef = virDomainDefGetSecurityLabelDef(vm->def, model); if (seclabeldef == NULL) { if (!(seclabeldef = virSecurityLabelDefNew(model))) goto error; seclabelgen = true; } seclabeldef->type = VIR_DOMAIN_SECLABEL_STATIC; if (VIR_ALLOC(seclabel) < 0) goto error; if (virSecurityManagerGetProcessLabel(sec_managers[i], vm->def, vm->pid, seclabel) < 0) goto error; if (VIR_STRDUP(seclabeldef->model, model) < 0) goto error; if (VIR_STRDUP(seclabeldef->label, seclabel->label) < 0) goto error; VIR_FREE(seclabel); if (seclabelgen) { if (VIR_APPEND_ELEMENT(vm->def->seclabels, vm->def->nseclabels, seclabeldef) < 0) goto error; seclabelgen = false; } } if (virSecurityManagerCheckAllLabel(driver->securityManager, vm->def) < 0) goto error; if (virSecurityManagerGenLabel(driver->securityManager, vm->def) < 0) goto error; if (qemuDomainPerfRestart(vm) < 0) goto error; VIR_DEBUG("Creating domain log file"); if (!(logCtxt = qemuDomainLogContextNew(driver, vm, QEMU_DOMAIN_LOG_CONTEXT_MODE_ATTACH))) goto error; VIR_DEBUG("Determining emulator version"); virObjectUnref(priv->qemuCaps); if (!(priv->qemuCaps = virQEMUCapsCacheLookupCopy(caps, driver->qemuCapsCache, vm->def->emulator, vm->def->os.machine))) goto error; VIR_DEBUG("Preparing monitor state"); priv->monConfig = monConfig; monConfig = NULL; priv->monJSON = monJSON; priv->gotShutdown = false; /* * Normally PCI addresses are assigned in the virDomainCreate * or virDomainDefine methods. We might still need to assign * some here to cope with the question of upgrades. Regardless * we also need to populate the PCI address set cache for later * use in hotplug */ VIR_DEBUG("Assigning domain PCI addresses"); if ((qemuDomainAssignAddresses(vm->def, priv->qemuCaps, driver, vm, false)) < 0) { goto error; } if ((timestamp = virTimeStringNow()) == NULL) goto error; qemuDomainLogContextWrite(logCtxt, "%s: attaching\n", timestamp); VIR_FREE(timestamp); qemuDomainObjTaint(driver, vm, VIR_DOMAIN_TAINT_EXTERNAL_LAUNCH, logCtxt); VIR_DEBUG("Waiting for monitor to show up"); if (qemuProcessWaitForMonitor(driver, vm, QEMU_ASYNC_JOB_NONE, priv->qemuCaps, NULL) < 0) goto error; if (qemuConnectAgent(driver, vm) < 0) goto error; VIR_DEBUG("Detecting VCPU PIDs"); if (qemuDomainRefreshVcpuInfo(driver, vm, QEMU_ASYNC_JOB_NONE, false) < 0) goto error; if (qemuDomainValidateVcpuInfo(vm) < 0) goto error; VIR_DEBUG("Detecting IOThread PIDs"); if (qemuProcessDetectIOThreadPIDs(driver, vm, QEMU_ASYNC_JOB_NONE) < 0) goto error; VIR_DEBUG("Getting initial memory amount"); qemuDomainObjEnterMonitor(driver, vm); if (qemuMonitorGetBalloonInfo(priv->mon, &vm->def->mem.cur_balloon) < 0) goto exit_monitor; if (qemuMonitorGetStatus(priv->mon, &running, &reason) < 0) goto exit_monitor; if (qemuMonitorGetVirtType(priv->mon, &vm->def->virtType) < 0) goto exit_monitor; if (qemuDomainObjExitMonitor(driver, vm) < 0) goto error; if (running) { virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, VIR_DOMAIN_RUNNING_UNPAUSED); if (vm->def->memballoon && vm->def->memballoon->model == VIR_DOMAIN_MEMBALLOON_MODEL_VIRTIO && vm->def->memballoon->period) { qemuDomainObjEnterMonitor(driver, vm); qemuMonitorSetMemoryStatsPeriod(priv->mon, vm->def->memballoon, vm->def->memballoon->period); if (qemuDomainObjExitMonitor(driver, vm) < 0) goto error; } } else { virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, reason); } VIR_DEBUG("Writing domain status to disk"); if (virDomainSaveStatus(driver->xmlopt, cfg->stateDir, vm, driver->caps) < 0) goto error; /* Run an hook to allow admins to do some magic */ if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) { char *xml = qemuDomainDefFormatXML(driver, vm->def, 0); int hookret; hookret = virHookCall(VIR_HOOK_DRIVER_QEMU, vm->def->name, VIR_HOOK_QEMU_OP_ATTACH, VIR_HOOK_SUBOP_BEGIN, NULL, xml, NULL); VIR_FREE(xml); /* * If the script raised an error abort the launch */ if (hookret < 0) goto error; } qemuDomainLogContextFree(logCtxt); VIR_FREE(seclabel); VIR_FREE(sec_managers); virObjectUnref(cfg); virObjectUnref(caps); return 0; exit_monitor: ignore_value(qemuDomainObjExitMonitor(driver, vm)); error: /* We jump here if we failed to attach to the VM for any reason. * Leave the domain running, but pretend we never attempted to * attach to it. */ if (active && virAtomicIntDecAndTest(&driver->nactive) && driver->inhibitCallback) driver->inhibitCallback(false, driver->inhibitOpaque); qemuMonitorClose(priv->mon); priv->mon = NULL; qemuDomainLogContextFree(logCtxt); VIR_FREE(seclabel); VIR_FREE(sec_managers); if (seclabelgen) virSecurityLabelDefFree(seclabeldef); virDomainChrSourceDefFree(monConfig); virObjectUnref(cfg); virObjectUnref(caps); return -1; } static virDomainObjPtr qemuProcessAutoDestroy(virDomainObjPtr dom, virConnectPtr conn, void *opaque) { virQEMUDriverPtr driver = opaque; qemuDomainObjPrivatePtr priv = dom->privateData; virObjectEventPtr event = NULL; unsigned int stopFlags = 0; VIR_DEBUG("vm=%s, conn=%p", dom->def->name, conn); virObjectRef(dom); if (priv->job.asyncJob == QEMU_ASYNC_JOB_MIGRATION_IN) stopFlags |= VIR_QEMU_PROCESS_STOP_MIGRATED; if (priv->job.asyncJob) { VIR_DEBUG("vm=%s has long-term job active, cancelling", dom->def->name); qemuDomainObjDiscardAsyncJob(driver, dom); } VIR_DEBUG("Killing domain"); if (qemuProcessBeginStopJob(driver, dom, QEMU_JOB_DESTROY, true) < 0) goto cleanup; qemuProcessStop(driver, dom, VIR_DOMAIN_SHUTOFF_DESTROYED, QEMU_ASYNC_JOB_NONE, stopFlags); virDomainAuditStop(dom, "destroyed"); event = virDomainEventLifecycleNewFromObj(dom, VIR_DOMAIN_EVENT_STOPPED, VIR_DOMAIN_EVENT_STOPPED_DESTROYED); qemuDomainObjEndJob(driver, dom); qemuDomainRemoveInactive(driver, dom); qemuDomainEventQueue(driver, event); cleanup: virDomainObjEndAPI(&dom); return dom; } int qemuProcessAutoDestroyAdd(virQEMUDriverPtr driver, virDomainObjPtr vm, virConnectPtr conn) { VIR_DEBUG("vm=%s, conn=%p", vm->def->name, conn); return virCloseCallbacksSet(driver->closeCallbacks, vm, conn, qemuProcessAutoDestroy); } int qemuProcessAutoDestroyRemove(virQEMUDriverPtr driver, virDomainObjPtr vm) { int ret; VIR_DEBUG("vm=%s", vm->def->name); ret = virCloseCallbacksUnset(driver->closeCallbacks, vm, qemuProcessAutoDestroy); return ret; } bool qemuProcessAutoDestroyActive(virQEMUDriverPtr driver, virDomainObjPtr vm) { virCloseCallback cb; VIR_DEBUG("vm=%s", vm->def->name); cb = virCloseCallbacksGet(driver->closeCallbacks, vm, NULL); return cb == qemuProcessAutoDestroy; } int qemuProcessRefreshDisks(virQEMUDriverPtr driver, virDomainObjPtr vm, qemuDomainAsyncJob asyncJob) { qemuDomainObjPrivatePtr priv = vm->privateData; virHashTablePtr table = NULL; int ret = -1; size_t i; if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) == 0) { table = qemuMonitorGetBlockInfo(priv->mon); if (qemuDomainObjExitMonitor(driver, vm) < 0) goto cleanup; } if (!table) goto cleanup; for (i = 0; i < vm->def->ndisks; i++) { virDomainDiskDefPtr disk = vm->def->disks[i]; qemuDomainDiskPrivatePtr diskpriv = QEMU_DOMAIN_DISK_PRIVATE(disk); struct qemuDomainDiskInfo *info; if (!(info = virHashLookup(table, disk->info.alias))) continue; if (info->removable) { if (info->empty) ignore_value(virDomainDiskSetSource(disk, NULL)); if (info->tray) { if (info->tray_open) disk->tray_status = VIR_DOMAIN_DISK_TRAY_OPEN; else disk->tray_status = VIR_DOMAIN_DISK_TRAY_CLOSED; } } /* fill in additional data */ diskpriv->removable = info->removable; diskpriv->tray = info->tray; } ret = 0; cleanup: virHashFree(table); return ret; }