diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index bf2eadfe79..39e59b99aa 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -62,7 +62,8 @@ VIR_ENUM_IMPL(virDomainTaint, VIR_DOMAIN_TAINT_LAST, "custom-monitor", "high-privileges", "shell-scripts", - "disk-probing"); + "disk-probing", + "external-launch"); VIR_ENUM_IMPL(virDomainVirt, VIR_DOMAIN_VIRT_LAST, "qemu", diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 8262d25e92..172d3c2878 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1272,6 +1272,7 @@ enum virDomainTaintFlags { VIR_DOMAIN_TAINT_HIGH_PRIVILEGES, /* Running with undesirably high privileges */ VIR_DOMAIN_TAINT_SHELL_SCRIPTS, /* Network configuration using opaque shell scripts */ VIR_DOMAIN_TAINT_DISK_PROBING, /* Relying on potentially unsafe disk format probing */ + VIR_DOMAIN_TAINT_EXTERNAL_LAUNCH, /* Externally launched guest domain */ VIR_DOMAIN_TAINT_LAST }; diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 67b35e6868..b6f9317514 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -6202,6 +6202,8 @@ virDomainDefPtr qemuParseCommandLine(virCapsPtr caps, if (!(def->name = strndup(val, process - val))) goto no_memory; } + if (STREQ(def->name, "")) + VIR_FREE(def->name); } else if (STREQ(arg, "-M")) { WANT_VALUE(); if (!(def->os.machine = strdup(val))) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 73833dd2b6..1ff9c25a23 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -4142,7 +4142,7 @@ qemudCanonicalizeMachineFromInfo(virDomainDefPtr def, if (!machine->canonical) continue; - if (STRNEQ(def->os.machine, machine->name)) + if (def->os.machine && STRNEQ(def->os.machine, machine->name)) continue; if (!(*canonical = strdup(machine->canonical))) { @@ -4169,7 +4169,7 @@ qemudCanonicalizeMachineDirect(virDomainDefPtr def, char **canonical) if (!machines[i]->canonical) continue; - if (STRNEQ(def->os.machine, machines[i]->name)) + if (def->os.machine && STRNEQ(def->os.machine, machines[i]->name)) continue; *canonical = machines[i]->canonical; @@ -8359,6 +8359,92 @@ cleanup: } +static virDomainPtr qemuDomainAttach(virConnectPtr conn, + unsigned int pid, + unsigned int flags) +{ + struct qemud_driver *driver = conn->privateData; + virDomainObjPtr vm = NULL; + virDomainDefPtr def = NULL; + virDomainPtr dom = NULL; + virDomainChrSourceDefPtr monConfig = NULL; + bool monJSON = false; + char *pidfile; + + virCheckFlags(0, NULL); + + qemuDriverLock(driver); + + if (!(def = qemuParseCommandLinePid(driver->caps, pid, + &pidfile, &monConfig, &monJSON))) + goto cleanup; + + if (!monConfig) { + qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("No monitor connection for pid %u"), + pid); + goto cleanup; + } + if (monConfig->type != VIR_DOMAIN_CHR_TYPE_UNIX) { + qemuReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Cannot connect to monitor connection of type '%s' for pid %u"), + virDomainChrTypeToString(monConfig->type), pid); + goto cleanup; + } + + if (!(def->name) && + virAsprintf(&def->name, "attach-pid-%u", pid) < 0) { + virReportOOMError(); + goto cleanup; + } + + if (virDomainObjIsDuplicate(&driver->domains, def, 1) < 0) + goto cleanup; + + if (qemudCanonicalizeMachine(driver, def) < 0) + goto cleanup; + + if (qemuDomainAssignPCIAddresses(def) < 0) + goto cleanup; + + if (!(vm = virDomainAssignDef(driver->caps, + &driver->domains, + def, false))) + goto cleanup; + + def = NULL; + + if (qemuDomainObjBeginJobWithDriver(driver, vm) < 0) + goto cleanup; + + if (qemuProcessAttach(conn, driver, vm, pid, + pidfile, monConfig, monJSON) < 0) { + monConfig = NULL; + goto endjob; + } + + monConfig = NULL; + + dom = virGetDomain(conn, vm->def->name, vm->def->uuid); + if (dom) dom->id = vm->def->id; + +endjob: + if (qemuDomainObjEndJob(vm) == 0) { + vm = NULL; + goto cleanup; + } + +cleanup: + virDomainDefFree(def); + virDomainChrSourceDefFree(monConfig); + if (vm) + virDomainObjUnlock(vm); + qemuDriverUnlock(driver); + VIR_FREE(pidfile); + return dom; +} + + static int qemuDomainOpenConsole(virDomainPtr dom, const char *devname, @@ -8551,6 +8637,7 @@ static virDriver qemuDriver = { .domainRevertToSnapshot = qemuDomainRevertToSnapshot, /* 0.8.0 */ .domainSnapshotDelete = qemuDomainSnapshotDelete, /* 0.8.0 */ .qemuDomainMonitorCommand = qemuDomainMonitorCommand, /* 0.8.3 */ + .qemuDomainAttach = qemuDomainAttach, /* 0.9.4 */ .domainOpenConsole = qemuDomainOpenConsole, /* 0.8.6 */ .domainInjectNMI = qemuDomainInjectNMI, /* 0.9.2 */ .domainMigrateBegin3 = qemuDomainMigrateBegin3, /* 0.9.2 */ diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index ae45f12caf..ff22cdeb0a 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -61,6 +61,7 @@ #define VIR_FROM_THIS VIR_FROM_QEMU #define START_POSTFIX ": starting up\n" +#define ATTACH_POSTFIX ": attaching\n" #define SHUTDOWN_POSTFIX ": shutting down\n" /** @@ -1125,32 +1126,35 @@ qemuProcessReadLogFD(int logfd, char *buf, int maxlen, int off) tmpbuf[ret] = '\0'; } + static int qemuProcessWaitForMonitor(struct qemud_driver* driver, virDomainObjPtr vm, virBitmapPtr qemuCaps, off_t pos) { - char *buf; + char *buf = NULL; size_t buf_size = 4096; /* Plenty of space to get startup greeting */ - int logfd; + int logfd = -1; int ret = -1; virHashTablePtr paths = NULL; qemuDomainObjPrivatePtr priv; - if ((logfd = qemuDomainOpenLog(driver, vm, pos)) < 0) - return -1; + if (pos != -1) { + if ((logfd = qemuDomainOpenLog(driver, vm, pos)) < 0) + return -1; - if (VIR_ALLOC_N(buf, buf_size) < 0) { - virReportOOMError(); - return -1; + if (VIR_ALLOC_N(buf, buf_size) < 0) { + virReportOOMError(); + return -1; + } + + if (qemuProcessReadLogOutput(vm, logfd, buf, buf_size, + qemuProcessFindCharDevicePTYs, + "console", 30) < 0) + goto closelog; } - if (qemuProcessReadLogOutput(vm, logfd, buf, buf_size, - qemuProcessFindCharDevicePTYs, - "console", 30) < 0) - goto closelog; - VIR_DEBUG("Connect monitor to %p '%s'", vm, vm->def->name); if (qemuConnectMonitor(driver, vm) < 0) { goto cleanup; @@ -1195,6 +1199,8 @@ closelog: virStrerror(errno, ebuf, sizeof ebuf)); } + VIR_FREE(buf); + return ret; } @@ -2974,6 +2980,189 @@ retry: } +int qemuProcessAttach(virConnectPtr conn ATTRIBUTE_UNUSED, + struct qemud_driver *driver, + virDomainObjPtr vm, + int pid, + const char *pidfile, + virDomainChrSourceDefPtr monConfig, + bool monJSON) +{ + char ebuf[1024]; + int logfile = -1; + char *timestamp; + qemuDomainObjPrivatePtr priv = vm->privateData; + bool running = true; + virSecurityLabelPtr seclabel = NULL; + + VIR_DEBUG("Beginning VM attach process"); + + if (virDomainObjIsActive(vm)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("VM is already active")); + return -1; + } + + /* 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(driver->caps, vm, true) < 0) + goto cleanup; + + vm->def->id = driver->nextvmid++; + + if (virFileMakePath(driver->logDir) < 0) { + virReportSystemError(errno, + _("cannot create log directory %s"), + driver->logDir); + goto cleanup; + } + + VIR_FREE(priv->pidfile); + if (pidfile && + !(priv->pidfile = strdup(pidfile))) + goto no_memory; + + VIR_DEBUG("Detect security driver config"); + vm->def->seclabel.type = VIR_DOMAIN_SECLABEL_STATIC; + if (VIR_ALLOC(seclabel) < 0) + goto no_memory; + if (virSecurityManagerGetProcessLabel(driver->securityManager, + vm, seclabel) < 0) + goto cleanup; + if (!(vm->def->seclabel.model = strdup(driver->caps->host.secModel.model))) + goto no_memory; + if (!(vm->def->seclabel.label = strdup(seclabel->label))) + goto no_memory; + + VIR_DEBUG("Creating domain log file"); + if ((logfile = qemuDomainCreateLog(driver, vm, false)) < 0) + goto cleanup; + + VIR_DEBUG("Determining emulator version"); + qemuCapsFree(priv->qemuCaps); + priv->qemuCaps = NULL; + if (qemuCapsExtractVersionInfo(vm->def->emulator, + vm->def->os.arch, + NULL, + &priv->qemuCaps) < 0) + goto cleanup; + + 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 + */ + if (qemuCapsGet(priv->qemuCaps, QEMU_CAPS_DEVICE)) { + VIR_DEBUG("Assigning domain PCI addresses"); + /* Populate cache with current addresses */ + if (priv->pciaddrs) { + qemuDomainPCIAddressSetFree(priv->pciaddrs); + priv->pciaddrs = NULL; + } + if (!(priv->pciaddrs = qemuDomainPCIAddressSetCreate(vm->def))) + goto cleanup; + + /* Assign any remaining addresses */ + if (qemuAssignDevicePCISlots(vm->def, priv->pciaddrs) < 0) + goto cleanup; + + priv->persistentAddrs = 1; + } else { + priv->persistentAddrs = 0; + } + + if ((timestamp = virTimestamp()) == NULL) { + virReportOOMError(); + goto cleanup; + } else { + if (safewrite(logfile, timestamp, strlen(timestamp)) < 0 || + safewrite(logfile, ATTACH_POSTFIX, strlen(ATTACH_POSTFIX)) < 0) { + VIR_WARN("Unable to write timestamp to logfile: %s", + virStrerror(errno, ebuf, sizeof ebuf)); + } + + VIR_FREE(timestamp); + } + + qemuDomainObjTaint(driver, vm, VIR_DOMAIN_TAINT_EXTERNAL_LAUNCH, logfile); + + vm->pid = pid; + + VIR_DEBUG("Waiting for monitor to show up"); + if (qemuProcessWaitForMonitor(driver, vm, priv->qemuCaps, -1) < 0) + goto cleanup; + + VIR_DEBUG("Detecting VCPU PIDs"); + if (qemuProcessDetectVcpuPIDs(driver, vm) < 0) + goto cleanup; + + /* If we have -device, then addresses are assigned explicitly. + * If not, then we have to detect dynamic ones here */ + if (!qemuCapsGet(priv->qemuCaps, QEMU_CAPS_DEVICE)) { + VIR_DEBUG("Determining domain device PCI addresses"); + if (qemuProcessInitPCIAddresses(driver, vm) < 0) + goto cleanup; + } + + VIR_DEBUG("Getting initial memory amount"); + qemuDomainObjEnterMonitorWithDriver(driver, vm); + if (qemuMonitorGetBalloonInfo(priv->mon, &vm->def->mem.cur_balloon) < 0) { + qemuDomainObjExitMonitorWithDriver(driver, vm); + goto cleanup; + } + if (qemuMonitorGetStatus(priv->mon, &running) < 0) { + qemuDomainObjExitMonitorWithDriver(driver, vm); + goto cleanup; + } + if (qemuMonitorGetVirtType(priv->mon, &vm->def->virtType) < 0) { + qemuDomainObjExitMonitorWithDriver(driver, vm); + goto cleanup; + } + qemuDomainObjExitMonitorWithDriver(driver, vm); + + if (!virDomainObjIsActive(vm)) + goto cleanup; + + if (running) + virDomainObjSetState(vm, VIR_DOMAIN_RUNNING, + VIR_DOMAIN_RUNNING_UNPAUSED); + else + virDomainObjSetState(vm, VIR_DOMAIN_PAUSED, VIR_DOMAIN_PAUSED_UNKNOWN); + + VIR_DEBUG("Writing domain status to disk"); + if (virDomainSaveStatus(driver->caps, driver->stateDir, vm) < 0) + goto cleanup; + + VIR_FORCE_CLOSE(logfile); + VIR_FREE(seclabel); + + return 0; + +no_memory: + virReportOOMError(); +cleanup: + /* We jump here if we failed to start the VM for any reason, or + * if we failed to initialize the now running VM. kill it off and + * pretend we never started it */ + VIR_FORCE_CLOSE(logfile); + VIR_FREE(seclabel); + virDomainChrSourceDefFree(monConfig); + return -1; +} + + int qemuProcessAutoDestroyInit(struct qemud_driver *driver) { if (!(driver->autodestroy = virHashCreate(5, NULL))) diff --git a/src/qemu/qemu_process.h b/src/qemu/qemu_process.h index 0e74d2a57a..449d7f1622 100644 --- a/src/qemu/qemu_process.h +++ b/src/qemu/qemu_process.h @@ -56,6 +56,14 @@ void qemuProcessStop(struct qemud_driver *driver, int migrated, virDomainShutoffReason reason); +int qemuProcessAttach(virConnectPtr conn, + struct qemud_driver *driver, + virDomainObjPtr vm, + int pid, + const char *pidfile, + virDomainChrSourceDefPtr monConfig, + bool monJSON); + void qemuProcessKill(virDomainObjPtr vm); int qemuProcessAutoDestroyInit(struct qemud_driver *driver);