/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * 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 org.jnode.fs.fat; import java.io.IOException; import java.io.PrintWriter; import java.nio.ByteBuffer; import java.util.Arrays; import org.jnode.driver.block.BlockDeviceAPI; import org.jnode.fs.FileSystemFullException; /** * @author epr */ public class Fat { private long[] entries; /** The type of FAT */ private FatType fatType; /** The number of sectors this fat takes */ private int nrSectors; /** The number of bytes/sector */ private int sectorSize; private boolean dirty; /** entry index for find next free entry */ private int lastFreeCluster = 2; /** * Create a new instance * * @param bitSize * @param nrSectors * @param sectorSize */ public Fat(FatType bitSize, int mediumDescriptor, int nrSectors, int sectorSize) { this.fatType = bitSize; this.nrSectors = nrSectors; this.sectorSize = sectorSize; this.dirty = false; switch (bitSize) { case FAT12: entries = new long[(int) ((nrSectors * sectorSize) / 1.5)]; break; case FAT16: entries = new long[(nrSectors * sectorSize) / 2]; break; case FAT32: entries = new long[(nrSectors * sectorSize) / 4]; break; default: throw new IllegalArgumentException("Invalid bitSize " + bitSize); } entries[0] = (mediumDescriptor & 0xFF) | 0xFFFFFF00; } /** * Read the contents of this FAT from the given device at the given offset. * * @param device */ public synchronized void read(BlockDeviceAPI device, long offset) throws IOException { byte[] data = new byte[nrSectors * sectorSize]; device.read(offset, ByteBuffer.wrap(data)); for (int i = 0; i < entries.length; i++) { int idx, b1, b2, v; switch (fatType) { case FAT12: idx = (int) (i * 1.5); b1 = data[idx] & 0xFF; b2 = data[idx + 1] & 0xFF; v = (b2 << 8) | b1; if ((i % 2) == 0) { entries[i] = v & 0xFFF; } else { entries[i] = v >> 4; } break; case FAT16: idx = i * 2; b1 = data[idx] & 0xFF; b2 = data[idx + 1] & 0xFF; entries[i] = (b2 << 8) | b1; break; case FAT32: idx = i * 4; long l1 = data[idx] & 0xFF; long l2 = data[idx + 1] & 0xFF; long l3 = data[idx + 2] & 0xFF; long l4 = data[idx + 3] & 0xFF; entries[i] = (l4 << 24) | (l3 << 16) | (l2 << 8) | l1; break; } } this.dirty = false; } /** * Write the contents of this FAT to the given device at the given offset. * * @param device */ public synchronized void write(BlockDeviceAPI device, long offset) throws IOException { byte[] data = new byte[nrSectors * sectorSize]; for (int i = 0; i < entries.length; i++) { long v = entries[i]; int idx; switch (fatType) { case FAT12: idx = (int) (i * 1.5); if ((i % 2) == 0) { data[idx] = (byte) (v & 0xFF); data[idx + 1] = (byte) ((v >> 8) & 0x0F); } else { data[idx] |= (byte) ((v & 0x0F) << 4); data[idx + 1] = (byte) ((v >> 4) & 0xFF); } break; case FAT16: idx = i << 1; data[idx] = (byte) (v & 0xFF); data[idx + 1] = (byte) ((v >> 8) & 0xFF); break; case FAT32: idx = i << 2; data[idx] = (byte) (v & 0xFF); data[idx + 1] = (byte) ((v >> 8) & 0xFF); data[idx + 2] = (byte) ((v >> 16) & 0xFF); data[idx + 3] = (byte) ((v >> 24) & 0xFF); break; } } device.write(offset, ByteBuffer.wrap(data)); this.dirty = false; } /** * Gets the medium descriptor byte * * @return int */ public int getMediumDescriptor() { return (int) (entries[0] & 0xFF); } /** * Sets the medium descriptor byte */ public void setMediumDescriptor(int descr) { entries[0] = 0xFFFFFF00 | (descr & 0xFF); } /** * Gets the number of entries of this fat * * @return int */ public int getNrEntries() { return entries.length; } /** * Gets the entry at a given offset * * @param index * @return long */ public long getEntry(int index) { return entries[index]; } public synchronized 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]; testCluster(cluster); // prevent infinite loop in common case where it hits a 0 } // 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 synchronized 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 */ public synchronized long allocNew() throws IOException { int i; int entryIndex = -1; for (i = lastFreeCluster; i < entries.length; i++) { if (isFreeCluster(entries[i])) { entryIndex = i; break; } } if (entryIndex < 0) { for (i = 2; i < lastFreeCluster; i++) { if (isFreeCluster(entries[i])) { entryIndex = i; break; } } } if (entryIndex < 0) { throw new FileSystemFullException("FAT Full (" + entries.length + ", " + i + ")"); } entries[entryIndex] = fatType.getEofMarker(); lastFreeCluster = entryIndex + 1; this.dirty = true; return entryIndex; } /** * Allocate a series of clusters for a new file * * @return long */ public synchronized long[] allocNew(int nrClusters) throws IOException { 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 * * @return long */ public synchronized 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 synchronized void setEof(long cluster) { testCluster(cluster); entries[(int) cluster] = fatType.getEofMarker(); } public synchronized void setFree(long cluster) { testCluster(cluster); entries[(int) cluster] = 0; } /** * Print the contents of this FAT to the given writer. Used for debugging * purposes. * * @param out */ public void printTo(PrintWriter out) { int freeCount = 0; out.println("medium descriptor 0x" + Integer.toHexString(getMediumDescriptor())); for (int i = 2; i < entries.length; i++) { long v = entries[i]; if (isFreeCluster(v)) { freeCount++; } else { out.print("0x" + Integer.toHexString(i) + " -> "); if (isEofCluster(v)) { out.println("eof"); } else if (isReservedCluster(v)) { out.println("reserved"); } else { out.println("0x" + Long.toHexString(v)); } } } out.println("Nr free entries " + freeCount); } /** * Compare this Fat with another Fat. */ public boolean equals(Object other) { if (other instanceof Fat) { Fat of = (Fat) other; return Arrays.equals(entries, of.entries); } else { return false; } } /** * Is the given entry a free cluster? * * @param entry * @return boolean */ protected boolean isFreeCluster(long entry) { return (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 < 2) || (cluster >= entries.length)) { throw new IllegalArgumentException("Invalid cluster value: 0x" + Long.toHexString(cluster)); } } /** * Returns the dirty. * * @return boolean */ public boolean isDirty() { return dirty; } }