/* JPC: An x86 PC Hardware Emulator for a pure Java Virtual Machine Release Version 2.4 A project from the Physics Dept, The University of Oxford Copyright (C) 2007-2010 The University of Oxford This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Details (including contact information) can be found at: jpc.sourceforge.net or the developer website sourceforge.net/projects/jpc/ Conceived and Developed by: Rhys Newman, Ian Preston, Chris Dennis End of licence header */ package org.jpc.support; import java.io.*; import java.lang.ref.WeakReference; import java.nio.charset.Charset; import java.util.*; import java.util.logging.*; /** * Presents a directory on the local machine as a FAT32 volume within the guest. * @author Ian Preston * @author Chris Dennis */ public class TreeBlockDevice implements BlockDevice { private static final Logger LOGGING = Logger.getLogger(TreeBlockDevice.class.getName()); private static final Charset US_ASCII = Charset.forName("US-ASCII"); private static final Charset UTF_16LE = Charset.forName("UTF-16LE"); private static final int PART_BOOT = 0x0; private static final int PART_START_CHS = 0x1; private static final int PART_TYPE = 0x4; private static final int PART_END_CHS = 0x5; private static final int PART_START = 0x8; private static final int PART_SIZE = 0xc; private static final int PART_1 = 0x1be; private static final int PART_MAGIC = 0x1fe; private static final int PART_TYPE_WIN95_OSR2_FAT32_LBA = 0x0c; private static final int FAT_BOOT_OEM = 0x03; private static final int FAT_BOOT_BPARAM_BYTES_PER_SECTOR = 0x0b; private static final int FAT_BOOT_BPARAM_SECTORS_PER_CLUSTER = 0x0d; private static final int FAT_BOOT_BPARAM_RESERVED_SECTORS = 0x0e; private static final int FAT_BOOT_BPARAM_FAT_COPIES = 0x10; private static final int FAT_BOOT_BPARAM_ROOT_ENTRIES = 0x11; private static final int FAT_BOOT_BPARAM_SMALL_SECTORS = 0x13; private static final int FAT_BOOT_BPARAM_MEDIA_TYPE = 0x15; private static final int FAT_BOOT_BPARAM_SECTORS_PER_FAT = 0x16; private static final int FAT_BOOT_BPARAM_SECTORS_PER_TRACK = 0x18; private static final int FAT_BOOT_BPARAM_HEADS = 0x1a; private static final int FAT_BOOT_BPARAM_HIDDEN_SECTORS = 0x1c; private static final int FAT_BOOT_BPARAM_LARGE_SECTORS = 0x20; private static final int FAT_BOOT_BPARAM_FAT32_SECTORS_PER_FAT = 0x24; private static final int FAT_BOOT_BPARAM_FAT32_ACTIVE_FAT = 0x28; private static final int FAT_BOOT_BPARAM_FAT32_FS_VERSION = 0x2a; private static final int FAT_BOOT_BPARAM_FAT32_ROOT_CLUSTER = 0x2c; private static final int FAT_BOOT_BPARAM_FAT32_FSINFO_SECTOR = 0x30; private static final int FAT_BOOT_BPARAM_FAT32_BOOT_BACKUP_SECTOR = 0x32; private static final int FAT_BOOT_BPARAM_FAT32_PHYSICAL_DRIVE = 0x40; private static final int FAT_BOOT_BPARAM_FAT32_SIGNATURE = 0x42; private static final int FAT_BOOT_BPARAM_FAT32_VOLUME_ID = 0x43; private static final int FAT_BOOT_BPARAM_FAT32_VOLUME_LABEL = 0x47; private static final int FAT_BOOT_BPARAM_FAT32_FS_TYPE = 0x52; private static final int FAT_FSINFO_LEAD_SIGNATURE = 0x00; private static final int FAT_FSINFO_STRUCTURE_SIGNATURE = 0x1e4; private static final int FAT_FSINFO_FREE_COUNT = 0x1e8; private static final int FAT_FSINFO_NEXT_FREE = 0x1ec; private static final int FAT_FSINFO_TRAIL_SIGNATURE = 0x1fc; private static final int FAT_COPIES = 2; private static final int MAX_FILE_LIMIT = 5000; private static final String OEM_LABEL = "MSWIN4.1"; private static final int SECTORS_PER_CLUSTER = 8; private static final int RESERVED_SECTORS = 32; //reserved sectors before start of fat private static final int BACKUP_BOOT_SECTOR = 6; private static final int FSINFO_SECTOR = 1; private static final int ROOT_START_CLUSTER = 2; private static final int HIDDEN_SECTORS = 63; //sectors before start of partition private static final int HEADER_SECTION_LENGTH = HIDDEN_SECTORS + RESERVED_SECTORS; private static final int HEADS_PER_CYLINDER = 16; private static final int SECTORS_PER_TRACK = 63; private static final int FAT_CHAIN_ENDMARK = 0x0fffffff; private static final long FAT32_MIN_CLUSTERS = 65525 + 16; private static final int FREE_SPACE_FACTOR = 2; private static final byte[] EMPTY = new byte[SECTOR_SIZE]; private byte[] fatImage, start; private long driveLength; private int fatSize; private int numberOfOpenFiles = 0; private Map<Long, FatEntry> sectorToFatEntry = new HashMap<Long, FatEntry>(); private Map<Long, byte[]> bufferedWrites = new HashMap<Long, byte[]>(); private Set<Long> unmappedClusters = new HashSet<Long>(); private OpenFilesCache fileCache; private boolean bufferWrites = false; private int dataSectionStart; /* notes: Write Tree: to write a copy of the entire directory tree to disk, create a new folder and pass it as a File to writeNewTree(File file) Synchronise: to continuously try to synchronise the virtual tree with the underlying tree set bufferWrites to false, otherwise all writes are buffered in an array */ /** * Constructs an unconfigured instance. */ public TreeBlockDevice() {} /** * Constructs a instance mapping the root directory <code>root</code>. * <p> * If <code>buffer</code> is true then writes to the drive are buffered in * memory. Otherwise they are commited back to the local filesystem. * @param root root directory * @param buffer <code>true</code> to buffer drive writes * @throws java.io.IOException */ public TreeBlockDevice(File root, boolean buffer) throws IOException { this.configure(root, buffer); } /** * Configure the given drive using this specification string. * <p> * The string is either of the form "$lt;path$gt;" or "sync:$lt;path$gt;". * The sync prefix indicates that writes should be written back to the local * filesystem. * @param specs specification string * @throws java.io.IOException on an underlying fs error */ public void configure(String specs) throws IOException { boolean buffer = true; String rootName = specs; if (specs.startsWith("sync:")) { buffer = false; rootName = specs.substring(5); } configure(new File(rootName), buffer); } private void configure(File directory, boolean buffer) throws IOException { bufferWrites = buffer; fileCache = new OpenFilesCache(10); //read in directory structure DirectoryEntry root = new DirectoryEntry(directory, 2, null); Map<Long, FatEntry> fat = new HashMap<Long, FatEntry>(); root.buildTree(fat); long dataSize = FREE_SPACE_FACTOR * (root.getDirectorySubClusters() + root.getSizeInClusters()); dataSize = Math.max(FAT32_MIN_CLUSTERS, dataSize); fatSize = (int)Math.ceil((double)((dataSize + 2) * 4) / SECTOR_SIZE); long volumeLength = (dataSize * SECTORS_PER_CLUSTER) + (FAT_COPIES * fatSize) + RESERVED_SECTORS; long suggestedLength = volumeLength + HIDDEN_SECTORS; driveLength = getLba(getCylinder(suggestedLength), HEADS_PER_CYLINDER, SECTORS_PER_TRACK); //revise numbers for Microsoft formulation long temp1 = driveLength - HEADER_SECTION_LENGTH; long temp2 = ((256 * SECTORS_PER_CLUSTER) + FAT_COPIES) / 2; fatSize = (int)((temp1 + temp2 - 1) / temp2); dataSectionStart = HEADER_SECTION_LENGTH + (FAT_COPIES * fatSize); fatImage = createFatImage(fat); sectorToFatEntry = createDataMap(fat); start = new byte[SECTOR_SIZE * HEADER_SECTION_LENGTH]; byte[] mbr = buildMasterBootRecord(HIDDEN_SECTORS, HIDDEN_SECTORS + volumeLength); byte[] pbr = buildPartitionBootRecord(volumeLength, fatSize); byte[] fsinfo = buildFsInfoSector(); System.arraycopy(mbr, 0, start, 0, mbr.length); System.arraycopy(pbr, 0, start, HIDDEN_SECTORS * SECTOR_SIZE, pbr.length); System.arraycopy(fsinfo, 0, start, (HIDDEN_SECTORS + FSINFO_SECTOR) * SECTOR_SIZE, fsinfo.length); System.arraycopy(pbr, 0, start, (HIDDEN_SECTORS + BACKUP_BOOT_SECTOR) * SECTOR_SIZE, pbr.length); System.arraycopy(fsinfo, 0, start, (HIDDEN_SECTORS + BACKUP_BOOT_SECTOR + FSINFO_SECTOR) * SECTOR_SIZE, fsinfo.length); } private static byte[] buildMasterBootRecord(long start, long end) { byte[] mbr = new byte[SECTOR_SIZE]; //partition table mbr[PART_1 + PART_BOOT] = (byte) 0x00;// 80 means system partition, 00 means do not use for booting mbr[PART_1 + PART_START_CHS] = (byte) getHead(start); mbr[PART_1 + PART_START_CHS + 1] = (byte) (((getCylinder(start) >> 2) & 0xC0) | (0x3F & getSector(start))); mbr[PART_1 + PART_START_CHS + 2] = (byte) getCylinder(start); mbr[PART_1 + PART_TYPE] = (byte) PART_TYPE_WIN95_OSR2_FAT32_LBA;//System ID mbr[PART_1 + PART_END_CHS] = (byte) getHead(end); mbr[PART_1 + PART_END_CHS + 1] = (byte) (((getCylinder(end) >> 2) & 0xC0) | (0x3F & getSector(end))); mbr[PART_1 + PART_END_CHS + 2] = (byte) getCylinder(end); putInt(mbr, PART_1 + PART_START, (int)start); putInt(mbr, PART_1 + PART_SIZE, (int)(end - start)); putShort(mbr, PART_MAGIC, (short) 0xAA55); return mbr; } private static byte[] buildPartitionBootRecord(long volumeSize, int fatSize) { byte[] pbr = new byte[SECTOR_SIZE]; pbr[0] = (byte) 0xEB; pbr[1] = (byte) 0x3C; pbr[2] = (byte) 0x90; System.arraycopy(OEM_LABEL.getBytes(US_ASCII), 0, pbr, FAT_BOOT_OEM, 8); //BIOS Parameter block putShort(pbr, FAT_BOOT_BPARAM_BYTES_PER_SECTOR, (short)SECTOR_SIZE); pbr[FAT_BOOT_BPARAM_SECTORS_PER_CLUSTER] = (byte) SECTORS_PER_CLUSTER; putShort(pbr, FAT_BOOT_BPARAM_RESERVED_SECTORS, (short)RESERVED_SECTORS); pbr[FAT_BOOT_BPARAM_FAT_COPIES] = (byte) FAT_COPIES;//number of copies of fatImage putShort(pbr, FAT_BOOT_BPARAM_ROOT_ENTRIES, (short)0x0000); putShort(pbr, FAT_BOOT_BPARAM_SMALL_SECTORS, (short)0x0000); pbr[FAT_BOOT_BPARAM_MEDIA_TYPE] = (byte) 0xF8;//do not change putShort(pbr, FAT_BOOT_BPARAM_SECTORS_PER_FAT, (short)0x0000); putShort(pbr, FAT_BOOT_BPARAM_SECTORS_PER_TRACK, (short)SECTORS_PER_TRACK); putShort(pbr, FAT_BOOT_BPARAM_HEADS, (short)HEADS_PER_CYLINDER); putInt(pbr, FAT_BOOT_BPARAM_HIDDEN_SECTORS, HIDDEN_SECTORS); putInt(pbr, FAT_BOOT_BPARAM_LARGE_SECTORS, (int)volumeSize); putInt(pbr, FAT_BOOT_BPARAM_FAT32_SECTORS_PER_FAT, fatSize); putShort(pbr, FAT_BOOT_BPARAM_FAT32_ACTIVE_FAT, (short)0x0000); putShort(pbr, FAT_BOOT_BPARAM_FAT32_FS_VERSION, (short)0x0000); putInt(pbr, FAT_BOOT_BPARAM_FAT32_ROOT_CLUSTER, ROOT_START_CLUSTER); putShort(pbr, FAT_BOOT_BPARAM_FAT32_FSINFO_SECTOR, (short)FSINFO_SECTOR); putShort(pbr, FAT_BOOT_BPARAM_FAT32_BOOT_BACKUP_SECTOR, (short)BACKUP_BOOT_SECTOR); pbr[FAT_BOOT_BPARAM_FAT32_PHYSICAL_DRIVE] = (byte) 0x00; pbr[FAT_BOOT_BPARAM_FAT32_SIGNATURE] = (byte) 0x29; putInt(pbr, FAT_BOOT_BPARAM_FAT32_VOLUME_ID, 0x31415926); System.arraycopy("JPCDIRDRIVE".getBytes(US_ASCII), 0, pbr, FAT_BOOT_BPARAM_FAT32_VOLUME_LABEL, 11); System.arraycopy("FAT32 ".getBytes(US_ASCII), 0, pbr, FAT_BOOT_BPARAM_FAT32_FS_TYPE, 8); putShort(pbr, PART_MAGIC, (short) 0xAA55); return pbr; } private static byte[] buildFsInfoSector() { byte[] fsinfo = new byte[SECTOR_SIZE]; putInt(fsinfo, FAT_FSINFO_LEAD_SIGNATURE, 0x41615252); putInt(fsinfo, FAT_FSINFO_STRUCTURE_SIGNATURE, 0x61417272); putInt(fsinfo, FAT_FSINFO_FREE_COUNT, -1); //could be set putInt(fsinfo, FAT_FSINFO_NEXT_FREE, -1); //could be set putInt(fsinfo, FAT_FSINFO_TRAIL_SIGNATURE, 0xaa550000); return fsinfo; } private int followFatChainLink(int cluster) { int fatOffset = cluster * 4; if (fatOffset < fatImage.length) return (fatImage[fatOffset] & 0xFF) + ((fatImage[fatOffset + 1] & 0xFF) << 8) + ((fatImage[fatOffset + 2] & 0xFF) << 16) + ((fatImage[fatOffset + 3] & 0xFF) << 24); else return 0; } /** * Closes this device and all associated local filesystem objects */ public void close() { fileCache.close(); } //READ up to 16 sectors at a time public int read(long sectorNumber, byte[] buffer, int size) { if (size != 1) LOGGING.log(Level.WARNING, "Request to do multiple sector read."); if (sectorNumber >= driveLength) return -1; //check map of writes to see if sector has been written to byte[] entry = bufferedWrites.get(Long.valueOf(sectorNumber)); if (entry != null) System.arraycopy(entry, 0, buffer, 0, SECTOR_SIZE); else if (sectorNumber < HEADER_SECTION_LENGTH) // Initial sectors System.arraycopy(start, (int) sectorNumber * SECTOR_SIZE, buffer, 0, size * SECTOR_SIZE); else if (sectorNumber < dataSectionStart) // fatImage sectors System.arraycopy(fatImage, (int) ((sectorNumber - HEADER_SECTION_LENGTH) % fatSize) * SECTOR_SIZE, buffer, 0, size * SECTOR_SIZE); else return readFromFileSystem(sectorNumber, buffer); return 0; } private int readFromFileSystem(long sectorNumber, byte[] buffer) { FatEntry entry = sectorToFatEntry.get(Long.valueOf(sectorNumber)); if (entry != null) try { entry.read(sectorNumber, buffer); } catch (IOException e) { LOGGING.log(Level.WARNING, "exception reading FAT filesystem", e); return -1; } else { System.arraycopy(EMPTY, 0, buffer, 0, SECTOR_SIZE); LOGGING.log(Level.FINE, "Read empty sector number {0,number,integer}", Long.valueOf(sectorNumber)); } return 0; } public int write(long sectorNumber, byte[] buffer, int size) { if (size != 1) LOGGING.log(Level.WARNING, "Request to do multiple sector write."); if (sectorNumber >= driveLength) return -1; if (bufferWrites) { byte[] write = new byte[SECTOR_SIZE]; System.arraycopy(buffer, 0, write, 0, SECTOR_SIZE); bufferedWrites.put(Long.valueOf(sectorNumber), write); } else if (sectorNumber < HEADER_SECTION_LENGTH) System.arraycopy(buffer, 0, start, (int) sectorNumber * SECTOR_SIZE, SECTOR_SIZE); else if (sectorNumber < dataSectionStart) writeToFat(buffer, sectorNumber); else return writeToFileSystem(sectorNumber, buffer); return 0; } private void writeToFat(byte[] buffer, long sectorNumber) { //read old fatImage first to compare to byte[] oldSector = new byte[SECTOR_SIZE]; read(sectorNumber, oldSector, 1); //perform write long fatSectorNumber = (sectorNumber - HEADER_SECTION_LENGTH) % fatSize; System.arraycopy(buffer, 0, fatImage, (int) fatSectorNumber * SECTOR_SIZE, SECTOR_SIZE); int minCluster = (int)((fatSectorNumber * SECTOR_SIZE) >>> 2); for (int entryOffset = 0; entryOffset < SECTOR_SIZE; entryOffset += 4) { boolean entryChanged = false; for (int i = 0; i < 4; i++) if (buffer[entryOffset + i] != oldSector[entryOffset + i]) entryChanged = true; if (entryChanged) if (followFatChainLink(minCluster + (entryOffset >>> 2)) == 0) { //a fatImage entry has been set to zero so we need to delete a file or dir FatEntry entry = sectorToFatEntry.get(Long.valueOf(getSectorNumber(entryOffset + minCluster))); if (entry != null) { entry.getFile().delete(); //remove all references to the file from the data Map long startingSector = getSectorNumber(entry.getStartCluster()); for (int k = 0; k < SECTORS_PER_CLUSTER; k++) sectorToFatEntry.remove(Long.valueOf(startingSector + k)); } } } //need to check if references have been made to allow us to commit writes from writeMap Iterator<Long> itt = unmappedClusters.iterator(); while (itt.hasNext()) { long thisCluster = itt.next().longValue(); //see if anything has been allocated with thisCluster value in fatImage for (long cluster = minCluster; cluster < minCluster + (SECTOR_SIZE >> 2); cluster++) if (followFatChainLink((int) cluster) == thisCluster) { FatEntry entry = sectorToFatEntry.get(Long.valueOf(getSectorNumber(cluster))); if (entry != null) { for (int j = 0; j < SECTORS_PER_CLUSTER; j++) { Long key = Long.valueOf(getSectorNumber(thisCluster) + j); byte[] array = bufferedWrites.remove(key); if (array != null) write(key.longValue(), buffer, 1); } itt.remove(); } } } } private int writeToFileSystem(long sectorNumber, byte[] buffer) { FatEntry entry = sectorToFatEntry.get(Long.valueOf(sectorNumber)); if (entry != null) try { entry.write(sectorNumber, buffer); } catch (IOException e) { return -1; } else { //cluster is not allocated // try apepnding to previous sector if it is non empty if (sectorToFatEntry.containsKey(Long.valueOf(sectorNumber-1))) { entry = sectorToFatEntry.get(Long.valueOf(sectorNumber-1)); try { entry.write(sectorNumber, buffer); } catch (IOException e) { return -1; } } else { byte[] temp = new byte[SECTOR_SIZE]; System.arraycopy(buffer, 0, temp, 0, SECTOR_SIZE); bufferedWrites.put(Long.valueOf(sectorNumber), temp); unmappedClusters.add(Long.valueOf(getClusterNumber(sectorNumber))); } } return 0; } private long getClusterNumber(long sector) { return (sector - dataSectionStart) / SECTORS_PER_CLUSTER + 2; } private long getClusterOffset(long sector) { return (sector - dataSectionStart) % SECTORS_PER_CLUSTER; } private long getSectorNumber(long cluster) { return ((cluster - 2) * SECTORS_PER_CLUSTER) + dataSectionStart; } /** * Returns <code>true</code> as hard drives are always inserted. * @return <code>true</code> */ public boolean isInserted() { return true; } /** * Returns <code>true</code> as hard drives are always locked. * @return <code>true</code> */ public boolean isLocked() { return true; } /** * Returns <code>false</code>. * <p> * Writes are always possible even though they may be buffered. * @return <code>false</code> */ public boolean isReadOnly() { return false; } /** * Does nothing, the drive is always locked. * @param l ignored */ public void setLock(boolean l) { } public long getTotalSectors() { return driveLength; } public int getCylinders() { return getCylinder(getTotalSectors()); } public int getHeads() { return HEADS_PER_CYLINDER; } public int getSectors() { return SECTORS_PER_TRACK; } /** * Returns <code>Type.HARDDRIVE</code>. * @return <code>Type.HARDDRIVE</code> */ public Type getType() { return Type.HARDDRIVE; } //convert FATmap to fatImage private byte[] createFatImage(Map<Long, FatEntry> fat) { byte[] image = new byte[fatSize * SECTOR_SIZE]; putInt(image, 0, 0xfffffff8); putInt(image, 4, FAT_CHAIN_ENDMARK); for (Map.Entry<Long, FatEntry> entry : fat.entrySet()) { long startCluster = entry.getKey().longValue(); long lengthClusters = entry.getValue().getSizeInClusters(); long endCluster = startCluster + lengthClusters; for (long j = startCluster; j < endCluster - 1; j++) { int pos = (int) (j * 4); int next = (int) (j + 1); image[pos++] = (byte) next; image[pos++] = (byte) (next >>> 8); image[pos++] = (byte) (next >>> 16); image[pos] = (byte) (next >>> 24); } putInt(image, (int)((endCluster - 1) * 4), FAT_CHAIN_ENDMARK); } return image; } //convert FATmap to data map private Map<Long, FatEntry> createDataMap(Map<Long, FatEntry> fat) { Map<Long, FatEntry> dataMap = new HashMap<Long, FatEntry>(); for (Map.Entry<Long, FatEntry> entry : fat.entrySet()) { FatEntry fatEntry = entry.getValue(); long startSector = getSectorNumber(entry.getKey().longValue()); for (long i = 0; i < fatEntry.getSizeSectors(); i++) dataMap.put(Long.valueOf(startSector + i), fatEntry); } return dataMap; } /** * Mirrors disk structure out to a new root directory. * @param root directory for copy */ public void writeNewTree(File root) { //write disk to a new directory structure { if (root.isDirectory() && root.canWrite()) { //convert fatImage to Hashmap Map hashFAT = new HashMap(); int sum; for (int j = 2; j < fatImage.length/4; j++) { //convert fatImage entry to an int sum = 0; for (int k = 0; k<4; k++) { sum += ((fatImage[4 * j + k] & 0xFF) << k * 8); } //check it isn't empty if (sum != 0) { hashFAT.put(Integer.valueOf(j), Integer.valueOf(sum)); } } readToWrite(hashFAT, 2, root); } } } //method to read a directory or file from this TBD and write it out to a new physical one private void readToWrite(Map hashFAT, int startingCluster, File file) //make this return an int to signify success { int cluster = startingCluster; byte[] buffer = new byte[SECTOR_SIZE]; if (file.isDirectory()) { byte[] acluster = new byte[SECTOR_SIZE*SECTORS_PER_CLUSTER]; byte[] dir = new byte[0]; //read directory into byte[] while (true) { //read cluster of dir sector by sector and store in array for (int i=0; i<SECTORS_PER_CLUSTER; i++) { if (read(getSectorNumber(cluster) + i, buffer, 1) == 0) { System.arraycopy(buffer, 0, acluster, i*SECTOR_SIZE, SECTOR_SIZE); } } byte[] newdir = new byte[dir.length + acluster.length]; System.arraycopy(dir, 0, newdir, 0, dir.length); System.arraycopy(acluster, 0, newdir, dir.length, acluster.length); dir = newdir; //check for more clusters cluster = ((Integer) hashFAT.get(Integer.valueOf(cluster))).intValue(); if (cluster == 268435455)//251592447) //endmark break; } //for each directory entry create a new file/dir and call this method on it with its start cluster int newStartCluster; boolean isDirectory; for (int k = 0; k < dir.length/32; k++) { if ((dir[32*k]==0) || dir[32*k +11] == 0xF) continue; String name = new String(dir, 32 * k, 8, US_ASCII).trim(); String ext = new String(dir, 32 * k + 8, 3, US_ASCII).trim(); newStartCluster = (dir[32*k + 26] & 0xFF) + ((dir[32*k + 27] & 0xFF) << 8) + ((dir[32*k + 20] & 0xFF) << 16) + ((dir[32*k + 21] & 0xFF) << 24); isDirectory = (dir[32*k + 11] & 0x10) == 0x10; //add in other attributes here like readonly, hidden etc. File next; if (isDirectory) { next = new File(file.getPath(), name + ext); next.mkdir(); } else { next = new File(file.getPath(), name + "." + ext); try { next.createNewFile(); } catch (IOException e) { LOGGING.log(Level.INFO, "cannot create new file", e); } catch (SecurityException e) { LOGGING.log(Level.INFO, "not allowed to create new file", e); } } readToWrite(hashFAT, newStartCluster, next); } } if (file.isFile()) { //open stream to write to file try { BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file)); while (true) { //read cluster of file sector by sector and write to disk int data = 0; for (int i=0; i<8; i++) { for (int h = 0; h < SECTOR_SIZE; h++) { buffer[h] = (byte) 0x00;} if (read(getSectorNumber(cluster) + i, buffer, 1) == 0) { //if the first byte is 0 check if it is a blank sector if (buffer[0] == 0) for ( int j = 0; j<SECTOR_SIZE; j++) { if (buffer[j]==0) data += 1; } //if it is a blank sector don't write it if (data == SECTOR_SIZE) break; //if sector ends in a 0 and we are in the last cluster, then trim the zeroes from the end if ((((Integer) hashFAT.get(Integer.valueOf(cluster))).intValue() == 268435455) && (buffer[511] == 0)) { int index = 511; while (buffer[index]==0) { index--; if (index == -1) break; } out.write(buffer,0,index+1); break; } out.write(buffer); } } //check for more clusters cluster = ((Integer) hashFAT.get(Integer.valueOf(cluster))).intValue(); if (cluster == 268435455) //EOF marker break; } out.close(); } catch (IOException e) { System.err.println("IO Exception in writing new directory tree"); e.printStackTrace(); } } } //write image of disk public void writeImage(DataOutput dout) throws IOException { byte[] buffer=new byte[SECTOR_SIZE]; for (long i = 0; i < driveLength; i++) { read(i, buffer, 1); dout.write(buffer); } } //***********************************************************************************// //build directory structure private abstract class FatEntry { private String shortName; private long startCluster, sizeSectors, sizeClusters; private File file; public Map<Long, Long> clusterList = new HashMap<Long, Long>(); // list of clusters in object private DirectoryEntry parent; FatEntry(File file, long startCluster, DirectoryEntry parent) { this.file = file; this.startCluster = startCluster; this.parent = parent; } protected String getShortName() { if (shortName == null && getParent() != null) shortName = getParent().generateShortName(getFile().getName()); return shortName; } public abstract void read(long sectorNumber, byte[] buffer) throws IOException; public abstract void write(long sectorNumber, byte[] buffer) throws IOException; protected abstract long buildTree(Map<Long, FatEntry> fat) throws IOException; protected byte[] getDirectoryEntryComponent() { byte[] entry = new byte[32]; if (getFile().isHidden()) entry[11] |= (byte) 0x02; //hidden if (!getFile().canWrite()) entry[11] |= (byte) 0x01;//read only //put in starting cluster (high 2 bytes) entry[20] = (byte) (startCluster >>> 16); entry[21] = (byte) (startCluster >>> 24); //put in starting cluster (low 2 bytes) entry[26] = (byte) startCluster; entry[27] = (byte) (startCluster >>> 8); //time and date stuff GregorianCalendar cal = new GregorianCalendar(); cal.setTimeInMillis(getFile().lastModified()); int time = (cal.get(Calendar.SECOND) >>> 1) | (cal.get(Calendar.MINUTE) << 5) | (cal.get(Calendar.HOUR_OF_DAY) << 11); int date = cal.get(Calendar.DAY_OF_MONTH) | ((cal.get(Calendar.MONTH) + 1) << 5) | ((cal.get(Calendar.YEAR) - 1980) << 9); entry[22] = (byte) time; entry[23] = (byte) (time >>> 8); entry[24] = (byte) date; entry[25] = (byte) (date >>> 8); entry[14] = entry[22]; entry[15] = entry[23]; entry[16] = entry[24]; entry[17] = entry[25]; entry[18] = entry[24]; entry[19] = entry[25]; String name = getShortName(); if (getParent() != null) { System.arraycopy(name.getBytes(US_ASCII), 0, entry, 0, name.length()); } return entry; } protected DirectoryEntry getParent() { return parent; } protected void setSizeSectors(long i) { sizeSectors = i; sizeClusters = (sizeSectors - 1) / SECTORS_PER_CLUSTER + 1; } protected void makeClusterList() { for (long counter = 0, cluster = getStartCluster(); counter < getSizeInClusters(); counter++, cluster++) clusterList.put(Long.valueOf(cluster), Long.valueOf(counter)); } protected void updateClusterList(long sectorNumber) { if (!clusterList.containsKey(Long.valueOf(getClusterNumber(sectorNumber)))) { clusterList.put(Long.valueOf(getClusterNumber(sectorNumber)), Long.valueOf(getSizeInClusters() + 1)); setSizeClusters(getSizeInClusters() + 1); } } public long getStartCluster() { return startCluster; } public long getSizeSectors() { return sizeSectors; } public long getSizeInClusters() { return sizeClusters; } protected void setSizeClusters(long length) { this.sizeClusters = length; } public File getFile() { return file; } protected void setFile(File file) { this.file = file; } protected byte[] createLongFileNameEntry(String hint) { byte[] name = getShortName().getBytes(US_ASCII); byte checksum = 0; for (int i = 0; i < 11; i++) checksum = (byte)((checksum << 7) + (checksum >>> 1) + name[i]); StringBuilder longName = new StringBuilder(hint.trim()); while (longName.charAt(longName.length() - 1) == '.') longName.deleteCharAt(longName.length() -1); for (int i = 0; i < longName.length(); i++) { char letter = longName.charAt(i); if (!Character.isLetterOrDigit(letter) && ".$%'-_@~`!(){}^#&+,;=[]".indexOf(letter) < 0) longName.setCharAt(i, '_'); } byte[] unicodeName = longName.toString().getBytes(UTF_16LE); List<byte[]> entries = new ArrayList<byte[]>(); for (int i = 0; ; i++) { byte[] entry = makeLongFileNameEntry(unicodeName, checksum, i); if (entry == null) break; entries.add(entry); } byte[] lfn = new byte[32 * entries.size()]; for (int i = 0; i < entries.size(); i++) System.arraycopy(entries.get(i), 0, lfn, 32 * (entries.size() - (i + 1)), 32); return lfn; } } //**************************// //File Class private class FileEntry extends FatEntry { private long fileSize; private WeakReference<RandomAccessFile> backingFile; FileEntry(File file, long start, DirectoryEntry parent) throws IOException { super(file, start, parent); //check if too many files have been loaded if (++numberOfOpenFiles > MAX_FILE_LIMIT) throw new IndexOutOfBoundsException("Too many files loaded: try a directory with fewer files."); fileSize = getFile().length(); setSizeSectors(((fileSize - 1) / SECTOR_SIZE) + 1); } public void read(long sectorNumber, byte[] buffer) throws IOException { long oddsectors = getClusterOffset(sectorNumber); long offset = clusterList.get(Long.valueOf(getClusterNumber(sectorNumber))).longValue() * SECTORS_PER_CLUSTER * SECTOR_SIZE; offset = offset + oddsectors * SECTOR_SIZE; //see if file is already open RandomAccessFile backing = null; if (backingFile != null) backing = backingFile.get(); if ((backing == null) || !backing.getFD().valid()) { backing = fileCache.getBackingFor(getFile()); backingFile = new WeakReference(backing); } backing.seek(offset); int len = Math.min(SECTOR_SIZE, (int) (backing.length() - offset)); backing.readFully(buffer, 0, len); } public void write(long sectorNumber, byte[] buffer) throws IOException { //continually commit writes //check to see if the sector is in the data section long offset = getClusterOffset(sectorNumber); long cluster = getClusterNumber(sectorNumber); //cluster is allocated //check if it is allocated to a file or a directory int clusterCount = 0; for (int nextCluster = (int) getStartCluster(); nextCluster != cluster; clusterCount++) nextCluster = followFatChainLink(nextCluster); RandomAccessFile out = new RandomAccessFile(getFile(), "rw"); try { out.seek((clusterCount * SECTORS_PER_CLUSTER + offset) * SECTOR_SIZE); int len = SECTOR_SIZE; if ((clusterCount * SECTORS_PER_CLUSTER + offset + 1) * SECTOR_SIZE > getFileSize()) len = (int) (SECTOR_SIZE + getFileSize() - (clusterCount * SECTORS_PER_CLUSTER + offset + 1) * SECTOR_SIZE); if (len < 0) len = SECTOR_SIZE; try { out.write(buffer, 0, len); //need to clip zeros here at end of file somehow } catch (IndexOutOfBoundsException e) { e.printStackTrace(); System.err.println("Writing error to file: " + getFile()); System.err.println("length of write is: " + len); System.err.println("offset: " + offset + ", clusterCount: " + clusterCount + ", fileSize: " + getFileSize()); } } finally { out.close(); } //update file's clusterlist updateClusterList(sectorNumber); sectorToFatEntry.put(Long.valueOf(sectorNumber), this); } protected long buildTree(Map<Long, FatEntry> fat) throws IOException { makeClusterList(); fat.put(Long.valueOf(getStartCluster()), this); return getSizeInClusters(); } public void setFileSize(long size) { this.fileSize = size; } public long getFileSize() { return fileSize; } protected byte[] getDirectoryEntryComponent() { byte[] entry = super.getDirectoryEntryComponent(); entry[11]=(byte) 0x20; //Attrib Byte (Marks As File) //put in file size in bytes entry[28] = (byte) fileSize; entry[29] = (byte) (fileSize >>> 8); entry[30] = (byte) (fileSize >>> 16); entry[31] = (byte) (fileSize >>> 24); String filename = getFile().getName(); if (filename.length() > 8) { byte[] lfn = createLongFileNameEntry(filename); byte[] fullEntry = new byte[lfn.length + entry.length]; System.arraycopy(lfn, 0, fullEntry, 0, lfn.length); System.arraycopy(entry, 0, fullEntry, lfn.length, entry.length); return fullEntry; } else return entry; } } //*********************// //Directory Class class DirectoryEntry extends FatEntry { // All files and directories in this directory: private List<FatEntry> files = new ArrayList<FatEntry>(); private long dirSubClusters; private Set<String> shortNames = new HashSet<String>(); private byte[] dirEntry = new byte[0]; private int size; public DirectoryEntry(File path, long startCluster, DirectoryEntry parent) { super(path, startCluster, parent); this.dirSubClusters = 0; } protected String generateShortName(String hint) { hint = hint.toUpperCase(); StringBuilder name; StringBuilder extension; int dot = hint.lastIndexOf('.'); if (dot > 0) { name = new StringBuilder(hint.substring(0, dot)); extension = new StringBuilder(hint.substring(dot + 1)); } else { name = new StringBuilder(hint); extension = new StringBuilder(); } if (name.length() > 8) name.setLength(8); if (extension.length() > 3) extension.setLength(3); for (int i = 0; i < name.length(); i++) { char letter = name.charAt(i); if (!Character.isLetterOrDigit(letter) && ("$%'-_@~`!(){}^#&".indexOf(letter) < 0)) name.setCharAt(i, '_'); } for (int i = 0; i < extension.length(); i++) { char letter = extension.charAt(i); if (!Character.isLetterOrDigit(letter) && ("$%'-_@~`!(){}^#&".indexOf(letter) < 0)) extension.setCharAt(i, '_'); } while (name.length() < 8) name.append(' '); while (extension.length() < 3) extension.append(' '); StringBuilder shortName = new StringBuilder(name); shortName.append(extension); if (shortNames.add(shortName.toString())) return shortName.toString(); StringBuilder attempt = new StringBuilder(); int twiddle = 1; while (true) { String s = '~' + Integer.toString(twiddle++); int i = shortName.indexOf(" "); if (i < 0) i = 0; else i = Math.min(8 - s.length(), i); attempt.append(shortName).replace(i, i + s.length(), s); if (shortNames.add(attempt.toString())) return attempt.toString(); attempt.setLength(0); } } //get set of directory entries for this directory public void read(long sectorNumber, byte[] buffer) throws IOException { long oddsectors = getClusterOffset(sectorNumber); long offset = clusterList.get(Long.valueOf(getClusterNumber(sectorNumber))).longValue() * SECTORS_PER_CLUSTER * SECTOR_SIZE; offset = offset + oddsectors * SECTOR_SIZE; // long clusterCount = (sectorNumber - dataSectionStart -(super.getStartCluster()-2)*SECTORS_PER_CLUSTER) * SECTOR_SIZE; int length = Math.min(dirEntry.length - (int)offset, SECTOR_SIZE); length = Math.max(0, length); System.arraycopy(dirEntry, (int) offset, buffer, 0, length); for (int i = length; i < SECTOR_SIZE; i++) buffer[i] = 0x00; } public void write(long sectorNumber, byte[] buffer) { //continually commit writes //check to see if the sector is in the data section long offset = getClusterOffset(sectorNumber); long cluster = getClusterNumber(sectorNumber); //cluster is allocated //check if it is allocated to a file or a directory int clusterCount = 0; for (int nextCluster = (int) getStartCluster(); nextCluster != cluster; clusterCount++) nextCluster = followFatChainLink(nextCluster); byte[] oldDirSector = new byte[SECTOR_SIZE]; //it is a directory, compare with old directory entry and act accordingly TreeBlockDevice.this.read(sectorNumber, oldDirSector, 1); //add buffer to directory's set of direntries writeDirectoryEntry(buffer, clusterCount * SECTORS_PER_CLUSTER + offset, sectorNumber); //update sectorToFatEntry sectorToFatEntry.put(Long.valueOf(sectorNumber), this); for (int i = 0; i < 16; i++) { int newStartCluster = (buffer[32 * i + 26] & 0xFF) + ((buffer[32 * i + 27] & 0xFF) << 8) + ((buffer[32 * i + 20] & 0xFF) << 16) + ((buffer[32 * i + 21] & 0xFF) << 24); if (((buffer[32 * i] & 0xFF) == 0xE5) && (followFatChainLink(newStartCluster) == 0)) { FatEntry entry = sectorToFatEntry.get(Long.valueOf(getSectorNumber(newStartCluster))); if (entry != null) { entry.getFile().delete(); //remove all entries for file in sectorToFatEntry for (int n = 0, next = newStartCluster; n < clusterCount + 1; n++, next = followFatChainLink(next)) for (int s = 0; s < SECTORS_PER_CLUSTER; s++) sectorToFatEntry.remove(Long.valueOf(getSectorNumber(next) + s)); } } else if ((buffer[32 * i + 11] & 0xFF) == 0xF) //skip LFN's for now continue; else if ((buffer[32 * i] & 0xFF) == 0xE5) continue; else { boolean changed = false; for (int j = 0; j < 32; j++) if ((oldDirSector[j + 32 * i] & 0xFF) != (buffer[j + 32 * i] & 0xFF)) changed = true; if (!changed) continue; String name = new String(buffer, 32 * i, 8, US_ASCII).trim(); String ext = new String(buffer, 32 * i + 8, 3, US_ASCII).trim(); if ((name.equals(".")) || (name.equals(".."))) //skip current and parent directory entries continue; long newStartSector = getSectorNumber(newStartCluster); boolean isDirectory = (buffer[32 * i + 11] & 0x10) == 0x10; //add in other attributes here like readonly, hidden etc. if (sectorToFatEntry.get(Long.valueOf(newStartSector)) == null) { //new dir entry was created and we need to create a new File File newFile; if (isDirectory) { newFile = new File(getFile().getPath(), name + ext); newFile.mkdir(); //make into a DirectoryEntry and add to data map DirectoryEntry newEntry = new DirectoryEntry(newFile, newStartCluster, this); //initialise it's dirEntry to a cluster of 0's byte[] zero = new byte[SECTOR_SIZE * SECTORS_PER_CLUSTER]; for (int c = 0; c < zero.length; c++) zero[c] = 0; newEntry.writeDirectoryEntry(zero, 0, newStartSector); //need to think about whether this is necessary for (int d = 0; d < SECTORS_PER_CLUSTER; d++) sectorToFatEntry.put(Long.valueOf(newStartSector + d), newEntry); } else { long fileSize = (buffer[32 * i + 28] & 0xFF) + ((buffer[32 * i + 29] & 0xFF) << 8) + ((buffer[32 * i + 30] & 0xFF) << 16) + ((buffer[32 * i + 31] & 0xFF) << 24); newFile = new File(getFile(), name + "." + ext); try { newFile.createNewFile(); //make into a FileEntry and add to data map FileEntry newEntry = new FileEntry(newFile, newStartCluster, this); newEntry.setSizeClusters(-1); newEntry.setFileSize(fileSize); sectorToFatEntry.put(Long.valueOf(newStartSector), newEntry); } catch (IOException e) { LOGGING.log(Level.WARNING, "cannot create new file", e); } catch (SecurityException e) { LOGGING.log(Level.WARNING, "not allowed to create new file", e); } } //check if the new object created corresponds to data which was written to an unallocated cluster for (int k = 0; k < SECTORS_PER_CLUSTER; k++) { byte[] array = bufferedWrites.remove(Long.valueOf(newStartSector + k)); if (array != null) TreeBlockDevice.this.write(newStartSector + k, array, 1); unmappedClusters.remove(Long.valueOf(newStartCluster)); } } else { //it has changed the properties of a file which is already allocated and we need to update, possibly rename it FatEntry changedFile = sectorToFatEntry.get(Long.valueOf(newStartSector)); File oldFile = changedFile.getFile(); File path = oldFile.getParentFile(); File newFile; if (isDirectory) newFile = new File(path, name + ext); else newFile = new File(path, name + "." + ext); oldFile.renameTo(newFile); changedFile.setFile(newFile); } } } } protected long buildTree(Map<Long, FatEntry> fat) throws IOException { if (!getFile().exists()) throw new IllegalStateException("Directory for virtual FAT32 drive doesn't exist! "+ getFile().getName()); File[] contents = getFile().listFiles(); //figure out size of directory entry size = 2; // 2 for the . and .. entries for (File f : contents) { String filename = f.getName(); if (filename.length() > 8) size += 1 + (((filename.length() - 1) / 13) + 1); else size += 1; } //set cluster size of this directory setSizeSectors((long) (size * 32 - 1) / SECTOR_SIZE + 1); long subClusters = 0; for (File f : contents) { FatEntry entry; if (f.isFile()) { entry = new FileEntry(f, getStartCluster() + getSizeInClusters() + subClusters, this); } else if (f.isDirectory()) { entry = new DirectoryEntry(f, getStartCluster() + getSizeInClusters() + subClusters, this); } else continue; subClusters += entry.buildTree(fat); addFile(entry); } dirSubClusters += subClusters; buildDirectoryEntry(); //generate cluster list makeClusterList(); //add entry to fatImage map fat.put(Long.valueOf(getStartCluster()), this); return dirSubClusters + getSizeInClusters(); } public void setFile(File file) { super.setFile(file); changePathOfTree(file); } private void changePathOfTree(File path) { //loop over sectors of dirEntry for (int i = 0; i < dirEntry.length / 32; i++) { int newStartCluster = (dirEntry[32 * i + 26] & 0xFF) + ((dirEntry[32 * i + 27] & 0xFF) << 8) + ((dirEntry[32 * i + 20] & 0xFF) << 16) + ((dirEntry[32 * i + 21] & 0xFF) << 24); if ((dirEntry[32 * i] & 0xFF) == 0xE5) continue; if ((dirEntry[32 * i + 11] & 0xFF) == 0xF) continue; //skip LFN's for now if ((dirEntry[32 * i] & 0xFF) == 0x00) continue; String name = new String(dirEntry, 32 * i, 8, US_ASCII).trim(); String ext = new String(dirEntry, 32 * i + 8, 3, US_ASCII).trim(); if ((name.equals(".")) || (name.equals(".."))) //skip current and parent directory entries continue; long newStartSector = getSectorNumber(newStartCluster); boolean isDirectory = (dirEntry[32 * i + 11] & 0x10) == 0x10; FatEntry myfile = sectorToFatEntry.get(Long.valueOf(newStartSector)); if (!isDirectory) myfile.setFile(new File(path, name + "." + ext)); else myfile.setFile(new File(path, name + ext)); } } public long getDirectorySubClusters() { return dirSubClusters; } public void addFile(FatEntry f) { files.add(f); } public void writeDirectoryEntry(byte[] buffer, long sectorOffset, long sectorNumber) { //update clusterlist clusterList.put(Long.valueOf(getClusterNumber(sectorNumber)), Long.valueOf(sectorOffset/SECTORS_PER_CLUSTER)); if (dirEntry.length < SECTOR_SIZE + sectorOffset*SECTOR_SIZE) { byte[] temp = new byte[(int) (sectorOffset+1)*SECTOR_SIZE]; System.arraycopy(dirEntry, 0, temp, 0, dirEntry.length); dirEntry = temp; } size = dirEntry.length/32; System.arraycopy(buffer, 0, dirEntry, (int) sectorOffset*SECTOR_SIZE, SECTOR_SIZE); } public void buildDirectoryEntry() { List<byte[]> entries = new ArrayList<byte[]>(); int totalSize = 0; DirectoryEntry parent = getParent(); if (parent != null) { //add . and .. entries to direntry { byte[] entry = new byte[32]; byte[] fullEntry = this.getDirectoryEntryComponent(); System.arraycopy(fullEntry, fullEntry.length - entry.length, entry, 0, entry.length); entry[0] = (byte) 0x2E; //'.' for (int i = 1; i < 11; i++) entry[i] = (byte) 0x20; totalSize += entry.length; entries.add(entry); } { byte[] entry = new byte[32]; byte[] fullEntry = parent.getDirectoryEntryComponent(); System.arraycopy(fullEntry, fullEntry.length - entry.length, entry, 0, entry.length); if (parent.getParent() == null) { //put in starting cluster (high 2 bytes) entry[20] = 0; entry[21] = 0; //put in starting cluster (low 2 bytes) entry[26] = 0; entry[27] = 0; } entry[0] = (byte) 0x2E; //'.' entry[1] = (byte) 0x2E; //'.' for (int i = 2; i < 11; i++) entry[i] = (byte) 0x20; entry[11] = (byte) 0x10; totalSize += entry.length; entries.add(entry); } } for (FatEntry e : files) { byte[] entry = e.getDirectoryEntryComponent(); totalSize += entry.length; entries.add(entry); } dirEntry = new byte[totalSize]; for (int i = 0, pos = 0; i < entries.size(); i++) { byte[] b = entries.get(i); System.arraycopy(b, 0, dirEntry, pos, b.length); pos += b.length; } } protected byte[] getDirectoryEntryComponent() { byte[] entry = super.getDirectoryEntryComponent(); entry[11]=(byte) 0x10; //Attrib Byte (Marks As Directory) String filename = getFile().getName(); if (filename.length() > 8) { byte[] lfn = createLongFileNameEntry(filename); byte[] fullEntry = new byte[lfn.length + entry.length]; System.arraycopy(lfn, 0, fullEntry, 0, lfn.length); System.arraycopy(entry, 0, fullEntry, lfn.length, entry.length); return fullEntry; } else return entry; } } private static class OpenFilesCache { private final LinkedList<RandomAccessFile> backing; private final int maxSize; public OpenFilesCache(int size) { maxSize = size; backing = new LinkedList<RandomAccessFile>(); } public RandomAccessFile getBackingFor(File f) throws FileNotFoundException { while (backing.size() >= maxSize) try { backing.removeLast().close(); } catch (IOException e) { LOGGING.log(Level.INFO, "IOException on RandomAccessFile close", e); } RandomAccessFile result = new RandomAccessFile(f, "r"); backing.addFirst(result); return result; } public void close() { for (RandomAccessFile f : backing) { try { f.close(); } catch (IOException e) { LOGGING.log(Level.INFO, "IOException on RandomAccessFile close", e); } } } } private static void putShort(byte[] data, int offset, short value) { data[offset] = (byte)value; data[offset + 1] = (byte)(value >> 8); } private static void putInt(byte[] data, int offset, int value) { data[offset] = (byte) value; data[offset + 1] = (byte) (value >> 8); data[offset + 2] = (byte) (value >> 16); data[offset + 3] = (byte) (value >> 24); } private static int getCylinder(long lba) { return (int)(lba / (HEADS_PER_CYLINDER * SECTORS_PER_TRACK)); } private static int getHead(long lba) { return (int)((lba % (HEADS_PER_CYLINDER * SECTORS_PER_TRACK)) / SECTORS_PER_TRACK); } private static int getSector(long lba) { return (int)((lba % (HEADS_PER_CYLINDER * SECTORS_PER_TRACK)) % SECTORS_PER_TRACK + 1); } private static int getLba(int cylinder, int head, int sector) { return (((cylinder * HEADS_PER_CYLINDER) + head) * SECTORS_PER_TRACK) + sector - 1; } private static byte[] makeLongFileNameEntry(byte[] unicodeName, byte checksum, int index) { int offset = index * 26; int remaining = unicodeName.length - offset; if (remaining <= 0) return null; boolean last = (remaining <= 26); byte[] entry = new byte[32]; if (last) entry[0] = (byte) ((index + 1) | 0x40); else entry[0] = (byte) (index + 1); entry[11] = 0x0f; entry[13] = checksum; if (last) { byte[] temp = new byte[offset + 26]; System.arraycopy(unicodeName, 0, temp, 0, unicodeName.length); for (int i = unicodeName.length; i < temp.length; i++) switch (i - unicodeName.length) { case 0: case 1: temp[i] = 0x00; break; default: temp[i] = (byte) 0xff; break; } unicodeName = temp; } //put up to 13 unicode characters into entry System.arraycopy(unicodeName, offset, entry, 1, 10); System.arraycopy(unicodeName, offset + 10, entry, 14, 12); System.arraycopy(unicodeName, offset + 22, entry, 28, 4); return entry; } }