/* * virsh-domain.c: Commands to manage domain * * Copyright (C) 2005, 2007-2016 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 * . */ #include #include "virsh-domain.h" #include "virsh-util.h" #include #include #include #include #include "internal.h" #include "virbitmap.h" #include "virbuffer.h" #include "conf/domain_conf.h" #include "viralloc.h" #include "vircommand.h" #include "virfile.h" #include "virjson.h" #include "virkeycode.h" #include "virmacaddr.h" #include "virnetdevbandwidth.h" #include "virprocess.h" #include "virstring.h" #include "virsh-console.h" #include "virsh-domain-monitor.h" #include "virsh-host.h" #include "virtime.h" #include "virtypedparam.h" #include "virxml.h" #include "viruri.h" #include "vsh-table.h" #include "virenum.h" #include "virutil.h" enum virshAddressType { VIRSH_ADDRESS_TYPE_PCI, VIRSH_ADDRESS_TYPE_SCSI, VIRSH_ADDRESS_TYPE_IDE, VIRSH_ADDRESS_TYPE_CCW, VIRSH_ADDRESS_TYPE_USB, VIRSH_ADDRESS_TYPE_SATA, VIRSH_ADDRESS_TYPE_LAST }; VIR_ENUM_DECL(virshAddress); VIR_ENUM_IMPL(virshAddress, VIRSH_ADDRESS_TYPE_LAST, "pci", "scsi", "ide", "ccw", "usb", "sata"); struct virshAddressPCI { unsigned int domain; unsigned int bus; unsigned int slot; unsigned int function; bool multifunction; }; struct virshAddressDrive { unsigned int controller; unsigned int bus; unsigned long long unit; }; struct virshAddressCCW { unsigned int cssid; unsigned int ssid; unsigned int devno; }; struct virshAddressUSB { unsigned int bus; unsigned int port; }; struct virshAddress { int type; /* enum virshAddressType */ union { struct virshAddressPCI pci; struct virshAddressDrive drive; struct virshAddressCCW ccw; struct virshAddressUSB usb; } addr; }; /* 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) * ccw disk address: ccw:0xfe.0.0000 (cssid:ssid:devno) * usb disk address: usb:00.00 (bus:port) * sata disk address: sata:00.00.0 (controller:bus:unit) */ static int virshAddressParse(const char *str, bool multifunction, struct virshAddress *addr) { g_autofree char *type = g_strdup(str); char *a = strchr(type, ':'); if (!a) return -1; *a = '\0'; addr->type = virshAddressTypeFromString(type); switch ((enum virshAddressType) addr->type) { case VIRSH_ADDRESS_TYPE_PCI: addr->addr.pci.multifunction = multifunction; if (virStrToLong_uip(++a, &a, 16, &addr->addr.pci.domain) < 0 || virStrToLong_uip(++a, &a, 16, &addr->addr.pci.bus) < 0 || virStrToLong_uip(++a, &a, 16, &addr->addr.pci.slot) < 0 || virStrToLong_uip(++a, &a, 16, &addr->addr.pci.function) < 0) return -1; break; case VIRSH_ADDRESS_TYPE_SATA: case VIRSH_ADDRESS_TYPE_IDE: case VIRSH_ADDRESS_TYPE_SCSI: if (virStrToLong_uip(++a, &a, 10, &addr->addr.drive.controller) < 0 || virStrToLong_uip(++a, &a, 10, &addr->addr.drive.bus) < 0 || virStrToLong_ullp(++a, &a, 10, &addr->addr.drive.unit) < 0) return -1; break; case VIRSH_ADDRESS_TYPE_CCW: if (virStrToLong_uip(++a, &a, 16, &addr->addr.ccw.cssid) < 0 || virStrToLong_uip(++a, &a, 16, &addr->addr.ccw.ssid) < 0 || virStrToLong_uip(++a, &a, 16, &addr->addr.ccw.devno) < 0) return -1; break; case VIRSH_ADDRESS_TYPE_USB: if (virStrToLong_uip(++a, &a, 10, &addr->addr.usb.bus) < 0 || virStrToLong_uip(++a, &a, 10, &addr->addr.usb.port) < 0) return -1; break; case VIRSH_ADDRESS_TYPE_LAST: default: return -1; } return 0; } static void virshAddressFormat(virBuffer *buf, struct virshAddress *addr) { switch ((enum virshAddressType) addr->type) { case VIRSH_ADDRESS_TYPE_PCI: virBufferAsprintf(buf, "
addr.pci.domain, addr->addr.pci.bus, addr->addr.pci.slot, addr->addr.pci.function); if (addr->addr.pci.multifunction) virBufferAddLit(buf, " multifunction='on'"); virBufferAddLit(buf, "/>\n"); break; case VIRSH_ADDRESS_TYPE_SATA: case VIRSH_ADDRESS_TYPE_IDE: case VIRSH_ADDRESS_TYPE_SCSI: virBufferAsprintf(buf, "
\n", addr->addr.drive.controller, addr->addr.drive.bus, addr->addr.drive.unit); break; case VIRSH_ADDRESS_TYPE_CCW: virBufferAsprintf(buf, "
\n", addr->addr.ccw.cssid, addr->addr.ccw.ssid, addr->addr.ccw.devno); break; case VIRSH_ADDRESS_TYPE_USB: virBufferAsprintf(buf, "
\n", addr->addr.usb.bus, addr->addr.usb.port); break; case VIRSH_ADDRESS_TYPE_LAST: default: return; } } /** * virshFetchPassFdsList * * Helper to process the 'pass-fds' argument. */ static int virshFetchPassFdsList(vshControl *ctl, const vshCmd *cmd, size_t *nfdsret, int **fdsret) { const char *fdopt; g_auto(GStrv) fdlist = NULL; g_autofree int *fds = NULL; size_t nfds = 0; size_t i; *nfdsret = 0; *fdsret = NULL; if (vshCommandOptStringQuiet(ctl, cmd, "pass-fds", &fdopt) <= 0) return 0; if (!(fdlist = g_strsplit(fdopt, ",", -1))) { vshError(ctl, _("Unable to split FD list '%1$s'"), fdopt); return -1; } nfds = g_strv_length(fdlist); fds = g_new0(int, nfds); for (i = 0; i < nfds; i++) { if (virStrToLong_i(fdlist[i], NULL, 10, fds + i) < 0) { vshError(ctl, _("Unable to parse FD number '%1$s'"), fdlist[i]); return -1; } } *fdsret = g_steal_pointer(&fds); *nfdsret = nfds; return 0; } #define VIRSH_COMMON_OPT_DOMAIN_PERSISTENT \ {.name = "persistent", \ .type = VSH_OT_BOOL, \ .help = N_("make live change persistent") \ } #define VIRSH_COMMON_OPT_DOMAIN_CONFIG \ VIRSH_COMMON_OPT_CONFIG(N_("affect next boot")) #define VIRSH_COMMON_OPT_DOMAIN_LIVE \ VIRSH_COMMON_OPT_LIVE(N_("affect running domain")) #define VIRSH_COMMON_OPT_DOMAIN_CURRENT \ VIRSH_COMMON_OPT_CURRENT(N_("affect current domain")) static virDomainPtr virshDomainDefine(virConnectPtr conn, const char *xml, unsigned int flags) { virDomainPtr dom; if (!flags) return virDomainDefineXML(conn, xml); dom = virDomainDefineXMLFlags(conn, xml, flags); /* If validate is the only flag, just drop it and * try again. */ if (!dom) { if ((virGetLastErrorCode() == VIR_ERR_NO_SUPPORT) && (flags == VIR_DOMAIN_DEFINE_VALIDATE)) dom = virDomainDefineXML(conn, xml); } return dom; } VIR_ENUM_DECL(virshDomainVcpuState); VIR_ENUM_IMPL(virshDomainVcpuState, VIR_VCPU_LAST, N_("offline"), N_("running"), N_("blocked")); static const char * virshDomainVcpuStateToString(int state) { const char *str = virshDomainVcpuStateTypeToString(state); return str ? _(str) : _("no state"); } /* * Determine number of CPU nodes present by trying * virNodeGetCPUMap and falling back to virNodeGetInfo * if needed. */ static int virshNodeGetCPUCount(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 = { .help = N_("attach device from an XML file"), .desc = N_("Attach device from an XML ."), }; static const vshCmdOptDef opts_attach_device[] = { VIRSH_COMMON_OPT_DOMAIN_FULL(0), VIRSH_COMMON_OPT_FILE(N_("XML file")), VIRSH_COMMON_OPT_DOMAIN_PERSISTENT, VIRSH_COMMON_OPT_DOMAIN_CONFIG, VIRSH_COMMON_OPT_DOMAIN_LIVE, VIRSH_COMMON_OPT_DOMAIN_CURRENT, {.name = NULL} }; static bool cmdAttachDevice(vshControl *ctl, const vshCmd *cmd) { g_autoptr(virshDomain) dom = NULL; const char *from = NULL; g_autofree char *buffer = NULL; int rv; 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 = virshCommandOptDomain(ctl, cmd, NULL))) return false; if (vshCommandOptStringReq(ctl, cmd, "file", &from) < 0) return false; if (persistent && virDomainIsActive(dom) == 1) flags |= VIR_DOMAIN_AFFECT_LIVE; if (virFileReadAll(from, VSH_MAX_XML_FILE, &buffer) < 0) { vshReportError(ctl); return false; } if (flags || current) rv = virDomainAttachDeviceFlags(dom, buffer, flags); else rv = virDomainAttachDevice(dom, buffer); if (rv < 0) { vshError(ctl, _("Failed to attach device from %1$s"), from); return false; } vshPrintExtra(ctl, "%s", _("Device attached successfully\n")); return true; } /* * "attach-disk" command */ static const vshCmdInfo info_attach_disk = { .help = N_("attach disk device"), .desc = N_("Attach new disk device."), }; static const vshCmdOptDef opts_attach_disk[] = { VIRSH_COMMON_OPT_DOMAIN_FULL(0), {.name = "source", .type = VSH_OT_STRING, .positional = true, .required = true, .flags = VSH_OFLAG_EMPTY_OK, .help = N_("source of disk device or name of network disk") }, {.name = "target", .type = VSH_OT_STRING, .positional = true, .required = true, .completer = virshCompleteEmpty, .help = N_("target of disk device") }, {.name = "targetbus", .type = VSH_OT_STRING, .flags = VSH_OFLAG_REQ_OPT, .help = N_("target bus of disk device") }, {.name = "driver", .type = VSH_OT_STRING, .flags = VSH_OFLAG_REQ_OPT, .help = N_("driver of disk device") }, {.name = "subdriver", .type = VSH_OT_STRING, .flags = VSH_OFLAG_REQ_OPT, .help = N_("subdriver of disk device") }, {.name = "iothread", .type = VSH_OT_STRING, .flags = VSH_OFLAG_REQ_OPT, .completer = virshDomainIOThreadIdCompleter, .help = N_("IOThread to be used by supported device") }, {.name = "cache", .type = VSH_OT_STRING, .flags = VSH_OFLAG_REQ_OPT, .help = N_("cache mode of disk device") }, {.name = "io", .type = VSH_OT_STRING, .flags = VSH_OFLAG_REQ_OPT, .help = N_("io policy of disk device") }, {.name = "type", .type = VSH_OT_STRING, .flags = VSH_OFLAG_REQ_OPT, .help = N_("target device type") }, {.name = "shareable", .type = VSH_OT_ALIAS, .help = "mode=shareable" }, {.name = "mode", .type = VSH_OT_STRING, .flags = VSH_OFLAG_REQ_OPT, .help = N_("mode of device reading and writing") }, {.name = "sourcetype", .type = VSH_OT_STRING, .flags = VSH_OFLAG_REQ_OPT, .help = N_("type of source (block|file|network)") }, {.name = "serial", .type = VSH_OT_STRING, .flags = VSH_OFLAG_REQ_OPT, .completer = virshCompleteEmpty, .help = N_("serial of disk device") }, {.name = "wwn", .type = VSH_OT_STRING, .flags = VSH_OFLAG_REQ_OPT, .completer = virshCompleteEmpty, .help = N_("wwn of disk device") }, {.name = "alias", .type = VSH_OT_STRING, .flags = VSH_OFLAG_REQ_OPT, .completer = virshCompleteEmpty, .help = N_("custom alias name of disk device") }, {.name = "rawio", .type = VSH_OT_BOOL, .help = N_("needs rawio capability") }, {.name = "address", .type = VSH_OT_STRING, .flags = VSH_OFLAG_REQ_OPT, .completer = virshCompleteEmpty, .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 = "source-protocol", .type = VSH_OT_STRING, .flags = VSH_OFLAG_REQ_OPT, .help = N_("protocol used by disk device source") }, {.name = "source-host-name", .type = VSH_OT_STRING, .flags = VSH_OFLAG_REQ_OPT, .completer = virshCompleteEmpty, .help = N_("host name for source of disk device") }, {.name = "source-host-transport", .type = VSH_OT_STRING, .flags = VSH_OFLAG_REQ_OPT, .help = N_("host transport for source of disk device") }, {.name = "source-host-socket", .type = VSH_OT_STRING, .flags = VSH_OFLAG_REQ_OPT, .help = N_("host socket for source of disk device") }, VIRSH_COMMON_OPT_DOMAIN_PERSISTENT, VIRSH_COMMON_OPT_DOMAIN_CONFIG, VIRSH_COMMON_OPT_DOMAIN_LIVE, VIRSH_COMMON_OPT_DOMAIN_CURRENT, {.name = NULL} }; static int cmdAttachDiskFormatAddress(vshControl *ctl, virBuffer *buf, const char *straddr, const char *target, bool multifunction) { struct virshAddress diskAddr; if (virshAddressParse(straddr, multifunction, &diskAddr) < 0) { vshError(ctl, _("Invalid address.")); return -1; } if (STRPREFIX((const char *)target, "vd")) { if (diskAddr.type != VIRSH_ADDRESS_TYPE_PCI && diskAddr.type != VIRSH_ADDRESS_TYPE_CCW) { vshError(ctl, "%s", _("expecting a pci:0000.00.00.00 or ccw:00.0.0000 address.")); return -1; } } else if (STRPREFIX((const char *)target, "sd")) { if (diskAddr.type != VIRSH_ADDRESS_TYPE_SCSI && diskAddr.type != VIRSH_ADDRESS_TYPE_USB && diskAddr.type != VIRSH_ADDRESS_TYPE_SATA) { vshError(ctl, "%s", _("expecting a scsi:00.00.00 or usb:00.00 or sata:00.00.00 address.")); return -1; } } else if (STRPREFIX((const char *)target, "hd")) { if (diskAddr.type != VIRSH_ADDRESS_TYPE_IDE) { vshError(ctl, "%s", _("expecting an ide:00.00.00 address.")); return -1; } } virshAddressFormat(buf, &diskAddr); return 0; } enum virshAttachDiskSourceType { VIRSH_ATTACH_DISK_SOURCE_TYPE_NONE, VIRSH_ATTACH_DISK_SOURCE_TYPE_FILE, VIRSH_ATTACH_DISK_SOURCE_TYPE_BLOCK, VIRSH_ATTACH_DISK_SOURCE_TYPE_NETWORK, VIRSH_ATTACH_DISK_SOURCE_TYPE_LAST }; VIR_ENUM_DECL(virshAttachDiskSource); VIR_ENUM_IMPL(virshAttachDiskSource, VIRSH_ATTACH_DISK_SOURCE_TYPE_LAST, "", "file", "block", "network"); static bool cmdAttachDisk(vshControl *ctl, const vshCmd *cmd) { g_autoptr(virshDomain) dom = NULL; const char *source = NULL; const char *target = NULL; const char *driver = NULL; const char *subdriver = NULL; const char *device = NULL; const char *mode = NULL; const char *iothread = NULL; const char *cache = NULL; const char *io = NULL; const char *serial = NULL; const char *straddr = NULL; const char *wwn = NULL; const char *targetbus = NULL; const char *alias = NULL; const char *source_protocol = NULL; const char *host_name = NULL; const char *host_transport = NULL; const char *host_socket = NULL; int ret; unsigned int flags = VIR_DOMAIN_AFFECT_CURRENT; const char *stype = NULL; int type = VIR_STORAGE_TYPE_NONE; g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; g_auto(virBuffer) diskAttrBuf = VIR_BUFFER_INITIALIZER; g_auto(virBuffer) diskChildBuf = VIR_BUFFER_INIT_CHILD(&buf); g_auto(virBuffer) driverAttrBuf = VIR_BUFFER_INITIALIZER; g_auto(virBuffer) sourceAttrBuf = VIR_BUFFER_INITIALIZER; g_auto(virBuffer) sourceChildBuf = VIR_BUFFER_INIT_CHILD(&diskChildBuf); g_auto(virBuffer) hostAttrBuf = VIR_BUFFER_INITIALIZER; g_autofree char *xml = NULL; struct stat st; bool current = vshCommandOptBool(cmd, "current"); bool config = vshCommandOptBool(cmd, "config"); bool live = vshCommandOptBool(cmd, "live"); bool persistent = vshCommandOptBool(cmd, "persistent"); bool multifunction = vshCommandOptBool(cmd, "multifunction"); VSH_EXCLUSIVE_OPTIONS_VAR(persistent, current); VSH_EXCLUSIVE_OPTIONS_VAR(current, live); VSH_EXCLUSIVE_OPTIONS_VAR(current, config); VSH_REQUIRE_OPTION("source-host-name", "source-protocol"); VSH_REQUIRE_OPTION("source-host-transport", "source-protocol"); VSH_REQUIRE_OPTION("source-host-socket", "source-protocol"); VSH_REQUIRE_OPTION("source-host-socket", "source-host-transport"); VSH_EXCLUSIVE_OPTIONS("source-host-name", "source-host-socket"); if (config || persistent) flags |= VIR_DOMAIN_AFFECT_CONFIG; if (live) flags |= VIR_DOMAIN_AFFECT_LIVE; 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", &device) < 0 || vshCommandOptStringReq(ctl, cmd, "mode", &mode) < 0 || vshCommandOptStringReq(ctl, cmd, "iothread", &iothread) < 0 || vshCommandOptStringReq(ctl, cmd, "cache", &cache) < 0 || vshCommandOptStringReq(ctl, cmd, "io", &io) < 0 || vshCommandOptStringReq(ctl, cmd, "serial", &serial) < 0 || vshCommandOptStringReq(ctl, cmd, "wwn", &wwn) < 0 || vshCommandOptStringReq(ctl, cmd, "address", &straddr) < 0 || vshCommandOptStringReq(ctl, cmd, "targetbus", &targetbus) < 0 || vshCommandOptStringReq(ctl, cmd, "alias", &alias) < 0 || vshCommandOptStringReq(ctl, cmd, "sourcetype", &stype) < 0 || vshCommandOptStringReq(ctl, cmd, "source-protocol", &source_protocol) < 0 || vshCommandOptStringReq(ctl, cmd, "source-host-name", &host_name) < 0 || vshCommandOptStringReq(ctl, cmd, "source-host-transport", &host_transport) < 0 || vshCommandOptStringReq(ctl, cmd, "source-host-socket", &host_socket) < 0) return false; if (stype && (type = virshAttachDiskSourceTypeFromString(stype)) < 0) { vshError(ctl, _("Unknown source type: '%1$s'"), stype); return false; } if (type == VIRSH_ATTACH_DISK_SOURCE_TYPE_NONE) { if (source_protocol) { type = VIRSH_ATTACH_DISK_SOURCE_TYPE_NETWORK; } else if (STRNEQ_NULLABLE(driver, "file") && STRNEQ_NULLABLE(driver, "tap") && source && stat(source, &st) == 0 && S_ISBLK(st.st_mode)) { type = VIRSH_ATTACH_DISK_SOURCE_TYPE_BLOCK; } else { type = VIRSH_ATTACH_DISK_SOURCE_TYPE_FILE; } } if ((type == VIRSH_ATTACH_DISK_SOURCE_TYPE_NETWORK) != !!source_protocol) { vshError(ctl, _("--source-protocol option requires --sourcetype network")); return false; } if (mode && STRNEQ(mode, "readonly") && STRNEQ(mode, "shareable")) { vshError(ctl, _("No support for %1$s in command 'attach-disk'"), mode); return false; } if (wwn && !virValidateWWN(wwn)) return false; virBufferAsprintf(&diskAttrBuf, " type='%s'", virshAttachDiskSourceTypeToString(type)); virBufferEscapeString(&diskAttrBuf, " device='%s'", device); if (vshCommandOptBool(cmd, "rawio")) virBufferAddLit(&diskAttrBuf, " rawio='yes'"); virBufferEscapeString(&driverAttrBuf, " name='%s'", driver); virBufferEscapeString(&driverAttrBuf, " type='%s'", subdriver); virBufferEscapeString(&driverAttrBuf, " iothread='%s'", iothread); virBufferEscapeString(&driverAttrBuf, " cache='%s'", cache); virBufferEscapeString(&driverAttrBuf, " io='%s'", io); virXMLFormatElement(&diskChildBuf, "driver", &driverAttrBuf, NULL); switch ((enum virshAttachDiskSourceType) type) { case VIRSH_ATTACH_DISK_SOURCE_TYPE_FILE: virBufferEscapeString(&sourceAttrBuf, " file='%s'", source); break; case VIRSH_ATTACH_DISK_SOURCE_TYPE_BLOCK: virBufferEscapeString(&sourceAttrBuf, " dev='%s'", source); break; case VIRSH_ATTACH_DISK_SOURCE_TYPE_NETWORK: virBufferEscapeString(&sourceAttrBuf, " protocol='%s'", source_protocol); virBufferEscapeString(&sourceAttrBuf, " name='%s'", source); virBufferEscapeString(&hostAttrBuf, " transport='%s'", host_transport); virBufferEscapeString(&hostAttrBuf, " socket='%s'", host_socket); if (host_name) { g_autofree char *host_name_copy = g_strdup(host_name); char *host_port = strchr(host_name_copy, ':'); if (host_port) { *host_port = '\0'; host_port++; } virBufferEscapeString(&hostAttrBuf, " name='%s'", host_name_copy); virBufferEscapeString(&hostAttrBuf, " port='%s'", host_port); } virXMLFormatElement(&sourceChildBuf, "host", &hostAttrBuf, NULL); break; case VIRSH_ATTACH_DISK_SOURCE_TYPE_NONE: case VIRSH_ATTACH_DISK_SOURCE_TYPE_LAST: break; } virXMLFormatElement(&diskChildBuf, "source", &sourceAttrBuf, &sourceChildBuf); virBufferAsprintf(&diskChildBuf, "\n"); if (mode) virBufferAsprintf(&diskChildBuf, "<%s/>\n", mode); if (serial) virBufferAsprintf(&diskChildBuf, "%s\n", serial); if (alias) virBufferAsprintf(&diskChildBuf, "\n", alias); if (wwn) virBufferAsprintf(&diskChildBuf, "%s\n", wwn); if (straddr && cmdAttachDiskFormatAddress(ctl, &diskChildBuf, straddr, target, multifunction) < 0) return false; virXMLFormatElement(&buf, "disk", &diskAttrBuf, &diskChildBuf); xml = virBufferContentAndReset(&buf); if (vshCommandOptBool(cmd, "print-xml")) { vshPrint(ctl, "%s", xml); return true; } if (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) return false; if (persistent && virDomainIsActive(dom) == 1) flags |= VIR_DOMAIN_AFFECT_LIVE; if (flags || current) ret = virDomainAttachDeviceFlags(dom, xml, flags); else ret = virDomainAttachDevice(dom, xml); if (ret < 0) { vshError(ctl, "%s", _("Failed to attach disk")); return false; } vshPrintExtra(ctl, "%s", _("Disk attached successfully\n")); return true; } /* * "attach-interface" command */ static const vshCmdInfo info_attach_interface = { .help = N_("attach network interface"), .desc = N_("Attach new network interface."), }; static const vshCmdOptDef opts_attach_interface[] = { VIRSH_COMMON_OPT_DOMAIN_FULL(0), {.name = "type", .type = VSH_OT_STRING, .positional = true, .required = true, .help = N_("network interface type") }, {.name = "source", .type = VSH_OT_STRING, .positional = true, .required = true, .help = N_("source of network interface") }, {.name = "target", .type = VSH_OT_STRING, .unwanted_positional = true, .completer = virshCompleteEmpty, .help = N_("target network name") }, {.name = "mac", .type = VSH_OT_STRING, .unwanted_positional = true, .completer = virshCompleteEmpty, .help = N_("MAC address") }, {.name = "script", .type = VSH_OT_STRING, .unwanted_positional = true, .help = N_("script used to bridge network interface") }, {.name = "model", .type = VSH_OT_STRING, .unwanted_positional = true, .help = N_("model type") }, {.name = "alias", .type = VSH_OT_STRING, .unwanted_positional = true, .completer = virshCompleteEmpty, .help = N_("custom alias name of interface device") }, {.name = "inbound", .type = VSH_OT_STRING, .unwanted_positional = true, .completer = virshCompleteEmpty, .help = N_("control domain's incoming traffics") }, {.name = "outbound", .type = VSH_OT_STRING, .unwanted_positional = true, .completer = virshCompleteEmpty, .help = N_("control domain's outgoing traffics") }, VIRSH_COMMON_OPT_DOMAIN_PERSISTENT, VIRSH_COMMON_OPT_DOMAIN_CONFIG, VIRSH_COMMON_OPT_DOMAIN_LIVE, VIRSH_COMMON_OPT_DOMAIN_CURRENT, {.name = "print-xml", .type = VSH_OT_BOOL, .help = N_("print XML document rather than attach the interface") }, {.name = "managed", .type = VSH_OT_BOOL, .help = N_("libvirt will automatically detach/attach the device from/to host") }, {.name = "source-mode", .type = VSH_OT_STRING, .unwanted_positional = true, .completer = virshDomainInterfaceSourceModeCompleter, .help = N_("mode attribute of element") }, {.name = NULL} }; VIR_ENUM_IMPL(virshDomainInterfaceSourceMode, VIRSH_DOMAIN_INTERFACE_SOURCE_MODE_LAST, "server", "client"); /* parse inbound and outbound which are in the format of * 'average,peak,burst,floor', in which peak and burst are optional, * thus 'average,,burst' and 'average,peak' are also legal. */ #define VIRSH_PARSE_RATE_FIELD(index, name) \ do { \ if (index < ntok && \ *tok[index] != '\0' && \ virStrToLong_ullp(tok[index], NULL, 10, &rate->name) < 0) { \ vshError(ctl, _("field '%1$s' is malformed"), #name); \ return -1; \ } \ } while (0) static int virshParseRateStr(vshControl *ctl, const char *rateStr, virNetDevBandwidthRate *rate) { g_auto(GStrv) tok = NULL; size_t ntok; if (!(tok = g_strsplit(rateStr, ",", 0))) return -1; if ((ntok = g_strv_length(tok)) > 4) { vshError(ctl, _("Rate string '%1$s' has too many fields"), rateStr); return -1; } VIRSH_PARSE_RATE_FIELD(0, average); VIRSH_PARSE_RATE_FIELD(1, peak); VIRSH_PARSE_RATE_FIELD(2, burst); VIRSH_PARSE_RATE_FIELD(3, floor); return 0; } #undef VIRSH_PARSE_RATE_FIELD static bool cmdAttachInterface(vshControl *ctl, const vshCmd *cmd) { g_autoptr(virshDomain) dom = NULL; const char *mac = NULL, *target = NULL, *script = NULL, *type = NULL, *source = NULL, *model = NULL, *inboundStr = NULL, *outboundStr = NULL, *alias = NULL; const char *sourceModeStr = NULL; int sourceMode = -1; virNetDevBandwidthRate inbound = { 0 }; virNetDevBandwidthRate outbound = { 0 }; virDomainNetType typ; int ret; g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; g_autofree char *xml = NULL; 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"); bool managed = vshCommandOptBool(cmd, "managed"); 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 (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, "alias", &alias) < 0 || vshCommandOptStringReq(ctl, cmd, "inbound", &inboundStr) < 0 || vshCommandOptStringReq(ctl, cmd, "outbound", &outboundStr) < 0 || vshCommandOptStringReq(ctl, cmd, "source-mode", &sourceModeStr) < 0) return false; /* check interface type */ if ((int)(typ = virDomainNetTypeFromString(type)) < 0) { vshError(ctl, _("No support for %1$s in command 'attach-interface'"), type); return false; } if (sourceModeStr && (sourceMode = virshDomainInterfaceSourceModeTypeFromString(sourceModeStr)) < 0) { vshError(ctl, _("Invalid source mode: %1$s"), sourceModeStr); return false; } if (inboundStr) { if (virshParseRateStr(ctl, inboundStr, &inbound) < 0) return false; if (!inbound.average && !inbound.floor) { vshError(ctl, _("either inbound average or floor is mandatory")); return false; } } if (outboundStr) { if (virshParseRateStr(ctl, outboundStr, &outbound) < 0) return false; if (outbound.average == 0) { vshError(ctl, _("outbound average is mandatory")); return false; } if (outbound.floor) { vshError(ctl, _("outbound floor is unsupported yet")); return false; } } /* Make XML of interface */ virBufferAsprintf(&buf, "\n"); else virBufferAddLit(&buf, ">\n"); virBufferAdjustIndent(&buf, 2); switch (typ) { case VIR_DOMAIN_NET_TYPE_NETWORK: case VIR_DOMAIN_NET_TYPE_BRIDGE: virBufferAsprintf(&buf, "\n", virDomainNetTypeToString(typ), source); break; case VIR_DOMAIN_NET_TYPE_DIRECT: virBufferAsprintf(&buf, "\n", source); break; case VIR_DOMAIN_NET_TYPE_HOSTDEV: { g_autofree char *pciaddrstr = g_strdup_printf("pci:%s", source); struct virshAddress addr = { 0 }; if (virshAddressParse(pciaddrstr, false, &addr) < 0) { vshError(ctl, _("cannot parse pci address '%1$s' for network interface"), source); return false; } virBufferAddLit(&buf, "\n"); virBufferAdjustIndent(&buf, 2); virshAddressFormat(&buf, &addr); virBufferAdjustIndent(&buf, -2); virBufferAddLit(&buf, "\n"); break; } case VIR_DOMAIN_NET_TYPE_VHOSTUSER: if (sourceMode < 0) { vshError(ctl, _("source-mode is mandatory")); return false; } virBufferAsprintf(&buf, "\n", source, virshDomainInterfaceSourceModeTypeToString(sourceMode)); break; case VIR_DOMAIN_NET_TYPE_USER: case VIR_DOMAIN_NET_TYPE_ETHERNET: case VIR_DOMAIN_NET_TYPE_SERVER: case VIR_DOMAIN_NET_TYPE_CLIENT: case VIR_DOMAIN_NET_TYPE_MCAST: case VIR_DOMAIN_NET_TYPE_UDP: case VIR_DOMAIN_NET_TYPE_VDPA: case VIR_DOMAIN_NET_TYPE_INTERNAL: case VIR_DOMAIN_NET_TYPE_NULL: case VIR_DOMAIN_NET_TYPE_VDS: case VIR_DOMAIN_NET_TYPE_LAST: vshError(ctl, _("No support for %1$s in command 'attach-interface'"), type); return false; break; } if (target != NULL) virBufferAsprintf(&buf, "\n", target); if (mac != NULL) virBufferAsprintf(&buf, "\n", mac); if (script != NULL) virBufferAsprintf(&buf, "