/*
* virstoragefile.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
* .
*/
#include
#include "virstoragefilebackend.h"
#include
#include
#include "viralloc.h"
#include "virxml.h"
#include "viruuid.h"
#include "virerror.h"
#include "virlog.h"
#include "virfile.h"
#include "vircommand.h"
#include "virhash.h"
#include "virendian.h"
#include "virstring.h"
#include "viruri.h"
#include "virbuffer.h"
#include "virjson.h"
#include "virstorageencryption.h"
#include "virsecret.h"
#include "virutil.h"
#define VIR_FROM_THIS VIR_FROM_STORAGE
VIR_LOG_INIT("util.storagefile");
static virClassPtr virStorageSourceClass;
VIR_ENUM_IMPL(virStorage,
VIR_STORAGE_TYPE_LAST,
"none",
"file",
"block",
"dir",
"network",
"volume",
"nvme",
);
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",
);
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",
);
enum lv_endian {
LV_LITTLE_ENDIAN = 1, /* 1234 */
LV_BIG_ENDIAN /* 4321 */
};
enum {
BACKING_STORE_OK,
BACKING_STORE_INVALID,
BACKING_STORE_ERROR,
};
#define FILE_TYPE_VERSIONS_LAST 3
struct FileEncryptionInfo {
int format; /* Encryption format to assign */
int magicOffset; /* Byte offset of the magic */
const char *magic; /* Optional string of magic */
enum lv_endian endian; /* Endianness of file format */
int versionOffset; /* Byte offset from start of file
* where we find version number,
* -1 to always fail the version test,
* -2 to always pass the version test */
int versionSize; /* Size in bytes of version data (0, 2, or 4) */
int versionNumbers[FILE_TYPE_VERSIONS_LAST];
/* Version numbers to validate. Zeroes are ignored. */
int modeOffset; /* Byte offset of the format native encryption mode */
char modeValue; /* Value expected at offset */
int payloadOffset; /* start offset of the volume data (in 512 byte sectors) */
};
struct FileTypeInfo {
int magicOffset; /* Byte offset of the magic */
const char *magic; /* Optional string of file magic
* to check at head of file */
enum lv_endian endian; /* Endianness of file format */
int versionOffset; /* Byte offset from start of file
* where we find version number,
* -1 to always fail the version test,
* -2 to always pass the version test */
int versionSize; /* Size in bytes of version data (0, 2, or 4) */
int versionNumbers[FILE_TYPE_VERSIONS_LAST];
/* Version numbers to validate. Zeroes are ignored. */
int sizeOffset; /* Byte offset from start of file
* where we find capacity info,
* -1 to use st_size as capacity */
int sizeBytes; /* Number of bytes for size field */
int sizeMultiplier; /* A scaling factor if size is not in bytes */
/* Store a COW base image path (possibly relative),
* or NULL if there is no COW base image, to RES;
* return BACKING_STORE_* */
const struct FileEncryptionInfo *cryptInfo; /* Encryption info */
int (*getBackingStore)(char **res, int *format,
const char *buf, size_t buf_size);
int (*getFeatures)(virBitmapPtr *features, int format,
char *buf, ssize_t len);
};
static int cowGetBackingStore(char **, int *,
const char *, size_t);
static int qcowXGetBackingStore(char **, int *,
const char *, size_t);
static int qcow2GetFeatures(virBitmapPtr *features, int format,
char *buf, ssize_t len);
static int vmdk4GetBackingStore(char **, int *,
const char *, size_t);
static int
qedGetBackingStore(char **, int *, const char *, size_t);
#define QCOWX_HDR_VERSION (4)
#define QCOWX_HDR_BACKING_FILE_OFFSET (QCOWX_HDR_VERSION+4)
#define QCOWX_HDR_BACKING_FILE_SIZE (QCOWX_HDR_BACKING_FILE_OFFSET+8)
#define QCOWX_HDR_IMAGE_SIZE (QCOWX_HDR_BACKING_FILE_SIZE+4+4)
#define QCOW1_HDR_CRYPT (QCOWX_HDR_IMAGE_SIZE+8+1+1+2)
#define QCOW2_HDR_CRYPT (QCOWX_HDR_IMAGE_SIZE+8)
#define QCOW1_HDR_TOTAL_SIZE (QCOW1_HDR_CRYPT+4+8)
#define QCOW2_HDR_TOTAL_SIZE (QCOW2_HDR_CRYPT+4+4+8+8+4+4+8)
#define QCOW2_HDR_EXTENSION_END 0
#define QCOW2_HDR_EXTENSION_BACKING_FORMAT 0xE2792ACA
#define QCOW2_HDR_EXTENSION_DATA_FILE 0x44415441
#define QCOW2v3_HDR_FEATURES_INCOMPATIBLE (QCOW2_HDR_TOTAL_SIZE)
#define QCOW2v3_HDR_FEATURES_COMPATIBLE (QCOW2v3_HDR_FEATURES_INCOMPATIBLE+8)
#define QCOW2v3_HDR_FEATURES_AUTOCLEAR (QCOW2v3_HDR_FEATURES_COMPATIBLE+8)
/* The location of the header size [4 bytes] */
#define QCOW2v3_HDR_SIZE (QCOW2_HDR_TOTAL_SIZE+8+8+8+4)
#define QED_HDR_FEATURES_OFFSET (4+4+4+4)
#define QED_HDR_IMAGE_SIZE (QED_HDR_FEATURES_OFFSET+8+8+8+8)
#define QED_HDR_BACKING_FILE_OFFSET (QED_HDR_IMAGE_SIZE+8)
#define QED_HDR_BACKING_FILE_SIZE (QED_HDR_BACKING_FILE_OFFSET+4)
#define QED_F_BACKING_FILE 0x01
#define QED_F_BACKING_FORMAT_NO_PROBE 0x04
#define PLOOP_IMAGE_SIZE_OFFSET 36
#define PLOOP_SIZE_MULTIPLIER 512
#define LUKS_HDR_MAGIC_LEN 6
#define LUKS_HDR_VERSION_LEN 2
#define LUKS_HDR_CIPHER_NAME_LEN 32
#define LUKS_HDR_CIPHER_MODE_LEN 32
#define LUKS_HDR_HASH_SPEC_LEN 32
#define LUKS_HDR_PAYLOAD_LEN 4
/* Format described by qemu commit id '3e308f20e' */
#define LUKS_HDR_VERSION_OFFSET LUKS_HDR_MAGIC_LEN
#define LUKS_HDR_PAYLOAD_OFFSET (LUKS_HDR_MAGIC_LEN+\
LUKS_HDR_VERSION_LEN+\
LUKS_HDR_CIPHER_NAME_LEN+\
LUKS_HDR_CIPHER_MODE_LEN+\
LUKS_HDR_HASH_SPEC_LEN)
static struct FileEncryptionInfo const luksEncryptionInfo[] = {
{
.format = VIR_STORAGE_ENCRYPTION_FORMAT_LUKS,
/* Magic is 'L','U','K','S', 0xBA, 0xBE */
.magicOffset = 0,
.magic = "\x4c\x55\x4b\x53\xba\xbe",
.endian = LV_BIG_ENDIAN,
.versionOffset = LUKS_HDR_VERSION_OFFSET,
.versionSize = LUKS_HDR_VERSION_LEN,
.versionNumbers = {1},
.modeOffset = -1,
.modeValue = -1,
.payloadOffset = LUKS_HDR_PAYLOAD_OFFSET,
},
{ 0 }
};
static struct FileEncryptionInfo const qcow1EncryptionInfo[] = {
{
.format = VIR_STORAGE_ENCRYPTION_FORMAT_QCOW,
.magicOffset = 0,
.magic = NULL,
.endian = LV_BIG_ENDIAN,
.versionOffset = -1,
.versionSize = 0,
.versionNumbers = {},
.modeOffset = QCOW1_HDR_CRYPT,
.modeValue = 1,
.payloadOffset = -1,
},
{ 0 }
};
static struct FileEncryptionInfo const qcow2EncryptionInfo[] = {
{
.format = VIR_STORAGE_ENCRYPTION_FORMAT_QCOW,
.magicOffset = 0,
.magic = NULL,
.endian = LV_BIG_ENDIAN,
.versionOffset = -1,
.versionSize = 0,
.versionNumbers = {},
.modeOffset = QCOW2_HDR_CRYPT,
.modeValue = 1,
.payloadOffset = -1,
},
{ 0 }
};
static struct FileTypeInfo const fileTypeInfo[] = {
[VIR_STORAGE_FILE_NONE] = { 0, NULL, LV_LITTLE_ENDIAN,
-1, 0, {0}, 0, 0, 0, NULL, NULL, NULL },
[VIR_STORAGE_FILE_RAW] = { 0, NULL, LV_LITTLE_ENDIAN,
-1, 0, {0}, 0, 0, 0,
luksEncryptionInfo,
NULL, NULL },
[VIR_STORAGE_FILE_DIR] = { 0, NULL, LV_LITTLE_ENDIAN,
-1, 0, {0}, 0, 0, 0, NULL, NULL, NULL },
[VIR_STORAGE_FILE_BOCHS] = {
/*"Bochs Virtual HD Image", */ /* Untested */
0, NULL,
LV_LITTLE_ENDIAN, 64, 4, {0x20000},
32+16+16+4+4+4+4+4, 8, 1, NULL, NULL, NULL
},
[VIR_STORAGE_FILE_CLOOP] = {
/* #!/bin/sh
#V2.0 Format
modprobe cloop file=$0 && mount -r -t iso9660 /dev/cloop $1
*/ /* Untested */
0, NULL,
LV_LITTLE_ENDIAN, -1, 0, {0},
-1, 0, 0, NULL, NULL, NULL
},
[VIR_STORAGE_FILE_DMG] = {
/* XXX QEMU says there's no magic for dmg,
* /usr/share/misc/magic lists double magic (both offsets
* would have to match) but then disables that check. */
0, NULL,
0, -1, 0, {0},
-1, 0, 0, NULL, NULL, NULL
},
[VIR_STORAGE_FILE_ISO] = {
32769, "CD001",
LV_LITTLE_ENDIAN, -2, 0, {0},
-1, 0, 0, NULL, NULL, NULL
},
[VIR_STORAGE_FILE_VPC] = {
0, "conectix",
LV_BIG_ENDIAN, 12, 4, {0x10000},
8 + 4 + 4 + 8 + 4 + 4 + 2 + 2 + 4, 8, 1, NULL, NULL, NULL
},
/* TODO: add getBackingStore function */
[VIR_STORAGE_FILE_VDI] = {
64, "\x7f\x10\xda\xbe",
LV_LITTLE_ENDIAN, 68, 4, {0x00010001},
64 + 5 * 4 + 256 + 7 * 4, 8, 1, NULL, NULL, NULL},
/* Not direct file formats, but used for various drivers */
[VIR_STORAGE_FILE_FAT] = { 0, NULL, LV_LITTLE_ENDIAN,
-1, 0, {0}, 0, 0, 0, NULL, NULL, NULL },
[VIR_STORAGE_FILE_VHD] = { 0, NULL, LV_LITTLE_ENDIAN,
-1, 0, {0}, 0, 0, 0, NULL, NULL, NULL },
[VIR_STORAGE_FILE_PLOOP] = { 0, "WithouFreSpacExt", LV_LITTLE_ENDIAN,
-2, 0, {0}, PLOOP_IMAGE_SIZE_OFFSET, 0,
PLOOP_SIZE_MULTIPLIER, NULL, NULL, NULL },
/* All formats with a backing store probe below here */
[VIR_STORAGE_FILE_COW] = {
0, "OOOM",
LV_BIG_ENDIAN, 4, 4, {2},
4+4+1024+4, 8, 1, NULL, cowGetBackingStore, NULL
},
[VIR_STORAGE_FILE_QCOW] = {
0, "QFI",
LV_BIG_ENDIAN, 4, 4, {1},
QCOWX_HDR_IMAGE_SIZE, 8, 1,
qcow1EncryptionInfo,
qcowXGetBackingStore, NULL
},
[VIR_STORAGE_FILE_QCOW2] = {
0, "QFI",
LV_BIG_ENDIAN, 4, 4, {2, 3},
QCOWX_HDR_IMAGE_SIZE, 8, 1,
qcow2EncryptionInfo,
qcowXGetBackingStore,
qcow2GetFeatures
},
[VIR_STORAGE_FILE_QED] = {
/* https://wiki.qemu.org/Features/QED */
0, "QED",
LV_LITTLE_ENDIAN, -2, 0, {0},
QED_HDR_IMAGE_SIZE, 8, 1, NULL, qedGetBackingStore, NULL
},
[VIR_STORAGE_FILE_VMDK] = {
0, "KDMV",
LV_LITTLE_ENDIAN, 4, 4, {1, 2, 3},
4+4+4, 8, 512, NULL, vmdk4GetBackingStore, NULL
},
};
G_STATIC_ASSERT(G_N_ELEMENTS(fileTypeInfo) == VIR_STORAGE_FILE_LAST);
/* qcow2 compatible features in the order they appear on-disk */
enum qcow2CompatibleFeature {
QCOW2_COMPATIBLE_FEATURE_LAZY_REFCOUNTS = 0,
QCOW2_COMPATIBLE_FEATURE_LAST
};
/* conversion to virStorageFileFeature */
static const int qcow2CompatibleFeatureArray[] = {
VIR_STORAGE_FILE_FEATURE_LAZY_REFCOUNTS,
};
G_STATIC_ASSERT(G_N_ELEMENTS(qcow2CompatibleFeatureArray) ==
QCOW2_COMPATIBLE_FEATURE_LAST);
static int
cowGetBackingStore(char **res,
int *format,
const char *buf,
size_t buf_size)
{
#define COW_FILENAME_MAXLEN 1024
*res = NULL;
*format = VIR_STORAGE_FILE_AUTO;
if (buf_size < 4+4+ COW_FILENAME_MAXLEN)
return BACKING_STORE_INVALID;
if (buf[4+4] == '\0') { /* cow_header_v2.backing_file[0] */
*format = VIR_STORAGE_FILE_NONE;
return BACKING_STORE_OK;
}
*res = g_strndup((const char *)buf + 4 + 4, COW_FILENAME_MAXLEN);
return BACKING_STORE_OK;
}
static int
qcow2GetExtensions(const char *buf,
size_t buf_size,
int *backingFormat,
char **externalDataStoreRaw)
{
size_t offset;
size_t extension_start;
size_t extension_end;
int version = virReadBufInt32BE(buf + QCOWX_HDR_VERSION);
if (version < 2) {
/* QCow1 doesn't have the extensions capability
* used to store backing format */
return 0;
}
if (version == 2)
extension_start = QCOW2_HDR_TOTAL_SIZE;
else
extension_start = virReadBufInt32BE(buf + QCOW2v3_HDR_SIZE);
/*
* Traditionally QCow2 files had a layout of
*
* [header]
* [backingStoreName]
*
* Although the backingStoreName typically followed
* the header immediately, this was not required by
* the format. By specifying a higher byte offset for
* the backing file offset in the header, it was
* possible to leave space between the header and
* start of backingStore.
*
* This hack is now used to store extensions to the
* qcow2 format:
*
* [header]
* [extensions]
* [backingStoreName]
*
* Thus the file region to search for extensions is
* between the end of the header (QCOW2_HDR_TOTAL_SIZE)
* and the start of the backingStoreName (offset)
*
* for qcow2 v3 images, the length of the header
* is stored at QCOW2v3_HDR_SIZE
*/
extension_end = virReadBufInt64BE(buf + QCOWX_HDR_BACKING_FILE_OFFSET);
if (extension_end > buf_size)
return -1;
/*
* The extensions take format of
*
* int32: magic
* int32: length
* byte[length]: payload
*
* Unknown extensions can be ignored by skipping
* over "length" bytes in the data stream.
*/
offset = extension_start;
while (offset < (buf_size-8) &&
offset < (extension_end-8)) {
unsigned int magic = virReadBufInt32BE(buf + offset);
unsigned int len = virReadBufInt32BE(buf + offset + 4);
offset += 8;
if ((offset + len) < offset)
break;
if ((offset + len) > buf_size)
break;
switch (magic) {
case QCOW2_HDR_EXTENSION_BACKING_FORMAT: {
g_autofree char *tmp = NULL;
if (!backingFormat)
break;
if (VIR_ALLOC_N(tmp, len + 1) < 0)
return -1;
memcpy(tmp, buf + offset, len);
tmp[len] = '\0';
*backingFormat = virStorageFileFormatTypeFromString(tmp);
if (*backingFormat <= VIR_STORAGE_FILE_NONE)
return -1;
break;
}
case QCOW2_HDR_EXTENSION_DATA_FILE: {
if (!externalDataStoreRaw)
break;
if (VIR_ALLOC_N(*externalDataStoreRaw, len + 1) < 0)
return -1;
memcpy(*externalDataStoreRaw, buf + offset, len);
(*externalDataStoreRaw)[len] = '\0';
VIR_DEBUG("parsed externalDataStoreRaw='%s'",
*externalDataStoreRaw);
break;
}
case QCOW2_HDR_EXTENSION_END:
return 0;
}
offset += len;
}
return 0;
}
static int
qcowXGetBackingStore(char **res,
int *format,
const char *buf,
size_t buf_size)
{
unsigned long long offset;
unsigned int size;
*res = NULL;
*format = VIR_STORAGE_FILE_AUTO;
if (buf_size < QCOWX_HDR_BACKING_FILE_OFFSET+8+4)
return BACKING_STORE_INVALID;
offset = virReadBufInt64BE(buf + QCOWX_HDR_BACKING_FILE_OFFSET);
if (offset > buf_size)
return BACKING_STORE_INVALID;
if (offset == 0) {
*format = VIR_STORAGE_FILE_NONE;
return BACKING_STORE_OK;
}
size = virReadBufInt32BE(buf + QCOWX_HDR_BACKING_FILE_SIZE);
if (size == 0) {
*format = VIR_STORAGE_FILE_NONE;
return BACKING_STORE_OK;
}
if (size > 1023)
return BACKING_STORE_INVALID;
if (offset + size > buf_size || offset + size < offset)
return BACKING_STORE_INVALID;
if (VIR_ALLOC_N(*res, size + 1) < 0)
return BACKING_STORE_ERROR;
memcpy(*res, buf + offset, size);
(*res)[size] = '\0';
if (qcow2GetExtensions(buf, buf_size, format, NULL) < 0)
return BACKING_STORE_INVALID;
return BACKING_STORE_OK;
}
static int
vmdk4GetBackingStore(char **res,
int *format,
const char *buf,
size_t buf_size)
{
static const char prefix[] = "parentFileNameHint=\"";
char *start, *end;
size_t len;
g_autofree char *desc = NULL;
if (VIR_ALLOC_N(desc, VIR_STORAGE_MAX_HEADER) < 0)
return BACKING_STORE_ERROR;
*res = NULL;
/*
* Technically this should have been VMDK, since
* VMDK spec / VMware impl only support VMDK backed
* by VMDK. QEMU isn't following this though and
* does probing on VMDK backing files, hence we set
* AUTO
*/
*format = VIR_STORAGE_FILE_AUTO;
if (buf_size <= 0x200)
return BACKING_STORE_INVALID;
len = buf_size - 0x200;
if (len > VIR_STORAGE_MAX_HEADER)
len = VIR_STORAGE_MAX_HEADER;
memcpy(desc, buf + 0x200, len);
desc[len] = '\0';
start = strstr(desc, prefix);
if (start == NULL) {
*format = VIR_STORAGE_FILE_NONE;
return BACKING_STORE_OK;
}
start += strlen(prefix);
end = strchr(start, '"');
if (end == NULL)
return BACKING_STORE_INVALID;
if (end == start) {
*format = VIR_STORAGE_FILE_NONE;
return BACKING_STORE_OK;
}
*end = '\0';
*res = g_strdup(start);
return BACKING_STORE_OK;
}
static int
qedGetBackingStore(char **res,
int *format,
const char *buf,
size_t buf_size)
{
unsigned long long flags;
unsigned long offset, size;
*res = NULL;
/* Check if this image has a backing file */
if (buf_size < QED_HDR_FEATURES_OFFSET+8)
return BACKING_STORE_INVALID;
flags = virReadBufInt64LE(buf + QED_HDR_FEATURES_OFFSET);
if (!(flags & QED_F_BACKING_FILE)) {
*format = VIR_STORAGE_FILE_NONE;
return BACKING_STORE_OK;
}
/* Parse the backing file */
if (buf_size < QED_HDR_BACKING_FILE_OFFSET+8)
return BACKING_STORE_INVALID;
offset = virReadBufInt32LE(buf + QED_HDR_BACKING_FILE_OFFSET);
if (offset > buf_size)
return BACKING_STORE_INVALID;
size = virReadBufInt32LE(buf + QED_HDR_BACKING_FILE_SIZE);
if (size == 0)
return BACKING_STORE_OK;
if (offset + size > buf_size || offset + size < offset)
return BACKING_STORE_INVALID;
if (VIR_ALLOC_N(*res, size + 1) < 0)
return BACKING_STORE_ERROR;
memcpy(*res, buf + offset, size);
(*res)[size] = '\0';
if (flags & QED_F_BACKING_FORMAT_NO_PROBE)
*format = VIR_STORAGE_FILE_RAW;
else
*format = VIR_STORAGE_FILE_AUTO_SAFE;
return BACKING_STORE_OK;
}
static bool
virStorageFileMatchesMagic(int magicOffset,
const char *magic,
char *buf,
size_t buflen)
{
int mlen;
if (magic == NULL)
return false;
/* Validate magic data */
mlen = strlen(magic);
if (magicOffset + mlen > buflen)
return false;
if (memcmp(buf + magicOffset, magic, mlen) != 0)
return false;
return true;
}
static bool
virStorageFileMatchesVersion(int versionOffset,
int versionSize,
const int *versionNumbers,
int endian,
char *buf,
size_t buflen)
{
int version;
size_t i;
/* Validate version number info */
if (versionOffset == -1)
return false;
/* -2 == non-versioned file format, so trivially match */
if (versionOffset == -2)
return true;
/* A positive versionOffset, requires using a valid versionSize */
if (versionSize != 2 && versionSize != 4)
return false;
if ((versionOffset + versionSize) > buflen)
return false;
if (endian == LV_LITTLE_ENDIAN) {
if (versionSize == 4)
version = virReadBufInt32LE(buf +
versionOffset);
else
version = virReadBufInt16LE(buf +
versionOffset);
} else {
if (versionSize == 4)
version = virReadBufInt32BE(buf +
versionOffset);
else
version = virReadBufInt16BE(buf +
versionOffset);
}
for (i = 0;
i < FILE_TYPE_VERSIONS_LAST && versionNumbers[i];
i++) {
VIR_DEBUG("Compare detected version %d vs one of the expected versions %d",
version, versionNumbers[i]);
if (version == versionNumbers[i])
return true;
}
return false;
}
bool
virStorageIsFile(const char *backing)
{
char *colon;
char *slash;
if (!backing)
return false;
colon = strchr(backing, ':');
slash = strchr(backing, '/');
/* Reject anything that looks like a protocol (such as nbd: or
* rbd:); if someone really does want a relative file name that
* includes ':', they can always prefix './'. */
if (colon && (!slash || colon < slash))
return false;
return true;
}
bool
virStorageIsRelative(const char *backing)
{
if (backing[0] == '/')
return false;
if (!virStorageIsFile(backing))
return false;
return true;
}
static int
virStorageFileProbeFormatFromBuf(const char *path,
char *buf,
size_t buflen)
{
int format = VIR_STORAGE_FILE_RAW;
size_t i;
int possibleFormat = VIR_STORAGE_FILE_RAW;
VIR_DEBUG("path=%s, buf=%p, buflen=%zu", path, buf, buflen);
/* First check file magic */
for (i = 0; i < VIR_STORAGE_FILE_LAST; i++) {
if (virStorageFileMatchesMagic(fileTypeInfo[i].magicOffset,
fileTypeInfo[i].magic,
buf, buflen)) {
if (!virStorageFileMatchesVersion(fileTypeInfo[i].versionOffset,
fileTypeInfo[i].versionSize,
fileTypeInfo[i].versionNumbers,
fileTypeInfo[i].endian,
buf, buflen)) {
possibleFormat = i;
continue;
}
format = i;
goto cleanup;
}
}
if (possibleFormat != VIR_STORAGE_FILE_RAW)
VIR_WARN("File %s matches %s magic, but version is wrong. "
"Please report new version to libvir-list@redhat.com",
path, virStorageFileFormatTypeToString(possibleFormat));
cleanup:
VIR_DEBUG("format=%d", format);
return format;
}
static int
qcow2GetFeatures(virBitmapPtr *features,
int format,
char *buf,
ssize_t len)
{
int version = -1;
virBitmapPtr feat = NULL;
uint64_t bits;
size_t i;
version = virReadBufInt32BE(buf + fileTypeInfo[format].versionOffset);
if (version == 2)
return 0;
if (len < QCOW2v3_HDR_SIZE)
return -1;
if (!(feat = virBitmapNew(VIR_STORAGE_FILE_FEATURE_LAST)))
return -1;
/* todo: check for incompatible or autoclear features? */
bits = virReadBufInt64BE(buf + QCOW2v3_HDR_FEATURES_COMPATIBLE);
for (i = 0; i < QCOW2_COMPATIBLE_FEATURE_LAST; i++) {
if (bits & ((uint64_t) 1 << i))
ignore_value(virBitmapSetBit(feat, qcow2CompatibleFeatureArray[i]));
}
*features = feat;
return 0;
}
static bool
virStorageFileHasEncryptionFormat(const struct FileEncryptionInfo *info,
char *buf,
size_t len)
{
if (!info->magic && info->modeOffset == -1)
return false; /* Shouldn't happen - expect at least one */
if (info->magic) {
if (!virStorageFileMatchesMagic(info->magicOffset,
info->magic,
buf, len))
return false;
if (info->versionOffset != -1 &&
!virStorageFileMatchesVersion(info->versionOffset,
info->versionSize,
info->versionNumbers,
info->endian,
buf, len))
return false;
return true;
} else if (info->modeOffset != -1) {
int crypt_format;
if (info->modeOffset >= len)
return false;
crypt_format = virReadBufInt32BE(buf + info->modeOffset);
if (crypt_format != info->modeValue)
return false;
return true;
} else {
return false;
}
}
static int
virStorageFileGetEncryptionPayloadOffset(const struct FileEncryptionInfo *info,
char *buf)
{
int payload_offset = -1;
if (info->payloadOffset != -1) {
if (info->endian == LV_LITTLE_ENDIAN)
payload_offset = virReadBufInt32LE(buf + info->payloadOffset);
else
payload_offset = virReadBufInt32BE(buf + info->payloadOffset);
}
return payload_offset;
}
/* Given a header in BUF with length LEN, as parsed from the storage file
* assuming it has the given FORMAT, populate information into META
* with information about the file and its backing store. Return format
* of the backing store as BACKING_FORMAT. PATH and FORMAT have to be
* pre-populated in META.
*
* Note that this function may be called repeatedly on @meta, so it must
* clean up any existing allocated memory which would be overwritten.
*/
static int
virStorageFileGetMetadataInternal(virStorageSourcePtr meta,
char *buf,
size_t len)
{
int format;
size_t i;
VIR_DEBUG("path=%s, buf=%p, len=%zu, meta->format=%d",
meta->path, buf, len, meta->format);
if (meta->format == VIR_STORAGE_FILE_AUTO)
meta->format = virStorageFileProbeFormatFromBuf(meta->path, buf, len);
if (meta->format <= VIR_STORAGE_FILE_NONE ||
meta->format >= VIR_STORAGE_FILE_LAST) {
virReportSystemError(EINVAL, _("unknown storage file meta->format %d"),
meta->format);
return -1;
}
if (fileTypeInfo[meta->format].cryptInfo != NULL) {
for (i = 0; fileTypeInfo[meta->format].cryptInfo[i].format != 0; i++) {
if (virStorageFileHasEncryptionFormat(&fileTypeInfo[meta->format].cryptInfo[i],
buf, len)) {
int expt_fmt = fileTypeInfo[meta->format].cryptInfo[i].format;
if (!meta->encryption) {
if (VIR_ALLOC(meta->encryption) < 0)
return -1;
meta->encryption->format = expt_fmt;
} else {
if (meta->encryption->format != expt_fmt) {
virReportError(VIR_ERR_XML_ERROR,
_("encryption format %d doesn't match "
"expected format %d"),
meta->encryption->format, expt_fmt);
return -1;
}
}
meta->encryption->payload_offset =
virStorageFileGetEncryptionPayloadOffset(&fileTypeInfo[meta->format].cryptInfo[i], buf);
}
}
}
/* XXX we should consider moving virStorageBackendUpdateVolInfo
* code into this method, for non-magic files
*/
if (!fileTypeInfo[meta->format].magic)
return 0;
/* Optionally extract capacity from file */
if (fileTypeInfo[meta->format].sizeOffset != -1) {
if ((fileTypeInfo[meta->format].sizeOffset + 8) > len)
return 0;
if (fileTypeInfo[meta->format].endian == LV_LITTLE_ENDIAN)
meta->capacity = virReadBufInt64LE(buf +
fileTypeInfo[meta->format].sizeOffset);
else
meta->capacity = virReadBufInt64BE(buf +
fileTypeInfo[meta->format].sizeOffset);
/* Avoid unlikely, but theoretically possible overflow */
if (meta->capacity > (ULLONG_MAX /
fileTypeInfo[meta->format].sizeMultiplier))
return 0;
meta->capacity *= fileTypeInfo[meta->format].sizeMultiplier;
}
VIR_FREE(meta->backingStoreRaw);
if (fileTypeInfo[meta->format].getBackingStore != NULL) {
int store = fileTypeInfo[meta->format].getBackingStore(&meta->backingStoreRaw,
&format,
buf, len);
meta->backingStoreRawFormat = format;
if (store == BACKING_STORE_INVALID)
return 0;
if (store == BACKING_STORE_ERROR)
return -1;
}
virBitmapFree(meta->features);
meta->features = NULL;
if (fileTypeInfo[meta->format].getFeatures != NULL &&
fileTypeInfo[meta->format].getFeatures(&meta->features, meta->format, buf, len) < 0)
return -1;
VIR_FREE(meta->externalDataStoreRaw);
if (meta->format == VIR_STORAGE_FILE_QCOW2 &&
qcow2GetExtensions(buf, len, NULL, &meta->externalDataStoreRaw) < 0) {
return -1;
}
VIR_FREE(meta->compat);
if (meta->format == VIR_STORAGE_FILE_QCOW2 && meta->features)
meta->compat = g_strdup("1.1");
return 0;
}
/**
* virStorageFileProbeFormat:
*
* Probe for the format of 'path', returning the detected
* disk format.
*
* Callers are advised never to trust the returned 'format'
* unless it is listed as VIR_STORAGE_FILE_RAW, since a
* malicious guest can turn a raw file into any other non-raw
* format at will.
*
* Best option: Don't use this function
*/
int
virStorageFileProbeFormat(const char *path, uid_t uid, gid_t gid)
{
struct stat sb;
ssize_t len = VIR_STORAGE_MAX_HEADER;
VIR_AUTOCLOSE fd = -1;
g_autofree char *header = NULL;
if ((fd = virFileOpenAs(path, O_RDONLY, 0, uid, gid, 0)) < 0) {
virReportSystemError(-fd, _("Failed to open file '%s'"), path);
return -1;
}
if (fstat(fd, &sb) < 0) {
virReportSystemError(errno, _("cannot stat file '%s'"), path);
return -1;
}
/* No header to probe for directories */
if (S_ISDIR(sb.st_mode))
return VIR_STORAGE_FILE_DIR;
if (lseek(fd, 0, SEEK_SET) == (off_t)-1) {
virReportSystemError(errno, _("cannot set to start of '%s'"), path);
return -1;
}
if ((len = virFileReadHeaderFD(fd, len, &header)) < 0) {
virReportSystemError(errno, _("cannot read header '%s'"), path);
return -1;
}
return virStorageFileProbeFormatFromBuf(path, header, len);
}
static virStorageSourcePtr
virStorageFileMetadataNew(const char *path,
int format)
{
g_autoptr(virStorageSource) def = NULL;
if (!(def = virStorageSourceNew()))
return NULL;
def->format = format;
def->type = VIR_STORAGE_TYPE_FILE;
def->path = g_strdup(path);
return g_steal_pointer(&def);
}
/**
* virStorageFileGetMetadataFromBuf:
* @path: name of file, for error messages
* @buf: header bytes from @path
* @len: length of @buf
* @format: format of the storage file
*
* Extract metadata about the storage volume with the specified image format.
* If image format is VIR_STORAGE_FILE_AUTO, it will probe to automatically
* identify the format. Does not recurse.
*
* Callers are advised never to use VIR_STORAGE_FILE_AUTO as a format on a file
* that might be raw if that file will then be passed to a guest, since a
* malicious guest can turn a raw file into any other non-raw format at will.
*
* If the 'backingStoreRawFormat' field of the returned structure is
* VIR_STORAGE_FILE_AUTO it indicates the image didn't specify an explicit
* format for its backing store. Callers are advised against probing for the
* backing store format in this case.
*
* Caller MUST free the result after use via virObjectUnref.
*/
virStorageSourcePtr
virStorageFileGetMetadataFromBuf(const char *path,
char *buf,
size_t len,
int format)
{
virStorageSourcePtr ret = NULL;
if (!(ret = virStorageFileMetadataNew(path, format)))
return NULL;
if (virStorageFileGetMetadataInternal(ret, buf, len) < 0) {
virObjectUnref(ret);
return NULL;
}
return ret;
}
/**
* virStorageFileGetMetadataFromFD:
*
* Extract metadata about the storage volume with the specified
* image format. If image format is VIR_STORAGE_FILE_AUTO, it
* will probe to automatically identify the format. Does not recurse.
*
* Callers are advised never to use VIR_STORAGE_FILE_AUTO as a
* format, since a malicious guest can turn a raw file into any
* other non-raw format at will.
*
* Caller MUST free the result after use via virObjectUnref.
*/
virStorageSourcePtr
virStorageFileGetMetadataFromFD(const char *path,
int fd,
int format)
{
ssize_t len = VIR_STORAGE_MAX_HEADER;
struct stat sb;
g_autofree char *buf = NULL;
g_autoptr(virStorageSource) meta = NULL;
if (fstat(fd, &sb) < 0) {
virReportSystemError(errno,
_("cannot stat file '%s'"), path);
return NULL;
}
if (!(meta = virStorageFileMetadataNew(path, format)))
return NULL;
if (S_ISDIR(sb.st_mode)) {
/* No header to probe for directories, but also no backing file. Just
* update the metadata.*/
meta->type = VIR_STORAGE_TYPE_DIR;
meta->format = VIR_STORAGE_FILE_DIR;
return g_steal_pointer(&meta);
}
if (lseek(fd, 0, SEEK_SET) == (off_t)-1) {
virReportSystemError(errno, _("cannot seek to start of '%s'"), meta->path);
return NULL;
}
if ((len = virFileReadHeaderFD(fd, len, &buf)) < 0) {
virReportSystemError(errno, _("cannot read header '%s'"), meta->path);
return NULL;
}
if (virStorageFileGetMetadataInternal(meta, buf, len) < 0)
return NULL;
if (S_ISREG(sb.st_mode))
meta->type = VIR_STORAGE_TYPE_FILE;
else if (S_ISBLK(sb.st_mode))
meta->type = VIR_STORAGE_TYPE_BLOCK;
return g_steal_pointer(&meta);
}
/**
* virStorageFileChainCheckBroken
*
* If CHAIN is broken, set *brokenFile to the broken file name,
* otherwise set it to NULL. Caller MUST free *brokenFile after use.
* Return 0 on success (including when brokenFile is set), negative on
* error (allocation failure).
*/
int
virStorageFileChainGetBroken(virStorageSourcePtr chain,
char **brokenFile)
{
virStorageSourcePtr tmp;
*brokenFile = NULL;
if (!chain)
return 0;
for (tmp = chain; virStorageSourceIsBacking(tmp); tmp = tmp->backingStore) {
/* Break when we hit end of chain; report error if we detected
* a missing backing file, infinite loop, or other error */
if (!tmp->backingStore && tmp->backingStoreRaw) {
*brokenFile = g_strdup(tmp->backingStoreRaw);
return 0;
}
}
return 0;
}
/**
* virStorageFileResize:
*
* Change the capacity of the raw storage file at 'path'.
*/
int
virStorageFileResize(const char *path,
unsigned long long capacity,
bool pre_allocate)
{
int rc;
VIR_AUTOCLOSE fd = -1;
if ((fd = open(path, O_RDWR)) < 0) {
virReportSystemError(errno, _("Unable to open '%s'"), path);
return -1;
}
if (pre_allocate) {
if ((rc = virFileAllocate(fd, 0, capacity)) != 0) {
if (rc == -2) {
virReportError(VIR_ERR_OPERATION_UNSUPPORTED, "%s",
_("preallocate is not supported on this platform"));
} else {
virReportSystemError(errno,
_("Failed to pre-allocate space for "
"file '%s'"), path);
}
return -1;
}
}
if (ftruncate(fd, capacity) < 0) {
virReportSystemError(errno,
_("Failed to truncate file '%s'"), path);
return -1;
}
if (VIR_CLOSE(fd) < 0) {
virReportSystemError(errno, _("Unable to save '%s'"), path);
return -1;
}
return 0;
}
int virStorageFileIsClusterFS(const char *path)
{
/* These are coherent cluster filesystems known to be safe for
* migration with cache != none
*/
return virFileIsSharedFSType(path,
VIR_FILE_SHFS_GFS2 |
VIR_FILE_SHFS_OCFS |
VIR_FILE_SHFS_CEPH);
}
#ifdef LVS
int virStorageFileGetLVMKey(const char *path,
char **key)
{
/*
* # lvs --noheadings --unbuffered --nosuffix --options "uuid" LVNAME
* 06UgP5-2rhb-w3Bo-3mdR-WeoL-pytO-SAa2ky
*/
int status;
int ret = -1;
g_autoptr(virCommand) cmd = NULL;
cmd = virCommandNewArgList(LVS, "--noheadings",
"--unbuffered", "--nosuffix",
"--options", "uuid", path,
NULL
);
*key = NULL;
/* Run the program and capture its output */
virCommandSetOutputBuffer(cmd, key);
if (virCommandRun(cmd, &status) < 0)
goto cleanup;
/* Explicitly check status == 0, rather than passing NULL
* to virCommandRun because we don't want to raise an actual
* error in this scenario, just return a NULL key.
*/
if (status == 0 && *key) {
char *nl;
char *tmp = *key;
/* Find first non-space character */
while (*tmp && g_ascii_isspace(*tmp))
tmp++;
/* Kill leading spaces */
if (tmp != *key)
memmove(*key, tmp, strlen(tmp)+1);
/* Kill trailing newline */
if ((nl = strchr(*key, '\n')))
*nl = '\0';
}
ret = 0;
cleanup:
if (*key && STREQ(*key, ""))
VIR_FREE(*key);
return ret;
}
#else
int virStorageFileGetLVMKey(const char *path,
char **key G_GNUC_UNUSED)
{
virReportSystemError(ENOSYS, _("Unable to get LVM key for %s"), path);
return -1;
}
#endif
#ifdef WITH_UDEV
/* virStorageFileGetSCSIKey
* @path: Path to the SCSI device
* @key: Unique key to be returned
* @ignoreError: Used to not report ENOSYS
*
* Using a udev specific function, query the @path to get and return a
* unique @key for the caller to use.
*
* Returns:
* 0 On success, with the @key filled in or @key=NULL if the
* returned string was empty.
* -1 When WITH_UDEV is undefined and a system error is reported
* -2 When WITH_UDEV is defined, but calling virCommandRun fails
*/
int
virStorageFileGetSCSIKey(const char *path,
char **key,
bool ignoreError G_GNUC_UNUSED)
{
int status;
g_autoptr(virCommand) cmd = NULL;
cmd = virCommandNewArgList("/lib/udev/scsi_id",
"--replace-whitespace",
"--whitelisted",
"--device", path,
NULL
);
*key = NULL;
/* Run the program and capture its output */
virCommandSetOutputBuffer(cmd, key);
if (virCommandRun(cmd, &status) < 0)
return -2;
/* Explicitly check status == 0, rather than passing NULL
* to virCommandRun because we don't want to raise an actual
* error in this scenario, just return a NULL key.
*/
if (status == 0 && *key) {
char *nl = strchr(*key, '\n');
if (nl)
*nl = '\0';
}
if (*key && STREQ(*key, ""))
VIR_FREE(*key);
return 0;
}
#else
int virStorageFileGetSCSIKey(const char *path,
char **key G_GNUC_UNUSED,
bool ignoreError)
{
if (!ignoreError)
virReportSystemError(ENOSYS, _("Unable to get SCSI key for %s"), path);
return -1;
}
#endif
#ifdef WITH_UDEV
/* virStorageFileGetNPIVKey
* @path: Path to the NPIV device
* @key: Unique key to be returned
*
* Using a udev specific function, query the @path to get and return a
* unique @key for the caller to use. Unlike the GetSCSIKey method, an
* NPIV LUN is uniquely identified by its ID_TARGET_PORT value.
*
* Returns:
* 0 On success, with the @key filled in or @key=NULL if the
* returned output string didn't have the data we need to
* formulate a unique key value
* -1 When WITH_UDEV is undefined and a system error is reported
* -2 When WITH_UDEV is defined, but calling virCommandRun fails
*/
# define ID_SERIAL "ID_SERIAL="
# define ID_TARGET_PORT "ID_TARGET_PORT="
int
virStorageFileGetNPIVKey(const char *path,
char **key)
{
int status;
const char *serial;
const char *port;
g_autofree char *outbuf = NULL;
g_autoptr(virCommand) cmd = NULL;
cmd = virCommandNewArgList("/lib/udev/scsi_id",
"--replace-whitespace",
"--whitelisted",
"--export",
"--device", path,
NULL
);
*key = NULL;
/* Run the program and capture its output */
virCommandSetOutputBuffer(cmd, &outbuf);
if (virCommandRun(cmd, &status) < 0)
return -2;
/* Explicitly check status == 0, rather than passing NULL
* to virCommandRun because we don't want to raise an actual
* error in this scenario, just return a NULL key.
*/
if (status == 0 && *outbuf &&
(serial = strstr(outbuf, ID_SERIAL)) &&
(port = strstr(outbuf, ID_TARGET_PORT))) {
char *tmp;
serial += strlen(ID_SERIAL);
port += strlen(ID_TARGET_PORT);
if ((tmp = strchr(serial, '\n')))
*tmp = '\0';
if ((tmp = strchr(port, '\n')))
*tmp = '\0';
if (*serial != '\0' && *port != '\0')
*key = g_strdup_printf("%s_PORT%s", serial, port);
}
return 0;
}
#else
int virStorageFileGetNPIVKey(const char *path G_GNUC_UNUSED,
char **key G_GNUC_UNUSED)
{
return -1;
}
#endif
/**
* virStorageFileParseBackingStoreStr:
* @str: backing store specifier string to parse
* @target: returns target device portion of the string
* @chainIndex: returns the backing store portion of the string
*
* Parses the backing store specifier string such as vda[1], or sda into
* components and returns them via arguments. If the string did not specify an
* index, 0 is assumed.
*
* Returns 0 on success -1 on error
*/
int
virStorageFileParseBackingStoreStr(const char *str,
char **target,
unsigned int *chainIndex)
{
size_t nstrings;
unsigned int idx = 0;
char *suffix;
VIR_AUTOSTRINGLIST strings = NULL;
*chainIndex = 0;
if (!(strings = virStringSplitCount(str, "[", 2, &nstrings)))
return -1;
if (nstrings == 2) {
if (virStrToLong_uip(strings[1], &suffix, 10, &idx) < 0 ||
STRNEQ(suffix, "]"))
return -1;
}
if (target)
*target = g_strdup(strings[0]);
*chainIndex = idx;
return 0;
}
int
virStorageFileParseChainIndex(const char *diskTarget,
const char *name,
unsigned int *chainIndex)
{
unsigned int idx = 0;
g_autofree char *target = NULL;
*chainIndex = 0;
if (!name || !diskTarget)
return 0;
if (virStorageFileParseBackingStoreStr(name, &target, &idx) < 0)
return 0;
if (idx == 0)
return 0;
if (STRNEQ(diskTarget, target)) {
virReportError(VIR_ERR_INVALID_ARG,
_("requested target '%s' does not match target '%s'"),
target, diskTarget);
return -1;
}
*chainIndex = idx;
return 0;
}
/**
* 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;
}
/* Given a @chain, look for the backing store @name that is a backing file
* of @startFrom (or any member of @chain if @startFrom is NULL) and return
* that location within the chain. @chain must always point to the top of
* the chain. Pass NULL for @name and 0 for @idx to find the base of the
* chain. Pass nonzero @idx to find the backing source according to its
* position in the backing chain. If @parent is not NULL, set *@parent to
* the preferred name of the parent (or to NULL if @name matches the start
* of the chain). Since the results point within @chain, they must not be
* independently freed. Reports an error and returns NULL if @name is not
* found.
*/
virStorageSourcePtr
virStorageFileChainLookup(virStorageSourcePtr chain,
virStorageSourcePtr startFrom,
const char *name,
unsigned int idx,
virStorageSourcePtr *parent)
{
virStorageSourcePtr prev;
const char *start = chain->path;
char *parentDir = NULL;
bool nameIsFile = virStorageIsFile(name);
if (!parent)
parent = &prev;
*parent = NULL;
if (startFrom) {
while (virStorageSourceIsBacking(chain) &&
chain != startFrom->backingStore)
chain = chain->backingStore;
*parent = startFrom;
}
while (virStorageSourceIsBacking(chain)) {
if (!name && !idx) {
if (!virStorageSourceHasBacking(chain))
break;
} else if (idx) {
VIR_DEBUG("%u: %s", chain->id, chain->path);
if (idx == chain->id)
break;
} else {
if (STREQ_NULLABLE(name, chain->relPath) ||
STREQ_NULLABLE(name, chain->path))
break;
if (nameIsFile && virStorageSourceIsLocalStorage(chain)) {
if (*parent && virStorageSourceIsLocalStorage(*parent))
parentDir = g_path_get_dirname((*parent)->path);
else
parentDir = g_strdup(".");
int result = virFileRelLinkPointsTo(parentDir, name,
chain->path);
VIR_FREE(parentDir);
if (result < 0)
goto error;
if (result > 0)
break;
}
}
*parent = chain;
chain = chain->backingStore;
}
if (!virStorageSourceIsBacking(chain))
goto error;
return chain;
error:
if (idx) {
virReportError(VIR_ERR_INVALID_ARG,
_("could not find backing store index %u in chain "
"for '%s'"),
idx, NULLSTR(start));
} else if (name) {
if (startFrom)
virReportError(VIR_ERR_INVALID_ARG,
_("could not find image '%s' beneath '%s' in "
"chain for '%s'"), name, NULLSTR(startFrom->path),
NULLSTR(start));
else
virReportError(VIR_ERR_INVALID_ARG,
_("could not find image '%s' in chain for '%s'"),
name, NULLSTR(start));
} else {
virReportError(VIR_ERR_INVALID_ARG,
_("could not find base image in chain for '%s'"),
NULLSTR(start));
}
*parent = NULL;
return NULL;
}
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]);
VIR_FREE(hosts);
}
static void
virStoragePermsFree(virStoragePermsPtr def)
{
if (!def)
return;
VIR_FREE(def->label);
VIR_FREE(def);
}
virStorageNetHostDefPtr
virStorageNetHostDefCopy(size_t nhosts,
virStorageNetHostDefPtr hosts)
{
virStorageNetHostDefPtr ret = NULL;
size_t i;
if (VIR_ALLOC_N(ret, nhosts) < 0)
goto error;
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;
error:
virStorageNetHostDefFree(nhosts, ret);
return NULL;
}
void
virStorageAuthDefFree(virStorageAuthDefPtr authdef)
{
if (!authdef)
return;
VIR_FREE(authdef->username);
VIR_FREE(authdef->secrettype);
virSecretLookupDefClear(&authdef->seclookupdef);
VIR_FREE(authdef);
}
virStorageAuthDefPtr
virStorageAuthDefCopy(const virStorageAuthDef *src)
{
g_autoptr(virStorageAuthDef) authdef = NULL;
if (VIR_ALLOC(authdef) < 0)
return NULL;
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)
{
xmlNodePtr saveNode = ctxt->node;
virStorageAuthDefPtr ret = NULL;
xmlNodePtr secretnode = NULL;
g_autoptr(virStorageAuthDef) authdef = NULL;
g_autofree char *authtype = NULL;
ctxt->node = node;
if (VIR_ALLOC(authdef) < 0)
goto cleanup;
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 element in auth"));
goto cleanup;
}
/* Used by the domain disk xml parsing in order to ensure the
* secrettype = virXMLPropString(secretnode, "type");
if (virSecretLookupParseSecret(secretnode, &authdef->seclookupdef) < 0)
goto cleanup;
ret = g_steal_pointer(&authdef);
cleanup:
ctxt->node = saveNode;
return ret;
}
void
virStorageAuthDefFormat(virBufferPtr buf,
virStorageAuthDefPtr authdef)
{
if (authdef->authType == VIR_STORAGE_AUTH_TYPE_NONE) {
virBufferEscapeString(buf, "\n", authdef->username);
} else {
virBufferAsprintf(buf, "authType));
virBufferEscapeString(buf, "username='%s'>\n", authdef->username);
}
virBufferAdjustIndent(buf, 2);
virSecretLookupFormatSecret(buf, authdef->secrettype,
&authdef->seclookupdef);
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
}
void
virStoragePRDefFree(virStoragePRDefPtr prd)
{
if (!prd)
return;
VIR_FREE(prd->path);
VIR_FREE(prd->mgralias);
VIR_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;
if (VIR_ALLOC(prd) < 0)
return NULL;
if (!(managed = virXPathString("string(./@managed)", ctxt))) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("missing @managed attribute for "));
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 "));
goto cleanup;
}
if (!path) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("missing path for "));
goto cleanup;
}
if (!mode) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("missing connection mode for "));
goto cleanup;
}
}
if (type && STRNEQ(type, "unix")) {
virReportError(VIR_ERR_XML_ERROR,
_("unsupported connection type for : %s"),
type);
goto cleanup;
}
if (mode && STRNEQ(mode, "client")) {
virReportError(VIR_ERR_XML_ERROR,
_("unsupported connection mode for : %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, "managed));
if (prd->path &&
(prd->managed == VIR_TRISTATE_BOOL_NO || !migratable)) {
virBufferAddLit(buf, ">\n");
virBufferAdjustIndent(buf, 2);
virBufferAddLit(buf, "\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;
if (VIR_ALLOC(copy) < 0)
return NULL;
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->namespace = src->namespace;
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->namespace != b->namespace ||
a->managed != b->managed ||
!virPCIDeviceAddressEqual(&a->pciAddr, &b->pciAddr))
return false;
return true;
}
void
virStorageSourceNVMeDefFree(virStorageSourceNVMeDefPtr def)
{
if (!def)
return;
VIR_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;
if (VIR_ALLOC_N(to->seclabels, from->nseclabels) < 0)
return -1;
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)
{
/* 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 invalid characters in value */
if (virStringHasChars(def->value, 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;
if (VIR_ALLOC(ret) < 0)
return NULL;
memcpy(ret, src, sizeof(*src));
return ret;
}
static virStoragePermsPtr
virStoragePermsCopy(const virStoragePerms *src)
{
virStoragePermsPtr ret;
if (VIR_ALLOC(ret) < 0)
return NULL;
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;
if (VIR_ALLOC(ret) < 0)
return NULL;
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 = NULL;
if (!(def = virStorageSourceNew()))
return NULL;
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;
/* 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->externalDataStoreRaw = g_strdup(src->externalDataStoreRaw);
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);
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)))
return NULL;
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;
}
if (src->externalDataStore) {
if (!(def->externalDataStore = virStorageSourceCopy(src->externalDataStore,
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);
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;
VIR_FREE(def->pool);
VIR_FREE(def->volume);
VIR_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_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);
virStorageSourceNetCookiesClear(def);
virStorageSourcePoolDefFree(def->srcpool);
virBitmapFree(def->features);
VIR_FREE(def->compat);
virStorageEncryptionFree(def->encryption);
virStoragePRDefFree(def->pr);
virStorageSourceNVMeDefFree(def->nvme);
virStorageSourceSeclabelsClear(def);
virStoragePermsFree(def->perms);
VIR_FREE(def->timestamps);
VIR_FREE(def->externalDataStoreRaw);
virStorageSourceSliceFree(def->sliceStorage);
virObjectUnref(def->externalDataStore);
def->externalDataStore = NULL;
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);
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)
{
if (virStorageSourceInitialize() < 0)
return NULL;
return virObjectNew(virStorageSourceClass);
}
static virStorageSourcePtr
virStorageSourceNewFromBackingRelative(virStorageSourcePtr parent,
const char *rel)
{
g_autofree char *dirname = NULL;
g_autoptr(virStorageSource) def = NULL;
if (!(def = virStorageSourceNew()))
return NULL;
/* store relative name */
def->relPath = g_strdup(rel);
dirname = g_path_get_dirname(parent->path);
if (STRNEQ(dirname, "/")) {
def->path = g_strdup_printf("%s/%s", dirname, rel);
} else {
def->path = g_strdup_printf("/%s", rel);
}
if (virStorageSourceGetActualType(parent) == VIR_STORAGE_TYPE_NETWORK) {
def->type = VIR_STORAGE_TYPE_NETWORK;
/* copy the host network part */
def->protocol = parent->protocol;
if (parent->nhosts) {
if (!(def->hosts = virStorageNetHostDefCopy(parent->nhosts,
parent->hosts)))
return NULL;
def->nhosts = parent->nhosts;
}
def->volume = g_strdup(parent->volume);
} else {
/* set the type to _FILE, the caller shall update it to the actual type */
def->type = VIR_STORAGE_TYPE_FILE;
}
return g_steal_pointer(&def);
}
static int
virStorageSourceParseBackingURI(virStorageSourcePtr src,
const char *uristr)
{
g_autoptr(virURI) uri = NULL;
const char *path = NULL;
VIR_AUTOSTRINGLIST scheme = NULL;
if (!(uri = virURIParse(uristr))) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("failed to parse backing file location '%s'"),
uristr);
return -1;
}
if (VIR_ALLOC(src->hosts) < 0)
return -1;
src->nhosts = 1;
if (!(scheme = virStringSplit(uri->scheme, "+", 2)))
return -1;
if (!scheme[0] ||
(src->protocol = virStorageNetProtocolTypeFromString(scheme[0])) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("invalid backing protocol '%s'"),
NULLSTR(scheme[0]));
return -1;
}
if (scheme[1] &&
(src->hosts->transport = virStorageNetHostTransportTypeFromString(scheme[1])) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("invalid protocol transport type '%s'"),
scheme[1]);
return -1;
}
/* handle socket stored as a query */
if (uri->query)
src->hosts->socket = g_strdup(STRSKIP(uri->query, "socket="));
/* uri->path is NULL if the URI does not contain slash after host:
* transport://host:port */
if (uri->path)
path = uri->path;
else
path = "";
/* possibly skip the leading slash */
if (path[0] == '/')
path++;
/* NBD allows empty export name (path) */
if (src->protocol == VIR_STORAGE_NET_PROTOCOL_NBD &&
path[0] == '\0')
path = NULL;
src->path = g_strdup(path);
if (src->protocol == VIR_STORAGE_NET_PROTOCOL_GLUSTER) {
char *tmp;
if (!src->path) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("missing volume name and path for gluster volume"));
return -1;
}
if (!(tmp = strchr(src->path, '/')) ||
tmp == src->path) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("missing volume name or file name in "
"gluster source path '%s'"), src->path);
return -1;
}
src->volume = src->path;
src->path = g_strdup(tmp + 1);
tmp[0] = '\0';
}
src->hosts->port = uri->port;
src->hosts->name = g_strdup(uri->server);
/* Libvirt doesn't handle inline authentication. Make the caller aware. */
if (uri->user)
return 1;
return 0;
}
static int
virStorageSourceRBDAddHost(virStorageSourcePtr src,
char *hostport)
{
char *port;
size_t skip;
VIR_AUTOSTRINGLIST parts = NULL;
if (VIR_EXPAND_N(src->hosts, src->nhosts, 1) < 0)
return -1;
if ((port = strchr(hostport, ']'))) {
/* ipv6, strip brackets */
hostport += 1;
skip = 3;
} else {
port = strstr(hostport, "\\:");
skip = 2;
}
if (port) {
*port = '\0';
port += skip;
if (virStringParsePort(port, &src->hosts[src->nhosts - 1].port) < 0)
goto error;
}
parts = virStringSplit(hostport, "\\:", 0);
if (!parts)
goto error;
src->hosts[src->nhosts-1].name = virStringListJoin((const char **)parts, ":");
if (!src->hosts[src->nhosts-1].name)
goto error;
src->hosts[src->nhosts-1].transport = VIR_STORAGE_NET_HOST_TRANS_TCP;
src->hosts[src->nhosts-1].socket = NULL;
return 0;
error:
VIR_FREE(src->hosts[src->nhosts-1].name);
return -1;
}
int
virStorageSourceParseRBDColonString(const char *rbdstr,
virStorageSourcePtr src)
{
char *p, *e, *next;
g_autofree char *options = NULL;
g_autoptr(virStorageAuthDef) authdef = NULL;
/* optionally skip the "rbd:" prefix if provided */
if (STRPREFIX(rbdstr, "rbd:"))
rbdstr += strlen("rbd:");
src->path = g_strdup(rbdstr);
p = strchr(src->path, ':');
if (p) {
options = g_strdup(p + 1);
*p = '\0';
}
/* snapshot name */
if ((p = strchr(src->path, '@'))) {
src->snapshot = g_strdup(p + 1);
*p = '\0';
}
/* pool vs. image name */
if ((p = strchr(src->path, '/'))) {
src->volume = g_steal_pointer(&src->path);
src->path = g_strdup(p + 1);
*p = '\0';
}
/* options */
if (!options)
return 0; /* all done */
p = options;
while (*p) {
/* find : delimiter or end of string */
for (e = p; *e && *e != ':'; ++e) {
if (*e == '\\') {
e++;
if (*e == '\0')
break;
}
}
if (*e == '\0') {
next = e; /* last kv pair */
} else {
next = e + 1;
*e = '\0';
}
if (STRPREFIX(p, "id=")) {
/* formulate authdef for src->auth */
if (src->auth) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("duplicate 'id' found in '%s'"), src->path);
return -1;
}
if (VIR_ALLOC(authdef) < 0)
return -1;
authdef->username = g_strdup(p + strlen("id="));
authdef->secrettype = g_strdup(virSecretUsageTypeToString(VIR_SECRET_USAGE_TYPE_CEPH));
src->auth = g_steal_pointer(&authdef);
src->authInherited = true;
/* Cannot formulate a secretType (eg, usage or uuid) given
* what is provided.
*/
}
if (STRPREFIX(p, "mon_host=")) {
char *h, *sep;
h = p + strlen("mon_host=");
while (h < e) {
for (sep = h; sep < e; ++sep) {
if (*sep == '\\' && (sep[1] == ',' ||
sep[1] == ';' ||
sep[1] == ' ')) {
*sep = '\0';
sep += 2;
break;
}
}
if (virStorageSourceRBDAddHost(src, h) < 0)
return -1;
h = sep;
}
}
if (STRPREFIX(p, "conf="))
src->configFile = g_strdup(p + strlen("conf="));
p = next;
}
return 0;
}
static int
virStorageSourceParseNBDColonString(const char *nbdstr,
virStorageSourcePtr src)
{
VIR_AUTOSTRINGLIST backing = NULL;
const char *exportname;
if (!(backing = virStringSplit(nbdstr, ":", 0)))
return -1;
/* we know that backing[0] now equals to "nbd" */
if (VIR_ALLOC_N(src->hosts, 1) < 0)
return -1;
src->nhosts = 1;
src->hosts->transport = VIR_STORAGE_NET_HOST_TRANS_TCP;
/* format: [] denotes optional sections, uppercase are variable strings
* nbd:unix:/PATH/TO/SOCKET[:exportname=EXPORTNAME]
* nbd:HOSTNAME:PORT[:exportname=EXPORTNAME]
*/
if (!backing[1]) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("missing remote information in '%s' for protocol nbd"),
nbdstr);
return -1;
} else if (STREQ(backing[1], "unix")) {
if (!backing[2]) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("missing unix socket path in nbd backing string %s"),
nbdstr);
return -1;
}
src->hosts->socket = g_strdup(backing[2]);
src->hosts->transport = VIR_STORAGE_NET_HOST_TRANS_UNIX;
} else {
src->hosts->name = g_strdup(backing[1]);
if (!backing[2]) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("missing port in nbd string '%s'"),
nbdstr);
return -1;
}
if (virStringParsePort(backing[2], &src->hosts->port) < 0)
return -1;
}
if ((exportname = strstr(nbdstr, "exportname="))) {
exportname += strlen("exportname=");
src->path = g_strdup(exportname);
}
return 0;
}
static int
virStorageSourceParseBackingColon(virStorageSourcePtr src,
const char *path)
{
const char *p;
g_autofree char *protocol = NULL;
if (!(p = strchr(path, ':'))) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("invalid backing protocol string '%s'"),
path);
return -1;
}
protocol = g_strndup(path, p - path);
if ((src->protocol = virStorageNetProtocolTypeFromString(protocol)) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("invalid backing protocol '%s'"),
protocol);
return -1;
}
switch ((virStorageNetProtocol) src->protocol) {
case VIR_STORAGE_NET_PROTOCOL_NBD:
if (virStorageSourceParseNBDColonString(path, src) < 0)
return -1;
break;
case VIR_STORAGE_NET_PROTOCOL_RBD:
if (virStorageSourceParseRBDColonString(path, src) < 0)
return -1;
break;
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
case VIR_STORAGE_NET_PROTOCOL_LAST:
case VIR_STORAGE_NET_PROTOCOL_NONE:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("backing store parser is not implemented for protocol %s"),
protocol);
return -1;
case VIR_STORAGE_NET_PROTOCOL_HTTP:
case VIR_STORAGE_NET_PROTOCOL_HTTPS:
case VIR_STORAGE_NET_PROTOCOL_FTP:
case VIR_STORAGE_NET_PROTOCOL_FTPS:
case VIR_STORAGE_NET_PROTOCOL_TFTP:
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
case VIR_STORAGE_NET_PROTOCOL_SSH:
case VIR_STORAGE_NET_PROTOCOL_VXHS:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("malformed backing store path for protocol %s"),
protocol);
return -1;
}
return 0;
}
static int
virStorageSourceParseBackingJSONInternal(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr,
bool allowformat);
static int
virStorageSourceParseBackingJSONPath(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr G_GNUC_UNUSED,
int type)
{
const char *path;
if (!(path = virJSONValueObjectGetString(json, "filename"))) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing 'filename' field in JSON backing volume "
"definition"));
return -1;
}
src->path = g_strdup(path);
src->type = type;
return 0;
}
static int
virStorageSourceParseBackingJSONUriStr(virStorageSourcePtr src,
const char *uri,
int protocol)
{
int rc;
if ((rc = virStorageSourceParseBackingURI(src, uri)) < 0)
return -1;
if (src->protocol != protocol) {
virReportError(VIR_ERR_INVALID_ARG,
_("expected protocol '%s' but got '%s' in URI JSON volume "
"definition"),
virStorageNetProtocolTypeToString(protocol),
virStorageNetProtocolTypeToString(src->protocol));
return -1;
}
return rc;
}
static int
virStorageSourceParseBackingJSONUriCookies(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr)
{
const char *cookiestr;
VIR_AUTOSTRINGLIST cookies = NULL;
size_t ncookies = 0;
size_t i;
if (!virJSONValueObjectHasKey(json, "cookie"))
return 0;
if (!(cookiestr = virJSONValueObjectGetString(json, "cookie"))) {
virReportError(VIR_ERR_INVALID_ARG,
_("wrong format of 'cookie' field in backing store definition '%s'"),
jsonstr);
return -1;
}
if (!(cookies = virStringSplitCount(cookiestr, ";", 0, &ncookies)))
return -1;
src->cookies = g_new0(virStorageNetCookieDefPtr, ncookies);
src->ncookies = ncookies;
for (i = 0; i < ncookies; i++) {
char *cookiename = cookies[i];
char *cookievalue;
virSkipSpaces((const char **) &cookiename);
if (!(cookievalue = strchr(cookiename, '='))) {
virReportError(VIR_ERR_INVALID_ARG,
_("malformed http cookie '%s' in backing store definition '%s'"),
cookies[i], jsonstr);
return -1;
}
*cookievalue = '\0';
cookievalue++;
src->cookies[i] = g_new0(virStorageNetCookieDef, 1);
src->cookies[i]->name = g_strdup(cookiename);
src->cookies[i]->value = g_strdup(cookievalue);
}
return 0;
}
static int
virStorageSourceParseBackingJSONUri(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr,
int protocol)
{
const char *uri;
if (!(uri = virJSONValueObjectGetString(json, "url"))) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing 'url' in JSON backing volume definition"));
return -1;
}
if (protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS ||
protocol == VIR_STORAGE_NET_PROTOCOL_FTPS) {
if (virJSONValueObjectHasKey(json, "sslverify")) {
const char *tmpstr;
bool tmp;
/* libguestfs still uses undocumented legacy value of 'off' */
if ((tmpstr = virJSONValueObjectGetString(json, "sslverify")) &&
STREQ(tmpstr, "off")) {
src->sslverify = VIR_TRISTATE_BOOL_NO;
} else {
if (virJSONValueObjectGetBoolean(json, "sslverify", &tmp) < 0) {
virReportError(VIR_ERR_INVALID_ARG,
_("malformed 'sslverify' field in backing store definition '%s'"),
jsonstr);
return -1;
}
src->sslverify = virTristateBoolFromBool(tmp);
}
}
}
if (protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS ||
protocol == VIR_STORAGE_NET_PROTOCOL_HTTP) {
if (virStorageSourceParseBackingJSONUriCookies(src, json, jsonstr) < 0)
return -1;
}
if (virJSONValueObjectHasKey(json, "readahead") &&
virJSONValueObjectGetNumberUlong(json, "readahead", &src->readahead) < 0) {
virReportError(VIR_ERR_INVALID_ARG,
_("malformed 'readahead' field in backing store definition '%s'"),
jsonstr);
return -1;
}
if (virJSONValueObjectHasKey(json, "timeout") &&
virJSONValueObjectGetNumberUlong(json, "timeout", &src->timeout) < 0) {
virReportError(VIR_ERR_INVALID_ARG,
_("malformed 'timeout' field in backing store definition '%s'"),
jsonstr);
return -1;
}
return virStorageSourceParseBackingJSONUriStr(src, uri, protocol);
}
static int
virStorageSourceParseBackingJSONInetSocketAddress(virStorageNetHostDefPtr host,
virJSONValuePtr json)
{
const char *hostname;
const char *port;
if (!json) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing remote server specification in JSON "
"backing volume definition"));
return -1;
}
hostname = virJSONValueObjectGetString(json, "host");
port = virJSONValueObjectGetString(json, "port");
if (!hostname) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing hostname for tcp backing server in "
"JSON backing volume definition"));
return -1;
}
host->transport = VIR_STORAGE_NET_HOST_TRANS_TCP;
host->name = g_strdup(hostname);
if (virStringParsePort(port, &host->port) < 0)
return -1;
return 0;
}
static int
virStorageSourceParseBackingJSONSocketAddress(virStorageNetHostDefPtr host,
virJSONValuePtr json)
{
const char *type;
const char *socket;
if (!json) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing remote server specification in JSON "
"backing volume definition"));
return -1;
}
if (!(type = virJSONValueObjectGetString(json, "type"))) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing socket address type in "
"JSON backing volume definition"));
return -1;
}
if (STREQ(type, "tcp") || STREQ(type, "inet")) {
return virStorageSourceParseBackingJSONInetSocketAddress(host, json);
} else if (STREQ(type, "unix")) {
host->transport = VIR_STORAGE_NET_HOST_TRANS_UNIX;
socket = virJSONValueObjectGetString(json, "path");
/* check for old spelling for gluster protocol */
if (!socket)
socket = virJSONValueObjectGetString(json, "socket");
if (!socket) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing socket path for udp backing server in "
"JSON backing volume definition"));
return -1;
}
host->socket = g_strdup(socket);
} else {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("backing store protocol '%s' is not yet supported"),
type);
return -1;
}
return 0;
}
static int
virStorageSourceParseBackingJSONGluster(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr G_GNUC_UNUSED,
int opaque G_GNUC_UNUSED)
{
const char *uri = virJSONValueObjectGetString(json, "filename");
const char *volume = virJSONValueObjectGetString(json, "volume");
const char *path = virJSONValueObjectGetString(json, "path");
virJSONValuePtr server = virJSONValueObjectGetArray(json, "server");
size_t nservers;
size_t i;
/* legacy URI based syntax passed via 'filename' option */
if (uri)
return virStorageSourceParseBackingJSONUriStr(src, uri,
VIR_STORAGE_NET_PROTOCOL_GLUSTER);
if (!volume || !path || !server) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing 'volume', 'path' or 'server' attribute in "
"JSON backing definition for gluster volume"));
return -1;
}
src->type = VIR_STORAGE_TYPE_NETWORK;
src->protocol = VIR_STORAGE_NET_PROTOCOL_GLUSTER;
src->volume = g_strdup(volume);
src->path = g_strdup(path);
nservers = virJSONValueArraySize(server);
if (nservers == 0) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("at least 1 server is necessary in "
"JSON backing definition for gluster volume"));
return -1;
}
if (VIR_ALLOC_N(src->hosts, nservers) < 0)
return -1;
src->nhosts = nservers;
for (i = 0; i < nservers; i++) {
if (virStorageSourceParseBackingJSONSocketAddress(src->hosts + i,
virJSONValueArrayGet(server, i)) < 0)
return -1;
}
return 0;
}
static int
virStorageSourceParseBackingJSONiSCSI(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr G_GNUC_UNUSED,
int opaque G_GNUC_UNUSED)
{
const char *transport = virJSONValueObjectGetString(json, "transport");
const char *portal = virJSONValueObjectGetString(json, "portal");
const char *target = virJSONValueObjectGetString(json, "target");
const char *lun = virJSONValueObjectGetStringOrNumber(json, "lun");
const char *uri;
char *port;
/* legacy URI based syntax passed via 'filename' option */
if ((uri = virJSONValueObjectGetString(json, "filename")))
return virStorageSourceParseBackingJSONUriStr(src, uri,
VIR_STORAGE_NET_PROTOCOL_ISCSI);
src->type = VIR_STORAGE_TYPE_NETWORK;
src->protocol = VIR_STORAGE_NET_PROTOCOL_ISCSI;
if (!lun)
lun = "0";
if (VIR_ALLOC(src->hosts) < 0)
return -1;
src->nhosts = 1;
if (STRNEQ_NULLABLE(transport, "tcp")) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("only TCP transport is supported for iSCSI volumes"));
return -1;
}
src->hosts->transport = VIR_STORAGE_NET_HOST_TRANS_TCP;
if (!portal) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing 'portal' address in iSCSI backing definition"));
return -1;
}
if (!target) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing 'target' in iSCSI backing definition"));
return -1;
}
src->hosts->name = g_strdup(portal);
if ((port = strrchr(src->hosts->name, ':')) &&
!strchr(port, ']')) {
if (virStringParsePort(port + 1, &src->hosts->port) < 0)
return -1;
*port = '\0';
}
src->path = g_strdup_printf("%s/%s", target, lun);
/* Libvirt doesn't handle inline authentication. Make the caller aware. */
if (virJSONValueObjectGetString(json, "user") ||
virJSONValueObjectGetString(json, "password"))
return 1;
return 0;
}
static int
virStorageSourceParseBackingJSONNbd(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr G_GNUC_UNUSED,
int opaque G_GNUC_UNUSED)
{
const char *path = virJSONValueObjectGetString(json, "path");
const char *host = virJSONValueObjectGetString(json, "host");
const char *port = virJSONValueObjectGetString(json, "port");
const char *export = virJSONValueObjectGetString(json, "export");
virJSONValuePtr server = virJSONValueObjectGetObject(json, "server");
if (!path && !host && !server) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing host specification of NBD server in JSON "
"backing volume definition"));
return -1;
}
src->type = VIR_STORAGE_TYPE_NETWORK;
src->protocol = VIR_STORAGE_NET_PROTOCOL_NBD;
src->path = g_strdup(export);
if (VIR_ALLOC_N(src->hosts, 1) < 0)
return -1;
src->nhosts = 1;
if (server) {
if (virStorageSourceParseBackingJSONSocketAddress(src->hosts, server) < 0)
return -1;
} else {
if (path) {
src->hosts[0].transport = VIR_STORAGE_NET_HOST_TRANS_UNIX;
src->hosts[0].socket = g_strdup(path);
} else {
src->hosts[0].transport = VIR_STORAGE_NET_HOST_TRANS_TCP;
src->hosts[0].name = g_strdup(host);
if (virStringParsePort(port, &src->hosts[0].port) < 0)
return -1;
}
}
return 0;
}
static int
virStorageSourceParseBackingJSONSheepdog(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr G_GNUC_UNUSED,
int opaque G_GNUC_UNUSED)
{
const char *filename;
const char *vdi = virJSONValueObjectGetString(json, "vdi");
virJSONValuePtr server = virJSONValueObjectGetObject(json, "server");
/* legacy URI based syntax passed via 'filename' option */
if ((filename = virJSONValueObjectGetString(json, "filename"))) {
if (strstr(filename, "://"))
return virStorageSourceParseBackingJSONUriStr(src, filename,
VIR_STORAGE_NET_PROTOCOL_SHEEPDOG);
/* libvirt doesn't implement a parser for the legacy non-URI syntax */
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing sheepdog URI in JSON backing volume definition"));
return -1;
}
src->type = VIR_STORAGE_TYPE_NETWORK;
src->protocol = VIR_STORAGE_NET_PROTOCOL_SHEEPDOG;
if (!vdi) {
virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing sheepdog vdi name"));
return -1;
}
src->path = g_strdup(vdi);
if (VIR_ALLOC(src->hosts) < 0)
return -1;
src->nhosts = 1;
if (virStorageSourceParseBackingJSONSocketAddress(src->hosts, server) < 0)
return -1;
return 0;
}
static int
virStorageSourceParseBackingJSONSSH(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr G_GNUC_UNUSED,
int opaque G_GNUC_UNUSED)
{
const char *path = virJSONValueObjectGetString(json, "path");
const char *host = virJSONValueObjectGetString(json, "host");
const char *port = virJSONValueObjectGetString(json, "port");
const char *user = virJSONValueObjectGetString(json, "user");
const char *host_key_check = virJSONValueObjectGetString(json, "host_key_check");
virJSONValuePtr server = virJSONValueObjectGetObject(json, "server");
if (!(host || server) || !path) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing host/server or path of SSH JSON backing "
"volume definition"));
return -1;
}
src->type = VIR_STORAGE_TYPE_NETWORK;
src->protocol = VIR_STORAGE_NET_PROTOCOL_SSH;
src->path = g_strdup(path);
if (VIR_ALLOC_N(src->hosts, 1) < 0)
return -1;
src->nhosts = 1;
if (server) {
if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts,
server) < 0)
return -1;
} else {
src->hosts[0].transport = VIR_STORAGE_NET_HOST_TRANS_TCP;
src->hosts[0].name = g_strdup(host);
if (virStringParsePort(port, &src->hosts[0].port) < 0)
return -1;
}
/* these two are parsed just to be passed back as we don't model them yet */
src->ssh_user = g_strdup(user);
if (STREQ_NULLABLE(host_key_check, "no"))
src->ssh_host_key_check_disabled = true;
return 0;
}
static int
virStorageSourceParseBackingJSONRBD(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr G_GNUC_UNUSED,
int opaque G_GNUC_UNUSED)
{
const char *filename;
const char *pool = virJSONValueObjectGetString(json, "pool");
const char *image = virJSONValueObjectGetString(json, "image");
const char *conf = virJSONValueObjectGetString(json, "conf");
const char *snapshot = virJSONValueObjectGetString(json, "snapshot");
virJSONValuePtr servers = virJSONValueObjectGetArray(json, "server");
size_t nservers;
size_t i;
src->type = VIR_STORAGE_TYPE_NETWORK;
src->protocol = VIR_STORAGE_NET_PROTOCOL_RBD;
/* legacy syntax passed via 'filename' option */
if ((filename = virJSONValueObjectGetString(json, "filename")))
return virStorageSourceParseRBDColonString(filename, src);
if (!pool || !image) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing pool or image name in ceph backing volume "
"JSON specification"));
return -1;
}
src->volume = g_strdup(pool);
src->path = g_strdup(image);
src->snapshot = g_strdup(snapshot);
src->configFile = g_strdup(conf);
if (servers) {
nservers = virJSONValueArraySize(servers);
if (VIR_ALLOC_N(src->hosts, nservers) < 0)
return -1;
src->nhosts = nservers;
for (i = 0; i < nservers; i++) {
if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts + i,
virJSONValueArrayGet(servers, i)) < 0)
return -1;
}
}
return 0;
}
static int
virStorageSourceParseBackingJSONRaw(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr,
int opaque G_GNUC_UNUSED)
{
bool has_offset = virJSONValueObjectHasKey(json, "offset");
bool has_size = virJSONValueObjectHasKey(json, "size");
virJSONValuePtr file;
if (has_offset || has_size) {
src->sliceStorage = g_new0(virStorageSourceSlice, 1);
if (has_offset &&
virJSONValueObjectGetNumberUlong(json, "offset", &src->sliceStorage->offset) < 0) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("malformed 'offset' property of 'raw' driver"));
return -1;
}
if (has_size &&
virJSONValueObjectGetNumberUlong(json, "size", &src->sliceStorage->size) < 0) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("malformed 'size' property of 'raw' driver"));
return -1;
}
}
/* 'raw' is a format driver so it can have protocol driver children */
if (!(file = virJSONValueObjectGetObject(json, "file"))) {
virReportError(VIR_ERR_INVALID_ARG,
_("JSON backing volume definition '%s' lacks 'file' object"),
jsonstr);
return -1;
}
return virStorageSourceParseBackingJSONInternal(src, file, jsonstr, false);
}
static int
virStorageSourceParseBackingJSONVxHS(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr G_GNUC_UNUSED,
int opaque G_GNUC_UNUSED)
{
const char *vdisk_id = virJSONValueObjectGetString(json, "vdisk-id");
virJSONValuePtr server = virJSONValueObjectGetObject(json, "server");
if (!vdisk_id || !server) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing 'vdisk-id' or 'server' attribute in "
"JSON backing definition for VxHS volume"));
return -1;
}
src->type = VIR_STORAGE_TYPE_NETWORK;
src->protocol = VIR_STORAGE_NET_PROTOCOL_VXHS;
src->path = g_strdup(vdisk_id);
if (VIR_ALLOC_N(src->hosts, 1) < 0)
return -1;
src->nhosts = 1;
if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts,
server) < 0)
return -1;
return 0;
}
struct virStorageSourceJSONDriverParser {
const char *drvname;
bool formatdriver;
/**
* The callback gets a pre-allocated storage source @src and the JSON
* object to parse. The callback shall return -1 on error and report error
* 0 on success and 1 in cases when the configuration itself is valid, but
* can't be converted to libvirt's configuration (e.g. inline authentication
* credentials are present).
*/
int (*func)(virStorageSourcePtr src, virJSONValuePtr json, const char *jsonstr, int opaque);
int opaque;
};
static const struct virStorageSourceJSONDriverParser jsonParsers[] = {
{"file", false, virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_FILE},
{"host_device", false, virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_BLOCK},
{"host_cdrom", false, virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_BLOCK},
{"http", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_HTTP},
{"https", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_HTTPS},
{"ftp", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_FTP},
{"ftps", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_FTPS},
{"tftp", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_TFTP},
{"gluster", false, virStorageSourceParseBackingJSONGluster, 0},
{"iscsi", false, virStorageSourceParseBackingJSONiSCSI, 0},
{"nbd", false, virStorageSourceParseBackingJSONNbd, 0},
{"sheepdog", false, virStorageSourceParseBackingJSONSheepdog, 0},
{"ssh", false, virStorageSourceParseBackingJSONSSH, 0},
{"rbd", false, virStorageSourceParseBackingJSONRBD, 0},
{"raw", true, virStorageSourceParseBackingJSONRaw, 0},
{"vxhs", false, virStorageSourceParseBackingJSONVxHS, 0},
};
static int
virStorageSourceParseBackingJSONInternal(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr,
bool allowformat)
{
const char *drvname;
size_t i;
if (!(drvname = virJSONValueObjectGetString(json, "driver"))) {
virReportError(VIR_ERR_INVALID_ARG,
_("JSON backing volume definition '%s' lacks driver name"),
jsonstr);
return -1;
}
for (i = 0; i < G_N_ELEMENTS(jsonParsers); i++) {
if (STRNEQ(drvname, jsonParsers[i].drvname))
continue;
if (jsonParsers[i].formatdriver && !allowformat) {
virReportError(VIR_ERR_INVALID_ARG,
_("JSON backing volume definition '%s' must not have nested format drivers"),
jsonstr);
return -1;
}
return jsonParsers[i].func(src, json, jsonstr, jsonParsers[i].opaque);
}
virReportError(VIR_ERR_INTERNAL_ERROR,
_("missing parser implementation for JSON backing volume "
"driver '%s'"), drvname);
return -1;
}
static int
virStorageSourceParseBackingJSON(virStorageSourcePtr src,
const char *json)
{
g_autoptr(virJSONValue) root = NULL;
g_autoptr(virJSONValue) deflattened = NULL;
virJSONValuePtr file = NULL;
if (!(root = virJSONValueFromString(json)))
return -1;
if (!(deflattened = virJSONValueObjectDeflatten(root)))
return -1;
/* There are 2 possible syntaxes:
* 1) json:{"file":{"driver":...}}
* 2) json:{"driver":...}
* Remove the 'file' wrapper object in case 1.
*/
if (!virJSONValueObjectHasKey(deflattened, "driver"))
file = virJSONValueObjectGetObject(deflattened, "file");
if (!file)
file = deflattened;
return virStorageSourceParseBackingJSONInternal(src, file, json, true);
}
/**
* virStorageSourceNewFromBackingAbsolute
* @path: string representing absolute location of a storage source
* @src: filled with virStorageSource object representing @path
*
* Returns 0 on success, 1 if we could parse all location data but @path
* specified other data unrepresentable by libvirt (e.g. inline authentication).
* In both cases @src is filled. On error -1 is returned @src is NULL and an
* error is reported.
*/
int
virStorageSourceNewFromBackingAbsolute(const char *path,
virStorageSourcePtr *src)
{
const char *json;
int rc = 0;
g_autoptr(virStorageSource) def = NULL;
*src = NULL;
if (!(def = virStorageSourceNew()))
return -1;
if (virStorageIsFile(path)) {
def->type = VIR_STORAGE_TYPE_FILE;
def->path = g_strdup(path);
} else {
def->type = VIR_STORAGE_TYPE_NETWORK;
VIR_DEBUG("parsing backing store string: '%s'", path);
/* handle URI formatted backing stores */
if ((json = STRSKIP(path, "json:")))
rc = virStorageSourceParseBackingJSON(def, json);
else if (strstr(path, "://"))
rc = virStorageSourceParseBackingURI(def, path);
else
rc = virStorageSourceParseBackingColon(def, path);
if (rc < 0)
return -1;
virStorageSourceNetworkAssignDefaultPorts(def);
/* Some of the legacy parsers parse authentication data since they are
* also used in other places. For backing store detection the
* authentication data would be invalid anyways, so we clear it */
if (def->auth) {
virStorageAuthDefFree(def->auth);
def->auth = NULL;
}
}
*src = g_steal_pointer(&def);
return rc;
}
/**
* virStorageSourceNewFromChild:
* @parent: storage source parent
* @child: returned child/backing store definition
* @parentRaw: raw child string (backingStoreRaw)
*
* Creates a storage source which describes the backing image of @parent and
* fills it into @backing depending on the passed parentRaw (backingStoreRaw)
* and other data. Note that for local storage this function accesses the file
* to update the actual type of the child store.
*
* Returns 0 on success, 1 if we could parse all location data but the child
* store specification contained other data unrepresentable by libvirt (e.g.
* inline authentication).
* In both cases @src is filled. On error -1 is returned @src is NULL and an
* error is reported.
*/
static int
virStorageSourceNewFromChild(virStorageSourcePtr parent,
const char *parentRaw,
virStorageSourcePtr *child)
{
struct stat st;
g_autoptr(virStorageSource) def = NULL;
int rc = 0;
*child = NULL;
if (virStorageIsRelative(parentRaw)) {
if (!(def = virStorageSourceNewFromBackingRelative(parent, parentRaw)))
return -1;
} else {
if ((rc = virStorageSourceNewFromBackingAbsolute(parentRaw, &def)) < 0)
return -1;
}
/* possibly update local type */
if (def->type == VIR_STORAGE_TYPE_FILE) {
if (stat(def->path, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
def->type = VIR_STORAGE_TYPE_DIR;
def->format = VIR_STORAGE_FILE_DIR;
} else if (S_ISBLK(st.st_mode)) {
def->type = VIR_STORAGE_TYPE_BLOCK;
}
}
}
/* copy parent's labelling and other top level stuff */
if (virStorageSourceInitChainElement(def, parent, true) < 0)
return -1;
def->detected = true;
*child = g_steal_pointer(&def);
return rc;
}
int
virStorageSourceNewFromBacking(virStorageSourcePtr parent,
virStorageSourcePtr *backing)
{
int rc;
if ((rc = virStorageSourceNewFromChild(parent,
parent->backingStoreRaw,
backing)) < 0)
return rc;
(*backing)->format = parent->backingStoreRawFormat;
(*backing)->readonly = true;
return rc;
}
static int
virStorageSourceNewFromExternalData(virStorageSourcePtr parent,
virStorageSourcePtr *externalDataStore)
{
int rc;
if ((rc = virStorageSourceNewFromChild(parent,
parent->externalDataStoreRaw,
externalDataStore)) < 0)
return rc;
/* qcow2 data_file can only be raw */
(*externalDataStore)->format = VIR_STORAGE_FILE_RAW;
(*externalDataStore)->readonly = parent->readonly;
return rc;
}
/**
* @src: disk source definition structure
* @fd: file descriptor
* @sb: stat buffer
*
* Updates src->physical depending on the actual type of storage being used.
* To be called for domain storage source reporting as the volume code does
* not set/use the 'type' field for the voldef->source.target
*
* Returns 0 on success, -1 on error. No libvirt errors are reported.
*/
int
virStorageSourceUpdatePhysicalSize(virStorageSourcePtr src,
int fd,
struct stat const *sb)
{
off_t end;
virStorageType actual_type = virStorageSourceGetActualType(src);
switch (actual_type) {
case VIR_STORAGE_TYPE_FILE:
case VIR_STORAGE_TYPE_NETWORK:
src->physical = sb->st_size;
break;
case VIR_STORAGE_TYPE_BLOCK:
if ((end = lseek(fd, 0, SEEK_END)) == (off_t) -1)
return -1;
src->physical = end;
break;
case VIR_STORAGE_TYPE_DIR:
src->physical = 0;
break;
/* We shouldn't get VOLUME, but the switch requires all cases */
case VIR_STORAGE_TYPE_VOLUME:
case VIR_STORAGE_TYPE_NVME:
case VIR_STORAGE_TYPE_NONE:
case VIR_STORAGE_TYPE_LAST:
return -1;
}
return 0;
}
/**
* @src: disk source definition structure
* @fd: file descriptor
* @sb: stat buffer
*
* Update the capacity, allocation, physical values for the storage @src
* Shared between the domain storage source for an inactive domain and the
* voldef source target as the result is not affected by the 'type' field.
*
* Returns 0 on success, -1 on error.
*/
int
virStorageSourceUpdateBackingSizes(virStorageSourcePtr src,
int fd,
struct stat const *sb)
{
/* Get info for normal formats */
if (S_ISREG(sb->st_mode) || fd == -1) {
#ifndef WIN32
src->allocation = (unsigned long long)sb->st_blocks *
(unsigned long long)DEV_BSIZE;
#else
src->allocation = sb->st_size;
#endif
/* Regular files may be sparse, so logical size (capacity) is not same
* as actual allocation above
*/
src->capacity = sb->st_size;
/* Allocation tracks when the file is sparse, physical is the
* last offset of the file. */
src->physical = sb->st_size;
} else if (S_ISDIR(sb->st_mode)) {
src->allocation = 0;
src->capacity = 0;
src->physical = 0;
} else if (fd >= 0) {
off_t end;
/* XXX this is POSIX compliant, but doesn't work for CHAR files,
* only BLOCK. There is a Linux specific ioctl() for getting
* size of both CHAR / BLOCK devices we should check for in
* configure
*
* NB. Because we configure with AC_SYS_LARGEFILE, off_t
* should be 64 bits on all platforms. For block devices, we
* have to seek (safe even if someone else is writing) to
* determine physical size, and assume that allocation is the
* same as physical (but can refine that assumption later if
* qemu is still running).
*/
if ((end = lseek(fd, 0, SEEK_END)) == (off_t)-1) {
virReportSystemError(errno,
_("failed to seek to end of %s"), src->path);
return -1;
}
src->physical = end;
src->allocation = end;
src->capacity = end;
}
return 0;
}
/**
* @src: disk source definition structure
* @buf: buffer to the storage file header
* @len: length of the storage file header
*
* Update the storage @src capacity.
*
* Returns 0 on success, -1 on error.
*/
int
virStorageSourceUpdateCapacity(virStorageSourcePtr src,
char *buf,
ssize_t len)
{
int format = src->format;
g_autoptr(virStorageSource) meta = NULL;
/* Raw files: capacity is physical size. For all other files: if
* the metadata has a capacity, use that, otherwise fall back to
* physical size. */
if (format == VIR_STORAGE_FILE_NONE) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("no disk format for %s was specified"),
src->path);
return -1;
}
if (format == VIR_STORAGE_FILE_RAW && !src->encryption) {
src->capacity = src->physical;
} else if ((meta = virStorageFileGetMetadataFromBuf(src->path, buf,
len, format))) {
src->capacity = meta->capacity ? meta->capacity : src->physical;
if (src->encryption && meta->encryption)
src->encryption->payload_offset = meta->encryption->payload_offset;
} else {
return -1;
}
if (src->encryption && src->encryption->payload_offset != -1)
src->capacity -= src->encryption->payload_offset * 512;
return 0;
}
static char *
virStorageFileCanonicalizeFormatPath(char **components,
size_t ncomponents,
bool beginSlash,
bool beginDoubleSlash)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
size_t i;
char *ret = NULL;
if (beginSlash)
virBufferAddLit(&buf, "/");
if (beginDoubleSlash)
virBufferAddLit(&buf, "/");
for (i = 0; i < ncomponents; i++) {
if (i != 0)
virBufferAddLit(&buf, "/");
virBufferAdd(&buf, components[i], -1);
}
/* if the output string is empty just return an empty string */
if (!(ret = virBufferContentAndReset(&buf)))
ret = g_strdup("");
return ret;
}
static int
virStorageFileCanonicalizeInjectSymlink(const char *path,
size_t at,
char ***components,
size_t *ncomponents)
{
char **tmp = NULL;
char **next;
size_t ntmp = 0;
int ret = -1;
if (!(tmp = virStringSplitCount(path, "/", 0, &ntmp)))
goto cleanup;
/* prepend */
for (next = tmp; *next; next++) {
if (VIR_INSERT_ELEMENT(*components, at, *ncomponents, *next) < 0)
goto cleanup;
at++;
}
ret = 0;
cleanup:
virStringListFreeCount(tmp, ntmp);
return ret;
}
char *
virStorageFileCanonicalizePath(const char *path,
virStorageFileSimplifyPathReadlinkCallback cb,
void *cbdata)
{
virHashTablePtr cycle = NULL;
bool beginSlash = false;
bool beginDoubleSlash = false;
char **components = NULL;
size_t ncomponents = 0;
size_t i = 0;
size_t j = 0;
int rc;
char *ret = NULL;
g_autofree char *linkpath = NULL;
g_autofree char *currentpath = NULL;
if (path[0] == '/') {
beginSlash = true;
if (path[1] == '/' && path[2] != '/')
beginDoubleSlash = true;
}
if (!(cycle = virHashCreate(10, NULL)))
goto cleanup;
if (!(components = virStringSplitCount(path, "/", 0, &ncomponents)))
goto cleanup;
j = 0;
while (j < ncomponents) {
/* skip slashes */
if (STREQ(components[j], "")) {
VIR_FREE(components[j]);
VIR_DELETE_ELEMENT(components, j, ncomponents);
continue;
}
j++;
}
while (i < ncomponents) {
/* skip '.'s unless it's the last one remaining */
if (STREQ(components[i], ".") &&
(beginSlash || ncomponents > 1)) {
VIR_FREE(components[i]);
VIR_DELETE_ELEMENT(components, i, ncomponents);
continue;
}
/* resolve changes to parent directory */
if (STREQ(components[i], "..")) {
if (!beginSlash &&
(i == 0 || STREQ(components[i - 1], ".."))) {
i++;
continue;
}
VIR_FREE(components[i]);
VIR_DELETE_ELEMENT(components, i, ncomponents);
if (i != 0) {
VIR_FREE(components[i - 1]);
VIR_DELETE_ELEMENT(components, i - 1, ncomponents);
i--;
}
continue;
}
/* check if the actual path isn't resulting into a symlink */
if (!(currentpath = virStorageFileCanonicalizeFormatPath(components,
i + 1,
beginSlash,
beginDoubleSlash)))
goto cleanup;
if ((rc = cb(currentpath, &linkpath, cbdata)) < 0)
goto cleanup;
if (rc == 0) {
if (virHashLookup(cycle, currentpath)) {
virReportSystemError(ELOOP,
_("Failed to canonicalize path '%s'"), path);
goto cleanup;
}
if (virHashAddEntry(cycle, currentpath, (void *) 1) < 0)
goto cleanup;
if (linkpath[0] == '/') {
/* kill everything from the beginning including the actual component */
i++;
while (i--) {
VIR_FREE(components[0]);
VIR_DELETE_ELEMENT(components, 0, ncomponents);
}
beginSlash = true;
if (linkpath[1] == '/' && linkpath[2] != '/')
beginDoubleSlash = true;
else
beginDoubleSlash = false;
i = 0;
} else {
VIR_FREE(components[i]);
VIR_DELETE_ELEMENT(components, i, ncomponents);
}
if (virStorageFileCanonicalizeInjectSymlink(linkpath,
i,
&components,
&ncomponents) < 0)
goto cleanup;
j = 0;
while (j < ncomponents) {
/* skip slashes */
if (STREQ(components[j], "")) {
VIR_FREE(components[j]);
VIR_DELETE_ELEMENT(components, j, ncomponents);
continue;
}
j++;
}
VIR_FREE(linkpath);
VIR_FREE(currentpath);
continue;
}
VIR_FREE(currentpath);
i++;
}
ret = virStorageFileCanonicalizeFormatPath(components, ncomponents,
beginSlash, beginDoubleSlash);
cleanup:
virHashFree(cycle);
virStringListFreeCount(components, ncomponents);
return ret;
}
/**
* virStorageFileRemoveLastPathComponent:
*
* @path: Path string to remove the last component from
*
* Removes the last path component of a path. This function is designed to be
* called on file paths only (no trailing slashes in @path). Caller is
* responsible to free the returned string.
*/
static char *
virStorageFileRemoveLastPathComponent(const char *path)
{
char *ret;
ret = g_strdup(NULLSTR_EMPTY(path));
virFileRemoveLastComponent(ret);
return ret;
}
/*
* virStorageFileGetRelativeBackingPath:
*
* Resolve relative path to be written to the overlay of @top image when
* collapsing the backing chain between @top and @base.
*
* Returns 0 on success; 1 if backing chain isn't relative and -1 on error.
*/
int
virStorageFileGetRelativeBackingPath(virStorageSourcePtr top,
virStorageSourcePtr base,
char **relpath)
{
virStorageSourcePtr next;
g_autofree char *tmp = NULL;
g_autofree char *path = NULL;
*relpath = NULL;
for (next = top; virStorageSourceIsBacking(next); next = next->backingStore) {
if (!next->relPath)
return 1;
if (!(tmp = virStorageFileRemoveLastPathComponent(path)))
return -1;
VIR_FREE(path);
path = g_strdup_printf("%s%s", tmp, next->relPath);
VIR_FREE(tmp);
if (next == base)
break;
}
if (next != base) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("failed to resolve relative backing name: "
"base image is not in backing chain"));
return -1;
}
*relpath = g_steal_pointer(&path);
return 0;
}
/*
* virStorageFileCheckCompat
*/
int
virStorageFileCheckCompat(const char *compat)
{
unsigned int result;
VIR_AUTOSTRINGLIST version = NULL;
if (!compat)
return 0;
version = virStringSplit(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;
}
/**
* 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_NONE:
case VIR_STORAGE_TYPE_LAST:
return false;
}
return false;
}
/**
* virStorageSourceFindByNodeName:
* @top: backing chain top
* @nodeName: node name to find in backing chain
* @index: if provided the index in the backing chain
*
* Looks up the given storage source in the backing chain and returns the
* pointer to it. If @index is passed then it's filled by the index in the
* backing chain. On failure NULL is returned and no error is reported.
*/
virStorageSourcePtr
virStorageSourceFindByNodeName(virStorageSourcePtr top,
const char *nodeName,
unsigned int *idx)
{
virStorageSourcePtr tmp;
if (idx)
*idx = 0;
for (tmp = top; virStorageSourceIsBacking(tmp); tmp = tmp->backingStore) {
if ((tmp->nodeformat && STREQ(tmp->nodeformat, nodeName)) ||
(tmp->nodestorage && STREQ(tmp->nodestorage, nodeName)))
return tmp;
if (idx)
(*idx)++;
}
if (idx)
*idx = 0;
return NULL;
}
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_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, "%s\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, "\n");
virBufferAdjustIndent(buf, 2);
virBufferEscapeString(buf, "\n", initiator->iqn);
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "\n");
}
int
virStorageSourceInitiatorCopy(virStorageSourceInitiatorDefPtr dest,
const virStorageSourceInitiatorDef *src)
{
dest->iqn = g_strdup(src->iqn);
return 0;
}
void
virStorageSourceInitiatorClear(virStorageSourceInitiatorDefPtr initiator)
{
VIR_FREE(initiator->iqn);
}
static bool
virStorageFileIsInitialized(const virStorageSource *src)
{
return src && src->drv;
}
/**
* virStorageFileGetBackendForSupportCheck:
* @src: storage source to check support for
* @backend: pointer to the storage backend for @src if it's supported
*
* Returns 0 if @src is not supported by any storage backend currently linked
* 1 if it is supported and -1 on error with an error reported.
*/
static int
virStorageFileGetBackendForSupportCheck(const virStorageSource *src,
virStorageFileBackendPtr *backend)
{
int actualType;
if (!src) {
*backend = NULL;
return 0;
}
if (src->drv) {
*backend = src->drv->backend;
return 1;
}
actualType = virStorageSourceGetActualType(src);
if (virStorageFileBackendForType(actualType, src->protocol, false, backend) < 0)
return -1;
if (!*backend)
return 0;
return 1;
}
int
virStorageFileSupportsBackingChainTraversal(const virStorageSource *src)
{
virStorageFileBackendPtr backend;
int rv;
if ((rv = virStorageFileGetBackendForSupportCheck(src, &backend)) < 1)
return rv;
return backend->storageFileGetUniqueIdentifier &&
backend->storageFileRead &&
backend->storageFileAccess ? 1 : 0;
}
/**
* virStorageFileSupportsSecurityDriver:
*
* @src: a storage file structure
*
* Check if a storage file supports operations needed by the security
* driver to perform labelling
*/
int
virStorageFileSupportsSecurityDriver(const virStorageSource *src)
{
virStorageFileBackendPtr backend;
int rv;
if ((rv = virStorageFileGetBackendForSupportCheck(src, &backend)) < 1)
return rv;
return backend->storageFileChown ? 1 : 0;
}
/**
* virStorageFileSupportsAccess:
*
* @src: a storage file structure
*
* Check if a storage file supports checking if the storage source is accessible
* for the given vm.
*/
int
virStorageFileSupportsAccess(const virStorageSource *src)
{
virStorageFileBackendPtr backend;
int rv;
if ((rv = virStorageFileGetBackendForSupportCheck(src, &backend)) < 1)
return rv;
return backend->storageFileAccess ? 1 : 0;
}
/**
* virStorageFileSupportsCreate:
* @src: a storage file structure
*
* Check if the storage driver supports creating storage described by @src
* via virStorageFileCreate.
*/
int
virStorageFileSupportsCreate(const virStorageSource *src)
{
virStorageFileBackendPtr backend;
int rv;
if ((rv = virStorageFileGetBackendForSupportCheck(src, &backend)) < 1)
return rv;
return backend->storageFileCreate ? 1 : 0;
}
void
virStorageFileDeinit(virStorageSourcePtr src)
{
if (!virStorageFileIsInitialized(src))
return;
if (src->drv->backend &&
src->drv->backend->backendDeinit)
src->drv->backend->backendDeinit(src);
VIR_FREE(src->drv);
}
/**
* virStorageFileInitAs:
*
* @src: storage source definition
* @uid: uid used to access the file, or -1 for current uid
* @gid: gid used to access the file, or -1 for current gid
*
* Initialize a storage source to be used with storage driver. Use the provided
* uid and gid if possible for the operations.
*
* Returns 0 if the storage file was successfully initialized, -1 if the
* initialization failed. Libvirt error is reported.
*/
int
virStorageFileInitAs(virStorageSourcePtr src,
uid_t uid, gid_t gid)
{
int actualType = virStorageSourceGetActualType(src);
if (VIR_ALLOC(src->drv) < 0)
return -1;
if (uid == (uid_t) -1)
src->drv->uid = geteuid();
else
src->drv->uid = uid;
if (gid == (gid_t) -1)
src->drv->gid = getegid();
else
src->drv->gid = gid;
if (virStorageFileBackendForType(actualType,
src->protocol,
true,
&src->drv->backend) < 0)
goto error;
if (src->drv->backend->backendInit &&
src->drv->backend->backendInit(src) < 0)
goto error;
return 0;
error:
VIR_FREE(src->drv);
return -1;
}
/**
* virStorageFileInit:
*
* See virStorageFileInitAs. The file is initialized to be accessed by the
* current user.
*/
int
virStorageFileInit(virStorageSourcePtr src)
{
return virStorageFileInitAs(src, -1, -1);
}
/**
* virStorageFileCreate: Creates an empty storage file via storage driver
*
* @src: file structure pointing to the file
*
* Returns 0 on success, -2 if the function isn't supported by the backend,
* -1 on other failure. Errno is set in case of failure.
*/
int
virStorageFileCreate(virStorageSourcePtr src)
{
int ret;
if (!virStorageFileIsInitialized(src) ||
!src->drv->backend->storageFileCreate) {
errno = ENOSYS;
return -2;
}
ret = src->drv->backend->storageFileCreate(src);
VIR_DEBUG("created storage file %p: ret=%d, errno=%d",
src, ret, errno);
return ret;
}
/**
* virStorageFileUnlink: Unlink storage file via storage driver
*
* @src: file structure pointing to the file
*
* Unlinks the file described by the @file structure.
*
* Returns 0 on success, -2 if the function isn't supported by the backend,
* -1 on other failure. Errno is set in case of failure.
*/
int
virStorageFileUnlink(virStorageSourcePtr src)
{
int ret;
if (!virStorageFileIsInitialized(src) ||
!src->drv->backend->storageFileUnlink) {
errno = ENOSYS;
return -2;
}
ret = src->drv->backend->storageFileUnlink(src);
VIR_DEBUG("unlinked storage file %p: ret=%d, errno=%d",
src, ret, errno);
return ret;
}
/**
* virStorageFileStat: returns stat struct of a file via storage driver
*
* @src: file structure pointing to the file
* @stat: stat structure to return data
*
* Returns 0 on success, -2 if the function isn't supported by the backend,
* -1 on other failure. Errno is set in case of failure.
*/
int
virStorageFileStat(virStorageSourcePtr src,
struct stat *st)
{
int ret;
if (!virStorageFileIsInitialized(src) ||
!src->drv->backend->storageFileStat) {
errno = ENOSYS;
return -2;
}
ret = src->drv->backend->storageFileStat(src, st);
VIR_DEBUG("stat of storage file %p: ret=%d, errno=%d",
src, ret, errno);
return ret;
}
/**
* virStorageFileRead: read bytes from a file into a buffer
*
* @src: file structure pointing to the file
* @offset: number of bytes to skip in the storage file
* @len: maximum number of bytes read from the storage file
* @buf: buffer to read the data into. (buffer shall be freed by caller)
*
* Returns the count of bytes read on success and -1 on failure, -2 if the
* function isn't supported by the backend.
* Libvirt error is reported on failure.
*/
ssize_t
virStorageFileRead(virStorageSourcePtr src,
size_t offset,
size_t len,
char **buf)
{
ssize_t ret;
if (!virStorageFileIsInitialized(src)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("storage file backend not initialized"));
return -1;
}
if (!src->drv->backend->storageFileRead)
return -2;
ret = src->drv->backend->storageFileRead(src, offset, len, buf);
VIR_DEBUG("read '%zd' bytes from storage '%p' starting at offset '%zu'",
ret, src, offset);
return ret;
}
/*
* virStorageFileGetUniqueIdentifier: Get a unique string describing the volume
*
* @src: file structure pointing to the file
*
* Returns a string uniquely describing a single volume (canonical path).
* The string shall not be freed and is valid until the storage file is
* deinitialized. Returns NULL on error and sets a libvirt error code */
const char *
virStorageFileGetUniqueIdentifier(virStorageSourcePtr src)
{
if (!virStorageFileIsInitialized(src)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("storage file backend not initialized"));
return NULL;
}
if (!src->drv->backend->storageFileGetUniqueIdentifier) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unique storage file identifier not implemented for "
"storage type %s (protocol: %s)'"),
virStorageTypeToString(src->type),
virStorageNetProtocolTypeToString(src->protocol));
return NULL;
}
return src->drv->backend->storageFileGetUniqueIdentifier(src);
}
/**
* virStorageFileAccess: Check accessibility of a storage file
*
* @src: storage file to check access permissions
* @mode: accessibility check options (see man 2 access)
*
* Returns 0 on success, -1 on error and sets errno. No libvirt
* error is reported. Returns -2 if the operation isn't supported
* by libvirt storage backend.
*/
int
virStorageFileAccess(virStorageSourcePtr src,
int mode)
{
if (!virStorageFileIsInitialized(src) ||
!src->drv->backend->storageFileAccess) {
errno = ENOSYS;
return -2;
}
return src->drv->backend->storageFileAccess(src, mode);
}
/**
* virStorageFileChown: Change owner of a storage file
*
* @src: storage file to change owner of
* @uid: new owner id
* @gid: new group id
*
* Returns 0 on success, -1 on error and sets errno. No libvirt
* error is reported. Returns -2 if the operation isn't supported
* by libvirt storage backend.
*/
int
virStorageFileChown(const virStorageSource *src,
uid_t uid,
gid_t gid)
{
if (!virStorageFileIsInitialized(src) ||
!src->drv->backend->storageFileChown) {
errno = ENOSYS;
return -2;
}
VIR_DEBUG("chown of storage file %p to %u:%u",
src, (unsigned int)uid, (unsigned int)gid);
return src->drv->backend->storageFileChown(src, uid, gid);
}
/**
* virStorageFileReportBrokenChain:
*
* @errcode: errno when accessing @src
* @src: inaccessible file in the backing chain of @parent
* @parent: root virStorageSource being checked
*
* Reports the correct error message if @src is missing in the backing chain
* for @parent.
*/
void
virStorageFileReportBrokenChain(int errcode,
virStorageSourcePtr src,
virStorageSourcePtr parent)
{
if (src->drv) {
unsigned int access_user = src->drv->uid;
unsigned int access_group = src->drv->gid;
if (src == parent) {
virReportSystemError(errcode,
_("Cannot access storage file '%s' "
"(as uid:%u, gid:%u)"),
src->path, access_user, access_group);
} else {
virReportSystemError(errcode,
_("Cannot access backing file '%s' "
"of storage file '%s' (as uid:%u, gid:%u)"),
src->path, parent->path, access_user, access_group);
}
} else {
if (src == parent) {
virReportSystemError(errcode,
_("Cannot access storage file '%s'"),
src->path);
} else {
virReportSystemError(errcode,
_("Cannot access backing file '%s' "
"of storage file '%s'"),
src->path, parent->path);
}
}
}
static int
virStorageFileGetMetadataRecurseReadHeader(virStorageSourcePtr src,
virStorageSourcePtr parent,
uid_t uid,
gid_t gid,
char **buf,
size_t *headerLen,
virHashTablePtr cycle)
{
int ret = -1;
const char *uniqueName;
ssize_t len;
if (virStorageFileInitAs(src, uid, gid) < 0)
return -1;
if (virStorageFileAccess(src, F_OK) < 0) {
virStorageFileReportBrokenChain(errno, src, parent);
goto cleanup;
}
if (!(uniqueName = virStorageFileGetUniqueIdentifier(src)))
goto cleanup;
if (virHashHasEntry(cycle, uniqueName)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("backing store for %s (%s) is self-referential"),
NULLSTR(src->path), uniqueName);
goto cleanup;
}
if (virHashAddEntry(cycle, uniqueName, NULL) < 0)
goto cleanup;
if ((len = virStorageFileRead(src, 0, VIR_STORAGE_MAX_HEADER, buf)) < 0)
goto cleanup;
*headerLen = len;
ret = 0;
cleanup:
virStorageFileDeinit(src);
return ret;
}
/* Recursive workhorse for virStorageFileGetMetadata. */
static int
virStorageFileGetMetadataRecurse(virStorageSourcePtr src,
virStorageSourcePtr parent,
uid_t uid, gid_t gid,
bool report_broken,
virHashTablePtr cycle,
unsigned int depth)
{
virStorageFileFormat orig_format = src->format;
size_t headerLen;
int rv;
g_autofree char *buf = NULL;
g_autoptr(virStorageSource) backingStore = NULL;
VIR_DEBUG("path=%s format=%d uid=%u gid=%u",
NULLSTR(src->path), src->format,
(unsigned int)uid, (unsigned int)gid);
if (src->format == VIR_STORAGE_FILE_AUTO_SAFE)
src->format = VIR_STORAGE_FILE_AUTO;
/* exit if we can't load information about the current image */
rv = virStorageFileSupportsBackingChainTraversal(src);
if (rv <= 0) {
if (orig_format == VIR_STORAGE_FILE_AUTO)
return -2;
return rv;
}
if (virStorageFileGetMetadataRecurseReadHeader(src, parent, uid, gid,
&buf, &headerLen, cycle) < 0)
return -1;
if (virStorageFileGetMetadataInternal(src, buf, headerLen) < 0)
return -1;
/* If we probed the format we MUST ensure that nothing else than the current
* image (this includes both backing files and external data store) is
* considered for security labelling and/or recursion. */
if (orig_format == VIR_STORAGE_FILE_AUTO) {
if (src->backingStoreRaw || src->externalDataStoreRaw) {
src->format = VIR_STORAGE_FILE_RAW;
VIR_FREE(src->backingStoreRaw);
VIR_FREE(src->externalDataStoreRaw);
return -2;
}
}
if (src->backingStoreRaw) {
if ((rv = virStorageSourceNewFromBacking(src, &backingStore)) < 0)
return -1;
/* the backing file would not be usable for VM usage */
if (rv == 1)
return 0;
if ((rv = virStorageFileGetMetadataRecurse(backingStore, parent,
uid, gid,
report_broken,
cycle, depth + 1)) < 0) {
if (!report_broken)
return 0;
if (rv == -2) {
virReportError(VIR_ERR_OPERATION_INVALID,
_("format of backing image '%s' of image '%s' was not specified in the image metadata "
"(See https://libvirt.org/kbase/backing_chains.html for troubleshooting)"),
src->backingStoreRaw, NULLSTR(src->path));
}
return -1;
}
backingStore->id = depth;
src->backingStore = g_steal_pointer(&backingStore);
} else {
/* add terminator */
if (!(src->backingStore = virStorageSourceNew()))
return -1;
}
if (src->externalDataStoreRaw) {
g_autoptr(virStorageSource) externalDataStore = NULL;
if ((rv = virStorageSourceNewFromExternalData(src,
&externalDataStore)) < 0)
return -1;
/* the file would not be usable for VM usage */
if (rv == 1)
return 0;
src->externalDataStore = g_steal_pointer(&externalDataStore);
}
return 0;
}
/**
* virStorageFileGetMetadata:
*
* Extract metadata about the storage volume with the specified
* image format. If image format is VIR_STORAGE_FILE_AUTO, it
* will probe to automatically identify the format. Recurses through
* the entire chain.
*
* Open files using UID and GID (or pass -1 for the current user/group).
* Treat any backing files without explicit type as raw, unless ALLOW_PROBE.
*
* Callers are advised never to use VIR_STORAGE_FILE_AUTO as a
* format, since a malicious guest can turn a raw file into any
* other non-raw format at will.
*
* If @report_broken is true, the whole function fails with a possibly sane
* error instead of just returning a broken chain. Note that the inability for
* libvirt to traverse a given source is not considered an error.
*
* Caller MUST free result after use via virObjectUnref.
*/
int
virStorageFileGetMetadata(virStorageSourcePtr src,
uid_t uid, gid_t gid,
bool report_broken)
{
VIR_DEBUG("path=%s format=%d uid=%u gid=%u report_broken=%d",
src->path, src->format, (unsigned int)uid, (unsigned int)gid,
report_broken);
virHashTablePtr cycle = NULL;
virStorageType actualType = virStorageSourceGetActualType(src);
int ret = -1;
if (!(cycle = virHashCreate(5, NULL)))
return -1;
if (src->format <= VIR_STORAGE_FILE_NONE) {
if (actualType == VIR_STORAGE_TYPE_DIR)
src->format = VIR_STORAGE_FILE_DIR;
else
src->format = VIR_STORAGE_FILE_RAW;
}
ret = virStorageFileGetMetadataRecurse(src, src, uid, gid,
report_broken, cycle, 1);
virHashFree(cycle);
return ret;
}
/**
* virStorageFileGetBackingStoreStr:
* @src: storage object
*
* Extracts the backing store string as stored in the storage volume described
* by @src and returns it to the user. Caller is responsible for freeing it.
* In case when the string can't be retrieved or does not exist NULL is
* returned.
*/
int
virStorageFileGetBackingStoreStr(virStorageSourcePtr src,
char **backing)
{
ssize_t headerLen;
int rv;
g_autofree char *buf = NULL;
g_autoptr(virStorageSource) tmp = NULL;
*backing = NULL;
/* exit if we can't load information about the current image */
if (!virStorageFileSupportsBackingChainTraversal(src))
return 0;
rv = virStorageFileAccess(src, F_OK);
if (rv == -2)
return 0;
if (rv < 0) {
virStorageFileReportBrokenChain(errno, src, src);
return -1;
}
if ((headerLen = virStorageFileRead(src, 0, VIR_STORAGE_MAX_HEADER,
&buf)) < 0) {
if (headerLen == -2)
return 0;
return -1;
}
if (!(tmp = virStorageSourceCopy(src, false)))
return -1;
if (virStorageFileGetMetadataInternal(tmp, buf, headerLen) < 0)
return -1;
*backing = g_steal_pointer(&tmp->backingStoreRaw);
return 0;
}