diff -r 859e487abe5f main/java/org/apache/commons/compress/compressors/bzip2/BZip2BlockListener.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main/java/org/apache/commons/compress/compressors/bzip2/BZip2BlockListener.java	Sun Nov 18 19:54:52 2012 +0100
@@ -0,0 +1,7 @@
+package org.apache.commons.compress.compressors.bzip2;
+
+import org.apache.commons.compress.compressors.CompressorInputStream;
+
+public interface BZip2BlockListener {
+    public void newBlock(CompressorInputStream in, long blockNo, long blockPositionInBits);
+}
diff -r 859e487abe5f src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStream.java
--- a/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStream.java	Sun Nov 18 19:54:19 2012 +0100
+++ b/src/main/java/org/apache/commons/compress/compressors/bzip2/BZip2CompressorInputStream.java	Sun Nov 18 19:54:52 2012 +0100
@@ -26,6 +26,8 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 import org.apache.commons.compress.compressors.CompressorInputStream;
 
@@ -55,7 +57,9 @@
 
     private boolean blockRandomised;
 
+    /** 32 bit buffer for read */
     private int bsBuff;
+    /** number of bits remaining in bsBuff buffer */
     private int bsLive;
     private final CRC crc = new CRC();
 
@@ -97,6 +101,12 @@
      */
     private BZip2CompressorInputStream.Data data;
 
+    private long currBlockNo;
+    private long currInPosition;
+    private long currBlockPosition;
+
+    private List<BZip2BlockListener> listener = new CopyOnWriteArrayList<BZip2BlockListener>();
+
     /**
      * Constructs a new BZip2CompressorInputStream which decompresses bytes
      * read from the specified stream. This doesn't suppprt decompressing
@@ -111,6 +121,22 @@
         this(in, false);
     }
 
+    public BZip2CompressorInputStream(InputStream in, boolean decompressConcatenated) throws IOException {
+        this(in,decompressConcatenated,null);
+    }
+
+    public void addListener(BZip2BlockListener listener) {
+        if(listener == null)
+            throw new IllegalArgumentException();
+        this.listener.add(listener);
+    }
+
+    public void removeListener(BZip2BlockListener listener) {
+        if(listener == null)
+            throw new IllegalArgumentException();
+        this.listener.remove(listener);
+    }
+
     /**
      * Constructs a new BZip2CompressorInputStream which decompresses bytes
      * read from the specified stream.
@@ -127,17 +153,18 @@
      * @throws NullPointerException
      *             if <tt>in == null</tt>
      */
-    public BZip2CompressorInputStream(final InputStream in,
-                                      final boolean decompressConcatenated)
+    public BZip2CompressorInputStream(InputStream in,
+                                      boolean decompressConcatenated,
+                                      BZip2BlockListener listener)
             throws IOException {
         super();
 
         this.in = in;
         this.decompressConcatenated = decompressConcatenated;
+        if(listener != null)
+            this.listener.add(listener);
 
         init(true);
-        initBlock();
-        setupBlock();
     }
 
     /** {@inheritDoc} */
@@ -176,12 +203,17 @@
 
         final int hi = offs + len;
         int destOffs = offs;
-        for (int b; (destOffs < hi) && ((b = read0()) >= 0);) {
-            dest[destOffs++] = (byte) b;
+        int c = 0;
+        for (int b; destOffs < hi;) {
+            b = read0();
+            if(b >= 0) {
+                count(1);
+                c++;
+                dest[destOffs++] = (byte) b;
+            } else
+                return b;
         }
 
-        int c = (destOffs == offs) ? -1 : (destOffs - offs);
-        count(c);
         return c;
     }
 
@@ -201,14 +233,17 @@
     }
 
     private int read0() throws IOException {
-        final int retChar = this.currentChar;
+        int retChar = this.currentChar;
 
         switch (this.currentState) {
         case EOF:
             return -1;
 
         case START_BLOCK_STATE:
-            throw new IllegalStateException();
+            initBlock();
+            setupBlock();
+            retChar = this.currentChar;
+            break;
 
         case RAND_PART_A_STATE:
             throw new IllegalStateException();
@@ -239,17 +274,24 @@
         return retChar;
     }
 
+    private int readNextByte(InputStream in) throws IOException {
+        int b = in.read();
+        if(b >= 0)
+            currInPosition += 8;
+        return b;
+    }
+
     private boolean init(boolean isFirstStream) throws IOException {
         if (null == in) {
             throw new IOException("No InputStream");
         }
 
-        int magic0 = this.in.read();
+        int magic0 = readNextByte(this.in);
         if (magic0 == -1 && !isFirstStream) {
             return false;
         }
-        int magic1 = this.in.read();
-        int magic2 = this.in.read();
+        int magic1 = readNextByte(this.in);
+        int magic2 = readNextByte(this.in);
 
         if (magic0 != 'B' || magic1 != 'Z' || magic2 != 'h') {
             throw new IOException(isFirstStream
@@ -257,7 +299,7 @@
                     : "Garbage after a valid BZip2 stream");
         }
 
-        int blockSize = this.in.read();
+        int blockSize = readNextByte(this.in);
         if ((blockSize < '1') || (blockSize > '9')) {
             throw new IOException("BZip2 block size is invalid");
         }
@@ -311,6 +353,7 @@
             this.currentState = EOF;
             throw new IOException("bad block header");
         } else {
+            notifyNewBlock();
             this.storedBlockCRC = bsGetInt();
             this.blockRandomised = bsR(1) == 1;
 
@@ -322,7 +365,6 @@
                 this.data = new Data(this.blockSize100k);
             }
 
-            // currBlockNo++;
             getAndMoveToFrontDecode();
 
             this.crc.initialiseCRC();
@@ -330,6 +372,14 @@
         }
     }
 
