From b00e213506c76adb305cf0df8224a87107544966 Mon Sep 17 00:00:00 2001 From: Timothe Litt Date: Fri, 25 Mar 2016 12:37:44 -0400 Subject: [PATCH] Windows support for libvhd and vhd-util Added windows support for libvhd and vhd-util. Includes bug fixes as well. See the README for details. --- extracters/libvhd/include/blk_uuid.h | 200 + extracters/libvhd/include/libvhd.h | 363 ++ extracters/libvhd/include/relative-path.h | 59 + extracters/libvhd/include/vhd.h | 228 + extracters/libvhd/libvhd.c | 4190 ++++++++++++++++++ extracters/libvhd/libvhd.sln | 41 + extracters/libvhd/libvhd.vcxproj | 125 + extracters/libvhd/libvhd.vcxproj.filters | 39 + extracters/libvhd/relative-path.c | 442 ++ extracters/vhd-util/COPYING | 33 + extracters/vhd-util/INSTALL | 31 + extracters/vhd-util/Makefile | 115 + extracters/vhd-util/README | 229 + extracters/vhd-util/atomicio.c | 62 + extracters/vhd-util/gnugetopt.c | 506 +++ extracters/vhd-util/include/atomicio.h | 33 + extracters/vhd-util/include/libvhd-journal.h | 68 + extracters/vhd-util/include/list.h | 124 + extracters/vhd-util/include/lvm-util.h | 71 + extracters/vhd-util/include/vhd-util-win32.h | 73 + extracters/vhd-util/include/vhd-util.h | 44 + extracters/vhd-util/include/wingetopt.h | 88 + extracters/vhd-util/libvhd-journal.c | 1618 +++++++ extracters/vhd-util/lvm-util.c | 349 ++ extracters/vhd-util/vhd-util | Bin 0 -> 93712 bytes extracters/vhd-util/vhd-util-check.c | 1024 +++++ extracters/vhd-util/vhd-util-coalesce.c | 232 + extracters/vhd-util/vhd-util-create.c | 82 + extracters/vhd-util/vhd-util-fill.c | 119 + extracters/vhd-util/vhd-util-modify.c | 144 + extracters/vhd-util/vhd-util-query.c | 161 + extracters/vhd-util/vhd-util-read.c | 876 ++++ extracters/vhd-util/vhd-util-repair.c | 86 + extracters/vhd-util/vhd-util-resize.c | 1199 +++++ extracters/vhd-util/vhd-util-revert.c | 108 + extracters/vhd-util/vhd-util-scan.c | 1319 ++++++ extracters/vhd-util/vhd-util-set-field.c | 106 + extracters/vhd-util/vhd-util-snapshot.c | 225 + extracters/vhd-util/vhd-util.c | 168 + extracters/vhd-util/vhd-util.exe | Bin 0 -> 202240 bytes extracters/vhd-util/vhd-util.vcxproj | 145 + extracters/vhd-util/vhd-util.vcxproj.filters | 93 + 42 files changed, 15218 insertions(+) create mode 100644 extracters/libvhd/include/blk_uuid.h create mode 100644 extracters/libvhd/include/libvhd.h create mode 100644 extracters/libvhd/include/relative-path.h create mode 100644 extracters/libvhd/include/vhd.h create mode 100644 extracters/libvhd/libvhd.c create mode 100644 extracters/libvhd/libvhd.sln create mode 100644 extracters/libvhd/libvhd.vcxproj create mode 100644 extracters/libvhd/libvhd.vcxproj.filters create mode 100644 extracters/libvhd/relative-path.c create mode 100644 extracters/vhd-util/COPYING create mode 100644 extracters/vhd-util/INSTALL create mode 100644 extracters/vhd-util/Makefile create mode 100644 extracters/vhd-util/README create mode 100644 extracters/vhd-util/atomicio.c create mode 100644 extracters/vhd-util/gnugetopt.c create mode 100644 extracters/vhd-util/include/atomicio.h create mode 100644 extracters/vhd-util/include/libvhd-journal.h create mode 100644 extracters/vhd-util/include/list.h create mode 100644 extracters/vhd-util/include/lvm-util.h create mode 100644 extracters/vhd-util/include/vhd-util-win32.h create mode 100644 extracters/vhd-util/include/vhd-util.h create mode 100644 extracters/vhd-util/include/wingetopt.h create mode 100644 extracters/vhd-util/libvhd-journal.c create mode 100644 extracters/vhd-util/lvm-util.c create mode 100755 extracters/vhd-util/vhd-util create mode 100644 extracters/vhd-util/vhd-util-check.c create mode 100644 extracters/vhd-util/vhd-util-coalesce.c create mode 100644 extracters/vhd-util/vhd-util-create.c create mode 100644 extracters/vhd-util/vhd-util-fill.c create mode 100644 extracters/vhd-util/vhd-util-modify.c create mode 100644 extracters/vhd-util/vhd-util-query.c create mode 100644 extracters/vhd-util/vhd-util-read.c create mode 100644 extracters/vhd-util/vhd-util-repair.c create mode 100644 extracters/vhd-util/vhd-util-resize.c create mode 100644 extracters/vhd-util/vhd-util-revert.c create mode 100644 extracters/vhd-util/vhd-util-scan.c create mode 100644 extracters/vhd-util/vhd-util-set-field.c create mode 100644 extracters/vhd-util/vhd-util-snapshot.c create mode 100644 extracters/vhd-util/vhd-util.c create mode 100755 extracters/vhd-util/vhd-util.exe create mode 100644 extracters/vhd-util/vhd-util.vcxproj create mode 100644 extracters/vhd-util/vhd-util.vcxproj.filters diff --git a/extracters/libvhd/include/blk_uuid.h b/extracters/libvhd/include/blk_uuid.h new file mode 100644 index 0000000..e176892 --- /dev/null +++ b/extracters/libvhd/include/blk_uuid.h @@ -0,0 +1,200 @@ +/* Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/* Modified March 2016 Timothe Litt to work under windows. +* Copyright (C) 2016 Timothe Litt +* Modifications subject to the same license terms as above, +* substituting "Timothe Litt" for "XenSource Inc." +*/ + +#ifndef __BLKTAP2_UUID_H__ +#define __BLKTAP2_UUID_H__ + +#if defined(__linux__) + +#include + +typedef struct { + uuid_t uuid; +} blk_uuid_t; + +static inline int blk_uuid_is_nil(blk_uuid_t *uuid) +{ + return uuid_is_null(uuid->uuid); +} + +static inline void blk_uuid_generate(blk_uuid_t *uuid) +{ + uuid_generate(uuid->uuid); +} + +static inline void blk_uuid_to_string(blk_uuid_t *uuid, char *out, size_t size) +{ + (void) size; + uuid_unparse(uuid->uuid, out); +} + +static inline void blk_uuid_from_string(blk_uuid_t *uuid, const char *in) +{ + uuid_parse(in, uuid->uuid); +} + +static inline void blk_uuid_copy(blk_uuid_t *dst, blk_uuid_t *src) +{ + uuid_copy(dst->uuid, src->uuid); +} + +static inline void blk_uuid_clear(blk_uuid_t *uuid) +{ + uuid_clear(uuid->uuid); +} + +static inline int blk_uuid_compare(blk_uuid_t *uuid1, blk_uuid_t *uuid2) +{ + return uuid_compare(uuid1->uuid, uuid2->uuid); +} + +#elif defined(__NetBSD__) + +#include +#include +#include + +typedef uuid_t blk_uuid_t; + +static inline int blk_uuid_is_nil(blk_uuid_t *uuid) +{ + uint32_t status; + return uuid_is_nil((uuid_t *)uuid, &status); +} + +static inline void blk_uuid_generate(blk_uuid_t *uuid) +{ + uint32_t status; + uuid_create((uuid_t *)uuid, &status); +} + +static inline void blk_uuid_to_string(blk_uuid_t *uuid, char *out, size_t size) +{ + uint32_t status; + char *_out = NULL; + uuid_to_string((uuid_t *)uuid, &_out, &status); + strlcpy(out, _out, size); + free(_out); +} + +static inline void blk_uuid_from_string(blk_uuid_t *uuid, const char *in) +{ + uint32_t status; + uuid_from_string(in, (uuid_t *)uuid, &status); +} + +static inline void blk_uuid_copy(blk_uuid_t *dst, blk_uuid_t *src) +{ + memcpy((uuid_t *)dst, (uuid_t *)src, sizeof(uuid_t)); +} + +static inline void blk_uuid_clear(blk_uuid_t *uuid) +{ + memset((uuid_t *)uuid, 0, sizeof(uuid_t)); +} + +static inline int blk_uuid_compare(blk_uuid_t *uuid1, blk_uuid_t *uuid2) +{ + uint32_t status; + return uuid_compare((uuid_t *)uuid1, (uuid_t *)uuid2, &status); +} + +#elif defined(_WIN32) +#include +typedef UUID blk_uuid_t; + +static size_t strlcpy( char *dst, const char *src, size_t size ) { + size_t srclen; + + size--; + srclen = strlen( src ); + + if( srclen > size ) + srclen = size; + + memcpy( dst, src, srclen ); + dst[srclen] = '\0'; + + return (srclen); +} +static inline int blk_uuid_is_nil( blk_uuid_t *uuid ) +{ + RPC_STATUS status; + return UuidIsNil( (uuid_t *)uuid, &status ); +} + +static inline void blk_uuid_generate( blk_uuid_t *uuid ) +{ + RPC_STATUS status; + status = UuidCreate( (uuid_t *)uuid ); + if( status == RPC_S_OK || status == RPC_S_UUID_LOCAL_ONLY ) + return; + abort(); +} + +static inline void blk_uuid_to_string( blk_uuid_t *uuid, char *out, size_t size ) +{ + RPC_CSTR _out = NULL; + if( UuidToString( (uuid_t *)uuid, &_out ) != RPC_S_OK ) + return; + strlcpy( out, (const char *)_out, size ); + RpcStringFree( &_out ); + return; +} + +static inline void blk_uuid_from_string( blk_uuid_t *uuid, const char *in ) +{ + UuidFromString( (RPC_CSTR)in, (uuid_t *)uuid ); +} + +static inline void blk_uuid_copy( blk_uuid_t *dst, blk_uuid_t *src ) +{ + memcpy( (uuid_t *)dst, (uuid_t *)src, sizeof( uuid_t ) ); +} + +static inline void blk_uuid_clear( blk_uuid_t *uuid ) +{ + memset( (uuid_t *)uuid, 0, sizeof( uuid_t ) ); +} + +static inline int blk_uuid_compare( blk_uuid_t *uuid1, blk_uuid_t *uuid2 ) +{ + RPC_STATUS status; + return UuidCompare( (uuid_t *)uuid1, (uuid_t *)uuid2, &status ); +} +#else + +#error "Please update blk_uuid.h for your OS" + +#endif + +#endif /* __BLKTAP2_UUID_H__ */ diff --git a/extracters/libvhd/include/libvhd.h b/extracters/libvhd/include/libvhd.h new file mode 100644 index 0000000..6f607f5 --- /dev/null +++ b/extracters/libvhd/include/libvhd.h @@ -0,0 +1,363 @@ +/* Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/* Modified March 2016 Timothe Litt to work under windows. +* Copyright (C) 2016 Timothe Litt +* Modifications subject to the same license terms as above, +* substituting "Timothe Litt" for "XenSource Inc." +*/ + +#ifndef _VHD_LIB_H_ +#define _VHD_LIB_H_ + +#define _CRT_SECURE_NO_WARNINGS 1 + +#include +#if defined(__linux__) +#include +#include +#elif defined(__NetBSD__) +#include +#include +#elif defined(_WIN32) +#undef BYTE_ORDER +#undef LITTLE_ENDIAN +#define BYTE_ORDER 1234 +#define LITTLE_ENDIAN 1234 +#define bswap_16(val) _byteswap_ushort(val) +#define bswap_32(val) _byteswap_ulong(val) +#define bswap_64(val) _byteswap_uint64(val) +#endif + +#include "blk_uuid.h" +#include "vhd.h" + +#ifndef O_LARGEFILE +#define O_LARGEFILE 0 +#endif + +#if defined(_WIN32) && defined(LIBVHD_DLL) +#ifdef LIBVHD_EXPORTS +#define LIBVHD_API __declspec(dllexport) +#else +#define LIBVHD_API __declspec(dllimport) +#endif +#else +#define LIBVHD_API +#endif + +#if BYTE_ORDER == LITTLE_ENDIAN +#if defined(__linux__) || defined(_WIN32) + #define BE16_IN(foo) (*(foo)) = bswap_16(*(foo)) + #define BE32_IN(foo) (*(foo)) = bswap_32(*(foo)) + #define BE64_IN(foo) (*(foo)) = bswap_64(*(foo)) + #define BE16_OUT(foo) (*(foo)) = bswap_16(*(foo)) + #define BE32_OUT(foo) (*(foo)) = bswap_32(*(foo)) + #define BE64_OUT(foo) (*(foo)) = bswap_64(*(foo)) +#elif defined(__NetBSD__) + #define BE16_IN(foo) (*(foo)) = bswap16(*(foo)) + #define BE32_IN(foo) (*(foo)) = bswap32(*(foo)) + #define BE64_IN(foo) (*(foo)) = bswap64(*(foo)) + #define BE16_OUT(foo) (*(foo)) = bswap16(*(foo)) + #define BE32_OUT(foo) (*(foo)) = bswap32(*(foo)) + #define BE64_OUT(foo) (*(foo)) = bswap64(*(foo)) +#endif +#else + #define BE16_IN(foo) + #define BE32_IN(foo) + #define BE64_IN(foo) + #define BE32_OUT(foo) + #define BE32_OUT(foo) + #define BE64_OUT(foo) +#endif + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +#define VHD_MAX_NAME_LEN 1024 + +#define VHD_BLOCK_SHIFT 21 +#define VHD_BLOCK_SIZE (1ULL << VHD_BLOCK_SHIFT) + +#define UTF_16 "UTF-16" +#define UTF_16LE "UTF-16LE" +#define UTF_16BE "UTF-16BE" + +#define VHD_OPEN_RDONLY 0x00001 +#define VHD_OPEN_RDWR 0x00002 +#define VHD_OPEN_FAST 0x00004 +#define VHD_OPEN_STRICT 0x00008 +#define VHD_OPEN_IGNORE_DISABLED 0x00010 + +#define VHD_FLAG_CREAT_PARENT_RAW 0x00001 + +#define vhd_flag_set(word, flag) ((word) |= (flag)) +#define vhd_flag_clear(word, flag) ((word) &= ~(flag)) +#define vhd_flag_test(word, flag) ((word) & (flag)) + + +#ifndef _WIN32x +#define ENABLE_FAILURE_TESTING 1 +#endif +#define FAIL_REPARENT_BEGIN 0 +#define FAIL_REPARENT_LOCATOR 1 +#define FAIL_REPARENT_END 2 +#define FAIL_RESIZE_BEGIN 3 +#define FAIL_RESIZE_DATA_MOVED 4 +#define FAIL_RESIZE_METADATA_MOVED 5 +#define FAIL_RESIZE_END 6 +#define NUM_FAIL_TESTS 7 + +#ifdef ENABLE_FAILURE_TESTING +#define TEST_FAIL_AT(point) \ + if (TEST_FAIL[point]) { \ + printf("Failing at %s\n", ENV_VAR_FAIL[point]); exit(EINVAL); } +#ifdef _WIN32 +#define TEST_FAIL_EXTERN_VARS \ +LIBVHD_API extern const char* ENV_VAR_FAIL[]; \ +LIBVHD_API extern int TEST_FAIL[]; +#else +#define TEST_FAIL_EXTERN_VARS \ +extern const char* ENV_VAR_FAIL[]; \ +extern int TEST_FAIL[]; +#endif +#else +#define TEST_FAIL_AT(point) +#define TEST_FAIL_EXTERN_VARS +#endif /* ENABLE_FAILURE_TESTING */ + + +static const char VHD_POISON_COOKIE[] = "v_poison"; + +typedef struct hd_ftr vhd_footer_t; +typedef struct dd_hdr vhd_header_t; +typedef struct vhd_bat vhd_bat_t; +typedef struct vhd_batmap vhd_batmap_t; +typedef struct dd_batmap_hdr vhd_batmap_header_t; +typedef struct prt_loc vhd_parent_locator_t; +typedef struct vhd_context vhd_context_t; +typedef uint32_t vhd_flag_creat_t; + +struct vhd_bat { + uint32_t spb; + uint32_t entries; + uint32_t *bat; +}; + +struct vhd_batmap { + vhd_batmap_header_t header; + char *map; +}; + +struct vhd_context { + int fd; + char *file; + int oflags; + int is_block; + + uint32_t spb; + uint32_t bm_secs; + + vhd_header_t header; + vhd_footer_t footer; + vhd_bat_t bat; + vhd_batmap_t batmap; +}; + +static inline uint32_t +secs_round_up(uint64_t bytes) +{ + return (uint32_t)((bytes + (VHD_SECTOR_SIZE - 1)) >> VHD_SECTOR_SHIFT); +} + +static inline uint32_t +secs_round_up_no_zero(uint64_t bytes) +{ + uint32_t result; + + result = secs_round_up(bytes); + return ( result? result : 1); +} + +static inline uint64_t +vhd_sectors_to_bytes(uint64_t sectors) +{ + return sectors << VHD_SECTOR_SHIFT; +} + +static inline uint64_t +vhd_bytes_padded(uint64_t bytes) +{ + return vhd_sectors_to_bytes(secs_round_up_no_zero(bytes)); +} + +static inline int +vhd_type_dynamic(vhd_context_t *ctx) +{ + return (ctx->footer.type == HD_TYPE_DYNAMIC || + ctx->footer.type == HD_TYPE_DIFF); +} + +static inline int +vhd_creator_tapdisk(vhd_context_t *ctx) +{ + return !strncmp(ctx->footer.crtr_app, "tap", 3); +} + +static inline int +vhd_disabled(vhd_context_t *ctx) +{ + return (!memcmp(ctx->footer.cookie, + VHD_POISON_COOKIE, sizeof(ctx->footer.cookie))); +} + +static inline size_t +vhd_parent_locator_size(vhd_parent_locator_t *loc) +{ + /* + * MICROSOFT_COMPAT + * data_space *should* be in sectors, + * but sometimes we find it in bytes + */ + if (loc->data_space < 512) + return (size_t)vhd_sectors_to_bytes(loc->data_space); + else if (loc->data_space % 512 == 0) + return loc->data_space; + else + return 0; +} + +static inline int +vhd_parent_raw(vhd_context_t *ctx) +{ + return blk_uuid_is_nil(&ctx->header.prt_uuid); +} + +LIBVHD_API void libvhd_set_log_level(int); + +LIBVHD_API int vhd_test_file_fixed(const char *, int *); + +LIBVHD_API uint32_t vhd_time(time_t time); +LIBVHD_API size_t vhd_time_to_string(uint32_t timestamp, char *target); +LIBVHD_API uint32_t vhd_chs(uint64_t size); + +LIBVHD_API uint32_t vhd_checksum_footer(vhd_footer_t *); +LIBVHD_API uint32_t vhd_checksum_header(vhd_header_t *); +LIBVHD_API uint32_t vhd_checksum_batmap(vhd_batmap_t *); + +LIBVHD_API void vhd_footer_in(vhd_footer_t *); +LIBVHD_API void vhd_footer_out(vhd_footer_t *); +LIBVHD_API void vhd_header_in(vhd_header_t *); +LIBVHD_API void vhd_header_out(vhd_header_t *); +LIBVHD_API void vhd_bat_in(vhd_bat_t *); +LIBVHD_API void vhd_bat_out(vhd_bat_t *); +LIBVHD_API void vhd_batmap_header_in(vhd_batmap_t *); +LIBVHD_API void vhd_batmap_header_out(vhd_batmap_t *); + +LIBVHD_API int vhd_validate_footer(vhd_footer_t *footer); +LIBVHD_API int vhd_validate_header(vhd_header_t *header); +LIBVHD_API int vhd_validate_batmap_header(vhd_batmap_t *batmap); +LIBVHD_API int vhd_validate_batmap(vhd_batmap_t *batmap); +LIBVHD_API int vhd_validate_platform_code(uint32_t code); + +LIBVHD_API int vhd_open(vhd_context_t *, const char *file, int flags); +LIBVHD_API void vhd_close(vhd_context_t *); +LIBVHD_API int vhd_create(const char *name, uint64_t bytes, int type, vhd_flag_creat_t); +/* vhd_snapshot: the bytes parameter is optional and can be 0 if the snapshot + * is to have the same size as the (first non-empty) parent */ +LIBVHD_API int vhd_snapshot(const char *snapshot, uint64_t bytes, const char *parent, + vhd_flag_creat_t); + +LIBVHD_API int vhd_hidden(vhd_context_t *, int *); +LIBVHD_API int vhd_chain_depth(vhd_context_t *, int *); + +LIBVHD_API off_t vhd_position(vhd_context_t *); +LIBVHD_API int vhd_seek(vhd_context_t *, off_t, int); +LIBVHD_API int vhd_read(vhd_context_t *, void *, size_t); +LIBVHD_API int vhd_write(vhd_context_t *, void *, size_t); + +LIBVHD_API int vhd_offset(vhd_context_t *, uint32_t, uint32_t *); + +LIBVHD_API int vhd_end_of_headers(vhd_context_t *ctx, off_t *off); +LIBVHD_API int vhd_end_of_data(vhd_context_t *ctx, off_t *off); +LIBVHD_API int vhd_batmap_header_offset(vhd_context_t *ctx, off_t *off); + +LIBVHD_API int vhd_get_header(vhd_context_t *); +LIBVHD_API int vhd_get_footer(vhd_context_t *); +LIBVHD_API int vhd_get_bat(vhd_context_t *); +LIBVHD_API int vhd_get_batmap(vhd_context_t *); + +LIBVHD_API void vhd_put_header(vhd_context_t *); +LIBVHD_API void vhd_put_footer(vhd_context_t *); +LIBVHD_API void vhd_put_bat(vhd_context_t *); +LIBVHD_API void vhd_put_batmap(vhd_context_t *); + +LIBVHD_API int vhd_has_batmap(vhd_context_t *); +LIBVHD_API int vhd_batmap_test(vhd_context_t *, vhd_batmap_t *, uint32_t); +LIBVHD_API void vhd_batmap_set(vhd_context_t *, vhd_batmap_t *, uint32_t); +LIBVHD_API void vhd_batmap_clear(vhd_context_t *, vhd_batmap_t *, uint32_t); + +LIBVHD_API int vhd_get_phys_size(vhd_context_t *, off_t *); +LIBVHD_API int vhd_set_phys_size(vhd_context_t *, off_t); + +LIBVHD_API int vhd_bitmap_test(vhd_context_t *, char *, uint32_t); +LIBVHD_API void vhd_bitmap_set(vhd_context_t *, char *, uint32_t); +LIBVHD_API void vhd_bitmap_clear(vhd_context_t *, char *, uint32_t); + +LIBVHD_API int vhd_parent_locator_count(vhd_context_t *); +LIBVHD_API int vhd_parent_locator_get(vhd_context_t *, char **); +LIBVHD_API int vhd_parent_locator_read(vhd_context_t *, vhd_parent_locator_t *, char **); +LIBVHD_API int vhd_find_parent(vhd_context_t *, const char *, char **); +LIBVHD_API int vhd_parent_locator_write_at(vhd_context_t *, const char *, + off_t, uint32_t, size_t, + vhd_parent_locator_t *); + +LIBVHD_API int vhd_header_decode_parent(vhd_context_t *, vhd_header_t *, char **); +LIBVHD_API int vhd_change_parent(vhd_context_t *, char *parent_path, int raw); + +LIBVHD_API int vhd_read_footer(vhd_context_t *, vhd_footer_t *); +LIBVHD_API int vhd_read_footer_at(vhd_context_t *, vhd_footer_t *, off_t); +LIBVHD_API int vhd_read_footer_strict(vhd_context_t *, vhd_footer_t *); +LIBVHD_API int vhd_read_header(vhd_context_t *, vhd_header_t *); +LIBVHD_API int vhd_read_header_at(vhd_context_t *, vhd_header_t *, off_t); +LIBVHD_API int vhd_read_bat(vhd_context_t *, vhd_bat_t *); +LIBVHD_API int vhd_read_batmap(vhd_context_t *, vhd_batmap_t *); +LIBVHD_API int vhd_read_bitmap(vhd_context_t *, uint32_t block, char **bufp); +LIBVHD_API int vhd_read_block(vhd_context_t *, uint32_t block, char **bufp); + +LIBVHD_API int vhd_write_footer(vhd_context_t *, vhd_footer_t *); +LIBVHD_API int vhd_write_footer_at(vhd_context_t *, vhd_footer_t *, off_t); +LIBVHD_API int vhd_write_header(vhd_context_t *, vhd_header_t *); +LIBVHD_API int vhd_write_header_at(vhd_context_t *, vhd_header_t *, off_t); +LIBVHD_API int vhd_write_bat(vhd_context_t *, vhd_bat_t *); +LIBVHD_API int vhd_write_batmap(vhd_context_t *, vhd_batmap_t *); +LIBVHD_API int vhd_write_bitmap(vhd_context_t *, uint32_t block, char *bitmap); +LIBVHD_API int vhd_write_block(vhd_context_t *, uint32_t block, char *data); + +LIBVHD_API int vhd_io_read(vhd_context_t *, char *, uint64_t, uint32_t); +LIBVHD_API int vhd_io_write(vhd_context_t *, char *, uint64_t, uint32_t); + +#endif diff --git a/extracters/libvhd/include/relative-path.h b/extracters/libvhd/include/relative-path.h new file mode 100644 index 0000000..18ed134 --- /dev/null +++ b/extracters/libvhd/include/relative-path.h @@ -0,0 +1,59 @@ +/* Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/* Modified March 2016 Timothe Litt to work under windows. +* Copyright (C) 2016 Timothe Litt +* Modifications subject to the same license terms as above, +* substituting "Timothe Litt" for "XenSource Inc." +*/ + +#ifndef _RELATIVE_PATH_H_ +#define _RELATIVE_PATH_H_ + +#define MAX_NAME_LEN 1000 + +#if defined(_WIN32) && defined(LIBVHD_DLL) +#ifdef LIBVHD_EXPORTS +#define LIBVHD_API __declspec(dllexport) +#else +#define LIBVHD_API __declspec(dllimport) +#endif +#else +#define LIBVHD_API +#endif + +/* + * returns a relative path from @src to @dest + * result should be freed + */ +LIBVHD_API char *relative_path_to(char *src, char *dest, int *err); + +#ifdef _WIN32 +LIBVHD_API char *realpath( const char *path, char *resolved ); +LIBVHD_API int asprintf( char **result, const char *fmt, ... ); +#endif + +#endif diff --git a/extracters/libvhd/include/vhd.h b/extracters/libvhd/include/vhd.h new file mode 100644 index 0000000..bc52c57 --- /dev/null +++ b/extracters/libvhd/include/vhd.h @@ -0,0 +1,228 @@ +/* Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/* Modified March 2016 Timothe Litt to work under windows. +* Copyright (C) 2016 Timothe Litt +* Modifications subject to the same license terms as above, +* substituting "Timothe Litt" for "XenSource Inc." +*/ +#ifndef __VHD_H__ +#define __VHD_H__ + +#include + +typedef uint32_t u32; +typedef uint64_t u64; + +/* #define DEBUG 1 */ + +/* ---------------------------------------------------------------------- */ +/* General definitions. */ +/* ---------------------------------------------------------------------- */ + +#define VHD_SECTOR_SIZE 512 +#define VHD_SECTOR_SHIFT 9 + +/* ---------------------------------------------------------------------- */ +/* This is the generic disk footer, used by all disks. */ +/* ---------------------------------------------------------------------- */ + +struct hd_ftr { + char cookie[8]; /* Identifies original creator of the disk */ + u32 features; /* Feature Support -- see below */ + u32 ff_version; /* (major,minor) version of disk file */ + u64 data_offset; /* Abs. offset from SOF to next structure */ + u32 timestamp; /* Creation time. secs since 1/1/2000GMT */ + char crtr_app[4]; /* Creator application */ + u32 crtr_ver; /* Creator version (major,minor) */ + u32 crtr_os; /* Creator host OS */ + u64 orig_size; /* Size at creation (bytes) */ + u64 curr_size; /* Current size of disk (bytes) */ + u32 geometry; /* Disk geometry */ + u32 type; /* Disk type */ + u32 checksum; /* 1's comp sum of this struct. */ + blk_uuid_t uuid; /* Unique disk ID, used for naming parents */ + char saved; /* one-bit -- is this disk/VM in a saved state? */ + char hidden; /* tapdisk-specific field: is this vdi hidden? */ + char reserved[426]; /* padding */ +}; + +/* VHD cookie string. */ +static const char HD_COOKIE[9] = "conectix"; + +/* Feature fields in hd_ftr */ +#define HD_NO_FEATURES 0x00000000 +#define HD_TEMPORARY 0x00000001 /* disk can be deleted on shutdown */ +#define HD_RESERVED 0x00000002 /* NOTE: must always be set */ + +/* Version field in hd_ftr */ +#define HD_FF_VERSION 0x00010000 + +/* Known creator OS type fields in hd_ftr.crtr_os */ +#define HD_CR_OS_WINDOWS 0x5769326B /* (Wi2k) */ +#define HD_CR_OS_MACINTOSH 0x4D616320 /* (Mac ) */ +#define HD_CR_OS_UNIX 0x556E6978 /* (Unix) */ +#define HD_CR_OS_VMS 0x4F564D53 /* (OVMS) */ +/* + * version 0.1: little endian bitmaps + * version 1.1: big endian bitmaps; batmap + * version 1.2: libvhd + * version 1.3: batmap version bump to 1.2 + */ +#define VHD_VERSION(major, minor) (((major) << 16) | ((minor) & 0x0000FFFF)) +#define VHD_CURRENT_VERSION VHD_VERSION(1, 3) + +/* Disk geometry accessor macros. */ +/* Geometry is a triple of (cylinders (2 bytes), tracks (1 byte), and + * secotrs-per-track (1 byte)) + */ +#define GEOM_GET_CYLS(_g) (((_g) >> 16) & 0xffff) +#define GEOM_GET_HEADS(_g) (((_g) >> 8) & 0xff) +#define GEOM_GET_SPT(_g) ((_g) & 0xff) + +#define GEOM_ENCODE(_c, _h, _s) (((_c) << 16) | ((_h) << 8) | (_s)) + +/* type field in hd_ftr */ +#define HD_TYPE_NONE 0 +#define HD_TYPE_FIXED 2 /* fixed-allocation disk */ +#define HD_TYPE_DYNAMIC 3 /* dynamic disk */ +#define HD_TYPE_DIFF 4 /* differencing disk */ + +/* String table for hd.type */ +#ifdef __GNUC__ +__attribute__((unused)) +#endif +static const char *HD_TYPE_STR[7] = { + "None", /* 0 */ + "Reserved (deprecated)", /* 1 */ + "Fixed hard disk", /* 2 */ + "Dynamic hard disk", /* 3 */ + "Differencing hard disk", /* 4 */ + "Reserved (deprecated)", /* 5 */ + "Reserved (deprecated)" /* 6 */ +}; + +#define HD_TYPE_MAX 6 + +struct prt_loc { + u32 code; /* Platform code -- see defines below. */ + u32 data_space; /* Number of 512-byte sectors to store locator */ + u32 data_len; /* Actual length of parent locator in bytes */ + u32 res; /* Must be zero */ + u64 data_offset; /* Absolute offset of locator data (bytes) */ +}; + +/* Platform Codes */ +#define PLAT_CODE_NONE 0x0 +#define PLAT_CODE_WI2R 0x57693272 /* deprecated */ +#define PLAT_CODE_WI2K 0x5769326B /* deprecated */ +#define PLAT_CODE_W2RU 0x57327275 /* Windows relative path (UTF-16) */ +#define PLAT_CODE_W2KU 0x57326B75 /* Windows absolute path (UTF-16) */ +#define PLAT_CODE_MAC 0x4D616320 /* MacOS alias stored as a blob. */ +#define PLAT_CODE_MACX 0x4D616358 /* File URL (UTF-8), see RFC 2396. */ + +/* ---------------------------------------------------------------------- */ +/* This is the dynamic disk header. */ +/* ---------------------------------------------------------------------- */ + +struct dd_hdr { + char cookie[8]; /* Should contain "cxsparse" */ + u64 data_offset; /* Byte offset of next record. (Unused) 0xffs */ + u64 table_offset; /* Absolute offset to the BAT. */ + u32 hdr_ver; /* Version of the dd_hdr (major,minor) */ + u32 max_bat_size; /* Maximum number of entries in the BAT */ + u32 block_size; /* Block size in bytes. Must be power of 2. */ + u32 checksum; /* Header checksum. 1's comp of all fields. */ + blk_uuid_t prt_uuid; /* ID of the parent disk. */ + u32 prt_ts; /* Modification time of the parent disk */ + u32 res1; /* Reserved. */ + char prt_name[512]; /* Parent unicode name. */ + struct prt_loc loc[8]; /* Parent locator entries. */ + char res2[256]; /* Reserved. */ +}; + +/* VHD cookie string. */ +static const char DD_COOKIE[9] = "cxsparse"; + +/* Version field in hd_ftr */ +#define DD_VERSION 0x00010000 + +/* Default blocksize is 2 meg. */ +#define DD_BLOCKSIZE_DEFAULT 0x00200000 + +#define DD_BLK_UNUSED 0xFFFFFFFF + +struct dd_batmap_hdr { + char cookie[8]; /* should contain "tdbatmap" */ + u64 batmap_offset; /* byte offset to batmap */ + u32 batmap_size; /* batmap size in sectors */ + u32 batmap_version; /* version of batmap */ + u32 checksum; /* batmap checksum -- 1's complement of batmap */ +}; + +static const char VHD_BATMAP_COOKIE[9] = "tdbatmap"; + +/* + * version 1.1: signed char checksum + */ +#define VHD_BATMAP_VERSION(major, minor) (((major) << 16) | ((minor) & 0x0000FFFF)) +#define VHD_BATMAP_CURRENT_VERSION VHD_BATMAP_VERSION(1, 2) + +/* Layout of a dynamic disk: + * + * +-------------------------------------------------+ + * | Mirror image of HD footer (hd_ftr) (512 bytes) | + * +-------------------------------------------------+ + * | Sparse drive header (dd_hdr) (1024 bytes) | + * +-------------------------------------------------+ + * | BAT (Block allocation table) | + * | - Array of absolute sector offsets into the | + * | file (u32). | + * | - Rounded up to a sector boundary. | + * | - Unused entries are marked as 0xFFFFFFFF | + * | - max entries in dd_hdr->max_bat_size | + * +-------------------------------------------------+ + * | Data Block 0 | + * | Bitmap (padded to 512 byte sector boundary) | + * | - each bit indicates whether the associated | + * | sector within this block is used. | + * | Data | + * | - power-of-two multiple of sectors. | + * | - default 2MB (4096 * 512) | + * | - Any entries with zero in bitmap should be | + * | zero on disk | + * +-------------------------------------------------+ + * | Data Block 1 | + * +-------------------------------------------------+ + * | ... | + * +-------------------------------------------------+ + * | Data Block n | + * +-------------------------------------------------+ + * | HD Footer (511 bytes) | + * +-------------------------------------------------+ + */ + +#endif diff --git a/extracters/libvhd/libvhd.c b/extracters/libvhd/libvhd.c new file mode 100644 index 0000000..b8c90d5 --- /dev/null +++ b/extracters/libvhd/libvhd.c @@ -0,0 +1,4190 @@ +/* Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/* Modified March 2016 Timothe Litt to work under windows. +* Copyright (C) 2016 Timothe Litt +* Modifications subject to the same license terms as above, +* substituting "Timothe Litt" for "XenSource Inc." +*/ + +#ifdef _WIN32 +#define _CRT_SECURE_NO_WARNINGS 1 +#endif + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#include +#define strdup _strdup +#define open _open +#ifndef O_BINARY +#define O_BINARY _O_BINARY +#endif +#define close _close +#define unlink _unlink +#define lseek _lseeki64 +#define read _read +#define write _write +#else +#include +#include +#define _aligned_free free +#include +#include +#include +#ifndef O_BINARY +#define O_BINARY 0 +#endif +#endif +#include +#include +#include +#include +#include "libvhd.h" +#include "relative-path.h" + +#ifndef O_DIRECT +#define O_DIRECT 0 +#endif + +static int libvhd_dbg = 0; + +/* Extension to Xen. Interactive utilities really shouldn't send + * errors to syslog. Set log_level to -1 for them to go to stderr. + * Existing callers will still go to syslog, as they all seem to use (1). + * + * On systems without syslog, stderr is the only choice. + */ + +void +libvhd_set_log_level(int level) +{ + if (level) + libvhd_dbg = level; +} + +#define FN __func__ + +#define VHDLOG(a) vhd_log_error a + +static void vhd_log_error( const char *func, const char *fmt, ... ) +#ifdef __GNUC__ + __attribute__((format(printf,2,3))); +#else + ; +#endif + +#ifndef LIBVHD_HAS_SYSLOG +#ifdef _WIN32 +#define LIBVHD_HAS_SYSLOG 0 +#else +#define LIBVHD_HAS_SYSLOG 1 +#endif +#endif + +static void vhd_log_error( const char *func, const char *fmt, ... ) +{ + char *buf, nilbuf; + size_t ilen, len; + va_list ap; + + if( !libvhd_dbg ) + return; + + ilen = sizeof( "libvhd::%s: " ) + strlen( func ) -2; + va_start(ap, fmt ); + len = vsnprintf( &nilbuf, 1, fmt, ap ); + va_end( ap ); + + if( (buf = malloc( ilen + len + 1 )) == NULL ) { + if( !LIBVHD_HAS_SYSLOG || libvhd_dbg < 0 ) + fprintf( stderr, "libvhd::%s: Out of memory for %s\n", func, fmt ); +#if LIBVHD_HAS_SYSLOG + if( libvhd_dbg > 0 ) + syslog( LOG_INFO, "libvhd::%s:: Out of memory", func ); +#endif + return; + } + va_start(ap, fmt); + (void) snprintf( buf, ilen, "libvhd::%s: ", func ); + (void) vsnprintf( buf + ilen -1, len+1, fmt, ap ); + va_end( ap ); + len += ilen -1; + if( buf[ len -1 ] != '\n' ) + buf[len++] = '\n'; + buf[len] = '\0'; + if( !LIBVHD_HAS_SYSLOG || libvhd_dbg < 0 ) + fputs( buf, stderr ); +#if LIBVHD_HAS_SYSLOG + if( libvhd_dbg > 0 ) + syslog(LOG_INFO, buf); +#endif + free( buf ); + + return; + } + +#ifdef _WIN32 +LIBVHD_API +char *dirname( char *path ) { + static char buf[MAX_PATH + 1]; + char *p, *d; + if( path == NULL || !*path ) + return "."; + strlcpy( buf, path, sizeof( buf ) ); + for( d = buf; *d && *d != ':' && *d != '/'; d++ ) + ; + if( !*d ) + return "."; + if( *d == ':' ) { + d++; + if( !strcmp( d, "." ) ) + return buf; + } + for( p = d + strlen( d ) - 1; p > d; p-- ) + if( *p != '/' ) + break; + if( p <= d ) { + *d++ = '/'; + *d = '\0'; + return buf; + } + *p = '\0'; + if( (p = strrchr( d, '/' )) == NULL ) { + *d++ = '.'; + *d = '\0'; + return buf; +} + if( p == d ) { + *p++ = '/'; + *p = '\0'; + } else + *p = '\0'; + return buf; +} +LIBVHD_API +char *basename( char *path ) { + static char buf[MAX_PATH + 1]; + char *p, *d; + if( path == NULL || !*path ) + return "."; + strlcpy( buf, path, sizeof( buf ) ); + for( d = buf; *d && *d != ':' && *d != '/'; d++ ) + ; + if( !*d ) + return buf; + if( *d == ':' ) { + d++; + if( !strcmp( d, "." ) ) + return buf; + } + for( p = d + strlen( d ) - 1; p > d; p-- ) + if( *p == '/' ) + *p = '\0'; + else + break; + if( p <= d ) { + if( !*++d ) + return "/"; + return d; + } + if( (p = strrchr( d, '/' )) == NULL ) { + return d; + } + return ++p; +} + +/* Partial simulation of iconv + * The usage here is restricted, always is just + * one string, so we don't have to worry about + * state. We do handle conversions among ASCII, + * UTF-8 and UTF-16. ASCII is defined as Latin-1. + */ + +typedef void *iconv_t; + +enum iconvcs { + icc_ascii, + icc_utf16, + icc_utf16be, + icc_utf16le, + icc_utf8 +}; +typedef struct iconvin { + enum iconvcs toset; + enum iconvcs fromset; +} iconvin_t; + +static int iconv_close( iconv_t cd ) { + if( cd != (iconv_t)-1 ) + free( cd ); + return 0; +} + +static enum iconvcs iconvcs( const char *setname ) { + if( !strcmp( setname, "ASCII" ) ) + return icc_ascii; + if( !strcmp( setname, "UTF-16" ) ) + return icc_utf16; + if( !strcmp( setname, "UTF-16BE" )) + return icc_utf16be; + if( !strcmp( setname, "UTF-16LE" )) + return icc_utf16le; + if( !strcmp( setname, "UTF-8" )) + return icc_utf8; + abort(); +} + +static iconv_t iconv_open( const char *toset, const char *fromset ) { + iconvin_t *ict; + + if( (ict = (iconvin_t *)malloc( sizeof( iconvin_t ) )) == NULL ) + return (iconv_t)-1; + + ict->toset = iconvcs( toset ); + ict->fromset = iconvcs( fromset ); + + return (iconv_t)ict; +} + +static size_t iconv( iconv_t cd, + const char **inbuf, size_t *inbytesleft, + char **outbuf, size_t *outbytesleft ) { + iconvin_t *ict; + size_t nonrev = 0; + const char *ip; + char *op; + enum iconvcs ics; + + ict = (iconvin_t *)cd; + + if( (inbuf == NULL || *inbuf == NULL) && (outbuf == NULL || *outbuf == NULL) ) + return 0; /* reset */ + + if( (inbuf == NULL || *inbuf == NULL) && (outbuf == NULL && *outbuf == NULL) ) + abort(); /* output shift */ + + if( inbuf == NULL || *inbuf == NULL || outbuf == NULL || *outbuf == NULL ) + abort(); /* undefined */ + + ip = *inbuf; + op = *outbuf; + + ics = ict->fromset; + + while( *inbytesleft && *outbytesleft ) { + size_t c, c2; + + switch( ics ) { + case icc_ascii: + c = *ip++; + --*inbytesleft; + break; + case icc_utf8: + c = *ip++; + --*inbytesleft; + if( !(c & 0x80) ) + break; + if( !*inbytesleft ) { + *inbuf = ip - 1; + *inbytesleft = 1; + *outbuf = op; + errno = EINVAL; + return (size_t)-1; + } + c2 = *ip++; + --*inbytesleft; + if( (c & 0xe0) == 0xc0 ) { + c = ((c & 0x1f) << 6) | (c2 & 0x3f); + break; + } + if( !*inbytesleft ) { + *inbuf = ip - 2; + *inbytesleft = 2; + *outbuf = op; + errno = EINVAL; + return (size_t)-1; + } + if( (c & 0xf0) == 0xe0 ) { + c = ((c & 0xf) << 12) | ((c2 & 0x3f) << 6) | (*ip++ & 0x3f); + --*inbytesleft; + break; + } + if( *inbytesleft < 2 ) { + *inbuf = ip - 2; + *inbytesleft += 2; + *outbuf = op; + errno = EINVAL; + return (size_t)-1; + } + if( (c & 0xf8) == 0xf0 ) { + c = ((c & 0x7) << 18) | ((c2 & 0x3f) << 12) | ((*ip++ & 0x3f) << 6); + c |= *ip++ & 0x3f; + *inbytesleft -= 2; + break; + } + *inbuf = ip-2; + *inbytesleft += 2; + *outbuf = op; + errno = EINVAL; + return (size_t)-1; + case icc_utf16: + if( *inbytesleft < 2 ) { + *inbuf = ip; + *outbuf = op; + errno = EINVAL; + return (size_t)-1; + } + *inbytesleft -= 2; + ip += 2; + if( ip[-2] == 0xfe && ip[-1] == 0xff ) { + ics = icc_utf16be; + continue; + } + if( ip[-2] == 0xff && ip[-1] == 0xfe ) { + ics = icc_utf16le; + continue; + } + c = (ip[-2] << 8) | ip[-1]; + if( c < 0xd800 || c >= 0xe000 ) + break; + if( *inbytesleft < 2 ) { + *inbuf = ip - 2; + *outbuf = op; + *inbytesleft += 2; + errno = EILSEQ; + return (size_t)-1; + } + *inbytesleft -= 2; + ip += 2; + c2 = (ip[-2] << 8) | ip[-1]; + if( c2 < 0xd800 || c2 >= 0xe000 ) { + *inbuf = ip - 4; + *outbuf = op; + *inbytesleft += 4; + errno = EILSEQ; + return (size_t)-1; + } + c = (c << 10) + c2 + (0x10000 - (0xD800 << 10) - 0xDC00); + break; + case icc_utf16be: + if( *inbytesleft < 2 ) { + *inbuf = ip; + *outbuf = op; + errno = EINVAL; + return (size_t)-1; + } + *inbytesleft -= 2; + ip += 2; + c = (ip[-2] << 8) | ip[-1]; + if( c < 0xd800 || c >= 0xe000 ) + break; + if( *inbytesleft < 2 ) { + *inbuf = ip - 2; + *outbuf = op; + *inbytesleft += 2; + errno = EILSEQ; + return (size_t)-1; + } + *inbytesleft -= 2; + ip += 2; + c2 = (ip[-2] << 8) | ip[-1]; + if( c2 < 0xd800 || c2 >= 0xe000 ) { + *inbuf = ip - 4; + *outbuf = op; + *inbytesleft += 4; + errno = EILSEQ; + return (size_t)-1; + } + c = (c << 10) + c2 + (0x10000 - (0xD800 << 10) - 0xDC00); + break; + case icc_utf16le: + if( *inbytesleft < 2 ) { + *inbuf = ip; + *outbuf = op; + errno = EINVAL; + return (size_t)-1; + } + *inbytesleft -= 2; + ip += 2; + c = (ip[-1] << 8) | ip[-2]; + if( c < 0xd800 || c >= 0xe000 ) + break; + if( *inbytesleft < 2 ) { + *inbuf = ip - 2; + *outbuf = op; + *inbytesleft += 2; + errno = EILSEQ; + return (size_t)-1; + } + *inbytesleft -= 2; + ip += 2; + c2 = (ip[-1] << 8) | ip[-2]; + if( c2 < 0xd800 || c2 >= 0xe000 ) { + *inbuf = ip - 4; + *outbuf = op; + *inbytesleft += 4; + errno = EILSEQ; + return (size_t)-1; + } + c = (c << 10) + c2 + (0x10000 - (0xD800 << 10) - 0xDC00); + break; + default: + abort(); + } + + switch( ict->toset ) { + case icc_ascii: + *op++ = c & 0xff; + --*outbytesleft; + if( c > 0xff ) + nonrev++; + continue; + case icc_utf8: + if( c <= 0x7F ) { + *op++ = c & 0x7f; + --*outbytesleft; + continue; + } + if( c <= 0x7ff ) { + if( *outbytesleft < 2 ) + break; + *op++ = (((c >> 6) & 0x1f) | 0xc0) & 0xff; + *op++ = ((c & 0x3f) | 0xc0) & 0xff; + *outbytesleft -= 2; + continue; + } + if( c <= 0xffff ) { + if( *outbytesleft < 3 ) + break; + *op++ = (((c >> 12) & 0xf) | 0xe0) & 0xff; + *op++ = (((c >> 6) & 0x3f) | 0xc0) & 0xff; + *op++ = ((c & 0x3f) | 0xc0) & 0xff; + *outbytesleft -= 3; + continue; + } + if( c <= 0x1fffff ) { + if( *outbytesleft < 4 ) + break; + *op++ = (((c >> 18) & 0x7) | 0xf0) & 0xff; + *op++ = (((c >> 12) & 0x3f) | 0xc0) & 0xff; + *op++ = (((c >> 6) & 0x3f) | 0xc0) & 0xff; + *op++ = ((c & 0x3f) | 0xc0) & 0xff; + *outbytesleft -= 4; + continue; + } + break; + case icc_utf16: + if( *outbytesleft < 2 ) + break; + if( c < 0x10000 ) { + *op++ = ( c >> 8 ) & 0xff; + *op++ = c & 0xff; + *outbytesleft -= 2; + continue; + } + if( *outbytesleft < 4 ) + break; + c2 = 0xDC00 + (c & 0x3FF); + c = (0xD800 - (0x10000 >> 10)) + (c >> 10); + op[0] = (c >> 8) & 0xff; + op[1] = c & 0xff; + op[2] = (c2 >> 8) & 0xff; + op[3] = c2 & 0xff; + op += 4; + *outbytesleft -=4; + continue; + case icc_utf16be: + if( *outbytesleft < 2 ) + break; + if( c < 0x10000 ) { + *op++ = ( c >> 8 ) & 0xff; + *op++ = c & 0xff; + *outbytesleft -= 2; + continue; + } + if( *outbytesleft < 4 ) + break; + c2 = 0xDC00 + (c & 0x3FF); + c = (0xD800 - (0x10000 >> 10)) + (c >> 10); + op[0] = (c >> 8) & 0xff; + op[1] = c & 0xff; + op[2] = (c2 >> 8) & 0xff; + op[3] = c2 & 0xff; + op += 4; + *outbytesleft -=4; + continue; + case icc_utf16le: + if( *outbytesleft < 2 ) + break; + if( c < 0x10000 ) { + *op++ = c & 0xff; + *op++ = (c >> 8) & 0xff; + *outbytesleft -= 2; + continue; + } + if( *outbytesleft < 4 ) + break; + c2 = 0xDC00 + (c & 0x3FF); + c = (0xD800 - (0x10000 >> 10)) + (c >> 10); + op[1] = (c >> 8) & 0xff; + op[0] = c & 0xff; + op[3] = (c2 >> 8) & 0xff; + op[2] = c2 & 0xff; + op += 4; + *outbytesleft -=4; + continue; + default: + abort(); + } + errno = E2BIG; + return (size_t)-1; + } + *inbuf = ip; + *outbuf = op; + + return nonrev; +} +#endif + +#define BIT_MASK 0x80 + +#ifdef ENABLE_FAILURE_TESTING +LIBVHD_API +const char* ENV_VAR_FAIL[NUM_FAIL_TESTS] = { + "VHD_UTIL_TEST_FAIL_REPARENT_BEGIN", + "VHD_UTIL_TEST_FAIL_REPARENT_LOCATOR", + "VHD_UTIL_TEST_FAIL_REPARENT_END", + "VHD_UTIL_TEST_FAIL_RESIZE_BEGIN", + "VHD_UTIL_TEST_FAIL_RESIZE_DATA_MOVED", + "VHD_UTIL_TEST_FAIL_RESIZE_METADATA_MOVED", + "VHD_UTIL_TEST_FAIL_RESIZE_END" +}; +LIBVHD_API +int TEST_FAIL[NUM_FAIL_TESTS]; +#endif /* ENABLE_FAILURE_TESTING */ + +static inline int +test_bit (volatile char *addr, int nr) +{ + return ((addr[nr >> 3] << (nr & 7)) & BIT_MASK) != 0; +} + +static inline void +set_bit (volatile char *addr, int nr) +{ + addr[nr >> 3] |= (BIT_MASK >> (nr & 7)); +} + +static inline void +clear_bit (volatile char *addr, int nr) +{ + addr[nr >> 3] &= ~(BIT_MASK >> (nr & 7)); +} + +static inline int +old_test_bit(volatile char *addr, int nr) +{ + return (((uint32_t *)addr)[nr >> 5] >> (nr & 31)) & 1; +} + +static inline void +old_set_bit(volatile char *addr, int nr) +{ + ((uint32_t *)addr)[nr >> 5] |= (1 << (nr & 31)); +} + +static inline void +old_clear_bit(volatile char *addr, int nr) +{ + ((uint32_t *)addr)[nr >> 5] &= ~(1 << (nr & 31)); +} + +void +vhd_footer_in(vhd_footer_t *footer) +{ + BE32_IN(&footer->features); + BE32_IN(&footer->ff_version); + BE64_IN(&footer->data_offset); + BE32_IN(&footer->timestamp); + BE32_IN(&footer->crtr_ver); + BE32_IN(&footer->crtr_os); + BE64_IN(&footer->orig_size); + BE64_IN(&footer->curr_size); + BE32_IN(&footer->geometry); + BE32_IN(&footer->type); + BE32_IN(&footer->checksum); +} + +void +vhd_footer_out(vhd_footer_t *footer) +{ + BE32_OUT(&footer->features); + BE32_OUT(&footer->ff_version); + BE64_OUT(&footer->data_offset); + BE32_OUT(&footer->timestamp); + BE32_OUT(&footer->crtr_ver); + BE32_OUT(&footer->crtr_os); + BE64_OUT(&footer->orig_size); + BE64_OUT(&footer->curr_size); + BE32_OUT(&footer->geometry); + BE32_OUT(&footer->type); + BE32_OUT(&footer->checksum); +} + +void +vhd_header_in(vhd_header_t *header) +{ + int i, n; + + BE64_IN(&header->data_offset); + BE64_IN(&header->table_offset); + BE32_IN(&header->hdr_ver); + BE32_IN(&header->max_bat_size); + BE32_IN(&header->block_size); + BE32_IN(&header->checksum); + BE32_IN(&header->prt_ts); + + n = sizeof(header->loc) / sizeof(vhd_parent_locator_t); + + for (i = 0; i < n; i++) { + BE32_IN(&header->loc[i].code); + BE32_IN(&header->loc[i].data_space); + BE32_IN(&header->loc[i].data_len); + BE64_IN(&header->loc[i].data_offset); + } +} + +void +vhd_header_out(vhd_header_t *header) +{ + int i, n; + + BE64_OUT(&header->data_offset); + BE64_OUT(&header->table_offset); + BE32_OUT(&header->hdr_ver); + BE32_OUT(&header->max_bat_size); + BE32_OUT(&header->block_size); + BE32_OUT(&header->checksum); + BE32_OUT(&header->prt_ts); + + n = sizeof(header->loc) / sizeof(vhd_parent_locator_t); + + for (i = 0; i < n; i++) { + BE32_OUT(&header->loc[i].code); + BE32_OUT(&header->loc[i].data_space); + BE32_OUT(&header->loc[i].data_len); + BE64_OUT(&header->loc[i].data_offset); + } +} + +void +vhd_batmap_header_in(vhd_batmap_t *batmap) +{ + BE64_IN(&batmap->header.batmap_offset); + BE32_IN(&batmap->header.batmap_size); + BE32_IN(&batmap->header.batmap_version); + BE32_IN(&batmap->header.checksum); +} + +void +vhd_batmap_header_out(vhd_batmap_t *batmap) +{ + BE64_OUT(&batmap->header.batmap_offset); + BE32_OUT(&batmap->header.batmap_size); + BE32_OUT(&batmap->header.batmap_version); + BE32_OUT(&batmap->header.checksum); +} + +void +vhd_bat_in(vhd_bat_t *bat) +{ + unsigned int i; + + for (i = 0; i < bat->entries; i++) + BE32_IN(&bat->bat[i]); +} + +void +vhd_bat_out(vhd_bat_t *bat) +{ + unsigned int i; + + for (i = 0; i < bat->entries; i++) + BE32_OUT(&bat->bat[i]); +} + +uint32_t +vhd_checksum_footer(vhd_footer_t *footer) +{ + size_t i; + unsigned char *blob; + uint32_t checksum, tmp; + + checksum = 0; + tmp = footer->checksum; + footer->checksum = 0; + + blob = (unsigned char *)footer; + for (i = 0; i < sizeof(vhd_footer_t); i++) + checksum += (uint32_t)blob[i]; + + footer->checksum = tmp; + return ~checksum; +} + +int +vhd_validate_footer(vhd_footer_t *footer) +{ + int csize; + uint32_t checksum; + + csize = sizeof(footer->cookie); + if (memcmp(footer->cookie, HD_COOKIE, csize) != 0 && + memcmp(footer->cookie, VHD_POISON_COOKIE, csize) != 0) { + char buf[9]; + strncpy(buf, footer->cookie, sizeof(buf)); + buf[sizeof(buf)-1]= '\0'; + VHDLOG((FN,"invalid footer cookie: %s\n", buf)); + return -EINVAL; + } + + checksum = vhd_checksum_footer(footer); + if (checksum != footer->checksum) { + /* + * early td-util did not re-calculate + * checksum when marking vhds 'hidden' + */ + if (footer->hidden && + !strncmp(footer->crtr_app, "tap", 3) && + (footer->crtr_ver == VHD_VERSION(0, 1) || + footer->crtr_ver == VHD_VERSION(1, 1))) { + char tmp = footer->hidden; + footer->hidden = 0; + checksum = vhd_checksum_footer(footer); + footer->hidden = tmp; + + if (checksum == footer->checksum) + return 0; + } + + VHDLOG((FN,"invalid footer checksum: " + "footer = 0x%08x, calculated = 0x%08x\n", + footer->checksum, checksum)); + return -EINVAL; + } + + return 0; +} + +uint32_t +vhd_checksum_header(vhd_header_t *header) +{ + size_t i; + unsigned char *blob; + uint32_t checksum, tmp; + + checksum = 0; + tmp = header->checksum; + header->checksum = 0; + + blob = (unsigned char *)header; + for (i = 0; i < sizeof(vhd_header_t); i++) + checksum += (uint32_t)blob[i]; + + header->checksum = tmp; + return ~checksum; +} + +int +vhd_validate_header(vhd_header_t *header) +{ + int i, n; + uint32_t checksum; + + if (memcmp(header->cookie, DD_COOKIE, 8) != 0) { + char buf[9]; + strncpy(buf, header->cookie, sizeof(buf)); + buf[sizeof(buf)-1]= '\0'; + VHDLOG((FN,"invalid header cookie: %s\n", buf)); + return -EINVAL; + } + + if (header->hdr_ver != 0x00010000) { + VHDLOG((FN,"invalid header version 0x%08x\n", header->hdr_ver)); + return -EINVAL; + } + + if (header->data_offset != 0xFFFFFFFFFFFFFFFF) { + VHDLOG((FN,"invalid header data_offset 0x%016"PRIx64"\n", + header->data_offset)); + return -EINVAL; + } + + n = sizeof(header->loc) / sizeof(vhd_parent_locator_t); + for (i = 0; i < n; i++) + if (vhd_validate_platform_code(header->loc[i].code)) + return -EINVAL; + + checksum = vhd_checksum_header(header); + if (checksum != header->checksum) { + VHDLOG((FN,"invalid header checksum: " + "header = 0x%08x, calculated = 0x%08x\n", + header->checksum, checksum)); + return -EINVAL; + } + + return 0; +} + +static inline int +vhd_validate_bat(vhd_bat_t *bat) +{ + if (!bat->bat) + return -EINVAL; + + return 0; +} + +uint32_t +vhd_checksum_batmap(vhd_batmap_t *batmap) +{ + u32 i, n; + char *blob; + uint32_t checksum; + + blob = batmap->map; + checksum = 0; + + n = (u32) vhd_sectors_to_bytes(batmap->header.batmap_size); + + for (i = 0; i < n; i++) { + if (batmap->header.batmap_version == VHD_BATMAP_VERSION(1, 1)) + checksum += (uint32_t)blob[i]; + else + checksum += (uint32_t)(unsigned char)blob[i]; + } + + return ~checksum; +} + +int +vhd_validate_batmap_header(vhd_batmap_t *batmap) +{ + if (memcmp(batmap->header.cookie, VHD_BATMAP_COOKIE, 8)) + return -EINVAL; + + if (batmap->header.batmap_version > VHD_BATMAP_CURRENT_VERSION) + return -EINVAL; + + return 0; +} + +int +vhd_validate_batmap(vhd_batmap_t *batmap) +{ + uint32_t checksum; + + if (!batmap->map) + return -EINVAL; + + checksum = vhd_checksum_batmap(batmap); + if (checksum != batmap->header.checksum) + return -EINVAL; + + return 0; +} + +int +vhd_batmap_header_offset(vhd_context_t *ctx, off_t *_off) +{ + off_t off; + size_t bat; + + *_off = 0; + + off = (off_t)ctx->header.table_offset; + bat = ctx->header.max_bat_size * sizeof(uint32_t); + off += (off_t)vhd_bytes_padded(bat); + + *_off = off; + return 0; +} + +int +vhd_validate_platform_code(uint32_t code) +{ + switch (code) { + case PLAT_CODE_NONE: + case PLAT_CODE_WI2R: + case PLAT_CODE_WI2K: + case PLAT_CODE_W2RU: + case PLAT_CODE_W2KU: + case PLAT_CODE_MAC: + case PLAT_CODE_MACX: + return 0; + default: + VHDLOG((FN,"invalid parent locator code %u\n", code)); + return -EINVAL; + } +} + +int +vhd_parent_locator_count(vhd_context_t *ctx) +{ + return (sizeof(ctx->header.loc) / sizeof(vhd_parent_locator_t)); +} + +int +vhd_hidden(vhd_context_t *ctx, int *hidden) +{ + int err; + + *hidden = 0; + + if (vhd_type_dynamic(ctx) && vhd_creator_tapdisk(ctx) && + (ctx->footer.crtr_ver == VHD_VERSION(0, 1) || + ctx->footer.crtr_ver == VHD_VERSION(1, 1))) { + vhd_footer_t copy; + + err = vhd_read_footer_at(ctx, ©, 0); + if (err) { + VHDLOG((FN,"error reading backup footer of %s: %d\n", + ctx->file, err)); + return err; + } + *hidden = copy.hidden; + } else + *hidden = ctx->footer.hidden; + + return 0; +} + +int +vhd_chain_depth(vhd_context_t *ctx, int *depth) +{ + char *file; + int err, cnt; + vhd_context_t vhd, *cur; + + err = 0; + cnt = 0; + *depth = 0; + file = NULL; + cur = ctx; + + for (;;) { + cnt++; + + if (cur->footer.type != HD_TYPE_DIFF) + break; + + if (vhd_parent_raw(cur)) { + cnt++; + break; + } + + err = vhd_parent_locator_get(cur, &file); + if (err) { + file = NULL; + break; + } + + if (cur != ctx) { + vhd_close(cur); + cur = NULL; + } + + err = vhd_open(&vhd, file, VHD_OPEN_RDONLY); + if (err) + break; + + cur = &vhd; + free(file); + file = NULL; + } + + free(file); + if (cur && cur != ctx) + vhd_close(cur); + + if (!err) + *depth = cnt; + + return err; +} + +int +vhd_batmap_test(vhd_context_t *ctx, vhd_batmap_t *batmap, uint32_t block) +{ + if (!vhd_has_batmap(ctx) || !batmap->map) + return 0; + + if (block >= (batmap->header.batmap_size << (VHD_SECTOR_SHIFT + 3))) + return 0; + + return test_bit(batmap->map, block); +} + +void +vhd_batmap_set(vhd_context_t *ctx, vhd_batmap_t *batmap, uint32_t block) +{ + if (!vhd_has_batmap(ctx) || !batmap->map) + return; + + if (block >= (batmap->header.batmap_size << (VHD_SECTOR_SHIFT + 3))) + return; + + set_bit(batmap->map, block); +} + +void +vhd_batmap_clear(vhd_context_t *ctx, vhd_batmap_t *batmap, uint32_t block) +{ + if (!vhd_has_batmap(ctx) || !batmap->map) + return; + + if (block >= (batmap->header.batmap_size << (VHD_SECTOR_SHIFT + 3))) + return; + + clear_bit(batmap->map, block); +} + +int +vhd_bitmap_test(vhd_context_t *ctx, char *map, uint32_t block) +{ + if (vhd_creator_tapdisk(ctx) && + ctx->footer.crtr_ver == 0x00000001) + return old_test_bit(map, block); + + return test_bit(map, block); +} + +void +vhd_bitmap_set(vhd_context_t *ctx, char *map, uint32_t block) +{ + if( vhd_creator_tapdisk( ctx ) && + ctx->footer.crtr_ver == 0x00000001 ) { + old_set_bit( map, block ); + return; + } + + set_bit(map, block); + return; +} + +void +vhd_bitmap_clear(vhd_context_t *ctx, char *map, uint32_t block) +{ + if( vhd_creator_tapdisk( ctx ) && + ctx->footer.crtr_ver == 0x00000001 ) { + old_clear_bit( map, block ); + return; + } + + clear_bit(map, block); + return; +} + +/* + * returns absolute offset of the first + * byte of the file which is not vhd metadata + */ +int +vhd_end_of_headers(vhd_context_t *ctx, off_t *end) +{ + int err, i, n; + uint32_t bat_bytes; + off_t eom, bat_end; + vhd_parent_locator_t *loc; + + *end = 0; + + if (!vhd_type_dynamic(ctx)) + return 0; + + eom = (off_t)(ctx->footer.data_offset + sizeof(vhd_header_t)); + + bat_bytes = (uint32_t)vhd_bytes_padded(ctx->header.max_bat_size * sizeof(uint32_t)); + bat_end = (off_t)(ctx->header.table_offset + bat_bytes); + + eom = MAX(eom, bat_end); + + if (vhd_has_batmap(ctx)) { + off_t hdr_end, hdr_secs, map_end, map_secs; + + err = vhd_get_batmap(ctx); + if (err) + return err; + + hdr_secs = secs_round_up_no_zero(sizeof(vhd_batmap_header_t)); + err = vhd_batmap_header_offset(ctx, &hdr_end); + if (err) + return err; + + hdr_end += (off_t)vhd_sectors_to_bytes(hdr_secs); + eom = MAX(eom, hdr_end); + + map_secs = ctx->batmap.header.batmap_size; + map_end = (off_t)(ctx->batmap.header.batmap_offset + + vhd_sectors_to_bytes(map_secs)); + eom = MAX(eom, map_end); + } + + /* parent locators */ + n = sizeof(ctx->header.loc) / sizeof(vhd_parent_locator_t); + + for (i = 0; i < n; i++) { + off_t loc_end; + + loc = &ctx->header.loc[i]; + if (loc->code == PLAT_CODE_NONE) + continue; + + loc_end = (off_t)(loc->data_offset + vhd_parent_locator_size(loc)); + eom = MAX(eom, loc_end); + } + + *end = eom; + return 0; +} + +int +vhd_end_of_data(vhd_context_t *ctx, off_t *end) +{ + unsigned int i; + int err; + off_t max; + uint64_t blk; + + if (!vhd_type_dynamic(ctx)) { + err = vhd_seek(ctx, 0, SEEK_END); + if (err) + return err; + + max = vhd_position(ctx); + if (max == (off_t)-1) + return -errno; + + *end = max - sizeof(vhd_footer_t); + return 0; + } + + err = vhd_end_of_headers(ctx, &max); + if (err) + return err; + + err = vhd_get_bat(ctx); + if (err) + return err; + + max >>= VHD_SECTOR_SHIFT; + + for (i = 0; i < ctx->bat.entries; i++) { + blk = ctx->bat.bat[i]; + + if (blk != DD_BLK_UNUSED) { + blk += ctx->spb + ctx->bm_secs; + max = MAX((off_t)blk, max); + } + } + + *end = (off_t)vhd_sectors_to_bytes(max); + return 0; +} + +uint32_t +vhd_time(time_t time) +{ + struct tm tm; + time_t micro_epoch; + + memset(&tm, 0, sizeof(struct tm)); + tm.tm_year = 100; + tm.tm_mon = 0; + tm.tm_mday = 1; + micro_epoch = mktime(&tm); + + return (uint32_t)(time - micro_epoch); +} + +/* + * Stringify the VHD timestamp for printing. + * As with ctime_r, target must be >=26 bytes. + */ +size_t +vhd_time_to_string(uint32_t timestamp, char *target) +{ + char *cr; + struct tm tm; + time_t t1, t2; +#ifdef _WIN32 + __time32_t t3; +#endif + memset(&tm, 0, sizeof(struct tm)); + + /* VHD uses an epoch of 12:00AM, Jan 1, 2000. */ + /* Need to adjust this to the expected epoch of 1970. */ + tm.tm_year = 100; + tm.tm_mon = 0; + tm.tm_mday = 1; + + t1 = mktime(&tm); + t2 = t1 + (time_t)timestamp; +#ifdef _WIN32 + t3 = (__time32_t)t2; + _ctime32_s( target, 26, &t3 ); +#else + ctime_r(&t2, target); +#endif + /* handle mad ctime_r newline appending. */ + if ((cr = strchr(target, '\n')) != NULL) + *cr = '\0'; + + return (strlen(target)); +} + +/* + * nabbed from vhd specs. + */ +uint32_t +vhd_chs(uint64_t size) +{ + uint32_t secs, cylinders, heads, spt, cth; + + secs = secs_round_up_no_zero(size); + + if (secs > 65535 * 16 * 255) + secs = 65535 * 16 * 255; + + if (secs >= 65535 * 16 * 63) { + spt = 255; + cth = secs / spt; + heads = 16; + } else { + spt = 17; + cth = secs / spt; + heads = (cth + 1023) / 1024; + + if (heads < 4) + heads = 4; + + if (cth >= (heads * 1024) || heads > 16) { + spt = 31; + cth = secs / spt; + heads = 16; + } + + if (cth >= heads * 1024) { + spt = 63; + cth = secs / spt; + heads = 16; + } + } + + cylinders = cth / heads; + + return GEOM_ENCODE(cylinders, heads, spt); +} + +int +vhd_get_footer(vhd_context_t *ctx) +{ + if (!vhd_validate_footer(&ctx->footer)) + return 0; + + return vhd_read_footer(ctx, &ctx->footer); +} + +int +vhd_get_header(vhd_context_t *ctx) +{ + if (!vhd_type_dynamic(ctx)) + return -EINVAL; + + if (!vhd_validate_header(&ctx->header)) + return 0; + + return vhd_read_header(ctx, &ctx->header); +} + +int +vhd_get_bat(vhd_context_t *ctx) +{ + if (!vhd_type_dynamic(ctx)) + return -EINVAL; + + if (!vhd_validate_bat(&ctx->bat)) + return 0; + + vhd_put_bat(ctx); + return vhd_read_bat(ctx, &ctx->bat); +} + +int +vhd_get_batmap(vhd_context_t *ctx) +{ + if (!vhd_has_batmap(ctx)) + return -EINVAL; + + if (!vhd_validate_batmap(&ctx->batmap)) + return 0; + + vhd_put_batmap(ctx); + return vhd_read_batmap(ctx, &ctx->batmap); +} + +void +vhd_put_footer(vhd_context_t *ctx) +{ + memset(&ctx->footer, 0, sizeof(vhd_footer_t)); +} + +void +vhd_put_header(vhd_context_t *ctx) +{ + memset(&ctx->header, 0, sizeof(vhd_header_t)); +} + +void +vhd_put_bat(vhd_context_t *ctx) +{ + if (!vhd_type_dynamic(ctx)) + return; + + _aligned_free(ctx->bat.bat); + memset(&ctx->bat, 0, sizeof(vhd_bat_t)); +} + +void +vhd_put_batmap(vhd_context_t *ctx) +{ + if (!vhd_type_dynamic(ctx)) + return; + + if (!vhd_has_batmap(ctx)) + return; + + _aligned_free(ctx->batmap.map); + memset(&ctx->batmap, 0, sizeof(vhd_batmap_t)); +} + +/* + * look for 511 byte footer at end of file + */ +int +vhd_read_short_footer(vhd_context_t *ctx, vhd_footer_t *footer) +{ + int err; + char *buf; + off_t eof; + + buf = NULL; + + err = vhd_seek(ctx, 0, SEEK_END); + if (err) + goto out; + + eof = vhd_position(ctx); + if (eof == (off_t)-1) { + err = -errno; + goto out; + } + + err = vhd_seek(ctx, eof - 511, SEEK_SET); + if (err) + goto out; + +#ifdef _WIN32 + buf = _aligned_malloc( sizeof(vhd_footer_t), VHD_SECTOR_SIZE ); + if( buf == NULL ) { + err = -errno; + goto out; + } +#else + err = posix_memalign((void **)&buf, + VHD_SECTOR_SIZE, sizeof(vhd_footer_t)); + if (err) { + buf = NULL; + err = -err; + goto out; + } +#endif + + memset(buf, 0, sizeof(vhd_footer_t)); + + /* + * expecting short read here + */ + vhd_read(ctx, buf, sizeof(vhd_footer_t)); + + memcpy(footer, buf, sizeof(vhd_footer_t)); + + vhd_footer_in(footer); + err = vhd_validate_footer(footer); + +out: + if (err) + VHDLOG((FN,"%s: failed reading short footer: %d\n", + ctx->file, err)); + _aligned_free(buf); + return err; +} + +int +vhd_read_footer_at(vhd_context_t *ctx, vhd_footer_t *footer, off_t off) +{ + int err; + char *buf; + + buf = NULL; + + err = vhd_seek(ctx, off, SEEK_SET); + if (err) + goto out; + +#ifdef _WIN32 + buf = _aligned_malloc( sizeof(vhd_footer_t), VHD_SECTOR_SIZE ); + if( buf == NULL ) { + err = -errno; + goto out; + } +#else + err = posix_memalign((void **)&buf, + VHD_SECTOR_SIZE, sizeof(vhd_footer_t)); + if (err) { + buf = NULL; + err = -err; + goto out; + } +#endif + + err = vhd_read(ctx, buf, sizeof(vhd_footer_t)); + if (err) + goto out; + + memcpy(footer, buf, sizeof(vhd_footer_t)); + + vhd_footer_in(footer); + err = vhd_validate_footer(footer); + +out: + if (err) + VHDLOG((FN,"%s: reading footer at 0x%08"PRIx64" failed: %d\n", + ctx->file, off, err)); + _aligned_free(buf); + return err; +} + +int +vhd_read_footer(vhd_context_t *ctx, vhd_footer_t *footer) +{ + int err; + off_t off; + + err = vhd_seek(ctx, 0, SEEK_END); + if (err) + return err; + + off = vhd_position(ctx); + if (off == (off_t)-1) + return -errno; + + err = vhd_read_footer_at(ctx, footer, off - 512); + if (err != -EINVAL) + return err; + + err = vhd_read_short_footer(ctx, footer); + if (err != -EINVAL) + return err; + + if (ctx->oflags & VHD_OPEN_STRICT) + return -EINVAL; + + return vhd_read_footer_at(ctx, footer, 0); +} + +int +vhd_read_header_at(vhd_context_t *ctx, vhd_header_t *header, off_t off) +{ + int err; + char *buf; + + buf = NULL; + + if (!vhd_type_dynamic(ctx)) { + err = -EINVAL; + goto out; + } + + err = vhd_seek(ctx, off, SEEK_SET); + if (err) + goto out; + +#ifdef _WIN32 + buf = _aligned_malloc( sizeof(vhd_header_t), VHD_SECTOR_SIZE ); + if( buf == NULL ) { + err = -errno; + goto out; + } +#else + err = posix_memalign((void **)&buf, + VHD_SECTOR_SIZE, sizeof(vhd_header_t)); + if (err) { + buf = NULL; + err = -err; + goto out; + } +#endif + + err = vhd_read(ctx, buf, sizeof(vhd_header_t)); + if (err) + goto out; + + memcpy(header, buf, sizeof(vhd_header_t)); + + vhd_header_in(header); + err = vhd_validate_header(header); + +out: + if (err) + VHDLOG((FN,"%s: reading header at 0x%08"PRIx64" failed: %d\n", + ctx->file, off, err)); + _aligned_free(buf); + return err; +} + +int +vhd_read_header(vhd_context_t *ctx, vhd_header_t *header) +{ + off_t off; + + if (!vhd_type_dynamic(ctx)) { + VHDLOG((FN,"%s is not dynamic!\n", ctx->file)); + return -EINVAL; + } + + off = (off_t)ctx->footer.data_offset; + return vhd_read_header_at(ctx, header, off); +} + +int +vhd_read_bat(vhd_context_t *ctx, vhd_bat_t *bat) +{ + int err; + char *buf; + off_t off; + size_t size; + + buf = NULL; + + if (!vhd_type_dynamic(ctx)) { + err = -EINVAL; + goto fail; + } + + off = (off_t)ctx->header.table_offset; + size = (size_t)vhd_bytes_padded(ctx->header.max_bat_size * sizeof(uint32_t)); + +#ifdef _WIN32 + buf = _aligned_malloc( size, VHD_SECTOR_SIZE ); + if( buf == NULL ) { + err = -errno; + goto fail; + } +#else + err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); + if (err) { + buf = NULL; + err = -err; + goto fail; + } +#endif + + err = vhd_seek(ctx, off, SEEK_SET); + if (err) + goto fail; + + err = vhd_read(ctx, buf, size); + if (err) + goto fail; + + bat->spb = ctx->header.block_size >> VHD_SECTOR_SHIFT; + bat->entries = ctx->header.max_bat_size; + bat->bat = (uint32_t *)buf; + + vhd_bat_in(bat); + + return 0; + +fail: + _aligned_free(buf); + memset(bat, 0, sizeof(vhd_bat_t)); + VHDLOG((FN,"%s: failed to read bat: %d\n", ctx->file, err)); + return err; +} + +static int +vhd_read_batmap_header(vhd_context_t *ctx, vhd_batmap_t *batmap) +{ + int err; + char *buf; + off_t off; + size_t size; + + buf = NULL; + + err = vhd_batmap_header_offset(ctx, &off); + if (err) + goto fail; + + err = vhd_seek(ctx, off, SEEK_SET); + if (err) + goto fail; + + size = (size_t)vhd_bytes_padded(sizeof(vhd_batmap_header_t)); +#ifdef _WIN32 + buf = _aligned_malloc( size, VHD_SECTOR_SIZE ); + if( buf == NULL ) { + err = -errno; + goto fail; + } +#else + err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); + if (err) { + buf = NULL; + err = -err; + goto fail; + } +#endif + + err = vhd_read(ctx, buf, size); + if (err) + goto fail; + + memcpy(&batmap->header, buf, sizeof(vhd_batmap_header_t)); + _aligned_free(buf); + buf = NULL; + + vhd_batmap_header_in(batmap); + + return 0; + +fail: + _aligned_free(buf); + memset(&batmap->header, 0, sizeof(vhd_batmap_header_t)); + VHDLOG((FN,"%s: failed to read batmap header: %d\n", ctx->file, err)); + return err; +} + +static int +vhd_read_batmap_map(vhd_context_t *ctx, vhd_batmap_t *batmap) +{ + int err; + char *buf; + off_t off; + size_t map_size; + + map_size = (size_t)vhd_sectors_to_bytes(batmap->header.batmap_size); + +#ifdef _WIN32 + buf = _aligned_malloc( map_size, VHD_SECTOR_SIZE ); + if( buf == NULL ) { + err = -errno; + goto fail; + } +#else + err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, map_size); + if (err) { + buf = NULL; + err = -err; + goto fail; + } +#endif + + off = (off_t)batmap->header.batmap_offset; + err = vhd_seek(ctx, off, SEEK_SET); + if (err) + goto fail; + + err = vhd_read(ctx, buf, map_size); + if (err) + goto fail; + + batmap->map = buf; + return 0; + +fail: + _aligned_free(buf); + batmap->map = NULL; + VHDLOG((FN,"%s: failed to read batmap: %d\n", ctx->file, err)); + return err; +} + +int +vhd_read_batmap(vhd_context_t *ctx, vhd_batmap_t *batmap) +{ + int err; + + if (!vhd_has_batmap(ctx)) + return -EINVAL; + + memset(batmap, 0, sizeof(vhd_batmap_t)); + + err = vhd_read_batmap_header(ctx, batmap); + if (err) + return err; + + err = vhd_validate_batmap_header(batmap); + if (err) + return err; + + err = vhd_read_batmap_map(ctx, batmap); + if (err) + return err; + + err = vhd_validate_batmap(batmap); + if (err) + goto fail; + + return 0; + +fail: + _aligned_free(batmap->map); + memset(batmap, 0, sizeof(vhd_batmap_t)); + return err; +} + +int +vhd_has_batmap(vhd_context_t *ctx) +{ + if (!vhd_type_dynamic(ctx)) + return 0; + + if (!vhd_creator_tapdisk(ctx)) + return 0; + + if (ctx->footer.crtr_ver <= VHD_VERSION(0, 1)) + return 0; + + if (ctx->footer.crtr_ver >= VHD_VERSION(1, 2)) + return 1; + + /* + * VHDs of version 1.1 probably have a batmap, but may not + * if they were updated from version 0.1 via vhd-update. + */ + if (!vhd_validate_batmap_header(&ctx->batmap)) + return 1; + + if (vhd_read_batmap_header(ctx, &ctx->batmap)) + return 0; + + return (!vhd_validate_batmap_header(&ctx->batmap)); +} + +/* + * Is this a block device (with a fixed size)? This affects whether the file + * can be truncated and where the footer is written for VHDs. + */ +int +vhd_test_file_fixed(const char *file, int *is_block) +{ + int err; + struct stat stats; + + err = stat(file, &stats); + if (err == -1) + return -errno; +#ifdef _WIN32 + *is_block = 0; /* Look at filename for initial \\?? */ +#else + *is_block = !!(S_ISBLK(stats.st_mode)); +#endif + return err; +} + +int +vhd_find_parent(vhd_context_t *ctx, const char *parent, char **_location) +{ + int err, isabs = 0, isrd = 0; + char *location, *cpath, *cdir, *path; + + err = 0; + path = NULL; + cpath = NULL; + location = NULL; + *_location = NULL; + + if (!parent) + return -EINVAL; + +#ifdef _WIN32 + if( parent[0] == '/' ) + isabs = 1; + else { + const char *p; + for( p = parent; *p && *p != '/' && *p != ':'; p++ ) + ; + isabs = ( *p == ':' ); + } + + if( isabs ) { + if( (isrd = _open( parent, _O_RDONLY | _O_BINARY )) == -1 ) + goto errout; + else { + _close( isrd ); + isrd = 1; + } + } +#else + if (parent[0] == '/') { + isabs = 1; + isrd = !access( parent, R_OK ); + } +#endif + if( isabs ) { + if( isrd ) { + path = strdup( parent ); + if( !path ) + return -ENOMEM; + *_location = path; + return 0; + } + goto errout; + } + + /* check parent path relative to child's directory */ + cpath = realpath(ctx->file, NULL); + if (!cpath) + goto errout; +#ifdef _WIN32 + cdir = dirname( cpath + 1 ); +#else + cdir = dirname( cpath ); +#endif + if (asprintf(&location, "%s/%s", cdir, parent) == -1) { + location = NULL; + goto errout; + } +#ifdef _WIN32 + if( (isrd = _open( location, _O_RDONLY | _O_BINARY )) == -1 ) + isrd = 0; + else { + _close( isrd ); + isrd = 1; + } +#else + isrd = !access( location, R_OK ); +#endif + if( isrd ) { + path = realpath( location, NULL ); + if( path ) { +#ifdef _WIN32 + *_location = strdup( path + 1 ); + free( path ); +#else + *_location = path; +#endif + free(cpath); + free(location); + return 0; + } + } + +errout: + err = -errno; + free(location); + free(cpath); + return err; +} + +static int +vhd_macx_encode_location(char *name, char **out, size_t *outlen) +{ + iconv_t cd; + int err; + size_t len; + size_t ibl, obl; + char *uri, *uri_utf8, *uri_utf8p, *ret; + const char *urip; + + err = 0; + ret = NULL; + *out = NULL; + *outlen = 0; + len = strlen(name) + strlen("file://"); + + ibl = len; + obl = len * 2; + + urip = uri = malloc(ibl + 1); + uri_utf8 = uri_utf8p = malloc(obl); + + if (!uri || !uri_utf8) + return -ENOMEM; + + cd = iconv_open("UTF-8", "ASCII"); + if (cd == (iconv_t)-1) { + err = -errno; + goto out; + } + + snprintf(uri, ibl+1, "file://%s", name); + + if (iconv(cd, +#ifdef __linux__ + (char **) +#endif + &urip, &ibl, &uri_utf8p, &obl) == (size_t)-1 || + ibl) { + err = (errno ? -errno : -EIO); + goto out; + } + len = (size_t)(uri_utf8p - uri_utf8); + ret = malloc(len); + if (!ret) { + err = -ENOMEM; + goto out; + } + + memcpy(ret, uri_utf8, len); + *outlen = len; + *out = ret; + + out: + free(uri); + free(uri_utf8); + if (cd != (iconv_t)-1) + iconv_close(cd); + + return err; +} + +static int +vhd_w2u_encode_location(char *name, char **out, size_t *outlen) +{ + int len, err; + size_t ibl, obl; + char *uri, *uri_utf16, *uri_utf16p, *tmp, *ret; + const char *urip; + iconv_t cd; + + cd = (iconv_t)-1; + + err = 0; + ret = NULL; + *out = NULL; + *outlen = 0; + + /* + * MICROSOFT_COMPAT + * relative paths must start with ".\" + */ + if (name[0] != '/') { + tmp = strstr(name, "./"); + if (tmp == name) + tmp += strlen("./"); + else + tmp = name; + + err = asprintf(&uri, ".\\%s", tmp); + } else +#ifdef _WIN32 + err = asprintf( &uri, "%s", name + 1 ); +#else + err = asprintf( &uri, "%s", name ); +#endif + + if (err == -1) + return -ENOMEM; + + tmp = uri; + while (*tmp != '\0') { + if (*tmp == '/') + *tmp = '\\'; + tmp++; + } + + len = strlen(uri); + ibl = len; + obl = len * 4; + urip = uri; + + uri_utf16 = uri_utf16p = malloc(obl); + if (!uri_utf16) { + err = -ENOMEM; + goto out; + } + + /* + * MICROSOFT_COMPAT + * little endian unicode here + */ + cd = iconv_open("UTF-16LE", "ASCII"); + if (cd == (iconv_t)-1) { + err = -errno; + goto out; + } + + if (iconv(cd, +#ifdef __linux__ + (char **) +#endif + &urip, &ibl, &uri_utf16p, &obl) == (size_t)-1 || + ibl) { + err = (errno ? -errno : -EIO); + goto out; + } + + len = (size_t)(uri_utf16p - uri_utf16); + ret = malloc(len); + if (!ret) { + err = -ENOMEM; + goto out; + } + + memcpy(ret, uri_utf16, len); + *outlen = len; + *out = ret; + err = 0; + + out: + free(uri); + free(uri_utf16); + if (cd != (iconv_t)-1) + iconv_close(cd); + + return err; +} + +static char * +vhd_macx_decode_location(const char *in, char *out, int len) +{ + iconv_t cd; + char *name; + size_t ibl, obl; + + name = out; + ibl = obl = len; + + cd = iconv_open("ASCII", "UTF-8"); + if (cd == (iconv_t)-1) + return NULL; + + if (iconv(cd, +#ifdef __linux__ + (char **) +#endif + &in, &ibl, &out, &obl) == (size_t)-1 || ibl) + return NULL; + + iconv_close(cd); + *out = '\0'; + + if (strstr(name, "file://") != name) + return NULL; + + name += strlen("file://"); + + return strdup(name); +} + +static char * +vhd_w2u_decode_location(const char *in, char *out, int len, char *utf_type) +{ + iconv_t cd; + char *name, *tmp; + size_t ibl, obl; + + tmp = name = out; + ibl = obl = len; + + cd = iconv_open("ASCII", utf_type); + if (cd == (iconv_t)-1) + return NULL; + + if (iconv(cd, +#ifdef __linux__ + (char **) +#endif + &in, &ibl, &out, &obl) == (size_t)-1 || ibl) { + iconv_close(cd); + return NULL; + } + + iconv_close(cd); + *out = '\0'; + + /* TODO: spaces */ + while (tmp != out) { + if (*tmp == '\\') + *tmp = '/'; + tmp++; + } + + if (strstr(name, "C:") == name || strstr(name, "c:") == name) + name += strlen("c:"); + return strdup(name); +} + +int +vhd_header_decode_parent(vhd_context_t *ctx, vhd_header_t *header, char **buf) +{ + char *code, out[512+1]; + + if (vhd_creator_tapdisk(ctx) && + ctx->footer.crtr_ver == VHD_VERSION(0, 1)) + code = UTF_16; + else + code = UTF_16BE; + + *buf = vhd_w2u_decode_location(header->prt_name, out, 512, code); + return (*buf == NULL ? -EINVAL : 0); +} + +int +vhd_parent_locator_read(vhd_context_t *ctx, + vhd_parent_locator_t *loc, char **parent) +{ + int err, size; + char *raw, *out, *name; + + raw = NULL; + out = NULL; + name = NULL; + *parent = NULL; + + if (ctx->footer.type != HD_TYPE_DIFF) { + err = -EINVAL; + goto out; + } + + switch (loc->code) { + case PLAT_CODE_MACX: + case PLAT_CODE_W2KU: + case PLAT_CODE_W2RU: + break; + default: + err = -EINVAL; + goto out; + } + + err = vhd_seek(ctx, (off_t)loc->data_offset, SEEK_SET); + if (err) + goto out; + + size = vhd_parent_locator_size(loc); + if (size <= 0) { + err = -EINVAL; + goto out; + } + +#ifdef _WIN32 + raw = _aligned_malloc( size, VHD_SECTOR_SIZE ); + if( raw == NULL ) { + err = -errno; + goto out; + } +#else + err = posix_memalign((void **)&raw, VHD_SECTOR_SIZE, size); + if (err) { + raw = NULL; + err = -err; + goto out; + } +#endif + + err = vhd_read(ctx, raw, size); + if (err) + goto out; + + out = malloc(loc->data_len + 1); + if (!out) { + err = -ENOMEM; + goto out; + } + + switch (loc->code) { + case PLAT_CODE_MACX: + name = vhd_macx_decode_location(raw, out, loc->data_len); + break; + case PLAT_CODE_W2KU: + case PLAT_CODE_W2RU: + name = vhd_w2u_decode_location(raw, out, + loc->data_len, UTF_16LE); + break; + } + + if (!name) { + err = -EINVAL; + goto out; + } + + err = 0; + *parent = name; + +out: + _aligned_free(raw); + free(out); + + if (err) { + VHDLOG((FN,"%s: error reading parent locator: %d\n", + ctx->file, err)); + VHDLOG((FN,"%s: locator: code %u, space 0x%x, len 0x%x, " + "off 0x%"PRIx64"\n", ctx->file, loc->code, loc->data_space, + loc->data_len, loc->data_offset)); + } + + return err; +} + +int +vhd_parent_locator_get(vhd_context_t *ctx, char **parent) +{ + int i, n, err; + char *name, *location; + vhd_parent_locator_t *loc; + + err = 0; + *parent = NULL; + + if (ctx->footer.type != HD_TYPE_DIFF) + return -EINVAL; + + n = vhd_parent_locator_count(ctx); + for (i = 0; i < n; i++) { + loc = ctx->header.loc + i; + err = vhd_parent_locator_read(ctx, loc, &name); + if (err) + continue; + + err = vhd_find_parent(ctx, name, &location); + if (err) + VHDLOG((FN,"%s: couldn't find parent %s (%d)\n", + ctx->file, name, err)); + free(name); + + if (!err) { + *parent = location; + return 0; + } + } + + return err; +} + +int +vhd_parent_locator_write_at(vhd_context_t *ctx, + const char *parent, off_t off, uint32_t code, + size_t max_bytes, vhd_parent_locator_t *loc) +{ + struct stat stats; + int err; + size_t size, len; + char *absolute_path, *relative_path, *encoded, *block; + + memset(loc, 0, sizeof(vhd_parent_locator_t)); + + if (ctx->footer.type != HD_TYPE_DIFF) + return -EINVAL; + + absolute_path = NULL; + relative_path = NULL; + encoded = NULL; + block = NULL; + size = 0; + len = 0; + + switch (code) { + case PLAT_CODE_MACX: + case PLAT_CODE_W2KU: + case PLAT_CODE_W2RU: + break; + default: + return -EINVAL; + } + + absolute_path = realpath(parent, NULL); + if (!absolute_path) { + err = -errno; + goto out; + } + + #ifdef _WIN32 + err = stat( absolute_path+1, &stats ); +#else + err = stat( absolute_path, &stats ); +#endif + if (err) { + err = -errno; + goto out; + } + +#ifdef _WIN32 + /* Look at filename for initial \\ to replace _S_IFBLK?? */ + if (!(stats.st_mode & _S_IFREG) || (stats.st_mode & _S_IFDIR)) { + err = -EINVAL; + goto out; + } +#else + if (!S_ISREG(stats.st_mode) && !S_ISBLK(stats.st_mode)) { + err = -EINVAL; + goto out; + } +#endif + + relative_path = relative_path_to(ctx->file, absolute_path, &err); + if (!relative_path || err) { + err = (err ? err : -EINVAL); + goto out; + } + + switch (code) { + case PLAT_CODE_MACX: + err = vhd_macx_encode_location(relative_path, &encoded, &len); + break; + case PLAT_CODE_W2KU: + err = vhd_w2u_encode_location( absolute_path, &encoded, &len ); + break; + case PLAT_CODE_W2RU: + err = vhd_w2u_encode_location(relative_path, &encoded, &len); + break; + default: + err = -EINVAL; + } + + if (err) + goto out; + + err = vhd_seek(ctx, off, SEEK_SET); + if (err) + goto out; + + size = (size_t)vhd_bytes_padded(len); + + if (max_bytes && size > max_bytes) { + err = -ENAMETOOLONG; + goto out; + } + +#ifdef _WIN32 + block = _aligned_malloc( size, VHD_SECTOR_SIZE ); + if( block == NULL ) { + err = -errno; + goto out; + } +#else + err = posix_memalign((void **)&block, VHD_SECTOR_SIZE, size); + if (err) { + block = NULL; + err = -err; + goto out; + } +#endif + + memset(block, 0, size); + memcpy(block, encoded, len); + + err = vhd_write(ctx, block, size); + if (err) + goto out; + + err = 0; + +out: + free(absolute_path); + free(relative_path); + free(encoded); + _aligned_free(block); + + if (!err) { + loc->res = 0; + loc->code = code; + loc->data_len = len; + /* + * write number of bytes ('size') instead of number of sectors + * into loc->data_space to be compatible with MSFT, even though + * this goes against the specs + */ + loc->data_space = size; + loc->data_offset = off; + } + + return err; +} + +static int +vhd_footer_offset_at_eof(vhd_context_t *ctx, off_t *off) +{ + int err; + if ((err = vhd_seek(ctx, 0, SEEK_END))) + return errno; + *off = vhd_position(ctx) - sizeof(vhd_footer_t); + return 0; +} + +int +vhd_read_bitmap(vhd_context_t *ctx, uint32_t block, char **bufp) +{ + int err; + char *buf; + size_t size; + off_t off; + uint64_t blk; + + buf = NULL; + *bufp = NULL; + + if (!vhd_type_dynamic(ctx)) + return -EINVAL; + + err = vhd_get_bat(ctx); + if (err) + return err; + + if (block >= ctx->bat.entries) + return -ERANGE; + + blk = ctx->bat.bat[block]; + if (blk == DD_BLK_UNUSED) + return -EINVAL; + + off = (off_t)vhd_sectors_to_bytes(blk); + size = (size_t)vhd_bytes_padded(ctx->spb >> 3); + + err = vhd_seek(ctx, off, SEEK_SET); + if (err) + return err; + +#ifdef _WIN32 + buf = _aligned_malloc( size, VHD_SECTOR_SIZE ); + if( buf == NULL ) + return -errno; +#else + err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); + if (err) + return -err; +#endif + + err = vhd_read(ctx, buf, size); + if (err) + goto fail; + + *bufp = buf; + return 0; + +fail: + _aligned_free(buf); + return err; +} + +int +vhd_read_block(vhd_context_t *ctx, uint32_t block, char **bufp) +{ + int err; + char *buf; + size_t size; + uint64_t blk; + off_t end, off; + + buf = NULL; + *bufp = NULL; + + if (!vhd_type_dynamic(ctx)) + return -EINVAL; + + err = vhd_get_bat(ctx); + if (err) + return err; + + if (block >= ctx->bat.entries) + return -ERANGE; + + blk = ctx->bat.bat[block]; + if (blk == DD_BLK_UNUSED) + return -EINVAL; + + off = (off_t)vhd_sectors_to_bytes(blk + ctx->bm_secs); + size = (size_t)vhd_sectors_to_bytes(ctx->spb); + + err = vhd_footer_offset_at_eof(ctx, &end); + if (err) + return err; + +#ifdef _WIN32 + buf = _aligned_malloc( size, VHD_SECTOR_SIZE ); + if( buf == NULL ) { + err = -errno; + goto fail; + } +#else + err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); + if (err) { + err = -err; + goto fail; + } +#endif + + if (end < off + (off_t)ctx->header.block_size) { + size = end - off; + memset(buf + size, 0, ctx->header.block_size - size); + } + + err = vhd_seek(ctx, off, SEEK_SET); + if (err) + goto fail; + + err = vhd_read(ctx, buf, size); + if (err) + goto fail; + + *bufp = buf; + return 0; + +fail: + _aligned_free(buf); + return err; +} + +int +vhd_write_footer_at(vhd_context_t *ctx, vhd_footer_t *footer, off_t off) +{ + int err; + vhd_footer_t *f; + + f = NULL; + +#ifdef _WIN32 + f = _aligned_malloc( sizeof(vhd_footer_t), VHD_SECTOR_SIZE ); + if( f == NULL ) { + err = -errno; + goto out; + } +#else + err = posix_memalign((void **)&f, + VHD_SECTOR_SIZE, sizeof(vhd_footer_t)); + if (err) { + f = NULL; + err = -err; + goto out; + } +#endif + + memcpy(f, footer, sizeof(vhd_footer_t)); + f->checksum = vhd_checksum_footer(f); + + err = vhd_validate_footer(f); + if (err) + goto out; + + err = vhd_seek(ctx, off, SEEK_SET); + if (err) + goto out; + + vhd_footer_out(f); + + err = vhd_write(ctx, f, sizeof(vhd_footer_t)); + +out: + if (err) + VHDLOG((FN,"%s: failed writing footer at 0x%08"PRIx64": %d\n", + ctx->file, off, err)); + _aligned_free(f); + return err; +} + +int +vhd_write_footer(vhd_context_t *ctx, vhd_footer_t *footer) +{ + int err; + off_t off; + + if (ctx->is_block) + err = vhd_footer_offset_at_eof(ctx, &off); + else + err = vhd_end_of_data(ctx, &off); + if (err) + return err; + + err = vhd_write_footer_at(ctx, footer, off); + if (err) + return err; + + if (!vhd_type_dynamic(ctx)) + return 0; + + return vhd_write_footer_at(ctx, footer, 0); +} + +int +vhd_write_header_at(vhd_context_t *ctx, vhd_header_t *header, off_t off) +{ + int err; + vhd_header_t *h; + + h = NULL; + + if (!vhd_type_dynamic(ctx)) { + err = -EINVAL; + goto out; + } + +#ifdef _WIN32 + h = _aligned_malloc( sizeof(vhd_header_t), VHD_SECTOR_SIZE ); + if( h == NULL ) { + err = -errno; + goto out; + } +#else + err = posix_memalign((void **)&h, + VHD_SECTOR_SIZE, sizeof(vhd_header_t)); + if (err) { + h = NULL; + err = -err; + goto out; + } +#endif + + memcpy(h, header, sizeof(vhd_header_t)); + + h->checksum = vhd_checksum_header(h); + err = vhd_validate_header(h); + if (err) + goto out; + + vhd_header_out(h); + + err = vhd_seek(ctx, off, SEEK_SET); + if (err) + goto out; + + err = vhd_write(ctx, h, sizeof(vhd_header_t)); + +out: + if (err) + VHDLOG((FN,"%s: failed writing header at 0x%08"PRIx64": %d\n", + ctx->file, off, err)); + _aligned_free(h); + return err; +} + +int +vhd_write_header(vhd_context_t *ctx, vhd_header_t *header) +{ + off_t off; + + if (!vhd_type_dynamic(ctx)) + return -EINVAL; + + off = (off_t)ctx->footer.data_offset; + return vhd_write_header_at(ctx, header, off); +} + +int +vhd_write_bat(vhd_context_t *ctx, vhd_bat_t *bat) +{ + int err; + off_t off; + vhd_bat_t b; + size_t size; + + if (!vhd_type_dynamic(ctx)) + return -EINVAL; + + err = vhd_validate_bat(&ctx->bat); + if (err) + return err; + + err = vhd_validate_bat(bat); + if (err) + return err; + + memset(&b, 0, sizeof(vhd_bat_t)); + + off = (off_t)ctx->header.table_offset; + size = (uint32_t)vhd_bytes_padded(bat->entries * sizeof(uint32_t)); + + err = vhd_seek(ctx, off, SEEK_SET); + if (err) + return err; + +#ifdef _WIN32 + b.bat = _aligned_malloc( size, VHD_SECTOR_SIZE ); + if( b.bat == NULL ) + return -errno; +#else + err = posix_memalign((void **)&b.bat, VHD_SECTOR_SIZE, size); + if (err) + return -err; +#endif + + memcpy(b.bat, bat->bat, size); + b.spb = bat->spb; + b.entries = bat->entries; + vhd_bat_out(&b); + + err = vhd_write(ctx, b.bat, size); + _aligned_free(b.bat); + + return err; +} + +int +vhd_write_batmap(vhd_context_t *ctx, vhd_batmap_t *batmap) +{ + int err; + off_t off; + vhd_batmap_t b; + char *buf, *map; + size_t size, map_size; + + buf = NULL; + map = NULL; + + if (!vhd_has_batmap(ctx)) { + err = -EINVAL; + goto out; + } + + b.header = batmap->header; + b.map = batmap->map; + + b.header.checksum = vhd_checksum_batmap(&b); + err = vhd_validate_batmap(&b); + if (err) + goto out; + + off = (off_t)b.header.batmap_offset; + map_size = (size_t)vhd_sectors_to_bytes(b.header.batmap_size); + + err = vhd_seek(ctx, off, SEEK_SET); + if (err) + goto out; + +#ifdef _WIN32 + map = _aligned_malloc( map_size, VHD_SECTOR_SIZE ); + if( map == NULL ) { + err = -errno; + goto out; + } +#else + err = posix_memalign((void **)&map, VHD_SECTOR_SIZE, map_size); + if (err) { + map = NULL; + err = -err; + goto out; + } +#endif + + memcpy(map, b.map, map_size); + + err = vhd_write(ctx, map, map_size); + if (err) + goto out; + + err = vhd_batmap_header_offset(ctx, &off); + if (err) + goto out; + + size = (size_t)vhd_bytes_padded(sizeof(vhd_batmap_header_t)); + + err = vhd_seek(ctx, off, SEEK_SET); + if (err) + goto out; + +#ifdef _WIN32 + buf = _aligned_malloc( size, VHD_SECTOR_SIZE ); + if( buf == NULL ) { + err = -errno; + goto out; + } +#else + err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); + if (err) { + err = -err; + buf = NULL; + goto out; + } +#endif + + vhd_batmap_header_out(&b); + memset(buf, 0, size); + memcpy(buf, &b.header, sizeof(vhd_batmap_header_t)); + + err = vhd_write(ctx, buf, size); + +out: + if (err) + VHDLOG((FN,"%s: failed writing batmap: %d\n", ctx->file, err)); + _aligned_free(buf); + _aligned_free(map); + return 0; +} + +int +vhd_write_bitmap(vhd_context_t *ctx, uint32_t block, char *bitmap) +{ + int err; + off_t off; + uint64_t blk; + size_t size; + + if (!vhd_type_dynamic(ctx)) + return -EINVAL; + + err = vhd_validate_bat(&ctx->bat); + if (err) + return err; + + if (block >= ctx->bat.entries) + return -ERANGE; + + if ((unsigned long)bitmap & (VHD_SECTOR_SIZE - 1)) + return -EINVAL; + + blk = ctx->bat.bat[block]; + if (blk == DD_BLK_UNUSED) + return -EINVAL; + + off = (off_t)vhd_sectors_to_bytes(blk); + size = (size_t)vhd_sectors_to_bytes(ctx->bm_secs); + + err = vhd_seek(ctx, off, SEEK_SET); + if (err) + return err; + + err = vhd_write(ctx, bitmap, size); + if (err) + return err; + + return 0; +} + +int +vhd_write_block(vhd_context_t *ctx, uint32_t block, char *data) +{ + int err; + off_t off; + size_t size; + uint64_t blk; + + if (!vhd_type_dynamic(ctx)) + return -EINVAL; + + err = vhd_validate_bat(&ctx->bat); + if (err) + return err; + + if (block >= ctx->bat.entries) + return -ERANGE; + + if ((unsigned long)data & ~(VHD_SECTOR_SIZE -1)) + return -EINVAL; + + blk = ctx->bat.bat[block]; + if (blk == DD_BLK_UNUSED) + return -EINVAL; + + off = (off_t)vhd_sectors_to_bytes(blk + ctx->bm_secs); + size = (size_t)vhd_sectors_to_bytes(ctx->spb); + + err = vhd_seek(ctx, off, SEEK_SET); + if (err) + return err; + + err = vhd_write(ctx, data, size); + if (err) + return err; + + return 0; +} + +static inline int +namedup(char **dup, const char *name) +{ + *dup = NULL; + + if (strnlen(name, MAX_NAME_LEN) >= MAX_NAME_LEN) + return -ENAMETOOLONG; + + *dup = strdup(name); + if (*dup == NULL) + return -ENOMEM; + + return 0; +} + +int +vhd_seek(vhd_context_t *ctx, off_t offset, int whence) +{ + off_t off; + + off = (off_t)lseek(ctx->fd, offset, whence); + if (off == (off_t)-1) { + VHDLOG((FN,"%s: seek(0x%08"PRIx64", %d) failed: %d\n", + ctx->file, offset, whence, -errno)); + return -errno; + } + + return 0; +} + +off_t +vhd_position(vhd_context_t *ctx) +{ + return (off_t)lseek(ctx->fd, 0, SEEK_CUR); +} + +int +vhd_read(vhd_context_t *ctx, void *buf, size_t size) +{ + size_t ret; + + errno = 0; + + ret = read(ctx->fd, buf, size); + if (ret == size) + return 0; + + VHDLOG((FN,"%s: read of %" PRIuMAX " returned %" PRIdMAX ", errno: %d\n", + ctx->file, (uintmax_t)size, (intmax_t)ret, -errno)); + + return (errno ? -errno : -EIO); +} + +int +vhd_write(vhd_context_t *ctx, void *buf, size_t size) +{ + size_t ret; + + errno = 0; + + ret = write(ctx->fd, buf, size); + if (ret == size) + return 0; + + VHDLOG((FN,"%s: write of %" PRIuMAX " returned %" PRIdMAX ", errno: %d\n", + ctx->file, (uintmax_t)size, (intmax_t)ret, -errno)); + + return (errno ? -errno : -EIO); +} + +int +vhd_offset(vhd_context_t *ctx, uint32_t sector, uint32_t *offset) +{ + int err; + uint32_t block; + + if (!vhd_type_dynamic(ctx)) + return sector; + + err = vhd_get_bat(ctx); + if (err) + return err; + + block = sector / ctx->spb; + if (ctx->bat.bat[block] == DD_BLK_UNUSED) + *offset = DD_BLK_UNUSED; + else + *offset = ctx->bat.bat[block] + + ctx->bm_secs + (sector % ctx->spb); + + return 0; +} + +int +vhd_open_fast(vhd_context_t *ctx) +{ + int err; + char *buf; + size_t size; + + size = sizeof(vhd_footer_t) + sizeof(vhd_header_t); +#ifdef _WIN32 + buf = _aligned_malloc( size, VHD_SECTOR_SIZE ); + if( buf == NULL ) { + VHDLOG((FN,"failed allocating %s: %d\n", ctx->file, -errno)); + return -errno; + } +#else + err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); + if (err) { + VHDLOG((FN,"failed allocating %s: %d\n", ctx->file, -err)); + return -err; + } +#endif + + err = vhd_read(ctx, buf, size); + if (err) { + VHDLOG((FN,"failed reading %s: %d\n", ctx->file, err)); + goto out; + } + + memcpy(&ctx->footer, buf, sizeof(vhd_footer_t)); + vhd_footer_in(&ctx->footer); + err = vhd_validate_footer(&ctx->footer); + if (err) + goto out; + + if (vhd_type_dynamic(ctx)) { + if (ctx->footer.data_offset != sizeof(vhd_footer_t)) + err = vhd_read_header(ctx, &ctx->header); + else { + memcpy(&ctx->header, + buf + sizeof(vhd_footer_t), + sizeof(vhd_header_t)); + vhd_header_in(&ctx->header); + err = vhd_validate_header(&ctx->header); + } + + if (err) + goto out; + + ctx->spb = ctx->header.block_size >> VHD_SECTOR_SHIFT; + ctx->bm_secs = secs_round_up_no_zero(ctx->spb >> 3); + } + +out: + _aligned_free(buf); + return err; +} + +int +vhd_open(vhd_context_t *ctx, const char *file, int flags) +{ + int err, oflags; + + if (flags & VHD_OPEN_STRICT) + vhd_flag_clear(flags, VHD_OPEN_FAST); + + memset(ctx, 0, sizeof(vhd_context_t)); + ctx->fd = -1; + ctx->oflags = flags; + + err = namedup(&ctx->file, file); + if (err) + return err; + + oflags = O_DIRECT | O_LARGEFILE | O_BINARY; + if (flags & VHD_OPEN_RDONLY) + oflags |= O_RDONLY; + if (flags & VHD_OPEN_RDWR) + oflags |= O_RDWR; + + ctx->fd = open(ctx->file, oflags, 0644); + if (ctx->fd == -1) { + err = -errno; + VHDLOG((FN,"failed to open %s: %d\n", ctx->file, err)); + goto fail; + } + + err = vhd_test_file_fixed(ctx->file, &ctx->is_block); + if (err) + goto fail; + + if (flags & VHD_OPEN_FAST) { + err = vhd_open_fast(ctx); + if (err) + goto fail; + + return 0; + } + + err = vhd_read_footer(ctx, &ctx->footer); + if (err) + goto fail; + + if (!(flags & VHD_OPEN_IGNORE_DISABLED) && vhd_disabled(ctx)) { + err = -EINVAL; + goto fail; + } + + if (vhd_type_dynamic(ctx)) { + err = vhd_read_header(ctx, &ctx->header); + if (err) + goto fail; + + ctx->spb = ctx->header.block_size >> VHD_SECTOR_SHIFT; + ctx->bm_secs = secs_round_up_no_zero(ctx->spb >> 3); + } + + return 0; + +fail: + if (ctx->fd != -1) + close(ctx->fd); + free(ctx->file); + memset(ctx, 0, sizeof(vhd_context_t)); + return err; +} + +void +vhd_close(vhd_context_t *ctx) +{ + if (ctx->file) + close(ctx->fd); + free(ctx->file); + _aligned_free(ctx->bat.bat); + _aligned_free(ctx->batmap.map); + memset(ctx, 0, sizeof(vhd_context_t)); +} + +static inline void +vhd_initialize_footer(vhd_context_t *ctx, int type, uint64_t size) +{ + memset(&ctx->footer, 0, sizeof(vhd_footer_t)); + memcpy(ctx->footer.cookie, HD_COOKIE, sizeof(ctx->footer.cookie)); + ctx->footer.features = HD_RESERVED; + ctx->footer.ff_version = HD_FF_VERSION; + ctx->footer.timestamp = vhd_time(time(NULL)); + ctx->footer.crtr_ver = VHD_CURRENT_VERSION; + #ifdef _WIN32 + ctx->footer.crtr_os = HD_CR_OS_WINDOWS; +#elif __APPLE__ + ctx->footer.crtr_os = HD_CR_OS_MACINTOSH; +#elif VMS + ctx->footer.crtr_os = HD_CR_OS_VMS; +#else + ctx->footer.crtr_os = HD_CR_OS_UNIX; +#endif + ctx->footer.orig_size = size; + ctx->footer.curr_size = size; + ctx->footer.geometry = vhd_chs(size); + ctx->footer.type = type; + ctx->footer.saved = 0; + ctx->footer.data_offset = 0xFFFFFFFFFFFFFFFF; + strcpy(ctx->footer.crtr_app, "tap"); + blk_uuid_generate(&ctx->footer.uuid); +} + +static int +vhd_initialize_header_parent_name(vhd_context_t *ctx, const char *parent_path) +{ + int err; + iconv_t cd; + size_t ibl, obl; + char *ppath, *dst; + const char *pname; + + err = 0; + pname = NULL; + ppath = NULL; + + /* + * MICROSOFT_COMPAT + * big endian unicode here + */ + cd = iconv_open(UTF_16BE, "ASCII"); + if (cd == (iconv_t)-1) { + err = -errno; + goto out; + } + + ppath = strdup(parent_path); + if (!ppath) { + err = -ENOMEM; + goto out; + } + + pname = basename(ppath); + if (!strcmp(pname, "")) { + err = -EINVAL; + goto out; + } + + ibl = strlen(pname); + obl = sizeof(ctx->header.prt_name); + dst = ctx->header.prt_name; + + memset(dst, 0, obl); + + if (iconv(cd, +#ifdef __linux__ + (char **) +#endif + &pname, &ibl, &dst, &obl) == (size_t)-1 || ibl) + err = (errno ? -errno : -EINVAL); + +out: + iconv_close(cd); + free(ppath); + return err; +} + +static off_t +get_file_size(const char *name) +{ + int fd; + off_t end; + + fd = open(name, O_LARGEFILE | O_RDONLY | O_BINARY); + if (fd == -1) { + VHDLOG((FN,"unable to open '%s': %d\n", name, errno)); + return -errno; + } + end = (off_t)lseek(fd, 0, SEEK_END); + close(fd); + return end; +} + +static int +vhd_initialize_header(vhd_context_t *ctx, const char *parent_path, + uint64_t size, int raw) +{ + int err; + struct stat stats; + vhd_context_t parent; + + if (!vhd_type_dynamic(ctx)) + return -EINVAL; + + memset(&ctx->header, 0, sizeof(vhd_header_t)); + memcpy(ctx->header.cookie, DD_COOKIE, sizeof(ctx->header.cookie)); + ctx->header.data_offset = (uint64_t)-1; + ctx->header.table_offset = VHD_SECTOR_SIZE * 3; /* 1 ftr + 2 hdr */ + ctx->header.hdr_ver = DD_VERSION; + ctx->header.block_size = VHD_BLOCK_SIZE; + ctx->header.prt_ts = 0; + ctx->header.res1 = 0; + ctx->header.max_bat_size = (u32)((ctx->footer.curr_size + + VHD_BLOCK_SIZE - 1) >> VHD_BLOCK_SHIFT); + + ctx->footer.data_offset = VHD_SECTOR_SIZE; + + if (ctx->footer.type == HD_TYPE_DYNAMIC) + return 0; + + err = stat(parent_path, &stats); + if (err == -1) + return -errno; + + if (raw) { + ctx->header.prt_ts = vhd_time(stats.st_mtime); + if (!size) + size = get_file_size(parent_path); + } + else { + err = vhd_open(&parent, parent_path, VHD_OPEN_RDONLY); + if (err) + return err; + + ctx->header.prt_ts = vhd_time(stats.st_mtime); + blk_uuid_copy(&ctx->header.prt_uuid, &parent.footer.uuid); + if (!size) + size = parent.footer.curr_size; + vhd_close(&parent); + } + ctx->footer.orig_size = size; + ctx->footer.curr_size = size; + ctx->footer.geometry = vhd_chs(size); + ctx->header.max_bat_size = (u32)( + (size + VHD_BLOCK_SIZE - 1) >> VHD_BLOCK_SHIFT); + + return vhd_initialize_header_parent_name(ctx, parent_path); +} + +static int +vhd_write_parent_locators(vhd_context_t *ctx, const char *parent) +{ + int i, err; + off_t off; + uint32_t code; + + code = PLAT_CODE_NONE; + + if (ctx->footer.type != HD_TYPE_DIFF) + return -EINVAL; + + off = (off_t)(ctx->batmap.header.batmap_offset + + vhd_sectors_to_bytes(ctx->batmap.header.batmap_size)); + if (off & (VHD_SECTOR_SIZE - 1)) + off = (off_t)vhd_bytes_padded(off); + + for (i = 0; i < 3; i++) { + switch (i) { + case 0: + code = PLAT_CODE_MACX; + break; + case 1: + code = PLAT_CODE_W2KU; + break; + case 2: + code = PLAT_CODE_W2RU; + break; + } + + err = vhd_parent_locator_write_at(ctx, parent, off, code, + 0, ctx->header.loc + i); + if (err) + return err; + + off += vhd_parent_locator_size(ctx->header.loc + i); + } + + return 0; +} + +int +vhd_change_parent(vhd_context_t *child, char *parent_path, int flags) +{ + int i, err; + char *ppath; + struct stat stats; + vhd_context_t parent; + + ppath = realpath(parent_path, NULL); + if (!ppath) { + VHDLOG((FN,"error resolving parent path %s for %s: %d\n", + parent_path, child->file, errno)); + return -errno; + } +#ifdef _WIN32 + ppath++; +#endif + err = stat(ppath, &stats); + if (err == -1) { + err = -errno; + goto out; + } + +#ifdef _WIN32 + /* Look at filename for initial \\ to replace _S_IFBLK?? */ + if (!(stats.st_mode & _S_IFREG) || (stats.st_mode & _S_IFDIR)) { + err = -EINVAL; + goto out; + } +#else + if (!S_ISREG(stats.st_mode) && !S_ISBLK(stats.st_mode)) { + err = -EINVAL; + goto out; + } +#endif + + if (flags & 1) { + blk_uuid_clear(&child->header.prt_uuid); + } else { + err = vhd_open(&parent, ppath, VHD_OPEN_RDONLY); + if (err) { + VHDLOG((FN,"error opening parent %s for %s: %d\n", + ppath, child->file, err)); + goto out; + } + /* Extension */ + if( flags & 2 ) { + if( blk_uuid_compare(&child->header.prt_uuid, + &parent.footer.uuid) != 0 ) { + VHDLOG((FN,"new parent %s for %s: parent UUID doesn't match child\n", + ppath, child->file)); + vhd_close(&parent); + err = -EINVAL; + goto out; + } + } else + blk_uuid_copy(&child->header.prt_uuid, &parent.footer.uuid); + vhd_close(&parent); + } + + vhd_initialize_header_parent_name(child, ppath); + child->header.prt_ts = vhd_time(stats.st_mtime); + + for (i = 0; i < vhd_parent_locator_count(child); i++) { + vhd_parent_locator_t *loc = child->header.loc + i; + size_t max = vhd_parent_locator_size(loc); + + switch (loc->code) { + case PLAT_CODE_MACX: + case PLAT_CODE_W2KU: + case PLAT_CODE_W2RU: + break; + default: + continue; + } + + err = vhd_parent_locator_write_at(child, ppath, + ((off_t)loc->data_offset), + loc->code, max, loc); + if (err) { + VHDLOG((FN,"error writing parent locator %d for %s: %d\n", + i, child->file, err)); + goto out; + } + } + + TEST_FAIL_AT(FAIL_REPARENT_LOCATOR); + + err = vhd_write_header(child, &child->header); + if (err) { + VHDLOG((FN,"error writing header for %s: %d\n", child->file, err)); + goto out; + } + + err = 0; + +out: +#ifdef _WIN32 + free(ppath -1); +#else + free(ppath); +#endif + return err; +} + +static int +vhd_create_batmap(vhd_context_t *ctx) +{ + off_t off; + int err; + size_t map_bytes; + vhd_batmap_header_t *header; + + if (!vhd_type_dynamic(ctx)) + return -EINVAL; + + map_bytes = (ctx->header.max_bat_size + 7) >> 3; + header = &ctx->batmap.header; + + memset(header, 0, sizeof(vhd_batmap_header_t)); + memcpy(header->cookie, VHD_BATMAP_COOKIE, sizeof(header->cookie)); + + err = vhd_batmap_header_offset(ctx, &off); + if (err) + return err; + + header->batmap_offset = off + + vhd_bytes_padded(sizeof(vhd_batmap_header_t)); + header->batmap_size = secs_round_up_no_zero(map_bytes); + header->batmap_version = VHD_BATMAP_CURRENT_VERSION; + + map_bytes = (size_t)vhd_sectors_to_bytes(header->batmap_size); + +#ifdef _WIN32 + ctx->batmap.map = _aligned_malloc( map_bytes, VHD_SECTOR_SIZE ); + if( ctx->batmap.map == NULL ) { + return -errno; + } +#else + err = posix_memalign((void **)&ctx->batmap.map, + VHD_SECTOR_SIZE, map_bytes); + if (err) { + ctx->batmap.map = NULL; + return -err; + } +#endif + + memset(ctx->batmap.map, 0, map_bytes); + + return vhd_write_batmap(ctx, &ctx->batmap); +} + +static int +vhd_create_bat(vhd_context_t *ctx) +{ + int err; + size_t i, size; + + if (!vhd_type_dynamic(ctx)) + return -EINVAL; + + size = (size_t)vhd_bytes_padded(ctx->header.max_bat_size * sizeof(uint32_t)); +#ifdef _WIN32 + ctx->bat.bat = _aligned_malloc( size, VHD_SECTOR_SIZE ); + if( ctx->bat.bat == NULL ) { + return -errno; + } +#else + err = posix_memalign((void **)&ctx->bat.bat, VHD_SECTOR_SIZE, size); + if (err) { + ctx->bat.bat = NULL; + return -err; + } +#endif + + memset(ctx->bat.bat, 0, size); + for (i = 0; i < ctx->header.max_bat_size; i++) + ctx->bat.bat[i] = DD_BLK_UNUSED; + + err = vhd_seek(ctx, (off_t)ctx->header.table_offset, SEEK_SET); + if (err) + return err; + + ctx->bat.entries = ctx->header.max_bat_size; + ctx->bat.spb = ctx->header.block_size >> VHD_SECTOR_SHIFT; + + return vhd_write_bat(ctx, &ctx->bat); +} + +static int +vhd_initialize_fixed_disk(vhd_context_t *ctx) +{ + char *buf; + size_t i; + int err; + + if (ctx->footer.type != HD_TYPE_FIXED) + return -EINVAL; + + err = vhd_seek(ctx, 0, SEEK_SET); + if (err) + return err; + +#ifdef _WIN32 + buf = VirtualAlloc( NULL, VHD_BLOCK_SIZE, MEM_COMMIT, PAGE_READONLY ); + if( buf == NULL ) + return -ENOMEM; +#else + buf = mmap(0, VHD_BLOCK_SIZE, PROT_READ, + MAP_SHARED | MAP_ANON, -1, 0); + if (buf == MAP_FAILED) + return -errno; +#endif + for (i = 0; i < ctx->footer.curr_size >> VHD_BLOCK_SHIFT; i++) { + err = vhd_write(ctx, buf, VHD_BLOCK_SIZE); + if (err) + goto out; + } + + err = 0; + +out: +#ifdef _WIN32 + VirtualFree( buf, 0, MEM_RELEASE ); +#else + munmap(buf, VHD_BLOCK_SIZE); +#endif + return err; +} + +int +vhd_get_phys_size(vhd_context_t *ctx, off_t *size) +{ + int err; + + if ((err = vhd_end_of_data(ctx, size))) + return err; + *size += sizeof(vhd_footer_t); + return 0; +} + +int +vhd_set_phys_size(vhd_context_t *ctx, off_t size) +{ + off_t phys_size; + int err; + + err = vhd_get_phys_size(ctx, &phys_size); + if (err) + return err; + if (size < phys_size) { + /* would result in data loss */ + VHDLOG((FN,"ERROR: new size (%"PRIu64") < phys size (%"PRIu64")\n", + size, phys_size)); + return -EINVAL; + } + return vhd_write_footer_at(ctx, &ctx->footer, + size - sizeof(vhd_footer_t)); +} + +static int +__vhd_create(const char *name, const char *parent, uint64_t bytes, int type, + vhd_flag_creat_t flags) +{ + int err; + off_t off; + vhd_context_t ctx; + uint64_t size, blks; + + switch (type) { + case HD_TYPE_DIFF: + if (!parent) + return -EINVAL; + case HD_TYPE_FIXED: + case HD_TYPE_DYNAMIC: + break; + default: + return -EINVAL; + } + + if (strnlen(name, VHD_MAX_NAME_LEN - 1) == VHD_MAX_NAME_LEN - 1) + return -ENAMETOOLONG; + + memset(&ctx, 0, sizeof(vhd_context_t)); + blks = (bytes + VHD_BLOCK_SIZE - 1) >> VHD_BLOCK_SHIFT; + size = blks << VHD_BLOCK_SHIFT; + + ctx.fd = open(name, O_WRONLY | O_CREAT | O_BINARY | + O_TRUNC | O_LARGEFILE | O_DIRECT, 0644); + if (ctx.fd == -1) + return -errno; + + ctx.file = strdup(name); + if (!ctx.file) { + err = -ENOMEM; + goto out; + } + + err = vhd_test_file_fixed(ctx.file, &ctx.is_block); + if (err) + goto out; + + vhd_initialize_footer(&ctx, type, size); + + if (type == HD_TYPE_FIXED) { + err = vhd_initialize_fixed_disk(&ctx); + if (err) + goto out; + } else { + int raw = vhd_flag_test(flags, VHD_FLAG_CREAT_PARENT_RAW); + err = vhd_initialize_header(&ctx, parent, size, raw); + if (err) + goto out; + + err = vhd_write_footer_at(&ctx, &ctx.footer, 0); + if (err) + goto out; + + err = vhd_write_header_at(&ctx, &ctx.header, VHD_SECTOR_SIZE); + if (err) + goto out; + + err = vhd_create_batmap(&ctx); + if (err) + goto out; + + err = vhd_create_bat(&ctx); + if (err) + goto out; + + if (type == HD_TYPE_DIFF) { + err = vhd_write_parent_locators(&ctx, parent); + if (err) + goto out; + } + + /* write header again since it may have changed */ + err = vhd_write_header_at(&ctx, &ctx.header, VHD_SECTOR_SIZE); + if (err) + goto out; + } + + err = vhd_seek(&ctx, 0, SEEK_END); + if (err) + goto out; + + off = vhd_position(&ctx); + if (off == (off_t)-1) { + err = -errno; + goto out; + } + + if (ctx.is_block) + off -= sizeof(vhd_footer_t); + + err = vhd_write_footer_at(&ctx, &ctx.footer, off); + if (err) + goto out; + + err = 0; + +out: + vhd_close(&ctx); + if (err && !ctx.is_block) + unlink(name); + return err; +} + +int +vhd_create(const char *name, uint64_t bytes, int type, vhd_flag_creat_t flags) +{ + return __vhd_create(name, NULL, bytes, type, flags); +} + +int +vhd_snapshot(const char *name, uint64_t bytes, const char *parent, + vhd_flag_creat_t flags) +{ + return __vhd_create(name, parent, bytes, HD_TYPE_DIFF, flags); +} + +static int +__vhd_io_fixed_read(vhd_context_t *ctx, + char *buf, uint64_t sec, uint32_t secs) +{ + int err; + + err = vhd_seek(ctx, (off_t)vhd_sectors_to_bytes(sec), SEEK_SET); + if (err) + return err; + + return vhd_read(ctx, buf, (off_t)vhd_sectors_to_bytes(secs)); +} + +static void +__vhd_io_dynamic_copy_data(vhd_context_t *ctx, + char *map, int map_off, + char *bitmap, int bitmap_off, + char *dst, char *src, int secs) +{ + int i; + + for (i = 0; i < secs; i++) { + if (test_bit(map, map_off + i)) + goto next; + + if (ctx && !vhd_bitmap_test(ctx, bitmap, bitmap_off + i)) + goto next; + + memcpy(dst, src, VHD_SECTOR_SIZE); + set_bit(map, map_off + i); + + next: + src += VHD_SECTOR_SIZE; + dst += VHD_SECTOR_SIZE; + } +} + +static int +__vhd_io_dynamic_read_link(vhd_context_t *ctx, char *map, + char *buf, uint64_t sector, uint32_t secs) +{ + off_t off; + uint32_t blk, sec; + int err, cnt, map_off; + char *bitmap, *data, *src; + + map_off = 0; + + do { + blk = (uint32_t)(sector / ctx->spb); + sec = sector % ctx->spb; + off = ctx->bat.bat[blk]; + data = NULL; + bitmap = NULL; + + if (off == DD_BLK_UNUSED) { + cnt = MIN(secs, ctx->spb); + goto next; + } + + err = vhd_read_bitmap(ctx, blk, &bitmap); + if (err) + return err; + + err = vhd_read_block(ctx, blk, &data); + if (err) { + _aligned_free(bitmap); + return err; + } + + cnt = MIN(secs, ctx->spb - sec); + src = data + vhd_sectors_to_bytes(sec); + + __vhd_io_dynamic_copy_data(ctx, + map, map_off, + bitmap, sec, + buf, src, cnt); + + next: + _aligned_free(data); + _aligned_free(bitmap); + + secs -= cnt; + sector += cnt; + map_off += cnt; + buf += vhd_sectors_to_bytes(cnt); + + } while (secs); + + return 0; +} + +static int +__raw_read_link(char *filename, + char *map, char *buf, uint64_t sec, uint32_t secs) +{ + int fd, err; + off_t off; + uint64_t size; + char *data; + + err = 0; + errno = 0; + fd = open(filename, O_RDONLY | O_DIRECT | O_LARGEFILE | O_BINARY); + if (fd == -1) { + VHDLOG((FN,"%s: failed to open: %d\n", filename, -errno)); + return -errno; + } + + off = (off_t)lseek(fd, (off_t)vhd_sectors_to_bytes(sec), SEEK_SET); + if (off == (off_t)-1) { + VHDLOG((FN,"%s: seek(0x%08"PRIx64") failed: %d\n", + filename, vhd_sectors_to_bytes(sec), -errno)); + err = -errno; + goto close; + } + + size = vhd_sectors_to_bytes(secs); +#ifdef _WIN32 + data = _aligned_malloc( (size_t)size, VHD_SECTOR_SIZE ); + if( data == NULL ) { + err = -errno; + goto close; + } +#else + err = posix_memalign((void **)&data, VHD_SECTOR_SIZE, size); + if (err) { + err = -err; + goto close; + } +#endif + +#ifdef _WIN32 + { + char *dp; + dp = data; + + while( size != 0 ) { + uint32_t n; + + if( size > UINT32_MAX ) + n = (uint32_t)((((UINT64)UINT32_MAX) + 1) / 2); + else + n = (uint32_t)size; + + err = read( fd, dp, n ); + if( err != n ) { + VHDLOG((FN, "%s: reading of %"PRIu64" returned %d, errno: %d\n", + filename, n, err, -errno )); + _aligned_free( data ); + err = errno ? -errno : -EIO; + goto close; + } + size -= n; + dp += n; + } + } +#else + err = read( fd, data, size ); + if( (size_t)err != size ) { + VHDLOG((FN, "%s: reading of %"PRIu64" returned %d, errno: %d\n", + filename, size, err, -errno )); + _aligned_free( data ); + err = errno ? -errno : -EIO; + goto close; + } +#endif + __vhd_io_dynamic_copy_data(NULL, map, 0, NULL, 0, buf, data, secs); + _aligned_free(data); + err = 0; + +close: + close(fd); + return err; +} + +static int +__vhd_io_dynamic_read(vhd_context_t *ctx, + char *buf, uint64_t sec, uint32_t secs) +{ + int err; + uint32_t i, done; + char *map, *next; + vhd_context_t parent, *vhd; + + err = vhd_get_bat(ctx); + if (err) + return err; + + vhd = ctx; + next = NULL; + map = calloc(1, secs << (VHD_SECTOR_SHIFT - 3)); + if (!map) + return -ENOMEM; + + memset(buf, 0, (size_t)vhd_sectors_to_bytes(secs)); + + for (;;) { + err = __vhd_io_dynamic_read_link(vhd, map, buf, sec, secs); + if (err) + goto close; + + for (done = 0, i = 0; i < secs; i++) + if (test_bit(map, i)) + done++; + + if (done == secs) { + err = 0; + goto close; + } + + if (vhd->footer.type == HD_TYPE_DIFF) { + err = vhd_parent_locator_get(vhd, &next); + if (err) + goto close; + if (vhd_parent_raw(vhd)) { + err = __raw_read_link(next, map, buf, sec, + secs); + goto close; + } + } else { + err = 0; + goto close; + } + + if (vhd != ctx) + vhd_close(vhd); + vhd = &parent; + + err = vhd_open(vhd, next, VHD_OPEN_RDONLY); + if (err) + goto out; + + err = vhd_get_bat(vhd); + if (err) + goto close; + + free(next); + next = NULL; + } + +close: + if (vhd != ctx) + vhd_close(vhd); +out: + free(map); + free(next); + return err; +} + +int +vhd_io_read(vhd_context_t *ctx, char *buf, uint64_t sec, uint32_t secs) +{ + if (vhd_sectors_to_bytes(sec + secs) > ctx->footer.curr_size) + return -ERANGE; + + if (!vhd_type_dynamic(ctx)) + return __vhd_io_fixed_read(ctx, buf, sec, secs); + + return __vhd_io_dynamic_read(ctx, buf, sec, secs); +} + +static int +__vhd_io_fixed_write(vhd_context_t *ctx, + char *buf, uint64_t sec, uint32_t secs) +{ + int err; + + err = vhd_seek(ctx, (off_t)vhd_sectors_to_bytes(sec), SEEK_SET); + if (err) + return err; + + return vhd_write(ctx, buf, (size_t)vhd_sectors_to_bytes(secs)); +} + +static int +__vhd_io_allocate_block(vhd_context_t *ctx, uint32_t block) +{ + char *buf; + size_t size; + off_t off, max; + int err, gap; + long spp; +#ifdef _WIN32 + SYSTEM_INFO inf; + + GetSystemInfo( &inf ); + spp = inf.dwPageSize >> VHD_SECTOR_SHIFT; +#elif defined( _SC_PAGESIZE) + spp = sysconf( _SC_PAGESIZE ) >> VHD_SECTOR_SHIFT; +#else + spp = getpagesize() >> VHD_SECTOR_SHIFT; +#endif + if( spp == 0 ) + abort(); + + err = vhd_end_of_data(ctx, &max); + if (err) + return err; + + gap = 0; + off = max; + max >>= VHD_SECTOR_SHIFT; + + /* data region of segment should begin on page boundary */ + if ((max + ctx->bm_secs) % spp) { + gap = (spp - ((max + ctx->bm_secs) % spp)); + max += gap; + } + + err = vhd_seek(ctx, off, SEEK_SET); + if (err) + return err; + + size = (size_t)vhd_sectors_to_bytes(ctx->spb + ctx->bm_secs + gap); +#ifdef _WIN32 + buf = VirtualAlloc( NULL, size, MEM_COMMIT, PAGE_READONLY ); + if( buf == NULL ) + return -ENOMEM; +#else + buf = mmap(0, size, PROT_READ, MAP_SHARED | MAP_ANON, -1, 0); + if (buf == MAP_FAILED) + return -errno; +#endif + + err = vhd_write(ctx, buf, size); + if (err) + goto out; + + ctx->bat.bat[block] = max; + err = vhd_write_bat(ctx, &ctx->bat); + if (err) + goto out; + + err = 0; + +out: +#ifdef _WIN32 + VirtualFree( buf, 0, MEM_RELEASE ); +#else + munmap(buf, size); +#endif + return err; +} + +static int +__vhd_io_dynamic_write(vhd_context_t *ctx, + char *buf, uint64_t sector, uint32_t secs) +{ + char *map; + off_t off; + uint32_t blk, sec; + int err, ret; + size_t i, cnt; + + if (vhd_sectors_to_bytes(sector + secs) > ctx->footer.curr_size) + return -ERANGE; + + err = vhd_get_bat(ctx); + if (err) + return err; + + if (vhd_has_batmap(ctx)) { + err = vhd_get_batmap(ctx); + if (err) + return err; + } + + do { + blk = (uint32_t)(sector / ctx->spb); + sec = sector % ctx->spb; + + off = ctx->bat.bat[blk]; + if (off == DD_BLK_UNUSED) { + err = __vhd_io_allocate_block(ctx, blk); + if (err) + return err; + + off = ctx->bat.bat[blk]; + } + + off += ctx->bm_secs + sec; + err = vhd_seek(ctx, (off_t)vhd_sectors_to_bytes(off), SEEK_SET); + if (err) + return err; + + cnt = MIN(secs, ctx->spb - sec); + err = vhd_write(ctx, buf, (size_t)vhd_sectors_to_bytes(cnt)); + if (err) + return err; + + if (vhd_has_batmap(ctx) && + vhd_batmap_test(ctx, &ctx->batmap, blk)) + goto next; + + err = vhd_read_bitmap(ctx, blk, &map); + if (err) + return err; + + for (i = 0; i < cnt; i++) + vhd_bitmap_set(ctx, map, sec + i); + + err = vhd_write_bitmap(ctx, blk, map); + if (err) + goto fail; + + if (vhd_has_batmap(ctx)) { + for (i = 0; i < ctx->spb; i++) + if (!vhd_bitmap_test(ctx, map, i)) { + _aligned_free(map); + goto next; + } + + vhd_batmap_set(ctx, &ctx->batmap, blk); + err = vhd_write_batmap(ctx, &ctx->batmap); + if (err) + goto fail; + } + + _aligned_free(map); + map = NULL; + + next: + secs -= cnt; + sector += cnt; + buf += vhd_sectors_to_bytes(cnt); + } while (secs); + + err = 0; + +out: + ret = vhd_write_footer(ctx, &ctx->footer); + return (err ? err : ret); + +fail: + _aligned_free(map); + goto out; +} + +int +vhd_io_write(vhd_context_t *ctx, char *buf, uint64_t sec, uint32_t secs) +{ + if (vhd_sectors_to_bytes(sec + secs) > ctx->footer.curr_size) + return -ERANGE; + + if (!vhd_type_dynamic(ctx)) + return __vhd_io_fixed_write(ctx, buf, sec, secs); + + return __vhd_io_dynamic_write(ctx, buf, sec, secs); +} diff --git a/extracters/libvhd/libvhd.sln b/extracters/libvhd/libvhd.sln new file mode 100644 index 0000000..f5b982f --- /dev/null +++ b/extracters/libvhd/libvhd.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.24720.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libvhd", "libvhd.vcxproj", "{695E504C-32F4-43AF-8AD6-761DE66D0EAB}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vhd-util", "..\vhd-util\vhd-util.vcxproj", "{CC5FB437-0C01-4ECF-A75F-10C37B607AC6}" + ProjectSection(ProjectDependencies) = postProject + {695E504C-32F4-43AF-8AD6-761DE66D0EAB} = {695E504C-32F4-43AF-8AD6-761DE66D0EAB} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {695E504C-32F4-43AF-8AD6-761DE66D0EAB}.Debug|x64.ActiveCfg = Debug|x64 + {695E504C-32F4-43AF-8AD6-761DE66D0EAB}.Debug|x64.Build.0 = Debug|x64 + {695E504C-32F4-43AF-8AD6-761DE66D0EAB}.Debug|x86.ActiveCfg = Debug|Win32 + {695E504C-32F4-43AF-8AD6-761DE66D0EAB}.Debug|x86.Build.0 = Debug|Win32 + {695E504C-32F4-43AF-8AD6-761DE66D0EAB}.Release|x64.ActiveCfg = Release|x64 + {695E504C-32F4-43AF-8AD6-761DE66D0EAB}.Release|x64.Build.0 = Release|x64 + {695E504C-32F4-43AF-8AD6-761DE66D0EAB}.Release|x86.ActiveCfg = Release|Win32 + {695E504C-32F4-43AF-8AD6-761DE66D0EAB}.Release|x86.Build.0 = Release|Win32 + {CC5FB437-0C01-4ECF-A75F-10C37B607AC6}.Debug|x64.ActiveCfg = Debug|x64 + {CC5FB437-0C01-4ECF-A75F-10C37B607AC6}.Debug|x64.Build.0 = Debug|x64 + {CC5FB437-0C01-4ECF-A75F-10C37B607AC6}.Debug|x86.ActiveCfg = Debug|Win32 + {CC5FB437-0C01-4ECF-A75F-10C37B607AC6}.Debug|x86.Build.0 = Debug|Win32 + {CC5FB437-0C01-4ECF-A75F-10C37B607AC6}.Release|x64.ActiveCfg = Release|x64 + {CC5FB437-0C01-4ECF-A75F-10C37B607AC6}.Release|x64.Build.0 = Release|x64 + {CC5FB437-0C01-4ECF-A75F-10C37B607AC6}.Release|x86.ActiveCfg = Release|Win32 + {CC5FB437-0C01-4ECF-A75F-10C37B607AC6}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/extracters/libvhd/libvhd.vcxproj b/extracters/libvhd/libvhd.vcxproj new file mode 100644 index 0000000..4840070 --- /dev/null +++ b/extracters/libvhd/libvhd.vcxproj @@ -0,0 +1,125 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {695E504C-32F4-43AF-8AD6-761DE66D0EAB} + Win32Proj + 8.1 + + + + StaticLibrary + true + v140 + + + StaticLibrary + false + v140 + + + Application + true + v140 + + + Application + false + v140 + + + + + + + + + + + + + + + + + + + + + true + + + true + + + + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreaded + Level3 + ProgramDatabase + Disabled + include;%(AdditionalIncludeDirectories) + + + MachineX86 + true + Console + Rpcrt4.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + false + true + + + + + + + + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreaded + Level3 + ProgramDatabase + include;%(AdditionalIncludeDirectories) + + + MachineX86 + true + Console + true + true + Rpcrt4.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extracters/libvhd/libvhd.vcxproj.filters b/extracters/libvhd/libvhd.vcxproj.filters new file mode 100644 index 0000000..f7c7898 --- /dev/null +++ b/extracters/libvhd/libvhd.vcxproj.filters @@ -0,0 +1,39 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/extracters/libvhd/relative-path.c b/extracters/libvhd/relative-path.c new file mode 100644 index 0000000..cc328b3 --- /dev/null +++ b/extracters/libvhd/relative-path.c @@ -0,0 +1,442 @@ +/* Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/* Modified March 2016 Timothe Litt to work under windows. + * Copyright (C) 2016 Timothe Litt + * Modifications subject to the same license terms as above, + * substituting "Timothe Litt" for "XenSource Inc." + */ + +#ifdef _WIN32 +#define _CRT_SECURE_NO_WARNINGS 1 +#endif + +#include +#include +#include +#include +#include + +#include "relative-path.h" + +#ifdef _WIN32 +#include +#include +#include +#define strdup _strdup +#else +#include +#endif + +#define DELIMITER '/' + +#define FN __func__ + +#define EPRINTF(a) rpath_log_error a + +static void rpath_log_error( const char *func, const char *fmt, ... ) +#ifdef __GNUC__ + __attribute__((format(printf,2,3))); +#else + ; +#endif + +#ifndef LIBVHD_HAS_SYSLOG +#ifdef _WIN32 +#define LIBVHD_HAS_SYSLOG 0 +#else +#define LIBVHD_HAS_SYSLOG 1 +#endif +#endif + +#define sfree(ptr) \ +do { \ + free(ptr); \ + ptr = NULL; \ +} while (0) + +#ifdef _WIN32 +LIBVHD_API char *realpath( const char *path, char *resolved ) { + char *p; + DWORD len; + + p = resolved; + if( resolved == NULL ) { + resolved = malloc( 1+ MAX_PATH + 1 ); + if( resolved == NULL ) { + return NULL; + } + } + if( (len = GetFullPathName( path[0] == '/'? path+1:path, MAX_PATH + 1, resolved, NULL )) == 0 ) { + if( p == NULL ) + free( resolved ); + return NULL; + } + if( len > MAX_PATH ) { + if( p != NULL ) + return NULL; + if( (p = realloc( resolved, len )) == NULL ) { + free( resolved ); + return NULL; + } + resolved = p; + len = GetFullPathName( path, MAX_PATH + 1, resolved, NULL ); + if( len > MAX_PATH || len == 0 ) { + free( resolved ); + return NULL; + } + } + + for( p = resolved; *p; p++ ) { + if( *p == '\\') + *p = '/'; + } + for( p = resolved; isalpha( *p ); p++ ) + ; + if( *p == ':' ) { /* Hide drive under '/' for callers. */ + memmove( resolved + 1, resolved, strlen( resolved ) +1 ); + resolved[0] = '/'; /* Callers must skip '/' when touching filesystem. */ + } + return resolved; +} +LIBVHD_API int asprintf( char **result, const char *fmt, ... ) { + int len; + va_list ap; + char *np; + + /* It would be better to va_copy() the list and do a prescan, + * but va_copy apparently has issues with versions of MS C + * still in the field. This approach is ugly and wasteful, but + * should work. (A KB just isn't what it used to be...) + */ + if( (*result = malloc( 1 * 100 * 1000 + 1 )) == NULL ) + return -1; + va_start( ap, fmt ); + len = vsprintf( *result, fmt, ap ); + np = realloc( *result, len + 1 ); + if( np != NULL ) + *result = np; + return len; +} +#endif + +/* + * count number of tokens between DELIMITER characters + */ + +int count_nodes(char *path) +{ + int i; + char *tmp; + + if (!path) + return 0; + + for (i = 0, tmp = path; *tmp != '\0'; tmp++) + if (*tmp == DELIMITER) + i++; + + return i; +} + +/* + * return copy of next node in @path, or NULL + * @path is moved to the end of the next node + * @err is set to -errno on failure + * copy should be freed + */ +static char * +next_node(char **path, int *err) +{ + int ret; + char *tmp, *start; + + if (!path || !*path) { + *err = -EINVAL; + return NULL; + } + + *err = 0; + start = *path; + + for (tmp = *path; *tmp != '\0'; tmp++) + if (*tmp == DELIMITER) { + int size; + char *node; + + size = tmp - start + 1; + node = malloc(size); + if (!node) { + *err = -ENOMEM; + return NULL; + } + + ret = snprintf(node, size, "%s", start); + if (ret < 0) { + free(node); + *err = -EINVAL; + return NULL; + } + + *path = tmp; + return node; + } + + return NULL; +} + +/* + * count number of nodes in common betwee @to and @from + * returns number of common nodes, or -errno on failure + */ +static int +count_common_nodes(char *to, char *from) +{ + int err, common; + char *to_node, *from_node; + + if (!to || !from) + return -EINVAL; + + err = 0; + common = 0; + to_node = NULL; + from_node = NULL; + + do { + to_node = next_node(&to, &err); + if (err || !to_node) + break; + + from_node = next_node(&from, &err); + if (err || !from_node) + break; + + if (strncmp(to_node, from_node, MAX_NAME_LEN)) + break; + + ++to; + ++from; + ++common; + sfree(to_node); + sfree(from_node); + + } while (1); + + sfree(to_node); + sfree(from_node); + + if (err) + return err; + + return common; +} + +/* + * construct path of @count '../', './' if @count is zero, or NULL on error + * result should be freed + */ +static char * +up_nodes(int count) +{ + char *path, *tmp; + int i, ret, len, size; + + if (!count) + return strdup("./"); + + len = strlen("../"); + size = len * count; + if (size >= MAX_NAME_LEN) + return NULL; + + path = malloc(size + 1); + if (!path) + return NULL; + + tmp = path; + for (i = 0; i < count; i++) { + ret = sprintf(tmp, "../"); + if (ret < 0 || ret != len) { + free(path); + return NULL; + } + tmp += ret; + } + + return path; +} + +/* + * return pointer to @offset'th node of path or NULL on error + */ +static char * +node_offset(char *from, int offset) +{ + char *path; + + if (!from || !offset) + return NULL; + + for (path = from; *path != '\0'; path++) { + if (*path == DELIMITER) + if (--offset == 0) + return path + 1; + } + + return NULL; +} + +/* + * return a relative path from @from to @to + * result should be freed + */ +LIBVHD_API +char * +relative_path_to(char *from, char *to, int *err) +{ + int from_nodes, common; + char *to_absolute, *from_absolute; + char *up, *common_target_path, *relative_path; + + *err = 0; + up = NULL; + to_absolute = NULL; + from_absolute = NULL; + relative_path = NULL; + + if (strnlen(to, MAX_NAME_LEN) == MAX_NAME_LEN || + strnlen(from, MAX_NAME_LEN) == MAX_NAME_LEN) { + EPRINTF((FN,"invalid input; max path length is %d\n", + MAX_NAME_LEN)); + *err = -ENAMETOOLONG; + return NULL; + } + + to_absolute = realpath(to, NULL); + if (!to_absolute) { + EPRINTF((FN,"failed to get absolute path of %s\n", to)); + *err = -errno; + goto out; + } + + from_absolute = realpath(from, NULL); + if (!from_absolute) { + EPRINTF((FN,"failed to get absolute path of %s\n", from)); + *err = -errno; + goto out; + } + + if (strnlen(to_absolute, MAX_NAME_LEN) == MAX_NAME_LEN || + strnlen(from_absolute, MAX_NAME_LEN) == MAX_NAME_LEN) { + EPRINTF((FN,"invalid input; max path length is %d\n", + MAX_NAME_LEN)); + *err = -ENAMETOOLONG; + goto out; + } + + /* count nodes in source path */ + from_nodes = count_nodes(from_absolute); + + /* count nodes in common */ + common = count_common_nodes(to_absolute + 1, from_absolute + 1); + if (common < 0) { + EPRINTF((FN,"failed to count common nodes of %s and %s: %d\n", + to_absolute, from_absolute, common)); + *err = common; + goto out; + } + + /* move up to common node */ + up = up_nodes(from_nodes - common - 1); + if (!up) { + EPRINTF((FN,"failed to allocate relative path for %s: %d\n", + from_absolute, -ENOMEM)); + *err = -ENOMEM; + goto out; + } + + /* get path from common node to target */ + common_target_path = node_offset(to_absolute, common + 1); + if (!common_target_path) { + EPRINTF((FN,"failed to find common target path to %s: %d\n", + to_absolute, -EINVAL)); + *err = -EINVAL; + goto out; + } + + /* get relative path */ + if (asprintf(&relative_path, "%s%s", up, common_target_path) == -1) { + EPRINTF((FN,"failed to construct final path %s%s: %d\n", + up, common_target_path, -ENOMEM)); + relative_path = NULL; + *err = -ENOMEM; + goto out; + } + +out: + sfree(up); + sfree(to_absolute); + sfree(from_absolute); + + return relative_path; +} +static void rpath_log_error( const char *func, const char *fmt, ... ) +{ + char *buf, nilbuf; + size_t ilen, len; + va_list ap; + + ilen = sizeof( "tap-err:%s: " ) + strlen( func ) -2; + va_start(ap, fmt ); + len = vsnprintf( &nilbuf, 1, fmt, ap ); + va_end( ap ); + + if( (buf = malloc( ilen + len + 1 )) == NULL ) { +#if LIBVHD_HAS_SYSLOG + syslog( LOG_INFO, "tap-err:%s: Out of memory", func ); +#else + fprintf( stderr, "tap-err%s: Out of memory for %s\n", func, fmt ); +#endif + return; + } + va_start(ap, fmt); + (void) snprintf( buf, ilen, "tap-err:%s: ", func ); + (void) vsnprintf( buf + ilen -1, len+1, fmt, ap ); + va_end( ap ); + len += ilen -1; + if( buf[ len -1 ] != '\n' ) + buf[len++] = '\n'; + buf[len] = '\0'; +#if LIBVHD_HAS_SYSLOG + syslog(LOG_INFO, buf); +#else + fputs( buf, stderr ); +#endif + free( buf ); + + return; + } diff --git a/extracters/vhd-util/COPYING b/extracters/vhd-util/COPYING new file mode 100644 index 0000000..673becb --- /dev/null +++ b/extracters/vhd-util/COPYING @@ -0,0 +1,33 @@ +* 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. +*/ + +The Windows version uses a stripped version of the GNU C library's getopt, +and hence is covered by the GPL. + +The modifications made by Timothe Litt are Copyright (C) 2016 by Timothe Litt. +They may be used under the same terms as the XenSource license above (substituting "Timothe Litt" for "Xensource Inc" as appropriate. + diff --git a/extracters/vhd-util/INSTALL b/extracters/vhd-util/INSTALL new file mode 100644 index 0000000..d60771c --- /dev/null +++ b/extracters/vhd-util/INSTALL @@ -0,0 +1,31 @@ +This is a port of the Xen vhd-util tool and the libvhd library to windows. + +It still works (with minor enhancements) on Unix. + +The distribution kit includes prebuilt executables for Linux and Windows. + +There are two components: + libvhd - library for VHD file access + vhd-util - tool for managing VHD files. + +Each has a directory, which should be at the same level. + +On Unix: + cd vhd-util && make && make install + + cd vhd-util && make PREFIX=/usr && make PREFIX=/usr + To place somewhere else. + + This creates a sharable library (libvhd) and the vhd-util command. + +On Windows: + Builds under microsoft Visual Studio (2015), free edition. + + Open libvhd/libvhd.sln and build it. + +On Windows, a DLL is NOT created as the API doesn't support it. +(The DLL allocates memory that the client must free, but doesn't + provide routines to do this. On windows, the pools are different.) + +Licensing is described in COPYING. + diff --git a/extracters/vhd-util/Makefile b/extracters/vhd-util/Makefile new file mode 100644 index 0000000..2e2bd1e --- /dev/null +++ b/extracters/vhd-util/Makefile @@ -0,0 +1,115 @@ +# -*- Makefile -*- + +# Builds vhd-util and the libvhd shared library. + +PREFIX = /usr/local +LIBDIR = $(PREFIX)/lib +BINDIR = $(PREFIX)/bin + +CPPFLAGS = -D_GNU_SOURCE -I include -I $(LIBVHD)/include +CFLAGS = -O4 +LDFLAGS = -L. -L $(LIBVHD) +LDLIBS = -lvhd-util -luuid -lvhd + +LDCONFIG_D = /etc/ld.so.conf.d/vhd-util + +LIBVHD = ../libvhd + +SRCS = atomicio.c \ +libvhd-journal.c \ +vhd-util-check.c \ +vhd-util-coalesce.c \ +vhd-util-create.c \ +vhd-util-fill.c \ +lvm-util.c \ +vhd-util-modify.c \ +vhd-util-query.c \ +vhd-util-read.c \ +vhd-util-repair.c \ +vhd-util-resize.c \ +vhd-util-revert.c \ +vhd-util-scan.c \ +vhd-util-set-field.c \ +vhd-util-snapshot.c + +OBJS = $(SRCS:.c=.o) + +LIBVHDSO = libvhd.so +LIBVHDSONAME = $(LIBVHDSO).2 +LIBVHDSOFILE = $(LIBVHDSONAME).1.0 + +all: vhd-util $(LIBVHD)/$(LIBVHDSO) + +# The utility + +libvhd-util.a : $(OBJS) + $(AR) -rs libvhd-util.a $(OBJS) + +vhd-util : vhd-util.o libvhd-util.a $(LIBVHD)/$(LIBVHDSO) + $(CC) $(LDFLAGS) -Wl,-rpath,$(LIBDIR) -o vhd-util vhd-util.o $(LDLIBS) + +# The sharable library + +$(LIBVHD)/$(LIBVHDSOFILE) : $(LIBVHD)/libvhd.c $(LIBVHD)/relative-path.c + $(CC) -shared -o $(LIBVHD)/$(LIBVHDSOFILE) -Wl,-soname,$(LIBVHDSONAME) $(CPPFLAGS) $(CFLAGS) -fPIC $(LIBVHD)/libvhd.c $(LIBVHD)/relative-path.c -luuid + +# The symlinks in the local directory necessary for linking + +$(LIBVHD)/$(LIBVHDSO) : $(LIBVHD)/$(LIBVHDSOFILE) + ldconfig -n $(LIBVHD) + ln -sf $(LIBVHD)/$(LIBVHDSOFILE) $(LIBVHD)/$(LIBVHDSO) + +# Install (note that to run the utility before installation, you must set +# LD_LOAD_LIBRARY to $(VLDLIB) + +install: all + cp -p $(LIBVHD)/$(LIBVHDSOFILE) $(LIBDIR)/ + cp -p vhd-util $(BINDIR)/ + echo "$(LIBDIR)" >$(LDCONFIG_D) + ldconfig + ln -sf $(LIBVHDSONAME) $(LIBDIR)/$(LIBVHDSO) + +# Un-install + +uninstall: + rm -f $(BINDIR)/vhd-util $(LIBDIR)/$(LIBVHDSOFILE) $(LIBDIR)/$(LIBVHDSONAME) $(LIBDIR)/$(LIBVHDSO) + ldconfig + rm -f $(LDCONFIG_D) + +# Clean working area + +clean: tidy + rm -f vhd-util vhd-util.exe $(LIBVHD)/$(LIBVHDSOFILE) $(LIBVHD)/$(LIBVHDSONAME) $(LIBVHD)/$(LIBVHDSO) + +# Clean intermediate files only, leaving outputs + +tidy: + rm -f $(OBJS) libvhd-util.a vhd-util.o + +KITFILES = \ +vhd-util/README \ +vhd-util/INSTALL \ +vhd-util/COPYING \ +vhd-util/*.c \ +vhd-util/include/*.h \ +vhd-util/vhd-util \ +vhd-util/vhd-util.exe \ +vhd-util/Makefile \ +vhd-util/vhd-util.vcxproj \ +vhd-util/vhd-util.vcxproj.filters \ +\ +libvhd/*.c \ +libvhd/libvhd.so* \ +libvhd/libvhd.sln \ +libvhd/libvhd.vcxproj \ +libvhd/libvhd.vcxproj.filters \ +libvhd/include/*.h \ + +KITEXES = \ +vhd-util/vhd-util \ +vhd-util/vhd-util.exe \ +libvhd/libvhd.so* + +kit: all + cd .. && chown -h $(USER).$(USER) $(KITFILES) && chmod -x $(KITFILES) && chmod +x $(KITEXES) && tar -czf vhd-tools.tgz $(KITFILES) + diff --git a/extracters/vhd-util/README b/extracters/vhd-util/README new file mode 100644 index 0000000..13e5f54 --- /dev/null +++ b/extracters/vhd-util/README @@ -0,0 +1,229 @@ +vhd-util is originally an unsupported tool provided with the Xen emulator +family. It was not designed as a user tool, but it does have some +functions that can be used without internals knowledge. + +It is a "sharp" tool - you can do irreversible damage with it. +Use at your own risk. + +This version has been modified to: + + o Work on Windows as well as Unix + o Resolve a number of compiler-detected issues + o Identify the creator OS + o Fix some bugs. + o The 'scan' command, which operates on LVM volumes, was + not ported to Windows. All other commands (should) work. + o An error message is generated when a command fails. + o Some safeguards have been added to the 'modify' command. + o read -P summarizes file and its parents. + +The tool is undocumentd by Xen, except for a few cryptic comments +in the README that assume the Xen environment. + +The following should be more useful as a stand-alone tool. + +This document is the result of reverse-engineering the vhd-util code, +and may contain errors. As with the tool, use with caution and at +your own risk. + +vhd-util is a command-line utility with the following general syntax: + + vhd-util COMMAND OPTIONS + +Most commands produce a cryptic usage with the -h option. +The target of a command is specified with the (required) -n "option". + +Commands tend to fail when they don't make sense - e.g. asking about +difference disk data structures on a fixed-size disk. + +Some terminology: + Sector - Address on virtual disk. Always 512 bytes. + Block - Allocation unit in sparse disks. Default 4096 sectors + (2 MB). + Fixed disk - Virtual disk of fixed size = maximum size + small header. + Dynamic disk - (Sparse disk) Virtual disk that grows as data is + written. Simulator sees it as fixed (max) size. + Diff disk - (Snapshot) Dynamic disk that contains (only) changes + to a parent Dynamic (or Diff) disk. Used for + backups, or to experiment with easy rollback. + BAT - "Block allocation table": map of virtual blocks to + physical sectors in a dynamic/diff disk. + Bitmap - Bits associated with each Block indicating which + sectors within the block have been written. + +vhd-util create + Create a new disk image file. + + -n filename File to create + -s size Specify file size (MB) + -r Create a fixed size image file (default is sparse) + +vhd-util snapshot + Create a new snapshot (difference image) of a sparse file. + Any subsequent modification to the parent file invalidates + the snapshot. The parent filename is stored in the snapshot. + Snapshots can be chained to arbitrary depth, at perfomance cost. + + -n filename File to create + -p filename Filename of parent disk + -l limit Maximum depth of snapshot chain + -m Parent disk is raw (physical). Not supported on windows. + +vhd-util check + Check metadata of VHD file for consistency and reports any errors + detected. + + -n filename File to check + -p Check parents as well + -i Ignore missing primary footer (repair can fix) + +vhd-util read + Read and format the metadata of a sparse (dynamic) disk. + + -n filename File to query + -p Print formatted header and footer. + -P Print formatted summary of file and its parents + + The following options are used for debugging. + + -t sector# Translate logical sector to VHD location + -b block# Print BAT directory for block + -m block# Print BINARY bitmap. (Pipe to a hex editor or file) + -i sec# Print block/sector address and written status + -a Print BINARY BAT map entries + -j block# Print allocation status of block + -d block# Print BINARY content of block data + -c # Repeat command # times for consecutivie blocks/sectors. + -r sector# Print BINARY content of sector(s) + -x Print (some) values in hex. + +vhd-util query + Print key fields from the metadata. Mostly unlabeled, suitable for + consumption by a script. + + -n filename File to query + -v Print virtual size (MB) + -p Print filename of parent + -s Print physical size (bytes) Includes metadata. + -f Print Xen's "hidden" state field. (Non-standard) + -d Print chain depth (# files from -n to ultimate parent) + +vhd-util coalesce + Merges a snapshot (differencing) disk into its parent. + + -n filename File to merge (parent is found automagically) + +vhd-util repair + Attempt to repair a damaged volume. + Copies redundant copy of metadata to correct place at the + end of the file. N.B. OS level data may still be corrupt... + + -n filename File to repair + +vhd-util resize + Change the size of an image file. Can expand, or if + unwritten space at end, shrink. N.B. Does NOT tell the + OS filesystem, nor respect it. Restricted to files created + by Xen (including vhd-util). + + Shrink currently aborts with "partially implemented" for + dynamic disks... + + -n filename File to resize + -j filename Journal file (Must not exist,deleted at end.) + -s size (MB) New size + +vhd-util fill + Reads and rewrites (with the same data) every block of the disk. + Effect is to allocate zero blocks for every page of a dynamic + (sparse) disk. + + -n filename File to process + +vhd-util modify + Modify file metadata + + -n filename File to modify + -P filename (Extension) change parent only after verifying + that the UUID of the parent matches the old one. + Safer alternative for case where parent has been + renamed or moved. + + The following **dangerous** options are used for debugging. + + -p filename Change parent to this file. Actually changing + to a different file will corrupt data. Think + at least three times before using! + -s byteaddress Move the file footer. Can be used for changing + physical size of file, but it's tricky. Xen " + Change the size of the file containing the VHD + image. This does NOT affect the VHD disk capacity, + only the physical size of the file containing + the VHD. Naturally, it is not possible to set the + file size to be less than what the VHD utilizes. + The operation doesn't actually change the file + size, but it writes the footer in the right + location such that resizing the file (manually, as + a separate step) will produce the correct results. + + If the new file size is greater than the current + file size, the file must first be expanded and + then altered with this operation. + + If the new size is smaller than the current size, + the VHD must first be altered with this operation + and then the file must be shrunk. + + Failing to resize the file will result in a + corrupted VHD." + -P is an extension to the Xen tool. + For protection against typos, -p must be specified twice. + +vhd-util revert + Applies a journal file to recover from a failed operation. + Resize can fail with VHD in an inconsistent state; the journal file + records the changes made so that they can (hopefully) be reversed. + + A VHD file in this state has a special 'poisoned' cookie to prevent + it from being accessed normally. + + -n filename The file to be recovered + -j filename The journal file (specified with -j on the resize) + +vhd-util set + Set the (Xen-specifix) hidden flag. + + -n filename File to modify + -f hidden Only "hidden" supported. + -v value Decimal value for field. + +vhd-util scan + Scan LVM volumes for VHD files and report capacity, size and parent. + Unix only. + + -m filter Select files to consider (match name) + -f Do a 'fast' scan + -c Continue on errors + -l volume Scan LVM volume + -p Pretty-print results + -a Scan parents + -v Verbose + +For example, to create a sparse image - + vhd-util create -n MyVHDFile -s 1024 + +This creates a sparse 1GB file named "MyVHDFile" that can be mounted +and populated with data. + +One can also base the image on an existing file - + vhd-util snapshot -n MyVHDFile -p SomeFile + +This creates a sparse VHD file named "MyVHDFile" using "SomeFile" +as a parent image. Copy-on-write semantics ensure that writes will be +stored in "MyVHDFile" while reads will be directed to the most +recently written version of the data, either in "MyVHDFile" or +"SomeFile" as is appropriate. + +This document is Copyright (C) 2016 Timothe Litt. + +See the COPYING file for license. diff --git a/extracters/vhd-util/atomicio.c b/extracters/vhd-util/atomicio.c new file mode 100644 index 0000000..95bd754 --- /dev/null +++ b/extracters/vhd-util/atomicio.c @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2005 Anil Madhavapeddy. All rights reserved. + * Copyright (c) 1995,1999 Theo de Raadt. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include "atomicio.h" + +/* + * ensure all of data on socket comes through. f==read || f==vwrite + */ +size_t +atomicio(f, fd, _s, n) + ssize_t (*f) (int, void *, size_t); + int fd; + void *_s; + size_t n; +{ + char *s = _s; + size_t pos = 0; + ssize_t res; + + while (n > pos) { + res = (f) (fd, s + pos, n - pos); + switch (res) { + case -1: + if (errno == EINTR || errno == EAGAIN) + continue; + return 0; + case 0: + errno = EPIPE; + return pos; + default: + pos += (size_t)res; + } + } + return (pos); +} + diff --git a/extracters/vhd-util/gnugetopt.c b/extracters/vhd-util/gnugetopt.c new file mode 100644 index 0000000..402be3e --- /dev/null +++ b/extracters/vhd-util/gnugetopt.c @@ -0,0 +1,506 @@ +/* This is GNU's getopt.c */ + +char *optarg; + +int optind = 1; + +int __getopt_initialized; + +static char *nextchar; + +int opterr = 1; + +int optopt = '?'; + +static enum +{ + REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER +} ordering; + +static char *posixly_correct = 0; +#include +#include +#include +#include + +#define _(x) x + +static char * +my_index (str, chr) + const char *str; + int chr; +{ + while (*str) + { + if (*str == chr) + return (char *) str; + str++; + } + return 0; +} + +static int first_nonopt; +static int last_nonopt; + +# define SWAP_FLAGS(ch1, ch2) + +#if defined __STDC__ && __STDC__ +static void exchange (char **); +#endif + +static void +exchange (argv) + char **argv; +{ + int bottom = first_nonopt; + int middle = last_nonopt; + int top = optind; + char *tem; + + while (top > middle && middle > bottom) + { + if (top - middle > middle - bottom) + { + int len = middle - bottom; + register int i; + + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + SWAP_FLAGS (bottom + i, top - (middle - bottom) + i); + } + top -= len; + } + else + { + int len = top - middle; + register int i; + + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + SWAP_FLAGS (bottom + i, middle + i); + } + bottom += len; + } + } + + first_nonopt += (optind - last_nonopt); + last_nonopt = optind; +} + +#if defined __STDC__ && __STDC__ +static const char *_getopt_initialize (int, char *const *, const char *); +#endif +static const char * +_getopt_initialize (argc, argv, optstring) + int argc; + char *const *argv; + const char *optstring; +{ + first_nonopt = last_nonopt = optind; + + nextchar = NULL; + + if (optstring[0] == '-') + { + ordering = RETURN_IN_ORDER; + ++optstring; + } + else if (optstring[0] == '+') + { + ordering = REQUIRE_ORDER; + ++optstring; + } + else if (posixly_correct != NULL) + ordering = REQUIRE_ORDER; + else + ordering = PERMUTE; + + return optstring; +} + +int +_getopt_internal (argc, argv, optstring, longopts, longind, long_only) + int argc; + char *const *argv; + const char *optstring; + const struct option *longopts; + int *longind; + int long_only; +{ + int print_errors = opterr; + if (optstring[0] == ':') + print_errors = 0; + + optarg = NULL; + + if (optind == 0 || !__getopt_initialized) + { + if (optind == 0) + optind = 1; /* Don't scan ARGV[0], the program name. */ + optstring = _getopt_initialize (argc, argv, optstring); + __getopt_initialized = 1; + } + +# define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0') + + if (nextchar == NULL || *nextchar == '\0') + { + if (last_nonopt > optind) + last_nonopt = optind; + if (first_nonopt > optind) + first_nonopt = optind; + + if (ordering == PERMUTE) + { + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (last_nonopt != optind) + first_nonopt = optind; + + while (optind < argc && NONOPTION_P) + optind++; + last_nonopt = optind; + } + + + if (optind != argc && !strcmp (argv[optind], "--")) + { + optind++; + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (first_nonopt == last_nonopt) + first_nonopt = optind; + last_nonopt = argc; + + optind = argc; + } + + if (optind == argc) + { + if (first_nonopt != last_nonopt) + optind = first_nonopt; + return -1; + } + + if (NONOPTION_P) + { + if (ordering == REQUIRE_ORDER) + return -1; + optarg = argv[optind++]; + return 1; + } + + nextchar = (argv[optind] + 1 + + (longopts != NULL && argv[optind][1] == '-')); + } + + + if (longopts != NULL + && (argv[optind][1] == '-' + || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1]))))) + { + char *nameend; + const struct option *p; + const struct option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = -1; + int option_index; + + for (nameend = nextchar; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, nextchar, nameend - nextchar)) + { + if ((unsigned int) (nameend - nextchar) + == (unsigned int) strlen (p->name)) + { + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + pfound = p; + indfound = option_index; + } + else + ambig = 1; + } + + if (ambig && !exact) + { + if (print_errors) + fprintf (stderr, _("%s: option `%s' is ambiguous\n"), + argv[0], argv[optind]); + nextchar += strlen (nextchar); + optind++; + optopt = 0; + return '?'; + } + + if (pfound != NULL) + { + option_index = indfound; + optind++; + if (*nameend) + { + if (pfound->has_arg) + optarg = nameend + 1; + else + { + if (print_errors) + { + if (argv[optind - 1][1] == '-') + fprintf (stderr, + _("%s: option `--%s' doesn't allow an argument\n"), + argv[0], pfound->name); + else + fprintf (stderr, + _("%s: option `%c%s' doesn't allow an argument\n"), + argv[0], argv[optind - 1][0], pfound->name); + } + + nextchar += strlen (nextchar); + + optopt = pfound->val; + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + if (print_errors) + fprintf (stderr, + _("%s: option `%s' requires an argument\n"), + argv[0], argv[optind - 1]); + nextchar += strlen (nextchar); + optopt = pfound->val; + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + + if (!long_only || argv[optind][1] == '-' + || my_index (optstring, *nextchar) == NULL) + { + if (print_errors) + { + if (argv[optind][1] == '-') + fprintf (stderr, _("%s: unrecognized option `--%s'\n"), + argv[0], nextchar); + else + /* +option or -option */ + fprintf (stderr, _("%s: unrecognized option `%c%s'\n"), + argv[0], argv[optind][0], nextchar); + } + nextchar = (char *) ""; + optind++; + optopt = 0; + return '?'; + } + } + { + char c = *nextchar++; + char *temp = my_index (optstring, c); + + if (*nextchar == '\0') + ++optind; + + if (temp == NULL || c == ':') + { + if (print_errors) + { + if (posixly_correct) + fprintf (stderr, _("%s: illegal option -- %c\n"), + argv[0], c); + else + fprintf (stderr, _("%s: invalid option -- %c\n"), + argv[0], c); + } + optopt = c; + return '?'; + } + if (temp[0] == 'W' && temp[1] == ';') + { + char *nameend; + const struct option *p; + const struct option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = 0; + int option_index; + + if (*nextchar != '\0') + { + optarg = nextchar; + optind++; + } + else if (optind == argc) + { + if (print_errors) + { + fprintf (stderr, _("%s: option requires an argument -- %c\n"), + argv[0], c); + } + optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + return c; + } + else + optarg = argv[optind++]; + + for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, nextchar, nameend - nextchar)) + { + if ((unsigned int) (nameend - nextchar) == strlen (p->name)) + { + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + pfound = p; + indfound = option_index; + } + else + ambig = 1; + } + if (ambig && !exact) + { + if (print_errors) + fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"), + argv[0], argv[optind]); + nextchar += strlen (nextchar); + optind++; + return '?'; + } + if (pfound != NULL) + { + option_index = indfound; + if (*nameend) + { + if (pfound->has_arg) + optarg = nameend + 1; + else + { + if (print_errors) + fprintf (stderr, _("\ +%s: option `-W %s' doesn't allow an argument\n"), + argv[0], pfound->name); + + nextchar += strlen (nextchar); + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + if (print_errors) + fprintf (stderr, + _("%s: option `%s' requires an argument\n"), + argv[0], argv[optind - 1]); + nextchar += strlen (nextchar); + return optstring[0] == ':' ? ':' : '?'; + } + } + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + nextchar = NULL; + return 'W'; /* Let the application handle it. */ + } + if (temp[1] == ':') + { + if (temp[2] == ':') + { + if (*nextchar != '\0') + { + optarg = nextchar; + optind++; + } + else + optarg = NULL; + nextchar = NULL; + } + else + { + if (*nextchar != '\0') + { + optarg = nextchar; + optind++; + } + else if (optind == argc) + { + if (print_errors) + { + fprintf (stderr, + _("%s: option requires an argument -- %c\n"), + argv[0], c); + } + optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + } + else + optarg = argv[optind++]; + nextchar = NULL; + } + } + return c; + } +} + +int +getopt (argc, argv, optstring) + int argc; + char *const *argv; + const char *optstring; +{ + return _getopt_internal (argc, argv, optstring, + (const struct option *) 0, + (int *) 0, + 0); +} + diff --git a/extracters/vhd-util/include/atomicio.h b/extracters/vhd-util/include/atomicio.h new file mode 100644 index 0000000..7eccf20 --- /dev/null +++ b/extracters/vhd-util/include/atomicio.h @@ -0,0 +1,33 @@ +/* $OpenBSD: atomicio.h,v 1.6 2005/05/24 17:32:43 avsm Exp $ */ + +/* + * Copyright (c) 1995,1999 Theo de Raadt. All rights reserved. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + */ + +/* + * Ensure all of data on socket comes through. f==read || f==vwrite + */ +size_t atomicio(ssize_t (*)(int, void *, size_t), int, void *, size_t); + +#define vwrite (ssize_t (*)(int, void *, size_t))write diff --git a/extracters/vhd-util/include/libvhd-journal.h b/extracters/vhd-util/include/libvhd-journal.h new file mode 100644 index 0000000..7585252 --- /dev/null +++ b/extracters/vhd-util/include/libvhd-journal.h @@ -0,0 +1,68 @@ +/* Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef _VHD_JOURNAL_H_ +#define _VHD_JOURNAL_H_ + +#include + +#include "libvhd.h" + +#define VHD_JOURNAL_METADATA 0x01 +#define VHD_JOURNAL_DATA 0x02 + +#define VHD_JOURNAL_HEADER_COOKIE "vjournal" +#define VHD_JOURNAL_ENTRY_COOKIE 0xaaaa12344321aaaa + +typedef struct vhd_journal_header { + char cookie[8]; + blk_uuid_t uuid; + uint64_t vhd_footer_offset; + uint32_t journal_data_entries; + uint32_t journal_metadata_entries; + uint64_t journal_data_offset; + uint64_t journal_metadata_offset; + uint64_t journal_eof; + char pad[448]; +} vhd_journal_header_t; + +typedef struct vhd_journal { + char *jname; + int jfd; + int is_block; /* is jfd a block device */ + vhd_journal_header_t header; + vhd_context_t vhd; +} vhd_journal_t; + +int vhd_journal_create(vhd_journal_t *, const char *file, const char *jfile); +int vhd_journal_open(vhd_journal_t *, const char *file, const char *jfile); +int vhd_journal_add_block(vhd_journal_t *, uint32_t block, char mode); +int vhd_journal_commit(vhd_journal_t *); +int vhd_journal_revert(vhd_journal_t *); +int vhd_journal_close(vhd_journal_t *); +int vhd_journal_remove(vhd_journal_t *); + +#endif diff --git a/extracters/vhd-util/include/list.h b/extracters/vhd-util/include/list.h new file mode 100644 index 0000000..2a5f855 --- /dev/null +++ b/extracters/vhd-util/include/list.h @@ -0,0 +1,124 @@ +/* + * list.h + * + * This is a subset of linux's list.h intended to be used in user-space. + * XXX The namespace conflicts with NetBSD's + * + */ + +#ifndef __LIST_H__ +#define __LIST_H__ + +#define LIST_POISON1 ((void *) 0x00100100) +#define LIST_POISON2 ((void *) 0x00200200) + +struct list_head { + struct list_head *next, *prev; +}; + +/* XXX workaround for conflicts. The list API should use its own + * namespace prefix, i.e. BLK_ + */ +#ifdef LIST_HEAD_INIT +#undef LIST_HEAD_INIT +#endif +#ifndef LIST_HEAD +#undef LIST_HEAD +#endif + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +static inline void INIT_LIST_HEAD(struct list_head *list) +{ + list->next = list; + list->prev = list; +} + +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +static inline void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +static inline void __list_del(struct list_head * prev, struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = LIST_POISON1; + entry->prev = LIST_POISON2; +} + +static inline void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +static inline int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +static inline int list_is_last(const struct list_head *list, + const struct list_head *head) +{ + return list->next == head; +} + +static inline void __list_splice(struct list_head *list, + struct list_head *head) +{ + struct list_head *first = list->next; + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; +} + +static inline void list_splice(struct list_head *list, struct list_head *head) +{ + if (!list_empty(list)) + __list_splice(list, head); +} + +#define list_entry(ptr, type, member) \ + ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) + +#define list_for_each_entry(pos, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, typeof(*pos), member)) + +#define list_for_each_entry_safe(pos, n, head, member) \ + for (pos = list_entry((head)->next, typeof(*pos), member), \ + n = list_entry(pos->member.next, typeof(*pos), member); \ + &pos->member != (head); \ + pos = n, n = list_entry(n->member.next, typeof(*n), member)) + +#endif /* __LIST_H__ */ diff --git a/extracters/vhd-util/include/lvm-util.h b/extracters/vhd-util/include/lvm-util.h new file mode 100644 index 0000000..95f3320 --- /dev/null +++ b/extracters/vhd-util/include/lvm-util.h @@ -0,0 +1,71 @@ +/* + * 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 _LVM_UTIL_H_ +#define _LVM_UTIL_H_ + +#include + +#define MAX_NAME_SIZE 256 + +#define LVM_SEG_TYPE_LINEAR 1 +#define LVM_SEG_TYPE_UNKNOWN 2 + +struct lv_segment { + uint8_t type; + char device[MAX_NAME_SIZE]; + uint64_t pe_start; + uint64_t pe_size; +}; + +struct lv { + char name[MAX_NAME_SIZE]; + uint64_t size; + uint32_t segments; + struct lv_segment first_segment; +}; + +struct pv { + char name[MAX_NAME_SIZE]; + uint64_t start; +}; + +struct vg { + char name[MAX_NAME_SIZE]; + uint64_t extent_size; + + int pv_cnt; + struct pv *pvs; + + int lv_cnt; + struct lv *lvs; +}; + +int lvm_scan_vg(const char *vg_name, struct vg *vg); +void lvm_free_vg(struct vg *vg); + +#endif diff --git a/extracters/vhd-util/include/vhd-util-win32.h b/extracters/vhd-util/include/vhd-util-win32.h new file mode 100644 index 0000000..6c6b574 --- /dev/null +++ b/extracters/vhd-util/include/vhd-util-win32.h @@ -0,0 +1,73 @@ +/* + * Compatibility file for Windows. + * Compiler-forced #include at line 1 of every source. + */ + +#include +#include +#include +#include +#include +#include +#if defined(_MSC_VER) +#include +typedef SSIZE_T ssize_t; +#endif +#include +#include "relative-path.h" +#define O_DIRECT 0 + +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 + +#define open _open +#define close _close + +static inline int ftruncate(int fd, __int64 length) +{ + HANDLE fh = (HANDLE)_get_osfhandle(fd); + + if (!fh || _lseeki64(fd, length, SEEK_SET)) + return -1; + return SetEndOfFile(fh) ? 0 : -1; +} + +#define fdatasync fsync + +static inline int +fsync (int fd) +{ + HANDLE h = (HANDLE) _get_osfhandle (fd); + DWORD err; + + if (h == INVALID_HANDLE_VALUE) + { + errno = EBADF; + return -1; + } + + if (!FlushFileBuffers (h)) + { + err = GetLastError (); + switch (err) + { + case ERROR_ACCESS_DENIED: + return 0; + + case ERROR_INVALID_HANDLE: + errno = EINVAL; + break; + + default: + errno = EIO; + } + return -1; + } + + return 0; +} + +/* Emulated in libvhd */ + +char *basename( char *path ); diff --git a/extracters/vhd-util/include/vhd-util.h b/extracters/vhd-util/include/vhd-util.h new file mode 100644 index 0000000..11f077e --- /dev/null +++ b/extracters/vhd-util/include/vhd-util.h @@ -0,0 +1,44 @@ +/* Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef _VHD_UTIL_H_ +#define _VHD_UTIL_H_ + +int vhd_util_create(int argc, char **argv); +int vhd_util_snapshot(int argc, char **argv); +int vhd_util_query(int argc, char **argv); +int vhd_util_read(int argc, char **argv); +int vhd_util_set_field(int argc, char **argv); +int vhd_util_repair(int argc, char **argv); +int vhd_util_fill(int argc, char **argv); +int vhd_util_resize(int argc, char **argv); +int vhd_util_coalesce(int argc, char **argv); +int vhd_util_modify(int argc, char **argv); +int vhd_util_scan(int argc, char **argv); +int vhd_util_check(int argc, char **argv); +int vhd_util_revert(int argc, char **argv); + +#endif diff --git a/extracters/vhd-util/include/wingetopt.h b/extracters/vhd-util/include/wingetopt.h new file mode 100644 index 0000000..8c32d0b --- /dev/null +++ b/extracters/vhd-util/include/wingetopt.h @@ -0,0 +1,88 @@ +/* Declarations for getopt. + Copyright (C) 1989,90,91,92,93,94,96,97,98 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ + +#ifndef _GETOPT_H + +#ifndef __need_getopt +# define _GETOPT_H 1 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +extern char *optarg; + +extern int optind; + +extern int opterr; + +extern int optopt; + +#ifndef __need_getopt + +struct option +{ +# if defined __STDC__ && __STDC__ + const char *name; +# else + char *name; +# endif + int has_arg; + int *flag; + int val; +}; + +# define no_argument 0 +# define required_argument 1 +# define optional_argument 2 +#endif /* need getopt */ + + +#if defined __STDC__ && __STDC__ +extern int getopt (); + +# ifndef __need_getopt +extern int getopt_long (int __argc, char *const *__argv, const char *__shortopts, + const struct option *__longopts, int *__longind); +extern int getopt_long_only (int __argc, char *const *__argv, + const char *__shortopts, + const struct option *__longopts, int *__longind); + +extern int _getopt_internal (int __argc, char *const *__argv, + const char *__shortopts, + const struct option *__longopts, int *__longind, + int __long_only); +# endif +#else /* not __STDC__ */ +extern int getopt (); +# ifndef __need_getopt +extern int getopt_long (); +extern int getopt_long_only (); + +extern int _getopt_internal (); +# endif +#endif /* __STDC__ */ + +#ifdef __cplusplus +} +#endif +#undef __need_getopt + +#endif /* getopt.h */ diff --git a/extracters/vhd-util/libvhd-journal.c b/extracters/vhd-util/libvhd-journal.c new file mode 100644 index 0000000..bc97b13 --- /dev/null +++ b/extracters/vhd-util/libvhd-journal.c @@ -0,0 +1,1618 @@ +/* Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include +#include +#ifndef _WIN32 +#include +#define O_BINARY 0 +#endif +#include + +#include "atomicio.h" +#include "libvhd-journal.h" + +#define VHD_JOURNAL_ENTRY_TYPE_FOOTER_P 1 +#define VHD_JOURNAL_ENTRY_TYPE_FOOTER_C 2 +#define VHD_JOURNAL_ENTRY_TYPE_HEADER 3 +#define VHD_JOURNAL_ENTRY_TYPE_LOCATOR 4 +#define VHD_JOURNAL_ENTRY_TYPE_BAT 5 +#define VHD_JOURNAL_ENTRY_TYPE_BATMAP_H 6 +#define VHD_JOURNAL_ENTRY_TYPE_BATMAP_M 7 +#define VHD_JOURNAL_ENTRY_TYPE_DATA 8 + +typedef struct vhd_journal_entry { + uint64_t cookie; + uint32_t type; + uint32_t size; + uint64_t offset; + uint32_t checksum; +} vhd_journal_entry_t; + +static inline int +vhd_journal_seek(vhd_journal_t *j, off_t offset, int whence) +{ + off_t off; + + off = lseek(j->jfd, offset, whence); + if (off == (off_t)-1) + return -errno; + + return 0; +} + +static inline off_t +vhd_journal_position(vhd_journal_t *j) +{ + return lseek(j->jfd, 0, SEEK_CUR); +} + +static inline int +vhd_journal_read(vhd_journal_t *j, void *buf, size_t size) +{ + ssize_t ret; + + errno = 0; + + ret = atomicio(read, j->jfd, buf, size); + if (ret != size) + return (errno ? -errno : -EIO); + + return 0; +} + +static inline int +vhd_journal_write(vhd_journal_t *j, void *buf, size_t size) +{ + ssize_t ret; + + errno = 0; + + ret = atomicio(vwrite, j->jfd, buf, size); + if (ret != size) + return (errno ? -errno : -EIO); + + return 0; +} + +static inline int +vhd_journal_truncate(vhd_journal_t *j, off_t length) +{ + int err; + + err = ftruncate(j->jfd, length); + if (err == -1) + return -errno; + + return 0; +} + +static inline int +vhd_journal_sync(vhd_journal_t *j) +{ + int err; + + err = fdatasync(j->jfd); + if (err) + return -errno; + + return 0; +} + +static inline void +vhd_journal_header_in(vhd_journal_header_t *header) +{ + BE64_IN(&header->vhd_footer_offset); + BE32_IN(&header->journal_data_entries); + BE32_IN(&header->journal_metadata_entries); + BE64_IN(&header->journal_data_offset); + BE64_IN(&header->journal_metadata_offset); +} + +static inline void +vhd_journal_header_out(vhd_journal_header_t *header) +{ + BE64_OUT(&header->vhd_footer_offset); + BE32_OUT(&header->journal_data_entries); + BE32_OUT(&header->journal_metadata_entries); + BE64_OUT(&header->journal_data_offset); + BE64_OUT(&header->journal_metadata_offset); +} + +static int +vhd_journal_validate_header(vhd_journal_t *j, vhd_journal_header_t *header) +{ + int err; + off_t eof; + + if (memcmp(header->cookie, + VHD_JOURNAL_HEADER_COOKIE, sizeof(header->cookie))) + return -EINVAL; + + err = vhd_journal_seek(j, (off_t)j->header.journal_eof, SEEK_SET); + if (err) + return err; + + eof = vhd_journal_position(j); + if (eof == (off_t)-1) + return -errno; + + if (j->header.journal_data_offset > j->header.journal_eof) + return -EINVAL; + + if (j->header.journal_metadata_offset > j->header.journal_eof) + return -EINVAL; + + return 0; +} + +static int +vhd_journal_read_journal_header(vhd_journal_t *j, vhd_journal_header_t *header) +{ + int err; + size_t size; + + size = sizeof(vhd_journal_header_t); + err = vhd_journal_seek(j, 0, SEEK_SET); + if (err) + return err; + + err = vhd_journal_read(j, header, size); + if (err) + return err; + + vhd_journal_header_in(header); + + return vhd_journal_validate_header(j, header); +} + +static int +vhd_journal_write_header(vhd_journal_t *j, vhd_journal_header_t *header) +{ + int err; + size_t size; + vhd_journal_header_t h; + + memcpy(&h, header, sizeof(vhd_journal_header_t)); + + err = vhd_journal_validate_header(j, &h); + if (err) + return err; + + vhd_journal_header_out(&h); + size = sizeof(vhd_journal_header_t); + + err = vhd_journal_seek(j, 0, SEEK_SET); + if (err) + return err; + + err = vhd_journal_write(j, &h, size); + if (err) + return err; + + return 0; +} + +static int +vhd_journal_add_journal_header(vhd_journal_t *j) +{ + int err; + off_t off; + vhd_context_t *vhd; + + vhd = &j->vhd; + memset(&j->header, 0, sizeof(vhd_journal_header_t)); + + err = vhd_seek(vhd, 0, SEEK_END); + if (err) + return err; + + off = vhd_position(vhd); + if (off == (off_t)-1) + return -errno; + + err = vhd_get_footer(vhd); + if (err) + return err; + + blk_uuid_copy(&j->header.uuid, &vhd->footer.uuid); + memcpy(j->header.cookie, + VHD_JOURNAL_HEADER_COOKIE, sizeof(j->header.cookie)); + j->header.vhd_footer_offset = off - sizeof(vhd_footer_t); + j->header.journal_eof = sizeof(vhd_journal_header_t); + + return vhd_journal_write_header(j, &j->header); +} + +static void +vhd_journal_entry_in(vhd_journal_entry_t *entry) +{ + BE32_IN(&entry->type); + BE32_IN(&entry->size); + BE64_IN(&entry->offset); + BE64_IN(&entry->cookie); + BE32_IN(&entry->checksum); +} + +static void +vhd_journal_entry_out(vhd_journal_entry_t *entry) +{ + BE32_OUT(&entry->type); + BE32_OUT(&entry->size); + BE64_OUT(&entry->offset); + BE64_OUT(&entry->cookie); + BE32_OUT(&entry->checksum); +} + +static uint32_t +vhd_journal_checksum_entry(vhd_journal_entry_t *entry, char *buf, size_t size) +{ + size_t i; + unsigned char *blob; + uint32_t checksum, tmp; + + checksum = 0; + tmp = entry->checksum; + entry->checksum = 0; + + blob = (unsigned char *)entry; + for (i = 0; i < sizeof(vhd_journal_entry_t); i++) + checksum += blob[i]; + + blob = (unsigned char *)buf; + for (i = 0; i < size; i++) + checksum += blob[i]; + + entry->checksum = tmp; + return ~checksum; +} + +static int +vhd_journal_validate_entry(vhd_journal_entry_t *entry) +{ + if (entry->size == 0) + return -EINVAL; + + if (entry->size & (VHD_SECTOR_SIZE - 1)) + return -EINVAL; + + if (entry->cookie != VHD_JOURNAL_ENTRY_COOKIE) + return -EINVAL; + + return 0; +} + +static int +vhd_journal_read_entry(vhd_journal_t *j, vhd_journal_entry_t *entry) +{ + int err; + + err = vhd_journal_read(j, entry, sizeof(vhd_journal_entry_t)); + if (err) + return err; + + vhd_journal_entry_in(entry); + return vhd_journal_validate_entry(entry); +} + +static int +vhd_journal_write_entry(vhd_journal_t *j, vhd_journal_entry_t *entry) +{ + int err; + vhd_journal_entry_t e; + + err = vhd_journal_validate_entry(entry); + if (err) + return err; + + memcpy(&e, entry, sizeof(vhd_journal_entry_t)); + vhd_journal_entry_out(&e); + + err = vhd_journal_write(j, &e, sizeof(vhd_journal_entry_t)); + if (err) + err; + + return 0; +} + +static int +vhd_journal_validate_entry_data(vhd_journal_entry_t *entry, char *buf) +{ + int err; + uint32_t checksum; + + err = 0; + checksum = vhd_journal_checksum_entry(entry, buf, entry->size); + + if (checksum != entry->checksum) + return -EINVAL; + + return err; +} + +static int +vhd_journal_update(vhd_journal_t *j, off_t offset, + char *buf, size_t size, uint32_t type) +{ + int err; + uint64_t *off, off_bak; + uint32_t *entries; + vhd_journal_entry_t entry; + + entry.type = type; + entry.size = size; + entry.offset = offset; + entry.cookie = VHD_JOURNAL_ENTRY_COOKIE; + entry.checksum = vhd_journal_checksum_entry(&entry, buf, size); + + err = vhd_journal_seek(j, (off_t)j->header.journal_eof, SEEK_SET); + if (err) + return err; + + err = vhd_journal_write_entry(j, &entry); + if (err) + goto fail; + + err = vhd_journal_write(j, buf, size); + if (err) + goto fail; + + if (type == VHD_JOURNAL_ENTRY_TYPE_DATA) { + off = &j->header.journal_data_offset; + entries = &j->header.journal_data_entries; + } else { + off = &j->header.journal_metadata_offset; + entries = &j->header.journal_metadata_entries; + } + + off_bak = *off; + if (!(*entries)++) + *off = j->header.journal_eof; + j->header.journal_eof += (size + sizeof(vhd_journal_entry_t)); + + err = vhd_journal_write_header(j, &j->header); + if (err) { + if (!--(*entries)) + *off = off_bak; + j->header.journal_eof -= (size + sizeof(vhd_journal_entry_t)); + goto fail; + } + + return 0; + +fail: + if (!j->is_block) + vhd_journal_truncate(j, (off_t)j->header.journal_eof); + return err; +} + +static int +vhd_journal_add_footer(vhd_journal_t *j) +{ + int err; + off_t off; + vhd_context_t *vhd; + vhd_footer_t footer; + + vhd = &j->vhd; + + err = vhd_seek(vhd, 0, SEEK_END); + if (err) + return err; + + off = vhd_position(vhd); + if (off == (off_t)-1) + return -errno; + + err = vhd_read_footer_at(vhd, &footer, off - sizeof(vhd_footer_t)); + if (err) + return err; + + vhd_footer_out(&footer); + err = vhd_journal_update(j, off - sizeof(vhd_footer_t), + (char *)&footer, + sizeof(vhd_footer_t), + VHD_JOURNAL_ENTRY_TYPE_FOOTER_P); + if (err) + return err; + + if (!vhd_type_dynamic(vhd)) + return 0; + + err = vhd_read_footer_at(vhd, &footer, 0); + if (err) + return err; + + vhd_footer_out(&footer); + err = vhd_journal_update(j, 0, + (char *)&footer, + sizeof(vhd_footer_t), + VHD_JOURNAL_ENTRY_TYPE_FOOTER_C); + + return err; +} + +static int +vhd_journal_add_header(vhd_journal_t *j) +{ + int err; + off_t off; + vhd_context_t *vhd; + vhd_header_t header; + + vhd = &j->vhd; + + err = vhd_read_header(vhd, &header); + if (err) + return err; + + off = (off_t)vhd->footer.data_offset; + + vhd_header_out(&header); + err = vhd_journal_update(j, off, + (char *)&header, + sizeof(vhd_header_t), + VHD_JOURNAL_ENTRY_TYPE_HEADER); + + return err; +} + +static int +vhd_journal_add_locators(vhd_journal_t *j) +{ + int i, n, err; + vhd_context_t *vhd; + + vhd = &j->vhd; + + err = vhd_get_header(vhd); + if (err) + return err; + + n = sizeof(vhd->header.loc) / sizeof(vhd_parent_locator_t); + for (i = 0; i < n; i++) { + char *buf; + off_t off; + size_t size; + vhd_parent_locator_t *loc; + + loc = vhd->header.loc + i; + err = vhd_validate_platform_code(loc->code); + if (err) + return err; + + if (loc->code == PLAT_CODE_NONE) + continue; + + off = (off_t)loc->data_offset; + size = vhd_parent_locator_size(loc); + +#ifdef _WIN32 + buf = _aligned_malloc( size, VHD_SECTOR_SIZE ); + if( buf == NULL ) + return -errno; +#else + err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); + if (err) + return -err; +#endif + + err = vhd_seek(vhd, off, SEEK_SET); + if (err) + goto end; + + err = vhd_read(vhd, buf, size); + if (err) + goto end; + + err = vhd_journal_update(j, off, buf, size, + VHD_JOURNAL_ENTRY_TYPE_LOCATOR); + if (err) + goto end; + + err = 0; + + end: +#ifdef _WIN32 + _aligned_free( buf ); +#else + free( buf ); +#endif + if (err) + break; + } + + return err; +} + +static int +vhd_journal_add_bat(vhd_journal_t *j) +{ + int err; + off_t off; + size_t size; + vhd_bat_t bat; + vhd_context_t *vhd; + + vhd = &j->vhd; + + err = vhd_get_header(vhd); + if (err) + return err; + + err = vhd_read_bat(vhd, &bat); + if (err) + return err; + + off = (off_t)vhd->header.table_offset; + size = (size_t)vhd_bytes_padded(bat.entries * sizeof(uint32_t)); + + vhd_bat_out(&bat); + err = vhd_journal_update(j, off, (char *)bat.bat, size, + VHD_JOURNAL_ENTRY_TYPE_BAT); + +#ifdef _WIN32 + _aligned_free( bat.bat ); +#else + free( bat.bat ); +#endif + return err; +} + +static int +vhd_journal_add_batmap(vhd_journal_t *j) +{ + int err; + off_t off; + size_t size; + vhd_context_t *vhd; + vhd_batmap_t batmap; + + vhd = &j->vhd; + + err = vhd_batmap_header_offset(vhd, &off); + if (err) + return err; + + err = vhd_read_batmap(vhd, &batmap); + if (err) + return err; + + size = (size_t)vhd_bytes_padded(sizeof(struct dd_batmap_hdr)); + + vhd_batmap_header_out(&batmap); + err = vhd_journal_update(j, off, (char *)&batmap.header, size, + VHD_JOURNAL_ENTRY_TYPE_BATMAP_H); + if (err) + goto out; + + vhd_batmap_header_in(&batmap); + off = (off_t)batmap.header.batmap_offset; + size = (size_t)vhd_sectors_to_bytes(batmap.header.batmap_size); + + err = vhd_journal_update(j, off, batmap.map, size, + VHD_JOURNAL_ENTRY_TYPE_BATMAP_M); + +out: +#ifdef _WIN32 + _aligned_free( batmap.map ); +#else + free( batmap.map ); +#endif + return err; +} + +static int +vhd_journal_add_metadata(vhd_journal_t *j) +{ + int err; + vhd_context_t *vhd; + + vhd = &j->vhd; + + err = vhd_journal_add_footer(j); + if (err) + return err; + + if (!vhd_type_dynamic(vhd)) + return 0; + + err = vhd_journal_add_header(j); + if (err) + return err; + + err = vhd_journal_add_locators(j); + if (err) + return err; + + err = vhd_journal_add_bat(j); + if (err) + return err; + + if (vhd_has_batmap(vhd)) { + err = vhd_journal_add_batmap(j); + if (err) + return err; + } + + j->header.journal_data_offset = j->header.journal_eof; + return vhd_journal_write_header(j, &j->header); +} + +static int +__vhd_journal_read_footer(vhd_journal_t *j, + vhd_footer_t *footer, uint32_t type) +{ + int err; + vhd_journal_entry_t entry; + + err = vhd_journal_read_entry(j, &entry); + if (err) + return err; + + if (entry.type != type) + return -EINVAL; + + if (entry.size != sizeof(vhd_footer_t)) + return -EINVAL; + + err = vhd_journal_read(j, footer, entry.size); + if (err) + return err; + + vhd_footer_in(footer); + return vhd_validate_footer(footer); +} + +static int +vhd_journal_read_footer(vhd_journal_t *j, vhd_footer_t *footer) +{ + return __vhd_journal_read_footer(j, footer, + VHD_JOURNAL_ENTRY_TYPE_FOOTER_P); +} + +static int +vhd_journal_read_footer_copy(vhd_journal_t *j, vhd_footer_t *footer) +{ + return __vhd_journal_read_footer(j, footer, + VHD_JOURNAL_ENTRY_TYPE_FOOTER_C); +} + +static int +vhd_journal_read_header(vhd_journal_t *j, vhd_header_t *header) +{ + int err; + vhd_journal_entry_t entry; + + err = vhd_journal_read_entry(j, &entry); + if (err) + return err; + + if (entry.type != VHD_JOURNAL_ENTRY_TYPE_HEADER) + return -EINVAL; + + if (entry.size != sizeof(vhd_header_t)) + return -EINVAL; + + err = vhd_journal_read(j, header, entry.size); + if (err) + return err; + + vhd_header_in(header); + return vhd_validate_header(header); +} + +static int +vhd_journal_read_locators(vhd_journal_t *j, char ***locators, int *locs) +{ + int err, n, _locs; + char **_locators, *buf; + off_t pos; + vhd_journal_entry_t entry; + + _locs = 0; + *locs = 0; + *locators = NULL; + + n = sizeof(j->vhd.header.loc) / sizeof(vhd_parent_locator_t); + _locators = calloc(n, sizeof(char *)); + if (!_locators) + return -ENOMEM; + + for( ;;) { + buf = NULL; + + pos = vhd_journal_position( j ); + err = vhd_journal_read_entry( j, &entry ); + if( err ) + goto fail; + + if( entry.type != VHD_JOURNAL_ENTRY_TYPE_LOCATOR ) { + err = vhd_journal_seek( j, pos, SEEK_SET ); + if( err ) + goto fail; + break; + } + + if( _locs >= n ) { + err = -EINVAL; + goto fail; + } + +#ifdef _WIN32 + buf = _aligned_malloc( entry.size, VHD_SECTOR_SIZE ); + if( buf == NULL ) { + err = -errno; + goto fail; + } +#else + err = posix_memalign((void **)&buf, + VHD_SECTOR_SIZE, entry.size); + if (err) { + err = -err; + buf = NULL; + goto fail; + } +#endif + err = vhd_journal_read(j, buf, entry.size); + if (err) + goto fail; + + _locators[_locs++] = buf; + err = 0; + } + + + *locs = _locs; + *locators = _locators; + + return 0; + +fail: + if (_locators) { + for (n = 0; n < _locs; n++) +#ifdef _WIN32 + _aligned_free( _locators[n] ); +#else + free( _locators[n] ); +#endif + free(_locators); + } + return err; +} + +static int +vhd_journal_read_bat(vhd_journal_t *j, vhd_bat_t *bat) +{ + int err; + size_t size; + vhd_context_t *vhd; + vhd_journal_entry_t entry; + + vhd = &j->vhd; + + size = (size_t)vhd_bytes_padded(vhd->header.max_bat_size * sizeof(uint32_t)); + + err = vhd_journal_read_entry(j, &entry); + if (err) + return err; + + if (entry.type != VHD_JOURNAL_ENTRY_TYPE_BAT) + return -EINVAL; + + if (entry.size != size) + return -EINVAL; + + if (entry.offset != vhd->header.table_offset) + return -EINVAL; + +#ifdef _WIN32 + bat->bat = _aligned_malloc( size, VHD_SECTOR_SIZE ); + if( bat->bat == NULL ) { + return -errno; + } +#else + err = posix_memalign((void **)&bat->bat, VHD_SECTOR_SIZE, size); + if (err) + return -err; +#endif + + err = vhd_journal_read(j, bat->bat, entry.size); + if (err) + goto fail; + + bat->spb = vhd->header.block_size >> VHD_SECTOR_SHIFT; + bat->entries = vhd->header.max_bat_size; + vhd_bat_in(bat); + + return 0; + +fail: +#ifdef _WIN32 + _aligned_free( bat->bat ); +#else + free( bat->bat ); +#endif + bat->bat = NULL; + return err; +} + +static int +vhd_journal_read_batmap_header(vhd_journal_t *j, vhd_batmap_t *batmap) +{ + int err; + char *buf; + size_t size; + vhd_journal_entry_t entry; + + size = (size_t)vhd_bytes_padded(sizeof(struct dd_batmap_hdr)); + + err = vhd_journal_read_entry(j, &entry); + if (err) + return err; + + if (entry.type != VHD_JOURNAL_ENTRY_TYPE_BATMAP_H) + return -EINVAL; + + if (entry.size != size) + return -EINVAL; + +#ifdef _WIN32 + buf = _aligned_malloc( size, VHD_SECTOR_SIZE ); + if( buf == NULL ) + return errno; +#else + err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); + if (err) + return err; +#endif + + err = vhd_journal_read(j, buf, entry.size); + if (err) { +#ifdef _WIN32 + _aligned_free( buf ); +#else + free( buf ); +#endif + return err; + } + + memcpy(&batmap->header, buf, sizeof(batmap->header)); + + vhd_batmap_header_in(batmap); + return vhd_validate_batmap_header(batmap); +} + +static int +vhd_journal_read_batmap_map(vhd_journal_t *j, vhd_batmap_t *batmap) +{ + int err; + vhd_journal_entry_t entry; + + err = vhd_journal_read_entry(j, &entry); + if (err) + return err; + + if (entry.type != VHD_JOURNAL_ENTRY_TYPE_BATMAP_M) + return -EINVAL; + + if (entry.size != vhd_sectors_to_bytes(batmap->header.batmap_size)) + return -EINVAL; + + if (entry.offset != batmap->header.batmap_offset) + return -EINVAL; + +#ifdef _WIN32 + batmap->map = _aligned_malloc( entry.size, VHD_SECTOR_SIZE ); + if( batmap->map == NULL ) + return -errno; +#else + err = posix_memalign((void **)&batmap->map, + VHD_SECTOR_SIZE, entry.size); + if (err) + return -err; +#endif + + err = vhd_journal_read(j, batmap->map, entry.size); + if (err) { +#ifdef _WIN32 + _aligned_free( batmap->map ); +#else + free( batmap->map ); + batmap->map = NULL; +#endif + return err; + } + + return 0; +} + +static int +vhd_journal_read_batmap(vhd_journal_t *j, vhd_batmap_t *batmap) +{ + int err; + + err = vhd_journal_read_batmap_header(j, batmap); + if (err) + return err; + + err = vhd_journal_read_batmap_map(j, batmap); + if (err) + return err; + + err = vhd_validate_batmap(batmap); + if (err) { +#ifdef _WIN32 + _aligned_free( batmap->map ); +#else + free( batmap->map ); +#endif + batmap->map = NULL; + return err; + } + + return 0; +} + +static int +vhd_journal_restore_footer(vhd_journal_t *j, vhd_footer_t *footer) +{ + return vhd_write_footer_at(&j->vhd, footer, + (off_t)j->header.vhd_footer_offset); +} + +static int +vhd_journal_restore_footer_copy(vhd_journal_t *j, vhd_footer_t *footer) +{ + return vhd_write_footer_at(&j->vhd, footer, 0); +} + +static int +vhd_journal_restore_header(vhd_journal_t *j, vhd_header_t *header) +{ + off_t off; + vhd_context_t *vhd; + + vhd = &j->vhd; + off = (off_t)vhd->footer.data_offset; + + return vhd_write_header_at(&j->vhd, header, off); +} + +static int +vhd_journal_restore_locators(vhd_journal_t *j, char **locators, int locs) +{ + size_t size; + vhd_context_t *vhd; + int i, n, lidx, err; + vhd_parent_locator_t *loc; + + lidx = 0; + vhd = &j->vhd; + + n = sizeof(vhd->header.loc) / sizeof(vhd_parent_locator_t); + + for (i = 0; i < n && lidx < locs; i++) { + loc = vhd->header.loc + i; + if (loc->code == PLAT_CODE_NONE) + continue; + + err = vhd_seek(vhd, (off_t)loc->data_offset, SEEK_SET); + if (err) + return err; + + size = vhd_parent_locator_size(loc); + err = vhd_write(vhd, locators[lidx++], size); + if (err) + return err; + } + + return 0; +} + +static int +vhd_journal_restore_bat(vhd_journal_t *j, vhd_bat_t *bat) +{ + return vhd_write_bat(&j->vhd, bat); +} + +static int +vhd_journal_restore_batmap(vhd_journal_t *j, vhd_batmap_t *batmap) +{ + return vhd_write_batmap(&j->vhd, batmap); +} + +static int +vhd_journal_restore_metadata(vhd_journal_t *j) +{ + off_t off; + char **locators; + vhd_footer_t copy; + vhd_context_t *vhd; + int i, locs, hlocs, err; + + vhd = &j->vhd; + locs = 0; + hlocs = 0; + locators = NULL; + + err = vhd_journal_seek(j, sizeof(vhd_journal_header_t), SEEK_SET); + if (err) + return err; + + err = vhd_journal_read_footer(j, &vhd->footer); + if (err) + return err; + + if (!vhd_type_dynamic(vhd)) + goto restore; + + err = vhd_journal_read_footer_copy(j, ©); + if (err) + return err; + + err = vhd_journal_read_header(j, &vhd->header); + if (err) + return err; + + for (hlocs = 0, i = 0; i < vhd_parent_locator_count(vhd); i++) { + if (vhd_validate_platform_code(vhd->header.loc[i].code)) + return err; + + if (vhd->header.loc[i].code != PLAT_CODE_NONE) + hlocs++; + } + + if (hlocs) { + err = vhd_journal_read_locators(j, &locators, &locs); + if (err) + return err; + + if (hlocs != locs) { + err = -EINVAL; + goto out; + } + } + + err = vhd_journal_read_bat(j, &vhd->bat); + if (err) + goto out; + + if (vhd_has_batmap(vhd)) { + err = vhd_journal_read_batmap(j, &vhd->batmap); + if (err) + goto out; + } + +restore: + off = vhd_journal_position(j); + if (off == (off_t)-1) + return -errno; + + if (j->header.journal_data_offset != off) + return -EINVAL; + + err = vhd_journal_restore_footer(j, &vhd->footer); + if (err) + goto out; + + if (!vhd_type_dynamic(vhd)) + goto out; + + err = vhd_journal_restore_footer_copy(j, ©); + if (err) + goto out; + + err = vhd_journal_restore_header(j, &vhd->header); + if (err) + goto out; + + if (locs) { + err = vhd_journal_restore_locators(j, locators, locs); + if (err) + goto out; + } + + err = vhd_journal_restore_bat(j, &vhd->bat); + if (err) + goto out; + + if (vhd_has_batmap(vhd)) { + err = vhd_journal_restore_batmap(j, &vhd->batmap); + if (err) + goto out; + } + + err = 0; + +out: + if (locators) { + for (i = 0; i < locs; i++) +#ifdef _WIN32 + _aligned_free( locators[i] ); +#else + free( locators[i] ); +#endif + free(locators); + } + + if (!err && !vhd->is_block) + err = ftruncate(vhd->fd, + j->header.vhd_footer_offset + + sizeof(vhd_footer_t)); + + return err; +} + +static int +vhd_journal_disable_vhd(vhd_journal_t *j) +{ + int err; + vhd_context_t *vhd; + + vhd = &j->vhd; + + err = vhd_get_footer(vhd); + if (err) + return err; + + memcpy(&vhd->footer.cookie, + VHD_POISON_COOKIE, sizeof(vhd->footer.cookie)); + vhd->footer.checksum = vhd_checksum_footer(&vhd->footer); + + err = vhd_write_footer(vhd, &vhd->footer); + if (err) + return err; + + return 0; +} + +static int +vhd_journal_enable_vhd(vhd_journal_t *j) +{ + int err; + vhd_context_t *vhd; + + vhd = &j->vhd; + + err = vhd_get_footer(vhd); + if (err) + return err; + + if (!vhd_disabled(vhd)) + return 0; + + memcpy(&vhd->footer.cookie, HD_COOKIE, sizeof(vhd->footer.cookie)); + vhd->footer.checksum = vhd_checksum_footer(&vhd->footer); + + err = vhd_write_footer(vhd, &vhd->footer); + if (err) + return err; + + return 0; +} + +int +vhd_journal_close(vhd_journal_t *j) +{ + if (j->jfd) + close(j->jfd); + + vhd_close(&j->vhd); + free(j->jname); + + return 0; +} + +int +vhd_journal_remove(vhd_journal_t *j) +{ + int err; + + err = vhd_journal_enable_vhd(j); + if (err) + return err; + + if (j->jfd) { + close(j->jfd); + if (!j->is_block) + unlink(j->jname); + } + + vhd_close(&j->vhd); + free(j->jname); + + return 0; +} + +int +vhd_journal_open(vhd_journal_t *j, const char *file, const char *jfile) +{ + int err; + vhd_context_t *vhd; + + memset(j, 0, sizeof(vhd_journal_t)); + + j->jfd = -1; + vhd = &j->vhd; + + j->jname = strdup(jfile); + if (j->jname == NULL) + return -ENOMEM; + + j->jfd = open(j->jname, O_LARGEFILE | O_RDWR | O_BINARY); + if (j->jfd == -1) { + err = -errno; + goto fail; + } + + err = vhd_test_file_fixed(j->jname, &j->is_block); + if (err) + goto fail; + + vhd->fd = open(file, O_LARGEFILE | O_RDWR | O_DIRECT | O_BINARY ); + if (vhd->fd == -1) { + err = -errno; + goto fail; + } + + err = vhd_test_file_fixed(file, &vhd->is_block); + if (err) + goto fail; + + err = vhd_journal_read_journal_header(j, &j->header); + if (err) + goto fail; + + err = vhd_journal_restore_metadata(j); + if (err) + goto fail; + + close(vhd->fd); + free(vhd->bat.bat); + free(vhd->batmap.map); + + err = vhd_open(vhd, file, VHD_OPEN_RDWR); + if (err) + goto fail; + + err = vhd_get_bat(vhd); + if (err) + goto fail; + + if (vhd_has_batmap(vhd)) { + err = vhd_get_batmap(vhd); + if (err) + goto fail; + } + + err = vhd_journal_disable_vhd(j); + if (err) + goto fail; + + return 0; + +fail: + vhd_journal_close(j); + return err; +} + +int +vhd_journal_create(vhd_journal_t *j, const char *file, const char *jfile) +{ + int err; + + memset(j, 0, sizeof(vhd_journal_t)); + j->jfd = -1; + + j->jname = strdup(jfile); + if (j->jname == NULL) { + err = -ENOMEM; + goto fail1; + } + +#ifdef _WIN32 + j->is_block = 0; + j->jfd = open(j->jname, + O_EXCL | O_CREAT | O_TRUNC | O_LARGEFILE | O_RDWR | O_BINARY, 0644); +#else + if (access(j->jname, F_OK) == 0) { + err = vhd_test_file_fixed(j->jname, &j->is_block); + if (err) + goto fail1; + + if (!j->is_block) { + err = -EEXIST; + goto fail1; + } + } + if (j->is_block) + j->jfd = open(j->jname, O_LARGEFILE | O_RDWR | O_BINARY, 0644); + else + j->jfd = open(j->jname, + O_CREAT | O_TRUNC | O_LARGEFILE | O_RDWR | O_BINARY, 0644); +#endif + if (j->jfd == -1) { + err = -errno; + goto fail1; + } + + err = vhd_open(&j->vhd, file, VHD_OPEN_RDWR | VHD_OPEN_STRICT); + if (err) + goto fail1; + + err = vhd_get_bat(&j->vhd); + if (err) + goto fail2; + + if (vhd_has_batmap(&j->vhd)) { + err = vhd_get_batmap(&j->vhd); + if (err) + goto fail2; + } + + err = vhd_journal_add_journal_header(j); + if (err) + goto fail2; + + err = vhd_journal_add_metadata(j); + if (err) + goto fail2; + + err = vhd_journal_disable_vhd(j); + if (err) + goto fail2; + + err = vhd_journal_sync(j); + if (err) + goto fail2; + + return 0; + +fail1: + if (j->jfd != -1) { + close(j->jfd); + if (!j->is_block) + unlink(j->jname); + } + free(j->jname); + memset(j, 0, sizeof(vhd_journal_t)); + + return err; + +fail2: + vhd_journal_remove(j); + return err; +} + +int +vhd_journal_add_block(vhd_journal_t *j, uint32_t block, char mode) +{ + int err; + char *buf; + off_t off; + size_t size; + uint64_t blk; + vhd_context_t *vhd; + + buf = NULL; + vhd = &j->vhd; + + if (!vhd_type_dynamic(vhd)) + return -EINVAL; + + err = vhd_get_bat(vhd); + if (err) + return err; + + if (block >= vhd->bat.entries) + return -ERANGE; + + blk = vhd->bat.bat[block]; + if (blk == DD_BLK_UNUSED) + return 0; + + off = (off_t)vhd_sectors_to_bytes(blk); + + if (mode & VHD_JOURNAL_METADATA) { + size = (size_t)vhd_sectors_to_bytes(vhd->bm_secs); + + err = vhd_read_bitmap(vhd, block, &buf); + if (err) + return err; + + err = vhd_journal_update(j, off, buf, size, + VHD_JOURNAL_ENTRY_TYPE_DATA); + + free(buf); + + if (err) + return err; + } + + if (mode & VHD_JOURNAL_DATA) { + off += (off_t)vhd_sectors_to_bytes(vhd->bm_secs); + size = (size_t)vhd_sectors_to_bytes(vhd->spb); + + err = vhd_read_block(vhd, block, &buf); + if (err) + return err; + + err = vhd_journal_update(j, off, buf, size, + VHD_JOURNAL_ENTRY_TYPE_DATA); + free(buf); + + if (err) + return err; + } + + return vhd_journal_sync(j); +} + +/* + * commit indicates the transaction completed + * successfully and we can remove the undo log + */ +int +vhd_journal_commit(vhd_journal_t *j) +{ + int err; + + j->header.journal_data_entries = 0; + j->header.journal_metadata_entries = 0; + j->header.journal_data_offset = 0; + j->header.journal_metadata_offset = 0; + + err = vhd_journal_write_header(j, &j->header); + if (err) + return err; + + if (!j->is_block) + err = vhd_journal_truncate(j, sizeof(vhd_journal_header_t)); + if (err) + return -errno; + + return 0; +} + +/* + * revert indicates the transaction failed + * and we should revert any changes via the undo log + */ +int +vhd_journal_revert(vhd_journal_t *j) +{ + size_t i; + int err; + char *buf, *file; + vhd_context_t *vhd; + vhd_journal_entry_t entry; + + err = 0; + vhd = &j->vhd; + buf = NULL; + + file = strdup(vhd->file); + if (!file) + return -ENOMEM; + + vhd_close(&j->vhd); + j->vhd.fd = open(file, O_RDWR | O_DIRECT | O_LARGEFILE | O_BINARY ); + if (j->vhd.fd == -1) { + free(file); + return -errno; + } + + err = vhd_test_file_fixed(file, &vhd->is_block); + if (err) { + free(file); + return err; + } + + err = vhd_journal_restore_metadata(j); + if (err) { + free(file); + return err; + } + + close(vhd->fd); + free(vhd->bat.bat); + free(vhd->batmap.map); + + err = vhd_open(vhd, file, VHD_OPEN_RDWR); + free(file); + if (err) + return err; + + err = vhd_journal_seek(j, (off_t)j->header.journal_data_offset, SEEK_SET); + if (err) + return err; + + for( i = 0; i < j->header.journal_data_entries; i++ ) { + err = vhd_journal_read_entry( j, &entry ); + if( err ) + goto end; + +#ifdef _WIN32 + buf = _aligned_malloc( entry.size, VHD_SECTOR_SIZE ); + if( buf == NULL ) { + err = -errno; + goto end; + } +#else + err = posix_memalign( (void **)&buf, + VHD_SECTOR_SIZE, entry.size ); + if( err ) { + err = -err; + buf = NULL; + goto end; + } +#endif + + err = vhd_journal_read( j, buf, entry.size ); + if( err ) + goto end; + + err = vhd_journal_validate_entry_data( &entry, buf ); + if( err ) + goto end; + + err = vhd_seek( vhd, (off_t)entry.offset, SEEK_SET ); + if( err ) + goto end; + + err = vhd_write( vhd, buf, entry.size ); + if( err ) + goto end; + + err = 0; + + end: +#ifdef _WIN32 + _aligned_free( buf ); +#else + free( buf ); +#endif + buf = NULL; + + if( err ) + break; + } + + if( err ) + return err; + + if (!vhd->is_block) { + err = ftruncate(vhd->fd, j->header.vhd_footer_offset + + sizeof(vhd_footer_t)); + if (err) + return -errno; + } + + return vhd_journal_sync(j); +} diff --git a/extracters/vhd-util/lvm-util.c b/extracters/vhd-util/lvm-util.c new file mode 100644 index 0000000..3fc5877 --- /dev/null +++ b/extracters/vhd-util/lvm-util.c @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include +#include + +#include "lvm-util.h" + +#define _NAME "%255s" +static char line[1024]; + +static inline int +lvm_read_line(FILE *scan) +{ + memset(line, 0, sizeof(line)); + return (fscanf(scan, "%1023[^\n]", line) != 1); +} + +static inline int +lvm_next_line(FILE *scan) +{ + return (fscanf(scan, "%1023[\n]", line) != 1); +} + +static int +lvm_copy_name(char *dst, const char *src, size_t size) +{ + if (strnlen(src, size) == size) + return -ENAMETOOLONG; + + strcpy(dst, src); + return 0; +} + +static int +lvm_parse_pv(struct vg *vg, const char *name, int pvs, uint64_t start) +{ + int i, err; + struct pv *pv; + + pv = NULL; + + if (!vg->pvs) { + vg->pvs = calloc(pvs, sizeof(struct pv)); + if (!vg->pvs) + return -ENOMEM; + } + + for (i = 0; i < pvs; i++) { + pv = vg->pvs + i; + + if (!pv->name[0]) + break; + + if (!strcmp(pv->name, name)) + return -EEXIST; + } + + if (!pv) + return -ENOENT; + + if (i == pvs) + return -ENOMEM; + + err = lvm_copy_name(pv->name, name, sizeof(pv->name) - 1); + if (err) + return err; + + pv->start = start; + return 0; +} + +static int +lvm_open_vg(const char *vgname, struct vg *vg) +{ + FILE *scan; + int i, err, pvs, lvs; + char *cmd, pvname[256]; + uint64_t size, pv_start; + + memset(vg, 0, sizeof(*vg)); + + err = asprintf(&cmd, "/usr/sbin/vgs %s --noheadings --nosuffix --units=b " + "--options=vg_name,vg_extent_size,lv_count,pv_count," + "pv_name,pe_start --unbuffered 2> /dev/null", vgname); + if (err == -1) + return -ENOMEM; + + errno = 0; + scan = popen(cmd, "r"); + if (!scan) { + err = (errno ? -errno : ENOMEM); + goto out; + } + + for (;;) { + if (lvm_read_line(scan)) + break; + + err = -EINVAL; + if (sscanf(line, _NAME" %"SCNu64" %d %d "_NAME" %"SCNu64, + vg->name, &size, &lvs, &pvs, pvname, &pv_start) != 6) + goto out; + + if (strcmp(vg->name, vgname)) + goto out; + + err = lvm_parse_pv(vg, pvname, pvs, pv_start); + if (err) + goto out; + + if (lvm_next_line(scan)) + break; + } + + err = -EINVAL; + if (strcmp(vg->name, vgname)) + goto out; + + for (i = 0; i < pvs; i++) + if (!vg->pvs[i].name[0]) + goto out; + + err = -ENOMEM; + vg->lvs = calloc(lvs, sizeof(struct lv)); + if (!vg->lvs) + goto out; + + err = 0; + vg->lv_cnt = lvs; + vg->pv_cnt = pvs; + vg->extent_size = size; + +out: + if (scan) + pclose(scan); + if (err) + lvm_free_vg(vg); + free(cmd); + return err; +} + +static int +lvm_parse_lv_devices(struct vg *vg, struct lv_segment *seg, char *devices) +{ + size_t i; + uint64_t start, pe_start; + + for (i = 0; i < strlen(devices); i++) + if (strchr(",()", devices[i])) + devices[i] = ' '; + + if (sscanf(devices, _NAME" %"SCNu64, seg->device, &start) != 2) + return -EINVAL; + + pe_start = -1; + for (i = 0; i < (size_t)vg->pv_cnt; i++) + if (!strcmp(vg->pvs[i].name, seg->device)) { + pe_start = vg->pvs[i].start; + break; + } + + if (pe_start == -1) + return -EINVAL; + + seg->pe_start = (start * vg->extent_size) + pe_start; + return 0; +} + +static int +lvm_scan_lvs(struct vg *vg) +{ + char *cmd; + FILE *scan; + int i, err; + + err = asprintf(&cmd, "/usr/sbin/lvs %s --noheadings --nosuffix --units=b " + "--options=lv_name,lv_size,segtype,seg_count,seg_start," + "seg_size,devices --unbuffered 2> /dev/null", vg->name); + if (err == -1) + return -ENOMEM; + + errno = 0; + scan = popen(cmd, "r"); + if (!scan) { + err = (errno ? -errno : -ENOMEM); + goto out; + } + + for (i = 0;;) { + int segs; + struct lv *lv; + struct lv_segment seg; + uint64_t size, seg_start; + char type[32], name[256], devices[1024]; + + if (i >= vg->lv_cnt) + break; + + if (lvm_read_line(scan)) { + vg->lv_cnt = i; + break; + } + + err = -EINVAL; + lv = vg->lvs + i; + + if (sscanf(line, _NAME" %"SCNu64" %31s %u %"SCNu64" %"SCNu64" %1023s", + name, &size, type, &segs, &seg_start, + &seg.pe_size, devices) != 7) + goto out; + + if (seg_start) + goto next; + + if (!strcmp(type, "linear")) + seg.type = LVM_SEG_TYPE_LINEAR; + else + seg.type = LVM_SEG_TYPE_UNKNOWN; + + if (lvm_parse_lv_devices(vg, &seg, devices)) + goto out; + + i++; + lv->size = size; + lv->segments = segs; + lv->first_segment = seg; + + err = lvm_copy_name(lv->name, name, sizeof(lv->name) - 1); + if (err) + goto out; + err = -EINVAL; + + next: + if (lvm_next_line(scan)) + goto out; + } + + err = 0; + +out: + if (scan) + pclose(scan); + free(cmd); + return err; +} + +void +lvm_free_vg(struct vg *vg) +{ + free(vg->lvs); + free(vg->pvs); + memset(vg, 0, sizeof(*vg)); +} + +int +lvm_scan_vg(const char *vg_name, struct vg *vg) +{ + int err; + + memset(vg, 0, sizeof(*vg)); + + err = lvm_open_vg(vg_name, vg); + if (err) + return err; + + err = lvm_scan_lvs(vg); + if (err) { + lvm_free_vg(vg); + return err; + } + + return 0; +} + +#ifdef LVM_UTIL +static int +usage(void) +{ + printf("usage: lvm-util \n"); + exit(EINVAL); +} + +int +main(int argc, char **argv) +{ + int i, err; + struct vg vg; + struct pv *pv; + struct lv *lv; + struct lv_segment *seg; + + if (argc != 2) + usage(); + + err = lvm_scan_vg(argv[1], &vg); + if (err) { + printf("scan failed: %d\n", err); + return (err >= 0 ? err : -err); + } + + + printf("vg %s: extent_size: %"PRIu64", pvs: %d, lvs: %d\n", + vg.name, vg.extent_size, vg.pv_cnt, vg.lv_cnt); + + for (i = 0; i < vg.pv_cnt; i++) { + pv = vg.pvs + i; + printf("pv %s: start %"PRIu64"\n", pv->name, pv->start); + } + + for (i = 0; i < vg.lv_cnt; i++) { + lv = vg.lvs + i; + seg = &lv->first_segment; + printf("lv %s: size: %"PRIu64", segments: %u, type: %u, " + "dev: %s, pe_start: %"PRIu64", pe_size: %"PRIu64"\n", + lv->name, lv->size, lv->segments, seg->type, + seg->device, seg->pe_start, seg->pe_size); + } + + lvm_free_vg(&vg); + return 0; +} +#endif diff --git a/extracters/vhd-util/vhd-util b/extracters/vhd-util/vhd-util new file mode 100755 index 0000000000000000000000000000000000000000..cae74121d9d359ac1416b73b4f6ba50346fa8401 GIT binary patch literal 93712 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 z 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 z zTU1ardcO5YEYZ)OAYB2rvMwh8<=m*Ml5Ym{eID^X#6PCxp!L3%dOxpdFeh&tTA$l% zX}&1fETqdv{qM$cs3zE2*24hxbbTI_ab12@QEAVJx}ps^c>{TcqM%3*9IN74@72^F z*y#LI5!UyNV!dxX&6d}54l<>^^Lm)Qmn9OX>v~_Gi#&DvtY!PW1Y}lOWL`Wvx6}FV z8{O)NEkTjbAis@^VdL>ky<4zuwIdqylO+Hm|p?)tpk7GPdPogzPX0B`xqL}DcC5gFy>{ziD1Zy^Cpex3xL zFPKOik7w)0_V6;`4S=uEdT>iF+QR_tscu*EXm`3)bt?Ob>YOt=?|kS-(gs~zRB*A_ zJ>J6{zBZ9KmvmoEdP#eA(em8eLD9vdgZOJT2-?v_#BakH8WJ_Xs5_@8Z(VNgMZofn zroQnuslABb&-kQWoKN~-1lNt2OZswK6)_7feG^GiK0inLLG<e)p_TZ{9)4U3&@4`RqDzZ2mr7@gpk^@?=2QhvHW z^3k5u(Js!TU92lQ@Y5TN6 zB$dI=s{WFC_jBYoxIU5C$NH$Jj$aKMd222$E)4A*kb&w95l#Gef!}aTBJnZhASw#{ zQ>mqbKL~uQ+Y-Gx5uKv@Ht03btsmt+3Gf+!cM&LZuctDwS6Slhe-;665O{IoNxz@F zLdtL`F!Q?sct?Tv1LD=M%j?O9sal_#+W=%~3;+}FhrsK;J(2jU!b5$}FKUoxvjmt* zF2s8tcuViV!4Q!Pwe-@Wjrjw)JtJ~o1>(gNKkMUf!1H`3k@z>Q7q{kKWYXiL%1K#R?O(a$#4futBF|TMt zLGA*|j`g%2@w*WJ2phEScQ%Z$>}cLZ`c76wv7J2${G~sJFN$X+=N79PL~d^)z7_Gi z8DF`OPcZn3wks^=5c} z(l%LtHz2>8;nP2WXWdSk6}L(s?neAL`1=1!y%IkG<@My%!6|Kmqfk{;3`apL1ncp6 z=J%UK;vVKFequd+L{*QMD&Y|CZ@@F2$7K)1yOgr?7kPR{DMia&DmujeWjyM+6?p7M ztRLai0ACKcIQmwE4silXeik_W-GY-P{?fx03T(r7P}ca-6!UiU79s!@F!|6JltHZ@QY|&wt4%g}MP95H_!^d>ELx->E zaGwqj>u}^*s@#)xc$N;Q>u|0P7wfP^hg~{cr^7pSc)t!G)8P&szM{i@Iy|hyk!S1j zb$FHzr|WR84j1dNMTcEFT&Kf3b$GuHAJgFu9loN&eL6g>!;z)Bd>x*p!|6JltHZ@Q zY|&wt4%g}MP95H_!^d>ELx->EaGwqj>u}^mUA_*_(&2O+&eh>!9k%GOONZ-pc&85U z*WqJ2+@Zr)bhuB4hjlpe99_N+&(h&^9nRI^VjZ^VuuF&QbavVXh4)53DV>;ZS!&h{;PltzfIC7FMUx#Pu zaJmlX>Tt0RTXfi^!*x2mQ-}BK@G%|k(BUgO+^56CIyC-^zwaJXbbA)6@E-l%py#I_ z(C>$qs`!WX`|^N#|5ieU%YUW9M|6DnTk8E+`n`Ltdf#fjFH-N@^?Sp7_5Ol>cQ01& zzt``}A6D|EypI$!MJ*8su8ZF=%ce{;KJAysUNhG-3?c>*?k29?PFcn^CSp8-ye6(Tp|EX|z2f9BC(Nh*At(fzed^nBUqo{f<`n&5r zlK!{y*D$Wgg5Q<}&N=W*<=&D7es31|&j2Sq#aAhM&ert&5^(ByvDR~!hCiwCJ!$xy zOP6}oaOypOZ)o^#4PP!$!}y1WS1j`gMCP9HGzJ4C=5xre;4-%i_`d+odf6~e$$y5V zGK`6UXOhDOS>W|q;L89PIozhol{r+D5!7(^9SVNDE_c0#do*0;OcD3T(MoRJm5qYw z*7!fx_#5x^2t?*Y@x-}z%Gs^uoUid;()fG7tMGd?e4mDg?^5vn8a@&UN&oVHRq*#T ze1e8Id{4n;P7`r88eVaag3FvG;19r{ksgnxhgY!hcO~#M$vK<_-kSyf9bIn27LVjx zCVyd9HN0EHyEObM4L7zb{J4g{rQw4b{sRsFNW(qb6u!*;Av_rvVjt=kz8gA@T@Rfk^7u4{uhRa+W;OhXV-ttpD{8@JLF2FO% z^FE!=p$|QRFLO|M+9>&a+2Dn0@&ljz{X*k+f8>#OnPbA!wk-J*DO0)61K&V@+ML!O z{ZYyt?mz6yg8z4c7Z3Au#$bLVCxc({RlvUl`EStu7tDhErU+cWID)xZ;8*Elix*I@`+`E->CDq#tpJ&g`C!B!QYSt{);T|XR^S#PvJ|NW@m6X z3w-RT%=l*r9Dc%|Jd({fb!OAD;9soqyWdjyGIxtKS7*Url?C3H1^&G(@LvFaJnWC< zPg?%fZLq6u%&MjPpU-B==Z{(7?`MJkGYi}W0y4?}^epf&>gB5$^zGa%_|u_~%)k3A zC1*U!Z(0`oIe-^K4&gL^yH@aF|Mj?1*8GvvQot$Cn>D^R8%A4}{8tJ7IAeqM^LJ}{ zy0hR9W`R!<{Tx0%x<~NgZ-4BOoFCBnKa?e(r?bF+p9MaY1zv`sU8Z{cMDXE{EmHKz zo&wM`8iqO({#jYzm092oS>RV@frqoeZ^{Dyw=D3VW`S?d0)IUVe80e9KL?e3WDOef zIg$n6D9kLkld{0i5cpW5;tiF*tR*4;DOvF6W`SRk1s=)*?*p9eVo@hFqOTLpit zVa+E^$x-mLv+#Q_^T|B{d)0Kmtk7l6DU<#@rRiB->d}}gcv-_!fOz?97WgRG!%X>~ zDscESdj3n+ZjleypxDnIn(C1vWN#IoW@O1{UKaS}S>T~8aIRHlqH_>%+Oc$d{{1ZY z|B(g0Jq!G0fx{j?toW5RbnyF57W^ZCQy(^JeSlez-{>rMgKJ!rr!`L~ds2{MQWkt~ z7I;12ZdFvu->b6Vb3b1ud3FPSe6GtVJ?2TtXF%|w&mSnbtbqc9`vEiM^NTF-XS2ZH z1pIjTx7K`@)xR9bf`2Rv{8UUyWTIzM7PvPHd|?*&N`YgXtNp-!Eu~di@Z(wF_hf

MRv zHE%6@;*i<70=IHahUaI2*JOcTkp&(BoN`F_XTFsM|F$gf2eZH*%>v&kaP<45&}n(fNEQ z3;x4d;M=mmU(5pEp9Ow63%n2$VwvPNUf^h7YZd*n*AYBiCw6K0`pcv&`COOi0iW&B;21%EBz)Q6*K{k}Sg(iU8C-pZz?^H$H8?w>xbtTWKu+88av35wY2 z^SBH*^}O=(HHS?>bn7IqQ^)-I$jm22c$x`INx0GVtU|S|^Izq_eU7e4%F8VNONrbV#oy*3snm zmrt8H!|`^0#ndUuxbrI}SJ*_T(iJ_{mAg{iI7sCWhWtX7ja{bBAUhM`C|{fNU0v7f zgvdR=P5=){;@$6GjT3)e22U{IrCDB-7*-`fnr_zRlgHb1hXD0C;S=AW!t3!yAxu_N6 z{OUBKU(ST$&{I3>IWr0m*{Y+Xwrc1caKn<$0Cz0m5S7u=*0@qBLb4z^hnG2L#e1|f zkX~{SS0zA~+5&!kFgCSh94^&N4QmrZnM&l)S=>BVCI<&oM5GVq0S0?Q{nn>`6_Q`S z$>HKP{$PjjU;#xNM7d|A!0pEBs8MGcl^Ya?9Hm6)8h65#T4l&77Df|OLbLKMoh%t@B?p zckaTPMgB$JIdwI(+o+!=e^Vp|o#+ViY;mBY%j20>S39T5Kc##Mni_3)3lu38YO9T9 zB#;*hk4lyCAaqhMS)uYKjTTpxSYivua64;b=kSbie;yB?qItTzaA=+~ zpnlj{sM$(?8}7PS>_}Czk|;dkRNsSd)w9vkA*bt8i_xO?L&+qiW#y#yaX|nIN=?v0 z`;;aLDs@{Rt>I;;M3k|p1|H&EZ*5(YPOT@}X|xs#7Vq2xb|dti8Y zU0ml-nhAnWL-$SDXkyzMsk;~Cn~{1F6$}PQ(HaE0y1a|H%42N zSx+@st4{kjd79y`y##{9iF!Zo%vDt;MQigx+vQ&!UWq!41bECmnH~EyszWLS*zCSi zfux!I^Nv$F#x7jg-I&d$-HEb~Hh6nTdet*0hiLquPue?fDB$sYM|+Q~iJ+aQ(hCl$zRRNqm zCrH(iF3j2Ws&CgYQ`HQW!vg~adQ+)2$|Gyq@ii5MFy0cjT2C1D1vbI30vGNtMU|Vw;zG_p>H#o_yMmg^GLVhZYcB&YC|6pYh?J za>-le1A~A>=vq09{XgZ#cKF)6c;CB!AexAab(Oj}s{Fw?xP(MXP*k|N2qPH}hwm15hyCop0&UJs7A2Pu`xYKN>6_aU}TZ<<rpB+vgKnxq%$Nba|zx-w7ONdg~m+>d%?} zg|zh6e*T+v`ts-X3PQO*LVe6-r-s0=cwDPU<6Ae>ULJJ>g^0VHp z@NGby{9EThm+JJq|H%2X(plmAY3Z%~6Ww1^gURO2a5Dx7cMnQ3nDFQ#S)U%1Unm_@1WX617XyU%?Iz gzkk91>+s)_n`Kw9jVELINALA%>V!Z literal 0 HcmV?d00001 diff --git a/extracters/vhd-util/vhd-util-check.c b/extracters/vhd-util/vhd-util-check.c new file mode 100644 index 0000000..e424201 --- /dev/null +++ b/extracters/vhd-util/vhd-util-check.c @@ -0,0 +1,1024 @@ +/* Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#include +#define O_BINARY 0 +#endif +#include +#include + +#include "libvhd.h" +#include "vhd-util.h" + +/* allow the VHD timestamp to be at most this many seconds into the future to + * account for time skew with NFS servers + */ +#define TIMESTAMP_MAX_SLACK 1800 + +static int +vhd_util_check_zeros(void *buf, size_t size) +{ + size_t i; + char *p; + + p = buf; + for (i = 0; i < size; i++) + if (p[i]) + return i; + + return 0; +} + +static int +vhd_util_check_footer_opened(vhd_footer_t *footer) +{ + int i, n; + uint32_t *buf; + + buf = (uint32_t *)footer; + n = sizeof(*footer) / sizeof(uint32_t); + + for (i = 0; i < n; i++) + if (buf[i] != 0xc7c7c7c7) + return 0; + + return 1; +} + +static char * +vhd_util_check_validate_footer(vhd_footer_t *footer) +{ + int size; + uint32_t checksum, now; + + size = sizeof(footer->cookie); + if (memcmp(footer->cookie, HD_COOKIE, size)) + return "invalid cookie"; + + checksum = vhd_checksum_footer(footer); + if (checksum != footer->checksum) { + 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) + goto ok; + } + + return "invalid checksum"; + } + +ok: + if (!(footer->features & HD_RESERVED)) + return "invalid 'reserved' feature"; + + if (footer->features & ~(HD_TEMPORARY | HD_RESERVED)) + return "invalid extra features"; + + if (footer->ff_version != HD_FF_VERSION) + return "invalid file format version"; + + if (footer->type != HD_TYPE_DYNAMIC && + footer->type != HD_TYPE_DIFF && + footer->data_offset != ~(0ULL)) + return "invalid data offset"; + + now = vhd_time(time(NULL)); + if (footer->timestamp > now + TIMESTAMP_MAX_SLACK) + return "creation time in future"; + + if (!strncmp(footer->crtr_app, "tap", 3) && + footer->crtr_ver > VHD_CURRENT_VERSION) + return "unsupported tap creator version"; + + if (vhd_chs(footer->curr_size) < footer->geometry) + return "geometry too large"; + + if (footer->type != HD_TYPE_FIXED && + footer->type != HD_TYPE_DYNAMIC && + footer->type != HD_TYPE_DIFF) + return "invalid type"; + + if (footer->saved && footer->saved != 1) + return "invalid 'saved' state"; + + if (footer->hidden && footer->hidden != 1) + return "invalid 'hidden' state"; + + if (vhd_util_check_zeros(footer->reserved, + sizeof(footer->reserved))) + return "invalid 'reserved' bits"; + + return NULL; +} + +static char * +vhd_util_check_validate_header(int fd, vhd_header_t *header) +{ + off_t eof; + int i, cnt, size; + uint32_t checksum; + + size = sizeof(header->cookie); + if (memcmp(header->cookie, DD_COOKIE, size)) + return "invalid cookie"; + + checksum = vhd_checksum_header(header); + if (checksum != header->checksum) + return "invalid checksum"; + + if (header->hdr_ver != 0x00010000) + return "invalid header version"; + + if (header->data_offset != ~(0ULL)) + return "invalid data offset"; + + eof = lseek(fd, 0, SEEK_END); + if (eof == (off_t)-1) + return "error finding eof"; + + if (header->table_offset <= 0 || + header->table_offset % 512 || + (header->table_offset + + (header->max_bat_size * sizeof(uint32_t)) > + eof - sizeof(vhd_footer_t))) + return "invalid table offset"; + + for (cnt = 0, i = 0; i < sizeof(header->block_size) * 8; i++) + if ((header->block_size >> i) & 1) + cnt++; + + if (cnt != 1) + return "invalid block size"; + + if (header->res1) + return "invalid reserved bits"; + + if (vhd_util_check_zeros(header->res2, sizeof(header->res2))) + return "invalid reserved bits"; + + return NULL; +} + +static char * +vhd_util_check_validate_differencing_header(vhd_context_t *vhd) +{ + vhd_header_t *header; + + header = &vhd->header; + + if (vhd->footer.type == HD_TYPE_DIFF) { + char *parent; + uint32_t now; + + now = vhd_time(time(NULL)); + if (header->prt_ts > now + TIMESTAMP_MAX_SLACK) + return "parent creation time in future"; + + if (vhd_header_decode_parent(vhd, header, &parent)) + return "invalid parent name"; + + free(parent); + } else { + if (vhd_util_check_zeros(header->prt_name, + sizeof(header->prt_name))) + return "invalid non-null parent name"; + + if (vhd_util_check_zeros(header->loc, sizeof(header->loc))) + return "invalid non-null parent locators"; + + if (!blk_uuid_is_nil(&header->prt_uuid)) + return "invalid non-null parent uuid"; + + if (header->prt_ts) + return "invalid non-zero parent timestamp"; + } + + return NULL; +} + +static char * +vhd_util_check_validate_batmap(vhd_context_t *vhd, vhd_batmap_t *batmap) +{ + int size; + off_t eof; + uint32_t checksum; + + size = sizeof(batmap->header.cookie); + if (memcmp(batmap->header.cookie, VHD_BATMAP_COOKIE, size)) + return "invalid cookie"; + + if (batmap->header.batmap_version > VHD_BATMAP_CURRENT_VERSION) + return "unsupported batmap version"; + + checksum = vhd_checksum_batmap(batmap); + if (checksum != batmap->header.checksum) + return "invalid checksum"; + + if (!batmap->header.batmap_size) + return "invalid size zero"; + + eof = lseek(vhd->fd, 0, SEEK_END); + if (eof == (off_t)-1) + return "error finding eof"; + + if (!batmap->header.batmap_offset || + batmap->header.batmap_offset % 512) + return "invalid batmap offset"; + + if ((batmap->header.batmap_offset + + vhd_sectors_to_bytes(batmap->header.batmap_size)) > + eof - sizeof(vhd_footer_t)) + return "invalid batmap size"; + + return NULL; +} + +static char * +vhd_util_check_validate_parent_locator(vhd_context_t *vhd, + vhd_parent_locator_t *loc) +{ + off_t eof; + + if (vhd_validate_platform_code(loc->code)) + return "invalid platform code"; + + if (loc->code == PLAT_CODE_NONE) { + if (vhd_util_check_zeros(loc, sizeof(*loc))) + return "non-zero locator"; + + return NULL; + } + + if (!loc->data_offset) + return "invalid data offset"; + + if (!loc->data_space) + return "invalid data space"; + + if (!loc->data_len) + return "invalid data length"; + + eof = lseek(vhd->fd, 0, SEEK_END); + if (eof == (off_t)-1) + return "error finding eof"; + + if (loc->data_offset + vhd_parent_locator_size(loc) > + eof - sizeof(vhd_footer_t)) + return "invalid size"; + + if (loc->res) + return "invalid reserved bits"; + + return NULL; +} + +static const char * +vhd_util_check_validate_parent(vhd_context_t *vhd, const char *ppath) +{ + const char *msg; + vhd_context_t parent; + + msg = NULL; + + if (vhd_parent_raw(vhd)) + return msg; + + if (vhd_open(&parent, ppath, + VHD_OPEN_RDONLY | VHD_OPEN_IGNORE_DISABLED)) + return "error opening parent"; + + if (blk_uuid_compare(&vhd->header.prt_uuid, &parent.footer.uuid)) { + msg = "invalid parent uuid"; + goto out; + } + +out: + vhd_close(&parent); + return msg; +} + +static int +vhd_util_check_footer(int fd, vhd_footer_t *footer, int ignore) +{ + size_t size; + int err, opened; + char *msg, *buf; + off_t eof, off; + vhd_footer_t primary, backup; + + memset(&primary, 0, sizeof(primary)); + memset(&backup, 0, sizeof(backup)); + +#ifdef _WIN32 + buf = _aligned_malloc( sizeof( primary ), VHD_SECTOR_SIZE ); + if( buf == NULL ) { + printf( "error allocating buffer: %d\n", -errno ); + return -errno; + } +#else + err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, sizeof(primary)); + if (err) { + printf("error allocating buffer: %d\n", err); + return -err; + } +#endif + + memset(buf, 0, sizeof(primary)); + + eof = lseek(fd, 0, SEEK_END); + if (eof == (off_t)-1) { + err = -errno; + printf("error calculating end of file: %d\n", err); + goto out; + } + + size = ((eof % 512) ? 511 : 512); + eof = lseek(fd, eof - size, SEEK_SET); + if (eof == (off_t)-1) { + err = -errno; + printf("error calculating end of file: %d\n", err); + goto out; + } + + err = read(fd, buf, 512); + if (err != size) { + err = (errno ? -errno : -EIO); + printf("error reading primary footer: %d\n", err); + goto out; + } + + memcpy(&primary, buf, sizeof(primary)); + opened = vhd_util_check_footer_opened(&primary); + vhd_footer_in(&primary); + + msg = vhd_util_check_validate_footer(&primary); + if (msg) { + if (opened && ignore) + goto check_backup; + + err = -EINVAL; + printf("primary footer invalid: %s\n", msg); + goto out; + } + + if (primary.type == HD_TYPE_FIXED) { + err = 0; + goto out; + } + +check_backup: + off = lseek(fd, 0, SEEK_SET); + if (off == (off_t)-1) { + err = -errno; + printf("error seeking to backup footer: %d\n", err); + goto out; + } + + size = 512; + memset(buf, 0, sizeof(primary)); + + err = read(fd, buf, size); + if (err != size) { + err = (errno ? -errno : -EIO); + printf("error reading backup footer: %d\n", err); + goto out; + } + + memcpy(&backup, buf, sizeof(backup)); + vhd_footer_in(&backup); + + msg = vhd_util_check_validate_footer(&backup); + if (msg) { + err = -EINVAL; + printf("backup footer invalid: %s\n", msg); + goto out; + } + + if (memcmp(&primary, &backup, sizeof(primary))) { + if (opened && ignore) { + memcpy(&primary, &backup, sizeof(primary)); + goto ok; + } + + if (backup.hidden && + !strncmp(backup.crtr_app, "tap", 3) && + (backup.crtr_ver == VHD_VERSION(0, 1) || + backup.crtr_ver == VHD_VERSION(1, 1))) { + char cmp, tmp = backup.hidden; + backup.hidden = 0; + cmp = memcmp(&primary, &backup, sizeof(primary)); + backup.hidden = tmp; + if (!cmp) + goto ok; + } + + err = -EINVAL; + printf("primary and backup footers do not match\n"); + goto out; + } + +ok: + err = 0; + memcpy(footer, &primary, sizeof(primary)); + +out: +#ifdef _WIN32 + _aligned_free( buf ); +#else + free( buf ); +#endif + return err; +} + +static int +vhd_util_check_header(int fd, vhd_footer_t *footer) +{ + int err; + off_t off; + char *msg, *buf; + vhd_header_t header; + +#ifdef _WIN32 + buf = _aligned_malloc( sizeof( header ), VHD_SECTOR_SIZE ); + if( buf == NULL ) { + printf( "error allocating header: %d\n", -errno ); + return -errno; + } +#else + err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, sizeof(header)); + if (err) { + printf("error allocating header: %d\n", err); + return err; + } +#endif + + off = (off_t)footer->data_offset; + off = lseek(fd, off, SEEK_SET); + if (off == (off_t)-1) { + err = -errno; + printf("error seeking to header: %d\n", err); + goto out; + } + + err = read(fd, buf, sizeof(header)); + if (err != sizeof(header)) { + err = (errno ? -errno : -EIO); + printf("error reading header: %d\n", err); + goto out; + } + + memcpy(&header, buf, sizeof(header)); + vhd_header_in(&header); + + msg = vhd_util_check_validate_header(fd, &header); + if (msg) { + err = -EINVAL; + printf("header is invalid: %s\n", msg); + goto out; + } + + err = 0; + +out: +#ifdef _WIN32 + _aligned_free( buf ); +#else + free( buf ); +#endif + return err; +} + +static int +vhd_util_check_differencing_header(vhd_context_t *vhd) +{ + char *msg; + + msg = vhd_util_check_validate_differencing_header(vhd); + if (msg) { + printf("differencing header is invalid: %s\n", msg); + return -EINVAL; + } + + return 0; +} + +static int +vhd_util_check_bat(vhd_context_t *vhd) +{ + off_t eof, eoh; + size_t i, j, block_size; + int err; + + err = vhd_seek(vhd, 0, SEEK_END); + if (err) { + printf("error calculating eof: %d\n", err); + return err; + } + + eof = vhd_position(vhd); + if (eof == (off_t)-1) { + printf("error calculating eof: %d\n", -errno); + return -errno; + } + + /* adjust eof for vhds with short footers */ + if (eof % 512) { + if (eof % 512 != 511) { + printf("invalid file size: 0x%"PRIx64"\n", (uintmax_t)eof); + return -EINVAL; + } + + eof++; + } + + err = vhd_get_bat(vhd); + if (err) { + printf("error reading bat: %d\n", err); + return err; + } + + err = vhd_end_of_headers(vhd, &eoh); + if (err) { + printf("error calculating end of metadata: %d\n", err); + return err; + } + + eof -= sizeof(vhd_footer_t); + eof >>= VHD_SECTOR_SHIFT; + eoh >>= VHD_SECTOR_SHIFT; + block_size = vhd->spb + vhd->bm_secs; + + for (i = 0; i < vhd->header.max_bat_size; i++) { + uint32_t off = vhd->bat.bat[i]; + if (off == DD_BLK_UNUSED) + continue; + + if ((off_t)off < eoh) { + printf("block %d (offset 0x%x) clobbers headers\n", + i, off); + return -EINVAL; + } + + if ((off_t)(off + block_size) > eof) { + printf("block %d (offset 0x%x) clobbers footer\n", + i, off); + return -EINVAL; + } + + for (j = 0; j < vhd->header.max_bat_size; j++) { + uint32_t joff = vhd->bat.bat[j]; + + if (i == j) + continue; + + if (joff == DD_BLK_UNUSED) + continue; + + if (off == joff) + err = -EINVAL; + + if (off > joff && off < joff + block_size) + err = -EINVAL; + + if (off + block_size > joff && + off + block_size < joff + block_size) + err = -EINVAL; + + if (err) { + printf("block %d (offset 0x%x) clobbers " + "block %d (offset 0x%x)\n", + i, off, j, joff); + return err; + } + } + } + + return 0; +} + +static int +vhd_util_check_batmap(vhd_context_t *vhd) +{ + char *msg; + size_t i; + int err; + + err = vhd_get_bat(vhd); + if (err) { + printf("error reading bat: %d\n", err); + return err; + } + + err = vhd_get_batmap(vhd); + if (err) { + printf("error reading batmap: %d\n", err); + return err; + } + + msg = vhd_util_check_validate_batmap(vhd, &vhd->batmap); + if (msg) { + printf("batmap is invalid: %s\n", msg); + return -EINVAL; + } + + for (i = 0; i < vhd->header.max_bat_size; i++) { + if (!vhd_batmap_test(vhd, &vhd->batmap, i)) + continue; + + if (vhd->bat.bat[i] == DD_BLK_UNUSED) { + printf("batmap shows unallocated block %d full\n", i); + return -EINVAL; + } + } + + return 0; +} + +static int +vhd_util_check_parent_locators(vhd_context_t *vhd) +{ + int i, n, err; + vhd_parent_locator_t *loc; + char *file, *ppath, *location, *pname; + const char *msg; + int mac, macx, w2ku, w2ru, wi2r, wi2k, found; + + mac = 0; + macx = 0; + w2ku = 0; + w2ru = 0; + wi2r = 0; + wi2k = 0; + found = 0; + pname = NULL; + ppath = NULL; + location = NULL; + + err = vhd_header_decode_parent(vhd, &vhd->header, &pname); + if (err) { + printf("error decoding parent name: %d\n", err); + return err; + } + + n = sizeof(vhd->header.loc) / sizeof(vhd->header.loc[0]); + for (i = 0; i < n; i++) { + ppath = NULL; + location = NULL; + loc = vhd->header.loc + i; + + msg = vhd_util_check_validate_parent_locator(vhd, loc); + if (msg) { + err = -EINVAL; + printf("invalid parent locator %d: %s\n", i, msg); + goto out; + } + + if (loc->code == PLAT_CODE_NONE) + continue; + + switch (loc->code) { + case PLAT_CODE_MACX: + if (macx++) + goto dup; + break; + + case PLAT_CODE_MAC: + if (mac++) + goto dup; + break; + + case PLAT_CODE_W2KU: + if (w2ku++) + goto dup; + break; + + case PLAT_CODE_W2RU: + if (w2ru++) + goto dup; + break; + + case PLAT_CODE_WI2R: + if (wi2r++) + goto dup; + break; + + case PLAT_CODE_WI2K: + if (wi2k++) + goto dup; + break; + + default: + err = -EINVAL; + printf("invalid platform code for locator %d\n", i); + goto out; + } + + if (loc->code != PLAT_CODE_MACX && + loc->code != PLAT_CODE_W2RU && + loc->code != PLAT_CODE_W2KU) + continue; + + err = vhd_parent_locator_read(vhd, loc, &ppath); + if (err) { + printf("error reading parent locator %d: %d\n", i, err); + goto out; + } + + file = basename(ppath); + if (strcmp(pname, file)) { + err = -EINVAL; + printf("parent locator %d name (%s) does not match " + "header name (%s)\n", i, file, pname); + goto out; + } + + err = vhd_find_parent(vhd, ppath, &location); + if (err) { + printf("error resolving %s: %d\n", ppath, err); + goto out; + } + +#ifdef _WIN32 + if( (err = _open( location, _O_RDONLY | _O_BINARY )) != -1 ) { + _close( err ); + err = 0; + } else + err = ENOENT; +#else + err = access(location, R_OK); +#endif + if (err && loc->code == PLAT_CODE_MACX) { + err = -errno; + printf("parent locator %d points to missing file %s " + "(resolved to %s)\n", i, ppath, location); + goto out; + } + + msg = vhd_util_check_validate_parent(vhd, location); + if (msg) { + err = -EINVAL; + printf("invalid parent %s: %s\n", location, msg); + goto out; + } + + found++; + free(ppath); + free(location); + ppath = NULL; + location = NULL; + + continue; + + dup: + printf("duplicate platform code in locator %d: 0x%x\n", + i, loc->code); + err = -EINVAL; + goto out; + } + + if (!found) { + err = -EINVAL; + printf("could not find parent %s\n", pname); + goto out; + } + + err = 0; + +out: + free(pname); + free(ppath); + free(location); + return err; +} + +static void +vhd_util_dump_headers(const char *name) +{ + char *argv[] = { "read", "-p", "-n", (char *)name }; + int argc = sizeof(argv) / sizeof(argv[0]); + + printf("%s appears invalid; dumping metadata\n", name); + vhd_util_read(argc, argv); +} + +static int +vhd_util_check_vhd(const char *name, int ignore) +{ + int fd, err; + vhd_context_t vhd; + struct stat stats; + vhd_footer_t footer; + + fd = -1; + memset(&vhd, 0, sizeof(vhd)); + memset(&footer, 0, sizeof(footer)); + + err = stat(name, &stats); + if (err == -1) { + printf("cannot stat %s: %d\n", name, errno); + return -errno; + } + +#ifdef _WIN32 + /* Look at filename for initial \\ to replace _S_IFBLK?? */ + if( !(stats.st_mode & _S_IFREG) || (stats.st_mode & _S_IFDIR) ) { + printf( "%s is not a regular file or block device\n", name ); + return -EINVAL; + } +#else + if (!S_ISREG(stats.st_mode) && !S_ISBLK(stats.st_mode)) { + printf("%s is not a regular file or block device\n", name); + return -EINVAL; + } +#endif + + fd = open(name, O_RDONLY | O_DIRECT | O_LARGEFILE | O_BINARY ); + if (fd == -1) { + printf("error opening %s\n", name); + return -errno; + } + + err = vhd_util_check_footer(fd, &footer, ignore); + if (err) + goto out; + + if (footer.type != HD_TYPE_DYNAMIC && footer.type != HD_TYPE_DIFF) + goto out; + + err = vhd_util_check_header(fd, &footer); + if (err) + goto out; + + err = vhd_open(&vhd, name, VHD_OPEN_RDONLY | VHD_OPEN_IGNORE_DISABLED); + if (err) + goto out; + + err = vhd_util_check_differencing_header(&vhd); + if (err) + goto out; + + err = vhd_util_check_bat(&vhd); + if (err) + goto out; + + if (vhd_has_batmap(&vhd)) { + err = vhd_util_check_batmap(&vhd); + if (err) + goto out; + } + + if (vhd.footer.type == HD_TYPE_DIFF) { + err = vhd_util_check_parent_locators(&vhd); + if (err) + goto out; + } + + err = 0; + printf("%s is valid\n", name); + +out: + if (err) + vhd_util_dump_headers(name); + if (fd != -1) + close(fd); + vhd_close(&vhd); + return err; +} + +static int +vhd_util_check_parents(const char *name, int ignore) +{ + int err; + vhd_context_t vhd; + char *cur, *parent; + + cur = (char *)name; + + for (;;) { + err = vhd_open(&vhd, cur, + VHD_OPEN_RDONLY | VHD_OPEN_IGNORE_DISABLED); + if (err) + goto out; + + if (vhd.footer.type != HD_TYPE_DIFF || vhd_parent_raw(&vhd)) { + vhd_close(&vhd); + goto out; + } + + err = vhd_parent_locator_get(&vhd, &parent); + vhd_close(&vhd); + + if (err) { + printf("error getting parent: %d\n", err); + goto out; + } + + if (cur != name) + free(cur); + cur = parent; + + err = vhd_util_check_vhd(cur, ignore); + if (err) + goto out; + } + +out: + if (err) + printf("error checking parents: %d\n", err); + if (cur != name) + free(cur); + return err; +} + +int +vhd_util_check(int argc, char **argv) +{ + char *name; + int c, err, ignore, parents; + + if (!argc || !argv) { + err = -EINVAL; + goto usage; + } + + ignore = 0; + parents = 0; + name = NULL; + + optind = 0; + while ((c = getopt(argc, argv, "n:iph")) != -1) { + switch (c) { + case 'n': + name = optarg; + break; + case 'i': + ignore = 1; + break; + case 'p': + parents = 1; + break; + case 'h': + err = 0; + goto usage; + default: + err = -EINVAL; + goto usage; + } + } + + if (!name || optind != argc) { + err = -EINVAL; + goto usage; + } + + err = vhd_util_check_vhd(name, ignore); + if (err) + goto out; + + if (parents) + err = vhd_util_check_parents(name, ignore); + +out: + return err; + +usage: + printf("options: -n [-i ignore missing primary footers] " + "[-p check parents] [-h help]\n"); + return err; +} diff --git a/extracters/vhd-util/vhd-util-coalesce.c b/extracters/vhd-util/vhd-util-coalesce.c new file mode 100644 index 0000000..dad6e03 --- /dev/null +++ b/extracters/vhd-util/vhd-util-coalesce.c @@ -0,0 +1,232 @@ +/* Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include +#include +#ifndef _WIN32 +#include +#define O_BINARY 0 +#endif + +#include "libvhd.h" + +static int +__raw_io_write(int fd, char* buf, uint64_t sec, uint32_t secs) +{ + off_t off; + size_t ret; + + errno = 0; + off = lseek(fd, (off_t)vhd_sectors_to_bytes(sec), SEEK_SET); + if (off == (off_t)-1) { + printf("raw parent: seek(0x%08"PRIx64") failed: %d\n", + vhd_sectors_to_bytes(sec), -errno); + return -errno; + } + + ret = write(fd, buf, (size_t)vhd_sectors_to_bytes(secs)); + if (ret == vhd_sectors_to_bytes(secs)) + return 0; + + printf("raw parent: write of 0x%"PRIx64" returned %zd, errno: %d\n", + vhd_sectors_to_bytes(secs), ret, -errno); + return (errno ? -errno : -EIO); +} + +/* + * Use 'parent' if the parent is VHD, and 'parent_fd' if the parent is raw + */ +static int +vhd_util_coalesce_block(vhd_context_t *vhd, vhd_context_t *parent, + int parent_fd, uint64_t block) +{ + size_t i; + int err; + char *buf, *map; + uint64_t sec, secs; + + buf = NULL; + map = NULL; + sec = block * vhd->spb; + + if (vhd->bat.bat[block] == DD_BLK_UNUSED) + return 0; + +#ifdef _WIN32 + buf = _aligned_malloc( vhd->header.block_size, 4096 ); + if( buf == NULL ) + return -errno; +#else + err = posix_memalign( (void **)&buf, 4096, vhd->header.block_size ); + if( err ) + return -err; +#endif + + err = vhd_io_read(vhd, buf, sec, vhd->spb); + if (err) + goto done; + + if (vhd_has_batmap(vhd) && vhd_batmap_test(vhd, &vhd->batmap, (uint32_t)block)) { + if (parent->file) + err = vhd_io_write(parent, buf, sec, vhd->spb); + else + err = __raw_io_write(parent_fd, buf, sec, vhd->spb); + goto done; + } + + err = vhd_read_bitmap(vhd, (uint32_t)block, &map); + if (err) + goto done; + + for (i = 0; i < vhd->spb; i++) { + if (!vhd_bitmap_test(vhd, map, i)) + continue; + + for (secs = 0; i + secs < vhd->spb; secs++) + if (!vhd_bitmap_test(vhd, map, i + (uint32_t)secs)) + break; + + if (parent->file) + err = vhd_io_write(parent, + buf + vhd_sectors_to_bytes(i), + sec + i, (uint32_t)secs); + else + err = __raw_io_write(parent_fd, + buf + vhd_sectors_to_bytes(i), + sec + i, (uint32_t)secs); + if (err) + goto done; + + i += (uint32_t)secs; + } + + err = 0; + +done: +#ifdef _WIN32 + _aligned_free( buf ); +#else + free( buf ); +#endif + free(map); + return err; +} + +int +vhd_util_coalesce(int argc, char **argv) +{ + int err, c; + uint64_t i; + char *name, *pname; + vhd_context_t vhd, parent; + int parent_fd = -1; + + name = NULL; + pname = NULL; + parent.file = NULL; + + if (!argc || !argv) + goto usage; + + optind = 0; + while ((c = getopt(argc, argv, "n:h")) != -1) { + switch (c) { + case 'n': + name = optarg; + break; + case 'h': + default: + goto usage; + } + } + + if (!name || optind != argc) + goto usage; + + err = vhd_open(&vhd, name, VHD_OPEN_RDONLY); + if (err) { + printf("error opening %s: %d\n", name, err); + return err; + } + + err = vhd_parent_locator_get(&vhd, &pname); + if (err) { + printf("error finding %s parent: %d\n", name, err); + vhd_close(&vhd); + return err; + } + + if (vhd_parent_raw(&vhd)) { + parent_fd = open(pname, O_RDWR | O_DIRECT | O_LARGEFILE | O_BINARY, 0644); + if (parent_fd == -1) { + err = -errno; + printf("failed to open parent %s: %d\n", pname, err); + vhd_close(&vhd); + return err; + } + } else { + err = vhd_open(&parent, pname, VHD_OPEN_RDWR); + if (err) { + printf("error opening %s: %d\n", pname, err); + free(pname); + vhd_close(&vhd); + return err; + } + } + + err = vhd_get_bat(&vhd); + if (err) + goto done; + + if (vhd_has_batmap(&vhd)) { + err = vhd_get_batmap(&vhd); + if (err) + goto done; + } + + for (i = 0; i < vhd.bat.entries; i++) { + err = vhd_util_coalesce_block(&vhd, &parent, parent_fd, i); + if (err) + goto done; + } + + err = 0; + + done: + free(pname); + vhd_close(&vhd); + if (parent.file) + vhd_close(&parent); + else + close(parent_fd); + return err; + +usage: + printf("options: <-n name> [-h help]\n"); + return -EINVAL; +} diff --git a/extracters/vhd-util/vhd-util-create.c b/extracters/vhd-util/vhd-util-create.c new file mode 100644 index 0000000..9e3a991 --- /dev/null +++ b/extracters/vhd-util/vhd-util-create.c @@ -0,0 +1,82 @@ +/* Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include +#ifndef _WIN32 +#include +#endif + +#include "libvhd.h" + +int +vhd_util_create(int argc, char **argv) +{ + char *name; + uint64_t size; + int c, sparse, err; + vhd_flag_creat_t flags; + + err = -EINVAL; + size = 0; + sparse = 1; + name = NULL; + flags = 0; + + if (!argc || !argv) + goto usage; + + optind = 0; + while ((c = getopt(argc, argv, "n:s:rh")) != -1) { + switch (c) { + case 'n': + name = optarg; + break; + case 's': + err = 0; + size = strtoull(optarg, NULL, 10); + break; + case 'r': + sparse = 0; + break; + case 'h': + default: + goto usage; + } + } + + if (err || !name || optind != argc) + goto usage; + + return vhd_create(name, size << 20, + (sparse ? HD_TYPE_DYNAMIC : HD_TYPE_FIXED), + flags); + +usage: + printf("options: <-n name> <-s size (MB)> [-r reserve] [-h help]\n"); + return -EINVAL; +} diff --git a/extracters/vhd-util/vhd-util-fill.c b/extracters/vhd-util/vhd-util-fill.c new file mode 100644 index 0000000..79f6228 --- /dev/null +++ b/extracters/vhd-util/vhd-util-fill.c @@ -0,0 +1,119 @@ +/* Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif + +#include "libvhd.h" + +int +vhd_util_fill( int argc, char **argv ) +{ + int err, c; + char *buf, *name; + vhd_context_t vhd; + uint64_t i, sec, secs; + + buf = NULL; + name = NULL; + + if( !argc || !argv ) + goto usage; + + optind = 0; + while( (c = getopt( argc, argv, "n:h" )) != -1 ) { + switch( c ) { + case 'n': + name = optarg; + break; + case 'h': + default: + goto usage; + } + } + + if( !name || optind != argc ) + goto usage; + + err = vhd_open( &vhd, name, VHD_OPEN_RDWR ); + if( err ) { + printf( "error opening %s: %d\n", name, err ); + return err; + } + + err = vhd_get_bat( &vhd ); + if( err ) + goto done; + +#ifdef _WIN32 + buf = _aligned_malloc( vhd.header.block_size, 4096 ); + if( buf == NULL ) { + err = -errno; + goto done; + } +#else + err = posix_memalign((void **)&buf, 4096, vhd.header.block_size); + if (err) { + err = -err; + goto done; + } +#endif + + sec = 0; + secs = vhd.header.block_size >> VHD_SECTOR_SHIFT; + + for (i = 0; i < vhd.header.max_bat_size; i++) { + err = vhd_io_read(&vhd, buf, sec, (uint32_t)secs); + if (err) + goto done; + + err = vhd_io_write(&vhd, buf, sec, (uint32_t)secs); + if (err) + goto done; + + sec += secs; + } + + err = 0; + + done: +#ifdef _WIN32 + _aligned_free( buf ); +#else + free( buf ); +#endif + vhd_close(&vhd); + return err; + +usage: + printf("options: <-n name> [-h help]\n"); + return -EINVAL; +} diff --git a/extracters/vhd-util/vhd-util-modify.c b/extracters/vhd-util/vhd-util-modify.c new file mode 100644 index 0000000..a73c75d --- /dev/null +++ b/extracters/vhd-util/vhd-util-modify.c @@ -0,0 +1,144 @@ +/* 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. + * + * Altering operations: + * + * 1. Change the parent pointer to another file. + * 2. Change the size of the file containing the VHD image. This does NOT + * affect the VHD disk capacity, only the physical size of the file containing + * the VHD. Naturally, it is not possible to set the file size to be less than + * the what VHD utilizes. + * The operation doesn't actually change the file size, but it writes the + * footer in the right location such that resizing the file (manually, as a + * separate step) will produce the correct results. If the new file size is + * greater than the current file size, the file must first be expanded and then + * altered with this operation. If the new size is smaller than the current + * size, the VHD must first be altered with this operation and then the file + * must be shrunk. Failing to resize the file will result in a corrupted VHD. + */ + +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif + +#include "libvhd.h" + +TEST_FAIL_EXTERN_VARS; + +int +vhd_util_modify(int argc, char **argv) +{ + char *name; + vhd_context_t vhd; + int err, c, size, parent, parent_flags; + off_t newsize = 0; + char *newparent = NULL; + + name = NULL; + size = 0; + parent = 0; + parent_flags = 0; + + optind = 0; + while ((c = getopt(argc, argv, "n:s:p:P:mh")) != -1) { + switch (c) { + case 'n': + name = optarg; + break; + case 's': + size = 1; + errno = 0; + newsize = (off_t)strtoll(optarg, NULL, 10); + if (errno) { + fprintf(stderr, "Invalid size '%s'\n", optarg); + goto usage; + } + break; + case 'p': + if( parent && strcmp( newparent, optarg ) ) { + fprintf(stderr, "Parent name doesn't match\n"); + goto usage; + } + ++parent; + newparent = optarg; + break; + case 'P': + parent = 2; + parent_flags |= 2; + newparent = optarg; + break; + case 'm': + parent_flags |= 1; + break; + + case 'h': + default: + goto usage; + } + } + + if (!name || optind != argc || (parent && parent <2)) + goto usage; + + err = vhd_open(&vhd, name, VHD_OPEN_RDWR); + if (err) { + printf("error opening %s: %d\n", name, err); + return err; + } + + if (size) { + err = vhd_set_phys_size(&vhd, newsize); + if (err) + printf("failed to set physical size to %"PRIu64":" + " %d\n", (uintmax_t)newsize, err); + } + + if (parent) { + TEST_FAIL_AT(FAIL_REPARENT_BEGIN); + err = vhd_change_parent(&vhd, newparent, parent_flags); + if (err) { + printf("failed to set parent to '%s': %d\n", + newparent, err); + goto done; + } + TEST_FAIL_AT(FAIL_REPARENT_END); + } + +done: + vhd_close(&vhd); + return err; + +usage: + printf("*** Dangerous operations, use with care ***\n"); + printf("options: <-n name> [-p NEW_PARENT set parent [-m raw]] " + "[-P change PARENT with uuid check] " + "[-s NEW_SIZE set size] [-h help]\n"); + return -EINVAL; +} diff --git a/extracters/vhd-util/vhd-util-query.c b/extracters/vhd-util/vhd-util-query.c new file mode 100644 index 0000000..d1312cf --- /dev/null +++ b/extracters/vhd-util/vhd-util-query.c @@ -0,0 +1,161 @@ +/* Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif + +#include "libvhd.h" + +int +vhd_util_query(int argc, char **argv) +{ + char *name; + vhd_context_t vhd; + off_t currsize; + int ret, err, c, size, physize, parent, fields, depth; + + name = NULL; + size = 0; + physize = 0; + parent = 0; + fields = 0; + depth = 0; + + if (!argc || !argv) { + err = -EINVAL; + goto usage; + } + + optind = 0; + while ((c = getopt(argc, argv, "n:vspfdh")) != -1) { + switch (c) { + case 'n': + name = optarg; + break; + case 'v': + size = 1; + break; + case 's': + physize = 1; + break; + case 'p': + parent = 1; + break; + case 'f': + fields = 1; + break; + case 'd': + depth = 1; + break; + case 'h': + err = 0; + goto usage; + default: + err = -EINVAL; + goto usage; + } + } + + if (!name || optind != argc) { + err = -EINVAL; + goto usage; + } + + err = vhd_open(&vhd, name, VHD_OPEN_RDONLY | VHD_OPEN_IGNORE_DISABLED); + if (err) { + printf("error opening %s: %d\n", name, err); + return err; + } + + if (size) + printf("%"PRIu64"\n", vhd.footer.curr_size >> 20); + + if (physize) { + err = vhd_get_phys_size(&vhd, &currsize); + if (err) + printf("failed to get physical size: %d\n", err); + else + printf("%"PRIu64"\n", (uintmax_t)currsize); + } + + if (parent) { + ret = 0; + + if (vhd.footer.type != HD_TYPE_DIFF) + printf("%s has no parent\n", name); + else { + char *pname; + + ret = vhd_parent_locator_get(&vhd, &pname); + if (ret) + printf("query failed\n"); + else { + printf("%s\n", pname); + free(pname); + } + } + + err = (err ? err : ret); + } + + if (fields) { + int hidden; + + ret = vhd_hidden(&vhd, &hidden); + if (ret) + printf("error checking 'hidden' field: %d\n", ret); + else + printf("hidden: %d\n", hidden); + + err = (err ? err : ret); + } + + if (depth) { + int length; + + ret = vhd_chain_depth(&vhd, &length); + if (ret) + printf("error checking chain depth: %d\n", ret); + else + printf("chain depth: %d\n", length); + + err = (err ? err : ret); + } + + vhd_close(&vhd); + return err; + +usage: + printf("options: <-n name> [-v print virtual size (in MB)] " + "[-s print physical utilization (bytes)] [-p print parent] " + "[-f print fields] [-d print chain depth] [-h help]\n"); + return err; +} diff --git a/extracters/vhd-util/vhd-util-read.c b/extracters/vhd-util/vhd-util-read.c new file mode 100644 index 0000000..b2638fd --- /dev/null +++ b/extracters/vhd-util/vhd-util-read.c @@ -0,0 +1,876 @@ +/* Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include +#include +#ifndef _WIN32 +#include +#define _O_BINARY 0 +#endif +#include + +#include "libvhd.h" +#include "vhd-util.h" + +#define nsize 15 +static char nbuf[nsize]; + +static inline char * +__xconv(uint64_t num) +{ + snprintf(nbuf, nsize, "%#" PRIx64 , num); + return nbuf; +} + +static inline char * +__dconv(uint64_t num) +{ + snprintf(nbuf, nsize, "%" PRIu64, num); + return nbuf; +} + +#define conv(hex, num) \ + (hex ? __xconv((uint64_t)num) : __dconv((uint64_t)num)) + +static void +vhd_print_header(vhd_context_t *vhd, vhd_header_t *h, int hex) +{ + int err; + uint32_t cksm; + char uuid[39], time_str[26], cookie[9], *name; + + printf("VHD Header Summary:\n-------------------\n"); + + snprintf(cookie, sizeof(cookie), "%s", h->cookie); + printf("Cookie : %s\n", cookie); + + printf("Data offset (unusd) : %s\n", conv(hex, h->data_offset)); + printf("Table offset : %s\n", conv(hex, h->table_offset)); + printf("Header version : 0x%08x\n", h->hdr_ver); + printf("Max BAT size : %s\n", conv(hex, h->max_bat_size)); + printf("Block size : %s ", conv(hex, h->block_size)); + printf("(%s MB)\n", conv(hex, h->block_size >> 20)); + + err = vhd_header_decode_parent(vhd, h, &name); + printf("Parent name : %s\n", + (err ? "failed to read name" : name)); + free(name); + + blk_uuid_to_string(&h->prt_uuid, uuid, sizeof(uuid)); + printf("Parent UUID : %s\n", uuid); + + vhd_time_to_string(h->prt_ts, time_str); + printf("Parent timestamp : %s\n", time_str); + + cksm = vhd_checksum_header(h); + printf("Checksum : 0x%x|0x%x (%s)\n", h->checksum, cksm, + h->checksum == cksm ? "Good!" : "Bad!"); + printf("\n"); +} + +static void +vhd_print_footer(vhd_footer_t *f, int hex) +{ + uint64_t c, h, s; + uint32_t ff_maj, ff_min, cr_maj, cr_min, cksm; + char time_str[26], creator[5], uuid[39], cookie[9]; + + printf("VHD Footer Summary:\n-------------------\n"); + + snprintf(cookie, sizeof(cookie), "%s", f->cookie); + printf("Cookie : %s\n", cookie); + + printf("Features : (0x%08x) %s%s\n", f->features, + (f->features & HD_TEMPORARY) ? "" : "", + (f->features & HD_RESERVED) ? "" : ""); + + ff_maj = f->ff_version >> 16; + ff_min = f->ff_version & 0xffff; + printf("File format version : Major: %d, Minor: %d\n", + ff_maj, ff_min); + + printf("Data offset : %s\n", conv(hex, f->data_offset)); + + vhd_time_to_string(f->timestamp, time_str); + printf("Timestamp : %s\n", time_str); + + memcpy(creator, f->crtr_app, 4); + creator[4] = '\0'; + printf("Creator Application : '%s'\n", creator); + + cr_maj = f->crtr_ver >> 16; + cr_min = f->crtr_ver & 0xffff; + printf("Creator version : Major: %d, Minor: %d\n", + cr_maj, cr_min); + + printf("Creator OS : %s\n", + ((f->crtr_os == HD_CR_OS_WINDOWS) ? "Windows" : + ((f->crtr_os == HD_CR_OS_MACINTOSH) ? "Macintosh" : + ((f->crtr_os == HD_CR_OS_UNIX) ? "Unix" : + ((f->crtr_os == HD_CR_OS_VMS) ? "VMS" : + "Unknown!"))))); + + printf("Original disk size : %s MB ", conv(hex, f->orig_size >> 20)); + printf("(%s Bytes)\n", conv(hex, f->orig_size)); + + printf("Current disk size : %s MB ", conv(hex, f->curr_size >> 20)); + printf("(%s Bytes)\n", conv(hex, f->curr_size)); + + c = f->geometry >> 16; + h = (f->geometry & 0x0000FF00) >> 8; + s = f->geometry & 0x000000FF; + printf("Geometry : Cyl: %s, ", conv(hex, c)); + printf("Hds: %s, ", conv(hex, h)); + printf("Sctrs: %s\n", conv(hex, s)); + printf(" : = %s MB ", conv(hex, (c * h * s) >> 11)); + printf("(%s Bytes)\n", conv(hex, c * h * s << 9)); + + printf("Disk type : %s\n", + f->type <= HD_TYPE_MAX ? + HD_TYPE_STR[f->type] : "Unknown"); + + cksm = vhd_checksum_footer(f); + printf("Checksum : 0x%x|0x%x (%s)\n", f->checksum, cksm, + f->checksum == cksm ? "Good!" : "Bad!"); + + blk_uuid_to_string(&f->uuid, uuid, sizeof(uuid)); + printf("UUID : %s\n", uuid); + + printf("Saved state : %s\n", f->saved == 0 ? "No" : "Yes"); + printf("Hidden : %d\n", f->hidden); + printf("\n"); +} + +static inline char * +code_name(uint32_t code) +{ + switch(code) { + case PLAT_CODE_NONE: + return "PLAT_CODE_NONE"; + case PLAT_CODE_WI2R: + return "PLAT_CODE_WI2R"; + case PLAT_CODE_WI2K: + return "PLAT_CODE_WI2K"; + case PLAT_CODE_W2RU: + return "PLAT_CODE_W2RU"; + case PLAT_CODE_W2KU: + return "PLAT_CODE_W2KU"; + case PLAT_CODE_MAC: + return "PLAT_CODE_MAC"; + case PLAT_CODE_MACX: + return "PLAT_CODE_MACX"; + default: + return "UNKOWN"; + } +} + +static void +vhd_print_parent(vhd_context_t *vhd, vhd_parent_locator_t *loc) +{ + int err; + char *buf; + + err = vhd_parent_locator_read(vhd, loc, &buf); + if (err) { + printf("failed to read parent name\n"); + return; + } + + printf(" decoded name : %s\n", buf); + free( buf ); +} + +static void +vhd_print_parent_locators(vhd_context_t *vhd, int hex) +{ + int i, n; + vhd_parent_locator_t *loc; + + printf("VHD Parent Locators:\n--------------------\n"); + + n = sizeof(vhd->header.loc) / sizeof(struct prt_loc); + for (i = 0; i < n; i++) { + loc = &vhd->header.loc[i]; + + if (loc->code == PLAT_CODE_NONE) + continue; + + printf("locator: : %d\n", i); + printf(" code : %s\n", + code_name(loc->code)); + printf(" data_space : %s\n", + conv(hex, loc->data_space)); + printf(" data_length : %s\n", + conv(hex, loc->data_len)); + printf(" data_offset : %s\n", + conv(hex, loc->data_offset)); + vhd_print_parent(vhd, loc); + printf("\n"); + } +} + +static void +vhd_print_batmap_header(vhd_batmap_t *batmap, int hex) +{ + uint32_t cksm; + + printf("VHD Batmap Summary:\n-------------------\n"); + printf("Batmap offset : %s\n", + conv(hex, batmap->header.batmap_offset)); + printf("Batmap size (secs) : %s\n", + conv(hex, batmap->header.batmap_size)); + printf("Batmap version : 0x%08x\n", + batmap->header.batmap_version); + + cksm = vhd_checksum_batmap(batmap); + printf("Checksum : 0x%x|0x%x (%s)\n", + batmap->header.checksum, cksm, + (batmap->header.checksum == cksm ? "Good!" : "Bad!")); + printf("\n"); +} + +static inline int +check_block_range(vhd_context_t *vhd, uint64_t block, int hex) +{ + if (block > vhd->header.max_bat_size) { + fprintf(stderr, "block %s past end of file\n", + conv(hex, block)); + return -ERANGE; + } + + return 0; +} + +static int +vhd_print_headers(vhd_context_t *vhd, int hex) +{ + int err; + + vhd_print_footer(&vhd->footer, hex); + + if (vhd_type_dynamic(vhd)) { + vhd_print_header(vhd, &vhd->header, hex); + + if (vhd->footer.type == HD_TYPE_DIFF) + vhd_print_parent_locators(vhd, hex); + + if (vhd_has_batmap(vhd)) { + err = vhd_get_batmap(vhd); + if (err) { + printf("failed to get batmap header\n"); + return err; + } + + vhd_print_batmap_header(&vhd->batmap, hex); + } + } + + return 0; +} + +static int +vhd_dump_headers(const char *name, int hex) +{ + vhd_context_t vhd; + + libvhd_set_log_level(1); + memset(&vhd, 0, sizeof(vhd)); + + printf("\n%s appears invalid; dumping headers\n\n", name); + + vhd.fd = open(name, O_DIRECT | O_LARGEFILE | O_RDONLY | _O_BINARY); + if (vhd.fd == -1) + return -errno; + + vhd.file = strdup(name); + + vhd_read_footer(&vhd, &vhd.footer); + vhd_read_header(&vhd, &vhd.header); + + vhd_print_footer(&vhd.footer, hex); + vhd_print_header(&vhd, &vhd.header, hex); + + close(vhd.fd); + free(vhd.file); + + return 0; +} + +static int +vhd_print_logical_to_physical(vhd_context_t *vhd, + uint64_t sector, int count, int hex) +{ + int i; + uint32_t blk, lsec; + uint64_t cur, offset; + + if (vhd_sectors_to_bytes(sector + count) > vhd->footer.curr_size) { + fprintf(stderr, "sector %s past end of file\n", + conv(hex, sector + count)); + return -ERANGE; + } + + for (i = 0; i < count; i++) { + cur = sector + i; + blk = (uint32_t)(cur / vhd->spb); + lsec = cur % vhd->spb; + offset = vhd->bat.bat[blk]; + + if (offset != DD_BLK_UNUSED) { + offset += lsec + 1; + offset = vhd_sectors_to_bytes(offset); + } + + printf("logical sector %s: ", conv(hex, cur)); + printf("block number: %s, ", conv(hex, blk)); + printf("sector offset: %s, ", conv(hex, lsec)); + printf("file offset: %s\n", (offset == DD_BLK_UNUSED ? + "not allocated" : conv(hex, offset))); + } + + return 0; +} + +static int +vhd_print_bat(vhd_context_t *vhd, uint64_t block, int count, int hex) +{ + int i; + uint64_t cur, offset; + + if (check_block_range(vhd, block + count, hex)) + return -ERANGE; + + for (i = 0; i < count; i++) { + cur = block + i; + offset = vhd->bat.bat[cur]; + + printf("block: %s: ", conv(hex, cur)); + printf("offset: %s\n", + (offset == DD_BLK_UNUSED ? "not allocated" : + conv(hex, vhd_sectors_to_bytes(offset)))); + } + + return 0; +} + +static inline void +write_full(int fd, void* buf, size_t count) +{ + ssize_t num_written = 0; + if (!buf) return; + + + while(count > 0) { + + num_written = write(fd, buf, count); + if (num_written == -1) { + if (errno == EINTR) + continue; + else + return; + } + + count -= num_written; + buf = ((char *)buf) + num_written; + } +} + +static int +vhd_print_bitmap(vhd_context_t *vhd, uint64_t block, int count, int hex) +{ + char *buf; + int i, err; + uint64_t cur; + + if (check_block_range(vhd, block + count, hex)) + return -ERANGE; + + for (i = 0; i < count; i++) { + cur = block + i; + + if (vhd->bat.bat[cur] == DD_BLK_UNUSED) { + printf("block %s not allocated\n", conv(hex, cur)); + continue; + } + + err = vhd_read_bitmap(vhd, (uint32_t)cur, &buf); + if (err) + goto out; + + write_full(STDOUT_FILENO, buf, + (size_t)vhd_sectors_to_bytes(vhd->bm_secs)); +#ifdef _WIN32 + _aligned_free(buf); +#else + free(buf); +#endif + } + + err = 0; +out: + return err; +} + +static int +vhd_test_bitmap(vhd_context_t *vhd, uint64_t sector, int count, int hex) +{ + char *buf; + uint64_t cur; + int i, err, bit; + uint32_t blk, bm_blk, sec; + + if (vhd_sectors_to_bytes(sector + count) > vhd->footer.curr_size) { + printf("sector %s past end of file\n", conv(hex, sector)); + return -ERANGE; + } + + bm_blk = -1; + buf = NULL; + + for (i = 0; i < count; i++) { + cur = sector + i; + blk = (uint32_t)(cur / vhd->spb); + sec = cur % vhd->spb; + + if (blk != bm_blk) { + bm_blk = blk; + free(buf); + buf = NULL; + + if (vhd->bat.bat[blk] != DD_BLK_UNUSED) { + err = vhd_read_bitmap(vhd, blk, &buf); + if (err) + goto out; + } + } + + if (vhd->bat.bat[blk] == DD_BLK_UNUSED) + bit = 0; + else + bit = vhd_bitmap_test(vhd, buf, blk); + + printf("block %s: ", conv(hex, blk)); + printf("sec: %s: %d\n", conv(hex, sec), bit); + } + + err = 0; + out: +#ifdef _WIN32 + _aligned_free( buf ); +#else + free( buf ); +#endif + return err; +} + +static int +vhd_print_batmap(vhd_context_t *vhd) +{ + int err; + size_t size; + + err = vhd_get_batmap(vhd); + if (err) { + printf("failed to read batmap: %d\n", err); + return err; + } + + size = (uint32_t)vhd_sectors_to_bytes(vhd->batmap.header.batmap_size); + write_full(STDOUT_FILENO, vhd->batmap.map, size); + + return 0; +} + +static int +vhd_test_batmap(vhd_context_t *vhd, uint64_t block, int count, int hex) +{ + int i, err; + uint64_t cur; + + if (check_block_range(vhd, block + count, hex)) + return -ERANGE; + + err = vhd_get_batmap(vhd); + if (err) { + fprintf(stderr, "failed to get batmap\n"); + return err; + } + + for (i = 0; i < count; i++) { + cur = block + i; + fprintf(stderr, "batmap for block %s: %d\n", conv(hex, cur), + vhd_batmap_test(vhd, &vhd->batmap, (uint32_t)cur)); + } + + return 0; +} + +static int +vhd_print_data(vhd_context_t *vhd, uint64_t block, int count, int hex) +{ + char *buf; + int i, err; + uint64_t cur; + + err = 0; + + if (check_block_range(vhd, block + count, hex)) + return -ERANGE; + + for (i = 0; i < count; i++) { + cur = block + i; + + if (vhd->bat.bat[cur] == DD_BLK_UNUSED) { + printf("block %s not allocated\n", conv(hex, cur)); + continue; + } + + err = vhd_read_block(vhd, (uint32_t)cur, &buf); + if (err) + break; + + write_full(STDOUT_FILENO, buf, vhd->header.block_size); + free(buf); + } + + return err; +} + +static int +vhd_read_data(vhd_context_t *vhd, uint64_t sec, int count, int hex) +{ + char *buf; + uint64_t cur; + int err; + size_t max, secs; + + if (vhd_sectors_to_bytes(sec + count) > vhd->footer.curr_size) + return -ERANGE; + + max = MIN((size_t)vhd_sectors_to_bytes(count), VHD_BLOCK_SIZE); +#ifdef _WIN32 + buf = _aligned_malloc( max, VHD_SECTOR_SIZE ); + if( buf == NULL ) + return -errno; +#else + err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, max); + if (err) + return -err; +#endif + + cur = sec; + while (count) { + secs = MIN((max >> VHD_SECTOR_SHIFT), (size_t)count); + err = vhd_io_read(vhd, buf, cur, secs); + if (err) + break; + + write_full(STDOUT_FILENO, buf, (size_t)vhd_sectors_to_bytes(secs)); + + cur += secs; + count -= secs; + } + +#ifdef _WIN32 + _aligned_free( buf ); +#else + free( buf ); +#endif + return err; +} + +static void vhd_print_brief(vhd_context_t *cvhd, char *name) { + int err; + size_t i, n, level = 0; + vhd_parent_locator_t *loc; + vhd_context_t *vhd; + char uuid[39], time_str[26], *pname; + + if( !vhd_type_dynamic(cvhd) ) { + printf( "%s has no parent\n", + cvhd->footer.type <= HD_TYPE_MAX? + HD_TYPE_STR[cvhd->footer.type] : + "Unknown disk type" ); + return; + } + + name = strdup( name ); + if( name == NULL ) + return; + + vhd = malloc( sizeof( vhd_context_t ) ); + if( vhd == NULL ) + goto out; + + memcpy( vhd, cvhd, sizeof( vhd_context_t ) ); + + pname = NULL; + level = 0; + + while( 1 ) { + printf( "%s\n", name ); + free( name ); + name = NULL; + + printf(" Disk type : %s\n", + vhd->footer.type <= HD_TYPE_MAX ? + HD_TYPE_STR[vhd->footer.type] : "Unknown"); + + if( vhd->footer.type != HD_TYPE_DIFF ) { + blk_uuid_to_string(&vhd->footer.uuid, uuid, sizeof(uuid)); + printf(" UUID : %s\n", uuid); + vhd_time_to_string(vhd->footer.timestamp, time_str); + printf(" Timestamp : %s\n", time_str); + break; + } + err = vhd_header_decode_parent(vhd, &vhd->header, &name); + printf(" Parent name : %s\n", + (err ? "failed to read name" : name)); + free(name); + name = NULL; + + blk_uuid_to_string(&vhd->header.prt_uuid, uuid, sizeof(uuid)); + printf(" Parent UUID : %s\n", uuid); + + vhd_time_to_string(vhd->header.prt_ts, time_str); + printf(" Parent timestamp : %s\n", time_str); + + n = sizeof(vhd->header.loc) / sizeof(struct prt_loc); + for (i = 0; i < n; i++) { + loc = &vhd->header.loc[i]; + + if (loc->code == PLAT_CODE_NONE) + continue; + err = vhd_parent_locator_read( vhd, loc, &name ); + if( err ) { + printf( "Failed to read parent locator %u", i ); + break; + } + + printf(" Parent location %4d: %s\n", i, name ); + + if( pname == NULL ) { + err = vhd_find_parent( vhd, name, &pname ); + } + free( name ); + name = NULL; + } + if( pname == NULL ) + break; + printf( "\nParent is " ); + name = pname; + pname = NULL; + if( level != 0 ) + vhd_close(vhd); + + err = vhd_open(vhd, name, VHD_OPEN_RDONLY | VHD_OPEN_IGNORE_DISABLED); + if (err) { + printf("unable to open: %d\n", err); + break; + } + ++level; + } + + out: + if( level > 0 ) + vhd_close( vhd ); + free( vhd ); + free( pname ); + free( name ); + + return; +} + +int +vhd_util_read(int argc, char **argv) +{ + char *name; + vhd_context_t vhd; + int c, err, headers, brief, hex; + uint64_t bat, bitmap, tbitmap, batmap, tbatmap, data, lsec, count, read; + + err = 0; + hex = 0; + headers = 0; + brief = 0; + count = 1; + bat = -1; + bitmap = -1; + tbitmap = -1; + batmap = -1; + tbatmap = -1; + data = -1; + lsec = -1; + read = -1; + name = NULL; + + if (!argc || !argv) + goto usage; + + optind = 0; + while ((c = getopt(argc, argv, "n:pPt:b:m:i:aj:d:c:r:xh")) != -1) { + switch(c) { + case 'n': + name = optarg; + break; + case 'p': + headers = 1; + break; + case 'P': + brief = 1; + break; + case 't': + lsec = strtoul(optarg, NULL, 10); + break; + case 'b': + bat = strtoull(optarg, NULL, 10); + break; + case 'm': + bitmap = strtoull(optarg, NULL, 10); + break; + case 'i': + tbitmap = strtoul(optarg, NULL, 10); + break; + case 'a': + batmap = 1; + break; + case 'j': + tbatmap = strtoull(optarg, NULL, 10); + break; + case 'd': + data = strtoull(optarg, NULL, 10); + break; + case 'r': + read = strtoull(optarg, NULL, 10); + break; + case 'c': + count = strtoul(optarg, NULL, 10); + break; + case 'x': + hex = 1; + break; + case 'h': + default: + goto usage; + } + } + + if (!name || optind != argc) + goto usage; + + err = vhd_open(&vhd, name, VHD_OPEN_RDONLY | VHD_OPEN_IGNORE_DISABLED); + if (err) { + printf("Failed to open %s: %d\n", name, err); + vhd_dump_headers(name, hex); + return err; + } + + err = vhd_get_bat(&vhd); + if (err) { + printf("Failed to get bat for %s: %d\n", name, err); + goto out; + } + + if (headers) + vhd_print_headers(&vhd, hex); + + if (brief) + vhd_print_brief(&vhd, name); + + if (lsec != -1) { + err = vhd_print_logical_to_physical(&vhd, lsec, (int)count, hex); + if (err) + goto out; + } + + if (bat != -1) { + err = vhd_print_bat(&vhd, bat, (int)count, hex); + if (err) + goto out; + } + + if (bitmap != -1) { + err = vhd_print_bitmap(&vhd, bitmap, (int)count, hex); + if (err) + goto out; + } + + if (tbitmap != -1) { + err = vhd_test_bitmap(&vhd, tbitmap, (int)count, hex); + if (err) + goto out; + } + + if (batmap != -1) { + err = vhd_print_batmap(&vhd); + if (err) + goto out; + } + + if (tbatmap != -1) { + err = vhd_test_batmap(&vhd, tbatmap, (int)count, hex); + if (err) + goto out; + } + + if (data != -1) { + err = vhd_print_data(&vhd, data, (int)count, hex); + if (err) + goto out; + } + + if (read != -1) { + err = vhd_read_data(&vhd, read, (int)count, hex); + if (err) + goto out; + } + + err = 0; + + out: + vhd_close(&vhd); + return err; + + usage: + printf("options:\n" + "-h help\n" + "-n name\n" + "-p print VHD headers\n" + "-P print parent summary\n" + "-t sec translate logical sector to VHD location\n" + "-b blk print bat entry\n" + "-m blk print bitmap\n" + "-i sec test bitmap for logical sector\n" + "-a print batmap\n" + "-j blk test batmap for block\n" + "-d blk print data\n" + "-c num num units\n" + "-r sec read num sectors at sec\n" + "-x print in hex\n"); + return EINVAL; +} diff --git a/extracters/vhd-util/vhd-util-repair.c b/extracters/vhd-util/vhd-util-repair.c new file mode 100644 index 0000000..b062030 --- /dev/null +++ b/extracters/vhd-util/vhd-util-repair.c @@ -0,0 +1,86 @@ +/* Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif + +#include "libvhd.h" + +int +vhd_util_repair(int argc, char **argv) +{ + char *name; + int err, c; + off_t eof; + vhd_context_t vhd; + + name = NULL; + + if (!argc || !argv) + goto usage; + + optind = 0; + while ((c = getopt(argc, argv, "n:h")) != -1) { + switch (c) { + case 'n': + name = optarg; + break; + case 'h': + default: + goto usage; + } + } + + if (!name || optind != argc) + goto usage; + + err = vhd_open(&vhd, name, VHD_OPEN_RDWR); + if (err) { + printf("error opening %s: %d\n", name, err); + return err; + } + + err = vhd_end_of_data(&vhd, &eof); + if (err) { + printf("error finding end of data: %d\n", err); + goto done; + } + + err = vhd_write_footer_at(&vhd, &vhd.footer, eof); + + done: + vhd_close(&vhd); + return err; + +usage: + printf("options: <-n name> [-h help]\n"); + return -EINVAL; +} diff --git a/extracters/vhd-util/vhd-util-resize.c b/extracters/vhd-util/vhd-util-resize.c new file mode 100644 index 0000000..84562fa --- /dev/null +++ b/extracters/vhd-util/vhd-util-resize.c @@ -0,0 +1,1199 @@ +/* Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include +#include +#ifndef _WIN32 +#include +#include +#include +#endif +#include +#include + +#include "libvhd-journal.h" + +#ifdef _WIN32 +#define EPRINTF(x) printf x +#else +#define EPRINTF(x) EPRINTFu x +#if 1 +#define DFPRINTF(_f, _a...) fprintf(stdout, _f, ##_a) +#else +#define DFPRINTF(_f, _a...) ((void)0) +#endif + +#define EPRINTFu(_f, _a...) \ + do { \ + syslog(LOG_INFO, "%s: " _f, __func__, ##_a); \ + DFPRINTF(_f, _a); \ + } while (0) +#endif + +typedef struct vhd_block { + uint32_t block; + uint32_t offset; +} vhd_block_t; + +TEST_FAIL_EXTERN_VARS; + +static inline uint32_t +secs_to_blocks_down(vhd_context_t *vhd, uint64_t secs) +{ + return (uint32_t)(secs / vhd->spb); +} + +static uint32_t +secs_to_blocks_up(vhd_context_t *vhd, uint64_t secs) +{ + uint32_t blocks; + + blocks = (uint32_t)(secs / vhd->spb); + if (secs % vhd->spb) + blocks++; + + return blocks; +} + +static int +vhd_fixed_shrink(vhd_journal_t *journal, uint64_t secs) +{ + int err; + uint64_t new_eof; + vhd_context_t *vhd; + + vhd = &journal->vhd; + + new_eof = vhd->footer.curr_size - vhd_sectors_to_bytes(secs); + if (new_eof <= sizeof(vhd_footer_t)) + return -EINVAL; + + err = ftruncate(vhd->fd, new_eof); + if (err) + return errno; + + vhd->footer.curr_size = new_eof; + return vhd_write_footer(vhd, &vhd->footer); +} + +static int +vhd_write_zeros(vhd_journal_t *journal, off_t off, uint64_t size) +{ + int err; + char *buf; + vhd_context_t *vhd; + uint64_t bytes, map; + + vhd = &journal->vhd; + map = MIN(size, VHD_BLOCK_SIZE); + + err = vhd_seek(vhd, off, SEEK_SET); + if (err) + return err; + +#ifdef _WIN32 + buf = VirtualAlloc( NULL, (SIZE_T)map, MEM_COMMIT, PAGE_READONLY ); + if( buf == NULL ) + return -ENOMEM; +#else + buf = mmap( 0, map, PROT_READ, MAP_SHARED | MAP_ANON, -1, 0 ); + if( buf == MAP_FAILED ) + return -errno; +#endif + + do { + bytes = MIN(size, map); + + err = vhd_write(vhd, buf, (size_t)bytes); + if (err) + break; + + size -= bytes; + } while (size); + +#ifdef _WIN32 + VirtualFree( buf, 0, MEM_RELEASE ); +#else + munmap( buf, map ); +#endif + + return err; +} + +static int +vhd_fixed_grow(vhd_journal_t *journal, uint64_t secs) +{ + int err; + vhd_context_t *vhd; + uint64_t size, eof, new_eof; + + size = vhd_sectors_to_bytes(secs); + vhd = &journal->vhd; + + err = vhd_seek(vhd, 0, SEEK_END); + if (err) + goto out; + + eof = vhd_position(vhd); + if (eof == (off_t)-1) { + err = -errno; + goto out; + } + + err = vhd_write_zeros(journal, (off_t)(eof - sizeof(vhd_footer_t)), size); + if (err) + goto out; + + new_eof = eof + size; + err = vhd_seek(vhd, (off_t)new_eof, SEEK_SET); + if (err) + goto out; + + vhd->footer.curr_size += size; + err = vhd_write_footer(vhd, &vhd->footer); + if (err) + goto out; + + err = 0; + +out: + return err; +} + +static int +vhd_fixed_resize(vhd_journal_t *journal, uint64_t size) +{ + int err; + vhd_context_t *vhd; + uint64_t cur_secs, new_secs; + + vhd = &journal->vhd; + cur_secs = vhd->footer.curr_size >> VHD_SECTOR_SHIFT; + new_secs = size << (20 - VHD_SECTOR_SHIFT); + + if (cur_secs == new_secs) + return 0; + else if (cur_secs > new_secs) + err = vhd_fixed_shrink(journal, cur_secs - new_secs); + else + err = vhd_fixed_grow(journal, new_secs - cur_secs); + + return err; +} + +static inline void +swap(vhd_block_t *list, int a, int b) +{ + vhd_block_t tmp; + + tmp = list[a]; + list[a] = list[b]; + list[b] = tmp; +} + +static int +partition(vhd_block_t *list, int left, int right, int pidx) +{ + int i, sidx; + long long pval; + + sidx = left; + pval = list[pidx].offset; + swap(list, pidx, right); + + for (i = left; i < right; i++) + if (list[i].offset >= pval) { + swap(list, sidx, i); + ++sidx; + } + + swap(list, right, sidx); + return sidx; +} + +static void +quicksort(vhd_block_t *list, int left, int right) +{ + int pidx, new_pidx; + + if (right < left) + return; + + pidx = left; + new_pidx = partition(list, left, right, pidx); + quicksort(list, left, new_pidx - 1); + quicksort(list, new_pidx + 1, right); +} + +static int +vhd_move_block(vhd_journal_t *journal, uint32_t src, off_t offset) +{ + int err; + char *buf; + size_t size; + vhd_context_t *vhd; + off_t off, src_off; + + buf = NULL; + vhd = &journal->vhd; + off = offset; + size = (size_t)vhd_sectors_to_bytes(vhd->bm_secs); + src_off = vhd->bat.bat[src]; + + if (src_off == DD_BLK_UNUSED) + return -EINVAL; + src_off = (off_t)vhd_sectors_to_bytes(src_off); + + err = vhd_journal_add_block(journal, src, + VHD_JOURNAL_DATA | VHD_JOURNAL_METADATA); + if (err) + goto out; + + err = vhd_read_bitmap(vhd, src, &buf); + if (err) + goto out; + + err = vhd_seek(vhd, off, SEEK_SET); + if (err) + goto out; + + err = vhd_write(vhd, buf, size); + if (err) + goto out; + + free(buf); + buf = NULL; + off += size; + size = (size_t)vhd_sectors_to_bytes(vhd->spb); + + err = vhd_read_block(vhd, src, &buf); + if (err) + goto out; + + err = vhd_seek(vhd, off, SEEK_SET); + if (err) + goto out; + + err = vhd_write(vhd, buf, size); + if (err) + goto out; + + vhd->bat.bat[src] = offset >> VHD_SECTOR_SHIFT; + + err = vhd_write_zeros(journal, src_off, + vhd_sectors_to_bytes(vhd->bm_secs + vhd->spb)); + +out: + free(buf); + return err; +} + +static int +vhd_clobber_block(vhd_journal_t *journal, uint32_t src, uint32_t dest) +{ + int err; + off_t off; + vhd_context_t *vhd; + + vhd = &journal->vhd; + off = (off_t)vhd_sectors_to_bytes(vhd->bat.bat[dest]); + + err = vhd_journal_add_block(journal, dest, + VHD_JOURNAL_DATA | VHD_JOURNAL_METADATA); + if (err) + return err; + + err = vhd_move_block(journal, src, off); + if (err) + return err; + + vhd->bat.bat[dest] = DD_BLK_UNUSED; + + return 0; +} + +/* + * remove a list of blocks from the vhd file + * if a block to be removed: + * - resides at the end of the file: simply clear its bat entry + * - resides elsewhere: move the last block in the file into its position + * and update the bat to reflect this + */ +static int +vhd_defrag_shrink(vhd_journal_t *journal, + vhd_block_t *original_free_list, int free_cnt) +{ + vhd_context_t *vhd; + size_t i, free_idx; + int j, err; + vhd_block_t *blocks, *free_list; + + err = 0; + blocks = NULL; + free_list = NULL; + vhd = &journal->vhd; + + blocks = malloc(vhd->bat.entries * sizeof(vhd_block_t)); + if (!blocks) { + err = -ENOMEM; + goto out; + } + + free_list = malloc(free_cnt * sizeof(vhd_block_t)); + if (!free_list) { + err = -ENOMEM; + goto out; + } + + for (i = 0; i < vhd->bat.entries; i++) { + blocks[i].block = i; + blocks[i].offset = vhd->bat.bat[i]; + } + + memcpy(free_list, original_free_list, + free_cnt * sizeof(vhd_block_t)); + + /* sort both the to-free list and the bat list + * in order of descending file offset */ + quicksort(free_list, 0, free_cnt - 1); + quicksort(blocks, 0, vhd->bat.entries - 1); + + for (i = 0, free_idx = 0; + i < vhd->bat.entries && free_idx < (size_t)free_cnt; i++) { + vhd_block_t *b = blocks + i; + + if (b->offset == DD_BLK_UNUSED) + continue; + + for (j = free_idx; j < free_cnt; j++) + if (b->block == free_list[j].block) { + /* the last block in the file is in the list of + * blocks to remove; no need to shuffle the + * data -- just clear the bat entry */ + vhd->bat.bat[free_list[j].block] = DD_BLK_UNUSED; + free_idx++; + continue; + } + + err = vhd_clobber_block(journal, b->block, + free_list[free_idx++].block); + if (err) + goto out; + } + + /* clear any bat entries for blocks we did not shuffle */ + for (i = free_idx; i < (size_t)free_cnt; i++) + vhd->bat.bat[free_list[i].block] = DD_BLK_UNUSED; + +out: + free(blocks); + free(free_list); + + return err; +} + +static int +vhd_clear_bat_entries(vhd_journal_t *journal, uint32_t entries) +{ + size_t i; + int err; + vhd_context_t *vhd; + off_t orig_map_off, new_map_off; + uint32_t orig_entries, new_entries; + + vhd = &journal->vhd; + orig_entries = vhd->header.max_bat_size; + new_entries = orig_entries - entries; + + if (vhd_has_batmap(vhd)) { + err = vhd_batmap_header_offset(vhd, &orig_map_off); + if (err) + return err; + } + + /* update header */ + vhd->header.max_bat_size = new_entries; + err = vhd_write_header(vhd, &vhd->header); + if (err) + return err; + + /* update footer */ + vhd->footer.curr_size = (uint64_t)new_entries * vhd->header.block_size; + vhd->footer.geometry = vhd_chs(vhd->footer.curr_size); + err = vhd_write_footer(vhd, &vhd->footer); + if (err) + return err; + + /* update bat -- we don't reclaim space, just clear entries */ + for (i = new_entries; i < orig_entries; i++) + vhd->bat.bat[i] = 0; + + err = vhd_write_bat(vhd, &vhd->bat); + if (err) + return err; + + /* update this after write_bat so the end of the bat is zeored */ + vhd->bat.entries = new_entries; + + if (!vhd_has_batmap(vhd)) + return 0; + + /* zero out old batmap header if new header has moved */ + err = vhd_batmap_header_offset(vhd, &new_map_off); + if (err) + return err; + + if (orig_map_off != new_map_off) { + size_t size; + + size = (size_t)vhd_bytes_padded(sizeof(struct dd_batmap_hdr)); + + err = vhd_write_zeros(journal, orig_map_off, size); + if (err) + return err; + } + + /* update batmap -- clear entries for freed blocks */ + for (i = new_entries; i < orig_entries; i++) + vhd_batmap_clear(vhd, &vhd->batmap, i); + + err = vhd_write_batmap(vhd, &vhd->batmap); + if (err) + return err; + + return 0; +} + +static int +vhd_dynamic_shrink(vhd_journal_t *journal, uint64_t secs) +{ + off_t eof; + uint32_t blocks; + vhd_context_t *vhd; + size_t i, j; + int err, free_cnt; + struct vhd_block *free_list; + + printf("dynamic shrink not fully implemented\n"); + return -ENOSYS; + + eof = 0; + free_cnt = 0; + free_list = NULL; + vhd = &journal->vhd; + + blocks = secs_to_blocks_down(vhd, secs); + if (blocks == 0) + return 0; + + if (vhd_has_batmap(vhd)) { + err = vhd_get_batmap(vhd); + if (err) + return err; + } + + free_list = malloc(blocks * sizeof(struct vhd_block)); + if (!free_list) + return -ENOMEM; + + for (i = vhd->bat.entries - 1, j = 0; i >= 0 && j < blocks; i--, j++) { + uint32_t blk = vhd->bat.bat[i]; + + if (blk != DD_BLK_UNUSED) { + free_list[free_cnt].block = i; + free_list[free_cnt].offset = blk; + free_cnt++; + } + } + + if (free_cnt) { + err = vhd_defrag_shrink(journal, free_list, free_cnt); + if (err) + goto out; + } + + err = vhd_clear_bat_entries(journal, blocks); + if (err) + goto out; + + /* remove data beyond footer */ + err = vhd_end_of_data(vhd, &eof); + if (err) + goto out; + + err = ftruncate(vhd->fd, eof + sizeof(vhd_footer_t)); + if (err) { + err = -errno; + goto out; + } + + err = 0; + +out: + free(free_list); + return err; +} + +static inline void +vhd_first_data_block(vhd_context_t *vhd, vhd_block_t *block) +{ + size_t i; + uint32_t blk; + + memset(block, 0, sizeof(vhd_block_t)); + + for (i = 0; i < vhd->bat.entries; i++) { + blk = vhd->bat.bat[i]; + + if (blk != DD_BLK_UNUSED) { + if (!block->offset || blk < block->offset) { + block->block = i; + block->offset = blk; + } + } + } +} + +static inline uint32_t +vhd_next_block_offset(vhd_context_t *vhd) +{ + size_t i; + uint32_t blk, end, spp, next; +#ifdef _WIN32 + SYSTEM_INFO inf; + + GetSystemInfo( &inf ); + spp = inf.dwPageSize >> VHD_SECTOR_SHIFT; +#elif defined( _SC_PAGESIZE) + spp = sysconf( _SC_PAGESIZE ) >> VHD_SECTOR_SHIFT; +#else + spp = getpagesize() >> VHD_SECTOR_SHIFT; +#endif + next = 0; + + for (i = 0; i < vhd->bat.entries; i++) { + blk = vhd->bat.bat[i]; + + if (blk != DD_BLK_UNUSED) { + end = blk + vhd->spb + vhd->bm_secs; + next = MAX(next, end); + } + } + + return next; +} + +static inline int +in_range(off_t off, off_t start, off_t size) +{ + return (start < off && start + size > off); +} + +#define SKIP_HEADER 0x01 +#define SKIP_BAT 0x02 +#define SKIP_BATMAP 0x04 +#define SKIP_PLOC 0x08 +#define SKIP_DATA 0x10 + +static inline int +skip_check(int mode, int type) +{ + return mode & type; +} + +static int +vhd_check_for_clobber(vhd_context_t *vhd, off_t off, int mode) +{ + size_t i, n; + char *msg; + size_t size; + vhd_block_t fb; + vhd_parent_locator_t *loc; + + msg = NULL; + + if (!vhd_type_dynamic(vhd)) + return 0; + + if (off < VHD_SECTOR_SIZE) { + msg = "backup footer"; + goto fail; + } + + if (!skip_check(mode, SKIP_HEADER)) + if (in_range(off, + (off_t)vhd->footer.data_offset, sizeof(vhd_header_t))) { + msg = "header"; + goto fail; + } + + if (!skip_check(mode, SKIP_BAT)) + if (in_range(off, (off_t)vhd->header.table_offset, + (off_t)vhd_bytes_padded(vhd->header.max_bat_size * + sizeof(uint32_t)))) { + msg = "bat"; + goto fail; + } + + if (!skip_check(mode, SKIP_BATMAP)) + if (vhd_has_batmap(vhd) && + in_range(off, (off_t)vhd->batmap.header.batmap_offset, + (off_t)vhd_bytes_padded(vhd->batmap.header.batmap_size))) { + msg = "batmap"; + goto fail; + } + + if (!skip_check(mode, SKIP_PLOC)) { + n = sizeof(vhd->header.loc) / sizeof(vhd_parent_locator_t); + for (i = 0; i < n; i++) { + loc = vhd->header.loc + i; + if (loc->code == PLAT_CODE_NONE) + continue; + + size = vhd_parent_locator_size(loc); + if (in_range(off, (off_t)loc->data_offset, size)) { + msg = "parent locator"; + goto fail; + } + } + } + + if (!skip_check(mode, SKIP_DATA)) { + vhd_first_data_block(vhd, &fb); + if (fb.offset && in_range(off, + (off_t)vhd_sectors_to_bytes(fb.offset), + VHD_BLOCK_SIZE)) { + msg = "data block"; + goto fail; + } + } + + return 0; + +fail: + EPRINTF(("write to 0x%08"PRIx64" would clobber %s\n", (uintmax_t)off, msg)); + return -EINVAL; +} + +/* + * take any metadata after the bat (@eob) and shift it + */ +static int +vhd_shift_metadata(vhd_journal_t *journal, off_t eob, + size_t bat_needed, size_t map_needed) +{ + int i, n, err; + vhd_context_t *vhd; + size_t size_needed; + char *buf, **locators; + vhd_parent_locator_t *loc; + + vhd = &journal->vhd; + size_needed = bat_needed + map_needed; + + n = sizeof(vhd->header.loc) / sizeof(vhd_parent_locator_t); + + locators = calloc(n, sizeof(char *)); + if (!locators) + return -ENOMEM; + + for (i = 0; i < n; i++) { + size_t size; + + loc = vhd->header.loc + i; + if (loc->code == PLAT_CODE_NONE) + continue; + + if (loc->data_offset < eob) + continue; + + size = vhd_parent_locator_size(loc); +#ifdef _WIN32 + buf = _aligned_malloc( size, VHD_SECTOR_SIZE ); + if( buf == NULL ) { + err = -errno; + goto out; + } +#else + err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); + if (err) { + err = -err; + buf = NULL; + goto out; + } +#endif + + err = vhd_seek(vhd, (off_t)loc->data_offset, SEEK_SET); + if (err) + goto out; + + err = vhd_read(vhd, buf, size); + if (err) + goto out; + + locators[i] = buf; + } + + for (i = 0; i < n; i++) { + off_t off; + size_t size; + + if (!locators[i]) + continue; + + loc = vhd->header.loc + i; + off = (off_t)loc->data_offset + size_needed; + size = vhd_parent_locator_size(loc); + + if (vhd_check_for_clobber(vhd, off + size, SKIP_PLOC)) { + EPRINTF(("%s: shifting locator %d would clobber data\n", + vhd->file, i)); + return -EINVAL; + } + + err = vhd_seek(vhd, off, SEEK_SET); + if (err) + goto out; + + err = vhd_write(vhd, locators[i], size); + if (err) + goto out; + + free(locators[i]); + locators[i] = NULL; + loc->data_offset = off; + + /* write the new header after writing the new bat */ + } + + if (vhd_has_batmap(vhd) && vhd->batmap.header.batmap_offset > eob) { + vhd->batmap.header.batmap_offset += bat_needed; + + /* write the new batmap after writing the new bat */ + } + + err = 0; + +out: + for (i = 0; i < n; i++) +#ifdef _WIN32 + _aligned_free( locators[i] ); +#else + free( locators[i] ); +#endif + free(locators); + + return err; +} + +static int +vhd_add_bat_entries(vhd_journal_t *journal, int entries) +{ + size_t i; + int err; + off_t off; + vhd_bat_t new_bat; + vhd_context_t *vhd; + uint32_t new_entries; + vhd_batmap_t new_batmap; + uint64_t bat_size, new_bat_size, map_size, new_map_size; + + vhd = &journal->vhd; + new_entries = vhd->header.max_bat_size + entries; + + bat_size = vhd_bytes_padded(vhd->header.max_bat_size * + sizeof(uint32_t)); + new_bat_size = vhd_bytes_padded(new_entries * sizeof(uint32_t)); + + map_size = vhd_bytes_padded((vhd->header.max_bat_size + 7) >> 3); + new_map_size = vhd_bytes_padded((new_entries + 7) >> 3); + + off = (off_t)(vhd->header.table_offset + new_bat_size); + if (vhd_check_for_clobber(vhd, off, SKIP_BAT | SKIP_BATMAP)) { + EPRINTF(("%s: writing new bat of 0x%"PRIx64" bytes " + "at 0x%08"PRIx64" would clobber data\n", + vhd->file, new_bat_size, vhd->header.table_offset)); + return -EINVAL; + } + + if (vhd_has_batmap(vhd)) { + off = (off_t)(vhd->batmap.header.batmap_offset + new_map_size); + if (vhd_check_for_clobber(vhd, off, 0)) { + EPRINTF(("%s: writing new batmap of 0x%"PRIx64" bytes" + " at 0x%08"PRIx64" would clobber data\n", vhd->file, + new_map_size, vhd->batmap.header.batmap_offset)); + return -EINVAL; + } + } + + /* update header */ + vhd->header.max_bat_size = new_entries; + err = vhd_write_header(vhd, &vhd->header); + if (err) + return err; + + /* update footer */ + vhd->footer.curr_size = (uint64_t)new_entries * vhd->header.block_size; + vhd->footer.geometry = vhd_chs(vhd->footer.curr_size); + vhd->footer.checksum = vhd_checksum_footer(&vhd->footer); + err = vhd_write_footer(vhd, &vhd->footer); + if (err) + return err; + + /* allocate new bat */ +#ifdef _WIN32 + new_bat.bat = _aligned_malloc( (size_t)new_bat_size, VHD_SECTOR_SIZE ); + if( new_bat.bat == NULL ) + return -errno; +#else + err = posix_memalign((void **)&new_bat.bat, VHD_SECTOR_SIZE, new_bat_size); + if (err) + return -err; +#endif + + new_bat.spb = vhd->bat.spb; + new_bat.entries = new_entries; + memcpy(new_bat.bat, vhd->bat.bat, (size_t)bat_size); + for (i = vhd->bat.entries; i < new_entries; i++) + new_bat.bat[i] = DD_BLK_UNUSED; + + /* write new bat */ + err = vhd_write_bat(vhd, &new_bat); + if (err) { +#ifdef _WIN32 + _aligned_free( new_bat.bat ); +#else + free(new_bat.bat); +#endif + return err; + } + + /* update in-memory bat */ +#ifdef _WIN32 + _aligned_free( vhd->bat.bat ); +#else + free(vhd->bat.bat); +#endif + vhd->bat = new_bat; + + if (!vhd_has_batmap(vhd)) + return 0; + + /* allocate new batmap */ +#ifdef _WIN32 + new_batmap.map = _aligned_malloc( (size_t)new_map_size, VHD_SECTOR_SIZE ); + if( new_batmap.map == NULL ) + return -errno; +#else + err = posix_memalign((void **)&new_batmap.map, + VHD_SECTOR_SIZE, new_map_size); + if (err) + return err; +#endif + new_batmap.header = vhd->batmap.header; + new_batmap.header.batmap_size = (u32)secs_round_up_no_zero(new_map_size); + memcpy(new_batmap.map, vhd->batmap.map, (size_t)map_size); + memset(new_batmap.map + map_size, 0, (size_t)(new_map_size - map_size)); + + /* write new batmap */ + err = vhd_write_batmap(vhd, &new_batmap); + if (err) { +#ifdef _WIN32 + _aligned_free( new_batmap.map ); +#else + free( new_batmap.map ); +#endif + return err; + } + + /* update in-memory batmap */ +#ifdef _WIN32 + _aligned_free( vhd->batmap.map ); +#else + free( vhd->batmap.map ); +#endif + vhd->batmap = new_batmap; + + return 0; +} + +static int +vhd_dynamic_grow(vhd_journal_t *journal, uint64_t secs) +{ + int err; + off_t eob, eom; + vhd_context_t *vhd; + vhd_block_t first_block; + uint64_t blocks, size_needed; + uint64_t bat_needed, bat_size, bat_avail, bat_bytes, bat_secs; + uint64_t map_needed, map_size, map_avail, map_bytes, map_secs; + + vhd = &journal->vhd; + + size_needed = 0; + bat_needed = 0; + map_needed = 0; + + /* number of vhd blocks to add */ + blocks = secs_to_blocks_up(vhd, secs); + + /* size in bytes needed for new bat entries */ + bat_needed = blocks * sizeof(uint32_t); + map_needed = (blocks >> 3) + 1; + + /* available bytes in current bat */ + bat_bytes = vhd->header.max_bat_size * sizeof(uint32_t); + bat_secs = secs_round_up_no_zero(bat_bytes); + bat_size = vhd_sectors_to_bytes(bat_secs); + bat_avail = bat_size - bat_bytes; + + if (vhd_has_batmap(vhd)) { + /* avaliable bytes in current batmap */ + map_bytes = (vhd->header.max_bat_size + 7) >> 3; + map_secs = vhd->batmap.header.batmap_size; + map_size = vhd_sectors_to_bytes(map_secs); + map_avail = map_size - map_bytes; + } else { + map_needed = 0; + map_avail = 0; + } + + /* we have enough space already; just extend the bat */ + if (bat_needed <= bat_avail && map_needed <= map_avail) + goto add_entries; + + /* we need to add new sectors to the bat */ + if (bat_needed > bat_avail) { + bat_needed -= bat_avail; + bat_needed = vhd_bytes_padded(bat_needed); + } else + bat_needed = 0; + + /* we need to add new sectors to the batmap */ + if (map_needed > map_avail) { + map_needed -= map_avail; + map_needed = vhd_bytes_padded(map_needed); + } else + map_needed = 0; + + /* how many additional bytes do we need? */ + size_needed = bat_needed + map_needed; + + /* calculate space between end of headers and beginning of data */ + err = vhd_end_of_headers(vhd, &eom); + if (err) + return err; + + eob = (off_t)(vhd->header.table_offset + vhd_sectors_to_bytes(bat_secs)); + vhd_first_data_block(vhd, &first_block); + + /* no blocks allocated; just shift post-bat metadata */ + if (!first_block.offset) + goto shift_metadata; + + /* + * not enough space -- + * move vhd data blocks to the end of the file to make room + */ + do { + off_t new_off, bm_size, gap_size; + + new_off = (off_t)vhd_sectors_to_bytes(vhd_next_block_offset(vhd)); + + /* data region of segment should begin on page boundary */ + bm_size = (off_t)vhd_sectors_to_bytes(vhd->bm_secs); + if ((new_off + bm_size) % 4096) { + gap_size = 4096 - ((new_off + bm_size) % 4096); + + err = vhd_write_zeros(journal, new_off, gap_size); + if (err) + return err; + + new_off += gap_size; + } + + err = vhd_move_block(journal, first_block.block, new_off); + if (err) + return err; + + vhd_first_data_block(vhd, &first_block); + + } while (eom + size_needed >= vhd_sectors_to_bytes(first_block.offset)); + + TEST_FAIL_AT(FAIL_RESIZE_DATA_MOVED); + +shift_metadata: + /* shift any metadata after the bat to make room for new bat sectors */ + err = vhd_shift_metadata(journal, eob, (size_t)bat_needed, (size_t)map_needed); + if (err) + return err; + + TEST_FAIL_AT(FAIL_RESIZE_METADATA_MOVED); + +add_entries: + return vhd_add_bat_entries(journal, (int)blocks); +} + +static int +vhd_dynamic_resize(vhd_journal_t *journal, uint64_t size) +{ + int err; + vhd_context_t *vhd; + uint64_t cur_secs, new_secs; + + vhd = &journal->vhd; + cur_secs = vhd->footer.curr_size >> VHD_SECTOR_SHIFT; + new_secs = size << (20 - VHD_SECTOR_SHIFT); + + if (cur_secs == new_secs) + return 0; + + err = vhd_get_header(vhd); + if (err) + return err; + + err = vhd_get_bat(vhd); + if (err) + return err; + + if (vhd_has_batmap(vhd)) { + err = vhd_get_batmap(vhd); + if (err) + return err; + } + + if (cur_secs > new_secs) + err = vhd_dynamic_shrink(journal, cur_secs - new_secs); + else + err = vhd_dynamic_grow(journal, new_secs - cur_secs); + + return err; +} + +static int +vhd_util_resize_check_creator(const char *name) +{ + int err; + vhd_context_t vhd; + + err = vhd_open(&vhd, name, VHD_OPEN_RDONLY | VHD_OPEN_STRICT); + if (err) { + printf("error opening %s: %d\n", name, err); + return err; + } + + if (!vhd_creator_tapdisk(&vhd)) { + printf("%s not created by xen; resize not supported\n", name); + err = -EINVAL; + } + + vhd_close(&vhd); + return err; +} + +int +vhd_util_resize(int argc, char **argv) +{ + char *name, *jname; + uint64_t size; + int c, err, jerr; + vhd_journal_t journal; + vhd_context_t *vhd; + + err = -EINVAL; + size = 0; + name = NULL; + jname = NULL; + + optind = 0; + while ((c = getopt(argc, argv, "n:j:s:h")) != -1) { + switch (c) { + case 'n': + name = optarg; + break; + case 'j': + jname = optarg; + break; + case 's': + err = 0; + size = strtoull(optarg, NULL, 10); + break; + case 'h': + default: + goto usage; + } + } + + if (err || !name || !jname || argc != optind) + goto usage; + + err = vhd_util_resize_check_creator(name); + if (err) + return err; + + libvhd_set_log_level(1); + err = vhd_journal_create(&journal, name, jname); + if (err) { + printf("creating journal failed: %d\n", err); + return err; + } + + vhd = &journal.vhd; + + err = vhd_get_footer(vhd); + if (err) + goto out; + + TEST_FAIL_AT(FAIL_RESIZE_BEGIN); + + if (vhd_type_dynamic(vhd)) + err = vhd_dynamic_resize(&journal, size); + else + err = vhd_fixed_resize(&journal, size); + + TEST_FAIL_AT(FAIL_RESIZE_END); + +out: + if (err) { + printf("resize failed: %d\n", err); + jerr = vhd_journal_revert(&journal); + } else + jerr = vhd_journal_commit(&journal); + + if (jerr) { + printf("closing journal failed: %d\n", jerr); + vhd_journal_close(&journal); + } else + vhd_journal_remove(&journal); + + return (err ? err : jerr); + +usage: + printf("options: <-n name> <-j journal> <-s size (in MB)> [-h help]\n"); + return -EINVAL; +} diff --git a/extracters/vhd-util/vhd-util-revert.c b/extracters/vhd-util/vhd-util-revert.c new file mode 100644 index 0000000..6add4a1 --- /dev/null +++ b/extracters/vhd-util/vhd-util-revert.c @@ -0,0 +1,108 @@ +/* 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. + * + * Altering operations: + * + * 1. Change the parent pointer to another file. + * 2. Change the size of the file containing the VHD image. This does NOT + * affect the VHD disk capacity, only the physical size of the file containing + * the VHD. Naturally, it is not possible to set the file size to be less than + * the what VHD utilizes. + * The operation doesn't actually change the file size, but it writes the + * footer in the right location such that resizing the file (manually, as a + * separate step) will produce the correct results. If the new file size is + * greater than the current file size, the file must first be expanded and then + * altered with this operation. If the new size is smaller than the current + * size, the VHD must first be altered with this operation and then the file + * must be shrunk. Failing to resize the file will result in a corrupted VHD. +*/ + +#include +/*#include */ +#include +/*#include */ +#ifndef _WIN32 +#include +#endif + +#include "libvhd.h" +#include "libvhd-journal.h" + +int +vhd_util_revert(int argc, char **argv) +{ + char *name, *jname; + vhd_journal_t journal; + int c, err; + + name = NULL; + jname = NULL; + + optind = 0; + while ((c = getopt(argc, argv, "n:j:h")) != -1) { + switch (c) { + case 'n': + name = optarg; + break; + case 'j': + jname = optarg; + break; + case 'h': + default: + goto usage; + } + } + + if (!name || !jname || argc != optind) + goto usage; + + libvhd_set_log_level(1); + err = vhd_journal_open(&journal, name, jname); + if (err) { + printf("opening journal failed: %d\n", err); + return err; + } + + err = vhd_journal_revert(&journal); + if (err) { + printf("reverting journal failed: %d\n", err); + vhd_journal_close(&journal); + return err; + } + + err = vhd_journal_remove(&journal); + if (err) { + printf("removing journal failed: %d\n", err); + vhd_journal_close(&journal); + return err; + } + + return 0; + +usage: + printf("options: <-n name> <-j journal> [-h help]\n"); + return -EINVAL; +} diff --git a/extracters/vhd-util/vhd-util-scan.c b/extracters/vhd-util/vhd-util-scan.c new file mode 100644 index 0000000..363ad65 --- /dev/null +++ b/extracters/vhd-util/vhd-util-scan.c @@ -0,0 +1,1319 @@ +/* Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif +#include +#include /* for basename() */ +#include + +#include "list.h" +#include "libvhd.h" +#include "lvm-util.h" + +#define VHD_SCAN_FAST 0x01 +#define VHD_SCAN_PRETTY 0x02 +#define VHD_SCAN_VOLUME 0x04 +#define VHD_SCAN_NOFAIL 0x08 +#define VHD_SCAN_VERBOSE 0x10 +#define VHD_SCAN_PARENTS 0x20 + +#define VHD_TYPE_RAW_FILE 0x01 +#define VHD_TYPE_VHD_FILE 0x02 +#define VHD_TYPE_RAW_VOLUME 0x04 +#define VHD_TYPE_VHD_VOLUME 0x08 + +static inline int +target_volume(uint8_t type) +{ + return (type == VHD_TYPE_RAW_VOLUME || type == VHD_TYPE_VHD_VOLUME); +} + +static inline int +target_vhd(uint8_t type) +{ + return (type == VHD_TYPE_VHD_FILE || type == VHD_TYPE_VHD_VOLUME); +} + +struct target { + char name[VHD_MAX_NAME_LEN]; + char device[VHD_MAX_NAME_LEN]; + uint64_t size; + uint64_t start; + uint64_t end; + uint8_t type; +}; + +struct iterator { + int cur; + int cur_size; + int max_size; + struct target *targets; +}; + +struct vhd_image { + char *name; + char *parent; + uint64_t capacity; + off_t size; + uint8_t hidden; + int error; + char *message; + + struct target *target; + + struct list_head sibling; + struct list_head children; + struct vhd_image *parent_image; +}; + +struct vhd_scan { + int cur; + int size; + + int lists_cur; + int lists_size; + + struct vhd_image **images; + struct vhd_image **lists; +}; + +static int flags; +static struct vg vg; +static struct vhd_scan scan; + +static int +vhd_util_scan_pretty_allocate_list(int cnt) +{ + int i; + struct vhd_image *list; + + memset(&scan, 0, sizeof(scan)); + + scan.lists_cur = 1; + scan.lists_size = 10; + + scan.lists = calloc(scan.lists_size, sizeof(struct vhd_image *)); + if (!scan.lists) + goto fail; + + scan.lists[0] = calloc(cnt, sizeof(struct vhd_image)); + if (!scan.lists[0]) + goto fail; + + scan.images = calloc(cnt, sizeof(struct vhd_image *)); + if (!scan.images) + goto fail; + + for (i = 0; i < cnt; i++) + scan.images[i] = scan.lists[0] + i; + + scan.cur = 0; + scan.size = cnt; + + return 0; + +fail: + if (scan.lists) { + free(scan.lists[0]); + free(scan.lists); + } + + free(scan.images); + memset(&scan, 0, sizeof(scan)); + return -ENOMEM; +} + +static void +vhd_util_scan_pretty_free_list(void) +{ + int i; + + if (scan.lists) { + for (i = 0; i < scan.lists_cur; i++) + free(scan.lists[i]); + free(scan.lists); + } + + free(scan.images); + memset(&scan, 0, sizeof(scan)); +} + +static int +vhd_util_scan_pretty_add_image(struct vhd_image *image) +{ + int i; + struct vhd_image *img; + + for (i = 0; i < scan.cur; i++) { + img = scan.images[i]; + if (!strcmp(img->name, image->name)) + return 0; + } + + if (scan.cur >= scan.size) { + struct vhd_image *new, **list; + + if (scan.lists_cur >= scan.lists_size) { + list = realloc(scan.lists, scan.lists_size * 2 * + sizeof(struct vhd_image *)); + if (!list) + return -ENOMEM; + + scan.lists_size *= 2; + scan.lists = list; + } + + new = calloc(scan.size, sizeof(struct vhd_image)); + if (!new) + return -ENOMEM; + + scan.lists[scan.lists_cur++] = new; + scan.size *= 2; + + list = realloc(scan.images, scan.size * + sizeof(struct vhd_image *)); + if (!list) + return -ENOMEM; + + scan.images = list; + for (i = 0; i + scan.cur < scan.size; i++) + scan.images[i + scan.cur] = new + i; + } + + img = scan.images[scan.cur]; + INIT_LIST_HEAD(&img->sibling); + INIT_LIST_HEAD(&img->children); + + img->capacity = image->capacity; + img->size = image->size; + img->hidden = image->hidden; + img->error = image->error; + img->message = image->message; + + img->name = strdup(image->name); + if (!img->name) + goto fail; + + if (image->parent) { + img->parent = strdup(image->parent); + if (!img->parent) + goto fail; + } + + scan.cur++; + return 0; + +fail: + free(img->name); + free(img->parent); + memset(img, 0, sizeof(*img)); + return -ENOMEM; +} + +static int +vhd_util_scan_pretty_image_compare(const void *lhs, const void *rhs) +{ + struct vhd_image *l, *r; + + l = *(struct vhd_image **)lhs; + r = *(struct vhd_image **)rhs; + + return strcmp(l->name, r->name); +} + +static void +vhd_util_scan_print_image_indent(struct vhd_image *image, int tab) +{ + char *pad, *name, *pmsg, *parent; + + pad = (tab ? " " : ""); + name = image->name; + parent = (image->parent ? : "none"); + + if ((flags & VHD_SCAN_PRETTY) && image->parent && !image->parent_image) + pmsg = " (not found in scan)"; + else + pmsg = ""; + + if (!(flags & VHD_SCAN_VERBOSE)) { + name = basename(image->name); + if (image->parent) + parent = basename(image->parent); + } + + if (image->error) + printf("%*svhd=%s scan-error=%d error-message='%s'\n", + tab, pad, image->name, image->error, image->message); + else + printf("%*svhd=%s capacity=%"PRIu64" size=%"PRIu64" hidden=%u " + "parent=%s%s\n", tab, pad, name, image->capacity, + image->size, image->hidden, parent, pmsg); +} + +static void +vhd_util_scan_pretty_print_tree(struct vhd_image *image, int depth) +{ + struct vhd_image *img, *tmp; + + vhd_util_scan_print_image_indent(image, depth * 3); + + list_for_each_entry_safe(img, tmp, &image->children, sibling) + if (!img->hidden) + vhd_util_scan_pretty_print_tree(img, depth + 1); + + list_for_each_entry_safe(img, tmp, &image->children, sibling) + if (img->hidden) + vhd_util_scan_pretty_print_tree(img, depth + 1); + + free(image->name); + free(image->parent); + + image->name = NULL; + image->parent = NULL; +} + +static void +vhd_util_scan_pretty_print_images(void) +{ + int i; + struct vhd_image *image, **parentp, *parent, *keyp, key; + + qsort(scan.images, scan.cur, sizeof(scan.images[0]), + vhd_util_scan_pretty_image_compare); + + for (i = 0; i < scan.cur; i++) { + image = scan.images[i]; + + if (!image->parent) { + image->parent_image = NULL; + continue; + } + + memset(&key, 0, sizeof(key)); + key.name = image->parent; + keyp = &key; + + parentp = bsearch(&keyp, scan.images, scan.cur, + sizeof(scan.images[0]), + vhd_util_scan_pretty_image_compare); + if (!parentp) { + image->parent_image = NULL; + continue; + } + + parent = *parentp; + image->parent_image = parent; + list_add_tail(&image->sibling, &parent->children); + } + + for (i = 0; i < scan.cur; i++) { + image = scan.images[i]; + + if (image->parent_image || !image->hidden) + continue; + + vhd_util_scan_pretty_print_tree(image, 0); + } + + for (i = 0; i < scan.cur; i++) { + image = scan.images[i]; + + if (!image->name || image->parent_image) + continue; + + vhd_util_scan_pretty_print_tree(image, 0); + } + + for (i = 0; i < scan.cur; i++) { + image = scan.images[i]; + + if (!image->name) + continue; + + vhd_util_scan_pretty_print_tree(image, 0); + } +} + +static void +vhd_util_scan_print_image(struct vhd_image *image) +{ + int err; + + if (!image->error && (flags & VHD_SCAN_PRETTY)) { + err = vhd_util_scan_pretty_add_image(image); + if (!err) + return; + + if (!image->error) { + image->error = err; + image->message = "allocating memory"; + } + } + + vhd_util_scan_print_image_indent(image, 0); +} + +static int +vhd_util_scan_error(const char *file, int err) +{ + struct vhd_image image; + + memset(&image, 0, sizeof(image)); + image.name = (char *)file; + image.error = err; + image.message = "failure scanning target"; + + vhd_util_scan_print_image(&image); + + /* + if (flags & VHD_SCAN_NOFAIL) + return 0; + */ + + return err; +} + +static vhd_parent_locator_t * +vhd_util_scan_get_parent_locator(vhd_context_t *vhd) +{ + int i; + vhd_parent_locator_t *loc; + + loc = NULL; + + for (i = 0; i < 8; i++) { + if (vhd->header.loc[i].code == PLAT_CODE_MACX) { + loc = vhd->header.loc + i; + break; + } + + if (vhd->header.loc[i].code == PLAT_CODE_W2RU) + loc = vhd->header.loc + i; + + if (!loc && vhd->header.loc[i].code != PLAT_CODE_NONE) + loc = vhd->header.loc + i; + } + + return loc; +} + +static inline int +copy_name(char *dst, const char *src) +{ + if (snprintf(dst, VHD_MAX_NAME_LEN, "%s", src) < VHD_MAX_NAME_LEN) + return 0; + + return -ENAMETOOLONG; +} + +/* + * LVHD stores realpath(parent) in parent locators, so + * /dev// becomes /dev/mapper/- + */ +static int +vhd_util_scan_extract_volume_name(char *dst, const char *src) +{ + int err; + char copy[VHD_MAX_NAME_LEN], *name, *s, *c; + + name = strrchr(src, '/'); + if (!name) + name = (char *)src; + + /* convert single dashes to slashes, double dashes to single dashes */ + for (c = copy, s = name; *s != '\0'; s++, c++) { + if (*s == '-') { + if (s[1] != '-') + *c = '/'; + else { + s++; + *c = '-'; + } + } else + *c = *s; + } + + *c = '\0'; + c = strrchr(copy, '/'); + if (c == name) { + /* unrecognized format */ + strcpy(dst, src); + return -EINVAL; + } + + strcpy(dst, ++c); + return 0; +} + +static int +vhd_util_scan_get_volume_parent(vhd_context_t *vhd, struct vhd_image *image) +{ + int err; + char name[VHD_MAX_NAME_LEN]; + vhd_parent_locator_t *loc, copy; + + if (flags & VHD_SCAN_FAST) { + err = vhd_header_decode_parent(vhd, + &vhd->header, &image->parent); + if (!err) + goto found; + } + + loc = vhd_util_scan_get_parent_locator(vhd); + if (!loc) + return -EINVAL; + + copy = *loc; + copy.data_offset += image->target->start; + err = vhd_parent_locator_read(vhd, ©, &image->parent); + if (err) + return err; + +found: + err = vhd_util_scan_extract_volume_name(name, image->parent); + if (!err) + return copy_name(image->parent, name); + + return 0; +} + +static int +vhd_util_scan_get_parent(vhd_context_t *vhd, struct vhd_image *image) +{ + int i, err; + vhd_parent_locator_t *loc; + + if (!target_vhd(image->target->type)) { + image->parent = NULL; + return 0; + } + + loc = NULL; + + if (target_volume(image->target->type)) + return vhd_util_scan_get_volume_parent(vhd, image); + + if (flags & VHD_SCAN_FAST) { + err = vhd_header_decode_parent(vhd, + &vhd->header, &image->parent); + if (!err) + return 0; + } else { + /* + * vhd_parent_locator_get checks for the existence of the + * parent file. if this call succeeds, all is well; if not, + * we'll try to return whatever string we have before failing + * outright. + */ + err = vhd_parent_locator_get(vhd, &image->parent); + if (!err) + return 0; + } + + loc = vhd_util_scan_get_parent_locator(vhd); + if (!loc) + return -EINVAL; + + return vhd_parent_locator_read(vhd, loc, &image->parent); +} + +static int +vhd_util_scan_get_hidden(vhd_context_t *vhd, struct vhd_image *image) +{ + int err, hidden; + + err = 0; + hidden = 0; + + if (target_vhd(image->target->type)) + err = vhd_hidden(vhd, &hidden); + else + hidden = 1; + + if (err) + return err; + + image->hidden = hidden; + return 0; +} + +static int +vhd_util_scan_get_size(vhd_context_t *vhd, struct vhd_image *image) +{ + image->size = image->target->size; + + if (target_vhd(image->target->type)) + image->capacity = vhd->footer.curr_size; + else + image->capacity = image->size; + + return 0; +} + +static int +vhd_util_scan_open_file(vhd_context_t *vhd, struct vhd_image *image) +{ + int err, vhd_flags; + + if (!target_vhd(image->target->type)) + return 0; + + vhd_flags = VHD_OPEN_RDONLY | VHD_OPEN_IGNORE_DISABLED; + if (flags & VHD_SCAN_FAST) + vhd_flags |= VHD_OPEN_FAST; + + err = vhd_open(vhd, image->name, vhd_flags); + if (err) { + vhd->file = NULL; + image->message = "opening file"; + image->error = err; + return image->error; + } + + return 0; +} + +static int +vhd_util_scan_read_volume_headers(vhd_context_t *vhd, struct vhd_image *image) +{ + int err; + char *buf; + size_t size; + struct target *target; + + buf = NULL; + target = image->target; + size = sizeof(vhd_footer_t) + sizeof(vhd_header_t); + + err = posix_memalign((void **)&buf, VHD_SECTOR_SIZE, size); + if (err) { + buf = NULL; + image->message = "allocating image"; + image->error = -err; + goto out; + } + + err = vhd_seek(vhd, target->start, SEEK_SET); + if (err) { + image->message = "seeking to headers"; + image->error = err; + goto out; + } + + err = vhd_read(vhd, buf, size); + if (err) { + image->message = "reading headers"; + image->error = err; + goto out; + } + + memcpy(&vhd->footer, buf, sizeof(vhd_footer_t)); + vhd_footer_in(&vhd->footer); + err = vhd_validate_footer(&vhd->footer); + if (err) { + image->message = "invalid footer"; + image->error = err; + goto out; + } + + /* lvhd vhds should always be dynamic */ + if (vhd_type_dynamic(vhd)) { + if (vhd->footer.data_offset != sizeof(vhd_footer_t)) + err = vhd_read_header_at(vhd, &vhd->header, + vhd->footer.data_offset + + target->start); + else { + memcpy(&vhd->header, + buf + sizeof(vhd_footer_t), + sizeof(vhd_header_t)); + vhd_header_in(&vhd->header); + err = vhd_validate_header(&vhd->header); + } + + if (err) { + image->message = "reading header"; + image->error = err; + goto out; + } + + vhd->spb = vhd->header.block_size >> VHD_SECTOR_SHIFT; + vhd->bm_secs = secs_round_up_no_zero(vhd->spb >> 3); + } + +out: + free(buf); + return image->error; +} + +static int +vhd_util_scan_open_volume(vhd_context_t *vhd, struct vhd_image *image) +{ + int err; + struct target *target; + + target = image->target; + memset(vhd, 0, sizeof(*vhd)); + vhd->oflags = VHD_OPEN_RDONLY | VHD_OPEN_FAST; + + if (target->end - target->start < 4096) { + image->message = "device too small"; + image->error = -EINVAL; + return image->error; + } + + vhd->file = strdup(image->name); + if (!vhd->file) { + image->message = "allocating device"; + image->error = -ENOMEM; + return image->error; + } + + vhd->fd = open(target->device, O_RDONLY | O_DIRECT | O_LARGEFILE); + if (vhd->fd == -1) { + free(vhd->file); + vhd->file = NULL; + + image->message = "opening device"; + image->error = -errno; + return image->error; + } + + if (target_vhd(target->type)) + return vhd_util_scan_read_volume_headers(vhd, image); + + return 0; +} + +static int +vhd_util_scan_open(vhd_context_t *vhd, struct vhd_image *image) +{ + struct target *target; + + target = image->target; + + if (target_volume(image->target->type) || !(flags & VHD_SCAN_PRETTY)) + image->name = target->name; + else { + image->name = realpath(target->name, NULL); + if (!image->name) { + image->name = target->name; + image->message = "resolving name"; + image->error = -errno; + return image->error; + } + } + + if (target_volume(target->type)) + return vhd_util_scan_open_volume(vhd, image); + else + return vhd_util_scan_open_file(vhd, image); +} + +static int +vhd_util_scan_init_file_target(struct target *target, + const char *file, uint8_t type) +{ + int err; + struct stat stats; + + err = stat(file, &stats); + if (err == -1) + return -errno; + + err = copy_name(target->name, file); + if (err) + return err; + + err = copy_name(target->device, file); + if (err) + return err; + + target->type = type; + target->start = 0; + target->size = stats.st_size; + target->end = stats.st_size; + + return 0; +} + +static int +vhd_util_scan_init_volume_target(struct target *target, + struct lv *lv, uint8_t type) +{ + int err; + + if (lv->first_segment.type != LVM_SEG_TYPE_LINEAR) + return -ENOSYS; + + err = copy_name(target->name, lv->name); + if (err) + return err; + + err = copy_name(target->device, lv->first_segment.device); + if (err) + return err; + + target->type = type; + target->size = lv->size; + target->start = lv->first_segment.pe_start; + target->end = target->start + lv->first_segment.pe_size; + + return 0; +} + +static int +iterator_init(struct iterator *itr, int cnt, struct target *targets) +{ + memset(itr, 0, sizeof(*itr)); + + itr->targets = malloc(sizeof(struct target) * cnt); + if (!itr->targets) + return -ENOMEM; + + memcpy(itr->targets, targets, sizeof(struct target) * cnt); + + itr->cur = 0; + itr->cur_size = cnt; + itr->max_size = cnt; + + return 0; +} + +static struct target * +iterator_next(struct iterator *itr) +{ + if (itr->cur == itr->cur_size) + return NULL; + + return itr->targets + itr->cur++; +} + +static int +iterator_add_file(struct iterator *itr, + struct target *target, const char *parent, uint8_t type) +{ + int i; + struct target *t; + char *lname, *rname; + + for (i = 0; i < itr->cur_size; i++) { + t = itr->targets + i; + lname = basename((char *)t->name); + rname = basename((char *)parent); + + if (!strcmp(lname, rname)) + return -EEXIST; + } + + return vhd_util_scan_init_file_target(target, parent, type); +} + +static int +iterator_add_volume(struct iterator *itr, + struct target *target, const char *parent, uint8_t type) +{ + int i, err; + struct lv *lv; + + lv = NULL; + err = -ENOENT; + + for (i = 0; i < itr->cur_size; i++) + if (!strcmp(parent, itr->targets[i].name)) + return -EEXIST; + + for (i = 0; i < vg.lv_cnt; i++) { + err = fnmatch(parent, vg.lvs[i].name, FNM_PATHNAME); + if (err != FNM_NOMATCH) { + lv = vg.lvs + i; + break; + } + } + + if (err && err != FNM_PATHNAME) + return err; + + if (!lv) + return -ENOENT; + + return vhd_util_scan_init_volume_target(target, lv, type); +} + +static int +iterator_add(struct iterator *itr, const char *parent, uint8_t type) +{ + int err; + struct target *target; + + if (itr->cur_size == itr->max_size) { + struct target *new; + + new = realloc(itr->targets, + sizeof(struct target) * + itr->max_size * 2); + if (!new) + return -ENOMEM; + + itr->max_size *= 2; + itr->targets = new; + } + + target = itr->targets + itr->cur_size; + + if (target_volume(type)) + err = iterator_add_volume(itr, target, parent, type); + else + err = iterator_add_file(itr, target, parent, type); + + if (err) + memset(target, 0, sizeof(*target)); + else + itr->cur_size++; + + return (err == -EEXIST ? 0 : err); +} + +static void +iterator_free(struct iterator *itr) +{ + free(itr->targets); + memset(itr, 0, sizeof(*itr)); +} + +static void +vhd_util_scan_add_parent(struct iterator *itr, + vhd_context_t *vhd, struct vhd_image *image) +{ + int err; + uint8_t type; + + if (vhd_parent_raw(vhd)) + type = target_volume(image->target->type) ? + VHD_TYPE_RAW_VOLUME : VHD_TYPE_RAW_FILE; + else + type = target_volume(image->target->type) ? + VHD_TYPE_VHD_VOLUME : VHD_TYPE_VHD_FILE; + + err = iterator_add(itr, image->parent, type); + if (err) + vhd_util_scan_error(image->parent, err); +} + +static int +vhd_util_scan_targets(int cnt, struct target *targets) +{ + int ret, err; + vhd_context_t vhd; + struct iterator itr; + struct target *target; + struct vhd_image image; + + ret = 0; + err = 0; + + err = iterator_init(&itr, cnt, targets); + if (err) + return err; + + while ((target = iterator_next(&itr))) { + memset(&vhd, 0, sizeof(vhd)); + memset(&image, 0, sizeof(image)); + + image.target = target; + + err = vhd_util_scan_open(&vhd, &image); + if (err) { + ret = -EAGAIN; + goto end; + } + + err = vhd_util_scan_get_size(&vhd, &image); + if (err) { + ret = -EAGAIN; + image.message = "getting physical size"; + image.error = err; + goto end; + } + + err = vhd_util_scan_get_hidden(&vhd, &image); + if (err) { + ret = -EAGAIN; + image.message = "checking 'hidden' field"; + image.error = err; + goto end; + } + + if (vhd.footer.type == HD_TYPE_DIFF) { + err = vhd_util_scan_get_parent(&vhd, &image); + if (err) { + ret = -EAGAIN; + image.message = "getting parent"; + image.error = err; + goto end; + } + } + + end: + vhd_util_scan_print_image(&image); + + if (flags & VHD_SCAN_PARENTS && image.parent) + vhd_util_scan_add_parent(&itr, &vhd, &image); + + if (vhd.file) + vhd_close(&vhd); + if (image.name != target->name) + free(image.name); + free(image.parent); + + if (err && !(flags & VHD_SCAN_NOFAIL)) + break; + } + + iterator_free(&itr); + + if (flags & VHD_SCAN_NOFAIL) + return ret; + + return err; +} + +static int +vhd_util_scan_targets_pretty(int cnt, struct target *targets) +{ + int err; + + err = vhd_util_scan_pretty_allocate_list(cnt); + if (err) { + printf("scan failed: no memory\n"); + return -ENOMEM; + } + + err = vhd_util_scan_targets(cnt, targets); + + vhd_util_scan_pretty_print_images(); + vhd_util_scan_pretty_free_list(); + + return ((flags & VHD_SCAN_NOFAIL) ? 0 : err); +} + +static int +vhd_util_scan_find_file_targets(int cnt, char **names, + const char *filter, + struct target **_targets, int *_total) +{ + glob_t g; + struct target *targets; + int i, globs, err, total; + + total = cnt; + globs = 0; + *_total = 0; + *_targets = NULL; + + memset(&g, 0, sizeof(g)); + + if (filter) { + int gflags = ((flags & VHD_SCAN_FAST) ? GLOB_NOSORT : 0); + + errno = 0; + err = glob(filter, gflags, vhd_util_scan_error, &g); + + switch (err) { + case GLOB_NOSPACE: + err = -ENOMEM; + break; + case GLOB_ABORTED: + err = -EIO; + break; + case GLOB_NOMATCH: + err = -errno; + break; + } + + if (err) { + vhd_util_scan_error(filter, err); + return err; + } + + globs = g.gl_pathc; + total += globs; + } + + targets = calloc(total, sizeof(struct target)); + if (!targets) { + err = -ENOMEM; + goto out; + } + + for (i = 0; i < g.gl_pathc; i++) { + err = vhd_util_scan_init_file_target(targets + i, + g.gl_pathv[i], + VHD_TYPE_VHD_FILE); + if (err) { + vhd_util_scan_error(g.gl_pathv[i], err); + if (!(flags & VHD_SCAN_NOFAIL)) + goto out; + } + } + + for (i = 0; i + globs < total; i++) { + err = vhd_util_scan_init_file_target(targets + i + globs, + names[i], + VHD_TYPE_VHD_FILE); + if (err) { + vhd_util_scan_error(names[i], err); + if (!(flags & VHD_SCAN_NOFAIL)) + goto out; + } + } + + err = 0; + *_total = total; + *_targets = targets; + +out: + if (err) + free(targets); + if (filter) + globfree(&g); + + return err; +} + +static inline void +swap_volume(struct lv *lvs, int dst, int src) +{ + struct lv copy, *ldst, *lsrc; + + if (dst == src) + return; + + lsrc = lvs + src; + ldst = lvs + dst; + + memcpy(©, ldst, sizeof(copy)); + memcpy(ldst, lsrc, sizeof(*ldst)); + memcpy(lsrc, ©, sizeof(copy)); +} + +static int +vhd_util_scan_sort_volumes(struct lv *lvs, int cnt, + const char *filter, int *_matches) +{ + struct lv *lv; + int i, err, matches; + + matches = 0; + *_matches = 0; + + if (!filter) + return 0; + + for (i = 0; i < cnt; i++) { + lv = lvs + i; + + err = fnmatch(filter, lv->name, FNM_PATHNAME); + if (err) { + if (err != FNM_NOMATCH) { + vhd_util_scan_error(lv->name, err); + if (!(flags & VHD_SCAN_NOFAIL)) + return err; + } + + continue; + } + + swap_volume(lvs, matches++, i); + } + + *_matches = matches; + return 0; +} + +static int +vhd_util_scan_find_volume_targets(int cnt, char **names, + const char *volume, const char *filter, + struct target **_targets, int *_total) +{ + struct target *targets; + int i, err, total, matches; + + *_total = 0; + *_targets = NULL; + targets = NULL; + + err = lvm_scan_vg(volume, &vg); + if (err) + return err; + + err = vhd_util_scan_sort_volumes(vg.lvs, vg.lv_cnt, + filter, &matches); + if (err) + goto out; + + total = matches; + for (i = 0; i < cnt; i++) { + err = vhd_util_scan_sort_volumes(vg.lvs + total, + vg.lv_cnt - total, + names[i], &matches); + if (err) + goto out; + + total += matches; + } + + targets = calloc(total, sizeof(struct target)); + if (!targets) { + err = -ENOMEM; + goto out; + } + + for (i = 0; i < total; i++) { + err = vhd_util_scan_init_volume_target(targets + i, + vg.lvs + i, + VHD_TYPE_VHD_VOLUME); + if (err) { + vhd_util_scan_error(vg.lvs[i].name, err); + if (!(flags & VHD_SCAN_NOFAIL)) + goto out; + } + } + + err = 0; + *_total = total; + *_targets = targets; + +out: + if (err) + free(targets); + return err; +} + +static int +vhd_util_scan_find_targets(int cnt, char **names, + const char *volume, const char *filter, + struct target **targets, int *total) +{ + if (flags & VHD_SCAN_VOLUME) + return vhd_util_scan_find_volume_targets(cnt, names, + volume, filter, + targets, total); + return vhd_util_scan_find_file_targets(cnt, names, + filter, targets, total); +} + +int +vhd_util_scan(int argc, char **argv) +{ + int c, ret, err, cnt; + char *filter, *volume; + struct target *targets; + + cnt = 0; + ret = 0; + err = 0; + flags = 0; + filter = NULL; + volume = NULL; + targets = NULL; + + optind = 0; + while ((c = getopt(argc, argv, "m:fcl:pavh")) != -1) { + switch (c) { + case 'm': + filter = optarg; + break; + case 'f': + flags |= VHD_SCAN_FAST; + break; + case 'c': + flags |= VHD_SCAN_NOFAIL; + break; + case 'l': + volume = optarg; + flags |= VHD_SCAN_VOLUME; + break; + case 'p': + flags |= VHD_SCAN_PRETTY; + break; + case 'a': + flags |= VHD_SCAN_PARENTS; + break; + case 'v': + flags |= VHD_SCAN_VERBOSE; + break; + case 'h': + goto usage; + default: + err = -EINVAL; + goto usage; + } + } + + if (!filter && argc - optind == 0) { + err = -EINVAL; + goto usage; + } + + if (flags & VHD_SCAN_PRETTY) + flags &= ~VHD_SCAN_FAST; + + err = vhd_util_scan_find_targets(argc - optind, argv + optind, + volume, filter, &targets, &cnt); + if (err) { + printf("scan failed: %d\n", err); + return err; + } + + if (!cnt) + return 0; + + if (flags & VHD_SCAN_PRETTY) + err = vhd_util_scan_targets_pretty(cnt, targets); + else + err = vhd_util_scan_targets(cnt, targets); + + free(targets); + lvm_free_vg(&vg); + + return ((flags & VHD_SCAN_NOFAIL) ? 0 : err); + +usage: + printf("usage: [OPTIONS] FILES\n" + "options: [-m match filter] [-f fast] [-c continue on failure] " + "[-l LVM volume] [-p pretty print] [-a scan parents] " + "[-v verbose] [-h help]\n"); + return err; +} diff --git a/extracters/vhd-util/vhd-util-set-field.c b/extracters/vhd-util/vhd-util-set-field.c new file mode 100644 index 0000000..efcc5ed --- /dev/null +++ b/extracters/vhd-util/vhd-util-set-field.c @@ -0,0 +1,106 @@ +/* Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif + +#include "libvhd.h" + +int +vhd_util_set_field(int argc, char **argv) +{ + long value; + int err, c; + vhd_context_t vhd; + char *name, *field; + + err = -EINVAL; + value = 0; + name = NULL; + field = NULL; + + if (!argc || !argv) + goto usage; + + optind = 0; + while ((c = getopt(argc, argv, "n:f:v:h")) != -1) { + switch (c) { + case 'n': + name = optarg; + break; + case 'f': + field = optarg; + break; + case 'v': + err = 0; + value = strtol(optarg, NULL, 10); + break; + case 'h': + default: + goto usage; + } + } + + if (!name || !field || optind != argc || err) + goto usage; + + if (strnlen(field, 25) >= 25) { + printf("invalid field\n"); + goto usage; + } + + if (strcmp(field, "hidden")) { + printf("invalid field %s\n", field); + goto usage; + } + + if (value < 0 || value > 255) { + printf("invalid value %ld\n", value); + goto usage; + } + + err = vhd_open(&vhd, name, VHD_OPEN_RDWR); + if (err) { + printf("error opening %s: %d\n", name, err); + return err; + } + + vhd.footer.hidden = (char)value; + + err = vhd_write_footer(&vhd, &vhd.footer); + + vhd_close(&vhd); + return err; + +usage: + printf("options: <-n name> <-f field> <-v value> [-h help]\n"); + return -EINVAL; +} diff --git a/extracters/vhd-util/vhd-util-snapshot.c b/extracters/vhd-util/vhd-util-snapshot.c new file mode 100644 index 0000000..c4b396a --- /dev/null +++ b/extracters/vhd-util/vhd-util-snapshot.c @@ -0,0 +1,225 @@ +/* Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif + +#include "libvhd.h" + +static int +vhd_util_find_snapshot_target(const char *name, char **result, int *parent_raw) +{ + size_t i; + int err; + char *target; + vhd_context_t vhd; + + *parent_raw = 0; + *result = NULL; + + target = strdup(name); + if (!target) + return -ENOMEM; + + for (;;) { + err = vhd_open(&vhd, target, VHD_OPEN_RDONLY); + if (err) + return err; + + if (vhd.footer.type != HD_TYPE_DIFF) + goto out; + + err = vhd_get_bat(&vhd); + if (err) + goto out; + + for (i = 0; i < vhd.bat.entries; i++) + if (vhd.bat.bat[i] != DD_BLK_UNUSED) + goto out; + + free(target); + err = vhd_parent_locator_get(&vhd, &target); + if (err) + goto out; + + if (vhd_parent_raw(&vhd)) { + *parent_raw = 1; + goto out; + } + + vhd_close(&vhd); + } + +out: + vhd_close(&vhd); + if (err) + free(target); + else + *result = target; + + return err; +} + +static int +vhd_util_check_depth(const char *name, int *depth) +{ + int err; + vhd_context_t vhd; + + err = vhd_open(&vhd, name, VHD_OPEN_RDONLY); + if (err) + return err; + + err = vhd_chain_depth(&vhd, depth); + vhd_close(&vhd); + + return err; +} + +int +vhd_util_snapshot(int argc, char **argv) +{ + vhd_flag_creat_t flags; + int c, err, prt_raw, limit; + char *name, *pname, *ppath, *backing; + uint64_t size; + vhd_context_t vhd; + + name = NULL; + pname = NULL; + ppath = NULL; + backing = NULL; + size = 0; + flags = 0; + limit = 0; + + if (!argc || !argv) { + err = -EINVAL; + goto usage; + } + + optind = 0; + while ((c = getopt(argc, argv, "n:p:l:mh")) != -1) { + switch (c) { + case 'n': + name = optarg; + break; + case 'p': + pname = optarg; + break; + case 'l': + limit = strtol(optarg, NULL, 10); + break; + case 'm': + vhd_flag_set(flags, VHD_FLAG_CREAT_PARENT_RAW); + break; + case 'h': + err = 0; + goto usage; + default: + err = -EINVAL; + goto usage; + } + } + + if (!name || !pname || optind != argc) { + err = -EINVAL; + goto usage; + } + + ppath = realpath(pname, NULL); + if (!ppath) + return -errno; +#ifdef _WIN32 + ppath++; +#endif + if (vhd_flag_test(flags, VHD_FLAG_CREAT_PARENT_RAW)) { + backing = strdup(ppath); + if (!backing) { + err = -ENOMEM; + goto out; + } + } else { + err = vhd_util_find_snapshot_target(ppath, &backing, &prt_raw); + if (err) { + backing = NULL; + goto out; + } + + /* + * if the sizes of the parent chain are non-uniform, we need to + * pick the right size: that of the supplied parent + */ + if (strcmp(ppath, backing)) { + err = vhd_open(&vhd, ppath, VHD_OPEN_RDONLY); + if (err) + goto out; + size = vhd.footer.curr_size; + vhd_close(&vhd); + } + + if (prt_raw) + vhd_flag_set(flags, VHD_FLAG_CREAT_PARENT_RAW); + } + + if (limit && !vhd_flag_test(flags, VHD_FLAG_CREAT_PARENT_RAW)) { + int depth; + + err = vhd_util_check_depth(backing, &depth); + if (err) + printf("error checking snapshot depth: %d\n", err); + else if (depth + 1 > limit) { + err = -ENOSPC; + printf("snapshot depth exceeded: " + "current depth: %d, limit: %d\n", depth, limit); + } + + if (err) + goto out; + } + + err = vhd_snapshot(name, size, pname, flags); + +out: +#ifdef _WIN32 + free( ppath - 1 ); +#else + free(ppath); +#endif + free(backing); + + return err; + +usage: + printf("options: <-n name> <-p parent name> [-l snapshot depth limit]" + " [-m parent_is_raw] [-h help]\n"); + return err; +} diff --git a/extracters/vhd-util/vhd-util.c b/extracters/vhd-util/vhd-util.c new file mode 100644 index 0000000..aa12e7a --- /dev/null +++ b/extracters/vhd-util/vhd-util.c @@ -0,0 +1,168 @@ +/* Copyright (c) 2008, XenSource Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of XenSource Inc. nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include +#include + +#include "libvhd.h" +#include "vhd-util.h" + +#ifndef _WIN32 +#if 1 +#define DFPRINTF(_f, _a...) fprintf(stdout, _f , ##_a) +#else +#define DFPRINTF(_f, _a...) ((void)0) +#endif +#endif + +typedef int (*vhd_util_func_t) (int, char **); + +struct command { + char *name; + vhd_util_func_t func; +}; + +struct command commands[] = { + { .name = "create", .func = vhd_util_create }, + { .name = "snapshot", .func = vhd_util_snapshot }, + { .name = "query", .func = vhd_util_query }, + { .name = "read", .func = vhd_util_read }, + { .name = "set", .func = vhd_util_set_field }, + { .name = "repair", .func = vhd_util_repair }, + { .name = "resize", .func = vhd_util_resize }, + { .name = "fill", .func = vhd_util_fill }, + { .name = "coalesce", .func = vhd_util_coalesce }, + { .name = "modify", .func = vhd_util_modify }, +#ifndef _WIN32 + { .name = "scan", .func = vhd_util_scan }, +#endif + {.name = "check", .func = vhd_util_check }, + { .name = "revert", .func = vhd_util_revert }, +}; + +#define print_commands() \ + do { \ + int i, n; \ + n = sizeof(commands) / sizeof(struct command); \ + printf("COMMAND := { "); \ + printf("%s", commands[0].name); \ + for (i = 1; i < n; i++) \ + printf(" | %s", commands[i].name); \ + printf(" }\n"); \ + } while (0) + +TEST_FAIL_EXTERN_VARS; + +void +help(void) +{ + printf("usage: vhd-util COMMAND [OPTIONS]\n"); + print_commands(); + exit(0); +} + +struct command * +get_command(char *command) +{ + int i, n; + + if (strnlen(command, 25) >= 25) + return NULL; + + n = sizeof(commands) / sizeof (struct command); + + for (i = 0; i < n; i++) + if (!strcmp(command, commands[i].name)) + return &commands[i]; + + return NULL; +} + +int +main(int argc, char *argv[]) +{ + char **cargv; + struct command *cmd; + int cargc, i, cnt, ret; + +#ifdef CORE_DUMP + #include + struct rlimit rlim; + rlim.rlim_cur = RLIM_INFINITY; + rlim.rlim_max = RLIM_INFINITY; + if (setrlimit(RLIMIT_CORE, &rlim) < 0) + fprintf(stderr, "setrlimit failed: %d\n", errno); +#endif + + ret = 0; + + libvhd_set_log_level(-1); + if (argc < 2) + help(); + + cargc = argc - 1; + cmd = get_command(argv[1]); + if (!cmd) { + fprintf(stderr, "invalid COMMAND %s\n", argv[1]); + help(); + } + + cargv = malloc(sizeof(char *) * cargc); + if (!cargv) + exit(ENOMEM); + + cnt = 1; + cargv[0] = cmd->name; + for (i = 1; i < cargc; i++) { + char *arg = argv[i + (argc - cargc)]; + + if (!strcmp(arg, "--debug")) { + libvhd_set_log_level(1); + continue; + } + + cargv[cnt++] = arg; + } + +#ifdef ENABLE_FAILURE_TESTING + for (i = 0; i < NUM_FAIL_TESTS; i++) { + TEST_FAIL[i] = 0; + if (getenv(ENV_VAR_FAIL[i])) + TEST_FAIL[i] = 1; + } +#endif /* ENABLE_FAILURE_TESTING */ + + ret = cmd->func(cnt, cargv); + + free(cargv); + + if( ret ) + printf( "Failed: (%d): %s\n", ret, strerror( (ret >0)? ret: -ret ) ); + + return (ret >= 0 ? ret : -ret); +} diff --git a/extracters/vhd-util/vhd-util.exe b/extracters/vhd-util/vhd-util.exe new file mode 100755 index 0000000000000000000000000000000000000000..c7452d738a3bfd5ecd5a547be09781b8249ca30c GIT binary patch literal 202240 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 literal 0 HcmV?d00001 diff --git a/extracters/vhd-util/vhd-util.vcxproj b/extracters/vhd-util/vhd-util.vcxproj new file mode 100644 index 0000000..ad50e8b --- /dev/null +++ b/extracters/vhd-util/vhd-util.vcxproj @@ -0,0 +1,145 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {CC5FB437-0C01-4ECF-A75F-10C37B607AC6} + Win32Proj + 8.1 + + + + Application + true + v140 + + + Application + false + v140 + + + Application + true + v140 + + + Application + false + v140 + + + + + + + + + + + + + + + + + + + + + true + $(ProjectDir)$(Configuration)\ + + + false + $(ProjectDir)$(Configuration)\ + + + + _CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDebug + Level3 + ProgramDatabase + Disabled + include;../libvhd/include;%(AdditionalIncludeDirectories) + vhd-util-win32.h + false + false + + + MachineX86 + true + Console + ../libvhd/Debug/libvhd.lib;Rpcrt4.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + _CRT_NONSTDC_NO_WARNINGS;_CRT_SECURE_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE + MultiThreaded + Level3 + ProgramDatabase + include;../libvhd/include;%(AdditionalIncludeDirectories) + false + vhd-util-win32.h + + + MachineX86 + true + Console + true + true + ../libvhd/Release/libvhd.lib;Rpcrt4.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + copy $(TargetPath) $(TargetDir)..\ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extracters/vhd-util/vhd-util.vcxproj.filters b/extracters/vhd-util/vhd-util.vcxproj.filters new file mode 100644 index 0000000..03dde85 --- /dev/null +++ b/extracters/vhd-util/vhd-util.vcxproj.filters @@ -0,0 +1,93 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file