diff --git a/extracters/ods2/vhd/blk_uuid.h b/extracters/ods2/vhd/blk_uuid.h new file mode 100644 index 0000000..3adf265 --- /dev/null +++ b/extracters/ods2/vhd/blk_uuid.h @@ -0,0 +1,130 @@ +/* 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. +*/ +#ifndef __BLKTAP2_UUID_H__ +#define __BLKTAP2_UUID_H__ + +#if defined(__linux__) + +#include + +typedef struct { + uuid_t uuid; +} blk_uuid_t; + +static inline int blk_uuid_is_nil(blk_uuid_t *uuid) +{ + return uuid_is_null(uuid->uuid); +} + +static inline void blk_uuid_generate(blk_uuid_t *uuid) +{ + uuid_generate(uuid->uuid); +} + +static inline void blk_uuid_to_string(blk_uuid_t *uuid, char *out, size_t size) +{ + uuid_unparse(uuid->uuid, out); +} + +static inline void blk_uuid_from_string(blk_uuid_t *uuid, const char *in) +{ + uuid_parse(in, uuid->uuid); +} + +static inline void blk_uuid_copy(blk_uuid_t *dst, blk_uuid_t *src) +{ + uuid_copy(dst->uuid, src->uuid); +} + +static inline void blk_uuid_clear(blk_uuid_t *uuid) +{ + uuid_clear(uuid->uuid); +} + +static inline int blk_uuid_compare(blk_uuid_t *uuid1, blk_uuid_t *uuid2) +{ + return uuid_compare(uuid1->uuid, uuid2->uuid); +} + +#elif defined(__NetBSD__) + +#include +#include +#include + +typedef uuid_t blk_uuid_t; + +static inline int blk_uuid_is_nil(blk_uuid_t *uuid) +{ + uint32_t status; + return uuid_is_nil((uuid_t *)uuid, &status); +} + +static inline void blk_uuid_generate(blk_uuid_t *uuid) +{ + uint32_t status; + uuid_create((uuid_t *)uuid, &status); +} + +static inline void blk_uuid_to_string(blk_uuid_t *uuid, char *out, size_t size) +{ + uint32_t status; + char *_out = NULL; + uuid_to_string((uuid_t *)uuid, &_out, &status); + strlcpy(out, _out, size); + free(_out); +} + +static inline void blk_uuid_from_string(blk_uuid_t *uuid, const char *in) +{ + uint32_t status; + uuid_from_string(in, (uuid_t *)uuid, &status); +} + +static inline void blk_uuid_copy(blk_uuid_t *dst, blk_uuid_t *src) +{ + memcpy((uuid_t *)dst, (uuid_t *)src, sizeof(uuid_t)); +} + +static inline void blk_uuid_clear(blk_uuid_t *uuid) +{ + memset((uuid_t *)uuid, 0, sizeof(uuid_t)); +} + +static inline int blk_uuid_compare(blk_uuid_t *uuid1, blk_uuid_t *uuid2) +{ + uint32_t status; + return uuid_compare((uuid_t *)uuid1, (uuid_t *)uuid2, &status); +} + +#else + +#error "Please update blk_uuid.h for your OS" + +#endif + +#endif /* __BLKTAP2_UUID_H__ */ diff --git a/extracters/ods2/vhd/libvhd.c b/extracters/ods2/vhd/libvhd.c new file mode 100644 index 0000000..34df11d --- /dev/null +++ b/extracters/ods2/vhd/libvhd.c @@ -0,0 +1,3352 @@ +/* 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. +*/ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libvhd.h" +#include "relative-path.h" + +static int libvhd_dbg = 0; + +void +libvhd_set_log_level(int level) +{ + if (level) + libvhd_dbg = 1; +} + +#define VHDLOG(_f, _a...) \ + do { \ + if (libvhd_dbg) \ + syslog(LOG_INFO, "libvhd::%s: "_f, \ + __func__, ##_a); \ + } while (0) + +#define BIT_MASK 0x80 + +#ifdef ENABLE_FAILURE_TESTING +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" +}; +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) +{ + int i; + + for (i = 0; i < bat->entries; i++) + BE32_IN(&bat->bat[i]); +} + +void +vhd_bat_out(vhd_bat_t *bat) +{ + int i; + + for (i = 0; i < bat->entries; i++) + BE32_OUT(&bat->bat[i]); +} + +uint32_t +vhd_checksum_footer(vhd_footer_t *footer) +{ + int 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("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("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) +{ + int 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("invalid header cookie: %s\n", buf); + return -EINVAL; + } + + if (header->hdr_ver != 0x00010000) { + VHDLOG("invalid header version 0x%08x\n", header->hdr_ver); + return -EINVAL; + } + + if (header->data_offset != 0xFFFFFFFFFFFFFFFF) { + VHDLOG("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("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) +{ + int i, n; + char *blob; + uint32_t checksum; + + blob = batmap->map; + checksum = 0; + + n = 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 = ctx->header.table_offset; + bat = ctx->header.max_bat_size * sizeof(uint32_t); + off += 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("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("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) + return old_set_bit(map, block); + + return set_bit(map, block); +} + +void +vhd_bitmap_clear(vhd_context_t *ctx, char *map, uint32_t block) +{ + if (vhd_creator_tapdisk(ctx) && + ctx->footer.crtr_ver == 0x00000001) + return old_clear_bit(map, block); + + return clear_bit(map, block); +} + +/* + * 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 = ctx->footer.data_offset + sizeof(vhd_header_t); + + bat_bytes = vhd_bytes_padded(ctx->header.max_bat_size * sizeof(uint32_t)); + bat_end = 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 += vhd_sectors_to_bytes(hdr_secs); + eom = MAX(eom, hdr_end); + + map_secs = ctx->batmap.header.batmap_size; + map_end = (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 = 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) +{ + int i, 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(blk, max); + } + } + + *end = 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; + + 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; + ctime_r(&t2, target); + + /* 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; + + 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; + + 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; + + err = posix_memalign((void **)&buf, + VHD_SECTOR_SIZE, sizeof(vhd_footer_t)); + if (err) { + buf = NULL; + err = -err; + goto out; + } + + 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("%s: failed reading short footer: %d\n", + ctx->file, err); + 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; + + err = posix_memalign((void **)&buf, + VHD_SECTOR_SIZE, sizeof(vhd_footer_t)); + if (err) { + buf = NULL; + err = -err; + goto out; + } + + 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("%s: reading footer at 0x%08"PRIx64" failed: %d\n", + ctx->file, off, err); + 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; + + err = posix_memalign((void **)&buf, + VHD_SECTOR_SIZE, sizeof(vhd_header_t)); + if (err) { + buf = NULL; + err = -err; + goto out; + } + + 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("%s: reading header at 0x%08"PRIx64" failed: %d\n", + ctx->file, off, err); + free(buf); + return err; +} + +int +vhd_read_header(vhd_context_t *ctx, vhd_header_t *header) +{ + int err; + off_t off; + + if (!vhd_type_dynamic(ctx)) { + VHDLOG("%s is not dynamic!\n", ctx->file); + return -EINVAL; + } + + off = 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 = ctx->header.table_offset; + size = vhd_bytes_padded(ctx->header.max_bat_size * sizeof(uint32_t)); + + err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); + if (err) { + buf = NULL; + err = -err; + goto fail; + } + + 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: + free(buf); + memset(bat, 0, sizeof(vhd_bat_t)); + VHDLOG("%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 = vhd_bytes_padded(sizeof(vhd_batmap_header_t)); + err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); + if (err) { + buf = NULL; + err = -err; + goto fail; + } + + err = vhd_read(ctx, buf, size); + if (err) + goto fail; + + memcpy(&batmap->header, buf, sizeof(vhd_batmap_header_t)); + free(buf); + buf = NULL; + + vhd_batmap_header_in(batmap); + + return 0; + +fail: + free(buf); + memset(&batmap->header, 0, sizeof(vhd_batmap_header_t)); + VHDLOG("%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 = vhd_sectors_to_bytes(batmap->header.batmap_size); + + err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, map_size); + if (err) { + buf = NULL; + err = -err; + goto fail; + } + + off = 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: + free(buf); + batmap->map = NULL; + VHDLOG("%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: + 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; + + *is_block = !!(S_ISBLK(stats.st_mode)); + return err; +} + +int +vhd_find_parent(vhd_context_t *ctx, const char *parent, char **_location) +{ + int err; + char *location, *cpath, *cdir, *path; + + err = 0; + path = NULL; + cpath = NULL; + location = NULL; + *_location = NULL; + + if (!parent) + return -EINVAL; + + if (parent[0] == '/') { + if (!access(parent, R_OK)) { + path = strdup(parent); + if (!path) + return -ENOMEM; + *_location = path; + return 0; + } + } + + /* check parent path relative to child's directory */ + cpath = realpath(ctx->file, NULL); + if (!cpath) { + err = -errno; + goto out; + } + + cdir = dirname(cpath); + if (asprintf(&location, "%s/%s", cdir, parent) == -1) { + err = -errno; + location = NULL; + goto out; + } + + if (!access(location, R_OK)) { + path = realpath(location, NULL); + if (path) { + *_location = path; + return 0; + } + } + err = -errno; + +out: + free(location); + free(cpath); + return err; +} + +static int +vhd_macx_encode_location(char *name, char **out, int *outlen) +{ + iconv_t cd; + int len, err; + 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; + + 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 || obl) { + err = (errno ? -errno : -EIO); + goto out; + } + + 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, int *outlen) +{ + iconv_t cd; + int len, err; + size_t ibl, obl; + char *uri, *uri_utf16, *uri_utf16p, *tmp, *ret; + const char *urip; + + err = 0; + ret = NULL; + *out = NULL; + *outlen = 0; + cd = (iconv_t) -1; + + /* + * 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 + err = asprintf(&uri, "%s", name); + + if (err == -1) + return -ENOMEM; + + tmp = uri; + while (*tmp != '\0') { + if (*tmp == '/') + *tmp = '\\'; + tmp++; + } + + len = strlen(uri); + ibl = len; + obl = len * 2; + 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 || obl) { + err = (errno ? -errno : -EIO); + goto out; + } + + len = len * 2; + 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) + 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]; + + 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, loc->data_offset, SEEK_SET); + if (err) + goto out; + + size = vhd_parent_locator_size(loc); + if (size <= 0) { + err = -EINVAL; + goto out; + } + + err = posix_memalign((void **)&raw, VHD_SECTOR_SIZE, size); + if (err) { + raw = NULL; + err = -err; + goto out; + } + + 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: + free(raw); + free(out); + + if (err) { + VHDLOG("%s: error reading parent locator: %d\n", + ctx->file, err); + VHDLOG("%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("%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, len, size; + 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; + } + + err = stat(absolute_path, &stats); + if (err) { + err = -errno; + goto out; + } + + if (!S_ISREG(stats.st_mode) && !S_ISBLK(stats.st_mode)) { + err = -EINVAL; + goto out; + } + + 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: + 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 = vhd_bytes_padded(len); + + if (max_bytes && size > max_bytes) { + err = -ENAMETOOLONG; + goto out; + } + + err = posix_memalign((void **)&block, VHD_SECTOR_SIZE, size); + if (err) { + block = NULL; + err = -err; + goto out; + } + + 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); + 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 = vhd_sectors_to_bytes(blk); + size = vhd_bytes_padded(ctx->spb >> 3); + + err = vhd_seek(ctx, off, SEEK_SET); + if (err) + return err; + + err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); + if (err) + return -err; + + err = vhd_read(ctx, buf, size); + if (err) + goto fail; + + *bufp = buf; + return 0; + +fail: + 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 = vhd_sectors_to_bytes(blk + ctx->bm_secs); + size = vhd_sectors_to_bytes(ctx->spb); + + err = vhd_footer_offset_at_eof(ctx, &end); + if (err) + return err; + + err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); + if (err) { + err = -err; + goto fail; + } + + if (end < off + 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: + 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; + + err = posix_memalign((void **)&f, + VHD_SECTOR_SIZE, sizeof(vhd_footer_t)); + if (err) { + f = NULL; + err = -err; + goto out; + } + + 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("%s: failed writing footer at 0x%08"PRIx64": %d\n", + ctx->file, off, err); + 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; + } + + err = posix_memalign((void **)&h, + VHD_SECTOR_SIZE, sizeof(vhd_header_t)); + if (err) { + h = NULL; + err = -err; + goto out; + } + + 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("%s: failed writing header at 0x%08"PRIx64": %d\n", + ctx->file, off, err); + free(h); + return err; +} + +int +vhd_write_header(vhd_context_t *ctx, vhd_header_t *header) +{ + int err; + off_t off; + + if (!vhd_type_dynamic(ctx)) + return -EINVAL; + + off = 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 = ctx->header.table_offset; + size = vhd_bytes_padded(bat->entries * sizeof(uint32_t)); + + err = vhd_seek(ctx, off, SEEK_SET); + if (err) + return err; + + err = posix_memalign((void **)&b.bat, VHD_SECTOR_SIZE, size); + if (err) + return -err; + + 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); + 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 = b.header.batmap_offset; + map_size = vhd_sectors_to_bytes(b.header.batmap_size); + + err = vhd_seek(ctx, off, SEEK_SET); + if (err) + goto out; + + err = posix_memalign((void **)&map, VHD_SECTOR_SIZE, map_size); + if (err) { + map = NULL; + err = -err; + goto out; + } + + 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 = vhd_bytes_padded(sizeof(vhd_batmap_header_t)); + + err = vhd_seek(ctx, off, SEEK_SET); + if (err) + goto out; + + err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); + if (err) { + err = -err; + buf = NULL; + goto out; + } + + 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("%s: failed writing batmap: %d\n", ctx->file, err); + free(buf); + 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 secs, 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 = vhd_sectors_to_bytes(blk); + size = 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 = vhd_sectors_to_bytes(blk + ctx->bm_secs); + size = 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 = lseek(ctx->fd, offset, whence); + if (off == (off_t)-1) { + VHDLOG("%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 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("%s: read of %zu returned %zd, errno: %d\n", + ctx->file, size, 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("%s: write of %zu returned %zd, errno: %d\n", + ctx->file, size, 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); + err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); + if (err) { + VHDLOG("failed allocating %s: %d\n", ctx->file, -err); + return -err; + } + + err = vhd_read(ctx, buf, size); + if (err) { + VHDLOG("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: + 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; + 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("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); + free(ctx->bat.bat); + 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; + ctx->footer.crtr_os = 0x00000000; + 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); + if (fd == -1) { + VHDLOG("unable to open '%s': %d\n", name, errno); + return -errno; + } + end = 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 = (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 = + (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 = ctx->batmap.header.batmap_offset + + vhd_sectors_to_bytes(ctx->batmap.header.batmap_size); + if (off & (VHD_SECTOR_SIZE - 1)) + off = 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 raw) +{ + int i, err; + char *ppath; + struct stat stats; + vhd_context_t parent; + + ppath = realpath(parent_path, NULL); + if (!ppath) { + VHDLOG("error resolving parent path %s for %s: %d\n", + parent_path, child->file, errno); + return -errno; + } + + err = stat(ppath, &stats); + if (err == -1) { + err = -errno; + goto out; + } + + if (!S_ISREG(stats.st_mode) && !S_ISBLK(stats.st_mode)) { + err = -EINVAL; + goto out; + } + + if (raw) { + blk_uuid_clear(&child->header.prt_uuid); + } else { + err = vhd_open(&parent, ppath, VHD_OPEN_RDONLY); + if (err) { + VHDLOG("error opening parent %s for %s: %d\n", + ppath, child->file, err); + goto out; + } + 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, + loc->data_offset, + loc->code, max, loc); + if (err) { + VHDLOG("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("error writing header for %s: %d\n", child->file, err); + goto out; + } + + err = 0; + +out: + free(ppath); + return err; +} + +static int +vhd_create_batmap(vhd_context_t *ctx) +{ + off_t off; + int err, 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 = vhd_sectors_to_bytes(header->batmap_size); + + err = posix_memalign((void **)&ctx->batmap.map, + VHD_SECTOR_SIZE, map_bytes); + if (err) { + ctx->batmap.map = NULL; + return -err; + } + + memset(ctx->batmap.map, 0, map_bytes); + + return vhd_write_batmap(ctx, &ctx->batmap); +} + +static int +vhd_create_bat(vhd_context_t *ctx) +{ + int i, err; + size_t size; + + if (!vhd_type_dynamic(ctx)) + return -EINVAL; + + size = vhd_bytes_padded(ctx->header.max_bat_size * sizeof(uint32_t)); + err = posix_memalign((void **)&ctx->bat.bat, VHD_SECTOR_SIZE, size); + if (err) { + ctx->bat.bat = NULL; + return err; + } + + 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, 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; + int i, err; + + if (ctx->footer.type != HD_TYPE_FIXED) + return -EINVAL; + + err = vhd_seek(ctx, 0, SEEK_SET); + if (err) + return err; + + buf = mmap(0, VHD_BLOCK_SIZE, PROT_READ, + MAP_SHARED | MAP_ANON, -1, 0); + if (buf == MAP_FAILED) + return -errno; + + 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: + munmap(buf, VHD_BLOCK_SIZE); + 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("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; + vhd_footer_t *footer; + vhd_header_t *header; + 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)); + footer = &ctx.footer; + header = &ctx.header; + blks = (bytes + VHD_BLOCK_SIZE - 1) >> VHD_BLOCK_SHIFT; + size = blks << VHD_BLOCK_SHIFT; + + ctx.fd = open(name, O_WRONLY | O_CREAT | + 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, vhd_sectors_to_bytes(sec), SEEK_SET); + if (err) + return err; + + return vhd_read(ctx, buf, 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 = 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) { + 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: + free(data); + 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); + if (fd == -1) { + VHDLOG("%s: failed to open: %d\n", filename, -errno); + return -errno; + } + + off = lseek(fd, vhd_sectors_to_bytes(sec), SEEK_SET); + if (off == (off_t)-1) { + VHDLOG("%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); + err = posix_memalign((void **)&data, VHD_SECTOR_SIZE, size); + if (err) + goto close; + + err = read(fd, data, size); + if (err != size) { + VHDLOG("%s: reading of %"PRIu64" returned %d, errno: %d\n", + filename, size, err, -errno); + free(data); + err = errno ? -errno : -EIO; + goto close; + } + __vhd_io_dynamic_copy_data(NULL, map, 0, NULL, 0, buf, data, secs); + 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, 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, vhd_sectors_to_bytes(sec), SEEK_SET); + if (err) + return err; + + return vhd_write(ctx, buf, 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 i, err, gap, spp; + + spp = getpagesize() >> VHD_SECTOR_SHIFT; + + 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 = vhd_sectors_to_bytes(ctx->spb + ctx->bm_secs + gap); + buf = mmap(0, size, PROT_READ, MAP_SHARED | MAP_ANON, -1, 0); + if (buf == MAP_FAILED) + return -errno; + + 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: + munmap(buf, size); + 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 i, err, cnt, ret; + + 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 = 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, vhd_sectors_to_bytes(off), SEEK_SET); + if (err) + return err; + + cnt = MIN(secs, ctx->spb - sec); + err = vhd_write(ctx, buf, 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)) { + free(map); + goto next; + } + + vhd_batmap_set(ctx, &ctx->batmap, blk); + err = vhd_write_batmap(ctx, &ctx->batmap); + if (err) + goto fail; + } + + 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: + 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); +} diff --git a/extracters/ods2/vhd/libvhd.h b/extracters/ods2/vhd/libvhd.h new file mode 100644 index 0000000..fc530e8 --- /dev/null +++ b/extracters/ods2/vhd/libvhd.h @@ -0,0 +1,326 @@ +/* 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. +*/ +#ifndef _VHD_LIB_H_ +#define _VHD_LIB_H_ + +#include +#if defined(__linux__) +#include +#include +#elif defined(__NetBSD__) +#include +#include +#endif + +#include "blk_uuid.h" +#include "vhd.h" + +#ifndef O_LARGEFILE +#define O_LARGEFILE 0 +#endif + +#if BYTE_ORDER == LITTLE_ENDIAN +#if defined(__linux__) + #define BE16_IN(foo) (*(foo)) = bswap_16(*(foo)) + #define BE32_IN(foo) (*(foo)) = bswap_32(*(foo)) + #define BE64_IN(foo) (*(foo)) = bswap_64(*(foo)) + #define BE16_OUT(foo) (*(foo)) = bswap_16(*(foo)) + #define BE32_OUT(foo) (*(foo)) = bswap_32(*(foo)) + #define BE64_OUT(foo) (*(foo)) = bswap_64(*(foo)) +#elif defined(__NetBSD__) + #define BE16_IN(foo) (*(foo)) = bswap16(*(foo)) + #define BE32_IN(foo) (*(foo)) = bswap32(*(foo)) + #define BE64_IN(foo) (*(foo)) = bswap64(*(foo)) + #define BE16_OUT(foo) (*(foo)) = bswap16(*(foo)) + #define BE32_OUT(foo) (*(foo)) = bswap32(*(foo)) + #define BE64_OUT(foo) (*(foo)) = bswap64(*(foo)) +#endif +#else + #define BE16_IN(foo) + #define BE32_IN(foo) + #define BE64_IN(foo) + #define BE32_OUT(foo) + #define BE32_OUT(foo) + #define BE64_OUT(foo) +#endif + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +#define VHD_MAX_NAME_LEN 1024 + +#define VHD_BLOCK_SHIFT 21 +#define VHD_BLOCK_SIZE (1ULL << VHD_BLOCK_SHIFT) + +#define UTF_16 "UTF-16" +#define UTF_16LE "UTF-16LE" +#define UTF_16BE "UTF-16BE" + +#define VHD_OPEN_RDONLY 0x00001 +#define VHD_OPEN_RDWR 0x00002 +#define VHD_OPEN_FAST 0x00004 +#define VHD_OPEN_STRICT 0x00008 +#define VHD_OPEN_IGNORE_DISABLED 0x00010 + +#define VHD_FLAG_CREAT_PARENT_RAW 0x00001 + +#define vhd_flag_set(word, flag) ((word) |= (flag)) +#define vhd_flag_clear(word, flag) ((word) &= ~(flag)) +#define vhd_flag_test(word, flag) ((word) & (flag)) + + +#define ENABLE_FAILURE_TESTING +#define FAIL_REPARENT_BEGIN 0 +#define FAIL_REPARENT_LOCATOR 1 +#define FAIL_REPARENT_END 2 +#define FAIL_RESIZE_BEGIN 3 +#define FAIL_RESIZE_DATA_MOVED 4 +#define FAIL_RESIZE_METADATA_MOVED 5 +#define FAIL_RESIZE_END 6 +#define NUM_FAIL_TESTS 7 + +#ifdef ENABLE_FAILURE_TESTING +#define TEST_FAIL_AT(point) \ + if (TEST_FAIL[point]) { \ + printf("Failing at %s\n", ENV_VAR_FAIL[point]); exit(EINVAL); } +#define TEST_FAIL_EXTERN_VARS \ + extern const char* ENV_VAR_FAIL[]; \ + extern int TEST_FAIL[]; +#else +#define TEST_FAIL_AT(point) +#define TEST_FAIL_EXTERN_VARS +#endif // ENABLE_FAILURE_TESTING + + +static const char VHD_POISON_COOKIE[] = "v_poison"; + +typedef struct hd_ftr vhd_footer_t; +typedef struct dd_hdr vhd_header_t; +typedef struct vhd_bat vhd_bat_t; +typedef struct vhd_batmap vhd_batmap_t; +typedef struct dd_batmap_hdr vhd_batmap_header_t; +typedef struct prt_loc vhd_parent_locator_t; +typedef struct vhd_context vhd_context_t; +typedef uint32_t vhd_flag_creat_t; + +struct vhd_bat { + uint32_t spb; + uint32_t entries; + uint32_t *bat; +}; + +struct vhd_batmap { + vhd_batmap_header_t header; + char *map; +}; + +struct vhd_context { + int fd; + char *file; + int oflags; + int is_block; + + uint32_t spb; + uint32_t bm_secs; + + vhd_header_t header; + vhd_footer_t footer; + vhd_bat_t bat; + vhd_batmap_t batmap; +}; + +static inline uint32_t +secs_round_up(uint64_t bytes) +{ + return ((bytes + (VHD_SECTOR_SIZE - 1)) >> VHD_SECTOR_SHIFT); +} + +static inline uint32_t +secs_round_up_no_zero(uint64_t bytes) +{ + return (secs_round_up(bytes) ? : 1); +} + +static inline uint64_t +vhd_sectors_to_bytes(uint64_t sectors) +{ + return sectors << VHD_SECTOR_SHIFT; +} + +static inline uint64_t +vhd_bytes_padded(uint64_t bytes) +{ + return vhd_sectors_to_bytes(secs_round_up_no_zero(bytes)); +} + +static inline int +vhd_type_dynamic(vhd_context_t *ctx) +{ + return (ctx->footer.type == HD_TYPE_DYNAMIC || + ctx->footer.type == HD_TYPE_DIFF); +} + +static inline int +vhd_creator_tapdisk(vhd_context_t *ctx) +{ + return !strncmp(ctx->footer.crtr_app, "tap", 3); +} + +static inline int +vhd_disabled(vhd_context_t *ctx) +{ + return (!memcmp(ctx->footer.cookie, + VHD_POISON_COOKIE, sizeof(ctx->footer.cookie))); +} + +static inline size_t +vhd_parent_locator_size(vhd_parent_locator_t *loc) +{ + /* + * MICROSOFT_COMPAT + * data_space *should* be in sectors, + * but sometimes we find it in bytes + */ + if (loc->data_space < 512) + return vhd_sectors_to_bytes(loc->data_space); + else if (loc->data_space % 512 == 0) + return loc->data_space; + else + return 0; +} + +static inline int +vhd_parent_raw(vhd_context_t *ctx) +{ + return blk_uuid_is_nil(&ctx->header.prt_uuid); +} + +void libvhd_set_log_level(int); + +int vhd_test_file_fixed(const char *, int *); + +uint32_t vhd_time(time_t time); +size_t vhd_time_to_string(uint32_t timestamp, char *target); +uint32_t vhd_chs(uint64_t size); + +uint32_t vhd_checksum_footer(vhd_footer_t *); +uint32_t vhd_checksum_header(vhd_header_t *); +uint32_t vhd_checksum_batmap(vhd_batmap_t *); + +void vhd_footer_in(vhd_footer_t *); +void vhd_footer_out(vhd_footer_t *); +void vhd_header_in(vhd_header_t *); +void vhd_header_out(vhd_header_t *); +void vhd_bat_in(vhd_bat_t *); +void vhd_bat_out(vhd_bat_t *); +void vhd_batmap_header_in(vhd_batmap_t *); +void vhd_batmap_header_out(vhd_batmap_t *); + +int vhd_validate_footer(vhd_footer_t *footer); +int vhd_validate_header(vhd_header_t *header); +int vhd_validate_batmap_header(vhd_batmap_t *batmap); +int vhd_validate_batmap(vhd_batmap_t *batmap); +int vhd_validate_platform_code(uint32_t code); + +int vhd_open(vhd_context_t *, const char *file, int flags); +void vhd_close(vhd_context_t *); +int vhd_create(const char *name, uint64_t bytes, int type, vhd_flag_creat_t); +/* vhd_snapshot: the bytes parameter is optional and can be 0 if the snapshot + * is to have the same size as the (first non-empty) parent */ +int vhd_snapshot(const char *snapshot, uint64_t bytes, const char *parent, + vhd_flag_creat_t); + +int vhd_hidden(vhd_context_t *, int *); +int vhd_chain_depth(vhd_context_t *, int *); + +off_t vhd_position(vhd_context_t *); +int vhd_seek(vhd_context_t *, off_t, int); +int vhd_read(vhd_context_t *, void *, size_t); +int vhd_write(vhd_context_t *, void *, size_t); + +int vhd_offset(vhd_context_t *, uint32_t, uint32_t *); + +int vhd_end_of_headers(vhd_context_t *ctx, off_t *off); +int vhd_end_of_data(vhd_context_t *ctx, off_t *off); +int vhd_batmap_header_offset(vhd_context_t *ctx, off_t *off); + +int vhd_get_header(vhd_context_t *); +int vhd_get_footer(vhd_context_t *); +int vhd_get_bat(vhd_context_t *); +int vhd_get_batmap(vhd_context_t *); + +void vhd_put_header(vhd_context_t *); +void vhd_put_footer(vhd_context_t *); +void vhd_put_bat(vhd_context_t *); +void vhd_put_batmap(vhd_context_t *); + +int vhd_has_batmap(vhd_context_t *); +int vhd_batmap_test(vhd_context_t *, vhd_batmap_t *, uint32_t); +void vhd_batmap_set(vhd_context_t *, vhd_batmap_t *, uint32_t); +void vhd_batmap_clear(vhd_context_t *, vhd_batmap_t *, uint32_t); + +int vhd_get_phys_size(vhd_context_t *, off_t *); +int vhd_set_phys_size(vhd_context_t *, off_t); + +int vhd_bitmap_test(vhd_context_t *, char *, uint32_t); +void vhd_bitmap_set(vhd_context_t *, char *, uint32_t); +void vhd_bitmap_clear(vhd_context_t *, char *, uint32_t); + +int vhd_parent_locator_count(vhd_context_t *); +int vhd_parent_locator_get(vhd_context_t *, char **); +int vhd_parent_locator_read(vhd_context_t *, vhd_parent_locator_t *, char **); +int vhd_find_parent(vhd_context_t *, const char *, char **); +int vhd_parent_locator_write_at(vhd_context_t *, const char *, + off_t, uint32_t, size_t, + vhd_parent_locator_t *); + +int vhd_header_decode_parent(vhd_context_t *, vhd_header_t *, char **); +int vhd_change_parent(vhd_context_t *, char *parent_path, int raw); + +int vhd_read_footer(vhd_context_t *, vhd_footer_t *); +int vhd_read_footer_at(vhd_context_t *, vhd_footer_t *, off_t); +int vhd_read_footer_strict(vhd_context_t *, vhd_footer_t *); +int vhd_read_header(vhd_context_t *, vhd_header_t *); +int vhd_read_header_at(vhd_context_t *, vhd_header_t *, off_t); +int vhd_read_bat(vhd_context_t *, vhd_bat_t *); +int vhd_read_batmap(vhd_context_t *, vhd_batmap_t *); +int vhd_read_bitmap(vhd_context_t *, uint32_t block, char **bufp); +int vhd_read_block(vhd_context_t *, uint32_t block, char **bufp); + +int vhd_write_footer(vhd_context_t *, vhd_footer_t *); +int vhd_write_footer_at(vhd_context_t *, vhd_footer_t *, off_t); +int vhd_write_header(vhd_context_t *, vhd_header_t *); +int vhd_write_header_at(vhd_context_t *, vhd_header_t *, off_t); +int vhd_write_bat(vhd_context_t *, vhd_bat_t *); +int vhd_write_batmap(vhd_context_t *, vhd_batmap_t *); +int vhd_write_bitmap(vhd_context_t *, uint32_t block, char *bitmap); +int vhd_write_block(vhd_context_t *, uint32_t block, char *data); + +int vhd_io_read(vhd_context_t *, char *, uint64_t, uint32_t); +int vhd_io_write(vhd_context_t *, char *, uint64_t, uint32_t); + +#endif diff --git a/extracters/ods2/vhd/relative-path.c b/extracters/ods2/vhd/relative-path.c new file mode 100644 index 0000000..6423b40 --- /dev/null +++ b/extracters/ods2/vhd/relative-path.c @@ -0,0 +1,299 @@ +/* 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. +*/ +#include +#include +#include +#include + +#include "relative-path.h" + +#define sfree(ptr) \ +do { \ + free(ptr); \ + ptr = NULL; \ +} while (0) + +/* + * count number of tokens between DELIMETER characters + */ +static int +count_nodes(char *path) +{ + int i; + char *tmp; + + if (!path) + return 0; + + for (i = 0, tmp = path; *tmp != '\0'; tmp++) + if (*tmp == DELIMITER) + i++; + + return i; +} + +/* + * return copy of next node in @path, or NULL + * @path is moved to the end of the next node + * @err is set to -errno on failure + * copy should be freed + */ +static char * +next_node(char **path, int *err) +{ + int ret; + char *tmp, *start; + + if (!path || !*path) { + *err = -EINVAL; + return NULL; + } + + *err = 0; + start = *path; + + for (tmp = *path; *tmp != '\0'; tmp++) + if (*tmp == DELIMITER) { + int size; + char *node; + + size = tmp - start + 1; + node = malloc(size); + if (!node) { + *err = -ENOMEM; + return NULL; + } + + ret = snprintf(node, size, "%s", start); + if (ret < 0) { + free(node); + *err = -EINVAL; + return NULL; + } + + *path = tmp; + return node; + } + + return NULL; +} + +/* + * count number of nodes in common betwee @to and @from + * returns number of common nodes, or -errno on failure + */ +static int +count_common_nodes(char *to, char *from) +{ + int err, common; + char *to_node, *from_node; + + if (!to || !from) + return -EINVAL; + + err = 0; + common = 0; + to_node = NULL; + from_node = NULL; + + do { + to_node = next_node(&to, &err); + if (err || !to_node) + break; + + from_node = next_node(&from, &err); + if (err || !from_node) + break; + + if (strncmp(to_node, from_node, MAX_NAME_LEN)) + break; + + ++to; + ++from; + ++common; + sfree(to_node); + sfree(from_node); + + } while (1); + + sfree(to_node); + sfree(from_node); + + if (err) + return err; + + return common; +} + +/* + * construct path of @count '../', './' if @count is zero, or NULL on error + * result should be freed + */ +static char * +up_nodes(int count) +{ + char *path, *tmp; + int i, ret, len, size; + + if (!count) + return strdup("./"); + + len = strlen("../"); + size = len * count; + if (size >= MAX_NAME_LEN) + return NULL; + + path = malloc(size + 1); + if (!path) + return NULL; + + tmp = path; + for (i = 0; i < count; i++) { + ret = sprintf(tmp, "../"); + if (ret < 0 || ret != len) { + free(path); + return NULL; + } + tmp += ret; + } + + return path; +} + +/* + * return pointer to @offset'th node of path or NULL on error + */ +static char * +node_offset(char *from, int offset) +{ + char *path; + + if (!from || !offset) + return NULL; + + for (path = from; *path != '\0'; path++) { + if (*path == DELIMITER) + if (--offset == 0) + return path + 1; + } + + return NULL; +} + +/* + * return a relative path from @from to @to + * result should be freed + */ +char * +relative_path_to(char *from, char *to, int *err) +{ + int from_nodes, common; + char *to_absolute, *from_absolute; + char *up, *common_target_path, *relative_path; + + *err = 0; + up = NULL; + to_absolute = NULL; + from_absolute = NULL; + relative_path = NULL; + + if (strnlen(to, MAX_NAME_LEN) == MAX_NAME_LEN || + strnlen(from, MAX_NAME_LEN) == MAX_NAME_LEN) { + EPRINTF("invalid input; max path length is %d\n", + MAX_NAME_LEN); + *err = -ENAMETOOLONG; + return NULL; + } + + to_absolute = realpath(to, NULL); + if (!to_absolute) { + EPRINTF("failed to get absolute path of %s\n", to); + *err = -errno; + goto out; + } + + from_absolute = realpath(from, NULL); + if (!from_absolute) { + EPRINTF("failed to get absolute path of %s\n", from); + *err = -errno; + goto out; + } + + if (strnlen(to_absolute, MAX_NAME_LEN) == MAX_NAME_LEN || + strnlen(from_absolute, MAX_NAME_LEN) == MAX_NAME_LEN) { + EPRINTF("invalid input; max path length is %d\n", + MAX_NAME_LEN); + *err = -ENAMETOOLONG; + goto out; + } + + /* count nodes in source path */ + from_nodes = count_nodes(from_absolute); + + /* count nodes in common */ + common = count_common_nodes(to_absolute + 1, from_absolute + 1); + if (common < 0) { + EPRINTF("failed to count common nodes of %s and %s: %d\n", + to_absolute, from_absolute, common); + *err = common; + goto out; + } + + /* move up to common node */ + up = up_nodes(from_nodes - common - 1); + if (!up) { + EPRINTF("failed to allocate relative path for %s: %d\n", + from_absolute, -ENOMEM); + *err = -ENOMEM; + goto out; + } + + /* get path from common node to target */ + common_target_path = node_offset(to_absolute, common + 1); + if (!common_target_path) { + EPRINTF("failed to find common target path to %s: %d\n", + to_absolute, -EINVAL); + *err = -EINVAL; + goto out; + } + + /* get relative path */ + if (asprintf(&relative_path, "%s%s", up, common_target_path) == -1) { + EPRINTF("failed to construct final path %s%s: %d\n", + up, common_target_path, -ENOMEM); + relative_path = NULL; + *err = -ENOMEM; + goto out; + } + +out: + sfree(up); + sfree(to_absolute); + sfree(from_absolute); + + return relative_path; +} diff --git a/extracters/ods2/vhd/relative-path.h b/extracters/ods2/vhd/relative-path.h new file mode 100644 index 0000000..9ab1efa --- /dev/null +++ b/extracters/ods2/vhd/relative-path.h @@ -0,0 +1,43 @@ +/* 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. +*/ +#ifndef _RELATIVE_PATH_H_ +#define _RELATIVE_PATH_H_ + +#include + +#define DELIMITER '/' +#define MAX_NAME_LEN 1000 + +#define EPRINTF(_f, _a...) syslog(LOG_ERR, "tap-err:%s: " _f, __func__, ##_a) + +/* + * returns a relative path from @src to @dest + * result should be freed + */ +char *relative_path_to(char *src, char *dest, int *err); + +#endif diff --git a/extracters/ods2/vhd/vhd.h b/extracters/ods2/vhd/vhd.h new file mode 100644 index 0000000..ca29638 --- /dev/null +++ b/extracters/ods2/vhd/vhd.h @@ -0,0 +1,219 @@ +/* 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. +*/ +#ifndef __VHD_H__ +#define __VHD_H__ + +#include + +typedef uint32_t u32; +typedef uint64_t u64; + +#define DEBUG 1 + +/* ---------------------------------------------------------------------- */ +/* General definitions. */ +/* ---------------------------------------------------------------------- */ + +#define VHD_SECTOR_SIZE 512 +#define VHD_SECTOR_SHIFT 9 + +/* ---------------------------------------------------------------------- */ +/* This is the generic disk footer, used by all disks. */ +/* ---------------------------------------------------------------------- */ + +struct hd_ftr { + char cookie[8]; /* Identifies original creator of the disk */ + u32 features; /* Feature Support -- see below */ + u32 ff_version; /* (major,minor) version of disk file */ + u64 data_offset; /* Abs. offset from SOF to next structure */ + u32 timestamp; /* Creation time. secs since 1/1/2000GMT */ + char crtr_app[4]; /* Creator application */ + u32 crtr_ver; /* Creator version (major,minor) */ + u32 crtr_os; /* Creator host OS */ + u64 orig_size; /* Size at creation (bytes) */ + u64 curr_size; /* Current size of disk (bytes) */ + u32 geometry; /* Disk geometry */ + u32 type; /* Disk type */ + u32 checksum; /* 1's comp sum of this struct. */ + blk_uuid_t uuid; /* Unique disk ID, used for naming parents */ + char saved; /* one-bit -- is this disk/VM in a saved state? */ + char hidden; /* tapdisk-specific field: is this vdi hidden? */ + char reserved[426]; /* padding */ +}; + +/* VHD cookie string. */ +static const char HD_COOKIE[9] = "conectix"; + +/* Feature fields in hd_ftr */ +#define HD_NO_FEATURES 0x00000000 +#define HD_TEMPORARY 0x00000001 /* disk can be deleted on shutdown */ +#define HD_RESERVED 0x00000002 /* NOTE: must always be set */ + +/* Version field in hd_ftr */ +#define HD_FF_VERSION 0x00010000 + +/* Known creator OS type fields in hd_ftr.crtr_os */ +#define HD_CR_OS_WINDOWS 0x5769326B /* (Wi2k) */ +#define HD_CR_OS_MACINTOSH 0x4D616320 /* (Mac ) */ + +/* + * version 0.1: little endian bitmaps + * version 1.1: big endian bitmaps; batmap + * version 1.2: libvhd + * version 1.3: batmap version bump to 1.2 + */ +#define VHD_VERSION(major, minor) (((major) << 16) | ((minor) & 0x0000FFFF)) +#define VHD_CURRENT_VERSION VHD_VERSION(1, 3) + +/* Disk geometry accessor macros. */ +/* Geometry is a triple of (cylinders (2 bytes), tracks (1 byte), and + * secotrs-per-track (1 byte)) + */ +#define GEOM_GET_CYLS(_g) (((_g) >> 16) & 0xffff) +#define GEOM_GET_HEADS(_g) (((_g) >> 8) & 0xff) +#define GEOM_GET_SPT(_g) ((_g) & 0xff) + +#define GEOM_ENCODE(_c, _h, _s) (((_c) << 16) | ((_h) << 8) | (_s)) + +/* type field in hd_ftr */ +#define HD_TYPE_NONE 0 +#define HD_TYPE_FIXED 2 /* fixed-allocation disk */ +#define HD_TYPE_DYNAMIC 3 /* dynamic disk */ +#define HD_TYPE_DIFF 4 /* differencing disk */ + +/* String table for hd.type */ +static const char *HD_TYPE_STR[7] = { + "None", /* 0 */ + "Reserved (deprecated)", /* 1 */ + "Fixed hard disk", /* 2 */ + "Dynamic hard disk", /* 3 */ + "Differencing hard disk", /* 4 */ + "Reserved (deprecated)", /* 5 */ + "Reserved (deprecated)" /* 6 */ +}; + +#define HD_TYPE_MAX 6 + +struct prt_loc { + u32 code; /* Platform code -- see defines below. */ + u32 data_space; /* Number of 512-byte sectors to store locator */ + u32 data_len; /* Actual length of parent locator in bytes */ + u32 res; /* Must be zero */ + u64 data_offset; /* Absolute offset of locator data (bytes) */ +}; + +/* Platform Codes */ +#define PLAT_CODE_NONE 0x0 +#define PLAT_CODE_WI2R 0x57693272 /* deprecated */ +#define PLAT_CODE_WI2K 0x5769326B /* deprecated */ +#define PLAT_CODE_W2RU 0x57327275 /* Windows relative path (UTF-16) */ +#define PLAT_CODE_W2KU 0x57326B75 /* Windows absolute path (UTF-16) */ +#define PLAT_CODE_MAC 0x4D616320 /* MacOS alias stored as a blob. */ +#define PLAT_CODE_MACX 0x4D616358 /* File URL (UTF-8), see RFC 2396. */ + +/* ---------------------------------------------------------------------- */ +/* This is the dynamic disk header. */ +/* ---------------------------------------------------------------------- */ + +struct dd_hdr { + char cookie[8]; /* Should contain "cxsparse" */ + u64 data_offset; /* Byte offset of next record. (Unused) 0xffs */ + u64 table_offset; /* Absolute offset to the BAT. */ + u32 hdr_ver; /* Version of the dd_hdr (major,minor) */ + u32 max_bat_size; /* Maximum number of entries in the BAT */ + u32 block_size; /* Block size in bytes. Must be power of 2. */ + u32 checksum; /* Header checksum. 1's comp of all fields. */ + blk_uuid_t prt_uuid; /* ID of the parent disk. */ + u32 prt_ts; /* Modification time of the parent disk */ + u32 res1; /* Reserved. */ + char prt_name[512]; /* Parent unicode name. */ + struct prt_loc loc[8]; /* Parent locator entries. */ + char res2[256]; /* Reserved. */ +}; + +/* VHD cookie string. */ +static const char DD_COOKIE[9] = "cxsparse"; + +/* Version field in hd_ftr */ +#define DD_VERSION 0x00010000 + +/* Default blocksize is 2 meg. */ +#define DD_BLOCKSIZE_DEFAULT 0x00200000 + +#define DD_BLK_UNUSED 0xFFFFFFFF + +struct dd_batmap_hdr { + char cookie[8]; /* should contain "tdbatmap" */ + u64 batmap_offset; /* byte offset to batmap */ + u32 batmap_size; /* batmap size in sectors */ + u32 batmap_version; /* version of batmap */ + u32 checksum; /* batmap checksum -- 1's complement of batmap */ +}; + +static const char VHD_BATMAP_COOKIE[9] = "tdbatmap"; + +/* + * version 1.1: signed char checksum + */ +#define VHD_BATMAP_VERSION(major, minor) (((major) << 16) | ((minor) & 0x0000FFFF)) +#define VHD_BATMAP_CURRENT_VERSION VHD_BATMAP_VERSION(1, 2) + +/* Layout of a dynamic disk: + * + * +-------------------------------------------------+ + * | Mirror image of HD footer (hd_ftr) (512 bytes) | + * +-------------------------------------------------+ + * | Sparse drive header (dd_hdr) (1024 bytes) | + * +-------------------------------------------------+ + * | BAT (Block allocation table) | + * | - Array of absolute sector offsets into the | + * | file (u32). | + * | - Rounded up to a sector boundary. | + * | - Unused entries are marked as 0xFFFFFFFF | + * | - max entries in dd_hdr->max_bat_size | + * +-------------------------------------------------+ + * | Data Block 0 | + * | Bitmap (padded to 512 byte sector boundary) | + * | - each bit indicates whether the associated | + * | sector within this block is used. | + * | Data | + * | - power-of-two multiple of sectors. | + * | - default 2MB (4096 * 512) | + * | - Any entries with zero in bitmap should be | + * | zero on disk | + * +-------------------------------------------------+ + * | Data Block 1 | + * +-------------------------------------------------+ + * | ... | + * +-------------------------------------------------+ + * | Data Block n | + * +-------------------------------------------------+ + * | HD Footer (511 bytes) | + * +-------------------------------------------------+ + */ + +#endif