>From f8d3e740cee4903bd2eef1a072e8190f5e9e92b9 Mon Sep 17 00:00:00 2001
From: Daniel P. Berrange <berrange@redhat.com>
Date: Tue, 15 Jun 2010 14:58:10 +0100
Subject: [PATCH 03/10] Refactor virStorageFileGetMetadataFromFD to separate functionality

The virStorageFileGetMetadataFromFD did two jobs in one. First
it probed for storage type, then it extracted metadata for the
type. It is desirable to be able to separate these jobs, allowing
probing without querying metadata, and querying metadata without
probing.

To prepare for this, split out probing code into a new pair of
methods

  virStorageFileProbeFormatFromFD
  virStorageFileProbeFormat

* src/util/storage_file.c, src/util/storage_file.h,
  src/libvirt_private.syms: Introduce virStorageFileProbeFormat
  and virStorageFileProbeFormatFromFD
---
 src/libvirt_private.syms |    2 +
 src/util/storage_file.c  |  393 ++++++++++++++++++++++++++++++++--------------
 src/util/storage_file.h  |    4 +
 3 files changed, 281 insertions(+), 118 deletions(-)

Index: libvirt-0.7.6/src/libvirt_private.syms
===================================================================
--- libvirt-0.7.6.orig/src/libvirt_private.syms
+++ libvirt-0.7.6/src/libvirt_private.syms
@@ -533,6 +533,8 @@ virStorageGenerateQcowPassphrase;
 # storage_file.h
 virStorageFileFormatTypeToString;
 virStorageFileFormatTypeFromString;
+virStorageFileProbeFormat;
+virStorageFileProbeFormatFromFD;
 virStorageFileGetMetadata;
 virStorageFileGetMetadataFromFD;
 
