458 lines
12 KiB
C++
458 lines
12 KiB
C++
/////////////////////////////////////////////////////////////////////////
|
|
// $Id$
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
/*
|
|
* This file provides support for the following VBox virtual
|
|
* disk image formats: VDI.
|
|
*
|
|
* Author: Benjamin D Lunt
|
|
* Contact: fys [at] fysnet [dot] net
|
|
*
|
|
* Copyright (C) 2015 Benjamin D Lunt.
|
|
* Copyright (C) 2006-2021 The Bochs Project
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Information found at:
|
|
* https://forums.virtualbox.org/viewtopic.php?t=8046
|
|
*
|
|
* Many Image Files can be found at:
|
|
* http://sourceforge.net/projects/virtualboximage/files/
|
|
*
|
|
*/
|
|
|
|
// Define BX_PLUGGABLE in files that can be compiled into plugins. For
|
|
// platforms that require a special tag on exported symbols, BX_PLUGGABLE
|
|
// is used to know when we are exporting symbols and when we are importing.
|
|
#define BX_PLUGGABLE
|
|
|
|
#ifdef BXIMAGE
|
|
#include "config.h"
|
|
#include "misc/bxcompat.h"
|
|
#include "misc/bswap.h"
|
|
#include "osdep.h"
|
|
#else
|
|
#include "bochs.h"
|
|
#include "plugin.h"
|
|
#endif
|
|
#include "hdimage.h"
|
|
#include "vbox.h"
|
|
|
|
#define LOG_THIS bx_hdimage_ctl.
|
|
|
|
const off_t vbox_image_t::INVALID_OFFSET = (off_t)-1;
|
|
const int vbox_image_t::SECTOR_SIZE = 512;
|
|
|
|
#ifndef BXIMAGE
|
|
|
|
// disk image plugin entry point
|
|
|
|
PLUGIN_ENTRY_FOR_IMG_MODULE(vbox)
|
|
{
|
|
if (mode == PLUGIN_PROBE) {
|
|
return (int)PLUGTYPE_IMG;
|
|
}
|
|
return 0; // Success
|
|
}
|
|
|
|
#endif
|
|
|
|
//
|
|
// Define the static class that registers the derived device image class,
|
|
// and allocates one on request.
|
|
//
|
|
class bx_vbox_locator_c : public hdimage_locator_c {
|
|
public:
|
|
bx_vbox_locator_c(void) : hdimage_locator_c("vbox") {}
|
|
protected:
|
|
device_image_t *allocate(Bit64u disk_size, const char *journal) {
|
|
return (new vbox_image_t());
|
|
}
|
|
int check_format(int fd, Bit64u disk_size) {
|
|
return (vbox_image_t::check_format(fd, disk_size));
|
|
}
|
|
} bx_vbox_match;
|
|
|
|
vbox_image_t::vbox_image_t()
|
|
: file_descriptor(-1),
|
|
mtlb(0),
|
|
block_data(0),
|
|
current_offset(INVALID_OFFSET),
|
|
mtlb_sector(0),
|
|
is_dirty(0),
|
|
mtlb_dirty(0),
|
|
header_dirty(0)
|
|
{
|
|
if (sizeof(_VBOX_VDI_Header) != 512) {
|
|
BX_FATAL(("system error: invalid header structure size"));
|
|
}
|
|
}
|
|
|
|
vbox_image_t::~vbox_image_t()
|
|
{
|
|
close();
|
|
}
|
|
|
|
int vbox_image_t::open(const char* _pathname, int flags)
|
|
{
|
|
Bit64u imgsize = 0;
|
|
|
|
pathname = _pathname;
|
|
close();
|
|
|
|
file_descriptor = hdimage_open_file(pathname, flags, &imgsize, &mtime);
|
|
|
|
if (!is_open())
|
|
return -1;
|
|
|
|
if (!read_header()) {
|
|
BX_PANIC(("unable to read vbox virtual disk header from file '%s'", pathname));
|
|
return -1;
|
|
}
|
|
|
|
// allocate one block of memory
|
|
block_data = new Bit8u[(unsigned) header.block_size];
|
|
if (block_data == 0) {
|
|
BX_PANIC(("unable to allocate %d bytes for vbox block size", header.block_size));
|
|
}
|
|
is_dirty = 0;
|
|
mtlb_dirty = 0;
|
|
header_dirty = 0;
|
|
|
|
// we allocate and read the image block map.
|
|
// it is not a very large size, since each entry is only 32-bits and
|
|
// a 10-gig image will only use 40k of memory in this block.
|
|
// (10gig = 10240 1-meg blocks with each entry using 4 bytes) = 40k
|
|
mtlb = new Bit32s[(unsigned) header.blocks_in_hdd];
|
|
if (mtlb == 0) {
|
|
BX_PANIC(("unable to allocate %lu bytes for vbox image's map table", header.blocks_in_hdd * sizeof(Bit32u)));
|
|
}
|
|
|
|
// read in the map table
|
|
if (bx_read_image(file_descriptor, header.offset_blocks, mtlb, (unsigned) header.blocks_in_hdd * sizeof(Bit32u))
|
|
!= (ssize_t)(header.blocks_in_hdd * sizeof(Bit32u))) {
|
|
BX_PANIC(("did not read in map table"));
|
|
}
|
|
|
|
// read in the first index so that we have something in memory
|
|
read_block(0);
|
|
|
|
mtlb_sector = 0;
|
|
current_offset = 0;
|
|
|
|
hd_size = header.disk_size;
|
|
sect_size = (unsigned) header.sector_size;
|
|
if ((unsigned) header.cylinders > 0) {
|
|
cylinders = (unsigned) header.cylinders;
|
|
heads = (unsigned) header.heads;
|
|
spt = (unsigned) header.sectors;
|
|
} else {
|
|
cylinders = (unsigned) ((header.disk_size / sect_size) / 16) / 63;
|
|
heads = 16;
|
|
spt = 63;
|
|
}
|
|
|
|
BX_DEBUG(("VBox VDI disk geometry:"));
|
|
BX_DEBUG((" .size = " FMT_LL "d", hd_size));
|
|
BX_DEBUG((" .cylinders = %d", cylinders));
|
|
BX_DEBUG((" .heads = %d", heads));
|
|
BX_DEBUG((" .sectors = %d", spt));
|
|
BX_DEBUG((" .sect_size = %d", sect_size));
|
|
|
|
return 1;
|
|
}
|
|
|
|
void vbox_image_t::close()
|
|
{
|
|
if (file_descriptor == -1)
|
|
return;
|
|
|
|
flush();
|
|
|
|
// write the map back to the disk
|
|
if (mtlb_dirty) {
|
|
if (bx_write_image(file_descriptor, header.offset_blocks, mtlb, (unsigned) header.blocks_in_hdd * sizeof(Bit32u))
|
|
!= (ssize_t)(header.blocks_in_hdd * sizeof(Bit32u))) {
|
|
BX_PANIC(("did not write map table"));
|
|
}
|
|
}
|
|
|
|
// write header back to image
|
|
if (header_dirty) {
|
|
if (bx_write_image(file_descriptor, 0, &header, sizeof(VBOX_VDI_Header)) != sizeof(VBOX_VDI_Header)) {
|
|
BX_PANIC(("did not write header"));
|
|
}
|
|
}
|
|
|
|
delete [] mtlb; mtlb = 0;
|
|
delete [] block_data; block_data = 0;
|
|
|
|
bx_close_image(file_descriptor, pathname);
|
|
file_descriptor = -1;
|
|
}
|
|
|
|
Bit64s vbox_image_t::lseek(Bit64s offset, int whence)
|
|
{
|
|
switch (whence) {
|
|
case SEEK_SET:
|
|
current_offset = (off_t) offset;
|
|
return current_offset;
|
|
case SEEK_CUR:
|
|
current_offset += (off_t) offset;
|
|
return current_offset;
|
|
case SEEK_END:
|
|
current_offset = header.disk_size + (off_t)offset;
|
|
return current_offset;
|
|
default:
|
|
BX_INFO(("unknown 'whence' value (%d) when trying to seek vbox image", whence));
|
|
return INVALID_OFFSET;
|
|
}
|
|
}
|
|
|
|
ssize_t vbox_image_t::read(void *buf, size_t count)
|
|
{
|
|
char *cbuf = (char*)buf;
|
|
ssize_t total = 0;
|
|
while (count > 0) {
|
|
off_t readable = perform_seek();
|
|
if (readable == INVALID_OFFSET) {
|
|
BX_ERROR(("vbox disk image read failed on %u bytes at " FMT_LL "d", (unsigned)count, current_offset));
|
|
return -1;
|
|
}
|
|
|
|
off_t copysize = ((off_t)count > readable) ? readable : count;
|
|
off_t offset = current_offset & (header.block_size - 1);
|
|
memcpy(cbuf, block_data + (size_t) offset, (size_t) copysize);
|
|
|
|
current_offset += copysize;
|
|
total += (long) copysize;
|
|
cbuf += copysize;
|
|
count -= (size_t) copysize;
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
ssize_t vbox_image_t::write(const void *buf, size_t count)
|
|
{
|
|
char *cbuf = (char*)buf;
|
|
ssize_t total = 0;
|
|
while (count > 0) {
|
|
off_t writable = perform_seek();
|
|
if (writable == INVALID_OFFSET) {
|
|
BX_ERROR(("vbox disk image write failed on %u bytes at " FMT_LL "d", (unsigned)count, current_offset));
|
|
return -1;
|
|
}
|
|
|
|
off_t writesize = ((off_t)count > writable) ? writable : count;
|
|
off_t offset = current_offset & (header.block_size - 1);
|
|
memcpy(block_data + offset, cbuf, (size_t) writesize);
|
|
|
|
current_offset += writesize;
|
|
total += (long) writesize;
|
|
cbuf += writesize;
|
|
count -= (size_t) writesize;
|
|
is_dirty = 1;
|
|
}
|
|
return total;
|
|
}
|
|
|
|
int vbox_image_t::check_format(int fd, Bit64u imgsize)
|
|
{
|
|
VBOX_VDI_Header temp_header;
|
|
|
|
if (bx_read_image(fd, 0, &temp_header, sizeof(VBOX_VDI_Header)) != sizeof(VBOX_VDI_Header))
|
|
return HDIMAGE_READ_ERROR;
|
|
|
|
// type can be 1 (Dynamic) or 2 (Static/Fixed)
|
|
// block size must be 1Meg (FIXME: I think it can be anything as long as it is a power of 2)
|
|
// sector size must be 512
|
|
if (((temp_header.image_type < 1) || (temp_header.image_type > 2))
|
|
|| (temp_header.block_size != 0x00100000)
|
|
|| (temp_header.sector_size != 0x00000200))
|
|
return HDIMAGE_NO_SIGNATURE;
|
|
|
|
// version must be 01.01
|
|
if (temp_header.version != 0x00010001)
|
|
return HDIMAGE_VERSION_ERROR;
|
|
|
|
return HDIMAGE_FORMAT_OK;
|
|
}
|
|
|
|
bool vbox_image_t::is_open() const
|
|
{
|
|
return (file_descriptor != -1);
|
|
}
|
|
|
|
bool vbox_image_t::read_header()
|
|
{
|
|
int ret;
|
|
|
|
if (!is_open())
|
|
BX_PANIC(("attempt to read vbox header from a closed file"));
|
|
|
|
if ((ret = check_format(file_descriptor, 0)) != HDIMAGE_FORMAT_OK) {
|
|
switch (ret) {
|
|
case HDIMAGE_READ_ERROR:
|
|
BX_ERROR(("vbox image read error"));
|
|
break;
|
|
case HDIMAGE_NO_SIGNATURE:
|
|
BX_ERROR(("not a vbox image"));
|
|
break;
|
|
case HDIMAGE_VERSION_ERROR:
|
|
BX_ERROR(("unsupported vbox image version"));
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (bx_read_image(file_descriptor, 0, &header, sizeof(VBOX_VDI_Header)) != sizeof(VBOX_VDI_Header)) {
|
|
return 0;
|
|
}
|
|
|
|
BX_DEBUG(("VBOX_VDI_Header (size=%u)", (unsigned)sizeof(VBOX_VDI_Header)));
|
|
BX_DEBUG((" .version = %08X", header.version));
|
|
BX_DEBUG((" .flags = %08X", header.flags));
|
|
BX_DEBUG((" .disk_size = " FMT_LL "d", header.disk_size));
|
|
BX_DEBUG((" .type = %d (%s)", header.image_type, (header.image_type == 1) ? "Dynamic" : "Static"));
|
|
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// Returns the number of bytes that can be read from the current offset before needing
|
|
// to perform another seek.
|
|
//
|
|
off_t vbox_image_t::perform_seek()
|
|
{
|
|
if (current_offset == INVALID_OFFSET) {
|
|
BX_ERROR(("invalid offset specified in vbox seek"));
|
|
return INVALID_OFFSET;
|
|
}
|
|
|
|
Bit32u index = (Bit32u) (current_offset / header.block_size);
|
|
|
|
if (mtlb_sector == index) {
|
|
return header.block_size - (current_offset & (header.block_size - 1));
|
|
} else {
|
|
flush();
|
|
|
|
read_block(index);
|
|
mtlb_sector = index;
|
|
|
|
return header.block_size;
|
|
}
|
|
}
|
|
|
|
void vbox_image_t::flush()
|
|
{
|
|
if (!is_dirty)
|
|
return;
|
|
|
|
//
|
|
// Write dirty sectors to disk.
|
|
//
|
|
write_block(mtlb_sector);
|
|
is_dirty = 0;
|
|
}
|
|
|
|
void vbox_image_t::read_block(const Bit32u index)
|
|
{
|
|
off_t offset;
|
|
|
|
// if the mtlb[index] returns -1, then we haven't written this sector
|
|
// to disk yet, so return an "empty" buffer
|
|
if (dtoh32(mtlb[index]) == -1) {
|
|
if (header.image_type == 2) {
|
|
BX_PANIC(("Found non-existing block in Static type image"));
|
|
}
|
|
memset(block_data, 0, header.block_size);
|
|
|
|
BX_DEBUG(("reading empty block index %d", index));
|
|
} else {
|
|
if (dtoh32(mtlb[index]) >= (int) header.blocks_in_hdd) {
|
|
BX_PANIC(("Trying to read past end of image (index out of range)"));
|
|
}
|
|
offset = dtoh32(mtlb[index]) * header.block_size;
|
|
bx_read_image(file_descriptor, header.offset_data + offset, block_data, header.block_size);
|
|
|
|
BX_DEBUG(("reading block index %d (%d) " FMT_LL "d", index, dtoh32(mtlb[index]), offset));
|
|
}
|
|
}
|
|
|
|
void vbox_image_t::write_block(const Bit32u index)
|
|
{
|
|
off_t offset;
|
|
|
|
// if the mtlb[index] returns -1, then we haven't written this sector
|
|
// to disk yet, so allocate another and write it to file
|
|
if (dtoh32(mtlb[index]) == -1) {
|
|
if (header.image_type == 2) {
|
|
BX_PANIC(("Found non-existing block in Static type image"));
|
|
}
|
|
mtlb[index] = htod32(header.blocks_allocated++);
|
|
BX_DEBUG(("allocating new block at block: %d", dtoh32(mtlb[index])));
|
|
mtlb_dirty = 1;
|
|
header_dirty = 1;
|
|
}
|
|
|
|
if (dtoh32(mtlb[index]) >= (int) header.blocks_in_hdd) {
|
|
BX_PANIC(("Trying to write past end of image (index out of range)"));
|
|
}
|
|
|
|
offset = dtoh32(mtlb[index]) * header.block_size;
|
|
|
|
BX_DEBUG(("writing block index %d (%d) " FMT_LL "d", index, dtoh32(mtlb[index]), offset));
|
|
|
|
bx_write_image(file_descriptor, header.offset_data + offset, block_data, header.block_size);
|
|
}
|
|
|
|
Bit32u vbox_image_t::get_capabilities(void)
|
|
{
|
|
return HDIMAGE_HAS_GEOMETRY;
|
|
}
|
|
|
|
#ifndef BXIMAGE
|
|
bool vbox_image_t::save_state(const char *backup_fname)
|
|
{
|
|
return hdimage_backup_file(file_descriptor, backup_fname);
|
|
}
|
|
|
|
void vbox_image_t::restore_state(const char *backup_fname)
|
|
{
|
|
int temp_fd;
|
|
Bit64u imgsize;
|
|
|
|
if ((temp_fd = hdimage_open_file(backup_fname, O_RDONLY, &imgsize, NULL)) < 0) {
|
|
BX_PANIC(("Cannot open vbox image backup '%s'", backup_fname));
|
|
return;
|
|
}
|
|
|
|
if (check_format(temp_fd, imgsize) < HDIMAGE_FORMAT_OK) {
|
|
::close(temp_fd);
|
|
BX_PANIC(("Cannot detect vbox image header"));
|
|
return;
|
|
}
|
|
::close(temp_fd);
|
|
close();
|
|
if (!hdimage_copy_file(backup_fname, pathname)) {
|
|
BX_PANIC(("Failed to restore vbox image '%s'", pathname));
|
|
return;
|
|
}
|
|
device_image_t::open(pathname);
|
|
}
|
|
#endif
|