/* * qemu_conf.c: QEMU configuration management * * Copyright (C) 2006, 2007, 2008, 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 #include #include #include #include #include #include "c-ctype.h" #include "virterror_internal.h" #include "qemu_conf.h" #include "uuid.h" #include "buf.h" #include "conf.h" #include "util.h" #include "memory.h" #include "verify.h" #include "datatypes.h" #include "xml.h" #include "nodeinfo.h" #include "logging.h" #define VIR_FROM_THIS VIR_FROM_QEMU VIR_ENUM_DECL(virDomainDiskQEMUBus) VIR_ENUM_IMPL(virDomainDiskQEMUBus, VIR_DOMAIN_DISK_BUS_LAST, "ide", "floppy", "scsi", "virtio", "xen", "usb", "uml") VIR_ENUM_DECL(qemuDiskCacheV1) VIR_ENUM_DECL(qemuDiskCacheV2) VIR_ENUM_IMPL(qemuDiskCacheV1, VIR_DOMAIN_DISK_CACHE_LAST, "default", "off", "off", /* writethrough not supported, so for safety, disable */ "on"); /* Old 'on' was equivalent to 'writeback' */ VIR_ENUM_IMPL(qemuDiskCacheV2, VIR_DOMAIN_DISK_CACHE_LAST, "default", "none", "writethrough", "writeback"); VIR_ENUM_DECL(qemuVideo) VIR_ENUM_IMPL(qemuVideo, VIR_DOMAIN_VIDEO_TYPE_LAST, "std", "cirrus", "vmware", NULL, /* no arg needed for xen */ NULL /* don't support vbox */); int qemudLoadDriverConfig(struct qemud_driver *driver, const char *filename) { virConfPtr conf; virConfValuePtr p; char *user; char *group; /* Setup 2 critical defaults */ if (!(driver->vncListen = strdup("127.0.0.1"))) { virReportOOMError(NULL); return -1; } if (!(driver->vncTLSx509certdir = strdup(SYSCONF_DIR "/pki/libvirt-vnc"))) { virReportOOMError(NULL); return -1; } /* Just check the file is readable before opening it, otherwise * libvirt emits an error. */ if (access (filename, R_OK) == -1) return 0; conf = virConfReadFile (filename, 0); if (!conf) return 0; #define CHECK_TYPE(name,typ) if (p && p->type != (typ)) { \ qemudReportError(NULL, NULL, NULL, VIR_ERR_INTERNAL_ERROR, \ "remoteReadConfigFile: %s: %s: expected type " #typ "\n", \ filename, (name)); \ virConfFree(conf); \ return -1; \ } p = virConfGetValue (conf, "vnc_tls"); CHECK_TYPE ("vnc_tls", VIR_CONF_LONG); if (p) driver->vncTLS = p->l; p = virConfGetValue (conf, "vnc_tls_x509_verify"); CHECK_TYPE ("vnc_tls_x509_verify", VIR_CONF_LONG); if (p) driver->vncTLSx509verify = p->l; p = virConfGetValue (conf, "vnc_tls_x509_cert_dir"); CHECK_TYPE ("vnc_tls_x509_cert_dir", VIR_CONF_STRING); if (p && p->str) { VIR_FREE(driver->vncTLSx509certdir); if (!(driver->vncTLSx509certdir = strdup(p->str))) { virReportOOMError(NULL); virConfFree(conf); return -1; } } p = virConfGetValue (conf, "vnc_listen"); CHECK_TYPE ("vnc_listen", VIR_CONF_STRING); if (p && p->str) { VIR_FREE(driver->vncListen); if (!(driver->vncListen = strdup(p->str))) { virReportOOMError(NULL); virConfFree(conf); return -1; } } p = virConfGetValue (conf, "vnc_password"); CHECK_TYPE ("vnc_password", VIR_CONF_STRING); if (p && p->str) { VIR_FREE(driver->vncPassword); if (!(driver->vncPassword = strdup(p->str))) { virReportOOMError(NULL); virConfFree(conf); return -1; } } p = virConfGetValue (conf, "security_driver"); CHECK_TYPE ("security_driver", VIR_CONF_STRING); if (p && p->str) { if (!(driver->securityDriverName = strdup(p->str))) { virReportOOMError(NULL); virConfFree(conf); return -1; } } p = virConfGetValue (conf, "vnc_sasl"); CHECK_TYPE ("vnc_sasl", VIR_CONF_LONG); if (p) driver->vncSASL = p->l; p = virConfGetValue (conf, "vnc_sasl_dir"); CHECK_TYPE ("vnc_sasl_dir", VIR_CONF_STRING); if (p && p->str) { VIR_FREE(driver->vncSASLdir); if (!(driver->vncSASLdir = strdup(p->str))) { virReportOOMError(NULL); virConfFree(conf); return -1; } } p = virConfGetValue (conf, "user"); CHECK_TYPE ("user", VIR_CONF_STRING); if (!(user = strdup(p && p->str ? p->str : QEMU_USER))) { virReportOOMError(NULL); virConfFree(conf); return -1; } if (virGetUserID(NULL, user, &driver->user) < 0) { VIR_FREE(user); virConfFree(conf); return -1; } VIR_FREE(user); p = virConfGetValue (conf, "group"); CHECK_TYPE ("group", VIR_CONF_STRING); if (!(group = strdup(p && p->str ? p->str : QEMU_GROUP))) { virReportOOMError(NULL); virConfFree(conf); return -1; } if (virGetGroupID(NULL, group, &driver->group) < 0) { VIR_FREE(group); virConfFree(conf); return -1; } VIR_FREE(group); virConfFree (conf); return 0; } /* The list of possible machine types for various architectures, as supported by QEMU - taken from 'qemu -M ?' for each arch */ static const char *const arch_info_hvm_x86_machines[] = { "pc", "isapc" }; static const char *const arch_info_hvm_arm_machines[] = { "versatilepb","integratorcp","versatileab","realview", "akita","spitz","borzoi","terrier","sx1-v1","sx1", "cheetah","n800","n810","lm3s811evb","lm3s6965evb", "connex","verdex","mainstone","musicpal","tosa", }; static const char *const arch_info_hvm_mips_machines[] = { "mips" }; static const char *const arch_info_hvm_sparc_machines[] = { "sun4m" }; static const char *const arch_info_hvm_ppc_machines[] = { "g3beige", "mac99", "prep" }; static const char *const arch_info_xen_x86_machines[] = { "xenner" }; struct qemu_feature_flags { const char *name; const int default_on; const int toggle; }; struct qemu_arch_info { const char *arch; int wordsize; const char *const *machines; int nmachines; const char *binary; const char *altbinary; const struct qemu_feature_flags *flags; int nflags; }; /* Feature flags for the architecture info */ static const struct qemu_feature_flags const arch_info_i686_flags [] = { { "pae", 1, 0 }, { "nonpae", 1, 0 }, { "acpi", 1, 1 }, { "apic", 1, 0 }, }; static const struct qemu_feature_flags const arch_info_x86_64_flags [] = { { "acpi", 1, 1 }, { "apic", 1, 0 }, }; /* The archicture tables for supported QEMU archs */ static const struct qemu_arch_info const arch_info_hvm[] = { { "i686", 32, arch_info_hvm_x86_machines, 2, "/usr/bin/qemu", "/usr/bin/qemu-system-x86_64", arch_info_i686_flags, 4 }, { "x86_64", 64, arch_info_hvm_x86_machines, 2, "/usr/bin/qemu-system-x86_64", NULL, arch_info_x86_64_flags, 2 }, { "arm", 32, arch_info_hvm_arm_machines, 20, "/usr/bin/qemu-system-arm", NULL, NULL, 0 }, { "mips", 32, arch_info_hvm_mips_machines, 1, "/usr/bin/qemu-system-mips", NULL, NULL, 0 }, { "mipsel", 32, arch_info_hvm_mips_machines, 1, "/usr/bin/qemu-system-mipsel", NULL, NULL, 0 }, { "sparc", 32, arch_info_hvm_sparc_machines, 1, "/usr/bin/qemu-system-sparc", NULL, NULL, 0 }, { "ppc", 32, arch_info_hvm_ppc_machines, 3, "/usr/bin/qemu-system-ppc", NULL, NULL, 0 }, }; static const struct qemu_arch_info const arch_info_xen[] = { { "i686", 32, arch_info_xen_x86_machines, 1, "/usr/bin/xenner", NULL, arch_info_i686_flags, 4 }, { "x86_64", 64, arch_info_xen_x86_machines, 1, "/usr/bin/xenner", NULL, arch_info_x86_64_flags, 2 }, }; static int qemudCapsInitGuest(virCapsPtr caps, const char *hostmachine, const struct qemu_arch_info *info, int hvm) { virCapsGuestPtr guest; int i; int hasbase = 0; int hasaltbase = 0; int haskvm = 0; int haskqemu = 0; const char *kvmbin = NULL; /* Check for existance of base emulator, or alternate base * which can be used with magic cpu choice */ hasbase = (access(info->binary, X_OK) == 0); hasaltbase = (info->altbinary && access(info->altbinary, X_OK) == 0); /* Can use acceleration for KVM/KQEMU if * - host & guest arches match * Or * - hostarch is x86_64 and guest arch is i686 * The latter simply needs "-cpu qemu32" */ if (STREQ(info->arch, hostmachine) || (STREQ(hostmachine, "x86_64") && STREQ(info->arch, "i686"))) { const char *const kvmbins[] = { "/usr/bin/qemu-kvm", /* Fedora */ "/usr/bin/kvm" }; /* Upstream .spec */ for (i = 0; i < ARRAY_CARDINALITY(kvmbins); ++i) { if (access(kvmbins[i], X_OK) == 0 && access("/dev/kvm", F_OK) == 0) { haskvm = 1; kvmbin = kvmbins[i]; break; } } if (access("/dev/kqemu", F_OK) == 0) haskqemu = 1; } if (!hasbase && !hasaltbase && !haskvm) return 0; /* We register kvm as the base emulator too, since we can * just give -no-kvm to disable acceleration if required */ if ((guest = virCapabilitiesAddGuest(caps, hvm ? "hvm" : "xen", info->arch, info->wordsize, (hasbase ? info->binary : (hasaltbase ? info->altbinary : kvmbin)), NULL, info->nmachines, info->machines)) == NULL) return -1; if (hvm) { if (virCapabilitiesAddGuestDomain(guest, "qemu", NULL, NULL, 0, NULL) == NULL) return -1; if (haskqemu && virCapabilitiesAddGuestDomain(guest, "kqemu", NULL, NULL, 0, NULL) == NULL) return -1; if (haskvm && virCapabilitiesAddGuestDomain(guest, "kvm", kvmbin, NULL, 0, NULL) == NULL) return -1; } else { if (virCapabilitiesAddGuestDomain(guest, "kvm", NULL, NULL, 0, NULL) == NULL) return -1; } if (info->nflags) { for (i = 0 ; i < info->nflags ; i++) { if (virCapabilitiesAddGuestFeature(guest, info->flags[i].name, info->flags[i].default_on, info->flags[i].toggle) == NULL) return -1; } } return 0; } virCapsPtr qemudCapsInit(void) { struct utsname utsname; virCapsPtr caps; int i; /* Really, this never fails - look at the man-page. */ uname (&utsname); if ((caps = virCapabilitiesNew(utsname.machine, 1, 1)) == NULL) goto no_memory; /* Using KVM's mac prefix for QEMU too */ virCapabilitiesSetMacPrefix(caps, (unsigned char[]){ 0x52, 0x54, 0x00 }); /* Some machines have problematic NUMA toplogy causing * unexpected failures. We don't want to break the QEMU * driver in this scenario, so log errors & carry on */ if (nodeCapsInitNUMA(caps) < 0) { virCapabilitiesFreeNUMAInfo(caps); VIR_WARN0("Failed to query host NUMA topology, disabling NUMA capabilities"); } virCapabilitiesAddHostMigrateTransport(caps, "tcp"); /* First the pure HVM guests */ for (i = 0 ; i < ARRAY_CARDINALITY(arch_info_hvm) ; i++) if (qemudCapsInitGuest(caps, utsname.machine, &arch_info_hvm[i], 1) < 0) goto no_memory; /* Then possibly the Xen paravirt guests (ie Xenner */ if (access("/usr/bin/xenner", X_OK) == 0 && access("/dev/kvm", F_OK) == 0) { for (i = 0 ; i < ARRAY_CARDINALITY(arch_info_xen) ; i++) /* Allow Xen 32-on-32, 32-on-64 and 64-on-64 */ if (STREQ(arch_info_xen[i].arch, utsname.machine) || (STREQ(utsname.machine, "x86_64") && STREQ(arch_info_xen[i].arch, "i686"))) { if (qemudCapsInitGuest(caps, utsname.machine, &arch_info_xen[i], 0) < 0) goto no_memory; } } /* QEMU Requires an emulator in the XML */ virCapabilitiesSetEmulatorRequired(caps); return caps; no_memory: virCapabilitiesFree(caps); return NULL; } static unsigned int qemudComputeCmdFlags(const char *help, unsigned int version, unsigned int is_kvm, unsigned int kvm_version) { unsigned int flags = 0; if (strstr(help, "-no-kqemu")) flags |= QEMUD_CMD_FLAG_KQEMU; if (strstr(help, "-no-kvm")) flags |= QEMUD_CMD_FLAG_KVM; if (strstr(help, "-no-reboot")) flags |= QEMUD_CMD_FLAG_NO_REBOOT; if (strstr(help, "-name")) flags |= QEMUD_CMD_FLAG_NAME; if (strstr(help, "-uuid")) flags |= QEMUD_CMD_FLAG_UUID; if (strstr(help, "-domid")) flags |= QEMUD_CMD_FLAG_DOMID; if (strstr(help, "-drive")) { flags |= QEMUD_CMD_FLAG_DRIVE; if (strstr(help, "cache=writethrough|writeback|none")) flags |= QEMUD_CMD_FLAG_DRIVE_CACHE_V2; if (strstr(help, "format=")) flags |= QEMUD_CMD_FLAG_DRIVE_FORMAT; } if (strstr(help, "-vga") && !strstr(help, "-std-vga")) flags |= QEMUD_CMD_FLAG_VGA; if (strstr(help, "boot=on")) flags |= QEMUD_CMD_FLAG_DRIVE_BOOT; if (version >= 9000) flags |= QEMUD_CMD_FLAG_VNC_COLON; if (is_kvm && (version >= 10000 || kvm_version >= 74)) flags |= QEMUD_CMD_FLAG_VNET_HDR; /* * Handling of -incoming arg with varying features * -incoming tcp (kvm >= 79, qemu >= 0.10.0) * -incoming exec (kvm >= 80, qemu >= 0.10.0) * -incoming stdio (all earlier kvm) * * NB, there was a pre-kvm-79 'tcp' support, but it * was broken, because it blocked the monitor console * while waiting for data, so pretend it doesn't exist */ if (version >= 10000) { flags |= QEMUD_CMD_FLAG_MIGRATE_QEMU_TCP; flags |= QEMUD_CMD_FLAG_MIGRATE_QEMU_EXEC; } else if (kvm_version >= 79) { flags |= QEMUD_CMD_FLAG_MIGRATE_QEMU_TCP; if (kvm_version >= 80) flags |= QEMUD_CMD_FLAG_MIGRATE_QEMU_EXEC; } else if (kvm_version > 0) { flags |= QEMUD_CMD_FLAG_MIGRATE_KVM_STDIO; } if (version >= 10000) flags |= QEMUD_CMD_FLAG_0_10; return flags; } /* We parse the output of 'qemu -help' to get the QEMU * version number. The first bit is easy, just parse * 'QEMU PC emulator version x.y.z'. * * With qemu-kvm, however, that is followed by a string * in parenthesis as follows: * - qemu-kvm-x.y.z in stable releases * - kvm-XX for kvm versions up to kvm-85 * - qemu-kvm-devel-XX for kvm version kvm-86 and later * * For qemu-kvm versions before 0.10.z, we need to detect * the KVM version number for some features. With 0.10.z * and later, we just need the QEMU version number and * whether it is KVM QEMU or mainline QEMU. */ #define QEMU_VERSION_STR "QEMU PC emulator version" #define QEMU_KVM_VER_PREFIX "(qemu-kvm-" #define KVM_VER_PREFIX "(kvm-" #define SKIP_BLANKS(p) do { while ((*(p) == ' ') || (*(p) == '\t')) (p)++; } while (0) int qemudParseHelpStr(const char *help, unsigned int *flags, unsigned int *version, unsigned int *is_kvm, unsigned int *kvm_version) { unsigned major, minor, micro; const char *p = help; *flags = *version = *is_kvm = *kvm_version = 0; if (!STRPREFIX(p, QEMU_VERSION_STR)) goto fail; p += strlen(QEMU_VERSION_STR); SKIP_BLANKS(p); major = virParseNumber(&p); if (major == -1 || *p != '.') goto fail; ++p; minor = virParseNumber(&p); if (major == -1 || *p != '.') goto fail; ++p; micro = virParseNumber(&p); if (major == -1) goto fail; SKIP_BLANKS(p); if (STRPREFIX(p, QEMU_KVM_VER_PREFIX)) { *is_kvm = 1; p += strlen(QEMU_KVM_VER_PREFIX); } else if (STRPREFIX(p, KVM_VER_PREFIX)) { int ret; *is_kvm = 1; p += strlen(KVM_VER_PREFIX); ret = virParseNumber(&p); if (ret == -1) goto fail; *kvm_version = ret; } *version = (major * 1000 * 1000) + (minor * 1000) + micro; *flags = qemudComputeCmdFlags(help, *version, *is_kvm, *kvm_version); qemudDebug("Version %u.%u.%u, cooked version %u, flags %u", major, minor, micro, *version, *flags); if (*kvm_version) qemudDebug("KVM version %d detected", *kvm_version); else if (*is_kvm) qemudDebug("qemu-kvm version %u.%u.%u detected", major, minor, micro); return 0; fail: p = strchr(help, '\n'); if (p) p = strndup(help, p - help); qemudReportError(NULL, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot parse QEMU version number in '%s'"), p ? p : help); VIR_FREE(p); return -1; } int qemudExtractVersionInfo(const char *qemu, unsigned int *retversion, unsigned int *retflags) { const char *const qemuarg[] = { qemu, "-help", NULL }; const char *const qemuenv[] = { "LC_ALL=C", NULL }; pid_t child; int newstdout = -1; int ret = -1, status; unsigned int version, is_kvm, kvm_version; unsigned int flags = 0; if (retflags) *retflags = 0; if (retversion) *retversion = 0; if (virExec(NULL, qemuarg, qemuenv, NULL, &child, -1, &newstdout, NULL, VIR_EXEC_CLEAR_CAPS) < 0) return -1; char *help = NULL; enum { MAX_HELP_OUTPUT_SIZE = 1024*64 }; int len = virFileReadLimFD(newstdout, MAX_HELP_OUTPUT_SIZE, &help); if (len < 0) { virReportSystemError(NULL, errno, "%s", _("Unable to read QEMU help output")); goto cleanup2; } if (qemudParseHelpStr(help, &flags, &version, &is_kvm, &kvm_version) == -1) goto cleanup2; if (retversion) *retversion = version; if (retflags) *retflags = flags; ret = 0; cleanup2: VIR_FREE(help); if (close(newstdout) < 0) ret = -1; rewait: if (waitpid(child, &status, 0) != child) { if (errno == EINTR) goto rewait; VIR_ERROR(_("Unexpected exit status from qemu %d pid %lu"), WEXITSTATUS(status), (unsigned long)child); ret = -1; } /* Check & log unexpected exit status, but don't fail, * as there's really no need to throw an error if we did * actually read a valid version number above */ if (WEXITSTATUS(status) != 0) { VIR_WARN(_("Unexpected exit status '%d', qemu probably failed"), WEXITSTATUS(status)); } return ret; } static void uname_normalize (struct utsname *ut) { uname(ut); /* Map i386, i486, i586 to i686. */ if (ut->machine[0] == 'i' && ut->machine[1] != '\0' && ut->machine[2] == '8' && ut->machine[3] == '6' && ut->machine[4] == '\0') ut->machine[1] = '6'; } int qemudExtractVersion(virConnectPtr conn, struct qemud_driver *driver) { const char *binary; struct stat sb; struct utsname ut; if (driver->qemuVersion > 0) return 0; uname_normalize(&ut); if ((binary = virCapabilitiesDefaultGuestEmulator(driver->caps, "hvm", ut.machine, "qemu")) == NULL) return -1; if (stat(binary, &sb) < 0) { char ebuf[1024]; qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("Cannot find QEMU binary %s: %s"), binary, virStrerror(errno, ebuf, sizeof ebuf)); return -1; } if (qemudExtractVersionInfo(binary, &driver->qemuVersion, NULL) < 0) { return -1; } return 0; } static int qemudNetworkIfaceConnect(virConnectPtr conn, struct qemud_driver *driver, virDomainNetDefPtr net, int vnet_hdr) { char *brname; int err; int tapfd = -1; if (net->type == VIR_DOMAIN_NET_TYPE_NETWORK) { virNetworkPtr network = virNetworkLookupByName(conn, net->data.network.name); if (!network) return -1; brname = virNetworkGetBridgeName(network); virNetworkFree(network); if (brname == NULL) return -1; } else if (net->type == VIR_DOMAIN_NET_TYPE_BRIDGE) { brname = net->data.bridge.brname; } else { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("Network type %d is not supported"), net->type); return -1; } if (!net->ifname || STRPREFIX(net->ifname, "vnet") || strchr(net->ifname, '%')) { VIR_FREE(net->ifname); if (!(net->ifname = strdup("vnet%d"))) { virReportOOMError(conn); return -1; } } char ebuf[1024]; if (!driver->brctl && (err = brInit(&driver->brctl))) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot initialize bridge support: %s"), virStrerror(err, ebuf, sizeof ebuf)); return -1; } if ((err = brAddTap(driver->brctl, brname, &net->ifname, vnet_hdr, &tapfd))) { if (errno == ENOTSUP) { /* In this particular case, give a better diagnostic. */ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("Failed to add tap interface to bridge. " "%s is not a bridge device"), brname); } else { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("Failed to add tap interface '%s' " "to bridge '%s' : %s"), net->ifname, brname, virStrerror(err, ebuf, sizeof ebuf)); } return -1; } return tapfd; } static int qemuBuildNicStr(virConnectPtr conn, virDomainNetDefPtr net, const char *prefix, char type_sep, int vlan, char **str) { if (virAsprintf(str, "%snic%cmacaddr=%02x:%02x:%02x:%02x:%02x:%02x,vlan=%d%s%s", prefix ? prefix : "", type_sep, net->mac[0], net->mac[1], net->mac[2], net->mac[3], net->mac[4], net->mac[5], vlan, (net->model ? ",model=" : ""), (net->model ? net->model : "")) < 0) { virReportOOMError(conn); return -1; } return 0; } static int qemuBuildHostNetStr(virConnectPtr conn, virDomainNetDefPtr net, const char *prefix, char type_sep, int vlan, int tapfd, char **str) { switch (net->type) { case VIR_DOMAIN_NET_TYPE_NETWORK: case VIR_DOMAIN_NET_TYPE_BRIDGE: if (virAsprintf(str, "%stap%cfd=%d,vlan=%d", prefix ? prefix : "", type_sep, tapfd, vlan) < 0) { virReportOOMError(conn); return -1; } break; case VIR_DOMAIN_NET_TYPE_ETHERNET: { virBuffer buf = VIR_BUFFER_INITIALIZER; if (prefix) virBufferAdd(&buf, prefix, strlen(prefix)); virBufferAddLit(&buf, "tap"); if (net->ifname) { virBufferVSprintf(&buf, "%cifname=%s", type_sep, net->ifname); type_sep = ','; } if (net->data.ethernet.script) { virBufferVSprintf(&buf, "%cscript=%s", type_sep, net->data.ethernet.script); type_sep = ','; } virBufferVSprintf(&buf, "%cvlan=%d", type_sep, vlan); if (virBufferError(&buf)) { virReportOOMError(conn); return -1; } *str = virBufferContentAndReset(&buf); } break; case VIR_DOMAIN_NET_TYPE_CLIENT: case VIR_DOMAIN_NET_TYPE_SERVER: case VIR_DOMAIN_NET_TYPE_MCAST: { const char *mode = NULL; switch (net->type) { case VIR_DOMAIN_NET_TYPE_CLIENT: mode = "connect"; break; case VIR_DOMAIN_NET_TYPE_SERVER: mode = "listen"; break; case VIR_DOMAIN_NET_TYPE_MCAST: mode = "mcast"; break; } if (virAsprintf(str, "%ssocket%c%s=%s:%d,vlan=%d", prefix ? prefix : "", type_sep, mode, net->data.socket.address, net->data.socket.port, vlan) < 0) { virReportOOMError(conn); return -1; } } break; case VIR_DOMAIN_NET_TYPE_USER: default: if (virAsprintf(str, "%suser%cvlan=%d", prefix ? prefix : "", type_sep, vlan) < 0) { virReportOOMError(conn); return -1; } break; } return 0; } static int qemudBuildCommandLineChrDevStr(virDomainChrDefPtr dev, char *buf, int buflen) { switch (dev->type) { case VIR_DOMAIN_CHR_TYPE_NULL: strncpy(buf, "null", buflen); buf[buflen-1] = '\0'; break; case VIR_DOMAIN_CHR_TYPE_VC: strncpy(buf, "vc", buflen); buf[buflen-1] = '\0'; break; case VIR_DOMAIN_CHR_TYPE_PTY: strncpy(buf, "pty", buflen); buf[buflen-1] = '\0'; break; case VIR_DOMAIN_CHR_TYPE_DEV: if (snprintf(buf, buflen, "%s", dev->data.file.path) >= buflen) return -1; break; case VIR_DOMAIN_CHR_TYPE_FILE: if (snprintf(buf, buflen, "file:%s", dev->data.file.path) >= buflen) return -1; break; case VIR_DOMAIN_CHR_TYPE_PIPE: if (snprintf(buf, buflen, "pipe:%s", dev->data.file.path) >= buflen) return -1; break; case VIR_DOMAIN_CHR_TYPE_STDIO: strncpy(buf, "stdio", buflen); buf[buflen-1] = '\0'; break; case VIR_DOMAIN_CHR_TYPE_UDP: if (snprintf(buf, buflen, "udp:%s:%s@%s:%s", dev->data.udp.connectHost, dev->data.udp.connectService, dev->data.udp.bindHost, dev->data.udp.bindService) >= buflen) return -1; break; case VIR_DOMAIN_CHR_TYPE_TCP: if (dev->data.tcp.protocol == VIR_DOMAIN_CHR_TCP_PROTOCOL_TELNET) { if (snprintf(buf, buflen, "telnet:%s:%s%s", dev->data.tcp.host, dev->data.tcp.service, dev->data.tcp.listen ? ",server,nowait" : "") >= buflen) return -1; } else { if (snprintf(buf, buflen, "tcp:%s:%s%s", dev->data.tcp.host, dev->data.tcp.service, dev->data.tcp.listen ? ",server,nowait" : "") >= buflen) return -1; } break; case VIR_DOMAIN_CHR_TYPE_UNIX: if (snprintf(buf, buflen, "unix:%s%s", dev->data.nix.path, dev->data.nix.listen ? ",server,nowait" : "") >= buflen) return -1; break; } return 0; } /* * Constructs a argv suitable for launching qemu with config defined * for a given virtual machine. */ int qemudBuildCommandLine(virConnectPtr conn, struct qemud_driver *driver, virDomainDefPtr def, virDomainChrDefPtr monitor_chr, unsigned int qemuCmdFlags, const char ***retargv, const char ***retenv, int **tapfds, int *ntapfds, const char *migrateFrom) { int i; char memory[50]; char vcpus[50]; char boot[VIR_DOMAIN_BOOT_LAST]; struct utsname ut; int disableKQEMU = 0; int disableKVM = 0; int qargc = 0, qarga = 0; const char **qargv = NULL; int qenvc = 0, qenva = 0; const char **qenv = NULL; const char *emulator; char uuid[VIR_UUID_STRING_BUFLEN]; char domid[50]; const char *cpu = NULL; uname_normalize(&ut); virUUIDFormat(def->uuid, uuid); /* Migration is very annoying due to wildly varying syntax & capabilities * over time of KVM / QEMU codebases */ if (migrateFrom) { if (STRPREFIX(migrateFrom, "tcp")) { if (!(qemuCmdFlags & QEMUD_CMD_FLAG_MIGRATE_QEMU_TCP)) { qemudReportError(conn, NULL, NULL, VIR_ERR_NO_SUPPORT, "%s", _("TCP migration is not supported with this QEMU binary")); return -1; } } else if (STREQ(migrateFrom, "stdio")) { if (qemuCmdFlags & QEMUD_CMD_FLAG_MIGRATE_QEMU_EXEC) { migrateFrom = "exec:cat"; } else if (!(qemuCmdFlags & QEMUD_CMD_FLAG_MIGRATE_KVM_STDIO)) { qemudReportError(conn, NULL, NULL, VIR_ERR_NO_SUPPORT, "%s", _("STDIO migration is not supported with this QEMU binary")); return -1; } } else if (STRPREFIX(migrateFrom, "exec")) { if (!(qemuCmdFlags & QEMUD_CMD_FLAG_MIGRATE_QEMU_EXEC)) { qemudReportError(conn, NULL, NULL, VIR_ERR_NO_SUPPORT, "%s", _("STDIO migration is not supported with this QEMU binary")); return -1; } } } emulator = def->emulator; /* Need to explicitly disable KQEMU if * 1. Arch matches host arch * 2. Guest domain is 'qemu' * 3. The qemu binary has the -no-kqemu flag */ if ((qemuCmdFlags & QEMUD_CMD_FLAG_KQEMU) && STREQ(ut.machine, def->os.arch) && def->virtType == VIR_DOMAIN_VIRT_QEMU) disableKQEMU = 1; /* Need to explicitly disable KVM if * 1. Arch matches host arch * 2. Guest domain is 'qemu' * 3. The qemu binary has the -no-kvm flag */ if ((qemuCmdFlags & QEMUD_CMD_FLAG_KVM) && STREQ(ut.machine, def->os.arch) && def->virtType == VIR_DOMAIN_VIRT_QEMU) disableKVM = 1; /* * Need to force a 32-bit guest CPU type if * * 1. guest OS is i686 * 2. host OS is x86_64 * 3. emulator is qemu-kvm or kvm * * Or * * 1. guest OS is i686 * 2. emulator is qemu-system-x86_64 */ if (STREQ(def->os.arch, "i686") && ((STREQ(ut.machine, "x86_64") && strstr(emulator, "kvm")) || strstr(emulator, "x86_64"))) cpu = "qemu32"; #define ADD_ARG_SPACE \ do { \ if (qargc == qarga) { \ qarga += 10; \ if (VIR_REALLOC_N(qargv, qarga) < 0) \ goto no_memory; \ } \ } while (0) #define ADD_ARG(thisarg) \ do { \ ADD_ARG_SPACE; \ qargv[qargc++] = thisarg; \ } while (0) #define ADD_ARG_LIT(thisarg) \ do { \ ADD_ARG_SPACE; \ if ((qargv[qargc++] = strdup(thisarg)) == NULL) \ goto no_memory; \ } while (0) #define ADD_USBDISK(thisarg) \ do { \ ADD_ARG_LIT("-usbdevice"); \ ADD_ARG_SPACE; \ if ((virAsprintf((char **)&(qargv[qargc++]), \ "disk:%s", thisarg)) == -1) { \ goto no_memory; \ } \ } while (0) #define ADD_ENV_SPACE \ do { \ if (qenvc == qenva) { \ qenva += 10; \ if (VIR_REALLOC_N(qenv, qenva) < 0) \ goto no_memory; \ } \ } while (0) #define ADD_ENV(thisarg) \ do { \ ADD_ENV_SPACE; \ qenv[qenvc++] = thisarg; \ } while (0) #define ADD_ENV_LIT(thisarg) \ do { \ ADD_ENV_SPACE; \ if ((qenv[qenvc++] = strdup(thisarg)) == NULL) \ goto no_memory; \ } while (0) #define ADD_ENV_PAIR(envname, val) \ do { \ char *envval; \ ADD_ENV_SPACE; \ if (virAsprintf(&envval, "%s=%s", envname, val) < 0) \ goto no_memory; \ qenv[qenvc++] = envval; \ } while (0) #define ADD_ENV_COPY(envname) \ do { \ char *val = getenv(envname); \ if (val != NULL) { \ ADD_ENV_PAIR(envname, val); \ } \ } while (0) /* Set '-m MB' based on maxmem, because the lower 'memory' limit * is set post-startup using the balloon driver. If balloon driver * is not supported, then they're out of luck anyway */ snprintf(memory, sizeof(memory), "%lu", def->maxmem/1024); snprintf(vcpus, sizeof(vcpus), "%lu", def->vcpus); snprintf(domid, sizeof(domid), "%d", def->id); ADD_ENV_LIT("LC_ALL=C"); ADD_ENV_COPY("LD_PRELOAD"); ADD_ENV_COPY("LD_LIBRARY_PATH"); ADD_ENV_COPY("PATH"); ADD_ENV_COPY("HOME"); ADD_ENV_COPY("USER"); ADD_ENV_COPY("LOGNAME"); ADD_ENV_COPY("TMPDIR"); ADD_ARG_LIT(emulator); ADD_ARG_LIT("-S"); /* This should *never* be NULL, since we always provide * a machine in the capabilities data for QEMU. So this * check is just here as a safety in case the unexpected * happens */ if (def->os.machine) { ADD_ARG_LIT("-M"); ADD_ARG_LIT(def->os.machine); } if (cpu) { ADD_ARG_LIT("-cpu"); ADD_ARG_LIT(cpu); } if (disableKQEMU) ADD_ARG_LIT("-no-kqemu"); if (disableKVM) ADD_ARG_LIT("-no-kvm"); ADD_ARG_LIT("-m"); ADD_ARG_LIT(memory); ADD_ARG_LIT("-smp"); ADD_ARG_LIT(vcpus); if (qemuCmdFlags & QEMUD_CMD_FLAG_NAME) { ADD_ARG_LIT("-name"); ADD_ARG_LIT(def->name); } if (qemuCmdFlags & QEMUD_CMD_FLAG_UUID) { ADD_ARG_LIT("-uuid"); ADD_ARG_LIT(uuid); } if (qemuCmdFlags & QEMUD_CMD_FLAG_DOMID) { ADD_ARG_LIT("-domid"); ADD_ARG_LIT(domid); } /* * NB, -nographic *MUST* come before any serial, or monitor * or parallel port flags due to QEMU craziness, where it * decides to change the serial port & monitor to be on stdout * if you ask for nographic. So we have to make sure we override * these defaults ourselves... */ if (!def->graphics) ADD_ARG_LIT("-nographic"); if (monitor_chr) { char buf[4096]; if (qemudBuildCommandLineChrDevStr(monitor_chr, buf, sizeof(buf)) < 0) goto error; ADD_ARG_LIT("-monitor"); ADD_ARG_LIT(buf); } if (def->localtime) ADD_ARG_LIT("-localtime"); if ((qemuCmdFlags & QEMUD_CMD_FLAG_NO_REBOOT) && def->onReboot != VIR_DOMAIN_LIFECYCLE_RESTART) ADD_ARG_LIT("-no-reboot"); if (!(def->features & (1 << VIR_DOMAIN_FEATURE_ACPI))) ADD_ARG_LIT("-no-acpi"); if (!def->os.bootloader) { for (i = 0 ; i < def->os.nBootDevs ; i++) { switch (def->os.bootDevs[i]) { case VIR_DOMAIN_BOOT_CDROM: boot[i] = 'd'; break; case VIR_DOMAIN_BOOT_FLOPPY: boot[i] = 'a'; break; case VIR_DOMAIN_BOOT_DISK: boot[i] = 'c'; break; case VIR_DOMAIN_BOOT_NET: boot[i] = 'n'; break; default: boot[i] = 'c'; break; } } boot[def->os.nBootDevs] = '\0'; ADD_ARG_LIT("-boot"); ADD_ARG_LIT(boot); if (def->os.kernel) { ADD_ARG_LIT("-kernel"); ADD_ARG_LIT(def->os.kernel); } if (def->os.initrd) { ADD_ARG_LIT("-initrd"); ADD_ARG_LIT(def->os.initrd); } if (def->os.cmdline) { ADD_ARG_LIT("-append"); ADD_ARG_LIT(def->os.cmdline); } } else { ADD_ARG_LIT("-bootloader"); ADD_ARG_LIT(def->os.bootloader); } for (i = 0 ; i < def->ndisks ; i++) { virDomainDiskDefPtr disk = def->disks[i]; if (disk->driverName != NULL && !STREQ(disk->driverName, "qemu")) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("unsupported driver name '%s' for disk '%s'"), disk->driverName, disk->src); goto error; } } /* If QEMU supports -drive param instead of old -hda, -hdb, -cdrom .. */ if (qemuCmdFlags & QEMUD_CMD_FLAG_DRIVE) { int bootCD = 0, bootFloppy = 0, bootDisk = 0; /* If QEMU supports boot=on for -drive param... */ if (qemuCmdFlags & QEMUD_CMD_FLAG_DRIVE_BOOT) { for (i = 0 ; i < def->os.nBootDevs ; i++) { switch (def->os.bootDevs[i]) { case VIR_DOMAIN_BOOT_CDROM: bootCD = 1; break; case VIR_DOMAIN_BOOT_FLOPPY: bootFloppy = 1; break; case VIR_DOMAIN_BOOT_DISK: bootDisk = 1; break; } } } for (i = 0 ; i < def->ndisks ; i++) { virBuffer opt = VIR_BUFFER_INITIALIZER; char *optstr; int bootable = 0; virDomainDiskDefPtr disk = def->disks[i]; int idx = virDiskNameToIndex(disk->dst); const char *bus = virDomainDiskQEMUBusTypeToString(disk->bus); if (disk->bus == VIR_DOMAIN_DISK_BUS_USB) { if (disk->device == VIR_DOMAIN_DISK_DEVICE_DISK) { ADD_USBDISK(disk->src); } else { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("unsupported usb disk type for '%s'"), disk->src); goto error; } continue; } if (idx < 0) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("unsupported disk type '%s'"), disk->dst); goto error; } switch (disk->device) { case VIR_DOMAIN_DISK_DEVICE_CDROM: bootable = bootCD; bootCD = 0; break; case VIR_DOMAIN_DISK_DEVICE_FLOPPY: bootable = bootFloppy; bootFloppy = 0; break; case VIR_DOMAIN_DISK_DEVICE_DISK: bootable = bootDisk; bootDisk = 0; break; } virBufferVSprintf(&opt, "file=%s", disk->src ? disk->src : ""); virBufferVSprintf(&opt, ",if=%s", bus); if (disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM) virBufferAddLit(&opt, ",media=cdrom"); virBufferVSprintf(&opt, ",index=%d", idx); if (bootable && disk->device == VIR_DOMAIN_DISK_DEVICE_DISK) virBufferAddLit(&opt, ",boot=on"); if (disk->driverType && qemuCmdFlags & QEMUD_CMD_FLAG_DRIVE_FORMAT) virBufferVSprintf(&opt, ",format=%s", disk->driverType); if (disk->cachemode) { const char *mode = (qemuCmdFlags & QEMUD_CMD_FLAG_DRIVE_CACHE_V2) ? qemuDiskCacheV2TypeToString(disk->cachemode) : qemuDiskCacheV1TypeToString(disk->cachemode); virBufferVSprintf(&opt, ",cache=%s", mode); } else if (disk->shared && !disk->readonly) { virBufferAddLit(&opt, ",cache=off"); } if (virBufferError(&opt)) { virReportOOMError(conn); goto error; } optstr = virBufferContentAndReset(&opt); ADD_ARG_LIT("-drive"); ADD_ARG(optstr); } } else { for (i = 0 ; i < def->ndisks ; i++) { char dev[NAME_MAX]; char file[PATH_MAX]; virDomainDiskDefPtr disk = def->disks[i]; if (disk->bus == VIR_DOMAIN_DISK_BUS_USB) { if (disk->device == VIR_DOMAIN_DISK_DEVICE_DISK) { ADD_USBDISK(disk->src); } else { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("unsupported usb disk type for '%s'"), disk->src); goto error; } continue; } if (STREQ(disk->dst, "hdc") && disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM) { if (disk->src) { snprintf(dev, NAME_MAX, "-%s", "cdrom"); } else { continue; } } else { if (STRPREFIX(disk->dst, "hd") || STRPREFIX(disk->dst, "fd")) { snprintf(dev, NAME_MAX, "-%s", disk->dst); } else { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("unsupported disk type '%s'"), disk->dst); goto error; } } snprintf(file, PATH_MAX, "%s", disk->src); ADD_ARG_LIT(dev); ADD_ARG_LIT(file); } } if (!def->nnets) { ADD_ARG_LIT("-net"); ADD_ARG_LIT("none"); } else { for (i = 0 ; i < def->nnets ; i++) { virDomainNetDefPtr net = def->nets[i]; char *nic, *host; int tapfd = -1; if (qemuBuildNicStr(conn, net, NULL, ',', i, &nic) < 0) goto error; ADD_ARG_LIT("-net"); ADD_ARG(nic); if (net->type == VIR_DOMAIN_NET_TYPE_NETWORK || net->type == VIR_DOMAIN_NET_TYPE_BRIDGE) { int vnet_hdr = 0; if (qemuCmdFlags & QEMUD_CMD_FLAG_VNET_HDR && net->model && STREQ(net->model, "virtio")) vnet_hdr = 1; tapfd = qemudNetworkIfaceConnect(conn, driver, net, vnet_hdr); if (tapfd < 0) goto error; if (VIR_REALLOC_N(*tapfds, (*ntapfds)+1) < 0) { close(tapfd); goto no_memory; } (*tapfds)[(*ntapfds)++] = tapfd; } if (qemuBuildHostNetStr(conn, net, NULL, ',', i, tapfd, &host) < 0) goto error; ADD_ARG_LIT("-net"); ADD_ARG(host); } } if (!def->nserials) { ADD_ARG_LIT("-serial"); ADD_ARG_LIT("none"); } else { for (i = 0 ; i < def->nserials ; i++) { char buf[4096]; virDomainChrDefPtr serial = def->serials[i]; if (qemudBuildCommandLineChrDevStr(serial, buf, sizeof(buf)) < 0) goto error; ADD_ARG_LIT("-serial"); ADD_ARG_LIT(buf); } } if (!def->nparallels) { ADD_ARG_LIT("-parallel"); ADD_ARG_LIT("none"); } else { for (i = 0 ; i < def->nparallels ; i++) { char buf[4096]; virDomainChrDefPtr parallel = def->parallels[i]; if (qemudBuildCommandLineChrDevStr(parallel, buf, sizeof(buf)) < 0) goto error; ADD_ARG_LIT("-parallel"); ADD_ARG_LIT(buf); } } ADD_ARG_LIT("-usb"); for (i = 0 ; i < def->ninputs ; i++) { virDomainInputDefPtr input = def->inputs[i]; if (input->bus == VIR_DOMAIN_INPUT_BUS_USB) { ADD_ARG_LIT("-usbdevice"); ADD_ARG_LIT(input->type == VIR_DOMAIN_INPUT_TYPE_MOUSE ? "mouse" : "tablet"); } } if ((def->ngraphics == 1) && def->graphics[0]->type == VIR_DOMAIN_GRAPHICS_TYPE_VNC) { virBuffer opt = VIR_BUFFER_INITIALIZER; char *optstr; if (qemuCmdFlags & QEMUD_CMD_FLAG_VNC_COLON) { if (def->graphics[0]->data.vnc.listenAddr) virBufferAdd(&opt, def->graphics[0]->data.vnc.listenAddr, -1); else if (driver->vncListen) virBufferAdd(&opt, driver->vncListen, -1); virBufferVSprintf(&opt, ":%d", def->graphics[0]->data.vnc.port - 5900); if (def->graphics[0]->data.vnc.passwd || driver->vncPassword) virBufferAddLit(&opt, ",password"); if (driver->vncTLS) { virBufferAddLit(&opt, ",tls"); if (driver->vncTLSx509verify) { virBufferVSprintf(&opt, ",x509verify=%s", driver->vncTLSx509certdir); } else { virBufferVSprintf(&opt, ",x509=%s", driver->vncTLSx509certdir); } } if (driver->vncSASL) { virBufferAddLit(&opt, ",sasl"); if (driver->vncSASLdir) ADD_ENV_PAIR("SASL_CONF_DIR", driver->vncSASLdir); /* TODO: Support ACLs later */ } } else { virBufferVSprintf(&opt, "%d", def->graphics[0]->data.vnc.port - 5900); } if (virBufferError(&opt)) goto no_memory; optstr = virBufferContentAndReset(&opt); ADD_ARG_LIT("-vnc"); ADD_ARG(optstr); if (def->graphics[0]->data.vnc.keymap) { ADD_ARG_LIT("-k"); ADD_ARG_LIT(def->graphics[0]->data.vnc.keymap); } } else if ((def->ngraphics == 1) && def->graphics[0]->type == VIR_DOMAIN_GRAPHICS_TYPE_SDL) { char *xauth = NULL; char *display = NULL; if (def->graphics[0]->data.sdl.xauth && virAsprintf(&xauth, "XAUTHORITY=%s", def->graphics[0]->data.sdl.xauth) < 0) goto no_memory; if (def->graphics[0]->data.sdl.display && virAsprintf(&display, "DISPLAY=%s", def->graphics[0]->data.sdl.display) < 0) { VIR_FREE(xauth); goto no_memory; } if (xauth) ADD_ENV(xauth); if (display) ADD_ENV(display); if (def->graphics[0]->data.sdl.fullscreen) ADD_ARG_LIT("-full-screen"); } if (def->nvideos) { if (def->nvideos > 1) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, "%s", _("only one video card is currentely supported")); goto error; } if (qemuCmdFlags & QEMUD_CMD_FLAG_VGA) { if (def->videos[0]->type == VIR_DOMAIN_VIDEO_TYPE_XEN) { /* nothing - vga has no effect on Xen pvfb */ } else { const char *vgastr = qemuVideoTypeToString(def->videos[0]->type); if (!vgastr) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("video type %s is not supported with QEMU"), virDomainVideoTypeToString(def->videos[0]->type)); goto error; } ADD_ARG_LIT("-vga"); ADD_ARG_LIT(vgastr); } } else { switch (def->videos[0]->type) { case VIR_DOMAIN_VIDEO_TYPE_VGA: ADD_ARG_LIT("-std-vga"); break; case VIR_DOMAIN_VIDEO_TYPE_VMVGA: ADD_ARG_LIT("-vmwarevga"); break; case VIR_DOMAIN_VIDEO_TYPE_XEN: case VIR_DOMAIN_VIDEO_TYPE_CIRRUS: /* No special args - this is the default */ break; default: qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("video type %s is not supported with QEMU"), virDomainVideoTypeToString(def->videos[0]->type)); goto error; } } } /* Add sound hardware */ if (def->nsounds) { int size = 100; char *modstr; if (VIR_ALLOC_N(modstr, size+1) < 0) goto no_memory; for (i = 0 ; i < def->nsounds && size > 0 ; i++) { virDomainSoundDefPtr sound = def->sounds[i]; const char *model = virDomainSoundModelTypeToString(sound->model); if (!model) { VIR_FREE(modstr); qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, "%s", _("invalid sound model")); goto error; } strncat(modstr, model, size); size -= strlen(model); if (i < (def->nsounds - 1)) strncat(modstr, ",", size--); } ADD_ARG_LIT("-soundhw"); ADD_ARG(modstr); } /* Add host passthrough hardware */ for (i = 0 ; i < def->nhostdevs ; i++) { int ret; char* usbdev; char* pcidev; virDomainHostdevDefPtr hostdev = def->hostdevs[i]; /* USB */ if (hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS && hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB) { if(hostdev->source.subsys.u.usb.vendor) { ret = virAsprintf(&usbdev, "host:%.4x:%.4x", hostdev->source.subsys.u.usb.vendor, hostdev->source.subsys.u.usb.product); } else { ret = virAsprintf(&usbdev, "host:%.3d.%.3d", hostdev->source.subsys.u.usb.bus, hostdev->source.subsys.u.usb.device); } if (ret < 0) goto error; ADD_ARG_LIT("-usbdevice"); ADD_ARG_LIT(usbdev); VIR_FREE(usbdev); } /* PCI */ if (hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS && hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI) { ret = virAsprintf(&pcidev, "host=%.2x:%.2x.%.1x", hostdev->source.subsys.u.pci.bus, hostdev->source.subsys.u.pci.slot, hostdev->source.subsys.u.pci.function); if (ret < 0) { pcidev = NULL; goto error; } ADD_ARG_LIT("-pcidevice"); ADD_ARG_LIT(pcidev); VIR_FREE(pcidev); } } if (migrateFrom) { ADD_ARG_LIT("-incoming"); ADD_ARG_LIT(migrateFrom); } ADD_ARG(NULL); ADD_ENV(NULL); *retargv = qargv; *retenv = qenv; return 0; no_memory: virReportOOMError(conn); error: if (tapfds && *tapfds) { for (i = 0; i < *ntapfds; i++) close((*tapfds)[i]); VIR_FREE(*tapfds); *ntapfds = 0; } if (qargv) { for (i = 0 ; i < qargc ; i++) VIR_FREE((qargv)[i]); VIR_FREE(qargv); } if (qenv) { for (i = 0 ; i < qenvc ; i++) VIR_FREE((qenv)[i]); VIR_FREE(qenv); } return -1; #undef ADD_ARG #undef ADD_ARG_LIT #undef ADD_ARG_SPACE #undef ADD_USBDISK #undef ADD_ENV #undef ADD_ENV_COPY #undef ADD_ENV_LIT #undef ADD_ENV_SPACE } /* * This method takes a string representing a QEMU command line ARGV set * optionall prefixed by a list of environment variables. It then tries * to split it up into a NULL terminated list of env & argv, splitting * on space */ static int qemuStringToArgvEnv(const char *args, const char ***retenv, const char ***retargv) { char **arglist = NULL; int argcount = 0; int argalloc = 0; int envend; int i; const char *curr = args; const char **progenv = NULL; const char **progargv = NULL; /* Iterate over string, splitting on sequences of ' ' */ while (curr && *curr != '\0') { char *arg; const char *next; if (*curr == '\'') { curr++; next = strchr(curr, '\''); } else if (*curr == '"') { curr++; next = strchr(curr, '"'); } else { next = strchr(curr, ' '); } if (!next) next = strchr(curr, '\n'); if (next) { arg = strndup(curr, next-curr); if (*next == '\'' || *next == '"') next++; } else { arg = strdup(curr); } if (!arg) goto no_memory; if (argalloc == argcount) { if (VIR_REALLOC_N(arglist, argalloc+10) < 0) { VIR_FREE(arg); goto no_memory; } argalloc+=10; } arglist[argcount++] = arg; while (next && c_isspace(*next)) next++; curr = next; } /* Iterate over list of args, finding first arg not containining * the '=' character (eg, skip over env vars FOO=bar) */ for (envend = 0 ; ((envend < argcount) && (strchr(arglist[envend], '=') != NULL)); envend++) ; /* nada */ /* Copy the list of env vars */ if (envend > 0) { if (VIR_REALLOC_N(progenv, envend+1) < 0) goto no_memory; for (i = 0 ; i < envend ; i++) { progenv[i] = arglist[i]; } progenv[i] = NULL; } /* Copy the list of argv */ if (VIR_REALLOC_N(progargv, argcount-envend + 1) < 0) goto no_memory; for (i = envend ; i < argcount ; i++) progargv[i-envend] = arglist[i]; progargv[i-envend] = NULL; VIR_FREE(arglist); *retenv = progenv; *retargv = progargv; return 0; no_memory: for (i = 0 ; progenv && progenv[i] ; i++) VIR_FREE(progenv[i]); VIR_FREE(progenv); for (i = 0 ; i < argcount ; i++) VIR_FREE(arglist[i]); VIR_FREE(arglist); return -1; } /* * Search for a named env variable, and return the value part */ static const char *qemuFindEnv(const char **progenv, const char *name) { int i; int len = strlen(name); for (i = 0 ; progenv && progenv[i] ; i++) { if (STREQLEN(progenv[i], name, len) && progenv[i][len] == '=') return progenv[i] + len + 1; } return NULL; } /* * Takes a string containing a set of key=value,key=value,key... * parameters and splits them up, returning two arrays with * the individual keys and values */ static int qemuParseCommandLineKeywords(virConnectPtr conn, const char *str, char ***retkeywords, char ***retvalues) { int keywordCount = 0; int keywordAlloc = 0; char **keywords = NULL; char **values = NULL; const char *start = str; int i; *retkeywords = NULL; *retvalues = NULL; while (start) { const char *separator; const char *endmark; char *keyword; char *value; if (!(separator = strchr(start, '='))) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("malformed keyword arguments in '%s'"), str); goto error; } if (!(keyword = strndup(start, separator - start))) goto no_memory; separator++; endmark = strchr(separator, ','); value = endmark ? strndup(separator, endmark - separator) : strdup(separator); if (!value) { VIR_FREE(keyword); goto no_memory; } if (keywordAlloc == keywordCount) { if (VIR_REALLOC_N(keywords, keywordAlloc + 10) < 0 || VIR_REALLOC_N(values, keywordAlloc + 10) < 0) { VIR_FREE(keyword); VIR_FREE(value); goto no_memory; } keywordAlloc += 10; } keywords[keywordCount] = keyword; values[keywordCount] = value; keywordCount++; start = endmark ? endmark + 1 : NULL; } *retkeywords = keywords; *retvalues = values; return keywordCount; no_memory: virReportOOMError(conn); error: for (i = 0 ; i < keywordCount ; i++) { VIR_FREE(keywords[i]); VIR_FREE(values[i]); } VIR_FREE(keywords); VIR_FREE(values); return -1; } /* * Tries to parse new style QEMU -drive args. * * eg -drive file=/dev/HostVG/VirtData1,if=ide,index=1 * * Will fail if not using the 'index' keyword */ static virDomainDiskDefPtr qemuParseCommandLineDisk(virConnectPtr conn, const char *val) { virDomainDiskDefPtr def = NULL; char **keywords; char **values; int nkeywords; int i; int idx = -1; if ((nkeywords = qemuParseCommandLineKeywords(conn, val, &keywords, &values)) < 0) return NULL; if (VIR_ALLOC(def) < 0) { virReportOOMError(conn); goto cleanup; } def->bus = VIR_DOMAIN_DISK_BUS_IDE; def->device = VIR_DOMAIN_DISK_DEVICE_DISK; for (i = 0 ; i < nkeywords ; i++) { if (STREQ(keywords[i], "file")) { if (values[i] && STRNEQ(values[i], "")) { def->src = values[i]; values[i] = NULL; if (STRPREFIX(def->src, "/dev/")) def->type = VIR_DOMAIN_DISK_TYPE_BLOCK; else def->type = VIR_DOMAIN_DISK_TYPE_FILE; } else { def->type = VIR_DOMAIN_DISK_TYPE_FILE; } } else if (STREQ(keywords[i], "if")) { if (STREQ(values[i], "ide")) def->bus = VIR_DOMAIN_DISK_BUS_IDE; else if (STREQ(values[i], "scsi")) def->bus = VIR_DOMAIN_DISK_BUS_SCSI; else if (STREQ(values[i], "virtio")) def->bus = VIR_DOMAIN_DISK_BUS_VIRTIO; else if (STREQ(values[i], "xen")) def->bus = VIR_DOMAIN_DISK_BUS_XEN; } else if (STREQ(keywords[i], "media")) { if (STREQ(values[i], "cdrom")) { def->device = VIR_DOMAIN_DISK_DEVICE_CDROM; def->readonly = 1; } else if (STREQ(values[i], "floppy")) def->device = VIR_DOMAIN_DISK_DEVICE_FLOPPY; } else if (STREQ(keywords[i], "format")) { def->driverName = strdup("qemu"); if (!def->driverName) { virDomainDiskDefFree(def); def = NULL; virReportOOMError(conn); goto cleanup; } def->driverType = values[i]; values[i] = NULL; } else if (STREQ(keywords[i], "cache")) { if (STREQ(values[i], "off") || STREQ(values[i], "none")) def->cachemode = VIR_DOMAIN_DISK_CACHE_DISABLE; else if (STREQ(values[i], "writeback") || STREQ(values[i], "on")) def->cachemode = VIR_DOMAIN_DISK_CACHE_WRITEBACK; else if (STREQ(values[i], "writethrough")) def->cachemode = VIR_DOMAIN_DISK_CACHE_WRITETHRU; } else if (STREQ(keywords[i], "index")) { if (virStrToLong_i(values[i], NULL, 10, &idx) < 0) { virDomainDiskDefFree(def); def = NULL; qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot parse drive index '%s'"), val); goto cleanup; } } } if (!def->src && def->device == VIR_DOMAIN_DISK_DEVICE_DISK) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("missing file parameter in drive '%s'"), val); virDomainDiskDefFree(def); def = NULL; goto cleanup; } if (idx == -1) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("missing index parameter in drive '%s'"), val); virDomainDiskDefFree(def); def = NULL; goto cleanup; } if (def->bus == VIR_DOMAIN_DISK_BUS_IDE) { def->dst = strdup("hda"); } else if (def->bus == VIR_DOMAIN_DISK_BUS_SCSI) { def->dst = strdup("sda"); } else if (def->bus == VIR_DOMAIN_DISK_BUS_VIRTIO) { def->dst = strdup("vda"); } else if (def->bus == VIR_DOMAIN_DISK_BUS_XEN) { def->dst = strdup("xvda"); } else { def->dst = strdup("hda"); } if (!def->dst) { virDomainDiskDefFree(def); def = NULL; virReportOOMError(conn); goto cleanup; } if (STREQ(def->dst, "xvda")) def->dst[3] = 'a' + idx; else def->dst[2] = 'a' + idx; cleanup: for (i = 0 ; i < nkeywords ; i++) { VIR_FREE(keywords[i]); VIR_FREE(values[i]); } VIR_FREE(keywords); VIR_FREE(values); return def; } /* * Tries to find a NIC definition matching a vlan we want */ static const char * qemuFindNICForVLAN(virConnectPtr conn, int nnics, const char **nics, int wantvlan) { int i; for (i = 0 ; i < nnics ; i++) { int gotvlan; const char *tmp = strstr(nics[i], "vlan="); char *end; if (!tmp) continue; tmp += strlen("vlan="); if (virStrToLong_i(tmp, &end, 10, &gotvlan) < 0) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot parse NIC vlan in '%s'"), nics[i]); return NULL; } if (gotvlan == wantvlan) return nics[i]; } if (wantvlan == 0 && nnics > 0) return nics[0]; qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot find NIC definition for vlan %d"), wantvlan); return NULL; } /* * Tries to parse a QEMU -net backend argument. Gets given * a list of all known -net frontend arguments to try and * match up against. Horribly complicated stuff */ static virDomainNetDefPtr qemuParseCommandLineNet(virConnectPtr conn, virCapsPtr caps, const char *val, int nnics, const char **nics) { virDomainNetDefPtr def = NULL; char **keywords = NULL; char **values = NULL; int nkeywords; const char *nic; int wantvlan = 0; const char *tmp; int genmac = 1; int i; tmp = strchr(val, ','); if (tmp) { if ((nkeywords = qemuParseCommandLineKeywords(conn, tmp+1, &keywords, &values)) < 0) return NULL; } else { nkeywords = 0; } if (VIR_ALLOC(def) < 0) { virReportOOMError(conn); goto cleanup; } /* 'tap' could turn into libvirt type=ethernet, type=bridge or * type=network, but we can't tell, so use the generic config */ if (STRPREFIX(val, "tap,")) def->type = VIR_DOMAIN_NET_TYPE_ETHERNET; else if (STRPREFIX(val, "socket")) def->type = VIR_DOMAIN_NET_TYPE_CLIENT; else if (STRPREFIX(val, "user")) def->type = VIR_DOMAIN_NET_TYPE_USER; else def->type = VIR_DOMAIN_NET_TYPE_ETHERNET; for (i = 0 ; i < nkeywords ; i++) { if (STREQ(keywords[i], "vlan")) { if (virStrToLong_i(values[i], NULL, 10, &wantvlan) < 0) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot parse vlan in '%s'"), val); virDomainNetDefFree(def); def = NULL; goto cleanup; } } else if (def->type == VIR_DOMAIN_NET_TYPE_ETHERNET && STREQ(keywords[i], "script") && STRNEQ(values[i], "")) { def->data.ethernet.script = values[i]; values[i] = NULL; } else if (def->type == VIR_DOMAIN_NET_TYPE_ETHERNET && STREQ(keywords[i], "ifname")) { def->ifname = values[i]; values[i] = NULL; } } /* Done parsing the nic backend. Now to try and find corresponding * frontend, based off vlan number. NB this assumes a 1-1 mapping */ nic = qemuFindNICForVLAN(conn, nnics, nics, wantvlan); if (!nic) { virDomainNetDefFree(def); def = NULL; goto cleanup; } if (!STRPREFIX(nic, "nic")) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot parse NIC definition '%s'"), nic); virDomainNetDefFree(def); def = NULL; goto cleanup; } for (i = 0 ; i < nkeywords ; i++) { VIR_FREE(keywords[i]); VIR_FREE(values[i]); } VIR_FREE(keywords); VIR_FREE(values); if (STRPREFIX(nic, "nic,")) { if ((nkeywords = qemuParseCommandLineKeywords(conn, nic + strlen("nic,"), &keywords, &values)) < 0) { virDomainNetDefFree(def); def = NULL; goto cleanup; } } else { nkeywords = 0; } for (i = 0 ; i < nkeywords ; i++) { if (STREQ(keywords[i], "macaddr")) { genmac = 0; virParseMacAddr(values[i], def->mac); } else if (STREQ(keywords[i], "model")) { def->model = values[i]; values[i] = NULL; } } if (genmac) virCapabilitiesGenerateMac(caps, def->mac); cleanup: for (i = 0 ; i < nkeywords ; i++) { VIR_FREE(keywords[i]); VIR_FREE(values[i]); } VIR_FREE(keywords); VIR_FREE(values); return def; } /* * Tries to parse a QEMU PCI device */ static virDomainHostdevDefPtr qemuParseCommandLinePCI(virConnectPtr conn, const char *val) { virDomainHostdevDefPtr def = NULL; int bus = 0, slot = 0, func = 0; const char *start; char *end; if (!STRPREFIX(val, "host=")) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("unknown PCI device syntax '%s'"), val); VIR_FREE(def); goto cleanup; } start = val + strlen("host="); if (virStrToLong_i(start, &end, 16, &bus) < 0 || !end || *end != ':') { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot extract PCI device bus '%s'"), val); VIR_FREE(def); goto cleanup; } start = end + 1; if (virStrToLong_i(start, &end, 16, &slot) < 0 || !end || *end != '.') { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot extract PCI device slot '%s'"), val); VIR_FREE(def); goto cleanup; } start = end + 1; if (virStrToLong_i(start, NULL, 16, &func) < 0) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot extract PCI device function '%s'"), val); VIR_FREE(def); goto cleanup; } if (VIR_ALLOC(def) < 0) { virReportOOMError(conn); goto cleanup; } def->mode = VIR_DOMAIN_HOSTDEV_MODE_SUBSYS; def->managed = 1; def->source.subsys.type = VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI; def->source.subsys.u.pci.bus = bus; def->source.subsys.u.pci.slot = slot; def->source.subsys.u.pci.function = func; cleanup: return def; } /* * Tries to parse a QEMU USB device */ static virDomainHostdevDefPtr qemuParseCommandLineUSB(virConnectPtr conn, const char *val) { virDomainHostdevDefPtr def = NULL; int first = 0, second = 0; const char *start; char *end; if (!STRPREFIX(val, "host:")) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("unknown PCI device syntax '%s'"), val); VIR_FREE(def); goto cleanup; } start = val + strlen("host:"); if (strchr(start, ':')) { if (virStrToLong_i(start, &end, 16, &first) < 0 || !end || *end != ':') { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot extract USB device vendor '%s'"), val); VIR_FREE(def); goto cleanup; } start = end + 1; if (virStrToLong_i(start, NULL, 16, &second) < 0) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot extract PCI device product '%s'"), val); VIR_FREE(def); goto cleanup; } } else { if (virStrToLong_i(start, &end, 10, &first) < 0 || !end || *end != '.') { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot extract PCI device bus '%s'"), val); VIR_FREE(def); goto cleanup; } start = end + 1; if (virStrToLong_i(start, NULL, 10, &second) < 0) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot extract PCI device address '%s'"), val); VIR_FREE(def); goto cleanup; } } if (VIR_ALLOC(def) < 0) { virReportOOMError(conn); goto cleanup; } def->mode = VIR_DOMAIN_HOSTDEV_MODE_SUBSYS; def->managed = 0; def->source.subsys.type = VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB; if (*end == '.') { def->source.subsys.u.usb.bus = first; def->source.subsys.u.usb.device = second; } else { def->source.subsys.u.usb.vendor = first; def->source.subsys.u.usb.product = second; } cleanup: return def; } /* * Tries to parse a QEMU serial/parallel device */ static virDomainChrDefPtr qemuParseCommandLineChr(virConnectPtr conn, const char *val) { virDomainChrDefPtr def; if (VIR_ALLOC(def) < 0) goto no_memory; if (STREQ(val, "null")) { def->type = VIR_DOMAIN_CHR_TYPE_NULL; } else if (STREQ(val, "vc")) { def->type = VIR_DOMAIN_CHR_TYPE_VC; } else if (STREQ(val, "pty")) { def->type = VIR_DOMAIN_CHR_TYPE_PTY; } else if (STRPREFIX(val, "file:")) { def->type = VIR_DOMAIN_CHR_TYPE_FILE; def->data.file.path = strdup(val+strlen("file:")); if (!def->data.file.path) goto no_memory; } else if (STRPREFIX(val, "pipe:")) { def->type = VIR_DOMAIN_CHR_TYPE_PIPE; def->data.file.path = strdup(val+strlen("pipe:")); if (!def->data.file.path) goto no_memory; } else if (STREQ(val, "stdio")) { def->type = VIR_DOMAIN_CHR_TYPE_STDIO; } else if (STRPREFIX(val, "udp:")) { const char *svc1, *host2, *svc2; def->type = VIR_DOMAIN_CHR_TYPE_UDP; val += strlen("udp:"); svc1 = strchr(val, ':'); host2 = svc1 ? strchr(svc1, '@') : NULL; svc2 = host2 ? strchr(host2, ':') : NULL; if (svc1) def->data.udp.connectHost = strndup(val, svc1-val); else def->data.udp.connectHost = strdup(val); if (svc1) { svc1++; if (host2) def->data.udp.connectService = strndup(svc1, host2-svc1); else def->data.udp.connectService = strdup(svc1); } if (host2) { host2++; if (svc2) def->data.udp.bindHost = strndup(host2, svc2-host2); else def->data.udp.bindHost = strdup(host2); } if (svc2) { svc2++; def->data.udp.bindService = strdup(svc2); } } else if (STRPREFIX(val, "tcp:") || STRPREFIX(val, "telnet:")) { const char *opt, *svc; def->type = VIR_DOMAIN_CHR_TYPE_TCP; if (STRPREFIX(val, "tcp:")) { val += strlen("tcp:"); } else { val += strlen("telnet:"); def->data.tcp.protocol = VIR_DOMAIN_CHR_TCP_PROTOCOL_TELNET; } svc = strchr(val, ':'); if (!svc) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("cannot find port number in character device %s"), val); goto error; } opt = strchr(svc, ','); if (opt && strstr(opt, "server")) def->data.tcp.listen = 1; def->data.tcp.host = strndup(val, svc-val); svc++; if (opt) { def->data.tcp.service = strndup(svc, opt-svc); } else { def->data.tcp.service = strdup(svc); } } else if (STRPREFIX(val, "unix:")) { const char *opt; val += strlen("unix:"); opt = strchr(val, ','); def->type = VIR_DOMAIN_CHR_TYPE_UNIX; if (opt) { if (strstr(opt, "listen")) def->data.nix.listen = 1; def->data.nix.path = strndup(val, opt-val); } else { def->data.nix.path = strdup(val); } if (!def->data.nix.path) goto no_memory; } else if (STRPREFIX(val, "/dev")) { def->type = VIR_DOMAIN_CHR_TYPE_DEV; def->data.file.path = strdup(val); if (!def->data.file.path) goto no_memory; } else { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("unknown character device syntax %s"), val); goto error; } return def; no_memory: virReportOOMError(conn); error: virDomainChrDefFree(def); return NULL; } /* * Analyse the env and argv settings and reconstruct a * virDomainDefPtr representing these settings as closely * as is practical. This is not an exact science.... */ virDomainDefPtr qemuParseCommandLine(virConnectPtr conn, virCapsPtr caps, const char **progenv, const char **progargv) { virDomainDefPtr def; int i; int nographics = 0; int fullscreen = 0; char *path; int nnics = 0; const char **nics = NULL; int video = VIR_DOMAIN_VIDEO_TYPE_CIRRUS; if (!progargv[0]) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, "%s", _("no emulator path found")); return NULL; } if (VIR_ALLOC(def) < 0) goto no_memory; virUUIDGenerate(def->uuid); def->id = -1; def->memory = def->maxmem = 64 * 1024; def->vcpus = 1; def->features = (1 << VIR_DOMAIN_FEATURE_ACPI) /*| (1 << VIR_DOMAIN_FEATURE_APIC)*/; def->onReboot = VIR_DOMAIN_LIFECYCLE_RESTART; def->onCrash = VIR_DOMAIN_LIFECYCLE_DESTROY; def->onPoweroff = VIR_DOMAIN_LIFECYCLE_DESTROY; def->virtType = VIR_DOMAIN_VIRT_QEMU; if (!(def->emulator = strdup(progargv[0]))) goto no_memory; if (strstr(def->emulator, "kvm")) { def->virtType = VIR_DOMAIN_VIRT_KVM; def->features |= (1 << VIR_DOMAIN_FEATURE_PAE); } if (strstr(def->emulator, "xenner")) { def->virtType = VIR_DOMAIN_VIRT_KVM; def->os.type = strdup("xen"); } else { def->os.type = strdup("hvm"); } if (!def->os.type) goto no_memory; if (STRPREFIX(def->emulator, "qemu")) path = def->emulator; else path = strstr(def->emulator, "qemu"); if (path && STRPREFIX(path, "qemu-system-")) def->os.arch = strdup(path + strlen("qemu-system-")); else def->os.arch = strdup("i686"); if (!def->os.arch) goto no_memory; #define WANT_VALUE() \ const char *val = progargv[++i]; \ if (!val) { \ qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, \ _("missing value for %s argument"), arg); \ goto error; \ } /* One initial loop to get list of NICs, so we * can correlate them later */ for (i = 1 ; progargv[i] ; i++) { const char *arg = progargv[i]; /* Make sure we have a single - for all options to simplify next logic */ if (STRPREFIX(arg, "--")) arg++; if (STREQ(arg, "-net")) { WANT_VALUE(); if (STRPREFIX(val, "nic")) { if (VIR_REALLOC_N(nics, nnics+1) < 0) goto no_memory; nics[nnics++] = val; } } } /* Now the real processing loop */ for (i = 1 ; progargv[i] ; i++) { const char *arg = progargv[i]; /* Make sure we have a single - for all options to simplify next logic */ if (STRPREFIX(arg, "--")) arg++; if (STREQ(arg, "-vnc")) { virDomainGraphicsDefPtr vnc; char *tmp; WANT_VALUE(); if (VIR_ALLOC(vnc) < 0) goto no_memory; vnc->type = VIR_DOMAIN_GRAPHICS_TYPE_VNC; tmp = strchr(val, ':'); if (tmp) { char *opts; if (virStrToLong_i(tmp+1, &opts, 10, &vnc->data.vnc.port) < 0) { VIR_FREE(vnc); qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, \ _("cannot parse VNC port '%s'"), tmp+1); goto error; } vnc->data.vnc.listenAddr = strndup(val, tmp-val); if (!vnc->data.vnc.listenAddr) { VIR_FREE(vnc); goto no_memory; } vnc->data.vnc.port += 5900; vnc->data.vnc.autoport = 0; } else { vnc->data.vnc.autoport = 1; } if (VIR_REALLOC_N(def->graphics, def->ngraphics+1) < 0) { virDomainGraphicsDefFree(vnc); goto no_memory; } def->graphics[def->ngraphics++] = vnc; } else if (STREQ(arg, "-m")) { int mem; WANT_VALUE(); if (virStrToLong_i(val, NULL, 10, &mem) < 0) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, \ _("cannot parse memory level '%s'"), val); goto error; } def->memory = def->maxmem = mem * 1024; } else if (STREQ(arg, "-smp")) { int vcpus; WANT_VALUE(); if (virStrToLong_i(val, NULL, 10, &vcpus) < 0) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, \ _("cannot parse CPU count '%s'"), val); goto error; } def->vcpus = vcpus; } else if (STREQ(arg, "-uuid")) { WANT_VALUE(); if (virUUIDParse(val, def->uuid) < 0) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, \ _("cannot parse UUID '%s'"), val); goto error; } } else if (STRPREFIX(arg, "-hd") || STRPREFIX(arg, "-sd") || STRPREFIX(arg, "-fd") || STREQ(arg, "-cdrom")) { WANT_VALUE(); virDomainDiskDefPtr disk; if (VIR_ALLOC(disk) < 0) goto no_memory; if (STRPREFIX(val, "/dev/")) disk->type = VIR_DOMAIN_DISK_TYPE_BLOCK; else disk->type = VIR_DOMAIN_DISK_TYPE_FILE; if (STREQ(arg, "-cdrom")) { disk->device = VIR_DOMAIN_DISK_DEVICE_CDROM; disk->dst = strdup("hdc"); disk->readonly = 1; } else { if (STRPREFIX(arg, "-fd")) { disk->device = VIR_DOMAIN_DISK_DEVICE_FLOPPY; disk->bus = VIR_DOMAIN_DISK_BUS_FDC; } else { disk->device = VIR_DOMAIN_DISK_DEVICE_DISK; if (STRPREFIX(arg, "-hd")) disk->bus = VIR_DOMAIN_DISK_BUS_IDE; else disk->bus = VIR_DOMAIN_DISK_BUS_SCSI; } disk->dst = strdup(arg + 1); } disk->src = strdup(val); if (!disk->src || !disk->dst) { virDomainDiskDefFree(disk); goto no_memory; } if (VIR_REALLOC_N(def->disks, def->ndisks+1) < 0) { virDomainDiskDefFree(disk); goto no_memory; } def->disks[def->ndisks++] = disk; } else if (STREQ(arg, "-no-acpi")) { def->features &= ~(1 << VIR_DOMAIN_FEATURE_ACPI); } else if (STREQ(arg, "-no-reboot")) { def->onReboot = VIR_DOMAIN_LIFECYCLE_DESTROY; } else if (STREQ(arg, "-no-kvm")) { def->virtType = VIR_DOMAIN_VIRT_QEMU; } else if (STREQ(arg, "-nographic")) { nographics = 1; } else if (STREQ(arg, "-full-screen")) { fullscreen = 1; } else if (STREQ(arg, "-localtime")) { def->localtime = 1; } else if (STREQ(arg, "-kernel")) { WANT_VALUE(); if (!(def->os.kernel = strdup(val))) goto no_memory; } else if (STREQ(arg, "-initrd")) { WANT_VALUE(); if (!(def->os.initrd = strdup(val))) goto no_memory; } else if (STREQ(arg, "-append")) { WANT_VALUE(); if (!(def->os.cmdline = strdup(val))) goto no_memory; } else if (STREQ(arg, "-boot")) { int n, b = 0; WANT_VALUE(); for (n = 0 ; val[n] && b < VIR_DOMAIN_BOOT_LAST ; n++) { if (val[n] == 'a') def->os.bootDevs[b++] = VIR_DOMAIN_BOOT_FLOPPY; else if (val[n] == 'c') def->os.bootDevs[b++] = VIR_DOMAIN_BOOT_DISK; else if (val[n] == 'd') def->os.bootDevs[b++] = VIR_DOMAIN_BOOT_CDROM; else if (val[n] == 'n') def->os.bootDevs[b++] = VIR_DOMAIN_BOOT_NET; } def->os.nBootDevs = b; } else if (STREQ(arg, "-name")) { WANT_VALUE(); if (!(def->name = strdup(val))) goto no_memory; } else if (STREQ(arg, "-M")) { WANT_VALUE(); if (!(def->os.machine = strdup(val))) goto no_memory; } else if (STREQ(arg, "-serial")) { WANT_VALUE(); if (STRNEQ(val, "none")) { virDomainChrDefPtr chr; if (!(chr = qemuParseCommandLineChr(conn, val))) goto error; if (VIR_REALLOC_N(def->serials, def->nserials+1) < 0) { virDomainChrDefFree(chr); goto no_memory; } chr->dstPort = def->nserials; def->serials[def->nserials++] = chr; } } else if (STREQ(arg, "-parallel")) { WANT_VALUE(); if (STRNEQ(val, "none")) { virDomainChrDefPtr chr; if (!(chr = qemuParseCommandLineChr(conn, val))) goto error; if (VIR_REALLOC_N(def->parallels, def->nparallels+1) < 0) { virDomainChrDefFree(chr); goto no_memory; } chr->dstPort = def->nparallels; def->parallels[def->nparallels++] = chr; } } else if (STREQ(arg, "-usbdevice")) { WANT_VALUE(); if (STREQ(val, "tablet") || STREQ(val, "mouse")) { virDomainInputDefPtr input; if (VIR_ALLOC(input) < 0) goto no_memory; input->bus = VIR_DOMAIN_INPUT_BUS_USB; if (STREQ(val, "tablet")) input->type = VIR_DOMAIN_INPUT_TYPE_TABLET; else input->type = VIR_DOMAIN_INPUT_TYPE_MOUSE; if (VIR_REALLOC_N(def->inputs, def->ninputs+1) < 0) { virDomainInputDefFree(input); goto no_memory; } def->inputs[def->ninputs++] = input; } else if (STRPREFIX(val, "disk:")) { virDomainDiskDefPtr disk; if (VIR_ALLOC(disk) < 0) goto no_memory; disk->src = strdup(val + strlen("disk:")); if (!disk->src) { virDomainDiskDefFree(disk); goto no_memory; } if (STRPREFIX(disk->src, "/dev/")) disk->type = VIR_DOMAIN_DISK_TYPE_BLOCK; else disk->type = VIR_DOMAIN_DISK_TYPE_FILE; disk->device = VIR_DOMAIN_DISK_DEVICE_DISK; disk->bus = VIR_DOMAIN_DISK_BUS_USB; if (!(disk->dst = strdup("sda"))) { virDomainDiskDefFree(disk); goto no_memory; } if (VIR_REALLOC_N(def->disks, def->ndisks+1) < 0) { virDomainDiskDefFree(disk); goto no_memory; } def->disks[def->ndisks++] = disk; } else { virDomainHostdevDefPtr hostdev; if (!(hostdev = qemuParseCommandLineUSB(conn, val))) goto error; if (VIR_REALLOC_N(def->hostdevs, def->nhostdevs+1) < 0) { virDomainHostdevDefFree(hostdev); goto no_memory; } def->hostdevs[def->nhostdevs++] = hostdev; } } else if (STREQ(arg, "-net")) { WANT_VALUE(); if (!STRPREFIX(val, "nic") && STRNEQ(val, "none")) { virDomainNetDefPtr net; if (!(net = qemuParseCommandLineNet(conn, caps, val, nnics, nics))) goto error; if (VIR_REALLOC_N(def->nets, def->nnets+1) < 0) { virDomainNetDefFree(net); goto no_memory; } def->nets[def->nnets++] = net; } } else if (STREQ(arg, "-drive")) { virDomainDiskDefPtr disk; WANT_VALUE(); if (!(disk = qemuParseCommandLineDisk(conn, val))) goto error; if (VIR_REALLOC_N(def->disks, def->ndisks+1) < 0) { virDomainDiskDefFree(disk); goto no_memory; } def->disks[def->ndisks++] = disk; } else if (STREQ(arg, "-pcidevice")) { virDomainHostdevDefPtr hostdev; WANT_VALUE(); if (!(hostdev = qemuParseCommandLinePCI(conn, val))) goto error; if (VIR_REALLOC_N(def->hostdevs, def->nhostdevs+1) < 0) { virDomainHostdevDefFree(hostdev); goto no_memory; } def->hostdevs[def->nhostdevs++] = hostdev; } else if (STREQ(arg, "-soundhw")) { const char *start; WANT_VALUE(); start = val; while (start) { const char *tmp = strchr(start, ','); int type = -1; if (STRPREFIX(start, "pcspk")) { type = VIR_DOMAIN_SOUND_MODEL_PCSPK; } else if (STRPREFIX(start, "sb16")) { type = VIR_DOMAIN_SOUND_MODEL_SB16; } else if (STRPREFIX(start, "es1370")) { type = VIR_DOMAIN_SOUND_MODEL_ES1370; } else if (STRPREFIX(start, "ac97")) { type = VIR_DOMAIN_SOUND_MODEL_AC97; } if (type != -1) { virDomainSoundDefPtr snd; if (VIR_ALLOC(snd) < 0) goto no_memory; snd->model = type; if (VIR_REALLOC_N(def->sounds, def->nsounds+1) < 0) { VIR_FREE(snd); goto no_memory; } def->sounds[def->nsounds++] = snd; } start = tmp ? tmp + 1 : NULL; } } else if (STREQ(arg, "-bootloader")) { WANT_VALUE(); def->os.bootloader = strdup(val); if (!def->os.bootloader) goto no_memory; } else if (STREQ(arg, "-vmwarevga")) { video = VIR_DOMAIN_VIDEO_TYPE_VMVGA; } else if (STREQ(arg, "-std-vga")) { video = VIR_DOMAIN_VIDEO_TYPE_VGA; } else if (STREQ(arg, "-vga")) { WANT_VALUE(); video = qemuVideoTypeFromString(val); if (video < 0) { qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("unknown video adapter type '%s'"), val); goto error; } } else if (STREQ(arg, "-domid")) { WANT_VALUE(); /* ignore, generted on the fly */ } else if (STREQ(arg, "-usb")) { /* ignore, always added by libvirt */ } else if (STREQ(arg, "-pidfile")) { WANT_VALUE(); /* ignore, used by libvirt as needed */ } else if (STREQ(arg, "-incoming")) { WANT_VALUE(); /* ignore, used via restore/migrate APIs */ } else if (STREQ(arg, "-monitor")) { WANT_VALUE(); /* ignore, used internally by libvirt */ } else if (STREQ(arg, "-S")) { /* ignore, always added by libvirt */ } else { VIR_WARN(_("unknown QEMU argument '%s' during conversion"), arg); #if 0 qemudReportError(conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, _("unknown argument '%s'"), arg); goto error; #endif } } #undef WANT_VALUE if (!nographics && def->ngraphics == 0) { virDomainGraphicsDefPtr sdl; const char *display = qemuFindEnv(progenv, "DISPLAY"); const char *xauth = qemuFindEnv(progenv, "XAUTHORITY"); if (VIR_ALLOC(sdl) < 0) goto no_memory; sdl->type = VIR_DOMAIN_GRAPHICS_TYPE_SDL; sdl->data.sdl.fullscreen = fullscreen; if (display && !(sdl->data.sdl.display = strdup(display))) { VIR_FREE(sdl); goto no_memory; } if (xauth && !(sdl->data.sdl.xauth = strdup(xauth))) { VIR_FREE(sdl); goto no_memory; } if (VIR_REALLOC_N(def->graphics, def->ngraphics+1) < 0) { virDomainGraphicsDefFree(sdl); goto no_memory; } def->graphics[def->ngraphics++] = sdl; } if (def->ngraphics) { virDomainVideoDefPtr vid; if (VIR_ALLOC(vid) < 0) goto no_memory; if (def->virtType == VIR_DOMAIN_VIRT_XEN) vid->type = VIR_DOMAIN_VIDEO_TYPE_XEN; else vid->type = video; vid->vram = virDomainVideoDefaultRAM(def, vid->type); vid->heads = 1; if (VIR_REALLOC_N(def->videos, def->nvideos+1) < 0) { virDomainVideoDefFree(vid); goto no_memory; } def->videos[def->nvideos++] = vid; } VIR_FREE(nics); if (!def->name) { if (!(def->name = strdup("unnamed"))) goto no_memory; } return def; no_memory: virReportOOMError(conn); error: virDomainDefFree(def); VIR_FREE(nics); return NULL; } virDomainDefPtr qemuParseCommandLineString(virConnectPtr conn, virCapsPtr caps, const char *args) { const char **progenv = NULL; const char **progargv = NULL; virDomainDefPtr def = NULL; int i; if (qemuStringToArgvEnv(args, &progenv, &progargv) < 0) goto cleanup; def = qemuParseCommandLine(conn, caps, progenv, progargv); cleanup: for (i = 0 ; progargv && progargv[i] ; i++) VIR_FREE(progargv[i]); VIR_FREE(progargv); for (i = 0 ; progenv && progenv[i] ; i++) VIR_FREE(progenv[i]); VIR_FREE(progenv); return def; }