///////////////////////////////////////////////////////////////////////// // $Id$ ///////////////////////////////////////////////////////////////////////// /* * This file provides support for VMWare's virtual disk image * format version 3. * * Author: Sharvil Nanavati, for Net Integration Technologies, Inc. * Contact: snrrrub@yahoo.com * * Copyright (C) 2003 Net Integration Technologies, Inc. * Copyright (C) 2003-2015 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 */ // 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 "osdep.h" #include "misc/bswap.h" #else #include "iodev.h" #endif #include "hdimage.h" #include "vmware3.h" const off_t vmware3_image_t::INVALID_OFFSET=(off_t)-1; #define LOG_THIS bx_devices.pluginHDImageCtl-> #define DTOH32_HEADER(field) (header.field = (dtoh32(header.field))) #define HTOD32_HEADER(field) (header.field = (htod32(header.field))) int vmware3_image_t::check_format(int fd, Bit64u imgsize) { COW_Header header; int ret; if ((ret = bx_read_image(fd, 0, &header, sizeof(COW_Header))) < 0) { return HDIMAGE_READ_ERROR; } if (header.id[0] != 'C' || header.id[1] != 'O' || header.id[2] != 'W' || header.id[3] != 'D') { return HDIMAGE_NO_SIGNATURE; } DTOH32_HEADER(header_version); DTOH32_HEADER(vmware_version); if (header.header_version != 3) { return HDIMAGE_VERSION_ERROR; } if (header.vmware_version != 2) { return HDIMAGE_VERSION_ERROR; } return HDIMAGE_FORMAT_OK; } bx_bool vmware3_image_t::read_header(int fd, COW_Header & header) { int ret; if ((ret = check_format(fd, 0)) != HDIMAGE_FORMAT_OK) { switch (ret) { case HDIMAGE_READ_ERROR: BX_ERROR(("vmware3 image read error")); break; case HDIMAGE_NO_SIGNATURE: BX_ERROR(("not a vmware3 COW disk")); break; case HDIMAGE_VERSION_ERROR: BX_ERROR(("unsupported vmware3 image version")); break; } return 0; } if (bx_read_image(fd, 0, &header, sizeof(COW_Header)) != sizeof(COW_Header)) return 0; DTOH32_HEADER(header_version); DTOH32_HEADER(flags); DTOH32_HEADER(total_sectors); DTOH32_HEADER(tlb_size_sectors); DTOH32_HEADER(flb_offset_sectors); DTOH32_HEADER(flb_count); DTOH32_HEADER(next_sector_to_allocate); DTOH32_HEADER(cylinders); DTOH32_HEADER(heads); DTOH32_HEADER(sectors); DTOH32_HEADER(last_modified_time); DTOH32_HEADER(last_modified_time_save); DTOH32_HEADER(chain_id); DTOH32_HEADER(number_of_chains); DTOH32_HEADER(cylinders_in_disk); DTOH32_HEADER(heads_in_disk); DTOH32_HEADER(sectors_in_disk); DTOH32_HEADER(total_sectors_in_disk); DTOH32_HEADER(vmware_version); return 1; } int vmware3_image_t::write_header(int fd, COW_Header & hostHeader) { COW_Header header; memcpy(&header, &hostHeader, sizeof(COW_Header)); HTOD32_HEADER(header_version); HTOD32_HEADER(flags); HTOD32_HEADER(total_sectors); HTOD32_HEADER(tlb_size_sectors); HTOD32_HEADER(flb_offset_sectors); HTOD32_HEADER(flb_count); HTOD32_HEADER(next_sector_to_allocate); HTOD32_HEADER(cylinders); HTOD32_HEADER(heads); HTOD32_HEADER(sectors); HTOD32_HEADER(last_modified_time); HTOD32_HEADER(last_modified_time_save); HTOD32_HEADER(chain_id); HTOD32_HEADER(number_of_chains); HTOD32_HEADER(cylinders_in_disk); HTOD32_HEADER(heads_in_disk); HTOD32_HEADER(sectors_in_disk); HTOD32_HEADER(total_sectors_in_disk); HTOD32_HEADER(vmware_version); return bx_write_image(fd, 0, &header, sizeof(COW_Header)); } #undef DTOH32_HEADER #undef HTOD32_HEADER int vmware3_image_t::read_ints(int fd, Bit32u *buffer, size_t count) { size_t i; Bit32u *p; int res = ::read(fd, (void*)buffer, count * 4); for (p = buffer, i=0; ifd = ::open(filename, flags); if (current->fd < 0) BX_PANIC(("unable to open vmware3 COW Disk file '%s'", filename)); if (!read_header(current->fd, current->header)) BX_PANIC(("unable to read header or invalid header in vmware3 COW Disk file '%s'", filename)); current->flb = new unsigned [current->header.flb_count]; if (current->flb == 0) BX_PANIC(("cannot allocate %d bytes for flb in vmware3 COW Disk '%s'", current->header.flb_count * 4, filename)); current->slb = new unsigned* [current->header.flb_count]; if (current->slb == 0) BX_PANIC(("cannot allocate %d bytes for slb in vmware3 COW Disk '%s'", current->header.flb_count * 4, filename)); unsigned j; for (j = 0; j < current->header.flb_count; ++j) { current->slb[j] = new unsigned [slb_count]; if (current->slb[j] == 0) BX_PANIC(("cannot allocate %d bytes for slb[] in vmware3 COW Disk '%s'", slb_count * 4, filename)); } current->tlb = new Bit8u[tlb_size]; if (current->tlb == 0) BX_PANIC(("cannot allocate %d bytes for tlb in vmware3 COW Disk '%s'", tlb_size, filename)); if (::lseek(current->fd, current->header.flb_offset_sectors * 512, SEEK_SET) < 0) BX_PANIC(("unable to seek vmware3 COW Disk file '%s'", filename)); if (read_ints(current->fd, current->flb, current->header.flb_count) < 0) BX_PANIC(("unable to read flb from vmware3 COW Disk file '%s'", filename)); for (j = 0; j < current->header.flb_count; ++j) if(current->flb[j] != 0) { if (::lseek(current->fd, current->flb[j] * 512, SEEK_SET) < 0) BX_PANIC(("unable to seek vmware3 COW Disk file '%s'", filename)); if (read_ints(current->fd, current->slb[j], slb_count) < 0) BX_PANIC(("unable to read slb from vmware3 COW Disk file '%s'", filename)); } current->min_offset = offset; offset += current->header.total_sectors * 512; current->max_offset = offset; current->offset = INVALID_OFFSET; current->synced = true; delete [] filename; } current = &images[0]; requested_offset = 0; if (header.total_sectors_in_disk != 0) { cylinders = header.cylinders_in_disk; heads = header.heads_in_disk; spt = header.sectors_in_disk; hd_size = header.total_sectors_in_disk * 512; } else { cylinders = header.cylinders; heads = header.heads; spt = header.sectors; hd_size = header.total_sectors * 512; } return 1; } off_t vmware3_image_t::perform_seek() { if(requested_offset < current->min_offset || requested_offset >= current->max_offset) { if(!sync()) { BX_DEBUG(("could not sync before switching vmware3 COW files")); return INVALID_OFFSET; } while(requested_offset < current->min_offset) current = &images[current->header.chain_id - 1]; while(requested_offset >= current->max_offset) current = &images[current->header.chain_id + 1]; } if(current->offset != INVALID_OFFSET && requested_offset >= current->offset && requested_offset < current->offset + tlb_size) return (requested_offset - current->offset); if(!sync()) { BX_DEBUG(("could not sync before seeking vmware3 COW file")); return INVALID_OFFSET; } unsigned relative_offset = (unsigned)(requested_offset - current->min_offset); unsigned i = relative_offset >> FL_SHIFT; unsigned j = (relative_offset & ~FL_MASK) / tlb_size; if(current->slb[i][j]) { if(::lseek(current->fd, current->slb[i][j] * 512, SEEK_SET) < 0) { BX_DEBUG(("could not seek vmware3 COW to sector slb[%d][%d]", i, j)); return INVALID_OFFSET; } if(::read(current->fd, current->tlb, tlb_size) < 0) { BX_DEBUG(("could not read %d bytes from vmware3 COW image", tlb_size)); return INVALID_OFFSET; } } else memset(current->tlb, 0, tlb_size); current->offset = (requested_offset / tlb_size) * tlb_size; return (requested_offset - current->offset); } ssize_t vmware3_image_t::read(void * buf, size_t count) { char *cbuf = (char*)buf; ssize_t total = 0; while (count > 0) { off_t offset = perform_seek(); if (offset == INVALID_OFFSET) { BX_DEBUG(("vmware3 COW read failed on %u bytes", (unsigned)count)); return -1; } unsigned bytes_remaining = (unsigned)(tlb_size - offset); unsigned amount = (bytes_remaining > count) ? count : bytes_remaining; memcpy(cbuf, current->tlb + offset, amount); requested_offset += amount; total += amount; cbuf += amount; count -= amount; } return total; } /* This could be done much better, I'm sure. In fact, the whole header doesn't * need to be re-written each time a new tlb is allocated nor does the whole * slb need to be re-written (most of the time) but that can be changed whenever * it becomes an issue... image I/O is not a bottleneck. */ bool vmware3_image_t::sync() { if(current->synced) return true; unsigned relative_offset = (unsigned)(current->offset - current->min_offset); unsigned i = relative_offset >> FL_SHIFT; unsigned j = (relative_offset & ~FL_MASK) / tlb_size; if (current->slb[i][j] == 0) { if (current->flb[i] == 0) { unsigned slb_size = slb_count * 4; /* Re-write the FLB */ current->flb[i] = current->header.next_sector_to_allocate; if(::lseek(current->fd, current->header.flb_offset_sectors * 512, SEEK_SET) < 0) { BX_DEBUG(("could not seek vmware3 COW image to flb on sync")); return false; } if(write_ints(current->fd, current->flb, current->header.flb_count) < 0) { BX_DEBUG(("could not re-write flb to vmware3 COW image on sync")); return false; } current->header.next_sector_to_allocate += (slb_size / 512) + ((slb_size % 512) ? 1 : 0); } /* Re-write the SLB */ current->slb[i][j] = current->header.next_sector_to_allocate; if(::lseek(current->fd, current->flb[i] * 512, SEEK_SET) < 0) { BX_DEBUG(("could not seek vmware3 COW image to slb on sync")); return false; } if(write_ints(current->fd, current->slb[i], slb_count) < 0) { BX_DEBUG(("could not re-write slb to vmware3 COW image on sync")); return false; } current->header.next_sector_to_allocate += current->header.tlb_size_sectors; /* Update the header */ if(::lseek(current->fd, 0, SEEK_SET) < 0) { BX_DEBUG(("could not seek to vmware3 COW image to offset 0 on sync")); return false; } if(write_header(current->fd, current->header) < 0) { BX_DEBUG(("could not re-write header to vmware3 COW image on sync")); return false; } } if(::lseek(current->fd, current->slb[i][j] * 512, SEEK_SET) < 0) { BX_DEBUG(("could not seek vmware3 COW image to offset %d on sync", current->slb[i][j] * 512)); return false; } if(::write(current->fd, current->tlb, tlb_size) < 0) { BX_DEBUG(("could not write tlb to vmware3 COW image on sync")); return false; } current->synced = true; return true; } ssize_t vmware3_image_t::write(const void * buf, size_t count) { char *cbuf = (char*)buf; ssize_t total = 0; while (count > 0) { off_t offset = perform_seek(); if(offset == INVALID_OFFSET) return -1; unsigned bytes_remaining = (unsigned)(tlb_size - offset); unsigned amount = 0; current->synced = false; if (bytes_remaining > count) { memcpy(current->tlb + offset, cbuf, count); amount = count; } else { memcpy(current->tlb + offset, cbuf, bytes_remaining); if (!sync()) { BX_DEBUG(("failed to sync when writing %u bytes", (unsigned)count)); return -1; } amount = bytes_remaining; } requested_offset += amount; total += amount; cbuf += amount; count -= amount; } return total; } Bit64s vmware3_image_t::lseek(Bit64s offset, int whence) { if(whence == SEEK_SET) requested_offset = (off_t)offset; else if (whence == SEEK_CUR) requested_offset += (off_t)offset; else if (whence == SEEK_END) requested_offset = (off_t)(current->header.total_sectors_in_disk * 512) + (off_t)offset; else { BX_DEBUG(("unknown 'whence' value (%d) when trying to seek vmware3 COW image", whence)); return -1; } return requested_offset; } void vmware3_image_t::close() { if(current == 0) return; unsigned count = current->header.number_of_chains; if (count < 1) count = 1; for(unsigned i = 0; i < count; ++i) { if (images != NULL) { current = &images[i]; for(unsigned j = 0; j < current->header.flb_count; ++j) delete[] current->slb[j]; delete[] current->flb; delete[] current->slb; delete[] current->tlb; ::close(current->fd); delete[] images; images = NULL; } } current = 0; } Bit32u vmware3_image_t::get_capabilities(void) { return HDIMAGE_HAS_GEOMETRY; } #ifndef BXIMAGE bx_bool vmware3_image_t::save_state(const char *backup_fname) { bx_bool ret = 1; char tempfn[BX_PATHNAME_LEN]; unsigned count = current->header.number_of_chains; if (count < 1) count = 1; for (unsigned i = 0; i < count; ++i) { sprintf(tempfn, "%s%d", backup_fname, i); ret &= hdimage_backup_file(images[i].fd, tempfn); if (ret == 0) break; } return ret; } void vmware3_image_t::restore_state(const char *backup_fname) { int temp_fd; Bit64u imgsize; bx_bool ret = 1; char tempfn[BX_PATHNAME_LEN]; if ((temp_fd = hdimage_open_file(backup_fname, O_RDONLY, &imgsize, NULL)) < 0) { BX_PANIC(("Cannot open vmware3 image backup '%s'", backup_fname)); return; } if (check_format(temp_fd, imgsize) < HDIMAGE_FORMAT_OK) { ::close(temp_fd); BX_PANIC(("Cannot detect vmware3 image header")); return; } ::close(temp_fd); unsigned count = current->header.number_of_chains; close(); if (count < 1) count = 1; for (unsigned i = 0; i < count; ++i) { sprintf(tempfn, "%s%d", backup_fname, i); char *filename = generate_cow_name(pathname, i); ret &= hdimage_copy_file(tempfn, filename); delete [] filename; if (ret == 0) { BX_PANIC(("Failed to restore vmware3 image '%s'", filename)); break; } } if (ret == 1) { device_image_t::open(pathname); } } #endif