/* XXL: The eXtensible and fleXible Library for data processing Copyright (C) 2000-2011 Prof. Dr. Bernhard Seeger Head of the Database Research Group Department of Mathematics and Computer Science University of Marburg Germany This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; If not, see <http://www.gnu.org/licenses/>. http://code.google.com/p/xxl/ */ package xxl.core.io.fat; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintStream; import java.io.RandomAccessFile; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; import java.util.List; import xxl.core.io.fat.errors.DirectoryException; import xxl.core.io.fat.errors.FileDoesntExist; import xxl.core.io.fat.errors.InitializationException; import xxl.core.io.fat.errors.NotEnoughMemory; import xxl.core.io.fat.errors.WrongFATType; import xxl.core.io.fat.errors.WrongLength; import xxl.core.io.fat.util.MyMath; import xxl.core.io.fat.util.StringOperations; import xxl.core.io.raw.RawAccess; /** * This class represents a device like a floppy, partition, disk or something else. * Each device has a BPB (BIOS Parameter Block), a FAT (FileAllocation Table), this * may be FAT12, FAT16, or Fat32, a directory, and if it's a FAT32 a FSI (File System * Info) as well as a Backup Boot Sector. */ public class FATDevice { /** * Object of the BIOS Parameter Block. */ private BPB bpb = null; /** * Object of the file allocation table */ private FAT fat = null; /** * Object of the directory structure. */ private DIR directory; /** * Byte array which can hold one sector. It's used as a tmp variable. */ private byte[] sectorBuffer; /** * The number of root sectors of the root directory. */ protected int rootDirSectors = 0; //in a FAT32 system this value is 0 /** * The first data sector relative to sector with BPB on it. */ protected long firstDataSector = 0; /** * The file name of the file system for this device. Normally this is the * same name as the given to the RawAccess object. */ private String deviceName; /** * Object of raw access to read and write from and to disk. */ protected RawAccess rawAccess; /** * The extension of a RandomAccessFile needs a file * that exists inside the OS-file system. The file is never used, * just opened read only and immediately closed again. This field * only exists because of the inflexible implementation of RandomAccessFile. */ private File dummyFile; /** * Output stream for messages. */ protected PrintStream out; /** * A map from file names to file information for open files. */ private HashMap fileMap = new HashMap(); /** * Constant String that indicates the file mode is read only. */ public static final String FILE_MODE_READ = "r"; /** * Constant String that indicates the file mode is read and write. */ public static final String FILE_MODE_READ_WRITE = "rw"; /** * Instance of Date. */ protected static Date date = new Date(); /** * Instance of GregorianCalendar. */ protected static GregorianCalendar calendar = new GregorianCalendar(); /** * Initialize the calendar. */ static { calendar.setTime(date); } /** * This class is used for the file information of the opened files. It holds the information about the * time and day of the file, number of users, and other things. */ class FileInfo { /** * Object of ExtendedRandomAccessFile that is open. */ ExtendedRandomAccessFile eraf; /** * The last write time of the file. */ long lastWriteTime; /** * The length of the file. */ long length; /** * The number of users of the file. */ int numOfUsers; /** * The year of the last write access, valid range from 1980 to 2107. */ int writeYear; /** * The month of the last write access, valid range from 1 to 12. */ int writeMonth; /** * The day of the last write access, valid range from 1 to 31. */ int writeDay; /** * Indicates if the writeXXX variables are valid. */ boolean writeIsValid = false; /** * The year of the last access, valid range from 1980 to 2107. */ int accessYear; /** * The month of the last access, valid range from 1 to 12. */ int accessMonth; /** * The day of the last access, valid range from 1 to 31. */ int accessDay; /** * Indicates if the accessXXX variables are valid. */ boolean accessIsValid = false; /** * Create a new instance of this object. * @param eraf ExtendedRandomAccessFile object. * @param length the length of the file. * @param writeTime the time of the last write access. */ FileInfo(ExtendedRandomAccessFile eraf, long length, long writeTime) { this.eraf = eraf; this.length = length; this.lastWriteTime = writeTime; numOfUsers = 1; } //end constructor. /** * Set the last write time. * @param writeTime the last write time. */ void setWriteTime(long writeTime) { this.lastWriteTime = writeTime; writeIsValid = true; } //end setWriteTime(long writeTime) /** * Set the length of the file. * @param length the length of the file. */ void setLength(long length) { this.length = length; } //end setLength(long length) /** * Get the last write time. * @return the last write time. */ long getWriteTime() { return lastWriteTime; } //end getWriteTime() /** * Get the length of the file. * @return the length of the file. */ long getLength() { return length; } //end getLength() /** * Add a new user for the file. */ void addUser() { numOfUsers++; } //end addUser() /** * Remove one user for the file. */ void removeUser() { numOfUsers--; } //end removeUser() /** * Get the number of users for the file. * @return the number of users. */ int getNumberOfUsers() { return numOfUsers; } //end getNumberOfUsers() /** * Set the last write date. * @param year the year of the last write access, valid range from 1980 to 2107. * @param month the month of the last write access, valid range from 1 to 12. * @param day the day of the last write access, valid range from 1 to 31. */ void setWriteDate(int year, int month, int day) { writeYear = year; writeMonth = month; writeDay = day; writeIsValid = true; } //end setWriteDate(int year, int month, int day) /** * Set the last access date. * @param year the year of the last access, valid range from 1980 to 2107. * @param month the month of the last access, valid range from 1 to 12. * @param day the day of the last access, valid range from 1 to 31. */ void setAccessDate(int year, int month, int day) { accessYear = year; accessMonth = month; accessDay = day; accessIsValid = true; } //end setAccessDate(int year, int month, int day) /** * Get the year of the last write access. * @return the year of the last write access. */ int getWriteYear() { return writeYear; } //end getWriteYear() /** * Get the month of the last write access. * @return the month of the last write access. */ int getWriteMonth() { return writeMonth; } //end getWriteMonth() /** * Get the day of the last write access. * @return the day of the last write access. */ int getWriteDay() { return writeDay; } //end getWriteDay() /** * Get the year of the last access. * @return the year of the last access. */ int getAccessYear() { return accessYear; } //end getAccessYear() /** * Get the month of the last access. * @return the month of the last access. */ int getAccessMonth() { return accessMonth; } //end getAccessMonth() /** * Get the day of the last access. * @return the day of the last access. */ int getAccessDay() { return accessDay; } //end getAccessDay() /** * Return true if the writeXXX variables are valid. * @return true if the writeXXX variables are valid. */ boolean writeIsValid() { return writeIsValid; } //end writeIsValid() /** * Return true if the accessXXX variables are valid. * @return true if the accessXXX variables are valid. */ boolean accessIsValid() { return accessIsValid; } //end accessIsValid() /** * Return the ExtendedRandomAccessFile object. * @return the extendedRandomAccessFile object. */ ExtendedRandomAccessFile getFile() { return eraf; } //end getFile() } //end inner class FileInfo /** * Create an instance of this object. This constructor will mount the device. * @param deviceName is the name of the file. * @param rawAccess is the file which supports all IO-Operations to the (real) raw device. * @param out Output stream for some messages of the FATDevice. You can use System.out for * example. * @param dummyFile The extension of a RandomAccessFile needs a file * that exists inside the OS-file system. The file is never used, * just opened read only and immediately closed again. This parameter * only exists because of the inflexible implementation of RandomAccessFile. * @throws InitializationException in case this object couldn't be initialized. */ public FATDevice(String deviceName, RawAccess rawAccess, PrintStream out, File dummyFile) throws InitializationException { this.deviceName = deviceName; this.rawAccess = rawAccess; this.out = out; this.dummyFile = dummyFile; boot(); fileMap = new HashMap(); } //end constructor mount device /** * Create an instance of this object. This constructor will format the device * with the given fat type. * For a FAT12 file system the length of rawAccess must be smaller than 4084 * 512-byte-blocks. * For a FAT16 file system the length of rawAccess must be smaller than 4194304 * 512-byte-blocks and bigger than 32680 512-byte-blocks.. * For a FAT32 file system the length of rawAccess must be smaller than 0xFFFFFFFF * bytes and bigger than 532480 512-byte-blocks. * @param deviceName is the name of the file. * @param fatType the type of the fat. * @param rawAccess is the file which supports all IO-Operations to the (real) raw device. * @param out Output stream for some messages of the FATDevice. You can use System.out for * example. * @param dummyFile The extension of a RandomAccessFile needs a file * that exists inside the OS-file system. The file is never used, * just opened read only and immediately closed again. This parameter * only exists because of the inflexible implementation of RandomAccessFile. * @throws WrongLength if the number of blocks of rawAccess are to big for the given fatType. * @throws InitializationException in case this object couldn't be initialized. */ public FATDevice(String deviceName, byte fatType, RawAccess rawAccess, PrintStream out, File dummyFile) throws WrongLength, InitializationException { this.deviceName = deviceName; this.rawAccess = rawAccess; this.out = out; this.dummyFile = dummyFile; //there is no fatType flag in this class see bpb or fat //initialize all bytes with 0xFF out.println("Format the device, this may take some minutes."); format(fatType); fileMap = new HashMap(); } //end constructor format /** * Format the volume with a new file system. * Note: Use this method only to generate a new file system. If there's * already a file system you will lose all data of it. * @param fatType the type of the fat. * @throws WrongLength if the number of sectors of the rawAccess is to big for the given fatType. * @throws InitializationException in case of an initialization error. */ private void format(byte fatType) throws WrongLength, InitializationException { //init the BPB bpb = new BPB(this, rawAccess.getNumSectors(), fatType); //must be initialized as early as possible rootDirSectors = bpb.getRootDirSectors(); firstDataSector = bpb.getFirstDataSector(); sectorBuffer = new byte[bpb.BytsPerSec]; //init FAT fat = new FAT(this, bpb, fatType); fat.initializeFat(); //init Directory directory = new DIR(this, bpb); //////////////////////////////////////////////////////////////////// // init data sectors (all bytes have 0xF6 value at the beginning) // //////////////////////////////////////////////////////////////////// out.println("Init the data sector."); byte[] dataBlock = new byte[bpb.BytsPerSec]; for (int i=0; i < 512; i++) dataBlock[i] = (byte)0xF6; long numDataSectors = bpb.getNumDataSectors(); long percent; long oldPercent = 0; for (long i=firstDataSector; i < firstDataSector + numDataSectors; i++) { try { rawAccess.write(dataBlock, i); percent = MyMath.roundDown(((double)i / (firstDataSector+numDataSectors))*100); if (Math.abs(oldPercent - percent) > 4) { out.print(percent+"%, "); oldPercent = percent; } } catch (Exception e) //catch exception if we wrote to a bad cluster. { fat.markSectorAsBad(i); } } out.println("File system successfully created"); } //end format disk /** * This methods reads all important data structures of the * file system from the volume. * @throws InitializationException in case of an initailization error. */ private void boot() throws InitializationException { //initialize BPB byte[] b = new byte[512]; rawAccess.read(b,0); bpb = new BPB(this,b); //////////////////////////////////// // initialize important variables // //////////////////////////////////// //calculate the first sector of cluster 2 long FATSz = 0; rootDirSectors = ((bpb.RootEntCnt * 32) + (bpb.BytsPerSec - 1)) / bpb.BytsPerSec; if (bpb.FATSz16 != 0) FATSz = bpb.FATSz16; else FATSz = bpb.FATSz32; firstDataSector = bpb.RsvdSecCnt + (bpb.NumFATs * FATSz) + rootDirSectors; sectorBuffer = new byte[bpb.BytsPerSec]; //initialize FAT fat = new FAT(this, bpb); fat.initializeFat(); //initialize DIR directory = new DIR(this, bpb, bpb.getFatType()); out.println("\n\nSuccessfully boot"); } //end boot disk /** * This operation will reinitialize the FAT and the root directory. * The FAT will be cleared, only the entries that are marked as bad * entries will remain. The root directory will be cleared, the only * remaining entry is the entry for the root directory. Notice that * this operation can only be performed to a existing device that * was formatted in the past. */ public void fastFormat() { if (fat == null || directory == null) return; fat.fastFormat(); directory = new DIR(this, bpb); } /** * Return the name of the device. Normally this method is used to handle * the device name with the raw access. See also {@link #getRealDeviceName()} * for further information. * @return name of the device. */ public String getDeviceName() { //return StringOperations.removeDeviceName(deviceName); return StringOperations.extractFileName(deviceName); } //end getDeviceName() /** * Return the real name of the device. In contrast to {@link #getDeviceName()} this method * returns the place and the name of the file. If you create a RAF-device you have * the opportunity to store the raf-file on an other (real) device. For example: * Your working directory is 'c:\xxl' you create a RAF-device with name 'fat12' on * disk 'd:' in subfolder 'dir' then the real device name is 'g:\dir\fat12' but the * device name that should be used operate on this file system implementation is 'fat12' * only. This device name is returned by the method {@link #getDeviceName()}. * If you want to get or create a device you have to use this method because devices * are unique by the (real) device name. For all other operations you should use the * {@link #getDeviceName()} method since for the file system implementation it doesn't matter * where the file which simulates the file system is stored. * @return the real name of the device. */ public String getRealDeviceName() { return deviceName; } /////////////////////////// // rawAccess-Methods // /////////////////////////// /** * Read the sector given by sectorNumber. * @param sectorContent the variable into which the data is read. * @param sectorNumber the number where the sector should be read. * @return true if the operation has succeeded. */ public boolean readSector(byte sectorContent[], long sectorNumber) { try { rawAccess.read(sectorContent,sectorNumber); return true; } catch (Exception e) { fat.markSectorAsBad(sectorNumber); out.println("FAILURE: Couldn't read from raw device. Sector: " + sectorNumber); return false; } } //end readSector(long sectorNumber) /** * Write one sector to disk. * @param sectorContent the data to write. * @param sectorNumber the number where the sector should be written. */ public void writeSector(byte[] sectorContent, long sectorNumber) { try { rawAccess.write(sectorContent, sectorNumber); } catch (Exception e) { fat.markSectorAsBad(sectorNumber); } } //end writeSector(byte[] sectorContent, long sectorNumber) /** * Return the cluster given by clusterNumber as a byte array. The * clusterNumber is the direct cluster number there is no indirection * with method getFirstSectorNumberOfCluster(clusterNumber). * The method reads bpb.SecPerClus sectors to build the cluster, there for * the clusterNumber should be the first sector number of the cluster * given by getFirstSectorNumberOfCluster(clusterNumber). * @param clusterNumber the number of the cluster that should be returned. * @return the cluster as byte array. */ public byte[] readCluster(long clusterNumber) { byte[] cluster = new byte[bpb.BytsPerSec*bpb.SecPerClus]; byte[] dataBlock = new byte[512]; for (int i=0; i < bpb.SecPerClus; i++) { try { rawAccess.read(dataBlock, clusterNumber + i); } catch (Exception e) { out.println(e); fat.markClusterAsBad(clusterNumber); return null; } System.arraycopy(dataBlock, 0, cluster, i*bpb.BytsPerSec, bpb.BytsPerSec); } return cluster; } //end readCluster(clusterNumber) /** * Write a cluster to disk. The cluster is written at the given clusterNumber. * @param clusterNumber the number of the cluster at which the given cluster should be written. * @param clusterContent the data to write. */ public void writeCluster(long clusterNumber, byte[] clusterContent) { byte[] dataBlock = new byte[bpb.BytsPerSec]; for (int i=0; i < bpb.SecPerClus; i++) { System.arraycopy(clusterContent, i*bpb.BytsPerSec, dataBlock, 0, bpb.BytsPerSec); try { rawAccess.write(dataBlock, clusterNumber + i); } catch (Exception e) { fat.markClusterAsBad(clusterNumber); } } } //end writeSector(clusterNumber, clusterContent) ///////////////////// // Fat-Methods // ///////////////////// /** * Return the content of the fat at the given clusterNumber. * @param clusterNumber the index in the fat. * @return the fat at the given clusterNumber */ public long getFatContent(long clusterNumber) { return fat.getFatContent(clusterNumber); } //end getFatContent(long clusterNumber) /** * Return the content of given fat fatBuffer at index clusterNumber. * @param clusterNumber the index in the fat. * @param fatBuffer the whole fat as a byte array. * @return the content at the index clusterNumber. */ public long getFatContent(long clusterNumber, byte[] fatBuffer) { return fat.getFatContent(clusterNumber, fatBuffer); } //end getFatContent(long clusterNumber, byte[] fatBuffer) /** * Check if the given clusterNumbers points to an EOC_MARK. * * @param clusterNumber the index in the fat. * @return true is the clusterNumber equals an EOC_MARK otherwise false. */ protected boolean isLastCluster(long clusterNumber) { return fat.isLastCluster(clusterNumber); } //end isLastCluster(long clusterNumber) /** * Return the fat type. * @return the fat type. */ protected byte getFatType() { return fat.getFatType(); } //end getFatType /** * Extends the size of a file. All new clusters that are allocated will be automatically * stored in the fat started at lastClusterNumber. * @param numOfClusters the number of clusters. * @param lastClusterNumber the last cluster number of the file. * @return a extended List. * @throws NotEnoughMemory in case there is not enough memory to support * this operation. */ protected List extendFileSize(long numOfClusters, long lastClusterNumber) throws NotEnoughMemory { return fat.getFreeClusters(numOfClusters, lastClusterNumber); } //end extendFileSize(long numOfClusters, long lastClusterNumber) /** * Mark all cluster numbers stored at the freeClustersList as free. * @param freeClusterList list of cluster numbers that should be * marked as free. */ public void addFreeClusters(List freeClusterList) { fat.addFreeClusters(freeClusterList); } /** * Mark cluster numbers as free. The cluster number startClusterNumber will be set to EOC_MARK * all next clusters belonging to the cluster chain will be marked as free until the EOC_MARK * is reached. * @param startClusterNumber the cluster number to start with. */ public void addFreeClustersMarkFirstAsEOC(long startClusterNumber) { fat.addFreeClustersMarkFirstAsEOC(startClusterNumber); } //end addFreeClustersMarkFirstAsEOC(long startClusterNumber, long numOfClusters) /** * Mark cluster numbers as free beginning with the given startClusterNumber. * @param startClusterNumber the cluster number to start with. */ public void addFreeClusters(long startClusterNumber) { fat.addFreeClusters(startClusterNumber); } //end addFreeClusters(long startClusterNumber, long numOfClusters) /** * Get a list of size numOfFreeClusters with free cluster numbers as entries. * @param numOfFreeClusters the number of free clusters. * @return list with free cluster numbers as entries. The list * is empty, if there are not enough free clusters. * @throws NotEnoughMemory in case there is not enough memory to support * this operation. */ public List getFreeClusters(long numOfFreeClusters) throws NotEnoughMemory { return fat.getFreeClusters(numOfFreeClusters); } //end getFreeClusters(long numOfFreeClusters) /** * Return a list with numOfFreeClusters free cluster numbers. All this * numbers are automatically stored at the fat started by the given * lastClusterNumber. * @param numOfFreeClusters the number of free clusters. * @param lastClusterNumber clusterNumber of the last cluster. * @return a list with numOfFreeClusters free cluster numbers. * @throws NotEnoughMemory in case there is not enough memory to support * this operation. */ public List getFreeClusters(long numOfFreeClusters, long lastClusterNumber) throws NotEnoughMemory { return fat.getFreeClusters(numOfFreeClusters, lastClusterNumber); } //end getFreeClusters(long numOfFreeClusters, long lastClusterNumber) /** * Set the EOC_MARK at clusterNumber in the fat. * @param clusterNumber the index where the EOC_MARK should be set. */ public void setFatEocMark(long clusterNumber) { fat.setFatEocMark(clusterNumber); } //end setFatEocMark(long clusterNumber) /** * Set the given content at clusterNumber in the fat. * @param clusterNumber the index in the fat. * @param content the data to set. */ public void setFatContent(long clusterNumber, long content) { fat.setFatContent(clusterNumber, content); } //end setFatContent(long clusterNumber, long content) /** * Return the FAT object. * @return the FAT object. */ protected FAT getFAT() { return fat; } //end getFAT() /////////////////////////// // Directory-Methods // /////////////////////////// /** * Return the cluster number stored in the directory entry of the given fileName. * @param fileName the name of the file. * @return the cluster number * @throws FileDoesntExist in case the file given by fileName doesn't exist. */ protected long getStartClusterNumber(String fileName) throws FileDoesntExist { return directory.getClusterNumber(fileName); } //end getStartClusterNumber(String fileName) /** * Return the file length stored at the directory entry for the given fileName. * @param fileName the name of the file. * @return the length of the file. */ protected long length(String fileName)// throws FileDoesntExist { if (fileMap.get(fileName) != null) return ((FileInfo)fileMap.get(fileName)).getLength(); return directory.length(fileName); } //end getFileSize(String fileName) /** * Check if the file given by fileName exists. * @param fileName the name of the file. * @return true if the file exists otherwise false. */ protected boolean fileExists(String fileName) { return directory.exists(fileName); } //end fileExists(String fileName) /** * Check if the file given by fileName is a directory. * @param fileName the name of the file. * @return true if the file is a directory otherwise false. */ public boolean isDirectory(String fileName) { return directory.isDirectory(fileName); } /** * Tests whether the file denoted by this fileName is a normal * file. * * @param fileName the name of the file. * @return true if and only if the file denoted by this * fileName exists and is a normal file; false otherwise. */ public boolean isFile(String fileName) { return directory.isFile(fileName); } //end isFile(String fileName) /** * Return true if the file given by fileName is marked as hidden. * * @param fileName the name of the file. * @return true if the file given by fileName is marked as hidden; false otherwise. */ public boolean isHidden(String fileName) { return directory.isHidden(fileName); } //end isHidden(String fileName) /** * Return the last write time of the file given * by the fileName. The returned value is the number of milliseconds * since January 1, 1970, 00:00:00 GMT. * @param fileName the name of the file. * @return the number of milliseconds since January 1, 1970, 00:00:00 GMT * until the last write time. */ public long getWriteTime(String fileName) { if (fileMap.get(fileName) != null) return ((FileInfo)fileMap.get(fileName)).getWriteTime(); return directory.getLastWriteTime(fileName); } //end getWriteTime(String fileName) /** * Return the creation time of the file given * by the fileName. The returned value is the number of milliseconds * since January 1, 1970, 00:00:00 GMT. * @param fileName the name of the file. * @return the number of milliseconds since January 1, 1970, 00:00:00 GMT, * until the creation time. */ public long getCreationTime(String fileName) { return directory.getCreationTime(fileName); } //end getCreationTime(String fileName) /** * Return the attribute stored at the directory entry given by fileName. * The returned attributes are the varaibles ATTR_XXX of the DIR-class. * @param fileName the name of the file. * @return the attribute of the directory entry given by fileName. */ public byte getAttribute(String fileName) { return directory.getAttribute(fileName); } //end getAttribute(String fileName) /** * Set a new length at the directory entry given by fileName. * @param fileName the name of the file. * @param fileLength the new length of the file. * @return true if the operation was successful, otherwise false. */ public boolean writeLength(String fileName, long fileLength) { return writeLength(fileName, fileLength, false); } //end writeLength(String fileName, long fileLength) /** * Set a new length at the directory entry given by fileName. * @param fileName the name of the file. * @param fileLength the new length of the file. * @param writeThrough if set the length is written to disk * otherwise the length is stored in a buffer * @return true if the operation was successful, otherwise false. */ public boolean writeLength(String fileName, long fileLength, boolean writeThrough) { if (!writeThrough && fileMap.get(fileName) != null) { ((FileInfo)fileMap.get(fileName)).setLength(fileLength); return true; } else return directory.writeLength(fileName, fileLength); } //end writeLength(String fileName, long fileLength) /** * Create file and return the start cluster number. * @param fileName the name of the file. * @return the cluster number stored at the directory entry given by the new file name. * @throws DirectoryException in case the entry couldn't be created. */ protected long createFile(String fileName) throws DirectoryException { return directory.createFile(fileName); } //end createFile(String fileName) /** * Creates the file and return the start cluster number. * @param fileName the name of the file. * @param length the initial length of the file. * @return the cluster number stored at the directory entry given by the new file fileName. * @throws DirectoryException in case the entry couldn't be created. */ protected long createFile(String fileName, long length) throws DirectoryException { return directory.createFile(fileName, length); } //end createFile(String fileName) /** * Deletes the file or directory denoted by this abstract pathname. If * this pathname denotes a directory, then the directory must be empty in * order to be deleted. * * @param fileName the name of the file. * @return true if and only if the file or directory is successfully * deleted; false otherwise. */ public boolean delete(String fileName) { return directory.delete(fileName); } //end delete(String fileName) /** * Extends the size of the file fileName with numOfClusters clusters. * @param fileName the name of the file. * @param numOfClusters the number of clusters the file should be extended. * @return the clusterNumber of the first new allocated cluster, * @throws DirectoryException in case the file size couldn't be extended. */ protected List extendFileSize(String fileName, long numOfClusters) throws DirectoryException { List freeClusters = fat.getFreeClusters(numOfClusters); long startClusterNumber = ((Long)freeClusters.get(0)).longValue(); directory.setClusterNumber(fileName, startClusterNumber); return freeClusters; } //end extendFileSize(String fileName, long numOfClusters) /** * Returns an array of strings naming the files and directories in the * directory denoted by this fileName. * If this fileName does not denote a directory, then this method returns * null. Otherwise an array of strings is returned, one for each file or * directory in the directory. Names denoting the directory itself and * the directory's parent directory are not included in the result. * Each string is a file name rather than a complete path. * There is no guarantee that the name strings in the resulting array * will appear in any specific order; they are not, in particular, * guaranteed to appear in alphabetical order. * @param fileName the name of the file. * @return an array of strings naming the files and directories in the * directory denoted by this pathname. The array will be empty if the * directory is empty. Returns null if this pathname does not denote a * directory, or if an I/O error occurs. */ protected String[] list(String fileName) { return directory.list(fileName); } //end list(String fileName) /** * Creates the directory named by this pathname. * @param directoryName the name of the directory. * @return true if and only if the directory was * created; false otherwise. */ protected boolean makeDirectory(String directoryName) { try { directory.createDirectory(directoryName); } catch (DirectoryException e) { return false; } return true; } //end makeDirectory(String directoryName) /** * Renames the file denoted by fileName to newName. If the entry named by fileName * is an existing file, it will be deleted and a new file/directory given by newName * will be created (with all subdirectories if they don't exist). It is not possible * to change the device name. In case the destination file/directory already exists, the * old file will not be deleted and the renaming operation will not processed. In case the * entry named by this pathname is a directory the directory is only deleted if it is * empty. * @param fileName the name of the file which should be renamed. * @param newName the new name for the named file. * @return true if and only if the renaming succeeded; false * otherwise. */ protected boolean renameTo(String fileName, String newName) { //check if newName already exists if (fileExists(StringOperations.removeDeviceName(newName))) return false; //check if fileName is in use by other users. FileInfo fileInfo = (FileInfo)fileMap.get(fileName); if (fileInfo != null && fileInfo.getNumberOfUsers() > 1) { out.println("source file:"+fileName+" is in use. The renaming will not be done."); return false; } return directory.renameTo(fileName, newName); } //end renameTo(String fileName, String newName) /** * Returns an array of strings naming the files in the root * directory. * There is no guarantee that the name strings in the resulting array * will appear in any specific order; they are not, in particular, * guaranteed to appear in alphabetical order. * * @return An array of strings naming the files and directories in the * root directory. The array will be empty if the directory is empty. * Returns null if an I/O error occurs. * */ protected String[] listRoots() { return directory.listRoots(); } //end listRoots() /** * Sets the last-modified time of the file or directory named by fileName. * @param fileName the name of the file. * @param time the new last-modified time, measured in milliseconds since * the epoch (00:00:00 GMT, January 1, 1970). * @return true if and only if the operation succeeded; false otherwise. */ protected boolean setLastWriteTime(String fileName, long time) { return setLastWriteTime(fileName, time, false); } //end setLastWriteTime(String fileName, long time) /** * Sets the last-modified time of the file or directory named by fileName. * @param fileName the name of the file. * @param time the new last-modified time, measured in milliseconds since * the epoch (00:00:00 GMT, January 1, 1970). * @param writeThrough indicates if this modification should be written to * disk immediately or later. If the value is true the modification is written * immediately. * @return true if and only if the operation succeeded; false otherwise. */ protected boolean setLastWriteTime(String fileName, long time, boolean writeThrough) { if (!writeThrough && fileMap.get(fileName) != null) { ((FileInfo)fileMap.get(fileName)).setWriteTime(time); return true; } return directory.setLastWriteTime(fileName, time); } //end setLastWriteTime(String fileName, long time, boolean writeThrough) /** * Set the last write date of the given fileName. * @param fileName the name of the file. * @param year the year of the last write date, valid range from 1980 to 2107. * @param month the month of the last write date, valid range from 1 to 12. * @param day the day of the last write date, valid range from 1 to 31. * @return true if the last date could be set; false otherwise. */ protected boolean setLastWriteDate(String fileName, int year, int month, int day) { return setLastWriteDate(fileName, year, month, day, false); } //end setLastWriteDate(String fileName, int year, int month, int day) /** * Set the last write date of the given fileName. * @param fileName the name of the file. * @param year the year of the last write date, valid range from 1980 to 2107. * @param month the month of the last write date, valid range from 1 to 12. * @param day the day of the last write date, valid range from 1 to 31. * @param writeThrough indicates if this modification should be written to * disk immediately or later. If the value is true the modification is written * immediately. * @return true if the last date could be set; false otherwise. */ protected boolean setLastWriteDate(String fileName, int year, int month, int day, boolean writeThrough) { if (!writeThrough && fileMap.get(fileName) != null) { ((FileInfo)fileMap.get(fileName)).setWriteDate(year, month, day); return true; } return directory.setLastWriteDate(fileName, year, month, day); } //end setLastWriteDate(String fileName, int year, int month, int day, boolean writeThrough) /** * Set the last access date to the given fileName. * @param fileName the name of the file. * @param year the last access year, valid range from 1980 to 2107. * @param month the last access month, valid range from 1 to 12. * @param day the last access day, valid range from 1 to 31. * @return true if and only if the operation is successfully * done; false otherwise. */ protected boolean setLastAccessDate(String fileName, int year, int month, int day) { return setLastAccessDate(fileName, year, month, day, false); } //end setLastAccessDate(String fileName, int year, int month, int day) /** * Set the last access date to the given directory entry. * @param fileName the name of the file. * @param year the last access year, valid range from 1980 to 2107. * @param month the last access month, valid range from 1 to 12. * @param day the last access day, valid range from 1 to 31. * @param writeThrough indicates if this modification should be written to * disk immediately or later. If the value is true the modification is written * immediately. * @return true if and only if the operation is successfully * done; false otherwise. */ protected boolean setLastAccessDate(String fileName, int year, int month, int day, boolean writeThrough) { if (!writeThrough && fileMap.get(fileName) != null) { ((FileInfo)fileMap.get(fileName)).setAccessDate(year, month, day); return true; } return directory.setLastAccessDate(fileName, year, month, day); } //end setLastAccessDate(String fileName, int year, int month, int day, boolean writeThrough) //////////////////// // BPB-Methods // //////////////////// /** * Return the number of sectors per cluster. * @return the number of sectors per cluster. */ protected int getSecPerClus() { return bpb.SecPerClus; } //end getSecPerClus() /** * Return the number of bytes per sector. * @return the number of bytes per sector. */ protected int getBytsPerSec() { return bpb.BytsPerSec; } //end getSecPerClus(String fileName) /** * Return the number of sectors of the root directory. * The returned value is only valid for FAT12 or FAT16 FAT'S. * @return the number of sectors of the root directory. */ public long getNumRootDirSectors() { return bpb.getRootDirSectors(); } //end getNumRootDirSectors() /** * Return the first root directory sector number. * @return the first root directory sector number. */ protected long getFirstRootDirSector() { return bpb.getFirstRootDirSecNum(); } //end getFirstRootDirSector() /** * Return the BPB object. * @return the BPB object. */ protected BPB getBPB() { return bpb; } //end getBPB() ///////////////////// // Other-Methods // ///////////////////// /** * Calculates the first sector number for the given cluster number. * Important: variable firstDataSector must be initialized. * @param clusterNumber the number of the cluster. * @return the first sector number for the cluster with the given cluster number. */ public long getFirstSectorNumberOfCluster(long clusterNumber) { return ((clusterNumber - 2)*bpb.SecPerClus) + firstDataSector; } //end getFirstSectorOfCluster(clusterNumber) /** * This method can be used to exchange the data of a file from the original file system * to this file system based on raw-access. The directory path to the file directoryName * must exist, otherwise this operation could not be done. * @param file the source file. * @param directoryName the name of the destination file. * @throws DirectoryException in case the copy process couldn't be done. * @throws FileNotFoundException in case the file couldn't be found. * @throws IOException in case an I/O error with file occurred. * @throws InitializationException in case the ExtendedRandomAccessFile couldn't be initialized. */ public void copyFileToRAW(File file, String directoryName) throws DirectoryException, FileNotFoundException, IOException, InitializationException { directoryName = StringOperations.removeDeviceName(directoryName); RandomAccessFile raf = new RandomAccessFile(file, "rw"); createFile(directoryName, raf.length()); ExtendedRandomAccessFile eraf = getRandomAccessFile(directoryName, "rw"); //copy in blocks of 512 bytes int read; //indicates how many bytes are at the buffer for (;;) { read = raf.read(sectorBuffer); if (read == -1) break; eraf.write(sectorBuffer, 0, read); } raf.close(); eraf.close(); } //end copyFileToRAW(File file, String directoryName) /** * This method can be used to exchange the data of a file from this file system based on * raw-access to the original file system. * @param rawDirectoryName the name of the source file. * @param originalFile the destination file. * @throws DirectoryException in case the copy process couldn't be done. * @throws IOException in case an I/O error with file occurred. * @throws InitializationException in case the ExtendedRandomAccessFile couldn't be initialized. */ public void copyFileToOriginalFileSystem(String rawDirectoryName, File originalFile) throws DirectoryException, IOException, InitializationException { String directoryName = StringOperations.removeDeviceName(rawDirectoryName); RandomAccessFile raf = new RandomAccessFile(originalFile, "rw"); ExtendedRandomAccessFile eraf = getRandomAccessFile(directoryName, "r"); //copy in blocks of 512 bytes int read; //indicates how many bytes are at the buffer for (;;) { read = eraf.read(sectorBuffer); if (read == -1) break; raf.write(sectorBuffer, 0, read); } raf.close(); eraf.close(); } //end copyFileToOriginalFileSystem(String rawDirectoryName, File originalFile) ////////////////////////////////////// // ExtendedFile-Methods // ////////////////////////////////////// /** * Return an instance of ExtendedFile. * * @param pathname the given pathname. * @return an instance of ExtendedFile with the given pathname. */ public ExtendedFile getFile(String pathname) { ExtendedFile ef = new ExtendedFile(this, pathname); return ef; } //end getFile(String pathname) /** * Return an instance of ExtendedFile. * @param parent is the path to the parent directory. * @param child is the name of the file or directory. * @return an instance of ExtendedFile with the given parent and * child name merged to a pathname. */ public ExtendedFile getFile(String parent, String child) { ExtendedFile ef = new ExtendedFile(this, parent, child); return ef; } //end getFile(String parent, String child) ////////////////////////////////////// // ExtendedRandomAccessFile-Methods // ////////////////////////////////////// /** * The mode argument must either be equal to "r" or "rw", indicating * that the file is to be opened for input only or for both input * and output, respectively. The write methods on this object will * always throw an IOException if the file is opened with a mode of * "r". If the mode is "rw" and the file does not exist, then an * attempt is made to create it. An IOException is thrown if the * name argument refers to a directory. * * @param fileName the name of the file. * @param mode indicates if the file is to be opened for input only * or for both input and output * @return an instance of ExtendedRandomAccessFile with the given fileName and mode, or * null in case there is already an opened ExtendedRandomAccessFile with an other mode. * @throws FileNotFoundException if the file exists but is a directory * rather than a regular file, or cannot be opened or created for any other reason. * @throws DirectoryException in case the ExtendedRandomAccessFile couldn't be initialized. */ public ExtendedRandomAccessFile getRandomAccessFile(String fileName, String mode) throws FileNotFoundException, DirectoryException { FileInfo fileInfo = (FileInfo)fileMap.get(fileName); if (fileInfo != null) { if (fileInfo.getFile().getMode().equals(FILE_MODE_READ) && mode.equals(FILE_MODE_READ_WRITE)) return null; fileInfo.addUser(); return fileInfo.getFile(); } ExtendedRandomAccessFile eraf = new ExtendedRandomAccessFile(this, fileName, mode, dummyFile); try { fileMap.put(fileName, new FileInfo(eraf, eraf.length(), 0)); } catch (IOException e) { throw new DirectoryException("ExtendedRandomAccessFile is not accessible."); } return eraf; } //end getRandomAccessFile(fileName, mode) /** * Close the file with name fileName. * @param fileName the name of the file. */ public void close(String fileName) { FileInfo fileInfo = (FileInfo)fileMap.get(fileName); if (fileInfo.getNumberOfUsers() <= 1) { if (fileInfo.writeIsValid()) { setLastWriteTime(fileName, fileInfo.getWriteTime(), true); setLastWriteDate(fileName, fileInfo.getWriteYear(), fileInfo.getWriteMonth(), fileInfo.getWriteDay(), true); setLastAccessDate(fileName, fileInfo.getAccessYear(), fileInfo.getAccessMonth(), fileInfo.getAccessDay(), true); } else if (fileInfo.accessIsValid()) { setLastAccessDate(fileName, fileInfo.getAccessYear(), fileInfo.getAccessMonth(), fileInfo.getAccessDay(), true); } fileMap.remove(fileName); } else fileInfo.removeUser(); } //end close() /** * Write the given value as a byte at the index calculated by filePointer * and sectorNumber. * * @param fileName the name of the file. * @param value the data. * @param filePointer the current position in a file. * @param sectorNumber the current sector number the filePointer points in. */ public void writeByte(String fileName, int value, long filePointer, long sectorNumber) { byte b[] = new byte[512]; readSector(b, sectorNumber); b[(int)(filePointer % bpb.BytsPerSec)] = (byte)value; writeSector(b, sectorNumber); setLastWriteTime(fileName, date.getTime()); setLastWriteDate( fileName, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.DAY_OF_MONTH) ); setLastAccessDate( fileName, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.DAY_OF_MONTH) ); } //end writeByte(int value, long filePointer, long sectorNumber) /** * Read a byte from the data region of a file given by filePointer and sectorNumber. * @param filePointer the current position in a file. * @param sectorNumber the current sector number the filePointer points in. * @return the read byte */ public byte readByte(long filePointer, long sectorNumber) { byte b[] = new byte[512]; readSector(b, sectorNumber); return b[(int)(filePointer - (filePointer/bpb.BytsPerSec)*bpb.BytsPerSec)]; //filePointer mod bpb.BytsPerSec } //end readByte(long filePointer, long sectorNumber) /** * Unmount the device. This method is called by the file system, never * call this method directly. */ protected void unmount() { Iterator fileIterator = fileMap.values().iterator(); while (fileIterator.hasNext()) { Object file = ((FileInfo)fileIterator.next()).getFile(); try { ((ExtendedRandomAccessFile)file).close(); } catch (Exception e) { out.println(e); } } //unmount fat fat.unmount(); //close raw access file rawAccess.close(); } //end unmount() /** * Return the total number of free bytes. * @return the total number of free bytes. */ public long getNumberOfFreeBytes() { return fat.getNumberOfFreeBytes(); } //end getNumberOfFreeBytes() //////////////////////////////////////////// // DEBUG-METHODS also used by RawExplorer // //////////////////////////////////////////// /** * Return the information about the BPB as String. * @return information about the BPB. */ public String getBPBInfo() { return bpb.toString(); } /** * Read the BPB-sector. * @return the BPB-Sector as byte array. */ public byte[] getBPBSector() { byte b[] = new byte[512]; if (readSector(b,0)) return b; else return null; } /** * Return the FAT with the given fatNumber. * @param fatNumber the number of the FAT. * @return the FAT indicated by fatNumber. */ public byte[] getFATSectors(int fatNumber) { return fat.getFAT(fatNumber); } /** * Return the start sector number for the given fatNumber. * @param fatNumber the number of the FAT. * @return the start sector number. */ public long getFATSectorNumber(int fatNumber) { return fat.getFATSectorNumber(fatNumber); } /** * Return the FSI-sector. * @return the FSI-sector as byte array. * @throws WrongFATType in case the actual FAT has no FSI. */ public byte[] getFSI() throws WrongFATType { byte b[] = new byte[512]; if (readSector(b, bpb.getFSInfoSectorNumber())) return b; else return null; } /** * Return the sector number of the FSI-sector. * @return the sector number of the FSI-sector. * @throws WrongFATType in case the actual FAT has no FSI. */ public long getFSISectorNumber() throws WrongFATType { return bpb.getFSInfoSectorNumber(); } /** * Return the root directory as byte array. * @return the root directory as byte array. */ public byte[] getRootDir() { return directory.getRootDir(); } /** * Return the first sector number of the root directory. * @return the first sector number of the root directory. */ public long getRootSectorNumber() { return bpb.getFirstRootDirSecNum(); } } //end class FATDevice