2011-03-01 17:48:11 -08:00

3353 lines
61 KiB
C

/* 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 <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <libgen.h>
#include <iconv.h>
#include <sys/mman.h>
#include <sys/stat.h>
#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 != 0xFFFFFFFFFFFFFFFFLLU) {
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, &copy, 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 = 0xFFFFFFFFFFFFFFFFLLU;
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);
}