Up until now we had a runtime code and XML related code in the same source file inside util directory. This patch takes the runtime part and extracts it into the new storage_file directory. Signed-off-by: Pavel Hrdina <phrdina@xxxxxxxxxx> --- po/POTFILES.in | 1 + src/libvirt_private.syms | 71 +- src/libxl/meson.build | 1 + src/libxl/xen_xl.c | 1 + src/qemu/meson.build | 1 + src/qemu/qemu_backup.c | 1 + src/qemu/qemu_block.c | 1 + src/qemu/qemu_domain.c | 1 + src/qemu/qemu_driver.c | 1 + src/qemu/qemu_hotplug.c | 1 + src/qemu/qemu_process.c | 1 + src/qemu/qemu_snapshot.c | 1 + src/storage/meson.build | 4 + src/storage/storage_backend_gluster.c | 1 + src/storage/storage_util.c | 1 + src/storage_file/meson.build | 18 + src/storage_file/storage_file.c | 3845 +++++++++++++++++++++++ src/storage_file/storage_file.h | 197 ++ src/storage_file/storage_file_gluster.c | 1 + src/util/virstoragefile.c | 3783 +--------------------- src/util/virstoragefile.h | 118 - tests/meson.build | 4 +- tests/qemublocktest.c | 1 + tests/virstoragetest.c | 1 + 24 files changed, 4120 insertions(+), 3936 deletions(-) create mode 100644 src/storage_file/storage_file.c create mode 100644 src/storage_file/storage_file.h diff --git a/po/POTFILES.in b/po/POTFILES.in index e9fc3991f1..6d6678bd36 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -223,6 +223,7 @@ @SRCDIR@src/storage/storage_backend_zfs.c @SRCDIR@src/storage/storage_driver.c @SRCDIR@src/storage/storage_util.c +@SRCDIR@src/storage_file/storage_file.c @SRCDIR@src/storage_file/storage_file_fs.c @SRCDIR@src/storage_file/storage_file_gluster.c @SRCDIR@src/test/test_driver.c diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 1bbf567847..43be8dd61a 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1618,6 +1618,43 @@ virSecurityManagerVerify; virSecurityXATTRNamespaceDefined; +# storage_file/storage_file.h +virStorageFileAccess; +virStorageFileCanonicalizePath; +virStorageFileChainLookup; +virStorageFileChown; +virStorageFileCreate; +virStorageFileDeinit; +virStorageFileGetBackingStoreStr; +virStorageFileGetMetadata; +virStorageFileGetMetadataFromBuf; +virStorageFileGetMetadataFromFD; +virStorageFileGetRelativeBackingPath; +virStorageFileGetUniqueIdentifier; +virStorageFileInit; +virStorageFileInitAs; +virStorageFileParseBackingStoreStr; +virStorageFileParseChainIndex; +virStorageFileProbeFormat; +virStorageFileRead; +virStorageFileReportBrokenChain; +virStorageFileStat; +virStorageFileSupportsAccess; +virStorageFileSupportsBackingChainTraversal; +virStorageFileSupportsCreate; +virStorageFileSupportsSecurityDriver; +virStorageFileUnlink; +virStorageSourceFindByNodeName; +virStorageSourceNewFromBacking; +virStorageSourceNewFromBackingAbsolute; +virStorageSourceParseRBDColonString; +virStorageSourcePrivateDataFormatRelPath; +virStorageSourcePrivateDataParseRelPath; +virStorageSourceUpdateBackingSizes; +virStorageSourceUpdateCapacity; +virStorageSourceUpdatePhysicalSize; + + # util/glibcompat.h vir_g_canonicalize_filename; vir_g_fsync; @@ -3127,37 +3164,12 @@ virStorageAuthDefCopy; virStorageAuthDefFormat; virStorageAuthDefFree; virStorageAuthDefParse; -virStorageFileAccess; -virStorageFileCanonicalizePath; -virStorageFileChainLookup; -virStorageFileChown; -virStorageFileCreate; -virStorageFileDeinit; virStorageFileFeatureTypeFromString; virStorageFileFeatureTypeToString; virStorageFileFormatTypeFromString; virStorageFileFormatTypeToString; -virStorageFileGetBackingStoreStr; -virStorageFileGetMetadata; -virStorageFileGetMetadataFromBuf; -virStorageFileGetMetadataFromFD; virStorageFileGetNPIVKey; -virStorageFileGetRelativeBackingPath; virStorageFileGetSCSIKey; -virStorageFileGetUniqueIdentifier; -virStorageFileInit; -virStorageFileInitAs; -virStorageFileParseBackingStoreStr; -virStorageFileParseChainIndex; -virStorageFileProbeFormat; -virStorageFileRead; -virStorageFileReportBrokenChain; -virStorageFileStat; -virStorageFileSupportsAccess; -virStorageFileSupportsBackingChainTraversal; -virStorageFileSupportsCreate; -virStorageFileSupportsSecurityDriver; -virStorageFileUnlink; virStorageNetHostDefClear; virStorageNetHostDefCopy; virStorageNetHostDefFree; @@ -3174,7 +3186,6 @@ virStorageSourceChainHasManagedPR; virStorageSourceChainHasNVMe; virStorageSourceClear; virStorageSourceCopy; -virStorageSourceFindByNodeName; virStorageSourceGetActualType; virStorageSourceGetSecurityLabelDef; virStorageSourceHasBacking; @@ -3192,18 +3203,10 @@ virStorageSourceIsSameLocation; virStorageSourceNetCookiesValidate; virStorageSourceNetworkAssignDefaultPorts; virStorageSourceNew; -virStorageSourceNewFromBacking; -virStorageSourceNewFromBackingAbsolute; virStorageSourceNVMeDefFree; -virStorageSourceParseRBDColonString; virStorageSourcePoolDefFree; virStorageSourcePoolModeTypeFromString; virStorageSourcePoolModeTypeToString; -virStorageSourcePrivateDataFormatRelPath; -virStorageSourcePrivateDataParseRelPath; -virStorageSourceUpdateBackingSizes; -virStorageSourceUpdateCapacity; -virStorageSourceUpdatePhysicalSize; virStorageTypeFromString; virStorageTypeToString; diff --git a/src/libxl/meson.build b/src/libxl/meson.build index 3bb6cc5f2e..783af6c667 100644 --- a/src/libxl/meson.build +++ b/src/libxl/meson.build @@ -27,6 +27,7 @@ if conf.has('WITH_LIBXL') include_directories: [ conf_inc_dir, hypervisor_inc_dir, + storage_file_inc_dir, ], ) diff --git a/src/libxl/xen_xl.c b/src/libxl/xen_xl.c index ba0942601f..1f97e6bdd4 100644 --- a/src/libxl/xen_xl.c +++ b/src/libxl/xen_xl.c @@ -30,6 +30,7 @@ #include "viralloc.h" #include "virstring.h" #include "virstoragefile.h" +#include "storage_file.h" #include "xen_xl.h" #include "libxl_capabilities.h" #include "libxl_conf.h" diff --git a/src/qemu/meson.build b/src/qemu/meson.build index 90640b03c6..7ab591d040 100644 --- a/src/qemu/meson.build +++ b/src/qemu/meson.build @@ -104,6 +104,7 @@ if conf.has('WITH_QEMU') include_directories: [ conf_inc_dir, hypervisor_inc_dir, + storage_file_inc_dir, ], ) diff --git a/src/qemu/qemu_backup.c b/src/qemu/qemu_backup.c index b2340eb1cf..bd699ad29f 100644 --- a/src/qemu/qemu_backup.c +++ b/src/qemu/qemu_backup.c @@ -29,6 +29,7 @@ #include "qemu_checkpoint.h" #include "qemu_command.h" +#include "storage_file.h" #include "virerror.h" #include "virlog.h" #include "virbuffer.h" diff --git a/src/qemu/qemu_block.c b/src/qemu/qemu_block.c index 0743db4d2c..5444b5f4b7 100644 --- a/src/qemu/qemu_block.c +++ b/src/qemu/qemu_block.c @@ -24,6 +24,7 @@ #include "qemu_alias.h" #include "qemu_security.h" +#include "storage_file.h" #include "viralloc.h" #include "virstring.h" #include "virlog.h" diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index bfb6e23942..ef073e5395 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -52,6 +52,7 @@ #include "virtime.h" #include "virnetdevopenvswitch.h" #include "virstoragefile.h" +#include "storage_file.h" #include "virstring.h" #include "virthreadjob.h" #include "virprocess.h" diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 5f0fb0a55f..00ce56c6b9 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 "storage_file.h" #include "virfile.h" #include "virfdstream.h" #include "configmake.h" diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index 9b93f256e8..a0727b8b90 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -53,6 +53,7 @@ #include "virnetdevmidonet.h" #include "device_conf.h" #include "virstoragefile.h" +#include "storage_file.h" #include "virstring.h" #include "virtime.h" #include "virqemu.h" diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index bb78967ca3..52f33985cf 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -95,6 +95,7 @@ #include "viridentity.h" #include "virthreadjob.h" #include "virutil.h" +#include "storage_file.h" #define VIR_FROM_THIS VIR_FROM_QEMU diff --git a/src/qemu/qemu_snapshot.c b/src/qemu/qemu_snapshot.c index 6f64111ca8..9633c8ab40 100644 --- a/src/qemu/qemu_snapshot.c +++ b/src/qemu/qemu_snapshot.c @@ -41,6 +41,7 @@ #include "virstring.h" #include "virdomainsnapshotobjlist.h" #include "virqemu.h" +#include "storage_file.h" #define VIR_FROM_THIS VIR_FROM_QEMU diff --git a/src/storage/meson.build b/src/storage/meson.build index 8537359e93..153ff6f846 100644 --- a/src/storage/meson.build +++ b/src/storage/meson.build @@ -79,6 +79,7 @@ if conf.has('WITH_STORAGE') ], include_directories: [ conf_inc_dir, + include_directories('../storage_file'), ], ) @@ -162,6 +163,9 @@ if conf.has('WITH_STORAGE_GLUSTER') 'deps': [ glusterfs_dep, ], + 'include': [ + include_directories('../storage_file'), + ], 'install_dir': storage_backend_install_dir, } endif diff --git a/src/storage/storage_backend_gluster.c b/src/storage/storage_backend_gluster.c index 6c99c270da..671d62bf15 100644 --- a/src/storage/storage_backend_gluster.c +++ b/src/storage/storage_backend_gluster.c @@ -25,6 +25,7 @@ #include "storage_backend_gluster.h" #include "storage_conf.h" +#include "storage_file.h" #include "viralloc.h" #include "virerror.h" #include "virlog.h" diff --git a/src/storage/storage_util.c b/src/storage/storage_util.c index 652f65fce2..a1949a5a24 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 "storage_file.h" #include "storage_util.h" #include "virlog.h" #include "virfile.h" diff --git a/src/storage_file/meson.build b/src/storage_file/meson.build index 20eb0176fc..b25ca63b40 100644 --- a/src/storage_file/meson.build +++ b/src/storage_file/meson.build @@ -1,3 +1,7 @@ +storage_file_sources = [ + 'storage_file.c' +] + stoarge_file_fs_sources = [ 'storage_file_fs.c', ] @@ -8,6 +12,18 @@ storage_file_gluster_sources = [ storage_file_install_dir = libdir / 'libvirt' / 'storage-file' +virt_storage_file_lib = static_library( + 'virt_storage_file', + [ + storage_file_sources, + ], + dependencies: [ + src_dep, + ], +) + +libvirt_libs += virt_storage_file_lib + if conf.has('WITH_STORAGE') virt_modules += { 'name': 'virt_storage_file_fs', @@ -36,3 +52,5 @@ if conf.has('WITH_STORAGE_GLUSTER') 'install_dir': storage_file_install_dir, } endif + +storage_file_inc_dir = include_directories('.') diff --git a/src/storage_file/storage_file.c b/src/storage_file/storage_file.c new file mode 100644 index 0000000000..6bfeb26233 --- /dev/null +++ b/src/storage_file/storage_file.c @@ -0,0 +1,3845 @@ +/* + * storage_file.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 <fcntl.h> +#include <unistd.h> + +#include "storage_file.h" +#include "viralloc.h" +#include "virendian.h" +#include "virfile.h" +#include "virhash.h" +#include "virjson.h" +#include "virlog.h" +#include "virstoragefilebackend.h" +#include "virstring.h" +#include "viruri.h" + +#define VIR_FROM_THIS VIR_FROM_STORAGE + +VIR_LOG_INIT("storage_file"); + + +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@xxxxxxxxxx", + path, virStorageFileFormatTypeToString(possibleFormat)); + + cleanup: + VIR_DEBUG("format=%d", format); + return format; +} + + +static int +qcow2GetFeatures(virBitmapPtr *features, + int format, + char *buf, + ssize_t len) +{ + int version = -1; + virBitmapPtr feat = NULL; + uint64_t bits; + size_t i; + + version = virReadBufInt32BE(buf + fileTypeInfo[format].versionOffset); + + if (version == 2) + return 0; + + if (len < QCOW2v3_HDR_SIZE) + return -1; + + feat = virBitmapNew(VIR_STORAGE_FILE_FEATURE_LAST); + + /* todo: check for incompatible or autoclear features? */ + bits = virReadBufInt64BE(buf + QCOW2v3_HDR_FEATURES_COMPATIBLE); + for (i = 0; i < QCOW2_COMPATIBLE_FEATURE_LAST; i++) { + if (bits & ((uint64_t) 1 << i)) + ignore_value(virBitmapSetBit(feat, qcow2CompatibleFeatureArray[i])); + } + + *features = feat; + return 0; +} + + +static bool +virStorageFileHasEncryptionFormat(const struct FileEncryptionInfo *info, + char *buf, + size_t len) +{ + if (!info->magic && info->modeOffset == -1) + return false; /* Shouldn't happen - expect at least one */ + + if (info->magic) { + if (!virStorageFileMatchesMagic(info->magicOffset, + info->magic, + buf, len)) + return false; + + if (info->versionOffset != -1 && + !virStorageFileMatchesVersion(info->versionOffset, + info->versionSize, + info->versionNumbers, + info->endian, + buf, len)) + return false; + + return true; + } else if (info->modeOffset != -1) { + int crypt_format; + + if (info->modeOffset >= len) + return false; + + crypt_format = virReadBufInt32BE(buf + info->modeOffset); + if (crypt_format != info->modeValue) + return false; + + return true; + } else { + return false; + } +} + + +static int +virStorageFileGetEncryptionPayloadOffset(const struct FileEncryptionInfo *info, + char *buf) +{ + int payload_offset = -1; + + if (info->payloadOffset != -1) { + if (info->endian == LV_LITTLE_ENDIAN) + payload_offset = virReadBufInt32LE(buf + info->payloadOffset); + else + payload_offset = virReadBufInt32BE(buf + info->payloadOffset); + } + + return payload_offset; +} + + +/* Given a header in BUF with length LEN, as parsed from the storage file + * assuming it has the given FORMAT, populate information into META + * with information about the file and its backing store. Return format + * of the backing store as BACKING_FORMAT. PATH and FORMAT have to be + * pre-populated in META. + * + * Note that this function may be called repeatedly on @meta, so it must + * clean up any existing allocated memory which would be overwritten. + */ +static int +virStorageFileGetMetadataInternal(virStorageSourcePtr meta, + char *buf, + size_t len) +{ + int format; + size_t i; + + VIR_DEBUG("path=%s, buf=%p, len=%zu, meta->format=%d", + meta->path, buf, len, meta->format); + + if (meta->format == VIR_STORAGE_FILE_AUTO) + meta->format = virStorageFileProbeFormatFromBuf(meta->path, buf, len); + + if (meta->format <= VIR_STORAGE_FILE_NONE || + meta->format >= VIR_STORAGE_FILE_LAST) { + virReportSystemError(EINVAL, _("unknown storage file meta->format %d"), + meta->format); + return -1; + } + + if (fileTypeInfo[meta->format].cryptInfo != NULL) { + for (i = 0; fileTypeInfo[meta->format].cryptInfo[i].format != 0; i++) { + if (virStorageFileHasEncryptionFormat(&fileTypeInfo[meta->format].cryptInfo[i], + buf, len)) { + int expt_fmt = fileTypeInfo[meta->format].cryptInfo[i].format; + if (!meta->encryption) { + meta->encryption = g_new0(virStorageEncryption, 1); + meta->encryption->format = expt_fmt; + } else { + if (meta->encryption->format != expt_fmt) { + virReportError(VIR_ERR_XML_ERROR, + _("encryption format %d doesn't match " + "expected format %d"), + meta->encryption->format, expt_fmt); + return -1; + } + } + meta->encryption->payload_offset = + virStorageFileGetEncryptionPayloadOffset(&fileTypeInfo[meta->format].cryptInfo[i], buf); + } + } + } + + /* XXX we should consider moving virStorageBackendUpdateVolInfo + * code into this method, for non-magic files + */ + if (!fileTypeInfo[meta->format].magic) + return 0; + + /* Optionally extract capacity from file */ + if (fileTypeInfo[meta->format].sizeOffset != -1) { + if ((fileTypeInfo[meta->format].sizeOffset + 8) > len) + return 0; + + if (fileTypeInfo[meta->format].endian == LV_LITTLE_ENDIAN) + meta->capacity = virReadBufInt64LE(buf + + fileTypeInfo[meta->format].sizeOffset); + else + meta->capacity = virReadBufInt64BE(buf + + fileTypeInfo[meta->format].sizeOffset); + /* Avoid unlikely, but theoretically possible overflow */ + if (meta->capacity > (ULLONG_MAX / + fileTypeInfo[meta->format].sizeMultiplier)) + return 0; + meta->capacity *= fileTypeInfo[meta->format].sizeMultiplier; + } + + VIR_FREE(meta->backingStoreRaw); + if (fileTypeInfo[meta->format].getBackingStore != NULL) { + int store = fileTypeInfo[meta->format].getBackingStore(&meta->backingStoreRaw, + &format, + buf, len); + meta->backingStoreRawFormat = format; + + if (store == BACKING_STORE_INVALID) + return 0; + + if (store == BACKING_STORE_ERROR) + return -1; + } + + virBitmapFree(meta->features); + meta->features = NULL; + if (fileTypeInfo[meta->format].getFeatures != NULL && + fileTypeInfo[meta->format].getFeatures(&meta->features, meta->format, buf, len) < 0) + return -1; + + VIR_FREE(meta->compat); + if (meta->format == VIR_STORAGE_FILE_QCOW2 && meta->features) + meta->compat = g_strdup("1.1"); + + return 0; +} + + +/** + * virStorageFileProbeFormat: + * + * Probe for the format of 'path', returning the detected + * disk format. + * + * Callers are advised never to trust the returned 'format' + * unless it is listed as VIR_STORAGE_FILE_RAW, since a + * malicious guest can turn a raw file into any other non-raw + * format at will. + * + * Best option: Don't use this function + */ +int +virStorageFileProbeFormat(const char *path, + uid_t uid, + gid_t gid) +{ + struct stat sb; + ssize_t len = VIR_STORAGE_MAX_HEADER; + VIR_AUTOCLOSE fd = -1; + g_autofree char *header = NULL; + + if ((fd = virFileOpenAs(path, O_RDONLY, 0, uid, gid, 0)) < 0) { + virReportSystemError(-fd, _("Failed to open file '%s'"), path); + return -1; + } + + if (fstat(fd, &sb) < 0) { + virReportSystemError(errno, _("cannot stat file '%s'"), path); + return -1; + } + + /* No header to probe for directories */ + if (S_ISDIR(sb.st_mode)) + return VIR_STORAGE_FILE_DIR; + + if (lseek(fd, 0, SEEK_SET) == (off_t)-1) { + virReportSystemError(errno, _("cannot set to start of '%s'"), path); + return -1; + } + + if ((len = virFileReadHeaderFD(fd, len, &header)) < 0) { + virReportSystemError(errno, _("cannot read header '%s'"), path); + return -1; + } + + return virStorageFileProbeFormatFromBuf(path, header, len); +} + + +static virStorageSourcePtr +virStorageFileMetadataNew(const char *path, + int format) +{ + g_autoptr(virStorageSource) def = virStorageSourceNew(); + + def->format = format; + def->type = VIR_STORAGE_TYPE_FILE; + + def->path = g_strdup(path); + + return g_steal_pointer(&def); +} + + +/** + * virStorageFileGetMetadataFromBuf: + * @path: name of file, for error messages + * @buf: header bytes from @path + * @len: length of @buf + * @format: format of the storage file + * + * Extract metadata about the storage volume with the specified image format. + * If image format is VIR_STORAGE_FILE_AUTO, it will probe to automatically + * identify the format. Does not recurse. + * + * Callers are advised never to use VIR_STORAGE_FILE_AUTO as a format on a file + * that might be raw if that file will then be passed to a guest, since a + * malicious guest can turn a raw file into any other non-raw format at will. + * + * If the 'backingStoreRawFormat' field of the returned structure is + * VIR_STORAGE_FILE_AUTO it indicates the image didn't specify an explicit + * format for its backing store. Callers are advised against probing for the + * backing store format in this case. + * + * Caller MUST free the result after use via virObjectUnref. + */ +virStorageSourcePtr +virStorageFileGetMetadataFromBuf(const char *path, + char *buf, + size_t len, + int format) +{ + virStorageSourcePtr ret = NULL; + + if (!(ret = virStorageFileMetadataNew(path, format))) + return NULL; + + if (virStorageFileGetMetadataInternal(ret, buf, len) < 0) { + virObjectUnref(ret); + return NULL; + } + + return ret; +} + + +/** + * virStorageFileGetMetadataFromFD: + * + * Extract metadata about the storage volume with the specified + * image format. If image format is VIR_STORAGE_FILE_AUTO, it + * will probe to automatically identify the format. Does not recurse. + * + * Callers are advised never to use VIR_STORAGE_FILE_AUTO as a + * format, since a malicious guest can turn a raw file into any + * other non-raw format at will. + * + * Caller MUST free the result after use via virObjectUnref. + */ +virStorageSourcePtr +virStorageFileGetMetadataFromFD(const char *path, + int fd, + int format) + +{ + ssize_t len = VIR_STORAGE_MAX_HEADER; + struct stat sb; + g_autofree char *buf = NULL; + g_autoptr(virStorageSource) meta = NULL; + + if (fstat(fd, &sb) < 0) { + virReportSystemError(errno, + _("cannot stat file '%s'"), path); + return NULL; + } + + if (!(meta = virStorageFileMetadataNew(path, format))) + return NULL; + + if (S_ISDIR(sb.st_mode)) { + /* No header to probe for directories, but also no backing file. Just + * update the metadata.*/ + meta->type = VIR_STORAGE_TYPE_DIR; + meta->format = VIR_STORAGE_FILE_DIR; + return g_steal_pointer(&meta); + } + + if (lseek(fd, 0, SEEK_SET) == (off_t)-1) { + virReportSystemError(errno, _("cannot seek to start of '%s'"), meta->path); + return NULL; + } + + if ((len = virFileReadHeaderFD(fd, len, &buf)) < 0) { + virReportSystemError(errno, _("cannot read header '%s'"), meta->path); + return NULL; + } + + if (virStorageFileGetMetadataInternal(meta, buf, len) < 0) + return NULL; + + if (S_ISREG(sb.st_mode)) + meta->type = VIR_STORAGE_TYPE_FILE; + else if (S_ISBLK(sb.st_mode)) + meta->type = VIR_STORAGE_TYPE_BLOCK; + + return g_steal_pointer(&meta); +} + + +/** + * virStorageFileParseBackingStoreStr: + * @str: backing store specifier string to parse + * @target: returns target device portion of the string + * @chainIndex: returns the backing store portion of the string + * + * Parses the backing store specifier string such as vda[1], or sda into + * components and returns them via arguments. If the string did not specify an + * index, 0 is assumed. + * + * Returns 0 on success -1 on error + */ +int +virStorageFileParseBackingStoreStr(const char *str, + char **target, + unsigned int *chainIndex) +{ + size_t nstrings; + unsigned int idx = 0; + char *suffix; + g_auto(GStrv) strings = NULL; + + *chainIndex = 0; + + if (!(strings = virStringSplitCount(str, "[", 2, &nstrings))) + return -1; + + if (nstrings == 2) { + if (virStrToLong_uip(strings[1], &suffix, 10, &idx) < 0 || + STRNEQ(suffix, "]")) + return -1; + } + + if (target) + *target = g_strdup(strings[0]); + + *chainIndex = idx; + return 0; +} + + +int +virStorageFileParseChainIndex(const char *diskTarget, + const char *name, + unsigned int *chainIndex) +{ + unsigned int idx = 0; + g_autofree char *target = NULL; + + *chainIndex = 0; + + if (!name || !diskTarget) + return 0; + + if (virStorageFileParseBackingStoreStr(name, &target, &idx) < 0) + return 0; + + if (idx == 0) + return 0; + + if (STRNEQ(diskTarget, target)) { + virReportError(VIR_ERR_INVALID_ARG, + _("requested target '%s' does not match target '%s'"), + target, diskTarget); + return -1; + } + + *chainIndex = idx; + + return 0; +} + + +/* Given a @chain, look for the backing store @name that is a backing file + * of @startFrom (or any member of @chain if @startFrom is NULL) and return + * that location within the chain. @chain must always point to the top of + * the chain. Pass NULL for @name and 0 for @idx to find the base of the + * chain. Pass nonzero @idx to find the backing source according to its + * position in the backing chain. If @parent is not NULL, set *@parent to + * the preferred name of the parent (or to NULL if @name matches the start + * of the chain). Since the results point within @chain, they must not be + * independently freed. Reports an error and returns NULL if @name is not + * found. + */ +virStorageSourcePtr +virStorageFileChainLookup(virStorageSourcePtr chain, + virStorageSourcePtr startFrom, + const char *name, + unsigned int idx, + virStorageSourcePtr *parent) +{ + virStorageSourcePtr prev; + const char *start = chain->path; + bool nameIsFile = virFileIsFile(name); + + if (!parent) + parent = &prev; + *parent = NULL; + + if (startFrom) { + while (virStorageSourceIsBacking(chain) && + chain != startFrom->backingStore) + chain = chain->backingStore; + + *parent = startFrom; + } + + while (virStorageSourceIsBacking(chain)) { + if (!name && !idx) { + if (!virStorageSourceHasBacking(chain)) + break; + } else if (idx) { + VIR_DEBUG("%u: %s", chain->id, chain->path); + if (idx == chain->id) + break; + } else { + if (STREQ_NULLABLE(name, chain->relPath) || + STREQ_NULLABLE(name, chain->path)) + break; + + if (nameIsFile && virStorageSourceIsLocalStorage(chain)) { + g_autofree char *parentDir = NULL; + int result; + + if (*parent && virStorageSourceIsLocalStorage(*parent)) + parentDir = g_path_get_dirname((*parent)->path); + else + parentDir = g_strdup("."); + + result = virFileRelLinkPointsTo(parentDir, name, + chain->path); + + if (result < 0) + goto error; + + if (result > 0) + break; + } + } + *parent = chain; + chain = chain->backingStore; + } + + if (!virStorageSourceIsBacking(chain)) + goto error; + + return chain; + + error: + if (idx) { + virReportError(VIR_ERR_INVALID_ARG, + _("could not find backing store index %u in chain " + "for '%s'"), + idx, NULLSTR(start)); + } else if (name) { + if (startFrom) + virReportError(VIR_ERR_INVALID_ARG, + _("could not find image '%s' beneath '%s' in " + "chain for '%s'"), name, NULLSTR(startFrom->path), + NULLSTR(start)); + else + virReportError(VIR_ERR_INVALID_ARG, + _("could not find image '%s' in chain for '%s'"), + name, NULLSTR(start)); + } else { + virReportError(VIR_ERR_INVALID_ARG, + _("could not find base image in chain for '%s'"), + NULLSTR(start)); + } + *parent = NULL; + return NULL; +} + + +static virStorageSourcePtr +virStorageSourceNewFromBackingRelative(virStorageSourcePtr parent, + const char *rel) +{ + g_autofree char *dirname = NULL; + g_autoptr(virStorageSource) def = virStorageSourceNew(); + + /* store relative name */ + def->relPath = g_strdup(rel); + + dirname = g_path_get_dirname(parent->path); + + if (STRNEQ(dirname, "/")) { + def->path = g_strdup_printf("%s/%s", dirname, rel); + } else { + def->path = g_strdup_printf("/%s", rel); + } + + if (virStorageSourceGetActualType(parent) == VIR_STORAGE_TYPE_NETWORK) { + def->type = VIR_STORAGE_TYPE_NETWORK; + + /* copy the host network part */ + def->protocol = parent->protocol; + if (parent->nhosts) { + if (!(def->hosts = virStorageNetHostDefCopy(parent->nhosts, + parent->hosts))) + return NULL; + + def->nhosts = parent->nhosts; + } + + def->volume = g_strdup(parent->volume); + } else { + /* set the type to _FILE, the caller shall update it to the actual type */ + def->type = VIR_STORAGE_TYPE_FILE; + } + + return g_steal_pointer(&def); +} + + +static int +virStorageSourceParseBackingURI(virStorageSourcePtr src, + const char *uristr) +{ + g_autoptr(virURI) uri = NULL; + const char *path = NULL; + g_auto(GStrv) scheme = NULL; + + if (!(uri = virURIParse(uristr))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("failed to parse backing file location '%s'"), + uristr); + return -1; + } + + src->hosts = g_new0(virStorageNetHostDef, 1); + src->nhosts = 1; + + if (!(scheme = virStringSplit(uri->scheme, "+", 2))) + return -1; + + if (!scheme[0] || + (src->protocol = virStorageNetProtocolTypeFromString(scheme[0])) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("invalid backing protocol '%s'"), + NULLSTR(scheme[0])); + return -1; + } + + if (scheme[1] && + (src->hosts->transport = virStorageNetHostTransportTypeFromString(scheme[1])) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("invalid protocol transport type '%s'"), + scheme[1]); + return -1; + } + + if (uri->query) { + if (src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTP || + src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS) { + src->query = g_strdup(uri->query); + } else { + /* handle socket stored as a query */ + if (STRPREFIX(uri->query, "socket=")) + src->hosts->socket = g_strdup(STRSKIP(uri->query, "socket=")); + } + } + + /* uri->path is NULL if the URI does not contain slash after host: + * transport://host:port */ + if (uri->path) + path = uri->path; + else + path = ""; + + /* possibly skip the leading slash */ + if (path[0] == '/') + path++; + + /* NBD allows empty export name (path) */ + if (src->protocol == VIR_STORAGE_NET_PROTOCOL_NBD && + path[0] == '\0') + path = NULL; + + src->path = g_strdup(path); + + if (src->protocol == VIR_STORAGE_NET_PROTOCOL_GLUSTER) { + char *tmp; + + if (!src->path) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("missing volume name and path for gluster volume")); + return -1; + } + + if (!(tmp = strchr(src->path, '/')) || + tmp == src->path) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("missing volume name or file name in " + "gluster source path '%s'"), src->path); + return -1; + } + + src->volume = src->path; + + src->path = g_strdup(tmp + 1); + + tmp[0] = '\0'; + } + + src->hosts->port = uri->port; + + src->hosts->name = g_strdup(uri->server); + + /* Libvirt doesn't handle inline authentication. Make the caller aware. */ + if (uri->user) + return 1; + + return 0; +} + + +static int +virStorageSourceRBDAddHost(virStorageSourcePtr src, + char *hostport) +{ + char *port; + size_t skip; + g_auto(GStrv) parts = NULL; + + if (VIR_EXPAND_N(src->hosts, src->nhosts, 1) < 0) + return -1; + + if ((port = strchr(hostport, ']'))) { + /* ipv6, strip brackets */ + hostport += 1; + skip = 3; + } else { + port = strstr(hostport, "\\:"); + skip = 2; + } + + if (port) { + *port = '\0'; + port += skip; + if (virStringParsePort(port, &src->hosts[src->nhosts - 1].port) < 0) + goto error; + } + + parts = virStringSplit(hostport, "\\:", 0); + if (!parts) + goto error; + src->hosts[src->nhosts-1].name = virStringListJoin((const char **)parts, ":"); + if (!src->hosts[src->nhosts-1].name) + goto error; + + src->hosts[src->nhosts-1].transport = VIR_STORAGE_NET_HOST_TRANS_TCP; + src->hosts[src->nhosts-1].socket = NULL; + + return 0; + + error: + VIR_FREE(src->hosts[src->nhosts-1].name); + return -1; +} + + +int +virStorageSourceParseRBDColonString(const char *rbdstr, + virStorageSourcePtr src) +{ + char *p, *e, *next; + g_autofree char *options = NULL; + g_autoptr(virStorageAuthDef) authdef = NULL; + + /* optionally skip the "rbd:" prefix if provided */ + if (STRPREFIX(rbdstr, "rbd:")) + rbdstr += strlen("rbd:"); + + src->path = g_strdup(rbdstr); + + p = strchr(src->path, ':'); + if (p) { + options = g_strdup(p + 1); + *p = '\0'; + } + + /* snapshot name */ + if ((p = strchr(src->path, '@'))) { + src->snapshot = g_strdup(p + 1); + *p = '\0'; + } + + /* pool vs. image name */ + if ((p = strchr(src->path, '/'))) { + src->volume = g_steal_pointer(&src->path); + src->path = g_strdup(p + 1); + *p = '\0'; + } + + /* options */ + if (!options) + return 0; /* all done */ + + p = options; + while (*p) { + /* find : delimiter or end of string */ + for (e = p; *e && *e != ':'; ++e) { + if (*e == '\\') { + e++; + if (*e == '\0') + break; + } + } + if (*e == '\0') { + next = e; /* last kv pair */ + } else { + next = e + 1; + *e = '\0'; + } + + if (STRPREFIX(p, "id=")) { + /* formulate authdef for src->auth */ + if (src->auth) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("duplicate 'id' found in '%s'"), src->path); + return -1; + } + + authdef = g_new0(virStorageAuthDef, 1); + + authdef->username = g_strdup(p + strlen("id=")); + + authdef->secrettype = g_strdup(virSecretUsageTypeToString(VIR_SECRET_USAGE_TYPE_CEPH)); + src->auth = g_steal_pointer(&authdef); + + /* Cannot formulate a secretType (eg, usage or uuid) given + * what is provided. + */ + } + if (STRPREFIX(p, "mon_host=")) { + char *h, *sep; + + h = p + strlen("mon_host="); + while (h < e) { + for (sep = h; sep < e; ++sep) { + if (*sep == '\\' && (sep[1] == ',' || + sep[1] == ';' || + sep[1] == ' ')) { + *sep = '\0'; + sep += 2; + break; + } + } + + if (virStorageSourceRBDAddHost(src, h) < 0) + return -1; + + h = sep; + } + } + + if (STRPREFIX(p, "conf=")) + src->configFile = g_strdup(p + strlen("conf=")); + + p = next; + } + return 0; +} + + +static int +virStorageSourceParseNBDColonString(const char *nbdstr, + virStorageSourcePtr src) +{ + g_autofree char *nbd = g_strdup(nbdstr); + char *export_name; + char *host_spec; + char *unixpath; + char *port; + + src->hosts = g_new0(virStorageNetHostDef, 1); + src->nhosts = 1; + + /* We extract the parameters in a similar way qemu does it */ + + /* format: [] denotes optional sections, uppercase are variable strings + * nbd:unix:/PATH/TO/SOCKET[:exportname=EXPORTNAME] + * nbd:HOSTNAME:PORT[:exportname=EXPORTNAME] + */ + + /* first look for ':exportname=' and cut it off */ + if ((export_name = strstr(nbd, ":exportname="))) { + src->path = g_strdup(export_name + strlen(":exportname=")); + export_name[0] = '\0'; + } + + /* Verify the prefix and contents. Note that we require a + * "host_spec" part to be present. */ + if (!(host_spec = STRSKIP(nbd, "nbd:")) || host_spec[0] == '\0') + goto malformed; + + if ((unixpath = STRSKIP(host_spec, "unix:"))) { + src->hosts->transport = VIR_STORAGE_NET_HOST_TRANS_UNIX; + + if (unixpath[0] == '\0') + goto malformed; + + src->hosts->socket = g_strdup(unixpath); + } else { + src->hosts->transport = VIR_STORAGE_NET_HOST_TRANS_TCP; + + if (host_spec[0] == ':') { + /* no host given */ + goto malformed; + } else if (host_spec[0] == '[') { + host_spec++; + /* IPv6 addr */ + if (!(port = strstr(host_spec, "]:"))) + goto malformed; + + port[0] = '\0'; + port += 2; + + if (host_spec[0] == '\0') + goto malformed; + } else { + if (!(port = strchr(host_spec, ':'))) + goto malformed; + + port[0] = '\0'; + port++; + } + + if (virStringParsePort(port, &src->hosts->port) < 0) + return -1; + + src->hosts->name = g_strdup(host_spec); + } + + return 0; + + malformed: + virReportError(VIR_ERR_INTERNAL_ERROR, + _("malformed nbd string '%s'"), nbdstr); + return -1; +} + + +static int +virStorageSourceParseBackingColon(virStorageSourcePtr src, + const char *path) +{ + const char *p; + g_autofree char *protocol = NULL; + + if (!(p = strchr(path, ':'))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("invalid backing protocol string '%s'"), + path); + return -1; + } + + protocol = g_strndup(path, p - path); + + if ((src->protocol = virStorageNetProtocolTypeFromString(protocol)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("invalid backing protocol '%s'"), + protocol); + return -1; + } + + switch ((virStorageNetProtocol) src->protocol) { + case VIR_STORAGE_NET_PROTOCOL_NBD: + if (virStorageSourceParseNBDColonString(path, src) < 0) + return -1; + break; + + case VIR_STORAGE_NET_PROTOCOL_RBD: + if (virStorageSourceParseRBDColonString(path, src) < 0) + return -1; + break; + + case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG: + case VIR_STORAGE_NET_PROTOCOL_LAST: + case VIR_STORAGE_NET_PROTOCOL_NONE: + virReportError(VIR_ERR_INTERNAL_ERROR, + _("backing store parser is not implemented for protocol %s"), + protocol); + return -1; + + case VIR_STORAGE_NET_PROTOCOL_HTTP: + case VIR_STORAGE_NET_PROTOCOL_HTTPS: + case VIR_STORAGE_NET_PROTOCOL_FTP: + case VIR_STORAGE_NET_PROTOCOL_FTPS: + case VIR_STORAGE_NET_PROTOCOL_TFTP: + case VIR_STORAGE_NET_PROTOCOL_ISCSI: + case VIR_STORAGE_NET_PROTOCOL_GLUSTER: + case VIR_STORAGE_NET_PROTOCOL_SSH: + case VIR_STORAGE_NET_PROTOCOL_VXHS: + virReportError(VIR_ERR_INTERNAL_ERROR, + _("malformed backing store path for protocol %s"), + protocol); + return -1; + } + + return 0; +} + + +static int +virStorageSourceParseBackingJSONInternal(virStorageSourcePtr src, + virJSONValuePtr json, + const char *jsonstr, + bool allowformat); + + +static int +virStorageSourceParseBackingJSONPath(virStorageSourcePtr src, + virJSONValuePtr json, + const char *jsonstr G_GNUC_UNUSED, + int type) +{ + const char *path; + + if (!(path = virJSONValueObjectGetString(json, "filename"))) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing 'filename' field in JSON backing volume " + "definition")); + return -1; + } + + src->path = g_strdup(path); + + src->type = type; + return 0; +} + + +static int +virStorageSourceParseBackingJSONUriStr(virStorageSourcePtr src, + const char *uri, + int protocol) +{ + int rc; + + if ((rc = virStorageSourceParseBackingURI(src, uri)) < 0) + return -1; + + if (src->protocol != protocol) { + virReportError(VIR_ERR_INVALID_ARG, + _("expected protocol '%s' but got '%s' in URI JSON volume " + "definition"), + virStorageNetProtocolTypeToString(protocol), + virStorageNetProtocolTypeToString(src->protocol)); + return -1; + } + + return rc; +} + + +static int +virStorageSourceParseBackingJSONUriCookies(virStorageSourcePtr src, + virJSONValuePtr json, + const char *jsonstr) +{ + const char *cookiestr; + g_auto(GStrv) cookies = NULL; + size_t ncookies = 0; + size_t i; + + if (!virJSONValueObjectHasKey(json, "cookie")) + return 0; + + if (!(cookiestr = virJSONValueObjectGetString(json, "cookie"))) { + virReportError(VIR_ERR_INVALID_ARG, + _("wrong format of 'cookie' field in backing store definition '%s'"), + jsonstr); + return -1; + } + + if (!(cookies = virStringSplitCount(cookiestr, ";", 0, &ncookies))) + return -1; + + src->cookies = g_new0(virStorageNetCookieDefPtr, ncookies); + src->ncookies = ncookies; + + for (i = 0; i < ncookies; i++) { + char *cookiename = cookies[i]; + char *cookievalue; + + virSkipSpaces((const char **) &cookiename); + + if (!(cookievalue = strchr(cookiename, '='))) { + virReportError(VIR_ERR_INVALID_ARG, + _("malformed http cookie '%s' in backing store definition '%s'"), + cookies[i], jsonstr); + return -1; + } + + *cookievalue = '\0'; + cookievalue++; + + src->cookies[i] = g_new0(virStorageNetCookieDef, 1); + src->cookies[i]->name = g_strdup(cookiename); + src->cookies[i]->value = g_strdup(cookievalue); + } + + return 0; +} + + +static int +virStorageSourceParseBackingJSONUri(virStorageSourcePtr src, + virJSONValuePtr json, + const char *jsonstr, + int protocol) +{ + const char *uri; + + if (!(uri = virJSONValueObjectGetString(json, "url"))) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing 'url' in JSON backing volume definition")); + return -1; + } + + if (protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS || + protocol == VIR_STORAGE_NET_PROTOCOL_FTPS) { + if (virJSONValueObjectHasKey(json, "sslverify")) { + const char *tmpstr; + bool tmp; + + /* libguestfs still uses undocumented legacy value of 'off' */ + if ((tmpstr = virJSONValueObjectGetString(json, "sslverify")) && + STREQ(tmpstr, "off")) { + src->sslverify = VIR_TRISTATE_BOOL_NO; + } else { + if (virJSONValueObjectGetBoolean(json, "sslverify", &tmp) < 0) { + virReportError(VIR_ERR_INVALID_ARG, + _("malformed 'sslverify' field in backing store definition '%s'"), + jsonstr); + return -1; + } + + src->sslverify = virTristateBoolFromBool(tmp); + } + } + } + + if (protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS || + protocol == VIR_STORAGE_NET_PROTOCOL_HTTP) { + if (virStorageSourceParseBackingJSONUriCookies(src, json, jsonstr) < 0) + return -1; + } + + if (virJSONValueObjectHasKey(json, "readahead") && + virJSONValueObjectGetNumberUlong(json, "readahead", &src->readahead) < 0) { + virReportError(VIR_ERR_INVALID_ARG, + _("malformed 'readahead' field in backing store definition '%s'"), + jsonstr); + return -1; + } + + if (virJSONValueObjectHasKey(json, "timeout") && + virJSONValueObjectGetNumberUlong(json, "timeout", &src->timeout) < 0) { + virReportError(VIR_ERR_INVALID_ARG, + _("malformed 'timeout' field in backing store definition '%s'"), + jsonstr); + return -1; + } + + return virStorageSourceParseBackingJSONUriStr(src, uri, protocol); +} + + +static int +virStorageSourceParseBackingJSONInetSocketAddress(virStorageNetHostDefPtr host, + virJSONValuePtr json) +{ + const char *hostname; + const char *port; + + if (!json) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing remote server specification in JSON " + "backing volume definition")); + return -1; + } + + hostname = virJSONValueObjectGetString(json, "host"); + port = virJSONValueObjectGetString(json, "port"); + + if (!hostname) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing hostname for tcp backing server in " + "JSON backing volume definition")); + return -1; + } + + host->transport = VIR_STORAGE_NET_HOST_TRANS_TCP; + host->name = g_strdup(hostname); + + if (virStringParsePort(port, &host->port) < 0) + return -1; + + return 0; +} + + +static int +virStorageSourceParseBackingJSONSocketAddress(virStorageNetHostDefPtr host, + virJSONValuePtr json) +{ + const char *type; + const char *socket; + + if (!json) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing remote server specification in JSON " + "backing volume definition")); + return -1; + } + + if (!(type = virJSONValueObjectGetString(json, "type"))) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing socket address type in " + "JSON backing volume definition")); + return -1; + } + + if (STREQ(type, "tcp") || STREQ(type, "inet")) { + return virStorageSourceParseBackingJSONInetSocketAddress(host, json); + + } else if (STREQ(type, "unix")) { + host->transport = VIR_STORAGE_NET_HOST_TRANS_UNIX; + + socket = virJSONValueObjectGetString(json, "path"); + + /* check for old spelling for gluster protocol */ + if (!socket) + socket = virJSONValueObjectGetString(json, "socket"); + + if (!socket) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing socket path for udp backing server in " + "JSON backing volume definition")); + return -1; + } + + host->socket = g_strdup(socket); + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("backing store protocol '%s' is not yet supported"), + type); + return -1; + } + + return 0; +} + + +static int +virStorageSourceParseBackingJSONGluster(virStorageSourcePtr src, + virJSONValuePtr json, + const char *jsonstr G_GNUC_UNUSED, + int opaque G_GNUC_UNUSED) +{ + const char *uri = virJSONValueObjectGetString(json, "filename"); + const char *volume = virJSONValueObjectGetString(json, "volume"); + const char *path = virJSONValueObjectGetString(json, "path"); + virJSONValuePtr server = virJSONValueObjectGetArray(json, "server"); + size_t nservers; + size_t i; + + /* legacy URI based syntax passed via 'filename' option */ + if (uri) + return virStorageSourceParseBackingJSONUriStr(src, uri, + VIR_STORAGE_NET_PROTOCOL_GLUSTER); + + if (!volume || !path || !server) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing 'volume', 'path' or 'server' attribute in " + "JSON backing definition for gluster volume")); + return -1; + } + + src->type = VIR_STORAGE_TYPE_NETWORK; + src->protocol = VIR_STORAGE_NET_PROTOCOL_GLUSTER; + + src->volume = g_strdup(volume); + src->path = g_strdup(path); + + nservers = virJSONValueArraySize(server); + if (nservers == 0) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("at least 1 server is necessary in " + "JSON backing definition for gluster volume")); + + return -1; + } + + src->hosts = g_new0(virStorageNetHostDef, nservers); + src->nhosts = nservers; + + for (i = 0; i < nservers; i++) { + if (virStorageSourceParseBackingJSONSocketAddress(src->hosts + i, + virJSONValueArrayGet(server, i)) < 0) + return -1; + } + + return 0; +} + + +static int +virStorageSourceParseBackingJSONiSCSI(virStorageSourcePtr src, + virJSONValuePtr json, + const char *jsonstr G_GNUC_UNUSED, + int opaque G_GNUC_UNUSED) +{ + const char *transport = virJSONValueObjectGetString(json, "transport"); + const char *portal = virJSONValueObjectGetString(json, "portal"); + const char *target = virJSONValueObjectGetString(json, "target"); + const char *lun = virJSONValueObjectGetStringOrNumber(json, "lun"); + const char *uri; + char *port; + + /* legacy URI based syntax passed via 'filename' option */ + if ((uri = virJSONValueObjectGetString(json, "filename"))) + return virStorageSourceParseBackingJSONUriStr(src, uri, + VIR_STORAGE_NET_PROTOCOL_ISCSI); + + src->type = VIR_STORAGE_TYPE_NETWORK; + src->protocol = VIR_STORAGE_NET_PROTOCOL_ISCSI; + + if (!lun) + lun = "0"; + + src->hosts = g_new0(virStorageNetHostDef, 1); + src->nhosts = 1; + + if (STRNEQ_NULLABLE(transport, "tcp")) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("only TCP transport is supported for iSCSI volumes")); + return -1; + } + + src->hosts->transport = VIR_STORAGE_NET_HOST_TRANS_TCP; + + if (!portal) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing 'portal' address in iSCSI backing definition")); + return -1; + } + + if (!target) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing 'target' in iSCSI backing definition")); + return -1; + } + + src->hosts->name = g_strdup(portal); + + if ((port = strrchr(src->hosts->name, ':')) && + !strchr(port, ']')) { + if (virStringParsePort(port + 1, &src->hosts->port) < 0) + return -1; + + *port = '\0'; + } + + src->path = g_strdup_printf("%s/%s", target, lun); + + /* Libvirt doesn't handle inline authentication. Make the caller aware. */ + if (virJSONValueObjectGetString(json, "user") || + virJSONValueObjectGetString(json, "password")) + return 1; + + return 0; +} + + +static int +virStorageSourceParseBackingJSONNbd(virStorageSourcePtr src, + virJSONValuePtr json, + const char *jsonstr G_GNUC_UNUSED, + int opaque G_GNUC_UNUSED) +{ + const char *path = virJSONValueObjectGetString(json, "path"); + const char *host = virJSONValueObjectGetString(json, "host"); + const char *port = virJSONValueObjectGetString(json, "port"); + const char *export = virJSONValueObjectGetString(json, "export"); + virJSONValuePtr server = virJSONValueObjectGetObject(json, "server"); + + if (!path && !host && !server) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing host specification of NBD server in JSON " + "backing volume definition")); + return -1; + } + + src->type = VIR_STORAGE_TYPE_NETWORK; + src->protocol = VIR_STORAGE_NET_PROTOCOL_NBD; + + src->path = g_strdup(export); + + src->hosts = g_new0(virStorageNetHostDef, 1); + src->nhosts = 1; + + if (server) { + if (virStorageSourceParseBackingJSONSocketAddress(src->hosts, server) < 0) + return -1; + } else { + if (path) { + src->hosts[0].transport = VIR_STORAGE_NET_HOST_TRANS_UNIX; + src->hosts[0].socket = g_strdup(path); + } else { + src->hosts[0].transport = VIR_STORAGE_NET_HOST_TRANS_TCP; + src->hosts[0].name = g_strdup(host); + + if (virStringParsePort(port, &src->hosts[0].port) < 0) + return -1; + } + } + + return 0; +} + + +static int +virStorageSourceParseBackingJSONSheepdog(virStorageSourcePtr src, + virJSONValuePtr json, + const char *jsonstr G_GNUC_UNUSED, + int opaque G_GNUC_UNUSED) +{ + const char *filename; + const char *vdi = virJSONValueObjectGetString(json, "vdi"); + virJSONValuePtr server = virJSONValueObjectGetObject(json, "server"); + + /* legacy URI based syntax passed via 'filename' option */ + if ((filename = virJSONValueObjectGetString(json, "filename"))) { + if (strstr(filename, "://")) + return virStorageSourceParseBackingJSONUriStr(src, filename, + VIR_STORAGE_NET_PROTOCOL_SHEEPDOG); + + /* libvirt doesn't implement a parser for the legacy non-URI syntax */ + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing sheepdog URI in JSON backing volume definition")); + return -1; + } + + src->type = VIR_STORAGE_TYPE_NETWORK; + src->protocol = VIR_STORAGE_NET_PROTOCOL_SHEEPDOG; + + if (!vdi) { + virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing sheepdog vdi name")); + return -1; + } + + src->path = g_strdup(vdi); + + src->hosts = g_new0(virStorageNetHostDef, 1); + src->nhosts = 1; + + if (virStorageSourceParseBackingJSONSocketAddress(src->hosts, server) < 0) + return -1; + + return 0; +} + + +static int +virStorageSourceParseBackingJSONSSH(virStorageSourcePtr src, + virJSONValuePtr json, + const char *jsonstr G_GNUC_UNUSED, + int opaque G_GNUC_UNUSED) +{ + const char *path = virJSONValueObjectGetString(json, "path"); + const char *host = virJSONValueObjectGetString(json, "host"); + const char *port = virJSONValueObjectGetString(json, "port"); + const char *user = virJSONValueObjectGetString(json, "user"); + const char *host_key_check = virJSONValueObjectGetString(json, "host_key_check"); + virJSONValuePtr server = virJSONValueObjectGetObject(json, "server"); + + if (!(host || server) || !path) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing host/server or path of SSH JSON backing " + "volume definition")); + return -1; + } + + src->type = VIR_STORAGE_TYPE_NETWORK; + src->protocol = VIR_STORAGE_NET_PROTOCOL_SSH; + + src->path = g_strdup(path); + + src->hosts = g_new0(virStorageNetHostDef, 1); + src->nhosts = 1; + + if (server) { + if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts, + server) < 0) + return -1; + } else { + src->hosts[0].transport = VIR_STORAGE_NET_HOST_TRANS_TCP; + src->hosts[0].name = g_strdup(host); + + if (virStringParsePort(port, &src->hosts[0].port) < 0) + return -1; + } + + /* these two are parsed just to be passed back as we don't model them yet */ + src->ssh_user = g_strdup(user); + if (STREQ_NULLABLE(host_key_check, "no")) + src->ssh_host_key_check_disabled = true; + + return 0; +} + + +static int +virStorageSourceParseBackingJSONRBD(virStorageSourcePtr src, + virJSONValuePtr json, + const char *jsonstr G_GNUC_UNUSED, + int opaque G_GNUC_UNUSED) +{ + const char *filename; + const char *pool = virJSONValueObjectGetString(json, "pool"); + const char *image = virJSONValueObjectGetString(json, "image"); + const char *conf = virJSONValueObjectGetString(json, "conf"); + const char *snapshot = virJSONValueObjectGetString(json, "snapshot"); + virJSONValuePtr servers = virJSONValueObjectGetArray(json, "server"); + size_t nservers; + size_t i; + + src->type = VIR_STORAGE_TYPE_NETWORK; + src->protocol = VIR_STORAGE_NET_PROTOCOL_RBD; + + /* legacy syntax passed via 'filename' option */ + if ((filename = virJSONValueObjectGetString(json, "filename"))) + return virStorageSourceParseRBDColonString(filename, src); + + if (!pool || !image) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing pool or image name in ceph backing volume " + "JSON specification")); + return -1; + } + + src->volume = g_strdup(pool); + src->path = g_strdup(image); + src->snapshot = g_strdup(snapshot); + src->configFile = g_strdup(conf); + + if (servers) { + nservers = virJSONValueArraySize(servers); + + src->hosts = g_new0(virStorageNetHostDef, nservers); + src->nhosts = nservers; + + for (i = 0; i < nservers; i++) { + if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts + i, + virJSONValueArrayGet(servers, i)) < 0) + return -1; + } + } + + return 0; +} + +static int +virStorageSourceParseBackingJSONRaw(virStorageSourcePtr src, + virJSONValuePtr json, + const char *jsonstr, + int opaque G_GNUC_UNUSED) +{ + bool has_offset = virJSONValueObjectHasKey(json, "offset"); + bool has_size = virJSONValueObjectHasKey(json, "size"); + virJSONValuePtr file; + + if (has_offset || has_size) { + src->sliceStorage = g_new0(virStorageSourceSlice, 1); + + if (has_offset && + virJSONValueObjectGetNumberUlong(json, "offset", &src->sliceStorage->offset) < 0) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("malformed 'offset' property of 'raw' driver")); + return -1; + } + + if (has_size && + virJSONValueObjectGetNumberUlong(json, "size", &src->sliceStorage->size) < 0) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("malformed 'size' property of 'raw' driver")); + return -1; + } + } + + /* 'raw' is a format driver so it can have protocol driver children */ + if (!(file = virJSONValueObjectGetObject(json, "file"))) { + virReportError(VIR_ERR_INVALID_ARG, + _("JSON backing volume definition '%s' lacks 'file' object"), + jsonstr); + return -1; + } + + return virStorageSourceParseBackingJSONInternal(src, file, jsonstr, false); +} + + +static int +virStorageSourceParseBackingJSONVxHS(virStorageSourcePtr src, + virJSONValuePtr json, + const char *jsonstr G_GNUC_UNUSED, + int opaque G_GNUC_UNUSED) +{ + const char *vdisk_id = virJSONValueObjectGetString(json, "vdisk-id"); + virJSONValuePtr server = virJSONValueObjectGetObject(json, "server"); + + if (!vdisk_id || !server) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing 'vdisk-id' or 'server' attribute in " + "JSON backing definition for VxHS volume")); + return -1; + } + + src->type = VIR_STORAGE_TYPE_NETWORK; + src->protocol = VIR_STORAGE_NET_PROTOCOL_VXHS; + + src->path = g_strdup(vdisk_id); + + src->hosts = g_new0(virStorageNetHostDef, 1); + src->nhosts = 1; + + if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts, + server) < 0) + return -1; + + return 0; +} + + +static int +virStorageSourceParseBackingJSONNVMe(virStorageSourcePtr src, + virJSONValuePtr json, + const char *jsonstr G_GNUC_UNUSED, + int opaque G_GNUC_UNUSED) +{ + g_autoptr(virStorageSourceNVMeDef) nvme = g_new0(virStorageSourceNVMeDef, 1); + const char *device = virJSONValueObjectGetString(json, "device"); + + if (!device || virPCIDeviceAddressParse((char *) device, &nvme->pciAddr) < 0) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing or malformed 'device' field of 'nvme' storage")); + return -1; + } + + if (virJSONValueObjectGetNumberUlong(json, "namespace", &nvme->namespc) < 0 || + nvme->namespc == 0) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing or malformed 'namespace' field of 'nvme' storage")); + return -1; + } + + src->type = VIR_STORAGE_TYPE_NVME; + src->nvme = g_steal_pointer(&nvme); + + return 0; +} + + +struct virStorageSourceJSONDriverParser { + const char *drvname; + bool formatdriver; + /** + * The callback gets a pre-allocated storage source @src and the JSON + * object to parse. The callback shall return -1 on error and report error + * 0 on success and 1 in cases when the configuration itself is valid, but + * can't be converted to libvirt's configuration (e.g. inline authentication + * credentials are present). + */ + int (*func)(virStorageSourcePtr src, virJSONValuePtr json, const char *jsonstr, int opaque); + int opaque; +}; + +static const struct virStorageSourceJSONDriverParser jsonParsers[] = { + {"file", false, virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_FILE}, + {"host_device", false, virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_BLOCK}, + {"host_cdrom", false, virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_BLOCK}, + {"http", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_HTTP}, + {"https", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_HTTPS}, + {"ftp", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_FTP}, + {"ftps", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_FTPS}, + {"tftp", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_TFTP}, + {"gluster", false, virStorageSourceParseBackingJSONGluster, 0}, + {"iscsi", false, virStorageSourceParseBackingJSONiSCSI, 0}, + {"nbd", false, virStorageSourceParseBackingJSONNbd, 0}, + {"sheepdog", false, virStorageSourceParseBackingJSONSheepdog, 0}, + {"ssh", false, virStorageSourceParseBackingJSONSSH, 0}, + {"rbd", false, virStorageSourceParseBackingJSONRBD, 0}, + {"raw", true, virStorageSourceParseBackingJSONRaw, 0}, + {"vxhs", false, virStorageSourceParseBackingJSONVxHS, 0}, + {"nvme", false, virStorageSourceParseBackingJSONNVMe, 0}, +}; + + + +static int +virStorageSourceParseBackingJSONInternal(virStorageSourcePtr src, + virJSONValuePtr json, + const char *jsonstr, + bool allowformat) +{ + const char *drvname; + size_t i; + + if (!(drvname = virJSONValueObjectGetString(json, "driver"))) { + virReportError(VIR_ERR_INVALID_ARG, + _("JSON backing volume definition '%s' lacks driver name"), + jsonstr); + return -1; + } + + for (i = 0; i < G_N_ELEMENTS(jsonParsers); i++) { + if (STRNEQ(drvname, jsonParsers[i].drvname)) + continue; + + if (jsonParsers[i].formatdriver && !allowformat) { + virReportError(VIR_ERR_INVALID_ARG, + _("JSON backing volume definition '%s' must not have nested format drivers"), + jsonstr); + return -1; + } + + return jsonParsers[i].func(src, json, jsonstr, jsonParsers[i].opaque); + } + + virReportError(VIR_ERR_INTERNAL_ERROR, + _("missing parser implementation for JSON backing volume " + "driver '%s'"), drvname); + return -1; +} + + +static int +virStorageSourceParseBackingJSON(virStorageSourcePtr src, + const char *json) +{ + g_autoptr(virJSONValue) root = NULL; + g_autoptr(virJSONValue) deflattened = NULL; + virJSONValuePtr file = NULL; + + if (!(root = virJSONValueFromString(json))) + return -1; + + if (!(deflattened = virJSONValueObjectDeflatten(root))) + return -1; + + /* There are 2 possible syntaxes: + * 1) json:{"file":{"driver":...}} + * 2) json:{"driver":...} + * Remove the 'file' wrapper object in case 1. + */ + if (!virJSONValueObjectHasKey(deflattened, "driver")) + file = virJSONValueObjectGetObject(deflattened, "file"); + + if (!file) + file = deflattened; + + return virStorageSourceParseBackingJSONInternal(src, file, json, true); +} + + +/** + * virStorageSourceNewFromBackingAbsolute + * @path: string representing absolute location of a storage source + * @src: filled with virStorageSource object representing @path + * + * Returns 0 on success, 1 if we could parse all location data but @path + * specified other data unrepresentable by libvirt (e.g. inline authentication). + * In both cases @src is filled. On error -1 is returned @src is NULL and an + * error is reported. + */ +int +virStorageSourceNewFromBackingAbsolute(const char *path, + virStorageSourcePtr *src) +{ + const char *json; + const char *dirpath; + int rc = 0; + g_autoptr(virStorageSource) def = virStorageSourceNew(); + + *src = NULL; + + if (virFileIsFile(path)) { + def->type = VIR_STORAGE_TYPE_FILE; + + def->path = g_strdup(path); + } else { + if ((dirpath = STRSKIP(path, "fat:"))) { + def->type = VIR_STORAGE_TYPE_DIR; + def->format = VIR_STORAGE_FILE_FAT; + def->path = g_strdup(dirpath); + *src = g_steal_pointer(&def); + return 0; + } + + def->type = VIR_STORAGE_TYPE_NETWORK; + + VIR_DEBUG("parsing backing store string: '%s'", path); + + /* handle URI formatted backing stores */ + if ((json = STRSKIP(path, "json:"))) + rc = virStorageSourceParseBackingJSON(def, json); + else if (strstr(path, "://")) + rc = virStorageSourceParseBackingURI(def, path); + else + rc = virStorageSourceParseBackingColon(def, path); + + if (rc < 0) + return -1; + + virStorageSourceNetworkAssignDefaultPorts(def); + + /* Some of the legacy parsers parse authentication data since they are + * also used in other places. For backing store detection the + * authentication data would be invalid anyways, so we clear it */ + if (def->auth) { + virStorageAuthDefFree(def->auth); + def->auth = NULL; + } + } + + *src = g_steal_pointer(&def); + return rc; +} + + +/** + * virStorageSourceNewFromChild: + * @parent: storage source parent + * @child: returned child/backing store definition + * @parentRaw: raw child string (backingStoreRaw) + * + * Creates a storage source which describes the backing image of @parent and + * fills it into @backing depending on the passed parentRaw (backingStoreRaw) + * and other data. Note that for local storage this function accesses the file + * to update the actual type of the child store. + * + * Returns 0 on success, 1 if we could parse all location data but the child + * store specification contained other data unrepresentable by libvirt (e.g. + * inline authentication). + * In both cases @src is filled. On error -1 is returned @src is NULL and an + * error is reported. + */ +static int +virStorageSourceNewFromChild(virStorageSourcePtr parent, + const char *parentRaw, + virStorageSourcePtr *child) +{ + struct stat st; + g_autoptr(virStorageSource) def = NULL; + int rc = 0; + + *child = NULL; + + if (virFileIsRelative(parentRaw)) { + if (!(def = virStorageSourceNewFromBackingRelative(parent, parentRaw))) + return -1; + } else { + if ((rc = virStorageSourceNewFromBackingAbsolute(parentRaw, &def)) < 0) + return -1; + } + + /* possibly update local type */ + if (def->type == VIR_STORAGE_TYPE_FILE) { + if (stat(def->path, &st) == 0) { + if (S_ISDIR(st.st_mode)) { + def->type = VIR_STORAGE_TYPE_DIR; + def->format = VIR_STORAGE_FILE_DIR; + } else if (S_ISBLK(st.st_mode)) { + def->type = VIR_STORAGE_TYPE_BLOCK; + } + } + } + + /* copy parent's labelling and other top level stuff */ + if (virStorageSourceInitChainElement(def, parent, true) < 0) + return -1; + + def->detected = true; + + *child = g_steal_pointer(&def); + return rc; +} + + +int +virStorageSourceNewFromBacking(virStorageSourcePtr parent, + virStorageSourcePtr *backing) +{ + int rc; + + if ((rc = virStorageSourceNewFromChild(parent, + parent->backingStoreRaw, + backing)) < 0) + return rc; + + (*backing)->format = parent->backingStoreRawFormat; + (*backing)->readonly = true; + return rc; +} + + +/** + * @src: disk source definition structure + * @fd: file descriptor + * @sb: stat buffer + * + * Updates src->physical depending on the actual type of storage being used. + * To be called for domain storage source reporting as the volume code does + * not set/use the 'type' field for the voldef->source.target + * + * Returns 0 on success, -1 on error. No libvirt errors are reported. + */ +int +virStorageSourceUpdatePhysicalSize(virStorageSourcePtr src, + int fd, + struct stat const *sb) +{ + off_t end; + virStorageType actual_type = virStorageSourceGetActualType(src); + + switch (actual_type) { + case VIR_STORAGE_TYPE_FILE: + case VIR_STORAGE_TYPE_NETWORK: + src->physical = sb->st_size; + break; + + case VIR_STORAGE_TYPE_BLOCK: + if ((end = lseek(fd, 0, SEEK_END)) == (off_t) -1) + return -1; + + src->physical = end; + break; + + case VIR_STORAGE_TYPE_DIR: + src->physical = 0; + break; + + /* We shouldn't get VOLUME, but the switch requires all cases */ + case VIR_STORAGE_TYPE_VOLUME: + case VIR_STORAGE_TYPE_NVME: + case VIR_STORAGE_TYPE_NONE: + case VIR_STORAGE_TYPE_LAST: + return -1; + } + + return 0; +} + + +/** + * @src: disk source definition structure + * @fd: file descriptor + * @sb: stat buffer + * + * Update the capacity, allocation, physical values for the storage @src + * Shared between the domain storage source for an inactive domain and the + * voldef source target as the result is not affected by the 'type' field. + * + * Returns 0 on success, -1 on error. + */ +int +virStorageSourceUpdateBackingSizes(virStorageSourcePtr src, + int fd, + struct stat const *sb) +{ + /* Get info for normal formats */ + if (S_ISREG(sb->st_mode) || fd == -1) { +#ifndef WIN32 + src->allocation = (unsigned long long)sb->st_blocks * + (unsigned long long)DEV_BSIZE; +#else + src->allocation = sb->st_size; +#endif + /* Regular files may be sparse, so logical size (capacity) is not same + * as actual allocation above + */ + src->capacity = sb->st_size; + + /* Allocation tracks when the file is sparse, physical is the + * last offset of the file. */ + src->physical = sb->st_size; + } else if (S_ISDIR(sb->st_mode)) { + src->allocation = 0; + src->capacity = 0; + src->physical = 0; + } else if (fd >= 0) { + off_t end; + + /* XXX this is POSIX compliant, but doesn't work for CHAR files, + * only BLOCK. There is a Linux specific ioctl() for getting + * size of both CHAR / BLOCK devices we should check for in + * configure + * + * NB. Because we configure with AC_SYS_LARGEFILE, off_t + * should be 64 bits on all platforms. For block devices, we + * have to seek (safe even if someone else is writing) to + * determine physical size, and assume that allocation is the + * same as physical (but can refine that assumption later if + * qemu is still running). + */ + if ((end = lseek(fd, 0, SEEK_END)) == (off_t)-1) { + virReportSystemError(errno, + _("failed to seek to end of %s"), src->path); + return -1; + } + src->physical = end; + src->allocation = end; + src->capacity = end; + } + + return 0; +} + + +/** + * @src: disk source definition structure + * @buf: buffer to the storage file header + * @len: length of the storage file header + * + * Update the storage @src capacity. + * + * Returns 0 on success, -1 on error. + */ +int +virStorageSourceUpdateCapacity(virStorageSourcePtr src, + char *buf, + ssize_t len) +{ + int format = src->format; + g_autoptr(virStorageSource) meta = NULL; + + /* Raw files: capacity is physical size. For all other files: if + * the metadata has a capacity, use that, otherwise fall back to + * physical size. */ + if (format == VIR_STORAGE_FILE_NONE) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("no disk format for %s was specified"), + src->path); + return -1; + } + + if (format == VIR_STORAGE_FILE_RAW && !src->encryption) { + src->capacity = src->physical; + } else if ((meta = virStorageFileGetMetadataFromBuf(src->path, buf, + len, format))) { + src->capacity = meta->capacity ? meta->capacity : src->physical; + if (src->encryption && meta->encryption) + src->encryption->payload_offset = meta->encryption->payload_offset; + } else { + return -1; + } + + if (src->encryption && src->encryption->payload_offset != -1) + src->capacity -= src->encryption->payload_offset * 512; + + return 0; +} + + +static char * +virStorageFileCanonicalizeFormatPath(char **components, + size_t ncomponents, + bool beginSlash, + bool beginDoubleSlash) +{ + g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; + size_t i; + char *ret = NULL; + + if (beginSlash) + virBufferAddLit(&buf, "/"); + + if (beginDoubleSlash) + virBufferAddLit(&buf, "/"); + + for (i = 0; i < ncomponents; i++) { + if (i != 0) + virBufferAddLit(&buf, "/"); + + virBufferAdd(&buf, components[i], -1); + } + + /* if the output string is empty just return an empty string */ + if (!(ret = virBufferContentAndReset(&buf))) + ret = g_strdup(""); + + return ret; +} + + +static int +virStorageFileCanonicalizeInjectSymlink(const char *path, + size_t at, + char ***components, + size_t *ncomponents) +{ + char **tmp = NULL; + char **next; + size_t ntmp = 0; + int ret = -1; + + if (!(tmp = virStringSplitCount(path, "/", 0, &ntmp))) + goto cleanup; + + /* prepend */ + for (next = tmp; *next; next++) { + if (VIR_INSERT_ELEMENT(*components, at, *ncomponents, *next) < 0) + goto cleanup; + + at++; + } + + ret = 0; + + cleanup: + virStringListFreeCount(tmp, ntmp); + return ret; +} + + +char * +virStorageFileCanonicalizePath(const char *path, + virStorageFileSimplifyPathReadlinkCallback cb, + void *cbdata) +{ + GHashTable *cycle = NULL; + bool beginSlash = false; + bool beginDoubleSlash = false; + char **components = NULL; + size_t ncomponents = 0; + size_t i = 0; + size_t j = 0; + int rc; + char *ret = NULL; + g_autofree char *linkpath = NULL; + g_autofree char *currentpath = NULL; + + if (path[0] == '/') { + beginSlash = true; + + if (path[1] == '/' && path[2] != '/') + beginDoubleSlash = true; + } + + if (!(cycle = virHashNew(NULL))) + goto cleanup; + + if (!(components = virStringSplitCount(path, "/", 0, &ncomponents))) + goto cleanup; + + j = 0; + while (j < ncomponents) { + /* skip slashes */ + if (STREQ(components[j], "")) { + VIR_FREE(components[j]); + VIR_DELETE_ELEMENT(components, j, ncomponents); + continue; + } + j++; + } + + while (i < ncomponents) { + /* skip '.'s unless it's the last one remaining */ + if (STREQ(components[i], ".") && + (beginSlash || ncomponents > 1)) { + VIR_FREE(components[i]); + VIR_DELETE_ELEMENT(components, i, ncomponents); + continue; + } + + /* resolve changes to parent directory */ + if (STREQ(components[i], "..")) { + if (!beginSlash && + (i == 0 || STREQ(components[i - 1], ".."))) { + i++; + continue; + } + + VIR_FREE(components[i]); + VIR_DELETE_ELEMENT(components, i, ncomponents); + + if (i != 0) { + VIR_FREE(components[i - 1]); + VIR_DELETE_ELEMENT(components, i - 1, ncomponents); + i--; + } + + continue; + } + + /* check if the actual path isn't resulting into a symlink */ + if (!(currentpath = virStorageFileCanonicalizeFormatPath(components, + i + 1, + beginSlash, + beginDoubleSlash))) + goto cleanup; + + if ((rc = cb(currentpath, &linkpath, cbdata)) < 0) + goto cleanup; + + if (rc == 0) { + if (virHashLookup(cycle, currentpath)) { + virReportSystemError(ELOOP, + _("Failed to canonicalize path '%s'"), path); + goto cleanup; + } + + if (virHashAddEntry(cycle, currentpath, (void *) 1) < 0) + goto cleanup; + + if (linkpath[0] == '/') { + /* kill everything from the beginning including the actual component */ + i++; + while (i--) { + VIR_FREE(components[0]); + VIR_DELETE_ELEMENT(components, 0, ncomponents); + } + beginSlash = true; + + if (linkpath[1] == '/' && linkpath[2] != '/') + beginDoubleSlash = true; + else + beginDoubleSlash = false; + + i = 0; + } else { + VIR_FREE(components[i]); + VIR_DELETE_ELEMENT(components, i, ncomponents); + } + + if (virStorageFileCanonicalizeInjectSymlink(linkpath, + i, + &components, + &ncomponents) < 0) + goto cleanup; + + j = 0; + while (j < ncomponents) { + /* skip slashes */ + if (STREQ(components[j], "")) { + VIR_FREE(components[j]); + VIR_DELETE_ELEMENT(components, j, ncomponents); + continue; + } + j++; + } + + VIR_FREE(linkpath); + VIR_FREE(currentpath); + + continue; + } + + VIR_FREE(currentpath); + + i++; + } + + ret = virStorageFileCanonicalizeFormatPath(components, ncomponents, + beginSlash, beginDoubleSlash); + + cleanup: + virHashFree(cycle); + virStringListFreeCount(components, ncomponents); + + return ret; +} + + +/** + * virStorageFileRemoveLastPathComponent: + * + * @path: Path string to remove the last component from + * + * Removes the last path component of a path. This function is designed to be + * called on file paths only (no trailing slashes in @path). Caller is + * responsible to free the returned string. + */ +static char * +virStorageFileRemoveLastPathComponent(const char *path) +{ + char *ret; + + ret = g_strdup(NULLSTR_EMPTY(path)); + + virFileRemoveLastComponent(ret); + + return ret; +} + + +/* + * virStorageFileGetRelativeBackingPath: + * + * Resolve relative path to be written to the overlay of @top image when + * collapsing the backing chain between @top and @base. + * + * Returns 0 on success; 1 if backing chain isn't relative and -1 on error. + */ +int +virStorageFileGetRelativeBackingPath(virStorageSourcePtr top, + virStorageSourcePtr base, + char **relpath) +{ + virStorageSourcePtr next; + g_autofree char *tmp = NULL; + g_autofree char *path = NULL; + + *relpath = NULL; + + for (next = top; virStorageSourceIsBacking(next); next = next->backingStore) { + if (!next->relPath) + return 1; + + if (!(tmp = virStorageFileRemoveLastPathComponent(path))) + return -1; + + VIR_FREE(path); + + path = g_strdup_printf("%s%s", tmp, next->relPath); + + VIR_FREE(tmp); + + if (next == base) + break; + } + + if (next != base) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to resolve relative backing name: " + "base image is not in backing chain")); + return -1; + } + + *relpath = g_steal_pointer(&path); + return 0; +} + + +/** + * virStorageSourceFindByNodeName: + * @top: backing chain top + * @nodeName: node name to find in backing chain + * + * Looks up the given storage source in the backing chain and returns the + * pointer to it. + * On failure NULL is returned and no error is reported. + */ +virStorageSourcePtr +virStorageSourceFindByNodeName(virStorageSourcePtr top, + const char *nodeName) +{ + virStorageSourcePtr tmp; + + for (tmp = top; virStorageSourceIsBacking(tmp); tmp = tmp->backingStore) { + if ((tmp->nodeformat && STREQ(tmp->nodeformat, nodeName)) || + (tmp->nodestorage && STREQ(tmp->nodestorage, nodeName))) + return tmp; + } + + return NULL; +} + + +int +virStorageSourcePrivateDataParseRelPath(xmlXPathContextPtr ctxt, + virStorageSourcePtr src) +{ + src->relPath = virXPathString("string(./relPath)", ctxt); + return 0; +} + + +int +virStorageSourcePrivateDataFormatRelPath(virStorageSourcePtr src, + virBufferPtr buf) +{ + if (src->relPath) + virBufferEscapeString(buf, "<relPath>%s</relPath>\n", src->relPath); + + return 0; +} + + +static bool +virStorageFileIsInitialized(const virStorageSource *src) +{ + return src && src->drv; +} + + +/** + * virStorageFileGetBackendForSupportCheck: + * @src: storage source to check support for + * @backend: pointer to the storage backend for @src if it's supported + * + * Returns 0 if @src is not supported by any storage backend currently linked + * 1 if it is supported and -1 on error with an error reported. + */ +static int +virStorageFileGetBackendForSupportCheck(const virStorageSource *src, + virStorageFileBackendPtr *backend) +{ + int actualType; + + + if (!src) { + *backend = NULL; + return 0; + } + + if (src->drv) { + virStorageDriverDataPtr drv = src->drv; + *backend = drv->backend; + return 1; + } + + actualType = virStorageSourceGetActualType(src); + + if (virStorageFileBackendForType(actualType, src->protocol, false, backend) < 0) + return -1; + + if (!*backend) + return 0; + + return 1; +} + + +int +virStorageFileSupportsBackingChainTraversal(const virStorageSource *src) +{ + virStorageFileBackendPtr backend; + int rv; + + if ((rv = virStorageFileGetBackendForSupportCheck(src, &backend)) < 1) + return rv; + + return backend->storageFileGetUniqueIdentifier && + backend->storageFileRead && + backend->storageFileAccess ? 1 : 0; +} + + +/** + * virStorageFileSupportsSecurityDriver: + * + * @src: a storage file structure + * + * Check if a storage file supports operations needed by the security + * driver to perform labelling + */ +int +virStorageFileSupportsSecurityDriver(const virStorageSource *src) +{ + virStorageFileBackendPtr backend; + int rv; + + if ((rv = virStorageFileGetBackendForSupportCheck(src, &backend)) < 1) + return rv; + + return backend->storageFileChown ? 1 : 0; +} + + +/** + * virStorageFileSupportsAccess: + * + * @src: a storage file structure + * + * Check if a storage file supports checking if the storage source is accessible + * for the given vm. + */ +int +virStorageFileSupportsAccess(const virStorageSource *src) +{ + virStorageFileBackendPtr backend; + int rv; + + if ((rv = virStorageFileGetBackendForSupportCheck(src, &backend)) < 1) + return rv; + + return backend->storageFileAccess ? 1 : 0; +} + + +/** + * virStorageFileSupportsCreate: + * @src: a storage file structure + * + * Check if the storage driver supports creating storage described by @src + * via virStorageFileCreate. + */ +int +virStorageFileSupportsCreate(const virStorageSource *src) +{ + virStorageFileBackendPtr backend; + int rv; + + if ((rv = virStorageFileGetBackendForSupportCheck(src, &backend)) < 1) + return rv; + + return backend->storageFileCreate ? 1 : 0; +} + + +void +virStorageFileDeinit(virStorageSourcePtr src) +{ + virStorageDriverDataPtr drv = NULL; + + if (!virStorageFileIsInitialized(src)) + return; + + drv = src->drv; + + if (drv->backend && + drv->backend->backendDeinit) + drv->backend->backendDeinit(src); + + VIR_FREE(src->drv); +} + + +/** + * virStorageFileInitAs: + * + * @src: storage source definition + * @uid: uid used to access the file, or -1 for current uid + * @gid: gid used to access the file, or -1 for current gid + * + * Initialize a storage source to be used with storage driver. Use the provided + * uid and gid if possible for the operations. + * + * Returns 0 if the storage file was successfully initialized, -1 if the + * initialization failed. Libvirt error is reported. + */ +int +virStorageFileInitAs(virStorageSourcePtr src, + uid_t uid, + gid_t gid) +{ + int actualType = virStorageSourceGetActualType(src); + virStorageDriverDataPtr drv = g_new0(virStorageDriverData, 1); + + src->drv = drv; + + if (uid == (uid_t) -1) + drv->uid = geteuid(); + else + drv->uid = uid; + + if (gid == (gid_t) -1) + drv->gid = getegid(); + else + drv->gid = gid; + + if (virStorageFileBackendForType(actualType, + src->protocol, + true, + &drv->backend) < 0) + goto error; + + if (drv->backend->backendInit && + drv->backend->backendInit(src) < 0) + goto error; + + return 0; + + error: + VIR_FREE(src->drv); + return -1; +} + + +/** + * virStorageFileInit: + * + * See virStorageFileInitAs. The file is initialized to be accessed by the + * current user. + */ +int +virStorageFileInit(virStorageSourcePtr src) +{ + return virStorageFileInitAs(src, -1, -1); +} + + +/** + * virStorageFileCreate: Creates an empty storage file via storage driver + * + * @src: file structure pointing to the file + * + * Returns 0 on success, -2 if the function isn't supported by the backend, + * -1 on other failure. Errno is set in case of failure. + */ +int +virStorageFileCreate(virStorageSourcePtr src) +{ + virStorageDriverDataPtr drv = NULL; + int ret; + + if (!virStorageFileIsInitialized(src)) { + errno = ENOSYS; + return -2; + } + + drv = src->drv; + + if (!drv->backend->storageFileCreate) { + errno = ENOSYS; + return -2; + } + + ret = drv->backend->storageFileCreate(src); + + VIR_DEBUG("created storage file %p: ret=%d, errno=%d", + src, ret, errno); + + return ret; +} + + +/** + * virStorageFileUnlink: Unlink storage file via storage driver + * + * @src: file structure pointing to the file + * + * Unlinks the file described by the @file structure. + * + * Returns 0 on success, -2 if the function isn't supported by the backend, + * -1 on other failure. Errno is set in case of failure. + */ +int +virStorageFileUnlink(virStorageSourcePtr src) +{ + virStorageDriverDataPtr drv = NULL; + int ret; + + if (!virStorageFileIsInitialized(src)) { + errno = ENOSYS; + return -2; + } + + drv = src->drv; + + if (!drv->backend->storageFileUnlink) { + errno = ENOSYS; + return -2; + } + + ret = drv->backend->storageFileUnlink(src); + + VIR_DEBUG("unlinked storage file %p: ret=%d, errno=%d", + src, ret, errno); + + return ret; +} + + +/** + * virStorageFileStat: returns stat struct of a file via storage driver + * + * @src: file structure pointing to the file + * @stat: stat structure to return data + * + * Returns 0 on success, -2 if the function isn't supported by the backend, + * -1 on other failure. Errno is set in case of failure. +*/ +int +virStorageFileStat(virStorageSourcePtr src, + struct stat *st) +{ + virStorageDriverDataPtr drv = NULL; + int ret; + + if (!virStorageFileIsInitialized(src)) { + errno = ENOSYS; + return -2; + } + + drv = src->drv; + + if (!drv->backend->storageFileStat) { + errno = ENOSYS; + return -2; + } + + ret = drv->backend->storageFileStat(src, st); + + VIR_DEBUG("stat of storage file %p: ret=%d, errno=%d", + src, ret, errno); + + return ret; +} + + +/** + * virStorageFileRead: read bytes from a file into a buffer + * + * @src: file structure pointing to the file + * @offset: number of bytes to skip in the storage file + * @len: maximum number of bytes read from the storage file + * @buf: buffer to read the data into. (buffer shall be freed by caller) + * + * Returns the count of bytes read on success and -1 on failure, -2 if the + * function isn't supported by the backend. + * Libvirt error is reported on failure. + */ +ssize_t +virStorageFileRead(virStorageSourcePtr src, + size_t offset, + size_t len, + char **buf) +{ + virStorageDriverDataPtr drv = NULL; + ssize_t ret; + + if (!virStorageFileIsInitialized(src)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("storage file backend not initialized")); + return -1; + } + + drv = src->drv; + + if (!drv->backend->storageFileRead) + return -2; + + ret = drv->backend->storageFileRead(src, offset, len, buf); + + VIR_DEBUG("read '%zd' bytes from storage '%p' starting at offset '%zu'", + ret, src, offset); + + return ret; +} + + +/* + * virStorageFileGetUniqueIdentifier: Get a unique string describing the volume + * + * @src: file structure pointing to the file + * + * Returns a string uniquely describing a single volume (canonical path). + * The string shall not be freed and is valid until the storage file is + * deinitialized. Returns NULL on error and sets a libvirt error code */ +const char * +virStorageFileGetUniqueIdentifier(virStorageSourcePtr src) +{ + virStorageDriverDataPtr drv = NULL; + + if (!virStorageFileIsInitialized(src)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("storage file backend not initialized")); + return NULL; + } + + drv = src->drv; + + if (!drv->backend->storageFileGetUniqueIdentifier) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unique storage file identifier not implemented for " + "storage type %s (protocol: %s)'"), + virStorageTypeToString(src->type), + virStorageNetProtocolTypeToString(src->protocol)); + return NULL; + } + + return drv->backend->storageFileGetUniqueIdentifier(src); +} + + +/** + * virStorageFileAccess: Check accessibility of a storage file + * + * @src: storage file to check access permissions + * @mode: accessibility check options (see man 2 access) + * + * Returns 0 on success, -1 on error and sets errno. No libvirt + * error is reported. Returns -2 if the operation isn't supported + * by libvirt storage backend. + */ +int +virStorageFileAccess(virStorageSourcePtr src, + int mode) +{ + virStorageDriverDataPtr drv = NULL; + + if (!virStorageFileIsInitialized(src)) { + errno = ENOSYS; + return -2; + } + + drv = src->drv; + + if (!drv->backend->storageFileAccess) { + errno = ENOSYS; + return -2; + } + + return drv->backend->storageFileAccess(src, mode); +} + + +/** + * virStorageFileChown: Change owner of a storage file + * + * @src: storage file to change owner of + * @uid: new owner id + * @gid: new group id + * + * Returns 0 on success, -1 on error and sets errno. No libvirt + * error is reported. Returns -2 if the operation isn't supported + * by libvirt storage backend. + */ +int +virStorageFileChown(const virStorageSource *src, + uid_t uid, + gid_t gid) +{ + virStorageDriverDataPtr drv = NULL; + + if (!virStorageFileIsInitialized(src)) { + errno = ENOSYS; + return -2; + } + + drv = src->drv; + + if (!drv->backend->storageFileChown) { + errno = ENOSYS; + return -2; + } + + VIR_DEBUG("chown of storage file %p to %u:%u", + src, (unsigned int)uid, (unsigned int)gid); + + return drv->backend->storageFileChown(src, uid, gid); +} + + +/** + * virStorageFileReportBrokenChain: + * + * @errcode: errno when accessing @src + * @src: inaccessible file in the backing chain of @parent + * @parent: root virStorageSource being checked + * + * Reports the correct error message if @src is missing in the backing chain + * for @parent. + */ +void +virStorageFileReportBrokenChain(int errcode, + virStorageSourcePtr src, + virStorageSourcePtr parent) +{ + if (src->drv) { + virStorageDriverDataPtr drv = src->drv; + unsigned int access_user = drv->uid; + unsigned int access_group = drv->gid; + + if (src == parent) { + virReportSystemError(errcode, + _("Cannot access storage file '%s' " + "(as uid:%u, gid:%u)"), + src->path, access_user, access_group); + } else { + virReportSystemError(errcode, + _("Cannot access backing file '%s' " + "of storage file '%s' (as uid:%u, gid:%u)"), + src->path, parent->path, access_user, access_group); + } + } else { + if (src == parent) { + virReportSystemError(errcode, + _("Cannot access storage file '%s'"), + src->path); + } else { + virReportSystemError(errcode, + _("Cannot access backing file '%s' " + "of storage file '%s'"), + src->path, parent->path); + } + } +} + + +static int +virStorageFileGetMetadataRecurseReadHeader(virStorageSourcePtr src, + virStorageSourcePtr parent, + uid_t uid, + gid_t gid, + char **buf, + size_t *headerLen, + GHashTable *cycle) +{ + int ret = -1; + const char *uniqueName; + ssize_t len; + + if (virStorageFileInitAs(src, uid, gid) < 0) + return -1; + + if (virStorageFileAccess(src, F_OK) < 0) { + virStorageFileReportBrokenChain(errno, src, parent); + goto cleanup; + } + + if (!(uniqueName = virStorageFileGetUniqueIdentifier(src))) + goto cleanup; + + if (virHashHasEntry(cycle, uniqueName)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("backing store for %s (%s) is self-referential"), + NULLSTR(src->path), uniqueName); + goto cleanup; + } + + if (virHashAddEntry(cycle, uniqueName, NULL) < 0) + goto cleanup; + + if ((len = virStorageFileRead(src, 0, VIR_STORAGE_MAX_HEADER, buf)) < 0) + goto cleanup; + + *headerLen = len; + ret = 0; + + cleanup: + virStorageFileDeinit(src); + return ret; +} + + +/* Recursive workhorse for virStorageFileGetMetadata. */ +static int +virStorageFileGetMetadataRecurse(virStorageSourcePtr src, + virStorageSourcePtr parent, + uid_t uid, gid_t gid, + bool report_broken, + GHashTable *cycle, + unsigned int depth) +{ + virStorageFileFormat orig_format = src->format; + size_t headerLen; + int rv; + g_autofree char *buf = NULL; + g_autoptr(virStorageSource) backingStore = NULL; + + VIR_DEBUG("path=%s format=%d uid=%u gid=%u", + NULLSTR(src->path), src->format, + (unsigned int)uid, (unsigned int)gid); + + if (src->format == VIR_STORAGE_FILE_AUTO_SAFE) + src->format = VIR_STORAGE_FILE_AUTO; + + /* exit if we can't load information about the current image */ + rv = virStorageFileSupportsBackingChainTraversal(src); + if (rv <= 0) { + if (orig_format == VIR_STORAGE_FILE_AUTO) + return -2; + + return rv; + } + + if (virStorageFileGetMetadataRecurseReadHeader(src, parent, uid, gid, + &buf, &headerLen, cycle) < 0) + return -1; + + if (virStorageFileGetMetadataInternal(src, buf, headerLen) < 0) + return -1; + + /* If we probed the format we MUST ensure that nothing else than the current + * image is considered for security labelling and/or recursion. */ + if (orig_format == VIR_STORAGE_FILE_AUTO) { + if (src->backingStoreRaw) { + src->format = VIR_STORAGE_FILE_RAW; + VIR_FREE(src->backingStoreRaw); + return -2; + } + } + + if (src->backingStoreRaw) { + if ((rv = virStorageSourceNewFromBacking(src, &backingStore)) < 0) + return -1; + + /* the backing file would not be usable for VM usage */ + if (rv == 1) + return 0; + + if ((rv = virStorageFileGetMetadataRecurse(backingStore, parent, + uid, gid, + report_broken, + cycle, depth + 1)) < 0) { + if (!report_broken) + return 0; + + if (rv == -2) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("format of backing image '%s' of image '%s' was not specified in the image metadata " + "(See https://libvirt.org/kbase/backing_chains.html for troubleshooting)"), + src->backingStoreRaw, NULLSTR(src->path)); + } + + return -1; + } + + backingStore->id = depth; + src->backingStore = g_steal_pointer(&backingStore); + } else { + /* add terminator */ + src->backingStore = virStorageSourceNew(); + } + + return 0; +} + + +/** + * virStorageFileGetMetadata: + * + * Extract metadata about the storage volume with the specified + * image format. If image format is VIR_STORAGE_FILE_AUTO, it + * will probe to automatically identify the format. Recurses through + * the entire chain. + * + * Open files using UID and GID (or pass -1 for the current user/group). + * Treat any backing files without explicit type as raw, unless ALLOW_PROBE. + * + * Callers are advised never to use VIR_STORAGE_FILE_AUTO as a + * format, since a malicious guest can turn a raw file into any + * other non-raw format at will. + * + * If @report_broken is true, the whole function fails with a possibly sane + * error instead of just returning a broken chain. Note that the inability for + * libvirt to traverse a given source is not considered an error. + * + * Caller MUST free result after use via virObjectUnref. + */ +int +virStorageFileGetMetadata(virStorageSourcePtr src, + uid_t uid, gid_t gid, + bool report_broken) +{ + GHashTable *cycle = NULL; + virStorageType actualType = virStorageSourceGetActualType(src); + int ret = -1; + + VIR_DEBUG("path=%s format=%d uid=%u gid=%u report_broken=%d", + src->path, src->format, (unsigned int)uid, (unsigned int)gid, + report_broken); + + if (!(cycle = virHashNew(NULL))) + return -1; + + if (src->format <= VIR_STORAGE_FILE_NONE) { + if (actualType == VIR_STORAGE_TYPE_DIR) + src->format = VIR_STORAGE_FILE_DIR; + else + src->format = VIR_STORAGE_FILE_RAW; + } + + ret = virStorageFileGetMetadataRecurse(src, src, uid, gid, + report_broken, cycle, 1); + + virHashFree(cycle); + return ret; +} + + +/** + * virStorageFileGetBackingStoreStr: + * @src: storage object + * + * Extracts the backing store string as stored in the storage volume described + * by @src and returns it to the user. Caller is responsible for freeing it. + * In case when the string can't be retrieved or does not exist NULL is + * returned. + */ +int +virStorageFileGetBackingStoreStr(virStorageSourcePtr src, + char **backing) +{ + ssize_t headerLen; + int rv; + g_autofree char *buf = NULL; + g_autoptr(virStorageSource) tmp = NULL; + + *backing = NULL; + + /* exit if we can't load information about the current image */ + if (!virStorageFileSupportsBackingChainTraversal(src)) + return 0; + + rv = virStorageFileAccess(src, F_OK); + if (rv == -2) + return 0; + if (rv < 0) { + virStorageFileReportBrokenChain(errno, src, src); + return -1; + } + + if ((headerLen = virStorageFileRead(src, 0, VIR_STORAGE_MAX_HEADER, + &buf)) < 0) { + if (headerLen == -2) + return 0; + return -1; + } + + if (!(tmp = virStorageSourceCopy(src, false))) + return -1; + + if (virStorageFileGetMetadataInternal(tmp, buf, headerLen) < 0) + return -1; + + *backing = g_steal_pointer(&tmp->backingStoreRaw); + return 0; +} diff --git a/src/storage_file/storage_file.h b/src/storage_file/storage_file.h new file mode 100644 index 0000000000..8afedc35b3 --- /dev/null +++ b/src/storage_file/storage_file.h @@ -0,0 +1,197 @@ +/* + * storage_file.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 "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 + +#ifndef DEV_BSIZE +# define DEV_BSIZE 512 +#endif + +int +virStorageFileProbeFormat(const char *path, + uid_t uid, + gid_t gid); + +virStorageSourcePtr +virStorageFileGetMetadataFromFD(const char *path, + int fd, + int format); + +virStorageSourcePtr +virStorageFileGetMetadataFromBuf(const char *path, + char *buf, + size_t len, + int format) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); + +int +virStorageFileParseChainIndex(const char *diskTarget, + const char *name, + unsigned int *chainIndex) + ATTRIBUTE_NONNULL(3); + +int +virStorageFileParseBackingStoreStr(const char *str, + char **target, + unsigned int *chainIndex) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3); + +virStorageSourcePtr +virStorageFileChainLookup(virStorageSourcePtr chain, + virStorageSourcePtr startFrom, + const char *name, + unsigned int idx, + virStorageSourcePtr *parent) + ATTRIBUTE_NONNULL(1); + +int +virStorageSourceUpdatePhysicalSize(virStorageSourcePtr src, + int fd, + struct stat const *sb); +int +virStorageSourceUpdateBackingSizes(virStorageSourcePtr src, + int fd, + struct stat const *sb); + +int +virStorageSourceUpdateCapacity(virStorageSourcePtr src, + char *buf, + ssize_t len); + +int +virStorageSourceNewFromBacking(virStorageSourcePtr parent, + virStorageSourcePtr *backing); + +int +virStorageSourceParseRBDColonString(const char *rbdstr, + virStorageSourcePtr src) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); + +typedef int +(*virStorageFileSimplifyPathReadlinkCallback)(const char *path, + char **link, + void *data); + +char * +virStorageFileCanonicalizePath(const char *path, + virStorageFileSimplifyPathReadlinkCallback cb, + void *cbdata); + +int +virStorageFileGetRelativeBackingPath(virStorageSourcePtr from, + virStorageSourcePtr to, + char **relpath) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3); + +int +virStorageSourceNewFromBackingAbsolute(const char *path, + virStorageSourcePtr *src); + +virStorageSourcePtr +virStorageSourceFindByNodeName(virStorageSourcePtr top, + const char *nodeName) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); + +int +virStorageSourcePrivateDataParseRelPath(xmlXPathContextPtr ctxt, + virStorageSourcePtr src); + +int +virStorageSourcePrivateDataFormatRelPath(virStorageSourcePtr src, + virBufferPtr buf); + +int +virStorageFileInit(virStorageSourcePtr src); + +int +virStorageFileInitAs(virStorageSourcePtr src, + uid_t uid, + gid_t gid); + +void +virStorageFileDeinit(virStorageSourcePtr src); + +int +virStorageFileCreate(virStorageSourcePtr src); + +int +virStorageFileUnlink(virStorageSourcePtr src); + +int +virStorageFileStat(virStorageSourcePtr src, + struct stat *stat); + +ssize_t +virStorageFileRead(virStorageSourcePtr src, + size_t offset, + size_t len, + char **buf); + +const char * +virStorageFileGetUniqueIdentifier(virStorageSourcePtr src); + +int +virStorageFileAccess(virStorageSourcePtr src, + int mode); + +int +virStorageFileChown(const virStorageSource *src, + uid_t uid, + gid_t gid); + +int +virStorageFileSupportsSecurityDriver(const virStorageSource *src); + +int +virStorageFileSupportsAccess(const virStorageSource *src); + +int +virStorageFileSupportsCreate(const virStorageSource *src); + +int +virStorageFileSupportsBackingChainTraversal(const virStorageSource *src); + +int +virStorageFileGetMetadata(virStorageSourcePtr src, + uid_t uid, gid_t gid, + bool report_broken) + ATTRIBUTE_NONNULL(1); + +int +virStorageFileGetBackingStoreStr(virStorageSourcePtr src, + char **backing) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); + +void +virStorageFileReportBrokenChain(int errcode, + virStorageSourcePtr src, + virStorageSourcePtr parent); diff --git a/src/storage_file/storage_file_gluster.c b/src/storage_file/storage_file_gluster.c index e26dfdfd99..599afada2c 100644 --- a/src/storage_file/storage_file_gluster.c +++ b/src/storage_file/storage_file_gluster.c @@ -23,6 +23,7 @@ #include <glusterfs/api/glfs.h> +#include "storage_file.h" #include "storage_file_gluster.h" #include "viralloc.h" #include "virerror.h" diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c index 524204a56c..3489934546 100644 --- a/src/util/virstoragefile.c +++ b/src/util/virstoragefile.c @@ -20,26 +20,17 @@ */ #include <config.h> -#include "virstoragefilebackend.h" +#include "virstoragefile.h" -#include <unistd.h> -#include <fcntl.h> #include "viralloc.h" #include "virxml.h" -#include "viruuid.h" #include "virerror.h" #include "virlog.h" -#include "virfile.h" #include "vircommand.h" -#include "virhash.h" -#include "virendian.h" #include "virstring.h" -#include "viruri.h" #include "virbuffer.h" -#include "virjson.h" #include "virstorageencryption.h" #include "virsecret.h" -#include "virutil.h" #define VIR_FROM_THIS VIR_FROM_STORAGE @@ -111,1054 +102,6 @@ VIR_ENUM_IMPL(virStorageAuth, "none", "chap", "ceph", ); -enum lv_endian { - LV_LITTLE_ENDIAN = 1, /* 1234 */ - LV_BIG_ENDIAN /* 4321 */ -}; - -enum { - BACKING_STORE_OK, - BACKING_STORE_INVALID, - BACKING_STORE_ERROR, -}; - -#define FILE_TYPE_VERSIONS_LAST 3 - -struct FileEncryptionInfo { - int format; /* Encryption format to assign */ - - int magicOffset; /* Byte offset of the magic */ - const char *magic; /* Optional string of magic */ - - enum lv_endian endian; /* Endianness of file format */ - - int versionOffset; /* Byte offset from start of file - * where we find version number, - * -1 to always fail the version test, - * -2 to always pass the version test */ - int versionSize; /* Size in bytes of version data (0, 2, or 4) */ - int versionNumbers[FILE_TYPE_VERSIONS_LAST]; - /* Version numbers to validate. Zeroes are ignored. */ - - int modeOffset; /* Byte offset of the format native encryption mode */ - char modeValue; /* Value expected at offset */ - - int payloadOffset; /* start offset of the volume data (in 512 byte sectors) */ -}; - -struct FileTypeInfo { - int magicOffset; /* Byte offset of the magic */ - const char *magic; /* Optional string of file magic - * to check at head of file */ - enum lv_endian endian; /* Endianness of file format */ - - int versionOffset; /* Byte offset from start of file - * where we find version number, - * -1 to always fail the version test, - * -2 to always pass the version test */ - int versionSize; /* Size in bytes of version data (0, 2, or 4) */ - int versionNumbers[FILE_TYPE_VERSIONS_LAST]; - /* Version numbers to validate. Zeroes are ignored. */ - int sizeOffset; /* Byte offset from start of file - * where we find capacity info, - * -1 to use st_size as capacity */ - int sizeBytes; /* Number of bytes for size field */ - int sizeMultiplier; /* A scaling factor if size is not in bytes */ - /* Store a COW base image path (possibly relative), - * or NULL if there is no COW base image, to RES; - * return BACKING_STORE_* */ - const struct FileEncryptionInfo *cryptInfo; /* Encryption info */ - int (*getBackingStore)(char **res, int *format, - const char *buf, size_t buf_size); - int (*getFeatures)(virBitmapPtr *features, int format, - char *buf, ssize_t len); -}; - - -static int cowGetBackingStore(char **, int *, - const char *, size_t); -static int qcowXGetBackingStore(char **, int *, - const char *, size_t); -static int qcow2GetFeatures(virBitmapPtr *features, int format, - char *buf, ssize_t len); -static int vmdk4GetBackingStore(char **, int *, - const char *, size_t); -static int -qedGetBackingStore(char **, int *, const char *, size_t); - -#define QCOWX_HDR_VERSION (4) -#define QCOWX_HDR_BACKING_FILE_OFFSET (QCOWX_HDR_VERSION+4) -#define QCOWX_HDR_BACKING_FILE_SIZE (QCOWX_HDR_BACKING_FILE_OFFSET+8) -#define QCOWX_HDR_IMAGE_SIZE (QCOWX_HDR_BACKING_FILE_SIZE+4+4) - -#define QCOW1_HDR_CRYPT (QCOWX_HDR_IMAGE_SIZE+8+1+1+2) -#define QCOW2_HDR_CRYPT (QCOWX_HDR_IMAGE_SIZE+8) - -#define QCOW1_HDR_TOTAL_SIZE (QCOW1_HDR_CRYPT+4+8) -#define QCOW2_HDR_TOTAL_SIZE (QCOW2_HDR_CRYPT+4+4+8+8+4+4+8) - -#define QCOW2_HDR_EXTENSION_END 0 -#define QCOW2_HDR_EXTENSION_BACKING_FORMAT 0xE2792ACA - -#define QCOW2v3_HDR_FEATURES_INCOMPATIBLE (QCOW2_HDR_TOTAL_SIZE) -#define QCOW2v3_HDR_FEATURES_COMPATIBLE (QCOW2v3_HDR_FEATURES_INCOMPATIBLE+8) -#define QCOW2v3_HDR_FEATURES_AUTOCLEAR (QCOW2v3_HDR_FEATURES_COMPATIBLE+8) - -/* The location of the header size [4 bytes] */ -#define QCOW2v3_HDR_SIZE (QCOW2_HDR_TOTAL_SIZE+8+8+8+4) - -#define QED_HDR_FEATURES_OFFSET (4+4+4+4) -#define QED_HDR_IMAGE_SIZE (QED_HDR_FEATURES_OFFSET+8+8+8+8) -#define QED_HDR_BACKING_FILE_OFFSET (QED_HDR_IMAGE_SIZE+8) -#define QED_HDR_BACKING_FILE_SIZE (QED_HDR_BACKING_FILE_OFFSET+4) -#define QED_F_BACKING_FILE 0x01 -#define QED_F_BACKING_FORMAT_NO_PROBE 0x04 - -#define PLOOP_IMAGE_SIZE_OFFSET 36 -#define PLOOP_SIZE_MULTIPLIER 512 - -#define LUKS_HDR_MAGIC_LEN 6 -#define LUKS_HDR_VERSION_LEN 2 -#define LUKS_HDR_CIPHER_NAME_LEN 32 -#define LUKS_HDR_CIPHER_MODE_LEN 32 -#define LUKS_HDR_HASH_SPEC_LEN 32 -#define LUKS_HDR_PAYLOAD_LEN 4 - -/* Format described by qemu commit id '3e308f20e' */ -#define LUKS_HDR_VERSION_OFFSET LUKS_HDR_MAGIC_LEN -#define LUKS_HDR_PAYLOAD_OFFSET (LUKS_HDR_MAGIC_LEN+\ - LUKS_HDR_VERSION_LEN+\ - LUKS_HDR_CIPHER_NAME_LEN+\ - LUKS_HDR_CIPHER_MODE_LEN+\ - LUKS_HDR_HASH_SPEC_LEN) - -static struct FileEncryptionInfo const luksEncryptionInfo[] = { - { - .format = VIR_STORAGE_ENCRYPTION_FORMAT_LUKS, - - /* Magic is 'L','U','K','S', 0xBA, 0xBE */ - .magicOffset = 0, - .magic = "\x4c\x55\x4b\x53\xba\xbe", - .endian = LV_BIG_ENDIAN, - - .versionOffset = LUKS_HDR_VERSION_OFFSET, - .versionSize = LUKS_HDR_VERSION_LEN, - .versionNumbers = {1}, - - .modeOffset = -1, - .modeValue = -1, - - .payloadOffset = LUKS_HDR_PAYLOAD_OFFSET, - }, - { 0 } -}; - -static struct FileEncryptionInfo const qcow1EncryptionInfo[] = { - { - .format = VIR_STORAGE_ENCRYPTION_FORMAT_QCOW, - - .magicOffset = 0, - .magic = NULL, - .endian = LV_BIG_ENDIAN, - - .versionOffset = -1, - .versionSize = 0, - .versionNumbers = {}, - - .modeOffset = QCOW1_HDR_CRYPT, - .modeValue = 1, - - .payloadOffset = -1, - }, - { 0 } -}; - -static struct FileEncryptionInfo const qcow2EncryptionInfo[] = { - { - .format = VIR_STORAGE_ENCRYPTION_FORMAT_QCOW, - - .magicOffset = 0, - .magic = NULL, - .endian = LV_BIG_ENDIAN, - - .versionOffset = -1, - .versionSize = 0, - .versionNumbers = {}, - - .modeOffset = QCOW2_HDR_CRYPT, - .modeValue = 1, - - .payloadOffset = -1, - }, - { - .format = VIR_STORAGE_ENCRYPTION_FORMAT_LUKS, - - .magicOffset = 0, - .magic = NULL, - .endian = LV_BIG_ENDIAN, - - .versionOffset = -1, - .versionSize = 0, - .versionNumbers = {}, - - .modeOffset = QCOW2_HDR_CRYPT, - .modeValue = 2, - - .payloadOffset = -1, - }, - { 0 } -}; - -static struct FileTypeInfo const fileTypeInfo[] = { - [VIR_STORAGE_FILE_NONE] = { 0, NULL, LV_LITTLE_ENDIAN, - -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL }, - [VIR_STORAGE_FILE_RAW] = { 0, NULL, LV_LITTLE_ENDIAN, - -1, 0, {0}, 0, 0, 0, - luksEncryptionInfo, - NULL, NULL }, - [VIR_STORAGE_FILE_DIR] = { 0, NULL, LV_LITTLE_ENDIAN, - -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL }, - [VIR_STORAGE_FILE_BOCHS] = { - /*"Bochs Virtual HD Image", */ /* Untested */ - 0, NULL, - LV_LITTLE_ENDIAN, 64, 4, {0x20000}, - 32+16+16+4+4+4+4+4, 8, 1, NULL, NULL, NULL - }, - [VIR_STORAGE_FILE_CLOOP] = { - /* #!/bin/sh - #V2.0 Format - modprobe cloop file=$0 && mount -r -t iso9660 /dev/cloop $1 - */ /* Untested */ - 0, NULL, - LV_LITTLE_ENDIAN, -1, 0, {0}, - -1, 0, 0, NULL, NULL, NULL - }, - [VIR_STORAGE_FILE_DMG] = { - /* XXX QEMU says there's no magic for dmg, - * /usr/share/misc/magic lists double magic (both offsets - * would have to match) but then disables that check. */ - 0, NULL, - 0, -1, 0, {0}, - -1, 0, 0, NULL, NULL, NULL - }, - [VIR_STORAGE_FILE_ISO] = { - 32769, "CD001", - LV_LITTLE_ENDIAN, -2, 0, {0}, - -1, 0, 0, NULL, NULL, NULL - }, - [VIR_STORAGE_FILE_VPC] = { - 0, "conectix", - LV_BIG_ENDIAN, 12, 4, {0x10000}, - 8 + 4 + 4 + 8 + 4 + 4 + 2 + 2 + 4, 8, 1, NULL, NULL, NULL - }, - /* TODO: add getBackingStore function */ - [VIR_STORAGE_FILE_VDI] = { - 64, "\x7f\x10\xda\xbe", - LV_LITTLE_ENDIAN, 68, 4, {0x00010001}, - 64 + 5 * 4 + 256 + 7 * 4, 8, 1, NULL, NULL, NULL}, - - /* Not direct file formats, but used for various drivers */ - [VIR_STORAGE_FILE_FAT] = { 0, NULL, LV_LITTLE_ENDIAN, - -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL }, - [VIR_STORAGE_FILE_VHD] = { 0, NULL, LV_LITTLE_ENDIAN, - -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL }, - [VIR_STORAGE_FILE_PLOOP] = { 0, "WithouFreSpacExt", LV_LITTLE_ENDIAN, - -2, 0, {0}, PLOOP_IMAGE_SIZE_OFFSET, 0, - PLOOP_SIZE_MULTIPLIER, NULL, NULL, NULL }, - - /* All formats with a backing store probe below here */ - [VIR_STORAGE_FILE_COW] = { - 0, "OOOM", - LV_BIG_ENDIAN, 4, 4, {2}, - 4+4+1024+4, 8, 1, NULL, cowGetBackingStore, NULL - }, - [VIR_STORAGE_FILE_QCOW] = { - 0, "QFI", - LV_BIG_ENDIAN, 4, 4, {1}, - QCOWX_HDR_IMAGE_SIZE, 8, 1, - qcow1EncryptionInfo, - qcowXGetBackingStore, NULL - }, - [VIR_STORAGE_FILE_QCOW2] = { - 0, "QFI", - LV_BIG_ENDIAN, 4, 4, {2, 3}, - QCOWX_HDR_IMAGE_SIZE, 8, 1, - qcow2EncryptionInfo, - qcowXGetBackingStore, - qcow2GetFeatures - }, - [VIR_STORAGE_FILE_QED] = { - /* https://wiki.qemu.org/Features/QED */ - 0, "QED", - LV_LITTLE_ENDIAN, -2, 0, {0}, - QED_HDR_IMAGE_SIZE, 8, 1, NULL, qedGetBackingStore, NULL - }, - [VIR_STORAGE_FILE_VMDK] = { - 0, "KDMV", - LV_LITTLE_ENDIAN, 4, 4, {1, 2, 3}, - 4+4+4, 8, 512, NULL, vmdk4GetBackingStore, NULL - }, -}; -G_STATIC_ASSERT(G_N_ELEMENTS(fileTypeInfo) == VIR_STORAGE_FILE_LAST); - - -/* qcow2 compatible features in the order they appear on-disk */ -enum qcow2CompatibleFeature { - QCOW2_COMPATIBLE_FEATURE_LAZY_REFCOUNTS = 0, - - QCOW2_COMPATIBLE_FEATURE_LAST -}; - -/* conversion to virStorageFileFeature */ -static const int qcow2CompatibleFeatureArray[] = { - VIR_STORAGE_FILE_FEATURE_LAZY_REFCOUNTS, -}; -G_STATIC_ASSERT(G_N_ELEMENTS(qcow2CompatibleFeatureArray) == - QCOW2_COMPATIBLE_FEATURE_LAST); - -static int -cowGetBackingStore(char **res, - int *format, - const char *buf, - size_t buf_size) -{ -#define COW_FILENAME_MAXLEN 1024 - *res = NULL; - *format = VIR_STORAGE_FILE_AUTO; - - if (buf_size < 4+4+ COW_FILENAME_MAXLEN) - return BACKING_STORE_INVALID; - if (buf[4+4] == '\0') { /* cow_header_v2.backing_file[0] */ - *format = VIR_STORAGE_FILE_NONE; - return BACKING_STORE_OK; - } - - *res = g_strndup((const char *)buf + 4 + 4, COW_FILENAME_MAXLEN); - return BACKING_STORE_OK; -} - - -static int -qcow2GetExtensions(const char *buf, - size_t buf_size, - int *backingFormat) -{ - size_t offset; - size_t extension_start; - size_t extension_end; - int version = virReadBufInt32BE(buf + QCOWX_HDR_VERSION); - - if (version < 2) { - /* QCow1 doesn't have the extensions capability - * used to store backing format */ - return 0; - } - - if (version == 2) - extension_start = QCOW2_HDR_TOTAL_SIZE; - else - extension_start = virReadBufInt32BE(buf + QCOW2v3_HDR_SIZE); - - /* - * Traditionally QCow2 files had a layout of - * - * [header] - * [backingStoreName] - * - * Although the backingStoreName typically followed - * the header immediately, this was not required by - * the format. By specifying a higher byte offset for - * the backing file offset in the header, it was - * possible to leave space between the header and - * start of backingStore. - * - * This hack is now used to store extensions to the - * qcow2 format: - * - * [header] - * [extensions] - * [backingStoreName] - * - * Thus the file region to search for extensions is - * between the end of the header (QCOW2_HDR_TOTAL_SIZE) - * and the start of the backingStoreName (offset) - * - * for qcow2 v3 images, the length of the header - * is stored at QCOW2v3_HDR_SIZE - */ - extension_end = virReadBufInt64BE(buf + QCOWX_HDR_BACKING_FILE_OFFSET); - if (extension_end > buf_size) - return -1; - - /* - * The extensions take format of - * - * int32: magic - * int32: length - * byte[length]: payload - * - * Unknown extensions can be ignored by skipping - * over "length" bytes in the data stream. - */ - offset = extension_start; - while (offset < (buf_size-8) && - offset < (extension_end-8)) { - unsigned int magic = virReadBufInt32BE(buf + offset); - unsigned int len = virReadBufInt32BE(buf + offset + 4); - - offset += 8; - - if ((offset + len) < offset) - break; - - if ((offset + len) > buf_size) - break; - - switch (magic) { - case QCOW2_HDR_EXTENSION_BACKING_FORMAT: { - g_autofree char *tmp = NULL; - if (!backingFormat) - break; - - tmp = g_new0(char, len + 1); - memcpy(tmp, buf + offset, len); - tmp[len] = '\0'; - - *backingFormat = virStorageFileFormatTypeFromString(tmp); - if (*backingFormat <= VIR_STORAGE_FILE_NONE) - return -1; - break; - } - - case QCOW2_HDR_EXTENSION_END: - return 0; - } - - offset += len; - } - - return 0; -} - - -static int -qcowXGetBackingStore(char **res, - int *format, - const char *buf, - size_t buf_size) -{ - unsigned long long offset; - unsigned int size; - - *res = NULL; - *format = VIR_STORAGE_FILE_AUTO; - - if (buf_size < QCOWX_HDR_BACKING_FILE_OFFSET+8+4) - return BACKING_STORE_INVALID; - - offset = virReadBufInt64BE(buf + QCOWX_HDR_BACKING_FILE_OFFSET); - if (offset > buf_size) - return BACKING_STORE_INVALID; - - if (offset == 0) { - *format = VIR_STORAGE_FILE_NONE; - return BACKING_STORE_OK; - } - - size = virReadBufInt32BE(buf + QCOWX_HDR_BACKING_FILE_SIZE); - if (size == 0) { - *format = VIR_STORAGE_FILE_NONE; - return BACKING_STORE_OK; - } - if (size > 1023) - return BACKING_STORE_INVALID; - if (offset + size > buf_size || offset + size < offset) - return BACKING_STORE_INVALID; - *res = g_new0(char, size + 1); - memcpy(*res, buf + offset, size); - (*res)[size] = '\0'; - - if (qcow2GetExtensions(buf, buf_size, format) < 0) - return BACKING_STORE_INVALID; - - return BACKING_STORE_OK; -} - - -static int -vmdk4GetBackingStore(char **res, - int *format, - const char *buf, - size_t buf_size) -{ - static const char prefix[] = "parentFileNameHint=\""; - char *start, *end; - size_t len; - g_autofree char *desc = NULL; - - desc = g_new0(char, VIR_STORAGE_MAX_HEADER); - - *res = NULL; - /* - * Technically this should have been VMDK, since - * VMDK spec / VMware impl only support VMDK backed - * by VMDK. QEMU isn't following this though and - * does probing on VMDK backing files, hence we set - * AUTO - */ - *format = VIR_STORAGE_FILE_AUTO; - - if (buf_size <= 0x200) - return BACKING_STORE_INVALID; - - len = buf_size - 0x200; - if (len > VIR_STORAGE_MAX_HEADER) - len = VIR_STORAGE_MAX_HEADER; - memcpy(desc, buf + 0x200, len); - desc[len] = '\0'; - start = strstr(desc, prefix); - if (start == NULL) { - *format = VIR_STORAGE_FILE_NONE; - return BACKING_STORE_OK; - } - start += strlen(prefix); - end = strchr(start, '"'); - if (end == NULL) - return BACKING_STORE_INVALID; - - if (end == start) { - *format = VIR_STORAGE_FILE_NONE; - return BACKING_STORE_OK; - } - *end = '\0'; - *res = g_strdup(start); - - return BACKING_STORE_OK; -} - -static int -qedGetBackingStore(char **res, - int *format, - const char *buf, - size_t buf_size) -{ - unsigned long long flags; - unsigned long offset, size; - - *res = NULL; - /* Check if this image has a backing file */ - if (buf_size < QED_HDR_FEATURES_OFFSET+8) - return BACKING_STORE_INVALID; - flags = virReadBufInt64LE(buf + QED_HDR_FEATURES_OFFSET); - if (!(flags & QED_F_BACKING_FILE)) { - *format = VIR_STORAGE_FILE_NONE; - return BACKING_STORE_OK; - } - - /* Parse the backing file */ - if (buf_size < QED_HDR_BACKING_FILE_OFFSET+8) - return BACKING_STORE_INVALID; - offset = virReadBufInt32LE(buf + QED_HDR_BACKING_FILE_OFFSET); - if (offset > buf_size) - return BACKING_STORE_INVALID; - size = virReadBufInt32LE(buf + QED_HDR_BACKING_FILE_SIZE); - if (size == 0) - return BACKING_STORE_OK; - if (offset + size > buf_size || offset + size < offset) - return BACKING_STORE_INVALID; - *res = g_new0(char, size + 1); - memcpy(*res, buf + offset, size); - (*res)[size] = '\0'; - - if (flags & QED_F_BACKING_FORMAT_NO_PROBE) - *format = VIR_STORAGE_FILE_RAW; - else - *format = VIR_STORAGE_FILE_AUTO_SAFE; - - return BACKING_STORE_OK; -} - - -static bool -virStorageFileMatchesMagic(int magicOffset, - const char *magic, - char *buf, - size_t buflen) -{ - int mlen; - - if (magic == NULL) - return false; - - /* Validate magic data */ - mlen = strlen(magic); - if (magicOffset + mlen > buflen) - return false; - - if (memcmp(buf + magicOffset, magic, mlen) != 0) - return false; - - return true; -} - - -static bool -virStorageFileMatchesVersion(int versionOffset, - int versionSize, - const int *versionNumbers, - int endian, - char *buf, - size_t buflen) -{ - int version; - size_t i; - - /* Validate version number info */ - if (versionOffset == -1) - return false; - - /* -2 == non-versioned file format, so trivially match */ - if (versionOffset == -2) - return true; - - /* A positive versionOffset, requires using a valid versionSize */ - if (versionSize != 2 && versionSize != 4) - return false; - - if ((versionOffset + versionSize) > buflen) - return false; - - if (endian == LV_LITTLE_ENDIAN) { - if (versionSize == 4) - version = virReadBufInt32LE(buf + - versionOffset); - else - version = virReadBufInt16LE(buf + - versionOffset); - } else { - if (versionSize == 4) - version = virReadBufInt32BE(buf + - versionOffset); - else - version = virReadBufInt16BE(buf + - versionOffset); - } - - for (i = 0; - i < FILE_TYPE_VERSIONS_LAST && versionNumbers[i]; - i++) { - VIR_DEBUG("Compare detected version %d vs one of the expected versions %d", - version, versionNumbers[i]); - if (version == versionNumbers[i]) - return true; - } - - return false; -} - - -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@xxxxxxxxxx", - path, virStorageFileFormatTypeToString(possibleFormat)); - - cleanup: - VIR_DEBUG("format=%d", format); - return format; -} - - -static int -qcow2GetFeatures(virBitmapPtr *features, - int format, - char *buf, - ssize_t len) -{ - int version = -1; - virBitmapPtr feat = NULL; - uint64_t bits; - size_t i; - - version = virReadBufInt32BE(buf + fileTypeInfo[format].versionOffset); - - if (version == 2) - return 0; - - if (len < QCOW2v3_HDR_SIZE) - return -1; - - feat = virBitmapNew(VIR_STORAGE_FILE_FEATURE_LAST); - - /* todo: check for incompatible or autoclear features? */ - bits = virReadBufInt64BE(buf + QCOW2v3_HDR_FEATURES_COMPATIBLE); - for (i = 0; i < QCOW2_COMPATIBLE_FEATURE_LAST; i++) { - if (bits & ((uint64_t) 1 << i)) - ignore_value(virBitmapSetBit(feat, qcow2CompatibleFeatureArray[i])); - } - - *features = feat; - return 0; -} - - -static bool -virStorageFileHasEncryptionFormat(const struct FileEncryptionInfo *info, - char *buf, - size_t len) -{ - if (!info->magic && info->modeOffset == -1) - return false; /* Shouldn't happen - expect at least one */ - - if (info->magic) { - if (!virStorageFileMatchesMagic(info->magicOffset, - info->magic, - buf, len)) - return false; - - if (info->versionOffset != -1 && - !virStorageFileMatchesVersion(info->versionOffset, - info->versionSize, - info->versionNumbers, - info->endian, - buf, len)) - return false; - - return true; - } else if (info->modeOffset != -1) { - int crypt_format; - - if (info->modeOffset >= len) - return false; - - crypt_format = virReadBufInt32BE(buf + info->modeOffset); - if (crypt_format != info->modeValue) - return false; - - return true; - } else { - return false; - } -} - - -static int -virStorageFileGetEncryptionPayloadOffset(const struct FileEncryptionInfo *info, - char *buf) -{ - int payload_offset = -1; - - if (info->payloadOffset != -1) { - if (info->endian == LV_LITTLE_ENDIAN) - payload_offset = virReadBufInt32LE(buf + info->payloadOffset); - else - payload_offset = virReadBufInt32BE(buf + info->payloadOffset); - } - - return payload_offset; -} - - -/* Given a header in BUF with length LEN, as parsed from the storage file - * assuming it has the given FORMAT, populate information into META - * with information about the file and its backing store. Return format - * of the backing store as BACKING_FORMAT. PATH and FORMAT have to be - * pre-populated in META. - * - * Note that this function may be called repeatedly on @meta, so it must - * clean up any existing allocated memory which would be overwritten. - */ -static int -virStorageFileGetMetadataInternal(virStorageSourcePtr meta, - char *buf, - size_t len) -{ - int format; - size_t i; - - VIR_DEBUG("path=%s, buf=%p, len=%zu, meta->format=%d", - meta->path, buf, len, meta->format); - - if (meta->format == VIR_STORAGE_FILE_AUTO) - meta->format = virStorageFileProbeFormatFromBuf(meta->path, buf, len); - - if (meta->format <= VIR_STORAGE_FILE_NONE || - meta->format >= VIR_STORAGE_FILE_LAST) { - virReportSystemError(EINVAL, _("unknown storage file meta->format %d"), - meta->format); - return -1; - } - - if (fileTypeInfo[meta->format].cryptInfo != NULL) { - for (i = 0; fileTypeInfo[meta->format].cryptInfo[i].format != 0; i++) { - if (virStorageFileHasEncryptionFormat(&fileTypeInfo[meta->format].cryptInfo[i], - buf, len)) { - int expt_fmt = fileTypeInfo[meta->format].cryptInfo[i].format; - if (!meta->encryption) { - meta->encryption = g_new0(virStorageEncryption, 1); - meta->encryption->format = expt_fmt; - } else { - if (meta->encryption->format != expt_fmt) { - virReportError(VIR_ERR_XML_ERROR, - _("encryption format %d doesn't match " - "expected format %d"), - meta->encryption->format, expt_fmt); - return -1; - } - } - meta->encryption->payload_offset = - virStorageFileGetEncryptionPayloadOffset(&fileTypeInfo[meta->format].cryptInfo[i], buf); - } - } - } - - /* XXX we should consider moving virStorageBackendUpdateVolInfo - * code into this method, for non-magic files - */ - if (!fileTypeInfo[meta->format].magic) - return 0; - - /* Optionally extract capacity from file */ - if (fileTypeInfo[meta->format].sizeOffset != -1) { - if ((fileTypeInfo[meta->format].sizeOffset + 8) > len) - return 0; - - if (fileTypeInfo[meta->format].endian == LV_LITTLE_ENDIAN) - meta->capacity = virReadBufInt64LE(buf + - fileTypeInfo[meta->format].sizeOffset); - else - meta->capacity = virReadBufInt64BE(buf + - fileTypeInfo[meta->format].sizeOffset); - /* Avoid unlikely, but theoretically possible overflow */ - if (meta->capacity > (ULLONG_MAX / - fileTypeInfo[meta->format].sizeMultiplier)) - return 0; - meta->capacity *= fileTypeInfo[meta->format].sizeMultiplier; - } - - VIR_FREE(meta->backingStoreRaw); - if (fileTypeInfo[meta->format].getBackingStore != NULL) { - int store = fileTypeInfo[meta->format].getBackingStore(&meta->backingStoreRaw, - &format, - buf, len); - meta->backingStoreRawFormat = format; - - if (store == BACKING_STORE_INVALID) - return 0; - - if (store == BACKING_STORE_ERROR) - return -1; - } - - virBitmapFree(meta->features); - meta->features = NULL; - if (fileTypeInfo[meta->format].getFeatures != NULL && - fileTypeInfo[meta->format].getFeatures(&meta->features, meta->format, buf, len) < 0) - return -1; - - VIR_FREE(meta->compat); - if (meta->format == VIR_STORAGE_FILE_QCOW2 && meta->features) - meta->compat = g_strdup("1.1"); - - return 0; -} - - -/** - * virStorageFileProbeFormat: - * - * Probe for the format of 'path', returning the detected - * disk format. - * - * Callers are advised never to trust the returned 'format' - * unless it is listed as VIR_STORAGE_FILE_RAW, since a - * malicious guest can turn a raw file into any other non-raw - * format at will. - * - * Best option: Don't use this function - */ -int -virStorageFileProbeFormat(const char *path, uid_t uid, gid_t gid) -{ - struct stat sb; - ssize_t len = VIR_STORAGE_MAX_HEADER; - VIR_AUTOCLOSE fd = -1; - g_autofree char *header = NULL; - - if ((fd = virFileOpenAs(path, O_RDONLY, 0, uid, gid, 0)) < 0) { - virReportSystemError(-fd, _("Failed to open file '%s'"), path); - return -1; - } - - if (fstat(fd, &sb) < 0) { - virReportSystemError(errno, _("cannot stat file '%s'"), path); - return -1; - } - - /* No header to probe for directories */ - if (S_ISDIR(sb.st_mode)) - return VIR_STORAGE_FILE_DIR; - - if (lseek(fd, 0, SEEK_SET) == (off_t)-1) { - virReportSystemError(errno, _("cannot set to start of '%s'"), path); - return -1; - } - - if ((len = virFileReadHeaderFD(fd, len, &header)) < 0) { - virReportSystemError(errno, _("cannot read header '%s'"), path); - return -1; - } - - return virStorageFileProbeFormatFromBuf(path, header, len); -} - - -static virStorageSourcePtr -virStorageFileMetadataNew(const char *path, - int format) -{ - g_autoptr(virStorageSource) def = virStorageSourceNew(); - - def->format = format; - def->type = VIR_STORAGE_TYPE_FILE; - - def->path = g_strdup(path); - - return g_steal_pointer(&def); -} - - -/** - * virStorageFileGetMetadataFromBuf: - * @path: name of file, for error messages - * @buf: header bytes from @path - * @len: length of @buf - * @format: format of the storage file - * - * Extract metadata about the storage volume with the specified image format. - * If image format is VIR_STORAGE_FILE_AUTO, it will probe to automatically - * identify the format. Does not recurse. - * - * Callers are advised never to use VIR_STORAGE_FILE_AUTO as a format on a file - * that might be raw if that file will then be passed to a guest, since a - * malicious guest can turn a raw file into any other non-raw format at will. - * - * If the 'backingStoreRawFormat' field of the returned structure is - * VIR_STORAGE_FILE_AUTO it indicates the image didn't specify an explicit - * format for its backing store. Callers are advised against probing for the - * backing store format in this case. - * - * Caller MUST free the result after use via virObjectUnref. - */ -virStorageSourcePtr -virStorageFileGetMetadataFromBuf(const char *path, - char *buf, - size_t len, - int format) -{ - virStorageSourcePtr ret = NULL; - - if (!(ret = virStorageFileMetadataNew(path, format))) - return NULL; - - if (virStorageFileGetMetadataInternal(ret, buf, len) < 0) { - virObjectUnref(ret); - return NULL; - } - - return ret; -} - - -/** - * virStorageFileGetMetadataFromFD: - * - * Extract metadata about the storage volume with the specified - * image format. If image format is VIR_STORAGE_FILE_AUTO, it - * will probe to automatically identify the format. Does not recurse. - * - * Callers are advised never to use VIR_STORAGE_FILE_AUTO as a - * format, since a malicious guest can turn a raw file into any - * other non-raw format at will. - * - * Caller MUST free the result after use via virObjectUnref. - */ -virStorageSourcePtr -virStorageFileGetMetadataFromFD(const char *path, - int fd, - int format) - -{ - ssize_t len = VIR_STORAGE_MAX_HEADER; - struct stat sb; - g_autofree char *buf = NULL; - g_autoptr(virStorageSource) meta = NULL; - - if (fstat(fd, &sb) < 0) { - virReportSystemError(errno, - _("cannot stat file '%s'"), path); - return NULL; - } - - if (!(meta = virStorageFileMetadataNew(path, format))) - return NULL; - - if (S_ISDIR(sb.st_mode)) { - /* No header to probe for directories, but also no backing file. Just - * update the metadata.*/ - meta->type = VIR_STORAGE_TYPE_DIR; - meta->format = VIR_STORAGE_FILE_DIR; - return g_steal_pointer(&meta); - } - - if (lseek(fd, 0, SEEK_SET) == (off_t)-1) { - virReportSystemError(errno, _("cannot seek to start of '%s'"), meta->path); - return NULL; - } - - if ((len = virFileReadHeaderFD(fd, len, &buf)) < 0) { - virReportSystemError(errno, _("cannot read header '%s'"), meta->path); - return NULL; - } - - if (virStorageFileGetMetadataInternal(meta, buf, len) < 0) - return NULL; - - if (S_ISREG(sb.st_mode)) - meta->type = VIR_STORAGE_TYPE_FILE; - else if (S_ISBLK(sb.st_mode)) - meta->type = VIR_STORAGE_TYPE_BLOCK; - - return g_steal_pointer(&meta); -} - #ifdef WITH_UDEV /* virStorageFileGetSCSIKey @@ -1297,78 +240,6 @@ int virStorageFileGetNPIVKey(const char *path G_GNUC_UNUSED, } #endif -/** - * virStorageFileParseBackingStoreStr: - * @str: backing store specifier string to parse - * @target: returns target device portion of the string - * @chainIndex: returns the backing store portion of the string - * - * Parses the backing store specifier string such as vda[1], or sda into - * components and returns them via arguments. If the string did not specify an - * index, 0 is assumed. - * - * Returns 0 on success -1 on error - */ -int -virStorageFileParseBackingStoreStr(const char *str, - char **target, - unsigned int *chainIndex) -{ - size_t nstrings; - unsigned int idx = 0; - char *suffix; - g_auto(GStrv) strings = NULL; - - *chainIndex = 0; - - if (!(strings = virStringSplitCount(str, "[", 2, &nstrings))) - return -1; - - if (nstrings == 2) { - if (virStrToLong_uip(strings[1], &suffix, 10, &idx) < 0 || - STRNEQ(suffix, "]")) - return -1; - } - - if (target) - *target = g_strdup(strings[0]); - - *chainIndex = idx; - return 0; -} - - -int -virStorageFileParseChainIndex(const char *diskTarget, - const char *name, - unsigned int *chainIndex) -{ - unsigned int idx = 0; - g_autofree char *target = NULL; - - *chainIndex = 0; - - if (!name || !diskTarget) - return 0; - - if (virStorageFileParseBackingStoreStr(name, &target, &idx) < 0) - return 0; - - if (idx == 0) - return 0; - - if (STRNEQ(diskTarget, target)) { - virReportError(VIR_ERR_INVALID_ARG, - _("requested target '%s' does not match target '%s'"), - target, diskTarget); - return -1; - } - - *chainIndex = idx; - - return 0; -} - /** * virStorageSourceIsBacking: @@ -1397,107 +268,6 @@ virStorageSourceHasBacking(const virStorageSource *src) } -/* Given a @chain, look for the backing store @name that is a backing file - * of @startFrom (or any member of @chain if @startFrom is NULL) and return - * that location within the chain. @chain must always point to the top of - * the chain. Pass NULL for @name and 0 for @idx to find the base of the - * chain. Pass nonzero @idx to find the backing source according to its - * position in the backing chain. If @parent is not NULL, set *@parent to - * the preferred name of the parent (or to NULL if @name matches the start - * of the chain). Since the results point within @chain, they must not be - * independently freed. Reports an error and returns NULL if @name is not - * found. - */ -virStorageSourcePtr -virStorageFileChainLookup(virStorageSourcePtr chain, - virStorageSourcePtr startFrom, - const char *name, - unsigned int idx, - virStorageSourcePtr *parent) -{ - virStorageSourcePtr prev; - const char *start = chain->path; - bool nameIsFile = virFileIsFile(name); - - if (!parent) - parent = &prev; - *parent = NULL; - - if (startFrom) { - while (virStorageSourceIsBacking(chain) && - chain != startFrom->backingStore) - chain = chain->backingStore; - - *parent = startFrom; - } - - while (virStorageSourceIsBacking(chain)) { - if (!name && !idx) { - if (!virStorageSourceHasBacking(chain)) - break; - } else if (idx) { - VIR_DEBUG("%u: %s", chain->id, chain->path); - if (idx == chain->id) - break; - } else { - if (STREQ_NULLABLE(name, chain->relPath) || - STREQ_NULLABLE(name, chain->path)) - break; - - if (nameIsFile && virStorageSourceIsLocalStorage(chain)) { - g_autofree char *parentDir = NULL; - int result; - - if (*parent && virStorageSourceIsLocalStorage(*parent)) - parentDir = g_path_get_dirname((*parent)->path); - else - parentDir = g_strdup("."); - - result = virFileRelLinkPointsTo(parentDir, name, - chain->path); - - if (result < 0) - goto error; - - if (result > 0) - break; - } - } - *parent = chain; - chain = chain->backingStore; - } - - if (!virStorageSourceIsBacking(chain)) - goto error; - - return chain; - - error: - if (idx) { - virReportError(VIR_ERR_INVALID_ARG, - _("could not find backing store index %u in chain " - "for '%s'"), - idx, NULLSTR(start)); - } else if (name) { - if (startFrom) - virReportError(VIR_ERR_INVALID_ARG, - _("could not find image '%s' beneath '%s' in " - "chain for '%s'"), name, NULLSTR(startFrom->path), - NULLSTR(start)); - else - virReportError(VIR_ERR_INVALID_ARG, - _("could not find image '%s' in chain for '%s'"), - name, NULLSTR(start)); - } else { - virReportError(VIR_ERR_INVALID_ARG, - _("could not find base image in chain for '%s'"), - NULLSTR(start)); - } - *parent = NULL; - return NULL; -} - - void virStorageNetHostDefClear(virStorageNetHostDefPtr def) { @@ -2545,1780 +1315,6 @@ virStorageSourceNew(void) } -static virStorageSourcePtr -virStorageSourceNewFromBackingRelative(virStorageSourcePtr parent, - const char *rel) -{ - g_autofree char *dirname = NULL; - g_autoptr(virStorageSource) def = virStorageSourceNew(); - - /* store relative name */ - def->relPath = g_strdup(rel); - - dirname = g_path_get_dirname(parent->path); - - if (STRNEQ(dirname, "/")) { - def->path = g_strdup_printf("%s/%s", dirname, rel); - } else { - def->path = g_strdup_printf("/%s", rel); - } - - if (virStorageSourceGetActualType(parent) == VIR_STORAGE_TYPE_NETWORK) { - def->type = VIR_STORAGE_TYPE_NETWORK; - - /* copy the host network part */ - def->protocol = parent->protocol; - if (parent->nhosts) { - if (!(def->hosts = virStorageNetHostDefCopy(parent->nhosts, - parent->hosts))) - return NULL; - - def->nhosts = parent->nhosts; - } - - def->volume = g_strdup(parent->volume); - } else { - /* set the type to _FILE, the caller shall update it to the actual type */ - def->type = VIR_STORAGE_TYPE_FILE; - } - - return g_steal_pointer(&def); -} - - -static int -virStorageSourceParseBackingURI(virStorageSourcePtr src, - const char *uristr) -{ - g_autoptr(virURI) uri = NULL; - const char *path = NULL; - g_auto(GStrv) scheme = NULL; - - if (!(uri = virURIParse(uristr))) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("failed to parse backing file location '%s'"), - uristr); - return -1; - } - - src->hosts = g_new0(virStorageNetHostDef, 1); - src->nhosts = 1; - - if (!(scheme = virStringSplit(uri->scheme, "+", 2))) - return -1; - - if (!scheme[0] || - (src->protocol = virStorageNetProtocolTypeFromString(scheme[0])) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("invalid backing protocol '%s'"), - NULLSTR(scheme[0])); - return -1; - } - - if (scheme[1] && - (src->hosts->transport = virStorageNetHostTransportTypeFromString(scheme[1])) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("invalid protocol transport type '%s'"), - scheme[1]); - return -1; - } - - if (uri->query) { - if (src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTP || - src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS) { - src->query = g_strdup(uri->query); - } else { - /* handle socket stored as a query */ - if (STRPREFIX(uri->query, "socket=")) - src->hosts->socket = g_strdup(STRSKIP(uri->query, "socket=")); - } - } - - /* uri->path is NULL if the URI does not contain slash after host: - * transport://host:port */ - if (uri->path) - path = uri->path; - else - path = ""; - - /* possibly skip the leading slash */ - if (path[0] == '/') - path++; - - /* NBD allows empty export name (path) */ - if (src->protocol == VIR_STORAGE_NET_PROTOCOL_NBD && - path[0] == '\0') - path = NULL; - - src->path = g_strdup(path); - - if (src->protocol == VIR_STORAGE_NET_PROTOCOL_GLUSTER) { - char *tmp; - - if (!src->path) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("missing volume name and path for gluster volume")); - return -1; - } - - if (!(tmp = strchr(src->path, '/')) || - tmp == src->path) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("missing volume name or file name in " - "gluster source path '%s'"), src->path); - return -1; - } - - src->volume = src->path; - - src->path = g_strdup(tmp + 1); - - tmp[0] = '\0'; - } - - src->hosts->port = uri->port; - - src->hosts->name = g_strdup(uri->server); - - /* Libvirt doesn't handle inline authentication. Make the caller aware. */ - if (uri->user) - return 1; - - return 0; -} - - -static int -virStorageSourceRBDAddHost(virStorageSourcePtr src, - char *hostport) -{ - char *port; - size_t skip; - g_auto(GStrv) parts = NULL; - - if (VIR_EXPAND_N(src->hosts, src->nhosts, 1) < 0) - return -1; - - if ((port = strchr(hostport, ']'))) { - /* ipv6, strip brackets */ - hostport += 1; - skip = 3; - } else { - port = strstr(hostport, "\\:"); - skip = 2; - } - - if (port) { - *port = '\0'; - port += skip; - if (virStringParsePort(port, &src->hosts[src->nhosts - 1].port) < 0) - goto error; - } - - parts = virStringSplit(hostport, "\\:", 0); - if (!parts) - goto error; - src->hosts[src->nhosts-1].name = virStringListJoin((const char **)parts, ":"); - if (!src->hosts[src->nhosts-1].name) - goto error; - - src->hosts[src->nhosts-1].transport = VIR_STORAGE_NET_HOST_TRANS_TCP; - src->hosts[src->nhosts-1].socket = NULL; - - return 0; - - error: - VIR_FREE(src->hosts[src->nhosts-1].name); - return -1; -} - - -int -virStorageSourceParseRBDColonString(const char *rbdstr, - virStorageSourcePtr src) -{ - char *p, *e, *next; - g_autofree char *options = NULL; - g_autoptr(virStorageAuthDef) authdef = NULL; - - /* optionally skip the "rbd:" prefix if provided */ - if (STRPREFIX(rbdstr, "rbd:")) - rbdstr += strlen("rbd:"); - - src->path = g_strdup(rbdstr); - - p = strchr(src->path, ':'); - if (p) { - options = g_strdup(p + 1); - *p = '\0'; - } - - /* snapshot name */ - if ((p = strchr(src->path, '@'))) { - src->snapshot = g_strdup(p + 1); - *p = '\0'; - } - - /* pool vs. image name */ - if ((p = strchr(src->path, '/'))) { - src->volume = g_steal_pointer(&src->path); - src->path = g_strdup(p + 1); - *p = '\0'; - } - - /* options */ - if (!options) - return 0; /* all done */ - - p = options; - while (*p) { - /* find : delimiter or end of string */ - for (e = p; *e && *e != ':'; ++e) { - if (*e == '\\') { - e++; - if (*e == '\0') - break; - } - } - if (*e == '\0') { - next = e; /* last kv pair */ - } else { - next = e + 1; - *e = '\0'; - } - - if (STRPREFIX(p, "id=")) { - /* formulate authdef for src->auth */ - if (src->auth) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("duplicate 'id' found in '%s'"), src->path); - return -1; - } - - authdef = g_new0(virStorageAuthDef, 1); - - authdef->username = g_strdup(p + strlen("id=")); - - authdef->secrettype = g_strdup(virSecretUsageTypeToString(VIR_SECRET_USAGE_TYPE_CEPH)); - src->auth = g_steal_pointer(&authdef); - - /* Cannot formulate a secretType (eg, usage or uuid) given - * what is provided. - */ - } - if (STRPREFIX(p, "mon_host=")) { - char *h, *sep; - - h = p + strlen("mon_host="); - while (h < e) { - for (sep = h; sep < e; ++sep) { - if (*sep == '\\' && (sep[1] == ',' || - sep[1] == ';' || - sep[1] == ' ')) { - *sep = '\0'; - sep += 2; - break; - } - } - - if (virStorageSourceRBDAddHost(src, h) < 0) - return -1; - - h = sep; - } - } - - if (STRPREFIX(p, "conf=")) - src->configFile = g_strdup(p + strlen("conf=")); - - p = next; - } - return 0; -} - - -static int -virStorageSourceParseNBDColonString(const char *nbdstr, - virStorageSourcePtr src) -{ - g_autofree char *nbd = g_strdup(nbdstr); - char *export_name; - char *host_spec; - char *unixpath; - char *port; - - src->hosts = g_new0(virStorageNetHostDef, 1); - src->nhosts = 1; - - /* We extract the parameters in a similar way qemu does it */ - - /* format: [] denotes optional sections, uppercase are variable strings - * nbd:unix:/PATH/TO/SOCKET[:exportname=EXPORTNAME] - * nbd:HOSTNAME:PORT[:exportname=EXPORTNAME] - */ - - /* first look for ':exportname=' and cut it off */ - if ((export_name = strstr(nbd, ":exportname="))) { - src->path = g_strdup(export_name + strlen(":exportname=")); - export_name[0] = '\0'; - } - - /* Verify the prefix and contents. Note that we require a - * "host_spec" part to be present. */ - if (!(host_spec = STRSKIP(nbd, "nbd:")) || host_spec[0] == '\0') - goto malformed; - - if ((unixpath = STRSKIP(host_spec, "unix:"))) { - src->hosts->transport = VIR_STORAGE_NET_HOST_TRANS_UNIX; - - if (unixpath[0] == '\0') - goto malformed; - - src->hosts->socket = g_strdup(unixpath); - } else { - src->hosts->transport = VIR_STORAGE_NET_HOST_TRANS_TCP; - - if (host_spec[0] == ':') { - /* no host given */ - goto malformed; - } else if (host_spec[0] == '[') { - host_spec++; - /* IPv6 addr */ - if (!(port = strstr(host_spec, "]:"))) - goto malformed; - - port[0] = '\0'; - port += 2; - - if (host_spec[0] == '\0') - goto malformed; - } else { - if (!(port = strchr(host_spec, ':'))) - goto malformed; - - port[0] = '\0'; - port++; - } - - if (virStringParsePort(port, &src->hosts->port) < 0) - return -1; - - src->hosts->name = g_strdup(host_spec); - } - - return 0; - - malformed: - virReportError(VIR_ERR_INTERNAL_ERROR, - _("malformed nbd string '%s'"), nbdstr); - return -1; -} - - -static int -virStorageSourceParseBackingColon(virStorageSourcePtr src, - const char *path) -{ - const char *p; - g_autofree char *protocol = NULL; - - if (!(p = strchr(path, ':'))) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("invalid backing protocol string '%s'"), - path); - return -1; - } - - protocol = g_strndup(path, p - path); - - if ((src->protocol = virStorageNetProtocolTypeFromString(protocol)) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("invalid backing protocol '%s'"), - protocol); - return -1; - } - - switch ((virStorageNetProtocol) src->protocol) { - case VIR_STORAGE_NET_PROTOCOL_NBD: - if (virStorageSourceParseNBDColonString(path, src) < 0) - return -1; - break; - - case VIR_STORAGE_NET_PROTOCOL_RBD: - if (virStorageSourceParseRBDColonString(path, src) < 0) - return -1; - break; - - case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG: - case VIR_STORAGE_NET_PROTOCOL_LAST: - case VIR_STORAGE_NET_PROTOCOL_NONE: - virReportError(VIR_ERR_INTERNAL_ERROR, - _("backing store parser is not implemented for protocol %s"), - protocol); - return -1; - - case VIR_STORAGE_NET_PROTOCOL_HTTP: - case VIR_STORAGE_NET_PROTOCOL_HTTPS: - case VIR_STORAGE_NET_PROTOCOL_FTP: - case VIR_STORAGE_NET_PROTOCOL_FTPS: - case VIR_STORAGE_NET_PROTOCOL_TFTP: - case VIR_STORAGE_NET_PROTOCOL_ISCSI: - case VIR_STORAGE_NET_PROTOCOL_GLUSTER: - case VIR_STORAGE_NET_PROTOCOL_SSH: - case VIR_STORAGE_NET_PROTOCOL_VXHS: - virReportError(VIR_ERR_INTERNAL_ERROR, - _("malformed backing store path for protocol %s"), - protocol); - return -1; - } - - return 0; -} - - -static int -virStorageSourceParseBackingJSONInternal(virStorageSourcePtr src, - virJSONValuePtr json, - const char *jsonstr, - bool allowformat); - - -static int -virStorageSourceParseBackingJSONPath(virStorageSourcePtr src, - virJSONValuePtr json, - const char *jsonstr G_GNUC_UNUSED, - int type) -{ - const char *path; - - if (!(path = virJSONValueObjectGetString(json, "filename"))) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing 'filename' field in JSON backing volume " - "definition")); - return -1; - } - - src->path = g_strdup(path); - - src->type = type; - return 0; -} - - -static int -virStorageSourceParseBackingJSONUriStr(virStorageSourcePtr src, - const char *uri, - int protocol) -{ - int rc; - - if ((rc = virStorageSourceParseBackingURI(src, uri)) < 0) - return -1; - - if (src->protocol != protocol) { - virReportError(VIR_ERR_INVALID_ARG, - _("expected protocol '%s' but got '%s' in URI JSON volume " - "definition"), - virStorageNetProtocolTypeToString(protocol), - virStorageNetProtocolTypeToString(src->protocol)); - return -1; - } - - return rc; -} - - -static int -virStorageSourceParseBackingJSONUriCookies(virStorageSourcePtr src, - virJSONValuePtr json, - const char *jsonstr) -{ - const char *cookiestr; - g_auto(GStrv) cookies = NULL; - size_t ncookies = 0; - size_t i; - - if (!virJSONValueObjectHasKey(json, "cookie")) - return 0; - - if (!(cookiestr = virJSONValueObjectGetString(json, "cookie"))) { - virReportError(VIR_ERR_INVALID_ARG, - _("wrong format of 'cookie' field in backing store definition '%s'"), - jsonstr); - return -1; - } - - if (!(cookies = virStringSplitCount(cookiestr, ";", 0, &ncookies))) - return -1; - - src->cookies = g_new0(virStorageNetCookieDefPtr, ncookies); - src->ncookies = ncookies; - - for (i = 0; i < ncookies; i++) { - char *cookiename = cookies[i]; - char *cookievalue; - - virSkipSpaces((const char **) &cookiename); - - if (!(cookievalue = strchr(cookiename, '='))) { - virReportError(VIR_ERR_INVALID_ARG, - _("malformed http cookie '%s' in backing store definition '%s'"), - cookies[i], jsonstr); - return -1; - } - - *cookievalue = '\0'; - cookievalue++; - - src->cookies[i] = g_new0(virStorageNetCookieDef, 1); - src->cookies[i]->name = g_strdup(cookiename); - src->cookies[i]->value = g_strdup(cookievalue); - } - - return 0; -} - - -static int -virStorageSourceParseBackingJSONUri(virStorageSourcePtr src, - virJSONValuePtr json, - const char *jsonstr, - int protocol) -{ - const char *uri; - - if (!(uri = virJSONValueObjectGetString(json, "url"))) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing 'url' in JSON backing volume definition")); - return -1; - } - - if (protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS || - protocol == VIR_STORAGE_NET_PROTOCOL_FTPS) { - if (virJSONValueObjectHasKey(json, "sslverify")) { - const char *tmpstr; - bool tmp; - - /* libguestfs still uses undocumented legacy value of 'off' */ - if ((tmpstr = virJSONValueObjectGetString(json, "sslverify")) && - STREQ(tmpstr, "off")) { - src->sslverify = VIR_TRISTATE_BOOL_NO; - } else { - if (virJSONValueObjectGetBoolean(json, "sslverify", &tmp) < 0) { - virReportError(VIR_ERR_INVALID_ARG, - _("malformed 'sslverify' field in backing store definition '%s'"), - jsonstr); - return -1; - } - - src->sslverify = virTristateBoolFromBool(tmp); - } - } - } - - if (protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS || - protocol == VIR_STORAGE_NET_PROTOCOL_HTTP) { - if (virStorageSourceParseBackingJSONUriCookies(src, json, jsonstr) < 0) - return -1; - } - - if (virJSONValueObjectHasKey(json, "readahead") && - virJSONValueObjectGetNumberUlong(json, "readahead", &src->readahead) < 0) { - virReportError(VIR_ERR_INVALID_ARG, - _("malformed 'readahead' field in backing store definition '%s'"), - jsonstr); - return -1; - } - - if (virJSONValueObjectHasKey(json, "timeout") && - virJSONValueObjectGetNumberUlong(json, "timeout", &src->timeout) < 0) { - virReportError(VIR_ERR_INVALID_ARG, - _("malformed 'timeout' field in backing store definition '%s'"), - jsonstr); - return -1; - } - - return virStorageSourceParseBackingJSONUriStr(src, uri, protocol); -} - - -static int -virStorageSourceParseBackingJSONInetSocketAddress(virStorageNetHostDefPtr host, - virJSONValuePtr json) -{ - const char *hostname; - const char *port; - - if (!json) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing remote server specification in JSON " - "backing volume definition")); - return -1; - } - - hostname = virJSONValueObjectGetString(json, "host"); - port = virJSONValueObjectGetString(json, "port"); - - if (!hostname) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing hostname for tcp backing server in " - "JSON backing volume definition")); - return -1; - } - - host->transport = VIR_STORAGE_NET_HOST_TRANS_TCP; - host->name = g_strdup(hostname); - - if (virStringParsePort(port, &host->port) < 0) - return -1; - - return 0; -} - - -static int -virStorageSourceParseBackingJSONSocketAddress(virStorageNetHostDefPtr host, - virJSONValuePtr json) -{ - const char *type; - const char *socket; - - if (!json) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing remote server specification in JSON " - "backing volume definition")); - return -1; - } - - if (!(type = virJSONValueObjectGetString(json, "type"))) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing socket address type in " - "JSON backing volume definition")); - return -1; - } - - if (STREQ(type, "tcp") || STREQ(type, "inet")) { - return virStorageSourceParseBackingJSONInetSocketAddress(host, json); - - } else if (STREQ(type, "unix")) { - host->transport = VIR_STORAGE_NET_HOST_TRANS_UNIX; - - socket = virJSONValueObjectGetString(json, "path"); - - /* check for old spelling for gluster protocol */ - if (!socket) - socket = virJSONValueObjectGetString(json, "socket"); - - if (!socket) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing socket path for udp backing server in " - "JSON backing volume definition")); - return -1; - } - - host->socket = g_strdup(socket); - } else { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("backing store protocol '%s' is not yet supported"), - type); - return -1; - } - - return 0; -} - - -static int -virStorageSourceParseBackingJSONGluster(virStorageSourcePtr src, - virJSONValuePtr json, - const char *jsonstr G_GNUC_UNUSED, - int opaque G_GNUC_UNUSED) -{ - const char *uri = virJSONValueObjectGetString(json, "filename"); - const char *volume = virJSONValueObjectGetString(json, "volume"); - const char *path = virJSONValueObjectGetString(json, "path"); - virJSONValuePtr server = virJSONValueObjectGetArray(json, "server"); - size_t nservers; - size_t i; - - /* legacy URI based syntax passed via 'filename' option */ - if (uri) - return virStorageSourceParseBackingJSONUriStr(src, uri, - VIR_STORAGE_NET_PROTOCOL_GLUSTER); - - if (!volume || !path || !server) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing 'volume', 'path' or 'server' attribute in " - "JSON backing definition for gluster volume")); - return -1; - } - - src->type = VIR_STORAGE_TYPE_NETWORK; - src->protocol = VIR_STORAGE_NET_PROTOCOL_GLUSTER; - - src->volume = g_strdup(volume); - src->path = g_strdup(path); - - nservers = virJSONValueArraySize(server); - if (nservers == 0) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("at least 1 server is necessary in " - "JSON backing definition for gluster volume")); - - return -1; - } - - src->hosts = g_new0(virStorageNetHostDef, nservers); - src->nhosts = nservers; - - for (i = 0; i < nservers; i++) { - if (virStorageSourceParseBackingJSONSocketAddress(src->hosts + i, - virJSONValueArrayGet(server, i)) < 0) - return -1; - } - - return 0; -} - - -static int -virStorageSourceParseBackingJSONiSCSI(virStorageSourcePtr src, - virJSONValuePtr json, - const char *jsonstr G_GNUC_UNUSED, - int opaque G_GNUC_UNUSED) -{ - const char *transport = virJSONValueObjectGetString(json, "transport"); - const char *portal = virJSONValueObjectGetString(json, "portal"); - const char *target = virJSONValueObjectGetString(json, "target"); - const char *lun = virJSONValueObjectGetStringOrNumber(json, "lun"); - const char *uri; - char *port; - - /* legacy URI based syntax passed via 'filename' option */ - if ((uri = virJSONValueObjectGetString(json, "filename"))) - return virStorageSourceParseBackingJSONUriStr(src, uri, - VIR_STORAGE_NET_PROTOCOL_ISCSI); - - src->type = VIR_STORAGE_TYPE_NETWORK; - src->protocol = VIR_STORAGE_NET_PROTOCOL_ISCSI; - - if (!lun) - lun = "0"; - - src->hosts = g_new0(virStorageNetHostDef, 1); - src->nhosts = 1; - - if (STRNEQ_NULLABLE(transport, "tcp")) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("only TCP transport is supported for iSCSI volumes")); - return -1; - } - - src->hosts->transport = VIR_STORAGE_NET_HOST_TRANS_TCP; - - if (!portal) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing 'portal' address in iSCSI backing definition")); - return -1; - } - - if (!target) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing 'target' in iSCSI backing definition")); - return -1; - } - - src->hosts->name = g_strdup(portal); - - if ((port = strrchr(src->hosts->name, ':')) && - !strchr(port, ']')) { - if (virStringParsePort(port + 1, &src->hosts->port) < 0) - return -1; - - *port = '\0'; - } - - src->path = g_strdup_printf("%s/%s", target, lun); - - /* Libvirt doesn't handle inline authentication. Make the caller aware. */ - if (virJSONValueObjectGetString(json, "user") || - virJSONValueObjectGetString(json, "password")) - return 1; - - return 0; -} - - -static int -virStorageSourceParseBackingJSONNbd(virStorageSourcePtr src, - virJSONValuePtr json, - const char *jsonstr G_GNUC_UNUSED, - int opaque G_GNUC_UNUSED) -{ - const char *path = virJSONValueObjectGetString(json, "path"); - const char *host = virJSONValueObjectGetString(json, "host"); - const char *port = virJSONValueObjectGetString(json, "port"); - const char *export = virJSONValueObjectGetString(json, "export"); - virJSONValuePtr server = virJSONValueObjectGetObject(json, "server"); - - if (!path && !host && !server) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing host specification of NBD server in JSON " - "backing volume definition")); - return -1; - } - - src->type = VIR_STORAGE_TYPE_NETWORK; - src->protocol = VIR_STORAGE_NET_PROTOCOL_NBD; - - src->path = g_strdup(export); - - src->hosts = g_new0(virStorageNetHostDef, 1); - src->nhosts = 1; - - if (server) { - if (virStorageSourceParseBackingJSONSocketAddress(src->hosts, server) < 0) - return -1; - } else { - if (path) { - src->hosts[0].transport = VIR_STORAGE_NET_HOST_TRANS_UNIX; - src->hosts[0].socket = g_strdup(path); - } else { - src->hosts[0].transport = VIR_STORAGE_NET_HOST_TRANS_TCP; - src->hosts[0].name = g_strdup(host); - - if (virStringParsePort(port, &src->hosts[0].port) < 0) - return -1; - } - } - - return 0; -} - - -static int -virStorageSourceParseBackingJSONSheepdog(virStorageSourcePtr src, - virJSONValuePtr json, - const char *jsonstr G_GNUC_UNUSED, - int opaque G_GNUC_UNUSED) -{ - const char *filename; - const char *vdi = virJSONValueObjectGetString(json, "vdi"); - virJSONValuePtr server = virJSONValueObjectGetObject(json, "server"); - - /* legacy URI based syntax passed via 'filename' option */ - if ((filename = virJSONValueObjectGetString(json, "filename"))) { - if (strstr(filename, "://")) - return virStorageSourceParseBackingJSONUriStr(src, filename, - VIR_STORAGE_NET_PROTOCOL_SHEEPDOG); - - /* libvirt doesn't implement a parser for the legacy non-URI syntax */ - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing sheepdog URI in JSON backing volume definition")); - return -1; - } - - src->type = VIR_STORAGE_TYPE_NETWORK; - src->protocol = VIR_STORAGE_NET_PROTOCOL_SHEEPDOG; - - if (!vdi) { - virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing sheepdog vdi name")); - return -1; - } - - src->path = g_strdup(vdi); - - src->hosts = g_new0(virStorageNetHostDef, 1); - src->nhosts = 1; - - if (virStorageSourceParseBackingJSONSocketAddress(src->hosts, server) < 0) - return -1; - - return 0; -} - - -static int -virStorageSourceParseBackingJSONSSH(virStorageSourcePtr src, - virJSONValuePtr json, - const char *jsonstr G_GNUC_UNUSED, - int opaque G_GNUC_UNUSED) -{ - const char *path = virJSONValueObjectGetString(json, "path"); - const char *host = virJSONValueObjectGetString(json, "host"); - const char *port = virJSONValueObjectGetString(json, "port"); - const char *user = virJSONValueObjectGetString(json, "user"); - const char *host_key_check = virJSONValueObjectGetString(json, "host_key_check"); - virJSONValuePtr server = virJSONValueObjectGetObject(json, "server"); - - if (!(host || server) || !path) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing host/server or path of SSH JSON backing " - "volume definition")); - return -1; - } - - src->type = VIR_STORAGE_TYPE_NETWORK; - src->protocol = VIR_STORAGE_NET_PROTOCOL_SSH; - - src->path = g_strdup(path); - - src->hosts = g_new0(virStorageNetHostDef, 1); - src->nhosts = 1; - - if (server) { - if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts, - server) < 0) - return -1; - } else { - src->hosts[0].transport = VIR_STORAGE_NET_HOST_TRANS_TCP; - src->hosts[0].name = g_strdup(host); - - if (virStringParsePort(port, &src->hosts[0].port) < 0) - return -1; - } - - /* these two are parsed just to be passed back as we don't model them yet */ - src->ssh_user = g_strdup(user); - if (STREQ_NULLABLE(host_key_check, "no")) - src->ssh_host_key_check_disabled = true; - - return 0; -} - - -static int -virStorageSourceParseBackingJSONRBD(virStorageSourcePtr src, - virJSONValuePtr json, - const char *jsonstr G_GNUC_UNUSED, - int opaque G_GNUC_UNUSED) -{ - const char *filename; - const char *pool = virJSONValueObjectGetString(json, "pool"); - const char *image = virJSONValueObjectGetString(json, "image"); - const char *conf = virJSONValueObjectGetString(json, "conf"); - const char *snapshot = virJSONValueObjectGetString(json, "snapshot"); - virJSONValuePtr servers = virJSONValueObjectGetArray(json, "server"); - size_t nservers; - size_t i; - - src->type = VIR_STORAGE_TYPE_NETWORK; - src->protocol = VIR_STORAGE_NET_PROTOCOL_RBD; - - /* legacy syntax passed via 'filename' option */ - if ((filename = virJSONValueObjectGetString(json, "filename"))) - return virStorageSourceParseRBDColonString(filename, src); - - if (!pool || !image) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing pool or image name in ceph backing volume " - "JSON specification")); - return -1; - } - - src->volume = g_strdup(pool); - src->path = g_strdup(image); - src->snapshot = g_strdup(snapshot); - src->configFile = g_strdup(conf); - - if (servers) { - nservers = virJSONValueArraySize(servers); - - src->hosts = g_new0(virStorageNetHostDef, nservers); - src->nhosts = nservers; - - for (i = 0; i < nservers; i++) { - if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts + i, - virJSONValueArrayGet(servers, i)) < 0) - return -1; - } - } - - return 0; -} - -static int -virStorageSourceParseBackingJSONRaw(virStorageSourcePtr src, - virJSONValuePtr json, - const char *jsonstr, - int opaque G_GNUC_UNUSED) -{ - bool has_offset = virJSONValueObjectHasKey(json, "offset"); - bool has_size = virJSONValueObjectHasKey(json, "size"); - virJSONValuePtr file; - - if (has_offset || has_size) { - src->sliceStorage = g_new0(virStorageSourceSlice, 1); - - if (has_offset && - virJSONValueObjectGetNumberUlong(json, "offset", &src->sliceStorage->offset) < 0) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("malformed 'offset' property of 'raw' driver")); - return -1; - } - - if (has_size && - virJSONValueObjectGetNumberUlong(json, "size", &src->sliceStorage->size) < 0) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("malformed 'size' property of 'raw' driver")); - return -1; - } - } - - /* 'raw' is a format driver so it can have protocol driver children */ - if (!(file = virJSONValueObjectGetObject(json, "file"))) { - virReportError(VIR_ERR_INVALID_ARG, - _("JSON backing volume definition '%s' lacks 'file' object"), - jsonstr); - return -1; - } - - return virStorageSourceParseBackingJSONInternal(src, file, jsonstr, false); -} - - -static int -virStorageSourceParseBackingJSONVxHS(virStorageSourcePtr src, - virJSONValuePtr json, - const char *jsonstr G_GNUC_UNUSED, - int opaque G_GNUC_UNUSED) -{ - const char *vdisk_id = virJSONValueObjectGetString(json, "vdisk-id"); - virJSONValuePtr server = virJSONValueObjectGetObject(json, "server"); - - if (!vdisk_id || !server) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing 'vdisk-id' or 'server' attribute in " - "JSON backing definition for VxHS volume")); - return -1; - } - - src->type = VIR_STORAGE_TYPE_NETWORK; - src->protocol = VIR_STORAGE_NET_PROTOCOL_VXHS; - - src->path = g_strdup(vdisk_id); - - src->hosts = g_new0(virStorageNetHostDef, 1); - src->nhosts = 1; - - if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts, - server) < 0) - return -1; - - return 0; -} - - -static int -virStorageSourceParseBackingJSONNVMe(virStorageSourcePtr src, - virJSONValuePtr json, - const char *jsonstr G_GNUC_UNUSED, - int opaque G_GNUC_UNUSED) -{ - g_autoptr(virStorageSourceNVMeDef) nvme = g_new0(virStorageSourceNVMeDef, 1); - const char *device = virJSONValueObjectGetString(json, "device"); - - if (!device || virPCIDeviceAddressParse((char *) device, &nvme->pciAddr) < 0) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing or malformed 'device' field of 'nvme' storage")); - return -1; - } - - if (virJSONValueObjectGetNumberUlong(json, "namespace", &nvme->namespc) < 0 || - nvme->namespc == 0) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing or malformed 'namespace' field of 'nvme' storage")); - return -1; - } - - src->type = VIR_STORAGE_TYPE_NVME; - src->nvme = g_steal_pointer(&nvme); - - return 0; -} - - -struct virStorageSourceJSONDriverParser { - const char *drvname; - bool formatdriver; - /** - * The callback gets a pre-allocated storage source @src and the JSON - * object to parse. The callback shall return -1 on error and report error - * 0 on success and 1 in cases when the configuration itself is valid, but - * can't be converted to libvirt's configuration (e.g. inline authentication - * credentials are present). - */ - int (*func)(virStorageSourcePtr src, virJSONValuePtr json, const char *jsonstr, int opaque); - int opaque; -}; - -static const struct virStorageSourceJSONDriverParser jsonParsers[] = { - {"file", false, virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_FILE}, - {"host_device", false, virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_BLOCK}, - {"host_cdrom", false, virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_BLOCK}, - {"http", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_HTTP}, - {"https", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_HTTPS}, - {"ftp", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_FTP}, - {"ftps", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_FTPS}, - {"tftp", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_TFTP}, - {"gluster", false, virStorageSourceParseBackingJSONGluster, 0}, - {"iscsi", false, virStorageSourceParseBackingJSONiSCSI, 0}, - {"nbd", false, virStorageSourceParseBackingJSONNbd, 0}, - {"sheepdog", false, virStorageSourceParseBackingJSONSheepdog, 0}, - {"ssh", false, virStorageSourceParseBackingJSONSSH, 0}, - {"rbd", false, virStorageSourceParseBackingJSONRBD, 0}, - {"raw", true, virStorageSourceParseBackingJSONRaw, 0}, - {"vxhs", false, virStorageSourceParseBackingJSONVxHS, 0}, - {"nvme", false, virStorageSourceParseBackingJSONNVMe, 0}, -}; - - - -static int -virStorageSourceParseBackingJSONInternal(virStorageSourcePtr src, - virJSONValuePtr json, - const char *jsonstr, - bool allowformat) -{ - const char *drvname; - size_t i; - - if (!(drvname = virJSONValueObjectGetString(json, "driver"))) { - virReportError(VIR_ERR_INVALID_ARG, - _("JSON backing volume definition '%s' lacks driver name"), - jsonstr); - return -1; - } - - for (i = 0; i < G_N_ELEMENTS(jsonParsers); i++) { - if (STRNEQ(drvname, jsonParsers[i].drvname)) - continue; - - if (jsonParsers[i].formatdriver && !allowformat) { - virReportError(VIR_ERR_INVALID_ARG, - _("JSON backing volume definition '%s' must not have nested format drivers"), - jsonstr); - return -1; - } - - return jsonParsers[i].func(src, json, jsonstr, jsonParsers[i].opaque); - } - - virReportError(VIR_ERR_INTERNAL_ERROR, - _("missing parser implementation for JSON backing volume " - "driver '%s'"), drvname); - return -1; -} - - -static int -virStorageSourceParseBackingJSON(virStorageSourcePtr src, - const char *json) -{ - g_autoptr(virJSONValue) root = NULL; - g_autoptr(virJSONValue) deflattened = NULL; - virJSONValuePtr file = NULL; - - if (!(root = virJSONValueFromString(json))) - return -1; - - if (!(deflattened = virJSONValueObjectDeflatten(root))) - return -1; - - /* There are 2 possible syntaxes: - * 1) json:{"file":{"driver":...}} - * 2) json:{"driver":...} - * Remove the 'file' wrapper object in case 1. - */ - if (!virJSONValueObjectHasKey(deflattened, "driver")) - file = virJSONValueObjectGetObject(deflattened, "file"); - - if (!file) - file = deflattened; - - return virStorageSourceParseBackingJSONInternal(src, file, json, true); -} - - -/** - * virStorageSourceNewFromBackingAbsolute - * @path: string representing absolute location of a storage source - * @src: filled with virStorageSource object representing @path - * - * Returns 0 on success, 1 if we could parse all location data but @path - * specified other data unrepresentable by libvirt (e.g. inline authentication). - * In both cases @src is filled. On error -1 is returned @src is NULL and an - * error is reported. - */ -int -virStorageSourceNewFromBackingAbsolute(const char *path, - virStorageSourcePtr *src) -{ - const char *json; - const char *dirpath; - int rc = 0; - g_autoptr(virStorageSource) def = virStorageSourceNew(); - - *src = NULL; - - if (virFileIsFile(path)) { - def->type = VIR_STORAGE_TYPE_FILE; - - def->path = g_strdup(path); - } else { - if ((dirpath = STRSKIP(path, "fat:"))) { - def->type = VIR_STORAGE_TYPE_DIR; - def->format = VIR_STORAGE_FILE_FAT; - def->path = g_strdup(dirpath); - *src = g_steal_pointer(&def); - return 0; - } - - def->type = VIR_STORAGE_TYPE_NETWORK; - - VIR_DEBUG("parsing backing store string: '%s'", path); - - /* handle URI formatted backing stores */ - if ((json = STRSKIP(path, "json:"))) - rc = virStorageSourceParseBackingJSON(def, json); - else if (strstr(path, "://")) - rc = virStorageSourceParseBackingURI(def, path); - else - rc = virStorageSourceParseBackingColon(def, path); - - if (rc < 0) - return -1; - - virStorageSourceNetworkAssignDefaultPorts(def); - - /* Some of the legacy parsers parse authentication data since they are - * also used in other places. For backing store detection the - * authentication data would be invalid anyways, so we clear it */ - if (def->auth) { - virStorageAuthDefFree(def->auth); - def->auth = NULL; - } - } - - *src = g_steal_pointer(&def); - return rc; -} - - -/** - * virStorageSourceNewFromChild: - * @parent: storage source parent - * @child: returned child/backing store definition - * @parentRaw: raw child string (backingStoreRaw) - * - * Creates a storage source which describes the backing image of @parent and - * fills it into @backing depending on the passed parentRaw (backingStoreRaw) - * and other data. Note that for local storage this function accesses the file - * to update the actual type of the child store. - * - * Returns 0 on success, 1 if we could parse all location data but the child - * store specification contained other data unrepresentable by libvirt (e.g. - * inline authentication). - * In both cases @src is filled. On error -1 is returned @src is NULL and an - * error is reported. - */ -static int -virStorageSourceNewFromChild(virStorageSourcePtr parent, - const char *parentRaw, - virStorageSourcePtr *child) -{ - struct stat st; - g_autoptr(virStorageSource) def = NULL; - int rc = 0; - - *child = NULL; - - if (virFileIsRelative(parentRaw)) { - if (!(def = virStorageSourceNewFromBackingRelative(parent, parentRaw))) - return -1; - } else { - if ((rc = virStorageSourceNewFromBackingAbsolute(parentRaw, &def)) < 0) - return -1; - } - - /* possibly update local type */ - if (def->type == VIR_STORAGE_TYPE_FILE) { - if (stat(def->path, &st) == 0) { - if (S_ISDIR(st.st_mode)) { - def->type = VIR_STORAGE_TYPE_DIR; - def->format = VIR_STORAGE_FILE_DIR; - } else if (S_ISBLK(st.st_mode)) { - def->type = VIR_STORAGE_TYPE_BLOCK; - } - } - } - - /* copy parent's labelling and other top level stuff */ - if (virStorageSourceInitChainElement(def, parent, true) < 0) - return -1; - - def->detected = true; - - *child = g_steal_pointer(&def); - return rc; -} - - -int -virStorageSourceNewFromBacking(virStorageSourcePtr parent, - virStorageSourcePtr *backing) -{ - int rc; - - if ((rc = virStorageSourceNewFromChild(parent, - parent->backingStoreRaw, - backing)) < 0) - return rc; - - (*backing)->format = parent->backingStoreRawFormat; - (*backing)->readonly = true; - return rc; -} - - -/** - * @src: disk source definition structure - * @fd: file descriptor - * @sb: stat buffer - * - * Updates src->physical depending on the actual type of storage being used. - * To be called for domain storage source reporting as the volume code does - * not set/use the 'type' field for the voldef->source.target - * - * Returns 0 on success, -1 on error. No libvirt errors are reported. - */ -int -virStorageSourceUpdatePhysicalSize(virStorageSourcePtr src, - int fd, - struct stat const *sb) -{ - off_t end; - virStorageType actual_type = virStorageSourceGetActualType(src); - - switch (actual_type) { - case VIR_STORAGE_TYPE_FILE: - case VIR_STORAGE_TYPE_NETWORK: - src->physical = sb->st_size; - break; - - case VIR_STORAGE_TYPE_BLOCK: - if ((end = lseek(fd, 0, SEEK_END)) == (off_t) -1) - return -1; - - src->physical = end; - break; - - case VIR_STORAGE_TYPE_DIR: - src->physical = 0; - break; - - /* We shouldn't get VOLUME, but the switch requires all cases */ - case VIR_STORAGE_TYPE_VOLUME: - case VIR_STORAGE_TYPE_NVME: - case VIR_STORAGE_TYPE_NONE: - case VIR_STORAGE_TYPE_LAST: - return -1; - } - - return 0; -} - - -/** - * @src: disk source definition structure - * @fd: file descriptor - * @sb: stat buffer - * - * Update the capacity, allocation, physical values for the storage @src - * Shared between the domain storage source for an inactive domain and the - * voldef source target as the result is not affected by the 'type' field. - * - * Returns 0 on success, -1 on error. - */ -int -virStorageSourceUpdateBackingSizes(virStorageSourcePtr src, - int fd, - struct stat const *sb) -{ - /* Get info for normal formats */ - if (S_ISREG(sb->st_mode) || fd == -1) { -#ifndef WIN32 - src->allocation = (unsigned long long)sb->st_blocks * - (unsigned long long)DEV_BSIZE; -#else - src->allocation = sb->st_size; -#endif - /* Regular files may be sparse, so logical size (capacity) is not same - * as actual allocation above - */ - src->capacity = sb->st_size; - - /* Allocation tracks when the file is sparse, physical is the - * last offset of the file. */ - src->physical = sb->st_size; - } else if (S_ISDIR(sb->st_mode)) { - src->allocation = 0; - src->capacity = 0; - src->physical = 0; - } else if (fd >= 0) { - off_t end; - - /* XXX this is POSIX compliant, but doesn't work for CHAR files, - * only BLOCK. There is a Linux specific ioctl() for getting - * size of both CHAR / BLOCK devices we should check for in - * configure - * - * NB. Because we configure with AC_SYS_LARGEFILE, off_t - * should be 64 bits on all platforms. For block devices, we - * have to seek (safe even if someone else is writing) to - * determine physical size, and assume that allocation is the - * same as physical (but can refine that assumption later if - * qemu is still running). - */ - if ((end = lseek(fd, 0, SEEK_END)) == (off_t)-1) { - virReportSystemError(errno, - _("failed to seek to end of %s"), src->path); - return -1; - } - src->physical = end; - src->allocation = end; - src->capacity = end; - } - - return 0; -} - - -/** - * @src: disk source definition structure - * @buf: buffer to the storage file header - * @len: length of the storage file header - * - * Update the storage @src capacity. - * - * Returns 0 on success, -1 on error. - */ -int -virStorageSourceUpdateCapacity(virStorageSourcePtr src, - char *buf, - ssize_t len) -{ - int format = src->format; - g_autoptr(virStorageSource) meta = NULL; - - /* Raw files: capacity is physical size. For all other files: if - * the metadata has a capacity, use that, otherwise fall back to - * physical size. */ - if (format == VIR_STORAGE_FILE_NONE) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("no disk format for %s was specified"), - src->path); - return -1; - } - - if (format == VIR_STORAGE_FILE_RAW && !src->encryption) { - src->capacity = src->physical; - } else if ((meta = virStorageFileGetMetadataFromBuf(src->path, buf, - len, format))) { - src->capacity = meta->capacity ? meta->capacity : src->physical; - if (src->encryption && meta->encryption) - src->encryption->payload_offset = meta->encryption->payload_offset; - } else { - return -1; - } - - if (src->encryption && src->encryption->payload_offset != -1) - src->capacity -= src->encryption->payload_offset * 512; - - return 0; -} - - -static char * -virStorageFileCanonicalizeFormatPath(char **components, - size_t ncomponents, - bool beginSlash, - bool beginDoubleSlash) -{ - g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; - size_t i; - char *ret = NULL; - - if (beginSlash) - virBufferAddLit(&buf, "/"); - - if (beginDoubleSlash) - virBufferAddLit(&buf, "/"); - - for (i = 0; i < ncomponents; i++) { - if (i != 0) - virBufferAddLit(&buf, "/"); - - virBufferAdd(&buf, components[i], -1); - } - - /* if the output string is empty just return an empty string */ - if (!(ret = virBufferContentAndReset(&buf))) - ret = g_strdup(""); - - return ret; -} - - -static int -virStorageFileCanonicalizeInjectSymlink(const char *path, - size_t at, - char ***components, - size_t *ncomponents) -{ - char **tmp = NULL; - char **next; - size_t ntmp = 0; - int ret = -1; - - if (!(tmp = virStringSplitCount(path, "/", 0, &ntmp))) - goto cleanup; - - /* prepend */ - for (next = tmp; *next; next++) { - if (VIR_INSERT_ELEMENT(*components, at, *ncomponents, *next) < 0) - goto cleanup; - - at++; - } - - ret = 0; - - cleanup: - virStringListFreeCount(tmp, ntmp); - return ret; -} - - -char * -virStorageFileCanonicalizePath(const char *path, - virStorageFileSimplifyPathReadlinkCallback cb, - void *cbdata) -{ - GHashTable *cycle = NULL; - bool beginSlash = false; - bool beginDoubleSlash = false; - char **components = NULL; - size_t ncomponents = 0; - size_t i = 0; - size_t j = 0; - int rc; - char *ret = NULL; - g_autofree char *linkpath = NULL; - g_autofree char *currentpath = NULL; - - if (path[0] == '/') { - beginSlash = true; - - if (path[1] == '/' && path[2] != '/') - beginDoubleSlash = true; - } - - if (!(cycle = virHashNew(NULL))) - goto cleanup; - - if (!(components = virStringSplitCount(path, "/", 0, &ncomponents))) - goto cleanup; - - j = 0; - while (j < ncomponents) { - /* skip slashes */ - if (STREQ(components[j], "")) { - VIR_FREE(components[j]); - VIR_DELETE_ELEMENT(components, j, ncomponents); - continue; - } - j++; - } - - while (i < ncomponents) { - /* skip '.'s unless it's the last one remaining */ - if (STREQ(components[i], ".") && - (beginSlash || ncomponents > 1)) { - VIR_FREE(components[i]); - VIR_DELETE_ELEMENT(components, i, ncomponents); - continue; - } - - /* resolve changes to parent directory */ - if (STREQ(components[i], "..")) { - if (!beginSlash && - (i == 0 || STREQ(components[i - 1], ".."))) { - i++; - continue; - } - - VIR_FREE(components[i]); - VIR_DELETE_ELEMENT(components, i, ncomponents); - - if (i != 0) { - VIR_FREE(components[i - 1]); - VIR_DELETE_ELEMENT(components, i - 1, ncomponents); - i--; - } - - continue; - } - - /* check if the actual path isn't resulting into a symlink */ - if (!(currentpath = virStorageFileCanonicalizeFormatPath(components, - i + 1, - beginSlash, - beginDoubleSlash))) - goto cleanup; - - if ((rc = cb(currentpath, &linkpath, cbdata)) < 0) - goto cleanup; - - if (rc == 0) { - if (virHashLookup(cycle, currentpath)) { - virReportSystemError(ELOOP, - _("Failed to canonicalize path '%s'"), path); - goto cleanup; - } - - if (virHashAddEntry(cycle, currentpath, (void *) 1) < 0) - goto cleanup; - - if (linkpath[0] == '/') { - /* kill everything from the beginning including the actual component */ - i++; - while (i--) { - VIR_FREE(components[0]); - VIR_DELETE_ELEMENT(components, 0, ncomponents); - } - beginSlash = true; - - if (linkpath[1] == '/' && linkpath[2] != '/') - beginDoubleSlash = true; - else - beginDoubleSlash = false; - - i = 0; - } else { - VIR_FREE(components[i]); - VIR_DELETE_ELEMENT(components, i, ncomponents); - } - - if (virStorageFileCanonicalizeInjectSymlink(linkpath, - i, - &components, - &ncomponents) < 0) - goto cleanup; - - j = 0; - while (j < ncomponents) { - /* skip slashes */ - if (STREQ(components[j], "")) { - VIR_FREE(components[j]); - VIR_DELETE_ELEMENT(components, j, ncomponents); - continue; - } - j++; - } - - VIR_FREE(linkpath); - VIR_FREE(currentpath); - - continue; - } - - VIR_FREE(currentpath); - - i++; - } - - ret = virStorageFileCanonicalizeFormatPath(components, ncomponents, - beginSlash, beginDoubleSlash); - - cleanup: - virHashFree(cycle); - virStringListFreeCount(components, ncomponents); - - return ret; -} - - -/** - * virStorageFileRemoveLastPathComponent: - * - * @path: Path string to remove the last component from - * - * Removes the last path component of a path. This function is designed to be - * called on file paths only (no trailing slashes in @path). Caller is - * responsible to free the returned string. - */ -static char * -virStorageFileRemoveLastPathComponent(const char *path) -{ - char *ret; - - ret = g_strdup(NULLSTR_EMPTY(path)); - - virFileRemoveLastComponent(ret); - - return ret; -} - - -/* - * virStorageFileGetRelativeBackingPath: - * - * Resolve relative path to be written to the overlay of @top image when - * collapsing the backing chain between @top and @base. - * - * Returns 0 on success; 1 if backing chain isn't relative and -1 on error. - */ -int -virStorageFileGetRelativeBackingPath(virStorageSourcePtr top, - virStorageSourcePtr base, - char **relpath) -{ - virStorageSourcePtr next; - g_autofree char *tmp = NULL; - g_autofree char *path = NULL; - - *relpath = NULL; - - for (next = top; virStorageSourceIsBacking(next); next = next->backingStore) { - if (!next->relPath) - return 1; - - if (!(tmp = virStorageFileRemoveLastPathComponent(path))) - return -1; - - VIR_FREE(path); - - path = g_strdup_printf("%s%s", tmp, next->relPath); - - VIR_FREE(tmp); - - if (next == base) - break; - } - - if (next != base) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("failed to resolve relative backing name: " - "base image is not in backing chain")); - return -1; - } - - *relpath = g_steal_pointer(&path); - return 0; -} - - /** * virStorageSourceIsRelative: * @src: storage source to check @@ -4351,31 +1347,6 @@ virStorageSourceIsRelative(virStorageSourcePtr src) } -/** - * virStorageSourceFindByNodeName: - * @top: backing chain top - * @nodeName: node name to find in backing chain - * - * Looks up the given storage source in the backing chain and returns the - * pointer to it. - * On failure NULL is returned and no error is reported. - */ -virStorageSourcePtr -virStorageSourceFindByNodeName(virStorageSourcePtr top, - const char *nodeName) -{ - virStorageSourcePtr tmp; - - for (tmp = top; virStorageSourceIsBacking(tmp); tmp = tmp->backingStore) { - if ((tmp->nodeformat && STREQ(tmp->nodeformat, nodeName)) || - (tmp->nodestorage && STREQ(tmp->nodestorage, nodeName))) - return tmp; - } - - return NULL; -} - - static unsigned int virStorageSourceNetworkDefaultPort(virStorageNetProtocol protocol) { @@ -4439,25 +1410,6 @@ virStorageSourceNetworkAssignDefaultPorts(virStorageSourcePtr src) } -int -virStorageSourcePrivateDataParseRelPath(xmlXPathContextPtr ctxt, - virStorageSourcePtr src) -{ - src->relPath = virXPathString("string(./relPath)", ctxt); - return 0; -} - - -int -virStorageSourcePrivateDataFormatRelPath(virStorageSourcePtr src, - virBufferPtr buf) -{ - if (src->relPath) - virBufferEscapeString(buf, "<relPath>%s</relPath>\n", src->relPath); - - return 0; -} - void virStorageSourceInitiatorParseXML(xmlXPathContextPtr ctxt, virStorageSourceInitiatorDefPtr initiator) @@ -4492,736 +1444,3 @@ virStorageSourceInitiatorClear(virStorageSourceInitiatorDefPtr initiator) { VIR_FREE(initiator->iqn); } - -static bool -virStorageFileIsInitialized(const virStorageSource *src) -{ - return src && src->drv; -} - - -/** - * virStorageFileGetBackendForSupportCheck: - * @src: storage source to check support for - * @backend: pointer to the storage backend for @src if it's supported - * - * Returns 0 if @src is not supported by any storage backend currently linked - * 1 if it is supported and -1 on error with an error reported. - */ -static int -virStorageFileGetBackendForSupportCheck(const virStorageSource *src, - virStorageFileBackendPtr *backend) -{ - int actualType; - - - if (!src) { - *backend = NULL; - return 0; - } - - if (src->drv) { - virStorageDriverDataPtr drv = src->drv; - *backend = drv->backend; - return 1; - } - - actualType = virStorageSourceGetActualType(src); - - if (virStorageFileBackendForType(actualType, src->protocol, false, backend) < 0) - return -1; - - if (!*backend) - return 0; - - return 1; -} - - -int -virStorageFileSupportsBackingChainTraversal(const virStorageSource *src) -{ - virStorageFileBackendPtr backend; - int rv; - - if ((rv = virStorageFileGetBackendForSupportCheck(src, &backend)) < 1) - return rv; - - return backend->storageFileGetUniqueIdentifier && - backend->storageFileRead && - backend->storageFileAccess ? 1 : 0; -} - - -/** - * virStorageFileSupportsSecurityDriver: - * - * @src: a storage file structure - * - * Check if a storage file supports operations needed by the security - * driver to perform labelling - */ -int -virStorageFileSupportsSecurityDriver(const virStorageSource *src) -{ - virStorageFileBackendPtr backend; - int rv; - - if ((rv = virStorageFileGetBackendForSupportCheck(src, &backend)) < 1) - return rv; - - return backend->storageFileChown ? 1 : 0; -} - - -/** - * virStorageFileSupportsAccess: - * - * @src: a storage file structure - * - * Check if a storage file supports checking if the storage source is accessible - * for the given vm. - */ -int -virStorageFileSupportsAccess(const virStorageSource *src) -{ - virStorageFileBackendPtr backend; - int rv; - - if ((rv = virStorageFileGetBackendForSupportCheck(src, &backend)) < 1) - return rv; - - return backend->storageFileAccess ? 1 : 0; -} - - -/** - * virStorageFileSupportsCreate: - * @src: a storage file structure - * - * Check if the storage driver supports creating storage described by @src - * via virStorageFileCreate. - */ -int -virStorageFileSupportsCreate(const virStorageSource *src) -{ - virStorageFileBackendPtr backend; - int rv; - - if ((rv = virStorageFileGetBackendForSupportCheck(src, &backend)) < 1) - return rv; - - return backend->storageFileCreate ? 1 : 0; -} - - -void -virStorageFileDeinit(virStorageSourcePtr src) -{ - virStorageDriverDataPtr drv = NULL; - - if (!virStorageFileIsInitialized(src)) - return; - - drv = src->drv; - - if (drv->backend && - drv->backend->backendDeinit) - drv->backend->backendDeinit(src); - - VIR_FREE(src->drv); -} - - -/** - * virStorageFileInitAs: - * - * @src: storage source definition - * @uid: uid used to access the file, or -1 for current uid - * @gid: gid used to access the file, or -1 for current gid - * - * Initialize a storage source to be used with storage driver. Use the provided - * uid and gid if possible for the operations. - * - * Returns 0 if the storage file was successfully initialized, -1 if the - * initialization failed. Libvirt error is reported. - */ -int -virStorageFileInitAs(virStorageSourcePtr src, - uid_t uid, gid_t gid) -{ - int actualType = virStorageSourceGetActualType(src); - virStorageDriverDataPtr drv = g_new0(virStorageDriverData, 1); - - src->drv = drv; - - if (uid == (uid_t) -1) - drv->uid = geteuid(); - else - drv->uid = uid; - - if (gid == (gid_t) -1) - drv->gid = getegid(); - else - drv->gid = gid; - - if (virStorageFileBackendForType(actualType, - src->protocol, - true, - &drv->backend) < 0) - goto error; - - if (drv->backend->backendInit && - drv->backend->backendInit(src) < 0) - goto error; - - return 0; - - error: - VIR_FREE(src->drv); - return -1; -} - - -/** - * virStorageFileInit: - * - * See virStorageFileInitAs. The file is initialized to be accessed by the - * current user. - */ -int -virStorageFileInit(virStorageSourcePtr src) -{ - return virStorageFileInitAs(src, -1, -1); -} - - -/** - * virStorageFileCreate: Creates an empty storage file via storage driver - * - * @src: file structure pointing to the file - * - * Returns 0 on success, -2 if the function isn't supported by the backend, - * -1 on other failure. Errno is set in case of failure. - */ -int -virStorageFileCreate(virStorageSourcePtr src) -{ - virStorageDriverDataPtr drv = NULL; - int ret; - - if (!virStorageFileIsInitialized(src)) { - errno = ENOSYS; - return -2; - } - - drv = src->drv; - - if (!drv->backend->storageFileCreate) { - errno = ENOSYS; - return -2; - } - - ret = drv->backend->storageFileCreate(src); - - VIR_DEBUG("created storage file %p: ret=%d, errno=%d", - src, ret, errno); - - return ret; -} - - -/** - * virStorageFileUnlink: Unlink storage file via storage driver - * - * @src: file structure pointing to the file - * - * Unlinks the file described by the @file structure. - * - * Returns 0 on success, -2 if the function isn't supported by the backend, - * -1 on other failure. Errno is set in case of failure. - */ -int -virStorageFileUnlink(virStorageSourcePtr src) -{ - virStorageDriverDataPtr drv = NULL; - int ret; - - if (!virStorageFileIsInitialized(src)) { - errno = ENOSYS; - return -2; - } - - drv = src->drv; - - if (!drv->backend->storageFileUnlink) { - errno = ENOSYS; - return -2; - } - - ret = drv->backend->storageFileUnlink(src); - - VIR_DEBUG("unlinked storage file %p: ret=%d, errno=%d", - src, ret, errno); - - return ret; -} - - -/** - * virStorageFileStat: returns stat struct of a file via storage driver - * - * @src: file structure pointing to the file - * @stat: stat structure to return data - * - * Returns 0 on success, -2 if the function isn't supported by the backend, - * -1 on other failure. Errno is set in case of failure. -*/ -int -virStorageFileStat(virStorageSourcePtr src, - struct stat *st) -{ - virStorageDriverDataPtr drv = NULL; - int ret; - - if (!virStorageFileIsInitialized(src)) { - errno = ENOSYS; - return -2; - } - - drv = src->drv; - - if (!drv->backend->storageFileStat) { - errno = ENOSYS; - return -2; - } - - ret = drv->backend->storageFileStat(src, st); - - VIR_DEBUG("stat of storage file %p: ret=%d, errno=%d", - src, ret, errno); - - return ret; -} - - -/** - * virStorageFileRead: read bytes from a file into a buffer - * - * @src: file structure pointing to the file - * @offset: number of bytes to skip in the storage file - * @len: maximum number of bytes read from the storage file - * @buf: buffer to read the data into. (buffer shall be freed by caller) - * - * Returns the count of bytes read on success and -1 on failure, -2 if the - * function isn't supported by the backend. - * Libvirt error is reported on failure. - */ -ssize_t -virStorageFileRead(virStorageSourcePtr src, - size_t offset, - size_t len, - char **buf) -{ - virStorageDriverDataPtr drv = NULL; - ssize_t ret; - - if (!virStorageFileIsInitialized(src)) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("storage file backend not initialized")); - return -1; - } - - drv = src->drv; - - if (!drv->backend->storageFileRead) - return -2; - - ret = drv->backend->storageFileRead(src, offset, len, buf); - - VIR_DEBUG("read '%zd' bytes from storage '%p' starting at offset '%zu'", - ret, src, offset); - - return ret; -} - - -/* - * virStorageFileGetUniqueIdentifier: Get a unique string describing the volume - * - * @src: file structure pointing to the file - * - * Returns a string uniquely describing a single volume (canonical path). - * The string shall not be freed and is valid until the storage file is - * deinitialized. Returns NULL on error and sets a libvirt error code */ -const char * -virStorageFileGetUniqueIdentifier(virStorageSourcePtr src) -{ - virStorageDriverDataPtr drv = NULL; - - if (!virStorageFileIsInitialized(src)) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("storage file backend not initialized")); - return NULL; - } - - drv = src->drv; - - if (!drv->backend->storageFileGetUniqueIdentifier) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unique storage file identifier not implemented for " - "storage type %s (protocol: %s)'"), - virStorageTypeToString(src->type), - virStorageNetProtocolTypeToString(src->protocol)); - return NULL; - } - - return drv->backend->storageFileGetUniqueIdentifier(src); -} - - -/** - * virStorageFileAccess: Check accessibility of a storage file - * - * @src: storage file to check access permissions - * @mode: accessibility check options (see man 2 access) - * - * Returns 0 on success, -1 on error and sets errno. No libvirt - * error is reported. Returns -2 if the operation isn't supported - * by libvirt storage backend. - */ -int -virStorageFileAccess(virStorageSourcePtr src, - int mode) -{ - virStorageDriverDataPtr drv = NULL; - - if (!virStorageFileIsInitialized(src)) { - errno = ENOSYS; - return -2; - } - - drv = src->drv; - - if (!drv->backend->storageFileAccess) { - errno = ENOSYS; - return -2; - } - - return drv->backend->storageFileAccess(src, mode); -} - - -/** - * virStorageFileChown: Change owner of a storage file - * - * @src: storage file to change owner of - * @uid: new owner id - * @gid: new group id - * - * Returns 0 on success, -1 on error and sets errno. No libvirt - * error is reported. Returns -2 if the operation isn't supported - * by libvirt storage backend. - */ -int -virStorageFileChown(const virStorageSource *src, - uid_t uid, - gid_t gid) -{ - virStorageDriverDataPtr drv = NULL; - - if (!virStorageFileIsInitialized(src)) { - errno = ENOSYS; - return -2; - } - - drv = src->drv; - - if (!drv->backend->storageFileChown) { - errno = ENOSYS; - return -2; - } - - VIR_DEBUG("chown of storage file %p to %u:%u", - src, (unsigned int)uid, (unsigned int)gid); - - return drv->backend->storageFileChown(src, uid, gid); -} - - -/** - * virStorageFileReportBrokenChain: - * - * @errcode: errno when accessing @src - * @src: inaccessible file in the backing chain of @parent - * @parent: root virStorageSource being checked - * - * Reports the correct error message if @src is missing in the backing chain - * for @parent. - */ -void -virStorageFileReportBrokenChain(int errcode, - virStorageSourcePtr src, - virStorageSourcePtr parent) -{ - if (src->drv) { - virStorageDriverDataPtr drv = src->drv; - unsigned int access_user = drv->uid; - unsigned int access_group = drv->gid; - - if (src == parent) { - virReportSystemError(errcode, - _("Cannot access storage file '%s' " - "(as uid:%u, gid:%u)"), - src->path, access_user, access_group); - } else { - virReportSystemError(errcode, - _("Cannot access backing file '%s' " - "of storage file '%s' (as uid:%u, gid:%u)"), - src->path, parent->path, access_user, access_group); - } - } else { - if (src == parent) { - virReportSystemError(errcode, - _("Cannot access storage file '%s'"), - src->path); - } else { - virReportSystemError(errcode, - _("Cannot access backing file '%s' " - "of storage file '%s'"), - src->path, parent->path); - } - } -} - - -static int -virStorageFileGetMetadataRecurseReadHeader(virStorageSourcePtr src, - virStorageSourcePtr parent, - uid_t uid, - gid_t gid, - char **buf, - size_t *headerLen, - GHashTable *cycle) -{ - int ret = -1; - const char *uniqueName; - ssize_t len; - - if (virStorageFileInitAs(src, uid, gid) < 0) - return -1; - - if (virStorageFileAccess(src, F_OK) < 0) { - virStorageFileReportBrokenChain(errno, src, parent); - goto cleanup; - } - - if (!(uniqueName = virStorageFileGetUniqueIdentifier(src))) - goto cleanup; - - if (virHashHasEntry(cycle, uniqueName)) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("backing store for %s (%s) is self-referential"), - NULLSTR(src->path), uniqueName); - goto cleanup; - } - - if (virHashAddEntry(cycle, uniqueName, NULL) < 0) - goto cleanup; - - if ((len = virStorageFileRead(src, 0, VIR_STORAGE_MAX_HEADER, buf)) < 0) - goto cleanup; - - *headerLen = len; - ret = 0; - - cleanup: - virStorageFileDeinit(src); - return ret; -} - - -/* Recursive workhorse for virStorageFileGetMetadata. */ -static int -virStorageFileGetMetadataRecurse(virStorageSourcePtr src, - virStorageSourcePtr parent, - uid_t uid, gid_t gid, - bool report_broken, - GHashTable *cycle, - unsigned int depth) -{ - virStorageFileFormat orig_format = src->format; - size_t headerLen; - int rv; - g_autofree char *buf = NULL; - g_autoptr(virStorageSource) backingStore = NULL; - - VIR_DEBUG("path=%s format=%d uid=%u gid=%u", - NULLSTR(src->path), src->format, - (unsigned int)uid, (unsigned int)gid); - - if (src->format == VIR_STORAGE_FILE_AUTO_SAFE) - src->format = VIR_STORAGE_FILE_AUTO; - - /* exit if we can't load information about the current image */ - rv = virStorageFileSupportsBackingChainTraversal(src); - if (rv <= 0) { - if (orig_format == VIR_STORAGE_FILE_AUTO) - return -2; - - return rv; - } - - if (virStorageFileGetMetadataRecurseReadHeader(src, parent, uid, gid, - &buf, &headerLen, cycle) < 0) - return -1; - - if (virStorageFileGetMetadataInternal(src, buf, headerLen) < 0) - return -1; - - /* If we probed the format we MUST ensure that nothing else than the current - * image is considered for security labelling and/or recursion. */ - if (orig_format == VIR_STORAGE_FILE_AUTO) { - if (src->backingStoreRaw) { - src->format = VIR_STORAGE_FILE_RAW; - VIR_FREE(src->backingStoreRaw); - return -2; - } - } - - if (src->backingStoreRaw) { - if ((rv = virStorageSourceNewFromBacking(src, &backingStore)) < 0) - return -1; - - /* the backing file would not be usable for VM usage */ - if (rv == 1) - return 0; - - if ((rv = virStorageFileGetMetadataRecurse(backingStore, parent, - uid, gid, - report_broken, - cycle, depth + 1)) < 0) { - if (!report_broken) - return 0; - - if (rv == -2) { - virReportError(VIR_ERR_OPERATION_INVALID, - _("format of backing image '%s' of image '%s' was not specified in the image metadata " - "(See https://libvirt.org/kbase/backing_chains.html for troubleshooting)"), - src->backingStoreRaw, NULLSTR(src->path)); - } - - return -1; - } - - backingStore->id = depth; - src->backingStore = g_steal_pointer(&backingStore); - } else { - /* add terminator */ - src->backingStore = virStorageSourceNew(); - } - - return 0; -} - - -/** - * virStorageFileGetMetadata: - * - * Extract metadata about the storage volume with the specified - * image format. If image format is VIR_STORAGE_FILE_AUTO, it - * will probe to automatically identify the format. Recurses through - * the entire chain. - * - * Open files using UID and GID (or pass -1 for the current user/group). - * Treat any backing files without explicit type as raw, unless ALLOW_PROBE. - * - * Callers are advised never to use VIR_STORAGE_FILE_AUTO as a - * format, since a malicious guest can turn a raw file into any - * other non-raw format at will. - * - * If @report_broken is true, the whole function fails with a possibly sane - * error instead of just returning a broken chain. Note that the inability for - * libvirt to traverse a given source is not considered an error. - * - * Caller MUST free result after use via virObjectUnref. - */ -int -virStorageFileGetMetadata(virStorageSourcePtr src, - uid_t uid, gid_t gid, - bool report_broken) -{ - GHashTable *cycle = NULL; - virStorageType actualType = virStorageSourceGetActualType(src); - int ret = -1; - - VIR_DEBUG("path=%s format=%d uid=%u gid=%u report_broken=%d", - src->path, src->format, (unsigned int)uid, (unsigned int)gid, - report_broken); - - if (!(cycle = virHashNew(NULL))) - return -1; - - if (src->format <= VIR_STORAGE_FILE_NONE) { - if (actualType == VIR_STORAGE_TYPE_DIR) - src->format = VIR_STORAGE_FILE_DIR; - else - src->format = VIR_STORAGE_FILE_RAW; - } - - ret = virStorageFileGetMetadataRecurse(src, src, uid, gid, - report_broken, cycle, 1); - - virHashFree(cycle); - return ret; -} - - -/** - * virStorageFileGetBackingStoreStr: - * @src: storage object - * - * Extracts the backing store string as stored in the storage volume described - * by @src and returns it to the user. Caller is responsible for freeing it. - * In case when the string can't be retrieved or does not exist NULL is - * returned. - */ -int -virStorageFileGetBackingStoreStr(virStorageSourcePtr src, - char **backing) -{ - ssize_t headerLen; - int rv; - g_autofree char *buf = NULL; - g_autoptr(virStorageSource) tmp = NULL; - - *backing = NULL; - - /* exit if we can't load information about the current image */ - if (!virStorageFileSupportsBackingChainTraversal(src)) - return 0; - - rv = virStorageFileAccess(src, F_OK); - if (rv == -2) - return 0; - if (rv < 0) { - virStorageFileReportBrokenChain(errno, src, src); - return -1; - } - - if ((headerLen = virStorageFileRead(src, 0, VIR_STORAGE_MAX_HEADER, - &buf)) < 0) { - if (headerLen == -2) - return 0; - return -1; - } - - if (!(tmp = virStorageSourceCopy(src, false))) - return -1; - - if (virStorageFileGetMetadataInternal(tmp, buf, headerLen) < 0) - return -1; - - *backing = g_steal_pointer(&tmp->backingStoreRaw); - return 0; -} diff --git a/src/util/virstoragefile.h b/src/util/virstoragefile.h index a1e0afb28f..557e7da81e 100644 --- a/src/util/virstoragefile.h +++ b/src/util/virstoragefile.h @@ -21,8 +21,6 @@ #pragma once -#include <sys/stat.h> - #include "virbitmap.h" #include "virobject.h" #include "virseclabel.h" @@ -31,14 +29,6 @@ #include "virenum.h" #include "virpci.h" -/* Minimum header size required to probe all known formats with - * virStorageFileProbeFormat, or obtain metadata from a known format. - * Rounded to multiple of 512 (ISO has a 5-byte magic at offset - * 32769). Some formats can be probed with fewer bytes. Although - * some formats theoretically permit metadata that can rely on offsets - * beyond this size, in practice that doesn't matter. */ -#define VIR_STORAGE_MAX_HEADER 0x8200 - /* Types of disk backends (host resource). Comparable to the public * virStorageVolType, except we have an undetermined state, don't have @@ -386,37 +376,6 @@ struct _virStorageSource { G_DEFINE_AUTOPTR_CLEANUP_FUNC(virStorageSource, virObjectUnref); -#ifndef DEV_BSIZE -# define DEV_BSIZE 512 -#endif - -int virStorageFileProbeFormat(const char *path, uid_t uid, gid_t gid); - -virStorageSourcePtr virStorageFileGetMetadataFromFD(const char *path, - int fd, - int format); -virStorageSourcePtr virStorageFileGetMetadataFromBuf(const char *path, - char *buf, - size_t len, - int format) - ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); -int virStorageFileParseChainIndex(const char *diskTarget, - const char *name, - unsigned int *chainIndex) - ATTRIBUTE_NONNULL(3); - -int virStorageFileParseBackingStoreStr(const char *str, - char **target, - unsigned int *chainIndex) - ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3); - -virStorageSourcePtr virStorageFileChainLookup(virStorageSourcePtr chain, - virStorageSourcePtr startFrom, - const char *name, - unsigned int idx, - virStorageSourcePtr *parent) - ATTRIBUTE_NONNULL(1); - int virStorageFileGetSCSIKey(const char *path, char **key, bool ignoreError); @@ -454,7 +413,6 @@ void virStorageNetHostDefClear(virStorageNetHostDefPtr def); void virStorageNetHostDefFree(size_t nhosts, virStorageNetHostDefPtr hosts); virStorageNetHostDefPtr virStorageNetHostDefCopy(size_t nhosts, virStorageNetHostDefPtr hosts); - int virStorageSourceInitChainElement(virStorageSourcePtr newelem, virStorageSourcePtr old, bool force); @@ -466,15 +424,6 @@ bool virStorageSourceIsEmpty(virStorageSourcePtr src); bool virStorageSourceIsBlockLocal(const virStorageSource *src); virStorageSourcePtr virStorageSourceNew(void); void virStorageSourceBackingStoreClear(virStorageSourcePtr def); -int virStorageSourceUpdatePhysicalSize(virStorageSourcePtr src, - int fd, struct stat const *sb); -int virStorageSourceUpdateBackingSizes(virStorageSourcePtr src, - int fd, struct stat const *sb); -int virStorageSourceUpdateCapacity(virStorageSourcePtr src, - char *buf, ssize_t len); - -int virStorageSourceNewFromBacking(virStorageSourcePtr parent, - virStorageSourcePtr *backing); int virStorageSourceNetCookiesValidate(virStorageSourcePtr src); @@ -485,33 +434,8 @@ bool virStorageSourceIsSameLocation(virStorageSourcePtr a, virStorageSourcePtr b) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); -int virStorageSourceParseRBDColonString(const char *rbdstr, - virStorageSourcePtr src) - ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); - -typedef int -(*virStorageFileSimplifyPathReadlinkCallback)(const char *path, - char **link, - void *data); -char *virStorageFileCanonicalizePath(const char *path, - virStorageFileSimplifyPathReadlinkCallback cb, - void *cbdata); - -int virStorageFileGetRelativeBackingPath(virStorageSourcePtr from, - virStorageSourcePtr to, - char **relpath) - ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3); - -int virStorageSourceNewFromBackingAbsolute(const char *path, - virStorageSourcePtr *src); - bool virStorageSourceIsRelative(virStorageSourcePtr src); -virStorageSourcePtr -virStorageSourceFindByNodeName(virStorageSourcePtr top, - const char *nodeName) - ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); - void virStorageSourceNetworkAssignDefaultPorts(virStorageSourcePtr src) ATTRIBUTE_NONNULL(1); @@ -522,13 +446,6 @@ bool virStorageSourceHasBacking(const virStorageSource *src); -int -virStorageSourcePrivateDataParseRelPath(xmlXPathContextPtr ctxt, - virStorageSourcePtr src); -int -virStorageSourcePrivateDataFormatRelPath(virStorageSourcePtr src, - virBufferPtr buf); - void virStorageSourceInitiatorParseXML(xmlXPathContextPtr ctxt, virStorageSourceInitiatorDefPtr initiator); @@ -544,39 +461,4 @@ virStorageSourceInitiatorCopy(virStorageSourceInitiatorDefPtr dest, void virStorageSourceInitiatorClear(virStorageSourceInitiatorDefPtr initiator); -int virStorageFileInit(virStorageSourcePtr src); -int virStorageFileInitAs(virStorageSourcePtr src, - uid_t uid, gid_t gid); -void virStorageFileDeinit(virStorageSourcePtr src); - -int virStorageFileCreate(virStorageSourcePtr src); -int virStorageFileUnlink(virStorageSourcePtr src); -int virStorageFileStat(virStorageSourcePtr src, - struct stat *stat); -ssize_t virStorageFileRead(virStorageSourcePtr src, - size_t offset, - size_t len, - char **buf); -const char *virStorageFileGetUniqueIdentifier(virStorageSourcePtr src); -int virStorageFileAccess(virStorageSourcePtr src, int mode); -int virStorageFileChown(const virStorageSource *src, uid_t uid, gid_t gid); - -int virStorageFileSupportsSecurityDriver(const virStorageSource *src); -int virStorageFileSupportsAccess(const virStorageSource *src); -int virStorageFileSupportsCreate(const virStorageSource *src); -int virStorageFileSupportsBackingChainTraversal(const virStorageSource *src); - -int virStorageFileGetMetadata(virStorageSourcePtr src, - uid_t uid, gid_t gid, - bool report_broken) - ATTRIBUTE_NONNULL(1); - -int virStorageFileGetBackingStoreStr(virStorageSourcePtr src, - char **backing) - ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); - -void virStorageFileReportBrokenChain(int errcode, - virStorageSourcePtr src, - virStorageSourcePtr parent); - G_DEFINE_AUTOPTR_CLEANUP_FUNC(virStorageAuthDef, virStorageAuthDefFree); diff --git a/tests/meson.build b/tests/meson.build index f1d91ca50d..ce5b625b43 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -442,7 +442,7 @@ endif if conf.has('WITH_QEMU') tests += [ { 'name': 'qemuagenttest', 'link_with': [ test_qemu_driver_lib, test_utils_qemu_monitor_lib ], 'link_whole': [ test_utils_qemu_lib ] }, - { 'name': 'qemublocktest', 'link_with': [ test_qemu_driver_lib, test_utils_qemu_monitor_lib ], 'link_whole': [ test_utils_qemu_lib ] }, + { 'name': 'qemublocktest', 'include': [ storage_file_inc_dir ], 'link_with': [ test_qemu_driver_lib, test_utils_qemu_monitor_lib ], 'link_whole': [ test_utils_qemu_lib ] }, { 'name': 'qemucapabilitiestest', 'link_with': [ test_qemu_driver_lib, test_utils_qemu_monitor_lib ], 'link_whole': [ test_utils_qemu_lib ] }, { 'name': 'qemucaps2xmltest', 'link_with': [ test_qemu_driver_lib ], 'link_whole': [ test_utils_qemu_lib ] }, { 'name': 'qemucommandutiltest', 'link_with': [ test_qemu_driver_lib, test_utils_qemu_monitor_lib ], 'link_whole': [ test_utils_qemu_lib ] }, @@ -509,7 +509,7 @@ endif if conf.has('WITH_STORAGE_FS') tests += [ - { 'name': 'virstoragetest', 'link_with': [ storage_driver_impl_lib ] }, + { 'name': 'virstoragetest', 'include': [ storage_file_inc_dir ], 'link_with': [ storage_driver_impl_lib ] }, ] endif diff --git a/tests/qemublocktest.c b/tests/qemublocktest.c index f86e9fd552..ee57449868 100644 --- a/tests/qemublocktest.c +++ b/tests/qemublocktest.c @@ -21,6 +21,7 @@ #include "testutilsqemu.h" #include "testutilsqemuschema.h" #include "virstoragefile.h" +#include "storage_file.h" #include "virstring.h" #include "virlog.h" #include "qemu/qemu_block.h" diff --git a/tests/virstoragetest.c b/tests/virstoragetest.c index a376154def..59c03255d4 100644 --- a/tests/virstoragetest.c +++ b/tests/virstoragetest.c @@ -20,6 +20,7 @@ #include <unistd.h> +#include "storage_file.h" #include "testutils.h" #include "vircommand.h" #include "virerror.h" -- 2.28.0