Finally, we get to the point of all this. Rename virStorageBackendProbeTarget() to virStorageFileProbeHeader() and move to src/util/storage_file.[ch] * src/storage/storage_backend_fs.c: move code from here ... * src/util/storage_file.[ch]: ... to here * src/libvirt_private.syms: export virStorageFileProbeHeader() --- src/libvirt_private.syms | 1 + src/storage/storage_backend_fs.c | 435 ++------------------------------------ src/util/storage_file.c | 409 +++++++++++++++++++++++++++++++++++ src/util/storage_file.h | 10 + 4 files changed, 435 insertions(+), 420 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 9b6f4a7..6a353ee 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -395,6 +395,7 @@ virStorageFileFormatTypeToString; virStorageFileFormatTypeFromString; virStorageFileGetInfo; virStorageFileGetInfoFromFD; +virStorageFileProbeHeader; # threads.h virMutexInit; diff --git a/src/storage/storage_backend_fs.c b/src/storage/storage_backend_fs.c index 49cfc6f..fdc12c3 100644 --- a/src/storage/storage_backend_fs.c +++ b/src/storage/storage_backend_fs.c @@ -46,419 +46,10 @@ #include "memory.h" #include "xml.h" -enum lv_endian { - LV_LITTLE_ENDIAN = 1, /* 1234 */ - LV_BIG_ENDIAN /* 4321 */ -}; - -enum { - BACKING_STORE_OK, - BACKING_STORE_INVALID, - BACKING_STORE_ERROR, -}; - -static int cowGetBackingStore(virConnectPtr, char **, - const unsigned char *, size_t); -static int qcowXGetBackingStore(virConnectPtr, char **, - const unsigned char *, size_t); -static int vmdk4GetBackingStore(virConnectPtr, char **, - const unsigned char *, size_t); - -/* Either 'magic' or 'extension' *must* be provided */ -struct FileTypeInfo { - int type; /* One of the constants above */ - const char *magic; /* Optional string of file magic - * to check at head of file */ - const char *extension; /* Optional file extension to check */ - enum lv_endian endian; /* Endianness of file format */ - int versionOffset; /* Byte offset from start of file - * where we find version number, - * -1 to skip version test */ - int versionNumber; /* Version number to validate */ - 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_* */ - int qcowCryptOffset; /* Byte offset from start of file - * where to find encryption mode, - * -1 if encryption is not used */ - int (*getBackingStore)(virConnectPtr conn, char **res, - const unsigned char *buf, size_t buf_size); -}; -struct FileTypeInfo const fileTypeInfo[] = { - /* Bochs */ - /* XXX Untested - { VIR_STORAGE_FILE_BOCHS, "Bochs Virtual HD Image", NULL, - LV_LITTLE_ENDIAN, 64, 0x20000, - 32+16+16+4+4+4+4+4, 8, 1, -1, NULL },*/ - /* CLoop */ - /* XXX Untested - { VIR_STORAGE_VOL_CLOOP, "#!/bin/sh\n#V2.0 Format\nmodprobe cloop file=$0 && mount -r -t iso9660 /dev/cloop $1\n", NULL, - LV_LITTLE_ENDIAN, -1, 0, - -1, 0, 0, -1, NULL }, */ - /* Cow */ - { VIR_STORAGE_FILE_COW, "OOOM", NULL, - LV_BIG_ENDIAN, 4, 2, - 4+4+1024+4, 8, 1, -1, cowGetBackingStore }, - /* DMG */ - /* XXX QEMU says there's no magic for dmg, but we should check... */ - { VIR_STORAGE_FILE_DMG, NULL, ".dmg", - 0, -1, 0, - -1, 0, 0, -1, NULL }, - /* XXX there's probably some magic for iso we can validate too... */ - { VIR_STORAGE_FILE_ISO, NULL, ".iso", - 0, -1, 0, - -1, 0, 0, -1, NULL }, - /* Parallels */ - /* XXX Untested - { VIR_STORAGE_FILE_PARALLELS, "WithoutFreeSpace", NULL, - LV_LITTLE_ENDIAN, 16, 2, - 16+4+4+4+4, 4, 512, -1, NULL }, - */ - /* QCow */ - { VIR_STORAGE_FILE_QCOW, "QFI", NULL, - LV_BIG_ENDIAN, 4, 1, - 4+4+8+4+4, 8, 1, 4+4+8+4+4+8+1+1+2, qcowXGetBackingStore }, - /* QCow 2 */ - { VIR_STORAGE_FILE_QCOW2, "QFI", NULL, - LV_BIG_ENDIAN, 4, 2, - 4+4+8+4+4, 8, 1, 4+4+8+4+4+8, qcowXGetBackingStore }, - /* VMDK 3 */ - /* XXX Untested - { VIR_STORAGE_FILE_VMDK, "COWD", NULL, - LV_LITTLE_ENDIAN, 4, 1, - 4+4+4, 4, 512, -1, NULL }, - */ - /* VMDK 4 */ - { VIR_STORAGE_FILE_VMDK, "KDMV", NULL, - LV_LITTLE_ENDIAN, 4, 1, - 4+4+4, 8, 512, -1, vmdk4GetBackingStore }, - /* Connectix / VirtualPC */ - /* XXX Untested - { VIR_STORAGE_FILE_VPC, "conectix", NULL, - LV_BIG_ENDIAN, -1, 0, - -1, 0, 0, -1, NULL}, - */ -}; +#if WITH_STORAGE_FS #define VIR_FROM_THIS VIR_FROM_STORAGE -static int -cowGetBackingStore(virConnectPtr conn, - char **res, - const unsigned char *buf, - size_t buf_size) -{ -#define COW_FILENAME_MAXLEN 1024 - *res = NULL; - if (buf_size < 4+4+ COW_FILENAME_MAXLEN) - return BACKING_STORE_INVALID; - if (buf[4+4] == '\0') /* cow_header_v2.backing_file[0] */ - return BACKING_STORE_OK; - - *res = strndup ((const char*)buf + 4+4, COW_FILENAME_MAXLEN); - if (*res == NULL) { - virReportOOMError(conn); - return BACKING_STORE_ERROR; - } - return BACKING_STORE_OK; -} - -static int -qcowXGetBackingStore(virConnectPtr conn, - char **res, - const unsigned char *buf, - size_t buf_size) -{ - unsigned long long offset; - unsigned long size; - - *res = NULL; - if (buf_size < 4+4+8+4) - return BACKING_STORE_INVALID; - offset = (((unsigned long long)buf[4+4] << 56) - | ((unsigned long long)buf[4+4+1] << 48) - | ((unsigned long long)buf[4+4+2] << 40) - | ((unsigned long long)buf[4+4+3] << 32) - | ((unsigned long long)buf[4+4+4] << 24) - | ((unsigned long long)buf[4+4+5] << 16) - | ((unsigned long long)buf[4+4+6] << 8) - | buf[4+4+7]); /* QCowHeader.backing_file_offset */ - if (offset > buf_size) - return BACKING_STORE_INVALID; - size = ((buf[4+4+8] << 24) - | (buf[4+4+8+1] << 16) - | (buf[4+4+8+2] << 8) - | buf[4+4+8+3]); /* QCowHeader.backing_file_size */ - if (size == 0) - return BACKING_STORE_OK; - if (offset + size > buf_size || offset + size < offset) - return BACKING_STORE_INVALID; - if (size + 1 == 0) - return BACKING_STORE_INVALID; - if (VIR_ALLOC_N(*res, size + 1) < 0) { - virReportOOMError(conn); - return BACKING_STORE_ERROR; - } - memcpy(*res, buf + offset, size); - (*res)[size] = '\0'; - return BACKING_STORE_OK; -} - - -static int -vmdk4GetBackingStore(virConnectPtr conn, - char **res, - const unsigned char *buf, - size_t buf_size) -{ - static const char prefix[] = "parentFileNameHint=\""; - - char desc[20*512 + 1], *start, *end; - size_t len; - - *res = NULL; - - if (buf_size <= 0x200) - return BACKING_STORE_INVALID; - len = buf_size - 0x200; - if (len > sizeof(desc) - 1) - len = sizeof(desc) - 1; - memcpy(desc, buf + 0x200, len); - desc[len] = '\0'; - start = strstr(desc, prefix); - if (start == NULL) - return BACKING_STORE_OK; - start += strlen(prefix); - end = strchr(start, '"'); - if (end == NULL) - return BACKING_STORE_INVALID; - if (end == start) - return BACKING_STORE_OK; - *end = '\0'; - *res = strdup(start); - if (*res == NULL) { - virReportOOMError(conn); - return BACKING_STORE_ERROR; - } - return BACKING_STORE_OK; -} - -/** - * Return an absolute path corresponding to PATH, which is absolute or relative - * to the directory containing BASE_FILE, or NULL on error - */ -static char *absolutePathFromBaseFile(const char *base_file, const char *path) -{ - size_t base_size, path_size; - char *res, *p; - - if (*path == '/') - return strdup(path); - - base_size = strlen(base_file) + 1; - path_size = strlen(path) + 1; - if (VIR_ALLOC_N(res, base_size - 1 + path_size) < 0) - return NULL; - memcpy(res, base_file, base_size); - p = strrchr(res, '/'); - if (p != NULL) - p++; - else - p = res; - memcpy(p, path, path_size); - if (VIR_REALLOC_N(res, (p + path_size) - res) < 0) { - /* Ignore failure */ - } - return res; -} - - - -/** - * Probe the header of a file to determine what type of disk image - * it is, and info about its capacity if available. - */ -static int virStorageBackendProbeTarget(virConnectPtr conn, - virStorageVolTargetPtr target, - char **backingStore, - unsigned long long *allocation, - unsigned long long *capacity, - virStorageEncryptionPtr *encryption) { - int fd; - unsigned char head[20*512]; /* vmdk4GetBackingStore needs this much. */ - int len, i, ret; - - if (backingStore) - *backingStore = NULL; - if (encryption) - *encryption = NULL; - - if ((fd = open(target->path, O_RDONLY)) < 0) { - virReportSystemError(conn, errno, - _("cannot open volume '%s'"), - target->path); - return -1; - } - - if ((ret = virStorageFileGetInfoFromFD(conn, target->path, fd, - &target->perms, - allocation, capacity)) < 0) { - close(fd); - return ret; /* Take care to propagate ret, it is not always -1 */ - } - - if ((len = read(fd, head, sizeof(head))) < 0) { - virReportSystemError(conn, errno, - _("cannot read header '%s'"), - target->path); - close(fd); - return -1; - } - - close(fd); - - /* First check file magic */ - for (i = 0 ; i < ARRAY_CARDINALITY(fileTypeInfo) ; i++) { - int mlen; - bool encrypted_qcow = false; - - if (fileTypeInfo[i].magic == NULL) - continue; - - /* Validate magic data */ - mlen = strlen(fileTypeInfo[i].magic); - if (mlen > len) - continue; - if (memcmp(head, fileTypeInfo[i].magic, mlen) != 0) - continue; - - /* Validate version number info */ - if (fileTypeInfo[i].versionNumber != -1) { - int version; - - if (fileTypeInfo[i].endian == LV_LITTLE_ENDIAN) { - version = (head[fileTypeInfo[i].versionOffset+3] << 24) | - (head[fileTypeInfo[i].versionOffset+2] << 16) | - (head[fileTypeInfo[i].versionOffset+1] << 8) | - head[fileTypeInfo[i].versionOffset]; - } else { - version = (head[fileTypeInfo[i].versionOffset] << 24) | - (head[fileTypeInfo[i].versionOffset+1] << 16) | - (head[fileTypeInfo[i].versionOffset+2] << 8) | - head[fileTypeInfo[i].versionOffset+3]; - } - if (version != fileTypeInfo[i].versionNumber) - continue; - } - - /* Optionally extract capacity from file */ - if (fileTypeInfo[i].sizeOffset != -1 && capacity) { - if (fileTypeInfo[i].endian == LV_LITTLE_ENDIAN) { - *capacity = - ((unsigned long long)head[fileTypeInfo[i].sizeOffset+7] << 56) | - ((unsigned long long)head[fileTypeInfo[i].sizeOffset+6] << 48) | - ((unsigned long long)head[fileTypeInfo[i].sizeOffset+5] << 40) | - ((unsigned long long)head[fileTypeInfo[i].sizeOffset+4] << 32) | - ((unsigned long long)head[fileTypeInfo[i].sizeOffset+3] << 24) | - ((unsigned long long)head[fileTypeInfo[i].sizeOffset+2] << 16) | - ((unsigned long long)head[fileTypeInfo[i].sizeOffset+1] << 8) | - ((unsigned long long)head[fileTypeInfo[i].sizeOffset]); - } else { - *capacity = - ((unsigned long long)head[fileTypeInfo[i].sizeOffset] << 56) | - ((unsigned long long)head[fileTypeInfo[i].sizeOffset+1] << 48) | - ((unsigned long long)head[fileTypeInfo[i].sizeOffset+2] << 40) | - ((unsigned long long)head[fileTypeInfo[i].sizeOffset+3] << 32) | - ((unsigned long long)head[fileTypeInfo[i].sizeOffset+4] << 24) | - ((unsigned long long)head[fileTypeInfo[i].sizeOffset+5] << 16) | - ((unsigned long long)head[fileTypeInfo[i].sizeOffset+6] << 8) | - ((unsigned long long)head[fileTypeInfo[i].sizeOffset+7]); - } - /* Avoid unlikely, but theoretically possible overflow */ - if (*capacity > (ULLONG_MAX / fileTypeInfo[i].sizeMultiplier)) - continue; - *capacity *= fileTypeInfo[i].sizeMultiplier; - } - - if (fileTypeInfo[i].qcowCryptOffset != -1) { - int crypt_format; - - crypt_format = (head[fileTypeInfo[i].qcowCryptOffset] << 24) | - (head[fileTypeInfo[i].qcowCryptOffset+1] << 16) | - (head[fileTypeInfo[i].qcowCryptOffset+2] << 8) | - head[fileTypeInfo[i].qcowCryptOffset+3]; - encrypted_qcow = crypt_format != 0; - } - - /* Validation passed, we know the file format now */ - target->format = fileTypeInfo[i].type; - if (fileTypeInfo[i].getBackingStore != NULL && backingStore) { - char *base; - - switch (fileTypeInfo[i].getBackingStore(conn, &base, head, len)) { - case BACKING_STORE_OK: - break; - - case BACKING_STORE_INVALID: - continue; - - case BACKING_STORE_ERROR: - return -1; - } - if (base != NULL) { - *backingStore - = absolutePathFromBaseFile(target->path, base); - VIR_FREE(base); - if (*backingStore == NULL) { - virReportOOMError(conn); - return -1; - } - } - } - if (encryption != NULL && encrypted_qcow) { - virStorageEncryptionPtr enc; - - if (VIR_ALLOC(enc) < 0) { - virReportOOMError(conn); - if (backingStore) - VIR_FREE(*backingStore); - return -1; - } - enc->format = VIR_STORAGE_ENCRYPTION_FORMAT_QCOW; - *encryption = enc; - /* XXX ideally we'd fill in secret UUID here - * but we cannot guarentee 'conn' is non-NULL - * at this point in time :-( So we only fill - * in secrets when someone first queries a vol - */ - } - return 0; - } - - /* No magic, so check file extension */ - for (i = 0 ; i < ARRAY_CARDINALITY(fileTypeInfo) ; i++) { - if (fileTypeInfo[i].extension == NULL) - continue; - - if (!virFileHasSuffix(target->path, fileTypeInfo[i].extension)) - continue; - - target->format = fileTypeInfo[i].type; - return 0; - } - - /* All fails, so call it a raw file */ - target->format = VIR_STORAGE_FILE_RAW; - return 0; -} - -#if WITH_STORAGE_FS - #include <mntent.h> struct _virNetfsDiscoverState { @@ -901,12 +492,14 @@ virStorageBackendFileSystemRefresh(virConnectPtr conn, if ((vol->key = strdup(vol->target.path)) == NULL) goto no_memory; - if ((ret = virStorageBackendProbeTarget(conn, - &vol->target, - &backingStore, - &vol->allocation, - &vol->capacity, - &vol->target.encryption) < 0)) { + if ((ret = virStorageFileProbeHeader(conn, + vol->target.path, + &vol->target.format, + &backingStore, + &vol->target.encryption, + &vol->target.perms, + &vol->allocation, + &vol->capacity) < 0)) { if (ret == -1) goto cleanup; else { @@ -942,10 +535,12 @@ virStorageBackendFileSystemRefresh(virConnectPtr conn, } else { vol->backingStore.path = backingStore; - if ((ret = virStorageBackendProbeTarget(conn, - &vol->backingStore, - NULL, NULL, NULL, - NULL)) < 0) { + if ((ret = virStorageFileProbeHeader(conn, + vol->backingStore.path, + &vol->backingStore.format, + NULL, NULL, + &vol->backingStore.perms, + NULL, NULL)) < 0) { if (ret == -1) goto cleanup; else { diff --git a/src/util/storage_file.c b/src/util/storage_file.c index c1eec9b..70ffac4 100644 --- a/src/util/storage_file.c +++ b/src/util/storage_file.c @@ -51,6 +51,415 @@ VIR_ENUM_IMPL(virStorageFileFormat, #define DEV_BSIZE 512 #endif +enum lv_endian { + LV_LITTLE_ENDIAN = 1, /* 1234 */ + LV_BIG_ENDIAN /* 4321 */ +}; + +enum { + BACKING_STORE_OK, + BACKING_STORE_INVALID, + BACKING_STORE_ERROR, +}; + +/* Either 'magic' or 'extension' *must* be provided */ +struct FileTypeInfo { + int type; /* One of the constants above */ + const char *magic; /* Optional string of file magic + * to check at head of file */ + const char *extension; /* Optional file extension to check */ + enum lv_endian endian; /* Endianness of file format */ + int versionOffset; /* Byte offset from start of file + * where we find version number, + * -1 to skip version test */ + int versionNumber; /* Version number to validate */ + 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_* */ + int qcowCryptOffset; /* Byte offset from start of file + * where to find encryption mode, + * -1 if encryption is not used */ + int (*getBackingStore)(virConnectPtr conn, char **res, + const unsigned char *buf, size_t buf_size); +}; + +static int cowGetBackingStore(virConnectPtr, char **, + const unsigned char *, size_t); +static int qcowXGetBackingStore(virConnectPtr, char **, + const unsigned char *, size_t); +static int vmdk4GetBackingStore(virConnectPtr, char **, + const unsigned char *, size_t); + +static struct FileTypeInfo const fileTypeInfo[] = { + /* Bochs */ + /* XXX Untested + { VIR_STORAGE_FILE_BOCHS, "Bochs Virtual HD Image", NULL, + LV_LITTLE_ENDIAN, 64, 0x20000, + 32+16+16+4+4+4+4+4, 8, 1, -1, NULL },*/ + /* CLoop */ + /* XXX Untested + { VIR_STORAGE_VOL_CLOOP, "#!/bin/sh\n#V2.0 Format\nmodprobe cloop file=$0 && mount -r -t iso9660 /dev/cloop $1\n", NULL, + LV_LITTLE_ENDIAN, -1, 0, + -1, 0, 0, -1, NULL }, */ + /* Cow */ + { VIR_STORAGE_FILE_COW, "OOOM", NULL, + LV_BIG_ENDIAN, 4, 2, + 4+4+1024+4, 8, 1, -1, cowGetBackingStore }, + /* DMG */ + /* XXX QEMU says there's no magic for dmg, but we should check... */ + { VIR_STORAGE_FILE_DMG, NULL, ".dmg", + 0, -1, 0, + -1, 0, 0, -1, NULL }, + /* XXX there's probably some magic for iso we can validate too... */ + { VIR_STORAGE_FILE_ISO, NULL, ".iso", + 0, -1, 0, + -1, 0, 0, -1, NULL }, + /* Parallels */ + /* XXX Untested + { VIR_STORAGE_FILE_PARALLELS, "WithoutFreeSpace", NULL, + LV_LITTLE_ENDIAN, 16, 2, + 16+4+4+4+4, 4, 512, -1, NULL }, + */ + /* QCow */ + { VIR_STORAGE_FILE_QCOW, "QFI", NULL, + LV_BIG_ENDIAN, 4, 1, + 4+4+8+4+4, 8, 1, 4+4+8+4+4+8+1+1+2, qcowXGetBackingStore }, + /* QCow 2 */ + { VIR_STORAGE_FILE_QCOW2, "QFI", NULL, + LV_BIG_ENDIAN, 4, 2, + 4+4+8+4+4, 8, 1, 4+4+8+4+4+8, qcowXGetBackingStore }, + /* VMDK 3 */ + /* XXX Untested + { VIR_STORAGE_FILE_VMDK, "COWD", NULL, + LV_LITTLE_ENDIAN, 4, 1, + 4+4+4, 4, 512, -1, NULL }, + */ + /* VMDK 4 */ + { VIR_STORAGE_FILE_VMDK, "KDMV", NULL, + LV_LITTLE_ENDIAN, 4, 1, + 4+4+4, 8, 512, -1, vmdk4GetBackingStore }, + /* Connectix / VirtualPC */ + /* XXX Untested + { VIR_STORAGE_FILE_VPC, "conectix", NULL, + LV_BIG_ENDIAN, -1, 0, + -1, 0, 0, -1, NULL}, + */ +}; + +static int +cowGetBackingStore(virConnectPtr conn, + char **res, + const unsigned char *buf, + size_t buf_size) +{ +#define COW_FILENAME_MAXLEN 1024 + *res = NULL; + if (buf_size < 4+4+ COW_FILENAME_MAXLEN) + return BACKING_STORE_INVALID; + if (buf[4+4] == '\0') /* cow_header_v2.backing_file[0] */ + return BACKING_STORE_OK; + + *res = strndup ((const char*)buf + 4+4, COW_FILENAME_MAXLEN); + if (*res == NULL) { + virReportOOMError(conn); + return BACKING_STORE_ERROR; + } + return BACKING_STORE_OK; +} + +static int +qcowXGetBackingStore(virConnectPtr conn, + char **res, + const unsigned char *buf, + size_t buf_size) +{ + unsigned long long offset; + unsigned long size; + + *res = NULL; + if (buf_size < 4+4+8+4) + return BACKING_STORE_INVALID; + offset = (((unsigned long long)buf[4+4] << 56) + | ((unsigned long long)buf[4+4+1] << 48) + | ((unsigned long long)buf[4+4+2] << 40) + | ((unsigned long long)buf[4+4+3] << 32) + | ((unsigned long long)buf[4+4+4] << 24) + | ((unsigned long long)buf[4+4+5] << 16) + | ((unsigned long long)buf[4+4+6] << 8) + | buf[4+4+7]); /* QCowHeader.backing_file_offset */ + if (offset > buf_size) + return BACKING_STORE_INVALID; + size = ((buf[4+4+8] << 24) + | (buf[4+4+8+1] << 16) + | (buf[4+4+8+2] << 8) + | buf[4+4+8+3]); /* QCowHeader.backing_file_size */ + if (size == 0) + return BACKING_STORE_OK; + if (offset + size > buf_size || offset + size < offset) + return BACKING_STORE_INVALID; + if (size + 1 == 0) + return BACKING_STORE_INVALID; + if (VIR_ALLOC_N(*res, size + 1) < 0) { + virReportOOMError(conn); + return BACKING_STORE_ERROR; + } + memcpy(*res, buf + offset, size); + (*res)[size] = '\0'; + return BACKING_STORE_OK; +} + +static int +vmdk4GetBackingStore(virConnectPtr conn, + char **res, + const unsigned char *buf, + size_t buf_size) +{ + static const char prefix[] = "parentFileNameHint=\""; + + char desc[20*512 + 1], *start, *end; + size_t len; + + *res = NULL; + + if (buf_size <= 0x200) + return BACKING_STORE_INVALID; + len = buf_size - 0x200; + if (len > sizeof(desc) - 1) + len = sizeof(desc) - 1; + memcpy(desc, buf + 0x200, len); + desc[len] = '\0'; + start = strstr(desc, prefix); + if (start == NULL) + return BACKING_STORE_OK; + start += strlen(prefix); + end = strchr(start, '"'); + if (end == NULL) + return BACKING_STORE_INVALID; + if (end == start) + return BACKING_STORE_OK; + *end = '\0'; + *res = strdup(start); + if (*res == NULL) { + virReportOOMError(conn); + return BACKING_STORE_ERROR; + } + return BACKING_STORE_OK; +} + +/** + * Return an absolute path corresponding to PATH, which is absolute or relative + * to the directory containing BASE_FILE, or NULL on error + */ +static char * +absolutePathFromBaseFile(const char *base_file, const char *path) +{ + size_t base_size, path_size; + char *res, *p; + + if (*path == '/') + return strdup(path); + + base_size = strlen(base_file) + 1; + path_size = strlen(path) + 1; + if (VIR_ALLOC_N(res, base_size - 1 + path_size) < 0) + return NULL; + memcpy(res, base_file, base_size); + p = strrchr(res, '/'); + if (p != NULL) + p++; + else + p = res; + memcpy(p, path, path_size); + if (VIR_REALLOC_N(res, (p + path_size) - res) < 0) { + /* Ignore failure */ + } + return res; +} + +/** + * Probe the header of a file to determine what type of disk image + * it is, and info about its capacity if available. + */ +int +virStorageFileProbeHeader(virConnectPtr conn, + const char *path, + int *format, + char **backingStore, + virStorageEncryptionPtr *encryption, + virStorageFilePermsPtr perms, + unsigned long long *allocation, + unsigned long long *capacity) +{ + int fd; + unsigned char head[20*512]; /* vmdk4GetBackingStore needs this much. */ + int len, i, ret; + + if (format) /* If all else fails, call it a raw file */ + *format = VIR_STORAGE_FILE_RAW; + if (backingStore) + *backingStore = NULL; + if (encryption) + *encryption = NULL; + + if ((fd = open(path, O_RDONLY)) < 0) { + virReportSystemError(conn, errno, + _("cannot open volume '%s'"), path); + return -1; + } + + if ((ret = virStorageFileGetInfoFromFD(conn, path, fd, perms, + allocation, capacity)) < 0) { + close(fd); + return ret; /* Take care to propagate ret, it is not always -1 */ + } + + if ((len = read(fd, head, sizeof(head))) < 0) { + virReportSystemError(conn, errno, + _("cannot read header '%s'"), path); + close(fd); + return -1; + } + + close(fd); + + /* First check file magic */ + for (i = 0 ; i < ARRAY_CARDINALITY(fileTypeInfo) ; i++) { + int mlen; + bool encrypted_qcow = false; + + if (fileTypeInfo[i].magic == NULL) + continue; + + /* Validate magic data */ + mlen = strlen(fileTypeInfo[i].magic); + if (mlen > len) + continue; + if (memcmp(head, fileTypeInfo[i].magic, mlen) != 0) + continue; + + /* Validate version number info */ + if (fileTypeInfo[i].versionNumber != -1) { + int version; + + if (fileTypeInfo[i].endian == LV_LITTLE_ENDIAN) { + version = (head[fileTypeInfo[i].versionOffset+3] << 24) | + (head[fileTypeInfo[i].versionOffset+2] << 16) | + (head[fileTypeInfo[i].versionOffset+1] << 8) | + head[fileTypeInfo[i].versionOffset]; + } else { + version = (head[fileTypeInfo[i].versionOffset] << 24) | + (head[fileTypeInfo[i].versionOffset+1] << 16) | + (head[fileTypeInfo[i].versionOffset+2] << 8) | + head[fileTypeInfo[i].versionOffset+3]; + } + if (version != fileTypeInfo[i].versionNumber) + continue; + } + + /* Optionally extract capacity from file */ + if (fileTypeInfo[i].sizeOffset != -1 && capacity) { + if (fileTypeInfo[i].endian == LV_LITTLE_ENDIAN) { + *capacity = + ((unsigned long long)head[fileTypeInfo[i].sizeOffset+7] << 56) | + ((unsigned long long)head[fileTypeInfo[i].sizeOffset+6] << 48) | + ((unsigned long long)head[fileTypeInfo[i].sizeOffset+5] << 40) | + ((unsigned long long)head[fileTypeInfo[i].sizeOffset+4] << 32) | + ((unsigned long long)head[fileTypeInfo[i].sizeOffset+3] << 24) | + ((unsigned long long)head[fileTypeInfo[i].sizeOffset+2] << 16) | + ((unsigned long long)head[fileTypeInfo[i].sizeOffset+1] << 8) | + ((unsigned long long)head[fileTypeInfo[i].sizeOffset]); + } else { + *capacity = + ((unsigned long long)head[fileTypeInfo[i].sizeOffset] << 56) | + ((unsigned long long)head[fileTypeInfo[i].sizeOffset+1] << 48) | + ((unsigned long long)head[fileTypeInfo[i].sizeOffset+2] << 40) | + ((unsigned long long)head[fileTypeInfo[i].sizeOffset+3] << 32) | + ((unsigned long long)head[fileTypeInfo[i].sizeOffset+4] << 24) | + ((unsigned long long)head[fileTypeInfo[i].sizeOffset+5] << 16) | + ((unsigned long long)head[fileTypeInfo[i].sizeOffset+6] << 8) | + ((unsigned long long)head[fileTypeInfo[i].sizeOffset+7]); + } + /* Avoid unlikely, but theoretically possible overflow */ + if (*capacity > (ULLONG_MAX / fileTypeInfo[i].sizeMultiplier)) + continue; + *capacity *= fileTypeInfo[i].sizeMultiplier; + } + + if (fileTypeInfo[i].qcowCryptOffset != -1) { + int crypt_format; + + crypt_format = (head[fileTypeInfo[i].qcowCryptOffset] << 24) | + (head[fileTypeInfo[i].qcowCryptOffset+1] << 16) | + (head[fileTypeInfo[i].qcowCryptOffset+2] << 8) | + head[fileTypeInfo[i].qcowCryptOffset+3]; + encrypted_qcow = crypt_format != 0; + } + + /* Validation passed, we know the file format now */ + if (format) + *format = fileTypeInfo[i].type; + if (fileTypeInfo[i].getBackingStore != NULL && backingStore) { + char *base; + + switch (fileTypeInfo[i].getBackingStore(conn, &base, head, len)) { + case BACKING_STORE_OK: + break; + + case BACKING_STORE_INVALID: + continue; + + case BACKING_STORE_ERROR: + return -1; + } + if (base != NULL) { + *backingStore = absolutePathFromBaseFile(path, base); + VIR_FREE(base); + if (*backingStore == NULL) { + virReportOOMError(conn); + return -1; + } + } + } + if (encryption != NULL && encrypted_qcow) { + if (VIR_ALLOC(*encryption) < 0) { + virReportOOMError(conn); + if (backingStore) + VIR_FREE(*backingStore); + return -1; + } + (*encryption)->format = VIR_STORAGE_ENCRYPTION_FORMAT_QCOW; + /* XXX ideally we'd fill in secret UUID here + * but we cannot guarentee 'conn' is non-NULL + * at this point in time :-( So we only fill + * in secrets when someone first queries a vol + */ + } + return 0; + } + + if (!format) + return 0; + + /* No magic, so check file extension */ + for (i = 0 ; i < ARRAY_CARDINALITY(fileTypeInfo) ; i++) { + if (fileTypeInfo[i].extension == NULL) + continue; + + if (!virFileHasSuffix(path, fileTypeInfo[i].extension)) + continue; + + *format = fileTypeInfo[i].type; + return 0; + } + + return 0; +} + /* * virStorageFileGetInfoFromFD: * @conn: connection to report errors on diff --git a/src/util/storage_file.h b/src/util/storage_file.h index efacc65..908ca3e 100644 --- a/src/util/storage_file.h +++ b/src/util/storage_file.h @@ -25,6 +25,7 @@ #define __VIR_STORAGE_FILE_H__ #include "util.h" +#include "storage_encryption.h" enum virStorageFileFormat { VIR_STORAGE_FILE_RAW = 0, @@ -52,6 +53,15 @@ struct _virStorageFilePerms { char *label; }; +int virStorageFileProbeHeader(virConnectPtr conn, + const char *path, + int *format, + char **backingStore, + virStorageEncryptionPtr *encryption, + virStorageFilePermsPtr perms, + unsigned long long *allocation, + unsigned long long *capacity); + int virStorageFileGetInfo(virConnectPtr conn, const char *path, virStorageFilePermsPtr perms, -- 1.6.2.5 -- Libvir-list mailing list Libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list