+    private void notifyNewBlock() {
+        currBlockNo++;
+        currBlockPosition = currInPosition - 48 - bsLive;
+        for(BZip2BlockListener b: listener) {
+            b.newBlock(this, currBlockNo, currBlockPosition);
+        }
+    }
+
     private void endBlock() throws IOException {
         this.computedBlockCRC = this.crc.getFinalCRC();
 
@@ -385,7 +435,7 @@
         if (bsLiveShadow < n) {
             final InputStream inShadow = this.in;
             do {
-                int thech = inShadow.read();
+                int thech = readNextByte(inShadow);
 
                 if (thech < 0) {
                     throw new IOException("unexpected end of stream");
@@ -403,23 +453,7 @@
     }
 
     private boolean bsGetBit() throws IOException {
-        int bsLiveShadow = this.bsLive;
-        int bsBuffShadow = this.bsBuff;
-
-        if (bsLiveShadow < 1) {
-            int thech = this.in.read();
-
-            if (thech < 0) {
-                throw new IOException("unexpected end of stream");
-            }
-
-            bsBuffShadow = (bsBuffShadow << 8) | thech;
-            bsLiveShadow += 8;
-            this.bsBuff = bsBuffShadow;
-        }
-
-        this.bsLive = bsLiveShadow - 1;
-        return ((bsBuffShadow >> (bsLiveShadow - 1)) & 1) != 0;
+        return bsR(1) != 0;
     }
 
     private char bsGetUByte() throws IOException {
@@ -652,7 +686,7 @@
                     // Inlined:
                     // int zvec = bsR(zn);
                     while (bsLiveShadow < zn) {
-                        final int thech = inShadow.read();
+                        final int thech = readNextByte(inShadow);
                         if (thech >= 0) {
                             bsBuffShadow = (bsBuffShadow << 8) | thech;
                             bsLiveShadow += 8;
@@ -668,7 +702,7 @@
                     while (zvec > limit_zt[zn]) {
                         zn++;
                         while (bsLiveShadow < 1) {
-                            final int thech = inShadow.read();
+                            final int thech = readNextByte(inShadow);
                             if (thech >= 0) {
                                 bsBuffShadow = (bsBuffShadow << 8) | thech;
                                 bsLiveShadow += 8;
@@ -735,7 +769,7 @@
                 // Inlined:
                 // int zvec = bsR(zn);
                 while (bsLiveShadow < zn) {
-                    final int thech = inShadow.read();
+                    final int thech = readNextByte(inShadow);
                     if (thech >= 0) {
                         bsBuffShadow = (bsBuffShadow << 8) | thech;
                         bsLiveShadow += 8;
@@ -751,7 +785,7 @@
                 while (zvec > limit_zt[zn]) {
                     zn++;
                     while (bsLiveShadow < 1) {
-                        final int thech = inShadow.read();
+                        final int thech = readNextByte(inShadow);
                         if (thech >= 0) {
                             bsBuffShadow = (bsBuffShadow << 8) | thech;
                             bsLiveShadow += 8;
@@ -785,7 +819,7 @@
         while (zvec > limit_zt[zn]) {
             zn++;
             while (bsLiveShadow < 1) {
-                final int thech = inShadow.read();
+                final int thech = readNextByte(inShadow);
 
                 if (thech >= 0) {
                     bsBuffShadow = (bsBuffShadow << 8) | thech;
@@ -1050,4 +1084,14 @@
 
         return true;
     }
+
+    /** reset current state to START_BLOCK and consume x additional bits */
+    public void resetBlock(byte bits) {
+        bsLive = 0;
+        bsBuff = 0;
+        try {
+            bsR(bits);
+        } catch (IOException e) {}
+        currentState = START_BLOCK_STATE;
+    }
 }
