/* * virstoragefile.c: file utility functions for FS storage backend * * Copyright (C) 2007-2013 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 * . * * Author: Daniel P. Berrange */ #include #include "virstoragefile.h" #include #include #include #include #ifdef __linux__ # if HAVE_LINUX_MAGIC_H # include # endif # include #endif #include "dirname.h" #include "viralloc.h" #include "virerror.h" #include "virlog.h" #include "virfile.h" #include "c-ctype.h" #include "vircommand.h" #include "virhash.h" #define VIR_FROM_THIS VIR_FROM_STORAGE VIR_ENUM_IMPL(virStorageFileFormat, VIR_STORAGE_FILE_LAST, "none", "raw", "dir", "bochs", "cloop", "cow", "dmg", "iso", "qcow", "qcow2", "qed", "vmdk", "vpc", "fat", "vhd", "vdi") 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 magicOffset; /* Byte offset of the magic */ 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)(char **res, int *format, const unsigned char *buf, size_t buf_size); }; static int cowGetBackingStore(char **, int *, const unsigned char *, size_t); static int qcow1GetBackingStore(char **, int *, const unsigned char *, size_t); static int qcow2GetBackingStore(char **, int *, const unsigned char *, size_t); static int vmdk4GetBackingStore(char **, int *, const unsigned char *, size_t); static int qedGetBackingStore(char **, int *, const unsigned 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) #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 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 /* VMDK needs at least 20*512 B to find backing store, * ISO has 5 Byte magic on offset 32769, * other formats need less */ #define STORAGE_MAX_HEAD 32769+5 static struct FileTypeInfo const fileTypeInfo[] = { [VIR_STORAGE_FILE_NONE] = { 0, NULL, NULL, LV_LITTLE_ENDIAN, -1, 0, 0, 0, 0, 0, NULL }, [VIR_STORAGE_FILE_RAW] = { 0, NULL, NULL, LV_LITTLE_ENDIAN, -1, 0, 0, 0, 0, 0, NULL }, [VIR_STORAGE_FILE_DIR] = { 0, NULL, NULL, LV_LITTLE_ENDIAN, -1, 0, 0, 0, 0, 0, NULL }, [VIR_STORAGE_FILE_BOCHS] = { /*"Bochs Virtual HD Image", */ /* Untested */ 0, NULL, NULL, LV_LITTLE_ENDIAN, 64, 0x20000, 32+16+16+4+4+4+4+4, 8, 1, -1, NULL }, [VIR_STORAGE_FILE_CLOOP] = { /* #!/bin/sh #V2.0 Format modprobe cloop file=$0 && mount -r -t iso9660 /dev/cloop $1 */ /* Untested */ 0, NULL, NULL, LV_LITTLE_ENDIAN, -1, 0, -1, 0, 0, -1, NULL }, [VIR_STORAGE_FILE_COW] = { 0, "OOOM", NULL, LV_BIG_ENDIAN, 4, 2, 4+4+1024+4, 8, 1, -1, cowGetBackingStore }, [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, ".dmg", 0, -1, 0, -1, 0, 0, -1, NULL }, [VIR_STORAGE_FILE_ISO] = { 32769, "CD001", ".iso", LV_LITTLE_ENDIAN, -2, 0, -1, 0, 0, -1, NULL }, [VIR_STORAGE_FILE_QCOW] = { 0, "QFI", NULL, LV_BIG_ENDIAN, 4, 1, QCOWX_HDR_IMAGE_SIZE, 8, 1, QCOW1_HDR_CRYPT, qcow1GetBackingStore, }, [VIR_STORAGE_FILE_QCOW2] = { 0, "QFI", NULL, LV_BIG_ENDIAN, 4, 2, QCOWX_HDR_IMAGE_SIZE, 8, 1, QCOW2_HDR_CRYPT, qcow2GetBackingStore, }, [VIR_STORAGE_FILE_QED] = { /* http://wiki.qemu.org/Features/QED */ 0, "QED", NULL, LV_LITTLE_ENDIAN, -2, -1, QED_HDR_IMAGE_SIZE, 8, 1, -1, qedGetBackingStore, }, [VIR_STORAGE_FILE_VMDK] = { 0, "KDMV", NULL, LV_LITTLE_ENDIAN, 4, 1, 4+4+4, 8, 512, -1, vmdk4GetBackingStore }, [VIR_STORAGE_FILE_VPC] = { 0, "conectix", NULL, LV_BIG_ENDIAN, 12, 0x10000, 8 + 4 + 4 + 8 + 4 + 4 + 2 + 2 + 4, 8, 1, -1, NULL }, /* TODO: add getBackingStore function */ [VIR_STORAGE_FILE_VDI] = { 64, "\x7f\x10\xda\xbe", ".vdi", LV_LITTLE_ENDIAN, 68, 0x00010001, 68 + 5 * 4 + 256 + 7 * 4, 8, 1, -1, NULL}, /* Not direct file formats, but used for various drivers */ [VIR_STORAGE_FILE_FAT] = { 0, NULL, NULL, LV_LITTLE_ENDIAN, -1, 0, 0, 0, 0, 0, NULL }, [VIR_STORAGE_FILE_VHD] = { 0, NULL, NULL, LV_LITTLE_ENDIAN, -1, 0, 0, 0, 0, 0, NULL }, }; verify(ARRAY_CARDINALITY(fileTypeInfo) == VIR_STORAGE_FILE_LAST); static int cowGetBackingStore(char **res, int *format, const unsigned 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 = strndup((const char*)buf + 4+4, COW_FILENAME_MAXLEN); if (*res == NULL) { virReportOOMError(); return BACKING_STORE_ERROR; } return BACKING_STORE_OK; } static int qcow2GetBackingStoreFormat(int *format, const unsigned char *buf, size_t buf_size, size_t extension_start, size_t extension_end) { size_t offset = extension_start; /* * 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. */ while (offset < (buf_size-8) && offset < (extension_end-8)) { unsigned int magic = (buf[offset] << 24) + (buf[offset+1] << 16) + (buf[offset+2] << 8) + (buf[offset+3]); unsigned int len = (buf[offset+4] << 24) + (buf[offset+5] << 16) + (buf[offset+6] << 8) + (buf[offset+7]); offset += 8; if ((offset + len) < offset) break; if ((offset + len) > buf_size) break; switch (magic) { case QCOW2_HDR_EXTENSION_END: goto done; case QCOW2_HDR_EXTENSION_BACKING_FORMAT: if (buf[offset+len] != '\0') break; *format = virStorageFileFormatTypeFromString( ((const char *)buf)+offset); if (*format <= VIR_STORAGE_FILE_NONE) return -1; } offset += len; } done: return 0; } static int qcowXGetBackingStore(char **res, int *format, const unsigned char *buf, size_t buf_size, bool isQCow2) { unsigned long long offset; unsigned int size; *res = NULL; if (format) *format = VIR_STORAGE_FILE_AUTO; if (buf_size < QCOWX_HDR_BACKING_FILE_OFFSET+8+4) return BACKING_STORE_INVALID; offset = (((unsigned long long)buf[QCOWX_HDR_BACKING_FILE_OFFSET] << 56) | ((unsigned long long)buf[QCOWX_HDR_BACKING_FILE_OFFSET+1] << 48) | ((unsigned long long)buf[QCOWX_HDR_BACKING_FILE_OFFSET+2] << 40) | ((unsigned long long)buf[QCOWX_HDR_BACKING_FILE_OFFSET+3] << 32) | ((unsigned long long)buf[QCOWX_HDR_BACKING_FILE_OFFSET+4] << 24) | ((unsigned long long)buf[QCOWX_HDR_BACKING_FILE_OFFSET+5] << 16) | ((unsigned long long)buf[QCOWX_HDR_BACKING_FILE_OFFSET+6] << 8) | buf[QCOWX_HDR_BACKING_FILE_OFFSET+7]); /* QCowHeader.backing_file_offset */ if (offset > buf_size) return BACKING_STORE_INVALID; size = ((buf[QCOWX_HDR_BACKING_FILE_SIZE] << 24) | (buf[QCOWX_HDR_BACKING_FILE_SIZE+1] << 16) | (buf[QCOWX_HDR_BACKING_FILE_SIZE+2] << 8) | buf[QCOWX_HDR_BACKING_FILE_SIZE+3]); /* QCowHeader.backing_file_size */ if (size == 0) { if (format) *format = VIR_STORAGE_FILE_NONE; 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(); return BACKING_STORE_ERROR; } memcpy(*res, buf + offset, size); (*res)[size] = '\0'; /* * 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) */ if (isQCow2 && format && qcow2GetBackingStoreFormat(format, buf, buf_size, QCOW2_HDR_TOTAL_SIZE, offset) < 0) return BACKING_STORE_INVALID; return BACKING_STORE_OK; } static int qcow1GetBackingStore(char **res, int *format, const unsigned char *buf, size_t buf_size) { int ret; /* QCow1 doesn't have the extensions capability * used to store backing format */ *format = VIR_STORAGE_FILE_AUTO; ret = qcowXGetBackingStore(res, NULL, buf, buf_size, false); if (ret == 0 && *buf == '\0') *format = VIR_STORAGE_FILE_NONE; return ret; } static int qcow2GetBackingStore(char **res, int *format, const unsigned char *buf, size_t buf_size) { return qcowXGetBackingStore(res, format, buf, buf_size, true); } static int vmdk4GetBackingStore(char **res, int *format, const unsigned char *buf, size_t buf_size) { static const char prefix[] = "parentFileNameHint=\""; char *desc, *start, *end; size_t len; int ret = BACKING_STORE_ERROR; if (VIR_ALLOC_N(desc, STORAGE_MAX_HEAD + 1) < 0) { virReportOOMError(); goto cleanup; } *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) { ret = BACKING_STORE_INVALID; goto cleanup; } len = buf_size - 0x200; if (len > STORAGE_MAX_HEAD) len = STORAGE_MAX_HEAD; memcpy(desc, buf + 0x200, len); desc[len] = '\0'; start = strstr(desc, prefix); if (start == NULL) { *format = VIR_STORAGE_FILE_NONE; ret = BACKING_STORE_OK; goto cleanup; } start += strlen(prefix); end = strchr(start, '"'); if (end == NULL) { ret = BACKING_STORE_INVALID; goto cleanup; } if (end == start) { *format = VIR_STORAGE_FILE_NONE; ret = BACKING_STORE_OK; goto cleanup; } *end = '\0'; *res = strdup(start); if (*res == NULL) { virReportOOMError(); goto cleanup; } ret = BACKING_STORE_OK; cleanup: VIR_FREE(desc); return ret; } static unsigned long qedGetHeaderUL(const unsigned char *loc) { return (((unsigned long)loc[3] << 24) | ((unsigned long)loc[2] << 16) | ((unsigned long)loc[1] << 8) | ((unsigned long)loc[0] << 0)); } static unsigned long long qedGetHeaderULL(const unsigned char *loc) { return (((unsigned long long)loc[7] << 56) | ((unsigned long long)loc[6] << 48) | ((unsigned long long)loc[5] << 40) | ((unsigned long long)loc[4] << 32) | ((unsigned long long)loc[3] << 24) | ((unsigned long long)loc[2] << 16) | ((unsigned long long)loc[1] << 8) | ((unsigned long long)loc[0] << 0)); } static int qedGetBackingStore(char **res, int *format, const unsigned 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 = qedGetHeaderULL(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 = qedGetHeaderUL(buf + QED_HDR_BACKING_FILE_OFFSET); if (offset > buf_size) return BACKING_STORE_INVALID; size = qedGetHeaderUL(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) { virReportOOMError(); 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; } /** * 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) { char *res = NULL; char *tmp = NULL; size_t d_len = dir_len(base_file); /* If path is already absolute, or if dirname(base_file) is ".", just return a copy of path. */ if (*path == '/' || d_len == 0) { if (!(res = canonicalize_file_name(path))) virReportSystemError(errno, _("Can't canonicalize path '%s'"), path); goto cleanup; } /* Ensure that the following cast-to-int is valid. */ if (d_len > INT_MAX) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Directory name too long: '%s'"), base_file); goto cleanup; } if (virAsprintf(&tmp, "%.*s/%s", (int) d_len, base_file, path) < 0) { virReportOOMError(); goto cleanup; } if (!(res = canonicalize_file_name(tmp))) virReportSystemError(errno, _("Can't canonicalize path '%s'"), path); cleanup: VIR_FREE(tmp); return res; } static bool virStorageFileMatchesMagic(int format, unsigned char *buf, size_t buflen) { int mlen; int magicOffset = fileTypeInfo[format].magicOffset; const char *magic = fileTypeInfo[format].magic; 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 virStorageFileMatchesExtension(int format, const char *path) { if (fileTypeInfo[format].extension == NULL) return false; if (virFileHasSuffix(path, fileTypeInfo[format].extension)) return true; return false; } static bool virStorageFileMatchesVersion(int format, unsigned char *buf, size_t buflen) { int version; /* Validate version number info */ if (fileTypeInfo[format].versionOffset == -1) return false; /* -2 == non-versioned file format, so trivially match */ if (fileTypeInfo[format].versionOffset == -2) return true; if ((fileTypeInfo[format].versionOffset + 4) > buflen) return false; if (fileTypeInfo[format].endian == LV_LITTLE_ENDIAN) { version = (buf[fileTypeInfo[format].versionOffset+3] << 24) | (buf[fileTypeInfo[format].versionOffset+2] << 16) | (buf[fileTypeInfo[format].versionOffset+1] << 8) | (buf[fileTypeInfo[format].versionOffset]); } else { version = (buf[fileTypeInfo[format].versionOffset] << 24) | (buf[fileTypeInfo[format].versionOffset+1] << 16) | (buf[fileTypeInfo[format].versionOffset+2] << 8) | (buf[fileTypeInfo[format].versionOffset+3]); } VIR_DEBUG("Compare detected version %d vs expected version %d", version, fileTypeInfo[format].versionNumber); if (version != fileTypeInfo[format].versionNumber) return false; return true; } static bool virBackingStoreIsFile(const char *backing) { /* Backing store is a network block device or Rados block device */ if (STRPREFIX(backing, "nbd:") || STRPREFIX(backing, "rbd:")) return false; return true; } static int virStorageFileGetMetadataFromBuf(int format, const char *path, unsigned char *buf, size_t buflen, virStorageFileMetadata *meta) { VIR_DEBUG("path=%s format=%d", path, format); /* XXX we should consider moving virStorageBackendUpdateVolInfo * code into this method, for non-magic files */ if (format <= VIR_STORAGE_FILE_NONE || format >= VIR_STORAGE_FILE_LAST || !fileTypeInfo[format].magic) { return 0; } /* Optionally extract capacity from file */ if (fileTypeInfo[format].sizeOffset != -1) { if ((fileTypeInfo[format].sizeOffset + 8) > buflen) return 1; if (fileTypeInfo[format].endian == LV_LITTLE_ENDIAN) { meta->capacity = ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+7] << 56) | ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+6] << 48) | ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+5] << 40) | ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+4] << 32) | ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+3] << 24) | ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+2] << 16) | ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+1] << 8) | ((unsigned long long)buf[fileTypeInfo[format].sizeOffset]); } else { meta->capacity = ((unsigned long long)buf[fileTypeInfo[format].sizeOffset] << 56) | ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+1] << 48) | ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+2] << 40) | ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+3] << 32) | ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+4] << 24) | ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+5] << 16) | ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+6] << 8) | ((unsigned long long)buf[fileTypeInfo[format].sizeOffset+7]); } /* Avoid unlikely, but theoretically possible overflow */ if (meta->capacity > (ULLONG_MAX / fileTypeInfo[format].sizeMultiplier)) return 1; meta->capacity *= fileTypeInfo[format].sizeMultiplier; } if (fileTypeInfo[format].qcowCryptOffset != -1) { int crypt_format; crypt_format = (buf[fileTypeInfo[format].qcowCryptOffset] << 24) | (buf[fileTypeInfo[format].qcowCryptOffset+1] << 16) | (buf[fileTypeInfo[format].qcowCryptOffset+2] << 8) | (buf[fileTypeInfo[format].qcowCryptOffset+3]); meta->encrypted = crypt_format != 0; } if (fileTypeInfo[format].getBackingStore != NULL) { char *backing; int backingFormat; int ret = fileTypeInfo[format].getBackingStore(&backing, &backingFormat, buf, buflen); if (ret == BACKING_STORE_INVALID) return 1; if (ret == BACKING_STORE_ERROR) return -1; meta->backingStoreIsFile = false; if (backing != NULL) { meta->backingStore = strdup(backing); if (meta->backingStore == NULL) { virReportOOMError(); VIR_FREE(backing); return -1; } if (virBackingStoreIsFile(backing)) { meta->backingStoreIsFile = true; meta->backingStoreRaw = meta->backingStore; meta->backingStore = absolutePathFromBaseFile(path, backing); if (meta->backingStore == NULL) { /* the backing file is (currently) unavailable, treat this * file as standalone: * backingStoreRaw is kept to mark broken image chains */ meta->backingStoreIsFile = false; backingFormat = VIR_STORAGE_FILE_NONE; VIR_WARN("Backing file '%s' of image '%s' is missing.", meta->backingStoreRaw, path); } } VIR_FREE(backing); meta->backingStoreFormat = backingFormat; } else { meta->backingStore = NULL; meta->backingStoreFormat = VIR_STORAGE_FILE_NONE; } } return 0; } static int virStorageFileProbeFormatFromBuf(const char *path, unsigned char *buf, size_t buflen) { int format = VIR_STORAGE_FILE_RAW; int i; int possibleFormat = VIR_STORAGE_FILE_RAW; VIR_DEBUG("path=%s", path); /* First check file magic */ for (i = 0 ; i < VIR_STORAGE_FILE_LAST ; i++) { if (virStorageFileMatchesMagic(i, buf, buflen)) { if (!virStorageFileMatchesVersion(i, 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)); /* No magic, so check file extension */ for (i = 0 ; i < VIR_STORAGE_FILE_LAST ; i++) { if (virStorageFileMatchesExtension(i, path)) { format = i; goto cleanup; } } cleanup: VIR_DEBUG("format=%d", format); return format; } /** * virStorageFileProbeFormatFromFD: * * Probe for the format of 'fd' (which is an open file descriptor * pointing to '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 file into any other non-raw * format at will. * * Best option: Don't use this function */ int virStorageFileProbeFormatFromFD(const char *path, int fd) { unsigned char *head; ssize_t len = STORAGE_MAX_HEAD; int ret = -1; struct stat sb; 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 (VIR_ALLOC_N(head, len) < 0) { virReportOOMError(); return -1; } if (lseek(fd, 0, SEEK_SET) == (off_t)-1) { virReportSystemError(errno, _("cannot set to start of '%s'"), path); goto cleanup; } if ((len = read(fd, head, len)) < 0) { virReportSystemError(errno, _("cannot read header '%s'"), path); goto cleanup; } ret = virStorageFileProbeFormatFromBuf(path, head, len); cleanup: VIR_FREE(head); return ret; } /** * 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) { int fd, ret; if ((fd = virFileOpenAs(path, O_RDONLY, 0, uid, gid, 0)) < 0) { virReportSystemError(errno, _("cannot open file '%s'"), path); return -1; } ret = virStorageFileProbeFormatFromFD(path, fd); VIR_FORCE_CLOSE(fd); 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. * * If the returned meta.backingStoreFormat 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 virStorageFileFreeMetadata. */ virStorageFileMetadataPtr virStorageFileGetMetadataFromFD(const char *path, int fd, int format) { virStorageFileMetadata *meta = NULL; unsigned char *head = NULL; ssize_t len = STORAGE_MAX_HEAD; virStorageFileMetadata *ret = NULL; struct stat sb; if (VIR_ALLOC(meta) < 0) { virReportOOMError(); return NULL; } if (fstat(fd, &sb) < 0) { virReportSystemError(errno, _("cannot stat file '%s'"), path); goto cleanup; } /* No header to probe for directories, but also no backing file */ if (S_ISDIR(sb.st_mode)) return meta; if (lseek(fd, 0, SEEK_SET) == (off_t)-1) { virReportSystemError(errno, _("cannot seek to start of '%s'"), path); goto cleanup; } if (VIR_ALLOC_N(head, len) < 0) { virReportOOMError(); goto cleanup; } if ((len = read(fd, head, len)) < 0) { virReportSystemError(errno, _("cannot read header '%s'"), path); goto cleanup; } if (format == VIR_STORAGE_FILE_AUTO) format = virStorageFileProbeFormatFromBuf(path, head, len); if (format <= VIR_STORAGE_FILE_NONE || format >= VIR_STORAGE_FILE_LAST) { virReportSystemError(EINVAL, _("unknown storage file format %d"), format); goto cleanup; } if (virStorageFileGetMetadataFromBuf(format, path, head, len, meta) < 0) goto cleanup; ret = meta; meta = NULL; cleanup: virStorageFileFreeMetadata(meta); VIR_FREE(head); return ret; } /* Recursive workhorse for virStorageFileGetMetadata. */ static virStorageFileMetadataPtr virStorageFileGetMetadataRecurse(const char *path, int format, uid_t uid, gid_t gid, bool allow_probe, virHashTablePtr cycle) { int fd; VIR_DEBUG("path=%s format=%d uid=%d gid=%d probe=%d", path, format, (int)uid, (int)gid, allow_probe); virStorageFileMetadataPtr ret = NULL; if (virHashLookup(cycle, path)) { virReportError(VIR_ERR_INTERNAL_ERROR, _("backing store for %s is self-referential"), path); return NULL; } if (virHashAddEntry(cycle, path, (void *)1) < 0) return NULL; if ((fd = virFileOpenAs(path, O_RDONLY, 0, uid, gid, 0)) < 0) { virReportSystemError(-fd, _("cannot open file '%s'"), path); return NULL; } ret = virStorageFileGetMetadataFromFD(path, fd, format); if (VIR_CLOSE(fd) < 0) VIR_WARN("could not close file %s", path); if (ret && ret->backingStoreIsFile) { if (ret->backingStoreFormat == VIR_STORAGE_FILE_AUTO && !allow_probe) ret->backingStoreFormat = VIR_STORAGE_FILE_RAW; else if (ret->backingStoreFormat == VIR_STORAGE_FILE_AUTO_SAFE) ret->backingStoreFormat = VIR_STORAGE_FILE_AUTO; format = ret->backingStoreFormat; ret->backingMeta = virStorageFileGetMetadataRecurse(ret->backingStore, format, uid, gid, allow_probe, cycle); } return ret; } /** * 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 the returned meta.backingStoreFormat is VIR_STORAGE_FILE_AUTO * it indicates the image didn't specify an explicit format for its * backing store. Callers are advised against using ALLOW_PROBE, as * it would probe the backing store format in this case. * * Caller MUST free result after use via virStorageFileFreeMetadata. */ virStorageFileMetadataPtr virStorageFileGetMetadata(const char *path, int format, uid_t uid, gid_t gid, bool allow_probe) { VIR_DEBUG("path=%s format=%d uid=%d gid=%d probe=%d", path, format, (int)uid, (int)gid, allow_probe); virHashTablePtr cycle = virHashCreate(5, NULL); virStorageFileMetadataPtr ret; if (!cycle) return NULL; if (format <= VIR_STORAGE_FILE_NONE) format = allow_probe ? VIR_STORAGE_FILE_AUTO : VIR_STORAGE_FILE_RAW; ret = virStorageFileGetMetadataRecurse(path, format, uid, gid, allow_probe, cycle); virHashFree(cycle); return ret; } /** * virStorageFileFreeMetadata: * * Free pointers in passed structure and structure itself. */ void virStorageFileFreeMetadata(virStorageFileMetadata *meta) { if (!meta) return; virStorageFileFreeMetadata(meta->backingMeta); VIR_FREE(meta->backingStore); VIR_FREE(meta->backingStoreRaw); VIR_FREE(meta); } /** * virStorageFileResize: * * Change the capacity of the raw storage file at 'path'. */ int virStorageFileResize(const char *path, unsigned long long capacity) { int fd = -1; int ret = -1; if ((fd = open(path, O_RDWR)) < 0) { virReportSystemError(errno, _("Unable to open '%s'"), path); goto cleanup; } if (ftruncate(fd, capacity) < 0) { virReportSystemError(errno, _("Failed to truncate file '%s'"), path); goto cleanup; } if (VIR_CLOSE(fd) < 0) { virReportSystemError(errno, _("Unable to save '%s'"), path); goto cleanup; } ret = 0; cleanup: VIR_FORCE_CLOSE(fd); return ret; } #ifdef __linux__ # ifndef NFS_SUPER_MAGIC # define NFS_SUPER_MAGIC 0x6969 # endif # ifndef OCFS2_SUPER_MAGIC # define OCFS2_SUPER_MAGIC 0x7461636f # endif # ifndef GFS2_MAGIC # define GFS2_MAGIC 0x01161970 # endif # ifndef AFS_FS_MAGIC # define AFS_FS_MAGIC 0x6B414653 # endif int virStorageFileIsSharedFSType(const char *path, int fstypes) { char *dirpath, *p; struct statfs sb; int statfs_ret; if ((dirpath = strdup(path)) == NULL) { virReportOOMError(); return -1; } do { /* Try less and less of the path until we get to a * directory we can stat. Even if we don't have 'x' * permission on any directory in the path on the NFS * server (assuming it's NFS), we will be able to stat the * mount point, and that will properly tell us if the * fstype is NFS. */ if ((p = strrchr(dirpath, '/')) == NULL) { virReportSystemError(EINVAL, _("Invalid relative path '%s'"), path); VIR_FREE(dirpath); return -1; } if (p == dirpath) *(p+1) = '\0'; else *p = '\0'; statfs_ret = statfs(dirpath, &sb); } while ((statfs_ret < 0) && (p != dirpath)); VIR_FREE(dirpath); if (statfs_ret < 0) { virReportSystemError(errno, _("cannot determine filesystem for '%s'"), path); return -1; } VIR_DEBUG("Check if path %s with FS magic %lld is shared", path, (long long int)sb.f_type); if ((fstypes & VIR_STORAGE_FILE_SHFS_NFS) && (sb.f_type == NFS_SUPER_MAGIC)) return 1; if ((fstypes & VIR_STORAGE_FILE_SHFS_GFS2) && (sb.f_type == GFS2_MAGIC)) return 1; if ((fstypes & VIR_STORAGE_FILE_SHFS_OCFS) && (sb.f_type == OCFS2_SUPER_MAGIC)) return 1; if ((fstypes & VIR_STORAGE_FILE_SHFS_AFS) && (sb.f_type == AFS_FS_MAGIC)) return 1; return 0; } #else int virStorageFileIsSharedFSType(const char *path ATTRIBUTE_UNUSED, int fstypes ATTRIBUTE_UNUSED) { /* XXX implement me :-) */ return 0; } #endif int virStorageFileIsSharedFS(const char *path) { return virStorageFileIsSharedFSType(path, VIR_STORAGE_FILE_SHFS_NFS | VIR_STORAGE_FILE_SHFS_GFS2 | VIR_STORAGE_FILE_SHFS_OCFS | VIR_STORAGE_FILE_SHFS_AFS); } int virStorageFileIsClusterFS(const char *path) { /* These are coherent cluster filesystems known to be safe for * migration with cache != none */ return virStorageFileIsSharedFSType(path, VIR_STORAGE_FILE_SHFS_GFS2 | VIR_STORAGE_FILE_SHFS_OCFS); } #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; virCommandPtr cmd = virCommandNewArgList( LVS, "--noheadings", "--unbuffered", "--nosuffix", "--options", "uuid", path, NULL ); int ret = -1; *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 && c_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); virCommandFree(cmd); return ret; } #else int virStorageFileGetLVMKey(const char *path, char **key ATTRIBUTE_UNUSED) { virReportSystemError(ENOSYS, _("Unable to get LVM key for %s"), path); return -1; } #endif #ifdef WITH_UDEV int virStorageFileGetSCSIKey(const char *path, char **key) { int status; virCommandPtr cmd = virCommandNewArgList( "/lib/udev/scsi_id", "--replace-whitespace", "--whitelisted", "--device", path, NULL ); int ret = -1; *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 = strchr(*key, '\n'); if (nl) *nl = '\0'; } ret = 0; cleanup: if (*key && STREQ(*key, "")) VIR_FREE(*key); virCommandFree(cmd); return ret; } #else int virStorageFileGetSCSIKey(const char *path, char **key ATTRIBUTE_UNUSED) { virReportSystemError(ENOSYS, _("Unable to get SCSI key for %s"), path); return -1; } #endif /* Given a CHAIN that starts at the named file START, return a string * pointing to either START or within CHAIN that gives the preferred * name for the backing file NAME within that chain. Pass NULL for * NAME to find the base of the chain. If META is not NULL, set *META * to the point in the chain that describes NAME (or to NULL if the * backing element is not a file). If PARENT is not NULL, set *PARENT * to the preferred name of the parent (or to NULL if NAME matches * START). Since the results point within CHAIN, they must not be * independently freed. */ const char * virStorageFileChainLookup(virStorageFileMetadataPtr chain, const char *start, const char *name, virStorageFileMetadataPtr *meta, const char **parent) { virStorageFileMetadataPtr owner; const char *tmp; if (!parent) parent = &tmp; *parent = NULL; if (name ? STREQ(start, name) || virFileLinkPointsTo(start, name) : !chain->backingStore) { if (meta) *meta = chain; return start; } owner = chain; *parent = start; while (owner) { if (!owner->backingStore) goto error; if (!name) { if (!owner->backingMeta || !owner->backingMeta->backingStore) break; } else if (STREQ_NULLABLE(name, owner->backingStoreRaw) || STREQ(name, owner->backingStore)) { break; } else if (owner->backingStoreIsFile) { char *absName = absolutePathFromBaseFile(*parent, name); if (absName && STREQ(absName, owner->backingStore)) { VIR_FREE(absName); break; } VIR_FREE(absName); } *parent = owner->backingStore; owner = owner->backingMeta; } if (!owner) goto error; if (meta) *meta = owner->backingMeta; return owner->backingStore; error: *parent = NULL; if (meta) *meta = NULL; return NULL; }