From nobody Sun Feb 8 21:11:39 2026 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of redhat.com designates 216.205.24.124 as permitted sender) client-ip=216.205.24.124; envelope-from=libvir-list-bounces@redhat.com; helo=us-smtp-delivery-124.mimecast.com; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 216.205.24.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1611590751; cv=none; d=zohomail.com; s=zohoarc; b=h9e1qWFP/S8b8uSQcK4qAS8xAVA+UakMHC1vldgjvHdTe9DZomiasrmjLqrypNkUNqQ99VO9pZSOepWz4oP9ktcVMy60KXIiJ3JCCIJvYioHKqN+RBfgXfUFc6UvcM1oMeCgIEq1hdgdujO4/2m6N65tai7WXvbTAly9oHiSw3k= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1611590751; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=B0YMvCKvXkF3B1TGv3sLHSNY8hZW9ej8WR/INZV5C3M=; b=PBn/sJNpIeD4q5dS0XHW6dKFdd2ZQDpJRW3rpHlz5MNQhr7Y12Na+ZGuNIs9QJjquz3JSZGkY7p7s8Ai6ngJrnQQ9IFHdIjM+nfWUiGrMpyf98pwFTi2l3OV4lZPzsBOVfP2V59OHyIWPNK4Cb7DDxhL6mX1e/xCaFJzoAMSZs4= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 216.205.24.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass header.from= (p=none dis=none) header.from= Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [216.205.24.124]) by mx.zohomail.com with SMTPS id 1611590751275599.7046289958515; Mon, 25 Jan 2021 08:05:51 -0800 (PST) Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-465-8UbfUtuPMRO_fNn7dolAfw-1; Mon, 25 Jan 2021 11:05:46 -0500 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 6E5B31005504; Mon, 25 Jan 2021 16:05:38 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 467545D9E4; Mon, 25 Jan 2021 16:05:38 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id EBCB01809CA0; Mon, 25 Jan 2021 16:05:37 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 10PG5YXx018226 for ; Mon, 25 Jan 2021 11:05:34 -0500 Received: by smtp.corp.redhat.com (Postfix) id A61185D9DC; Mon, 25 Jan 2021 16:05:34 +0000 (UTC) Received: from speedmetal.redhat.com (unknown [10.40.208.11]) by smtp.corp.redhat.com (Postfix) with ESMTP id 287FC5D9DB for ; Mon, 25 Jan 2021 16:05:32 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1611590750; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:list-id:list-help: list-unsubscribe:list-subscribe:list-post; bh=B0YMvCKvXkF3B1TGv3sLHSNY8hZW9ej8WR/INZV5C3M=; b=Wc2CaCi7jdaRbjvBSQ9elB4W5sedZFShlXDhKNvlFNvMHYA74My98JWiED8hAmzWkXqzzO 6WXcR6qt01TpaEQWGfXHtWM2qfd420ibyXpSMHDjGgSwWYOOmuNseTr5aHH62paG1CYhUX fGOXgw+O6uaB7Vo3ZBGc2JcLhiaC1YE= X-MC-Unique: 8UbfUtuPMRO_fNn7dolAfw-1 From: Peter Krempa To: libvir-list@redhat.com Subject: [PATCH 04/12] storage_source: Move backing store parsers into new file Date: Mon, 25 Jan 2021 17:05:16 +0100 Message-Id: <21b4db97f726d4893f2b8f2a5fdeae7abddfa526.1611590679.git.pkrempa@redhat.com> In-Reply-To: References: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 X-loop: libvir-list@redhat.com X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=libvir-list-bounces@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: quoted-printable X-ZohoMail-DKIM: pass (identity @redhat.com) Content-Type: text/plain; charset="utf-8" The parsers for the backing store strings are relatively self-contained and rather massive piece of code. Move them to a new module called storage_source_backingstore. Signed-off-by: Peter Krempa --- po/POTFILES.in | 1 + src/storage_file/meson.build | 1 + src/storage_file/storage_source.c | 1207 +--------------- .../storage_source_backingstore.c | 1240 +++++++++++++++++ .../storage_source_backingstore.h | 40 + 5 files changed, 1283 insertions(+), 1206 deletions(-) create mode 100644 src/storage_file/storage_source_backingstore.c create mode 100644 src/storage_file/storage_source_backingstore.h 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 =3D [ '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_s= ource.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(virStorageSou= rcePtr parent, } -static int -virStorageSourceParseBackingURI(virStorageSourcePtr src, - const char *uristr) -{ - g_autoptr(virURI) uri =3D NULL; - const char *path =3D NULL; - g_auto(GStrv) scheme =3D NULL; - - if (!(uri =3D virURIParse(uristr))) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("failed to parse backing file location '%s'"), - uristr); - return -1; - } - - src->hosts =3D g_new0(virStorageNetHostDef, 1); - src->nhosts =3D 1; - - if (!(scheme =3D virStringSplit(uri->scheme, "+", 2))) - return -1; - - if (!scheme[0] || - (src->protocol =3D virStorageNetProtocolTypeFromString(scheme[0]))= < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("invalid backing protocol '%s'"), - NULLSTR(scheme[0])); - return -1; - } - - if (scheme[1] && - (src->hosts->transport =3D virStorageNetHostTransportTypeFromStrin= g(scheme[1])) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("invalid protocol transport type '%s'"), - scheme[1]); - return -1; - } - - if (uri->query) { - if (src->protocol =3D=3D VIR_STORAGE_NET_PROTOCOL_HTTP || - src->protocol =3D=3D VIR_STORAGE_NET_PROTOCOL_HTTPS) { - src->query =3D g_strdup(uri->query); - } else { - /* handle socket stored as a query */ - if (STRPREFIX(uri->query, "socket=3D")) - src->hosts->socket =3D g_strdup(STRSKIP(uri->query, "socke= t=3D")); - } - } - - /* uri->path is NULL if the URI does not contain slash after host: - * transport://host:port */ - if (uri->path) - path =3D uri->path; - else - path =3D ""; - - /* possibly skip the leading slash */ - if (path[0] =3D=3D '/') - path++; - - /* NBD allows empty export name (path) */ - if (src->protocol =3D=3D VIR_STORAGE_NET_PROTOCOL_NBD && - path[0] =3D=3D '\0') - path =3D NULL; - - src->path =3D g_strdup(path); - - if (src->protocol =3D=3D VIR_STORAGE_NET_PROTOCOL_GLUSTER) { - char *tmp; - - if (!src->path) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("missing volume name and path for gluster vol= ume")); - return -1; - } - - if (!(tmp =3D strchr(src->path, '/')) || - tmp =3D=3D src->path) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("missing volume name or file name in " - "gluster source path '%s'"), src->path); - return -1; - } - - src->volume =3D src->path; - - src->path =3D g_strdup(tmp + 1); - - tmp[0] =3D '\0'; - } - - src->hosts->port =3D uri->port; - - src->hosts->name =3D 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 =3D NULL; - - if (VIR_EXPAND_N(src->hosts, src->nhosts, 1) < 0) - return -1; - - if ((port =3D strchr(hostport, ']'))) { - /* ipv6, strip brackets */ - hostport +=3D 1; - skip =3D 3; - } else { - port =3D strstr(hostport, "\\:"); - skip =3D 2; - } - - if (port) { - *port =3D '\0'; - port +=3D skip; - if (virStringParsePort(port, &src->hosts[src->nhosts - 1].port) < = 0) - goto error; - } - - parts =3D virStringSplit(hostport, "\\:", 0); - if (!parts) - goto error; - src->hosts[src->nhosts-1].name =3D virStringListJoin((const char **)pa= rts, ":"); - if (!src->hosts[src->nhosts-1].name) - goto error; - - src->hosts[src->nhosts-1].transport =3D VIR_STORAGE_NET_HOST_TRANS_TCP; - src->hosts[src->nhosts-1].socket =3D 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 =3D NULL; - g_autoptr(virStorageAuthDef) authdef =3D NULL; - - /* optionally skip the "rbd:" prefix if provided */ - if (STRPREFIX(rbdstr, "rbd:")) - rbdstr +=3D strlen("rbd:"); - - src->path =3D g_strdup(rbdstr); - - p =3D strchr(src->path, ':'); - if (p) { - options =3D g_strdup(p + 1); - *p =3D '\0'; - } - - /* snapshot name */ - if ((p =3D strchr(src->path, '@'))) { - src->snapshot =3D g_strdup(p + 1); - *p =3D '\0'; - } - - /* pool vs. image name */ - if ((p =3D strchr(src->path, '/'))) { - src->volume =3D g_steal_pointer(&src->path); - src->path =3D g_strdup(p + 1); - *p =3D '\0'; - } - - /* options */ - if (!options) - return 0; /* all done */ - - p =3D options; - while (*p) { - /* find : delimiter or end of string */ - for (e =3D p; *e && *e !=3D ':'; ++e) { - if (*e =3D=3D '\\') { - e++; - if (*e =3D=3D '\0') - break; - } - } - if (*e =3D=3D '\0') { - next =3D e; /* last kv pair */ - } else { - next =3D e + 1; - *e =3D '\0'; - } - - if (STRPREFIX(p, "id=3D")) { - /* formulate authdef for src->auth */ - if (src->auth) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("duplicate 'id' found in '%s'"), src->pat= h); - return -1; - } - - authdef =3D g_new0(virStorageAuthDef, 1); - - authdef->username =3D g_strdup(p + strlen("id=3D")); - - authdef->secrettype =3D g_strdup(virSecretUsageTypeToString(VI= R_SECRET_USAGE_TYPE_CEPH)); - src->auth =3D g_steal_pointer(&authdef); - - /* Cannot formulate a secretType (eg, usage or uuid) given - * what is provided. - */ - } - if (STRPREFIX(p, "mon_host=3D")) { - char *h, *sep; - - h =3D p + strlen("mon_host=3D"); - while (h < e) { - for (sep =3D h; sep < e; ++sep) { - if (*sep =3D=3D '\\' && (sep[1] =3D=3D ',' || - sep[1] =3D=3D ';' || - sep[1] =3D=3D ' ')) { - *sep =3D '\0'; - sep +=3D 2; - break; - } - } - - if (virStorageSourceRBDAddHost(src, h) < 0) - return -1; - - h =3D sep; - } - } - - if (STRPREFIX(p, "conf=3D")) - src->configFile =3D g_strdup(p + strlen("conf=3D")); - - p =3D next; - } - return 0; -} - - -static int -virStorageSourceParseNBDColonString(const char *nbdstr, - virStorageSourcePtr src) -{ - g_autofree char *nbd =3D g_strdup(nbdstr); - char *export_name; - char *host_spec; - char *unixpath; - char *port; - - src->hosts =3D g_new0(virStorageNetHostDef, 1); - src->nhosts =3D 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=3DEXPORTNAME] - * nbd:HOSTNAME:PORT[:exportname=3DEXPORTNAME] - */ - - /* first look for ':exportname=3D' and cut it off */ - if ((export_name =3D strstr(nbd, ":exportname=3D"))) { - src->path =3D g_strdup(export_name + strlen(":exportname=3D")); - export_name[0] =3D '\0'; - } - - /* Verify the prefix and contents. Note that we require a - * "host_spec" part to be present. */ - if (!(host_spec =3D STRSKIP(nbd, "nbd:")) || host_spec[0] =3D=3D '\0') - goto malformed; - - if ((unixpath =3D STRSKIP(host_spec, "unix:"))) { - src->hosts->transport =3D VIR_STORAGE_NET_HOST_TRANS_UNIX; - - if (unixpath[0] =3D=3D '\0') - goto malformed; - - src->hosts->socket =3D g_strdup(unixpath); - } else { - src->hosts->transport =3D VIR_STORAGE_NET_HOST_TRANS_TCP; - - if (host_spec[0] =3D=3D ':') { - /* no host given */ - goto malformed; - } else if (host_spec[0] =3D=3D '[') { - host_spec++; - /* IPv6 addr */ - if (!(port =3D strstr(host_spec, "]:"))) - goto malformed; - - port[0] =3D '\0'; - port +=3D 2; - - if (host_spec[0] =3D=3D '\0') - goto malformed; - } else { - if (!(port =3D strchr(host_spec, ':'))) - goto malformed; - - port[0] =3D '\0'; - port++; - } - - if (virStringParsePort(port, &src->hosts->port) < 0) - return -1; - - src->hosts->name =3D 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 =3D NULL; - - if (!(p =3D strchr(path, ':'))) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("invalid backing protocol string '%s'"), - path); - return -1; - } - - protocol =3D g_strndup(path, p - path); - - if ((src->protocol =3D 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 prot= ocol %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 =3D virJSONValueObjectGetString(json, "filename"))) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing 'filename' field in JSON backing volume " - "definition")); - return -1; - } - - src->path =3D g_strdup(path); - - src->type =3D type; - return 0; -} - - -static int -virStorageSourceParseBackingJSONUriStr(virStorageSourcePtr src, - const char *uri, - int protocol) -{ - int rc; - - if ((rc =3D virStorageSourceParseBackingURI(src, uri)) < 0) - return -1; - - if (src->protocol !=3D 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 =3D NULL; - size_t ncookies =3D 0; - size_t i; - - if (!virJSONValueObjectHasKey(json, "cookie")) - return 0; - - if (!(cookiestr =3D virJSONValueObjectGetString(json, "cookie"))) { - virReportError(VIR_ERR_INVALID_ARG, - _("wrong format of 'cookie' field in backing store = definition '%s'"), - jsonstr); - return -1; - } - - if (!(cookies =3D virStringSplitCount(cookiestr, ";", 0, &ncookies))) - return -1; - - src->cookies =3D g_new0(virStorageNetCookieDefPtr, ncookies); - src->ncookies =3D ncookies; - - for (i =3D 0; i < ncookies; i++) { - char *cookiename =3D cookies[i]; - char *cookievalue; - - virSkipSpaces((const char **) &cookiename); - - if (!(cookievalue =3D strchr(cookiename, '=3D'))) { - virReportError(VIR_ERR_INVALID_ARG, - _("malformed http cookie '%s' in backing store = definition '%s'"), - cookies[i], jsonstr); - return -1; - } - - *cookievalue =3D '\0'; - cookievalue++; - - src->cookies[i] =3D g_new0(virStorageNetCookieDef, 1); - src->cookies[i]->name =3D g_strdup(cookiename); - src->cookies[i]->value =3D g_strdup(cookievalue); - } - - return 0; -} - - -static int -virStorageSourceParseBackingJSONUri(virStorageSourcePtr src, - virJSONValuePtr json, - const char *jsonstr, - int protocol) -{ - const char *uri; - - if (!(uri =3D virJSONValueObjectGetString(json, "url"))) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing 'url' in JSON backing volume definition"= )); - return -1; - } - - if (protocol =3D=3D VIR_STORAGE_NET_PROTOCOL_HTTPS || - protocol =3D=3D VIR_STORAGE_NET_PROTOCOL_FTPS) { - if (virJSONValueObjectHasKey(json, "sslverify")) { - const char *tmpstr; - bool tmp; - - /* libguestfs still uses undocumented legacy value of 'off' */ - if ((tmpstr =3D virJSONValueObjectGetString(json, "sslverify")= ) && - STREQ(tmpstr, "off")) { - src->sslverify =3D VIR_TRISTATE_BOOL_NO; - } else { - if (virJSONValueObjectGetBoolean(json, "sslverify", &tmp) = < 0) { - virReportError(VIR_ERR_INVALID_ARG, - _("malformed 'sslverify' field in backi= ng store definition '%s'"), - jsonstr); - return -1; - } - - src->sslverify =3D virTristateBoolFromBool(tmp); - } - } - } - - if (protocol =3D=3D VIR_STORAGE_NET_PROTOCOL_HTTPS || - protocol =3D=3D VIR_STORAGE_NET_PROTOCOL_HTTP) { - if (virStorageSourceParseBackingJSONUriCookies(src, json, jsonstr)= < 0) - return -1; - } - - if (virJSONValueObjectHasKey(json, "readahead") && - virJSONValueObjectGetNumberUlong(json, "readahead", &src->readahea= d) < 0) { - virReportError(VIR_ERR_INVALID_ARG, - _("malformed 'readahead' field in backing store def= inition '%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 defin= ition '%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 =3D virJSONValueObjectGetString(json, "host"); - port =3D 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 =3D VIR_STORAGE_NET_HOST_TRANS_TCP; - host->name =3D 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 =3D 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, jso= n); - - } else if (STREQ(type, "unix")) { - host->transport =3D VIR_STORAGE_NET_HOST_TRANS_UNIX; - - socket =3D virJSONValueObjectGetString(json, "path"); - - /* check for old spelling for gluster protocol */ - if (!socket) - socket =3D virJSONValueObjectGetString(json, "socket"); - - if (!socket) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing socket path for udp backing server i= n " - "JSON backing volume definition")); - return -1; - } - - host->socket =3D 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 =3D virJSONValueObjectGetString(json, "filename"); - const char *volume =3D virJSONValueObjectGetString(json, "volume"); - const char *path =3D virJSONValueObjectGetString(json, "path"); - virJSONValuePtr server =3D 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_PROT= OCOL_GLUSTER); - - if (!volume || !path || !server) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing 'volume', 'path' or 'server' attribute i= n " - "JSON backing definition for gluster volume")); - return -1; - } - - src->type =3D VIR_STORAGE_TYPE_NETWORK; - src->protocol =3D VIR_STORAGE_NET_PROTOCOL_GLUSTER; - - src->volume =3D g_strdup(volume); - src->path =3D g_strdup(path); - - nservers =3D virJSONValueArraySize(server); - if (nservers =3D=3D 0) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("at least 1 server is necessary in " - "JSON backing definition for gluster volume")); - - return -1; - } - - src->hosts =3D g_new0(virStorageNetHostDef, nservers); - src->nhosts =3D nservers; - - for (i =3D 0; i < nservers; i++) { - if (virStorageSourceParseBackingJSONSocketAddress(src->hosts + i, - virJSONValueArra= yGet(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 =3D virJSONValueObjectGetString(json, "transport= "); - const char *portal =3D virJSONValueObjectGetString(json, "portal"); - const char *target =3D virJSONValueObjectGetString(json, "target"); - const char *lun =3D virJSONValueObjectGetStringOrNumber(json, "lun"); - const char *uri; - char *port; - - /* legacy URI based syntax passed via 'filename' option */ - if ((uri =3D virJSONValueObjectGetString(json, "filename"))) - return virStorageSourceParseBackingJSONUriStr(src, uri, - VIR_STORAGE_NET_PROT= OCOL_ISCSI); - - src->type =3D VIR_STORAGE_TYPE_NETWORK; - src->protocol =3D VIR_STORAGE_NET_PROTOCOL_ISCSI; - - if (!lun) - lun =3D "0"; - - src->hosts =3D g_new0(virStorageNetHostDef, 1); - src->nhosts =3D 1; - - if (STRNEQ_NULLABLE(transport, "tcp")) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("only TCP transport is supported for iSCSI volume= s")); - return -1; - } - - src->hosts->transport =3D VIR_STORAGE_NET_HOST_TRANS_TCP; - - if (!portal) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing 'portal' address in iSCSI backing defini= tion")); - return -1; - } - - if (!target) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing 'target' in iSCSI backing definition")); - return -1; - } - - src->hosts->name =3D g_strdup(portal); - - if ((port =3D strrchr(src->hosts->name, ':')) && - !strchr(port, ']')) { - if (virStringParsePort(port + 1, &src->hosts->port) < 0) - return -1; - - *port =3D '\0'; - } - - src->path =3D 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 =3D virJSONValueObjectGetString(json, "path"); - const char *host =3D virJSONValueObjectGetString(json, "host"); - const char *port =3D virJSONValueObjectGetString(json, "port"); - const char *export =3D virJSONValueObjectGetString(json, "export"); - virJSONValuePtr server =3D 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 =3D VIR_STORAGE_TYPE_NETWORK; - src->protocol =3D VIR_STORAGE_NET_PROTOCOL_NBD; - - src->path =3D g_strdup(export); - - src->hosts =3D g_new0(virStorageNetHostDef, 1); - src->nhosts =3D 1; - - if (server) { - if (virStorageSourceParseBackingJSONSocketAddress(src->hosts, serv= er) < 0) - return -1; - } else { - if (path) { - src->hosts[0].transport =3D VIR_STORAGE_NET_HOST_TRANS_UNIX; - src->hosts[0].socket =3D g_strdup(path); - } else { - src->hosts[0].transport =3D VIR_STORAGE_NET_HOST_TRANS_TCP; - src->hosts[0].name =3D 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 =3D virJSONValueObjectGetString(json, "vdi"); - virJSONValuePtr server =3D virJSONValueObjectGetObject(json, "server"); - - /* legacy URI based syntax passed via 'filename' option */ - if ((filename =3D 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 synta= x */ - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing sheepdog URI in JSON backing volume defi= nition")); - return -1; - } - - src->type =3D VIR_STORAGE_TYPE_NETWORK; - src->protocol =3D VIR_STORAGE_NET_PROTOCOL_SHEEPDOG; - - if (!vdi) { - virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing sheepdog vdi = name")); - return -1; - } - - src->path =3D g_strdup(vdi); - - src->hosts =3D g_new0(virStorageNetHostDef, 1); - src->nhosts =3D 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 =3D virJSONValueObjectGetString(json, "path"); - const char *host =3D virJSONValueObjectGetString(json, "host"); - const char *port =3D virJSONValueObjectGetString(json, "port"); - const char *user =3D virJSONValueObjectGetString(json, "user"); - const char *host_key_check =3D virJSONValueObjectGetString(json, "host= _key_check"); - virJSONValuePtr server =3D 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 =3D VIR_STORAGE_TYPE_NETWORK; - src->protocol =3D VIR_STORAGE_NET_PROTOCOL_SSH; - - src->path =3D g_strdup(path); - - src->hosts =3D g_new0(virStorageNetHostDef, 1); - src->nhosts =3D 1; - - if (server) { - if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts, - server) < 0) - return -1; - } else { - src->hosts[0].transport =3D VIR_STORAGE_NET_HOST_TRANS_TCP; - src->hosts[0].name =3D 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 =3D g_strdup(user); - if (STREQ_NULLABLE(host_key_check, "no")) - src->ssh_host_key_check_disabled =3D 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 =3D virJSONValueObjectGetString(json, "pool"); - const char *image =3D virJSONValueObjectGetString(json, "image"); - const char *conf =3D virJSONValueObjectGetString(json, "conf"); - const char *snapshot =3D virJSONValueObjectGetString(json, "snapshot"); - virJSONValuePtr servers =3D virJSONValueObjectGetArray(json, "server"); - size_t nservers; - size_t i; - - src->type =3D VIR_STORAGE_TYPE_NETWORK; - src->protocol =3D VIR_STORAGE_NET_PROTOCOL_RBD; - - /* legacy syntax passed via 'filename' option */ - if ((filename =3D virJSONValueObjectGetString(json, "filename"))) - return virStorageSourceParseRBDColonString(filename, src); - - if (!pool || !image) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing pool or image name in ceph backing volum= e " - "JSON specification")); - return -1; - } - - src->volume =3D g_strdup(pool); - src->path =3D g_strdup(image); - src->snapshot =3D g_strdup(snapshot); - src->configFile =3D g_strdup(conf); - - if (servers) { - nservers =3D virJSONValueArraySize(servers); - - src->hosts =3D g_new0(virStorageNetHostDef, nservers); - src->nhosts =3D nservers; - - for (i =3D 0; i < nservers; i++) { - if (virStorageSourceParseBackingJSONInetSocketAddress(src->hos= ts + i, - virJSONV= alueArrayGet(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 =3D virJSONValueObjectHasKey(json, "offset"); - bool has_size =3D virJSONValueObjectHasKey(json, "size"); - virJSONValuePtr file; - - if (has_offset || has_size) { - src->sliceStorage =3D g_new0(virStorageSourceSlice, 1); - - if (has_offset && - virJSONValueObjectGetNumberUlong(json, "offset", &src->sliceSt= orage->offset) < 0) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("malformed 'offset' property of 'raw' driver"= )); - return -1; - } - - if (has_size && - virJSONValueObjectGetNumberUlong(json, "size", &src->sliceStor= age->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 =3D virJSONValueObjectGetObject(json, "file"))) { - virReportError(VIR_ERR_INVALID_ARG, - _("JSON backing volume definition '%s' lacks 'file'= object"), - jsonstr); - return -1; - } - - return virStorageSourceParseBackingJSONInternal(src, file, jsonstr, fa= lse); -} - - -static int -virStorageSourceParseBackingJSONVxHS(virStorageSourcePtr src, - virJSONValuePtr json, - const char *jsonstr G_GNUC_UNUSED, - int opaque G_GNUC_UNUSED) -{ - const char *vdisk_id =3D virJSONValueObjectGetString(json, "vdisk-id"); - virJSONValuePtr server =3D 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 =3D VIR_STORAGE_TYPE_NETWORK; - src->protocol =3D VIR_STORAGE_NET_PROTOCOL_VXHS; - - src->path =3D g_strdup(vdisk_id); - - src->hosts =3D g_new0(virStorageNetHostDef, 1); - src->nhosts =3D 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 =3D virJSONValueObjectGetObject(json, "server"); - int uidStore =3D -1; - int gidStore =3D -1; - int gotUID =3D virJSONValueObjectGetNumberInt(json, "user", &uidStore); - int gotGID =3D virJSONValueObjectGetNumberInt(json, "group", &gidStore= ); - - if (!server) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing 'server' attribute in JSON backing defin= ition for NFS volume")); - return -1; - } - - if (gotUID < 0 || gotGID < 0) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing 'user' or 'group' attribute in JSON back= ing definition for NFS volume")); - return -1; - } - - src->path =3D g_strdup(virJSONValueObjectGetString(json, "path")); - if (!src->path) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing 'path' attribute in JSON backing definit= ion for NFS volume")); - return -1; - } - - src->nfs_user =3D g_strdup_printf("+%d", uidStore); - src->nfs_group =3D g_strdup_printf("+%d", gidStore); - - src->type =3D VIR_STORAGE_TYPE_NETWORK; - src->protocol =3D VIR_STORAGE_NET_PROTOCOL_NFS; - - src->hosts =3D g_new0(virStorageNetHostDef, 1); - src->nhosts =3D 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 =3D g_new0(virStorageSourceNVM= eDef, 1); - const char *device =3D virJSONValueObjectGetString(json, "device"); - - if (!device || virPCIDeviceAddressParse((char *) device, &nvme->pciAdd= r) < 0) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing or malformed 'device' field of 'nvme' st= orage")); - return -1; - } - - if (virJSONValueObjectGetNumberUlong(json, "namespace", &nvme->namespc= ) < 0 || - nvme->namespc =3D=3D 0) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing or malformed 'namespace' field of 'nvme'= storage")); - return -1; - } - - src->type =3D VIR_STORAGE_TYPE_NVME; - src->nvme =3D 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 e= rror - * 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 authenti= cation - * credentials are present). - */ - int (*func)(virStorageSourcePtr src, virJSONValuePtr json, const char = *jsonstr, int opaque); - int opaque; -}; - -static const struct virStorageSourceJSONDriverParser jsonParsers[] =3D { - {"file", false, virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE= _FILE}, - {"host_device", false, virStorageSourceParseBackingJSONPath, VIR_STORA= GE_TYPE_BLOCK}, - {"host_cdrom", false, virStorageSourceParseBackingJSONPath, VIR_STORAG= E_TYPE_BLOCK}, - {"http", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_P= ROTOCOL_HTTP}, - {"https", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_= PROTOCOL_HTTPS}, - {"ftp", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PR= OTOCOL_FTP}, - {"ftps", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_P= ROTOCOL_FTPS}, - {"tftp", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_P= ROTOCOL_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 =3D virJSONValueObjectGetString(json, "driver"))) { - virReportError(VIR_ERR_INVALID_ARG, - _("JSON backing volume definition '%s' lacks driver= name"), - jsonstr); - return -1; - } - - for (i =3D 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].opaq= ue); - } - - virReportError(VIR_ERR_INTERNAL_ERROR, - _("missing parser implementation for JSON backing volum= e " - "driver '%s'"), drvname); - return -1; -} - - -static int -virStorageSourceParseBackingJSON(virStorageSourcePtr src, - const char *json) -{ - g_autoptr(virJSONValue) root =3D NULL; - g_autoptr(virJSONValue) deflattened =3D NULL; - virJSONValuePtr file =3D NULL; - - if (!(root =3D virJSONValueFromString(json))) - return -1; - - if (!(deflattened =3D 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 =3D virJSONValueObjectGetObject(deflattened, "file"); - - if (!file) - file =3D 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_f= ile/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 =3D NULL; + const char *path =3D NULL; + g_auto(GStrv) scheme =3D NULL; + + if (!(uri =3D virURIParse(uristr))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("failed to parse backing file location '%s'"), + uristr); + return -1; + } + + src->hosts =3D g_new0(virStorageNetHostDef, 1); + src->nhosts =3D 1; + + if (!(scheme =3D virStringSplit(uri->scheme, "+", 2))) + return -1; + + if (!scheme[0] || + (src->protocol =3D virStorageNetProtocolTypeFromString(scheme[0]))= < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("invalid backing protocol '%s'"), + NULLSTR(scheme[0])); + return -1; + } + + if (scheme[1] && + (src->hosts->transport =3D virStorageNetHostTransportTypeFromStrin= g(scheme[1])) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("invalid protocol transport type '%s'"), + scheme[1]); + return -1; + } + + if (uri->query) { + if (src->protocol =3D=3D VIR_STORAGE_NET_PROTOCOL_HTTP || + src->protocol =3D=3D VIR_STORAGE_NET_PROTOCOL_HTTPS) { + src->query =3D g_strdup(uri->query); + } else { + /* handle socket stored as a query */ + if (STRPREFIX(uri->query, "socket=3D")) + src->hosts->socket =3D g_strdup(STRSKIP(uri->query, "socke= t=3D")); + } + } + + /* uri->path is NULL if the URI does not contain slash after host: + * transport://host:port */ + if (uri->path) + path =3D uri->path; + else + path =3D ""; + + /* possibly skip the leading slash */ + if (path[0] =3D=3D '/') + path++; + + /* NBD allows empty export name (path) */ + if (src->protocol =3D=3D VIR_STORAGE_NET_PROTOCOL_NBD && + path[0] =3D=3D '\0') + path =3D NULL; + + src->path =3D g_strdup(path); + + if (src->protocol =3D=3D VIR_STORAGE_NET_PROTOCOL_GLUSTER) { + char *tmp; + + if (!src->path) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("missing volume name and path for gluster vol= ume")); + return -1; + } + + if (!(tmp =3D strchr(src->path, '/')) || + tmp =3D=3D src->path) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("missing volume name or file name in " + "gluster source path '%s'"), src->path); + return -1; + } + + src->volume =3D src->path; + + src->path =3D g_strdup(tmp + 1); + + tmp[0] =3D '\0'; + } + + src->hosts->port =3D uri->port; + + src->hosts->name =3D 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 =3D NULL; + + if (VIR_EXPAND_N(src->hosts, src->nhosts, 1) < 0) + return -1; + + if ((port =3D strchr(hostport, ']'))) { + /* ipv6, strip brackets */ + hostport +=3D 1; + skip =3D 3; + } else { + port =3D strstr(hostport, "\\:"); + skip =3D 2; + } + + if (port) { + *port =3D '\0'; + port +=3D skip; + if (virStringParsePort(port, &src->hosts[src->nhosts - 1].port) < = 0) + goto error; + } + + parts =3D virStringSplit(hostport, "\\:", 0); + if (!parts) + goto error; + src->hosts[src->nhosts-1].name =3D virStringListJoin((const char **)pa= rts, ":"); + if (!src->hosts[src->nhosts-1].name) + goto error; + + src->hosts[src->nhosts-1].transport =3D VIR_STORAGE_NET_HOST_TRANS_TCP; + src->hosts[src->nhosts-1].socket =3D 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 =3D NULL; + g_autoptr(virStorageAuthDef) authdef =3D NULL; + + /* optionally skip the "rbd:" prefix if provided */ + if (STRPREFIX(rbdstr, "rbd:")) + rbdstr +=3D strlen("rbd:"); + + src->path =3D g_strdup(rbdstr); + + p =3D strchr(src->path, ':'); + if (p) { + options =3D g_strdup(p + 1); + *p =3D '\0'; + } + + /* snapshot name */ + if ((p =3D strchr(src->path, '@'))) { + src->snapshot =3D g_strdup(p + 1); + *p =3D '\0'; + } + + /* pool vs. image name */ + if ((p =3D strchr(src->path, '/'))) { + src->volume =3D g_steal_pointer(&src->path); + src->path =3D g_strdup(p + 1); + *p =3D '\0'; + } + + /* options */ + if (!options) + return 0; /* all done */ + + p =3D options; + while (*p) { + /* find : delimiter or end of string */ + for (e =3D p; *e && *e !=3D ':'; ++e) { + if (*e =3D=3D '\\') { + e++; + if (*e =3D=3D '\0') + break; + } + } + if (*e =3D=3D '\0') { + next =3D e; /* last kv pair */ + } else { + next =3D e + 1; + *e =3D '\0'; + } + + if (STRPREFIX(p, "id=3D")) { + /* formulate authdef for src->auth */ + if (src->auth) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("duplicate 'id' found in '%s'"), src->pat= h); + return -1; + } + + authdef =3D g_new0(virStorageAuthDef, 1); + + authdef->username =3D g_strdup(p + strlen("id=3D")); + + authdef->secrettype =3D g_strdup(virSecretUsageTypeToString(VI= R_SECRET_USAGE_TYPE_CEPH)); + src->auth =3D g_steal_pointer(&authdef); + + /* Cannot formulate a secretType (eg, usage or uuid) given + * what is provided. + */ + } + if (STRPREFIX(p, "mon_host=3D")) { + char *h, *sep; + + h =3D p + strlen("mon_host=3D"); + while (h < e) { + for (sep =3D h; sep < e; ++sep) { + if (*sep =3D=3D '\\' && (sep[1] =3D=3D ',' || + sep[1] =3D=3D ';' || + sep[1] =3D=3D ' ')) { + *sep =3D '\0'; + sep +=3D 2; + break; + } + } + + if (virStorageSourceRBDAddHost(src, h) < 0) + return -1; + + h =3D sep; + } + } + + if (STRPREFIX(p, "conf=3D")) + src->configFile =3D g_strdup(p + strlen("conf=3D")); + + p =3D next; + } + return 0; +} + + +static int +virStorageSourceParseNBDColonString(const char *nbdstr, + virStorageSourcePtr src) +{ + g_autofree char *nbd =3D g_strdup(nbdstr); + char *export_name; + char *host_spec; + char *unixpath; + char *port; + + src->hosts =3D g_new0(virStorageNetHostDef, 1); + src->nhosts =3D 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=3DEXPORTNAME] + * nbd:HOSTNAME:PORT[:exportname=3DEXPORTNAME] + */ + + /* first look for ':exportname=3D' and cut it off */ + if ((export_name =3D strstr(nbd, ":exportname=3D"))) { + src->path =3D g_strdup(export_name + strlen(":exportname=3D")); + export_name[0] =3D '\0'; + } + + /* Verify the prefix and contents. Note that we require a + * "host_spec" part to be present. */ + if (!(host_spec =3D STRSKIP(nbd, "nbd:")) || host_spec[0] =3D=3D '\0') + goto malformed; + + if ((unixpath =3D STRSKIP(host_spec, "unix:"))) { + src->hosts->transport =3D VIR_STORAGE_NET_HOST_TRANS_UNIX; + + if (unixpath[0] =3D=3D '\0') + goto malformed; + + src->hosts->socket =3D g_strdup(unixpath); + } else { + src->hosts->transport =3D VIR_STORAGE_NET_HOST_TRANS_TCP; + + if (host_spec[0] =3D=3D ':') { + /* no host given */ + goto malformed; + } else if (host_spec[0] =3D=3D '[') { + host_spec++; + /* IPv6 addr */ + if (!(port =3D strstr(host_spec, "]:"))) + goto malformed; + + port[0] =3D '\0'; + port +=3D 2; + + if (host_spec[0] =3D=3D '\0') + goto malformed; + } else { + if (!(port =3D strchr(host_spec, ':'))) + goto malformed; + + port[0] =3D '\0'; + port++; + } + + if (virStringParsePort(port, &src->hosts->port) < 0) + return -1; + + src->hosts->name =3D 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 =3D NULL; + + if (!(p =3D strchr(path, ':'))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("invalid backing protocol string '%s'"), + path); + return -1; + } + + protocol =3D g_strndup(path, p - path); + + if ((src->protocol =3D 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 prot= ocol %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 =3D virJSONValueObjectGetString(json, "filename"))) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing 'filename' field in JSON backing volume " + "definition")); + return -1; + } + + src->path =3D g_strdup(path); + + src->type =3D type; + return 0; +} + + +static int +virStorageSourceParseBackingJSONUriStr(virStorageSourcePtr src, + const char *uri, + int protocol) +{ + int rc; + + if ((rc =3D virStorageSourceParseBackingURI(src, uri)) < 0) + return -1; + + if (src->protocol !=3D 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 =3D NULL; + size_t ncookies =3D 0; + size_t i; + + if (!virJSONValueObjectHasKey(json, "cookie")) + return 0; + + if (!(cookiestr =3D virJSONValueObjectGetString(json, "cookie"))) { + virReportError(VIR_ERR_INVALID_ARG, + _("wrong format of 'cookie' field in backing store = definition '%s'"), + jsonstr); + return -1; + } + + if (!(cookies =3D virStringSplitCount(cookiestr, ";", 0, &ncookies))) + return -1; + + src->cookies =3D g_new0(virStorageNetCookieDefPtr, ncookies); + src->ncookies =3D ncookies; + + for (i =3D 0; i < ncookies; i++) { + char *cookiename =3D cookies[i]; + char *cookievalue; + + virSkipSpaces((const char **) &cookiename); + + if (!(cookievalue =3D strchr(cookiename, '=3D'))) { + virReportError(VIR_ERR_INVALID_ARG, + _("malformed http cookie '%s' in backing store = definition '%s'"), + cookies[i], jsonstr); + return -1; + } + + *cookievalue =3D '\0'; + cookievalue++; + + src->cookies[i] =3D g_new0(virStorageNetCookieDef, 1); + src->cookies[i]->name =3D g_strdup(cookiename); + src->cookies[i]->value =3D g_strdup(cookievalue); + } + + return 0; +} + + +static int +virStorageSourceParseBackingJSONUri(virStorageSourcePtr src, + virJSONValuePtr json, + const char *jsonstr, + int protocol) +{ + const char *uri; + + if (!(uri =3D virJSONValueObjectGetString(json, "url"))) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing 'url' in JSON backing volume definition"= )); + return -1; + } + + if (protocol =3D=3D VIR_STORAGE_NET_PROTOCOL_HTTPS || + protocol =3D=3D VIR_STORAGE_NET_PROTOCOL_FTPS) { + if (virJSONValueObjectHasKey(json, "sslverify")) { + const char *tmpstr; + bool tmp; + + /* libguestfs still uses undocumented legacy value of 'off' */ + if ((tmpstr =3D virJSONValueObjectGetString(json, "sslverify")= ) && + STREQ(tmpstr, "off")) { + src->sslverify =3D VIR_TRISTATE_BOOL_NO; + } else { + if (virJSONValueObjectGetBoolean(json, "sslverify", &tmp) = < 0) { + virReportError(VIR_ERR_INVALID_ARG, + _("malformed 'sslverify' field in backi= ng store definition '%s'"), + jsonstr); + return -1; + } + + src->sslverify =3D virTristateBoolFromBool(tmp); + } + } + } + + if (protocol =3D=3D VIR_STORAGE_NET_PROTOCOL_HTTPS || + protocol =3D=3D VIR_STORAGE_NET_PROTOCOL_HTTP) { + if (virStorageSourceParseBackingJSONUriCookies(src, json, jsonstr)= < 0) + return -1; + } + + if (virJSONValueObjectHasKey(json, "readahead") && + virJSONValueObjectGetNumberUlong(json, "readahead", &src->readahea= d) < 0) { + virReportError(VIR_ERR_INVALID_ARG, + _("malformed 'readahead' field in backing store def= inition '%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 defin= ition '%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 =3D virJSONValueObjectGetString(json, "host"); + port =3D 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 =3D VIR_STORAGE_NET_HOST_TRANS_TCP; + host->name =3D 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 =3D 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, jso= n); + + } else if (STREQ(type, "unix")) { + host->transport =3D VIR_STORAGE_NET_HOST_TRANS_UNIX; + + socket =3D virJSONValueObjectGetString(json, "path"); + + /* check for old spelling for gluster protocol */ + if (!socket) + socket =3D virJSONValueObjectGetString(json, "socket"); + + if (!socket) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing socket path for udp backing server i= n " + "JSON backing volume definition")); + return -1; + } + + host->socket =3D 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 =3D virJSONValueObjectGetString(json, "filename"); + const char *volume =3D virJSONValueObjectGetString(json, "volume"); + const char *path =3D virJSONValueObjectGetString(json, "path"); + virJSONValuePtr server =3D 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_PROT= OCOL_GLUSTER); + + if (!volume || !path || !server) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing 'volume', 'path' or 'server' attribute i= n " + "JSON backing definition for gluster volume")); + return -1; + } + + src->type =3D VIR_STORAGE_TYPE_NETWORK; + src->protocol =3D VIR_STORAGE_NET_PROTOCOL_GLUSTER; + + src->volume =3D g_strdup(volume); + src->path =3D g_strdup(path); + + nservers =3D virJSONValueArraySize(server); + if (nservers =3D=3D 0) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("at least 1 server is necessary in " + "JSON backing definition for gluster volume")); + + return -1; + } + + src->hosts =3D g_new0(virStorageNetHostDef, nservers); + src->nhosts =3D nservers; + + for (i =3D 0; i < nservers; i++) { + if (virStorageSourceParseBackingJSONSocketAddress(src->hosts + i, + virJSONValueArra= yGet(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 =3D virJSONValueObjectGetString(json, "transport= "); + const char *portal =3D virJSONValueObjectGetString(json, "portal"); + const char *target =3D virJSONValueObjectGetString(json, "target"); + const char *lun =3D virJSONValueObjectGetStringOrNumber(json, "lun"); + const char *uri; + char *port; + + /* legacy URI based syntax passed via 'filename' option */ + if ((uri =3D virJSONValueObjectGetString(json, "filename"))) + return virStorageSourceParseBackingJSONUriStr(src, uri, + VIR_STORAGE_NET_PROT= OCOL_ISCSI); + + src->type =3D VIR_STORAGE_TYPE_NETWORK; + src->protocol =3D VIR_STORAGE_NET_PROTOCOL_ISCSI; + + if (!lun) + lun =3D "0"; + + src->hosts =3D g_new0(virStorageNetHostDef, 1); + src->nhosts =3D 1; + + if (STRNEQ_NULLABLE(transport, "tcp")) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("only TCP transport is supported for iSCSI volume= s")); + return -1; + } + + src->hosts->transport =3D VIR_STORAGE_NET_HOST_TRANS_TCP; + + if (!portal) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing 'portal' address in iSCSI backing defini= tion")); + return -1; + } + + if (!target) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing 'target' in iSCSI backing definition")); + return -1; + } + + src->hosts->name =3D g_strdup(portal); + + if ((port =3D strrchr(src->hosts->name, ':')) && + !strchr(port, ']')) { + if (virStringParsePort(port + 1, &src->hosts->port) < 0) + return -1; + + *port =3D '\0'; + } + + src->path =3D 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 =3D virJSONValueObjectGetString(json, "path"); + const char *host =3D virJSONValueObjectGetString(json, "host"); + const char *port =3D virJSONValueObjectGetString(json, "port"); + const char *export =3D virJSONValueObjectGetString(json, "export"); + virJSONValuePtr server =3D 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 =3D VIR_STORAGE_TYPE_NETWORK; + src->protocol =3D VIR_STORAGE_NET_PROTOCOL_NBD; + + src->path =3D g_strdup(export); + + src->hosts =3D g_new0(virStorageNetHostDef, 1); + src->nhosts =3D 1; + + if (server) { + if (virStorageSourceParseBackingJSONSocketAddress(src->hosts, serv= er) < 0) + return -1; + } else { + if (path) { + src->hosts[0].transport =3D VIR_STORAGE_NET_HOST_TRANS_UNIX; + src->hosts[0].socket =3D g_strdup(path); + } else { + src->hosts[0].transport =3D VIR_STORAGE_NET_HOST_TRANS_TCP; + src->hosts[0].name =3D 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 =3D virJSONValueObjectGetString(json, "vdi"); + virJSONValuePtr server =3D virJSONValueObjectGetObject(json, "server"); + + /* legacy URI based syntax passed via 'filename' option */ + if ((filename =3D 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 synta= x */ + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing sheepdog URI in JSON backing volume defi= nition")); + return -1; + } + + src->type =3D VIR_STORAGE_TYPE_NETWORK; + src->protocol =3D VIR_STORAGE_NET_PROTOCOL_SHEEPDOG; + + if (!vdi) { + virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing sheepdog vdi = name")); + return -1; + } + + src->path =3D g_strdup(vdi); + + src->hosts =3D g_new0(virStorageNetHostDef, 1); + src->nhosts =3D 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 =3D virJSONValueObjectGetString(json, "path"); + const char *host =3D virJSONValueObjectGetString(json, "host"); + const char *port =3D virJSONValueObjectGetString(json, "port"); + const char *user =3D virJSONValueObjectGetString(json, "user"); + const char *host_key_check =3D virJSONValueObjectGetString(json, "host= _key_check"); + virJSONValuePtr server =3D 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 =3D VIR_STORAGE_TYPE_NETWORK; + src->protocol =3D VIR_STORAGE_NET_PROTOCOL_SSH; + + src->path =3D g_strdup(path); + + src->hosts =3D g_new0(virStorageNetHostDef, 1); + src->nhosts =3D 1; + + if (server) { + if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts, + server) < 0) + return -1; + } else { + src->hosts[0].transport =3D VIR_STORAGE_NET_HOST_TRANS_TCP; + src->hosts[0].name =3D 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 =3D g_strdup(user); + if (STREQ_NULLABLE(host_key_check, "no")) + src->ssh_host_key_check_disabled =3D 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 =3D virJSONValueObjectGetString(json, "pool"); + const char *image =3D virJSONValueObjectGetString(json, "image"); + const char *conf =3D virJSONValueObjectGetString(json, "conf"); + const char *snapshot =3D virJSONValueObjectGetString(json, "snapshot"); + virJSONValuePtr servers =3D virJSONValueObjectGetArray(json, "server"); + size_t nservers; + size_t i; + + src->type =3D VIR_STORAGE_TYPE_NETWORK; + src->protocol =3D VIR_STORAGE_NET_PROTOCOL_RBD; + + /* legacy syntax passed via 'filename' option */ + if ((filename =3D virJSONValueObjectGetString(json, "filename"))) + return virStorageSourceParseRBDColonString(filename, src); + + if (!pool || !image) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing pool or image name in ceph backing volum= e " + "JSON specification")); + return -1; + } + + src->volume =3D g_strdup(pool); + src->path =3D g_strdup(image); + src->snapshot =3D g_strdup(snapshot); + src->configFile =3D g_strdup(conf); + + if (servers) { + nservers =3D virJSONValueArraySize(servers); + + src->hosts =3D g_new0(virStorageNetHostDef, nservers); + src->nhosts =3D nservers; + + for (i =3D 0; i < nservers; i++) { + if (virStorageSourceParseBackingJSONInetSocketAddress(src->hos= ts + i, + virJSONV= alueArrayGet(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 =3D virJSONValueObjectHasKey(json, "offset"); + bool has_size =3D virJSONValueObjectHasKey(json, "size"); + virJSONValuePtr file; + + if (has_offset || has_size) { + src->sliceStorage =3D g_new0(virStorageSourceSlice, 1); + + if (has_offset && + virJSONValueObjectGetNumberUlong(json, "offset", &src->sliceSt= orage->offset) < 0) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("malformed 'offset' property of 'raw' driver"= )); + return -1; + } + + if (has_size && + virJSONValueObjectGetNumberUlong(json, "size", &src->sliceStor= age->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 =3D virJSONValueObjectGetObject(json, "file"))) { + virReportError(VIR_ERR_INVALID_ARG, + _("JSON backing volume definition '%s' lacks 'file'= object"), + jsonstr); + return -1; + } + + return virStorageSourceParseBackingJSONInternal(src, file, jsonstr, fa= lse); +} + + +static int +virStorageSourceParseBackingJSONVxHS(virStorageSourcePtr src, + virJSONValuePtr json, + const char *jsonstr G_GNUC_UNUSED, + int opaque G_GNUC_UNUSED) +{ + const char *vdisk_id =3D virJSONValueObjectGetString(json, "vdisk-id"); + virJSONValuePtr server =3D 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 =3D VIR_STORAGE_TYPE_NETWORK; + src->protocol =3D VIR_STORAGE_NET_PROTOCOL_VXHS; + + src->path =3D g_strdup(vdisk_id); + + src->hosts =3D g_new0(virStorageNetHostDef, 1); + src->nhosts =3D 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 =3D virJSONValueObjectGetObject(json, "server"); + int uidStore =3D -1; + int gidStore =3D -1; + int gotUID =3D virJSONValueObjectGetNumberInt(json, "user", &uidStore); + int gotGID =3D virJSONValueObjectGetNumberInt(json, "group", &gidStore= ); + + if (!server) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing 'server' attribute in JSON backing defin= ition for NFS volume")); + return -1; + } + + if (gotUID < 0 || gotGID < 0) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing 'user' or 'group' attribute in JSON back= ing definition for NFS volume")); + return -1; + } + + src->path =3D g_strdup(virJSONValueObjectGetString(json, "path")); + if (!src->path) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing 'path' attribute in JSON backing definit= ion for NFS volume")); + return -1; + } + + src->nfs_user =3D g_strdup_printf("+%d", uidStore); + src->nfs_group =3D g_strdup_printf("+%d", gidStore); + + src->type =3D VIR_STORAGE_TYPE_NETWORK; + src->protocol =3D VIR_STORAGE_NET_PROTOCOL_NFS; + + src->hosts =3D g_new0(virStorageNetHostDef, 1); + src->nhosts =3D 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 =3D g_new0(virStorageSourceNVM= eDef, 1); + const char *device =3D virJSONValueObjectGetString(json, "device"); + + if (!device || virPCIDeviceAddressParse((char *) device, &nvme->pciAdd= r) < 0) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing or malformed 'device' field of 'nvme' st= orage")); + return -1; + } + + if (virJSONValueObjectGetNumberUlong(json, "namespace", &nvme->namespc= ) < 0 || + nvme->namespc =3D=3D 0) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing or malformed 'namespace' field of 'nvme'= storage")); + return -1; + } + + src->type =3D VIR_STORAGE_TYPE_NVME; + src->nvme =3D 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 e= rror + * 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 authenti= cation + * credentials are present). + */ + int (*func)(virStorageSourcePtr src, virJSONValuePtr json, const char = *jsonstr, int opaque); + int opaque; +}; + +static const struct virStorageSourceJSONDriverParser jsonParsers[] =3D { + {"file", false, virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE= _FILE}, + {"host_device", false, virStorageSourceParseBackingJSONPath, VIR_STORA= GE_TYPE_BLOCK}, + {"host_cdrom", false, virStorageSourceParseBackingJSONPath, VIR_STORAG= E_TYPE_BLOCK}, + {"http", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_P= ROTOCOL_HTTP}, + {"https", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_= PROTOCOL_HTTPS}, + {"ftp", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PR= OTOCOL_FTP}, + {"ftps", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_P= ROTOCOL_FTPS}, + {"tftp", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_P= ROTOCOL_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 =3D virJSONValueObjectGetString(json, "driver"))) { + virReportError(VIR_ERR_INVALID_ARG, + _("JSON backing volume definition '%s' lacks driver= name"), + jsonstr); + return -1; + } + + for (i =3D 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].opaq= ue); + } + + virReportError(VIR_ERR_INTERNAL_ERROR, + _("missing parser implementation for JSON backing volum= e " + "driver '%s'"), drvname); + return -1; +} + + +int +virStorageSourceParseBackingJSON(virStorageSourcePtr src, + const char *json) +{ + g_autoptr(virJSONValue) root =3D NULL; + g_autoptr(virJSONValue) deflattened =3D NULL; + virJSONValuePtr file =3D NULL; + + if (!(root =3D virJSONValueFromString(json))) + return -1; + + if (!(deflattened =3D 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 =3D virJSONValueObjectGetObject(deflattened, "file"); + + if (!file) + file =3D deflattened; + + return virStorageSourceParseBackingJSONInternal(src, file, json, true); +} diff --git a/src/storage_file/storage_source_backingstore.h b/src/storage_f= ile/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); --=20 2.29.2