From nobody Mon Feb 9 09:29:15 2026 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of redhat.com designates 63.128.21.124 as permitted sender) client-ip=63.128.21.124; envelope-from=libvir-list-bounces@redhat.com; helo=us-smtp-delivery-124.mimecast.com; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 63.128.21.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1611257773; cv=none; d=zohomail.com; s=zohoarc; b=eZqOdrxvEhv46iUC/DLpSEeAaJmfNhYO9aacbh7kQYsL/Es28cE0aFamTlA5V7yBVpWndbmsczU/rHGz4+7CFaJD9KuBGV5Obe08abPxDcNpxnXlISh2BujHKzOgVeYi5vyg6tZidlaDji66Z+HnE2JpjwkAA/Jbps8pfmfZPU4= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1611257773; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=1Y/FSLXYCh9NrgWfjlpq4mI8tvpQ2m+4/zsvz+1Gtzg=; b=f9zSm8hTEOTz/52ga2Ry0QcAbG9tRpOOIYTTHiznPc8117re3WVGG1V5j6jlNhAOFN4O4L39oKStplI22kfQIo2ghuH22zLCrC3HuPARERFoeqtxIfzF8O7xpo2j27Qzk8ZPpGoeM9D5iUvQI/ZF3XTwDRzj2IxTaxWcMWRlGUY= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 63.128.21.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass header.from= (p=none dis=none) header.from= Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [63.128.21.124]) by mx.zohomail.com with SMTPS id 1611257773654715.6192740668532; Thu, 21 Jan 2021 11:36:13 -0800 (PST) Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-17-fe3z9Kv6P12b-d0U7gS5vQ-1; Thu, 21 Jan 2021 14:34:55 -0500 Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.phx2.redhat.com [10.5.11.15]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 20844B810C; Thu, 21 Jan 2021 19:34:49 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id EBB0A2C318; Thu, 21 Jan 2021 19:34:48 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id AAC7E1809CA7; Thu, 21 Jan 2021 19:34:48 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 10LJYeMn023196 for ; Thu, 21 Jan 2021 14:34:40 -0500 Received: by smtp.corp.redhat.com (Postfix) id 6B42A1002382; Thu, 21 Jan 2021 19:34:40 +0000 (UTC) Received: from antique-laptop.redhat.com (unknown [10.40.208.8]) by smtp.corp.redhat.com (Postfix) with ESMTP id 6B7B310021AA for ; Thu, 21 Jan 2021 19:34:39 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1611257772; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:list-id:list-help: list-unsubscribe:list-subscribe:list-post; bh=1Y/FSLXYCh9NrgWfjlpq4mI8tvpQ2m+4/zsvz+1Gtzg=; b=aqf+4gV33CYnMp/Xd7fL0kAa/kXltHmfBEFaFikBpfV7kqwwBywYcLKXiwqF2Wrex7pecC tsMZMZdQElrfp3EPs64bTRV1Kd9y2YDo3+y2beu8bF9OPa2YvWejmf1LswI3SPJC73AcZ8 Uqmlzl+uanQWHrobfQqaBGkd8Y7em/k= X-MC-Unique: fe3z9Kv6P12b-d0U7gS5vQ-1 From: Pavel Hrdina To: libvir-list@redhat.com Subject: [libvirt PATCH v2 06/13] util: extract storage file probe code into virtstoragefileprobe.c Date: Thu, 21 Jan 2021 20:34:20 +0100 Message-Id: In-Reply-To: References: MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 X-loop: libvir-list@redhat.com X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.15 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=libvir-list-bounces@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: quoted-printable X-ZohoMail-DKIM: pass (identity @redhat.com) Content-Type: text/plain; charset="utf-8" This code is not directly relevant to virStorageSource so move it to separate file. Signed-off-by: Pavel Hrdina Reviewed-by: Peter Krempa --- 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@src/util/virstorageencryption.c @SRCDIR@src/util/virstoragefile.c @SRCDIR@src/util/virstoragefilebackend.c +@SRCDIR@src/util/virstoragefileprobe.c @SRCDIR@src/util/virstring.c @SRCDIR@src/util/virsysinfo.c @SRCDIR@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; =20 =20 +# 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_ba= ckend_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 =3D [ '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 #include "virstoragefile.h" =20 -#include -#include #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" =20 @@ -113,650 +111,6 @@ VIR_ENUM_IMPL(virStorageAuth, "none", "chap", "ceph", ); =20 -enum lv_endian { - LV_LITTLE_ENDIAN =3D 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 ignor= ed. */ - - 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 sec= tors) */ -}; - -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 ignor= ed. */ - 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 relativ= e), - * 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[] =3D { - { - .format =3D VIR_STORAGE_ENCRYPTION_FORMAT_LUKS, - - /* Magic is 'L','U','K','S', 0xBA, 0xBE */ - .magicOffset =3D 0, - .magic =3D "\x4c\x55\x4b\x53\xba\xbe", - .endian =3D LV_BIG_ENDIAN, - - .versionOffset =3D LUKS_HDR_VERSION_OFFSET, - .versionSize =3D LUKS_HDR_VERSION_LEN, - .versionNumbers =3D {1}, - - .modeOffset =3D -1, - .modeValue =3D -1, - - .payloadOffset =3D LUKS_HDR_PAYLOAD_OFFSET, - }, - { 0 } -}; - -static struct FileEncryptionInfo const qcow1EncryptionInfo[] =3D { - { - .format =3D VIR_STORAGE_ENCRYPTION_FORMAT_QCOW, - - .magicOffset =3D 0, - .magic =3D NULL, - .endian =3D LV_BIG_ENDIAN, - - .versionOffset =3D -1, - .versionSize =3D 0, - .versionNumbers =3D {}, - - .modeOffset =3D QCOW1_HDR_CRYPT, - .modeValue =3D 1, - - .payloadOffset =3D -1, - }, - { 0 } -}; - -static struct FileEncryptionInfo const qcow2EncryptionInfo[] =3D { - { - .format =3D VIR_STORAGE_ENCRYPTION_FORMAT_QCOW, - - .magicOffset =3D 0, - .magic =3D NULL, - .endian =3D LV_BIG_ENDIAN, - - .versionOffset =3D -1, - .versionSize =3D 0, - .versionNumbers =3D {}, - - .modeOffset =3D QCOW2_HDR_CRYPT, - .modeValue =3D 1, - - .payloadOffset =3D -1, - }, - { - .format =3D VIR_STORAGE_ENCRYPTION_FORMAT_LUKS, - - .magicOffset =3D 0, - .magic =3D NULL, - .endian =3D LV_BIG_ENDIAN, - - .versionOffset =3D -1, - .versionSize =3D 0, - .versionNumbers =3D {}, - - .modeOffset =3D QCOW2_HDR_CRYPT, - .modeValue =3D 2, - - .payloadOffset =3D -1, - }, - { 0 } -}; - -static struct FileTypeInfo const fileTypeInfo[] =3D { - [VIR_STORAGE_FILE_NONE] =3D { 0, NULL, LV_LITTLE_ENDIAN, - -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL }, - [VIR_STORAGE_FILE_RAW] =3D { 0, NULL, LV_LITTLE_ENDIAN, - -1, 0, {0}, 0, 0, 0, - luksEncryptionInfo, - NULL, NULL }, - [VIR_STORAGE_FILE_DIR] =3D { 0, NULL, LV_LITTLE_ENDIAN, - -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL }, - [VIR_STORAGE_FILE_BOCHS] =3D { - /*"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] =3D { - /* #!/bin/sh - #V2.0 Format - modprobe cloop file=3D$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] =3D { - /* 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] =3D { - 32769, "CD001", - LV_LITTLE_ENDIAN, -2, 0, {0}, - -1, 0, 0, NULL, NULL, NULL - }, - [VIR_STORAGE_FILE_VPC] =3D { - 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] =3D { - 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] =3D { 0, NULL, LV_LITTLE_ENDIAN, - -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL }, - [VIR_STORAGE_FILE_VHD] =3D { 0, NULL, LV_LITTLE_ENDIAN, - -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL }, - [VIR_STORAGE_FILE_PLOOP] =3D { 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] =3D { - 0, "OOOM", - LV_BIG_ENDIAN, 4, 4, {2}, - 4+4+1024+4, 8, 1, NULL, cowGetBackingStore, NULL - }, - [VIR_STORAGE_FILE_QCOW] =3D { - 0, "QFI", - LV_BIG_ENDIAN, 4, 4, {1}, - QCOWX_HDR_IMAGE_SIZE, 8, 1, - qcow1EncryptionInfo, - qcowXGetBackingStore, NULL - }, - [VIR_STORAGE_FILE_QCOW2] =3D { - 0, "QFI", - LV_BIG_ENDIAN, 4, 4, {2, 3}, - QCOWX_HDR_IMAGE_SIZE, 8, 1, - qcow2EncryptionInfo, - qcowXGetBackingStore, - qcow2GetFeatures - }, - [VIR_STORAGE_FILE_QED] =3D { - /* 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] =3D { - 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) =3D=3D VIR_STORAGE_FILE_LAST); - - -/* qcow2 compatible features in the order they appear on-disk */ -enum qcow2CompatibleFeature { - QCOW2_COMPATIBLE_FEATURE_LAZY_REFCOUNTS =3D 0, - - QCOW2_COMPATIBLE_FEATURE_LAST -}; - -/* conversion to virStorageFileFeature */ -static const int qcow2CompatibleFeatureArray[] =3D { - VIR_STORAGE_FILE_FEATURE_LAZY_REFCOUNTS, -}; -G_STATIC_ASSERT(G_N_ELEMENTS(qcow2CompatibleFeatureArray) =3D=3D - QCOW2_COMPATIBLE_FEATURE_LAST); - -static int -cowGetBackingStore(char **res, - int *format, - const char *buf, - size_t buf_size) -{ -#define COW_FILENAME_MAXLEN 1024 - *res =3D NULL; - *format =3D VIR_STORAGE_FILE_AUTO; - - if (buf_size < 4+4+ COW_FILENAME_MAXLEN) - return BACKING_STORE_INVALID; - if (buf[4+4] =3D=3D '\0') { /* cow_header_v2.backing_file[0] */ - *format =3D VIR_STORAGE_FILE_NONE; - return BACKING_STORE_OK; - } - - *res =3D 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 =3D virReadBufInt32BE(buf + QCOWX_HDR_VERSION); - - if (version < 2) { - /* QCow1 doesn't have the extensions capability - * used to store backing format */ - return 0; - } - - if (version =3D=3D 2) - extension_start =3D QCOW2_HDR_TOTAL_SIZE; - else - extension_start =3D 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 =3D virReadBufInt64BE(buf + QCOWX_HDR_BACKING_FILE_OFFSE= T); - 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 =3D extension_start; - while (offset < (buf_size-8) && - offset < (extension_end-8)) { - unsigned int magic =3D virReadBufInt32BE(buf + offset); - unsigned int len =3D virReadBufInt32BE(buf + offset + 4); - - offset +=3D 8; - - if ((offset + len) < offset) - break; - - if ((offset + len) > buf_size) - break; - - switch (magic) { - case QCOW2_HDR_EXTENSION_BACKING_FORMAT: { - g_autofree char *tmp =3D NULL; - if (!backingFormat) - break; - - tmp =3D g_new0(char, len + 1); - memcpy(tmp, buf + offset, len); - tmp[len] =3D '\0'; - - *backingFormat =3D virStorageFileFormatTypeFromString(tmp); - if (*backingFormat <=3D VIR_STORAGE_FILE_NONE) - return -1; - break; - } - - case QCOW2_HDR_EXTENSION_END: - return 0; - } - - offset +=3D 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 =3D NULL; - *format =3D VIR_STORAGE_FILE_AUTO; - - if (buf_size < QCOWX_HDR_BACKING_FILE_OFFSET+8+4) - return BACKING_STORE_INVALID; - - offset =3D virReadBufInt64BE(buf + QCOWX_HDR_BACKING_FILE_OFFSET); - if (offset > buf_size) - return BACKING_STORE_INVALID; - - if (offset =3D=3D 0) { - *format =3D VIR_STORAGE_FILE_NONE; - return BACKING_STORE_OK; - } - - size =3D virReadBufInt32BE(buf + QCOWX_HDR_BACKING_FILE_SIZE); - if (size =3D=3D 0) { - *format =3D 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 =3D g_new0(char, size + 1); - memcpy(*res, buf + offset, size); - (*res)[size] =3D '\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[] =3D "parentFileNameHint=3D\""; - char *start, *end; - size_t len; - g_autofree char *desc =3D NULL; - - desc =3D g_new0(char, VIR_STORAGE_MAX_HEADER); - - *res =3D 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 =3D VIR_STORAGE_FILE_AUTO; - - if (buf_size <=3D 0x200) - return BACKING_STORE_INVALID; - - len =3D buf_size - 0x200; - if (len > VIR_STORAGE_MAX_HEADER) - len =3D VIR_STORAGE_MAX_HEADER; - memcpy(desc, buf + 0x200, len); - desc[len] =3D '\0'; - start =3D strstr(desc, prefix); - if (start =3D=3D NULL) { - *format =3D VIR_STORAGE_FILE_NONE; - return BACKING_STORE_OK; - } - start +=3D strlen(prefix); - end =3D strchr(start, '"'); - if (end =3D=3D NULL) - return BACKING_STORE_INVALID; - - if (end =3D=3D start) { - *format =3D VIR_STORAGE_FILE_NONE; - return BACKING_STORE_OK; - } - *end =3D '\0'; - *res =3D 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 =3D NULL; - /* Check if this image has a backing file */ - if (buf_size < QED_HDR_FEATURES_OFFSET+8) - return BACKING_STORE_INVALID; - flags =3D virReadBufInt64LE(buf + QED_HDR_FEATURES_OFFSET); - if (!(flags & QED_F_BACKING_FILE)) { - *format =3D 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 =3D virReadBufInt32LE(buf + QED_HDR_BACKING_FILE_OFFSET); - if (offset > buf_size) - return BACKING_STORE_INVALID; - size =3D virReadBufInt32LE(buf + QED_HDR_BACKING_FILE_SIZE); - if (size =3D=3D 0) - return BACKING_STORE_OK; - if (offset + size > buf_size || offset + size < offset) - return BACKING_STORE_INVALID; - *res =3D g_new0(char, size + 1); - memcpy(*res, buf + offset, size); - (*res)[size] =3D '\0'; - - if (flags & QED_F_BACKING_FORMAT_NO_PROBE) - *format =3D VIR_STORAGE_FILE_RAW; - else - *format =3D 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 =3D=3D NULL) - return false; - - /* Validate magic data */ - mlen =3D strlen(magic); - if (magicOffset + mlen > buflen) - return false; - - if (memcmp(buf + magicOffset, magic, mlen) !=3D 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 =3D=3D -1) - return false; - - /* -2 =3D=3D non-versioned file format, so trivially match */ - if (versionOffset =3D=3D -2) - return true; - - /* A positive versionOffset, requires using a valid versionSize */ - if (versionSize !=3D 2 && versionSize !=3D 4) - return false; - - if ((versionOffset + versionSize) > buflen) - return false; - - if (endian =3D=3D LV_LITTLE_ENDIAN) { - if (versionSize =3D=3D 4) - version =3D virReadBufInt32LE(buf + - versionOffset); - else - version =3D virReadBufInt16LE(buf + - versionOffset); - } else { - if (versionSize =3D=3D 4) - version =3D virReadBufInt32BE(buf + - versionOffset); - else - version =3D virReadBufInt16BE(buf + - versionOffset); - } - - for (i =3D 0; - i < FILE_TYPE_VERSIONS_LAST && versionNumbers[i]; - i++) { - VIR_DEBUG("Compare detected version %d vs one of the expected vers= ions %d", - version, versionNumbers[i]); - if (version =3D=3D versionNumbers[i]) - return true; - } - - return false; -} =20 bool virStorageIsFile(const char *backing) @@ -792,289 +146,6 @@ virStorageIsRelative(const char *backing) } =20 =20 -static int -virStorageFileProbeFormatFromBuf(const char *path, - char *buf, - size_t buflen) -{ - int format =3D VIR_STORAGE_FILE_RAW; - size_t i; - int possibleFormat =3D VIR_STORAGE_FILE_RAW; - VIR_DEBUG("path=3D%s, buf=3D%p, buflen=3D%zu", path, buf, buflen); - - /* First check file magic */ - for (i =3D 0; i < VIR_STORAGE_FILE_LAST; i++) { - if (virStorageFileMatchesMagic(fileTypeInfo[i].magicOffset, - fileTypeInfo[i].magic, - buf, buflen)) { - if (!virStorageFileMatchesVersion(fileTypeInfo[i].versionOffse= t, - fileTypeInfo[i].versionSize, - fileTypeInfo[i].versionNumbe= rs, - fileTypeInfo[i].endian, - buf, buflen)) { - possibleFormat =3D i; - continue; - } - format =3D i; - goto cleanup; - } - } - - if (possibleFormat !=3D 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=3D%d", format); - return format; -} - - -static int -qcow2GetFeatures(virBitmapPtr *features, - int format, - char *buf, - ssize_t len) -{ - int version =3D -1; - virBitmapPtr feat =3D NULL; - uint64_t bits; - size_t i; - - version =3D virReadBufInt32BE(buf + fileTypeInfo[format].versionOffset= ); - - if (version =3D=3D 2) - return 0; - - if (len < QCOW2v3_HDR_SIZE) - return -1; - - feat =3D virBitmapNew(VIR_STORAGE_FILE_FEATURE_LAST); - - /* todo: check for incompatible or autoclear features? */ - bits =3D virReadBufInt64BE(buf + QCOW2v3_HDR_FEATURES_COMPATIBLE); - for (i =3D 0; i < QCOW2_COMPATIBLE_FEATURE_LAST; i++) { - if (bits & ((uint64_t) 1 << i)) - ignore_value(virBitmapSetBit(feat, qcow2CompatibleFeatureArray= [i])); - } - - *features =3D feat; - return 0; -} - - -static bool -virStorageFileHasEncryptionFormat(const struct FileEncryptionInfo *info, - char *buf, - size_t len) -{ - if (!info->magic && info->modeOffset =3D=3D -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 !=3D -1 && - !virStorageFileMatchesVersion(info->versionOffset, - info->versionSize, - info->versionNumbers, - info->endian, - buf, len)) - return false; - - return true; - } else if (info->modeOffset !=3D -1) { - int crypt_format; - - if (info->modeOffset >=3D len) - return false; - - crypt_format =3D virReadBufInt32BE(buf + info->modeOffset); - if (crypt_format !=3D info->modeValue) - return false; - - return true; - } else { - return false; - } -} - - -static int -virStorageFileGetEncryptionPayloadOffset(const struct FileEncryptionInfo *= info, - char *buf) -{ - int payload_offset =3D -1; - - if (info->payloadOffset !=3D -1) { - if (info->endian =3D=3D LV_LITTLE_ENDIAN) - payload_offset =3D virReadBufInt32LE(buf + info->payloadOffset= ); - else - payload_offset =3D 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=3D%s, buf=3D%p, len=3D%zu, meta->format=3D%d", - meta->path, buf, len, meta->format); - - if (meta->format =3D=3D VIR_STORAGE_FILE_AUTO) - meta->format =3D virStorageFileProbeFormatFromBuf(meta->path, buf,= len); - - if (meta->format <=3D VIR_STORAGE_FILE_NONE || - meta->format >=3D VIR_STORAGE_FILE_LAST) { - virReportSystemError(EINVAL, _("unknown storage file meta->format = %d"), - meta->format); - return -1; - } - - if (fileTypeInfo[meta->format].cryptInfo !=3D NULL) { - for (i =3D 0; fileTypeInfo[meta->format].cryptInfo[i].format !=3D = 0; i++) { - if (virStorageFileHasEncryptionFormat(&fileTypeInfo[meta->form= at].cryptInfo[i], - buf, len)) { - int expt_fmt =3D fileTypeInfo[meta->format].cryptInfo[i].f= ormat; - if (!meta->encryption) { - meta->encryption =3D g_new0(virStorageEncryption, 1); - meta->encryption->format =3D expt_fmt; - } else { - if (meta->encryption->format !=3D expt_fmt) { - virReportError(VIR_ERR_XML_ERROR, - _("encryption format %d doesn't mat= ch " - "expected format %d"), - meta->encryption->format, expt_fmt); - return -1; - } - } - meta->encryption->payload_offset =3D - 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 !=3D -1) { - if ((fileTypeInfo[meta->format].sizeOffset + 8) > len) - return 0; - - if (fileTypeInfo[meta->format].endian =3D=3D LV_LITTLE_ENDIAN) - meta->capacity =3D virReadBufInt64LE(buf + - fileTypeInfo[meta->format].= sizeOffset); - else - meta->capacity =3D 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 *=3D fileTypeInfo[meta->format].sizeMultiplier; - } - - VIR_FREE(meta->backingStoreRaw); - if (fileTypeInfo[meta->format].getBackingStore !=3D NULL) { - int store =3D fileTypeInfo[meta->format].getBackingStore(&meta->ba= ckingStoreRaw, - &format, - buf, len); - meta->backingStoreRawFormat =3D format; - - if (store =3D=3D BACKING_STORE_INVALID) - return 0; - - if (store =3D=3D BACKING_STORE_ERROR) - return -1; - } - - virBitmapFree(meta->features); - meta->features =3D NULL; - if (fileTypeInfo[meta->format].getFeatures !=3D NULL && - fileTypeInfo[meta->format].getFeatures(&meta->features, meta->form= at, buf, len) < 0) - return -1; - - VIR_FREE(meta->compat); - if (meta->format =3D=3D VIR_STORAGE_FILE_QCOW2 && meta->features) - meta->compat =3D 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 =3D VIR_STORAGE_MAX_HEADER; - VIR_AUTOCLOSE fd =3D -1; - g_autofree char *header =3D NULL; - - if ((fd =3D 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) =3D=3D (off_t)-1) { - virReportSystemError(errno, _("cannot set to start of '%s'"), path= ); - return -1; - } - - if ((len =3D 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 =3D virStorageFileMetadataNew(path, format))) return NULL; =20 - 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; } =20 - if (virStorageFileGetMetadataInternal(meta, buf, len) < 0) + if (virStorageFileProbeGetMetadata(meta, buf, len) < 0) return NULL; =20 if (S_ISREG(sb.st_mode)) @@ -5149,7 +4220,7 @@ virStorageFileGetMetadataRecurse(virStorageSourcePtr = src, &buf, &headerLen, cycle= ) < 0) return -1; =20 - if (virStorageFileGetMetadataInternal(src, buf, headerLen) < 0) + if (virStorageFileProbeGetMetadata(src, buf, headerLen) < 0) return -1; =20 /* If we probed the format we MUST ensure that nothing else than the c= urrent @@ -5292,7 +4363,7 @@ virStorageFileGetBackingStoreStr(virStorageSourcePtr = src, if (!(tmp =3D virStorageSourceCopy(src, false))) return -1; =20 - if (virStorageFileGetMetadataInternal(tmp, buf, headerLen) < 0) + if (virStorageFileProbeGetMetadata(tmp, buf, headerLen) < 0) return -1; =20 *backing =3D 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" =20 -/* 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, virObje= ctUnref); # define DEV_BSIZE 512 #endif =20 -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 + * . + */ + +#include + +#include +#include +#include + +#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 =3D 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 ignor= ed. */ + + 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 sec= tors) */ +}; + +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 ignor= ed. */ + 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 relativ= e), + * 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[] =3D { + { + .format =3D VIR_STORAGE_ENCRYPTION_FORMAT_LUKS, + + /* Magic is 'L','U','K','S', 0xBA, 0xBE */ + .magicOffset =3D 0, + .magic =3D "\x4c\x55\x4b\x53\xba\xbe", + .endian =3D LV_BIG_ENDIAN, + + .versionOffset =3D LUKS_HDR_VERSION_OFFSET, + .versionSize =3D LUKS_HDR_VERSION_LEN, + .versionNumbers =3D {1}, + + .modeOffset =3D -1, + .modeValue =3D -1, + + .payloadOffset =3D LUKS_HDR_PAYLOAD_OFFSET, + }, + { 0 } +}; + +static struct FileEncryptionInfo const qcow1EncryptionInfo[] =3D { + { + .format =3D VIR_STORAGE_ENCRYPTION_FORMAT_QCOW, + + .magicOffset =3D 0, + .magic =3D NULL, + .endian =3D LV_BIG_ENDIAN, + + .versionOffset =3D -1, + .versionSize =3D 0, + .versionNumbers =3D {}, + + .modeOffset =3D QCOW1_HDR_CRYPT, + .modeValue =3D 1, + + .payloadOffset =3D -1, + }, + { 0 } +}; + +static struct FileEncryptionInfo const qcow2EncryptionInfo[] =3D { + { + .format =3D VIR_STORAGE_ENCRYPTION_FORMAT_QCOW, + + .magicOffset =3D 0, + .magic =3D NULL, + .endian =3D LV_BIG_ENDIAN, + + .versionOffset =3D -1, + .versionSize =3D 0, + .versionNumbers =3D {}, + + .modeOffset =3D QCOW2_HDR_CRYPT, + .modeValue =3D 1, + + .payloadOffset =3D -1, + }, + { + .format =3D VIR_STORAGE_ENCRYPTION_FORMAT_LUKS, + + .magicOffset =3D 0, + .magic =3D NULL, + .endian =3D LV_BIG_ENDIAN, + + .versionOffset =3D -1, + .versionSize =3D 0, + .versionNumbers =3D {}, + + .modeOffset =3D QCOW2_HDR_CRYPT, + .modeValue =3D 2, + + .payloadOffset =3D -1, + }, + { 0 } +}; + +static struct FileTypeInfo const fileTypeInfo[] =3D { + [VIR_STORAGE_FILE_NONE] =3D { 0, NULL, LV_LITTLE_ENDIAN, + -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL }, + [VIR_STORAGE_FILE_RAW] =3D { 0, NULL, LV_LITTLE_ENDIAN, + -1, 0, {0}, 0, 0, 0, + luksEncryptionInfo, + NULL, NULL }, + [VIR_STORAGE_FILE_DIR] =3D { 0, NULL, LV_LITTLE_ENDIAN, + -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL }, + [VIR_STORAGE_FILE_BOCHS] =3D { + /*"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] =3D { + /* #!/bin/sh + #V2.0 Format + modprobe cloop file=3D$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] =3D { + /* 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] =3D { + 32769, "CD001", + LV_LITTLE_ENDIAN, -2, 0, {0}, + -1, 0, 0, NULL, NULL, NULL + }, + [VIR_STORAGE_FILE_VPC] =3D { + 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] =3D { + 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] =3D { 0, NULL, LV_LITTLE_ENDIAN, + -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL }, + [VIR_STORAGE_FILE_VHD] =3D { 0, NULL, LV_LITTLE_ENDIAN, + -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL }, + [VIR_STORAGE_FILE_PLOOP] =3D { 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] =3D { + 0, "OOOM", + LV_BIG_ENDIAN, 4, 4, {2}, + 4+4+1024+4, 8, 1, NULL, cowGetBackingStore, NULL + }, + [VIR_STORAGE_FILE_QCOW] =3D { + 0, "QFI", + LV_BIG_ENDIAN, 4, 4, {1}, + QCOWX_HDR_IMAGE_SIZE, 8, 1, + qcow1EncryptionInfo, + qcowXGetBackingStore, NULL + }, + [VIR_STORAGE_FILE_QCOW2] =3D { + 0, "QFI", + LV_BIG_ENDIAN, 4, 4, {2, 3}, + QCOWX_HDR_IMAGE_SIZE, 8, 1, + qcow2EncryptionInfo, + qcowXGetBackingStore, + qcow2GetFeatures + }, + [VIR_STORAGE_FILE_QED] =3D { + /* 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] =3D { + 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) =3D=3D VIR_STORAGE_FILE_LAST); + + +/* qcow2 compatible features in the order they appear on-disk */ +enum qcow2CompatibleFeature { + QCOW2_COMPATIBLE_FEATURE_LAZY_REFCOUNTS =3D 0, + + QCOW2_COMPATIBLE_FEATURE_LAST +}; + +/* conversion to virStorageFileFeature */ +static const int qcow2CompatibleFeatureArray[] =3D { + VIR_STORAGE_FILE_FEATURE_LAZY_REFCOUNTS, +}; +G_STATIC_ASSERT(G_N_ELEMENTS(qcow2CompatibleFeatureArray) =3D=3D + QCOW2_COMPATIBLE_FEATURE_LAST); + +static int +cowGetBackingStore(char **res, + int *format, + const char *buf, + size_t buf_size) +{ +#define COW_FILENAME_MAXLEN 1024 + *res =3D NULL; + *format =3D VIR_STORAGE_FILE_AUTO; + + if (buf_size < 4+4+ COW_FILENAME_MAXLEN) + return BACKING_STORE_INVALID; + if (buf[4+4] =3D=3D '\0') { /* cow_header_v2.backing_file[0] */ + *format =3D VIR_STORAGE_FILE_NONE; + return BACKING_STORE_OK; + } + + *res =3D 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 =3D virReadBufInt32BE(buf + QCOWX_HDR_VERSION); + + if (version < 2) { + /* QCow1 doesn't have the extensions capability + * used to store backing format */ + return 0; + } + + if (version =3D=3D 2) + extension_start =3D QCOW2_HDR_TOTAL_SIZE; + else + extension_start =3D 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 =3D virReadBufInt64BE(buf + QCOWX_HDR_BACKING_FILE_OFFSE= T); + 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 =3D extension_start; + while (offset < (buf_size-8) && + offset < (extension_end-8)) { + unsigned int magic =3D virReadBufInt32BE(buf + offset); + unsigned int len =3D virReadBufInt32BE(buf + offset + 4); + + offset +=3D 8; + + if ((offset + len) < offset) + break; + + if ((offset + len) > buf_size) + break; + + switch (magic) { + case QCOW2_HDR_EXTENSION_BACKING_FORMAT: { + g_autofree char *tmp =3D NULL; + if (!backingFormat) + break; + + tmp =3D g_new0(char, len + 1); + memcpy(tmp, buf + offset, len); + tmp[len] =3D '\0'; + + *backingFormat =3D virStorageFileFormatTypeFromString(tmp); + if (*backingFormat <=3D VIR_STORAGE_FILE_NONE) + return -1; + break; + } + + case QCOW2_HDR_EXTENSION_END: + return 0; + } + + offset +=3D 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 =3D NULL; + *format =3D VIR_STORAGE_FILE_AUTO; + + if (buf_size < QCOWX_HDR_BACKING_FILE_OFFSET+8+4) + return BACKING_STORE_INVALID; + + offset =3D virReadBufInt64BE(buf + QCOWX_HDR_BACKING_FILE_OFFSET); + if (offset > buf_size) + return BACKING_STORE_INVALID; + + if (offset =3D=3D 0) { + *format =3D VIR_STORAGE_FILE_NONE; + return BACKING_STORE_OK; + } + + size =3D virReadBufInt32BE(buf + QCOWX_HDR_BACKING_FILE_SIZE); + if (size =3D=3D 0) { + *format =3D 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 =3D g_new0(char, size + 1); + memcpy(*res, buf + offset, size); + (*res)[size] =3D '\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[] =3D "parentFileNameHint=3D\""; + char *start, *end; + size_t len; + g_autofree char *desc =3D NULL; + + desc =3D g_new0(char, VIR_STORAGE_MAX_HEADER); + + *res =3D 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 =3D VIR_STORAGE_FILE_AUTO; + + if (buf_size <=3D 0x200) + return BACKING_STORE_INVALID; + + len =3D buf_size - 0x200; + if (len > VIR_STORAGE_MAX_HEADER) + len =3D VIR_STORAGE_MAX_HEADER; + memcpy(desc, buf + 0x200, len); + desc[len] =3D '\0'; + start =3D strstr(desc, prefix); + if (start =3D=3D NULL) { + *format =3D VIR_STORAGE_FILE_NONE; + return BACKING_STORE_OK; + } + start +=3D strlen(prefix); + end =3D strchr(start, '"'); + if (end =3D=3D NULL) + return BACKING_STORE_INVALID; + + if (end =3D=3D start) { + *format =3D VIR_STORAGE_FILE_NONE; + return BACKING_STORE_OK; + } + *end =3D '\0'; + *res =3D 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 =3D NULL; + /* Check if this image has a backing file */ + if (buf_size < QED_HDR_FEATURES_OFFSET+8) + return BACKING_STORE_INVALID; + flags =3D virReadBufInt64LE(buf + QED_HDR_FEATURES_OFFSET); + if (!(flags & QED_F_BACKING_FILE)) { + *format =3D 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 =3D virReadBufInt32LE(buf + QED_HDR_BACKING_FILE_OFFSET); + if (offset > buf_size) + return BACKING_STORE_INVALID; + size =3D virReadBufInt32LE(buf + QED_HDR_BACKING_FILE_SIZE); + if (size =3D=3D 0) + return BACKING_STORE_OK; + if (offset + size > buf_size || offset + size < offset) + return BACKING_STORE_INVALID; + *res =3D g_new0(char, size + 1); + memcpy(*res, buf + offset, size); + (*res)[size] =3D '\0'; + + if (flags & QED_F_BACKING_FORMAT_NO_PROBE) + *format =3D VIR_STORAGE_FILE_RAW; + else + *format =3D 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 =3D=3D NULL) + return false; + + /* Validate magic data */ + mlen =3D strlen(magic); + if (magicOffset + mlen > buflen) + return false; + + if (memcmp(buf + magicOffset, magic, mlen) !=3D 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 =3D=3D -1) + return false; + + /* -2 =3D=3D non-versioned file format, so trivially match */ + if (versionOffset =3D=3D -2) + return true; + + /* A positive versionOffset, requires using a valid versionSize */ + if (versionSize !=3D 2 && versionSize !=3D 4) + return false; + + if ((versionOffset + versionSize) > buflen) + return false; + + if (endian =3D=3D LV_LITTLE_ENDIAN) { + if (versionSize =3D=3D 4) + version =3D virReadBufInt32LE(buf + + versionOffset); + else + version =3D virReadBufInt16LE(buf + + versionOffset); + } else { + if (versionSize =3D=3D 4) + version =3D virReadBufInt32BE(buf + + versionOffset); + else + version =3D virReadBufInt16BE(buf + + versionOffset); + } + + for (i =3D 0; + i < FILE_TYPE_VERSIONS_LAST && versionNumbers[i]; + i++) { + VIR_DEBUG("Compare detected version %d vs one of the expected vers= ions %d", + version, versionNumbers[i]); + if (version =3D=3D versionNumbers[i]) + return true; + } + + return false; +} + + +static int +virStorageFileProbeFormatFromBuf(const char *path, + char *buf, + size_t buflen) +{ + int format =3D VIR_STORAGE_FILE_RAW; + size_t i; + int possibleFormat =3D VIR_STORAGE_FILE_RAW; + VIR_DEBUG("path=3D%s, buf=3D%p, buflen=3D%zu", path, buf, buflen); + + /* First check file magic */ + for (i =3D 0; i < VIR_STORAGE_FILE_LAST; i++) { + if (virStorageFileMatchesMagic(fileTypeInfo[i].magicOffset, + fileTypeInfo[i].magic, + buf, buflen)) { + if (!virStorageFileMatchesVersion(fileTypeInfo[i].versionOffse= t, + fileTypeInfo[i].versionSize, + fileTypeInfo[i].versionNumbe= rs, + fileTypeInfo[i].endian, + buf, buflen)) { + possibleFormat =3D i; + continue; + } + format =3D i; + goto cleanup; + } + } + + if (possibleFormat !=3D 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=3D%d", format); + return format; +} + + +static int +qcow2GetFeatures(virBitmapPtr *features, + int format, + char *buf, + ssize_t len) +{ + int version =3D -1; + virBitmapPtr feat =3D NULL; + uint64_t bits; + size_t i; + + version =3D virReadBufInt32BE(buf + fileTypeInfo[format].versionOffset= ); + + if (version =3D=3D 2) + return 0; + + if (len < QCOW2v3_HDR_SIZE) + return -1; + + feat =3D virBitmapNew(VIR_STORAGE_FILE_FEATURE_LAST); + + /* todo: check for incompatible or autoclear features? */ + bits =3D virReadBufInt64BE(buf + QCOW2v3_HDR_FEATURES_COMPATIBLE); + for (i =3D 0; i < QCOW2_COMPATIBLE_FEATURE_LAST; i++) { + if (bits & ((uint64_t) 1 << i)) + ignore_value(virBitmapSetBit(feat, qcow2CompatibleFeatureArray= [i])); + } + + *features =3D feat; + return 0; +} + + +static bool +virStorageFileHasEncryptionFormat(const struct FileEncryptionInfo *info, + char *buf, + size_t len) +{ + if (!info->magic && info->modeOffset =3D=3D -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 !=3D -1 && + !virStorageFileMatchesVersion(info->versionOffset, + info->versionSize, + info->versionNumbers, + info->endian, + buf, len)) + return false; + + return true; + } else if (info->modeOffset !=3D -1) { + int crypt_format; + + if (info->modeOffset >=3D len) + return false; + + crypt_format =3D virReadBufInt32BE(buf + info->modeOffset); + if (crypt_format !=3D info->modeValue) + return false; + + return true; + } else { + return false; + } +} + + +static int +virStorageFileGetEncryptionPayloadOffset(const struct FileEncryptionInfo *= info, + char *buf) +{ + int payload_offset =3D -1; + + if (info->payloadOffset !=3D -1) { + if (info->endian =3D=3D LV_LITTLE_ENDIAN) + payload_offset =3D virReadBufInt32LE(buf + info->payloadOffset= ); + else + payload_offset =3D 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=3D%s, buf=3D%p, len=3D%zu, meta->format=3D%d", + meta->path, buf, len, meta->format); + + if (meta->format =3D=3D VIR_STORAGE_FILE_AUTO) + meta->format =3D virStorageFileProbeFormatFromBuf(meta->path, buf,= len); + + if (meta->format <=3D VIR_STORAGE_FILE_NONE || + meta->format >=3D VIR_STORAGE_FILE_LAST) { + virReportSystemError(EINVAL, _("unknown storage file meta->format = %d"), + meta->format); + return -1; + } + + if (fileTypeInfo[meta->format].cryptInfo !=3D NULL) { + for (i =3D 0; fileTypeInfo[meta->format].cryptInfo[i].format !=3D = 0; i++) { + if (virStorageFileHasEncryptionFormat(&fileTypeInfo[meta->form= at].cryptInfo[i], + buf, len)) { + int expt_fmt =3D fileTypeInfo[meta->format].cryptInfo[i].f= ormat; + if (!meta->encryption) { + meta->encryption =3D g_new0(virStorageEncryption, 1); + meta->encryption->format =3D expt_fmt; + } else { + if (meta->encryption->format !=3D expt_fmt) { + virReportError(VIR_ERR_XML_ERROR, + _("encryption format %d doesn't mat= ch " + "expected format %d"), + meta->encryption->format, expt_fmt); + return -1; + } + } + meta->encryption->payload_offset =3D + 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 !=3D -1) { + if ((fileTypeInfo[meta->format].sizeOffset + 8) > len) + return 0; + + if (fileTypeInfo[meta->format].endian =3D=3D LV_LITTLE_ENDIAN) + meta->capacity =3D virReadBufInt64LE(buf + + fileTypeInfo[meta->format].= sizeOffset); + else + meta->capacity =3D 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 *=3D fileTypeInfo[meta->format].sizeMultiplier; + } + + VIR_FREE(meta->backingStoreRaw); + if (fileTypeInfo[meta->format].getBackingStore !=3D NULL) { + int store =3D fileTypeInfo[meta->format].getBackingStore(&meta->ba= ckingStoreRaw, + &format, + buf, len); + meta->backingStoreRawFormat =3D format; + + if (store =3D=3D BACKING_STORE_INVALID) + return 0; + + if (store =3D=3D BACKING_STORE_ERROR) + return -1; + } + + virBitmapFree(meta->features); + meta->features =3D NULL; + if (fileTypeInfo[meta->format].getFeatures !=3D NULL && + fileTypeInfo[meta->format].getFeatures(&meta->features, meta->form= at, buf, len) < 0) + return -1; + + VIR_FREE(meta->compat); + if (meta->format =3D=3D VIR_STORAGE_FILE_QCOW2 && meta->features) + meta->compat =3D 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 =3D VIR_STORAGE_MAX_HEADER; + VIR_AUTOCLOSE fd =3D -1; + g_autofree char *header =3D NULL; + + if ((fd =3D 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) =3D=3D (off_t)-1) { + virReportSystemError(errno, _("cannot set to start of '%s'"), path= ); + return -1; + } + + if ((len =3D 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 + * . + */ + +#pragma once + +#include + +#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); --=20 2.29.2