mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
3353 lines
61 KiB
C
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 != 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);
|
|
}
|