libclamav/dmg.c
ca019d6d
 /*
c442ca9c
  *  Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
ca019d6d
  *  Copyright (C) 2013 Sourcefire, Inc.
  *
  *  Authors: David Raynor <draynor@sourcefire.com>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2 as
  *  published by the Free Software Foundation.
  *
  *  This program 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 General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
  */
 
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
 #include <stdio.h>
217295cc
 #include <errno.h>
 #if HAVE_STRING_H
 #include <string.h>
 #endif
ca019d6d
 #include <ctype.h>
217295cc
 #include <fcntl.h>
 #if HAVE_SYS_PARAM_H
 #include <sys/param.h>  /* for NAME_MAX */
 #endif
 
 #if HAVE_LIBZ
 #include <zlib.h>
 #endif
 #if HAVE_BZLIB_H
 #include <bzlib.h>
 #ifdef NOBZ2PREFIX
 #define BZ2_bzDecompress bzDecompress
 #define BZ2_bzDecompressEnd bzDecompressEnd
 #define BZ2_bzDecompressInit bzDecompressInit
 #endif
 #endif
 
f496f638
 #if HAVE_LIBXML2
217295cc
 #include <libxml/xmlreader.h>
f496f638
 #endif
ca019d6d
 
60d8d2c3
 #include "clamav.h"
ca019d6d
 #include "others.h"
 #include "dmg.h"
 #include "scanners.h"
217295cc
 #include "sf_base64decode.h"
adf1c03b
 #include "adc.h"
217295cc
 
adf1c03b
 /* #define DEBUG_DMG_PARSE */
 /* #define DEBUG_DMG_BZIP */
217295cc
 
 #ifdef DEBUG_DMG_PARSE
 #  define dmg_parsemsg(...) cli_dbgmsg( __VA_ARGS__)
 #else
 #  define dmg_parsemsg(...) ;
 #endif
 
5305eb96
 #ifdef DEBUG_DMG_BZIP
 #  define dmg_bzipmsg(...) cli_dbgmsg( __VA_ARGS__)
 #else
 #  define dmg_bzipmsg(...) ;
 #endif
 
217295cc
 enum dmgReadState {
     DMG_FIND_BASE_PLIST = 0,
     DMG_FIND_BASE_DICT = 1,
     DMG_FIND_KEY_RESOURCE_FORK = 2,
     DMG_FIND_DICT_RESOURCE_FORK = 3,
     DMG_FIND_KEY_BLKX = 4,
     DMG_FIND_BLKX_CONTAINER = 5,
     DMG_FIND_KEY_DATA = 6,
     DMG_FIND_DATA_MISH = 7,
     DMG_MAX_STATE = 8
 };
 
 static int dmg_extract_xml(cli_ctx *, char *, struct dmg_koly_block *);
 #if HAVE_LIBXML2
 static int dmg_decode_mish(cli_ctx *, unsigned int *, xmlChar *, struct dmg_mish_with_stripes *);
 #endif
 static int cmp_mish_stripes(const void * stripe_a, const void * stripe_b);
 static int dmg_track_sectors(uint64_t *, uint8_t *, uint32_t, uint32_t, uint64_t);
 static int dmg_handle_mish(cli_ctx *, unsigned int, char *, uint64_t, struct dmg_mish_with_stripes *);
