/*
* $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.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.NoSuchElementException;
import org.apache.log4j.Logger;
import org.jnode.fs.FileSystemFullException;
/**
* @author gvt
*
*/
public class FatChain {
private static final Logger log = Logger.getLogger(FatChain.class);
private final FatFileSystem fs;
private final Fat fat;
private int head;
private boolean dirty;
private boolean dolog = false;
private ChainPosition position;
private ChainIterator iterator;
public FatChain(FatFileSystem fs, int startEntry) {
this.fs = fs;
this.fat = fs.getFat();
this.position = new ChainPosition();
this.iterator = listIterator();
setStartCluster(startEntry);
this.dirty = false;
}
private void mylog(String msg) {
log.debug(msg);
}
public FatFileSystem getFatFileSystem() {
return fs;
}
public int getStartCluster() {
return head;
}
private void setStartCluster(int value) {
if ((value < 0) || (value > fat.size()))
throw new IllegalArgumentException("illegal head: " + value);
head = value;
iterator.reset();
position.setPosition(0);
dirty = true;
}
public boolean isDirty() {
return dirty;
}
public void flush() {
dirty = false;
}
public ChainIterator listIterator() {
return new ChainIterator();
}
public ChainIterator listIterator(int index) throws IOException {
return new ChainIterator(index);
}
private int getEndCluster() throws IOException {
int last = 0;
/*
* not cheap: we have to follow the whole chain to get the last cluster
* value
*/
for (ChainIterator i = listIterator(0); i.hasNext(); last = i.next())
;
return last;
}
public int size() throws IOException {
int count = 0;
/*
* not cheap: we have to follow the whole chain to know the chain size
*/
for (ChainIterator i = listIterator(0); i.hasNext(); i.next())
count++;
return count;
}
private int allocateTail(int n, int m, int offset, boolean zero) throws IOException {
if (n <= 0)
throw new IllegalArgumentException("n<=0");
if (m < 0)
throw new IllegalArgumentException("m<0");
if (offset < 0)
throw new IllegalArgumentException("offset<0");
if (dolog)
mylog("n[" + n + "] m[" + m + "] offset[" + offset + "]");
final int last;
int i, found = 0, l = 0;
int k = (offset > 0) ? 2 : 1;
for (i = fat.getLastFree(); i < fat.size(); i++) {
if (fat.isFreeEntry(i)) {
l = i;
found++;
}
if (found == n)
break;
}
if (found < n) {
for (i = fat.firstCluster(); i < fat.getLastFree(); i++) {
if (fat.isFreeEntry(i))
found++;
if (found == n)
break;
}
}
if (found < n)
throw new FileSystemFullException("no free clusters");
last = l;
if (dolog)
mylog("found[" + found + "] last[" + last + "]");
fat.set(last, fat.eofChain());
if (dolog)
mylog(n + "\t|allo|\t" + last + " " + fat.eofChain());
if (zero) {
if (dolog)
mylog(n + "\t|ZERO|\t" + last + " " + fat.eofChain());
fat.clearCluster(last);
}
//
found = 0;
l = last;
i = last;
//
for (; found < (n - m - k); i--) {
if (fat.isFreeEntry(i)) {
fat.set(i, l);
if (dolog)
mylog((n - found - 1) + "\t|allo|\t" + i + " " + l);
l = i;
found++;
}
}
//
if (offset > 0) {
for (;; i--) {
if (fat.isFreeEntry(i)) {
fat.clearCluster(i, 0, offset);
fat.set(i, l);
if (dolog)
mylog((n - found - 1) + "\t|part|\t" + i + " " + l);
l = i;
found++;
break;
}
}
}
//
for (; found < (n - 1); i--) {
if (fat.isFreeEntry(i)) {
fat.clearCluster(i);
fat.set(i, l);
if (dolog)
mylog((n - found - 1) + "\t|zero|\t" + i + " " + l);
l = i;
found++;
}
}
//
fat.rewindFree();
//
for (i = last; i < fat.size(); i++) {
if (fat.isFreeEntry(i)) {
fat.setLastFree(i);
break;
}
}
if (dolog)
mylog("LastFree: " + fat.getLastFree());
return l;
}
private int allocateTail(int n, int m, int offset) throws IOException {
return allocateTail(n, m, offset, false);
}
private int allocateTail(int n) throws IOException {
return allocateTail(n, 0, 0);
}
/*
* private void allocate ( int n ) throws IOException { try { int last =
* allocateTail ( n ); int first = getEndCluster();
*
* if ( dolog ) mylog ( first + ":" + last );
*
* if ( first != 0 ) fat.set ( first, last ); else { if ( dolog ) mylog (
* "allocate chain" ); setStartCluster ( last ); } } finally { fat.flush(); } }
*/
public void allocateAndClear(int n) throws IOException {
try {
int last = allocateTail(n, n - 1, 0, true);
int first = getEndCluster();
if (dolog)
mylog(first + ":" + last);
if (first != 0)
fat.set(first, last);
else {
if (dolog)
mylog("allocate chain");
setStartCluster(last);
}
} finally {
fat.flush();
}
}
public void free(int n) throws IOException {
if (n <= 0)
throw new IllegalArgumentException("n<=0");
int count = size();
if (count < n)
throw new IOException("not enough cluster: count[" + count + "] n[" + n + "]");
if (dolog)
mylog("count[" + count + "] n[" + n + "]");
ChainIterator i;
try {
if (count > n) {
i = listIterator(count - n - 1);
int l = i.next();
fat.set(l, fat.eofChain());
if (dolog)
mylog(l + ":" + fat.eofChain());
} else
i = listIterator(0);
while (i.hasNext()) {
int l = i.next();
fat.set(l, fat.freeEntry());
if (dolog)
mylog(l + ":" + fat.freeEntry());
}
} finally {
fat.flush();
}
if (count == n) {
setStartCluster(0);
if (dolog)
mylog("zero");
}
}
/*
* implemented separately for efficiency
*/
public void freeAllClusters() throws IOException {
ChainIterator i = listIterator(0);
try {
while (i.hasNext()) {
int l = i.next();
fat.set(l, fat.freeEntry());
}
} finally {
fat.flush();
}
setStartCluster(0);
}
public void read(long offset, ByteBuffer dst) throws IOException {
if (offset < 0)
throw new IllegalArgumentException("offset<0");
if (dst.remaining() == 0)
return;
ChainPosition p = position;
ChainIterator i = iterator;
p.setPosition(offset);
try {
i.setPosition(p.getIndex());
} catch (NoSuchElementException ex) {
final IOException ioe = new IOException("attempt to seek after End Of Chain " + offset);
ioe.initCause(ex);
throw ioe;
}
for (int l = dst.remaining(), sz = p.getPartial(), ofs = p.getOffset(), size; l > 0; l -=
size, sz = p.getSize(), ofs = 0) {
int cluster = i.next();
size = Math.min(sz, l);
if (dolog)
mylog("read " + size + " bytes from cluster " + cluster + " at offset " + ofs);
int limit = dst.limit();
try {
dst.limit(dst.position() + size);
fat.readCluster(cluster, ofs, dst);
} finally {
dst.limit(limit);
}
}
}
/*
* length is used to zero the last cluster allocated to a chain when this is
* required i.e. from FatFile
*
* when there is no need to zero the cluster at the end of the chain, last
* cluster, we can use any clsize multiple or zero
*/
public void write(long length, long offset, ByteBuffer src) throws IOException {
if (length < 0)
throw new IllegalArgumentException("length<0");
if (offset < 0)
throw new IllegalArgumentException("offset<0");
// ChainPosition p = new ChainPosition ( offset );
ChainPosition p = position;
p.setPosition(offset);
int clsize = p.getSize();
int clidx = p.getIndex();
// int last;
// int cluster = 0;
// ChainIterator i = listIterator ( 0 );
ChainIterator i = iterator;
int cluster = i.getCluster(clidx);
int last = i.nextIndex();
// System.out.println ( "head=" + head + " clidx=" + clidx + " cluster="
// + cluster + " last=" + last );
/*
* for ( last = 0; last < clidx; last++ ) if ( i.hasNext() ) cluster =
* i.next(); else break;
*/
try {
if (last != clidx) {
int m = clidx - last;
long lst = offset + src.remaining() - last * clsize;
int n = (int) (lst / clsize);
if ((lst % clsize) != 0)
n++;
last = allocateTail(n, m, p.getOffset());
if (cluster != 0) {
fat.set(cluster, last);
((ChainIterator) i).appendChain(last);
} else {
setStartCluster(last);
// i = listIterator ( clidx );
}
/*
* here length is used to decide if we have to zero the data
* inside the last cluster tail
*/
int ofs = (int) (length % clsize);
if (ofs != 0)
fat.clearCluster(cluster, ofs, clsize);
}
} finally {
fat.flush();
}
for (int l = src.remaining(), sz = p.getPartial(), ofs = p.getOffset(), size; l > 0; l -=
size, sz = clsize, ofs = 0) {
if (!i.hasNext()) {
int n = l / clsize;
if ((l % clsize) != 0)
n++;
try {
last = allocateTail(n);
if (cluster != 0) {
fat.set(cluster, last);
((ChainIterator) i).appendChain(last);
} else {
setStartCluster(last);
// i = listIterator ( 0 );
}
} finally {
fat.flush();
}
}
cluster = i.next();
size = Math.min(sz, l);
if (dolog)
mylog("write " + size + " bytes to cluster " + cluster + " at offset " + ofs);
int limit = src.limit();
try {
src.limit(src.position() + size);
fat.writeCluster(cluster, ofs, src);
} finally {
src.limit(limit);
}
}
}
/*
* used when we don't need to zero the data inside the last cluster tail
*/
public void write(long offset, ByteBuffer src) throws IOException {
write(0, offset, src);
}
public long getLength() throws IOException {
/*
* not cheap: we have to follow the whole chain to know the chain length
*/
return size() * fat.getClusterSize();
}
public String toString() {
StrWriter out = new StrWriter();
boolean first = true;
int prev = 0;
int last = 0;
try {
ChainIterator i = listIterator(0);
out.print("[(Start:" + head + ",Size:" + size() + ") ");
out.print("<");
while (i.hasNext()) {
int curr = i.next();
if (first) {
first = false;
out.print(curr);
last = curr;
} else if (curr != prev + 1) {
if (prev != last)
out.print("-" + prev);
out.print("> <" + curr);
last = curr;
}
prev = curr;
}
if (prev != last)
out.print("-" + prev);
out.print(">]");
} catch (IOException ex) {
log.debug("error in chain");
out.print("error in chain");
}
return out.toString();
}
/*
* dump a chain on a file: used for debugging and testing inside the
* FatChain class size() can and must be used
*/
public void dump(String fileName) throws IOException, FileNotFoundException {
int size = size();
FileOutputStream f = new FileOutputStream(fileName);
ByteBuffer buf = ByteBuffer.allocate(fat.getClusterSize());
for (int i = 0; i < size; i++) {
buf.clear();
read(i * fat.getClusterSize(), buf);
buf.flip();
f.getChannel().write(buf);
}
f.close();
}
/*
* dump a chain cluster: used for debugging and testing "inside" the
* FatChain class
*/
public void dumpCluster(String fileName, int index) throws IOException, FileNotFoundException {
FileOutputStream f = new FileOutputStream(fileName);
ByteBuffer buf = ByteBuffer.allocate(fat.getClusterSize());
buf.clear();
read(index * fat.getClusterSize(), buf);
buf.flip();
f.getChannel().write(buf);
f.close();
}
private class ChainPosition {
private long position;
private int index;
private int offset;
private final int size;
private ChainPosition() {
this(0);
}
private ChainPosition(long pos) {
this.size = fat.getClusterSize();
setPosition(pos);
}
private final int getIndex() {
return index;
}
private final int getOffset() {
return offset;
}
private final int getSize() {
return size;
}
private final int getPartial() {
return (size - offset);
}
@SuppressWarnings("unused")
private final long getPosition() {
return position;
}
private final void setPosition(long value) {
if (value < 0L || value > 0xFFFFFFFFL)
throw new IllegalArgumentException();
this.position = value;
this.index = (int) (value / size);
this.offset = (int) (value % size);
}
}
public class ChainIterator {
private int address;
private int cursor;
private int index;
private ChainIterator() {
reset();
}
private ChainIterator(int index) throws IOException {
this();
setPosition(index);
}
private void reset() {
address = head;
cursor = head;
index = 0;
}
private void setPosition(int position) throws IOException {
if (position < 0)
throw new IllegalArgumentException("negative index: " + position);
if (position > index) {
for (int i = index; i < position; i++)
next();
} else if (position < index) {
reset();
for (int i = 0; i < position; i++)
next();
}
}
private int getCluster(int position) throws IOException {
int cluster = 0;
if (position > index) {
for (int i = index; i < position; i++)
if (hasNext())
cluster = next();
else
break;
} else if (position < index) {
reset();
for (int i = 0; i < position; i++)
if (hasNext())
cluster = next();
else
break;
} else
cluster = address;
return cluster;
}
/**
* this method is used to append a new allocated chain to the current
* chain while is positioned at the end of chain
*
* it can be used if and only if cursor is an EndOfChain it will throw
* an exception otherwise
*
* chain index is not changed ... the chain remains positioned where it
* was ...
*/
private void appendChain(int startCluster) {
if (!fat.isEofChain(cursor))
throw new IllegalArgumentException("cannot append to: " + cursor);
cursor = startCluster;
}
public boolean hasNext() {
return (fat.hasNext(cursor));
}
public int next() throws IOException {
if (!hasNext())
throw new NoSuchElementException();
address = cursor;
cursor = fat.get(address);
if (cursor == address)
throw new IOException("circular chain at: " + cursor);
if (fat.isFree(cursor))
throw new IOException("free entry in chain at: " + address);
index++;
return address;
}
private boolean hasPrevious() {
return !(cursor == head);
}
/*
* Take care: this method is implemented for the sake of interface
* completness, but it is expensive ... this a "true forward only list"
* ... the previous element can be recovered only by a complete list
* scan
*
* ... peraphs an UnsupportedOperationException would be better here ...
* but who knows? ;-)
*/
@SuppressWarnings("unused")
private int previous() throws IOException {
if (!hasPrevious())
throw new NoSuchElementException();
int prev = index - 1;
reset();
for (int i = 0; i < prev; i++)
next();
return cursor;
}
private int nextIndex() {
return index;
}
@SuppressWarnings("unused")
private int previousIndex() {
return (index - 1);
}
}
}