/* * virsh-domain.c: Commands to manage domain * * Copyright (C) 2005, 2007-2012 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; If not, see * . * * Daniel Veillard * Karel Zak * Daniel P. Berrange * */ #include #include "virsh-domain.h" #include #include #include #include #include #include #include #include #include #include "internal.h" #include "bitmap.h" #include "buf.h" #include "c-ctype.h" #include "conf/domain_conf.h" #include "console.h" #include "memory.h" #include "util.h" #include "virfile.h" #include "virkeycode.h" #include "virmacaddr.h" #include "virsh-domain-monitor.h" #include "virterror_internal.h" #include "virtypedparam.h" #include "xml.h" /* Gnulib doesn't guarantee SA_SIGINFO support. */ #ifndef SA_SIGINFO # define SA_SIGINFO 0 #endif virDomainPtr vshCommandOptDomainBy(vshControl *ctl, const vshCmd *cmd, const char **name, unsigned int flags) { virDomainPtr dom = NULL; const char *n = NULL; int id; const char *optname = "domain"; virCheckFlags(VSH_BYID | VSH_BYUUID | VSH_BYNAME, NULL); if (!vshCmdHasOption(ctl, cmd, optname)) return NULL; if (vshCommandOptString(cmd, optname, &n) <= 0) return NULL; vshDebug(ctl, VSH_ERR_INFO, "%s: found option <%s>: %s\n", cmd->def->name, optname, n); if (name) *name = n; /* try it by ID */ if (flags & VSH_BYID) { if (virStrToLong_i(n, NULL, 10, &id) == 0 && id >= 0) { vshDebug(ctl, VSH_ERR_DEBUG, "%s: <%s> seems like domain ID\n", cmd->def->name, optname); dom = virDomainLookupByID(ctl->conn, id); } } /* try it by UUID */ if (!dom && (flags & VSH_BYUUID) && strlen(n) == VIR_UUID_STRING_BUFLEN-1) { vshDebug(ctl, VSH_ERR_DEBUG, "%s: <%s> trying as domain UUID\n", cmd->def->name, optname); dom = virDomainLookupByUUIDString(ctl->conn, n); } /* try it by NAME */ if (!dom && (flags & VSH_BYNAME)) { vshDebug(ctl, VSH_ERR_DEBUG, "%s: <%s> trying as domain NAME\n", cmd->def->name, optname); dom = virDomainLookupByName(ctl->conn, n); } if (!dom) vshError(ctl, _("failed to get domain '%s'"), n); return dom; } static const char * vshDomainVcpuStateToString(int state) { switch (state) { case VIR_VCPU_OFFLINE: return N_("offline"); case VIR_VCPU_BLOCKED: return N_("idle"); case VIR_VCPU_RUNNING: return N_("running"); default: ;/*FALLTHROUGH*/ } return N_("no state"); } /* * "attach-device" command */ static const vshCmdInfo info_attach_device[] = { {"help", N_("attach device from an XML file")}, {"desc", N_("Attach device from an XML .")}, {NULL, NULL} }; static const vshCmdOptDef opts_attach_device[] = { {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, {"file", VSH_OT_DATA, VSH_OFLAG_REQ, N_("XML file")}, {"persistent", VSH_OT_ALIAS, 0, "config"}, {"config", VSH_OT_BOOL, 0, N_("affect next boot")}, {NULL, 0, 0, NULL} }; static bool cmdAttachDevice(vshControl *ctl, const vshCmd *cmd) { virDomainPtr dom; const char *from = NULL; char *buffer; int ret; unsigned int flags; if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) return false; if (vshCommandOptString(cmd, "file", &from) <= 0) { virDomainFree(dom); return false; } if (virFileReadAll(from, VSH_MAX_XML_FILE, &buffer) < 0) { vshReportError(ctl); virDomainFree(dom); return false; } if (vshCommandOptBool(cmd, "config")) { flags = VIR_DOMAIN_AFFECT_CONFIG; if (virDomainIsActive(dom) == 1) flags |= VIR_DOMAIN_AFFECT_LIVE; ret = virDomainAttachDeviceFlags(dom, buffer, flags); } else { ret = virDomainAttachDevice(dom, buffer); } VIR_FREE(buffer); if (ret < 0) { vshError(ctl, _("Failed to attach device from %s"), from); virDomainFree(dom); return false; } else { vshPrint(ctl, "%s", _("Device attached successfully\n")); } virDomainFree(dom); return true; } /* * "attach-disk" command */ static const vshCmdInfo info_attach_disk[] = { {"help", N_("attach disk device")}, {"desc", N_("Attach new disk device.")}, {NULL, NULL} }; static const vshCmdOptDef opts_attach_disk[] = { {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, {"source", VSH_OT_DATA, VSH_OFLAG_REQ | VSH_OFLAG_EMPTY_OK, N_("source of disk device")}, {"target", VSH_OT_DATA, VSH_OFLAG_REQ, N_("target of disk device")}, {"driver", VSH_OT_STRING, 0, N_("driver of disk device")}, {"subdriver", VSH_OT_STRING, 0, N_("subdriver of disk device")}, {"cache", VSH_OT_STRING, 0, N_("cache mode of disk device")}, {"type", VSH_OT_STRING, 0, N_("target device type")}, {"mode", VSH_OT_STRING, 0, N_("mode of device reading and writing")}, {"persistent", VSH_OT_ALIAS, 0, "config"}, {"config", VSH_OT_BOOL, 0, N_("affect next boot")}, {"sourcetype", VSH_OT_STRING, 0, N_("type of source (block|file)")}, {"serial", VSH_OT_STRING, 0, N_("serial of disk device")}, {"shareable", VSH_OT_BOOL, 0, N_("shareable between domains")}, {"rawio", VSH_OT_BOOL, 0, N_("needs rawio capability")}, {"address", VSH_OT_STRING, 0, N_("address of disk device")}, {"multifunction", VSH_OT_BOOL, 0, N_("use multifunction pci under specified address")}, {NULL, 0, 0, NULL} }; enum { DISK_ADDR_TYPE_INVALID, DISK_ADDR_TYPE_PCI, DISK_ADDR_TYPE_SCSI, DISK_ADDR_TYPE_IDE, }; struct PCIAddress { unsigned int domain; unsigned int bus; unsigned int slot; unsigned int function; }; struct SCSIAddress { unsigned int controller; unsigned int bus; unsigned int unit; }; struct IDEAddress { unsigned int controller; unsigned int bus; unsigned int unit; }; struct DiskAddress { int type; union { struct PCIAddress pci; struct SCSIAddress scsi; struct IDEAddress ide; } addr; }; static int str2PCIAddress(const char *str, struct PCIAddress *pciAddr) { char *domain, *bus, *slot, *function; if (!pciAddr) return -1; if (!str) return -1; domain = (char *)str; if (virStrToLong_ui(domain, &bus, 0, &pciAddr->domain) != 0) return -1; bus++; if (virStrToLong_ui(bus, &slot, 0, &pciAddr->bus) != 0) return -1; slot++; if (virStrToLong_ui(slot, &function, 0, &pciAddr->slot) != 0) return -1; function++; if (virStrToLong_ui(function, NULL, 0, &pciAddr->function) != 0) return -1; return 0; } static int str2SCSIAddress(const char *str, struct SCSIAddress *scsiAddr) { char *controller, *bus, *unit; if (!scsiAddr) return -1; if (!str) return -1; controller = (char *)str; if (virStrToLong_ui(controller, &bus, 0, &scsiAddr->controller) != 0) return -1; bus++; if (virStrToLong_ui(bus, &unit, 0, &scsiAddr->bus) != 0) return -1; unit++; if (virStrToLong_ui(unit, NULL, 0, &scsiAddr->unit) != 0) return -1; return 0; } static int str2IDEAddress(const char *str, struct IDEAddress *ideAddr) { char *controller, *bus, *unit; if (!ideAddr) return -1; if (!str) return -1; controller = (char *)str; if (virStrToLong_ui(controller, &bus, 0, &ideAddr->controller) != 0) return -1; bus++; if (virStrToLong_ui(bus, &unit, 0, &ideAddr->bus) != 0) return -1; unit++; if (virStrToLong_ui(unit, NULL, 0, &ideAddr->unit) != 0) return -1; return 0; } /* pci address pci:0000.00.0x0a.0 (domain:bus:slot:function) * ide disk address: ide:00.00.0 (controller:bus:unit) * scsi disk address: scsi:00.00.0 (controller:bus:unit) */ static int str2DiskAddress(const char *str, struct DiskAddress *diskAddr) { char *type, *addr; if (!diskAddr) return -1; if (!str) return -1; type = (char *)str; addr = strchr(type, ':'); if (!addr) return -1; if (STREQLEN(type, "pci", addr - type)) { diskAddr->type = DISK_ADDR_TYPE_PCI; return str2PCIAddress(addr + 1, &diskAddr->addr.pci); } else if (STREQLEN(type, "scsi", addr - type)) { diskAddr->type = DISK_ADDR_TYPE_SCSI; return str2SCSIAddress(addr + 1, &diskAddr->addr.scsi); } else if (STREQLEN(type, "ide", addr - type)) { diskAddr->type = DISK_ADDR_TYPE_IDE; return str2IDEAddress(addr + 1, &diskAddr->addr.ide); } return -1; } static bool cmdAttachDisk(vshControl *ctl, const vshCmd *cmd) { virDomainPtr dom = NULL; const char *source = NULL, *target = NULL, *driver = NULL, *subdriver = NULL, *type = NULL, *mode = NULL, *cache = NULL, *serial = NULL, *straddr = NULL; struct DiskAddress diskAddr; bool isFile = false, functionReturn = false; int ret; unsigned int flags; const char *stype = NULL; virBuffer buf = VIR_BUFFER_INITIALIZER; char *xml; struct stat st; if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) goto cleanup; if (vshCommandOptString(cmd, "source", &source) <= 0) goto cleanup; /* Allow empty string as a placeholder that implies no source, for * use in adding a cdrom drive with no disk. */ if (!*source) source = NULL; if (vshCommandOptString(cmd, "target", &target) <= 0) goto cleanup; if (vshCommandOptString(cmd, "driver", &driver) < 0 || vshCommandOptString(cmd, "subdriver", &subdriver) < 0 || vshCommandOptString(cmd, "type", &type) < 0 || vshCommandOptString(cmd, "mode", &mode) < 0 || vshCommandOptString(cmd, "cache", &cache) < 0 || vshCommandOptString(cmd, "serial", &serial) < 0 || vshCommandOptString(cmd, "address", &straddr) < 0 || vshCommandOptString(cmd, "sourcetype", &stype) < 0) { vshError(ctl, "%s", _("missing option")); goto cleanup; } if (!stype) { if (driver && (STREQ(driver, "file") || STREQ(driver, "tap"))) { isFile = true; } else { if (source && !stat(source, &st)) isFile = S_ISREG(st.st_mode) ? true : false; } } else if (STREQ(stype, "file")) { isFile = true; } else if (STRNEQ(stype, "block")) { vshError(ctl, _("Unknown source type: '%s'"), stype); goto cleanup; } if (mode) { if (STRNEQ(mode, "readonly") && STRNEQ(mode, "shareable")) { vshError(ctl, _("No support for %s in command 'attach-disk'"), mode); goto cleanup; } } /* Make XML of disk */ virBufferAsprintf(&buf, "\n"); if (driver || subdriver || cache) { virBufferAsprintf(&buf, " \n"); } if (source) virBufferAsprintf(&buf, " \n", (isFile) ? "file" : "dev", source); virBufferAsprintf(&buf, " \n", target); if (mode) virBufferAsprintf(&buf, " <%s/>\n", mode); if (serial) virBufferAsprintf(&buf, " %s\n", serial); if (vshCommandOptBool(cmd, "shareable")) virBufferAsprintf(&buf, " \n"); if (straddr) { if (str2DiskAddress(straddr, &diskAddr) != 0) { vshError(ctl, _("Invalid address.")); goto cleanup; } if (STRPREFIX((const char *)target, "vd")) { if (diskAddr.type == DISK_ADDR_TYPE_PCI) { virBufferAsprintf(&buf, "
\n"); } else { vshError(ctl, "%s", _("expecting a pci:0000.00.00.00 address.")); goto cleanup; } } else if (STRPREFIX((const char *)target, "sd")) { if (diskAddr.type == DISK_ADDR_TYPE_SCSI) { virBufferAsprintf(&buf, "
\n", diskAddr.addr.scsi.controller, diskAddr.addr.scsi.bus, diskAddr.addr.scsi.unit); } else { vshError(ctl, "%s", _("expecting a scsi:00.00.00 address.")); goto cleanup; } } else if (STRPREFIX((const char *)target, "hd")) { if (diskAddr.type == DISK_ADDR_TYPE_IDE) { virBufferAsprintf(&buf, "
\n", diskAddr.addr.ide.controller, diskAddr.addr.ide.bus, diskAddr.addr.ide.unit); } else { vshError(ctl, "%s", _("expecting an ide:00.00.00 address.")); goto cleanup; } } } virBufferAddLit(&buf, "\n"); if (virBufferError(&buf)) { vshPrint(ctl, "%s", _("Failed to allocate XML buffer")); return false; } xml = virBufferContentAndReset(&buf); if (vshCommandOptBool(cmd, "config")) { flags = VIR_DOMAIN_AFFECT_CONFIG; if (virDomainIsActive(dom) == 1) flags |= VIR_DOMAIN_AFFECT_LIVE; ret = virDomainAttachDeviceFlags(dom, xml, flags); } else { ret = virDomainAttachDevice(dom, xml); } VIR_FREE(xml); if (ret != 0) { vshError(ctl, "%s", _("Failed to attach disk")); } else { vshPrint(ctl, "%s", _("Disk attached successfully\n")); functionReturn = true; } cleanup: if (dom) virDomainFree(dom); virBufferFreeAndReset(&buf); return functionReturn; } /* * "attach-interface" command */ static const vshCmdInfo info_attach_interface[] = { {"help", N_("attach network interface")}, {"desc", N_("Attach new network interface.")}, {NULL, NULL} }; static const vshCmdOptDef opts_attach_interface[] = { {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")}, {"type", VSH_OT_DATA, VSH_OFLAG_REQ, N_("network interface type")}, {"source", VSH_OT_DATA, VSH_OFLAG_REQ, N_("source of network interface")}, {"target", VSH_OT_DATA, 0, N_("target network name")}, {"mac", VSH_OT_DATA, 0, N_("MAC address")}, {"script", VSH_OT_DATA, 0, N_("script used to bridge network interface")}, {"model", VSH_OT_DATA, 0, N_("model type")}, {"persistent", VSH_OT_ALIAS, 0, "config"}, {"config", VSH_OT_BOOL, 0, N_("affect next boot")}, {"inbound", VSH_OT_DATA, VSH_OFLAG_NONE, N_("control domain's incoming traffics")}, {"outbound", VSH_OT_DATA, VSH_OFLAG_NONE, N_("control domain's outgoing traffics")}, {NULL, 0, 0, NULL} }; /* parse inbound and outbound which are in the format of * 'average,peak,burst', in which peak and burst are optional, * thus 'average,,burst' and 'average,peak' are also legal. */ static int parseRateStr(const char *rateStr, virNetDevBandwidthRatePtr rate) { const char *average = NULL; char *peak = NULL, *burst = NULL; average = rateStr; if (!average) return -1; if (virStrToLong_ull(average, &peak, 10, &rate->average) < 0) return -1; /* peak will be updated to point to the end of rateStr in case * of 'average' */ if (peak && *peak != '\0') { burst = strchr(peak + 1, ','); if (!(burst && (burst - peak == 1))) { if (virStrToLong_ull(peak + 1, &burst, 10, &rate->peak) < 0) return -1; } /* burst will be updated to point to the end of rateStr in case * of 'average,peak' */ if (burst && *burst != '\0') { if (virStrToLong_ull(burst + 1, NULL, 10, &rate->burst) < 0) return -1; } } return 0; } static bool cmdAttachInterface(vshControl *ctl, const vshCmd *cmd) { virDomainPtr dom = NULL; const char *mac = NULL, *target = NULL, *script = NULL, *type = NULL, *source = NULL, *model = NULL, *inboundStr = NULL, *outboundStr = NULL; virNetDevBandwidthRate inbound, outbound; int typ; int ret; bool functionReturn = false; unsigned int flags; virBuffer buf = VIR_BUFFER_INITIALIZER; char *xml; if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) goto cleanup; if (vshCommandOptString(cmd, "type", &type) <= 0) goto cleanup; if (vshCommandOptString(cmd, "source", &source) < 0 || vshCommandOptString(cmd, "target", &target) < 0 || vshCommandOptString(cmd, "mac", &mac) < 0 || vshCommandOptString(cmd, "script", &script) < 0 || vshCommandOptString(cmd, "model", &model) < 0 || vshCommandOptString(cmd, "inbound", &inboundStr) < 0 || vshCommandOptString(cmd, "outbound", &outboundStr) < 0) { vshError(ctl, "missing argument"); goto cleanup; } /* check interface type */ if (STREQ(type, "network")) { typ = 1; } else if (STREQ(type, "bridge")) { typ = 2; } else { vshError(ctl, _("No support for %s in command 'attach-interface'"), type); goto cleanup; } if (inboundStr) { memset(&inbound, 0, sizeof(inbound)); if (parseRateStr(inboundStr, &inbound) < 0) { vshError(ctl, _("inbound format is incorrect")); goto cleanup; } if (inbound.average == 0) { vshError(ctl, _("inbound average is mandatory")); goto cleanup; } } if (outboundStr) { memset(&outbound, 0, sizeof(outbound)); if (parseRateStr(outboundStr, &outbound) < 0) { vshError(ctl, _("outbound format is incorrect")); goto cleanup; } if (outbound.average == 0) { vshError(ctl, _("outbound average is mandatory")); goto cleanup; } } /* Make XML of interface */ virBufferAsprintf(&buf, "\n", type); if (typ == 1) virBufferAsprintf(&buf, " \n", source); else if (typ == 2) virBufferAsprintf(&buf, " \n", source); if (target != NULL) virBufferAsprintf(&buf, " \n", target); if (mac != NULL) virBufferAsprintf(&buf, " \n", mac); if (script != NULL) virBufferAsprintf(&buf, "