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