Index: libvirt-0.7.6/src/util/storage_file.c
===================================================================
--- libvirt-0.7.6.orig/src/util/storage_file.c
+++ libvirt-0.7.6/src/util/storage_file.c
@@ -84,8 +84,8 @@ static int vmdk4GetBackingStore(virConne
 
 
 static struct FileTypeInfo const fileTypeInfo[] = {
-    [VIR_STORAGE_FILE_RAW] = { NULL, NULL, LV_LITTLE_ENDIAN, 0, 0, 0, 0, 0, 0, NULL },
-    [VIR_STORAGE_FILE_DIR] = { NULL, NULL, LV_LITTLE_ENDIAN, 0, 0, 0, 0, 0, 0, NULL },
+    [VIR_STORAGE_FILE_RAW] = { NULL, NULL, LV_LITTLE_ENDIAN, -1, 0, 0, 0, 0, 0, NULL },
+    [VIR_STORAGE_FILE_DIR] = { NULL, NULL, LV_LITTLE_ENDIAN, -1, 0, 0, 0, 0, 0, NULL },
     [VIR_STORAGE_FILE_BOCHS] = {
         /*"Bochs Virtual HD Image", */ /* Untested */ NULL,
         NULL,
@@ -401,147 +401,306 @@ absolutePathFromBaseFile(const char *bas
     return res;
 }
 
+static int
+virStorageFileMatchesMagic(int format,
+                           unsigned char *buf,
+                           size_t buflen)
+{
+    int mlen;
+
+    if (fileTypeInfo[format].magic == NULL)
+        return 0;
+
+    /* Validate magic data */
+    mlen = strlen(fileTypeInfo[format].magic);
+    if (mlen > buflen)
+        return 0;
+
+    if (memcmp(buf, fileTypeInfo[format].magic, mlen) != 0)
+        return 0;
+
+    return 1;
+}
+
+
+static int
+virStorageFileMatchesExtension(int format,
+                               const char *path)
+{
+    if (fileTypeInfo[format].extension == NULL)
+        return 0;
+
+    if (virFileHasSuffix(path, fileTypeInfo[format].extension))
+        return 1;
+
+    return 0;
+}
+
+
+static int
+virStorageFileMatchesVersion(int format,
+                             unsigned char *buf,
+                             size_t buflen)
+{
+    int version;
+
+    /* Validate version number info */
+    if (fileTypeInfo[format].versionOffset == -1)
+        return 0;
+
+    if ((fileTypeInfo[format].versionOffset + 4) > buflen)
+        return 0;
+
+    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]);
+    }
+    if (version != fileTypeInfo[format].versionNumber)
+        return 0;
+
+    return 1;
+}
+
+
+static int
+virStorageFileGetMetadataFromBuf(virConnectPtr conn,
+                                 int format,
+                                 const char *path,
+                                 unsigned char *buf,
+                                 size_t buflen,
+                                 virStorageFileMetadata *meta)
+{
+    /* XXX we should consider moving virStorageBackendUpdateVolInfo
+     * code into this method, for non-magic files
+     */
+    if (!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(conn,
+                                                       &backing,
+                                                       &backingFormat,
+                                                       buf, buflen);
+        if (ret == BACKING_STORE_INVALID)
+            return 1;
+
+        if (ret == BACKING_STORE_ERROR)
+            return -1;
+
+        if (backing != NULL) {
+            meta->backingStore = absolutePathFromBaseFile(path, backing);
+            VIR_FREE(backing);
+            if (meta->backingStore == NULL) {
+                virReportOOMError(conn);
+                return -1;
+            }
+            meta->backingStoreFormat = backingFormat;
+        } else {
+            meta->backingStore = NULL;
+            meta->backingStoreFormat = VIR_STORAGE_FILE_AUTO;
+        }
+    }
+
+    return 0;
+}
+
+
+static int
+virStorageFileProbeFormatFromBuf(const char *path,
+                                 unsigned char *buf,
+                                 size_t buflen)
+{
+    int format = VIR_STORAGE_FILE_RAW;
+    int i;
+
+    /* First check file magic */
+    for (i = 0 ; i < VIR_STORAGE_FILE_LAST ; i++) {
+        if (virStorageFileMatchesMagic(i, buf, buflen) &&
+            virStorageFileMatchesVersion(i, buf, buflen)) {
+            format = i;
+            goto cleanup;
+        }
+    }
+
+    /* No magic, so check file extension */
+    for (i = 0 ; i < VIR_STORAGE_FILE_LAST ; i++) {
+        if (virStorageFileMatchesExtension(i, path)) {
+            format = i;
+            goto cleanup;
+        }
+    }
+
+cleanup:
+    return format;
+}
 /**
- * Probe the header of a file to determine what type of disk image
- * it is, and info about its capacity if available.
+ * 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
-virStorageFileGetMetadataFromFD(virConnectPtr conn,
+virStorageFileProbeFormatFromFD(virConnectPtr conn,
                                 const char *path,
-                                int fd,
-                                virStorageFileMetadata *meta)
+                                int fd)
 {
     unsigned char head[20*512]; /* vmdk4GetBackingStore needs this much. */
-    int len, i;
+    int len;
 
-    /* If all else fails, call it a raw file */
-    meta->format = VIR_STORAGE_FILE_RAW;
+    if (lseek(fd, 0, SEEK_SET) == (off_t)-1) {
+        virReportSystemError(conn, errno, _("cannot set to start of '%s'"), path);
+        return -1;
+    }
 
     if ((len = read(fd, head, sizeof(head))) < 0) {
         virReportSystemError(conn, errno, _("cannot read header '%s'"), path);
         return -1;
     }
 
-    /* First check file magic */
-    for (i = 0 ; i < ARRAY_CARDINALITY(fileTypeInfo) ; i++) {
-        int mlen;
+    return virStorageFileProbeFormatFromBuf(path, head, len);
+}
 