ca019d6d
 
 int cli_scandmg(cli_ctx *ctx)
 {
     struct dmg_koly_block hdr;
cd94be7a
     int ret;
217295cc
     size_t maplen, nread;
     off_t pos = 0;
cd94be7a
     char *dirname;
217295cc
     const char *outdata;
     unsigned int file = 0;
bc4b91ec
     struct dmg_mish_with_stripes *mish_list = NULL, *mish_list_tail = NULL;
     enum dmgReadState state = DMG_FIND_BASE_PLIST;
     int stateDepth[DMG_MAX_STATE];
 #if HAVE_LIBXML2
     xmlTextReaderPtr reader;
 #endif
ca019d6d
 
     if (!ctx || !ctx->fmap) {
         cli_errmsg("cli_scandmg: Invalid context\n");
         return CL_ENULLARG;
     }
 
     maplen = (*ctx->fmap)->real_len;
     pos = maplen - 512;
     if (pos <= 0) {
         cli_dbgmsg("cli_scandmg: Sizing problem for DMG archive.\n");
         return CL_CLEAN;
     }
 
     /* Grab koly block */
     if (fmap_readn(*ctx->fmap, &hdr, pos, sizeof(hdr)) != sizeof(hdr)) {
         cli_dbgmsg("cli_scandmg: Invalid DMG trailer block\n");
         return CL_EFORMAT;
     }
 
     /* Check magic */
     hdr.magic = be32_to_host(hdr.magic);
     if (hdr.magic == 0x6b6f6c79) {
         cli_dbgmsg("cli_scandmg: Found koly block @ %ld\n", (long) pos);
     }
     else {
         cli_dbgmsg("cli_scandmg: No koly magic, %8x\n", hdr.magic);
         return CL_EFORMAT;
     }
 
5305eb96
     hdr.dataForkOffset = be64_to_host(hdr.dataForkOffset);
     hdr.dataForkLength = be64_to_host(hdr.dataForkLength);
     cli_dbgmsg("cli_scandmg: data offset %lu len %d\n", (unsigned long)hdr.dataForkOffset, (int)hdr.dataForkLength);
 
217295cc
     hdr.xmlOffset = be64_to_host(hdr.xmlOffset);
     hdr.xmlLength = be64_to_host(hdr.xmlLength);
     if (hdr.xmlLength > (uint64_t)INT_MAX) {
         cli_dbgmsg("cli_scandmg: The embedded XML is way larger than necessary, and probably corrupt or tampered with.\n");
         return CL_EFORMAT;
     }
     if ((hdr.xmlOffset > (uint64_t)maplen) || (hdr.xmlLength > (uint64_t)maplen)
         || (hdr.xmlOffset + hdr.xmlLength) > (uint64_t)maplen) {
         cli_dbgmsg("cli_scandmg: XML out of range for this file\n");
         return CL_EFORMAT;
     }
     cli_dbgmsg("cli_scandmg: XML offset %lu len %d\n", (unsigned long)hdr.xmlOffset, (int)hdr.xmlLength);
52724d1c
     if (hdr.xmlLength == 0) {
         cli_dbgmsg("cli_scandmg: Embedded XML length is zero.\n");
         return CL_EFORMAT;
     }
217295cc
 
     /* Create temp folder for contents */
     if (!(dirname = cli_gentemp(ctx->engine->tmpdir))) {
         return CL_ETMPDIR;
     }
     if (mkdir(dirname, 0700)) {
         cli_errmsg("cli_scandmg: Cannot create temporary directory %s\n", dirname);
         free(dirname);
         return CL_ETMPDIR;
     }
     cli_dbgmsg("cli_scandmg: Extracting into %s\n", dirname);
 
     /* Dump XML to tempfile, if needed */
f456c5ad
     if (ctx->engine->keeptmp && !(ctx->engine->engine_options & ENGINE_OPTIONS_FORCE_TO_DISK)) {
217295cc
         int xret;
         xret = dmg_extract_xml(ctx, dirname, &hdr);
 
         if (xret != CL_SUCCESS) {
             /* Printed err detail inside dmg_extract_xml */
             free(dirname);
             return xret;
         }
     }
ca019d6d
 
217295cc
     /* scan XML with cli_map_scandesc */
328a3325
     ret = cli_map_scan(*ctx->fmap, (off_t)hdr.xmlOffset, (size_t)hdr.xmlLength, ctx, CL_TYPE_ANY);
217295cc
     if (ret != CL_CLEAN) {
         cli_dbgmsg("cli_scandmg: retcode from scanning TOC xml: %s\n", cl_strerror(ret));
         if (!ctx->engine->keeptmp)
             cli_rmdirs(dirname);
         free(dirname);
         return ret;
     }
 
     /* page data from map */
     outdata = fmap_need_off_once_len(*ctx->fmap, hdr.xmlOffset, hdr.xmlLength, &nread);
     if (!outdata || (nread != hdr.xmlLength)) {
         cli_errmsg("cli_scandmg: Failed getting XML from map, len %d\n", (int)hdr.xmlLength);
         if (!ctx->engine->keeptmp)
             cli_rmdirs(dirname);
         free(dirname);
         return CL_EMAP;
     }
 
     /* time to walk the tree */
     /* plist -> dict -> (key:resource_fork) dict -> (key:blkx) array -> dict */
     /* each of those bottom level dict should have 4 parts */
     /* [ Attributes, Data, ID, Name ], where Data is Base64 mish block */
 
 /* This is the block where we require libxml2 */
 #if HAVE_LIBXML2
 
 /* XML_PARSE_NOENT | XML_PARSE_NONET | XML_PARSE_COMPACT */
d3e752ad
 #define DMG_XML_PARSE_OPTS ((1 << 1 | 1 << 11 | 1 << 16) | CLAMAV_MIN_XMLREADER_FLAGS)
217295cc
 
bc4b91ec
     reader = xmlReaderForMemory(outdata, (int)hdr.xmlLength, "toc.xml", NULL, DMG_XML_PARSE_OPTS);
217295cc
     if (!reader) {
         cli_dbgmsg("cli_scandmg: Failed parsing XML!\n");
         if (!ctx->engine->keeptmp)
             cli_rmdirs(dirname);
         free(dirname);
         return CL_EFORMAT;
     }
 
     stateDepth[DMG_FIND_BASE_PLIST] = -1;
 
     // May need to check for (xmlTextReaderIsEmptyElement(reader) == 0)
 
     /* Break loop if have return code or reader can't read any more */
     while ((ret == CL_CLEAN) && (xmlTextReaderRead(reader) == 1)) {
         xmlReaderTypes nodeType;
         nodeType = xmlTextReaderNodeType(reader);
 
         if (nodeType == XML_READER_TYPE_ELEMENT) {
             // New element, do name check
             xmlChar *nodeName;
             int depth;
 
             depth = xmlTextReaderDepth(reader);
             if (depth < 0) {
                 break;
             }
d7979d4f
             if ((depth > 50) && SCAN_HEURISTICS) {
217295cc
                 // Possible heuristic, should limit runaway
                 cli_dbgmsg("cli_scandmg: Excessive nesting in DMG TOC.\n");
                 break;
             }
             nodeName = xmlTextReaderLocalName(reader);
             if (!nodeName)
                 continue;
             dmg_parsemsg("read: name %s depth %d\n", nodeName, depth);
 
             if ((state == DMG_FIND_DATA_MISH)
                     && (depth == stateDepth[state-1])) {
                 xmlChar * textValue;
bc4b91ec
                 struct dmg_mish_with_stripes *mish_set;
217295cc
                 /* Reset state early, for continue cases */
                 stateDepth[DMG_FIND_KEY_DATA] = -1;
                 state--;
cd94be7a
                 if (xmlStrcmp(nodeName, (const xmlChar *)"data") != 0) {
217295cc
                     cli_dbgmsg("cli_scandmg: Not blkx data element\n");
                     xmlFree(nodeName);
                     continue;
                 }
                 dmg_parsemsg("read: Found blkx data element\n");
                 /* Pull out data content from text */
                 if (xmlTextReaderIsEmptyElement(reader)) {
                     cli_dbgmsg("cli_scandmg: blkx data element is empty\n");
                     xmlFree(nodeName);
                     continue;
                 }
                 if (xmlTextReaderRead(reader) != 1) {
                     xmlFree(nodeName);
                     break;
                 }   
                 if (xmlTextReaderNodeType(reader) != XML_READER_TYPE_TEXT) {
                     cli_dbgmsg("cli_scandmg: Next node not text\n");
                     xmlFree(nodeName);
                     continue;
                 }
                 textValue = xmlTextReaderValue(reader);
                 if (textValue == NULL) {
                     xmlFree(nodeName);
                     continue;
                 }
                 /* Have encoded mish block */
bc4b91ec
                 mish_set = cli_malloc(sizeof(struct dmg_mish_with_stripes));
                 if (mish_set == NULL) {
                     ret = CL_EMEM;
                     xmlFree(textValue);
                     xmlFree(nodeName);
                     break;
                 }
                 ret = dmg_decode_mish(ctx, &file, textValue, mish_set);
217295cc
                 xmlFree(textValue);
                 if (ret == CL_EFORMAT) {
                     /* Didn't decode, or not a mish block */
                     ret = CL_CLEAN;
4d035342
                     free(mish_set);
217295cc
                     xmlFree(nodeName);
                     continue;
                 }
                 else if (ret != CL_CLEAN) {
                     xmlFree(nodeName);
4d035342
                     free(mish_set);
217295cc
                     continue;
                 }
bc4b91ec
                 /* Add mish block to list */
                 if (mish_list_tail != NULL) {
                     mish_list_tail->next = mish_set;
                     mish_list_tail = mish_set;
                 }
                 else {
                     mish_list = mish_set;
                     mish_list_tail = mish_set;
                 }
                 mish_list_tail->next = NULL;
217295cc
             }
             if ((state == DMG_FIND_KEY_DATA)
                     && (depth > stateDepth[state-1])
cd94be7a
                     && (xmlStrcmp(nodeName, (const xmlChar *)"key") == 0)) {
217295cc
                 xmlChar * textValue;
                 dmg_parsemsg("read: Found key - checking for Data\n");
                 if (xmlTextReaderRead(reader) != 1) {
                     xmlFree(nodeName);
                     break;
                 }   
                 if (xmlTextReaderNodeType(reader) != XML_READER_TYPE_TEXT) {
                     cli_dbgmsg("cli_scandmg: Key node no text\n");
                     xmlFree(nodeName);
                     continue;
                 }
                 textValue = xmlTextReaderValue(reader);
                 if (textValue == NULL) {
                     cli_dbgmsg("cli_scandmg: no value from xmlTextReaderValue\n");
                     xmlFree(nodeName);
                     continue;
                 }
cd94be7a
                 if (xmlStrcmp(textValue, (const xmlChar *)"Data") == 0) {
217295cc
                     dmg_parsemsg("read: Matched data\n");
                     stateDepth[DMG_FIND_KEY_DATA] = depth;
                     state++;
                 }
                 else {
                     dmg_parsemsg("read: text value is %s\n", textValue);
                 }
                 xmlFree(textValue);
             }
             if ((state == DMG_FIND_BLKX_CONTAINER)
                     && (depth == stateDepth[state-1])) {
cd94be7a
                 if (xmlStrcmp(nodeName, (const xmlChar *)"array") == 0) {
217295cc
                     dmg_parsemsg("read: Found array blkx\n");
                     stateDepth[DMG_FIND_BLKX_CONTAINER] = depth;
                     state++;
                 }
cd94be7a
                 else if (xmlStrcmp(nodeName, (const xmlChar *)"dict") == 0) {
217295cc
                     dmg_parsemsg("read: Found dict blkx\n");
                     stateDepth[DMG_FIND_BLKX_CONTAINER] = depth;
                     state++;
                 }
                 else {
                     cli_dbgmsg("cli_scandmg: Bad blkx, not container\n");
                     stateDepth[DMG_FIND_KEY_BLKX] = -1;
                     state--;
                 }
             }
             if ((state == DMG_FIND_KEY_BLKX)
                     && (depth == stateDepth[state-1] + 1)
cd94be7a
                     && (xmlStrcmp(nodeName, (const xmlChar *)"key") == 0)) {
217295cc
                 xmlChar * textValue;
                 dmg_parsemsg("read: Found key - checking for blkx\n");
                 if (xmlTextReaderRead(reader) != 1) {
                     xmlFree(nodeName);
                     break;
                 }   
                 if (xmlTextReaderNodeType(reader) != XML_READER_TYPE_TEXT) {
                     cli_dbgmsg("cli_scandmg: Key node no text\n");
                     xmlFree(nodeName);
                     continue;
                 }
                 textValue = xmlTextReaderValue(reader);
                 if (textValue == NULL) {
                     cli_dbgmsg("cli_scandmg: no value from xmlTextReaderValue\n");
                     xmlFree(nodeName);
                     continue;
                 }
cd94be7a
                 if (xmlStrcmp(textValue, (const xmlChar *)"blkx") == 0) {
217295cc
                     cli_dbgmsg("cli_scandmg: Matched blkx\n");
                     stateDepth[DMG_FIND_KEY_BLKX] = depth;
                     state++;
                 }
                 else {
                     cli_dbgmsg("cli_scandmg: wanted blkx, text value is %s\n", textValue);
                 }
                 xmlFree(textValue);
             }
             if ((state == DMG_FIND_DICT_RESOURCE_FORK)
                     && (depth == stateDepth[state-1])) {
cd94be7a
                 if (xmlStrcmp(nodeName, (const xmlChar *)"dict") == 0) {
217295cc
                     dmg_parsemsg("read: Found resource-fork dict\n");
                     stateDepth[DMG_FIND_DICT_RESOURCE_FORK] = depth;
                     state++;
                 }
                 else {
                     dmg_parsemsg("read: Not resource-fork dict\n");
                     stateDepth[DMG_FIND_KEY_RESOURCE_FORK] = -1;
                     state--;
                 }
             }
             if ((state == DMG_FIND_KEY_RESOURCE_FORK)
                     && (depth == stateDepth[state-1] + 1)
cd94be7a
                     && (xmlStrcmp(nodeName, (const xmlChar *)"key") == 0)) {
217295cc
                 dmg_parsemsg("read: Found resource-fork key\n");
                 stateDepth[DMG_FIND_KEY_RESOURCE_FORK] = depth;
                 state++;
             }
             if ((state == DMG_FIND_BASE_DICT)
                     && (depth == stateDepth[state-1] + 1)
cd94be7a
                     && (xmlStrcmp(nodeName, (const xmlChar *)"dict") == 0)) {
217295cc
                 dmg_parsemsg("read: Found dict start\n");
                 stateDepth[DMG_FIND_BASE_DICT] = depth;
                 state++;
             }
cd94be7a
             if ((state == DMG_FIND_BASE_PLIST) && (xmlStrcmp(nodeName, (const xmlChar *)"plist") == 0)) {
217295cc
                 dmg_parsemsg("read: Found plist start\n");
                 stateDepth[DMG_FIND_BASE_PLIST] = depth;
                 state++;
             }
             xmlFree(nodeName);
         }
         else if ((nodeType == XML_READER_TYPE_END_ELEMENT) && (state > DMG_FIND_BASE_PLIST)) {
             int significantEnd = 0;
             int depth = xmlTextReaderDepth(reader);
             if (depth < 0) {
                 break;
             }
             else if (depth < stateDepth[state-1]) {
                 significantEnd = 1;
             }
             else if ((depth == stateDepth[state-1])
                     && (state-1 == DMG_FIND_BLKX_CONTAINER)) {
                 /* Special case, ending blkx container */
                 significantEnd = 1;
             }
             if (significantEnd) {
                 dmg_parsemsg("read: significant end tag, state %d\n", state);
                 stateDepth[state-1] = -1;
                 state--;
                 if ((state-1 == DMG_FIND_KEY_RESOURCE_FORK)
                         || (state-1 == DMG_FIND_KEY_BLKX)) {
                     /* Keys end their own tag (validly) and the next state depends on the following tag */
                     // cli_dbgmsg("read: significant end tag ending prior key state\n");
                     stateDepth[state-1] = -1;
                     state--;
                 }
             }
             else {
                 dmg_parsemsg("read: not significant end tag, state %d depth %d prior depth %d\n", state, depth, stateDepth[state-1]);
             }
         }
     }
 
bc4b91ec
     xmlFreeTextReader(reader);
 
217295cc
 #else
 
     cli_dbgmsg("cli_scandmg: libxml2 support is compiled out. It is required for full DMG support.\n");
 
 #endif
 
bc4b91ec
     /* Loop over mish array */
     file = 0;
     while ((ret == CL_CLEAN) && (mish_list != NULL)) {
         /* Handle & scan mish block */
         ret = dmg_handle_mish(ctx, file++, dirname, hdr.xmlOffset, mish_list);
         free(mish_list->mish);
         mish_list_tail = mish_list;
         mish_list = mish_list->next;
         free(mish_list_tail);
     }
 
217295cc
     /* Cleanup */
bc4b91ec
     /* If error occurred, need to free mish items and mish blocks */
     while (mish_list != NULL) {
         free(mish_list->mish);
         mish_list_tail = mish_list;
         mish_list = mish_list->next;
         free(mish_list_tail);
     }
217295cc
     if (!ctx->engine->keeptmp)
         cli_rmdirs(dirname);
     free(dirname);
     return ret;
 }
 
 #if HAVE_LIBXML2
 /* Transform the base64-encoded string into the binary structure
  * After this, the base64 string (from xml) can be released
  * If mish_set->mish is set by this function, it must be freed by the caller */
 static int dmg_decode_mish(cli_ctx *ctx, unsigned int *mishblocknum, xmlChar *mish_base64,
         struct dmg_mish_with_stripes *mish_set)
 {
     size_t base64_len, buff_size, decoded_len;
     uint8_t *decoded;
     const uint8_t mish_magic[4] = { 0x6d, 0x69, 0x73, 0x68 };
 
cd94be7a
     UNUSEDPARAM(ctx);
 
217295cc
     (*mishblocknum)++;
cd94be7a
     base64_len = strlen((const char *)mish_base64);
39b6afcd
     dmg_parsemsg("dmg_decode_mish: len of encoded block %u is %lu\n", *mishblocknum, base64_len);
217295cc
 
     /* speed vs memory, could walk the encoded data and skip whitespace in calculation */
     buff_size = 3 * base64_len / 4 + 4;
39b6afcd
     dmg_parsemsg("dmg_decode_mish: buffer for mish block %u is %lu\n", *mishblocknum, (unsigned long)buff_size);
217295cc
     decoded = cli_malloc(buff_size);
     if (!decoded)
         return CL_EMEM;
 
     if (sf_base64decode((uint8_t *)mish_base64, base64_len, decoded, buff_size - 1, &decoded_len)) {
         cli_dbgmsg("dmg_decode_mish: failed base64 decoding on mish block %u\n", *mishblocknum);
         free(decoded);
         return CL_EFORMAT;
     }
39b6afcd
     dmg_parsemsg("dmg_decode_mish: len of decoded mish block %u is %lu\n", *mishblocknum, (unsigned long)decoded_len);
217295cc
     
     if (decoded_len < sizeof(struct dmg_mish_block)) {
         cli_dbgmsg("dmg_decode_mish: block %u too short for valid mish block\n", *mishblocknum);
         free(decoded);
         return CL_EFORMAT;
     }
     /* mish check: magic is mish, have to check after conversion from base64
      * mish base64 is bWlzaA [but last character can change last two bytes]
      * won't see that in practice much (affects value of version field) */
     if (memcmp(decoded, mish_magic, 4)) {
         cli_dbgmsg("dmg_decode_mish: block %u does not have mish magic\n", *mishblocknum);
         free(decoded);
         return CL_EFORMAT;
     }
 
     mish_set->mish = (struct dmg_mish_block *)decoded;
     mish_set->mish->startSector = be64_to_host(mish_set->mish->startSector);
     mish_set->mish->sectorCount = be64_to_host(mish_set->mish->sectorCount);
     mish_set->mish->dataOffset = be64_to_host(mish_set->mish->dataOffset);
     // mish_set->mish->bufferCount = be32_to_host(mish_set->mish->bufferCount);
     mish_set->mish->blockDataCount = be32_to_host(mish_set->mish->blockDataCount);
 
38fe35f0
     cli_dbgmsg("dmg_decode_mish: startSector = " STDu64 " sectorCount = " STDu64
         " dataOffset = " STDu64 " stripeCount = " STDu32 "\n",
39b6afcd
         mish_set->mish->startSector, mish_set->mish->sectorCount,
         mish_set->mish->dataOffset, mish_set->mish->blockDataCount);
217295cc
 
     /* decoded length should be mish block + blockDataCount * 40 */
     if (decoded_len < (sizeof(struct dmg_mish_block)
          + mish_set->mish->blockDataCount * sizeof(struct dmg_block_data))) {
         cli_dbgmsg("dmg_decode_mish: mish block %u too small\n", *mishblocknum);
         free(decoded);
         mish_set->mish = NULL;
         return CL_EFORMAT;
     }
     else if (decoded_len > (sizeof(struct dmg_mish_block)
          + mish_set->mish->blockDataCount * sizeof(struct dmg_block_data))) {
         cli_dbgmsg("dmg_decode_mish: mish block %u bigger than needed, continuing\n", *mishblocknum);
     }
 
     mish_set->stripes = (struct dmg_block_data *)(decoded + sizeof(struct dmg_mish_block));
ca019d6d
     return CL_CLEAN;
 }
