/*
* storage_conf.c: config handling for storage driver
*
* Copyright (C) 2006-2016 Red Hat, Inc.
* Copyright (C) 2006-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
#include
#include
#include
#include "virerror.h"
#include "storage_adapter_conf.h"
#include "storage_conf.h"
#include "storage_source_conf.h"
#include "virxml.h"
#include "viruuid.h"
#include "virbuffer.h"
#include "viralloc.h"
#include "virfile.h"
#include "virstring.h"
#include "virlog.h"
#include "virutil.h"
#define VIR_FROM_THIS VIR_FROM_STORAGE
VIR_LOG_INIT("conf.storage_conf");
VIR_ENUM_IMPL(virStorageVol,
VIR_STORAGE_VOL_LAST,
"file", "block", "dir", "network",
"netdir", "ploop",
);
VIR_ENUM_IMPL(virStoragePool,
VIR_STORAGE_POOL_LAST,
"dir", "fs", "netfs",
"logical", "disk", "iscsi",
"iscsi-direct", "scsi", "mpath",
"rbd", "sheepdog", "gluster",
"zfs", "vstorage",
);
VIR_ENUM_IMPL(virStoragePoolFormatFileSystem,
VIR_STORAGE_POOL_FS_LAST,
"auto", "ext2", "ext3",
"ext4", "ufs", "iso9660", "udf",
"gfs", "gfs2", "vfat", "hfs+", "xfs", "ocfs2",
"vmfs",
);
VIR_ENUM_IMPL(virStoragePoolFormatFileSystemNet,
VIR_STORAGE_POOL_NETFS_LAST,
"auto", "nfs", "glusterfs", "cifs",
);
VIR_ENUM_IMPL(virStoragePoolFormatDisk,
VIR_STORAGE_POOL_DISK_LAST,
"unknown", "dos", "dvh", "gpt",
"mac", "bsd", "pc98", "sun", "lvm2",
);
VIR_ENUM_IMPL(virStoragePoolFormatLogical,
VIR_STORAGE_POOL_LOGICAL_LAST,
"unknown", "lvm2",
);
VIR_ENUM_IMPL(virStorageVolFormatDisk,
VIR_STORAGE_VOL_DISK_LAST,
"none", "linux", "fat16",
"fat32", "linux-swap",
"linux-lvm", "linux-raid",
"extended",
);
VIR_ENUM_IMPL(virStorageVolDefRefreshAllocation,
VIR_STORAGE_VOL_DEF_REFRESH_ALLOCATION_LAST,
"default", "capacity",
);
VIR_ENUM_IMPL(virStoragePartedFs,
VIR_STORAGE_PARTED_FS_TYPE_LAST,
"ext2", "ext2", "fat16",
"fat32", "linux-swap",
"ext2", "ext2",
"extended",
);
typedef const char *(*virStorageVolFormatToString)(int format);
typedef int (*virStorageVolFormatFromString)(const char *format);
typedef const char *(*virStoragePoolFormatToString)(int format);
typedef int (*virStoragePoolFormatFromString)(const char *format);
typedef struct _virStorageVolOptions virStorageVolOptions;
struct _virStorageVolOptions {
int defaultFormat;
int lastFormat;
virStorageVolFormatToString formatToString;
virStorageVolFormatFromString formatFromString;
};
/* Flags to indicate mandatory components in the pool source */
enum {
VIR_STORAGE_POOL_SOURCE_HOST = (1 << 0),
VIR_STORAGE_POOL_SOURCE_DEVICE = (1 << 1),
VIR_STORAGE_POOL_SOURCE_DIR = (1 << 2),
VIR_STORAGE_POOL_SOURCE_ADAPTER = (1 << 3),
VIR_STORAGE_POOL_SOURCE_NAME = (1 << 4),
VIR_STORAGE_POOL_SOURCE_INITIATOR_IQN = (1 << 5),
VIR_STORAGE_POOL_SOURCE_NETWORK = (1 << 6),
};
typedef struct _virStoragePoolOptions virStoragePoolOptions;
struct _virStoragePoolOptions {
unsigned int flags;
int defaultFormat;
int lastFormat;
virXMLNamespace ns;
virStoragePoolFormatToString formatToString;
virStoragePoolFormatFromString formatFromString;
};
typedef struct _virStoragePoolTypeInfo virStoragePoolTypeInfo;
struct _virStoragePoolTypeInfo {
int poolType;
virStoragePoolOptions poolOptions;
virStorageVolOptions volOptions;
};
static int
virStorageVolumeFormatFromString(const char *format)
{
int ret = virStorageFileFormatTypeFromString(format);
if (ret == VIR_STORAGE_FILE_NONE)
return -1;
return ret;
}
static virStoragePoolTypeInfo poolTypeInfo[] = {
{.poolType = VIR_STORAGE_POOL_LOGICAL,
.poolOptions = {
.flags = (VIR_STORAGE_POOL_SOURCE_NAME |
VIR_STORAGE_POOL_SOURCE_DEVICE),
.defaultFormat = VIR_STORAGE_POOL_LOGICAL_LVM2,
.lastFormat = VIR_STORAGE_POOL_LOGICAL_LAST,
.formatFromString = virStoragePoolFormatLogicalTypeFromString,
.formatToString = virStoragePoolFormatLogicalTypeToString,
},
},
{.poolType = VIR_STORAGE_POOL_DIR,
.volOptions = {
.defaultFormat = VIR_STORAGE_FILE_RAW,
.lastFormat = VIR_STORAGE_FILE_LAST,
.formatFromString = virStorageVolumeFormatFromString,
.formatToString = virStorageFileFormatTypeToString,
},
},
{.poolType = VIR_STORAGE_POOL_FS,
.poolOptions = {
.flags = (VIR_STORAGE_POOL_SOURCE_DEVICE),
.defaultFormat = VIR_STORAGE_POOL_FS_AUTO,
.lastFormat = VIR_STORAGE_POOL_FS_LAST,
.formatFromString = virStoragePoolFormatFileSystemTypeFromString,
.formatToString = virStoragePoolFormatFileSystemTypeToString,
},
.volOptions = {
.defaultFormat = VIR_STORAGE_FILE_RAW,
.lastFormat = VIR_STORAGE_FILE_LAST,
.formatFromString = virStorageVolumeFormatFromString,
.formatToString = virStorageFileFormatTypeToString,
},
},
{.poolType = VIR_STORAGE_POOL_NETFS,
.poolOptions = {
.flags = (VIR_STORAGE_POOL_SOURCE_HOST |
VIR_STORAGE_POOL_SOURCE_DIR),
.defaultFormat = VIR_STORAGE_POOL_NETFS_AUTO,
.lastFormat = VIR_STORAGE_POOL_NETFS_LAST,
.formatFromString = virStoragePoolFormatFileSystemNetTypeFromString,
.formatToString = virStoragePoolFormatFileSystemNetTypeToString,
},
.volOptions = {
.defaultFormat = VIR_STORAGE_FILE_RAW,
.lastFormat = VIR_STORAGE_FILE_LAST,
.formatFromString = virStorageVolumeFormatFromString,
.formatToString = virStorageFileFormatTypeToString,
},
},
{.poolType = VIR_STORAGE_POOL_ISCSI,
.poolOptions = {
.flags = (VIR_STORAGE_POOL_SOURCE_HOST |
VIR_STORAGE_POOL_SOURCE_DEVICE |
VIR_STORAGE_POOL_SOURCE_INITIATOR_IQN),
},
},
{.poolType = VIR_STORAGE_POOL_ISCSI_DIRECT,
.poolOptions = {
.flags = (VIR_STORAGE_POOL_SOURCE_HOST |
VIR_STORAGE_POOL_SOURCE_DEVICE |
VIR_STORAGE_POOL_SOURCE_NETWORK |
VIR_STORAGE_POOL_SOURCE_INITIATOR_IQN),
},
},
{.poolType = VIR_STORAGE_POOL_SCSI,
.poolOptions = {
.flags = (VIR_STORAGE_POOL_SOURCE_ADAPTER),
},
},
{.poolType = VIR_STORAGE_POOL_RBD,
.poolOptions = {
.flags = (VIR_STORAGE_POOL_SOURCE_HOST |
VIR_STORAGE_POOL_SOURCE_NETWORK |
VIR_STORAGE_POOL_SOURCE_NAME),
},
.volOptions = {
.defaultFormat = VIR_STORAGE_FILE_RAW,
.formatFromString = virStorageVolumeFormatFromString,
.formatToString = virStorageFileFormatTypeToString,
}
},
{.poolType = VIR_STORAGE_POOL_SHEEPDOG,
.poolOptions = {
.flags = (VIR_STORAGE_POOL_SOURCE_HOST |
VIR_STORAGE_POOL_SOURCE_NETWORK |
VIR_STORAGE_POOL_SOURCE_NAME),
},
},
{.poolType = VIR_STORAGE_POOL_GLUSTER,
.poolOptions = {
.flags = (VIR_STORAGE_POOL_SOURCE_HOST |
VIR_STORAGE_POOL_SOURCE_NETWORK |
VIR_STORAGE_POOL_SOURCE_NAME |
VIR_STORAGE_POOL_SOURCE_DIR),
},
.volOptions = {
.defaultFormat = VIR_STORAGE_FILE_RAW,
.lastFormat = VIR_STORAGE_FILE_LAST,
.formatToString = virStorageFileFormatTypeToString,
.formatFromString = virStorageVolumeFormatFromString,
}
},
{.poolType = VIR_STORAGE_POOL_MPATH,
},
{.poolType = VIR_STORAGE_POOL_DISK,
.poolOptions = {
.flags = (VIR_STORAGE_POOL_SOURCE_DEVICE),
.defaultFormat = VIR_STORAGE_POOL_DISK_UNKNOWN,
.lastFormat = VIR_STORAGE_POOL_DISK_LAST,
.formatFromString = virStoragePoolFormatDiskTypeFromString,
.formatToString = virStoragePoolFormatDiskTypeToString,
},
.volOptions = {
.defaultFormat = VIR_STORAGE_VOL_DISK_NONE,
.lastFormat = VIR_STORAGE_VOL_DISK_LAST,
.formatFromString = virStorageVolFormatDiskTypeFromString,
.formatToString = virStorageVolFormatDiskTypeToString,
},
},
{.poolType = VIR_STORAGE_POOL_ZFS,
.poolOptions = {
.flags = (VIR_STORAGE_POOL_SOURCE_NAME |
VIR_STORAGE_POOL_SOURCE_DEVICE),
},
},
{.poolType = VIR_STORAGE_POOL_VSTORAGE,
.poolOptions = {
.flags = VIR_STORAGE_POOL_SOURCE_NAME,
},
.volOptions = {
.defaultFormat = VIR_STORAGE_FILE_RAW,
.lastFormat = VIR_STORAGE_FILE_LAST,
.formatFromString = virStorageVolumeFormatFromString,
.formatToString = virStorageFileFormatTypeToString,
},
},
};
static virStoragePoolTypeInfo *
virStoragePoolTypeInfoLookup(int type)
{
size_t i;
for (i = 0; i < G_N_ELEMENTS(poolTypeInfo); i++)
if (poolTypeInfo[i].poolType == type)
return &poolTypeInfo[i];
virReportError(VIR_ERR_INTERNAL_ERROR,
_("missing backend for pool type %d"), type);
return NULL;
}
static virStoragePoolOptions *
virStoragePoolOptionsForPoolType(int type)
{
virStoragePoolTypeInfo *backend = virStoragePoolTypeInfoLookup(type);
if (backend == NULL)
return NULL;
return &backend->poolOptions;
}
/* virStoragePoolOptionsPoolTypeSetXMLNamespace:
* @type: virStoragePoolType
* @ns: xmlopt namespace pointer
*
* Store the @ns in the pool options for the particular backend.
* This allows the parse/format code to then directly call the Namespace
* method space (parse, format, href, free) as needed during processing.
*
* Returns: 0 on success, -1 on failure.
*/
int
virStoragePoolOptionsPoolTypeSetXMLNamespace(int type,
virXMLNamespace *ns)
{
virStoragePoolTypeInfo *backend = virStoragePoolTypeInfoLookup(type);
if (!backend)
return -1;
backend->poolOptions.ns = *ns;
return 0;
}
static virStorageVolOptions *
virStorageVolOptionsForPoolType(int type)
{
virStoragePoolTypeInfo *backend = virStoragePoolTypeInfoLookup(type);
if (backend == NULL)
return NULL;
return &backend->volOptions;
}
int
virStoragePoolOptionsFormatPool(virBuffer *buf,
int type)
{
virStoragePoolOptions *poolOptions;
if (!(poolOptions = virStoragePoolOptionsForPoolType(type)))
return -1;
if (!poolOptions->formatToString)
return 0;
virBufferAddLit(buf, "\n");
virBufferAdjustIndent(buf, 2);
if (poolOptions->formatToString) {
size_t i;
virBufferAsprintf(buf, "\n",
(poolOptions->formatToString)(poolOptions->defaultFormat));
virBufferAddLit(buf, "\n");
virBufferAdjustIndent(buf, 2);
for (i = 0; i < poolOptions->lastFormat; i++)
virBufferAsprintf(buf, "%s\n",
(poolOptions->formatToString)(i));
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
}
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
return 0;
}
int
virStoragePoolOptionsFormatVolume(virBuffer *buf,
int type)
{
size_t i;
virStorageVolOptions *volOptions;
if (!(volOptions = virStorageVolOptionsForPoolType(type)))
return -1;
if (!volOptions->formatToString)
return 0;
virBufferAddLit(buf, "\n");
virBufferAdjustIndent(buf, 2);
virBufferAsprintf(buf, "\n",
(volOptions->formatToString)(volOptions->defaultFormat));
virBufferAddLit(buf, "\n");
virBufferAdjustIndent(buf, 2);
for (i = 0; i < volOptions->lastFormat; i++)
virBufferAsprintf(buf, "%s\n",
(volOptions->formatToString)(i));
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
return 0;
}
void
virStorageVolDefFree(virStorageVolDef *def)
{
size_t i;
if (!def)
return;
g_free(def->name);
g_free(def->key);
for (i = 0; i < def->source.nextent; i++)
g_free(def->source.extents[i].path);
g_free(def->source.extents);
virStorageSourceClear(&def->target);
g_free(def);
}
void
virStoragePoolSourceDeviceClear(virStoragePoolSourceDevice *dev)
{
VIR_FREE(dev->freeExtents);
VIR_FREE(dev->path);
}
void
virStoragePoolSourceClear(virStoragePoolSource *source)
{
size_t i;
if (!source)
return;
for (i = 0; i < source->nhost; i++)
VIR_FREE(source->hosts[i].name);
VIR_FREE(source->hosts);
for (i = 0; i < source->ndevice; i++)
virStoragePoolSourceDeviceClear(&source->devices[i]);
VIR_FREE(source->devices);
VIR_FREE(source->dir);
VIR_FREE(source->name);
virStorageAdapterClear(&source->adapter);
virStorageSourceInitiatorClear(&source->initiator);
virStorageAuthDefFree(source->auth);
VIR_FREE(source->vendor);
VIR_FREE(source->product);
VIR_FREE(source->protocolVer);
}
void
virStoragePoolSourceFree(virStoragePoolSource *source)
{
virStoragePoolSourceClear(source);
g_free(source);
}
void
virStoragePoolDefFree(virStoragePoolDef *def)
{
if (!def)
return;
g_free(def->name);
virStoragePoolSourceClear(&def->source);
g_free(def->target.path);
g_free(def->target.perms.label);
g_free(def->refresh);
if (def->namespaceData && def->ns.free)
(def->ns.free)(def->namespaceData);
g_free(def);
}
static int
virStoragePoolDefParseSource(xmlXPathContextPtr ctxt,
virStoragePoolSource *source,
int pool_type,
xmlNodePtr node)
{
xmlNodePtr authnode;
xmlNodePtr adapternode;
int nsource;
size_t i;
virStoragePoolOptions *options;
int n;
g_autoptr(virStorageAuthDef) authdef = NULL;
g_autofree xmlNodePtr *nodeset = NULL;
g_autofree char *sourcedir = NULL;
VIR_XPATH_NODE_AUTORESTORE(ctxt)
ctxt->node = node;
if ((options = virStoragePoolOptionsForPoolType(pool_type)) == NULL)
return -1;
source->name = virXPathString("string(./name)", ctxt);
if (pool_type == VIR_STORAGE_POOL_RBD && source->name == NULL) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("element 'name' is mandatory for RBD pool"));
return -1;
}
if (options->formatFromString) {
g_autofree char *format = NULL;
format = virXPathString("string(./format/@type)", ctxt);
if (format == NULL)
source->format = options->defaultFormat;
else
source->format = options->formatFromString(format);
if (source->format < 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unknown pool format type %s"), format);
return -1;
}
}
if ((n = virXPathNodeSet("./host", ctxt, &nodeset)) < 0)
return -1;
if (n) {
source->hosts = g_new0(virStoragePoolSourceHost, n);
source->nhost = n;
for (i = 0; i < source->nhost; i++) {
source->hosts[i].name = virXMLPropString(nodeset[i], "name");
if (!source->hosts[i].name) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("missing storage pool host name"));
return -1;
}
if (virXMLPropInt(nodeset[i], "port", 10, VIR_XML_PROP_NONE,
&source->hosts[i].port, 0) < 0)
return -1;
}
}
VIR_FREE(nodeset);
virStorageSourceInitiatorParseXML(ctxt, &source->initiator);
nsource = virXPathNodeSet("./device", ctxt, &nodeset);
if (nsource < 0)
return -1;
for (i = 0; i < nsource; i++) {
virStoragePoolSourceDevice dev = { .path = NULL };
dev.path = virXMLPropString(nodeset[i], "path");
if (dev.path == NULL) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("missing storage pool source device path"));
return -1;
}
if (virXMLPropTristateBool(nodeset[i], "part_separator",
VIR_XML_PROP_NONE,
&dev.part_separator) < 0) {
virStoragePoolSourceDeviceClear(&dev);
return -1;
}
VIR_APPEND_ELEMENT(source->devices, source->ndevice, dev);
}
sourcedir = virXPathString("string(./dir/@path)", ctxt);
if (sourcedir)
source->dir = virFileSanitizePath(sourcedir);
/* In gluster, a missing dir defaults to "/" */
if (!source->dir && pool_type == VIR_STORAGE_POOL_GLUSTER)
source->dir = g_strdup("/");
if ((adapternode = virXPathNode("./adapter", ctxt))) {
if (virStorageAdapterParseXML(&source->adapter, adapternode, ctxt) < 0)
return -1;
}
if ((authnode = virXPathNode("./auth", ctxt))) {
if (!(authdef = virStorageAuthDefParse(authnode, ctxt)))
return -1;
if (authdef->authType == VIR_STORAGE_AUTH_TYPE_NONE) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("storage pool missing auth type"));
return -1;
}
source->auth = g_steal_pointer(&authdef);
}
/* Option protocol version string (NFSvN) */
if ((source->protocolVer = virXPathString("string(./protocol/@ver)", ctxt))) {
if ((source->format != VIR_STORAGE_POOL_NETFS_NFS) &&
(source->format != VIR_STORAGE_POOL_NETFS_AUTO)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("storage pool protocol ver unsupported for "
"pool type '%s'"),
virStoragePoolFormatFileSystemNetTypeToString(source->format));
return -1;
}
if (strchr(source->protocolVer, ',')) {
virReportError(VIR_ERR_XML_DETAIL,
_("storage pool protocol ver '%s' must not contain ','"),
source->protocolVer);
return -1;
}
}
source->vendor = virXPathString("string(./vendor/@name)", ctxt);
source->product = virXPathString("string(./product/@name)", ctxt);
return 0;
}
virStoragePoolSource *
virStoragePoolDefParseSourceString(const char *srcSpec,
int pool_type)
{
g_autoptr(xmlDoc) doc = NULL;
g_autoptr(xmlXPathContext) xpath_ctxt = NULL;
g_autoptr(virStoragePoolSource) def = NULL;
if (!(doc = virXMLParse(NULL, srcSpec, _("(storage_source_specification)"),
"source", &xpath_ctxt, NULL, false)))
return NULL;
def = g_new0(virStoragePoolSource, 1);
if (virStoragePoolDefParseSource(xpath_ctxt, def, pool_type,
xpath_ctxt->node) < 0)
return NULL;
return g_steal_pointer(&def);
}
static int
virStorageDefParsePerms(xmlXPathContextPtr ctxt,
virStoragePerms *perms,
const char *permxpath)
{
long long val;
VIR_XPATH_NODE_AUTORESTORE(ctxt)
xmlNodePtr node;
g_autofree char *mode = NULL;
node = virXPathNode(permxpath, ctxt);
if (node == NULL) {
/* Set default values if there is not element */
perms->mode = (mode_t) -1;
perms->uid = (uid_t) -1;
perms->gid = (gid_t) -1;
perms->label = NULL;
return 0;
}
ctxt->node = node;
if ((mode = virXPathString("string(./mode)", ctxt))) {
int tmp;
if (virStrToLong_i(mode, NULL, 8, &tmp) < 0 || (tmp & ~0777)) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("malformed octal mode"));
return -1;
}
perms->mode = tmp;
} else {
perms->mode = (mode_t) -1;
}
if (virXPathNode("./owner", ctxt) == NULL) {
perms->uid = (uid_t) -1;
} else {
/* We previously could output -1, so continue to parse it */
if (virXPathLongLong("number(./owner)", ctxt, &val) < 0 ||
((uid_t)val != val &&
val != -1)) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("malformed owner element"));
return -1;
}
perms->uid = val;
}
if (virXPathNode("./group", ctxt) == NULL) {
perms->gid = (gid_t) -1;
} else {
/* We previously could output -1, so continue to parse it */
if (virXPathLongLong("number(./group)", ctxt, &val) < 0 ||
((gid_t) val != val &&
val != -1)) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("malformed group element"));
return -1;
}
perms->gid = val;
}
/* NB, we're ignoring missing labels here - they'll simply inherit */
perms->label = virXPathString("string(./label)", ctxt);
return 0;
}
static int
virStoragePoolDefRefreshParse(xmlXPathContextPtr ctxt,
virStoragePoolDef *def)
{
g_autofree virStoragePoolDefRefresh *refresh = NULL;
g_autofree char *allocation = NULL;
int tmp;
allocation = virXPathString("string(./refresh/volume/@allocation)", ctxt);
if (!allocation)
return 0;
if ((tmp = virStorageVolDefRefreshAllocationTypeFromString(allocation)) < 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unknown storage pool volume refresh allocation type %s"),
allocation);
return -1;
}
refresh = g_new0(virStoragePoolDefRefresh, 1);
refresh->volume.allocation = tmp;
def->refresh = g_steal_pointer(&refresh);
return 0;
}
static void
virStoragePoolDefRefreshFormat(virBuffer *buf,
virStoragePoolDefRefresh *refresh)
{
if (!refresh)
return;
virBufferAddLit(buf, "\n");
virBufferAdjustIndent(buf, 2);
virBufferAsprintf(buf, "\n",
virStorageVolDefRefreshAllocationTypeToString(refresh->volume.allocation));
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
}
static int
virStoragePoolDefParseFeatures(virStoragePoolDef *def,
xmlXPathContextPtr ctxt)
{
xmlNodePtr node = virXPathNode("./features/cow", ctxt);
virTristateBool val;
int rv;
if ((rv = virXMLPropTristateBool(node, "state",
VIR_XML_PROP_NONE,
&val)) < 0) {
return -1;
} else if (rv > 0) {
if (def->type != VIR_STORAGE_POOL_FS &&
def->type != VIR_STORAGE_POOL_DIR) {
virReportError(VIR_ERR_NO_SUPPORT, "%s",
_("cow feature may only be used for 'fs' and 'dir' pools"));
return -1;
}
def->features.cow = val;
}
return 0;
}
virStoragePoolDef *
virStoragePoolDefParseXML(xmlXPathContextPtr ctxt)
{
virStoragePoolOptions *options;
xmlNodePtr source_node;
g_autoptr(virStoragePoolDef) def = NULL;
virStoragePoolType type;
g_autofree char *uuid = NULL;
g_autofree char *target_path = NULL;
def = g_new0(virStoragePoolDef, 1);
if (virXMLPropEnum(ctxt->node, "type", virStoragePoolTypeFromString,
VIR_XML_PROP_REQUIRED, &type) < 0)
return NULL;
def->type = type;
if ((options = virStoragePoolOptionsForPoolType(def->type)) == NULL)
return NULL;
source_node = virXPathNode("./source", ctxt);
if (source_node) {
if (virStoragePoolDefParseSource(ctxt, &def->source, def->type,
source_node) < 0)
return NULL;
} else {
if (options->formatFromString)
def->source.format = options->defaultFormat;
}
def->name = virXPathString("string(./name)", ctxt);
if (def->name == NULL &&
options->flags & VIR_STORAGE_POOL_SOURCE_NAME)
def->name = g_strdup(def->source.name);
if (def->name == NULL) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("missing pool source name element"));
return NULL;
}
if (strchr(def->name, '/')) {
virReportError(VIR_ERR_XML_ERROR,
_("name %s cannot contain '/'"), def->name);
return NULL;
}
uuid = virXPathString("string(./uuid)", ctxt);
if (uuid == NULL) {
if (virUUIDGenerate(def->uuid) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("unable to generate uuid"));
return NULL;
}
} else {
if (virUUIDParse(uuid, def->uuid) < 0) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("malformed uuid element"));
return NULL;
}
}
if (virStoragePoolDefParseFeatures(def, ctxt) < 0)
return NULL;
if (options->flags & VIR_STORAGE_POOL_SOURCE_HOST) {
if (!def->source.nhost) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("missing storage pool source host name"));
return NULL;
}
}
if (options->flags & VIR_STORAGE_POOL_SOURCE_DIR) {
if (!def->source.dir) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("missing storage pool source path"));
return NULL;
}
}
if (options->flags & VIR_STORAGE_POOL_SOURCE_NAME) {
if (def->source.name == NULL) {
/* source name defaults to pool name */
def->source.name = g_strdup(def->name);
}
}
if ((options->flags & VIR_STORAGE_POOL_SOURCE_ADAPTER) &&
(virStorageAdapterValidate(&def->source.adapter)) < 0)
return NULL;
/* If DEVICE is the only source type, then its required */
if (options->flags == VIR_STORAGE_POOL_SOURCE_DEVICE) {
if (!def->source.ndevice) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("missing storage pool source device name"));
return NULL;
}
}
/* When we are working with a virtual disk we can skip the target
* path and permissions */
if (!(options->flags & VIR_STORAGE_POOL_SOURCE_NETWORK)) {
if (def->type == VIR_STORAGE_POOL_LOGICAL) {
target_path = g_strdup_printf("/dev/%s", def->source.name);
} else if (def->type == VIR_STORAGE_POOL_ZFS) {
target_path = g_strdup_printf("/dev/zvol/%s", def->source.name);
} else {
target_path = virXPathString("string(./target/path)", ctxt);
if (!target_path) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("missing storage pool target path"));
return NULL;
}
}
def->target.path = virFileSanitizePath(target_path);
if (!def->target.path)
return NULL;
if (virStorageDefParsePerms(ctxt, &def->target.perms,
"./target/permissions") < 0)
return NULL;
}
if (def->type == VIR_STORAGE_POOL_ISCSI_DIRECT &&
!def->source.initiator.iqn) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("missing initiator IQN"));
return NULL;
}
if (virStoragePoolDefRefreshParse(ctxt, def) < 0)
return NULL;
/* Make a copy of all the callback pointers here for easier use,
* especially during the virStoragePoolSourceClear method */
def->ns = options->ns;
if (def->ns.parse) {
if (virXMLNamespaceRegister(ctxt, &def->ns) < 0)
return NULL;
if ((def->ns.parse)(ctxt, &def->namespaceData) < 0)
return NULL;
}
return g_steal_pointer(&def);
}
virStoragePoolDef *
virStoragePoolDefParse(const char *xmlStr,
const char *filename,
unsigned int flags)
{
g_autoptr(xmlDoc) xml = NULL;
g_autoptr(xmlXPathContext) ctxt = NULL;
bool validate = flags & VIR_STORAGE_POOL_DEFINE_VALIDATE;
if (!(xml = virXMLParse(filename, xmlStr, _("(storage_pool_definition)"),
"pool", &ctxt, "storagepool.rng", validate)))
return NULL;
return virStoragePoolDefParseXML(ctxt);
}
static int
virStoragePoolSourceFormat(virBuffer *buf,
virStoragePoolOptions *options,
virStoragePoolSource *src)
{
size_t i, j;
virBufferAddLit(buf, "\n");
return 0;
}
static void
virStoragePoolDefFormatFeatures(virBuffer *buf,
virStoragePoolDef *def)
{
if (def->features.cow == VIR_TRISTATE_BOOL_ABSENT)
return;
virBufferAddLit(buf, "\n");
virBufferAdjustIndent(buf, 2);
if (def->features.cow != VIR_TRISTATE_BOOL_ABSENT)
virBufferAsprintf(buf, "\n",
virTristateBoolTypeToString(def->features.cow));
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
}
static int
virStoragePoolDefFormatBuf(virBuffer *buf,
virStoragePoolDef *def)
{
virStoragePoolOptions *options;
char uuid[VIR_UUID_STRING_BUFLEN];
const char *type;
options = virStoragePoolOptionsForPoolType(def->type);
if (options == NULL)
return -1;
type = virStoragePoolTypeToString(def->type);
if (!type) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("unexpected pool type"));
return -1;
}
virBufferAsprintf(buf, "namespaceData && def->ns.format)
virXMLNamespaceFormatNS(buf, &def->ns);
virBufferAddLit(buf, ">\n");
virBufferAdjustIndent(buf, 2);
virBufferEscapeString(buf, "%s\n", def->name);
virUUIDFormat(def->uuid, uuid);
virBufferAsprintf(buf, "%s\n", uuid);
virBufferAsprintf(buf, "%llu\n",
def->capacity);
virBufferAsprintf(buf, "%llu\n",
def->allocation);
virBufferAsprintf(buf, "%llu\n",
def->available);
virStoragePoolDefFormatFeatures(buf, def);
if (virStoragePoolSourceFormat(buf, options, &def->source) < 0)
return -1;
/* RBD, Sheepdog, Gluster and Iscsi-direct devices are not local block devs nor
* files, so they don't have a target */
if (def->type != VIR_STORAGE_POOL_RBD &&
def->type != VIR_STORAGE_POOL_SHEEPDOG &&
def->type != VIR_STORAGE_POOL_GLUSTER &&
def->type != VIR_STORAGE_POOL_ISCSI_DIRECT) {
virBufferAddLit(buf, "\n");
virBufferAdjustIndent(buf, 2);
virBufferEscapeString(buf, "%s\n", def->target.path);
if (def->target.perms.mode != (mode_t) -1 ||
def->target.perms.uid != (uid_t) -1 ||
def->target.perms.gid != (gid_t) -1 ||
def->target.perms.label) {
virBufferAddLit(buf, "\n");
virBufferAdjustIndent(buf, 2);
if (def->target.perms.mode != (mode_t) -1)
virBufferAsprintf(buf, "0%o\n",
def->target.perms.mode);
if (def->target.perms.uid != (uid_t) -1)
virBufferAsprintf(buf, "%d\n",
(int) def->target.perms.uid);
if (def->target.perms.gid != (gid_t) -1)
virBufferAsprintf(buf, "%d\n",
(int) def->target.perms.gid);
virBufferEscapeString(buf, "\n",
def->target.perms.label);
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
}
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
}
virStoragePoolDefRefreshFormat(buf, def->refresh);
if (def->namespaceData && def->ns.format) {
if ((def->ns.format)(buf, def->namespaceData) < 0)
return -1;
}
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
return 0;
}
char *
virStoragePoolDefFormat(virStoragePoolDef *def)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
if (virStoragePoolDefFormatBuf(&buf, def) < 0)
return NULL;
return virBufferContentAndReset(&buf);
}
static int
virStorageSize(const char *unit,
const char *val,
unsigned long long *ret)
{
if (virStrToLong_ullp(val, NULL, 10, ret) < 0) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("malformed capacity element"));
return -1;
}
/* off_t is signed, so you cannot create a file larger than 2**63
* bytes in the first place. */
if (virScaleInteger(ret, unit, 1, LLONG_MAX) < 0)
return -1;
return 0;
}
static int
virStorageCheckCompat(const char *compat)
{
unsigned int result;
g_auto(GStrv) version = NULL;
if (!compat)
return 0;
version = g_strsplit(compat, ".", 2);
if (!version || !version[1] ||
virStrToLong_ui(version[0], NULL, 10, &result) < 0 ||
virStrToLong_ui(version[1], NULL, 10, &result) < 0) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("forbidden characters in 'compat' attribute"));
return -1;
}
return 0;
}
virStorageVolDef *
virStorageVolDefParseXML(virStoragePoolDef *pool,
xmlXPathContextPtr ctxt,
unsigned int flags)
{
virStorageVolOptions *options;
xmlNodePtr node;
size_t i;
int n;
g_autoptr(virStorageVolDef) def = NULL;
g_autofree char *type = NULL;
g_autofree char *allocation = NULL;
g_autofree char *capacity = NULL;
g_autofree char *unit = NULL;
g_autofree char *backingStore = NULL;
g_autofree xmlNodePtr *nodes = NULL;
virCheckFlags(VIR_VOL_XML_PARSE_NO_CAPACITY |
VIR_VOL_XML_PARSE_OPT_CAPACITY, NULL);
options = virStorageVolOptionsForPoolType(pool->type);
if (options == NULL)
return NULL;
def = g_new0(virStorageVolDef, 1);
def->target.type = VIR_STORAGE_TYPE_FILE;
def->name = virXPathString("string(./name)", ctxt);
if (def->name == NULL) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("missing volume name element"));
return NULL;
}
/* Normally generated by pool refresh, but useful for unit tests */
def->key = virXPathString("string(./key)", ctxt);
/* Technically overridden by pool refresh, but useful for unit tests */
type = virXPathString("string(./@type)", ctxt);
if (type) {
if ((def->type = virStorageVolTypeFromString(type)) < 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unknown volume type '%s'"), type);
return NULL;
}
}
if ((backingStore = virXPathString("string(./backingStore/path)", ctxt))) {
def->target.backingStore = virStorageSourceNew();
def->target.backingStore->type = VIR_STORAGE_TYPE_FILE;
def->target.backingStore->path = g_steal_pointer(&backingStore);
if (options->formatFromString) {
g_autofree char *format = NULL;
format = virXPathString("string(./backingStore/format/@type)", ctxt);
if (format == NULL)
def->target.backingStore->format = options->defaultFormat;
else
def->target.backingStore->format = (options->formatFromString)(format);
if (def->target.backingStore->format < 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unknown volume format type %s"), format);
return NULL;
}
}
def->target.backingStore->perms = g_new0(virStoragePerms, 1);
if (virStorageDefParsePerms(ctxt, def->target.backingStore->perms,
"./backingStore/permissions") < 0)
return NULL;
}
capacity = virXPathString("string(./capacity)", ctxt);
unit = virXPathString("string(./capacity/@unit)", ctxt);
if (capacity) {
if (virStorageSize(unit, capacity, &def->target.capacity) < 0)
return NULL;
} else if (!(flags & VIR_VOL_XML_PARSE_NO_CAPACITY) &&
!((flags & VIR_VOL_XML_PARSE_OPT_CAPACITY) &&
virStorageSourceHasBacking(&def->target))) {
virReportError(VIR_ERR_XML_ERROR, "%s", _("missing capacity element"));
return NULL;
}
VIR_FREE(unit);
allocation = virXPathString("string(./allocation)", ctxt);
if (allocation) {
unit = virXPathString("string(./allocation/@unit)", ctxt);
if (virStorageSize(unit, allocation, &def->target.allocation) < 0)
return NULL;
def->target.has_allocation = true;
} else {
def->target.allocation = def->target.capacity;
}
def->target.path = virXPathString("string(./target/path)", ctxt);
if (options->formatFromString) {
g_autofree char *format = NULL;
format = virXPathString("string(./target/format/@type)", ctxt);
if (format == NULL)
def->target.format = options->defaultFormat;
else
def->target.format = (options->formatFromString)(format);
if (def->target.format < 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unknown volume format type %s"), format);
return NULL;
}
}
def->target.perms = g_new0(virStoragePerms, 1);
if (virStorageDefParsePerms(ctxt, def->target.perms,
"./target/permissions") < 0)
return NULL;
node = virXPathNode("./target/encryption", ctxt);
if (node != NULL) {
def->target.encryption = virStorageEncryptionParseNode(node, ctxt);
if (def->target.encryption == NULL)
return NULL;
}
def->target.compat = virXPathString("string(./target/compat)", ctxt);
if (virStorageCheckCompat(def->target.compat) < 0)
return NULL;
if (virXPathNode("./target/nocow", ctxt))
def->target.nocow = true;
if (virParseScaledValue("./target/clusterSize",
"./target/clusterSize/@unit",
ctxt, &def->target.clusterSize,
1, ULLONG_MAX, false) < 0) {
return NULL;
}
if (virXPathNode("./target/features", ctxt)) {
if ((n = virXPathNodeSet("./target/features/*", ctxt, &nodes)) < 0)
return NULL;
if (!def->target.compat)
def->target.compat = g_strdup("1.1");
def->target.features = virBitmapNew(VIR_STORAGE_FILE_FEATURE_LAST);
for (i = 0; i < n; i++) {
int f = virStorageFileFeatureTypeFromString((const char*)nodes[i]->name);
if (f < 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("unsupported feature %s"),
(const char*)nodes[i]->name);
return NULL;
}
ignore_value(virBitmapSetBit(def->target.features, f));
}
VIR_FREE(nodes);
}
return g_steal_pointer(&def);
}
virStorageVolDef *
virStorageVolDefParse(virStoragePoolDef *pool,
const char *xmlStr,
const char *filename,
unsigned int flags)
{
g_autoptr(xmlDoc) xml = NULL;
g_autoptr(xmlXPathContext) ctxt = NULL;
if (!(xml = virXMLParse(filename, xmlStr, _("(storage_volume_definition)"),
"volume", &ctxt, NULL, false)))
return NULL;
return virStorageVolDefParseXML(pool, ctxt, flags);
}
static void
virStorageVolTimestampFormat(virBuffer *buf, const char *name,
struct timespec *ts)
{
if (ts->tv_nsec < 0)
return;
virBufferAsprintf(buf, "<%s>%llu", name,
(unsigned long long) ts->tv_sec);
if (ts->tv_nsec)
virBufferAsprintf(buf, ".%09ld", ts->tv_nsec);
virBufferAsprintf(buf, "%s>\n", name);
}
static int
virStorageVolTargetDefFormat(virStorageVolOptions *options,
virBuffer *buf,
virStorageSource *def,
const char *type)
{
virBufferAsprintf(buf, "<%s>\n", type);
virBufferAdjustIndent(buf, 2);
virBufferEscapeString(buf, "%s\n", def->path);
if (options->formatToString) {
const char *format = (options->formatToString)(def->format);
if (!format) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unknown volume format number %d"),
def->format);
return -1;
}
virBufferAsprintf(buf, "\n", format);
}
if (def->perms &&
(def->perms->mode != (mode_t) -1 ||
def->perms->uid != (uid_t) -1 ||
def->perms->gid != (gid_t) -1 ||
def->perms->label)) {
virBufferAddLit(buf, "\n");
virBufferAdjustIndent(buf, 2);
if (def->perms->mode != (mode_t) -1)
virBufferAsprintf(buf, "0%o\n",
def->perms->mode);
if (def->perms->uid != (uid_t) -1)
virBufferAsprintf(buf, "%d\n",
(int) def->perms->uid);
if (def->perms->gid != (gid_t) -1)
virBufferAsprintf(buf, "%d\n",
(int) def->perms->gid);
virBufferEscapeString(buf, "\n",
def->perms->label);
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
}
if (def->timestamps) {
virBufferAddLit(buf, "\n");
virBufferAdjustIndent(buf, 2);
virStorageVolTimestampFormat(buf, "atime", &def->timestamps->atime);
virStorageVolTimestampFormat(buf, "mtime", &def->timestamps->mtime);
virStorageVolTimestampFormat(buf, "ctime", &def->timestamps->ctime);
virStorageVolTimestampFormat(buf, "btime", &def->timestamps->btime);
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
}
if (def->encryption &&
virStorageEncryptionFormat(buf, def->encryption) < 0)
return -1;
virBufferEscapeString(buf, "%s\n", def->compat);
if (def->clusterSize > 0) {
virBufferAsprintf(buf, "%llu\n",
def->clusterSize);
}
if (def->features) {
size_t i;
bool empty = virBitmapIsAllClear(def->features);
if (empty) {
virBufferAddLit(buf, "\n");
} else {
virBufferAddLit(buf, "\n");
virBufferAdjustIndent(buf, 2);
}
for (i = 0; i < VIR_STORAGE_FILE_FEATURE_LAST; i++) {
if (virBitmapIsBitSet(def->features, i))
virBufferAsprintf(buf, "<%s/>\n",
virStorageFileFeatureTypeToString(i));
}
if (!empty) {
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
}
}
virBufferAdjustIndent(buf, -2);
virBufferAsprintf(buf, "%s>\n", type);
return 0;
}
static void
virStorageVolDefFormatSourceExtents(virBuffer *buf,
virStorageVolDef *def)
{
size_t i;
const char *thispath = NULL;
for (i = 0; i < def->source.nextent; i++) {
if (thispath == NULL ||
STRNEQ(thispath, def->source.extents[i].path)) {
if (thispath != NULL)
virBufferAddLit(buf, "\n");
virBufferEscapeString(buf, "\n",
def->source.extents[i].path);
}
virBufferAdjustIndent(buf, 2);
virBufferAsprintf(buf, "\n",
def->source.extents[i].start,
def->source.extents[i].end);
virBufferAdjustIndent(buf, -2);
thispath = def->source.extents[i].path;
}
if (thispath != NULL)
virBufferAddLit(buf, "\n");
}
char *
virStorageVolDefFormat(virStoragePoolDef *pool,
virStorageVolDef *def)
{
virStorageVolOptions *options;
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
g_auto(virBuffer) sourceChildBuf = VIR_BUFFER_INITIALIZER;
options = virStorageVolOptionsForPoolType(pool->type);
if (options == NULL)
return NULL;
virBufferAsprintf(&buf, "\n",
virStorageVolTypeToString(def->type));
virBufferAdjustIndent(&buf, 2);
virBufferEscapeString(&buf, "%s\n", def->name);
virBufferEscapeString(&buf, "%s\n", def->key);
virBufferSetIndent(&sourceChildBuf, virBufferGetIndent(&buf) + 2);
if (def->source.nextent)
virStorageVolDefFormatSourceExtents(&sourceChildBuf, def);
virXMLFormatElement(&buf, "source", NULL, &sourceChildBuf);
virBufferAsprintf(&buf, "%llu\n",
def->target.capacity);
virBufferAsprintf(&buf, "%llu\n",
def->target.allocation);
/* NB: Display only - since virStorageVolInfo is limited to just
* 'capacity' and 'allocation' on output. Since we don't read this
* in, be sure it was filled in before printing */
if (def->target.physical)
virBufferAsprintf(&buf, "%llu\n",
def->target.physical);
if (virStorageVolTargetDefFormat(options, &buf,
&def->target, "target") < 0)
return NULL;
if (virStorageSourceHasBacking(&def->target) &&
virStorageVolTargetDefFormat(options, &buf,
def->target.backingStore,
"backingStore") < 0)
return NULL;
virBufferAdjustIndent(&buf, -2);
virBufferAddLit(&buf, "\n");
return virBufferContentAndReset(&buf);
}
static int
virStoragePoolSaveXML(const char *path,
virStoragePoolDef *def,
const char *xml)
{
char uuidstr[VIR_UUID_STRING_BUFLEN];
virUUIDFormat(def->uuid, uuidstr);
return virXMLSaveFile(path,
virXMLPickShellSafeComment(def->name, uuidstr),
"pool-edit", xml);
}
int
virStoragePoolSaveState(const char *stateFile,
virStoragePoolDef *def)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
g_autofree char *xml = NULL;
virBufferAddLit(&buf, "\n");
virBufferAdjustIndent(&buf, 2);
if (virStoragePoolDefFormatBuf(&buf, def) < 0)
return -1;
virBufferAdjustIndent(&buf, -2);
virBufferAddLit(&buf, "\n");
if (!(xml = virBufferContentAndReset(&buf)))
return -1;
if (virStoragePoolSaveXML(stateFile, def, xml))
return -1;
return 0;
}
int
virStoragePoolSaveConfig(const char *configFile,
virStoragePoolDef *def)
{
g_autofree char *xml = NULL;
if (!(xml = virStoragePoolDefFormat(def))) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("failed to generate XML"));
return -1;
}
return virStoragePoolSaveXML(configFile, def, xml);
}
virStoragePoolSource *
virStoragePoolSourceListNewSource(virStoragePoolSourceList *list)
{
virStoragePoolSource *source;
VIR_REALLOC_N(list->sources, list->nsources + 1);
source = &list->sources[list->nsources++];
memset(source, 0, sizeof(*source));
return source;
}
char *
virStoragePoolSourceListFormat(virStoragePoolSourceList *def)
{
virStoragePoolOptions *options;
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
const char *type;
size_t i;
options = virStoragePoolOptionsForPoolType(def->type);
if (options == NULL)
return NULL;
type = virStoragePoolTypeToString(def->type);
if (!type) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("unexpected pool type"));
return NULL;
}
virBufferAddLit(&buf, "\n");
virBufferAdjustIndent(&buf, 2);
for (i = 0; i < def->nsources; i++)
virStoragePoolSourceFormat(&buf, options, &def->sources[i]);
virBufferAdjustIndent(&buf, -2);
virBufferAddLit(&buf, "\n");
return virBufferContentAndReset(&buf);
}
void
virStoragePoolSourceListFree(virStoragePoolSourceList *list)
{
size_t i;
if (!list)
return;
for (i = 0; i < list->nsources; i++)
virStoragePoolSourceClear(&list->sources[i]);
g_free(list->sources);
g_free(list);
}