This code is not directly relevant to virStorageSource so move it to
separate file.
Signed-off-by: Pavel Hrdina <phrdina(a)redhat.com>
---
po/POTFILES.in | 1 +
src/libvirt_private.syms | 6 +-
src/qemu/qemu_driver.c | 1 +
src/storage/storage_backend_gluster.c | 1 +
src/storage/storage_util.c | 1 +
src/util/meson.build | 1 +
src/util/virstoragefile.c | 939 +------------------------
src/util/virstoragefile.h | 11 -
src/util/virstoragefileprobe.c | 967 ++++++++++++++++++++++++++
src/util/virstoragefileprobe.h | 44 ++
10 files changed, 1026 insertions(+), 946 deletions(-)
create mode 100644 src/util/virstoragefileprobe.c
create mode 100644 src/util/virstoragefileprobe.h
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e9fc3991f1..19eb15ada0 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -306,6 +306,7 @@
@SRCDIR(a)src/util/virstorageencryption.c
@SRCDIR(a)src/util/virstoragefile.c
@SRCDIR(a)src/util/virstoragefilebackend.c
+@SRCDIR(a)src/util/virstoragefileprobe.c
@SRCDIR(a)src/util/virstring.c
@SRCDIR(a)src/util/virsysinfo.c
@SRCDIR(a)src/util/virsystemd.c
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 84b650cb86..2dfc7e32d5 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -3148,7 +3148,6 @@ virStorageFileInit;
virStorageFileInitAs;
virStorageFileParseBackingStoreStr;
virStorageFileParseChainIndex;
-virStorageFileProbeFormat;
virStorageFileRead;
virStorageFileReportBrokenChain;
virStorageFileStat;
@@ -3212,6 +3211,11 @@ virStorageTypeToString;
virStorageFileBackendRegister;
+# util/virstoragefileprobe.h
+virStorageFileProbeFormat;
+virStorageFileProbeGetMetadata;
+
+
# util/virstring.h
virSkipSpaces;
virSkipSpacesAndBackslash;
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index 027617deef..34a8fbe233 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -83,6 +83,7 @@
#include "domain_nwfilter.h"
#include "virhook.h"
#include "virstoragefile.h"
+#include "virstoragefileprobe.h"
#include "virfile.h"
#include "virfdstream.h"
#include "configmake.h"
diff --git a/src/storage/storage_backend_gluster.c
b/src/storage/storage_backend_gluster.c
index 6c99c270da..205a707a17 100644
--- a/src/storage/storage_backend_gluster.c
+++ b/src/storage/storage_backend_gluster.c
@@ -28,6 +28,7 @@
#include "viralloc.h"
#include "virerror.h"
#include "virlog.h"
+#include "virstoragefileprobe.h"
#include "virstring.h"
#include "viruri.h"
#include "storage_util.h"
diff --git a/src/storage/storage_util.c b/src/storage/storage_util.c
index 2e2c7dc68a..4117127d65 100644
--- a/src/storage/storage_util.c
+++ b/src/storage/storage_util.c
@@ -62,6 +62,7 @@
#include "vircrypto.h"
#include "viruuid.h"
#include "virstoragefile.h"
+#include "virstoragefileprobe.h"
#include "storage_util.h"
#include "virlog.h"
#include "virfile.h"
diff --git a/src/util/meson.build b/src/util/meson.build
index 395e70fd38..9fb270fadd 100644
--- a/src/util/meson.build
+++ b/src/util/meson.build
@@ -91,6 +91,7 @@ util_sources = [
'virstorageencryption.c',
'virstoragefile.c',
'virstoragefilebackend.c',
+ 'virstoragefileprobe.c',
'virstring.c',
'virsysinfo.c',
'virsystemd.c',
diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c
index 13a86f34e5..98a3222d09 100644
--- a/src/util/virstoragefile.c
+++ b/src/util/virstoragefile.c
@@ -22,8 +22,6 @@
#include <config.h>
#include "virstoragefile.h"
-#include <unistd.h>
-#include <fcntl.h>
#include "viralloc.h"
#include "virxml.h"
#include "viruuid.h"
@@ -32,13 +30,13 @@
#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 "virstoragefilebackend.h"
+#include "virstoragefileprobe.h"
#include "virsecret.h"
#include "virutil.h"
@@ -113,650 +111,6 @@ VIR_ENUM_IMPL(virStorageAuth,
"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 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,
- },
- {
- .format = VIR_STORAGE_ENCRYPTION_FORMAT_LUKS,
-
- .magicOffset = 0,
- .magic = NULL,
- .endian = LV_BIG_ENDIAN,
-
- .versionOffset = -1,
- .versionSize = 0,
- .versionNumbers = {},
-
- .modeOffset = QCOW2_HDR_CRYPT,
- .modeValue = 2,
-
- .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)
-{
- 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;
-
- tmp = g_new0(char, len + 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_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;
- *res = g_new0(char, size + 1);
- memcpy(*res, buf + offset, size);
- (*res)[size] = '\0';
-
- if (qcow2GetExtensions(buf, buf_size, format) < 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;
-
- desc = g_new0(char, VIR_STORAGE_MAX_HEADER);
-
- *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;
- *res = g_new0(char, size + 1);
- 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)
@@ -792,289 +146,6 @@ virStorageIsRelative(const char *backing)
}
-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(a)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;
-
- feat = virBitmapNew(VIR_STORAGE_FILE_FEATURE_LAST);
-
- /* 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) {
- meta->encryption = g_new0(virStorageEncryption, 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->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)
@@ -1123,7 +194,7 @@ virStorageFileGetMetadataFromBuf(const char *path,
if (!(ret = virStorageFileMetadataNew(path, format)))
return NULL;
- if (virStorageFileGetMetadataInternal(ret, buf, len) < 0) {
+ if (virStorageFileProbeGetMetadata(ret, buf, len) < 0) {
virObjectUnref(ret);
return NULL;
}
@@ -1183,7 +254,7 @@ virStorageFileGetMetadataFromFD(const char *path,
return NULL;
}
- if (virStorageFileGetMetadataInternal(meta, buf, len) < 0)
+ if (virStorageFileProbeGetMetadata(meta, buf, len) < 0)
return NULL;
if (S_ISREG(sb.st_mode))
@@ -5149,7 +4220,7 @@ virStorageFileGetMetadataRecurse(virStorageSourcePtr src,
&buf, &headerLen, cycle) <
0)
return -1;
- if (virStorageFileGetMetadataInternal(src, buf, headerLen) < 0)
+ if (virStorageFileProbeGetMetadata(src, buf, headerLen) < 0)
return -1;
/* If we probed the format we MUST ensure that nothing else than the current
@@ -5292,7 +4363,7 @@ virStorageFileGetBackingStoreStr(virStorageSourcePtr src,
if (!(tmp = virStorageSourceCopy(src, false)))
return -1;
- if (virStorageFileGetMetadataInternal(tmp, buf, headerLen) < 0)
+ if (virStorageFileProbeGetMetadata(tmp, buf, headerLen) < 0)
return -1;
*backing = g_steal_pointer(&tmp->backingStoreRaw);
diff --git a/src/util/virstoragefile.h b/src/util/virstoragefile.h
index 46da6a8a18..27ac6a493f 100644
--- a/src/util/virstoragefile.h
+++ b/src/util/virstoragefile.h
@@ -31,15 +31,6 @@
#include "virenum.h"
#include "virpci.h"
-/* Minimum header size required to probe all known formats with
- * virStorageFileProbeFormat, or obtain metadata from a known format.
- * Rounded to multiple of 512 (ISO has a 5-byte magic at offset
- * 32769). Some formats can be probed with fewer bytes. Although
- * some formats theoretically permit metadata that can rely on offsets
- * beyond this size, in practice that doesn't matter. */
-#define VIR_STORAGE_MAX_HEADER 0x8200
-
-
/* Types of disk backends (host resource). Comparable to the public
* virStorageVolType, except we have an undetermined state, don't have
* a netdir type, and add a volume type for reference through a
@@ -401,8 +392,6 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(virStorageSource, virObjectUnref);
# define DEV_BSIZE 512
#endif
-int virStorageFileProbeFormat(const char *path, uid_t uid, gid_t gid);
-
virStorageSourcePtr virStorageFileGetMetadataFromFD(const char *path,
int fd,
int format);
diff --git a/src/util/virstoragefileprobe.c b/src/util/virstoragefileprobe.c
new file mode 100644
index 0000000000..bca098cd35
--- /dev/null
+++ b/src/util/virstoragefileprobe.c
@@ -0,0 +1,967 @@
+/*
+ * virstoragefileprobe.c: file utility functions for FS storage backend
+ *
+ * Copyright (C) 2007-2017 Red Hat, Inc.
+ * Copyright (C) 2007-2008 Daniel P. Berrange
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * <
http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "internal.h"
+#include "viralloc.h"
+#include "virbitmap.h"
+#include "virendian.h"
+#include "virfile.h"
+#include "virlog.h"
+#include "virstoragefile.h"
+#include "virstoragefileprobe.h"
+
+#define VIR_FROM_THIS VIR_FROM_STORAGE
+
+VIR_LOG_INIT("util.storagefileprobe");
+
+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 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,
+ },
+ {
+ .format = VIR_STORAGE_ENCRYPTION_FORMAT_LUKS,
+
+ .magicOffset = 0,
+ .magic = NULL,
+ .endian = LV_BIG_ENDIAN,
+
+ .versionOffset = -1,
+ .versionSize = 0,
+ .versionNumbers = {},
+
+ .modeOffset = QCOW2_HDR_CRYPT,
+ .modeValue = 2,
+
+ .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)
+{
+ 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;
+
+ tmp = g_new0(char, len + 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_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;
+ *res = g_new0(char, size + 1);
+ memcpy(*res, buf + offset, size);
+ (*res)[size] = '\0';
+
+ if (qcow2GetExtensions(buf, buf_size, format) < 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;
+
+ desc = g_new0(char, VIR_STORAGE_MAX_HEADER);
+
+ *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;
+ *res = g_new0(char, size + 1);
+ 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;
+}
+
+
+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(a)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;
+
+ feat = virBitmapNew(VIR_STORAGE_FILE_FEATURE_LAST);
+
+ /* 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.
+ */
+int
+virStorageFileProbeGetMetadata(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) {
+ meta->encryption = g_new0(virStorageEncryption, 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->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);
+}
diff --git a/src/util/virstoragefileprobe.h b/src/util/virstoragefileprobe.h
new file mode 100644
index 0000000000..2b94a4ae51
--- /dev/null
+++ b/src/util/virstoragefileprobe.h
@@ -0,0 +1,44 @@
+/*
+ * virstoragefileprobe.h: file utility functions for FS storage backend
+ *
+ * Copyright (C) 2007-2009, 2012-2016 Red Hat, Inc.
+ * Copyright (C) 2007-2008 Daniel P. Berrange
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * <
http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <sys/stat.h>
+
+#include "virstoragefile.h"
+
+/* Minimum header size required to probe all known formats with
+ * virStorageFileProbeFormat, or obtain metadata from a known format.
+ * Rounded to multiple of 512 (ISO has a 5-byte magic at offset
+ * 32769). Some formats can be probed with fewer bytes. Although
+ * some formats theoretically permit metadata that can rely on offsets
+ * beyond this size, in practice that doesn't matter. */
+#define VIR_STORAGE_MAX_HEADER 0x8200
+
+int
+virStorageFileProbeGetMetadata(virStorageSourcePtr meta,
+ char *buf,
+ size_t len);
+
+int
+virStorageFileProbeFormat(const char *path,
+ uid_t uid,
+ gid_t gid);
--
2.29.2