|
@@ -26,6 +26,10 @@
|
|
#include "qemu/bswap.h"
|
|
#include "qemu/bswap.h"
|
|
#include "qemu/module.h"
|
|
#include "qemu/module.h"
|
|
#include <zlib.h>
|
|
#include <zlib.h>
|
|
|
|
+#ifdef CONFIG_BZIP2
|
|
|
|
+#include <bzlib.h>
|
|
|
|
+#endif
|
|
|
|
+#include <glib.h>
|
|
|
|
|
|
enum {
|
|
enum {
|
|
/* Limit chunk sizes to prevent unreasonable amounts of memory being used
|
|
/* Limit chunk sizes to prevent unreasonable amounts of memory being used
|
|
@@ -55,6 +59,9 @@ typedef struct BDRVDMGState {
|
|
uint8_t *compressed_chunk;
|
|
uint8_t *compressed_chunk;
|
|
uint8_t *uncompressed_chunk;
|
|
uint8_t *uncompressed_chunk;
|
|
z_stream zstream;
|
|
z_stream zstream;
|
|
|
|
+#ifdef CONFIG_BZIP2
|
|
|
|
+ bz_stream bzstream;
|
|
|
|
+#endif
|
|
} BDRVDMGState;
|
|
} BDRVDMGState;
|
|
|
|
|
|
static int dmg_probe(const uint8_t *buf, int buf_size, const char *filename)
|
|
static int dmg_probe(const uint8_t *buf, int buf_size, const char *filename)
|
|
@@ -100,6 +107,16 @@ static int read_uint32(BlockDriverState *bs, int64_t offset, uint32_t *result)
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static inline uint64_t buff_read_uint64(const uint8_t *buffer, int64_t offset)
|
|
|
|
+{
|
|
|
|
+ return be64_to_cpu(*(uint64_t *)&buffer[offset]);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static inline uint32_t buff_read_uint32(const uint8_t *buffer, int64_t offset)
|
|
|
|
+{
|
|
|
|
+ return be32_to_cpu(*(uint32_t *)&buffer[offset]);
|
|
|
|
+}
|
|
|
|
+
|
|
/* Increase max chunk sizes, if necessary. This function is used to calculate
|
|
/* Increase max chunk sizes, if necessary. This function is used to calculate
|
|
* the buffer sizes needed for compressed/uncompressed chunk I/O.
|
|
* the buffer sizes needed for compressed/uncompressed chunk I/O.
|
|
*/
|
|
*/
|
|
@@ -112,6 +129,7 @@ static void update_max_chunk_size(BDRVDMGState *s, uint32_t chunk,
|
|
|
|
|
|
switch (s->types[chunk]) {
|
|
switch (s->types[chunk]) {
|
|
case 0x80000005: /* zlib compressed */
|
|
case 0x80000005: /* zlib compressed */
|
|
|
|
+ case 0x80000006: /* bzip2 compressed */
|
|
compressed_size = s->lengths[chunk];
|
|
compressed_size = s->lengths[chunk];
|
|
uncompressed_sectors = s->sectorcounts[chunk];
|
|
uncompressed_sectors = s->sectorcounts[chunk];
|
|
break;
|
|
break;
|
|
@@ -119,7 +137,9 @@ static void update_max_chunk_size(BDRVDMGState *s, uint32_t chunk,
|
|
uncompressed_sectors = (s->lengths[chunk] + 511) / 512;
|
|
uncompressed_sectors = (s->lengths[chunk] + 511) / 512;
|
|
break;
|
|
break;
|
|
case 2: /* zero */
|
|
case 2: /* zero */
|
|
- uncompressed_sectors = s->sectorcounts[chunk];
|
|
|
|
|
|
+ /* as the all-zeroes block may be large, it is treated specially: the
|
|
|
|
+ * sector is not copied from a large buffer, a simple memset is used
|
|
|
|
+ * instead. Therefore uncompressed_sectors does not need to be set. */
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -131,163 +151,372 @@ static void update_max_chunk_size(BDRVDMGState *s, uint32_t chunk,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static int64_t dmg_find_koly_offset(BlockDriverState *file_bs, Error **errp)
|
|
|
|
+{
|
|
|
|
+ int64_t length;
|
|
|
|
+ int64_t offset = 0;
|
|
|
|
+ uint8_t buffer[515];
|
|
|
|
+ int i, ret;
|
|
|
|
+
|
|
|
|
+ /* bdrv_getlength returns a multiple of block size (512), rounded up. Since
|
|
|
|
+ * dmg images can have odd sizes, try to look for the "koly" magic which
|
|
|
|
+ * marks the begin of the UDIF trailer (512 bytes). This magic can be found
|
|
|
|
+ * in the last 511 bytes of the second-last sector or the first 4 bytes of
|
|
|
|
+ * the last sector (search space: 515 bytes) */
|
|
|
|
+ length = bdrv_getlength(file_bs);
|
|
|
|
+ if (length < 0) {
|
|
|
|
+ error_setg_errno(errp, -length,
|
|
|
|
+ "Failed to get file size while reading UDIF trailer");
|
|
|
|
+ return length;
|
|
|
|
+ } else if (length < 512) {
|
|
|
|
+ error_setg(errp, "dmg file must be at least 512 bytes long");
|
|
|
|
+ return -EINVAL;
|
|
|
|
+ }
|
|
|
|
+ if (length > 511 + 512) {
|
|
|
|
+ offset = length - 511 - 512;
|
|
|
|
+ }
|
|
|
|
+ length = length < 515 ? length : 515;
|
|
|
|
+ ret = bdrv_pread(file_bs, offset, buffer, length);
|
|
|
|
+ if (ret < 0) {
|
|
|
|
+ error_setg_errno(errp, -ret, "Failed while reading UDIF trailer");
|
|
|
|
+ return ret;
|
|
|
|
+ }
|
|
|
|
+ for (i = 0; i < length - 3; i++) {
|
|
|
|
+ if (buffer[i] == 'k' && buffer[i+1] == 'o' &&
|
|
|
|
+ buffer[i+2] == 'l' && buffer[i+3] == 'y') {
|
|
|
|
+ return offset + i;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ error_setg(errp, "Could not locate UDIF trailer in dmg file");
|
|
|
|
+ return -EINVAL;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/* used when building the sector table */
|
|
|
|
+typedef struct DmgHeaderState {
|
|
|
|
+ /* used internally by dmg_read_mish_block to remember offsets of blocks
|
|
|
|
+ * across calls */
|
|
|
|
+ uint64_t data_fork_offset;
|
|
|
|
+ /* exported for dmg_open */
|
|
|
|
+ uint32_t max_compressed_size;
|
|
|
|
+ uint32_t max_sectors_per_chunk;
|
|
|
|
+} DmgHeaderState;
|
|
|
|
+
|
|
|
|
+static bool dmg_is_known_block_type(uint32_t entry_type)
|
|
|
|
+{
|
|
|
|
+ switch (entry_type) {
|
|
|
|
+ case 0x00000001: /* uncompressed */
|
|
|
|
+ case 0x00000002: /* zeroes */
|
|
|
|
+ case 0x80000005: /* zlib */
|
|
|
|
+#ifdef CONFIG_BZIP2
|
|
|
|
+ case 0x80000006: /* bzip2 */
|
|
|
|
+#endif
|
|
|
|
+ return true;
|
|
|
|
+ default:
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int dmg_read_mish_block(BDRVDMGState *s, DmgHeaderState *ds,
|
|
|
|
+ uint8_t *buffer, uint32_t count)
|
|
|
|
+{
|
|
|
|
+ uint32_t type, i;
|
|
|
|
+ int ret;
|
|
|
|
+ size_t new_size;
|
|
|
|
+ uint32_t chunk_count;
|
|
|
|
+ int64_t offset = 0;
|
|
|
|
+ uint64_t data_offset;
|
|
|
|
+ uint64_t in_offset = ds->data_fork_offset;
|
|
|
|
+ uint64_t out_offset;
|
|
|
|
+
|
|
|
|
+ type = buff_read_uint32(buffer, offset);
|
|
|
|
+ /* skip data that is not a valid MISH block (invalid magic or too small) */
|
|
|
|
+ if (type != 0x6d697368 || count < 244) {
|
|
|
|
+ /* assume success for now */
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* chunk offsets are relative to this sector number */
|
|
|
|
+ out_offset = buff_read_uint64(buffer, offset + 8);
|
|
|
|
+
|
|
|
|
+ /* location in data fork for (compressed) blob (in bytes) */
|
|
|
|
+ data_offset = buff_read_uint64(buffer, offset + 0x18);
|
|
|
|
+ in_offset += data_offset;
|
|
|
|
+
|
|
|
|
+ /* move to begin of chunk entries */
|
|
|
|
+ offset += 204;
|
|
|
|
+
|
|
|
|
+ chunk_count = (count - 204) / 40;
|
|
|
|
+ new_size = sizeof(uint64_t) * (s->n_chunks + chunk_count);
|
|
|
|
+ s->types = g_realloc(s->types, new_size / 2);
|
|
|
|
+ s->offsets = g_realloc(s->offsets, new_size);
|
|
|
|
+ s->lengths = g_realloc(s->lengths, new_size);
|
|
|
|
+ s->sectors = g_realloc(s->sectors, new_size);
|
|
|
|
+ s->sectorcounts = g_realloc(s->sectorcounts, new_size);
|
|
|
|
+
|
|
|
|
+ for (i = s->n_chunks; i < s->n_chunks + chunk_count; i++) {
|
|
|
|
+ s->types[i] = buff_read_uint32(buffer, offset);
|
|
|
|
+ if (!dmg_is_known_block_type(s->types[i])) {
|
|
|
|
+ chunk_count--;
|
|
|
|
+ i--;
|
|
|
|
+ offset += 40;
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* sector number */
|
|
|
|
+ s->sectors[i] = buff_read_uint64(buffer, offset + 8);
|
|
|
|
+ s->sectors[i] += out_offset;
|
|
|
|
+
|
|
|
|
+ /* sector count */
|
|
|
|
+ s->sectorcounts[i] = buff_read_uint64(buffer, offset + 0x10);
|
|
|
|
+
|
|
|
|
+ /* all-zeroes sector (type 2) does not need to be "uncompressed" and can
|
|
|
|
+ * therefore be unbounded. */
|
|
|
|
+ if (s->types[i] != 2 && s->sectorcounts[i] > DMG_SECTORCOUNTS_MAX) {
|
|
|
|
+ error_report("sector count %" PRIu64 " for chunk %" PRIu32
|
|
|
|
+ " is larger than max (%u)",
|
|
|
|
+ s->sectorcounts[i], i, DMG_SECTORCOUNTS_MAX);
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ goto fail;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* offset in (compressed) data fork */
|
|
|
|
+ s->offsets[i] = buff_read_uint64(buffer, offset + 0x18);
|
|
|
|
+ s->offsets[i] += in_offset;
|
|
|
|
+
|
|
|
|
+ /* length in (compressed) data fork */
|
|
|
|
+ s->lengths[i] = buff_read_uint64(buffer, offset + 0x20);
|
|
|
|
+
|
|
|
|
+ if (s->lengths[i] > DMG_LENGTHS_MAX) {
|
|
|
|
+ error_report("length %" PRIu64 " for chunk %" PRIu32
|
|
|
|
+ " is larger than max (%u)",
|
|
|
|
+ s->lengths[i], i, DMG_LENGTHS_MAX);
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ goto fail;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ update_max_chunk_size(s, i, &ds->max_compressed_size,
|
|
|
|
+ &ds->max_sectors_per_chunk);
|
|
|
|
+ offset += 40;
|
|
|
|
+ }
|
|
|
|
+ s->n_chunks += chunk_count;
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+fail:
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int dmg_read_resource_fork(BlockDriverState *bs, DmgHeaderState *ds,
|
|
|
|
+ uint64_t info_begin, uint64_t info_length)
|
|
|
|
+{
|
|
|
|
+ BDRVDMGState *s = bs->opaque;
|
|
|
|
+ int ret;
|
|
|
|
+ uint32_t count, rsrc_data_offset;
|
|
|
|
+ uint8_t *buffer = NULL;
|
|
|
|
+ uint64_t info_end;
|
|
|
|
+ uint64_t offset;
|
|
|
|
+
|
|
|
|
+ /* read offset from begin of resource fork (info_begin) to resource data */
|
|
|
|
+ ret = read_uint32(bs, info_begin, &rsrc_data_offset);
|
|
|
|
+ if (ret < 0) {
|
|
|
|
+ goto fail;
|
|
|
|
+ } else if (rsrc_data_offset > info_length) {
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ goto fail;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* read length of resource data */
|
|
|
|
+ ret = read_uint32(bs, info_begin + 8, &count);
|
|
|
|
+ if (ret < 0) {
|
|
|
|
+ goto fail;
|
|
|
|
+ } else if (count == 0 || rsrc_data_offset + count > info_length) {
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ goto fail;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* begin of resource data (consisting of one or more resources) */
|
|
|
|
+ offset = info_begin + rsrc_data_offset;
|
|
|
|
+
|
|
|
|
+ /* end of resource data (there is possibly a following resource map
|
|
|
|
+ * which will be ignored). */
|
|
|
|
+ info_end = offset + count;
|
|
|
|
+
|
|
|
|
+ /* read offsets (mish blocks) from one or more resources in resource data */
|
|
|
|
+ while (offset < info_end) {
|
|
|
|
+ /* size of following resource */
|
|
|
|
+ ret = read_uint32(bs, offset, &count);
|
|
|
|
+ if (ret < 0) {
|
|
|
|
+ goto fail;
|
|
|
|
+ } else if (count == 0 || count > info_end - offset) {
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ goto fail;
|
|
|
|
+ }
|
|
|
|
+ offset += 4;
|
|
|
|
+
|
|
|
|
+ buffer = g_realloc(buffer, count);
|
|
|
|
+ ret = bdrv_pread(bs->file, offset, buffer, count);
|
|
|
|
+ if (ret < 0) {
|
|
|
|
+ goto fail;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ret = dmg_read_mish_block(s, ds, buffer, count);
|
|
|
|
+ if (ret < 0) {
|
|
|
|
+ goto fail;
|
|
|
|
+ }
|
|
|
|
+ /* advance offset by size of resource */
|
|
|
|
+ offset += count;
|
|
|
|
+ }
|
|
|
|
+ ret = 0;
|
|
|
|
+
|
|
|
|
+fail:
|
|
|
|
+ g_free(buffer);
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int dmg_read_plist_xml(BlockDriverState *bs, DmgHeaderState *ds,
|
|
|
|
+ uint64_t info_begin, uint64_t info_length)
|
|
|
|
+{
|
|
|
|
+ BDRVDMGState *s = bs->opaque;
|
|
|
|
+ int ret;
|
|
|
|
+ uint8_t *buffer = NULL;
|
|
|
|
+ char *data_begin, *data_end;
|
|
|
|
+
|
|
|
|
+ /* Have at least some length to avoid NULL for g_malloc. Attempt to set a
|
|
|
|
+ * safe upper cap on the data length. A test sample had a XML length of
|
|
|
|
+ * about 1 MiB. */
|
|
|
|
+ if (info_length == 0 || info_length > 16 * 1024 * 1024) {
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ goto fail;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ buffer = g_malloc(info_length + 1);
|
|
|
|
+ buffer[info_length] = '\0';
|
|
|
|
+ ret = bdrv_pread(bs->file, info_begin, buffer, info_length);
|
|
|
|
+ if (ret != info_length) {
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ goto fail;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* look for <data>...</data>. The data is 284 (0x11c) bytes after base64
|
|
|
|
+ * decode. The actual data element has 431 (0x1af) bytes which includes tabs
|
|
|
|
+ * and line feeds. */
|
|
|
|
+ data_end = (char *)buffer;
|
|
|
|
+ while ((data_begin = strstr(data_end, "<data>")) != NULL) {
|
|
|
|
+ guchar *mish;
|
|
|
|
+ gsize out_len = 0;
|
|
|
|
+
|
|
|
|
+ data_begin += 6;
|
|
|
|
+ data_end = strstr(data_begin, "</data>");
|
|
|
|
+ /* malformed XML? */
|
|
|
|
+ if (data_end == NULL) {
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ goto fail;
|
|
|
|
+ }
|
|
|
|
+ *data_end++ = '\0';
|
|
|
|
+ mish = g_base64_decode(data_begin, &out_len);
|
|
|
|
+ ret = dmg_read_mish_block(s, ds, mish, (uint32_t)out_len);
|
|
|
|
+ g_free(mish);
|
|
|
|
+ if (ret < 0) {
|
|
|
|
+ goto fail;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ ret = 0;
|
|
|
|
+
|
|
|
|
+fail:
|
|
|
|
+ g_free(buffer);
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
|
|
static int dmg_open(BlockDriverState *bs, QDict *options, int flags,
|
|
Error **errp)
|
|
Error **errp)
|
|
{
|
|
{
|
|
BDRVDMGState *s = bs->opaque;
|
|
BDRVDMGState *s = bs->opaque;
|
|
- uint64_t info_begin, info_end, last_in_offset, last_out_offset;
|
|
|
|
- uint32_t count, tmp;
|
|
|
|
- uint32_t max_compressed_size = 1, max_sectors_per_chunk = 1, i;
|
|
|
|
|
|
+ DmgHeaderState ds;
|
|
|
|
+ uint64_t rsrc_fork_offset, rsrc_fork_length;
|
|
|
|
+ uint64_t plist_xml_offset, plist_xml_length;
|
|
int64_t offset;
|
|
int64_t offset;
|
|
int ret;
|
|
int ret;
|
|
|
|
|
|
bs->read_only = 1;
|
|
bs->read_only = 1;
|
|
s->n_chunks = 0;
|
|
s->n_chunks = 0;
|
|
s->offsets = s->lengths = s->sectors = s->sectorcounts = NULL;
|
|
s->offsets = s->lengths = s->sectors = s->sectorcounts = NULL;
|
|
|
|
+ /* used by dmg_read_mish_block to keep track of the current I/O position */
|
|
|
|
+ ds.data_fork_offset = 0;
|
|
|
|
+ ds.max_compressed_size = 1;
|
|
|
|
+ ds.max_sectors_per_chunk = 1;
|
|
|
|
|
|
- /* read offset of info blocks */
|
|
|
|
- offset = bdrv_getlength(bs->file);
|
|
|
|
|
|
+ /* locate the UDIF trailer */
|
|
|
|
+ offset = dmg_find_koly_offset(bs->file, errp);
|
|
if (offset < 0) {
|
|
if (offset < 0) {
|
|
ret = offset;
|
|
ret = offset;
|
|
goto fail;
|
|
goto fail;
|
|
}
|
|
}
|
|
- offset -= 0x1d8;
|
|
|
|
|
|
|
|
- ret = read_uint64(bs, offset, &info_begin);
|
|
|
|
|
|
+ /* offset of data fork (DataForkOffset) */
|
|
|
|
+ ret = read_uint64(bs, offset + 0x18, &ds.data_fork_offset);
|
|
if (ret < 0) {
|
|
if (ret < 0) {
|
|
goto fail;
|
|
goto fail;
|
|
- } else if (info_begin == 0) {
|
|
|
|
|
|
+ } else if (ds.data_fork_offset > offset) {
|
|
ret = -EINVAL;
|
|
ret = -EINVAL;
|
|
goto fail;
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
|
|
- ret = read_uint32(bs, info_begin, &tmp);
|
|
|
|
|
|
+ /* offset of resource fork (RsrcForkOffset) */
|
|
|
|
+ ret = read_uint64(bs, offset + 0x28, &rsrc_fork_offset);
|
|
if (ret < 0) {
|
|
if (ret < 0) {
|
|
goto fail;
|
|
goto fail;
|
|
- } else if (tmp != 0x100) {
|
|
|
|
|
|
+ }
|
|
|
|
+ ret = read_uint64(bs, offset + 0x30, &rsrc_fork_length);
|
|
|
|
+ if (ret < 0) {
|
|
|
|
+ goto fail;
|
|
|
|
+ }
|
|
|
|
+ if (rsrc_fork_offset >= offset ||
|
|
|
|
+ rsrc_fork_length > offset - rsrc_fork_offset) {
|
|
ret = -EINVAL;
|
|
ret = -EINVAL;
|
|
goto fail;
|
|
goto fail;
|
|
}
|
|
}
|
|
-
|
|
|
|
- ret = read_uint32(bs, info_begin + 4, &count);
|
|
|
|
|
|
+ /* offset of property list (XMLOffset) */
|
|
|
|
+ ret = read_uint64(bs, offset + 0xd8, &plist_xml_offset);
|
|
if (ret < 0) {
|
|
if (ret < 0) {
|
|
goto fail;
|
|
goto fail;
|
|
- } else if (count == 0) {
|
|
|
|
|
|
+ }
|
|
|
|
+ ret = read_uint64(bs, offset + 0xe0, &plist_xml_length);
|
|
|
|
+ if (ret < 0) {
|
|
|
|
+ goto fail;
|
|
|
|
+ }
|
|
|
|
+ if (plist_xml_offset >= offset ||
|
|
|
|
+ plist_xml_length > offset - plist_xml_offset) {
|
|
ret = -EINVAL;
|
|
ret = -EINVAL;
|
|
goto fail;
|
|
goto fail;
|
|
}
|
|
}
|
|
- info_end = info_begin + count;
|
|
|
|
-
|
|
|
|
- offset = info_begin + 0x100;
|
|
|
|
-
|
|
|
|
- /* read offsets */
|
|
|
|
- last_in_offset = last_out_offset = 0;
|
|
|
|
- while (offset < info_end) {
|
|
|
|
- uint32_t type;
|
|
|
|
-
|
|
|
|
- ret = read_uint32(bs, offset, &count);
|
|
|
|
|
|
+ ret = read_uint64(bs, offset + 0x1ec, (uint64_t *)&bs->total_sectors);
|
|
|
|
+ if (ret < 0) {
|
|
|
|
+ goto fail;
|
|
|
|
+ }
|
|
|
|
+ if (bs->total_sectors < 0) {
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ goto fail;
|
|
|
|
+ }
|
|
|
|
+ if (rsrc_fork_length != 0) {
|
|
|
|
+ ret = dmg_read_resource_fork(bs, &ds,
|
|
|
|
+ rsrc_fork_offset, rsrc_fork_length);
|
|
if (ret < 0) {
|
|
if (ret < 0) {
|
|
goto fail;
|
|
goto fail;
|
|
- } else if (count == 0) {
|
|
|
|
- ret = -EINVAL;
|
|
|
|
- goto fail;
|
|
|
|
}
|
|
}
|
|
- offset += 4;
|
|
|
|
-
|
|
|
|
- ret = read_uint32(bs, offset, &type);
|
|
|
|
|
|
+ } else if (plist_xml_length != 0) {
|
|
|
|
+ ret = dmg_read_plist_xml(bs, &ds, plist_xml_offset, plist_xml_length);
|
|
if (ret < 0) {
|
|
if (ret < 0) {
|
|
goto fail;
|
|
goto fail;
|
|
}
|
|
}
|
|
-
|
|
|
|
- if (type == 0x6d697368 && count >= 244) {
|
|
|
|
- size_t new_size;
|
|
|
|
- uint32_t chunk_count;
|
|
|
|
-
|
|
|
|
- offset += 4;
|
|
|
|
- offset += 200;
|
|
|
|
-
|
|
|
|
- chunk_count = (count - 204) / 40;
|
|
|
|
- new_size = sizeof(uint64_t) * (s->n_chunks + chunk_count);
|
|
|
|
- s->types = g_realloc(s->types, new_size / 2);
|
|
|
|
- s->offsets = g_realloc(s->offsets, new_size);
|
|
|
|
- s->lengths = g_realloc(s->lengths, new_size);
|
|
|
|
- s->sectors = g_realloc(s->sectors, new_size);
|
|
|
|
- s->sectorcounts = g_realloc(s->sectorcounts, new_size);
|
|
|
|
-
|
|
|
|
- for (i = s->n_chunks; i < s->n_chunks + chunk_count; i++) {
|
|
|
|
- ret = read_uint32(bs, offset, &s->types[i]);
|
|
|
|
- if (ret < 0) {
|
|
|
|
- goto fail;
|
|
|
|
- }
|
|
|
|
- offset += 4;
|
|
|
|
- if (s->types[i] != 0x80000005 && s->types[i] != 1 &&
|
|
|
|
- s->types[i] != 2) {
|
|
|
|
- if (s->types[i] == 0xffffffff && i > 0) {
|
|
|
|
- last_in_offset = s->offsets[i - 1] + s->lengths[i - 1];
|
|
|
|
- last_out_offset = s->sectors[i - 1] +
|
|
|
|
- s->sectorcounts[i - 1];
|
|
|
|
- }
|
|
|
|
- chunk_count--;
|
|
|
|
- i--;
|
|
|
|
- offset += 36;
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
- offset += 4;
|
|
|
|
-
|
|
|
|
- ret = read_uint64(bs, offset, &s->sectors[i]);
|
|
|
|
- if (ret < 0) {
|
|
|
|
- goto fail;
|
|
|
|
- }
|
|
|
|
- s->sectors[i] += last_out_offset;
|
|
|
|
- offset += 8;
|
|
|
|
-
|
|
|
|
- ret = read_uint64(bs, offset, &s->sectorcounts[i]);
|
|
|
|
- if (ret < 0) {
|
|
|
|
- goto fail;
|
|
|
|
- }
|
|
|
|
- offset += 8;
|
|
|
|
-
|
|
|
|
- if (s->sectorcounts[i] > DMG_SECTORCOUNTS_MAX) {
|
|
|
|
- error_report("sector count %" PRIu64 " for chunk %" PRIu32
|
|
|
|
- " is larger than max (%u)",
|
|
|
|
- s->sectorcounts[i], i, DMG_SECTORCOUNTS_MAX);
|
|
|
|
- ret = -EINVAL;
|
|
|
|
- goto fail;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- ret = read_uint64(bs, offset, &s->offsets[i]);
|
|
|
|
- if (ret < 0) {
|
|
|
|
- goto fail;
|
|
|
|
- }
|
|
|
|
- s->offsets[i] += last_in_offset;
|
|
|
|
- offset += 8;
|
|
|
|
-
|
|
|
|
- ret = read_uint64(bs, offset, &s->lengths[i]);
|
|
|
|
- if (ret < 0) {
|
|
|
|
- goto fail;
|
|
|
|
- }
|
|
|
|
- offset += 8;
|
|
|
|
-
|
|
|
|
- if (s->lengths[i] > DMG_LENGTHS_MAX) {
|
|
|
|
- error_report("length %" PRIu64 " for chunk %" PRIu32
|
|
|
|
- " is larger than max (%u)",
|
|
|
|
- s->lengths[i], i, DMG_LENGTHS_MAX);
|
|
|
|
- ret = -EINVAL;
|
|
|
|
- goto fail;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- update_max_chunk_size(s, i, &max_compressed_size,
|
|
|
|
- &max_sectors_per_chunk);
|
|
|
|
- }
|
|
|
|
- s->n_chunks += chunk_count;
|
|
|
|
- }
|
|
|
|
|
|
+ } else {
|
|
|
|
+ ret = -EINVAL;
|
|
|
|
+ goto fail;
|
|
}
|
|
}
|
|
|
|
|
|
/* initialize zlib engine */
|
|
/* initialize zlib engine */
|
|
s->compressed_chunk = qemu_try_blockalign(bs->file,
|
|
s->compressed_chunk = qemu_try_blockalign(bs->file,
|
|
- max_compressed_size + 1);
|
|
|
|
|
|
+ ds.max_compressed_size + 1);
|
|
s->uncompressed_chunk = qemu_try_blockalign(bs->file,
|
|
s->uncompressed_chunk = qemu_try_blockalign(bs->file,
|
|
- 512 * max_sectors_per_chunk);
|
|
|
|
|
|
+ 512 * ds.max_sectors_per_chunk);
|
|
if (s->compressed_chunk == NULL || s->uncompressed_chunk == NULL) {
|
|
if (s->compressed_chunk == NULL || s->uncompressed_chunk == NULL) {
|
|
ret = -ENOMEM;
|
|
ret = -ENOMEM;
|
|
goto fail;
|
|
goto fail;
|
|
@@ -349,13 +578,16 @@ static inline int dmg_read_chunk(BlockDriverState *bs, uint64_t sector_num)
|
|
if (!is_sector_in_chunk(s, s->current_chunk, sector_num)) {
|
|
if (!is_sector_in_chunk(s, s->current_chunk, sector_num)) {
|
|
int ret;
|
|
int ret;
|
|
uint32_t chunk = search_chunk(s, sector_num);
|
|
uint32_t chunk = search_chunk(s, sector_num);
|
|
|
|
+#ifdef CONFIG_BZIP2
|
|
|
|
+ uint64_t total_out;
|
|
|
|
+#endif
|
|
|
|
|
|
if (chunk >= s->n_chunks) {
|
|
if (chunk >= s->n_chunks) {
|
|
return -1;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
s->current_chunk = s->n_chunks;
|
|
s->current_chunk = s->n_chunks;
|
|
- switch (s->types[chunk]) {
|
|
|
|
|
|
+ switch (s->types[chunk]) { /* block entry type */
|
|
case 0x80000005: { /* zlib compressed */
|
|
case 0x80000005: { /* zlib compressed */
|
|
/* we need to buffer, because only the chunk as whole can be
|
|
/* we need to buffer, because only the chunk as whole can be
|
|
* inflated. */
|
|
* inflated. */
|
|
@@ -379,6 +611,34 @@ static inline int dmg_read_chunk(BlockDriverState *bs, uint64_t sector_num)
|
|
return -1;
|
|
return -1;
|
|
}
|
|
}
|
|
break; }
|
|
break; }
|
|
|
|
+#ifdef CONFIG_BZIP2
|
|
|
|
+ case 0x80000006: /* bzip2 compressed */
|
|
|
|
+ /* we need to buffer, because only the chunk as whole can be
|
|
|
|
+ * inflated. */
|
|
|
|
+ ret = bdrv_pread(bs->file, s->offsets[chunk],
|
|
|
|
+ s->compressed_chunk, s->lengths[chunk]);
|
|
|
|
+ if (ret != s->lengths[chunk]) {
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ret = BZ2_bzDecompressInit(&s->bzstream, 0, 0);
|
|
|
|
+ if (ret != BZ_OK) {
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+ s->bzstream.next_in = (char *)s->compressed_chunk;
|
|
|
|
+ s->bzstream.avail_in = (unsigned int) s->lengths[chunk];
|
|
|
|
+ s->bzstream.next_out = (char *)s->uncompressed_chunk;
|
|
|
|
+ s->bzstream.avail_out = (unsigned int) 512 * s->sectorcounts[chunk];
|
|
|
|
+ ret = BZ2_bzDecompress(&s->bzstream);
|
|
|
|
+ total_out = ((uint64_t)s->bzstream.total_out_hi32 << 32) +
|
|
|
|
+ s->bzstream.total_out_lo32;
|
|
|
|
+ BZ2_bzDecompressEnd(&s->bzstream);
|
|
|
|
+ if (ret != BZ_STREAM_END ||
|
|
|
|
+ total_out != 512 * s->sectorcounts[chunk]) {
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+#endif /* CONFIG_BZIP2 */
|
|
case 1: /* copy */
|
|
case 1: /* copy */
|
|
ret = bdrv_pread(bs->file, s->offsets[chunk],
|
|
ret = bdrv_pread(bs->file, s->offsets[chunk],
|
|
s->uncompressed_chunk, s->lengths[chunk]);
|
|
s->uncompressed_chunk, s->lengths[chunk]);
|
|
@@ -387,7 +647,8 @@ static inline int dmg_read_chunk(BlockDriverState *bs, uint64_t sector_num)
|
|
}
|
|
}
|
|
break;
|
|
break;
|
|
case 2: /* zero */
|
|
case 2: /* zero */
|
|
- memset(s->uncompressed_chunk, 0, 512 * s->sectorcounts[chunk]);
|
|
|
|
|
|
+ /* see dmg_read, it is treated specially. No buffer needs to be
|
|
|
|
+ * pre-filled, the zeroes can be set directly. */
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
s->current_chunk = chunk;
|
|
s->current_chunk = chunk;
|
|
@@ -406,6 +667,13 @@ static int dmg_read(BlockDriverState *bs, int64_t sector_num,
|
|
if (dmg_read_chunk(bs, sector_num + i) != 0) {
|
|
if (dmg_read_chunk(bs, sector_num + i) != 0) {
|
|
return -1;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
+ /* Special case: current chunk is all zeroes. Do not perform a memcpy as
|
|
|
|
+ * s->uncompressed_chunk may be too small to cover the large all-zeroes
|
|
|
|
+ * section. dmg_read_chunk is called to find s->current_chunk */
|
|
|
|
+ if (s->types[s->current_chunk] == 2) { /* all zeroes block entry */
|
|
|
|
+ memset(buf + i * 512, 0, 512);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
sector_offset_in_chunk = sector_num + i - s->sectors[s->current_chunk];
|
|
sector_offset_in_chunk = sector_num + i - s->sectors[s->current_chunk];
|
|
memcpy(buf + i * 512,
|
|
memcpy(buf + i * 512,
|
|
s->uncompressed_chunk + sector_offset_in_chunk * 512, 512);
|
|
s->uncompressed_chunk + sector_offset_in_chunk * 512, 512);
|