-        if (fileTypeInfo[i].magic == NULL)
-            continue;
+/**
+ * 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 file into any other non-raw
+ * format at will.
+ *
+ * Best option: Don't use this function
+ */
+int
+virStorageFileProbeFormat(virConnectPtr conn, const char *path)
+{
+    int fd, ret;
 
-        /* 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;
-        }
+    if ((fd = open(path, O_RDONLY)) < 0) {
+        virReportSystemError(conn, errno, _("cannot open file '%s'"), path);
+        return -1;
+    }
 
-        /* Optionally extract capacity from file */
-        if (fileTypeInfo[i].sizeOffset != -1) {
-            if (fileTypeInfo[i].endian == LV_LITTLE_ENDIAN) {
-                meta->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 {
-                meta->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 (meta->capacity > (ULLONG_MAX / fileTypeInfo[i].sizeMultiplier))
-                continue;
-            meta->capacity *= fileTypeInfo[i].sizeMultiplier;
-        }
+    ret = virStorageFileProbeFormatFromFD(conn, path, fd);
 
-        if (fileTypeInfo[i].qcowCryptOffset != -1) {
-            int crypt_format;
+    close(fd);
 
-            crypt_format = (head[fileTypeInfo[i].qcowCryptOffset] << 24) |
-                (head[fileTypeInfo[i].qcowCryptOffset+1] << 16) |
-                (head[fileTypeInfo[i].qcowCryptOffset+2] << 8) |
-                head[fileTypeInfo[i].qcowCryptOffset+3];
-            meta->encrypted = crypt_format != 0;
-        }
+    return ret;
+}
 
-        /* Validation passed, we know the file format now */
-        meta->format = i;
-        if (fileTypeInfo[i].getBackingStore != NULL) {
-            char *backing;
-            int backingFormat;
-
-            switch (fileTypeInfo[i].getBackingStore(conn, &backing,
-                                                    &backingFormat,
-                                                    head, len)) {
-            case BACKING_STORE_OK:
-                break;
+/**
+ * virStorageFileGetMetadataFromFD:
+ *
+ * Probe for the format of 'fd' (which is an open file descriptor
+ * for the file 'path'), filling 'meta' with the detected
+ * format and other associated metadata.
+ *
+ * Callers are advised never to trust the returned 'meta->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.
+ */
+int
+virStorageFileGetMetadataFromFD(virConnectPtr conn,
+                                const char *path,
+                                int fd,
+                                virStorageFileMetadata *meta)
+{
+    unsigned char head[20*512]; /* vmdk4GetBackingStore needs this much. */
+    int len;
 
-            case BACKING_STORE_INVALID:
-                continue;
+    memset(meta, 0, sizeof (*meta));
 
-            case BACKING_STORE_ERROR:
-                return -1;
-            }
-            if (backing != NULL) {
-                meta->backingStore = absolutePathFromBaseFile(path, backing);
-                VIR_FREE(backing);
-                if (meta->backingStore == NULL) {
-                    virReportOOMError(conn);
-                    return -1;
-                }
-                meta->backingStoreFormat = backingFormat;
-            } else {
-                meta->backingStoreFormat = VIR_STORAGE_FILE_AUTO;
-            }
-        }
-        return 0;
+    if (lseek(fd, 0, SEEK_SET) == (off_t)-1) {
+        virReportSystemError(conn, errno, _("cannot set to start of '%s'"), path);
+        return -1;
     }
 
-    /* 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;
-
-        meta->format = i;
-        return 0;
+    if ((len = read(fd, head, sizeof(head))) < 0) {
+        virReportSystemError(conn, errno, _("cannot read header '%s'"), path);
+        return -1;
     }
 
-    return 0;
+    meta->format = virStorageFileProbeFormatFromBuf(path, head, len);
+
+    return virStorageFileGetMetadataFromBuf(conn, meta->format, path, head, len, meta);
 }
 
+/**
+ * virStorageFileGetMetadata:
+ *
+ * Probe for the format of 'path', filling 'meta' with the detected
+ * format and other associated metadata.
+ *
+ * Callers are advised never to trust the returned 'meta->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.
+ */
 int
 virStorageFileGetMetadata(virConnectPtr conn,
                           const char *path,
Index: libvirt-0.7.6/src/util/storage_file.h
===================================================================
--- libvirt-0.7.6.orig/src/util/storage_file.h
+++ libvirt-0.7.6/src/util/storage_file.h
@@ -53,6 +53,12 @@ typedef struct _virStorageFileMetadata {
     bool encrypted;
 } virStorageFileMetadata;
 
+int virStorageFileProbeFormat(virConnectPtr conn,
+                              const char *path);
+int virStorageFileProbeFormatFromFD(virConnectPtr conn,
+                                    const char *path,
+                                    int fd);
+
 int virStorageFileGetMetadata(virConnectPtr conn,
                               const char *path,
                               virStorageFileMetadata *meta);
