/* * storage_source_backingstore.c: helpers for parsing backing store strings * * Copyright (C) 2007-2017 Red Hat, Inc. * Copyright (C) 2007-2008 Daniel P. Berrange * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . */ #include #include "internal.h" #include "storage_source_backingstore.h" #include "viruri.h" #include "virstring.h" #include "virjson.h" #include "virlog.h" #include "viralloc.h" #define VIR_FROM_THIS VIR_FROM_STORAGE VIR_LOG_INIT("storage_source_backingstore"); int virStorageSourceParseBackingURI(virStorageSourcePtr src, const char *uristr) { g_autoptr(virURI) uri = NULL; const char *path = NULL; g_auto(GStrv) scheme = NULL; if (!(uri = virURIParse(uristr))) { virReportError(VIR_ERR_INTERNAL_ERROR, _("failed to parse backing file location '%s'"), uristr); return -1; } src->hosts = g_new0(virStorageNetHostDef, 1); src->nhosts = 1; if (!(scheme = g_strsplit(uri->scheme, "+", 2))) return -1; if (!scheme[0] || (src->protocol = virStorageNetProtocolTypeFromString(scheme[0])) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("invalid backing protocol '%s'"), NULLSTR(scheme[0])); return -1; } if (scheme[1] && (src->hosts->transport = virStorageNetHostTransportTypeFromString(scheme[1])) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("invalid protocol transport type '%s'"), scheme[1]); return -1; } if (uri->query) { if (src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTP || src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS) { src->query = g_strdup(uri->query); } else { /* handle socket stored as a query */ if (STRPREFIX(uri->query, "socket=")) src->hosts->socket = g_strdup(STRSKIP(uri->query, "socket=")); } } /* uri->path is NULL if the URI does not contain slash after host: * transport://host:port */ if (uri->path) path = uri->path; else path = ""; /* possibly skip the leading slash */ if (path[0] == '/') path++; /* NBD allows empty export name (path) */ if (src->protocol == VIR_STORAGE_NET_PROTOCOL_NBD && path[0] == '\0') path = NULL; src->path = g_strdup(path); if (src->protocol == VIR_STORAGE_NET_PROTOCOL_GLUSTER) { char *tmp; if (!src->path) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("missing volume name and path for gluster volume")); return -1; } if (!(tmp = strchr(src->path, '/')) || tmp == src->path) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("missing volume name or file name in " "gluster source path '%s'"), src->path); return -1; } src->volume = src->path; src->path = g_strdup(tmp + 1); tmp[0] = '\0'; } src->hosts->port = uri->port; src->hosts->name = g_strdup(uri->server); /* Libvirt doesn't handle inline authentication. Make the caller aware. */ if (uri->user) return 1; return 0; } static int virStorageSourceRBDAddHost(virStorageSourcePtr src, char *hostport) { char *port; size_t skip; g_auto(GStrv) parts = NULL; if (VIR_EXPAND_N(src->hosts, src->nhosts, 1) < 0) return -1; if ((port = strchr(hostport, ']'))) { /* ipv6, strip brackets */ hostport += 1; skip = 3; } else { port = strstr(hostport, "\\:"); skip = 2; } if (port) { *port = '\0'; port += skip; if (virStringParsePort(port, &src->hosts[src->nhosts - 1].port) < 0) goto error; } parts = g_strsplit(hostport, "\\:", 0); if (!parts) goto error; src->hosts[src->nhosts-1].name = virStringListJoin((const char **)parts, ":"); if (!src->hosts[src->nhosts-1].name) goto error; src->hosts[src->nhosts-1].transport = VIR_STORAGE_NET_HOST_TRANS_TCP; src->hosts[src->nhosts-1].socket = NULL; return 0; error: VIR_FREE(src->hosts[src->nhosts-1].name); return -1; } int virStorageSourceParseRBDColonString(const char *rbdstr, virStorageSourcePtr src) { char *p, *e, *next; g_autofree char *options = NULL; g_autoptr(virStorageAuthDef) authdef = NULL; /* optionally skip the "rbd:" prefix if provided */ if (STRPREFIX(rbdstr, "rbd:")) rbdstr += strlen("rbd:"); src->path = g_strdup(rbdstr); p = strchr(src->path, ':'); if (p) { options = g_strdup(p + 1); *p = '\0'; } /* snapshot name */ if ((p = strchr(src->path, '@'))) { src->snapshot = g_strdup(p + 1); *p = '\0'; } /* pool vs. image name */ if ((p = strchr(src->path, '/'))) { src->volume = g_steal_pointer(&src->path); src->path = g_strdup(p + 1); *p = '\0'; } /* options */ if (!options) return 0; /* all done */ p = options; while (*p) { /* find : delimiter or end of string */ for (e = p; *e && *e != ':'; ++e) { if (*e == '\\') { e++; if (*e == '\0') break; } } if (*e == '\0') { next = e; /* last kv pair */ } else { next = e + 1; *e = '\0'; } if (STRPREFIX(p, "id=")) { /* formulate authdef for src->auth */ if (src->auth) { virReportError(VIR_ERR_INTERNAL_ERROR, _("duplicate 'id' found in '%s'"), src->path); return -1; } authdef = g_new0(virStorageAuthDef, 1); authdef->username = g_strdup(p + strlen("id=")); authdef->secrettype = g_strdup(virSecretUsageTypeToString(VIR_SECRET_USAGE_TYPE_CEPH)); src->auth = g_steal_pointer(&authdef); /* Cannot formulate a secretType (eg, usage or uuid) given * what is provided. */ } if (STRPREFIX(p, "mon_host=")) { char *h, *sep; h = p + strlen("mon_host="); while (h < e) { for (sep = h; sep < e; ++sep) { if (*sep == '\\' && (sep[1] == ',' || sep[1] == ';' || sep[1] == ' ')) { *sep = '\0'; sep += 2; break; } } if (virStorageSourceRBDAddHost(src, h) < 0) return -1; h = sep; } } if (STRPREFIX(p, "conf=")) src->configFile = g_strdup(p + strlen("conf=")); p = next; } return 0; } static int virStorageSourceParseNBDColonString(const char *nbdstr, virStorageSourcePtr src) { g_autofree char *nbd = g_strdup(nbdstr); char *export_name; char *host_spec; char *unixpath; char *port; src->hosts = g_new0(virStorageNetHostDef, 1); src->nhosts = 1; /* We extract the parameters in a similar way qemu does it */ /* format: [] denotes optional sections, uppercase are variable strings * nbd:unix:/PATH/TO/SOCKET[:exportname=EXPORTNAME] * nbd:HOSTNAME:PORT[:exportname=EXPORTNAME] */ /* first look for ':exportname=' and cut it off */ if ((export_name = strstr(nbd, ":exportname="))) { src->path = g_strdup(export_name + strlen(":exportname=")); export_name[0] = '\0'; } /* Verify the prefix and contents. Note that we require a * "host_spec" part to be present. */ if (!(host_spec = STRSKIP(nbd, "nbd:")) || host_spec[0] == '\0') goto malformed; if ((unixpath = STRSKIP(host_spec, "unix:"))) { src->hosts->transport = VIR_STORAGE_NET_HOST_TRANS_UNIX; if (unixpath[0] == '\0') goto malformed; src->hosts->socket = g_strdup(unixpath); } else { src->hosts->transport = VIR_STORAGE_NET_HOST_TRANS_TCP; if (host_spec[0] == ':') { /* no host given */ goto malformed; } else if (host_spec[0] == '[') { host_spec++; /* IPv6 addr */ if (!(port = strstr(host_spec, "]:"))) goto malformed; port[0] = '\0'; port += 2; if (host_spec[0] == '\0') goto malformed; } else { if (!(port = strchr(host_spec, ':'))) goto malformed; port[0] = '\0'; port++; } if (virStringParsePort(port, &src->hosts->port) < 0) return -1; src->hosts->name = g_strdup(host_spec); } return 0; malformed: virReportError(VIR_ERR_INTERNAL_ERROR, _("malformed nbd string '%s'"), nbdstr); return -1; } int virStorageSourceParseBackingColon(virStorageSourcePtr src, const char *path) { const char *p; g_autofree char *protocol = NULL; if (!(p = strchr(path, ':'))) { virReportError(VIR_ERR_INTERNAL_ERROR, _("invalid backing protocol string '%s'"), path); return -1; } protocol = g_strndup(path, p - path); if ((src->protocol = virStorageNetProtocolTypeFromString(protocol)) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("invalid backing protocol '%s'"), protocol); return -1; } switch ((virStorageNetProtocol) src->protocol) { case VIR_STORAGE_NET_PROTOCOL_NBD: if (virStorageSourceParseNBDColonString(path, src) < 0) return -1; break; case VIR_STORAGE_NET_PROTOCOL_RBD: if (virStorageSourceParseRBDColonString(path, src) < 0) return -1; break; case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG: case VIR_STORAGE_NET_PROTOCOL_LAST: case VIR_STORAGE_NET_PROTOCOL_NONE: virReportError(VIR_ERR_INTERNAL_ERROR, _("backing store parser is not implemented for protocol %s"), protocol); return -1; 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: case VIR_STORAGE_NET_PROTOCOL_ISCSI: case VIR_STORAGE_NET_PROTOCOL_GLUSTER: case VIR_STORAGE_NET_PROTOCOL_SSH: case VIR_STORAGE_NET_PROTOCOL_VXHS: case VIR_STORAGE_NET_PROTOCOL_NFS: virReportError(VIR_ERR_INTERNAL_ERROR, _("malformed backing store path for protocol %s"), protocol); return -1; } return 0; } static int virStorageSourceParseBackingJSONInternal(virStorageSourcePtr src, virJSONValuePtr json, const char *jsonstr, bool allowformat); static int virStorageSourceParseBackingJSONPath(virStorageSourcePtr src, virJSONValuePtr json, const char *jsonstr G_GNUC_UNUSED, int type) { const char *path; if (!(path = virJSONValueObjectGetString(json, "filename"))) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing 'filename' field in JSON backing volume " "definition")); return -1; } src->path = g_strdup(path); src->type = type; return 0; } static int virStorageSourceParseBackingJSONUriStr(virStorageSourcePtr src, const char *uri, int protocol) { int rc; if ((rc = virStorageSourceParseBackingURI(src, uri)) < 0) return -1; if (src->protocol != protocol) { virReportError(VIR_ERR_INVALID_ARG, _("expected protocol '%s' but got '%s' in URI JSON volume " "definition"), virStorageNetProtocolTypeToString(protocol), virStorageNetProtocolTypeToString(src->protocol)); return -1; } return rc; } static int virStorageSourceParseBackingJSONUriCookies(virStorageSourcePtr src, virJSONValuePtr json, const char *jsonstr) { const char *cookiestr; g_auto(GStrv) cookies = NULL; size_t ncookies = 0; size_t i; if (!virJSONValueObjectHasKey(json, "cookie")) return 0; if (!(cookiestr = virJSONValueObjectGetString(json, "cookie"))) { virReportError(VIR_ERR_INVALID_ARG, _("wrong format of 'cookie' field in backing store definition '%s'"), jsonstr); return -1; } if (!(cookies = virStringSplitCount(cookiestr, ";", 0, &ncookies))) return -1; src->cookies = g_new0(virStorageNetCookieDefPtr, ncookies); src->ncookies = ncookies; for (i = 0; i < ncookies; i++) { char *cookiename = cookies[i]; char *cookievalue; virSkipSpaces((const char **) &cookiename); if (!(cookievalue = strchr(cookiename, '='))) { virReportError(VIR_ERR_INVALID_ARG, _("malformed http cookie '%s' in backing store definition '%s'"), cookies[i], jsonstr); return -1; } *cookievalue = '\0'; cookievalue++; src->cookies[i] = g_new0(virStorageNetCookieDef, 1); src->cookies[i]->name = g_strdup(cookiename); src->cookies[i]->value = g_strdup(cookievalue); } return 0; } static int virStorageSourceParseBackingJSONUri(virStorageSourcePtr src, virJSONValuePtr json, const char *jsonstr, int protocol) { const char *uri; if (!(uri = virJSONValueObjectGetString(json, "url"))) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing 'url' in JSON backing volume definition")); return -1; } if (protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS || protocol == VIR_STORAGE_NET_PROTOCOL_FTPS) { if (virJSONValueObjectHasKey(json, "sslverify")) { const char *tmpstr; bool tmp; /* libguestfs still uses undocumented legacy value of 'off' */ if ((tmpstr = virJSONValueObjectGetString(json, "sslverify")) && STREQ(tmpstr, "off")) { src->sslverify = VIR_TRISTATE_BOOL_NO; } else { if (virJSONValueObjectGetBoolean(json, "sslverify", &tmp) < 0) { virReportError(VIR_ERR_INVALID_ARG, _("malformed 'sslverify' field in backing store definition '%s'"), jsonstr); return -1; } src->sslverify = virTristateBoolFromBool(tmp); } } } if (protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS || protocol == VIR_STORAGE_NET_PROTOCOL_HTTP) { if (virStorageSourceParseBackingJSONUriCookies(src, json, jsonstr) < 0) return -1; } if (virJSONValueObjectHasKey(json, "readahead") && virJSONValueObjectGetNumberUlong(json, "readahead", &src->readahead) < 0) { virReportError(VIR_ERR_INVALID_ARG, _("malformed 'readahead' field in backing store definition '%s'"), jsonstr); return -1; } if (virJSONValueObjectHasKey(json, "timeout") && virJSONValueObjectGetNumberUlong(json, "timeout", &src->timeout) < 0) { virReportError(VIR_ERR_INVALID_ARG, _("malformed 'timeout' field in backing store definition '%s'"), jsonstr); return -1; } return virStorageSourceParseBackingJSONUriStr(src, uri, protocol); } static int virStorageSourceParseBackingJSONInetSocketAddress(virStorageNetHostDefPtr host, virJSONValuePtr json) { const char *hostname; const char *port; if (!json) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing remote server specification in JSON " "backing volume definition")); return -1; } hostname = virJSONValueObjectGetString(json, "host"); port = virJSONValueObjectGetString(json, "port"); if (!hostname) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing hostname for tcp backing server in " "JSON backing volume definition")); return -1; } host->transport = VIR_STORAGE_NET_HOST_TRANS_TCP; host->name = g_strdup(hostname); if (virStringParsePort(port, &host->port) < 0) return -1; return 0; } static int virStorageSourceParseBackingJSONSocketAddress(virStorageNetHostDefPtr host, virJSONValuePtr json) { const char *type; const char *socket; if (!json) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing remote server specification in JSON " "backing volume definition")); return -1; } if (!(type = virJSONValueObjectGetString(json, "type"))) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing socket address type in " "JSON backing volume definition")); return -1; } if (STREQ(type, "tcp") || STREQ(type, "inet")) { return virStorageSourceParseBackingJSONInetSocketAddress(host, json); } else if (STREQ(type, "unix")) { host->transport = VIR_STORAGE_NET_HOST_TRANS_UNIX; socket = virJSONValueObjectGetString(json, "path"); /* check for old spelling for gluster protocol */ if (!socket) socket = virJSONValueObjectGetString(json, "socket"); if (!socket) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing socket path for udp backing server in " "JSON backing volume definition")); return -1; } host->socket = g_strdup(socket); } else { virReportError(VIR_ERR_INTERNAL_ERROR, _("backing store protocol '%s' is not yet supported"), type); return -1; } return 0; } static int virStorageSourceParseBackingJSONGluster(virStorageSourcePtr src, virJSONValuePtr json, const char *jsonstr G_GNUC_UNUSED, int opaque G_GNUC_UNUSED) { const char *uri = virJSONValueObjectGetString(json, "filename"); const char *volume = virJSONValueObjectGetString(json, "volume"); const char *path = virJSONValueObjectGetString(json, "path"); virJSONValuePtr server = virJSONValueObjectGetArray(json, "server"); size_t nservers; size_t i; /* legacy URI based syntax passed via 'filename' option */ if (uri) return virStorageSourceParseBackingJSONUriStr(src, uri, VIR_STORAGE_NET_PROTOCOL_GLUSTER); if (!volume || !path || !server) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing 'volume', 'path' or 'server' attribute in " "JSON backing definition for gluster volume")); return -1; } src->type = VIR_STORAGE_TYPE_NETWORK; src->protocol = VIR_STORAGE_NET_PROTOCOL_GLUSTER; src->volume = g_strdup(volume); src->path = g_strdup(path); nservers = virJSONValueArraySize(server); if (nservers == 0) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("at least 1 server is necessary in " "JSON backing definition for gluster volume")); return -1; } src->hosts = g_new0(virStorageNetHostDef, nservers); src->nhosts = nservers; for (i = 0; i < nservers; i++) { if (virStorageSourceParseBackingJSONSocketAddress(src->hosts + i, virJSONValueArrayGet(server, i)) < 0) return -1; } return 0; } static int virStorageSourceParseBackingJSONiSCSI(virStorageSourcePtr src, virJSONValuePtr json, const char *jsonstr G_GNUC_UNUSED, int opaque G_GNUC_UNUSED) { const char *transport = virJSONValueObjectGetString(json, "transport"); const char *portal = virJSONValueObjectGetString(json, "portal"); const char *target = virJSONValueObjectGetString(json, "target"); const char *lun = virJSONValueObjectGetStringOrNumber(json, "lun"); const char *uri; char *port; /* legacy URI based syntax passed via 'filename' option */ if ((uri = virJSONValueObjectGetString(json, "filename"))) return virStorageSourceParseBackingJSONUriStr(src, uri, VIR_STORAGE_NET_PROTOCOL_ISCSI); src->type = VIR_STORAGE_TYPE_NETWORK; src->protocol = VIR_STORAGE_NET_PROTOCOL_ISCSI; if (!lun) lun = "0"; src->hosts = g_new0(virStorageNetHostDef, 1); src->nhosts = 1; if (STRNEQ_NULLABLE(transport, "tcp")) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("only TCP transport is supported for iSCSI volumes")); return -1; } src->hosts->transport = VIR_STORAGE_NET_HOST_TRANS_TCP; if (!portal) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing 'portal' address in iSCSI backing definition")); return -1; } if (!target) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing 'target' in iSCSI backing definition")); return -1; } src->hosts->name = g_strdup(portal); if ((port = strrchr(src->hosts->name, ':')) && !strchr(port, ']')) { if (virStringParsePort(port + 1, &src->hosts->port) < 0) return -1; *port = '\0'; } src->path = g_strdup_printf("%s/%s", target, lun); /* Libvirt doesn't handle inline authentication. Make the caller aware. */ if (virJSONValueObjectGetString(json, "user") || virJSONValueObjectGetString(json, "password")) return 1; return 0; } static int virStorageSourceParseBackingJSONNbd(virStorageSourcePtr src, virJSONValuePtr json, const char *jsonstr G_GNUC_UNUSED, int opaque G_GNUC_UNUSED) { const char *path = virJSONValueObjectGetString(json, "path"); const char *host = virJSONValueObjectGetString(json, "host"); const char *port = virJSONValueObjectGetString(json, "port"); const char *export = virJSONValueObjectGetString(json, "export"); virJSONValuePtr server = virJSONValueObjectGetObject(json, "server"); if (!path && !host && !server) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing host specification of NBD server in JSON " "backing volume definition")); return -1; } src->type = VIR_STORAGE_TYPE_NETWORK; src->protocol = VIR_STORAGE_NET_PROTOCOL_NBD; src->path = g_strdup(export); src->hosts = g_new0(virStorageNetHostDef, 1); src->nhosts = 1; if (server) { if (virStorageSourceParseBackingJSONSocketAddress(src->hosts, server) < 0) return -1; } else { if (path) { src->hosts[0].transport = VIR_STORAGE_NET_HOST_TRANS_UNIX; src->hosts[0].socket = g_strdup(path); } else { src->hosts[0].transport = VIR_STORAGE_NET_HOST_TRANS_TCP; src->hosts[0].name = g_strdup(host); if (virStringParsePort(port, &src->hosts[0].port) < 0) return -1; } } return 0; } static int virStorageSourceParseBackingJSONSheepdog(virStorageSourcePtr src, virJSONValuePtr json, const char *jsonstr G_GNUC_UNUSED, int opaque G_GNUC_UNUSED) { const char *filename; const char *vdi = virJSONValueObjectGetString(json, "vdi"); virJSONValuePtr server = virJSONValueObjectGetObject(json, "server"); /* legacy URI based syntax passed via 'filename' option */ if ((filename = virJSONValueObjectGetString(json, "filename"))) { if (strstr(filename, "://")) return virStorageSourceParseBackingJSONUriStr(src, filename, VIR_STORAGE_NET_PROTOCOL_SHEEPDOG); /* libvirt doesn't implement a parser for the legacy non-URI syntax */ virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing sheepdog URI in JSON backing volume definition")); return -1; } src->type = VIR_STORAGE_TYPE_NETWORK; src->protocol = VIR_STORAGE_NET_PROTOCOL_SHEEPDOG; if (!vdi) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing sheepdog vdi name")); return -1; } src->path = g_strdup(vdi); src->hosts = g_new0(virStorageNetHostDef, 1); src->nhosts = 1; if (virStorageSourceParseBackingJSONSocketAddress(src->hosts, server) < 0) return -1; return 0; } static int virStorageSourceParseBackingJSONSSH(virStorageSourcePtr src, virJSONValuePtr json, const char *jsonstr G_GNUC_UNUSED, int opaque G_GNUC_UNUSED) { const char *path = virJSONValueObjectGetString(json, "path"); const char *host = virJSONValueObjectGetString(json, "host"); const char *port = virJSONValueObjectGetString(json, "port"); const char *user = virJSONValueObjectGetString(json, "user"); const char *host_key_check = virJSONValueObjectGetString(json, "host_key_check"); virJSONValuePtr server = virJSONValueObjectGetObject(json, "server"); if (!(host || server) || !path) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing host/server or path of SSH JSON backing " "volume definition")); return -1; } src->type = VIR_STORAGE_TYPE_NETWORK; src->protocol = VIR_STORAGE_NET_PROTOCOL_SSH; src->path = g_strdup(path); src->hosts = g_new0(virStorageNetHostDef, 1); src->nhosts = 1; if (server) { if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts, server) < 0) return -1; } else { src->hosts[0].transport = VIR_STORAGE_NET_HOST_TRANS_TCP; src->hosts[0].name = g_strdup(host); if (virStringParsePort(port, &src->hosts[0].port) < 0) return -1; } /* these two are parsed just to be passed back as we don't model them yet */ src->ssh_user = g_strdup(user); if (STREQ_NULLABLE(host_key_check, "no")) src->ssh_host_key_check_disabled = true; return 0; } static int virStorageSourceParseBackingJSONRBD(virStorageSourcePtr src, virJSONValuePtr json, const char *jsonstr G_GNUC_UNUSED, int opaque G_GNUC_UNUSED) { const char *filename; const char *pool = virJSONValueObjectGetString(json, "pool"); const char *image = virJSONValueObjectGetString(json, "image"); const char *conf = virJSONValueObjectGetString(json, "conf"); const char *snapshot = virJSONValueObjectGetString(json, "snapshot"); virJSONValuePtr servers = virJSONValueObjectGetArray(json, "server"); size_t nservers; size_t i; src->type = VIR_STORAGE_TYPE_NETWORK; src->protocol = VIR_STORAGE_NET_PROTOCOL_RBD; /* legacy syntax passed via 'filename' option */ if ((filename = virJSONValueObjectGetString(json, "filename"))) return virStorageSourceParseRBDColonString(filename, src); if (!pool || !image) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing pool or image name in ceph backing volume " "JSON specification")); return -1; } src->volume = g_strdup(pool); src->path = g_strdup(image); src->snapshot = g_strdup(snapshot); src->configFile = g_strdup(conf); if (servers) { nservers = virJSONValueArraySize(servers); src->hosts = g_new0(virStorageNetHostDef, nservers); src->nhosts = nservers; for (i = 0; i < nservers; i++) { if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts + i, virJSONValueArrayGet(servers, i)) < 0) return -1; } } return 0; } static int virStorageSourceParseBackingJSONRaw(virStorageSourcePtr src, virJSONValuePtr json, const char *jsonstr, int opaque G_GNUC_UNUSED) { bool has_offset = virJSONValueObjectHasKey(json, "offset"); bool has_size = virJSONValueObjectHasKey(json, "size"); virJSONValuePtr file; if (has_offset || has_size) { src->sliceStorage = g_new0(virStorageSourceSlice, 1); if (has_offset && virJSONValueObjectGetNumberUlong(json, "offset", &src->sliceStorage->offset) < 0) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("malformed 'offset' property of 'raw' driver")); return -1; } if (has_size && virJSONValueObjectGetNumberUlong(json, "size", &src->sliceStorage->size) < 0) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("malformed 'size' property of 'raw' driver")); return -1; } } /* 'raw' is a format driver so it can have protocol driver children */ if (!(file = virJSONValueObjectGetObject(json, "file"))) { virReportError(VIR_ERR_INVALID_ARG, _("JSON backing volume definition '%s' lacks 'file' object"), jsonstr); return -1; } return virStorageSourceParseBackingJSONInternal(src, file, jsonstr, false); } static int virStorageSourceParseBackingJSONVxHS(virStorageSourcePtr src, virJSONValuePtr json, const char *jsonstr G_GNUC_UNUSED, int opaque G_GNUC_UNUSED) { const char *vdisk_id = virJSONValueObjectGetString(json, "vdisk-id"); virJSONValuePtr server = virJSONValueObjectGetObject(json, "server"); if (!vdisk_id || !server) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing 'vdisk-id' or 'server' attribute in " "JSON backing definition for VxHS volume")); return -1; } src->type = VIR_STORAGE_TYPE_NETWORK; src->protocol = VIR_STORAGE_NET_PROTOCOL_VXHS; src->path = g_strdup(vdisk_id); src->hosts = g_new0(virStorageNetHostDef, 1); src->nhosts = 1; if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts, server) < 0) return -1; return 0; } static int virStorageSourceParseBackingJSONNFS(virStorageSourcePtr src, virJSONValuePtr json, const char *jsonstr G_GNUC_UNUSED, int opaque G_GNUC_UNUSED) { virJSONValuePtr server = virJSONValueObjectGetObject(json, "server"); int uidStore = -1; int gidStore = -1; int gotUID = virJSONValueObjectGetNumberInt(json, "user", &uidStore); int gotGID = virJSONValueObjectGetNumberInt(json, "group", &gidStore); if (!server) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing 'server' attribute in JSON backing definition for NFS volume")); return -1; } if (gotUID < 0 || gotGID < 0) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing 'user' or 'group' attribute in JSON backing definition for NFS volume")); return -1; } src->path = g_strdup(virJSONValueObjectGetString(json, "path")); if (!src->path) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing 'path' attribute in JSON backing definition for NFS volume")); return -1; } src->nfs_user = g_strdup_printf("+%d", uidStore); src->nfs_group = g_strdup_printf("+%d", gidStore); src->type = VIR_STORAGE_TYPE_NETWORK; src->protocol = VIR_STORAGE_NET_PROTOCOL_NFS; src->hosts = g_new0(virStorageNetHostDef, 1); src->nhosts = 1; if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts, server) < 0) return -1; return 0; } static int virStorageSourceParseBackingJSONNVMe(virStorageSourcePtr src, virJSONValuePtr json, const char *jsonstr G_GNUC_UNUSED, int opaque G_GNUC_UNUSED) { g_autoptr(virStorageSourceNVMeDef) nvme = g_new0(virStorageSourceNVMeDef, 1); const char *device = virJSONValueObjectGetString(json, "device"); if (!device || virPCIDeviceAddressParse((char *) device, &nvme->pciAddr) < 0) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing or malformed 'device' field of 'nvme' storage")); return -1; } if (virJSONValueObjectGetNumberUlong(json, "namespace", &nvme->namespc) < 0 || nvme->namespc == 0) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing or malformed 'namespace' field of 'nvme' storage")); return -1; } src->type = VIR_STORAGE_TYPE_NVME; src->nvme = g_steal_pointer(&nvme); return 0; } struct virStorageSourceJSONDriverParser { const char *drvname; bool formatdriver; /** * The callback gets a pre-allocated storage source @src and the JSON * object to parse. The callback shall return -1 on error and report error * 0 on success and 1 in cases when the configuration itself is valid, but * can't be converted to libvirt's configuration (e.g. inline authentication * credentials are present). */ int (*func)(virStorageSourcePtr src, virJSONValuePtr json, const char *jsonstr, int opaque); int opaque; }; static const struct virStorageSourceJSONDriverParser jsonParsers[] = { {"file", false, virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_FILE}, {"host_device", false, virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_BLOCK}, {"host_cdrom", false, virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_BLOCK}, {"http", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_HTTP}, {"https", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_HTTPS}, {"ftp", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_FTP}, {"ftps", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_FTPS}, {"tftp", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_TFTP}, {"gluster", false, virStorageSourceParseBackingJSONGluster, 0}, {"iscsi", false, virStorageSourceParseBackingJSONiSCSI, 0}, {"nbd", false, virStorageSourceParseBackingJSONNbd, 0}, {"sheepdog", false, virStorageSourceParseBackingJSONSheepdog, 0}, {"ssh", false, virStorageSourceParseBackingJSONSSH, 0}, {"rbd", false, virStorageSourceParseBackingJSONRBD, 0}, {"raw", true, virStorageSourceParseBackingJSONRaw, 0}, {"nfs", false, virStorageSourceParseBackingJSONNFS, 0}, {"vxhs", false, virStorageSourceParseBackingJSONVxHS, 0}, {"nvme", false, virStorageSourceParseBackingJSONNVMe, 0}, }; static int virStorageSourceParseBackingJSONInternal(virStorageSourcePtr src, virJSONValuePtr json, const char *jsonstr, bool allowformat) { const char *drvname; size_t i; if (!(drvname = virJSONValueObjectGetString(json, "driver"))) { virReportError(VIR_ERR_INVALID_ARG, _("JSON backing volume definition '%s' lacks driver name"), jsonstr); return -1; } for (i = 0; i < G_N_ELEMENTS(jsonParsers); i++) { if (STRNEQ(drvname, jsonParsers[i].drvname)) continue; if (jsonParsers[i].formatdriver && !allowformat) { virReportError(VIR_ERR_INVALID_ARG, _("JSON backing volume definition '%s' must not have nested format drivers"), jsonstr); return -1; } return jsonParsers[i].func(src, json, jsonstr, jsonParsers[i].opaque); } virReportError(VIR_ERR_INTERNAL_ERROR, _("missing parser implementation for JSON backing volume " "driver '%s'"), drvname); return -1; } int virStorageSourceParseBackingJSON(virStorageSourcePtr src, const char *json) { g_autoptr(virJSONValue) root = NULL; g_autoptr(virJSONValue) deflattened = NULL; virJSONValuePtr file = NULL; if (!(root = virJSONValueFromString(json))) return -1; if (!(deflattened = virJSONValueObjectDeflatten(root))) return -1; /* There are 2 possible syntaxes: * 1) json:{"file":{"driver":...}} * 2) json:{"driver":...} * Remove the 'file' wrapper object in case 1. */ if (!virJSONValueObjectHasKey(deflattened, "driver")) file = virJSONValueObjectGetObject(deflattened, "file"); if (!file) file = deflattened; return virStorageSourceParseBackingJSONInternal(src, file, json, true); }