/* Copyright (c) 2008, XenSource Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of XenSource Inc. nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* Modified March 2016 Timothe Litt to work under windows. * Copyright (C) 2016 Timothe Litt * Modifications subject to the same license terms as above, * substituting "Timothe Litt" for "XenSource Inc." */ #ifdef _WIN32 #define _CRT_SECURE_NO_WARNINGS 1 #endif #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include #include #include #ifdef _WIN32 #include #include #define strdup _strdup #define open _open #ifndef O_BINARY #define O_BINARY _O_BINARY #endif #define close _close #define unlink _unlink #define lseek _lseeki64 #define read _read #define write _write #else #include #include #define _aligned_free free #include #include #include #ifndef O_BINARY #define O_BINARY 0 #endif #endif #include #include #include #include #include "libvhd.h" #include "relative-path.h" #ifndef O_DIRECT #define O_DIRECT 0 #endif static int libvhd_dbg = 0; /* Extension to Xen. Interactive utilities really shouldn't send * errors to syslog. Set log_level to -1 for them to go to stderr. * Existing callers will still go to syslog, as they all seem to use (1). * * On systems without syslog, stderr is the only choice. */ void libvhd_set_log_level(int level) { if (level) libvhd_dbg = level; } #define FN __func__ #define VHDLOG(a) vhd_log_error a static void vhd_log_error( const char *func, const char *fmt, ... ) #ifdef __GNUC__ __attribute__((format(printf,2,3))); #else ; #endif #ifndef LIBVHD_HAS_SYSLOG #ifdef _WIN32 #define LIBVHD_HAS_SYSLOG 0 #else #define LIBVHD_HAS_SYSLOG 1 #endif #endif static void vhd_log_error( const char *func, const char *fmt, ... ) { char *buf, nilbuf; size_t ilen, len; va_list ap; if( !libvhd_dbg ) return; ilen = sizeof( "libvhd::%s: " ) + strlen( func ) -2; va_start(ap, fmt ); len = vsnprintf( &nilbuf, 1, fmt, ap ); va_end( ap ); if( (buf = malloc( ilen + len + 1 )) == NULL ) { if( !LIBVHD_HAS_SYSLOG || libvhd_dbg < 0 ) fprintf( stderr, "libvhd::%s: Out of memory for %s\n", func, fmt ); #if LIBVHD_HAS_SYSLOG if( libvhd_dbg > 0 ) syslog( LOG_INFO, "libvhd::%s:: Out of memory", func ); #endif return; } va_start(ap, fmt); (void) snprintf( buf, ilen, "libvhd::%s: ", func ); (void) vsnprintf( buf + ilen -1, len+1, fmt, ap ); va_end( ap ); len += ilen -1; if( buf[ len -1 ] != '\n' ) buf[len++] = '\n'; buf[len] = '\0'; if( !LIBVHD_HAS_SYSLOG || libvhd_dbg < 0 ) fputs( buf, stderr ); #if LIBVHD_HAS_SYSLOG if( libvhd_dbg > 0 ) syslog(LOG_INFO, buf); #endif free( buf ); return; } #ifdef _WIN32 LIBVHD_API char *dirname( char *path ) { static char buf[MAX_PATH + 1]; char *p, *d; if( path == NULL || !*path ) return "."; strlcpy( buf, path, sizeof( buf ) ); for( d = buf; *d && *d != ':' && *d != '/'; d++ ) ; if( !*d ) return "."; if( *d == ':' ) { d++; if( !strcmp( d, "." ) ) return buf; } for( p = d + strlen( d ) - 1; p > d; p-- ) if( *p != '/' ) break; if( p <= d ) { *d++ = '/'; *d = '\0'; return buf; } *p = '\0'; if( (p = strrchr( d, '/' )) == NULL ) { *d++ = '.'; *d = '\0'; return buf; } if( p == d ) { *p++ = '/'; *p = '\0'; } else *p = '\0'; return buf; } LIBVHD_API char *basename( char *path ) { static char buf[MAX_PATH + 1]; char *p, *d; if( path == NULL || !*path ) return "."; strlcpy( buf, path, sizeof( buf ) ); for( d = buf; *d && *d != ':' && *d != '/'; d++ ) ; if( !*d ) return buf; if( *d == ':' ) { d++; if( !strcmp( d, "." ) ) return buf; } for( p = d + strlen( d ) - 1; p > d; p-- ) if( *p == '/' ) *p = '\0'; else break; if( p <= d ) { if( !*++d ) return "/"; return d; } if( (p = strrchr( d, '/' )) == NULL ) { return d; } return ++p; } /* Partial simulation of iconv * The usage here is restricted, always is just * one string, so we don't have to worry about * state. We do handle conversions among ASCII, * UTF-8 and UTF-16. ASCII is defined as Latin-1. */ typedef void *iconv_t; enum iconvcs { icc_ascii, icc_utf16, icc_utf16be, icc_utf16le, icc_utf8 }; typedef struct iconvin { enum iconvcs toset; enum iconvcs fromset; } iconvin_t; static int iconv_close( iconv_t cd ) { if( cd != (iconv_t)-1 ) free( cd ); return 0; } static enum iconvcs iconvcs( const char *setname ) { if( !strcmp( setname, "ASCII" ) ) return icc_ascii; if( !strcmp( setname, "UTF-16" ) ) return icc_utf16; if( !strcmp( setname, "UTF-16BE" )) return icc_utf16be; if( !strcmp( setname, "UTF-16LE" )) return icc_utf16le; if( !strcmp( setname, "UTF-8" )) return icc_utf8; abort(); } static iconv_t iconv_open( const char *toset, const char *fromset ) { iconvin_t *ict; if( (ict = (iconvin_t *)malloc( sizeof( iconvin_t ) )) == NULL ) return (iconv_t)-1; ict->toset = iconvcs( toset ); ict->fromset = iconvcs( fromset ); return (iconv_t)ict; } static size_t iconv( iconv_t cd, const char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft ) { iconvin_t *ict; size_t nonrev = 0; const char *ip; char *op; enum iconvcs ics; ict = (iconvin_t *)cd; if( (inbuf == NULL || *inbuf == NULL) && (outbuf == NULL || *outbuf == NULL) ) return 0; /* reset */ if( (inbuf == NULL || *inbuf == NULL) && (outbuf == NULL && *outbuf == NULL) ) abort(); /* output shift */ if( inbuf == NULL || *inbuf == NULL || outbuf == NULL || *outbuf == NULL ) abort(); /* undefined */ ip = *inbuf; op = *outbuf; ics = ict->fromset; while( *inbytesleft && *outbytesleft ) { size_t c, c2; switch( ics ) { case icc_ascii: c = *ip++; --*inbytesleft; break; case icc_utf8: c = *ip++; --*inbytesleft; if( !(c & 0x80) ) break; if( !*inbytesleft ) { *inbuf = ip - 1; *inbytesleft = 1; *outbuf = op; errno = EINVAL; return (size_t)-1; } c2 = *ip++; --*inbytesleft; if( (c & 0xe0) == 0xc0 ) { c = ((c & 0x1f) << 6) | (c2 & 0x3f); break; } if( !*inbytesleft ) { *inbuf = ip - 2; *inbytesleft = 2; *outbuf = op; errno = EINVAL; return (size_t)-1; } if( (c & 0xf0) == 0xe0 ) { c = ((c & 0xf) << 12) | ((c2 & 0x3f) << 6) | (*ip++ & 0x3f); --*inbytesleft; break; } if( *inbytesleft < 2 ) { *inbuf = ip - 2; *inbytesleft += 2; *outbuf = op; errno = EINVAL; return (size_t)-1; } if( (c & 0xf8) == 0xf0 ) { c = ((c & 0x7) << 18) | ((c2 & 0x3f) << 12) | ((*ip++ & 0x3f) << 6); c |= *ip++ & 0x3f; *inbytesleft -= 2; break; } *inbuf = ip-2; *inbytesleft += 2; *outbuf = op; errno = EINVAL; return (size_t)-1; case icc_utf16: if( *inbytesleft < 2 ) { *inbuf = ip; *outbuf = op; errno = EINVAL; return (size_t)-1; } *inbytesleft -= 2; ip += 2; if( ip[-2] == 0xfe && ip[-1] == 0xff ) { ics = icc_utf16be; continue; } if( ip[-2] == 0xff && ip[-1] == 0xfe ) { ics = icc_utf16le; continue; } c = (ip[-2] << 8) | ip[-1]; if( c < 0xd800 || c >= 0xe000 ) break; if( *inbytesleft < 2 ) { *inbuf = ip - 2; *outbuf = op; *inbytesleft += 2; errno = EILSEQ; return (size_t)-1; } *inbytesleft -= 2; ip += 2; c2 = (ip[-2] << 8) | ip[-1]; if( c2 < 0xd800 || c2 >= 0xe000 ) { *inbuf = ip - 4; *outbuf = op; *inbytesleft += 4; errno = EILSEQ; return (size_t)-1; } c = (c << 10) + c2 + (0x10000 - (0xD800 << 10) - 0xDC00); break; case icc_utf16be: if( *inbytesleft < 2 ) { *inbuf = ip; *outbuf = op; errno = EINVAL; return (size_t)-1; } *inbytesleft -= 2; ip += 2; c = (ip[-2] << 8) | ip[-1]; if( c < 0xd800 || c >= 0xe000 ) break; if( *inbytesleft < 2 ) { *inbuf = ip - 2; *outbuf = op; *inbytesleft += 2; errno = EILSEQ; return (size_t)-1; } *inbytesleft -= 2; ip += 2; c2 = (ip[-2] << 8) | ip[-1]; if( c2 < 0xd800 || c2 >= 0xe000 ) { *inbuf = ip - 4; *outbuf = op; *inbytesleft += 4; errno = EILSEQ; return (size_t)-1; } c = (c << 10) + c2 + (0x10000 - (0xD800 << 10) - 0xDC00); break; case icc_utf16le: if( *inbytesleft < 2 ) { *inbuf = ip; *outbuf = op; errno = EINVAL; return (size_t)-1; } *inbytesleft -= 2; ip += 2; c = (ip[-1] << 8) | ip[-2]; if( c < 0xd800 || c >= 0xe000 ) break; if( *inbytesleft < 2 ) { *inbuf = ip - 2; *outbuf = op; *inbytesleft += 2; errno = EILSEQ; return (size_t)-1; } *inbytesleft -= 2; ip += 2; c2 = (ip[-1] << 8) | ip[-2]; if( c2 < 0xd800 || c2 >= 0xe000 ) { *inbuf = ip - 4; *outbuf = op; *inbytesleft += 4; errno = EILSEQ; return (size_t)-1; } c = (c << 10) + c2 + (0x10000 - (0xD800 << 10) - 0xDC00); break; default: abort(); } switch( ict->toset ) { case icc_ascii: *op++ = c & 0xff; --*outbytesleft; if( c > 0xff ) nonrev++; continue; case icc_utf8: if( c <= 0x7F ) { *op++ = c & 0x7f; --*outbytesleft; continue; } if( c <= 0x7ff ) { if( *outbytesleft < 2 ) break; *op++ = (((c >> 6) & 0x1f) | 0xc0) & 0xff; *op++ = ((c & 0x3f) | 0xc0) & 0xff; *outbytesleft -= 2; continue; } if( c <= 0xffff ) { if( *outbytesleft < 3 ) break; *op++ = (((c >> 12) & 0xf) | 0xe0) & 0xff; *op++ = (((c >> 6) & 0x3f) | 0xc0) & 0xff; *op++ = ((c & 0x3f) | 0xc0) & 0xff; *outbytesleft -= 3; continue; } if( c <= 0x1fffff ) { if( *outbytesleft < 4 ) break; *op++ = (((c >> 18) & 0x7) | 0xf0) & 0xff; *op++ = (((c >> 12) & 0x3f) | 0xc0) & 0xff; *op++ = (((c >> 6) & 0x3f) | 0xc0) & 0xff; *op++ = ((c & 0x3f) | 0xc0) & 0xff; *outbytesleft -= 4; continue; } break; case icc_utf16: if( *outbytesleft < 2 ) break; if( c < 0x10000 ) { *op++ = ( c >> 8 ) & 0xff; *op++ = c & 0xff; *outbytesleft -= 2; continue; } if( *outbytesleft < 4 ) break; c2 = 0xDC00 + (c & 0x3FF); c = (0xD800 - (0x10000 >> 10)) + (c >> 10); op[0] = (c >> 8) & 0xff; op[1] = c & 0xff; op[2] = (c2 >> 8) & 0xff; op[3] = c2 & 0xff; op += 4; *outbytesleft -=4; continue; case icc_utf16be: if( *outbytesleft < 2 ) break; if( c < 0x10000 ) { *op++ = ( c >> 8 ) & 0xff; *op++ = c & 0xff; *outbytesleft -= 2; continue; } if( *outbytesleft < 4 ) break; c2 = 0xDC00 + (c & 0x3FF); c = (0xD800 - (0x10000 >> 10)) + (c >> 10); op[0] = (c >> 8) & 0xff; op[1] = c & 0xff; op[2] = (c2 >> 8) & 0xff; op[3] = c2 & 0xff; op += 4; *outbytesleft -=4; continue; case icc_utf16le: if( *outbytesleft < 2 ) break; if( c < 0x10000 ) { *op++ = c & 0xff; *op++ = (c >> 8) & 0xff; *outbytesleft -= 2; continue; } if( *outbytesleft < 4 ) break; c2 = 0xDC00 + (c & 0x3FF); c = (0xD800 - (0x10000 >> 10)) + (c >> 10); op[1] = (c >> 8) & 0xff; op[0] = c & 0xff; op[3] = (c2 >> 8) & 0xff; op[2] = c2 & 0xff; op += 4; *outbytesleft -=4; continue; default: abort(); } errno = E2BIG; return (size_t)-1; } *inbuf = ip; *outbuf = op; return nonrev; } #endif #define BIT_MASK 0x80 #ifdef ENABLE_FAILURE_TESTING LIBVHD_API const char* ENV_VAR_FAIL[NUM_FAIL_TESTS] = { "VHD_UTIL_TEST_FAIL_REPARENT_BEGIN", "VHD_UTIL_TEST_FAIL_REPARENT_LOCATOR", "VHD_UTIL_TEST_FAIL_REPARENT_END", "VHD_UTIL_TEST_FAIL_RESIZE_BEGIN", "VHD_UTIL_TEST_FAIL_RESIZE_DATA_MOVED", "VHD_UTIL_TEST_FAIL_RESIZE_METADATA_MOVED", "VHD_UTIL_TEST_FAIL_RESIZE_END" }; LIBVHD_API int TEST_FAIL[NUM_FAIL_TESTS]; #endif /* ENABLE_FAILURE_TESTING */ static inline int test_bit (volatile char *addr, int nr) { return ((addr[nr >> 3] << (nr & 7)) & BIT_MASK) != 0; } static inline void set_bit (volatile char *addr, int nr) { addr[nr >> 3] |= (BIT_MASK >> (nr & 7)); } static inline void clear_bit (volatile char *addr, int nr) { addr[nr >> 3] &= ~(BIT_MASK >> (nr & 7)); } static inline int old_test_bit(volatile char *addr, int nr) { return (((uint32_t *)addr)[nr >> 5] >> (nr & 31)) & 1; } static inline void old_set_bit(volatile char *addr, int nr) { ((uint32_t *)addr)[nr >> 5] |= (1 << (nr & 31)); } static inline void old_clear_bit(volatile char *addr, int nr) { ((uint32_t *)addr)[nr >> 5] &= ~(1 << (nr & 31)); } void vhd_footer_in(vhd_footer_t *footer) { BE32_IN(&footer->features); BE32_IN(&footer->ff_version); BE64_IN(&footer->data_offset); BE32_IN(&footer->timestamp); BE32_IN(&footer->crtr_ver); BE32_IN(&footer->crtr_os); BE64_IN(&footer->orig_size); BE64_IN(&footer->curr_size); BE32_IN(&footer->geometry); BE32_IN(&footer->type); BE32_IN(&footer->checksum); } void vhd_footer_out(vhd_footer_t *footer) { BE32_OUT(&footer->features); BE32_OUT(&footer->ff_version); BE64_OUT(&footer->data_offset); BE32_OUT(&footer->timestamp); BE32_OUT(&footer->crtr_ver); BE32_OUT(&footer->crtr_os); BE64_OUT(&footer->orig_size); BE64_OUT(&footer->curr_size); BE32_OUT(&footer->geometry); BE32_OUT(&footer->type); BE32_OUT(&footer->checksum); } void vhd_header_in(vhd_header_t *header) { int i, n; BE64_IN(&header->data_offset); BE64_IN(&header->table_offset); BE32_IN(&header->hdr_ver); BE32_IN(&header->max_bat_size); BE32_IN(&header->block_size); BE32_IN(&header->checksum); BE32_IN(&header->prt_ts); n = sizeof(header->loc) / sizeof(vhd_parent_locator_t); for (i = 0; i < n; i++) { BE32_IN(&header->loc[i].code); BE32_IN(&header->loc[i].data_space); BE32_IN(&header->loc[i].data_len); BE64_IN(&header->loc[i].data_offset); } } void vhd_header_out(vhd_header_t *header) { int i, n; BE64_OUT(&header->data_offset); BE64_OUT(&header->table_offset); BE32_OUT(&header->hdr_ver); BE32_OUT(&header->max_bat_size); BE32_OUT(&header->block_size); BE32_OUT(&header->checksum); BE32_OUT(&header->prt_ts); n = sizeof(header->loc) / sizeof(vhd_parent_locator_t); for (i = 0; i < n; i++) { BE32_OUT(&header->loc[i].code); BE32_OUT(&header->loc[i].data_space); BE32_OUT(&header->loc[i].data_len); BE64_OUT(&header->loc[i].data_offset); } } void vhd_batmap_header_in(vhd_batmap_t *batmap) { BE64_IN(&batmap->header.batmap_offset); BE32_IN(&batmap->header.batmap_size); BE32_IN(&batmap->header.batmap_version); BE32_IN(&batmap->header.checksum); } void vhd_batmap_header_out(vhd_batmap_t *batmap) { BE64_OUT(&batmap->header.batmap_offset); BE32_OUT(&batmap->header.batmap_size); BE32_OUT(&batmap->header.batmap_version); BE32_OUT(&batmap->header.checksum); } void vhd_bat_in(vhd_bat_t *bat) { unsigned int i; for (i = 0; i < bat->entries; i++) BE32_IN(&bat->bat[i]); } void vhd_bat_out(vhd_bat_t *bat) { unsigned int i; for (i = 0; i < bat->entries; i++) BE32_OUT(&bat->bat[i]); } uint32_t vhd_checksum_footer(vhd_footer_t *footer) { size_t i; unsigned char *blob; uint32_t checksum, tmp; checksum = 0; tmp = footer->checksum; footer->checksum = 0; blob = (unsigned char *)footer; for (i = 0; i < sizeof(vhd_footer_t); i++) checksum += (uint32_t)blob[i]; footer->checksum = tmp; return ~checksum; } int vhd_validate_footer(vhd_footer_t *footer) { int csize; uint32_t checksum; csize = sizeof(footer->cookie); if (memcmp(footer->cookie, HD_COOKIE, csize) != 0 && memcmp(footer->cookie, VHD_POISON_COOKIE, csize) != 0) { char buf[9]; strncpy(buf, footer->cookie, sizeof(buf)); buf[sizeof(buf)-1]= '\0'; VHDLOG((FN,"invalid footer cookie: %s\n", buf)); return -EINVAL; } checksum = vhd_checksum_footer(footer); if (checksum != footer->checksum) { /* * early td-util did not re-calculate * checksum when marking vhds 'hidden' */ if (footer->hidden && !strncmp(footer->crtr_app, "tap", 3) && (footer->crtr_ver == VHD_VERSION(0, 1) || footer->crtr_ver == VHD_VERSION(1, 1))) { char tmp = footer->hidden; footer->hidden = 0; checksum = vhd_checksum_footer(footer); footer->hidden = tmp; if (checksum == footer->checksum) return 0; } VHDLOG((FN,"invalid footer checksum: " "footer = 0x%08x, calculated = 0x%08x\n", footer->checksum, checksum)); return -EINVAL; } return 0; } uint32_t vhd_checksum_header(vhd_header_t *header) { size_t i; unsigned char *blob; uint32_t checksum, tmp; checksum = 0; tmp = header->checksum; header->checksum = 0; blob = (unsigned char *)header; for (i = 0; i < sizeof(vhd_header_t); i++) checksum += (uint32_t)blob[i]; header->checksum = tmp; return ~checksum; } int vhd_validate_header(vhd_header_t *header) { int i, n; uint32_t checksum; if (memcmp(header->cookie, DD_COOKIE, 8) != 0) { char buf[9]; strncpy(buf, header->cookie, sizeof(buf)); buf[sizeof(buf)-1]= '\0'; VHDLOG((FN,"invalid header cookie: %s\n", buf)); return -EINVAL; } if (header->hdr_ver != 0x00010000) { VHDLOG((FN,"invalid header version 0x%08x\n", header->hdr_ver)); return -EINVAL; } if (header->data_offset != 0xFFFFFFFFFFFFFFFF) { VHDLOG((FN,"invalid header data_offset 0x%016"PRIx64"\n", header->data_offset)); return -EINVAL; } n = sizeof(header->loc) / sizeof(vhd_parent_locator_t); for (i = 0; i < n; i++) if (vhd_validate_platform_code(header->loc[i].code)) return -EINVAL; checksum = vhd_checksum_header(header); if (checksum != header->checksum) { VHDLOG((FN,"invalid header checksum: " "header = 0x%08x, calculated = 0x%08x\n", header->checksum, checksum)); return -EINVAL; } return 0; } static inline int vhd_validate_bat(vhd_bat_t *bat) { if (!bat->bat) return -EINVAL; return 0; } uint32_t vhd_checksum_batmap(vhd_batmap_t *batmap) { u32 i, n; char *blob; uint32_t checksum; blob = batmap->map; checksum = 0; n = (u32) vhd_sectors_to_bytes(batmap->header.batmap_size); for (i = 0; i < n; i++) { if (batmap->header.batmap_version == VHD_BATMAP_VERSION(1, 1)) checksum += (uint32_t)blob[i]; else checksum += (uint32_t)(unsigned char)blob[i]; } return ~checksum; } int vhd_validate_batmap_header(vhd_batmap_t *batmap) { if (memcmp(batmap->header.cookie, VHD_BATMAP_COOKIE, 8)) return -EINVAL; if (batmap->header.batmap_version > VHD_BATMAP_CURRENT_VERSION) return -EINVAL; return 0; } int vhd_validate_batmap(vhd_batmap_t *batmap) { uint32_t checksum; if (!batmap->map) return -EINVAL; checksum = vhd_checksum_batmap(batmap); if (checksum != batmap->header.checksum) return -EINVAL; return 0; } int vhd_batmap_header_offset(vhd_context_t *ctx, off_t *_off) { off_t off; size_t bat; *_off = 0; off = (off_t)ctx->header.table_offset; bat = ctx->header.max_bat_size * sizeof(uint32_t); off += (off_t)vhd_bytes_padded(bat); *_off = off; return 0; } int vhd_validate_platform_code(uint32_t code) { switch (code) { case PLAT_CODE_NONE: case PLAT_CODE_WI2R: case PLAT_CODE_WI2K: case PLAT_CODE_W2RU: case PLAT_CODE_W2KU: case PLAT_CODE_MAC: case PLAT_CODE_MACX: return 0; default: VHDLOG((FN,"invalid parent locator code %u\n", code)); return -EINVAL; } } int vhd_parent_locator_count(vhd_context_t *ctx) { return (sizeof(ctx->header.loc) / sizeof(vhd_parent_locator_t)); } int vhd_hidden(vhd_context_t *ctx, int *hidden) { int err; *hidden = 0; if (vhd_type_dynamic(ctx) && vhd_creator_tapdisk(ctx) && (ctx->footer.crtr_ver == VHD_VERSION(0, 1) || ctx->footer.crtr_ver == VHD_VERSION(1, 1))) { vhd_footer_t copy; err = vhd_read_footer_at(ctx, ©, 0); if (err) { VHDLOG((FN,"error reading backup footer of %s: %d\n", ctx->file, err)); return err; } *hidden = copy.hidden; } else *hidden = ctx->footer.hidden; return 0; } int vhd_chain_depth(vhd_context_t *ctx, int *depth) { char *file; int err, cnt; vhd_context_t vhd, *cur; err = 0; cnt = 0; *depth = 0; file = NULL; cur = ctx; for (;;) { cnt++; if (cur->footer.type != HD_TYPE_DIFF) break; if (vhd_parent_raw(cur)) { cnt++; break; } err = vhd_parent_locator_get(cur, &file); if (err) { file = NULL; break; } if (cur != ctx) { vhd_close(cur); cur = NULL; } err = vhd_open(&vhd, file, VHD_OPEN_RDONLY); if (err) break; cur = &vhd; free(file); file = NULL; } free(file); if (cur && cur != ctx) vhd_close(cur); if (!err) *depth = cnt; return err; } int vhd_batmap_test(vhd_context_t *ctx, vhd_batmap_t *batmap, uint32_t block) { if (!vhd_has_batmap(ctx) || !batmap->map) return 0; if (block >= (batmap->header.batmap_size << (VHD_SECTOR_SHIFT + 3))) return 0; return test_bit(batmap->map, block); } void vhd_batmap_set(vhd_context_t *ctx, vhd_batmap_t *batmap, uint32_t block) { if (!vhd_has_batmap(ctx) || !batmap->map) return; if (block >= (batmap->header.batmap_size << (VHD_SECTOR_SHIFT + 3))) return; set_bit(batmap->map, block); } void vhd_batmap_clear(vhd_context_t *ctx, vhd_batmap_t *batmap, uint32_t block) { if (!vhd_has_batmap(ctx) || !batmap->map) return; if (block >= (batmap->header.batmap_size << (VHD_SECTOR_SHIFT + 3))) return; clear_bit(batmap->map, block); } int vhd_bitmap_test(vhd_context_t *ctx, char *map, uint32_t block) { if (vhd_creator_tapdisk(ctx) && ctx->footer.crtr_ver == 0x00000001) return old_test_bit(map, block); return test_bit(map, block); } void vhd_bitmap_set(vhd_context_t *ctx, char *map, uint32_t block) { if( vhd_creator_tapdisk( ctx ) && ctx->footer.crtr_ver == 0x00000001 ) { old_set_bit( map, block ); return; } set_bit(map, block); return; } void vhd_bitmap_clear(vhd_context_t *ctx, char *map, uint32_t block) { if( vhd_creator_tapdisk( ctx ) && ctx->footer.crtr_ver == 0x00000001 ) { old_clear_bit( map, block ); return; } clear_bit(map, block); return; } /* * returns absolute offset of the first * byte of the file which is not vhd metadata */ int vhd_end_of_headers(vhd_context_t *ctx, off_t *end) { int err, i, n; uint32_t bat_bytes; off_t eom, bat_end; vhd_parent_locator_t *loc; *end = 0; if (!vhd_type_dynamic(ctx)) return 0; eom = (off_t)(ctx->footer.data_offset + sizeof(vhd_header_t)); bat_bytes = (uint32_t)vhd_bytes_padded(ctx->header.max_bat_size * sizeof(uint32_t)); bat_end = (off_t)(ctx->header.table_offset + bat_bytes); eom = MAX(eom, bat_end); if (vhd_has_batmap(ctx)) { off_t hdr_end, hdr_secs, map_end, map_secs; err = vhd_get_batmap(ctx); if (err) return err; hdr_secs = secs_round_up_no_zero(sizeof(vhd_batmap_header_t)); err = vhd_batmap_header_offset(ctx, &hdr_end); if (err) return err; hdr_end += (off_t)vhd_sectors_to_bytes(hdr_secs); eom = MAX(eom, hdr_end); map_secs = ctx->batmap.header.batmap_size; map_end = (off_t)(ctx->batmap.header.batmap_offset + vhd_sectors_to_bytes(map_secs)); eom = MAX(eom, map_end); } /* parent locators */ n = sizeof(ctx->header.loc) / sizeof(vhd_parent_locator_t); for (i = 0; i < n; i++) { off_t loc_end; loc = &ctx->header.loc[i]; if (loc->code == PLAT_CODE_NONE) continue; loc_end = (off_t)(loc->data_offset + vhd_parent_locator_size(loc)); eom = MAX(eom, loc_end); } *end = eom; return 0; } int vhd_end_of_data(vhd_context_t *ctx, off_t *end) { unsigned int i; int err; off_t max; uint64_t blk; if (!vhd_type_dynamic(ctx)) { err = vhd_seek(ctx, 0, SEEK_END); if (err) return err; max = vhd_position(ctx); if (max == (off_t)-1) return -errno; *end = max - sizeof(vhd_footer_t); return 0; } err = vhd_end_of_headers(ctx, &max); if (err) return err; err = vhd_get_bat(ctx); if (err) return err; max >>= VHD_SECTOR_SHIFT; for (i = 0; i < ctx->bat.entries; i++) { blk = ctx->bat.bat[i]; if (blk != DD_BLK_UNUSED) { blk += ctx->spb + ctx->bm_secs; max = MAX((off_t)blk, max); } } *end = (off_t)vhd_sectors_to_bytes(max); return 0; } uint32_t vhd_time(time_t time) { struct tm tm; time_t micro_epoch; memset(&tm, 0, sizeof(struct tm)); tm.tm_year = 100; tm.tm_mon = 0; tm.tm_mday = 1; micro_epoch = mktime(&tm); return (uint32_t)(time - micro_epoch); } /* * Stringify the VHD timestamp for printing. * As with ctime_r, target must be >=26 bytes. */ size_t vhd_time_to_string(uint32_t timestamp, char *target) { char *cr; struct tm tm; time_t t1, t2; #ifdef _WIN32 __time32_t t3; #endif memset(&tm, 0, sizeof(struct tm)); /* VHD uses an epoch of 12:00AM, Jan 1, 2000. */ /* Need to adjust this to the expected epoch of 1970. */ tm.tm_year = 100; tm.tm_mon = 0; tm.tm_mday = 1; t1 = mktime(&tm); t2 = t1 + (time_t)timestamp; #ifdef _WIN32 t3 = (__time32_t)t2; _ctime32_s( target, 26, &t3 ); #else ctime_r(&t2, target); #endif /* handle mad ctime_r newline appending. */ if ((cr = strchr(target, '\n')) != NULL) *cr = '\0'; return (strlen(target)); } /* * nabbed from vhd specs. */ uint32_t vhd_chs(uint64_t size) { uint32_t secs, cylinders, heads, spt, cth; secs = secs_round_up_no_zero(size); if (secs > 65535 * 16 * 255) secs = 65535 * 16 * 255; if (secs >= 65535 * 16 * 63) { spt = 255; cth = secs / spt; heads = 16; } else { spt = 17; cth = secs / spt; heads = (cth + 1023) / 1024; if (heads < 4) heads = 4; if (cth >= (heads * 1024) || heads > 16) { spt = 31; cth = secs / spt; heads = 16; } if (cth >= heads * 1024) { spt = 63; cth = secs / spt; heads = 16; } } cylinders = cth / heads; return GEOM_ENCODE(cylinders, heads, spt); } int vhd_get_footer(vhd_context_t *ctx) { if (!vhd_validate_footer(&ctx->footer)) return 0; return vhd_read_footer(ctx, &ctx->footer); } int vhd_get_header(vhd_context_t *ctx) { if (!vhd_type_dynamic(ctx)) return -EINVAL; if (!vhd_validate_header(&ctx->header)) return 0; return vhd_read_header(ctx, &ctx->header); } int vhd_get_bat(vhd_context_t *ctx) { if (!vhd_type_dynamic(ctx)) return -EINVAL; if (!vhd_validate_bat(&ctx->bat)) return 0; vhd_put_bat(ctx); return vhd_read_bat(ctx, &ctx->bat); } int vhd_get_batmap(vhd_context_t *ctx) { if (!vhd_has_batmap(ctx)) return -EINVAL; if (!vhd_validate_batmap(&ctx->batmap)) return 0; vhd_put_batmap(ctx); return vhd_read_batmap(ctx, &ctx->batmap); } void vhd_put_footer(vhd_context_t *ctx) { memset(&ctx->footer, 0, sizeof(vhd_footer_t)); } void vhd_put_header(vhd_context_t *ctx) { memset(&ctx->header, 0, sizeof(vhd_header_t)); } void vhd_put_bat(vhd_context_t *ctx) { if (!vhd_type_dynamic(ctx)) return; _aligned_free(ctx->bat.bat); memset(&ctx->bat, 0, sizeof(vhd_bat_t)); } void vhd_put_batmap(vhd_context_t *ctx) { if (!vhd_type_dynamic(ctx)) return; if (!vhd_has_batmap(ctx)) return; _aligned_free(ctx->batmap.map); memset(&ctx->batmap, 0, sizeof(vhd_batmap_t)); } /* * look for 511 byte footer at end of file */ int vhd_read_short_footer(vhd_context_t *ctx, vhd_footer_t *footer) { int err; char *buf; off_t eof; buf = NULL; err = vhd_seek(ctx, 0, SEEK_END); if (err) goto out; eof = vhd_position(ctx); if (eof == (off_t)-1) { err = -errno; goto out; } err = vhd_seek(ctx, eof - 511, SEEK_SET); if (err) goto out; #ifdef _WIN32 buf = _aligned_malloc( sizeof(vhd_footer_t), VHD_SECTOR_SIZE ); if( buf == NULL ) { err = -errno; goto out; } #else err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, sizeof(vhd_footer_t)); if (err) { buf = NULL; err = -err; goto out; } #endif memset(buf, 0, sizeof(vhd_footer_t)); /* * expecting short read here */ vhd_read(ctx, buf, sizeof(vhd_footer_t)); memcpy(footer, buf, sizeof(vhd_footer_t)); vhd_footer_in(footer); err = vhd_validate_footer(footer); out: if (err) VHDLOG((FN,"%s: failed reading short footer: %d\n", ctx->file, err)); _aligned_free(buf); return err; } int vhd_read_footer_at(vhd_context_t *ctx, vhd_footer_t *footer, off_t off) { int err; char *buf; buf = NULL; err = vhd_seek(ctx, off, SEEK_SET); if (err) goto out; #ifdef _WIN32 buf = _aligned_malloc( sizeof(vhd_footer_t), VHD_SECTOR_SIZE ); if( buf == NULL ) { err = -errno; goto out; } #else err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, sizeof(vhd_footer_t)); if (err) { buf = NULL; err = -err; goto out; } #endif err = vhd_read(ctx, buf, sizeof(vhd_footer_t)); if (err) goto out; memcpy(footer, buf, sizeof(vhd_footer_t)); vhd_footer_in(footer); err = vhd_validate_footer(footer); out: if (err) VHDLOG((FN,"%s: reading footer at 0x%08"PRIx64" failed: %d\n", ctx->file, off, err)); _aligned_free(buf); return err; } int vhd_read_footer(vhd_context_t *ctx, vhd_footer_t *footer) { int err; off_t off; err = vhd_seek(ctx, 0, SEEK_END); if (err) return err; off = vhd_position(ctx); if (off == (off_t)-1) return -errno; err = vhd_read_footer_at(ctx, footer, off - 512); if (err != -EINVAL) return err; err = vhd_read_short_footer(ctx, footer); if (err != -EINVAL) return err; if (ctx->oflags & VHD_OPEN_STRICT) return -EINVAL; return vhd_read_footer_at(ctx, footer, 0); } int vhd_read_header_at(vhd_context_t *ctx, vhd_header_t *header, off_t off) { int err; char *buf; buf = NULL; if (!vhd_type_dynamic(ctx)) { err = -EINVAL; goto out; } err = vhd_seek(ctx, off, SEEK_SET); if (err) goto out; #ifdef _WIN32 buf = _aligned_malloc( sizeof(vhd_header_t), VHD_SECTOR_SIZE ); if( buf == NULL ) { err = -errno; goto out; } #else err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, sizeof(vhd_header_t)); if (err) { buf = NULL; err = -err; goto out; } #endif err = vhd_read(ctx, buf, sizeof(vhd_header_t)); if (err) goto out; memcpy(header, buf, sizeof(vhd_header_t)); vhd_header_in(header); err = vhd_validate_header(header); out: if (err) VHDLOG((FN,"%s: reading header at 0x%08"PRIx64" failed: %d\n", ctx->file, off, err)); _aligned_free(buf); return err; } int vhd_read_header(vhd_context_t *ctx, vhd_header_t *header) { off_t off; if (!vhd_type_dynamic(ctx)) { VHDLOG((FN,"%s is not dynamic!\n", ctx->file)); return -EINVAL; } off = (off_t)ctx->footer.data_offset; return vhd_read_header_at(ctx, header, off); } int vhd_read_bat(vhd_context_t *ctx, vhd_bat_t *bat) { int err; char *buf; off_t off; size_t size; buf = NULL; if (!vhd_type_dynamic(ctx)) { err = -EINVAL; goto fail; } off = (off_t)ctx->header.table_offset; size = (size_t)vhd_bytes_padded(ctx->header.max_bat_size * sizeof(uint32_t)); #ifdef _WIN32 buf = _aligned_malloc( size, VHD_SECTOR_SIZE ); if( buf == NULL ) { err = -errno; goto fail; } #else err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); if (err) { buf = NULL; err = -err; goto fail; } #endif err = vhd_seek(ctx, off, SEEK_SET); if (err) goto fail; err = vhd_read(ctx, buf, size); if (err) goto fail; bat->spb = ctx->header.block_size >> VHD_SECTOR_SHIFT; bat->entries = ctx->header.max_bat_size; bat->bat = (uint32_t *)buf; vhd_bat_in(bat); return 0; fail: _aligned_free(buf); memset(bat, 0, sizeof(vhd_bat_t)); VHDLOG((FN,"%s: failed to read bat: %d\n", ctx->file, err)); return err; } static int vhd_read_batmap_header(vhd_context_t *ctx, vhd_batmap_t *batmap) { int err; char *buf; off_t off; size_t size; buf = NULL; err = vhd_batmap_header_offset(ctx, &off); if (err) goto fail; err = vhd_seek(ctx, off, SEEK_SET); if (err) goto fail; size = (size_t)vhd_bytes_padded(sizeof(vhd_batmap_header_t)); #ifdef _WIN32 buf = _aligned_malloc( size, VHD_SECTOR_SIZE ); if( buf == NULL ) { err = -errno; goto fail; } #else err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); if (err) { buf = NULL; err = -err; goto fail; } #endif err = vhd_read(ctx, buf, size); if (err) goto fail; memcpy(&batmap->header, buf, sizeof(vhd_batmap_header_t)); _aligned_free(buf); buf = NULL; vhd_batmap_header_in(batmap); return 0; fail: _aligned_free(buf); memset(&batmap->header, 0, sizeof(vhd_batmap_header_t)); VHDLOG((FN,"%s: failed to read batmap header: %d\n", ctx->file, err)); return err; } static int vhd_read_batmap_map(vhd_context_t *ctx, vhd_batmap_t *batmap) { int err; char *buf; off_t off; size_t map_size; map_size = (size_t)vhd_sectors_to_bytes(batmap->header.batmap_size); #ifdef _WIN32 buf = _aligned_malloc( map_size, VHD_SECTOR_SIZE ); if( buf == NULL ) { err = -errno; goto fail; } #else err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, map_size); if (err) { buf = NULL; err = -err; goto fail; } #endif off = (off_t)batmap->header.batmap_offset; err = vhd_seek(ctx, off, SEEK_SET); if (err) goto fail; err = vhd_read(ctx, buf, map_size); if (err) goto fail; batmap->map = buf; return 0; fail: _aligned_free(buf); batmap->map = NULL; VHDLOG((FN,"%s: failed to read batmap: %d\n", ctx->file, err)); return err; } int vhd_read_batmap(vhd_context_t *ctx, vhd_batmap_t *batmap) { int err; if (!vhd_has_batmap(ctx)) return -EINVAL; memset(batmap, 0, sizeof(vhd_batmap_t)); err = vhd_read_batmap_header(ctx, batmap); if (err) return err; err = vhd_validate_batmap_header(batmap); if (err) return err; err = vhd_read_batmap_map(ctx, batmap); if (err) return err; err = vhd_validate_batmap(batmap); if (err) goto fail; return 0; fail: _aligned_free(batmap->map); memset(batmap, 0, sizeof(vhd_batmap_t)); return err; } int vhd_has_batmap(vhd_context_t *ctx) { if (!vhd_type_dynamic(ctx)) return 0; if (!vhd_creator_tapdisk(ctx)) return 0; if (ctx->footer.crtr_ver <= VHD_VERSION(0, 1)) return 0; if (ctx->footer.crtr_ver >= VHD_VERSION(1, 2)) return 1; /* * VHDs of version 1.1 probably have a batmap, but may not * if they were updated from version 0.1 via vhd-update. */ if (!vhd_validate_batmap_header(&ctx->batmap)) return 1; if (vhd_read_batmap_header(ctx, &ctx->batmap)) return 0; return (!vhd_validate_batmap_header(&ctx->batmap)); } /* * Is this a block device (with a fixed size)? This affects whether the file * can be truncated and where the footer is written for VHDs. */ int vhd_test_file_fixed(const char *file, int *is_block) { int err; struct stat stats; err = stat(file, &stats); if (err == -1) return -errno; #ifdef _WIN32 *is_block = 0; /* Look at filename for initial \\?? */ #else *is_block = !!(S_ISBLK(stats.st_mode)); #endif return err; } int vhd_find_parent(vhd_context_t *ctx, const char *parent, char **_location) { int err, isabs = 0, isrd = 0; char *location, *cpath, *cdir, *path; err = 0; path = NULL; cpath = NULL; location = NULL; *_location = NULL; if (!parent) return -EINVAL; #ifdef _WIN32 if( parent[0] == '/' ) isabs = 1; else { const char *p; for( p = parent; *p && *p != '/' && *p != ':'; p++ ) ; isabs = ( *p == ':' ); } if( isabs ) { if( (isrd = _open( parent, _O_RDONLY | _O_BINARY )) == -1 ) goto errout; else { _close( isrd ); isrd = 1; } } #else if (parent[0] == '/') { isabs = 1; isrd = !access( parent, R_OK ); } #endif if( isabs ) { if( isrd ) { path = strdup( parent ); if( !path ) return -ENOMEM; *_location = path; return 0; } goto errout; } /* check parent path relative to child's directory */ cpath = realpath(ctx->file, NULL); if (!cpath) goto errout; #ifdef _WIN32 cdir = dirname( cpath + 1 ); #else cdir = dirname( cpath ); #endif if (asprintf(&location, "%s/%s", cdir, parent) == -1) { location = NULL; goto errout; } #ifdef _WIN32 if( (isrd = _open( location, _O_RDONLY | _O_BINARY )) == -1 ) isrd = 0; else { _close( isrd ); isrd = 1; } #else isrd = !access( location, R_OK ); #endif if( isrd ) { path = realpath( location, NULL ); if( path ) { #ifdef _WIN32 *_location = strdup( path + 1 ); free( path ); #else *_location = path; #endif free(cpath); free(location); return 0; } } errout: err = -errno; free(location); free(cpath); return err; } static int vhd_macx_encode_location(char *name, char **out, size_t *outlen) { iconv_t cd; int err; size_t len; size_t ibl, obl; char *uri, *uri_utf8, *uri_utf8p, *ret; const char *urip; err = 0; ret = NULL; *out = NULL; *outlen = 0; len = strlen(name) + strlen("file://"); ibl = len; obl = len * 2; urip = uri = malloc(ibl + 1); uri_utf8 = uri_utf8p = malloc(obl); if (!uri || !uri_utf8) return -ENOMEM; cd = iconv_open("UTF-8", "ASCII"); if (cd == (iconv_t)-1) { err = -errno; goto out; } snprintf(uri, ibl+1, "file://%s", name); if (iconv(cd, #ifdef __linux__ (char **) #endif &urip, &ibl, &uri_utf8p, &obl) == (size_t)-1 || ibl) { err = (errno ? -errno : -EIO); goto out; } len = (size_t)(uri_utf8p - uri_utf8); ret = malloc(len); if (!ret) { err = -ENOMEM; goto out; } memcpy(ret, uri_utf8, len); *outlen = len; *out = ret; out: free(uri); free(uri_utf8); if (cd != (iconv_t)-1) iconv_close(cd); return err; } static int vhd_w2u_encode_location(char *name, char **out, size_t *outlen) { int len, err; size_t ibl, obl; char *uri, *uri_utf16, *uri_utf16p, *tmp, *ret; const char *urip; iconv_t cd; cd = (iconv_t)-1; err = 0; ret = NULL; *out = NULL; *outlen = 0; /* * MICROSOFT_COMPAT * relative paths must start with ".\" */ if (name[0] != '/') { tmp = strstr(name, "./"); if (tmp == name) tmp += strlen("./"); else tmp = name; err = asprintf(&uri, ".\\%s", tmp); } else #ifdef _WIN32 err = asprintf( &uri, "%s", name + 1 ); #else err = asprintf( &uri, "%s", name ); #endif if (err == -1) return -ENOMEM; tmp = uri; while (*tmp != '\0') { if (*tmp == '/') *tmp = '\\'; tmp++; } len = strlen(uri); ibl = len; obl = len * 4; urip = uri; uri_utf16 = uri_utf16p = malloc(obl); if (!uri_utf16) { err = -ENOMEM; goto out; } /* * MICROSOFT_COMPAT * little endian unicode here */ cd = iconv_open("UTF-16LE", "ASCII"); if (cd == (iconv_t)-1) { err = -errno; goto out; } if (iconv(cd, #ifdef __linux__ (char **) #endif &urip, &ibl, &uri_utf16p, &obl) == (size_t)-1 || ibl) { err = (errno ? -errno : -EIO); goto out; } len = (size_t)(uri_utf16p - uri_utf16); ret = malloc(len); if (!ret) { err = -ENOMEM; goto out; } memcpy(ret, uri_utf16, len); *outlen = len; *out = ret; err = 0; out: free(uri); free(uri_utf16); if (cd != (iconv_t)-1) iconv_close(cd); return err; } static char * vhd_macx_decode_location(const char *in, char *out, int len) { iconv_t cd; char *name; size_t ibl, obl; name = out; ibl = obl = len; cd = iconv_open("ASCII", "UTF-8"); if (cd == (iconv_t)-1) return NULL; if (iconv(cd, #ifdef __linux__ (char **) #endif &in, &ibl, &out, &obl) == (size_t)-1 || ibl) return NULL; iconv_close(cd); *out = '\0'; if (strstr(name, "file://") != name) return NULL; name += strlen("file://"); return strdup(name); } static char * vhd_w2u_decode_location(const char *in, char *out, int len, char *utf_type) { iconv_t cd; char *name, *tmp; size_t ibl, obl; tmp = name = out; ibl = obl = len; cd = iconv_open("ASCII", utf_type); if (cd == (iconv_t)-1) return NULL; if (iconv(cd, #ifdef __linux__ (char **) #endif &in, &ibl, &out, &obl) == (size_t)-1 || ibl) { iconv_close(cd); return NULL; } iconv_close(cd); *out = '\0'; /* TODO: spaces */ while (tmp != out) { if (*tmp == '\\') *tmp = '/'; tmp++; } if (strstr(name, "C:") == name || strstr(name, "c:") == name) name += strlen("c:"); return strdup(name); } int vhd_header_decode_parent(vhd_context_t *ctx, vhd_header_t *header, char **buf) { char *code, out[512+1]; if (vhd_creator_tapdisk(ctx) && ctx->footer.crtr_ver == VHD_VERSION(0, 1)) code = UTF_16; else code = UTF_16BE; *buf = vhd_w2u_decode_location(header->prt_name, out, 512, code); return (*buf == NULL ? -EINVAL : 0); } int vhd_parent_locator_read(vhd_context_t *ctx, vhd_parent_locator_t *loc, char **parent) { int err, size; char *raw, *out, *name; raw = NULL; out = NULL; name = NULL; *parent = NULL; if (ctx->footer.type != HD_TYPE_DIFF) { err = -EINVAL; goto out; } switch (loc->code) { case PLAT_CODE_MACX: case PLAT_CODE_W2KU: case PLAT_CODE_W2RU: break; default: err = -EINVAL; goto out; } err = vhd_seek(ctx, (off_t)loc->data_offset, SEEK_SET); if (err) goto out; size = vhd_parent_locator_size(loc); if (size <= 0) { err = -EINVAL; goto out; } #ifdef _WIN32 raw = _aligned_malloc( size, VHD_SECTOR_SIZE ); if( raw == NULL ) { err = -errno; goto out; } #else err = posix_memalign((void **)&raw, VHD_SECTOR_SIZE, size); if (err) { raw = NULL; err = -err; goto out; } #endif err = vhd_read(ctx, raw, size); if (err) goto out; out = malloc(loc->data_len + 1); if (!out) { err = -ENOMEM; goto out; } switch (loc->code) { case PLAT_CODE_MACX: name = vhd_macx_decode_location(raw, out, loc->data_len); break; case PLAT_CODE_W2KU: case PLAT_CODE_W2RU: name = vhd_w2u_decode_location(raw, out, loc->data_len, UTF_16LE); break; } if (!name) { err = -EINVAL; goto out; } err = 0; *parent = name; out: _aligned_free(raw); free(out); if (err) { VHDLOG((FN,"%s: error reading parent locator: %d\n", ctx->file, err)); VHDLOG((FN,"%s: locator: code %u, space 0x%x, len 0x%x, " "off 0x%"PRIx64"\n", ctx->file, loc->code, loc->data_space, loc->data_len, loc->data_offset)); } return err; } int vhd_parent_locator_get(vhd_context_t *ctx, char **parent) { int i, n, err; char *name, *location; vhd_parent_locator_t *loc; err = 0; *parent = NULL; if (ctx->footer.type != HD_TYPE_DIFF) return -EINVAL; n = vhd_parent_locator_count(ctx); for (i = 0; i < n; i++) { loc = ctx->header.loc + i; err = vhd_parent_locator_read(ctx, loc, &name); if (err) continue; err = vhd_find_parent(ctx, name, &location); if (err) VHDLOG((FN,"%s: couldn't find parent %s (%d)\n", ctx->file, name, err)); free(name); if (!err) { *parent = location; return 0; } } return err; } int vhd_parent_locator_write_at(vhd_context_t *ctx, const char *parent, off_t off, uint32_t code, size_t max_bytes, vhd_parent_locator_t *loc) { struct stat stats; int err; size_t size, len; char *absolute_path, *relative_path, *encoded, *block; memset(loc, 0, sizeof(vhd_parent_locator_t)); if (ctx->footer.type != HD_TYPE_DIFF) return -EINVAL; absolute_path = NULL; relative_path = NULL; encoded = NULL; block = NULL; size = 0; len = 0; switch (code) { case PLAT_CODE_MACX: case PLAT_CODE_W2KU: case PLAT_CODE_W2RU: break; default: return -EINVAL; } absolute_path = realpath(parent, NULL); if (!absolute_path) { err = -errno; goto out; } #ifdef _WIN32 err = stat( absolute_path+1, &stats ); #else err = stat( absolute_path, &stats ); #endif if (err) { err = -errno; goto out; } #ifdef _WIN32 /* Look at filename for initial \\ to replace _S_IFBLK?? */ if (!(stats.st_mode & _S_IFREG) || (stats.st_mode & _S_IFDIR)) { err = -EINVAL; goto out; } #else if (!S_ISREG(stats.st_mode) && !S_ISBLK(stats.st_mode)) { err = -EINVAL; goto out; } #endif relative_path = relative_path_to(ctx->file, absolute_path, &err); if (!relative_path || err) { err = (err ? err : -EINVAL); goto out; } switch (code) { case PLAT_CODE_MACX: err = vhd_macx_encode_location(relative_path, &encoded, &len); break; case PLAT_CODE_W2KU: err = vhd_w2u_encode_location( absolute_path, &encoded, &len ); break; case PLAT_CODE_W2RU: err = vhd_w2u_encode_location(relative_path, &encoded, &len); break; default: err = -EINVAL; } if (err) goto out; err = vhd_seek(ctx, off, SEEK_SET); if (err) goto out; size = (size_t)vhd_bytes_padded(len); if (max_bytes && size > max_bytes) { err = -ENAMETOOLONG; goto out; } #ifdef _WIN32 block = _aligned_malloc( size, VHD_SECTOR_SIZE ); if( block == NULL ) { err = -errno; goto out; } #else err = posix_memalign((void **)&block, VHD_SECTOR_SIZE, size); if (err) { block = NULL; err = -err; goto out; } #endif memset(block, 0, size); memcpy(block, encoded, len); err = vhd_write(ctx, block, size); if (err) goto out; err = 0; out: free(absolute_path); free(relative_path); free(encoded); _aligned_free(block); if (!err) { loc->res = 0; loc->code = code; loc->data_len = len; /* * write number of bytes ('size') instead of number of sectors * into loc->data_space to be compatible with MSFT, even though * this goes against the specs */ loc->data_space = size; loc->data_offset = off; } return err; } static int vhd_footer_offset_at_eof(vhd_context_t *ctx, off_t *off) { int err; if ((err = vhd_seek(ctx, 0, SEEK_END))) return errno; *off = vhd_position(ctx) - sizeof(vhd_footer_t); return 0; } int vhd_read_bitmap(vhd_context_t *ctx, uint32_t block, char **bufp) { int err; char *buf; size_t size; off_t off; uint64_t blk; buf = NULL; *bufp = NULL; if (!vhd_type_dynamic(ctx)) return -EINVAL; err = vhd_get_bat(ctx); if (err) return err; if (block >= ctx->bat.entries) return -ERANGE; blk = ctx->bat.bat[block]; if (blk == DD_BLK_UNUSED) return -EINVAL; off = (off_t)vhd_sectors_to_bytes(blk); size = (size_t)vhd_bytes_padded(ctx->spb >> 3); err = vhd_seek(ctx, off, SEEK_SET); if (err) return err; #ifdef _WIN32 buf = _aligned_malloc( size, VHD_SECTOR_SIZE ); if( buf == NULL ) return -errno; #else err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); if (err) return -err; #endif err = vhd_read(ctx, buf, size); if (err) goto fail; *bufp = buf; return 0; fail: _aligned_free(buf); return err; } int vhd_read_block(vhd_context_t *ctx, uint32_t block, char **bufp) { int err; char *buf; size_t size; uint64_t blk; off_t end, off; buf = NULL; *bufp = NULL; if (!vhd_type_dynamic(ctx)) return -EINVAL; err = vhd_get_bat(ctx); if (err) return err; if (block >= ctx->bat.entries) return -ERANGE; blk = ctx->bat.bat[block]; if (blk == DD_BLK_UNUSED) return -EINVAL; off = (off_t)vhd_sectors_to_bytes(blk + ctx->bm_secs); size = (size_t)vhd_sectors_to_bytes(ctx->spb); err = vhd_footer_offset_at_eof(ctx, &end); if (err) return err; #ifdef _WIN32 buf = _aligned_malloc( size, VHD_SECTOR_SIZE ); if( buf == NULL ) { err = -errno; goto fail; } #else err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); if (err) { err = -err; goto fail; } #endif if (end < off + (off_t)ctx->header.block_size) { size = end - off; memset(buf + size, 0, ctx->header.block_size - size); } err = vhd_seek(ctx, off, SEEK_SET); if (err) goto fail; err = vhd_read(ctx, buf, size); if (err) goto fail; *bufp = buf; return 0; fail: _aligned_free(buf); return err; } int vhd_write_footer_at(vhd_context_t *ctx, vhd_footer_t *footer, off_t off) { int err; vhd_footer_t *f; f = NULL; #ifdef _WIN32 f = _aligned_malloc( sizeof(vhd_footer_t), VHD_SECTOR_SIZE ); if( f == NULL ) { err = -errno; goto out; } #else err = posix_memalign((void **)&f, VHD_SECTOR_SIZE, sizeof(vhd_footer_t)); if (err) { f = NULL; err = -err; goto out; } #endif memcpy(f, footer, sizeof(vhd_footer_t)); f->checksum = vhd_checksum_footer(f); err = vhd_validate_footer(f); if (err) goto out; err = vhd_seek(ctx, off, SEEK_SET); if (err) goto out; vhd_footer_out(f); err = vhd_write(ctx, f, sizeof(vhd_footer_t)); out: if (err) VHDLOG((FN,"%s: failed writing footer at 0x%08"PRIx64": %d\n", ctx->file, off, err)); _aligned_free(f); return err; } int vhd_write_footer(vhd_context_t *ctx, vhd_footer_t *footer) { int err; off_t off; if (ctx->is_block) err = vhd_footer_offset_at_eof(ctx, &off); else err = vhd_end_of_data(ctx, &off); if (err) return err; err = vhd_write_footer_at(ctx, footer, off); if (err) return err; if (!vhd_type_dynamic(ctx)) return 0; return vhd_write_footer_at(ctx, footer, 0); } int vhd_write_header_at(vhd_context_t *ctx, vhd_header_t *header, off_t off) { int err; vhd_header_t *h; h = NULL; if (!vhd_type_dynamic(ctx)) { err = -EINVAL; goto out; } #ifdef _WIN32 h = _aligned_malloc( sizeof(vhd_header_t), VHD_SECTOR_SIZE ); if( h == NULL ) { err = -errno; goto out; } #else err = posix_memalign((void **)&h, VHD_SECTOR_SIZE, sizeof(vhd_header_t)); if (err) { h = NULL; err = -err; goto out; } #endif memcpy(h, header, sizeof(vhd_header_t)); h->checksum = vhd_checksum_header(h); err = vhd_validate_header(h); if (err) goto out; vhd_header_out(h); err = vhd_seek(ctx, off, SEEK_SET); if (err) goto out; err = vhd_write(ctx, h, sizeof(vhd_header_t)); out: if (err) VHDLOG((FN,"%s: failed writing header at 0x%08"PRIx64": %d\n", ctx->file, off, err)); _aligned_free(h); return err; } int vhd_write_header(vhd_context_t *ctx, vhd_header_t *header) { off_t off; if (!vhd_type_dynamic(ctx)) return -EINVAL; off = (off_t)ctx->footer.data_offset; return vhd_write_header_at(ctx, header, off); } int vhd_write_bat(vhd_context_t *ctx, vhd_bat_t *bat) { int err; off_t off; vhd_bat_t b; size_t size; if (!vhd_type_dynamic(ctx)) return -EINVAL; err = vhd_validate_bat(&ctx->bat); if (err) return err; err = vhd_validate_bat(bat); if (err) return err; memset(&b, 0, sizeof(vhd_bat_t)); off = (off_t)ctx->header.table_offset; size = (uint32_t)vhd_bytes_padded(bat->entries * sizeof(uint32_t)); err = vhd_seek(ctx, off, SEEK_SET); if (err) return err; #ifdef _WIN32 b.bat = _aligned_malloc( size, VHD_SECTOR_SIZE ); if( b.bat == NULL ) return -errno; #else err = posix_memalign((void **)&b.bat, VHD_SECTOR_SIZE, size); if (err) return -err; #endif memcpy(b.bat, bat->bat, size); b.spb = bat->spb; b.entries = bat->entries; vhd_bat_out(&b); err = vhd_write(ctx, b.bat, size); _aligned_free(b.bat); return err; } int vhd_write_batmap(vhd_context_t *ctx, vhd_batmap_t *batmap) { int err; off_t off; vhd_batmap_t b; char *buf, *map; size_t size, map_size; buf = NULL; map = NULL; if (!vhd_has_batmap(ctx)) { err = -EINVAL; goto out; } b.header = batmap->header; b.map = batmap->map; b.header.checksum = vhd_checksum_batmap(&b); err = vhd_validate_batmap(&b); if (err) goto out; off = (off_t)b.header.batmap_offset; map_size = (size_t)vhd_sectors_to_bytes(b.header.batmap_size); err = vhd_seek(ctx, off, SEEK_SET); if (err) goto out; #ifdef _WIN32 map = _aligned_malloc( map_size, VHD_SECTOR_SIZE ); if( map == NULL ) { err = -errno; goto out; } #else err = posix_memalign((void **)&map, VHD_SECTOR_SIZE, map_size); if (err) { map = NULL; err = -err; goto out; } #endif memcpy(map, b.map, map_size); err = vhd_write(ctx, map, map_size); if (err) goto out; err = vhd_batmap_header_offset(ctx, &off); if (err) goto out; size = (size_t)vhd_bytes_padded(sizeof(vhd_batmap_header_t)); err = vhd_seek(ctx, off, SEEK_SET); if (err) goto out; #ifdef _WIN32 buf = _aligned_malloc( size, VHD_SECTOR_SIZE ); if( buf == NULL ) { err = -errno; goto out; } #else err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); if (err) { err = -err; buf = NULL; goto out; } #endif vhd_batmap_header_out(&b); memset(buf, 0, size); memcpy(buf, &b.header, sizeof(vhd_batmap_header_t)); err = vhd_write(ctx, buf, size); out: if (err) VHDLOG((FN,"%s: failed writing batmap: %d\n", ctx->file, err)); _aligned_free(buf); _aligned_free(map); return 0; } int vhd_write_bitmap(vhd_context_t *ctx, uint32_t block, char *bitmap) { int err; off_t off; uint64_t blk; size_t size; if (!vhd_type_dynamic(ctx)) return -EINVAL; err = vhd_validate_bat(&ctx->bat); if (err) return err; if (block >= ctx->bat.entries) return -ERANGE; if ((unsigned long)bitmap & (VHD_SECTOR_SIZE - 1)) return -EINVAL; blk = ctx->bat.bat[block]; if (blk == DD_BLK_UNUSED) return -EINVAL; off = (off_t)vhd_sectors_to_bytes(blk); size = (size_t)vhd_sectors_to_bytes(ctx->bm_secs); err = vhd_seek(ctx, off, SEEK_SET); if (err) return err; err = vhd_write(ctx, bitmap, size); if (err) return err; return 0; } int vhd_write_block(vhd_context_t *ctx, uint32_t block, char *data) { int err; off_t off; size_t size; uint64_t blk; if (!vhd_type_dynamic(ctx)) return -EINVAL; err = vhd_validate_bat(&ctx->bat); if (err) return err; if (block >= ctx->bat.entries) return -ERANGE; if ((unsigned long)data & ~(VHD_SECTOR_SIZE -1)) return -EINVAL; blk = ctx->bat.bat[block]; if (blk == DD_BLK_UNUSED) return -EINVAL; off = (off_t)vhd_sectors_to_bytes(blk + ctx->bm_secs); size = (size_t)vhd_sectors_to_bytes(ctx->spb); err = vhd_seek(ctx, off, SEEK_SET); if (err) return err; err = vhd_write(ctx, data, size); if (err) return err; return 0; } static inline int namedup(char **dup, const char *name) { *dup = NULL; if (strnlen(name, MAX_NAME_LEN) >= MAX_NAME_LEN) return -ENAMETOOLONG; *dup = strdup(name); if (*dup == NULL) return -ENOMEM; return 0; } int vhd_seek(vhd_context_t *ctx, off_t offset, int whence) { off_t off; off = (off_t)lseek(ctx->fd, offset, whence); if (off == (off_t)-1) { VHDLOG((FN,"%s: seek(0x%08"PRIx64", %d) failed: %d\n", ctx->file, offset, whence, -errno)); return -errno; } return 0; } off_t vhd_position(vhd_context_t *ctx) { return (off_t)lseek(ctx->fd, 0, SEEK_CUR); } int vhd_read(vhd_context_t *ctx, void *buf, size_t size) { size_t ret; errno = 0; ret = read(ctx->fd, buf, size); if (ret == size) return 0; VHDLOG((FN,"%s: read of %" PRIuMAX " returned %" PRIdMAX ", errno: %d\n", ctx->file, (uintmax_t)size, (intmax_t)ret, -errno)); return (errno ? -errno : -EIO); } int vhd_write(vhd_context_t *ctx, void *buf, size_t size) { size_t ret; errno = 0; ret = write(ctx->fd, buf, size); if (ret == size) return 0; VHDLOG((FN,"%s: write of %" PRIuMAX " returned %" PRIdMAX ", errno: %d\n", ctx->file, (uintmax_t)size, (intmax_t)ret, -errno)); return (errno ? -errno : -EIO); } int vhd_offset(vhd_context_t *ctx, uint32_t sector, uint32_t *offset) { int err; uint32_t block; if (!vhd_type_dynamic(ctx)) return sector; err = vhd_get_bat(ctx); if (err) return err; block = sector / ctx->spb; if (ctx->bat.bat[block] == DD_BLK_UNUSED) *offset = DD_BLK_UNUSED; else *offset = ctx->bat.bat[block] + ctx->bm_secs + (sector % ctx->spb); return 0; } int vhd_open_fast(vhd_context_t *ctx) { int err; char *buf; size_t size; size = sizeof(vhd_footer_t) + sizeof(vhd_header_t); #ifdef _WIN32 buf = _aligned_malloc( size, VHD_SECTOR_SIZE ); if( buf == NULL ) { VHDLOG((FN,"failed allocating %s: %d\n", ctx->file, -errno)); return -errno; } #else err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); if (err) { VHDLOG((FN,"failed allocating %s: %d\n", ctx->file, -err)); return -err; } #endif err = vhd_read(ctx, buf, size); if (err) { VHDLOG((FN,"failed reading %s: %d\n", ctx->file, err)); goto out; } memcpy(&ctx->footer, buf, sizeof(vhd_footer_t)); vhd_footer_in(&ctx->footer); err = vhd_validate_footer(&ctx->footer); if (err) goto out; if (vhd_type_dynamic(ctx)) { if (ctx->footer.data_offset != sizeof(vhd_footer_t)) err = vhd_read_header(ctx, &ctx->header); else { memcpy(&ctx->header, buf + sizeof(vhd_footer_t), sizeof(vhd_header_t)); vhd_header_in(&ctx->header); err = vhd_validate_header(&ctx->header); } if (err) goto out; ctx->spb = ctx->header.block_size >> VHD_SECTOR_SHIFT; ctx->bm_secs = secs_round_up_no_zero(ctx->spb >> 3); } out: _aligned_free(buf); return err; } int vhd_open(vhd_context_t *ctx, const char *file, int flags) { int err, oflags; if (flags & VHD_OPEN_STRICT) vhd_flag_clear(flags, VHD_OPEN_FAST); memset(ctx, 0, sizeof(vhd_context_t)); ctx->fd = -1; ctx->oflags = flags; err = namedup(&ctx->file, file); if (err) return err; oflags = O_DIRECT | O_LARGEFILE | O_BINARY; if (flags & VHD_OPEN_RDONLY) oflags |= O_RDONLY; if (flags & VHD_OPEN_RDWR) oflags |= O_RDWR; ctx->fd = open(ctx->file, oflags, 0644); if (ctx->fd == -1) { err = -errno; VHDLOG((FN,"failed to open %s: %d\n", ctx->file, err)); goto fail; } err = vhd_test_file_fixed(ctx->file, &ctx->is_block); if (err) goto fail; if (flags & VHD_OPEN_FAST) { err = vhd_open_fast(ctx); if (err) goto fail; return 0; } err = vhd_read_footer(ctx, &ctx->footer); if (err) goto fail; if (!(flags & VHD_OPEN_IGNORE_DISABLED) && vhd_disabled(ctx)) { err = -EINVAL; goto fail; } if (vhd_type_dynamic(ctx)) { err = vhd_read_header(ctx, &ctx->header); if (err) goto fail; ctx->spb = ctx->header.block_size >> VHD_SECTOR_SHIFT; ctx->bm_secs = secs_round_up_no_zero(ctx->spb >> 3); } return 0; fail: if (ctx->fd != -1) close(ctx->fd); free(ctx->file); memset(ctx, 0, sizeof(vhd_context_t)); return err; } void vhd_close(vhd_context_t *ctx) { if (ctx->file) close(ctx->fd); free(ctx->file); _aligned_free(ctx->bat.bat); _aligned_free(ctx->batmap.map); memset(ctx, 0, sizeof(vhd_context_t)); } static inline void vhd_initialize_footer(vhd_context_t *ctx, int type, uint64_t size) { memset(&ctx->footer, 0, sizeof(vhd_footer_t)); memcpy(ctx->footer.cookie, HD_COOKIE, sizeof(ctx->footer.cookie)); ctx->footer.features = HD_RESERVED; ctx->footer.ff_version = HD_FF_VERSION; ctx->footer.timestamp = vhd_time(time(NULL)); ctx->footer.crtr_ver = VHD_CURRENT_VERSION; #ifdef _WIN32 ctx->footer.crtr_os = HD_CR_OS_WINDOWS; #elif __APPLE__ ctx->footer.crtr_os = HD_CR_OS_MACINTOSH; #elif VMS ctx->footer.crtr_os = HD_CR_OS_VMS; #else ctx->footer.crtr_os = HD_CR_OS_UNIX; #endif ctx->footer.orig_size = size; ctx->footer.curr_size = size; ctx->footer.geometry = vhd_chs(size); ctx->footer.type = type; ctx->footer.saved = 0; ctx->footer.data_offset = 0xFFFFFFFFFFFFFFFF; strcpy(ctx->footer.crtr_app, "tap"); blk_uuid_generate(&ctx->footer.uuid); } static int vhd_initialize_header_parent_name(vhd_context_t *ctx, const char *parent_path) { int err; iconv_t cd; size_t ibl, obl; char *ppath, *dst; const char *pname; err = 0; pname = NULL; ppath = NULL; /* * MICROSOFT_COMPAT * big endian unicode here */ cd = iconv_open(UTF_16BE, "ASCII"); if (cd == (iconv_t)-1) { err = -errno; goto out; } ppath = strdup(parent_path); if (!ppath) { err = -ENOMEM; goto out; } pname = basename(ppath); if (!strcmp(pname, "")) { err = -EINVAL; goto out; } ibl = strlen(pname); obl = sizeof(ctx->header.prt_name); dst = ctx->header.prt_name; memset(dst, 0, obl); if (iconv(cd, #ifdef __linux__ (char **) #endif &pname, &ibl, &dst, &obl) == (size_t)-1 || ibl) err = (errno ? -errno : -EINVAL); out: iconv_close(cd); free(ppath); return err; } static off_t get_file_size(const char *name) { int fd; off_t end; fd = open(name, O_LARGEFILE | O_RDONLY | O_BINARY); if (fd == -1) { VHDLOG((FN,"unable to open '%s': %d\n", name, errno)); return -errno; } end = (off_t)lseek(fd, 0, SEEK_END); close(fd); return end; } static int vhd_initialize_header(vhd_context_t *ctx, const char *parent_path, uint64_t size, int raw) { int err; struct stat stats; vhd_context_t parent; if (!vhd_type_dynamic(ctx)) return -EINVAL; memset(&ctx->header, 0, sizeof(vhd_header_t)); memcpy(ctx->header.cookie, DD_COOKIE, sizeof(ctx->header.cookie)); ctx->header.data_offset = (uint64_t)-1; ctx->header.table_offset = VHD_SECTOR_SIZE * 3; /* 1 ftr + 2 hdr */ ctx->header.hdr_ver = DD_VERSION; ctx->header.block_size = VHD_BLOCK_SIZE; ctx->header.prt_ts = 0; ctx->header.res1 = 0; ctx->header.max_bat_size = (u32)((ctx->footer.curr_size + VHD_BLOCK_SIZE - 1) >> VHD_BLOCK_SHIFT); ctx->footer.data_offset = VHD_SECTOR_SIZE; if (ctx->footer.type == HD_TYPE_DYNAMIC) return 0; err = stat(parent_path, &stats); if (err == -1) return -errno; if (raw) { ctx->header.prt_ts = vhd_time(stats.st_mtime); if (!size) size = get_file_size(parent_path); } else { err = vhd_open(&parent, parent_path, VHD_OPEN_RDONLY); if (err) return err; ctx->header.prt_ts = vhd_time(stats.st_mtime); blk_uuid_copy(&ctx->header.prt_uuid, &parent.footer.uuid); if (!size) size = parent.footer.curr_size; vhd_close(&parent); } ctx->footer.orig_size = size; ctx->footer.curr_size = size; ctx->footer.geometry = vhd_chs(size); ctx->header.max_bat_size = (u32)( (size + VHD_BLOCK_SIZE - 1) >> VHD_BLOCK_SHIFT); return vhd_initialize_header_parent_name(ctx, parent_path); } static int vhd_write_parent_locators(vhd_context_t *ctx, const char *parent) { int i, err; off_t off; uint32_t code; code = PLAT_CODE_NONE; if (ctx->footer.type != HD_TYPE_DIFF) return -EINVAL; off = (off_t)(ctx->batmap.header.batmap_offset + vhd_sectors_to_bytes(ctx->batmap.header.batmap_size)); if (off & (VHD_SECTOR_SIZE - 1)) off = (off_t)vhd_bytes_padded(off); for (i = 0; i < 3; i++) { switch (i) { case 0: code = PLAT_CODE_MACX; break; case 1: code = PLAT_CODE_W2KU; break; case 2: code = PLAT_CODE_W2RU; break; } err = vhd_parent_locator_write_at(ctx, parent, off, code, 0, ctx->header.loc + i); if (err) return err; off += vhd_parent_locator_size(ctx->header.loc + i); } return 0; } int vhd_change_parent(vhd_context_t *child, char *parent_path, int flags) { int i, err; char *ppath; struct stat stats; vhd_context_t parent; ppath = realpath(parent_path, NULL); if (!ppath) { VHDLOG((FN,"error resolving parent path %s for %s: %d\n", parent_path, child->file, errno)); return -errno; } #ifdef _WIN32 ppath++; #endif err = stat(ppath, &stats); if (err == -1) { err = -errno; goto out; } #ifdef _WIN32 /* Look at filename for initial \\ to replace _S_IFBLK?? */ if (!(stats.st_mode & _S_IFREG) || (stats.st_mode & _S_IFDIR)) { err = -EINVAL; goto out; } #else if (!S_ISREG(stats.st_mode) && !S_ISBLK(stats.st_mode)) { err = -EINVAL; goto out; } #endif if (flags & 1) { blk_uuid_clear(&child->header.prt_uuid); } else { err = vhd_open(&parent, ppath, VHD_OPEN_RDONLY); if (err) { VHDLOG((FN,"error opening parent %s for %s: %d\n", ppath, child->file, err)); goto out; } /* Extension */ if( flags & 2 ) { if( blk_uuid_compare(&child->header.prt_uuid, &parent.footer.uuid) != 0 ) { VHDLOG((FN,"new parent %s for %s: parent UUID doesn't match child\n", ppath, child->file)); vhd_close(&parent); err = -EINVAL; goto out; } } else blk_uuid_copy(&child->header.prt_uuid, &parent.footer.uuid); vhd_close(&parent); } vhd_initialize_header_parent_name(child, ppath); child->header.prt_ts = vhd_time(stats.st_mtime); for (i = 0; i < vhd_parent_locator_count(child); i++) { vhd_parent_locator_t *loc = child->header.loc + i; size_t max = vhd_parent_locator_size(loc); switch (loc->code) { case PLAT_CODE_MACX: case PLAT_CODE_W2KU: case PLAT_CODE_W2RU: break; default: continue; } err = vhd_parent_locator_write_at(child, ppath, ((off_t)loc->data_offset), loc->code, max, loc); if (err) { VHDLOG((FN,"error writing parent locator %d for %s: %d\n", i, child->file, err)); goto out; } } TEST_FAIL_AT(FAIL_REPARENT_LOCATOR); err = vhd_write_header(child, &child->header); if (err) { VHDLOG((FN,"error writing header for %s: %d\n", child->file, err)); goto out; } err = 0; out: #ifdef _WIN32 free(ppath -1); #else free(ppath); #endif return err; } static int vhd_create_batmap(vhd_context_t *ctx) { off_t off; int err; size_t map_bytes; vhd_batmap_header_t *header; if (!vhd_type_dynamic(ctx)) return -EINVAL; map_bytes = (ctx->header.max_bat_size + 7) >> 3; header = &ctx->batmap.header; memset(header, 0, sizeof(vhd_batmap_header_t)); memcpy(header->cookie, VHD_BATMAP_COOKIE, sizeof(header->cookie)); err = vhd_batmap_header_offset(ctx, &off); if (err) return err; header->batmap_offset = off + vhd_bytes_padded(sizeof(vhd_batmap_header_t)); header->batmap_size = secs_round_up_no_zero(map_bytes); header->batmap_version = VHD_BATMAP_CURRENT_VERSION; map_bytes = (size_t)vhd_sectors_to_bytes(header->batmap_size); #ifdef _WIN32 ctx->batmap.map = _aligned_malloc( map_bytes, VHD_SECTOR_SIZE ); if( ctx->batmap.map == NULL ) { return -errno; } #else err = posix_memalign((void **)&ctx->batmap.map, VHD_SECTOR_SIZE, map_bytes); if (err) { ctx->batmap.map = NULL; return -err; } #endif memset(ctx->batmap.map, 0, map_bytes); return vhd_write_batmap(ctx, &ctx->batmap); } static int vhd_create_bat(vhd_context_t *ctx) { int err; size_t i, size; if (!vhd_type_dynamic(ctx)) return -EINVAL; size = (size_t)vhd_bytes_padded(ctx->header.max_bat_size * sizeof(uint32_t)); #ifdef _WIN32 ctx->bat.bat = _aligned_malloc( size, VHD_SECTOR_SIZE ); if( ctx->bat.bat == NULL ) { return -errno; } #else err = posix_memalign((void **)&ctx->bat.bat, VHD_SECTOR_SIZE, size); if (err) { ctx->bat.bat = NULL; return -err; } #endif memset(ctx->bat.bat, 0, size); for (i = 0; i < ctx->header.max_bat_size; i++) ctx->bat.bat[i] = DD_BLK_UNUSED; err = vhd_seek(ctx, (off_t)ctx->header.table_offset, SEEK_SET); if (err) return err; ctx->bat.entries = ctx->header.max_bat_size; ctx->bat.spb = ctx->header.block_size >> VHD_SECTOR_SHIFT; return vhd_write_bat(ctx, &ctx->bat); } static int vhd_initialize_fixed_disk(vhd_context_t *ctx) { char *buf; size_t i; int err; if (ctx->footer.type != HD_TYPE_FIXED) return -EINVAL; err = vhd_seek(ctx, 0, SEEK_SET); if (err) return err; #ifdef _WIN32 buf = VirtualAlloc( NULL, VHD_BLOCK_SIZE, MEM_COMMIT, PAGE_READONLY ); if( buf == NULL ) return -ENOMEM; #else buf = mmap(0, VHD_BLOCK_SIZE, PROT_READ, MAP_SHARED | MAP_ANON, -1, 0); if (buf == MAP_FAILED) return -errno; #endif for (i = 0; i < ctx->footer.curr_size >> VHD_BLOCK_SHIFT; i++) { err = vhd_write(ctx, buf, VHD_BLOCK_SIZE); if (err) goto out; } err = 0; out: #ifdef _WIN32 VirtualFree( buf, 0, MEM_RELEASE ); #else munmap(buf, VHD_BLOCK_SIZE); #endif return err; } int vhd_get_phys_size(vhd_context_t *ctx, off_t *size) { int err; if ((err = vhd_end_of_data(ctx, size))) return err; *size += sizeof(vhd_footer_t); return 0; } int vhd_set_phys_size(vhd_context_t *ctx, off_t size) { off_t phys_size; int err; err = vhd_get_phys_size(ctx, &phys_size); if (err) return err; if (size < phys_size) { /* would result in data loss */ VHDLOG((FN,"ERROR: new size (%"PRIu64") < phys size (%"PRIu64")\n", size, phys_size)); return -EINVAL; } return vhd_write_footer_at(ctx, &ctx->footer, size - sizeof(vhd_footer_t)); } static int __vhd_create(const char *name, const char *parent, uint64_t bytes, int type, vhd_flag_creat_t flags) { int err; off_t off; vhd_context_t ctx; uint64_t size, blks; switch (type) { case HD_TYPE_DIFF: if (!parent) return -EINVAL; case HD_TYPE_FIXED: case HD_TYPE_DYNAMIC: break; default: return -EINVAL; } if (strnlen(name, VHD_MAX_NAME_LEN - 1) == VHD_MAX_NAME_LEN - 1) return -ENAMETOOLONG; memset(&ctx, 0, sizeof(vhd_context_t)); blks = (bytes + VHD_BLOCK_SIZE - 1) >> VHD_BLOCK_SHIFT; size = blks << VHD_BLOCK_SHIFT; ctx.fd = open(name, O_WRONLY | O_CREAT | O_BINARY | O_TRUNC | O_LARGEFILE | O_DIRECT, 0644); if (ctx.fd == -1) return -errno; ctx.file = strdup(name); if (!ctx.file) { err = -ENOMEM; goto out; } err = vhd_test_file_fixed(ctx.file, &ctx.is_block); if (err) goto out; vhd_initialize_footer(&ctx, type, size); if (type == HD_TYPE_FIXED) { err = vhd_initialize_fixed_disk(&ctx); if (err) goto out; } else { int raw = vhd_flag_test(flags, VHD_FLAG_CREAT_PARENT_RAW); err = vhd_initialize_header(&ctx, parent, size, raw); if (err) goto out; err = vhd_write_footer_at(&ctx, &ctx.footer, 0); if (err) goto out; err = vhd_write_header_at(&ctx, &ctx.header, VHD_SECTOR_SIZE); if (err) goto out; err = vhd_create_batmap(&ctx); if (err) goto out; err = vhd_create_bat(&ctx); if (err) goto out; if (type == HD_TYPE_DIFF) { err = vhd_write_parent_locators(&ctx, parent); if (err) goto out; } /* write header again since it may have changed */ err = vhd_write_header_at(&ctx, &ctx.header, VHD_SECTOR_SIZE); if (err) goto out; } err = vhd_seek(&ctx, 0, SEEK_END); if (err) goto out; off = vhd_position(&ctx); if (off == (off_t)-1) { err = -errno; goto out; } if (ctx.is_block) off -= sizeof(vhd_footer_t); err = vhd_write_footer_at(&ctx, &ctx.footer, off); if (err) goto out; err = 0; out: vhd_close(&ctx); if (err && !ctx.is_block) unlink(name); return err; } int vhd_create(const char *name, uint64_t bytes, int type, vhd_flag_creat_t flags) { return __vhd_create(name, NULL, bytes, type, flags); } int vhd_snapshot(const char *name, uint64_t bytes, const char *parent, vhd_flag_creat_t flags) { return __vhd_create(name, parent, bytes, HD_TYPE_DIFF, flags); } static int __vhd_io_fixed_read(vhd_context_t *ctx, char *buf, uint64_t sec, uint32_t secs) { int err; err = vhd_seek(ctx, (off_t)vhd_sectors_to_bytes(sec), SEEK_SET); if (err) return err; return vhd_read(ctx, buf, (off_t)vhd_sectors_to_bytes(secs)); } static void __vhd_io_dynamic_copy_data(vhd_context_t *ctx, char *map, int map_off, char *bitmap, int bitmap_off, char *dst, char *src, int secs) { int i; for (i = 0; i < secs; i++) { if (test_bit(map, map_off + i)) goto next; if (ctx && !vhd_bitmap_test(ctx, bitmap, bitmap_off + i)) goto next; memcpy(dst, src, VHD_SECTOR_SIZE); set_bit(map, map_off + i); next: src += VHD_SECTOR_SIZE; dst += VHD_SECTOR_SIZE; } } static int __vhd_io_dynamic_read_link(vhd_context_t *ctx, char *map, char *buf, uint64_t sector, uint32_t secs) { off_t off; uint32_t blk, sec; int err, cnt, map_off; char *bitmap, *data, *src; map_off = 0; do { blk = (uint32_t)(sector / ctx->spb); sec = sector % ctx->spb; off = ctx->bat.bat[blk]; data = NULL; bitmap = NULL; if (off == DD_BLK_UNUSED) { cnt = MIN(secs, ctx->spb); goto next; } err = vhd_read_bitmap(ctx, blk, &bitmap); if (err) return err; err = vhd_read_block(ctx, blk, &data); if (err) { _aligned_free(bitmap); return err; } cnt = MIN(secs, ctx->spb - sec); src = data + vhd_sectors_to_bytes(sec); __vhd_io_dynamic_copy_data(ctx, map, map_off, bitmap, sec, buf, src, cnt); next: _aligned_free(data); _aligned_free(bitmap); secs -= cnt; sector += cnt; map_off += cnt; buf += vhd_sectors_to_bytes(cnt); } while (secs); return 0; } static int __raw_read_link(char *filename, char *map, char *buf, uint64_t sec, uint32_t secs) { int fd, err; off_t off; uint64_t size; char *data; err = 0; errno = 0; fd = open(filename, O_RDONLY | O_DIRECT | O_LARGEFILE | O_BINARY); if (fd == -1) { VHDLOG((FN,"%s: failed to open: %d\n", filename, -errno)); return -errno; } off = (off_t)lseek(fd, (off_t)vhd_sectors_to_bytes(sec), SEEK_SET); if (off == (off_t)-1) { VHDLOG((FN,"%s: seek(0x%08"PRIx64") failed: %d\n", filename, vhd_sectors_to_bytes(sec), -errno)); err = -errno; goto close; } size = vhd_sectors_to_bytes(secs); #ifdef _WIN32 data = _aligned_malloc( (size_t)size, VHD_SECTOR_SIZE ); if( data == NULL ) { err = -errno; goto close; } #else err = posix_memalign((void **)&data, VHD_SECTOR_SIZE, size); if (err) { err = -err; goto close; } #endif #ifdef _WIN32 { char *dp; dp = data; while( size != 0 ) { uint32_t n; if( size > UINT32_MAX ) n = (uint32_t)((((UINT64)UINT32_MAX) + 1) / 2); else n = (uint32_t)size; err = read( fd, dp, n ); if( err != n ) { VHDLOG((FN, "%s: reading of %"PRIu64" returned %d, errno: %d\n", filename, n, err, -errno )); _aligned_free( data ); err = errno ? -errno : -EIO; goto close; } size -= n; dp += n; } } #else err = read( fd, data, size ); if( (size_t)err != size ) { VHDLOG((FN, "%s: reading of %"PRIu64" returned %d, errno: %d\n", filename, size, err, -errno )); _aligned_free( data ); err = errno ? -errno : -EIO; goto close; } #endif __vhd_io_dynamic_copy_data(NULL, map, 0, NULL, 0, buf, data, secs); _aligned_free(data); err = 0; close: close(fd); return err; } static int __vhd_io_dynamic_read(vhd_context_t *ctx, char *buf, uint64_t sec, uint32_t secs) { int err; uint32_t i, done; char *map, *next; vhd_context_t parent, *vhd; err = vhd_get_bat(ctx); if (err) return err; vhd = ctx; next = NULL; map = calloc(1, secs << (VHD_SECTOR_SHIFT - 3)); if (!map) return -ENOMEM; memset(buf, 0, (size_t)vhd_sectors_to_bytes(secs)); for (;;) { err = __vhd_io_dynamic_read_link(vhd, map, buf, sec, secs); if (err) goto close; for (done = 0, i = 0; i < secs; i++) if (test_bit(map, i)) done++; if (done == secs) { err = 0; goto close; } if (vhd->footer.type == HD_TYPE_DIFF) { err = vhd_parent_locator_get(vhd, &next); if (err) goto close; if (vhd_parent_raw(vhd)) { err = __raw_read_link(next, map, buf, sec, secs); goto close; } } else { err = 0; goto close; } if (vhd != ctx) vhd_close(vhd); vhd = &parent; err = vhd_open(vhd, next, VHD_OPEN_RDONLY); if (err) goto out; err = vhd_get_bat(vhd); if (err) goto close; free(next); next = NULL; } close: if (vhd != ctx) vhd_close(vhd); out: free(map); free(next); return err; } int vhd_io_read(vhd_context_t *ctx, char *buf, uint64_t sec, uint32_t secs) { if (vhd_sectors_to_bytes(sec + secs) > ctx->footer.curr_size) return -ERANGE; if (!vhd_type_dynamic(ctx)) return __vhd_io_fixed_read(ctx, buf, sec, secs); return __vhd_io_dynamic_read(ctx, buf, sec, secs); } static int __vhd_io_fixed_write(vhd_context_t *ctx, char *buf, uint64_t sec, uint32_t secs) { int err; err = vhd_seek(ctx, (off_t)vhd_sectors_to_bytes(sec), SEEK_SET); if (err) return err; return vhd_write(ctx, buf, (size_t)vhd_sectors_to_bytes(secs)); } static int __vhd_io_allocate_block(vhd_context_t *ctx, uint32_t block) { char *buf; size_t size; off_t off, max; int err, gap; long spp; #ifdef _WIN32 SYSTEM_INFO inf; GetSystemInfo( &inf ); spp = inf.dwPageSize >> VHD_SECTOR_SHIFT; #elif defined( _SC_PAGESIZE) spp = sysconf( _SC_PAGESIZE ) >> VHD_SECTOR_SHIFT; #else spp = getpagesize() >> VHD_SECTOR_SHIFT; #endif if( spp == 0 ) abort(); err = vhd_end_of_data(ctx, &max); if (err) return err; gap = 0; off = max; max >>= VHD_SECTOR_SHIFT; /* data region of segment should begin on page boundary */ if ((max + ctx->bm_secs) % spp) { gap = (spp - ((max + ctx->bm_secs) % spp)); max += gap; } err = vhd_seek(ctx, off, SEEK_SET); if (err) return err; size = (size_t)vhd_sectors_to_bytes(ctx->spb + ctx->bm_secs + gap); #ifdef _WIN32 buf = VirtualAlloc( NULL, size, MEM_COMMIT, PAGE_READONLY ); if( buf == NULL ) return -ENOMEM; #else buf = mmap(0, size, PROT_READ, MAP_SHARED | MAP_ANON, -1, 0); if (buf == MAP_FAILED) return -errno; #endif err = vhd_write(ctx, buf, size); if (err) goto out; ctx->bat.bat[block] = max; err = vhd_write_bat(ctx, &ctx->bat); if (err) goto out; err = 0; out: #ifdef _WIN32 VirtualFree( buf, 0, MEM_RELEASE ); #else munmap(buf, size); #endif return err; } static int __vhd_io_dynamic_write(vhd_context_t *ctx, char *buf, uint64_t sector, uint32_t secs) { char *map; off_t off; uint32_t blk, sec; int err, ret; size_t i, cnt; if (vhd_sectors_to_bytes(sector + secs) > ctx->footer.curr_size) return -ERANGE; err = vhd_get_bat(ctx); if (err) return err; if (vhd_has_batmap(ctx)) { err = vhd_get_batmap(ctx); if (err) return err; } do { blk = (uint32_t)(sector / ctx->spb); sec = sector % ctx->spb; off = ctx->bat.bat[blk]; if (off == DD_BLK_UNUSED) { err = __vhd_io_allocate_block(ctx, blk); if (err) return err; off = ctx->bat.bat[blk]; } off += ctx->bm_secs + sec; err = vhd_seek(ctx, (off_t)vhd_sectors_to_bytes(off), SEEK_SET); if (err) return err; cnt = MIN(secs, ctx->spb - sec); err = vhd_write(ctx, buf, (size_t)vhd_sectors_to_bytes(cnt)); if (err) return err; if (vhd_has_batmap(ctx) && vhd_batmap_test(ctx, &ctx->batmap, blk)) goto next; err = vhd_read_bitmap(ctx, blk, &map); if (err) return err; for (i = 0; i < cnt; i++) vhd_bitmap_set(ctx, map, sec + i); err = vhd_write_bitmap(ctx, blk, map); if (err) goto fail; if (vhd_has_batmap(ctx)) { for (i = 0; i < ctx->spb; i++) if (!vhd_bitmap_test(ctx, map, i)) { _aligned_free(map); goto next; } vhd_batmap_set(ctx, &ctx->batmap, blk); err = vhd_write_batmap(ctx, &ctx->batmap); if (err) goto fail; } _aligned_free(map); map = NULL; next: secs -= cnt; sector += cnt; buf += vhd_sectors_to_bytes(cnt); } while (secs); err = 0; out: ret = vhd_write_footer(ctx, &ctx->footer); return (err ? err : ret); fail: _aligned_free(map); goto out; } int vhd_io_write(vhd_context_t *ctx, char *buf, uint64_t sec, uint32_t secs) { if (vhd_sectors_to_bytes(sec + secs) > ctx->footer.curr_size) return -ERANGE; if (!vhd_type_dynamic(ctx)) return __vhd_io_fixed_write(ctx, buf, sec, secs); return __vhd_io_dynamic_write(ctx, buf, sec, secs); }