/*
* $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.jfat;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import org.jnode.driver.block.BlockDeviceAPI;
import org.jnode.fs.FileSystemException;
/**
* @author gvt
*/
public abstract class Fat {
private final BlockDeviceAPI api;
private final BootSector bs;
private final FatCache cache;
private int lastfree;
private final ByteBuffer clearbuf;
protected Fat(BootSector bs, BlockDeviceAPI api) {
this.bs = bs;
this.api = api;
/*
* create a suitable cache
*/
cache = new FatCache(this, 8192, 512);
/*
* set lastfree
*/
rewindFree();
/*
* and blank the clear buffer
*/
byte[] cleardata = new byte[getClusterSize()];
Arrays.fill(cleardata, 0, cleardata.length, (byte) 0x00);
/*
* setup the clear buffer
*/
clearbuf = ByteBuffer.wrap(cleardata).asReadOnlyBuffer();
}
public static Fat create(BlockDeviceAPI api) throws IOException, FileSystemException {
BootSector bs = new BootSector(512);
bs.read(api);
if (bs.isFat32()) {
return new Fat32(bs, api);
} else if (bs.isFat16()) {
return new Fat16(bs, api);
} else if (bs.isFat12()) {
return new Fat12(bs, api);
}
throw new FileSystemException("FAT not recognized");
}
public final BootSector getBootSector() {
return bs;
}
public final BlockDeviceAPI getApi() {
return api;
}
public final int getClusterSize() {
return getBootSector().getBytesPerSector() * getBootSector().getSectorsPerCluster();
}
public final long getFirstSector(int fatnum) {
if (fatnum < 0 || fatnum >= getBootSector().getNrFats()) {
throw new IndexOutOfBoundsException("illegal fat: " + fatnum);
}
return (long) getBootSector().getNrReservedSectors() + getBootSector().getSectorsPerFat() *
(long) fatnum;
}
public final boolean isFirstSector(int fatnum, long sector) {
return (sector == getFirstSector(fatnum));
}
public final long getLastSector(int fatnum) {
return getFirstSector(fatnum) + getBootSector().getSectorsPerFat() - 1;
}
public final boolean isLastSector(int fatnum, long sector) {
return (sector == getLastSector(fatnum));
}
public final long getFirst(int fatnum) {
return getFirstSector(fatnum) * (long) getBootSector().getBytesPerSector();
}
public final long getLast(int fatnum) {
return getLast(fatnum) + offset(size() - 1);
}
protected final long position(int fatnum, int index) throws IOException {
if (index < 0 || index >= size()) {
throw new IllegalArgumentException("illegal entry: " + index);
}
return getFirst(fatnum) + offset(index);
}
public void readCluster(int cluster, int offset, ByteBuffer dst) throws IOException {
if (offset < 0) {
throw new IllegalArgumentException("offset<0");
}
if ((offset + dst.remaining()) > getClusterSize()) {
throw new IllegalArgumentException("length[" + (offset + dst.remaining()) + "] " +
"exceed clusterSize[" + getClusterSize() + "]");
}
getApi().read(getClusterPosition(cluster) + offset, dst);
}
public void writeCluster(int cluster, int offset, ByteBuffer src) throws IOException {
if (offset < 0) {
throw new IllegalArgumentException("offset<0");
}
if ((offset + src.remaining()) > getClusterSize()) {
throw new IllegalArgumentException("length[" + (offset + src.remaining()) + "] " +
"exceed clusterSize[" + getClusterSize() + "]");
}
getApi().write(getClusterPosition(cluster) + offset, src);
}
public void clearCluster(int cluster, int start, int end) throws IOException {
if (start < 0) {
throw new IllegalArgumentException("start<0");
}
if (end < start) {
throw new IllegalArgumentException("end<start " + start + " " + end);
}
if (end > getClusterSize()) {
throw new IllegalArgumentException("end[" + end + "] " + "exceed clusterSize[" +
getClusterSize() + "]");
}
clearbuf.clear();
clearbuf.limit(end - start);
writeCluster(cluster, start, clearbuf);
}
public void clearCluster(int cluster) throws IOException {
clearCluster(cluster, 0, getClusterSize());
}
public final int firstCluster() {
return 2;
}
public final long getClusterSector(int index) {
if (index < firstCluster() || index >= size()) {
throw new IllegalArgumentException("illegal cluster # : " + index);
}
return (long) (index - firstCluster()) * (long) bs.getSectorsPerCluster() +
getBootSector().getFirstDataSector();
}
public abstract long getClusterPosition(int index);
public final int size() {
return (int) (bs.getCountOfClusters() + firstCluster());
}
protected abstract long offset(int index);
public abstract boolean isEofChain(int entry);
public abstract int eofChain();
public boolean hasNext(int entry) {
/*
* cluster 0(zero) and 1(one) are EndOfChains!
*/
if ((entry == 0) || (entry == 1)) {
return false;
}
return !isEofChain(entry);
}
public final int freeEntry() {
return 0;
}
public final boolean isFree(int entry) {
return (entry == freeEntry());
}
public long getUInt16(int index) throws IOException {
return cache.getUInt16(index);
}
public long getUInt32(int index) throws IOException {
return cache.getUInt32(index);
}
public void setInt16(int index, int element) throws IOException {
cache.setInt16(index, element);
}
public void setInt32(int index, int element) throws IOException {
cache.setInt32(index, element);
}
public abstract int get(int index) throws IOException;
public abstract int set(int index, int element) throws IOException;
public void flush() throws IOException {
cache.flush();
}
public final boolean isFreeEntry(int entry) throws IOException {
return isFree(get(entry));
}
public final int getLastFree() {
return lastfree;
}
public final void setLastFree(int value) {
lastfree = value;
}
public final void rewindFree() {
lastfree = firstCluster();
}
public final int freeEntries() throws IOException {
int count = 0;
for (int i = 0; i < size(); i++) {
if (isFreeEntry(i)) {
count++;
}
}
return count;
}
public final boolean isFat32() {
return getBootSector().isFat32();
}
public final boolean isFat16() {
return getBootSector().isFat16();
}
public final boolean isFat12() {
return getBootSector().isFat12();
}
public String getCacheStat() {
StrWriter out = new StrWriter();
out.println("Access: " + cache.getAccess() + " Hits: " + cache.getHit() + " Ratio: " +
cache.getRatio() * 100 + "%");
return out.toString();
}
public String toString() {
return String.format("FAT cluster:%d boot sector: %s", getClusterSize(), getBootSector());
}
public String toDebugString() {
StrWriter out = new StrWriter();
out.println("*************************** Fat **************************");
out.println(getBootSector());
out.println("ClusterSize\t" + getClusterSize());
out.println("Size\t\t" + size());
out.print("FirstSector");
for (int i = 0; i < getBootSector().getNrFats(); i++)
out.print("\t" + getFirstSector(i));
out.println();
out.print("LastSector");
for (int i = 0; i < getBootSector().getNrFats(); i++)
out.print("\t" + getLastSector(i));
out.println();
//out.println ( "FreeEntries\t" + freeEntries() );
out.print("*************************************************************");
return out.toString();
}
}