/* * Copyright (C) 2003-2009 JNode.org * 2009,2010 Matthias Treydte * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package de.waldheinz.fs.fat; import de.waldheinz.fs.BlockDevice; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; /** * * * @author Ewout Prangsma <epr at jnode.org> * @author Matthias Treydte <waldheinz at gmail.com> */ public final class Fat { /** * The first cluster that really holds user data in a FAT. */ public final static int FIRST_CLUSTER = 2; private final long[] entries; private final FatType fatType; private final int sectorCount; private final int sectorSize; private final BlockDevice device; private final BootSector bs; private final long offset; private final int lastClusterIndex; private int lastAllocatedCluster; /** * Reads a {@code Fat} as specified by a {@code BootSector}. * * @param bs the boot sector specifying the {@code Fat} layout * @param fatNr the number of the {@code Fat} to read * @return the {@code Fat} that was read * @throws IOException on read error * @throws IllegalArgumentException if {@code fatNr} is greater than * {@link BootSector#getNrFats()} */ public static Fat read(BootSector bs, int fatNr) throws IOException, IllegalArgumentException { if (fatNr > bs.getNrFats()) { throw new IllegalArgumentException( "boot sector says there are only " + bs.getNrFats() + " FATs when reading FAT #" + fatNr); } final long fatOffset = FatUtils.getFatOffset(bs, fatNr); final Fat result = new Fat(bs, fatOffset); result.read(); return result; } /** * Creates a new {@code Fat} as specified by a {@code BootSector}. * * @param bs the boot sector specifying the {@code Fat} layout * @param fatNr the number of the {@code Fat} to create * @return the {@code Fat} that was created * @throws IOException on write error * @throws IllegalArgumentException if {@code fatNr} is greater than * {@link BootSector#getNrFats()} */ public static Fat create(BootSector bs, int fatNr) throws IOException, IllegalArgumentException { if (fatNr > bs.getNrFats()) { throw new IllegalArgumentException( "boot sector says there are only " + bs.getNrFats() + " FATs when creating FAT #" + fatNr); } final long fatOffset = FatUtils.getFatOffset(bs, fatNr); final Fat result = new Fat(bs, fatOffset); if (bs.getDataClusterCount() > result.entries.length) throw new IOException("FAT too small for device"); result.init(bs.getMediumDescriptor()); result.write(); return result; } private Fat(BootSector bs, long offset) throws IOException { this.bs = bs; this.fatType = bs.getFatType(); if (bs.getSectorsPerFat() > Integer.MAX_VALUE) throw new IllegalArgumentException("FAT too large"); if (bs.getSectorsPerFat() <= 0) throw new IOException( "boot sector says there are " + bs.getSectorsPerFat() + " sectors per FAT"); if (bs.getBytesPerSector() <= 0) throw new IOException( "boot sector says there are " + bs.getBytesPerSector() + " bytes per sector"); this.sectorCount = (int) bs.getSectorsPerFat(); this.sectorSize = bs.getBytesPerSector(); this.device = bs.getDevice(); this.offset = offset; this.lastAllocatedCluster = FIRST_CLUSTER; if (bs.getDataClusterCount() > Integer.MAX_VALUE) throw new IOException("too many data clusters"); if (bs.getDataClusterCount() == 0) throw new IOException("no data clusters"); this.lastClusterIndex = (int) bs.getDataClusterCount() + FIRST_CLUSTER; entries = new long[(int) ((sectorCount * sectorSize) / fatType.getEntrySize())]; if (lastClusterIndex > entries.length) throw new IOException( "file system has " + lastClusterIndex + "clusters but only " + entries.length + " FAT entries"); } public FatType getFatType() { return fatType; } /** * Returns the {@code BootSector} that specifies this {@code Fat}. * * @return this {@code Fat}'s {@code BootSector} */ public BootSector getBootSector() { return this.bs; } /** * Returns the {@code BlockDevice} where this {@code Fat} is stored. * * @return the device holding this FAT */ public BlockDevice getDevice() { return device; } private void init(int mediumDescriptor) { entries[0] = (mediumDescriptor & 0xFF) | (0xFFFFF00L & fatType.getBitMask()); entries[1] = fatType.getEofMarker(); } /** * Read the contents of this FAT from the given device at the given offset. * * @param offset the byte offset where to read the FAT from the device * @throws IOException on read error */ private void read() throws IOException { final byte[] data = new byte[sectorCount * sectorSize]; device.read(offset, ByteBuffer.wrap(data)); for (int i = 0; i < entries.length; i++) entries[i] = fatType.readEntry(data, i); } public void write() throws IOException { this.writeCopy(offset); } /** * Write the contents of this FAT to the given device at the given offset. * * @param offset the device offset where to write the FAT copy * @throws IOException on write error */ public void writeCopy(long offset) throws IOException { final byte[] data = new byte[sectorCount * sectorSize]; for (int index = 0; index < entries.length; index++) { fatType.writeEntry(data, index, entries[index]); } device.write(offset, ByteBuffer.wrap(data)); } /** * Gets the medium descriptor byte * * @return int */ public int getMediumDescriptor() { return (int) (entries[0] & 0xFF); } /** * Gets the entry at a given offset * * @param index * @return long */ public long getEntry(int index) { return entries[index]; } /** * Returns the last free cluster that was accessed in this FAT. * * @return the last seen free cluster */ public int getLastFreeCluster() { return this.lastAllocatedCluster; } public long[] getChain(long startCluster) { testCluster(startCluster); // Count the chain first int count = 1; long cluster = startCluster; while (!isEofCluster(entries[(int) cluster])) { count++; cluster = entries[(int) cluster]; } // Now create the chain long[] chain = new long[count]; chain[0] = startCluster; cluster = startCluster; int i = 0; while (!isEofCluster(entries[(int) cluster])) { cluster = entries[(int) cluster]; chain[++i] = cluster; } return chain; } /** * Gets the cluster after the given cluster * * @param cluster * @return long The next cluster number or -1 which means eof. */ public long getNextCluster(long cluster) { testCluster(cluster); long entry = entries[(int) cluster]; if (isEofCluster(entry)) { return -1; } else { return entry; } } /** * Allocate a cluster for a new file * * @return long the number of the newly allocated cluster * @throws IOException if there are no free clusters */ public long allocNew() throws IOException { int i; int entryIndex = -1; for (i = lastAllocatedCluster; i < lastClusterIndex; i++) { if (isFreeCluster(i)) { entryIndex = i; break; } } if (entryIndex < 0) { for (i = FIRST_CLUSTER; i < lastAllocatedCluster; i++) { if (isFreeCluster(i)) { entryIndex = i; break; } } } if (entryIndex < 0) { throw new IOException( "FAT Full (" + (lastClusterIndex - FIRST_CLUSTER) + ", " + i + ")"); //NOI18N } entries[entryIndex] = fatType.getEofMarker(); lastAllocatedCluster = entryIndex % lastClusterIndex; if (lastAllocatedCluster < FIRST_CLUSTER) lastAllocatedCluster = FIRST_CLUSTER; return entryIndex; } /** * Returns the number of clusters that are currently not in use by this FAT. * This estimate does only account for clusters that are really available in * the data portion of the file system, not for clusters that might only * theoretically be stored in the {@code Fat}. * * @return the free cluster count * @see FsInfoSector#setFreeClusterCount(long) * @see FsInfoSector#getFreeClusterCount() * @see BootSector#getDataClusterCount() */ public int getFreeClusterCount() { int result = 0; for (int i=FIRST_CLUSTER; i < lastClusterIndex; i++) { if (isFreeCluster(i)) result++; } return result; } /** * Returns the cluster number that was last allocated in this fat. * * @return */ public int getLastAllocatedCluster() { return this.lastAllocatedCluster; } /** * Allocate a series of clusters for a new file. * * @param nrClusters when number of clusters to allocate * @return long * @throws IOException if there are no free clusters */ public long[] allocNew(int nrClusters) throws IOException { final long rc[] = new long[nrClusters]; rc[0] = allocNew(); for (int i = 1; i < nrClusters; i++) { rc[i] = allocAppend(rc[i - 1]); } return rc; } /** * Allocate a cluster to append to a new file * * @param cluster a cluster from a chain where the new cluster should be * appended * @return long the newly allocated and appended cluster number * @throws IOException if there are no free clusters */ public long allocAppend(long cluster) throws IOException { testCluster(cluster); while (!isEofCluster(entries[(int) cluster])) { cluster = entries[(int) cluster]; } long newCluster = allocNew(); entries[(int) cluster] = newCluster; return newCluster; } public void setEof(long cluster) { testCluster(cluster); entries[(int) cluster] = fatType.getEofMarker(); } public void setFree(long cluster) { testCluster(cluster); entries[(int) cluster] = 0; } @Override public boolean equals(Object obj) { if (!(obj instanceof Fat)) return false; final Fat other = (Fat) obj; if (this.fatType != other.fatType) return false; if (this.sectorCount != other.sectorCount) return false; if (this.sectorSize != other.sectorSize) return false; if (this.lastClusterIndex != other.lastClusterIndex) return false; if (!Arrays.equals(this.entries, other.entries)) return false; if (this.getMediumDescriptor() != other.getMediumDescriptor()) return false; return true; } @Override public int hashCode() { int hash = 7; hash = 23 * hash + Arrays.hashCode(this.entries); hash = 23 * hash + this.fatType.hashCode(); hash = 23 * hash + this.sectorCount; hash = 23 * hash + this.sectorSize; hash = 23 * hash + this.lastClusterIndex; return hash; } /** * Is the given entry a free cluster? * * @param entry * @return boolean */ protected boolean isFreeCluster(long entry) { if (entry > Integer.MAX_VALUE) throw new IllegalArgumentException(); return (entries[(int) entry] == 0); } /** * Is the given entry a reserved cluster? * * @param entry * @return boolean */ protected boolean isReservedCluster(long entry) { return fatType.isReservedCluster(entry); } /** * Is the given entry an EOF marker * * @param entry * @return boolean */ protected boolean isEofCluster(long entry) { return fatType.isEofCluster(entry); } protected void testCluster(long cluster) throws IllegalArgumentException { if ((cluster < FIRST_CLUSTER) || (cluster >= entries.length)) { throw new IllegalArgumentException( "invalid cluster value " + cluster); } } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append(this.getClass().getSimpleName()); sb.append("[type="); sb.append(fatType); sb.append(", mediumDescriptor=0x"); sb.append(Integer.toHexString(getMediumDescriptor())); sb.append(", sectorCount="); sb.append(sectorCount); sb.append(", sectorSize="); sb.append(sectorSize); sb.append(", freeClusters="); sb.append(getFreeClusterCount()); sb.append("]"); return sb.toString(); } }