///////////////////////////////////////////////////////////////////////// // $Id$ ///////////////////////////////////////////////////////////////////////// // // Virtual VFAT image support (shadows a local directory) // ported from QEMU block driver with some additions (see below) // // Copyright (c) 2004,2005 Johannes E. Schindelin // Copyright (C) 2010-2014 The Bochs Project // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // ///////////////////////////////////////////////////////////////////////// // ADDITIONS: // - win32 specific directory functions (required for MSVC) // - configurable disk geometry // - read MBR and boot sector from file // - FAT32 support // - volatile runtime write support using the hdimage redolog_t class // - ask user on Bochs exit if directory and file changes should be committed // - save and restore FAT file attributes using a separate file // - set file modification date and time after committing file changes // - vvfat floppy support (1.44 MB media only) // 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 #ifndef WIN32 #include #include #endif #include "iodev.h" #include "hdimage.h" #include "vvfat.h" #define LOG_THIS bx_devices.pluginHDImageCtl-> #define VVFAT_MBR "vvfat_mbr.bin" #define VVFAT_BOOT "vvfat_boot.bin" #define VVFAT_ATTR "vvfat_attr.cfg" // portable mkdir / rmdir static int bx_mkdir(const char *path) { #ifndef WIN32 return mkdir(path, 0755); #else return (CreateDirectory(path, NULL) != 0) ? 0 : -1; #endif } static int bx_rmdir(const char *path) { #ifndef WIN32 return rmdir(path); #else return (RemoveDirectory(path) != 0) ? 0 : -1; #endif } // dynamic array functions static inline void array_init(array_t* array,unsigned int item_size) { array->pointer = NULL; array->size = 0; array->next = 0; array->item_size = item_size; } static inline void array_free(array_t* array) { if (array->pointer) free(array->pointer); array->size=array->next = 0; } // does not automatically grow static inline void* array_get(array_t* array,unsigned int index) { assert(index < array->next); return array->pointer + index * array->item_size; } static inline int array_ensure_allocated(array_t* array, int index) { if ((index + 1) * array->item_size > array->size) { int new_size = (index + 32) * array->item_size; array->pointer = (char*)realloc(array->pointer, new_size); if (!array->pointer) return -1; memset(array->pointer + array->size, 0, new_size - array->size); array->size = new_size; array->next = index + 1; } return 0; } static inline void* array_get_next(array_t* array) { unsigned int next = array->next; void* result; if (array_ensure_allocated(array, next) < 0) return NULL; array->next = next + 1; result = array_get(array, next); return result; } static inline void* array_insert(array_t* array,unsigned int index,unsigned int count) { if ((array->next+count)*array->item_size > array->size) { int increment = count*array->item_size; array->pointer = (char*)realloc(array->pointer, array->size+increment); if (!array->pointer) return NULL; array->size += increment; } memmove(array->pointer+(index+count)*array->item_size, array->pointer+index*array->item_size, (array->next-index)*array->item_size); array->next += count; return array->pointer+index*array->item_size; } /* this performs a "roll", so that the element which was at index_from becomes * index_to, but the order of all other elements is preserved. */ static inline int array_roll(array_t* array, int index_to, int index_from, int count) { char* buf; char* from; char* to; int is; if (!array || (index_to < 0) || (index_to >= (int)array->next) || (index_from < 0 || (index_from >= (int)array->next))) return -1; if (index_to == index_from) return 0; is = array->item_size; from = array->pointer+index_from*is; to = array->pointer+index_to*is; buf = (char*)malloc(is*count); memcpy(buf, from, is*count); if (index_to < index_from) memmove(to+is*count, to, from-to); else memmove(from, from+is*count, to-from); memcpy(to, buf, is*count); free(buf); return 0; } #if 0 static inline int array_remove_slice(array_t* array,int index, int count) { assert(index >=0); assert(count > 0); assert(index + count <= (int)array->next); if (array_roll(array,array->next-1,index,count)) return -1; array->next -= count; return 0; } static int array_remove(array_t* array,int index) { return array_remove_slice(array, index, 1); } // return the index for a given member static int array_index(array_t* array, void* pointer) { size_t offset = (char*)pointer - array->pointer; assert((offset % array->item_size) == 0); assert(offset/array->item_size < array->next); return offset/array->item_size; } #endif #if defined(_MSC_VER) #pragma pack(push, 1) #elif defined(__MWERKS__) && defined(macintosh) #pragma options align=packed #endif typedef #if defined(_MSC_VER) && (_MSC_VER>=1300) __declspec(align(1)) #endif struct bootsector_t { Bit8u jump[3]; Bit8u name[8]; Bit16u sector_size; Bit8u sectors_per_cluster; Bit16u reserved_sectors; Bit8u number_of_fats; Bit16u root_entries; Bit16u total_sectors16; Bit8u media_type; Bit16u sectors_per_fat; Bit16u sectors_per_track; Bit16u number_of_heads; Bit32u hidden_sectors; Bit32u total_sectors; union { #if defined(_MSC_VER) && (_MSC_VER>=1300) __declspec(align(1)) #endif struct { Bit8u drive_number; Bit8u reserved; Bit8u signature; Bit32u id; Bit8u volume_label[11]; Bit8u fat_type[8]; Bit8u ignored[0x1c0]; } #if !defined(_MSC_VER) GCC_ATTRIBUTE((packed)) #endif fat16; #if defined(_MSC_VER) && (_MSC_VER>=1300) __declspec(align(1)) #endif struct { Bit32u sectors_per_fat; Bit16u flags; Bit8u major, minor; Bit32u first_cluster_of_root_dir; Bit16u info_sector; Bit16u backup_boot_sector; Bit8u reserved1[12]; Bit8u drive_number; Bit8u reserved2; Bit8u signature; Bit32u id; Bit8u volume_label[11]; Bit8u fat_type[8]; Bit8u ignored[0x1a4]; } #if !defined(_MSC_VER) GCC_ATTRIBUTE((packed)) #endif fat32; } u; Bit8u magic[2]; } #if !defined(_MSC_VER) GCC_ATTRIBUTE((packed)) #endif bootsector_t; typedef #if defined(_MSC_VER) && (_MSC_VER>=1300) __declspec(align(1)) #endif struct partition_t { Bit8u attributes; /* 0x80 = bootable */ mbr_chs_t start_CHS; Bit8u fs_type; /* 0x1 = FAT12, 0x6 = FAT16, 0xe = FAT16_LBA, 0xb = FAT32, 0xc = FAT32_LBA */ mbr_chs_t end_CHS; Bit32u start_sector_long; Bit32u length_sector_long; } #if !defined(_MSC_VER) GCC_ATTRIBUTE((packed)) #endif partition_t; typedef #if defined(_MSC_VER) && (_MSC_VER>=1300) __declspec(align(1)) #endif struct mbr_t { Bit8u ignored[0x1b8]; Bit32u nt_id; Bit8u ignored2[2]; partition_t partition[4]; Bit8u magic[2]; } #if !defined(_MSC_VER) GCC_ATTRIBUTE((packed)) #endif mbr_t; typedef #if defined(_MSC_VER) && (_MSC_VER>=1300) __declspec(align(1)) #endif struct infosector_t { Bit32u signature1; Bit8u ignored[0x1e0]; Bit32u signature2; Bit32u free_clusters; Bit32u mra_cluster; // most recently allocated cluster Bit8u reserved[14]; Bit8u magic[2]; } #if !defined(_MSC_VER) GCC_ATTRIBUTE((packed)) #endif infosector_t; #if defined(_MSC_VER) #pragma pack(pop) #elif defined(__MWERKS__) && defined(macintosh) #pragma options align=reset #endif vvfat_image_t::vvfat_image_t(Bit64u size, const char* _redolog_name) { if (sizeof(bootsector_t) != 512) { BX_PANIC(("system error: invalid bootsector structure size")); } first_sectors = new Bit8u[0xc000]; memset(&first_sectors[0], 0, 0xc000); hd_size = size; redolog = new redolog_t(); redolog_temp = NULL; redolog_name = NULL; if (_redolog_name != NULL) { if ((strlen(_redolog_name) > 0) && (strcmp(_redolog_name,"none") != 0)) { redolog_name = strdup(_redolog_name); } } } vvfat_image_t::~vvfat_image_t() { delete [] first_sectors; delete redolog; } bx_bool vvfat_image_t::sector2CHS(Bit32u spos, mbr_chs_t *chs) { Bit32u head, sector; sector = spos % spt; spos /= spt; head = spos % heads; spos /= heads; if (spos > 1023) { /* Overflow, it happens if 32bit sector positions are used, while CHS is only 24bit. Windows/Dos is said to take 1023/255/63 as nonrepresentable CHS */ chs->head = 0xff; chs->sector = 0xff; chs->cylinder = 0xff; return 1; } chs->head = (Bit8u)head; chs->sector = (Bit8u)((sector+1) | ((spos >> 8) << 6)); chs->cylinder = (Bit8u)spos; return 0; } void vvfat_image_t::init_mbr(void) { mbr_t* real_mbr = (mbr_t*)first_sectors; partition_t* partition = &(real_mbr->partition[0]); bx_bool lba; // Win NT Disk Signature real_mbr->nt_id = htod32(0xbe1afdfa); partition->attributes = 0x80; // bootable // LBA is used when partition is outside the CHS geometry lba = sector2CHS(offset_to_bootsector, &partition->start_CHS); lba |= sector2CHS(sector_count - 1, &partition->end_CHS); // LBA partitions are identified only by start/length_sector_long not by CHS partition->start_sector_long = htod32(offset_to_bootsector); partition->length_sector_long = htod32(sector_count - offset_to_bootsector); /* FAT12/FAT16/FAT32 */ /* DOS uses different types when partition is LBA, probably to prevent older versions from using CHS on them */ partition->fs_type = fat_type==12 ? 0x1: fat_type==16 ? (lba?0xe:0x06): /*fat_tyoe==32*/ (lba?0xc:0x0b); real_mbr->magic[0] = 0x55; real_mbr->magic[1] = 0xaa; } // dest is assumed to hold 258 bytes, and pads with 0xffff up to next multiple of 26 static inline int short2long_name(char* dest,const char* src) { int i; int len; for (i = 0; (i < 129) && src[i]; i++) { dest[2*i] = src[i]; dest[2*i+1] = 0; } len = 2 * i; dest[2*i] = dest[2*i+1] = 0; for (i = 2 * i + 2; (i % 26); i++) dest[i] = (char)0xff; return len; } direntry_t* vvfat_image_t::create_long_filename(const char* filename) { char buffer[258]; int length = short2long_name(buffer, filename), number_of_entries = (length+25) / 26, i; direntry_t* entry; for (i = 0; i < number_of_entries; i++) { entry = (direntry_t*)array_get_next(&directory); entry->attributes = 0xf; entry->reserved[0] = 0; entry->begin = 0; entry->name[0] = (number_of_entries - i) | (i==0 ? 0x40:0); } for (i = 0; i < 26 * number_of_entries; i++) { int offset = (i % 26); if (offset < 10) offset = 1 + offset; else if (offset < 22) offset = 14 + offset - 10; else offset = 28 + offset - 22; entry = (direntry_t*)array_get(&directory, directory.next - 1 - (i / 26)); entry->name[offset] = buffer[i]; } return (direntry_t*)array_get(&directory, directory.next-number_of_entries); } static char is_long_name(const direntry_t* direntry) { return direntry->attributes == 0xf; } static void set_begin_of_direntry(direntry_t* direntry, Bit32u begin) { direntry->begin = htod16(begin & 0xffff); direntry->begin_hi = htod16((begin >> 16) & 0xffff); } static inline Bit8u fat_chksum(const direntry_t* entry) { Bit8u chksum = 0; int i; for (i = 0; i < 11; i++) { unsigned char c; c = (i < 8) ? entry->name[i] : entry->extension[i-8]; chksum = (((chksum & 0xfe) >> 1) | ((chksum & 0x01) ? 0x80:0)) + c; } return chksum; } void vvfat_image_t::fat_set(unsigned int cluster, Bit32u value) { if (fat_type == 32) { Bit32u* entry = (Bit32u*)array_get(&fat, cluster); *entry = htod32(value); } else if (fat_type == 16) { Bit16u* entry = (Bit16u*)array_get(&fat, cluster); *entry = htod16(value & 0xffff); } else { int offset = (cluster * 3 / 2); Bit8u* p = (Bit8u*)array_get(&fat, offset); switch (cluster & 1) { case 0: p[0] = value & 0xff; p[1] = (p[1] & 0xf0) | ((value>>8) & 0xf); break; case 1: p[0] = (p[0]&0xf) | ((value&0xf)<<4); p[1] = (value>>4); break; } } } void vvfat_image_t::init_fat(void) { if (fat_type == 12) { array_init(&fat, 1); array_ensure_allocated(&fat, sectors_per_fat * 0x200 * 3 / 2 - 1); } else { array_init(&fat, (fat_type==32) ? 4:2); array_ensure_allocated(&fat, sectors_per_fat * 0x200 / fat.item_size - 1); } memset(fat.pointer, 0, fat.size); switch (fat_type) { case 12: max_fat_value = 0xfff; break; case 16: max_fat_value = 0xffff; break; case 32: max_fat_value = 0x0fffffff; break; default: max_fat_value = 0; /* error... */ } } direntry_t* vvfat_image_t::create_short_and_long_name( unsigned int directory_start, const char* filename, int is_dot) { int i, j, long_index = directory.next; direntry_t* entry = NULL; direntry_t* entry_long = NULL; char tempfn[BX_PATHNAME_LEN]; if (is_dot) { entry = (direntry_t*)array_get_next(&directory); memset(entry->name,0x20,11); memcpy(entry->name,filename,strlen(filename)); return entry; } entry_long = create_long_filename(filename); // short name should not contain spaces j = 0; for (i = 0; i < (int)strlen(filename); i++) { if (filename[i] != ' ') tempfn[j++] = filename[i]; } tempfn[j] = 0; i = strlen(tempfn); for (j = i - 1; j > 0 && tempfn[j] != '.'; j--); if (j > 0) i = (j > 8 ? 8 : j); else if (i > 8) i = 8; entry = (direntry_t*)array_get_next(&directory); memset(entry->name, 0x20, 11); memcpy(entry->name, tempfn, i); if (j > 0) for (i = 0; i < 3 && tempfn[j+1+i]; i++) entry->extension[i] = tempfn[j+1+i]; // upcase & remove unwanted characters for (i=10;i>=0;i--) { if (i==10 || i==7) for (;i>0 && entry->name[i]==' ';i--); if ((entry->name[i]<' ') || (entry->name[i]>0x7f) || strchr(".*?<>|\":/\\[];,+='",entry->name[i])) entry->name[i]='_'; else if (entry->name[i]>='a' && entry->name[i]<='z') entry->name[i]+='A'-'a'; } if (entry->name[0] == 0xe5) entry->name[0] = 0x05; // mangle duplicates while (1) { direntry_t* entry1 = (direntry_t*)array_get(&directory, directory_start); int j; for (;entry1name,entry->name,11)) break; // found dupe if (entry1==entry) // no dupe found break; // use all 8 characters of name if (entry->name[7]==' ') { int j; for(j=6;j>0 && entry->name[j]==' ';j--) entry->name[j]='~'; } // increment number for (j=7;j>0 && entry->name[j]=='9';j--) entry->name[j]='0'; if (j > 0) { if (entry->name[j]<'0' || entry->name[j]>'9') entry->name[j]='0'; else entry->name[j]++; } } // calculate checksum; propagate to long name if (entry_long) { Bit8u chksum = fat_chksum(entry); // calculate anew, because realloc could have taken place entry_long = (direntry_t*)array_get(&directory, long_index); while (entry_longreserved[1]=chksum; entry_long++; } } return entry; } /* * Read a directory. (the index of the corresponding mapping must be passed). */ int vvfat_image_t::read_directory(int mapping_index) { mapping_t* mapping = (mapping_t*)array_get(&this->mapping, mapping_index); direntry_t* direntry; const char* dirname = mapping->path; Bit32u first_cluster = mapping->begin; int parent_index = mapping->info.dir.parent_mapping_index; mapping_t* parent_mapping = (mapping_t*) (parent_index >= 0 ? array_get(&this->mapping, parent_index) : NULL); int first_cluster_of_parent = parent_mapping ? (int)parent_mapping->begin : -1; int count = 0; #ifndef WIN32 DIR* dir = opendir(dirname); struct dirent* entry; int i; assert(mapping->mode & MODE_DIRECTORY); if (!dir) { mapping->end = mapping->begin; return -1; } i = mapping->info.dir.first_dir_index = first_cluster == first_cluster_of_root_dir ? 0 : directory.next; if (first_cluster != first_cluster_of_root_dir) { // create the top entries of a subdirectory direntry = create_short_and_long_name(i, ".", 1); direntry = create_short_and_long_name(i, "..", 1); } // actually read the directory, and allocate the mappings while ((entry=readdir(dir))) { if ((first_cluster == 0) && (directory.next >= (Bit16u)(root_entries - 1))) { BX_ERROR(("Too many entries in root directory, using only %d", count)); closedir(dir); return -2; } unsigned int length = strlen(dirname) + 2 + strlen(entry->d_name); char* buffer; direntry_t* direntry; struct stat st; bx_bool is_dot = !strcmp(entry->d_name, "."); bx_bool is_dotdot = !strcmp(entry->d_name, ".."); if ((first_cluster == first_cluster_of_root_dir) && (is_dotdot || is_dot)) continue; buffer = (char*)malloc(length); snprintf(buffer,length,"%s/%s",dirname,entry->d_name); if (stat(buffer, &st) < 0) { free(buffer); continue; } bx_bool is_mbr_file = !strcmp(entry->d_name, VVFAT_MBR); bx_bool is_boot_file = !strcmp(entry->d_name, VVFAT_BOOT); bx_bool is_attr_file = !strcmp(entry->d_name, VVFAT_ATTR); if (first_cluster == first_cluster_of_root_dir) { if (is_attr_file || ((is_mbr_file || is_boot_file) && (st.st_size == 512))) { free(buffer); continue; } } count++; // create directory entry for this file if (!is_dot && !is_dotdot) { direntry = create_short_and_long_name(i, entry->d_name, 0); } else { direntry = (direntry_t*)array_get(&directory, is_dot ? i : i + 1); } direntry->attributes = (S_ISDIR(st.st_mode) ? 0x10 : 0x20); direntry->reserved[0] = direntry->reserved[1]=0; direntry->ctime = fat_datetime(st.st_ctime, 1); direntry->cdate = fat_datetime(st.st_ctime, 0); direntry->adate = fat_datetime(st.st_atime, 0); direntry->begin_hi = 0; direntry->mtime = fat_datetime(st.st_mtime, 1); direntry->mdate = fat_datetime(st.st_mtime, 0); if (is_dotdot) set_begin_of_direntry(direntry, first_cluster_of_parent); else if (is_dot) set_begin_of_direntry(direntry, first_cluster); else direntry->begin = 0; // do that later if (st.st_size > 0x7fffffff) { BX_ERROR(("File '%s' is larger than 2GB", buffer)); free(buffer); closedir(dir); return -3; } direntry->size = htod32(S_ISDIR(st.st_mode) ? 0:st.st_size); // create mapping for this file if (!is_dot && !is_dotdot && (S_ISDIR(st.st_mode) || st.st_size)) { current_mapping = (mapping_t*)array_get_next(&this->mapping); current_mapping->begin = 0; current_mapping->end = st.st_size; /* * we get the direntry of the most recent direntry, which * contains the short name and all the relevant information. */ current_mapping->dir_index = directory.next-1; current_mapping->first_mapping_index = -1; if (S_ISDIR(st.st_mode)) { current_mapping->mode = MODE_DIRECTORY; current_mapping->info.dir.parent_mapping_index = mapping_index; } else { current_mapping->mode = MODE_UNDEFINED; current_mapping->info.file.offset = 0; } current_mapping->path = buffer; current_mapping->read_only = (st.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) == 0; } else { free(buffer); } } closedir(dir); #else WIN32_FIND_DATA finddata; char filter[MAX_PATH]; wsprintf(filter, "%s\\*.*", dirname); HANDLE hFind = FindFirstFile(filter, &finddata); int i; assert(mapping->mode & MODE_DIRECTORY); if (hFind == INVALID_HANDLE_VALUE) { mapping->end = mapping->begin; return -1; } i = mapping->info.dir.first_dir_index = first_cluster == first_cluster_of_root_dir ? 0 : directory.next; if (first_cluster != first_cluster_of_root_dir) { // create the top entries of a subdirectory direntry = create_short_and_long_name(i, ".", 1); direntry = create_short_and_long_name(i, "..", 1); } // actually read the directory, and allocate the mappings do { if ((first_cluster == 0) && (directory.next >= (Bit16u)(root_entries - 1))) { BX_ERROR(("Too many entries in root directory, using only %d", count)); FindClose(hFind); return -2; } unsigned int length = lstrlen(dirname) + 2 + lstrlen(finddata.cFileName); char* buffer; direntry_t* direntry; bx_bool is_dot = !lstrcmp(finddata.cFileName, "."); bx_bool is_dotdot = !lstrcmp(finddata.cFileName, ".."); if ((first_cluster == first_cluster_of_root_dir) && (is_dotdot || is_dot)) continue; bx_bool is_mbr_file = !lstrcmp(finddata.cFileName, VVFAT_MBR); bx_bool is_boot_file = !lstrcmp(finddata.cFileName, VVFAT_BOOT); bx_bool is_attr_file = !lstrcmp(finddata.cFileName, VVFAT_ATTR); if (first_cluster == first_cluster_of_root_dir) { if (is_attr_file || ((is_mbr_file || is_boot_file) && (finddata.nFileSizeLow == 512))) continue; } buffer = (char*)malloc(length); snprintf(buffer, length, "%s/%s", dirname, finddata.cFileName); count++; // create directory entry for this file if (!is_dot && !is_dotdot) { direntry = create_short_and_long_name(i, finddata.cFileName, 0); } else { direntry = (direntry_t*)array_get(&directory, is_dot ? i : i + 1); } direntry->attributes = ((finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? 0x10 : 0x20); direntry->reserved[0] = direntry->reserved[1]=0; direntry->ctime = fat_datetime(finddata.ftCreationTime, 1); direntry->cdate = fat_datetime(finddata.ftCreationTime, 0); direntry->adate = fat_datetime(finddata.ftLastAccessTime, 0); direntry->begin_hi = 0; direntry->mtime = fat_datetime(finddata.ftLastWriteTime, 1); direntry->mdate = fat_datetime(finddata.ftLastWriteTime, 0); if (is_dotdot) set_begin_of_direntry(direntry, first_cluster_of_parent); else if (is_dot) set_begin_of_direntry(direntry, first_cluster); else direntry->begin = 0; // do that later if (finddata.nFileSizeLow > 0x7fffffff) { BX_ERROR(("File '%s' is larger than 2GB", buffer)); free(buffer); FindClose(hFind); return -3; } direntry->size = htod32((finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? 0:finddata.nFileSizeLow); // create mapping for this file if (!is_dot && !is_dotdot && ((finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) || finddata.nFileSizeLow)) { current_mapping = (mapping_t*)array_get_next(&this->mapping); current_mapping->begin = 0; current_mapping->end = finddata.nFileSizeLow; /* * we get the direntry of the most recent direntry, which * contains the short name and all the relevant information. */ current_mapping->dir_index = directory.next-1; current_mapping->first_mapping_index = -1; if (finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { current_mapping->mode = MODE_DIRECTORY; current_mapping->info.dir.parent_mapping_index = mapping_index; } else { current_mapping->mode = MODE_UNDEFINED; current_mapping->info.file.offset = 0; } current_mapping->path = buffer; current_mapping->read_only = (finddata.dwFileAttributes & FILE_ATTRIBUTE_READONLY); } else { free(buffer); } } while (FindNextFile(hFind, &finddata)); FindClose(hFind); #endif // fill with zeroes up to the end of the cluster while (directory.next % (0x10 * sectors_per_cluster)) { direntry_t* direntry = (direntry_t*)array_get_next(&directory); memset(direntry, 0, sizeof(direntry_t)); } if (fat_type != 32) { if ((mapping_index == 0) && (directory.next < root_entries)) { // root directory int cur = directory.next; array_ensure_allocated(&directory, root_entries - 1); memset(array_get(&directory, cur), 0, (root_entries - cur) * sizeof(direntry_t)); } } // reget the mapping, since this->mapping was possibly realloc()ed mapping = (mapping_t*)array_get(&this->mapping, mapping_index); if (first_cluster == 0) { first_cluster = 2; } else { first_cluster += (directory.next - mapping->info.dir.first_dir_index) * 0x20 / cluster_size; } mapping->end = first_cluster; direntry = (direntry_t*)array_get(&directory, mapping->dir_index); set_begin_of_direntry(direntry, mapping->begin); return 0; } Bit32u vvfat_image_t::sector2cluster(off_t sector_num) { return (Bit32u)((sector_num - offset_to_data) / sectors_per_cluster) + 2; } off_t vvfat_image_t::cluster2sector(Bit32u cluster_num) { return (off_t)(offset_to_data + (cluster_num - 2) * sectors_per_cluster); } int vvfat_image_t::init_directories(const char* dirname) { bootsector_t* bootsector; infosector_t* infosector; mapping_t* mapping; unsigned int i; unsigned int cluster; char size_txt[8]; Bit64u volume_sector_count = 0, tmpsc; cluster_size = sectors_per_cluster * 0x200; cluster_buffer = new Bit8u[cluster_size]; bootsector = (bootsector_t*)(first_sectors + offset_to_bootsector * 0x200); if (!use_boot_file) { volume_sector_count = sector_count - offset_to_bootsector; tmpsc = volume_sector_count - reserved_sectors - root_entries / 16; cluster_count = (Bit32u)((tmpsc * 0x200) / ((sectors_per_cluster * 0x200) + fat_type / 4)); sectors_per_fat = ((cluster_count + 2) * fat_type / 8) / 0x200; sectors_per_fat += (((cluster_count + 2) * fat_type / 8) % 0x200) > 0; } else { if (fat_type != 32) { sectors_per_fat = bootsector->sectors_per_fat; } else { sectors_per_fat = bootsector->u.fat32.sectors_per_fat; } } offset_to_fat = offset_to_bootsector + reserved_sectors; offset_to_root_dir = offset_to_fat + sectors_per_fat * 2; offset_to_data = offset_to_root_dir + root_entries / 16; if (use_boot_file) { cluster_count = (sector_count - offset_to_data) / sectors_per_cluster; } array_init(&this->mapping, sizeof(mapping_t)); array_init(&directory, sizeof(direntry_t)); /* add volume label */ { direntry_t *entry = (direntry_t*)array_get_next(&directory); entry->attributes = 0x28; // archive | volume label entry->mdate = 0x3d81; // 01.12.2010 entry->mtime = 0x6000; // 12:00:00 memcpy(entry->name, "BOCHS VV", 8); memcpy(entry->extension, "FAT", 3); } // Now build FAT, and write back information into directory init_fat(); mapping = (mapping_t*)array_get_next(&this->mapping); mapping->begin = 0; mapping->dir_index = 0; mapping->info.dir.parent_mapping_index = -1; mapping->first_mapping_index = -1; mapping->path = strdup(dirname); i = strlen(mapping->path); if (i > 0 && mapping->path[i - 1] == '/') mapping->path[i - 1] = '\0'; mapping->mode = MODE_DIRECTORY; mapping->read_only = 0; vvfat_path = mapping->path; for (i = 0, cluster = first_cluster_of_root_dir; i < this->mapping.next; i++) { // fix fat entry if not root directory of FAT12/FAT16 int fix_fat = (cluster != 0); mapping = (mapping_t*)array_get(&this->mapping, i); if (mapping->mode & MODE_DIRECTORY) { mapping->begin = cluster; if (read_directory(i)) { BX_PANIC(("Could not read directory '%s'", mapping->path)); return -1; } mapping = (mapping_t*)array_get(&this->mapping, i); } else { assert(mapping->mode == MODE_UNDEFINED); mapping->mode = MODE_NORMAL; mapping->begin = cluster; if (mapping->end > 0) { direntry_t* direntry = (direntry_t*)array_get(&directory, mapping->dir_index); mapping->end = cluster + 1 + (mapping->end-1) / cluster_size; set_begin_of_direntry(direntry, mapping->begin); } else { mapping->end = cluster + 1; fix_fat = 0; } } assert(mapping->begin < mapping->end); /* next free cluster */ cluster = mapping->end; if (cluster >= (cluster_count + 2)) { sprintf(size_txt, "%d", (sector_count >> 11)); BX_PANIC(("Directory does not fit in FAT%d (capacity %s MB)", fat_type, (fat_type == 12) ? (sector_count == 2880) ? "1.44":"2.88" : size_txt)); return -EINVAL; } // fix fat for entry if (fix_fat) { int j; for (j = mapping->begin; j < (int)(mapping->end - 1); j++) fat_set(j, j + 1); fat_set(mapping->end - 1, max_fat_value); } } mapping = (mapping_t*)array_get(&this->mapping, 0); assert((fat_type == 32) || (mapping->end == 2)); // the FAT signature fat_set(0, max_fat_value); fat_set(1, max_fat_value); current_mapping = NULL; if (!use_boot_file) { bootsector->jump[0] = 0xeb; if (fat_type != 32) { bootsector->jump[1] = 0x3e; } else { bootsector->jump[1] = 0x58; } bootsector->jump[2] = 0x90; memcpy(bootsector->name,"MSWIN4.1", 8); // Win95/98 need this to detect FAT32 bootsector->sector_size = htod16(0x200); bootsector->sectors_per_cluster = sectors_per_cluster; bootsector->reserved_sectors = htod16(reserved_sectors); bootsector->number_of_fats = 0x2; if (fat_type != 32) { bootsector->root_entries = htod16(root_entries); } bootsector->total_sectors16 = (Bit16u)((volume_sector_count > 0xffff) ? 0:htod16(volume_sector_count)); bootsector->media_type = ((fat_type != 12) ? 0xf8:0xf0); if (fat_type != 32) { bootsector->sectors_per_fat = htod16(sectors_per_fat); } bootsector->sectors_per_track = htod16(spt); bootsector->number_of_heads = htod16(heads); bootsector->hidden_sectors = htod32(offset_to_bootsector); bootsector->total_sectors = (Bit32u)(htod32((volume_sector_count > 0xffff) ? volume_sector_count:0)); if (fat_type != 32) { bootsector->u.fat16.drive_number = (fat_type == 12) ? 0:0x80; // assume this is hda (TODO) bootsector->u.fat16.signature = 0x29; bootsector->u.fat16.id = htod32(0xfabe1afd); memcpy(bootsector->u.fat16.volume_label, "BOCHS VVFAT", 11); memcpy(bootsector->u.fat16.fat_type, (fat_type==12) ? "FAT12 ":"FAT16 ", 8); } else { bootsector->u.fat32.sectors_per_fat = htod32(sectors_per_fat); bootsector->u.fat32.first_cluster_of_root_dir = first_cluster_of_root_dir; bootsector->u.fat32.info_sector = htod16(1); bootsector->u.fat32.backup_boot_sector = htod16(6); bootsector->u.fat32.drive_number = 0x80; // assume this is hda (TODO) bootsector->u.fat32.signature = 0x29; bootsector->u.fat32.id = htod32(0xfabe1afd); memcpy(bootsector->u.fat32.volume_label, "BOCHS VVFAT", 11); memcpy(bootsector->u.fat32.fat_type, "FAT32 ", 8); } bootsector->magic[0] = 0x55; bootsector->magic[1] = 0xaa; } fat.pointer[0] = bootsector->media_type; if (fat_type == 32) { // backup boot sector memcpy(&first_sectors[(offset_to_bootsector + 6) * 0x200], &first_sectors[offset_to_bootsector * 0x200], 0x200); // FS info sector infosector = (infosector_t*)(first_sectors + (offset_to_bootsector + 1) * 0x200); infosector->signature1 = htod32(0x41615252); infosector->signature2 = htod32(0x61417272); infosector->free_clusters = htod32(cluster_count - cluster + 2); infosector->mra_cluster = htod32(2); infosector->magic[0] = 0x55; infosector->magic[1] = 0xaa; } return 0; } bx_bool vvfat_image_t::read_sector_from_file(const char *path, Bit8u *buffer, Bit32u sector) { int fd = ::open(path, O_RDONLY #ifdef O_BINARY | O_BINARY #endif #ifdef O_LARGEFILE | O_LARGEFILE #endif ); if (fd < 0) return 0; int offset = sector * 0x200; if (::lseek(fd, offset, SEEK_SET) != offset) { ::close(fd); return 0; } int result = ::read(fd, buffer, 0x200); ::close(fd); bx_bool bootsig = ((buffer[0x1fe] == 0x55) && (buffer[0x1ff] == 0xaa)); return (result == 0x200) && bootsig; } void vvfat_image_t::set_file_attributes(void) { char path[BX_PATHNAME_LEN]; char fpath[BX_PATHNAME_LEN]; char line[512]; char *ret, *ptr; FILE *fd; Bit8u attributes; int i; sprintf(path, "%s/%s", vvfat_path, VVFAT_ATTR); fd = fopen(path, "r"); if (fd != NULL) { do { ret = fgets(line, sizeof(line) - 1, fd); if (ret != NULL) { line[sizeof(line) - 1] = '\0'; size_t len = strlen(line); if ((len > 0) && (line[len - 1] < ' ')) line[len - 1] = '\0'; ptr = strtok(line, ":"); if (ptr[0] == 34) { strcpy(fpath, ptr + 1); } else { strcpy(fpath, ptr); } if (fpath[strlen(fpath) - 1] == 34) { fpath[strlen(fpath) - 1] = '\0'; } if (strncmp(fpath, vvfat_path, strlen(vvfat_path))) { strcpy(path, fpath); sprintf(fpath, "%s/%s", vvfat_path, path); } mapping_t* mapping = find_mapping_for_path(fpath); if (mapping != NULL) { direntry_t* entry = (direntry_t*)array_get(&directory, mapping->dir_index); attributes = entry->attributes; ptr = strtok(NULL, ""); for (i = 0; i < (int)strlen(ptr); i++) { switch (ptr[i]) { case 'a': attributes &= ~0x20; break; case 'S': attributes |= 0x04; break; case 'H': attributes |= 0x02; break; case 'R': attributes |= 0x01; break; } } entry->attributes = attributes; } } } while (!feof(fd)); fclose(fd); } } int vvfat_image_t::open(const char* dirname, int flags) { Bit32u size_in_mb; char path[BX_PATHNAME_LEN]; Bit8u sector_buffer[0x200]; int filedes; const char *logname = NULL; char ftype[10]; bx_bool ftype_ok; UNUSED(flags); use_mbr_file = 0; use_boot_file = 0; fat_type = 0; sectors_per_cluster = 0; snprintf(path, BX_PATHNAME_LEN, "%s/%s", dirname, VVFAT_MBR); if (read_sector_from_file(path, sector_buffer, 0)) { mbr_t* real_mbr = (mbr_t*)sector_buffer; partition_t* partition = &(real_mbr->partition[0]); if ((partition->fs_type != 0) && (partition->length_sector_long > 0)) { if ((partition->fs_type == 0x06) || (partition->fs_type == 0x0e)) { fat_type = 16; } else if ((partition->fs_type == 0x0b) || (partition->fs_type == 0x0c)) { fat_type = 32; } else { BX_ERROR(("MBR file: unsupported FS type = 0x%02x", partition->fs_type)); } if (fat_type != 0) { sector_count = partition->start_sector_long + partition->length_sector_long; spt = partition->start_sector_long; if (partition->end_CHS.head > 15) { heads = 16; } else { heads = partition->end_CHS.head + 1; } cylinders = sector_count / (heads * spt); offset_to_bootsector = spt; memcpy(&first_sectors[0], sector_buffer, 0x200); use_mbr_file = 1; BX_INFO(("VVFAT: using MBR from file")); } } } snprintf(path, BX_PATHNAME_LEN, "%s/%s", dirname, VVFAT_BOOT); if (read_sector_from_file(path, sector_buffer, 0)) { bootsector_t* bs = (bootsector_t*)sector_buffer; if (use_mbr_file) { sprintf(ftype, "FAT%d ", fat_type); if (fat_type == 32) { ftype_ok = memcmp(bs->u.fat32.fat_type, ftype, 8) == 0; } else { ftype_ok = memcmp(bs->u.fat16.fat_type, ftype, 8) == 0; } Bit32u sc = bs->total_sectors16 + bs->total_sectors + bs->hidden_sectors; if (ftype_ok && (sc == sector_count) && (bs->number_of_fats == 2)) { use_boot_file = 1; } } else { if (memcmp(bs->u.fat16.fat_type, "FAT12 ", 8) == 0) { fat_type = 12; } else if (memcmp(bs->u.fat16.fat_type, "FAT16 ", 8) == 0) { fat_type = 16; } else if (memcmp(bs->u.fat32.fat_type, "FAT32 ", 8) == 0) { fat_type = 32; } else { memcpy(ftype, bs->u.fat16.fat_type, 8); ftype[8] = 0; BX_PANIC(("boot sector file: unsupported FS type = '%s'", ftype)); return -1; } if ((fat_type != 0) && (bs->number_of_fats == 2)) { sector_count = bs->total_sectors16 + bs->total_sectors + bs->hidden_sectors; spt = bs->sectors_per_track; if (bs->number_of_heads > 15) { heads = 16; } else { heads = bs->number_of_heads; } cylinders = sector_count / (heads * spt); offset_to_bootsector = bs->hidden_sectors; use_boot_file = 1; } } if (use_boot_file) { sectors_per_cluster = bs->sectors_per_cluster; reserved_sectors = bs->reserved_sectors; root_entries = bs->root_entries; first_cluster_of_root_dir = (fat_type != 32) ? 0 : bs->u.fat32.first_cluster_of_root_dir; memcpy(&first_sectors[offset_to_bootsector * 0x200], sector_buffer, 0x200); BX_INFO(("VVFAT: using boot sector from file")); } } if (!use_mbr_file && !use_boot_file) { if (hd_size == 1474560) { // floppy support cylinders = 80; heads = 2; spt = 18; offset_to_bootsector = 0; fat_type = 12; sectors_per_cluster = 1; first_cluster_of_root_dir = 0; root_entries = 224; reserved_sectors = 1; } else { if (cylinders == 0) { cylinders = 1024; heads = 16; spt = 63; } offset_to_bootsector = spt; } sector_count = cylinders * heads * spt; } hd_size = 512L * ((Bit64u)sector_count); if (sectors_per_cluster == 0) { size_in_mb = (Bit32u)(hd_size >> 20); if ((size_in_mb >= 2047) || (fat_type == 32)) { fat_type = 32; if (size_in_mb >= 32767) { sectors_per_cluster = 64; } else if (size_in_mb >= 16383) { sectors_per_cluster = 32; } else if (size_in_mb >= 8191) { sectors_per_cluster = 16; } else { sectors_per_cluster = 8; } first_cluster_of_root_dir = 2; root_entries = 0; reserved_sectors = 32; } else { fat_type = 16; if (size_in_mb >= 1023) { sectors_per_cluster = 64; } else if (size_in_mb >= 511) { sectors_per_cluster = 32; } else if (size_in_mb >= 255) { sectors_per_cluster = 16; } else if (size_in_mb >= 127) { sectors_per_cluster = 8; } else { sectors_per_cluster = 4; } first_cluster_of_root_dir = 0; root_entries = 512; reserved_sectors = 1; } } current_cluster = 0xffff; current_fd = 0; if ((!use_mbr_file) && (offset_to_bootsector > 0)) init_mbr(); init_directories(dirname); set_file_attributes(); // VOLATILE WRITE SUPPORT snprintf(path, BX_PATHNAME_LEN, "%s/vvfat.dir", dirname); // if redolog name was set if (redolog_name != NULL) { if (strcmp(redolog_name, "") != 0) { logname = redolog_name; } } // otherwise use path as template if (logname == NULL) { logname = path; } redolog_temp = (char*)malloc(strlen(logname) + VOLATILE_REDOLOG_EXTENSION_LENGTH + 1); sprintf(redolog_temp, "%s%s", logname, VOLATILE_REDOLOG_EXTENSION); filedes = mkstemp(redolog_temp); if (filedes < 0) { BX_PANIC(("Can't create volatile redolog '%s'", redolog_temp)); return -1; } if (redolog->create(filedes, REDOLOG_SUBTYPE_VOLATILE, hd_size) < 0) { BX_PANIC(("Can't create volatile redolog '%s'", redolog_temp)); return -1; } #if (!defined(WIN32)) && !BX_WITH_MACOS // on unix it is legal to delete an open file unlink(redolog_temp); #endif vvfat_modified = 0; BX_INFO(("'vvfat' disk opened: directory is '%s', redolog is '%s'", dirname, redolog_temp)); return 0; } direntry_t* vvfat_image_t::read_direntry(Bit8u *buffer, char *filename) { const Bit8u lfn_map[13] = {1, 3, 5, 7, 9, 14, 16, 18, 20, 22, 24, 28, 30}; direntry_t *entry; bx_bool entry_ok = 0, has_lfn = 0; char lfn_tmp[BX_PATHNAME_LEN]; int i; memset(filename, 0, BX_PATHNAME_LEN); lfn_tmp[0] = 0; do { entry = (direntry_t*)buffer; if (entry->name[0] == 0) { entry = NULL; break; } else if ((entry->name[0] != '.') && (entry->name[0] != 0xe5) && ((entry->attributes & 0x0f) != 0x08)) { if (is_long_name(entry)) { for (i = 0; i < 13; i++) { lfn_tmp[i] = buffer[lfn_map[i]]; } lfn_tmp[i] = 0; strcat(lfn_tmp, filename); strcpy(filename, lfn_tmp); has_lfn = 1; buffer += 32; } else { if (!has_lfn) { if (entry->name[0] == 0x05) entry->name[0] = 0xe5; memcpy(filename, entry->name, 8); i = 7; while ((i > 0) && (filename[i] == ' ')) filename[i--] = 0; if (entry->extension[0] != ' ') strcat(filename, "."); memcpy(filename+i+2, entry->extension, 3); i = strlen(filename) - 1; while (filename[i] == ' ') filename[i--] = 0; for (i = 0; i < (int)strlen(filename); i++) { if ((filename[i] > 0x40) && (filename[i] < 0x5b)) { filename[i] |= 0x20; } } } entry_ok = 1; } } else { buffer += 32; } } while (!entry_ok); return entry; } Bit32u vvfat_image_t::fat_get_next(Bit32u current) { if (fat_type == 32) { return dtoh32(((Bit32u*)fat2)[current]); } else if (fat_type == 16) { return dtoh16(((Bit16u*)fat2)[current]); } else { int offset = (current * 3 / 2); Bit8u* p = (((Bit8u*)fat2) + offset); Bit32u value = 0; switch (current & 1) { case 0: value = p[0] | ((p[1] & 0x0f) << 8); break; case 1: value = (p[0] >> 4) | (p[1] << 4); break; } return value; } } bx_bool vvfat_image_t::write_file(const char *path, direntry_t *entry, bx_bool create) { int fd; Bit32u csize, fsize, fstart, cur, next, rsvd_clusters, bad_cluster; Bit64u offset; Bit8u *buffer = NULL; #ifndef WIN32 struct tm tv; struct utimbuf ut; #else HANDLE hFile; FILETIME at, mt, tmp; SYSTEMTIME st; #endif csize = sectors_per_cluster * 0x200; rsvd_clusters = max_fat_value - 15; bad_cluster = max_fat_value - 8; fsize = dtoh32(entry->size); fstart = dtoh16(entry->begin) | (dtoh16(entry->begin_hi) << 16); if (create) { fd = ::open(path, O_CREAT | O_RDWR | O_TRUNC #ifdef O_BINARY | O_BINARY #endif #ifdef O_LARGEFILE | O_LARGEFILE #endif , 0644); } else { fd = ::open(path, O_RDWR | O_TRUNC #ifdef O_BINARY | O_BINARY #endif #ifdef O_LARGEFILE | O_LARGEFILE #endif ); } if (fd < 0) return 0; buffer = (Bit8u*)malloc(csize); next = fstart; do { cur = next; offset = cluster2sector(cur); lseek(offset * 0x200, SEEK_SET); read(buffer, csize); if (fsize > csize) { ::write(fd, buffer, csize); fsize -= csize; } else { ::write(fd, buffer, fsize); } next = fat_get_next(cur); if ((next >= rsvd_clusters) && (next < bad_cluster)) { BX_ERROR(("reserved clusters not supported")); } } while (next < rsvd_clusters); ::close(fd); #ifndef WIN32 tv.tm_year = (entry->mdate >> 9) + 80; tv.tm_mon = ((entry->mdate >> 5) & 0x0f) - 1; tv.tm_mday = entry->mdate & 0x1f; tv.tm_hour = (entry->mtime >> 11); tv.tm_min = (entry->mtime >> 5) & 0x3f; tv.tm_sec = (entry->mtime & 0x1f) << 1; tv.tm_isdst = -1; ut.modtime = mktime(&tv); if (entry->adate != 0) { tv.tm_year = (entry->adate >> 9) + 80; tv.tm_mon = ((entry->adate >> 5) & 0x0f) - 1; tv.tm_mday = entry->adate & 0x1f; tv.tm_hour = 0; tv.tm_min = 0; tv.tm_sec = 0; ut.actime = mktime(&tv); } else { ut.actime = ut.modtime; } utime(path, &ut); #else hFile = CreateFile(path, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile != INVALID_HANDLE_VALUE) { memset(&st, 0, sizeof(st)); st.wYear = (entry->mdate >> 9) + 1980; st.wMonth = ((entry->mdate >> 5) & 0x0f); st.wDay = entry->mdate & 0x1f; st.wHour = (entry->mtime >> 11); st.wMinute = (entry->mtime >> 5) & 0x3f; st.wSecond = (entry->mtime & 0x1f) << 1; SystemTimeToFileTime(&st, &tmp); LocalFileTimeToFileTime(&tmp, &mt); if (entry->adate != 0) { st.wYear = (entry->adate >> 9) + 1980; st.wMonth = ((entry->adate >> 5) & 0x0f); st.wDay = entry->adate & 0x1f; st.wHour = 0; st.wMinute = 0; st.wSecond = 0; SystemTimeToFileTime(&st, &tmp); LocalFileTimeToFileTime(&tmp, &at); } else { at = mt; } SetFileTime(hFile, NULL, &at, &mt); CloseHandle(hFile); } #endif if(buffer) free(buffer); return 1; } void vvfat_image_t::parse_directory(const char *path, Bit32u start_cluster) { Bit32u csize, fstart, cur, next, size, rsvd_clusters; Bit64u offset; Bit8u *buffer, *ptr; direntry_t *entry, *newentry; char filename[BX_PATHNAME_LEN]; char full_path[BX_PATHNAME_LEN]; char attr_txt[4]; const char *rel_path; mapping_t *mapping; csize = sectors_per_cluster * 0x200; rsvd_clusters = max_fat_value - 15; if (start_cluster == 0) { size = root_entries * 32; offset = offset_to_root_dir; buffer = (Bit8u*)malloc(size); lseek(offset * 0x200, SEEK_SET); read(buffer, size); } else { size = csize; buffer = (Bit8u*)malloc(size); next = start_cluster; do { cur = next; offset = cluster2sector(cur); lseek(offset * 0x200, SEEK_SET); read(buffer+(size-csize), csize); next = fat_get_next(cur); if (next < rsvd_clusters) { size += csize; buffer = (Bit8u*)realloc(buffer, size); } } while (next < rsvd_clusters); } ptr = buffer; do { newentry = read_direntry(ptr, filename); if (newentry != NULL) { sprintf(full_path, "%s/%s", path, filename); if ((newentry->attributes != 0x10) && (newentry->attributes != 0x20)) { if (vvfat_attr_fd != NULL) { attr_txt[0] = 0; if ((newentry->attributes & 0x30) == 0) strcpy(attr_txt, "a"); if (newentry->attributes & 0x04) strcpy(attr_txt, "S"); if (newentry->attributes & 0x02) strcat(attr_txt, "H"); if (newentry->attributes & 0x01) strcat(attr_txt, "R"); if (!strncmp(full_path, vvfat_path, strlen(vvfat_path))) { rel_path = (const char*)(full_path + strlen(vvfat_path) + 1); } else { rel_path = (const char*)full_path; } fprintf(vvfat_attr_fd, "\"%s\":%s\n", rel_path, attr_txt); } } fstart = dtoh16(newentry->begin) | (dtoh16(newentry->begin_hi) << 16); mapping = find_mapping_for_cluster(fstart); if (mapping == NULL) { if ((newentry->attributes & 0x10) > 0) { bx_mkdir(full_path); parse_directory(full_path, fstart); } else { if (access(full_path, F_OK) == 0) { mapping = find_mapping_for_path(full_path); if (mapping != NULL) { mapping->mode &= ~MODE_DELETED; } write_file(full_path, newentry, 0); } else { write_file(full_path, newentry, 1); } } } else { entry = (direntry_t*)array_get(&directory, mapping->dir_index); if (!strcmp(full_path, mapping->path)) { if ((newentry->attributes & 0x10) > 0) { parse_directory(full_path, fstart); mapping->mode &= ~MODE_DELETED; } else { if ((newentry->mdate != entry->mdate) || (newentry->mtime != entry->mtime) || (newentry->size != entry->size)) { write_file(full_path, newentry, 0); } mapping->mode &= ~MODE_DELETED; } } else { if ((newentry->cdate == entry->cdate) && (newentry->ctime == entry->ctime)) { rename(mapping->path, full_path); if (newentry->attributes == 0x10) { parse_directory(full_path, fstart); mapping->mode &= ~MODE_DELETED; } else { if ((newentry->mdate != entry->mdate) || (newentry->mtime != entry->mtime) || (newentry->size != entry->size)) { write_file(full_path, newentry, 0); } mapping->mode &= ~MODE_DELETED; } } else { if ((newentry->attributes & 0x10) > 0) { bx_mkdir(full_path); parse_directory(full_path, fstart); } else { if (access(full_path, F_OK) == 0) { mapping = find_mapping_for_path(full_path); if (mapping != NULL) { mapping->mode &= ~MODE_DELETED; } write_file(full_path, newentry, 0); } else { write_file(full_path, newentry, 1); } } } } } ptr = (Bit8u*)newentry+32; } } while ((newentry != NULL) && ((Bit32u)(ptr - buffer) < size)); free(buffer); } void vvfat_image_t::commit_changes(void) { char path[BX_PATHNAME_LEN]; mapping_t *mapping; int i; // read modified FAT fat2 = malloc(sectors_per_fat * 0x200); lseek(offset_to_fat * 0x200, SEEK_SET); read(fat2, sectors_per_fat * 0x200); // mark all mapped directories / files for delete for (i = 1; i < (int)this->mapping.next; i++) { mapping = (mapping_t*)array_get(&this->mapping, i); if (mapping->first_mapping_index < 0) { mapping->mode |= MODE_DELETED; } } sprintf(path, "%s/%s", vvfat_path, VVFAT_ATTR); vvfat_attr_fd = fopen(path, "w"); // parse new directory tree and create / modify directories and files parse_directory(vvfat_path, (fat_type == 32) ? first_cluster_of_root_dir : 0); if (vvfat_attr_fd != NULL) fclose(vvfat_attr_fd); // remove all directories and files still marked for delete for (i = this->mapping.next - 1; i > 0; i--) { mapping = (mapping_t*)array_get(&this->mapping, i); if (mapping->mode & MODE_DELETED) { direntry_t* entry = (direntry_t*)array_get(&directory, mapping->dir_index); if (entry->attributes == 0x10) { bx_rmdir(mapping->path); } else { unlink(mapping->path); } } } free(fat2); } void vvfat_image_t::close(void) { char msg[BX_PATHNAME_LEN + 80]; mapping_t *mapping; if (vvfat_modified) { sprintf(msg, "Write back changes to directory '%s'?\n\nWARNING: This feature is still experimental!", vvfat_path); if (SIM->ask_yes_no("Bochs VVFAT modified", msg, 0)) { commit_changes(); } } array_free(&fat); array_free(&directory); for (unsigned i = 0; i < this->mapping.next; i++) { mapping = (mapping_t*)array_get(&this->mapping, i); free(mapping->path); } array_free(&this->mapping); if (cluster_buffer != NULL) delete [] cluster_buffer; redolog->close(); #if defined(WIN32) || BX_WITH_MACOS // on non-unix we have to wait till the file is closed to delete it unlink(redolog_temp); #endif if (redolog_temp!=NULL) free(redolog_temp); if (redolog_name!=NULL) free(redolog_name); } Bit64s vvfat_image_t::lseek(Bit64s offset, int whence) { redolog->lseek(offset, whence); if (whence == SEEK_SET) { sector_num = (Bit32u)(offset / 512); } else if (whence == SEEK_CUR) { sector_num += (Bit32u)(offset / 512); } else { BX_ERROR(("lseek: mode not supported yet")); return -1; } if (sector_num >= sector_count) return -1; return 0; } void vvfat_image_t::close_current_file(void) { if(current_mapping) { current_mapping = NULL; if (current_fd) { ::close(current_fd); current_fd = 0; } } current_cluster = 0xffff; } // mappings between index1 and index2-1 are supposed to be ordered // return value is the index of the last mapping for which end>cluster_num int vvfat_image_t::find_mapping_for_cluster_aux(int cluster_num, int index1, int index2) { while(1) { int index3; mapping_t* mapping; index3 = (index1+index2) / 2; mapping = (mapping_t*)array_get(&this->mapping, index3); assert(mapping->begin < mapping->end); if (mapping->begin >= (unsigned int)cluster_num) { assert(index2 != index3 || index2 == 0); if (index2 == index3) return index1; index2 = index3; } else { if (index1 == index3) return (mapping->end <= (unsigned int)cluster_num) ? index2 : index1; index1 = index3; } assert(index1 <= index2); } } mapping_t* vvfat_image_t::find_mapping_for_cluster(int cluster_num) { int index = find_mapping_for_cluster_aux(cluster_num, 0, mapping.next); mapping_t* mapping; if (index >= (int)this->mapping.next) return NULL; mapping = (mapping_t*)array_get(&this->mapping, index); if ((int)mapping->begin > cluster_num) return NULL; assert(((int)mapping->begin <= cluster_num) && ((int)mapping->end > cluster_num)); return mapping; } // This function simply compares path == mapping->path. Since the mappings // are sorted by cluster, this is expensive: O(n). mapping_t* vvfat_image_t::find_mapping_for_path(const char* path) { int i; for (i = 0; i < (int)this->mapping.next; i++) { mapping_t* mapping = (mapping_t*)array_get(&this->mapping, i); if ((mapping->first_mapping_index < 0) && !strcmp(path, mapping->path)) return mapping; } return NULL; } int vvfat_image_t::open_file(mapping_t* mapping) { if (!mapping) return -1; if (!current_mapping || strcmp(current_mapping->path, mapping->path)) { /* open file */ int fd = ::open(mapping->path, O_RDONLY #ifdef O_BINARY | O_BINARY #endif #ifdef O_LARGEFILE | O_LARGEFILE #endif ); if (fd < 0) return -1; close_current_file(); current_fd = fd; current_mapping = mapping; } return 0; } int vvfat_image_t::read_cluster(int cluster_num) { mapping_t* mapping; if (current_cluster != cluster_num) { int result=0; off_t offset; assert(!current_mapping || current_fd || (current_mapping->mode & MODE_DIRECTORY)); if (!current_mapping || ((int)current_mapping->begin > cluster_num) || ((int)current_mapping->end <= cluster_num)) { // binary search of mappings for file mapping = find_mapping_for_cluster(cluster_num); assert(!mapping || ((cluster_num >= (int)mapping->begin) && (cluster_num < (int)mapping->end))); if (mapping && mapping->mode & MODE_DIRECTORY) { close_current_file(); current_mapping = mapping; read_cluster_directory: offset = cluster_size * (cluster_num - current_mapping->begin); cluster = (unsigned char*)directory.pointer+offset + 0x20 * current_mapping->info.dir.first_dir_index; assert(((cluster -(unsigned char*)directory.pointer) % cluster_size) == 0); assert((char*)cluster + cluster_size <= directory.pointer + directory.next * directory.item_size); current_cluster = cluster_num; return 0; } if (open_file(mapping)) return -2; } else if (current_mapping->mode & MODE_DIRECTORY) goto read_cluster_directory; assert(current_fd); offset = cluster_size * (cluster_num - current_mapping->begin) + current_mapping->info.file.offset; if (::lseek(current_fd, offset, SEEK_SET) != offset) return -3; cluster = cluster_buffer; result = ::read(current_fd, cluster, cluster_size); if (result < 0) { current_cluster = 0xffff; return -1; } current_cluster = cluster_num; } return 0; } ssize_t vvfat_image_t::read(void* buf, size_t count) { char *cbuf = (char*)buf; Bit32u scount = (Bit32u)(count / 0x200); while (scount-- > 0) { if ((ssize_t)redolog->read(cbuf, 0x200) != 0x200) { if (sector_num < offset_to_data) { if (sector_num < (offset_to_bootsector + reserved_sectors)) memcpy(cbuf, &first_sectors[sector_num * 0x200], 0x200); else if ((sector_num - offset_to_fat) < sectors_per_fat) memcpy(cbuf, &fat.pointer[(sector_num - offset_to_fat) * 0x200], 0x200); else if ((sector_num - offset_to_fat - sectors_per_fat) < sectors_per_fat) memcpy(cbuf, &fat.pointer[(sector_num - offset_to_fat - sectors_per_fat) * 0x200], 0x200); else memcpy(cbuf, &directory.pointer[(sector_num - offset_to_root_dir) * 0x200], 0x200); } else { Bit32u sector = sector_num - offset_to_data, sector_offset_in_cluster = (sector % sectors_per_cluster), cluster_num = sector / sectors_per_cluster + 2; if (read_cluster(cluster_num) != 0) { memset(cbuf, 0, 0x200); } else { memcpy(cbuf, cluster + sector_offset_in_cluster * 0x200, 0x200); } } redolog->lseek((sector_num + 1) * 0x200, SEEK_SET); } sector_num++; cbuf += 0x200; } return count; } ssize_t vvfat_image_t::write(const void* buf, size_t count) { ssize_t ret = 0; char *cbuf = (char*)buf; Bit32u scount = (Bit32u)(count / 512); bx_bool update_imagepos; while (scount-- > 0) { update_imagepos = 1; if (sector_num == 0) { // allow writing to MBR (except partition table) memcpy(&first_sectors[0], cbuf, 0x1b8); } else if (sector_num == offset_to_bootsector) { // allow writing to boot sector memcpy(&first_sectors[sector_num * 0x200], cbuf, 0x200); } else if ((fat_type == 32) && (sector_num == (offset_to_bootsector + 1))) { // allow writing to FS info sector memcpy(&first_sectors[sector_num * 0x200], cbuf, 0x200); } else if (sector_num < (offset_to_bootsector + reserved_sectors)) { BX_ERROR(("VVFAT write ignored: sector=%d, count=%d", sector_num, scount)); ret = -1; } else { vvfat_modified = 1; update_imagepos = 0; ret = redolog->write(cbuf, 0x200); } if (ret < 0) break; sector_num++; cbuf += 0x200; if (update_imagepos) { redolog->lseek(sector_num * 0x200, SEEK_SET); } } return (ret < 0) ? ret : count; } Bit32u vvfat_image_t::get_capabilities(void) { return HDIMAGE_HAS_GEOMETRY; }