mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-10-31 18:33:11 +00:00
676e56e515
The @vhostuser member of virStorageSource structure is allocated during parsing in virDomainDiskSourceVHostUserParse() but never freed leading to a memleak. Since the member is an object it has to be unrefed instead of g_free()-d. Signed-off-by: Michal Privoznik <mprivozn@redhat.com> Reviewed-by: Pavel Hrdina <phrdina@redhat.com>
1355 lines
33 KiB
C
1355 lines
33 KiB
C
/*
|
|
* storage_source_conf.c: file utility functions for FS storage backend
|
|
*
|
|
* 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
|
|
* <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include "storage_source_conf.h"
|
|
|
|
#include "viralloc.h"
|
|
#include "virbuffer.h"
|
|
#include "virerror.h"
|
|
#include "virlog.h"
|
|
#include "virstring.h"
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_STORAGE
|
|
|
|
VIR_LOG_INIT("conf.storage_source_conf");
|
|
|
|
|
|
static virClassPtr virStorageSourceClass;
|
|
|
|
|
|
VIR_ENUM_IMPL(virStorage,
|
|
VIR_STORAGE_TYPE_LAST,
|
|
"none",
|
|
"file",
|
|
"block",
|
|
"dir",
|
|
"network",
|
|
"volume",
|
|
"nvme",
|
|
"vhostuser"
|
|
);
|
|
|
|
|
|
VIR_ENUM_IMPL(virStorageFileFormat,
|
|
VIR_STORAGE_FILE_LAST,
|
|
"none",
|
|
"raw", "dir", "bochs",
|
|
"cloop", "dmg", "iso",
|
|
"vpc", "vdi",
|
|
/* Not direct file formats, but used for various drivers */
|
|
"fat", "vhd", "ploop",
|
|
/* Formats with backing file below here */
|
|
"cow", "qcow", "qcow2", "qed", "vmdk",
|
|
);
|
|
|
|
|
|
VIR_ENUM_IMPL(virStorageFileFeature,
|
|
VIR_STORAGE_FILE_FEATURE_LAST,
|
|
"lazy_refcounts",
|
|
);
|
|
|
|
|
|
VIR_ENUM_IMPL(virStorageNetProtocol,
|
|
VIR_STORAGE_NET_PROTOCOL_LAST,
|
|
"none",
|
|
"nbd",
|
|
"rbd",
|
|
"sheepdog",
|
|
"gluster",
|
|
"iscsi",
|
|
"http",
|
|
"https",
|
|
"ftp",
|
|
"ftps",
|
|
"tftp",
|
|
"ssh",
|
|
"vxhs",
|
|
"nfs",
|
|
);
|
|
|
|
|
|
VIR_ENUM_IMPL(virStorageNetHostTransport,
|
|
VIR_STORAGE_NET_HOST_TRANS_LAST,
|
|
"tcp",
|
|
"unix",
|
|
"rdma",
|
|
);
|
|
|
|
|
|
VIR_ENUM_IMPL(virStorageSourcePoolMode,
|
|
VIR_STORAGE_SOURCE_POOL_MODE_LAST,
|
|
"default",
|
|
"host",
|
|
"direct",
|
|
);
|
|
|
|
|
|
VIR_ENUM_IMPL(virStorageAuth,
|
|
VIR_STORAGE_AUTH_TYPE_LAST,
|
|
"none", "chap", "ceph",
|
|
);
|
|
|
|
|
|
/**
|
|
* virStorageSourceIsBacking:
|
|
* @src: storage source
|
|
*
|
|
* Returns true if @src is a eligible backing store structure. Useful
|
|
* for iterators.
|
|
*/
|
|
bool
|
|
virStorageSourceIsBacking(const virStorageSource *src)
|
|
{
|
|
return src && src->type != VIR_STORAGE_TYPE_NONE;
|
|
}
|
|
|
|
/**
|
|
* virStorageSourceHasBacking:
|
|
* @src: storage source
|
|
*
|
|
* Returns true if @src has backing store/chain.
|
|
*/
|
|
bool
|
|
virStorageSourceHasBacking(const virStorageSource *src)
|
|
{
|
|
return virStorageSourceIsBacking(src) && src->backingStore &&
|
|
src->backingStore->type != VIR_STORAGE_TYPE_NONE;
|
|
}
|
|
|
|
|
|
void
|
|
virStorageNetHostDefClear(virStorageNetHostDefPtr def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
VIR_FREE(def->name);
|
|
VIR_FREE(def->socket);
|
|
}
|
|
|
|
|
|
void
|
|
virStorageNetHostDefFree(size_t nhosts,
|
|
virStorageNetHostDefPtr hosts)
|
|
{
|
|
size_t i;
|
|
|
|
if (!hosts)
|
|
return;
|
|
|
|
for (i = 0; i < nhosts; i++)
|
|
virStorageNetHostDefClear(&hosts[i]);
|
|
|
|
g_free(hosts);
|
|
}
|
|
|
|
|
|
static void
|
|
virStoragePermsFree(virStoragePermsPtr def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
g_free(def->label);
|
|
g_free(def);
|
|
}
|
|
|
|
|
|
virStorageNetHostDefPtr
|
|
virStorageNetHostDefCopy(size_t nhosts,
|
|
virStorageNetHostDefPtr hosts)
|
|
{
|
|
virStorageNetHostDefPtr ret = NULL;
|
|
size_t i;
|
|
|
|
ret = g_new0(virStorageNetHostDef, nhosts);
|
|
|
|
for (i = 0; i < nhosts; i++) {
|
|
virStorageNetHostDefPtr src = &hosts[i];
|
|
virStorageNetHostDefPtr dst = &ret[i];
|
|
|
|
dst->transport = src->transport;
|
|
dst->port = src->port;
|
|
|
|
dst->name = g_strdup(src->name);
|
|
dst->socket = g_strdup(src->socket);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
void
|
|
virStorageAuthDefFree(virStorageAuthDefPtr authdef)
|
|
{
|
|
if (!authdef)
|
|
return;
|
|
|
|
g_free(authdef->username);
|
|
g_free(authdef->secrettype);
|
|
virSecretLookupDefClear(&authdef->seclookupdef);
|
|
g_free(authdef);
|
|
}
|
|
|
|
|
|
virStorageAuthDefPtr
|
|
virStorageAuthDefCopy(const virStorageAuthDef *src)
|
|
{
|
|
g_autoptr(virStorageAuthDef) authdef = NULL;
|
|
|
|
authdef = g_new0(virStorageAuthDef, 1);
|
|
|
|
authdef->username = g_strdup(src->username);
|
|
/* Not present for storage pool, but used for disk source */
|
|
authdef->secrettype = g_strdup(src->secrettype);
|
|
authdef->authType = src->authType;
|
|
|
|
virSecretLookupDefCopy(&authdef->seclookupdef, &src->seclookupdef);
|
|
|
|
return g_steal_pointer(&authdef);
|
|
}
|
|
|
|
|
|
virStorageAuthDefPtr
|
|
virStorageAuthDefParse(xmlNodePtr node,
|
|
xmlXPathContextPtr ctxt)
|
|
{
|
|
VIR_XPATH_NODE_AUTORESTORE(ctxt)
|
|
virStorageAuthDefPtr ret = NULL;
|
|
xmlNodePtr secretnode = NULL;
|
|
g_autoptr(virStorageAuthDef) authdef = NULL;
|
|
g_autofree char *authtype = NULL;
|
|
|
|
ctxt->node = node;
|
|
|
|
authdef = g_new0(virStorageAuthDef, 1);
|
|
|
|
if (!(authdef->username = virXPathString("string(./@username)", ctxt))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing username for auth"));
|
|
goto cleanup;
|
|
}
|
|
|
|
authdef->authType = VIR_STORAGE_AUTH_TYPE_NONE;
|
|
authtype = virXPathString("string(./@type)", ctxt);
|
|
if (authtype) {
|
|
/* Used by the storage pool instead of the secret type field
|
|
* to define whether chap or ceph being used
|
|
*/
|
|
if ((authdef->authType = virStorageAuthTypeFromString(authtype)) < 0) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("unknown auth type '%s'"), authtype);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (!(secretnode = virXPathNode("./secret ", ctxt))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("Missing <secret> element in auth"));
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Used by the domain disk xml parsing in order to ensure the
|
|
* <secret type='%s' value matches the expected secret type for
|
|
* the style of disk (iscsi is chap, nbd is ceph). For some reason
|
|
* the virSecretUsageType{From|To}String() cannot be linked here
|
|
* and because only the domain parsing code cares - just keep
|
|
* it as a string.
|
|
*/
|
|
authdef->secrettype = virXMLPropString(secretnode, "type");
|
|
|
|
if (virSecretLookupParseSecret(secretnode, &authdef->seclookupdef) < 0)
|
|
goto cleanup;
|
|
|
|
ret = g_steal_pointer(&authdef);
|
|
|
|
cleanup:
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
void
|
|
virStorageAuthDefFormat(virBufferPtr buf,
|
|
virStorageAuthDefPtr authdef)
|
|
{
|
|
if (authdef->authType == VIR_STORAGE_AUTH_TYPE_NONE) {
|
|
virBufferEscapeString(buf, "<auth username='%s'>\n", authdef->username);
|
|
} else {
|
|
virBufferAsprintf(buf, "<auth type='%s' ",
|
|
virStorageAuthTypeToString(authdef->authType));
|
|
virBufferEscapeString(buf, "username='%s'>\n", authdef->username);
|
|
}
|
|
|
|
virBufferAdjustIndent(buf, 2);
|
|
virSecretLookupFormatSecret(buf, authdef->secrettype,
|
|
&authdef->seclookupdef);
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</auth>\n");
|
|
}
|
|
|
|
|
|
void
|
|
virStoragePRDefFree(virStoragePRDefPtr prd)
|
|
{
|
|
if (!prd)
|
|
return;
|
|
|
|
g_free(prd->path);
|
|
g_free(prd->mgralias);
|
|
g_free(prd);
|
|
}
|
|
|
|
|
|
virStoragePRDefPtr
|
|
virStoragePRDefParseXML(xmlXPathContextPtr ctxt)
|
|
{
|
|
virStoragePRDefPtr prd;
|
|
virStoragePRDefPtr ret = NULL;
|
|
g_autofree char *managed = NULL;
|
|
g_autofree char *type = NULL;
|
|
g_autofree char *path = NULL;
|
|
g_autofree char *mode = NULL;
|
|
|
|
prd = g_new0(virStoragePRDef, 1);
|
|
|
|
if (!(managed = virXPathString("string(./@managed)", ctxt))) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing @managed attribute for <reservations/>"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if ((prd->managed = virTristateBoolTypeFromString(managed)) <= 0) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("invalid value for 'managed': %s"), managed);
|
|
goto cleanup;
|
|
}
|
|
|
|
type = virXPathString("string(./source[1]/@type)", ctxt);
|
|
path = virXPathString("string(./source[1]/@path)", ctxt);
|
|
mode = virXPathString("string(./source[1]/@mode)", ctxt);
|
|
|
|
if (prd->managed == VIR_TRISTATE_BOOL_NO || type || path || mode) {
|
|
if (!type) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing connection type for <reservations/>"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!path) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing path for <reservations/>"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!mode) {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("missing connection mode for <reservations/>"));
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (type && STRNEQ(type, "unix")) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("unsupported connection type for <reservations/>: %s"),
|
|
type);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (mode && STRNEQ(mode, "client")) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("unsupported connection mode for <reservations/>: %s"),
|
|
mode);
|
|
goto cleanup;
|
|
}
|
|
|
|
prd->path = g_steal_pointer(&path);
|
|
ret = g_steal_pointer(&prd);
|
|
|
|
cleanup:
|
|
virStoragePRDefFree(prd);
|
|
return ret;
|
|
}
|
|
|
|
|
|
void
|
|
virStoragePRDefFormat(virBufferPtr buf,
|
|
virStoragePRDefPtr prd,
|
|
bool migratable)
|
|
{
|
|
virBufferAsprintf(buf, "<reservations managed='%s'",
|
|
virTristateBoolTypeToString(prd->managed));
|
|
if (prd->path &&
|
|
(prd->managed == VIR_TRISTATE_BOOL_NO || !migratable)) {
|
|
virBufferAddLit(buf, ">\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
virBufferAddLit(buf, "<source type='unix'");
|
|
virBufferEscapeString(buf, " path='%s'", prd->path);
|
|
virBufferAddLit(buf, " mode='client'/>\n");
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</reservations>\n");
|
|
} else {
|
|
virBufferAddLit(buf, "/>\n");
|
|
}
|
|
}
|
|
|
|
|
|
bool
|
|
virStoragePRDefIsEqual(virStoragePRDefPtr a,
|
|
virStoragePRDefPtr b)
|
|
{
|
|
if (!a && !b)
|
|
return true;
|
|
|
|
if (!a || !b)
|
|
return false;
|
|
|
|
if (a->managed != b->managed ||
|
|
STRNEQ_NULLABLE(a->path, b->path))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool
|
|
virStoragePRDefIsManaged(virStoragePRDefPtr prd)
|
|
{
|
|
return prd && prd->managed == VIR_TRISTATE_BOOL_YES;
|
|
}
|
|
|
|
|
|
bool
|
|
virStorageSourceChainHasManagedPR(virStorageSourcePtr src)
|
|
{
|
|
virStorageSourcePtr n;
|
|
|
|
for (n = src; virStorageSourceIsBacking(n); n = n->backingStore) {
|
|
if (virStoragePRDefIsManaged(n->pr))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static virStoragePRDefPtr
|
|
virStoragePRDefCopy(virStoragePRDefPtr src)
|
|
{
|
|
virStoragePRDefPtr copy = NULL;
|
|
virStoragePRDefPtr ret = NULL;
|
|
|
|
copy = g_new0(virStoragePRDef, 1);
|
|
|
|
copy->managed = src->managed;
|
|
|
|
copy->path = g_strdup(src->path);
|
|
copy->mgralias = g_strdup(src->mgralias);
|
|
|
|
ret = g_steal_pointer(©);
|
|
|
|
virStoragePRDefFree(copy);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static virStorageSourceNVMeDefPtr
|
|
virStorageSourceNVMeDefCopy(const virStorageSourceNVMeDef *src)
|
|
{
|
|
virStorageSourceNVMeDefPtr ret = NULL;
|
|
|
|
ret = g_new0(virStorageSourceNVMeDef, 1);
|
|
|
|
ret->namespc = src->namespc;
|
|
ret->managed = src->managed;
|
|
virPCIDeviceAddressCopy(&ret->pciAddr, &src->pciAddr);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static bool
|
|
virStorageSourceNVMeDefIsEqual(const virStorageSourceNVMeDef *a,
|
|
const virStorageSourceNVMeDef *b)
|
|
{
|
|
if (!a && !b)
|
|
return true;
|
|
|
|
if (!a || !b)
|
|
return false;
|
|
|
|
if (a->namespc != b->namespc ||
|
|
a->managed != b->managed ||
|
|
!virPCIDeviceAddressEqual(&a->pciAddr, &b->pciAddr))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void
|
|
virStorageSourceNVMeDefFree(virStorageSourceNVMeDefPtr def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
g_free(def);
|
|
}
|
|
|
|
|
|
bool
|
|
virStorageSourceChainHasNVMe(const virStorageSource *src)
|
|
{
|
|
const virStorageSource *n;
|
|
|
|
for (n = src; virStorageSourceIsBacking(n); n = n->backingStore) {
|
|
if (n->type == VIR_STORAGE_TYPE_NVME)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
virSecurityDeviceLabelDefPtr
|
|
virStorageSourceGetSecurityLabelDef(virStorageSourcePtr src,
|
|
const char *model)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < src->nseclabels; i++) {
|
|
if (STREQ_NULLABLE(src->seclabels[i]->model, model))
|
|
return src->seclabels[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static void
|
|
virStorageSourceSeclabelsClear(virStorageSourcePtr def)
|
|
{
|
|
size_t i;
|
|
|
|
if (def->seclabels) {
|
|
for (i = 0; i < def->nseclabels; i++)
|
|
virSecurityDeviceLabelDefFree(def->seclabels[i]);
|
|
VIR_FREE(def->seclabels);
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
virStorageSourceSeclabelsCopy(virStorageSourcePtr to,
|
|
const virStorageSource *from)
|
|
{
|
|
size_t i;
|
|
|
|
if (from->nseclabels == 0)
|
|
return 0;
|
|
|
|
to->seclabels = g_new0(virSecurityDeviceLabelDefPtr, from->nseclabels);
|
|
to->nseclabels = from->nseclabels;
|
|
|
|
for (i = 0; i < to->nseclabels; i++) {
|
|
if (!(to->seclabels[i] = virSecurityDeviceLabelDefCopy(from->seclabels[i])))
|
|
goto error;
|
|
}
|
|
|
|
return 0;
|
|
|
|
error:
|
|
virStorageSourceSeclabelsClear(to);
|
|
return -1;
|
|
}
|
|
|
|
|
|
void
|
|
virStorageNetCookieDefFree(virStorageNetCookieDefPtr def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
g_free(def->name);
|
|
g_free(def->value);
|
|
|
|
g_free(def);
|
|
}
|
|
|
|
|
|
static void
|
|
virStorageSourceNetCookiesClear(virStorageSourcePtr src)
|
|
{
|
|
size_t i;
|
|
|
|
if (!src || !src->cookies)
|
|
return;
|
|
|
|
for (i = 0; i < src->ncookies; i++)
|
|
virStorageNetCookieDefFree(src->cookies[i]);
|
|
|
|
g_clear_pointer(&src->cookies, g_free);
|
|
src->ncookies = 0;
|
|
}
|
|
|
|
|
|
static void
|
|
virStorageSourceNetCookiesCopy(virStorageSourcePtr to,
|
|
const virStorageSource *from)
|
|
{
|
|
size_t i;
|
|
|
|
if (from->ncookies == 0)
|
|
return;
|
|
|
|
to->cookies = g_new0(virStorageNetCookieDefPtr, from->ncookies);
|
|
to->ncookies = from->ncookies;
|
|
|
|
for (i = 0; i < from->ncookies; i++) {
|
|
to->cookies[i]->name = g_strdup(from->cookies[i]->name);
|
|
to->cookies[i]->value = g_strdup(from->cookies[i]->value);
|
|
}
|
|
}
|
|
|
|
|
|
/* see https://tools.ietf.org/html/rfc6265#section-4.1.1 */
|
|
static const char virStorageSourceCookieValueInvalidChars[] =
|
|
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"
|
|
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
|
|
" \",;\\";
|
|
|
|
/* in addition cookie name can't contain these */
|
|
static const char virStorageSourceCookieNameInvalidChars[] =
|
|
"()<>@:/[]?={}";
|
|
|
|
static int
|
|
virStorageSourceNetCookieValidate(virStorageNetCookieDefPtr def)
|
|
{
|
|
g_autofree char *val = g_strdup(def->value);
|
|
const char *checkval = val;
|
|
size_t len = strlen(val);
|
|
|
|
/* name must have at least 1 character */
|
|
if (*(def->name) == '\0') {
|
|
virReportError(VIR_ERR_XML_ERROR, "%s",
|
|
_("cookie name must not be empty"));
|
|
return -1;
|
|
}
|
|
|
|
/* check invalid characters in name */
|
|
if (virStringHasChars(def->name, virStorageSourceCookieValueInvalidChars) ||
|
|
virStringHasChars(def->name, virStorageSourceCookieNameInvalidChars)) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("cookie name '%s' contains invalid characters"),
|
|
def->name);
|
|
return -1;
|
|
}
|
|
|
|
/* check for optional quotes around the cookie value string */
|
|
if (val[0] == '"') {
|
|
if (val[len - 1] != '"') {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("value of cookie '%s' contains invalid characters"),
|
|
def->name);
|
|
return -1;
|
|
}
|
|
|
|
val[len - 1] = '\0';
|
|
checkval++;
|
|
}
|
|
|
|
/* check invalid characters in value */
|
|
if (virStringHasChars(checkval, virStorageSourceCookieValueInvalidChars)) {
|
|
virReportError(VIR_ERR_XML_ERROR,
|
|
_("value of cookie '%s' contains invalid characters"),
|
|
def->name);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
virStorageSourceNetCookiesValidate(virStorageSourcePtr src)
|
|
{
|
|
size_t i;
|
|
size_t j;
|
|
|
|
for (i = 0; i < src->ncookies; i++) {
|
|
if (virStorageSourceNetCookieValidate(src->cookies[i]) < 0)
|
|
return -1;
|
|
|
|
for (j = i + 1; j < src->ncookies; j++) {
|
|
if (STREQ(src->cookies[i]->name, src->cookies[j]->name)) {
|
|
virReportError(VIR_ERR_XML_ERROR, _("duplicate cookie '%s'"),
|
|
src->cookies[i]->name);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static virStorageTimestampsPtr
|
|
virStorageTimestampsCopy(const virStorageTimestamps *src)
|
|
{
|
|
virStorageTimestampsPtr ret;
|
|
|
|
ret = g_new0(virStorageTimestamps, 1);
|
|
|
|
memcpy(ret, src, sizeof(*src));
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static virStoragePermsPtr
|
|
virStoragePermsCopy(const virStoragePerms *src)
|
|
{
|
|
virStoragePermsPtr ret;
|
|
|
|
ret = g_new0(virStoragePerms, 1);
|
|
|
|
ret->mode = src->mode;
|
|
ret->uid = src->uid;
|
|
ret->gid = src->gid;
|
|
|
|
ret->label = g_strdup(src->label);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static virStorageSourcePoolDefPtr
|
|
virStorageSourcePoolDefCopy(const virStorageSourcePoolDef *src)
|
|
{
|
|
virStorageSourcePoolDefPtr ret;
|
|
|
|
ret = g_new0(virStorageSourcePoolDef, 1);
|
|
|
|
ret->voltype = src->voltype;
|
|
ret->pooltype = src->pooltype;
|
|
ret->actualtype = src->actualtype;
|
|
ret->mode = src->mode;
|
|
|
|
ret->pool = g_strdup(src->pool);
|
|
ret->volume = g_strdup(src->volume);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static virStorageSourceSlicePtr
|
|
virStorageSourceSliceCopy(const virStorageSourceSlice *src)
|
|
{
|
|
virStorageSourceSlicePtr ret = g_new0(virStorageSourceSlice, 1);
|
|
|
|
ret->offset = src->offset;
|
|
ret->size = src->size;
|
|
ret->nodename = g_strdup(src->nodename);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void
|
|
virStorageSourceSliceFree(virStorageSourceSlicePtr slice)
|
|
{
|
|
if (!slice)
|
|
return;
|
|
|
|
g_free(slice->nodename);
|
|
g_free(slice);
|
|
}
|
|
|
|
|
|
/**
|
|
* virStorageSourcePtr:
|
|
*
|
|
* Deep-copies a virStorageSource structure. If @backing chain is true
|
|
* then also copies the backing chain recursively, otherwise just
|
|
* the top element is copied. This function doesn't copy the
|
|
* storage driver access structure and thus the struct needs to be initialized
|
|
* separately.
|
|
*/
|
|
virStorageSourcePtr
|
|
virStorageSourceCopy(const virStorageSource *src,
|
|
bool backingChain)
|
|
{
|
|
g_autoptr(virStorageSource) def = virStorageSourceNew();
|
|
|
|
def->id = src->id;
|
|
def->type = src->type;
|
|
def->protocol = src->protocol;
|
|
def->format = src->format;
|
|
def->capacity = src->capacity;
|
|
def->allocation = src->allocation;
|
|
def->has_allocation = src->has_allocation;
|
|
def->physical = src->physical;
|
|
def->readonly = src->readonly;
|
|
def->shared = src->shared;
|
|
def->haveTLS = src->haveTLS;
|
|
def->tlsFromConfig = src->tlsFromConfig;
|
|
def->detected = src->detected;
|
|
def->debugLevel = src->debugLevel;
|
|
def->debug = src->debug;
|
|
def->iomode = src->iomode;
|
|
def->cachemode = src->cachemode;
|
|
def->discard = src->discard;
|
|
def->detect_zeroes = src->detect_zeroes;
|
|
def->sslverify = src->sslverify;
|
|
def->readahead = src->readahead;
|
|
def->timeout = src->timeout;
|
|
def->metadataCacheMaxSize = src->metadataCacheMaxSize;
|
|
|
|
/* storage driver metadata are not copied */
|
|
def->drv = NULL;
|
|
|
|
def->path = g_strdup(src->path);
|
|
def->volume = g_strdup(src->volume);
|
|
def->relPath = g_strdup(src->relPath);
|
|
def->backingStoreRaw = g_strdup(src->backingStoreRaw);
|
|
def->backingStoreRawFormat = src->backingStoreRawFormat;
|
|
def->snapshot = g_strdup(src->snapshot);
|
|
def->configFile = g_strdup(src->configFile);
|
|
def->nodeformat = g_strdup(src->nodeformat);
|
|
def->nodestorage = g_strdup(src->nodestorage);
|
|
def->compat = g_strdup(src->compat);
|
|
def->tlsAlias = g_strdup(src->tlsAlias);
|
|
def->tlsCertdir = g_strdup(src->tlsCertdir);
|
|
def->query = g_strdup(src->query);
|
|
|
|
if (src->sliceStorage)
|
|
def->sliceStorage = virStorageSourceSliceCopy(src->sliceStorage);
|
|
|
|
if (src->nhosts) {
|
|
if (!(def->hosts = virStorageNetHostDefCopy(src->nhosts, src->hosts)))
|
|
return NULL;
|
|
|
|
def->nhosts = src->nhosts;
|
|
}
|
|
|
|
virStorageSourceNetCookiesCopy(def, src);
|
|
|
|
if (src->srcpool &&
|
|
!(def->srcpool = virStorageSourcePoolDefCopy(src->srcpool)))
|
|
return NULL;
|
|
|
|
if (src->features)
|
|
def->features = virBitmapNewCopy(src->features);
|
|
|
|
if (src->encryption &&
|
|
!(def->encryption = virStorageEncryptionCopy(src->encryption)))
|
|
return NULL;
|
|
|
|
if (src->perms &&
|
|
!(def->perms = virStoragePermsCopy(src->perms)))
|
|
return NULL;
|
|
|
|
if (src->timestamps &&
|
|
!(def->timestamps = virStorageTimestampsCopy(src->timestamps)))
|
|
return NULL;
|
|
|
|
if (virStorageSourceSeclabelsCopy(def, src) < 0)
|
|
return NULL;
|
|
|
|
if (src->auth &&
|
|
!(def->auth = virStorageAuthDefCopy(src->auth)))
|
|
return NULL;
|
|
|
|
if (src->pr &&
|
|
!(def->pr = virStoragePRDefCopy(src->pr)))
|
|
return NULL;
|
|
|
|
if (src->nvme)
|
|
def->nvme = virStorageSourceNVMeDefCopy(src->nvme);
|
|
|
|
if (virStorageSourceInitiatorCopy(&def->initiator, &src->initiator) < 0)
|
|
return NULL;
|
|
|
|
if (backingChain && src->backingStore) {
|
|
if (!(def->backingStore = virStorageSourceCopy(src->backingStore,
|
|
true)))
|
|
return NULL;
|
|
}
|
|
|
|
/* ssh config passthrough for libguestfs */
|
|
def->ssh_host_key_check_disabled = src->ssh_host_key_check_disabled;
|
|
def->ssh_user = g_strdup(src->ssh_user);
|
|
|
|
def->nfs_user = g_strdup(src->nfs_user);
|
|
def->nfs_group = g_strdup(src->nfs_group);
|
|
def->nfs_uid = src->nfs_uid;
|
|
def->nfs_gid = src->nfs_gid;
|
|
|
|
return g_steal_pointer(&def);
|
|
}
|
|
|
|
|
|
/**
|
|
* virStorageSourceIsSameLocation:
|
|
*
|
|
* Returns true if the sources @a and @b point to the same storage location.
|
|
* This does not compare any other configuration option
|
|
*/
|
|
bool
|
|
virStorageSourceIsSameLocation(virStorageSourcePtr a,
|
|
virStorageSourcePtr b)
|
|
{
|
|
size_t i;
|
|
|
|
/* there are multiple possibilities to define an empty source */
|
|
if (virStorageSourceIsEmpty(a) &&
|
|
virStorageSourceIsEmpty(b))
|
|
return true;
|
|
|
|
if (virStorageSourceGetActualType(a) != virStorageSourceGetActualType(b))
|
|
return false;
|
|
|
|
if (STRNEQ_NULLABLE(a->path, b->path) ||
|
|
STRNEQ_NULLABLE(a->volume, b->volume) ||
|
|
STRNEQ_NULLABLE(a->snapshot, b->snapshot))
|
|
return false;
|
|
|
|
if (a->type == VIR_STORAGE_TYPE_NETWORK) {
|
|
if (a->protocol != b->protocol ||
|
|
a->nhosts != b->nhosts)
|
|
return false;
|
|
|
|
for (i = 0; i < a->nhosts; i++) {
|
|
if (a->hosts[i].transport != b->hosts[i].transport ||
|
|
a->hosts[i].port != b->hosts[i].port ||
|
|
STRNEQ_NULLABLE(a->hosts[i].name, b->hosts[i].name) ||
|
|
STRNEQ_NULLABLE(a->hosts[i].socket, b->hosts[i].socket))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (a->type == VIR_STORAGE_TYPE_NVME &&
|
|
!virStorageSourceNVMeDefIsEqual(a->nvme, b->nvme))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* virStorageSourceInitChainElement:
|
|
* @newelem: New backing chain element disk source
|
|
* @old: Existing top level disk source
|
|
* @transferLabels: Transfer security labels.
|
|
*
|
|
* Transfers relevant information from the existing disk source to the new
|
|
* backing chain element if they weren't supplied so that labelling info
|
|
* and possibly other stuff is correct.
|
|
*
|
|
* If @transferLabels is true, security labels from the existing disk are copied
|
|
* to the new disk. Otherwise the default domain imagelabel label will be used.
|
|
*
|
|
* Returns 0 on success, -1 on error.
|
|
*/
|
|
int
|
|
virStorageSourceInitChainElement(virStorageSourcePtr newelem,
|
|
virStorageSourcePtr old,
|
|
bool transferLabels)
|
|
{
|
|
if (transferLabels &&
|
|
!newelem->seclabels &&
|
|
virStorageSourceSeclabelsCopy(newelem, old) < 0)
|
|
return -1;
|
|
|
|
newelem->shared = old->shared;
|
|
newelem->readonly = old->readonly;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
virStorageSourcePoolDefFree(virStorageSourcePoolDefPtr def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
g_free(def->pool);
|
|
g_free(def->volume);
|
|
|
|
g_free(def);
|
|
}
|
|
|
|
|
|
/**
|
|
* virStorageSourceGetActualType:
|
|
* @def: storage source definition
|
|
*
|
|
* Returns type of @def. In case when the type is VIR_STORAGE_TYPE_VOLUME
|
|
* and virDomainDiskTranslateSourcePool was called on @def the actual type
|
|
* of the storage volume is returned rather than VIR_STORAGE_TYPE_VOLUME.
|
|
*/
|
|
int
|
|
virStorageSourceGetActualType(const virStorageSource *def)
|
|
{
|
|
if (def->type == VIR_STORAGE_TYPE_VOLUME &&
|
|
def->srcpool &&
|
|
def->srcpool->actualtype != VIR_STORAGE_TYPE_NONE)
|
|
return def->srcpool->actualtype;
|
|
|
|
return def->type;
|
|
}
|
|
|
|
|
|
bool
|
|
virStorageSourceIsLocalStorage(const virStorageSource *src)
|
|
{
|
|
virStorageType type = virStorageSourceGetActualType(src);
|
|
|
|
switch (type) {
|
|
case VIR_STORAGE_TYPE_FILE:
|
|
case VIR_STORAGE_TYPE_BLOCK:
|
|
case VIR_STORAGE_TYPE_DIR:
|
|
return true;
|
|
|
|
case VIR_STORAGE_TYPE_NETWORK:
|
|
case VIR_STORAGE_TYPE_VOLUME:
|
|
/* While NVMe disks are local, they are not accessible via src->path.
|
|
* Therefore, we have to return false here. */
|
|
case VIR_STORAGE_TYPE_NVME:
|
|
case VIR_STORAGE_TYPE_VHOST_USER:
|
|
case VIR_STORAGE_TYPE_LAST:
|
|
case VIR_STORAGE_TYPE_NONE:
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* virStorageSourceIsEmpty:
|
|
*
|
|
* @src: disk source to check
|
|
*
|
|
* Returns true if the guest disk has no associated host storage source
|
|
* (such as an empty cdrom drive).
|
|
*/
|
|
bool
|
|
virStorageSourceIsEmpty(virStorageSourcePtr src)
|
|
{
|
|
if (virStorageSourceIsLocalStorage(src) && !src->path)
|
|
return true;
|
|
|
|
if (src->type == VIR_STORAGE_TYPE_NONE)
|
|
return true;
|
|
|
|
if (src->type == VIR_STORAGE_TYPE_NETWORK &&
|
|
src->protocol == VIR_STORAGE_NET_PROTOCOL_NONE)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* virStorageSourceIsBlockLocal:
|
|
* @src: disk source definition
|
|
*
|
|
* Returns true if @src describes a locally accessible block storage source.
|
|
* This includes block devices and host-mapped iSCSI volumes.
|
|
*/
|
|
bool
|
|
virStorageSourceIsBlockLocal(const virStorageSource *src)
|
|
{
|
|
return virStorageSourceGetActualType(src) == VIR_STORAGE_TYPE_BLOCK;
|
|
}
|
|
|
|
|
|
/**
|
|
* virStorageSourceBackingStoreClear:
|
|
*
|
|
* @src: disk source to clear
|
|
*
|
|
* Clears information about backing store of the current storage file.
|
|
*/
|
|
void
|
|
virStorageSourceBackingStoreClear(virStorageSourcePtr def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
VIR_FREE(def->relPath);
|
|
VIR_FREE(def->backingStoreRaw);
|
|
|
|
/* recursively free backing chain */
|
|
virObjectUnref(def->backingStore);
|
|
def->backingStore = NULL;
|
|
}
|
|
|
|
|
|
void
|
|
virStorageSourceClear(virStorageSourcePtr def)
|
|
{
|
|
if (!def)
|
|
return;
|
|
|
|
VIR_FREE(def->path);
|
|
VIR_FREE(def->volume);
|
|
VIR_FREE(def->snapshot);
|
|
VIR_FREE(def->configFile);
|
|
VIR_FREE(def->query);
|
|
virStorageSourceNetCookiesClear(def);
|
|
virStorageSourcePoolDefFree(def->srcpool);
|
|
virBitmapFree(def->features);
|
|
VIR_FREE(def->compat);
|
|
virStorageEncryptionFree(def->encryption);
|
|
virStoragePRDefFree(def->pr);
|
|
virStorageSourceNVMeDefFree(def->nvme);
|
|
virObjectUnref(def->vhostuser);
|
|
virStorageSourceSeclabelsClear(def);
|
|
virStoragePermsFree(def->perms);
|
|
VIR_FREE(def->timestamps);
|
|
|
|
virStorageSourceSliceFree(def->sliceStorage);
|
|
|
|
virStorageNetHostDefFree(def->nhosts, def->hosts);
|
|
virStorageAuthDefFree(def->auth);
|
|
virObjectUnref(def->privateData);
|
|
|
|
VIR_FREE(def->nodestorage);
|
|
VIR_FREE(def->nodeformat);
|
|
|
|
virStorageSourceBackingStoreClear(def);
|
|
|
|
VIR_FREE(def->tlsAlias);
|
|
VIR_FREE(def->tlsCertdir);
|
|
|
|
VIR_FREE(def->ssh_user);
|
|
|
|
VIR_FREE(def->nfs_user);
|
|
VIR_FREE(def->nfs_group);
|
|
|
|
virStorageSourceInitiatorClear(&def->initiator);
|
|
|
|
/* clear everything except the class header as the object APIs
|
|
* will break otherwise */
|
|
memset((char *) def + sizeof(def->parent), 0,
|
|
sizeof(*def) - sizeof(def->parent));
|
|
}
|
|
|
|
|
|
static void
|
|
virStorageSourceDispose(void *obj)
|
|
{
|
|
virStorageSourcePtr src = obj;
|
|
|
|
virStorageSourceClear(src);
|
|
}
|
|
|
|
|
|
static int
|
|
virStorageSourceOnceInit(void)
|
|
{
|
|
if (!VIR_CLASS_NEW(virStorageSource, virClassForObject()))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
VIR_ONCE_GLOBAL_INIT(virStorageSource);
|
|
|
|
|
|
virStorageSourcePtr
|
|
virStorageSourceNew(void)
|
|
{
|
|
virStorageSourcePtr ret;
|
|
|
|
if (virStorageSourceInitialize() < 0)
|
|
abort();
|
|
|
|
if (!(ret = virObjectNew(virStorageSourceClass)))
|
|
abort();
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* virStorageSourceIsRelative:
|
|
* @src: storage source to check
|
|
*
|
|
* Returns true if given storage source definition is a relative path.
|
|
*/
|
|
bool
|
|
virStorageSourceIsRelative(virStorageSourcePtr src)
|
|
{
|
|
virStorageType actual_type = virStorageSourceGetActualType(src);
|
|
|
|
if (!src->path)
|
|
return false;
|
|
|
|
switch (actual_type) {
|
|
case VIR_STORAGE_TYPE_FILE:
|
|
case VIR_STORAGE_TYPE_BLOCK:
|
|
case VIR_STORAGE_TYPE_DIR:
|
|
return src->path[0] != '/';
|
|
|
|
case VIR_STORAGE_TYPE_NETWORK:
|
|
case VIR_STORAGE_TYPE_VOLUME:
|
|
case VIR_STORAGE_TYPE_NVME:
|
|
case VIR_STORAGE_TYPE_VHOST_USER:
|
|
case VIR_STORAGE_TYPE_NONE:
|
|
case VIR_STORAGE_TYPE_LAST:
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static unsigned int
|
|
virStorageSourceNetworkDefaultPort(virStorageNetProtocol protocol)
|
|
{
|
|
switch (protocol) {
|
|
case VIR_STORAGE_NET_PROTOCOL_HTTP:
|
|
return 80;
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_HTTPS:
|
|
return 443;
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_FTP:
|
|
return 21;
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_FTPS:
|
|
return 990;
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_TFTP:
|
|
return 69;
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
|
|
return 7000;
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_NBD:
|
|
return 10809;
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_SSH:
|
|
return 22;
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
|
|
return 3260;
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
|
|
return 24007;
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_RBD:
|
|
/* we don't provide a default for RBD */
|
|
return 0;
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_VXHS:
|
|
return 9999;
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_NFS:
|
|
/* Port is not supported by NFS, so no default is provided */
|
|
return 0;
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_LAST:
|
|
case VIR_STORAGE_NET_PROTOCOL_NONE:
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
virStorageSourceNetworkAssignDefaultPorts(virStorageSourcePtr src)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < src->nhosts; i++) {
|
|
if (src->hosts[i].transport == VIR_STORAGE_NET_HOST_TRANS_TCP &&
|
|
src->hosts[i].port == 0)
|
|
src->hosts[i].port = virStorageSourceNetworkDefaultPort(src->protocol);
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
virStorageSourcePrivateDataParseRelPath(xmlXPathContextPtr ctxt,
|
|
virStorageSourcePtr src)
|
|
{
|
|
src->relPath = virXPathString("string(./relPath)", ctxt);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
virStorageSourcePrivateDataFormatRelPath(virStorageSourcePtr src,
|
|
virBufferPtr buf)
|
|
{
|
|
if (src->relPath)
|
|
virBufferEscapeString(buf, "<relPath>%s</relPath>\n", src->relPath);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
virStorageSourceInitiatorParseXML(xmlXPathContextPtr ctxt,
|
|
virStorageSourceInitiatorDefPtr initiator)
|
|
{
|
|
initiator->iqn = virXPathString("string(./initiator/iqn/@name)", ctxt);
|
|
}
|
|
|
|
|
|
void
|
|
virStorageSourceInitiatorFormatXML(virStorageSourceInitiatorDefPtr initiator,
|
|
virBufferPtr buf)
|
|
{
|
|
if (!initiator->iqn)
|
|
return;
|
|
|
|
virBufferAddLit(buf, "<initiator>\n");
|
|
virBufferAdjustIndent(buf, 2);
|
|
virBufferEscapeString(buf, "<iqn name='%s'/>\n", initiator->iqn);
|
|
virBufferAdjustIndent(buf, -2);
|
|
virBufferAddLit(buf, "</initiator>\n");
|
|
}
|
|
|
|
|
|
int
|
|
virStorageSourceInitiatorCopy(virStorageSourceInitiatorDefPtr dest,
|
|
const virStorageSourceInitiatorDef *src)
|
|
{
|
|
dest->iqn = g_strdup(src->iqn);
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
virStorageSourceInitiatorClear(virStorageSourceInitiatorDefPtr initiator)
|
|
{
|
|
VIR_FREE(initiator->iqn);
|
|
}
|