/* * qemu_monitor_text.c: interaction with QEMU monitor console * * Copyright (C) 2006-2009 Red Hat, Inc. * Copyright (C) 2006 Daniel P. Berrange * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Author: Daniel P. Berrange */ #include #include #include #include #include #include #include #include "qemu_monitor_text.h" #include "qemu_conf.h" #include "c-ctype.h" #include "memory.h" #include "logging.h" #include "driver.h" #include "datatypes.h" #include "virterror_internal.h" #define VIR_FROM_THIS VIR_FROM_QEMU static char *qemudEscape(const char *in, int shell) { int len = 0; int i, j; char *out; /* To pass through the QEMU monitor, we need to use escape sequences: \r, \n, \", \\ To pass through both QEMU + the shell, we need to escape the single character ' as the five characters '\\'' */ for (i = 0; in[i] != '\0'; i++) { switch(in[i]) { case '\r': case '\n': case '"': case '\\': len += 2; break; case '\'': if (shell) len += 5; else len += 1; break; default: len += 1; break; } } if (VIR_ALLOC_N(out, len + 1) < 0) return NULL; for (i = j = 0; in[i] != '\0'; i++) { switch(in[i]) { case '\r': out[j++] = '\\'; out[j++] = 'r'; break; case '\n': out[j++] = '\\'; out[j++] = 'n'; break; case '"': case '\\': out[j++] = '\\'; out[j++] = in[i]; break; case '\'': if (shell) { out[j++] = '\''; out[j++] = '\\'; out[j++] = '\\'; out[j++] = '\''; out[j++] = '\''; } else { out[j++] = in[i]; } break; default: out[j++] = in[i]; break; } } out[j] = '\0'; return out; } static char *qemudEscapeMonitorArg(const char *in) { return qemudEscape(in, 0); } static char *qemudEscapeShellArg(const char *in) { return qemudEscape(in, 1); } /* Throw away any data available on the monitor * This is done before executing a command, in order * to allow re-synchronization if something went badly * wrong in the past. it also deals with problem of * QEMU *sometimes* re-printing its initial greeting * when we reconnect to the monitor after restarts. */ static void qemuMonitorDiscardPendingData(virDomainObjPtr vm) { char buf[1024]; int ret = 0; /* Monitor is non-blocking, so just loop till we * get -1 or 0. Don't bother with detecting * errors, since we'll deal with that better later */ do { ret = read(vm->monitor, buf, sizeof (buf)-1); } while (ret > 0); } static int qemudMonitorSendUnix(const virDomainObjPtr vm, const char *cmd, size_t cmdlen, int scm_fd) { struct msghdr msg; struct iovec iov[1]; ssize_t ret; memset(&msg, 0, sizeof(msg)); iov[0].iov_base = (void *)cmd; iov[0].iov_len = cmdlen; msg.msg_iov = iov; msg.msg_iovlen = 1; if (scm_fd != -1) { char control[CMSG_SPACE(sizeof(int))]; struct cmsghdr *cmsg; msg.msg_control = control; msg.msg_controllen = sizeof(control); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_len = CMSG_LEN(sizeof(int)); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; memcpy(CMSG_DATA(cmsg), &scm_fd, sizeof(int)); } do { ret = sendmsg(vm->monitor, &msg, 0); } while (ret < 0 && errno == EINTR); return ret == cmdlen ? 0 : -1; } static int qemudMonitorSend(const virDomainObjPtr vm, const char *cmd, int scm_fd) { char *full; size_t len; int ret = -1; if (virAsprintf(&full, "%s\r", cmd) < 0) return -1; len = strlen(full); switch (vm->monitor_chr->type) { case VIR_DOMAIN_CHR_TYPE_UNIX: if (qemudMonitorSendUnix(vm, full, len, scm_fd) < 0) goto out; break; default: case VIR_DOMAIN_CHR_TYPE_PTY: if (safewrite(vm->monitor, full, len) != len) goto out; break; } ret = 0; out: VIR_FREE(full); return ret; } int qemudMonitorCommandWithHandler(const virDomainObjPtr vm, const char *cmd, const char *extraPrompt, qemudMonitorExtraPromptHandler extraHandler, void *handlerData, int scm_fd, char **reply) { int size = 0; char *buf = NULL; /* Should never happen, but just in case, protect * against null monitor (ocurrs when VM is inactive) */ if (!vm->monitor_chr) return -1; qemuMonitorDiscardPendingData(vm); VIR_DEBUG("Send '%s'", cmd); if (qemudMonitorSend(vm, cmd, scm_fd) < 0) return -1; *reply = NULL; for (;;) { struct pollfd fd = { vm->monitor, POLLIN | POLLERR | POLLHUP, 0 }; char *tmp; /* Read all the data QEMU has sent thus far */ for (;;) { char data[1024]; int got = read(vm->monitor, data, sizeof(data)); if (got == 0) goto error; if (got < 0) { if (errno == EINTR) continue; if (errno == EAGAIN) break; goto error; } if (VIR_REALLOC_N(buf, size+got+1) < 0) goto error; memmove(buf+size, data, got); buf[size+got] = '\0'; size += got; } /* Look for QEMU prompt to indicate completion */ if (buf) { char *foundPrompt; if (extraPrompt && (foundPrompt = strstr(buf, extraPrompt)) != NULL) { char *promptEnd; if (extraHandler(vm, buf, foundPrompt, handlerData) < 0) return -1; /* Discard output so far, necessary to detect whether extraPrompt appears again. We don't need the output between original command and this prompt anyway. */ promptEnd = foundPrompt + strlen(extraPrompt); memmove(buf, promptEnd, strlen(promptEnd)+1); size -= promptEnd - buf; } else if ((tmp = strstr(buf, QEMU_CMD_PROMPT)) != NULL) { char *commptr = NULL, *nlptr = NULL; /* Preserve the newline */ tmp[1] = '\0'; /* The monitor doesn't dump clean output after we have written to * it. Every character we write dumps a bunch of useless stuff, * so the result looks like "cXcoXcomXcommXcommaXcommanXcommand" * Try to throw away everything before the first full command * occurence, and inbetween the command and the newline starting * the response */ if ((commptr = strstr(buf, cmd))) { memmove(buf, commptr, strlen(commptr)+1); if ((nlptr = strchr(buf, '\n'))) memmove(buf+strlen(cmd), nlptr, strlen(nlptr)+1); } break; } } pollagain: /* Need to wait for more data */ if (poll(&fd, 1, -1) < 0) { if (errno == EINTR) goto pollagain; goto error; } } *reply = buf; return 0; error: VIR_FREE(buf); return -1; } struct extraHandlerData { const char *reply; bool first; }; static int qemudMonitorCommandSimpleExtraHandler(const virDomainObjPtr vm, const char *buf ATTRIBUTE_UNUSED, const char *prompt ATTRIBUTE_UNUSED, void *data_) { struct extraHandlerData *data = data_; if (!data->first) return 0; if (qemudMonitorSend(vm, data->reply, -1) < 0) return -1; data->first = false; return 0; } int qemudMonitorCommandExtra(const virDomainObjPtr vm, const char *cmd, const char *extra, const char *extraPrompt, int scm_fd, char **reply) { struct extraHandlerData data; data.reply = extra; data.first = true; return qemudMonitorCommandWithHandler(vm, cmd, extraPrompt, qemudMonitorCommandSimpleExtraHandler, &data, scm_fd, reply); } int qemudMonitorCommandWithFd(const virDomainObjPtr vm, const char *cmd, int scm_fd, char **reply) { return qemudMonitorCommandExtra(vm, cmd, NULL, NULL, scm_fd, reply); } int qemudMonitorCommand(const virDomainObjPtr vm, const char *cmd, char **reply) { return qemudMonitorCommandWithFd(vm, cmd, -1, reply); } static virStorageEncryptionPtr findDomainDiskEncryption(virConnectPtr conn, virDomainObjPtr vm, const char *path) { bool seen_volume; int i; seen_volume = false; for (i = 0; i < vm->def->ndisks; i++) { virDomainDiskDefPtr disk; disk = vm->def->disks[i]; if (disk->src != NULL && STREQ(disk->src, path)) { seen_volume = true; if (disk->encryption != NULL) return disk->encryption; } } if (seen_volume) qemudReportError(conn, NULL, NULL, VIR_ERR_INVALID_DOMAIN, _("missing for volume %s"), path); else qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("unexpected passphrase request for volume %s"), path); return NULL; } static char * findVolumeQcowPassphrase(virConnectPtr conn, virDomainObjPtr vm, const char *path, size_t *passphrase_len) { virStorageEncryptionPtr enc; virSecretPtr secret; char *passphrase; unsigned char *data; size_t size; if (conn->secretDriver == NULL || conn->secretDriver->lookupByUUID == NULL || conn->secretDriver->getValue == NULL) { qemudReportError(conn, NULL, NULL, VIR_ERR_NO_SUPPORT, "%s", _("secret storage not supported")); return NULL; } enc = findDomainDiskEncryption(conn, vm, path); if (enc == NULL) return NULL; if (enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_QCOW || enc->nsecrets != 1 || enc->secrets[0]->type != VIR_STORAGE_ENCRYPTION_SECRET_TYPE_PASSPHRASE) { qemudReportError(conn, NULL, NULL, VIR_ERR_INVALID_DOMAIN, _("invalid for volume %s"), path); return NULL; } secret = conn->secretDriver->lookupByUUID(conn, enc->secrets[0]->uuid); if (secret == NULL) return NULL; data = conn->secretDriver->getValue(secret, &size, VIR_SECRET_GET_VALUE_INTERNAL_CALL); virUnrefSecret(secret); if (data == NULL) return NULL; if (memchr(data, '\0', size) != NULL) { memset(data, 0, size); VIR_FREE(data); qemudReportError(conn, NULL, NULL, VIR_ERR_INVALID_SECRET, _("format='qcow' passphrase for %s must not contain a " "'\\0'"), path); return NULL; } if (VIR_ALLOC_N(passphrase, size + 1) < 0) { memset(data, 0, size); VIR_FREE(data); virReportOOMError(conn); return NULL; } memcpy(passphrase, data, size); passphrase[size] = '\0'; memset(data, 0, size); VIR_FREE(data); *passphrase_len = size; return passphrase; } static int qemudMonitorSendVolumePassphrase(const virDomainObjPtr vm, const char *buf, const char *prompt, void *data) { virConnectPtr conn = data; char *passphrase, *path; const char *prompt_path; size_t path_len, passphrase_len = 0; int res; /* The complete prompt looks like this: ide0-hd0 (/path/to/volume) is encrypted. Password: "prompt" starts with ") is encrypted". Extract /path/to/volume. */ for (prompt_path = prompt; prompt_path > buf && prompt_path[-1] != '('; prompt_path--) ; if (prompt_path == buf) return -1; path_len = prompt - prompt_path; if (VIR_ALLOC_N(path, path_len + 1) < 0) return -1; memcpy(path, prompt_path, path_len); path[path_len] = '\0'; passphrase = findVolumeQcowPassphrase(conn, vm, path, &passphrase_len); VIR_FREE(path); if (passphrase == NULL) return -1; res = qemudMonitorSend(vm, passphrase, -1); memset(passphrase, 0, passphrase_len); VIR_FREE(passphrase); return res; } int qemuMonitorStartCPUs(virConnectPtr conn, const virDomainObjPtr vm) { char *reply; if (qemudMonitorCommandWithHandler(vm, "cont", ") is encrypted.", qemudMonitorSendVolumePassphrase, conn, -1, &reply) < 0) return -1; qemudDebug ("%s: cont reply: %s", vm->def->name, info); VIR_FREE(reply); return 0; } int qemuMonitorStopCPUs(const virDomainObjPtr vm) { char *info; if (qemudMonitorCommand(vm, "stop", &info) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, "%s", _("cannot stop CPU execution")); return -1; } VIR_FREE(info); return 0; } int qemuMonitorSystemPowerdown(const virDomainObjPtr vm) { char *info; if (qemudMonitorCommand(vm, "system_powerdown", &info) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, "%s", _("system shutdown operation failed")); return -1; } VIR_FREE(info); return 0; } int qemuMonitorGetCPUInfo(const virDomainObjPtr vm, int **pids) { char *qemucpus = NULL; char *line; int lastVcpu = -1; pid_t *cpupids = NULL; size_t ncpupids = 0; if (qemudMonitorCommand(vm, "info cpus", &qemucpus) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_INTERNAL_ERROR, "%s", _("cannot run monitor command to fetch CPU thread info")); return -1; } /* * This is the gross format we're about to parse :-{ * * (qemu) info cpus * * CPU #0: pc=0x00000000000f0c4a thread_id=30019 * CPU #1: pc=0x00000000fffffff0 thread_id=30020 * CPU #2: pc=0x00000000fffffff0 thread_id=30021 * */ line = qemucpus; do { char *offset = strchr(line, '#'); char *end = NULL; int vcpu = 0, tid = 0; /* See if we're all done */ if (offset == NULL) break; /* Extract VCPU number */ if (virStrToLong_i(offset + 1, &end, 10, &vcpu) < 0) goto error; if (end == NULL || *end != ':') goto error; /* Extract host Thread ID */ if ((offset = strstr(line, "thread_id=")) == NULL) goto error; if (virStrToLong_i(offset + strlen("thread_id="), &end, 10, &tid) < 0) goto error; if (end == NULL || !c_isspace(*end)) goto error; if (vcpu != (lastVcpu + 1)) goto error; if (VIR_REALLOC_N(cpupids, ncpupids+1) < 0) goto error; cpupids[ncpupids++] = tid; lastVcpu = vcpu; /* Skip to next data line */ line = strchr(offset, '\r'); if (line == NULL) line = strchr(offset, '\n'); } while (line != NULL); /* Validate we got data for all VCPUs we expected */ VIR_FREE(qemucpus); *pids = cpupids; return ncpupids; error: VIR_FREE(qemucpus); VIR_FREE(cpupids); /* Returning 0 to indicate non-fatal failure, since * older QEMU does not have VCPU<->PID mapping and * we don't want to fail on that */ return 0; } /* The reply from QEMU contains 'ballon: actual=421' where value is in MB */ #define BALLOON_PREFIX "balloon: actual=" /* * Returns: 0 if balloon not supported, +1 if balloon query worked * or -1 on failure */ int qemuMonitorGetBalloonInfo(const virDomainObjPtr vm, unsigned long *currmem) { char *reply = NULL; int ret = -1; char *offset; if (qemudMonitorCommand(vm, "info balloon", &reply) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, "%s", _("could not query memory balloon allocation")); return -1; } DEBUG ("%s: balloon reply: '%s'", vm->def->name, reply); if ((offset = strstr(reply, BALLOON_PREFIX)) != NULL) { unsigned int memMB; char *end; offset += strlen(BALLOON_PREFIX); if (virStrToLong_ui(offset, &end, 10, &memMB) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, _("could not parse memory balloon allocation from '%s'"), reply); goto cleanup; } *currmem = memMB * 1024; ret = 1; } else { /* We don't raise an error here, since its to be expected that * many QEMU's don't support ballooning */ ret = 0; } cleanup: VIR_FREE(reply); return ret; } int qemuMonitorGetBlockStatsInfo(const virDomainObjPtr vm, const char *devname, long long *rd_req, long long *rd_bytes, long long *wr_req, long long *wr_bytes, long long *errs) { char *info = NULL; int ret = -1; char *dummy; const char *p, *eol; int devnamelen = strlen(devname); if (qemudMonitorCommand (vm, "info blockstats", &info) < 0) { qemudReportError (NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, "%s", _("'info blockstats' command failed")); goto cleanup; } DEBUG ("%s: info blockstats reply: %s", vm->def->name, info); /* If the command isn't supported then qemu prints the supported * info commands, so the output starts "info ". Since this is * unlikely to be the name of a block device, we can use this * to detect if qemu supports the command. */ if (strstr(info, "\ninfo ")) { qemudReportError (NULL, NULL, NULL, VIR_ERR_NO_SUPPORT, "%s", _("'info blockstats' not supported by this qemu")); goto cleanup; } *rd_req = -1; *rd_bytes = -1; *wr_req = -1; *wr_bytes = -1; *errs = -1; /* The output format for both qemu & KVM is: * blockdevice: rd_bytes=% wr_bytes=% rd_operations=% wr_operations=% * (repeated for each block device) * where '%' is a 64 bit number. */ p = info; while (*p) { if (STREQLEN (p, devname, devnamelen) && p[devnamelen] == ':' && p[devnamelen+1] == ' ') { eol = strchr (p, '\n'); if (!eol) eol = p + strlen (p); p += devnamelen+2; /* Skip to first label. */ while (*p) { if (STRPREFIX (p, "rd_bytes=")) { p += 9; if (virStrToLong_ll (p, &dummy, 10, rd_bytes) == -1) DEBUG ("%s: error reading rd_bytes: %s", vm->def->name, p); } else if (STRPREFIX (p, "wr_bytes=")) { p += 9; if (virStrToLong_ll (p, &dummy, 10, wr_bytes) == -1) DEBUG ("%s: error reading wr_bytes: %s", vm->def->name, p); } else if (STRPREFIX (p, "rd_operations=")) { p += 14; if (virStrToLong_ll (p, &dummy, 10, rd_req) == -1) DEBUG ("%s: error reading rd_req: %s", vm->def->name, p); } else if (STRPREFIX (p, "wr_operations=")) { p += 14; if (virStrToLong_ll (p, &dummy, 10, wr_req) == -1) DEBUG ("%s: error reading wr_req: %s", vm->def->name, p); } else DEBUG ("%s: unknown block stat near %s", vm->def->name, p); /* Skip to next label. */ p = strchr (p, ' '); if (!p || p >= eol) break; p++; } ret = 0; goto cleanup; } /* Skip to next line. */ p = strchr (p, '\n'); if (!p) break; p++; } /* If we reach here then the device was not found. */ qemudReportError (NULL, NULL, NULL, VIR_ERR_INVALID_ARG, _("no stats found for device %s"), devname); cleanup: VIR_FREE(info); return ret; } int qemuMonitorSetVNCPassword(const virDomainObjPtr vm, const char *password) { char *info = NULL; if (qemudMonitorCommandExtra(vm, "change vnc password", password, QEMU_PASSWD_PROMPT, -1, &info) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_INTERNAL_ERROR, "%s", _("setting VNC password failed")); return -1; } VIR_FREE(info); return 0; } /* * Returns: 0 if balloon not supported, +1 if balloon adjust worked * or -1 on failure */ int qemuMonitorSetBalloon(const virDomainObjPtr vm, unsigned long newmem) { char *cmd; char *reply = NULL; int ret = -1; /* * 'newmem' is in KB, QEMU monitor works in MB, and we all wish * we just worked in bytes with unsigned long long everywhere. */ if (virAsprintf(&cmd, "balloon %lu", (newmem / 1024)) < 0) { virReportOOMError(NULL); return -1; } if (qemudMonitorCommand(vm, cmd, &reply) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, "%s", _("could not balloon memory allocation")); VIR_FREE(cmd); return -1; } VIR_FREE(cmd); /* If the command failed qemu prints: 'unknown command' * No message is printed on success it seems */ DEBUG ("%s: balloon reply: %s", vm->def->name, reply); if (strstr(reply, "\nunknown command:")) { /* Don't set error - it is expected memory balloon fails on many qemu */ ret = 0; } else { ret = 1; } VIR_FREE(reply); return ret; } int qemuMonitorEjectMedia(const virDomainObjPtr vm, const char *devname) { char *cmd = NULL; char *reply = NULL; int ret = -1; if (virAsprintf(&cmd, "eject %s", devname) < 0) { virReportOOMError(NULL); goto cleanup; } if (qemudMonitorCommand(vm, cmd, &reply) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, _("could not eject media on %s"), devname); goto cleanup; } /* If the command failed qemu prints: * device not found, device is locked ... * No message is printed on success it seems */ DEBUG ("%s: ejectable media change reply: %s", vm->def->name, reply); if (strstr(reply, "\ndevice ")) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, _("could not eject media on %s: %s"), devname, reply); goto cleanup; } ret = 0; cleanup: VIR_FREE(reply); VIR_FREE(cmd); return ret; } int qemuMonitorChangeMedia(const virDomainObjPtr vm, const char *devname, const char *newmedia) { char *cmd = NULL; char *reply = NULL; char *safepath = NULL; int ret = -1; if (!(safepath = qemudEscapeMonitorArg(newmedia))) { virReportOOMError(NULL); goto cleanup; } if (virAsprintf(&cmd, "change %s \"%s\"", devname, safepath) < 0) { virReportOOMError(NULL); goto cleanup; } if (qemudMonitorCommand(vm, cmd, &reply) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, _("could not eject media on %s"), devname); goto cleanup; } /* If the command failed qemu prints: * device not found, device is locked ... * No message is printed on success it seems */ DEBUG ("%s: ejectable media change reply: %s", vm->def->name, reply); if (strstr(reply, "\ndevice ")) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, _("could not eject media on %s: %s"), devname, reply); goto cleanup; } ret = 0; cleanup: VIR_FREE(reply); VIR_FREE(cmd); VIR_FREE(safepath); return ret; } static int qemuMonitorSaveMemory(const virDomainObjPtr vm, const char *cmdtype, unsigned long long offset, size_t length, const char *path) { char *cmd = NULL; char *reply = NULL; char *safepath = NULL; int ret = -1; if (!(safepath = qemudEscapeMonitorArg(path))) { virReportOOMError(NULL); goto cleanup; } if (virAsprintf(&cmd, "%s %llu %zi \"%s\"", cmdtype, offset, length, safepath) < 0) { virReportOOMError(NULL); goto cleanup; } if (qemudMonitorCommand(vm, cmd, &reply) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, _("could save memory region to '%s'"), path); goto cleanup; } /* XXX what is printed on failure ? */ ret = 0; cleanup: VIR_FREE(cmd); VIR_FREE(reply); VIR_FREE(safepath); return ret; } int qemuMonitorSaveVirtualMemory(const virDomainObjPtr vm, unsigned long long offset, size_t length, const char *path) { return qemuMonitorSaveMemory(vm, "memsave", offset, length, path); } int qemuMonitorSavePhysicalMemory(const virDomainObjPtr vm, unsigned long long offset, size_t length, const char *path) { return qemuMonitorSaveMemory(vm, "pmemsave", offset, length, path); } int qemuMonitorSetMigrationSpeed(const virDomainObjPtr vm, unsigned long bandwidth) { char *cmd = NULL; char *info = NULL; int ret = -1; if (virAsprintf(&cmd, "migrate_set_speed %lum", bandwidth) < 0) { virReportOOMError(NULL); goto cleanup; } if (qemudMonitorCommand(vm, cmd, &info) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, "%s", _("could restrict migration speed")); goto cleanup; } DEBUG("%s: migrate_set_speed reply: %s", vm->def->name, info); ret = 0; cleanup: VIR_FREE(info); VIR_FREE(cmd); return ret; } #define MIGRATION_PREFIX "Migration status: " #define MIGRATION_TRANSFER_PREFIX "transferred ram: " #define MIGRATION_REMAINING_PREFIX "remaining ram: " #define MIGRATION_TOTAL_PREFIX "total ram: " VIR_ENUM_DECL(qemuMonitorMigrationStatus) VIR_ENUM_IMPL(qemuMonitorMigrationStatus, QEMU_MONITOR_MIGRATION_STATUS_LAST, "inactive", "active", "completed", "failed", "cancelled") int qemuMonitorGetMigrationStatus(const virDomainObjPtr vm, int *status, unsigned long long *transferred, unsigned long long *remaining, unsigned long long *total) { char *reply; char *tmp; char *end; int ret = -1; *status = QEMU_MONITOR_MIGRATION_STATUS_INACTIVE; *transferred = 0; *remaining = 0; *total = 0; if (qemudMonitorCommand(vm, "info migration", &reply) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, "%s", _("cannot query migration status")); return -1; } if ((tmp = strstr(reply, MIGRATION_PREFIX)) != NULL) { tmp += strlen(MIGRATION_PREFIX); end = strchr(tmp, '\n'); *end = '\0'; if ((*status = qemuMonitorMigrationStatusTypeFromString(tmp)) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("unexpected migration status in %s"), reply); goto cleanup; } if (*status == QEMU_MONITOR_MIGRATION_STATUS_ACTIVE) { tmp = end + 1; if (!(tmp = strstr(tmp, MIGRATION_TRANSFER_PREFIX))) goto done; tmp += strlen(MIGRATION_TRANSFER_PREFIX); if (virStrToLong_ull(tmp, NULL, 10, transferred) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot parse migration data transferred statistic %s"), tmp); goto cleanup; } *transferred *= 1024; if (!(tmp = strstr(tmp, MIGRATION_REMAINING_PREFIX))) goto done; tmp += strlen(MIGRATION_REMAINING_PREFIX); if (virStrToLong_ull(tmp, NULL, 10, remaining) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot parse migration data remaining statistic %s"), tmp); goto cleanup; } *remaining *= 1024; if (!(tmp = strstr(tmp, MIGRATION_TOTAL_PREFIX))) goto done; tmp += strlen(MIGRATION_TOTAL_PREFIX); if (virStrToLong_ull(tmp, NULL, 10, total) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot parse migration data total statistic %s"), tmp); goto cleanup; } *total *= 1024; } } done: ret = 0; cleanup: VIR_FREE(reply); return ret; } static int qemuMonitorMigrate(const virDomainObjPtr vm, const char *dest) { char *cmd = NULL; char *info = NULL; int ret = -1; char *safedest = qemudEscapeMonitorArg(dest); if (!safedest) { virReportOOMError(NULL); return -1; } if (virAsprintf(&cmd, "migrate \"%s\"", safedest) < 0) { virReportOOMError(NULL); goto cleanup; } if (qemudMonitorCommand(vm, cmd, &info) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("unable to start migration to %s"), dest); goto cleanup; } DEBUG ("%s: migrate reply: %s", vm->def->name, info); /* Now check for "fail" in the output string */ if (strstr(info, "fail") != NULL) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, _("migration to '%s' failed: %s"), dest, info); goto cleanup; } /* If the command isn't supported then qemu prints: * unknown command: migrate" */ if (strstr(info, "unknown command:")) { qemudReportError(NULL, NULL, NULL, VIR_ERR_NO_SUPPORT, _("migration to '%s' not supported by this qemu: %s"), dest, info); goto cleanup; } ret = 0; cleanup: VIR_FREE(safedest); VIR_FREE(info); VIR_FREE(cmd); return ret; } int qemuMonitorMigrateToHost(const virDomainObjPtr vm, const char *hostname, int port) { char *uri = NULL; int ret; if (virAsprintf(&uri, "tcp:%s:%d", hostname, port) < 0) { virReportOOMError(NULL); return -1; } ret = qemuMonitorMigrate(vm, uri); VIR_FREE(uri); return ret; } int qemuMonitorMigrateToCommand(const virDomainObjPtr vm, const char * const *argv, const char *target) { char *argstr; char *dest = NULL; int ret = -1; char *safe_target = NULL; argstr = virArgvToString(argv); if (!argstr) { virReportOOMError(NULL); goto cleanup; } /* Migrate to file */ safe_target = qemudEscapeShellArg(target); if (!safe_target) { virReportOOMError(NULL); goto cleanup; } if (virAsprintf(&dest, "exec:%s >>%s 2>/dev/null", argstr, safe_target) < 0) { virReportOOMError(NULL); goto cleanup; } ret = qemuMonitorMigrate(vm, dest); cleanup: VIR_FREE(safe_target); VIR_FREE(argstr); VIR_FREE(dest); return ret; } int qemuMonitorAddUSBDisk(const virDomainObjPtr vm, const char *path) { char *cmd = NULL; char *safepath; int ret = -1; char *info = NULL; safepath = qemudEscapeMonitorArg(path); if (!safepath) { virReportOOMError(NULL); return -1; } if (virAsprintf(&cmd, "usb_add disk:%s", safepath) < 0) { virReportOOMError(NULL); goto cleanup; } if (qemudMonitorCommand(vm, cmd, &info) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_INTERNAL_ERROR, "%s", _("cannot run monitor command to add usb disk")); goto cleanup; } DEBUG ("%s: usb_add reply: %s", vm->def->name, info); /* If the command failed qemu prints: * Could not add ... */ if (strstr(info, "Could not add ")) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, _("unable to add USB disk %s: %s"), path, info); goto cleanup; } ret = 0; cleanup: VIR_FREE(cmd); VIR_FREE(safepath); return ret; } static int qemuMonitorAddUSBDevice(const virDomainObjPtr vm, const char *addr) { char *cmd; char *reply = NULL; int ret = -1; if (virAsprintf(&cmd, "usb_add %s", addr) < 0) { virReportOOMError(NULL); return -1; } if (qemudMonitorCommand(vm, cmd, &reply) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, "%s", _("cannot attach usb device")); goto cleanup; } DEBUG ("%s: attach_usb reply: %s", vm->def->name, reply); /* If the command failed qemu prints: * Could not add ... */ if (strstr(reply, "Could not add ")) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, "%s", _("adding usb device failed")); goto cleanup; } ret = 0; cleanup: VIR_FREE(cmd); VIR_FREE(reply); return ret; } int qemuMonitorAddUSBDeviceExact(const virDomainObjPtr vm, int bus, int dev) { int ret; char *addr; if (virAsprintf(&addr, "host:%.3d.%.3d", bus, dev) < 0) { virReportOOMError(NULL); return -1; } ret = qemuMonitorAddUSBDevice(vm, addr); VIR_FREE(addr); return ret; } int qemuMonitorAddUSBDeviceMatch(const virDomainObjPtr vm, int vendor, int product) { int ret; char *addr; if (virAsprintf(&addr, "host:%.4x:%.4x", vendor, product) < 0) { virReportOOMError(NULL); return -1; } ret = qemuMonitorAddUSBDevice(vm, addr); VIR_FREE(addr); return ret; } static int qemuMonitorParsePciAddReply(virDomainObjPtr vm, const char *reply, unsigned *domain, unsigned *bus, unsigned *slot) { char *s, *e; DEBUG("%s: pci_add reply: %s", vm->def->name, reply); /* If the command succeeds qemu prints: * OK bus 0, slot XXX... * or * OK domain 0, bus 0, slot XXX */ if (!(s = strstr(reply, "OK "))) return -1; s += 3; if (STRPREFIX(s, "domain ")) { s += strlen("domain "); if (virStrToLong_ui(s, &e, 10, domain) == -1) { VIR_WARN(_("Unable to parse domain number '%s'\n"), s); return -1; } if (!STRPREFIX(e, ", ")) { VIR_WARN(_("Expected ', ' parsing pci_add reply '%s'\n"), s); return -1; } s = e + 2; } if (!STRPREFIX(s, "bus ")) { VIR_WARN(_("Expected 'bus ' parsing pci_add reply '%s'\n"), s); return -1; } s += strlen("bus "); if (virStrToLong_ui(s, &e, 10, bus) == -1) { VIR_WARN(_("Unable to parse bus number '%s'\n"), s); return -1; } if (!STRPREFIX(e, ", ")) { VIR_WARN(_("Expected ', ' parsing pci_add reply '%s'\n"), s); return -1; } s = e + 2; if (!STRPREFIX(s, "slot ")) { VIR_WARN(_("Expected 'slot ' parsing pci_add reply '%s'\n"), s); return -1; } s += strlen("slot "); if (virStrToLong_ui(s, &e, 10, slot) == -1) { VIR_WARN(_("Unable to parse slot number '%s'\n"), s); return -1; } return 0; } int qemuMonitorAddPCIHostDevice(const virDomainObjPtr vm, unsigned hostDomain ATTRIBUTE_UNUSED, unsigned hostBus, unsigned hostSlot, unsigned hostFunction, unsigned *guestDomain, unsigned *guestBus, unsigned *guestSlot) { char *cmd; char *reply = NULL; int ret = -1; *guestDomain = *guestBus = *guestSlot = 0; /* XXX hostDomain */ if (virAsprintf(&cmd, "pci_add pci_addr=auto host host=%.2x:%.2x.%.1x", hostBus, hostSlot, hostFunction) < 0) { virReportOOMError(NULL); goto cleanup; } if (qemudMonitorCommand(vm, cmd, &reply) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, "%s", _("cannot attach host pci device")); goto cleanup; } if (strstr(reply, "invalid type: host")) { qemudReportError(NULL, NULL, NULL, VIR_ERR_NO_SUPPORT, "%s", _("PCI device assignment is not supported by this version of qemu")); goto cleanup; } if (qemuMonitorParsePciAddReply(vm, reply, guestDomain, guestBus, guestSlot) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, _("parsing pci_add reply failed: %s"), reply); goto cleanup; } ret = 0; cleanup: VIR_FREE(cmd); VIR_FREE(reply); return ret; } int qemuMonitorAddPCIDisk(const virDomainObjPtr vm, const char *path, const char *bus, unsigned *guestDomain, unsigned *guestBus, unsigned *guestSlot) { char *cmd = NULL; char *reply = NULL; char *safe_path = NULL; int tryOldSyntax = 0; int ret = -1; safe_path = qemudEscapeMonitorArg(path); if (!safe_path) { virReportOOMError(NULL); return -1; } try_command: if (virAsprintf(&cmd, "pci_add %s storage file=%s,if=%s", (tryOldSyntax ? "0": "pci_addr=auto"), safe_path, bus) < 0) { virReportOOMError(NULL); goto cleanup; } if (qemudMonitorCommand(vm, cmd, &reply) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, _("cannot attach %s disk %s"), bus, path); goto cleanup; } if (qemuMonitorParsePciAddReply(vm, reply, guestDomain, guestBus, guestSlot) < 0) { if (!tryOldSyntax && strstr(reply, "invalid char in expression")) { VIR_FREE(reply); VIR_FREE(cmd); tryOldSyntax = 1; goto try_command; } qemudReportError (NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, _("adding %s disk failed %s: %s"), bus, path, reply); goto cleanup; } ret = 0; cleanup: VIR_FREE(safe_path); VIR_FREE(cmd); VIR_FREE(reply); return ret; } int qemuMonitorAddPCINetwork(const virDomainObjPtr vm, const char *nicstr, unsigned *guestDomain, unsigned *guestBus, unsigned *guestSlot) { char *cmd; char *reply = NULL; int ret = -1; if (virAsprintf(&cmd, "pci_add pci_addr=auto nic %s", nicstr) < 0) { virReportOOMError(NULL); return -1; } if (qemudMonitorCommand(vm, cmd, &reply) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, _("failed to add NIC with '%s'"), cmd); goto cleanup; } if (qemuMonitorParsePciAddReply(vm, reply, guestDomain, guestBus, guestSlot) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, _("parsing pci_add reply failed: %s"), reply); goto cleanup; } ret = 0; cleanup: VIR_FREE(reply); VIR_FREE(cmd); return ret; } int qemuMonitorRemovePCIDevice(const virDomainObjPtr vm, unsigned guestDomain, unsigned guestBus, unsigned guestSlot) { char *cmd = NULL; char *reply = NULL; int tryOldSyntax = 0; int ret = -1; try_command: if (tryOldSyntax) { if (virAsprintf(&cmd, "pci_del 0 %.2x", guestSlot) < 0) { virReportOOMError(NULL); goto cleanup; } } else { if (virAsprintf(&cmd, "pci_del pci_addr=%.4x:%.2x:%.2x", guestDomain, guestBus, guestSlot) < 0) { virReportOOMError(NULL); goto cleanup; } } if (qemudMonitorCommand(vm, cmd, &reply) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, "%s", _("failed to remove PCI device")); goto cleanup; } DEBUG ("%s: pci_del reply: %s",vm->def->name, reply); /* Syntax changed when KVM merged PCI hotplug upstream to QEMU, * so check for an error message from old KVM indicating the * need to try the old syntax */ if (!tryOldSyntax && strstr(reply, "extraneous characters")) { tryOldSyntax = 1; VIR_FREE(reply); VIR_FREE(cmd); goto try_command; } /* If the command fails due to a wrong slot qemu prints: invalid slot, * nothing is printed on success */ if (strstr(reply, "invalid slot") || strstr(reply, "Invalid pci address")) { qemudReportError (NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, _("failed to detach PCI device, invalid address %.4x:%.2x:%.2x: %s"), guestDomain, guestBus, guestSlot, reply); goto cleanup; } ret = 0; cleanup: VIR_FREE(cmd); VIR_FREE(reply); return ret; } int qemuMonitorSendFileHandle(const virDomainObjPtr vm, const char *fdname, int fd) { char *cmd; char *reply = NULL; int ret = -1; if (virAsprintf(&cmd, "getfd %s", fdname) < 0) { virReportOOMError(NULL); return -1; } if (qemudMonitorCommandWithFd(vm, cmd, fd, &reply) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, _("failed to pass fd to qemu with '%s'"), cmd); goto cleanup; } DEBUG("%s: getfd reply: %s", vm->def->name, reply); /* If the command isn't supported then qemu prints: * unknown command: getfd" */ if (strstr(reply, "unknown command:")) { qemudReportError(NULL, NULL, NULL, VIR_ERR_NO_SUPPORT, _("qemu does not support sending of file handles: %s"), reply); goto cleanup; } ret = 0; cleanup: VIR_FREE(cmd); VIR_FREE(reply); return ret; } int qemuMonitorCloseFileHandle(const virDomainObjPtr vm, const char *fdname) { char *cmd; char *reply = NULL; int ret = -1; if (virAsprintf(&cmd, "closefd %s", fdname) < 0) { virReportOOMError(NULL); return -1; } if (qemudMonitorCommand(vm, cmd, &reply) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, _("failed to close fd in qemu with '%s'"), cmd); goto cleanup; } DEBUG("%s: closefd reply: %s", vm->def->name, reply); /* If the command isn't supported then qemu prints: * unknown command: getfd" */ if (strstr(reply, "unknown command:")) { qemudReportError(NULL, NULL, NULL, VIR_ERR_NO_SUPPORT, _("qemu does not support closing of file handles: %s"), reply); goto cleanup; } ret = 0; cleanup: VIR_FREE(cmd); VIR_FREE(reply); return ret; } int qemuMonitorAddHostNetwork(const virDomainObjPtr vm, const char *netstr) { char *cmd; char *reply = NULL; int ret = -1; if (virAsprintf(&cmd, "host_net_add %s", netstr) < 0) { virReportOOMError(NULL); return -1; } if (qemudMonitorCommand(vm, cmd, &reply) < 0) { qemudReportError(NULL, NULL, NULL, VIR_ERR_OPERATION_FAILED, _("failed to close fd in qemu with '%s'"), cmd); goto cleanup; } DEBUG("%s: host_net_add reply: %s", vm->def->name, reply); /* XXX error messages here ? */ ret = 0; cleanup: VIR_FREE(cmd); VIR_FREE(reply); return ret; }