---------------------------------------------------------------------- Patch name: patch.sparsedisk-justinsb Author: Justin SB Date: 27 Jan 2003 Status: Obsolete, replaced by patch.harddisk-modes Detailed description: This patch enables "sparse hard drive" support: 1) A large hard drive can be created, and only used space will be stored in the file. In practice this is not a large gain as Unix does this anyway. 2) Multiple sparse drive images can be mounted on top of each other. Writes go to the top image. This allows several similar configurations to share a master "base" file, and also allows filesystem rollback or no-write options. Up to 10 disk images can be layered on top of each other. There is a need for supporting utilities: 1) to merge two sparse disk images into a single image 2) to defragment a sparse disk image and remove unused space Examples: Space Saving 1) Create a sparse disk image using bximage. Set size to eg 10GB. Only allocated space will be stored, so your drive image should be only about as large as the files stored on it. Disk Rollback: 1) Create a sparse disk image called "c.img.0". Point .bochsrc at "c.img.0". In bochs, install your favourite OS. Switch off bochs. 2) Create a sparse disk image (of the same size) and name it "c.img.1". Point .bochsrc at "c.img.1" "c.img.0" is visible, but all writes go to "c.img.1". After using bochs, you can simply delete "c.img.1" to undo changes and go back to a clean OS install. Disk Optional Commit: 1) Create a sparse disk image called "c.img.0". Point .bochsrc at "c.img.0". In bochs, install your favourite OS. Switch off bochs. 2) Create a sparse disk image (of the same size) and name it "c.img.1". Point .bochsrc at "c.img.1" "c.img.0" is visible, but all writes go to "c.img.1". After using bochs, if you want to keep the changes, use the (currently non-existant) merge utility to make a single unified drive image. Alternatively simply create a new partition on top called "c.img.3". Common Base: 1) Create a sparse disk image called "base.img". Point .bochsrc at "base.img". In bochs, install your favourite OS. Switch off bochs. 2) Create a sparse disk image (of the same size) and name it "www.img.1". Make "wwww.img.0" a symlink to "base.img". Point .bochsrc at "www.img.1". Using bochs, install a webserver. 3) Create a symlink to "base.img" called "db.img.0". Create a sparse disk image (of the same size) and name it "db.img.1". Point .bochsrc at "db.img.1". Using bochs, install a database server. Now both a database server and webserver can be run in separate virtual machines, but they share the common OS image, saving drive space. Disk Rollback and Optional Commit will probably be the most used of these options. Patch was created with: cvs diff -u Apply patch to what version: cvs checked out on 27 Jan 2003 Instructions: To patch, go to main bochs directory. Type "patch -p0 < patch.sparsedisk". ---------------------------------------------------------------------- Index: bochs.h =================================================================== RCS file: /cvsroot/bochs/bochs/bochs.h,v retrieving revision 1.117 diff -u -r1.117 bochs.h --- bochs.h 10 Jan 2003 22:43:51 -0000 1.117 +++ bochs.h 27 Jan 2003 12:37:19 -0000 @@ -49,6 +49,8 @@ #include #include #include +#include + #ifndef WIN32 # include #else Index: config.h.in =================================================================== RCS file: /cvsroot/bochs/bochs/config.h.in,v retrieving revision 1.104 diff -u -r1.104 config.h.in --- config.h.in 23 Jan 2003 19:31:25 -0000 1.104 +++ config.h.in 27 Jan 2003 12:37:21 -0000 @@ -294,6 +294,16 @@ // [[Provide example of partitioning]] #define BX_SPLIT_HD_SUPPORT 0 +// This option enables "sparse hard drive" support, which means that +// 1) A large hard drive can be created, and only used space will be stored +// in the file. In practice this is not a large gain as Unix does this +// anyway. +// 2) Multiple sparse drive images can be mounted on top of each other. +// Writes go to the top image. This allows several similar configurations +// to share a master "base" file, and also allows filesystem rollback or +// no-write options +#define BX_SPARSE_HD_SUPPORT 0 + // This option defines the number of supported ATA channels. // There are up to two drives per ATA channel. #define BX_MAX_ATA_CHANNEL 4 Index: configure =================================================================== RCS file: /cvsroot/bochs/bochs/configure,v retrieving revision 1.198 diff -u -r1.198 configure --- configure 20 Jan 2003 20:12:59 -0000 1.198 +++ configure 27 Jan 2003 12:37:44 -0000 @@ -1015,6 +1015,7 @@ --enable-cpu-level select cpu level (3,4,5,6) --enable-apic enable APIC support --enable-split-hd allows split hard disk image + --enable-sparse-hd allows sparse hard disk image --enable-ne2000 enable limited ne2000 support --enable-pci enable limited i440FX PCI support --enable-dc2300-vlb-ide enable Promise DC2300 VLB-IDE support @@ -4208,7 +4209,7 @@ case $host in *-*-irix6*) # Find out which ABI we are using. - echo '#line 4211 "configure"' > conftest.$ac_ext + echo '#line 4212 "configure"' > conftest.$ac_ext if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5 (eval $ac_compile) 2>&5 ac_status=$? @@ -4758,7 +4759,7 @@ save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS -o out/conftest2.$ac_objext" compiler_c_o=no -if { (eval echo configure:4761: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>out/conftest.err; } && test -s out/conftest2.$ac_objext; then +if { (eval echo configure:4762: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>out/conftest.err; } && test -s out/conftest2.$ac_objext; then # The compiler can only warn and ignore the option if not recognized # So say no if there are warnings if test -s out/conftest.err; then @@ -6589,7 +6590,7 @@ lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 lt_status=$lt_dlunknown cat > conftest.$ac_ext < conftest.$ac_ext < conftest.$ac_ext <&5 +echo $ECHO_N "checking for sparse hard disk image support... $ECHO_C" >&6 +# Check whether --enable-sparse-hd or --disable-sparse-hd was given. +if test "${enable_sparse_hd+set}" = set; then + enableval="$enable_sparse_hd" + if test "$enableval" = yes; then + echo "$as_me:$LINENO: result: yes" >&5 +echo "${ECHO_T}yes" >&6 + cat >>confdefs.h <<\_ACEOF +#define BX_SPARSE_HD_SUPPORT 1 +_ACEOF + + else + echo "$as_me:$LINENO: result: no" >&5 +echo "${ECHO_T}no" >&6 + cat >>confdefs.h <<\_ACEOF +#define BX_SPARSE_HD_SUPPORT 0 +_ACEOF + + fi +else + + echo "$as_me:$LINENO: result: no" >&5 +echo "${ECHO_T}no" >&6 + cat >>confdefs.h <<\_ACEOF +#define BX_SPARSE_HD_SUPPORT 0 +_ACEOF + + + +fi; + + echo "$as_me:$LINENO: checking for NE2000 support" >&5 echo $ECHO_N "checking for NE2000 support... $ECHO_C" >&6 # Check whether --enable-ne2000 or --disable-ne2000 was given. @@ -23453,6 +23487,7 @@ s,@IOAPIC_OBJS@,$IOAPIC_OBJS,;t t s,@APIC_OBJS@,$APIC_OBJS,;t t s,@BX_SPLIT_HD_SUPPORT@,$BX_SPLIT_HD_SUPPORT,;t t +s,@BX_SPARSE_HD_SUPPORT@,$BX_SPARSE_HD_SUPPORT,;t t s,@NE2K_OBJS@,$NE2K_OBJS,;t t s,@NETLOW_OBJS@,$NETLOW_OBJS,;t t s,@PCI_OBJ@,$PCI_OBJ,;t t Index: configure.in =================================================================== RCS file: /cvsroot/bochs/bochs/configure.in,v retrieving revision 1.198 diff -u -r1.198 configure.in --- configure.in 20 Jan 2003 20:10:25 -0000 1.198 +++ configure.in 27 Jan 2003 12:37:46 -0000 @@ -701,6 +701,23 @@ ) AC_SUBST(BX_SPLIT_HD_SUPPORT) +AC_MSG_CHECKING(for sparse hard disk image support) +AC_ARG_ENABLE(sparse-hd, + [ --enable-sparse-hd allows sparse hard disk image], + [if test "$enableval" = yes; then + AC_MSG_RESULT(yes) + AC_DEFINE(BX_SPARSE_HD_SUPPORT, 1) + else + AC_MSG_RESULT(no) + AC_DEFINE(BX_SPARSE_HD_SUPPORT, 0) + fi], + [ + AC_MSG_RESULT(no) + AC_DEFINE(BX_SPARSE_HD_SUPPORT, 0) + ] + ) +AC_SUBST(BX_SPARSE_HD_SUPPORT) + AC_MSG_CHECKING(for NE2000 support) AC_ARG_ENABLE(ne2000, [ --enable-ne2000 enable limited ne2000 support], Index: iodev/harddrv.cc =================================================================== RCS file: /cvsroot/bochs/bochs/iodev/harddrv.cc,v retrieving revision 1.94 diff -u -r1.94 harddrv.cc --- iodev/harddrv.cc 5 Jan 2003 03:22:03 -0000 1.94 +++ iodev/harddrv.cc 27 Jan 2003 12:37:52 -0000 @@ -31,12 +31,14 @@ // Define BX_PLUGGABLE in files that can be compiled into plugins. For -// platforms that require a special tag on exported symbols, BX_PLUGGABLE +// 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 #include "bochs.h" +#include + #define LOG_THIS theHardDrive-> // WARNING: dangerous options! @@ -118,31 +120,43 @@ #if DLL_HD_SUPPORT # error code must be fixed to use DLL_HD_SUPPORT and 4 ata channels #endif - + for (Bit8u channel=0; channelstr); // choke on zero length strings + p--; // point to last character of the string + (*p) += diff; // increment to next/previous ascii code. + BX_DEBUG(("increment string returning '%s'", str)); + return (*p); +} +#endif + #if BX_SPLIT_HD_SUPPORT /*** concat_image_t function definitions ***/ @@ -3171,13 +3199,7 @@ void concat_image_t::increment_string (char *str) { - // find the last character of the string, and increment it. - char *p = str; - while (*p != 0) p++; - BX_ASSERT (p>str); // choke on zero length strings - p--; // point to last character of the string - ++(*p); // increment to next ascii code. - BX_DEBUG(("concat_image.increment string returning '%s'", str)); + ::increment_string(str, +1); } int concat_image_t::open (const char* pathname0) @@ -3287,7 +3309,7 @@ // notice if anyone does sequential read or write without seek in between. // This can be supported pretty easily, but needs additional checks for // end of a partial image. - if (!seek_was_last_op) + if (!seek_was_last_op) BX_PANIC( ("no seek before read")); return ::read(fd, (char*) buf, count); } @@ -3303,6 +3325,566 @@ return ::write(fd, (char*) buf, count); } #endif /* BX_SPLIT_HD_SUPPORT */ + +#if BX_SPARSE_HD_SUPPORT +/* Do these utility functions maybe already exist elsewhere? +*/ + +inline uint32 little_to_local_endian(uint32 value) +{ +#ifdef BX_LITTLE_ENDIAN + // Already in local endian + return value; +#else /* BX_BIG_ENDIAN */ + return read_32bit(&value); +#endif +} + + +inline uint32 local_to_little_endian(uint32 value) +{ +#ifdef BX_LITTLE_ENDIAN + // Already in local endian + return value; +#else /* BX_BIG_ENDIAN */ + uint32 ret = (((value >> 0) & 0xff) << 24) + (((value >> 8) & 0xff) << 16) + (((value >> 16) & 0xff) << 8) + (((value >> 24) & 0xff) << 0); + + return ret; +#endif +} +#endif // BX_SPARSE_HD_SUPPORT + +#if BX_SPARSE_HD_SUPPORT + +/*** sparse_image_t function definitions ***/ +sparse_image_t::sparse_image_t () +{ + fd = -1; + pathname = NULL; +#ifdef _POSIX_MAPPED_FILES + mmap_header = NULL; +#endif + pagetable = NULL; +} + + +/* +void showpagetable(uint32 * pagetable, size_t numpages) +{ + printf("Non null pages: "); + for (int i = 0; i < numpages; i++) + { + if (pagetable[i] != 0xffffffff) + { + printf("%d ", i); + } + } + printf("\n"); +} +*/ + + +void sparse_image_t::read_header() +{ + BX_ASSERT(sizeof(header) == HEADER_SIZE); + + int ret = ::read(fd, &header, sizeof(header)); + + if (-1 == ret) + { + panic(strerror(errno)); + } + + if (sizeof(header) != ret) + { + panic("could not read entire header"); + } + + if (little_to_local_endian(header.magic) != SPARSE_HEADER_MAGIC) + { + panic("failed header magic check"); + } + + if (little_to_local_endian(header.version) != 1) + { + panic("unknown version in header"); + } + + pagesize = little_to_local_endian(header.pagesize); + uint32 numpages = little_to_local_endian(header.numpages); + + total_size = pagesize; + total_size *= numpages; + + pagesize_shift = 0; + while ((pagesize >> pagesize_shift) > 1) pagesize_shift++; + + if ((1 << pagesize_shift) != pagesize) + { + panic("failed block size header check"); + } + + pagesize_mask = pagesize - 1; + + size_t preamble_size = (sizeof(uint32) * numpages) + sizeof(header); + data_start = 0; + while (data_start < preamble_size) data_start += pagesize; + + bool did_mmap = false; + +#ifdef _POSIX_MAPPED_FILES +// Try to memory map from the beginning of the file (0 is trivially a page multiple) + void * mmap_header = mmap(NULL, preamble_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (mmap_header == MAP_FAILED) + { + BX_INFO(("failed to mmap sparse disk file - using conventional file access")); + mmap_header = NULL; + } + else + { + mmap_length = preamble_size; + did_mmap = true; + pagetable = ((uint32 *) (((uint8 *) mmap_header) + sizeof(header))); + +// system_pagesize = getpagesize(); + system_pagesize_mask = getpagesize() - 1; + } +#endif + + if (!did_mmap) + { + pagetable = new uint32[numpages]; + + if (pagetable == NULL) + { + panic("could not allocate memory for sparse disk block table"); + } + + ret = ::read(fd, pagetable, sizeof(uint32) * numpages); + + if (-1 == ret) + { + panic(strerror(errno)); + } + + if ((sizeof(uint32) * numpages) != ret) + { + panic("could not read entire block table"); + } + } +} + +int sparse_image_t::open (const char* pathname0) +{ + pathname = strdup(pathname0); + BX_DEBUG(("sparse_image_t.open")); + + fd = ::open(pathname, O_RDWR +#ifdef O_BINARY + | O_BINARY +#endif + ); + + if (fd < 0) + { + // open failed. + return -1; + } + BX_DEBUG(("sparse_image: open image %s", pathname)); + + read_header(); + + struct stat stat_buf; + if (0 != fstat(fd, &stat_buf)) panic(("fstat() returns error!")); + + underlying_filesize = stat_buf.st_size; + + if ((underlying_filesize % pagesize) != 0) + panic("size of sparse disk image is not multiple of page size"); + + underlying_current_filepos = 0; + if (-1 == ::lseek(fd, 0, SEEK_SET)) + panic("error while seeking to start of file"); + + lseek(0, SEEK_SET); + + //showpagetable(pagetable, header.numpages); + + char * parentpathname = strdup(pathname); + char lastchar = ::increment_string(parentpathname, -1); + + if ((lastchar >= '0') && (lastchar <= '9')) + { + struct stat stat_buf; + if (0 == lstat(parentpathname, &stat_buf)) + { + parent_image = new sparse_image_t(); + int ret = parent_image->open(parentpathname); + if (ret != 0) return ret; + if ( (parent_image->pagesize != pagesize) + || (parent_image->total_size != total_size)) + { + panic("child drive image does not have same page count/page size configuration"); + } + } + } + + if (parentpathname != NULL) free(parentpathname); + + return 0; // success. +} + +void sparse_image_t::close () +{ + BX_DEBUG(("concat_image_t.close")); + if (pathname != NULL) + { + free(pathname); + } +#ifdef _POSIX_MAPPED_FILES + if (mmap_header != NULL) + { + int ret = munmap(mmap_header, mmap_length); + if (ret != 0) + BX_INFO(("failed to un-memory map sparse disk file")); + } + pagetable = NULL; // We didn't malloc it +#endif + if (fd > -1) { + ::close(fd); + } + if (pagetable != NULL) + { + delete [] pagetable; + } + if (parent_image != NULL) + { + delete parent_image; + } +} + +off_t sparse_image_t::lseek (off_t offset, int whence) +{ + //showpagetable(pagetable, header.numpages); + + if ((offset % 512) != 0) + BX_PANIC( ("lseek HD with offset not multiple of 512")); + if (whence != SEEK_SET) + BX_PANIC( ("lseek HD with whence not SEEK_SET")); + + BX_DEBUG(("sparse_image_t.lseek(%d)", whence)); + + if (offset > total_size) + { + BX_PANIC(("sparse_image_t.lseek to byte %ld failed", (long)offset)); + return -1; + } + + //printf("Seeking to position %ld\n", (long) offset); + + set_virtual_page(offset >> pagesize_shift); + position_page_offset = offset & pagesize_mask; + + return 0; +} + +inline off_t sparse_image_t::get_physical_offset() +{ + off_t physical_offset = data_start; + physical_offset += (position_physical_page << pagesize_shift); + physical_offset += position_page_offset; + + return physical_offset; +} + +inline void sparse_image_t::set_virtual_page(uint32 new_virtual_page) +{ + position_virtual_page = new_virtual_page; + + position_physical_page = little_to_local_endian(pagetable[position_virtual_page]); +} + +ssize_t sparse_image_t::read_page_fragment(uint32 read_virtual_page, uint32 read_page_offset, size_t read_size, void * buf) +{ + if (read_virtual_page != position_virtual_page) + { + set_virtual_page(read_virtual_page); + } + + position_page_offset = read_page_offset; + + if (position_physical_page == PAGE_NOT_ALLOCATED) + { + if (parent_image != NULL) + { + return parent_image->read_page_fragment(read_virtual_page, read_page_offset, read_size, buf); + } + else + { + memset(buf, read_size, 0); + } + } + else + { + off_t physical_offset = get_physical_offset(); + + if (physical_offset != underlying_current_filepos) + { + int ret = ::lseek(fd, physical_offset, SEEK_SET); + // underlying_current_filepos update deferred + if (ret == -1) + panic(strerror(errno)); + } + + //printf("Reading %s at position %ld size %d\n", pathname, (long) physical_offset, (long) read_size); + ssize_t readret = ::read(fd, buf, read_size); + + if (readret == -1) + { + panic(strerror(errno)); + } + + if (readret != read_size) + { + panic("could not read block contents from file"); + } + + underlying_current_filepos = physical_offset + read_size; + } + + return read_size; +} + +ssize_t sparse_image_t::read(void* buf, size_t count) +{ + //showpagetable(pagetable, header.numpages); + ssize_t total_read = 0; + + if (bx_dbg.disk) + BX_DEBUG(("sparse_image_t.read %ld bytes", (long)count)); + + while (count != 0) + { + size_t can_read = pagesize - position_page_offset; + if (count < can_read) can_read = count; + + BX_ASSERT (can_read != 0); + + size_t was_read = read_page_fragment(position_virtual_page, position_page_offset, can_read, buf); + + BX_ASSERT(was_read == can_read); + + total_read += can_read; + + position_page_offset += can_read; + if (position_page_offset == pagesize) + { + position_page_offset = 0; + set_virtual_page(position_virtual_page + 1); + } + + BX_ASSERT(position_page_offset < pagesize); + + buf = (((uint8 *) buf) + can_read); + count -= can_read; + } + + return total_read; +} + +void sparse_image_t::panic(const char * message) +{ + char buffer[1024]; + if (message == NULL) + { + snprintf(buffer, sizeof(buffer), "error with sparse disk image %s", pathname); + } + else + { + snprintf(buffer, sizeof(buffer), "error with sparse disk image %s - %s", pathname, message); + } + BX_PANIC((buffer)); +} + +ssize_t sparse_image_t::write (const void* buf, size_t count) +{ + //showpagetable(pagetable, header.numpages); + + ssize_t total_written = 0; + + uint32 update_pagetable_start = position_virtual_page; + uint32 update_pagetable_count = 0; + + if (bx_dbg.disk) + BX_DEBUG(("sparse_image_t.write %ld bytes", (long)count)); + + while (count != 0) + { + size_t can_write = pagesize - position_page_offset; + if (count < can_write) can_write = count; + + BX_ASSERT (can_write != 0); + + if (position_physical_page == PAGE_NOT_ALLOCATED) + { + // We just add on another page at the end of the file + // Reclamation, compaction etc should currently be done off-line + + size_t data_size = underlying_filesize - data_start; + BX_ASSERT((data_size % pagesize) == 0); + + + uint32 data_size_pages = data_size / pagesize; + uint32 next_data_page = data_size_pages; + + pagetable[position_virtual_page] = local_to_little_endian(next_data_page); + position_physical_page = next_data_page; + + off_t page_file_start = data_start + (position_physical_page << pagesize_shift); + + if (parent_image != NULL) + { + // If we have a parent, we must merge our portion with the parent + void * writebuffer = NULL; + + if (can_write == pagesize) + { + writebuffer = (void *) buf; + } + else + { + writebuffer = malloc(pagesize); + if (writebuffer == NULL) + panic("Cannot allocate sufficient memory for page-merge in write"); + + // Read entire page - could optimize, but simple for now + parent_image->read_page_fragment(position_virtual_page, 0, pagesize, writebuffer); + + void * dest_start = ((uint8 *) writebuffer) + position_page_offset; + memcpy(dest_start, buf, can_write); + } + + int ret; + ret = ::lseek(fd, page_file_start, SEEK_SET); + // underlying_current_filepos update deferred + if (-1 == ret) panic(strerror(errno)); + + ret = ::write(fd, writebuffer, pagesize); + + if (-1 == ret) panic(strerror(errno)); + + if (pagesize != ret) panic("failed to write entire merged page to disk"); + + if (can_write != pagesize) + { + free(writebuffer); + } + } + else + { + // We need to write a zero page because read has been returning zeroes + // We seek as close to the page end as possible, and then write a little + // This produces a sparse file which has blanks + // Also very quick, even when pagesize is massive + int ret; + ret = ::lseek(fd, page_file_start + pagesize - 4, SEEK_SET); + // underlying_current_filepos update deferred + if (-1 == ret) panic(strerror(errno)); + + uint32 zero = 0; + ret = ::write(fd, &zero, 4); + + if (-1 == ret) panic(strerror(errno)); + + if (4 != ret) panic("failed to write entire blank page to disk"); + } + + update_pagetable_count = (position_virtual_page - update_pagetable_start) + 1; + underlying_filesize = underlying_current_filepos = page_file_start + pagesize; + } + + BX_ASSERT(position_physical_page != PAGE_NOT_ALLOCATED); + + off_t physical_offset = get_physical_offset(); + + if (physical_offset != underlying_current_filepos) + { + int ret = ::lseek(fd, physical_offset, SEEK_SET); + // underlying_current_filepos update deferred + if (ret == -1) + panic(strerror(errno)); + } + + //printf("Writing at position %ld size %d\n", (long) physical_offset, can_write); + ssize_t writeret = ::write(fd, buf, can_write); + + if (writeret == -1) + { + panic(strerror(errno)); + } + + if (writeret != can_write) + { + panic("could not write block contents to file"); + } + + underlying_current_filepos = physical_offset + can_write; + + total_written += can_write; + + position_page_offset += can_write; + if (position_page_offset == pagesize) + { + position_page_offset = 0; + set_virtual_page(position_virtual_page + 1); + } + + BX_ASSERT(position_page_offset < pagesize); + + buf = (((uint8 *) buf) + can_write); + count -= can_write; + } + + if (update_pagetable_count != 0) + { + bool done = false; + off_t pagetable_write_from = sizeof(header) + (sizeof(uint32) * update_pagetable_start); + size_t write_bytecount = update_pagetable_count * sizeof(uint32); + +#ifdef _POSIX_MAPPED_FILES + if (mmap_header != NULL) + { + // Sync from the beginning of the page + size_t system_page_offset = pagetable_write_from & system_pagesize_mask; + void * start = ((uint8 *) mmap_header + pagetable_write_from - system_page_offset); + + int ret = msync(start, system_page_offset + write_bytecount, MS_ASYNC); + + if (ret != 0) + panic(strerror(errno)); + + done = true; + } +#endif + + if (!done) + { + int ret = ::lseek(fd, pagetable_write_from, SEEK_SET); + // underlying_current_filepos update deferred + if (ret == -1) panic(strerror(errno)); + + //printf("Writing header at position %ld size %ld\n", (long) pagetable_write_from, (long) write_bytecount); + ret = ::write(fd, &pagetable[update_pagetable_start], write_bytecount); + if (ret == -1) panic(strerror(errno)); + if (ret != write_bytecount) panic("could not write entire updated block header"); + + underlying_current_filepos = pagetable_write_from + write_bytecount; + } + } + + return total_written; +} +#endif /* BX_SPARSE_HD_SUPPORT */ #if DLL_HD_SUPPORT /*** dll_image_t function definitions ***/ Index: iodev/harddrv.h =================================================================== RCS file: /cvsroot/bochs/bochs/iodev/harddrv.h,v retrieving revision 1.18 diff -u -r1.18 harddrv.h --- iodev/harddrv.h 25 Oct 2002 11:44:40 -0000 1.18 +++ iodev/harddrv.h 27 Jan 2003 12:37:53 -0000 @@ -24,6 +24,32 @@ // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#if BX_SPARSE_HD_SUPPORT + +// Format of a sparse file: +// 256 byte header, containing details such as page size and number of pages +// Page indirection table, mapping virtual pages to physical pages within file +// Physical pages till end of file + +#define SPARSE_HEADER_MAGIC (0x02468ace) +#define SPARSE_HEADER_VERSION 1 +#define HEADER_SIZE (256) // Plenty of room for later +#define PAGE_NOT_ALLOCATED (0xffffffff) + + typedef struct + { + uint32 magic; + uint32 version; + uint32 pagesize; + uint32 numpages; + + uint32 padding[60]; + } sparse_header_t; +#endif /* BX_SPARSE_HD_SUPPORT */ + +#ifndef INCLUDE_ONLY_SPARSE_HEADER + typedef enum _sense { SENSE_NONE = 0, SENSE_NOT_READY = 2, SENSE_ILLEGAL_REQUEST = 5, SENSE_UNIT_ATTENTION = 6 @@ -87,7 +113,7 @@ private: int fd; - + }; #if BX_SPLIT_HD_SUPPORT @@ -137,6 +163,77 @@ }; #endif /* BX_SPLIT_HD_SUPPORT */ +#if BX_SPARSE_HD_SUPPORT + +class sparse_image_t : public device_image_t +{ + public: + // Default constructor + sparse_image_t(); + + // Open a image. Returns non-negative if successful. + int open (const char* pathname); + + // Close the image. + void close (); + + // Position ourselves. Return the resulting offset from the + // beginning of the file. + off_t lseek (off_t offset, int whence); + + // Read count bytes to the buffer buf. Return the number of + // bytes read (count). + ssize_t read (void* buf, size_t count); + + // Write count bytes from buf. Return the number of bytes + // written (count). + ssize_t write (const void* buf, size_t count); + + private: + int fd; + +#ifdef _POSIX_MAPPED_FILES + void * mmap_header; + size_t mmap_length; + size_t system_pagesize_mask; +#endif + uint32 * pagetable; + + // Header is written to disk in little-endian (x86) format + // Thus needs to be converted on big-endian systems before read + // The pagetable is also kept little endian + + sparse_header_t header; + + uint32 pagesize; + int pagesize_shift; + uint32 pagesize_mask; + + off_t data_start; + off_t underlying_filesize; + + char * pathname; + + off_t position; + + uint32 position_virtual_page; + uint32 position_physical_page; + uint32 position_page_offset; + + off_t underlying_current_filepos; + + off_t total_size; + + void panic(const char * message); + off_t sparse_image_t::get_physical_offset(); + void sparse_image_t::set_virtual_page(uint32 new_virtual_page); + void read_header(); + ssize_t read_page_fragment(uint32 read_virtual_page, uint32 read_page_offset, size_t read_size, void * buf); + + sparse_image_t * parent_image; +}; +#endif /* BX_SPARSE_HD_SUPPORT */ + #if EXTERNAL_DISK_SIMULATOR #include "external-disk-simulator.h" #endif @@ -367,3 +464,5 @@ #endif }; + +#endif Index: misc/bximage.c =================================================================== RCS file: /cvsroot/bochs/bochs/misc/bximage.c,v retrieving revision 1.17 diff -u -r1.17 bximage.c --- misc/bximage.c 26 Nov 2002 11:21:31 -0000 1.17 +++ misc/bximage.c 27 Jan 2003 12:37:53 -0000 @@ -1,4 +1,4 @@ -/* +/* * misc/bximage.c * $Id: patch.sparsedisk-justinsb,v 1.2 2003-03-04 21:05:38 cbothamy Exp $ * @@ -16,6 +16,18 @@ #endif #include "config.h" +#if BX_SPARSE_HD_SUPPORT +#include + +#define uint8 Bit8u +#define uint16 Bit16u +#define uint32 Bit32u + +#define INCLUDE_ONLY_SPARSE_HEADER 1 +#include "../iodev/harddrv.h" + +#endif // BX_SPARSE_HD_SUPPORT + char *EOF_ERR = "ERROR: End of input"; char *rcsid = "$Id: patch.sparsedisk-justinsb,v 1.2 2003-03-04 21:05:38 cbothamy Exp $"; char *divider = "========================================================================"; @@ -190,12 +202,41 @@ return 0; } +#if BX_SPARSE_HD_SUPPORT +// fileset is like memset but for a file handle +void fileset(FILE * fp, int c, size_t n) +{ +#define BLOCK_SIZE (1024) + int block[BLOCK_SIZE]; + size_t left_to_write = n; + + memset(block, c, sizeof(block)); + + while (left_to_write > 0) + { + size_t write = sizeof(block); + if (write > left_to_write) write = left_to_write; + + if (1 != fwrite(block, write, 1, fp)) + { + fclose (fp); + fatal ("ERROR: The disk image is not complete - could not write data block!"); + } + + left_to_write -= write; + } + +} +#endif + /* produce the image file */ -int make_image (Bit64u sec, char *filename) +int make_image (Bit64u sec, char *filename, int sparse) { - FILE *fp; + FILE *fp; char buffer[1024]; - +#if BX_SPARSE_HD_SUPPORT + Bit64u numpages; +#endif // check if it exists before trashing someone's disk image fp = fopen (filename, "r"); if (fp) { @@ -203,7 +244,7 @@ sprintf (buffer, "\nThe disk image '%s' already exists. Are you sure you want to replace it?\nPlease type yes or no. [no] ", filename); if (ask_yn (buffer, 0, &confirm) < 0) fatal (EOF_ERR); - if (!confirm) + if (!confirm) fatal ("ERROR: Aborted"); fclose (fp); } @@ -221,27 +262,69 @@ printf ("\nWriting: ["); - /* +#if BX_SPARSE_HD_SUPPORT + if (sparse) + { + sparse_header_t header; + size_t sizesofar; + size_t padtopagesize; + + memset(&header, 0, sizeof(header)); + header.magic = SPARSE_HEADER_MAGIC; + header.version = SPARSE_HEADER_VERSION; + + header.pagesize = (1 << 10) * 32; // Use 32 KB Pages - could be configurable + numpages = (sec / (header.pagesize / 512)) + 1; + + header.numpages = numpages; + + if (numpages != header.numpages) + { + fclose (fp); + fatal ("ERROR: The disk image is too large for a sparse image!"); + // Could increase page size here. + // But note this only happens at 128 Terabytes! + } + + if (fwrite(&header, sizeof(header), 1, fp) != 1) + { + fclose (fp); + fatal ("ERROR: The disk image is not complete - could not write header!"); + } + + fileset(fp, 0xff, 4 * header.numpages); + + sizesofar = HEADER_SIZE + (4 * header.numpages); + padtopagesize = header.pagesize - (sizesofar & (header.pagesize - 1)); + + fileset(fp, 0, padtopagesize); + } +#endif + + if (!sparse) + { + /* * seek to sec*512-1 and write a single character. * can't just do: fseek(fp, 512*sec-1, SEEK_SET) * because 512*sec may be too large for signed int. */ - while (sec > 0) - { - /* temp <-- min(sec, 4194303) + while (sec > 0) + { + /* temp <-- min(sec, 4194303) * 4194303 is (int)(0x7FFFFFFF/512) */ - long temp = ((sec < 4194303) ? sec : 4194303); - fseek(fp, 512*temp, SEEK_CUR); - sec -= temp; - } - - fseek(fp, -1, SEEK_CUR); - if (fputc('\0', fp) == EOF) - { - fclose (fp); - fatal ("ERROR: The disk image is not complete!"); - } + long temp = ((sec < 4194303) ? sec : 4194303); + fseek(fp, 512*temp, SEEK_CUR); + sec -= temp; + } + + fseek(fp, -1, SEEK_CUR); + if (fputc('\0', fp) == EOF) + { + fclose (fp); + fatal ("ERROR: The disk image is not complete!"); + } + } printf ("] Done.\n"); fclose (fp); @@ -254,6 +337,7 @@ Bit64s sectors = 0; char filename[256]; char bochsrc_line[256]; + int sparse = 0; print_banner (); filename[0] = 0; if (ask_menu (fdhd_menu, fdhd_n_choices, fdhd_choices, 1, &hd) < 0) @@ -273,6 +357,10 @@ printf (" total size=%.2f megabytes\n", (float)sectors*512.0/1024.0/1024.0); if (ask_string ("\nWhat should I name the image?\n[c.img] ", "c.img", filename) < 0) fatal (EOF_ERR); +#if BX_SPARSE_HD_SUPPORT + if (ask_yn ("\nShould I create a sparse image?\nPlease type yes or no. [no] ", 0, &sparse) < 0) + fatal (EOF_ERR); +#endif //BX_SPARSE_HD_SUPPORT sprintf (bochsrc_line, "ata0-master: type=disk, path=\"%s\", cylinders=%d, heads=%d, spt=%d", filename, cyl, heads, spt); } else { int fdsize, cyl=0, heads=0, spt=0; @@ -285,7 +373,7 @@ case 2: name="1_2"; cyl=80; heads=2; spt=15; break; /* 1.2 meg */ case 3: name="1_44"; cyl=80; heads=2; spt=18; break; /* 1.44 meg */ case 4: name="2_88"; cyl=80; heads=2; spt=36; break; /* 2.88 meg */ - default: + default: fatal ("ERROR: fdsize out of range"); } sectors = cyl*heads*spt; @@ -303,13 +391,13 @@ fatal ("ERROR: Illegal disk size!"); if (strlen (filename) < 1) fatal ("ERROR: Illegal filename"); - make_image (sectors, filename); + make_image (sectors, filename, sparse); printf ("\nI wrote %lld bytes to %s.\n", sectors*512, filename); printf ("\nThe following line should appear in your bochsrc:\n"); printf (" %s\n", bochsrc_line); myexit(0); // make picky compilers (c++, gcc) happy, - // even though we leave via 'myexit' just above + // even though we leave via 'myexit' just above return 0; }