217295cc
 #endif
 
 /* Comparator for stripe sorting */
 static int cmp_mish_stripes(const void * stripe_a, const void * stripe_b)
 {
     const struct dmg_block_data *a = stripe_a, *b = stripe_b;
     return a->startSector - b->startSector;
 }
 
 /* Safely track sector sizes for output estimate */
 static int dmg_track_sectors(uint64_t *total, uint8_t *data_to_write,
     uint32_t stripeNum, uint32_t stripeType, uint64_t stripeCount)
 {
     int ret = CL_CLEAN, usable = 0;
 
     switch (stripeType) {
         case DMG_STRIPE_STORED:
             *data_to_write = 1;
             usable = 1;
             break;
         case DMG_STRIPE_ADC:
             *data_to_write = 1;
             usable = 1;
             break;
         case DMG_STRIPE_DEFLATE:
 #if HAVE_LIBZ
             *data_to_write = 1;
             usable = 1;
 #else
             cli_warnmsg("dmg_track_sectors: Need zlib decompression to properly scan this file.\n");
             return CL_EFORMAT;
 #endif
             break;
         case DMG_STRIPE_BZ:
 #if HAVE_BZLIB_H
             *data_to_write = 1;
             usable = 1;
 #else
             cli_warnmsg("dmg_track_sectors: Need bzip2 decompression to properly scan this file.\n");
             return CL_EFORMAT;
 #endif
             break;
         case DMG_STRIPE_EMPTY:
         case DMG_STRIPE_ZEROES:
             /* Usable, but only zeroes is not worth scanning on its own */
             usable = 1;
             break;
         case DMG_STRIPE_SKIP:
         case DMG_STRIPE_END:
             /* These should be sectorCount 0 */
             break;
         default:
             if (stripeCount) {
                 /* Continue for now */
38fe35f0
                 cli_dbgmsg("dmg_track_sectors: unknown type on stripe " STDu32 ", will skip\n", stripeNum);
217295cc
             }
             else {
                 /* Continue, no sectors missed  */
38fe35f0
                 cli_dbgmsg("dmg_track_sectors: unknown type on empty stripe " STDu32 "\n", stripeNum);
217295cc
             }
             break;
     }
 
     if (usable) {
         /* Check for wrap */
         if (*total < (*total + stripeCount)) {
             *total = *total + stripeCount;
         }
         else if (stripeCount) {
             cli_dbgmsg("dmg_track_sectors: *total would wrap uint64, suspicious\n");
             ret = CL_EFORMAT;
         }
         else {
             /* Can continue */
38fe35f0
             cli_dbgmsg("dmg_track_sectors: unexpected zero sectorCount on stripe " STDu32 "\n", stripeNum);
217295cc
         }
     }
 
     return ret;
 }
 
