/* * virsh-domain.c: Commands to manage domain * * Copyright (C) 2005, 2007-2013 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 "internal.h" #include "virbitmap.h" #include "virbuffer.h" #include "c-ctype.h" #include "conf/domain_conf.h" #include "console.h" #include "viralloc.h" #include "vircommand.h" #include "virfile.h" #include "virjson.h" #include "virkeycode.h" #include "virmacaddr.h" #include "virprocess.h" #include "virstring.h" #include "virsh-domain-monitor.h" #include "virerror.h" #include "virtypedparam.h" #include "virxml.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 (vshCommandOptStringReq(ctl, 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 ((virVcpuState) state) { case VIR_VCPU_OFFLINE: return N_("offline"); case VIR_VCPU_BLOCKED: return N_("idle"); case VIR_VCPU_RUNNING: return N_("running"); case VIR_VCPU_LAST: break; } return N_("no state"); } /* * Determine number of CPU nodes present by trying * virNodeGetCPUMap and falling back to virNodeGetInfo * if needed. */ static int vshNodeGetCPUCount(virConnectPtr conn) { int ret; virNodeInfo nodeinfo; if ((ret = virNodeGetCPUMap(conn, NULL, NULL, 0)) < 0) { /* fall back to nodeinfo */ vshResetLibvirtError(); if (virNodeGetInfo(conn, &nodeinfo) == 0) { ret = VIR_NODEINFO_MAXCPUS(nodeinfo); } } return ret; } /* * "attach-device" command */ static const vshCmdInfo info_attach_device[] = { {.name = "help", .data = N_("attach device from an XML file") }, {.name = "desc", .data = N_("Attach device from an XML .") }, {.name = NULL} }; static const vshCmdOptDef opts_attach_device[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, .help = N_("domain name, id or uuid") }, {.name = "file", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, .help = N_("XML file") }, {.name = "persistent", .type = VSH_OT_BOOL, .help = N_("make live change persistent") }, {.name = "config", .type = VSH_OT_BOOL, .help = N_("affect next boot") }, {.name = "live", .type = VSH_OT_BOOL, .help = N_("affect running domain") }, {.name = "current", .type = VSH_OT_BOOL, .help = N_("affect current domain") }, {.name = NULL} }; static bool cmdAttachDevice(vshControl *ctl, const vshCmd *cmd) { virDomainPtr dom; const char *from = NULL; char *buffer; int rv; bool ret = false; unsigned int flags = VIR_DOMAIN_AFFECT_CURRENT; bool current = vshCommandOptBool(cmd, "current"); bool config = vshCommandOptBool(cmd, "config"); bool live = vshCommandOptBool(cmd, "live"); bool persistent = vshCommandOptBool(cmd, "persistent"); VSH_EXCLUSIVE_OPTIONS_VAR(persistent, current); VSH_EXCLUSIVE_OPTIONS_VAR(current, live); VSH_EXCLUSIVE_OPTIONS_VAR(current, config); if (config || persistent) flags |= VIR_DOMAIN_AFFECT_CONFIG; if (live) flags |= VIR_DOMAIN_AFFECT_LIVE; if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) return false; if (vshCommandOptStringReq(ctl, cmd, "file", &from) < 0) goto cleanup; if (persistent && virDomainIsActive(dom) == 1) flags |= VIR_DOMAIN_AFFECT_LIVE; if (virFileReadAll(from, VSH_MAX_XML_FILE, &buffer) < 0) { vshReportError(ctl); goto cleanup; } if (flags) rv = virDomainAttachDeviceFlags(dom, buffer, flags); else rv = virDomainAttachDevice(dom, buffer); VIR_FREE(buffer); if (rv < 0) { vshError(ctl, _("Failed to attach device from %s"), from); goto cleanup; } vshPrint(ctl, "%s", _("Device attached successfully\n")); ret = true; cleanup: virDomainFree(dom); return ret; } /* * "attach-disk" command */ static const vshCmdInfo info_attach_disk[] = { {.name = "help", .data = N_("attach disk device") }, {.name = "desc", .data = N_("Attach new disk device.") }, {.name = NULL} }; static const vshCmdOptDef opts_attach_disk[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, .help = N_("domain name, id or uuid") }, {.name = "source", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ | VSH_OFLAG_EMPTY_OK, .help = N_("source of disk device") }, {.name = "target", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, .help = N_("target of disk device") }, {.name = "driver", .type = VSH_OT_STRING, .help = N_("driver of disk device") }, {.name = "subdriver", .type = VSH_OT_STRING, .help = N_("subdriver of disk device") }, {.name = "cache", .type = VSH_OT_STRING, .help = N_("cache mode of disk device") }, {.name = "type", .type = VSH_OT_STRING, .help = N_("target device type") }, {.name = "mode", .type = VSH_OT_STRING, .help = N_("mode of device reading and writing") }, {.name = "persistent", .type = VSH_OT_ALIAS, .help = "config" }, {.name = "config", .type = VSH_OT_BOOL, .help = N_("affect next boot") }, {.name = "sourcetype", .type = VSH_OT_STRING, .help = N_("type of source (block|file)") }, {.name = "serial", .type = VSH_OT_STRING, .help = N_("serial of disk device") }, {.name = "shareable", .type = VSH_OT_BOOL, .help = N_("shareable between domains") }, {.name = "rawio", .type = VSH_OT_BOOL, .help = N_("needs rawio capability") }, {.name = "address", .type = VSH_OT_STRING, .help = N_("address of disk device") }, {.name = "multifunction", .type = VSH_OT_BOOL, .help = N_("use multifunction pci under specified address") }, {.name = "print-xml", .type = VSH_OT_BOOL, .help = N_("print XML document rather than attach the disk") }, {.name = 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 = NULL; struct stat st; if (!(dom = vshCommandOptDomain(ctl, cmd, NULL))) return false; if (vshCommandOptStringReq(ctl, cmd, "source", &source) < 0 || vshCommandOptStringReq(ctl, cmd, "target", &target) < 0 || vshCommandOptStringReq(ctl, cmd, "driver", &driver) < 0 || vshCommandOptStringReq(ctl, cmd, "subdriver", &subdriver) < 0 || vshCommandOptStringReq(ctl, cmd, "type", &type) < 0 || vshCommandOptStringReq(ctl, cmd, "mode", &mode) < 0 || vshCommandOptStringReq(ctl, cmd, "cache", &cache) < 0 || vshCommandOptStringReq(ctl, cmd, "serial", &serial) < 0 || vshCommandOptStringReq(ctl, cmd, "address", &straddr) < 0 || vshCommandOptStringReq(ctl, cmd, "sourcetype", &stype) < 0) 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) { virBufferAddLit(&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")) virBufferAddLit(&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, "print-xml")) { vshPrint(ctl, "%s", xml); functionReturn = true; goto cleanup; } 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); } if (ret != 0) { vshError(ctl, "%s", _("Failed to attach disk")); } else { vshPrint(ctl, "%s", _("Disk attached successfully\n")); functionReturn = true; } cleanup: VIR_FREE(xml); virDomainFree(dom); virBufferFreeAndReset(&buf); return functionReturn; } /* * "attach-interface" command */ static const vshCmdInfo info_attach_interface[] = { {.name = "help", .data = N_("attach network interface") }, {.name = "desc", .data = N_("Attach new network interface.") }, {.name = NULL} }; static const vshCmdOptDef opts_attach_interface[] = { {.name = "domain", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, .help = N_("domain name, id or uuid") }, {.name = "type", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, .help = N_("network interface type") }, {.name = "source", .type = VSH_OT_DATA, .flags = VSH_OFLAG_REQ, .help = N_("source of network interface") }, {.name = "target", .type = VSH_OT_DATA, .help = N_("target network name") }, {.name = "mac", .type = VSH_OT_DATA, .help = N_("MAC address") }, {.name = "script", .type = VSH_OT_DATA, .help = N_("script used to bridge network interface") }, {.name = "model", .type = VSH_OT_DATA, .help = N_("model type") }, {.name = "persistent", .type = VSH_OT_ALIAS, .help = "config" }, {.name = "config", .type = VSH_OT_BOOL, .help = N_("affect next boot") }, {.name = "inbound", .type = VSH_OT_DATA, .help = N_("control domain's incoming traffics") }, {.name = "outbound", .type = VSH_OT_DATA, .help = N_("control domain's outgoing traffics") }, {.name = 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 (vshCommandOptStringReq(ctl, cmd, "type", &type) < 0 || vshCommandOptStringReq(ctl, cmd, "source", &source) < 0 || vshCommandOptStringReq(ctl, cmd, "target", &target) < 0 || vshCommandOptStringReq(ctl, cmd, "mac", &mac) < 0 || vshCommandOptStringReq(ctl, cmd, "script", &script) < 0 || vshCommandOptStringReq(ctl, cmd, "model", &model) < 0 || vshCommandOptStringReq(ctl, cmd, "inbound", &inboundStr) < 0 || vshCommandOptStringReq(ctl, cmd, "outbound", &outboundStr) < 0) 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, "