Jim's Depository

this code is not yet written
 

VHD is a nice format for exchanging disks. It doesn’t take up a lot of bytes with the empty space of a filesystem. But if you want to access the data without a virtual machine it can be a bit of a pain.

The attached program will expand most VHD files into a sparse image of the disk. You can then do whatever you wish with that, such as:

  • Use it with a virtual machine that takes raw images.
  • Convert it to a VMDK with qemu-img from the qemu folk. (Note: some people say qemu-img can read VHD format already. It didn’t work for me and the man page doesn’t mention it.)
  • Mount it on your Linux box, though you have to skip some bytes to get over the MBR and down to the first partition. Go look at Creating and using disk images mini-howto | Marc’s Realm and find “The dirty way”. Basically it boils down to trying all the plausible offsets until you find the right one:

    for ((i=0 ; $i < 10000 ; i=$i + 1)) ; do    mount -o loop,offset=$(($i * 512)) image.bootable /mnt && breakdone
    
  • Give it a sound grepping.

Note: I don’t support this program or even intend to run it ever again. I converted my data. If you need to convert your data, then enjoy.

Attachments

vhd2img.tar.gz 4515 bytes
Hello Jim,

This looks like just what the doctor ordered but it failed to compile for me:

Did I do something wrong or do you have any suggestions to help fix it?

Thanks much,
Patrick -> patrickkirchner   AT yahoo . com

Current Directory = /tmp/vhd2img
-->make
cc -g -MMD -Wstrict-prototypes -Wall -Werror    vhd2img.c   -o vhd2img
vhd2img.c:38:38: error: missing terminating ' character
vhd2img.c:59:37: error: missing terminating ' character
vhd2img.c:67:67: error: missing terminating ' character
make: *** [vhd2img] Error 1

Hello again,

Well, err, uh, duh!  I poked around in the .c file and removed the whole license preamble then it compiled just fine.  Sorry about that.  It worked great but the resulting .raw and converted .qcow2 file give me a BSOD when run with kvm.

Thanks,
Patrick.
I've fixed the compilation error and made it 64bit aware, I still need to test it anyway here the download link:

http://oss.netfarm.it/download/vhd2img-64bit-aware.tar.bz2

(use wget or direct link, a link from a page will cause 503)