bc4b91ec
 /* Stripe handling: zero block (type 0x0 or 0x2) */
 static int dmg_stripe_zeroes(cli_ctx *ctx, int fd, uint32_t index, struct dmg_mish_with_stripes *mish_set)
 {
     int ret = CL_CLEAN;
     size_t len = mish_set->stripes[index].sectorCount * DMG_SECTOR_SIZE;
     ssize_t written;
     uint8_t obuf[BUFSIZ];
 
cd94be7a
     UNUSEDPARAM(ctx);
 
38fe35f0
     cli_dbgmsg("dmg_stripe_zeroes: stripe " STDu32 "\n", index);
bc4b91ec
     if (len == 0)
         return CL_CLEAN;
 
     memset(obuf, 0, sizeof(obuf));
     while (len > sizeof(obuf)) {
         written = cli_writen(fd, obuf, sizeof(obuf));
cd94be7a
         if ((size_t)written != sizeof(obuf)) {
bc4b91ec
             ret = CL_EWRITE;
             break;
         }
         len -= sizeof(obuf);
     }
 
     if ((ret == CL_CLEAN) && (len > 0)) {
         written = cli_writen(fd, obuf, len);
cd94be7a
         if ((size_t)written != len) {
bc4b91ec
             ret = CL_EWRITE;
         }
     }
 
     if (ret != CL_CLEAN) {
         cli_errmsg("dmg_stripe_zeroes: error writing bytes to file (out of disk space?)\n");
         return CL_EWRITE;
     }
     return CL_CLEAN;
 }
 
 /* Stripe handling: stored block (type 0x1) */
 static int dmg_stripe_store(cli_ctx *ctx, int fd, uint32_t index, struct dmg_mish_with_stripes *mish_set)
 {
     const void *obuf;
     size_t off = mish_set->stripes[index].dataOffset;
     size_t len = mish_set->stripes[index].dataLength;
     ssize_t written;
 
38fe35f0
     cli_dbgmsg("dmg_stripe_store: stripe " STDu32 "\n", index);
bc4b91ec
     if (len == 0)
         return CL_CLEAN;
 
     obuf = (void *)fmap_need_off_once(*ctx->fmap, off, len);
     if (!obuf) {
38fe35f0
         cli_warnmsg("dmg_stripe_store: fmap need failed on stripe " STDu32 "\n", index);
bc4b91ec
         return CL_EMAP;
     }
     written = cli_writen(fd, obuf, len);
     if (written < 0) {
         cli_errmsg("dmg_stripe_store: error writing bytes to file (out of disk space?)\n");
         return CL_EWRITE;
     }
cd94be7a
     else if ((size_t)written != len) {
bc4b91ec
         cli_errmsg("dmg_stripe_store: error writing bytes to file (out of disk space?)\n");
         return CL_EWRITE;
     }
     return CL_CLEAN;
 }
 
 /* Stripe handling: ADC block (type 0x80000004) */
 static int dmg_stripe_adc(cli_ctx *ctx, int fd, uint32_t index, struct dmg_mish_with_stripes *mish_set)
 {
cd94be7a
     int adcret;
adf1c03b
     adc_stream strm;
     size_t off = mish_set->stripes[index].dataOffset;
     size_t len = mish_set->stripes[index].dataLength;
     uint64_t size_so_far = 0;
     uint64_t expected_len = mish_set->stripes[index].sectorCount * DMG_SECTOR_SIZE;
     uint8_t obuf[BUFSIZ];
 
     cli_dbgmsg("dmg_stripe_adc: stripe " STDu32 " initial len " STDu64 " expected len " STDu64 "\n",
57ff98ca
             index, (uint64_t)len, (uint64_t)expected_len);
adf1c03b
     if (len == 0)
         return CL_CLEAN;
 
     memset(&strm, 0, sizeof(strm));
57ff98ca
     strm.next_in = (uint8_t *)fmap_need_off_once(*ctx->fmap, off, len);
adf1c03b
     if (!strm.next_in) {
         cli_warnmsg("dmg_stripe_adc: fmap need failed on stripe " STDu32 "\n", index);
         return CL_EMAP;
     }
     strm.avail_in = len;
     strm.next_out = obuf;
     strm.avail_out = sizeof(obuf);
 
     adcret = adc_decompressInit(&strm);
     if(adcret != ADC_OK) {
         cli_warnmsg("dmg_stripe_adc: adc_decompressInit failed\n");
         return CL_EMEM;
     }
 
     while(adcret == ADC_OK) {
         int written;
         if (size_so_far > expected_len) {
             cli_warnmsg("dmg_stripe_adc: expected size exceeded!\n");
             adc_decompressEnd(&strm);
             return CL_EFORMAT;
         }
         adcret = adc_decompress(&strm);
         switch(adcret) {
             case ADC_OK:
                 if(strm.avail_out == 0) {
                     if ((written=cli_writen(fd, obuf, sizeof(obuf)))!=sizeof(obuf)) {
                         cli_errmsg("dmg_stripe_adc: failed write to output file\n");
                         adc_decompressEnd(&strm);
                         return CL_EWRITE;
                     }
                     size_so_far += written;
                     strm.next_out = obuf;
                     strm.avail_out = sizeof(obuf);
                 }
                 continue;
             case ADC_STREAM_END:
             default:
                 written = sizeof(obuf) - strm.avail_out;
                 if (written) {
                     if ((cli_writen(fd, obuf, written))!=written) {
                         cli_errmsg("dmg_stripe_adc: failed write to output file\n");
                         adc_decompressEnd(&strm);
                         return CL_EWRITE;
                     }
                     size_so_far += written;
                     strm.next_out = obuf;
                     strm.avail_out = sizeof(obuf);
                 }
333e792f
                 if (adcret == ADC_STREAM_END)
adf1c03b
                     break;
                 cli_dbgmsg("dmg_stripe_adc: after writing " STDu64 " bytes, "
                            "got error %d decompressing stripe " STDu32 "\n",
                            size_so_far, adcret, index);
                 adc_decompressEnd(&strm);
                 return CL_EFORMAT;
         }
         break;
     }
 
     adc_decompressEnd(&strm);
     cli_dbgmsg("dmg_stripe_adc: stripe " STDu32 " actual len " STDu64 " expected len " STDu64 "\n",
             index, size_so_far, expected_len);
     return CL_CLEAN;
bc4b91ec
 }
 
 /* Stripe handling: deflate block (type 0x80000005) */
 static int dmg_stripe_inflate(cli_ctx *ctx, int fd, uint32_t index, struct dmg_mish_with_stripes *mish_set)
 {
cd94be7a
     int zstat;
bc4b91ec
     z_stream strm;
     size_t off = mish_set->stripes[index].dataOffset;
     size_t len = mish_set->stripes[index].dataLength;
ab1a0665
     uint64_t size_so_far = 0;
     uint64_t expected_len = mish_set->stripes[index].sectorCount * DMG_SECTOR_SIZE;
5305eb96
     uint8_t obuf[BUFSIZ];
bc4b91ec
 
38fe35f0
     cli_dbgmsg("dmg_stripe_inflate: stripe " STDu32 "\n", index);
bc4b91ec
     if (len == 0)
         return CL_CLEAN;
 
     memset(&strm, 0, sizeof(strm));
     strm.next_in = (void*)fmap_need_off_once(*ctx->fmap, off, len);
     if (!strm.next_in) {
38fe35f0
         cli_warnmsg("dmg_stripe_inflate: fmap need failed on stripe " STDu32 "\n", index);
bc4b91ec
         return CL_EMAP;
     }
     strm.avail_in = len;
     strm.next_out = obuf;
     strm.avail_out = sizeof(obuf);
 
     zstat = inflateInit(&strm);
     if(zstat != Z_OK) {
         cli_warnmsg("dmg_stripe_inflate: inflateInit failed\n");
         return CL_EMEM;
     }
 
     while(strm.avail_in) {
         int written;
ab1a0665
         if (size_so_far > expected_len) {
             cli_warnmsg("dmg_stripe_inflate: expected size exceeded!\n");
             inflateEnd(&strm);
             return CL_EFORMAT;
         }
bc4b91ec
         zstat = inflate(&strm, Z_NO_FLUSH);   /* zlib */
         switch(zstat) {
             case Z_OK:
                 if(strm.avail_out == 0) {
                     if ((written=cli_writen(fd, obuf, sizeof(obuf)))!=sizeof(obuf)) {
                         cli_errmsg("dmg_stripe_inflate: failed write to output file\n");
                         inflateEnd(&strm);
                         return CL_EWRITE;
                     }
ab1a0665
                     size_so_far += written;
bc4b91ec
                     strm.next_out = (Bytef *)obuf;
                     strm.avail_out = sizeof(obuf);
                 }
                 continue;
             case Z_STREAM_END:
             default:
                 written = sizeof(obuf) - strm.avail_out;
                 if (written) {
                     if ((cli_writen(fd, obuf, written))!=written) {
                         cli_errmsg("dmg_stripe_inflate: failed write to output file\n");
                         inflateEnd(&strm);
                         return CL_EWRITE;
                     }
ab1a0665
                     size_so_far += written;
bc4b91ec
                     strm.next_out = (Bytef *)obuf;
                     strm.avail_out = sizeof(obuf);
                     if (zstat == Z_STREAM_END)
                         break;
                 }
                 if(strm.msg)
ab1a0665
                     cli_dbgmsg("dmg_stripe_inflate: after writing " STDu64 " bytes, "
38fe35f0
                                "got error \"%s\" inflating stripe " STDu32 "\n",
ab1a0665
                                size_so_far, strm.msg, index);
bc4b91ec
                 else
ab1a0665
                     cli_dbgmsg("dmg_stripe_inflate: after writing " STDu64 " bytes, "
38fe35f0
                                "got error %d inflating stripe " STDu32 "\n",
ab1a0665
                                size_so_far, zstat, index);
bc4b91ec
                 inflateEnd(&strm);
                 return CL_EFORMAT;
         }
         break;
     }
 
     if(strm.avail_out != sizeof(obuf)) {
         if(cli_writen(fd, obuf, sizeof(obuf) - strm.avail_out) < 0) {
             cli_errmsg("dmg_stripe_inflate: failed write to output file\n");
             inflateEnd(&strm);
             return CL_EWRITE;
         }
     }
 
     inflateEnd(&strm);
     return CL_CLEAN;
 }
 
 /* Stripe handling: bzip block (type 0x80000006) */
 static int dmg_stripe_bzip(cli_ctx *ctx, int fd, uint32_t index, struct dmg_mish_with_stripes *mish_set)
 {
5305eb96
     int ret = CL_CLEAN;
ab1a0665
     size_t off = mish_set->stripes[index].dataOffset;
5305eb96
     size_t len = mish_set->stripes[index].dataLength;
ab1a0665
     uint64_t size_so_far = 0;
     uint64_t expected_len = mish_set->stripes[index].sectorCount * DMG_SECTOR_SIZE;
5305eb96
 #if HAVE_BZLIB_H
     int rc;
     bz_stream strm;
     uint8_t obuf[BUFSIZ];
 #endif
bc4b91ec
 
38fe35f0
     cli_dbgmsg("dmg_stripe_bzip: stripe " STDu32 " initial len " STDu64 " expected len " STDu64 "\n",
c990d220
             index, (uint64_t)len, (uint64_t)expected_len);
5305eb96
 
 #if HAVE_BZLIB_H
     memset(&strm, 0, sizeof(strm));
cd94be7a
     strm.next_out = (char *)obuf;
5305eb96
     strm.avail_out = sizeof(obuf);
     if (BZ2_bzDecompressInit(&strm, 0, 0) != BZ_OK) {
         cli_dbgmsg("dmg_stripe_bzip: bzDecompressInit failed\n");
         return CL_EOPEN;
     }
 
     do {
ab1a0665
         if (size_so_far > expected_len) {
             cli_warnmsg("dmg_stripe_bzip: expected size exceeded!\n");
             ret = CL_EFORMAT;
             break;
         }
5305eb96
         if (strm.avail_in == 0) {
             size_t next_len = (len > sizeof(obuf)) ? sizeof(obuf) : len;
             dmg_bzipmsg("dmg_stripe_bzip: off %lu len %lu next_len %lu\n", off, len, next_len);
             strm.next_in = (void*)fmap_need_off_once(*ctx->fmap, off, next_len);
             if (strm.next_in == NULL) {
                 cli_dbgmsg("dmg_stripe_bzip: expected more stream\n");
                 ret = CL_EMAP;
                 break;
             }
             strm.avail_in = next_len;
             len -= next_len;
             off += next_len;
         }
 
         dmg_bzipmsg("dmg_stripe_bzip: before = strm.avail_in %lu strm.avail_out: %lu\n", strm.avail_in, strm.avail_out);
         rc = BZ2_bzDecompress(&strm);
         if ((rc != BZ_OK) && (rc != BZ_STREAM_END)) {
             cli_dbgmsg("dmg_stripe_bzip: decompress error: %d\n", rc);
             ret = CL_EFORMAT;
             break;
         }
 
         dmg_bzipmsg("dmg_stripe_bzip: after = strm.avail_in %lu strm.avail_out: %lu rc: %d %d\n",
                 strm.avail_in, strm.avail_out, rc, BZ_STREAM_END);
         /* Drain output buffer */
         if (!strm.avail_out) {
             size_t next_write = sizeof(obuf);
             do {
                 size_so_far += next_write;
ab1a0665
                 dmg_bzipmsg("dmg_stripe_bzip: size_so_far: " STDu64 " next_write: %lu\n", size_so_far, next_write);
                 if (size_so_far > expected_len) {
                     cli_warnmsg("dmg_stripe_bzip: expected size exceeded!\n");
                     ret = CL_EFORMAT;
                     rc = BZ_DATA_ERROR; /* prevent stream end block */
                     break;
                 }
5305eb96
 
                 ret = cli_checklimits("dmg_stripe_bzip", ctx, (unsigned long)(size_so_far + sizeof(obuf)), 0, 0);
                 if (ret != CL_CLEAN) {
                     break;
                 }
 
cd94be7a
                 if ((size_t)cli_writen(fd, obuf, next_write) != next_write) {
5305eb96
                     cli_dbgmsg("dmg_stripe_bzip: error writing to tmpfile\n");
                     ret = CL_EWRITE;
                     break;
                 }
 
cd94be7a
                 strm.next_out = (char *)obuf;
5305eb96
                 strm.avail_out = sizeof(obuf);
 
                 if (rc == BZ_OK)
                     rc = BZ2_bzDecompress(&strm);
                 if ((rc != BZ_OK) && (rc != BZ_STREAM_END)) {
                     cli_dbgmsg("dmg_stripe_bzip: decompress error: %d\n", rc);
                     ret = CL_EFORMAT;
                     break;
                 }
             } while (!strm.avail_out);
         }
         /* Stream end, so write data if any remains in buffer */
         if (rc == BZ_STREAM_END) {
             size_t next_write = sizeof(obuf) - strm.avail_out;
             size_so_far += next_write;
ab1a0665
             dmg_bzipmsg("dmg_stripe_bzip: size_so_far: " STDu64 " next_write: %lu\n", size_so_far, next_write);
5305eb96
 
ab1a0665
             ret = cli_checklimits("dmg_stripe_bzip", ctx, (unsigned long)(size_so_far + sizeof(obuf)), 0, 0);
5305eb96
             if (ret != CL_CLEAN) {
                 break;
             }
 
cd94be7a
             if ((size_t)cli_writen(fd, obuf, next_write) != next_write) {
5305eb96
                 cli_dbgmsg("dmg_stripe_bzip: error writing to tmpfile\n");
                 ret = CL_EWRITE;
                 break;
             }
 
cd94be7a
             strm.next_out = (char *)obuf;
5305eb96
             strm.avail_out = sizeof(obuf);
         }
     } while ((rc == BZ_OK) && (len > 0));
 
     BZ2_bzDecompressEnd(&strm);
 #endif
 
ab1a0665
     if (ret == CL_CLEAN) {
         if (size_so_far != expected_len) {
             cli_dbgmsg("dmg_stripe_bzip: output does not match expected size!\n");
         }
     }
5305eb96
     return ret;
bc4b91ec
 }
 
