/* * qemu_block.c: helper functions for QEMU block subsystem * * 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 "qemu_block.h" #include "qemu_command.h" #include "qemu_domain.h" #include "qemu_alias.h" #include "qemu_monitor_json.h" #include "viralloc.h" #include "virstring.h" #include "virlog.h" #define VIR_FROM_THIS VIR_FROM_QEMU VIR_LOG_INIT("qemu.qemu_block"); /* qemu declares the buffer for node names as a 32 byte array */ static const size_t qemuBlockNodeNameBufSize = 32; static int qemuBlockNodeNameValidate(const char *nn) { if (!nn) return 0; if (strlen(nn) >= qemuBlockNodeNameBufSize) { virReportError(VIR_ERR_INTERNAL_ERROR, _("node-name '%s' too long for qemu"), nn); return -1; } return 0; } static int qemuBlockNamedNodesArrayToHash(size_t pos ATTRIBUTE_UNUSED, virJSONValuePtr item, void *opaque) { virHashTablePtr table = opaque; const char *name; if (!(name = virJSONValueObjectGetString(item, "node-name"))) return 1; if (virHashAddEntry(table, name, item) < 0) return -1; return 0; } static void qemuBlockNodeNameBackingChainDataFree(qemuBlockNodeNameBackingChainDataPtr data) { if (!data) return; VIR_FREE(data->nodeformat); VIR_FREE(data->nodestorage); VIR_FREE(data->qemufilename); VIR_FREE(data->drvformat); VIR_FREE(data->drvstorage); qemuBlockNodeNameBackingChainDataFree(data->backing); VIR_FREE(data); } static void qemuBlockNodeNameBackingChainDataHashEntryFree(void *opaque, const void *name ATTRIBUTE_UNUSED) { qemuBlockNodeNameBackingChainDataFree(opaque); } /* list of driver names of layers that qemu automatically adds into the * backing chain */ static const char *qemuBlockDriversBlockjob[] = { "mirror_top", "commit_top", NULL }; static bool qemuBlockDriverMatch(const char *drvname, const char **drivers) { while (*drivers) { if (STREQ(drvname, *drivers)) return true; drivers++; } return false; } struct qemuBlockNodeNameGetBackingChainData { virHashTablePtr nodenamestable; virHashTablePtr disks; }; static int qemuBlockNodeNameGetBackingChainBacking(virJSONValuePtr next, virHashTablePtr nodenamestable, qemuBlockNodeNameBackingChainDataPtr *nodenamedata) { qemuBlockNodeNameBackingChainDataPtr data = NULL; qemuBlockNodeNameBackingChainDataPtr backingdata = NULL; virJSONValuePtr backing = virJSONValueObjectGetObject(next, "backing"); virJSONValuePtr parent = virJSONValueObjectGetObject(next, "parent"); virJSONValuePtr parentnodedata; virJSONValuePtr nodedata; const char *nodename = virJSONValueObjectGetString(next, "node-name"); const char *drvname = NULL; const char *drvparent = NULL; const char *parentnodename = NULL; const char *filename = NULL; int ret = -1; if (!nodename) return 0; if ((nodedata = virHashLookup(nodenamestable, nodename)) && (drvname = virJSONValueObjectGetString(nodedata, "drv"))) { /* qemu 2.9 reports layers in the backing chain which don't correspond * to files. skip them */ if (qemuBlockDriverMatch(drvname, qemuBlockDriversBlockjob)) { if (backing) { return qemuBlockNodeNameGetBackingChainBacking(backing, nodenamestable, nodenamedata); } else { return 0; } } } if (parent && (parentnodename = virJSONValueObjectGetString(parent, "node-name"))) { if ((parentnodedata = virHashLookup(nodenamestable, parentnodename))) { filename = virJSONValueObjectGetString(parentnodedata, "file"); drvparent = virJSONValueObjectGetString(parentnodedata, "drv"); } } if (VIR_ALLOC(data) < 0) goto cleanup; if (VIR_STRDUP(data->nodeformat, nodename) < 0 || VIR_STRDUP(data->nodestorage, parentnodename) < 0 || VIR_STRDUP(data->qemufilename, filename) < 0 || VIR_STRDUP(data->drvformat, drvname) < 0 || VIR_STRDUP(data->drvstorage, drvparent) < 0) goto cleanup; if (backing && qemuBlockNodeNameGetBackingChainBacking(backing, nodenamestable, &backingdata) < 0) goto cleanup; VIR_STEAL_PTR(data->backing, backingdata); VIR_STEAL_PTR(*nodenamedata, data); ret = 0; cleanup: qemuBlockNodeNameBackingChainDataFree(data); return ret; } static int qemuBlockNodeNameGetBackingChainDisk(size_t pos ATTRIBUTE_UNUSED, virJSONValuePtr item, void *opaque) { struct qemuBlockNodeNameGetBackingChainData *data = opaque; const char *device = virJSONValueObjectGetString(item, "device"); qemuBlockNodeNameBackingChainDataPtr devicedata = NULL; int ret = -1; if (qemuBlockNodeNameGetBackingChainBacking(item, data->nodenamestable, &devicedata) < 0) goto cleanup; if (devicedata && virHashAddEntry(data->disks, device, devicedata) < 0) goto cleanup; devicedata = NULL; ret = 1; /* we don't really want to steal @item */ cleanup: qemuBlockNodeNameBackingChainDataFree(devicedata); return ret; } /** * qemuBlockNodeNameGetBackingChain: * @namednodes: JSON array of data returned from 'query-named-block-nodes' * @blockstats: JSON array of data returned from 'query-blockstats' * * Tries to reconstruct the backing chain from @json to allow detection of * node names that were auto-assigned by qemu. This is a best-effort operation * and may not be successful. The returned hash table contains the entries as * qemuBlockNodeNameBackingChainDataPtr accessible by the node name. The fields * then can be used to recover the full backing chain. * * Returns a hash table on success and NULL on failure. */ virHashTablePtr qemuBlockNodeNameGetBackingChain(virJSONValuePtr namednodes, virJSONValuePtr blockstats) { struct qemuBlockNodeNameGetBackingChainData data; virHashTablePtr namednodestable = NULL; virHashTablePtr disks = NULL; virHashTablePtr ret = NULL; memset(&data, 0, sizeof(data)); if (!(namednodestable = virHashCreate(50, virJSONValueHashFree))) goto cleanup; if (virJSONValueArrayForeachSteal(namednodes, qemuBlockNamedNodesArrayToHash, namednodestable) < 0) goto cleanup; if (!(disks = virHashCreate(50, qemuBlockNodeNameBackingChainDataHashEntryFree))) goto cleanup; data.nodenamestable = namednodestable; data.disks = disks; if (virJSONValueArrayForeachSteal(blockstats, qemuBlockNodeNameGetBackingChainDisk, &data) < 0) goto cleanup; VIR_STEAL_PTR(ret, disks); cleanup: virHashFree(namednodestable); virHashFree(disks); return ret; } static void qemuBlockDiskClearDetectedNodes(virDomainDiskDefPtr disk) { virStorageSourcePtr next = disk->src; while (virStorageSourceIsBacking(next)) { VIR_FREE(next->nodeformat); VIR_FREE(next->nodestorage); next = next->backingStore; } } static int qemuBlockDiskDetectNodes(virDomainDiskDefPtr disk, virHashTablePtr disktable) { qemuBlockNodeNameBackingChainDataPtr entry = NULL; virStorageSourcePtr src = disk->src; char *alias = NULL; int ret = -1; /* don't attempt the detection if the top level already has node names */ if (src->nodeformat || src->nodestorage) return 0; if (!(alias = qemuAliasDiskDriveFromDisk(disk))) goto cleanup; if (!(entry = virHashLookup(disktable, alias))) { ret = 0; goto cleanup; } while (virStorageSourceIsBacking(src) && entry) { if (src->nodeformat || src->nodestorage) { if (STRNEQ_NULLABLE(src->nodeformat, entry->nodeformat) || STRNEQ_NULLABLE(src->nodestorage, entry->nodestorage)) goto cleanup; break; } else { if (VIR_STRDUP(src->nodeformat, entry->nodeformat) < 0 || VIR_STRDUP(src->nodestorage, entry->nodestorage) < 0) goto cleanup; } entry = entry->backing; src = src->backingStore; } ret = 0; cleanup: VIR_FREE(alias); if (ret < 0) qemuBlockDiskClearDetectedNodes(disk); return ret; } int qemuBlockNodeNamesDetect(virQEMUDriverPtr driver, virDomainObjPtr vm, qemuDomainAsyncJob asyncJob) { qemuDomainObjPrivatePtr priv = vm->privateData; virHashTablePtr disktable = NULL; virJSONValuePtr data = NULL; virJSONValuePtr blockstats = NULL; virDomainDiskDefPtr disk; size_t i; int ret = -1; if (!virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_QUERY_NAMED_BLOCK_NODES)) return 0; if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) return -1; data = qemuMonitorQueryNamedBlockNodes(qemuDomainGetMonitor(vm)); blockstats = qemuMonitorQueryBlockstats(qemuDomainGetMonitor(vm)); if (qemuDomainObjExitMonitor(driver, vm) < 0 || !data || !blockstats) goto cleanup; if (!(disktable = qemuBlockNodeNameGetBackingChain(data, blockstats))) goto cleanup; for (i = 0; i < vm->def->ndisks; i++) { disk = vm->def->disks[i]; if (qemuBlockDiskDetectNodes(disk, disktable) < 0) goto cleanup; } ret = 0; cleanup: virJSONValueFree(data); virJSONValueFree(blockstats); virHashFree(disktable); return ret; } /** * qemuBlockGetNodeData: * @data: JSON object returned from query-named-block-nodes * * Returns a hash table organized by the node name of the JSON value objects of * data for given qemu block nodes. * * Returns a filled virHashTablePtr on success NULL on error. */ virHashTablePtr qemuBlockGetNodeData(virJSONValuePtr data) { virHashTablePtr ret = NULL; if (!(ret = virHashCreate(50, virJSONValueHashFree))) return NULL; if (virJSONValueArrayForeachSteal(data, qemuBlockNamedNodesArrayToHash, ret) < 0) goto error; return ret; error: virHashFree(ret); return NULL; } /** * qemuBlockStorageSourceSupportsConcurrentAccess: * @src: disk storage source * * Returns true if the given storage format supports concurrent access from two * separate processes. */ bool qemuBlockStorageSourceSupportsConcurrentAccess(virStorageSourcePtr src) { /* no need to check in backing chain since only RAW storage supports this */ return src->format == VIR_STORAGE_FILE_RAW; } /** * qemuBlockStorageSourceGetURI: * @src: disk storage source * * Formats a URI from a virStorageSource. */ virURIPtr qemuBlockStorageSourceGetURI(virStorageSourcePtr src) { virURIPtr uri = NULL; virURIPtr ret = NULL; if (src->nhosts != 1) { virReportError(VIR_ERR_INTERNAL_ERROR, _("protocol '%s' accepts only one host"), virStorageNetProtocolTypeToString(src->protocol)); goto cleanup; } if (VIR_ALLOC(uri) < 0) goto cleanup; if (src->hosts->transport == VIR_STORAGE_NET_HOST_TRANS_TCP) { uri->port = src->hosts->port; if (VIR_STRDUP(uri->scheme, virStorageNetProtocolTypeToString(src->protocol)) < 0) goto cleanup; } else { if (virAsprintf(&uri->scheme, "%s+%s", virStorageNetProtocolTypeToString(src->protocol), virStorageNetHostTransportTypeToString(src->hosts->transport)) < 0) goto cleanup; } if (src->path) { if (src->volume) { if (virAsprintf(&uri->path, "/%s/%s", src->volume, src->path) < 0) goto cleanup; } else { if (virAsprintf(&uri->path, "%s%s", src->path[0] == '/' ? "" : "/", src->path) < 0) goto cleanup; } } if (VIR_STRDUP(uri->server, src->hosts->name) < 0) goto cleanup; VIR_STEAL_PTR(ret, uri); cleanup: virURIFree(uri); return ret; } /** * qemuBlockStorageSourceBuildJSONSocketAddress * @host: the virStorageNetHostDefPtr definition to build * @legacy: use old field names/values * * Formats @hosts into a json object conforming to the 'SocketAddress' type * in qemu. * * For compatibility with old approach used in the gluster driver of old qemus * use the old spelling for TCP transport and, the path field of the unix socket. * * Returns a virJSONValuePtr for a single server. */ static virJSONValuePtr qemuBlockStorageSourceBuildJSONSocketAddress(virStorageNetHostDefPtr host, bool legacy) { virJSONValuePtr server = NULL; virJSONValuePtr ret = NULL; const char *transport; const char *field; char *port = NULL; switch ((virStorageNetHostTransport) host->transport) { case VIR_STORAGE_NET_HOST_TRANS_TCP: if (legacy) transport = "tcp"; else transport = "inet"; if (virAsprintf(&port, "%u", host->port) < 0) goto cleanup; if (virJSONValueObjectCreate(&server, "s:type", transport, "s:host", host->name, "s:port", port, NULL) < 0) goto cleanup; break; case VIR_STORAGE_NET_HOST_TRANS_UNIX: if (legacy) field = "s:socket"; else field = "s:path"; if (virJSONValueObjectCreate(&server, "s:type", "unix", field, host->socket, NULL) < 0) goto cleanup; break; case VIR_STORAGE_NET_HOST_TRANS_RDMA: case VIR_STORAGE_NET_HOST_TRANS_LAST: virReportError(VIR_ERR_INTERNAL_ERROR, _("transport protocol '%s' is not yet supported"), virStorageNetHostTransportTypeToString(host->transport)); goto cleanup; } VIR_STEAL_PTR(ret, server); cleanup: VIR_FREE(port); virJSONValueFree(server); return ret; } /** * qemuBlockStorageSourceBuildHostsJSONSocketAddress: * @src: disk storage source * @legacy: use 'tcp' instead of 'inet' for compatibility reasons * * Formats src->hosts into a json object conforming to the 'SocketAddress' type * in qemu. */ static virJSONValuePtr qemuBlockStorageSourceBuildHostsJSONSocketAddress(virStorageSourcePtr src, bool legacy) { virJSONValuePtr servers = NULL; virJSONValuePtr server = NULL; virJSONValuePtr ret = NULL; virStorageNetHostDefPtr host; size_t i; if (!(servers = virJSONValueNewArray())) goto cleanup; for (i = 0; i < src->nhosts; i++) { host = src->hosts + i; if (!(server = qemuBlockStorageSourceBuildJSONSocketAddress(host, legacy))) goto cleanup; if (virJSONValueArrayAppend(servers, server) < 0) goto cleanup; server = NULL; } VIR_STEAL_PTR(ret, servers); cleanup: virJSONValueFree(servers); virJSONValueFree(server); return ret; } /** * qemuBlockStorageSourceBuildJSONInetSocketAddress * @host: the virStorageNetHostDefPtr definition to build * * Formats @hosts into a json object conforming to the 'InetSocketAddress' type * in qemu. * * Returns a virJSONValuePtr for a single server. */ static virJSONValuePtr qemuBlockStorageSourceBuildJSONInetSocketAddress(virStorageNetHostDefPtr host) { virJSONValuePtr ret = NULL; char *port = NULL; if (host->transport != VIR_STORAGE_NET_HOST_TRANS_TCP) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("only TCP protocol can be converted to InetSocketAddress")); return NULL; } if (virAsprintf(&port, "%u", host->port) < 0) return NULL; ignore_value(virJSONValueObjectCreate(&ret, "s:host", host->name, "s:port", port, NULL)); VIR_FREE(port); return ret; } /** * qemuBlockStorageSourceBuildHostsJSONInetSocketAddress: * @src: disk storage source * * Formats src->hosts into a json object conforming to the 'InetSocketAddress' * type in qemu. */ static virJSONValuePtr qemuBlockStorageSourceBuildHostsJSONInetSocketAddress(virStorageSourcePtr src) { virJSONValuePtr servers = NULL; virJSONValuePtr server = NULL; virJSONValuePtr ret = NULL; virStorageNetHostDefPtr host; size_t i; if (!(servers = virJSONValueNewArray())) goto cleanup; for (i = 0; i < src->nhosts; i++) { host = src->hosts + i; if (!(server = qemuBlockStorageSourceBuildJSONInetSocketAddress(host))) goto cleanup; if (virJSONValueArrayAppend(servers, server) < 0) goto cleanup; server = NULL; } VIR_STEAL_PTR(ret, servers); cleanup: virJSONValueFree(servers); virJSONValueFree(server); return ret; } static virJSONValuePtr qemuBlockStorageSourceGetGlusterProps(virStorageSourcePtr src, bool legacy) { virJSONValuePtr servers = NULL; virJSONValuePtr props = NULL; virJSONValuePtr ret = NULL; if (!(servers = qemuBlockStorageSourceBuildHostsJSONSocketAddress(src, legacy))) return NULL; /* { driver:"gluster", * volume:"testvol", * path:"/a.img", * server :[{type:"tcp", host:"1.2.3.4", port:24007}, * {type:"unix", socket:"/tmp/glusterd.socket"}, ...]} */ if (virJSONValueObjectCreate(&props, "s:driver", "gluster", "s:volume", src->volume, "s:path", src->path, "a:server", &servers, NULL) < 0) goto cleanup; if (src->debug && virJSONValueObjectAdd(props, "u:debug", src->debugLevel, NULL) < 0) goto cleanup; VIR_STEAL_PTR(ret, props); cleanup: virJSONValueFree(servers); virJSONValueFree(props); return ret; } static virJSONValuePtr qemuBlockStorageSourceGetVxHSProps(virStorageSourcePtr src) { const char *protocol = virStorageNetProtocolTypeToString(src->protocol); virJSONValuePtr server = NULL; virJSONValuePtr ret = NULL; if (src->nhosts != 1) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("VxHS protocol accepts only one host")); return NULL; } if (!(server = qemuBlockStorageSourceBuildJSONInetSocketAddress(&src->hosts[0]))) return NULL; /* VxHS disk specification example: * { driver:"vxhs", * tls-creds:"objvirtio-disk0_tls0", * vdisk-id:"eb90327c-8302-4725-4e85ed4dc251", * server:{type:"tcp", host:"1.2.3.4", port:9999}} */ if (virJSONValueObjectCreate(&ret, "s:driver", protocol, "S:tls-creds", src->tlsAlias, "s:vdisk-id", src->path, "a:server", &server, NULL) < 0) virJSONValueFree(server); return ret; } static virJSONValuePtr qemuBlockStorageSourceGetCURLProps(virStorageSourcePtr src) { qemuDomainStorageSourcePrivatePtr srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); const char *passwordalias = NULL; const char *username = NULL; virJSONValuePtr ret = NULL; virURIPtr uri = NULL; char *uristr = NULL; const char *driver; /** * Common options: * url, readahead, timeout, username, password-secret, proxy-username, * proxy-password-secret * * Options for http transport: * cookie, cookie-secret * * Options for secure transport (ftps, https): * sslverify */ driver = virStorageNetProtocolTypeToString(src->protocol); if (!(uri = qemuBlockStorageSourceGetURI(src))) goto cleanup; if (!(uristr = virURIFormat(uri))) goto cleanup; if (src->auth) { username = src->auth->username; passwordalias = srcPriv->secinfo->s.aes.alias; } ignore_value(virJSONValueObjectCreate(&ret, "s:driver", driver, "s:url", uristr, "S:username", username, "S:password-secret", passwordalias, NULL)); cleanup: virURIFree(uri); VIR_FREE(uristr); return ret; } static virJSONValuePtr qemuBlockStorageSourceGetISCSIProps(virStorageSourcePtr src) { qemuDomainStorageSourcePrivatePtr srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); const char *protocol = virStorageNetProtocolTypeToString(src->protocol); char *target = NULL; char *lunStr = NULL; char *username = NULL; char *objalias = NULL; char *portal = NULL; unsigned int lun = 0; virJSONValuePtr ret = NULL; /* { driver:"iscsi", * transport:"tcp", ("iser" also possible) * portal:"example.com", * target:"iqn.2017-04.com.example:iscsi-disks", * lun:1, * user:"username", * password-secret:"secret-alias", * initiator-name:"iqn.2017-04.com.example:client" * } */ if (VIR_STRDUP(target, src->path) < 0) goto cleanup; /* Separate the target and lun */ if ((lunStr = strchr(target, '/'))) { *(lunStr++) = '\0'; if (virStrToLong_ui(lunStr, NULL, 10, &lun) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("cannot parse target for lunStr '%s'"), target); goto cleanup; } } /* combine host and port into portal */ if (virSocketAddrNumericFamily(src->hosts[0].name) == AF_INET6) { if (virAsprintf(&portal, "[%s]:%u", src->hosts[0].name, src->hosts[0].port) < 0) goto cleanup; } else { if (virAsprintf(&portal, "%s:%u", src->hosts[0].name, src->hosts[0].port) < 0) goto cleanup; } if (src->auth) { username = src->auth->username; objalias = srcPriv->secinfo->s.aes.alias; } ignore_value(virJSONValueObjectCreate(&ret, "s:driver", protocol, "s:portal", portal, "s:target", target, "u:lun", lun, "s:transport", "tcp", "S:user", username, "S:password-secret", objalias, "S:initiator-name", src->initiator.iqn, NULL)); goto cleanup; cleanup: VIR_FREE(target); VIR_FREE(portal); return ret; } static virJSONValuePtr qemuBlockStorageSourceGetNBDProps(virStorageSourcePtr src) { virJSONValuePtr serverprops; virJSONValuePtr ret = NULL; if (src->nhosts != 1) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("nbd protocol accepts only one host")); return NULL; } serverprops = qemuBlockStorageSourceBuildJSONSocketAddress(&src->hosts[0], false); if (!serverprops) return NULL; if (virJSONValueObjectCreate(&ret, "s:driver", "nbd", "a:server", &serverprops, "S:export", src->path, "S:tls-creds", src->tlsAlias, NULL) < 0) goto cleanup; cleanup: virJSONValueFree(serverprops); return ret; } static virJSONValuePtr qemuBlockStorageSourceGetRBDProps(virStorageSourcePtr src) { qemuDomainStorageSourcePrivatePtr srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); virJSONValuePtr servers = NULL; virJSONValuePtr ret = NULL; const char *username = NULL; virJSONValuePtr authmodes = NULL; virJSONValuePtr mode = NULL; const char *keysecret = NULL; if (src->nhosts > 0 && !(servers = qemuBlockStorageSourceBuildHostsJSONInetSocketAddress(src))) return NULL; if (src->auth) { username = srcPriv->secinfo->s.aes.username; keysecret = srcPriv->secinfo->s.aes.alias; /* the auth modes are modelled after our old command line generator */ if (!(authmodes = virJSONValueNewArray())) goto cleanup; if (!(mode = virJSONValueNewString("cephx")) || virJSONValueArrayAppend(authmodes, mode) < 0) goto cleanup; mode = NULL; if (!(mode = virJSONValueNewString("none")) || virJSONValueArrayAppend(authmodes, mode) < 0) goto cleanup; mode = NULL; } if (virJSONValueObjectCreate(&ret, "s:driver", "rbd", "s:pool", src->volume, "s:image", src->path, "S:snapshot", src->snapshot, "S:conf", src->configFile, "A:server", &servers, "S:user", username, "A:auth-client-required", &authmodes, "S:key-secret", keysecret, NULL) < 0) goto cleanup; cleanup: virJSONValueFree(authmodes); virJSONValueFree(mode); virJSONValueFree(servers); return ret; } static virJSONValuePtr qemuBlockStorageSourceGetSheepdogProps(virStorageSourcePtr src) { virJSONValuePtr serverprops; virJSONValuePtr ret = NULL; if (src->nhosts != 1) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("sheepdog protocol accepts only one host")); return NULL; } serverprops = qemuBlockStorageSourceBuildJSONSocketAddress(&src->hosts[0], false); if (!serverprops) return NULL; /* libvirt does not support the 'snap-id' and 'tag' properties */ if (virJSONValueObjectCreate(&ret, "s:driver", "sheepdog", "a:server", &serverprops, "s:vdi", src->path, NULL) < 0) goto cleanup; cleanup: virJSONValueFree(serverprops); return ret; } static virJSONValuePtr qemuBlockStorageSourceGetSshProps(virStorageSourcePtr src) { virJSONValuePtr serverprops; virJSONValuePtr ret = NULL; const char *username = NULL; if (src->nhosts != 1) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("sheepdog protocol accepts only one host")); return NULL; } serverprops = qemuBlockStorageSourceBuildJSONInetSocketAddress(&src->hosts[0]); if (!serverprops) return NULL; if (src->auth) username = src->auth->username; if (virJSONValueObjectCreate(&ret, "s:driver", "ssh", "s:path", src->path, "a:server", &serverprops, "S:user", username, NULL) < 0) goto cleanup; cleanup: virJSONValueFree(serverprops); return ret; } static virJSONValuePtr qemuBlockStorageSourceGetFileProps(virStorageSourcePtr src) { const char *driver = "file"; const char *iomode = NULL; const char *prManagerAlias = NULL; virJSONValuePtr ret = NULL; if (src->iomode != VIR_DOMAIN_DISK_IO_DEFAULT) iomode = virDomainDiskIoTypeToString(src->iomode); if (virStorageSourceIsBlockLocal(src)) { if (src->hostcdrom) driver = "host_cdrom"; else driver = "host_device"; } if (src->pr) prManagerAlias = src->pr->mgralias; ignore_value(virJSONValueObjectCreate(&ret, "s:driver", driver, "s:filename", src->path, "S:aio", iomode, "S:pr-manager", prManagerAlias, NULL) < 0); return ret; } static virJSONValuePtr qemuBlockStorageSourceGetVvfatProps(virStorageSourcePtr src) { virJSONValuePtr ret = NULL; /* libvirt currently does not handle the following attributes: * '*fat-type': 'int' * '*label': 'str' */ ignore_value(virJSONValueObjectCreate(&ret, "s:driver", "vvfat", "s:dir", src->path, "b:floppy", src->floppyimg, "b:rw", !src->readonly, NULL)); return ret; } static int qemuBlockStorageSourceGetBlockdevGetCacheProps(virStorageSourcePtr src, virJSONValuePtr props) { virJSONValuePtr cacheobj; bool direct = false; bool noflush = false; if (src->cachemode == VIR_DOMAIN_DISK_CACHE_DEFAULT) return 0; if (qemuDomainDiskCachemodeFlags(src->cachemode, NULL, &direct, &noflush) < 0) return -1; if (virJSONValueObjectCreate(&cacheobj, "b:direct", direct, "b:no-flush", noflush, NULL) < 0) return -1; if (virJSONValueObjectAppend(props, "cache", cacheobj) < 0) { virJSONValueFree(cacheobj); return -1; } return 0; } /** * qemuBlockStorageSourceGetBackendProps: * @src: disk source * @legacy: use legacy formatting of attributes (for -drive / old qemus) * * Creates a JSON object describing the underlying storage or protocol of a * storage source. Returns NULL on error and reports an appropriate error message. */ virJSONValuePtr qemuBlockStorageSourceGetBackendProps(virStorageSourcePtr src, bool legacy) { int actualType = virStorageSourceGetActualType(src); virJSONValuePtr fileprops = NULL; virJSONValuePtr ret = NULL; switch ((virStorageType)actualType) { case VIR_STORAGE_TYPE_BLOCK: case VIR_STORAGE_TYPE_FILE: if (!(fileprops = qemuBlockStorageSourceGetFileProps(src))) return NULL; break; case VIR_STORAGE_TYPE_DIR: /* qemu handles directories by exposing them as a device with emulated * FAT filesystem */ if (!(fileprops = qemuBlockStorageSourceGetVvfatProps(src))) return NULL; break; case VIR_STORAGE_TYPE_VOLUME: case VIR_STORAGE_TYPE_NONE: case VIR_STORAGE_TYPE_LAST: return NULL; case VIR_STORAGE_TYPE_NETWORK: switch ((virStorageNetProtocol) src->protocol) { case VIR_STORAGE_NET_PROTOCOL_GLUSTER: if (!(fileprops = qemuBlockStorageSourceGetGlusterProps(src, legacy))) return NULL; break; case VIR_STORAGE_NET_PROTOCOL_VXHS: if (!(fileprops = qemuBlockStorageSourceGetVxHSProps(src))) return NULL; break; case VIR_STORAGE_NET_PROTOCOL_HTTP: case VIR_STORAGE_NET_PROTOCOL_HTTPS: case VIR_STORAGE_NET_PROTOCOL_FTP: case VIR_STORAGE_NET_PROTOCOL_FTPS: case VIR_STORAGE_NET_PROTOCOL_TFTP: if (!(fileprops = qemuBlockStorageSourceGetCURLProps(src))) return NULL; break; case VIR_STORAGE_NET_PROTOCOL_ISCSI: if (!(fileprops = qemuBlockStorageSourceGetISCSIProps(src))) return NULL; break; case VIR_STORAGE_NET_PROTOCOL_NBD: if (!(fileprops = qemuBlockStorageSourceGetNBDProps(src))) return NULL; break; case VIR_STORAGE_NET_PROTOCOL_RBD: if (!(fileprops = qemuBlockStorageSourceGetRBDProps(src))) return NULL; break; case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG: if (!(fileprops = qemuBlockStorageSourceGetSheepdogProps(src))) return NULL; break; case VIR_STORAGE_NET_PROTOCOL_SSH: if (!(fileprops = qemuBlockStorageSourceGetSshProps(src))) return NULL; break; case VIR_STORAGE_NET_PROTOCOL_NONE: case VIR_STORAGE_NET_PROTOCOL_LAST: return NULL; } break; } if (qemuBlockNodeNameValidate(src->nodestorage) < 0 || virJSONValueObjectAdd(fileprops, "S:node-name", src->nodestorage, NULL) < 0) goto cleanup; if (!legacy) { if (qemuBlockStorageSourceGetBlockdevGetCacheProps(src, fileprops) < 0) goto cleanup; if (virJSONValueObjectAdd(fileprops, "b:read-only", src->readonly, "s:discard", "unmap", NULL) < 0) goto cleanup; } VIR_STEAL_PTR(ret, fileprops); cleanup: virJSONValueFree(fileprops); return ret; } static int qemuBlockStorageSourceGetFormatRawProps(virStorageSourcePtr src, virJSONValuePtr props) { qemuDomainStorageSourcePrivatePtr srcPriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); const char *driver = "raw"; const char *secretalias = NULL; if (src->encryption && src->encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS && srcPriv && srcPriv->encinfo) { driver = "luks"; secretalias = srcPriv->encinfo->s.aes.alias; } /* currently unhandled properties for the 'raw' driver: * 'offset' * 'size' */ if (virJSONValueObjectAdd(props, "s:driver", driver, "S:key-secret", secretalias, NULL) < 0) return -1; return 0; } static int qemuBlockStorageSourceGetCryptoProps(virStorageSourcePtr src, virJSONValuePtr *encprops) { qemuDomainStorageSourcePrivatePtr srcpriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src); const char *encformat = NULL; *encprops = NULL; /* qemu requires encrypted secrets regardless of encryption method used when * passed using the blockdev infrastructure, thus only * VIR_DOMAIN_SECRET_INFO_TYPE_AES works here. The correct type needs to be * instantiated elsewhere. */ if (!src->encryption || !srcpriv || !srcpriv->encinfo || srcpriv->encinfo->type != VIR_DOMAIN_SECRET_INFO_TYPE_AES) return 0; switch ((virStorageEncryptionFormatType) src->encryption->format) { case VIR_STORAGE_ENCRYPTION_FORMAT_QCOW: encformat = "aes"; break; case VIR_STORAGE_ENCRYPTION_FORMAT_LUKS: encformat = "luks"; break; case VIR_STORAGE_ENCRYPTION_FORMAT_DEFAULT: case VIR_STORAGE_ENCRYPTION_FORMAT_LAST: default: virReportEnumRangeError(virStorageEncryptionFormatType, src->encryption->format); return -1; } return virJSONValueObjectCreate(encprops, "s:format", encformat, "s:key-secret", srcpriv->encinfo->s.aes.alias, NULL); } static int qemuBlockStorageSourceGetFormatQcowGenericProps(virStorageSourcePtr src, const char *format, virJSONValuePtr props) { virJSONValuePtr encprops = NULL; int ret = -1; if (qemuBlockStorageSourceGetCryptoProps(src, &encprops) < 0) return -1; if (virJSONValueObjectAdd(props, "s:driver", format, "A:encrypt", &encprops, NULL) < 0) goto cleanup; ret = 0; cleanup: virJSONValueFree(encprops); return ret; } static int qemuBlockStorageSourceGetFormatQcow2Props(virStorageSourcePtr src, virJSONValuePtr props) { /* currently unhandled qcow2 props: * * 'lazy-refcounts' * 'pass-discard-request' * 'pass-discard-snapshot' * 'pass-discard-other' * 'overlap-check' * 'l2-cache-size' * 'l2-cache-entry-size' * 'refcount-cache-size' * 'cache-clean-interval' */ if (qemuBlockStorageSourceGetFormatQcowGenericProps(src, "qcow2", props) < 0) return -1; return 0; } static virJSONValuePtr qemuBlockStorageSourceGetBlockdevFormatCommonProps(virStorageSourcePtr src) { const char *detectZeroes = NULL; const char *discard = NULL; int detectZeroesMode = virDomainDiskGetDetectZeroesMode(src->discard, src->detect_zeroes); virJSONValuePtr props = NULL; virJSONValuePtr ret = NULL; if (qemuBlockNodeNameValidate(src->nodeformat) < 0) return NULL; if (src->discard) discard = virDomainDiskDiscardTypeToString(src->discard); if (detectZeroesMode) detectZeroes = virDomainDiskDetectZeroesTypeToString(detectZeroesMode); /* currently unhandled global properties: * '*force-share': 'bool' */ if (virJSONValueObjectCreate(&props, "s:node-name", src->nodeformat, "b:read-only", src->readonly, "S:discard", discard, "S:detect-zeroes", detectZeroes, NULL) < 0) return NULL; if (qemuBlockStorageSourceGetBlockdevGetCacheProps(src, props) < 0) goto cleanup; VIR_STEAL_PTR(ret, props); cleanup: virJSONValueFree(props); return ret; } static virJSONValuePtr qemuBlockStorageSourceGetBlockdevFormatProps(virStorageSourcePtr src) { const char *driver = NULL; virJSONValuePtr props = NULL; virJSONValuePtr ret = NULL; if (!(props = qemuBlockStorageSourceGetBlockdevFormatCommonProps(src))) goto cleanup; switch ((virStorageFileFormat) src->format) { case VIR_STORAGE_FILE_FAT: /* The fat layer is emulated by the storage access layer, so we need to * put a raw layer on top */ case VIR_STORAGE_FILE_RAW: if (qemuBlockStorageSourceGetFormatRawProps(src, props) < 0) goto cleanup; break; case VIR_STORAGE_FILE_QCOW2: if (qemuBlockStorageSourceGetFormatQcow2Props(src, props) < 0) goto cleanup; break; case VIR_STORAGE_FILE_QCOW: if (qemuBlockStorageSourceGetFormatQcowGenericProps(src, "qcow", props) < 0) goto cleanup; break; /* formats without any special parameters */ case VIR_STORAGE_FILE_PLOOP: driver = "parallels"; break; case VIR_STORAGE_FILE_VHD: driver = "vhdx"; break; case VIR_STORAGE_FILE_BOCHS: case VIR_STORAGE_FILE_CLOOP: case VIR_STORAGE_FILE_DMG: case VIR_STORAGE_FILE_VDI: case VIR_STORAGE_FILE_VPC: case VIR_STORAGE_FILE_QED: case VIR_STORAGE_FILE_VMDK: driver = virStorageFileFormatTypeToString(src->format); break; case VIR_STORAGE_FILE_AUTO_SAFE: case VIR_STORAGE_FILE_AUTO: case VIR_STORAGE_FILE_NONE: case VIR_STORAGE_FILE_COW: case VIR_STORAGE_FILE_ISO: case VIR_STORAGE_FILE_DIR: virReportError(VIR_ERR_INTERNAL_ERROR, _("mishandled storage format '%s'"), virStorageFileFormatTypeToString(src->format)); goto cleanup; case VIR_STORAGE_FILE_LAST: default: virReportEnumRangeError(virStorageFileFormat, src->format); goto cleanup; } if (driver && virJSONValueObjectAdd(props, "s:driver", driver, NULL) < 0) goto cleanup; VIR_STEAL_PTR(ret, props); cleanup: virJSONValueFree(props); return ret; } /** * qemuBlockStorageSourceGetBlockdevProps: * * @src: storage source to format * * Formats @src into a JSON object which can be used with blockdev-add or * -blockdev. The formatted object contains both the storage and format layer * in nested form including link to the backing chain layer if necessary. */ virJSONValuePtr qemuBlockStorageSourceGetBlockdevProps(virStorageSourcePtr src) { bool backingSupported = src->format >= VIR_STORAGE_FILE_BACKING; virJSONValuePtr props = NULL; virJSONValuePtr ret = NULL; if (virStorageSourceHasBacking(src) && !backingSupported) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("storage format '%s' does not support backing store"), virStorageFileFormatTypeToString(src->format)); goto cleanup; } if (!(props = qemuBlockStorageSourceGetBlockdevFormatProps(src))) goto cleanup; if (virJSONValueObjectAppendString(props, "file", src->nodestorage) < 0) goto cleanup; if (src->backingStore && backingSupported) { if (virStorageSourceHasBacking(src)) { if (virJSONValueObjectAppendString(props, "backing", src->backingStore->nodeformat) < 0) goto cleanup; } else { /* chain is terminated, indicate that no detection should happen * in qemu */ if (virJSONValueObjectAppendNull(props, "backing") < 0) goto cleanup; } } VIR_STEAL_PTR(ret, props); cleanup: virJSONValueFree(props); return ret; } void qemuBlockStorageSourceAttachDataFree(qemuBlockStorageSourceAttachDataPtr data) { if (!data) return; virJSONValueFree(data->storageProps); virJSONValueFree(data->formatProps); virJSONValueFree(data->prmgrProps); virJSONValueFree(data->authsecretProps); virJSONValueFree(data->encryptsecretProps); virJSONValueFree(data->tlsProps); VIR_FREE(data->tlsAlias); VIR_FREE(data->authsecretAlias); VIR_FREE(data->encryptsecretAlias); VIR_FREE(data->driveCmd); VIR_FREE(data->driveAlias); VIR_FREE(data); } /** * qemuBlockStorageSourceAttachPrepareBlockdev: * @src: storage source to prepare data from * * Creates a qemuBlockStorageSourceAttachData structure containing data to attach * @src to a VM using the blockdev-add approach. Note that this function only * creates the data for the storage source itself, any other related * authentication/encryption/... objects need to be prepared separately. * * The changes are then applied using qemuBlockStorageSourceAttachApply. * * Returns the filled data structure on success or NULL on error and a libvirt * error is reported */ qemuBlockStorageSourceAttachDataPtr qemuBlockStorageSourceAttachPrepareBlockdev(virStorageSourcePtr src) { qemuBlockStorageSourceAttachDataPtr data; qemuBlockStorageSourceAttachDataPtr ret = NULL; if (VIR_ALLOC(data) < 0) return NULL; if (!(data->formatProps = qemuBlockStorageSourceGetBlockdevProps(src)) || !(data->storageProps = qemuBlockStorageSourceGetBackendProps(src, false))) goto cleanup; data->storageNodeName = src->nodestorage; data->formatNodeName = src->nodeformat; VIR_STEAL_PTR(ret, data); cleanup: qemuBlockStorageSourceAttachDataFree(data); return ret; } /** * qemuBlockStorageSourceAttachApply: * @mon: monitor object * @data: structure holding data of block device to apply * * Attaches a virStorageSource definition converted to * qemuBlockStorageSourceAttachData to a running VM. This function expects being * called after the monitor was entered. * * Returns 0 on success and -1 on error with a libvirt error reported. If an * error occured, changes which were already applied need to be rolled back by * calling qemuBlockStorageSourceAttachRollback. */ int qemuBlockStorageSourceAttachApply(qemuMonitorPtr mon, qemuBlockStorageSourceAttachDataPtr data) { int rv; if (data->prmgrProps && qemuMonitorAddObject(mon, &data->prmgrProps, &data->prmgrAlias) < 0) return -1; if (data->authsecretProps && qemuMonitorAddObject(mon, &data->authsecretProps, &data->authsecretAlias) < 0) return -1; if (data->encryptsecretProps && qemuMonitorAddObject(mon, &data->encryptsecretProps, &data->encryptsecretAlias) < 0) return -1; if (data->tlsProps && qemuMonitorAddObject(mon, &data->tlsProps, &data->tlsAlias) < 0) return -1; if (data->storageProps) { rv = qemuMonitorBlockdevAdd(mon, data->storageProps); data->storageProps = NULL; if (rv < 0) return -1; data->storageAttached = true; } if (data->formatProps) { rv = qemuMonitorBlockdevAdd(mon, data->formatProps); data->formatProps = NULL; if (rv < 0) return -1; data->formatAttached = true; } if (data->driveCmd) { if (qemuMonitorAddDrive(mon, data->driveCmd) < 0) return -1; data->driveAdded = true; } return 0; } /** * qemuBlockStorageSourceAttachRollback: * @mon: monitor object * @data: structure holding data of block device to roll back * * Attempts a best effort rollback of changes which were made to a running VM by * qemuBlockStorageSourceAttachApply. Preserves any existing errors. * * This function expects being called after the monitor was entered. */ void qemuBlockStorageSourceAttachRollback(qemuMonitorPtr mon, qemuBlockStorageSourceAttachDataPtr data) { virErrorPtr orig_err; virErrorPreserveLast(&orig_err); if (data->driveAdded) { if (qemuMonitorDriveDel(mon, data->driveAlias) < 0) VIR_WARN("Unable to remove drive %s (%s) after failed " "qemuMonitorAddDevice", data->driveAlias, data->driveCmd); } if (data->formatAttached) ignore_value(qemuMonitorBlockdevDel(mon, data->formatNodeName)); if (data->storageAttached) ignore_value(qemuMonitorBlockdevDel(mon, data->storageNodeName)); if (data->prmgrAlias) ignore_value(qemuMonitorDelObject(mon, data->prmgrAlias)); if (data->authsecretAlias) ignore_value(qemuMonitorDelObject(mon, data->authsecretAlias)); if (data->encryptsecretAlias) ignore_value(qemuMonitorDelObject(mon, data->encryptsecretAlias)); if (data->tlsAlias) ignore_value(qemuMonitorDelObject(mon, data->tlsAlias)); virErrorRestore(&orig_err); } /** * qemuBlockStorageSourceDetachOneBlockdev: * @driver: qemu driver object * @vm: domain object * @asyncJob: currently running async job * @src: storage source to detach * * Detaches one virStorageSource using blockdev-del. Note that this does not * detach any authentication/encryption objects. This function enters the * monitor internally. */ int qemuBlockStorageSourceDetachOneBlockdev(virQEMUDriverPtr driver, virDomainObjPtr vm, qemuDomainAsyncJob asyncJob, virStorageSourcePtr src) { int ret; if (qemuDomainObjEnterMonitorAsync(driver, vm, asyncJob) < 0) return -1; ret = qemuMonitorBlockdevDel(qemuDomainGetMonitor(vm), src->nodeformat); if (ret == 0) ret = qemuMonitorBlockdevDel(qemuDomainGetMonitor(vm), src->nodestorage); if (qemuDomainObjExitMonitor(driver, vm) < 0) return -1; return ret; } int qemuBlockSnapshotAddLegacy(virJSONValuePtr actions, virDomainDiskDefPtr disk, virStorageSourcePtr newsrc, bool reuse) { const char *format = virStorageFileFormatTypeToString(newsrc->format); char *device = NULL; char *source = NULL; int ret = -1; if (!(device = qemuAliasDiskDriveFromDisk(disk))) goto cleanup; if (qemuGetDriveSourceString(newsrc, NULL, &source) < 0) goto cleanup; if (qemuMonitorJSONTransactionAdd(actions, "blockdev-snapshot-sync", "s:device", device, "s:snapshot-file", source, "s:format", format, "S:mode", reuse ? "existing" : NULL, NULL) < 0) goto cleanup; ret = 0; cleanup: VIR_FREE(device); VIR_FREE(source); return ret; }