/*
* backup_conf.c: domain backup XML processing
*
* 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 "configmake.h"
#include "internal.h"
#include "virbuffer.h"
#include "datatypes.h"
#include "domain_conf.h"
#include "virlog.h"
#include "viralloc.h"
#include "backup_conf.h"
#include "storage_source_conf.h"
#include "virfile.h"
#include "virerror.h"
#include "virxml.h"
#include "virstring.h"
#include "virhash.h"
#include "virenum.h"
#define VIR_FROM_THIS VIR_FROM_DOMAIN
VIR_LOG_INIT("conf.backup_conf");
VIR_ENUM_DECL(virDomainBackup);
VIR_ENUM_IMPL(virDomainBackup,
VIR_DOMAIN_BACKUP_TYPE_LAST,
"default",
"push",
"pull");
/* following values appear in the status XML */
VIR_ENUM_DECL(virDomainBackupDiskState);
VIR_ENUM_IMPL(virDomainBackupDiskState,
VIR_DOMAIN_BACKUP_DISK_STATE_LAST,
"",
"running",
"complete",
"failed",
"cancelling",
"cancelled");
VIR_ENUM_DECL(virDomainBackupDiskBackupMode);
VIR_ENUM_IMPL(virDomainBackupDiskBackupMode,
VIR_DOMAIN_BACKUP_DISK_BACKUP_MODE_LAST,
"",
"full",
"incremental");
void
virDomainBackupDefFree(virDomainBackupDef *def)
{
size_t i;
if (!def)
return;
g_free(def->incremental);
g_free(def->errmsg);
virStorageNetHostDefFree(1, def->server);
for (i = 0; i < def->ndisks; i++) {
virDomainBackupDiskDef *disk = def->disks + i;
g_free(disk->name);
g_free(disk->incremental);
g_free(disk->exportname);
g_free(disk->exportbitmap);
virObjectUnref(disk->store);
}
g_free(def->disks);
g_free(def->tlsAlias);
g_free(def->tlsSecretAlias);
g_free(def);
}
static int
virDomainBackupDiskDefParseXML(xmlNodePtr node,
xmlXPathContextPtr ctxt,
virDomainBackupDiskDef *def,
bool push,
unsigned int flags,
virDomainXMLOption *xmlopt)
{
VIR_XPATH_NODE_AUTORESTORE(ctxt)
g_autofree char *type = NULL;
g_autofree char *format = NULL;
g_autofree char *idx = NULL;
xmlNodePtr srcNode;
unsigned int storageSourceParseFlags = 0;
bool internal = flags & VIR_DOMAIN_BACKUP_PARSE_INTERNAL;
if (internal)
storageSourceParseFlags = VIR_DOMAIN_DEF_PARSE_STATUS;
ctxt->node = node;
if (!(def->name = virXMLPropString(node, "name"))) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("missing name from disk backup element"));
return -1;
}
if (virXMLPropTristateBool(node, "backup", VIR_XML_PROP_NONE,
&def->backup) < 0)
return -1;
if (def->backup == VIR_TRISTATE_BOOL_ABSENT)
def->backup = VIR_TRISTATE_BOOL_YES;
/* don't parse anything else if backup is disabled */
if (def->backup == VIR_TRISTATE_BOOL_NO)
return 0;
if (!push) {
def->exportname = virXMLPropString(node, "exportname");
def->exportbitmap = virXMLPropString(node, "exportbitmap");
}
if (virXMLPropEnum(node, "backupmode",
virDomainBackupDiskBackupModeTypeFromString,
VIR_XML_PROP_NONE, &def->backupmode) < 0)
return -1;
def->incremental = virXMLPropString(node, "incremental");
if (internal) {
if (virXMLPropEnum(node, "state",
virDomainBackupDiskStateTypeFromString,
VIR_XML_PROP_REQUIRED, &def->state) < 0)
return -1;
}
type = virXMLPropString(node, "type");
format = virXPathString("string(./driver/@type)", ctxt);
if (internal)
idx = virXMLPropString(node, "index");
if (!(def->store = virDomainStorageSourceParseBase(type, format, idx)))
return -1;
if (def->store->type != VIR_STORAGE_TYPE_FILE &&
def->store->type != VIR_STORAGE_TYPE_BLOCK) {
virReportError(VIR_ERR_XML_ERROR,
_("unsupported disk backup type '%s'"), type);
return -1;
}
if (push)
srcNode = virXPathNode("./target", ctxt);
else
srcNode = virXPathNode("./scratch", ctxt);
if (srcNode &&
virDomainStorageSourceParse(srcNode, ctxt, def->store,
storageSourceParseFlags, xmlopt) < 0)
return -1;
return 0;
}
static void
virDomainBackupDefParsePrivate(virDomainBackupDef *def,
xmlXPathContextPtr ctxt,
unsigned int flags)
{
if (!(flags & VIR_DOMAIN_BACKUP_PARSE_INTERNAL))
return;
def->tlsSecretAlias = virXPathString("string(./privateData/objects/secret[@type='tlskey']/@alias)", ctxt);
def->tlsAlias = virXPathString("string(./privateData/objects/TLSx509/@alias)", ctxt);
}
static virDomainBackupDef *
virDomainBackupDefParse(xmlXPathContextPtr ctxt,
virDomainXMLOption *xmlopt,
unsigned int flags)
{
g_autoptr(virDomainBackupDef) def = NULL;
g_autofree xmlNodePtr *nodes = NULL;
xmlNodePtr node = NULL;
g_autofree char *mode = NULL;
bool push;
size_t i;
int n;
def = g_new0(virDomainBackupDef, 1);
def->type = VIR_DOMAIN_BACKUP_TYPE_PUSH;
if ((mode = virXMLPropString(ctxt->node, "mode"))) {
if ((def->type = virDomainBackupTypeFromString(mode)) <= 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("unknown backup mode '%s'"), mode);
return NULL;
}
}
push = def->type == VIR_DOMAIN_BACKUP_TYPE_PUSH;
def->incremental = virXPathString("string(./incremental)", ctxt);
if ((node = virXPathNode("./server", ctxt))) {
if (def->type != VIR_DOMAIN_BACKUP_TYPE_PULL) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("use of requires pull mode backup"));
return NULL;
}
def->server = g_new0(virStorageNetHostDef, 1);
if (virDomainStorageNetworkParseHost(node, def->server) < 0)
return NULL;
if (def->server->transport == VIR_STORAGE_NET_HOST_TRANS_RDMA) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("transport rdma is not supported for "));
return NULL;
}
if (def->server->transport == VIR_STORAGE_NET_HOST_TRANS_UNIX &&
!g_path_is_absolute(def->server->socket)) {
virReportError(VIR_ERR_XML_ERROR,
_("backup socket path '%s' must be absolute"),
def->server->socket);
return NULL;
}
if (virXMLPropTristateBool(node, "tls", VIR_XML_PROP_NONE,
&def->tls) < 0)
return NULL;
}
if ((n = virXPathNodeSet("./disks/*", ctxt, &nodes)) < 0)
return NULL;
def->disks = g_new0(virDomainBackupDiskDef, n);
def->ndisks = n;
for (i = 0; i < def->ndisks; i++) {
if (virDomainBackupDiskDefParseXML(nodes[i], ctxt,
&def->disks[i], push,
flags, xmlopt) < 0)
return NULL;
}
virDomainBackupDefParsePrivate(def, ctxt, flags);
return g_steal_pointer(&def);
}
virDomainBackupDef *
virDomainBackupDefParseString(const char *xmlStr,
virDomainXMLOption *xmlopt,
unsigned int flags)
{
virDomainBackupDef *ret = NULL;
g_autoptr(xmlDoc) xml = NULL;
int keepBlanksDefault = xmlKeepBlanksDefault(0);
if ((xml = virXMLParse(NULL, xmlStr, _("(domain_backup)"), "domainbackup.rng",
!(flags & VIR_DOMAIN_BACKUP_PARSE_INTERNAL)))) {
xmlKeepBlanksDefault(keepBlanksDefault);
ret = virDomainBackupDefParseNode(xml, xmlDocGetRootElement(xml),
xmlopt, flags);
}
xmlKeepBlanksDefault(keepBlanksDefault);
return ret;
}
virDomainBackupDef *
virDomainBackupDefParseNode(xmlDocPtr xml,
xmlNodePtr root,
virDomainXMLOption *xmlopt,
unsigned int flags)
{
g_autoptr(xmlXPathContext) ctxt = NULL;
if (!virXMLNodeNameEqual(root, "domainbackup")) {
virReportError(VIR_ERR_XML_ERROR, "%s", _("domainbackup"));
return NULL;
}
if (!(ctxt = virXMLXPathContextNew(xml)))
return NULL;
ctxt->node = root;
return virDomainBackupDefParse(ctxt, xmlopt, flags);
}
static int
virDomainBackupDiskDefFormat(virBuffer *buf,
virDomainBackupDiskDef *disk,
bool push,
bool internal)
{
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
const char *sourcename = "scratch";
unsigned int storageSourceFormatFlags = 0;
if (push)
sourcename = "target";
if (internal)
storageSourceFormatFlags |= VIR_DOMAIN_DEF_FORMAT_STATUS;
virBufferEscapeString(&attrBuf, " name='%s'", disk->name);
virBufferAsprintf(&attrBuf, " backup='%s'", virTristateBoolTypeToString(disk->backup));
if (internal && disk->state != VIR_DOMAIN_BACKUP_DISK_STATE_NONE)
virBufferAsprintf(&attrBuf, " state='%s'", virDomainBackupDiskStateTypeToString(disk->state));
if (disk->backup == VIR_TRISTATE_BOOL_YES) {
virBufferAsprintf(&attrBuf, " type='%s'", virStorageTypeToString(disk->store->type));
if (disk->backupmode != VIR_DOMAIN_BACKUP_DISK_BACKUP_MODE_DEFAULT) {
virBufferAsprintf(&attrBuf, " backupmode='%s'",
virDomainBackupDiskBackupModeTypeToString(disk->backupmode));
}
virBufferEscapeString(&attrBuf, " incremental='%s'", disk->incremental);
virBufferEscapeString(&attrBuf, " exportname='%s'", disk->exportname);
virBufferEscapeString(&attrBuf, " exportbitmap='%s'", disk->exportbitmap);
if (disk->store->id != 0)
virBufferAsprintf(&attrBuf, " index='%u'", disk->store->id);
if (disk->store->format > 0)
virBufferEscapeString(&childBuf, "\n",
virStorageFileFormatTypeToString(disk->store->format));
if (virDomainDiskSourceFormat(&childBuf, disk->store, sourcename,
0, false, storageSourceFormatFlags,
false, false, NULL) < 0)
return -1;
}
virXMLFormatElement(buf, "disk", &attrBuf, &childBuf);
return 0;
}
static void
virDomainBackupDefFormatPrivate(virBuffer *buf,
virDomainBackupDef *def,
bool internal)
{
g_auto(virBuffer) privChildBuf = VIR_BUFFER_INIT_CHILD(buf);
g_auto(virBuffer) objectsChildBuf = VIR_BUFFER_INIT_CHILD(&privChildBuf);
if (!internal)
return;
virBufferEscapeString(&objectsChildBuf, "\n",
def->tlsSecretAlias);
virBufferEscapeString(&objectsChildBuf, "\n", def->tlsAlias);
virXMLFormatElement(&privChildBuf, "objects", NULL, &objectsChildBuf);
virXMLFormatElement(buf, "privateData", NULL, &privChildBuf);
}
int
virDomainBackupDefFormat(virBuffer *buf,
virDomainBackupDef *def,
bool internal)
{
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
g_auto(virBuffer) serverAttrBuf = VIR_BUFFER_INITIALIZER;
g_auto(virBuffer) disksChildBuf = VIR_BUFFER_INIT_CHILD(&childBuf);
size_t i;
virBufferAsprintf(&attrBuf, " mode='%s'", virDomainBackupTypeToString(def->type));
virBufferEscapeString(&childBuf, "%s\n", def->incremental);
if (def->server) {
virBufferAsprintf(&serverAttrBuf, " transport='%s'",
virStorageNetHostTransportTypeToString(def->server->transport));
if (def->tls != VIR_TRISTATE_BOOL_ABSENT)
virBufferAsprintf(&serverAttrBuf, " tls='%s'", virTristateBoolTypeToString(def->tls));
virBufferEscapeString(&serverAttrBuf, " name='%s'", def->server->name);
if (def->server->port)
virBufferAsprintf(&serverAttrBuf, " port='%u'", def->server->port);
virBufferEscapeString(&serverAttrBuf, " socket='%s'", def->server->socket);
}
virXMLFormatElement(&childBuf, "server", &serverAttrBuf, NULL);
for (i = 0; i < def->ndisks; i++) {
if (virDomainBackupDiskDefFormat(&disksChildBuf, &def->disks[i],
def->type == VIR_DOMAIN_BACKUP_TYPE_PUSH,
internal) < 0)
return -1;
}
virXMLFormatElement(&childBuf, "disks", NULL, &disksChildBuf);
virDomainBackupDefFormatPrivate(&childBuf, def, internal);
virXMLFormatElement(buf, "domainbackup", &attrBuf, &childBuf);
return 0;
}
static int
virDomainBackupDefAssignStore(virDomainBackupDiskDef *disk,
virStorageSource *src,
const char *suffix)
{
if (virStorageSourceIsEmpty(src)) {
if (disk->store) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("disk '%s' has no media"), disk->name);
return -1;
}
} else if (!disk->store) {
if (virStorageSourceGetActualType(src) == VIR_STORAGE_TYPE_FILE) {
disk->store = virStorageSourceNew();
disk->store->type = VIR_STORAGE_TYPE_FILE;
disk->store->path = g_strdup_printf("%s.%s", src->path, suffix);
} else {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("refusing to generate file name for disk '%s'"),
disk->name);
return -1;
}
}
return 0;
}
int
virDomainBackupAlignDisks(virDomainBackupDef *def,
virDomainDef *dom,
const char *suffix)
{
g_autoptr(GHashTable) disks = virHashNew(NULL);
size_t i;
int ndisks;
bool backup_all = false;
/* Unlikely to have a guest without disks but technically possible. */
if (!dom->ndisks) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("domain must have at least one disk to perform backup"));
return -1;
}
/* Double check requested disks. */
for (i = 0; i < def->ndisks; i++) {
virDomainBackupDiskDef *backupdisk = &def->disks[i];
virDomainDiskDef *domdisk;
if (!(domdisk = virDomainDiskByTarget(dom, backupdisk->name))) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("no disk named '%s'"), backupdisk->name);
return -1;
}
if (virHashAddEntry(disks, backupdisk->name, NULL) < 0) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("disk '%s' specified twice"),
backupdisk->name);
return -1;
}
if (backupdisk->backupmode == VIR_DOMAIN_BACKUP_DISK_BACKUP_MODE_INCREMENTAL &&
!backupdisk->incremental &&
!def->incremental) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("'incremental' backup mode of disk '%s' requires setting 'incremental' field for disk or backup"),
backupdisk->name);
return -1;
}
if (backupdisk->backup == VIR_TRISTATE_BOOL_YES &&
virDomainBackupDefAssignStore(backupdisk, domdisk->src, suffix) < 0)
return -1;
}
if (def->ndisks == 0)
backup_all = true;
ndisks = def->ndisks;
VIR_EXPAND_N(def->disks, def->ndisks, dom->ndisks - def->ndisks);
for (i = 0; i < dom->ndisks; i++) {
virDomainBackupDiskDef *backupdisk = NULL;
virDomainDiskDef *domdisk = dom->disks[i];
if (virHashHasEntry(disks, domdisk->dst))
continue;
backupdisk = &def->disks[ndisks++];
backupdisk->name = g_strdup(domdisk->dst);
if (backup_all &&
!virStorageSourceIsEmpty(domdisk->src) &&
!domdisk->src->readonly) {
backupdisk->backup = VIR_TRISTATE_BOOL_YES;
if (virDomainBackupDefAssignStore(backupdisk, domdisk->src, suffix) < 0)
return -1;
} else {
backupdisk->backup = VIR_TRISTATE_BOOL_NO;
}
}
for (i = 0; i < def->ndisks; i++) {
virDomainBackupDiskDef *backupdisk = &def->disks[i];
if (backupdisk->backupmode == VIR_DOMAIN_BACKUP_DISK_BACKUP_MODE_DEFAULT) {
if (def->incremental || backupdisk->incremental) {
backupdisk->backupmode = VIR_DOMAIN_BACKUP_DISK_BACKUP_MODE_INCREMENTAL;
} else {
backupdisk->backupmode = VIR_DOMAIN_BACKUP_DISK_BACKUP_MODE_FULL;
}
}
if (!backupdisk->incremental &&
backupdisk->backupmode == VIR_DOMAIN_BACKUP_DISK_BACKUP_MODE_INCREMENTAL)
backupdisk->incremental = g_strdup(def->incremental);
}
return 0;
}