the bsod is probably a 7b error (inaccessible boot device, you may need to fix system registry hive to use standard ide but it's tricky and you need a windows box or of course a working windows vm :D)

contact me if interested (sherpya@netfarm.it)

I successfully started a converted windows vista evaulation vhd I'm still fighting with the mouse and vnc :)
Well, it's been two years since anyone touched this, so no one probably cares, but I needed to do this in Windows, so I ported it.  Here it is for anyone that cares.  It should still be Linux compatible.  I built and used it with Visual Studio 2010 as an x64 app, but it should work fine any way that you build it.  Oh, and it's cpp now.

[code]
#ifdef WIN32
#define _CRT_SECURE_NO_WARNINGS
#include <winsock.h>
#include <stdint.h>
#define off_t uint64_t
#define fseeko _fseeki64
#define ftello _ftelli64 
#define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop) )
#else
/*
** some defines to make Linux happy with big files
*/
#define _FILE_OFFSET_BITS 64
#define _LARGEFILE_SOURCE /* enable fseeko */
#include <arpa/inet.h>
#include <inttypes.h>
#define PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__))
#endif
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

static int verbose = 1;

typedef struct {
uint16_t cylinders;
unsigned char heads;
unsigned char sectors;
} VHD_GEOMETRY;

PACK(
struct VHD_FOOTER{
char cookie[8];
uint32_t features;
uint32_t version;
uint64_t dataOffset;
uint32_t timeStamp;
char creatorApplication[4];
uint32_t creatorVersion;
char creatorOS[4];
uint64_t originalSize;
uint64_t currentSize;
VHD_GEOMETRY diskGeometry;
uint32_t diskType;
uint32_t checksum;
unsigned char uniqueId[16];
unsigned char savedState;
unsigned char padding[427];
});

PACK(
typedef struct{
char cookie[8];
uint64_t dataOffset;
uint64_t tableOffset;
uint32_t headerVersion;
uint32_t maxTableEntries;
uint32_t blockSize;
uint32_t checksum;
unsigned char parentUniqueId[16];
uint32_t parentTimeStamp;
uint32_t reserved1;
unsigned char parentUnicodeName[512];
PACK(
struct{
unsigned char platformCode[4];
uint32_t platformDataSpace;
uint32_t platformDataLength;
uint32_t reserved;
uint64_t platformDataOffset;
}) partentLocator[8];
unsigned char reserved2[256];
}) VHD_DYNAMIC;

/*
** network byte order to host, "double" i.e. long long
*/
static uint64_t ntoh64( uint64_t v)
{
if (htons(1) == 1) return v;
else {
return(uint64_t)ntohl( v&0x00000000ffffffff) << 32 | 
(uint64_t)ntohl( (v>>32)&0x00000000ffffffff);
}
}

static void dump_footer(VHD_FOOTER *footer)
{
int i;

fprintf(stderr,"==================== VHD Footer =====================\n");
fprintf(stderr,"%24s : '%8.8s'\n", "cookie", footer->cookie);
fprintf(stderr,"%24s : %08x\n", "features", ntohl(footer->features));
fprintf(stderr,"%24s : %08x\n", "version", ntohl(footer->version));
fprintf(stderr,"%24s : %016lx\n", "data offset", ntoh64(footer->dataOffset));
fprintf(stderr,"%24s : %08x\n", "time stamp", ntohl(footer->timeStamp));
fprintf(stderr,"%24s : '%4.4s'\n", "creator application", footer->creatorApplication);
fprintf(stderr,"%24s : %08x\n", "creator version", ntohl(footer->creatorVersion));
fprintf(stderr,"%24s : '%4.4s'\n", "creator os", footer->creatorOS);
fprintf(stderr,"%24s : %016lx\n", "original size", ntoh64(footer->originalSize));
fprintf(stderr,"%24s : %016lx\n", "current size", ntoh64(footer->currentSize));
fprintf(stderr,"%24s : %d\n", "cylinders", ntohs(footer->diskGeometry.cylinders));
fprintf(stderr,"%24s : %d\n", "heads", footer->diskGeometry.heads);
fprintf(stderr,"%24s : %d\n", "sectors", footer->diskGeometry.sectors);
fprintf(stderr,"%24s : %08x\n", "disk type", ntohl(footer->diskType));
fprintf(stderr,"%24s : %08x\n", "disk type", ntohl(footer->checksum));
fprintf(stderr,"%24s : ", "unique id");
for (i = 0; i < 16; i++) {
fprintf(stderr,"%02x%s", footer->uniqueId[i], "...-.-.-.-......"[i] == '-' ? "-" : "");
}
fprintf(stderr,"\n");
fprintf(stderr,"%24s : %02x\n", "saved state", footer->savedState);
/* should add the parent locator entries if I ever see a differenced file */
}

static void dump_dynamic(VHD_DYNAMIC *dynamic)
{
int i;

fprintf(stderr,"==================== VHD Dynamic =====================\n");
fprintf(stderr,"%24s : '%8.8s'\n", "cookie", dynamic->cookie);
fprintf(stderr,"%24s : %016lx\n", "data offset", ntoh64(dynamic->dataOffset));
fprintf(stderr,"%24s : %016lx\n", "table offset", ntoh64(dynamic->tableOffset));
fprintf(stderr,"%24s : %08x\n", "version", ntohl(dynamic->headerVersion));
fprintf(stderr,"%24s : %d\n", "max table entries", ntohl(dynamic->maxTableEntries));
fprintf(stderr,"%24s : %d\n", "block size", ntohl(dynamic->blockSize));
fprintf(stderr,"%24s : %08x\n", "checksum", ntohl(dynamic->checksum));
fprintf(stderr,"%24s : ", "parent unique id");
for (i = 0; i < 16; i++) {
fprintf(stderr,"%02x%s", dynamic->parentUniqueId[i], "...-.-.-.-......"[i] == '-' ? "-" : "");
}
fprintf(stderr,"\n");
fprintf(stderr,"%24s : %08x\n", "parent time stamp", ntohl(dynamic->parentTimeStamp));
fprintf(stderr,"%24s : %08x\n", "reserved", ntohl(dynamic->reserved1));
fprintf(stderr,"%24s : ", "parent unicode name");
for (i = 0; i < 512; i++) {
fprintf(stderr,"%02x %s", dynamic->parentUniqueId[i], (i%32)==31 ? "\n" : "");
}

}

uint32_t checksum(void *buffer, int nBytes) {
uint32_t checksum=0;
uint8_t *bytes = (uint8_t *)buffer;
int counter;
for(counter = 0; counter < nBytes; counter++) {
checksum += bytes[counter];
}
return ~checksum;
}

off_t get_footer(FILE *file, VHD_FOOTER *footer) {
uint64_t footerOffset = 0;
VHD_FOOTER returnFooter;
bool passedChecksum = false;

// First, try the footer at the end of the file.
if (fseeko(file, -512, SEEK_END) != 0) {
fprintf(stderr,"Failed to seek to the footer: %s\n", strerror(errno));
exit(1);
}
footerOffset = ftello(file);
if ( fread( &returnFooter, sizeof(returnFooter), 1, file) != 1) {
fprintf(stderr,"Failed to read footer of input file: %s\n", strerror(errno));
exit(1);
}
// Now, calculate the check-sum.
uint32_t uiChecksum = returnFooter.checksum;
returnFooter.checksum = 0;
uint32_t ourChecksum = checksum(&returnFooter, sizeof(returnFooter));
passedChecksum = uiChecksum == htonl(ourChecksum);
returnFooter.checksum = uiChecksum;
// If the first footer didn't pass the checksum, try the one at the end.
if(!passedChecksum) {
fprintf(stderr, "Checksum for footer failed, trying backup copy.\n");
if(fseeko(file, 0, SEEK_SET) != 0) {
fprintf(stderr,"Failed to backup footer of input file: %s\n", strerror(errno));
exit(1);
}
if ( fread( &returnFooter, sizeof(returnFooter), 1, file) != 1) {
fprintf(stderr,"Failed to read footer of input file: %s\n", strerror(errno));
exit(1);
}
// Now, calculate the check-sum.
uint32_t uiChecksum = returnFooter.checksum;
returnFooter.checksum = 0;
uint32_t ourChecksum = checksum(&returnFooter, sizeof(returnFooter));
passedChecksum = uiChecksum == htonl(ourChecksum);
returnFooter.checksum = uiChecksum;
}

if(passedChecksum) {
memcpy(footer, &returnFooter, sizeof(returnFooter));
} else {
fprintf(stderr,"Both the header and footer failed the checksum.\n");
exit(1);
}

return footerOffset;
}

int main( int argc, char **argv)
{
/*
** Make sure we were compiled correctly.
*/
assert( sizeof(VHD_FOOTER)==512);
assert( sizeof(VHD_DYNAMIC)==1024);

if (argc != 3) {
fprintf(stderr,"Usage: vhd2img INPUTFILE OUTPUTFILE\n");
exit(1);
}

FILE *in = fopen(argv[1],"rb");
if (!in) {
fprintf(stderr,"Failed to open input file '%s': %s\n", argv[1], strerror(errno));
exit(1);
}

FILE *out = fopen(argv[2],"wb");
if (!out) {
fprintf(stderr,"Failed to open output file '%s': %s\n", argv[1], strerror(errno));
exit(1);
}

VHD_FOOTER footer;
off_t footerOffset = get_footer(in, &footer);

if (verbose) dump_footer(&footer);

switch (htonl(footer.diskType)) {
case 4:
fprintf(stderr,"Differencing VHDs not supported.\n");
exit(1);
case 2:
/*
** Warning: untested code. I don't have one of these files.
*/
if (verbose) fprintf(stderr,"Processing fixed VHD...\n");
{
off_t o = 0;
char buf[512];

if (fseeko(in,(off_t)0,SEEK_SET) != 0) {
fprintf(stderr,"Failed to rewind input file: %s\n", strerror(errno));
exit(1);
}
for (o = 0; o < footerOffset; o += 512) {
if (fread( buf, 512, 1, in) != 1) {
fprintf(stderr,"Failed to read input file: %s\n", strerror(errno));
exit(1);
}
if (fwrite( buf, 512, 1, out) != 1) {
fprintf(stderr,"Failed to write output file: %s\n", strerror(errno));
exit(1);
}
}
fprintf(stderr,"Completed. %lld bytes written.\n", (long long)footerOffset);
return 0;
}
case 3:
if (verbose) fprintf(stderr,"Processing dynamic VHD...\n");
break;
default:
fprintf(stderr,"Disk type %08x not recognized.\n", htonl(footer.diskType));
exit(1);
}

VHD_DYNAMIC dynamic;

if (fseeko(in,ntoh64(footer.dataOffset),SEEK_SET) != 0) {
fprintf(stderr,"Failed to seek to dynamic disk header: %s\n", strerror(errno));
exit(1);
}

if (fread( &dynamic, sizeof(dynamic), 1, in) != 1) {
fprintf(stderr,"Failed ot read dynamic disk header: %s\n", strerror(errno));
exit(1);
}

if (verbose) dump_dynamic( &dynamic);

uint32_t blockBitmapSectorCount = (ntohl(dynamic.blockSize)/512/8+511)/512;
uint32_t sectorsPerBlock = ntohl(dynamic.blockSize)/512;
uint8_t *bitmap = (uint8_t *)malloc(blockBitmapSectorCount*512);

if (verbose) fprintf(stderr, "block bitmap sector count is %d\n", blockBitmapSectorCount);

if (fseeko(in,ntoh64(dynamic.tableOffset),SEEK_SET) != 0) {
fprintf(stderr,"Failed to seek to block allocation table: %s\n", strerror(errno));
exit(1);
}

uint32_t bats = ntohl( dynamic.maxTableEntries);
uint32_t *bat = (uint32_t *)calloc( sizeof(*bat) , bats);
if (fread( bat, sizeof(*bat), bats, in) != bats) {
fprintf(stderr,"Failed to read block allocation table: %s\n", strerror(errno));
exit(1);
}

if (fseeko(out, bats * sectorsPerBlock * 512 - 1,SEEK_SET) != 0) {
fprintf(stderr,"Failed to seek to the end of the image: %s\n", strerror(errno));
exit(1);
}
if (fwrite("", 1, 1, out) != 1) {
fprintf(stderr,"Failed to write the last byte of image: %s\n", strerror(errno));
exit(1);
}

unsigned int b;
uint32_t emptySectors = 0;
uint32_t usedSectors = 0;
uint32_t usedZeroes = 0;
char buf[512];

for (b = 0; b < bats; b++) {
if (ntohl(bat[b]) == 0xffffffff) {
emptySectors += sectorsPerBlock;
continue;   /* totally empty block */
}

uint64_t bo = ntohl(bat[b])*512LL;

if (bo > footerOffset) {
fprintf(stderr,"Bad block offset\n");
exit(1);
}
if (fseeko(in, bo, SEEK_SET) != 0) {
fprintf(stderr,"Failed to seek to data block bitmap: %s\n", strerror(errno));
exit(1);
}
if (fread( bitmap, 512*blockBitmapSectorCount, 1, in) != 1) {
fprintf(stderr,"Failed to read block bitmap(%ld): %s\n", bo, strerror(errno));
exit(1);
}

unsigned int s,k;
uint64_t opos = 0xffffffffffffffffLL;

if (fseeko(in, bo+512*blockBitmapSectorCount, SEEK_SET) != 0) {
fprintf(stderr,"Failed to seek to input sectors: %s\n", strerror(errno));
exit(1);
}
for (s = 0; s < sectorsPerBlock; s++) {
if (fread( buf, 512, 1, in) != 1) {
fprintf(stderr,"Failed to read sector: %s\n", strerror(errno));
exit(1);
}

int empty = 1;
for (k = 0; k < 512; k++) {
if (buf[k]) {
empty = 0;
break;
}
}

if ((bitmap[s/8] & (1<<(7-s%8))) == 0) {
emptySectors++;
if (!empty) {
fprintf(stderr,"block %d, sector %d should be empty and isn't.\n", b, s);
}
} else {
usedSectors++;
if (empty) {
usedZeroes++;
} else {
uint64_t pos = (b*sectorsPerBlock + s) * 512LL;
if (pos != opos) {
if (fseeko( out, pos, SEEK_SET) != 0) {
fprintf(stderr,"Failed to seek output file for sector %lld (block %d of %u,sector %d of %d): %s\n", 
(b*sectorsPerBlock + s) * 512LL, b,bats,s,sectorsPerBlock,
strerror(errno));
exit(1);
}
opos = pos;
}
if (fwrite( buf, 512, 1, out) != 1) {
fprintf(stderr,"Failed to write sector: %s\n", strerror(errno));
exit(1);
}
opos += 512LL;
}
}
}
}

if (verbose) {
fprintf(stderr,"%4.1f%% of the sectors were used.\n", 
100.0*usedSectors/(usedSectors+emptySectors));
fprintf(stderr,"%4.1f%% of the sectors are used after removing zeroes.\n", 
100.0*(usedSectors-usedZeroes)/(usedSectors+emptySectors));
}

return 0;
}
[/code]
Basically it boils down to trying all the plausible offsets until you find the right one
Wow! It is not needed! If you have cylinder-aligned partition, just skip 63*512 bytes. If you have megabyte-aligned, 1024*2*512.
I just built qemu from the source for OS X 10.7 on Mac. I had to use these flags to get the make to complete without an error on compiling darwin-user:
>>  ./configure --disable-darwin-user --disable-sdl --enable-cocoa --disable-kvm --disable-bsd-user

Then i directly converted a Windows 7 x64 created VHD image into a raw... well it's not done yet, but seems to be running fine. The latest qemu-img does understand the source file is .vhd and so the command I had to use is:
>>  qemu-img convert -O raw source.vhd output.raw

next step is to use dd to lay down the raw image onto a hdd partition... 
I haven't actually mounted the resulting image file yet, but the conversion seemed to run OK. If you want to check your output image file contents, for information on what is actually in there, you can use the "disktype" package
from sourceforge (disktype.sourceforge.net). That program is also included in your favorite package manager. It'll make it a bit easier to figure out where your
partitions are located. This is a sample output, using my newly converted image file. "disktype myimage.img"

Regular file, size 31.00 GiB (33286000640 bytes)
DOS/MBR partition map
Partition 1: 29.31 GiB (31473008640 bytes, 61470720 sectors from 2048, bootable)
  Type 0x83 (Linux)
  Ext3 file system
    UUID 09DE2E1E-2C2F-4378-A8E6-59C8723865C7 (DCE, v4)
    Volume size 29.31 GiB (31473008640 bytes, 7683840 blocks of 4 KiB)
Partition 2: 2.687 GiB (2884632576 bytes, 5634048 sectors from 61472768)
  Type 0x82 (Linux swap / Solaris)
  Linux swap, version 2, subversion 1, 4 KiB pages, little-endian
    Swap size 2.687 GiB (2884624384 bytes, 704254 pages of 4 KiB)