diff --git a/po/POTFILES.in b/po/POTFILES.in index a2c46dd239..b09e58f14a 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -230,6 +230,7 @@ @SRCDIR@src/storage_file/storage_file_backend_gluster.c @SRCDIR@src/storage_file/storage_file_probe.c @SRCDIR@src/storage_file/storage_source.c +@SRCDIR@src/storage_file/storage_source_backingstore.c @SRCDIR@src/test/test_driver.c @SRCDIR@src/util/iohelper.c @SRCDIR@src/util/viralloc.c diff --git a/src/storage_file/meson.build b/src/storage_file/meson.build index 4f8068848c..d40e98befa 100644 --- a/src/storage_file/meson.build +++ b/src/storage_file/meson.build @@ -1,5 +1,6 @@ storage_file_sources = [ 'storage_source.c', + 'storage_source_backingstore.c', 'storage_file_backend.c', 'storage_file_probe.c', ] diff --git a/src/storage_file/storage_source.c b/src/storage_file/storage_source.c index df9fb6c055..9d3761f5bd 100644 --- a/src/storage_file/storage_source.c +++ b/src/storage_file/storage_source.c @@ -28,16 +28,15 @@ #include "storage_file_backend.h" #include "storage_file_probe.h" #include "storage_source.h" +#include "storage_source_backingstore.h" #include "viralloc.h" #include "virerror.h" #include "virfile.h" #include "virhash.h" -#include "virjson.h" #include "virlog.h" #include "virobject.h" #include "virstoragefile.h" #include "virstring.h" -#include "viruri.h" #include "virutil.h" #define VIR_FROM_THIS VIR_FROM_STORAGE @@ -341,1210 +340,6 @@ virStorageSourceNewFromBackingRelative(virStorageSourcePtr parent, } -static 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 = virStringSplit(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 = virStringSplit(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; -} - - -static 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; -} - - -static 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); -} - - /** * virStorageSourceNewFromBackingAbsolute * @path: string representing absolute location of a storage source diff --git a/src/storage_file/storage_source_backingstore.c b/src/storage_file/storage_source_backingstore.c new file mode 100644 index 0000000000..bbcc720af1 --- /dev/null +++ b/src/storage_file/storage_source_backingstore.c @@ -0,0 +1,1240 @@ +/* + * 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 = virStringSplit(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 = virStringSplit(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); +} diff --git a/src/storage_file/storage_source_backingstore.h b/src/storage_file/storage_source_backingstore.h new file mode 100644 index 0000000000..e51063e9e2 --- /dev/null +++ b/src/storage_file/storage_source_backingstore.h @@ -0,0 +1,40 @@ +/* + * storage_source_backingstore.h: helpers for parsing backing store strings + * + * Copyright (C) 2007-2009, 2012-2016 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 + * . + */ + +#pragma once + +#include "storage_source_conf.h" + +int +virStorageSourceParseBackingURI(virStorageSourcePtr src, + const char *uristr); + +int +virStorageSourceParseRBDColonString(const char *rbdstr, + virStorageSourcePtr src); + +int +virStorageSourceParseBackingColon(virStorageSourcePtr src, + const char *path); + +int +virStorageSourceParseBackingJSON(virStorageSourcePtr src, + const char *json);