From: Nick Burch Date: Thu, 23 Dec 2010 08:02:50 +0000 (+0000) Subject: Add NPOIFSFileSystem support for identifying free blocks, along with partial unit... X-Git-Tag: REL_3_8_BETA1~77 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=9605338d1f87a38d8721d7195b83616253f6db31;p=poi.git Add NPOIFSFileSystem support for identifying free blocks, along with partial unit tests for it git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1052194 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/java/org/apache/poi/poifs/filesystem/NPOIFSFileSystem.java b/src/java/org/apache/poi/poifs/filesystem/NPOIFSFileSystem.java index 9a3aed8c9f..d4fb341037 100644 --- a/src/java/org/apache/poi/poifs/filesystem/NPOIFSFileSystem.java +++ b/src/java/org/apache/poi/poifs/filesystem/NPOIFSFileSystem.java @@ -272,7 +272,9 @@ public class NPOIFSFileSystem for(int fatAt : _header.getBATArray()) { loopDetector.claim(fatAt); ByteBuffer fatData = getBlockAt(fatAt); - _bat_blocks.add(BATBlock.createBATBlock(bigBlockSize, fatData)); + BATBlock bat = BATBlock.createBATBlock(bigBlockSize, fatData); + bat.setOurBlockIndex(fatAt); + _bat_blocks.add(bat); } // Now read the XFAT blocks @@ -282,6 +284,7 @@ public class NPOIFSFileSystem loopDetector.claim(nextAt); ByteBuffer fatData = getBlockAt(nextAt); xfat = BATBlock.createBATBlock(bigBlockSize, fatData); + xfat.setOurBlockIndex(nextAt); nextAt = xfat.getValueAt(bigBlockSize.getNextXBATChainOffset()); _bat_blocks.add(xfat); @@ -296,8 +299,6 @@ public class NPOIFSFileSystem * Load the block at the given offset. */ protected ByteBuffer getBlockAt(final int offset) throws IOException { - ByteBuffer data = ByteBuffer.allocate(bigBlockSize.getBigBlockSize()); - // The header block doesn't count, so add one long startAt = (offset+1) * bigBlockSize.getBigBlockSize(); return _data.read(bigBlockSize.getBigBlockSize(), startAt); @@ -336,9 +337,79 @@ public class NPOIFSFileSystem * This method will extend the file if needed, and if doing * so, allocate new FAT blocks to address the extra space. */ - protected int getFreeBlock() { - // TODO - return -1; + protected int getFreeBlock() throws IOException { + // First up, do we have any spare ones? + int offset = 0; + for(int i=0; i<_bat_blocks.size(); i++) { + boolean isXBAT = (i >= _header.getBATCount()); + + int numSectors = bigBlockSize.getBATEntriesPerBlock(); + if(isXBAT) { + numSectors = bigBlockSize.getXBATEntriesPerBlock(); + } + + // Check this one + BATBlock bat = _bat_blocks.get(i); + if(bat.hasFreeSectors()) { + // Claim one of them and return it + for(int j=0; j= 109) { + isBAT = false; + } + + // Create a new BATBlock + BATBlock newBAT = BATBlock.createEmptyBATBlock(bigBlockSize, !isBAT); + newBAT.setOurBlockIndex(offset); + // Ensure there's a spot in the file for it + ByteBuffer buffer = ByteBuffer.allocate(bigBlockSize.getBigBlockSize()); + int writeTo = (1+offset) * bigBlockSize.getBigBlockSize(); // Header isn't in BATs + _data.write(buffer, writeTo); + + // Allocate ourself within ourselves, at the first point + if(isBAT) { + newBAT.setValueAt(0, POIFSConstants.FAT_SECTOR_BLOCK); + } else { + newBAT.setValueAt(0, POIFSConstants.DIFAT_SECTOR_BLOCK); + } + + // Store us + _bat_blocks.add(newBAT); + if(isBAT) { + // Put it in the BAT array in the header + int[] newBATs = new int[_header.getBATCount()+1]; + System.arraycopy(_header.getBATArray(), 0, newBATs, 0, newBATs.length-1); + newBATs[newBATs.length-1] = offset; + _header.setBATArray(newBATs); + _header.setBATCount(newBATs.length); + } else if(_header.getXBATCount() == 0) { + // Store our first XBAT offset in the header + _header.setXBATStart(offset); + _header.setXBATCount(1); + } else { + // Chain it off the last XBAT + BATBlock lastXBAT = _bat_blocks.get(_bat_blocks.size()-1); + lastXBAT.setValueAt(bigBlockSize.getNextXBATChainOffset(), offset); + _header.setXBATCount(_header.getXBATCount()+1); + } + + // The first offset stores us, but the 2nd is free + return offset+1; } /** diff --git a/src/java/org/apache/poi/poifs/filesystem/NPOIFSStream.java b/src/java/org/apache/poi/poifs/filesystem/NPOIFSStream.java index 1739532631..edffb6b45a 100644 --- a/src/java/org/apache/poi/poifs/filesystem/NPOIFSStream.java +++ b/src/java/org/apache/poi/poifs/filesystem/NPOIFSStream.java @@ -40,7 +40,7 @@ import org.apache.poi.poifs.storage.HeaderBlock; * handle small block ones. * This uses the new NIO code * - * TODO Add loop checking on read and on write + * TODO Implement a streaming write method */ public class NPOIFSStream implements Iterable @@ -137,13 +137,18 @@ public class NPOIFSStream implements Iterable } // If we're overwriting, free any remaining blocks - // TODO + while(nextBlock != POIFSConstants.END_OF_CHAIN) { + int thisBlock = nextBlock; + loopDetector.claim(thisBlock); + nextBlock = filesystem.getNextBlock(thisBlock); + filesystem.setNextBlock(thisBlock, POIFSConstants.UNUSED_BLOCK); + } // Mark the end of the stream filesystem.setNextBlock(nextBlock, POIFSConstants.END_OF_CHAIN); } - // TODO Streaming write too + // TODO Streaming write support too /** * Class that handles a streaming read of one stream diff --git a/src/java/org/apache/poi/poifs/storage/BATBlock.java b/src/java/org/apache/poi/poifs/storage/BATBlock.java index 3c4bb68a46..636a46f2be 100644 --- a/src/java/org/apache/poi/poifs/storage/BATBlock.java +++ b/src/java/org/apache/poi/poifs/storage/BATBlock.java @@ -47,10 +47,14 @@ public final class BATBlock extends BigBlock { */ private boolean _has_free_sectors; + /** + * Where in the file are we? + */ + private int ourBlockIndex; + /** * Create a single instance initialized with default values */ - private BATBlock(POIFSBigBlockSize bigBlockSize) { super(bigBlockSize); @@ -118,6 +122,17 @@ public final class BATBlock extends BigBlock { // All done return block; } + + /** + * Creates a single BATBlock, with all the values set to empty. + */ + public static BATBlock createEmptyBATBlock(final POIFSBigBlockSize bigBlockSize, boolean isXBAT) { + BATBlock block = new BATBlock(bigBlockSize); + if(isXBAT) { + block.setXBATChain(bigBlockSize, POIFSConstants.END_OF_CHAIN); + } + return block; + } /** * Create an array of BATBlocks from an array of int block @@ -295,10 +310,24 @@ public final class BATBlock extends BigBlock { recomputeFree(); } } + + /** + * Record where in the file we live + */ + public void setOurBlockIndex(int index) { + this.ourBlockIndex = index; + } + /** + * Retrieve where in the file we live + */ + public int getOurBlockIndex() { + return ourBlockIndex; + } + /* ********** START extension of BigBlock ********** */ - /** + /** * Write the block's data to an OutputStream * * @param stream the OutputStream to which the stored data should diff --git a/src/testcases/org/apache/poi/poifs/filesystem/TestNPOIFSFileSystem.java b/src/testcases/org/apache/poi/poifs/filesystem/TestNPOIFSFileSystem.java index c511df3741..828315e3db 100644 --- a/src/testcases/org/apache/poi/poifs/filesystem/TestNPOIFSFileSystem.java +++ b/src/testcases/org/apache/poi/poifs/filesystem/TestNPOIFSFileSystem.java @@ -146,4 +146,84 @@ public final class TestNPOIFSFileSystem extends TestCase { assertEquals((byte)0x00, b.get()); assertEquals((byte)0x00, b.get()); } + + /** + * Ask for free blocks where there are some already + * to be had from the FAT + */ + public void testGetFreeBlockWithSpare() throws Exception { + NPOIFSFileSystem fs = new NPOIFSFileSystem(_inst.getFile("BlockSize512.zvi")); + + // Our first BAT block has spares + assertEquals(true, fs.getBATBlockAndIndex(0).getBlock().hasFreeSectors()); + + // First free one is 100 + assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(100)); + assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(101)); + assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(102)); + assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(103)); + + // Ask, will get 100 + assertEquals(100, fs.getFreeBlock()); + + // Ask again, will still get 100 as not written to + assertEquals(100, fs.getFreeBlock()); + + // Allocate it, then ask again + fs.setNextBlock(100, POIFSConstants.END_OF_CHAIN); + assertEquals(101, fs.getFreeBlock()); + } + + /** + * Ask for free blocks where no free ones exist, and so the + * file needs to be extended and another BAT/XBAT added + */ + public void testGetFreeBlockWithNoneSpare() throws Exception { + NPOIFSFileSystem fs = new NPOIFSFileSystem(_inst.openResourceAsStream("BlockSize512.zvi")); + + // We've spare ones from 100 to 128 + for(int i=100; i<128; i++) { + assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(i)); + } + + // Check our BAT knows it's free + assertEquals(true, fs.getBATBlockAndIndex(0).getBlock().hasFreeSectors()); + + // Allocate all the spare ones + for(int i=100; i<128; i++) { + fs.setNextBlock(i, POIFSConstants.END_OF_CHAIN); + } + + // BAT is now full, but there's only the one + assertEquals(false, fs.getBATBlockAndIndex(0).getBlock().hasFreeSectors()); + try { + assertEquals(false, fs.getBATBlockAndIndex(128).getBlock().hasFreeSectors()); + fail("Should only be one BAT"); + } catch(IndexOutOfBoundsException e) {} + + // Now ask for a free one, will need to extend the file + assertEquals(129, fs.getFreeBlock()); + + assertEquals(false, fs.getBATBlockAndIndex(0).getBlock().hasFreeSectors()); + assertEquals(true, fs.getBATBlockAndIndex(128).getBlock().hasFreeSectors()); + assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs.getNextBlock(128)); + assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(129)); + + + // Fill up to hold 109 BAT blocks + // TODO + + // Ask for another, will get our first XBAT + // TODO + + // Fill the XBAT + // TODO + + // Ask for another, will get our 2nd XBAT + // TODO + + // Write it out and read it back in again + // Ensure it's correct + // TODO + } } diff --git a/src/testcases/org/apache/poi/poifs/filesystem/TestNPOIFSStream.java b/src/testcases/org/apache/poi/poifs/filesystem/TestNPOIFSStream.java index ef792e768a..4ace3f1aa9 100644 --- a/src/testcases/org/apache/poi/poifs/filesystem/TestNPOIFSStream.java +++ b/src/testcases/org/apache/poi/poifs/filesystem/TestNPOIFSStream.java @@ -26,6 +26,8 @@ import org.apache.poi.POIDataSamples; /** * Tests {@link NPOIFSStream} + * + * TODO Write unit tests */ public final class TestNPOIFSStream extends TestCase { private static final POIDataSamples _inst = POIDataSamples.getPOIFSInstance(); @@ -211,7 +213,38 @@ public final class TestNPOIFSStream extends TestCase { * Craft a nasty file with a loop, and ensure we don't get stuck */ public void testReadFailsOnLoop() throws Exception { - // TODO + NPOIFSFileSystem fs = new NPOIFSFileSystem(_inst.getFile("BlockSize512.zvi")); + + // Hack the FAT so that it goes 0->1->2->0 + fs.setNextBlock(0, 1); + fs.setNextBlock(1, 2); + fs.setNextBlock(2, 0); + + // Now try to read + NPOIFSStream stream = new NPOIFSStream(fs, 0); + Iterator i = stream.getBlockIterator(); + assertEquals(true, i.hasNext()); + + // 1st read works + i.next(); + assertEquals(true, i.hasNext()); + + // 2nd read works + i.next(); + assertEquals(true, i.hasNext()); + + // 3rd read works + i.next(); + assertEquals(true, i.hasNext()); + + // 4th read blows up as it loops back to 0 + try { + i.next(); + fail("Loop should have been detected but wasn't!"); + } catch(RuntimeException e) { + // Good, it was detected + } + assertEquals(true, i.hasNext()); } /**