217295cc
 /* Given mish data, reconstruct the partition details */
 static int dmg_handle_mish(cli_ctx *ctx, unsigned int mishblocknum, char *dir,
         uint64_t xmlOffset, struct dmg_mish_with_stripes *mish_set)
 {
     struct dmg_block_data *blocklist = mish_set->stripes;
     uint64_t totalSectors = 0;
     uint32_t i;
     unsigned long projected_size;
     int ret = CL_CLEAN, ofd;
     uint8_t sorted = 1, writeable_data = 0;
     char outfile[NAME_MAX + 1];
 
     /* First loop, fix endian-ness and check if already sorted */
     for (i = 0; i < mish_set->mish->blockDataCount; i++) {
         blocklist[i].type = be32_to_host(blocklist[i].type);
         // blocklist[i].reserved = be32_to_host(blocklist[i].reserved);
         blocklist[i].startSector = be64_to_host(blocklist[i].startSector);
         blocklist[i].sectorCount = be64_to_host(blocklist[i].sectorCount);
         blocklist[i].dataOffset = be64_to_host(blocklist[i].dataOffset);
         blocklist[i].dataLength = be64_to_host(blocklist[i].dataLength);
38fe35f0
         cli_dbgmsg("mish %u stripe " STDu32 " type " STDx32 " start " STDu64
             " count " STDu64 " source " STDu64 " length " STDu64 "\n",
             mishblocknum, i, blocklist[i].type, blocklist[i].startSector, blocklist[i].sectorCount,
217295cc
             blocklist[i].dataOffset, blocklist[i].dataLength);
         if ((blocklist[i].dataOffset > xmlOffset) || 
                (blocklist[i].dataOffset + blocklist[i].dataLength > xmlOffset)) {
             cli_dbgmsg("dmg_handle_mish: invalid stripe offset and/or length\n");
             return CL_EFORMAT;
         }
         if ((i > 0) && sorted && (blocklist[i].startSector < blocklist[i-1].startSector)) {
             cli_dbgmsg("dmg_handle_mish: stripes not in order, will have to sort\n");
             sorted = 0;
         }
         if (dmg_track_sectors(&totalSectors, &writeable_data, i, blocklist[i].type, blocklist[i].sectorCount)) {
             /* reason was logged from dmg_track_sector_count */
             return CL_EFORMAT;
         }
     }
 
     if (!sorted) {
         cli_qsort(blocklist, mish_set->mish->blockDataCount, sizeof(struct dmg_block_data), cmp_mish_stripes);
     }
     cli_dbgmsg("dmg_handle_mish: stripes in order!\n");
 
     /* Size checks */
     if ((writeable_data == 0) || (totalSectors == 0)) {
         cli_dbgmsg("dmg_handle_mish: no data to output\n");
         return CL_CLEAN;
     }
     else if (totalSectors > (ULONG_MAX / DMG_SECTOR_SIZE)) {
         /* cli_checklimits only takes unsigned long for now */
         cli_warnmsg("dmg_handle_mish: mish block %u too big to handle (for now)", mishblocknum);
         return CL_CLEAN;
     }
     projected_size = (unsigned long)(totalSectors * DMG_SECTOR_SIZE);
     ret = cli_checklimits("cli_scandmg", ctx, projected_size, 0, 0);
     if (ret != CL_CLEAN) {
         /* limits exceeded */
f496f638
         cli_dbgmsg("dmg_handle_mish: skipping block %u, limits exceeded\n", mishblocknum);
217295cc
         return ret;
     }
 
     /* Prepare for file */
     snprintf(outfile, sizeof(outfile)-1, "%s"PATHSEP"dmg%02u", dir, mishblocknum);
     outfile[sizeof(outfile)-1] = '\0';
     ofd = open(outfile, O_RDWR|O_CREAT|O_EXCL|O_TRUNC|O_BINARY, 0600);
     if (ofd < 0) {
         char err[128];
         cli_errmsg("cli_scandmg: Can't create temporary file %s: %s\n", 
             outfile, cli_strerror(errno, err, sizeof(err)));
         return CL_ETMPFILE;
     }
     cli_dbgmsg("dmg_handle_mish: extracting block %u to %s\n", mishblocknum, outfile);
 
f496f638
     /* Push data, stripe by stripe */
217295cc
     for(i=0; i < mish_set->mish->blockDataCount && ret == CL_CLEAN; i++) {
         switch (blocklist[i].type) {
             case DMG_STRIPE_EMPTY:
             case DMG_STRIPE_ZEROES:
bc4b91ec
                 ret = dmg_stripe_zeroes(ctx, ofd, i, mish_set);
217295cc
                 break;
             case DMG_STRIPE_STORED:
bc4b91ec
                 ret = dmg_stripe_store(ctx, ofd, i, mish_set);
217295cc
                 break;
             case DMG_STRIPE_ADC:
bc4b91ec
                 ret = dmg_stripe_adc(ctx, ofd, i, mish_set);
217295cc
                 break;
             case DMG_STRIPE_DEFLATE:
bc4b91ec
                 ret = dmg_stripe_inflate(ctx, ofd, i, mish_set);
217295cc
                 break;
             case DMG_STRIPE_BZ:
bc4b91ec
                 ret = dmg_stripe_bzip(ctx, ofd, i, mish_set);
217295cc
                 break;
             case DMG_STRIPE_SKIP:
             case DMG_STRIPE_END:
             default:
38fe35f0
                 cli_dbgmsg("dmg_handle_mish: stripe " STDu32 ", skipped\n", i);
217295cc
                 break;
         }
     }
 
39b6afcd
     /* If okay so far, scan rebuilt partition */
     if (ret == CL_CLEAN) {
d39cb658
         ret = cli_partition_scandesc(ofd, outfile, ctx);
39b6afcd
     }
 
217295cc
     close(ofd);
     if (!ctx->engine->keeptmp)
         if (cli_unlink(outfile)) return CL_EUNLINK;
     
     return ret;
 }
 
 static int dmg_extract_xml(cli_ctx *ctx, char *dir, struct dmg_koly_block *hdr)
 {
     char * xmlfile;
     const char *outdata;
     size_t namelen, nread;
     int ofd;
 
     /* Prep TOC XML for output */
     outdata = fmap_need_off_once_len(*ctx->fmap, hdr->xmlOffset, hdr->xmlLength, &nread);
     if (!outdata || (nread != hdr->xmlLength)) {
38fe35f0
         cli_errmsg("cli_scandmg: Failed getting XML from map, len " STDu64 "\n", hdr->xmlLength);
217295cc
         return CL_EMAP;
     }
 
     namelen = strlen(dir) + 1 + 7 + 1;
     if (!(xmlfile = cli_malloc(namelen))) {
         return CL_EMEM;
     }
     snprintf(xmlfile, namelen, "%s"PATHSEP"toc.xml", dir);
     cli_dbgmsg("cli_scandmg: Extracting XML as %s\n", xmlfile);
 
     /* Write out TOC XML */
     if ((ofd = open(xmlfile, O_CREAT|O_RDWR|O_EXCL|O_TRUNC|O_BINARY, S_IRWXU)) < 0) {
a74278b9
         char err[128];
52724d1c
         cli_errmsg("cli_scandmg: Can't create temporary file %s: %s\n",
             xmlfile, cli_strerror(errno, err, sizeof(err)));
         free(xmlfile);
217295cc
         return CL_ETMPFILE;
     }
 
cd94be7a
     if ((uint64_t)cli_writen(ofd, outdata, hdr->xmlLength) != hdr->xmlLength) {
217295cc
         cli_errmsg("cli_scandmg: Not all bytes written!\n");
         close(ofd);
         free(xmlfile);
         return CL_EWRITE;
     }
 
     close(ofd);
     free(xmlfile);
     return CL_SUCCESS;
 }
ca019d6d