mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-08-27 11:01:19 +00:00
util: extract storage file probe code into virtstoragefileprobe.c
This code is not directly relevant to virStorageSource so move it to separate file. Signed-off-by: Pavel Hrdina <phrdina@redhat.com> Reviewed-by: Peter Krempa <pkrempa@redhat.com>
This commit is contained in:
parent
eaa0b3288e
commit
296032bfb2
@ -306,6 +306,7 @@
|
|||||||
@SRCDIR@src/util/virstorageencryption.c
|
@SRCDIR@src/util/virstorageencryption.c
|
||||||
@SRCDIR@src/util/virstoragefile.c
|
@SRCDIR@src/util/virstoragefile.c
|
||||||
@SRCDIR@src/util/virstoragefilebackend.c
|
@SRCDIR@src/util/virstoragefilebackend.c
|
||||||
|
@SRCDIR@src/util/virstoragefileprobe.c
|
||||||
@SRCDIR@src/util/virstring.c
|
@SRCDIR@src/util/virstring.c
|
||||||
@SRCDIR@src/util/virsysinfo.c
|
@SRCDIR@src/util/virsysinfo.c
|
||||||
@SRCDIR@src/util/virsystemd.c
|
@SRCDIR@src/util/virsystemd.c
|
||||||
|
@ -3148,7 +3148,6 @@ virStorageFileInit;
|
|||||||
virStorageFileInitAs;
|
virStorageFileInitAs;
|
||||||
virStorageFileParseBackingStoreStr;
|
virStorageFileParseBackingStoreStr;
|
||||||
virStorageFileParseChainIndex;
|
virStorageFileParseChainIndex;
|
||||||
virStorageFileProbeFormat;
|
|
||||||
virStorageFileRead;
|
virStorageFileRead;
|
||||||
virStorageFileReportBrokenChain;
|
virStorageFileReportBrokenChain;
|
||||||
virStorageFileStat;
|
virStorageFileStat;
|
||||||
@ -3212,6 +3211,11 @@ virStorageTypeToString;
|
|||||||
virStorageFileBackendRegister;
|
virStorageFileBackendRegister;
|
||||||
|
|
||||||
|
|
||||||
|
# util/virstoragefileprobe.h
|
||||||
|
virStorageFileProbeFormat;
|
||||||
|
virStorageFileProbeGetMetadata;
|
||||||
|
|
||||||
|
|
||||||
# util/virstring.h
|
# util/virstring.h
|
||||||
virSkipSpaces;
|
virSkipSpaces;
|
||||||
virSkipSpacesAndBackslash;
|
virSkipSpacesAndBackslash;
|
||||||
|
@ -83,6 +83,7 @@
|
|||||||
#include "domain_nwfilter.h"
|
#include "domain_nwfilter.h"
|
||||||
#include "virhook.h"
|
#include "virhook.h"
|
||||||
#include "virstoragefile.h"
|
#include "virstoragefile.h"
|
||||||
|
#include "virstoragefileprobe.h"
|
||||||
#include "virfile.h"
|
#include "virfile.h"
|
||||||
#include "virfdstream.h"
|
#include "virfdstream.h"
|
||||||
#include "configmake.h"
|
#include "configmake.h"
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
#include "viralloc.h"
|
#include "viralloc.h"
|
||||||
#include "virerror.h"
|
#include "virerror.h"
|
||||||
#include "virlog.h"
|
#include "virlog.h"
|
||||||
|
#include "virstoragefileprobe.h"
|
||||||
#include "virstring.h"
|
#include "virstring.h"
|
||||||
#include "viruri.h"
|
#include "viruri.h"
|
||||||
#include "storage_util.h"
|
#include "storage_util.h"
|
||||||
|
@ -62,6 +62,7 @@
|
|||||||
#include "vircrypto.h"
|
#include "vircrypto.h"
|
||||||
#include "viruuid.h"
|
#include "viruuid.h"
|
||||||
#include "virstoragefile.h"
|
#include "virstoragefile.h"
|
||||||
|
#include "virstoragefileprobe.h"
|
||||||
#include "storage_util.h"
|
#include "storage_util.h"
|
||||||
#include "virlog.h"
|
#include "virlog.h"
|
||||||
#include "virfile.h"
|
#include "virfile.h"
|
||||||
|
@ -91,6 +91,7 @@ util_sources = [
|
|||||||
'virstorageencryption.c',
|
'virstorageencryption.c',
|
||||||
'virstoragefile.c',
|
'virstoragefile.c',
|
||||||
'virstoragefilebackend.c',
|
'virstoragefilebackend.c',
|
||||||
|
'virstoragefileprobe.c',
|
||||||
'virstring.c',
|
'virstring.c',
|
||||||
'virsysinfo.c',
|
'virsysinfo.c',
|
||||||
'virsystemd.c',
|
'virsystemd.c',
|
||||||
|
@ -22,8 +22,6 @@
|
|||||||
#include <config.h>
|
#include <config.h>
|
||||||
#include "virstoragefile.h"
|
#include "virstoragefile.h"
|
||||||
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include "viralloc.h"
|
#include "viralloc.h"
|
||||||
#include "virxml.h"
|
#include "virxml.h"
|
||||||
#include "viruuid.h"
|
#include "viruuid.h"
|
||||||
@ -32,13 +30,13 @@
|
|||||||
#include "virfile.h"
|
#include "virfile.h"
|
||||||
#include "vircommand.h"
|
#include "vircommand.h"
|
||||||
#include "virhash.h"
|
#include "virhash.h"
|
||||||
#include "virendian.h"
|
|
||||||
#include "virstring.h"
|
#include "virstring.h"
|
||||||
#include "viruri.h"
|
#include "viruri.h"
|
||||||
#include "virbuffer.h"
|
#include "virbuffer.h"
|
||||||
#include "virjson.h"
|
#include "virjson.h"
|
||||||
#include "virstorageencryption.h"
|
#include "virstorageencryption.h"
|
||||||
#include "virstoragefilebackend.h"
|
#include "virstoragefilebackend.h"
|
||||||
|
#include "virstoragefileprobe.h"
|
||||||
#include "virsecret.h"
|
#include "virsecret.h"
|
||||||
#include "virutil.h"
|
#include "virutil.h"
|
||||||
|
|
||||||
@ -113,650 +111,6 @@ VIR_ENUM_IMPL(virStorageAuth,
|
|||||||
"none", "chap", "ceph",
|
"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
|
bool
|
||||||
virStorageIsFile(const char *backing)
|
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@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
|
static virStorageSourcePtr
|
||||||
virStorageFileMetadataNew(const char *path,
|
virStorageFileMetadataNew(const char *path,
|
||||||
int format)
|
int format)
|
||||||
@ -1123,7 +194,7 @@ virStorageFileGetMetadataFromBuf(const char *path,
|
|||||||
if (!(ret = virStorageFileMetadataNew(path, format)))
|
if (!(ret = virStorageFileMetadataNew(path, format)))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (virStorageFileGetMetadataInternal(ret, buf, len) < 0) {
|
if (virStorageFileProbeGetMetadata(ret, buf, len) < 0) {
|
||||||
virObjectUnref(ret);
|
virObjectUnref(ret);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@ -1183,7 +254,7 @@ virStorageFileGetMetadataFromFD(const char *path,
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (virStorageFileGetMetadataInternal(meta, buf, len) < 0)
|
if (virStorageFileProbeGetMetadata(meta, buf, len) < 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (S_ISREG(sb.st_mode))
|
if (S_ISREG(sb.st_mode))
|
||||||
@ -5149,7 +4220,7 @@ virStorageFileGetMetadataRecurse(virStorageSourcePtr src,
|
|||||||
&buf, &headerLen, cycle) < 0)
|
&buf, &headerLen, cycle) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (virStorageFileGetMetadataInternal(src, buf, headerLen) < 0)
|
if (virStorageFileProbeGetMetadata(src, buf, headerLen) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
/* If we probed the format we MUST ensure that nothing else than the current
|
/* 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)))
|
if (!(tmp = virStorageSourceCopy(src, false)))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
if (virStorageFileGetMetadataInternal(tmp, buf, headerLen) < 0)
|
if (virStorageFileProbeGetMetadata(tmp, buf, headerLen) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
*backing = g_steal_pointer(&tmp->backingStoreRaw);
|
*backing = g_steal_pointer(&tmp->backingStoreRaw);
|
||||||
|
@ -31,15 +31,6 @@
|
|||||||
#include "virenum.h"
|
#include "virenum.h"
|
||||||
#include "virpci.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
|
/* Types of disk backends (host resource). Comparable to the public
|
||||||
* virStorageVolType, except we have an undetermined state, don't have
|
* virStorageVolType, except we have an undetermined state, don't have
|
||||||
* a netdir type, and add a volume type for reference through a
|
* 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
|
# define DEV_BSIZE 512
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int virStorageFileProbeFormat(const char *path, uid_t uid, gid_t gid);
|
|
||||||
|
|
||||||
virStorageSourcePtr virStorageFileGetMetadataFromFD(const char *path,
|
virStorageSourcePtr virStorageFileGetMetadataFromFD(const char *path,
|
||||||
int fd,
|
int fd,
|
||||||
int format);
|
int format);
|
||||||
|
967
src/util/virstoragefileprobe.c
Normal file
967
src/util/virstoragefileprobe.c
Normal file
@ -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@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);
|
||||||
|
}
|
44
src/util/virstoragefileprobe.h
Normal file
44
src/util/virstoragefileprobe.h
Normal file
@ -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);
|
Loading…
Reference in New Issue
Block a user