View Javadoc
1   /*
2    *   Licensed to the Apache Software Foundation (ASF) under one
3    *   or more contributor license agreements.  See the NOTICE file
4    *   distributed with this work for additional information
5    *   regarding copyright ownership.  The ASF licenses this file
6    *   to you under the Apache License, Version 2.0 (the
7    *   "License"); you may not use this file except in compliance
8    *   with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *   Unless required by applicable law or agreed to in writing,
13   *   software distributed under the License is distributed on an
14   *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *   KIND, either express or implied.  See the License for the
16   *   specific language governing permissions and limitations
17   *   under the License.
18   *
19   */
20  package org.apache.directory.mavibot.btree;
21  
22  
23  import java.io.BufferedReader;
24  import java.io.File;
25  import java.io.IOException;
26  import java.io.InputStreamReader;
27  import java.io.RandomAccessFile;
28  import java.nio.BufferUnderflowException;
29  import java.nio.ByteBuffer;
30  import java.nio.channels.FileChannel;
31  import java.util.ArrayList;
32  import java.util.HashMap;
33  import java.util.HashSet;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.Set;
37  
38  import org.apache.directory.mavibot.btree.exception.InvalidBTreeException;
39  import org.apache.directory.mavibot.btree.serializer.ElementSerializer;
40  import org.apache.directory.mavibot.btree.serializer.LongSerializer;
41  import org.apache.directory.mavibot.btree.serializer.StringSerializer;
42  import org.apache.directory.mavibot.btree.util.Strings;
43  
44  
45  /**
46   * A class to examine a Mavibot database file.
47   *
48   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
49   */
50  public class MavibotInspector
51  {
52      // The file to be read
53      private File dbFile;
54  
55      // The recordManager
56      private static RecordManager rm;
57  
58      private BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) );
59  
60      // The name of the two page arrays for the global file and teh free pages
61      private static final String GLOBAL_PAGES_NAME = "__global__";
62      private static final String FREE_PAGES_NAME = "__free-pages__";
63  
64      // The set of page array we already know about
65      private static Set<String> knownPagesArrays = new HashSet<String>();
66  
67      // Create an array of pages to be checked for each B-tree, plus
68      // two others for the free pages and the global one
69      // We use one bit per page. It's 0 when the page
70      // hasn't been checked, 1 otherwise.
71      private static Map<String, int[]> checkedPages = new HashMap<String, int[]>();
72  
73      static
74      {
75          knownPagesArrays.add( GLOBAL_PAGES_NAME );
76          knownPagesArrays.add( FREE_PAGES_NAME );
77          knownPagesArrays.add( RecordManager.BTREE_OF_BTREES_NAME );
78          knownPagesArrays.add( RecordManager.COPIED_PAGE_BTREE_NAME );
79      }
80  
81  
82      /**
83       * A private class to store a few informations about a btree
84       *
85      
86      private static BtreeInfo btreeInfo;
87      
88      static
89      {
90          btreeInfo = new BtreeInfo();
91      }
92  
93      /**
94       * Create an instance of MavibotInspector
95       * @param dbFile The file to read
96       */
97      public MavibotInspector( File dbFile )
98      {
99          this.dbFile = dbFile;
100     }
101 
102 
103     /**
104      * Check that the file exists
105      */
106     private boolean checkFilePresence()
107     {
108         if ( dbFile == null )
109         {
110             System.out.println( "No mavibot database file was given" );
111             return false;
112         }
113 
114         if ( !dbFile.exists() )
115         {
116             System.out.println( "Given mavibot database file " + dbFile + " doesn't exist" );
117             return false;
118         }
119 
120         return true;
121     }
122 
123 
124     /**
125      * Pretty print the file size
126      */
127     public void printFileSize() throws IOException
128     {
129         FileChannel fileChannel = new RandomAccessFile( dbFile, "r" ).getChannel();
130 
131         long l = fileChannel.size();
132 
133         fileChannel.close();
134 
135         String msg;
136 
137         if ( l < 1024 )
138         {
139             msg = l + " bytes";
140         }
141         else
142         {
143             msg = ( l / 1024 ) + " KB";
144         }
145 
146         System.out.println( msg );
147 
148         fileChannel.close();
149     }
150 
151 
152     /**
153      * Print the number of B-trees
154      */
155     public void printNumberOfBTrees()
156     {
157         int nbBtrees = 0;
158 
159         if ( rm != null )
160         {
161             nbBtrees = rm.getNbManagedTrees();
162         }
163 
164         // The number of trees. It must be at least 2 and > 0
165         System.out.println( "Total Number of BTrees: " + nbBtrees );
166     }
167 
168 
169     /**
170      * Print the B-tree's name
171      */
172     public void printBTreeNames()
173     {
174         if ( rm == null )
175         {
176             System.out.println( "Couldn't find the number of managed btrees" );
177             return;
178         }
179 
180         Set<String> trees = rm.getManagedTrees();
181         System.out.println( "\nManaged BTrees:" );
182 
183         for ( String tree : trees )
184         {
185             System.out.println( tree );
186         }
187 
188         System.out.println();
189     }
190 
191 
192     /**
193      * Check a B-tree
194      */
195     public void inspectBTree()
196     {
197         if ( rm == null )
198         {
199             System.out.println( "Cannot check BTree(s)" );
200             return;
201         }
202 
203         System.out.print( "BTree Name: " );
204         String name = readLine();
205 
206         PersistedBTree<?, ?> pb = ( PersistedBTree<?, ?> ) rm.getManagedTree( name );
207 
208         if ( pb == null )
209         {
210             System.out.println( "No BTree exists with the name '" + name + "'" );
211             return;
212         }
213 
214         System.out.println( "\nBTree offset: " + String.format( "0x%1$08x", pb.getBtreeOffset() ) );
215         System.out.println( "BTree _info_ offset: " + String.format( "0x%1$08x", pb.getBtreeInfoOffset() ) );
216         System.out.println( "BTree root page offset: " + String.format( "0x%1$08x", pb.getRootPageOffset() ) );
217         System.out.println( "Number of elements present: " + pb.getNbElems() );
218         System.out.println( "BTree Page size: " + pb.getPageSize() );
219         System.out.println( "BTree revision: " + pb.getRevision() );
220         System.out.println( "Key serializer: " + pb.getKeySerializerFQCN() );
221         System.out.println( "Value serializer: " + pb.getValueSerializerFQCN() );
222         System.out.println();
223     }
224 
225 
226     /**
227      * Load the full fie into a new RecordManager
228      */
229     private boolean loadRm()
230     {
231         try
232         {
233             if ( rm != null )
234             {
235                 System.out.println( "Closing record manager" );
236                 rm.close();
237             }
238 
239             rm = new RecordManager( dbFile.getAbsolutePath() );
240             System.out.println( "Loaded record manager" );
241         }
242         catch ( Exception e )
243         {
244             System.out.println( "Given database file seems to be corrupted. " + e.getMessage() );
245             return false;
246         }
247 
248         return true;
249     }
250 
251 
252     /**
253      * Check the whole file
254      */
255     /* no qualifier */static void check( RecordManager recordManager )
256     {
257         try
258         {
259             rm = recordManager;
260             
261             // First check the RMheader
262             ByteBuffer recordManagerHeader = ByteBuffer.allocate( RecordManager.RECORD_MANAGER_HEADER_SIZE );
263             long fileSize = recordManager.fileChannel.size();
264 
265             if ( fileSize < RecordManager.RECORD_MANAGER_HEADER_SIZE )
266             {
267                 throw new InvalidBTreeException( "File size too small : " + fileSize );
268             }
269 
270             // Read the RMHeader
271             recordManager.fileChannel.read( recordManagerHeader, 0L );
272             recordManagerHeader.flip();
273 
274             // The page size. It must be a power of 2, and above 16.
275             int pageSize = recordManagerHeader.getInt();
276 
277             if ( ( pageSize != recordManager.pageSize ) || ( pageSize < 32 )
278                 || ( ( pageSize & ( ~pageSize + 1 ) ) != pageSize ) )
279             {
280                 throw new InvalidBTreeException( "Wrong page size : " + pageSize );
281             }
282 
283             // Compute the number of pages in this file
284             long nbPages = ( fileSize - RecordManager.RECORD_MANAGER_HEADER_SIZE ) / pageSize;
285 
286             // The number of trees. It must be at least >= 2
287             int nbBtrees = recordManagerHeader.getInt();
288 
289             if ( ( nbBtrees < 0 ) || ( nbBtrees != recordManager.nbBtree ) )
290             {
291                 throw new InvalidBTreeException( "Wrong nb trees : " + nbBtrees );
292             }
293 
294             // The first free page offset. It must be either -1 or below file size
295             // and its value must be a modulo of pageSize
296             long firstFreePage = recordManagerHeader.getLong();
297 
298             if ( firstFreePage != RecordManager.NO_PAGE )
299             {
300                 checkOffset( recordManager, firstFreePage );
301             }
302 
303             int nbPageBits = ( int ) ( nbPages / 32 );
304 
305             // The global page array
306             checkedPages.put( GLOBAL_PAGES_NAME, new int[nbPageBits + 1] );
307 
308             // The freePages array
309             checkedPages.put( FREE_PAGES_NAME, new int[nbPageBits + 1] );
310 
311             // The B-tree of B-trees array
312             checkedPages.put( RecordManager.BTREE_OF_BTREES_NAME, new int[nbPageBits + 1] );
313 
314             // Last, the Copied Pages B-tree array
315             checkedPages.put( RecordManager.COPIED_PAGE_BTREE_NAME, new int[nbPageBits + 1] );
316 
317             // Check the free files
318             checkFreePages( recordManager, checkedPages );
319 
320             // The B-trees offsets
321             long currentBtreeOfBtreesOffset = recordManagerHeader.getLong();
322             long previousBtreeOfBtreesOffset = recordManagerHeader.getLong();
323             long currentCopiedPagesBtreeOffset = recordManagerHeader.getLong();
324             long previousCopiedPagesBtreeOffset = recordManagerHeader.getLong();
325 
326             // Check that the previous BOB offset is not pointing to something
327             if ( previousBtreeOfBtreesOffset != RecordManager.NO_PAGE )
328             {
329                 System.out.println( "The previous Btree of Btrees offset is not valid : "
330                     + previousBtreeOfBtreesOffset );
331                 return;
332             }
333 
334             // Check that the previous CPB offset is not pointing to something
335             if ( previousCopiedPagesBtreeOffset != RecordManager.NO_PAGE )
336             {
337                 System.out.println( "The previous Copied Pages Btree offset is not valid : "
338                     + previousCopiedPagesBtreeOffset );
339                 return;
340             }
341 
342             // Check that the current BOB offset is valid
343             checkOffset( recordManager, currentBtreeOfBtreesOffset );
344 
345             // Check that the current CPB offset is valid
346             checkOffset( recordManager, currentCopiedPagesBtreeOffset );
347 
348             // Now, check the BTree of Btrees
349             checkBtreeOfBtrees( recordManager, checkedPages );
350 
351             // And the Copied Pages BTree
352             checkBtree( recordManager, currentCopiedPagesBtreeOffset, checkedPages );
353 
354             // We can now dump the checked pages
355             dumpCheckedPages( recordManager, checkedPages );
356         }
357         catch ( Exception e )
358         {
359             // We catch the exception and rethrow it immediately to be able to
360             // put a breakpoint here
361             e.printStackTrace();
362             throw new InvalidBTreeException( "Error : " + e.getMessage() );
363         }
364     }
365 
366 
367     /**
368      * Check the Btree of Btrees
369      */
370     private static <K, V> void checkBtreeOfBtrees( RecordManager recordManager, Map<String, int[]> checkedPages )
371         throws Exception
372     {
373         // Read the BOB header
374         PageIO[] bobHeaderPageIos = recordManager
375             .readPageIOs( recordManager.currentBtreeOfBtreesOffset, Long.MAX_VALUE );
376 
377         // update the checkedPages
378         updateCheckedPages( checkedPages.get( RecordManager.BTREE_OF_BTREES_NAME ), recordManager.pageSize,
379             bobHeaderPageIos );
380         updateCheckedPages( checkedPages.get( GLOBAL_PAGES_NAME ), recordManager.pageSize, bobHeaderPageIos );
381 
382         long dataPos = 0L;
383 
384         // The B-tree current revision
385         recordManager.readLong( bobHeaderPageIos, dataPos );
386         dataPos += RecordManager.LONG_SIZE;
387 
388         // The nb elems in the tree
389         recordManager.readLong( bobHeaderPageIos, dataPos );
390         dataPos += RecordManager.LONG_SIZE;
391 
392         // The B-tree rootPage offset
393         long rootPageOffset = recordManager.readLong( bobHeaderPageIos, dataPos );
394 
395         checkOffset( recordManager, rootPageOffset );
396 
397         dataPos += RecordManager.LONG_SIZE;
398 
399         // The B-tree info offset
400         long btreeInfoOffset = recordManager.readLong( bobHeaderPageIos, dataPos );
401 
402         checkOffset( recordManager, btreeInfoOffset );
403 
404         checkBtreeInfo( recordManager, checkedPages, btreeInfoOffset, -1L );
405 
406         // Check the elements in the btree itself
407         // We will read every single page
408         checkBtreeOfBtreesPage( recordManager, checkedPages, rootPageOffset );
409     }
410 
411 
412     /**
413      * Check a user's B-tree
414      */
415     private static <K, V> void checkBtree( RecordManager recordManager, long btreeOffset,
416         Map<String, int[]> checkedPages ) throws Exception
417     {
418         // Read the B-tree header
419         PageIO[] btreeHeaderPageIos = recordManager.readPageIOs( btreeOffset, Long.MAX_VALUE );
420 
421         long dataPos = 0L;
422 
423         // The B-tree current revision
424         long btreeRevision = recordManager.readLong( btreeHeaderPageIos, dataPos );
425         dataPos += RecordManager.LONG_SIZE;
426 
427         // The nb elems in the tree
428         recordManager.readLong( btreeHeaderPageIos, dataPos );
429         dataPos += RecordManager.LONG_SIZE;
430 
431         // The B-tree rootPage offset
432         long rootPageOffset = recordManager.readLong( btreeHeaderPageIos, dataPos );
433 
434         checkOffset( recordManager, rootPageOffset );
435 
436         dataPos += RecordManager.LONG_SIZE;
437 
438         // The B-tree info offset
439         long btreeInfoOffset = recordManager.readLong( btreeHeaderPageIos, dataPos );
440 
441         checkOffset( recordManager, btreeInfoOffset );
442 
443         BtreeInfo<K, V> btreeInfo = checkBtreeInfo( recordManager, checkedPages, btreeInfoOffset, btreeRevision );
444 
445         // Update the checked pages
446         updateCheckedPages( checkedPages.get( btreeInfo.btreeName ), recordManager.pageSize, btreeHeaderPageIos );
447         updateCheckedPages( checkedPages.get( GLOBAL_PAGES_NAME ), recordManager.pageSize, btreeHeaderPageIos );
448 
449         // And now, process the rootPage
450         checkBtreePage( recordManager, btreeInfo, checkedPages, rootPageOffset );
451     }
452 
453 
454     /**
455      * Check the Btree of Btrees rootPage
456      */
457     private static <K, V> void checkBtreePage( RecordManager recordManager, BtreeInfo<K, V> btreeInfo,
458         Map<String, int[]> checkedPages, long pageOffset ) throws Exception
459     {
460         PageIO[] pageIos = recordManager.readPageIOs( pageOffset, Long.MAX_VALUE );
461 
462         // Update the checkedPages array
463         updateCheckedPages( checkedPages.get( btreeInfo.btreeName ), recordManager.pageSize, pageIos );
464         updateCheckedPages( checkedPages.get( GLOBAL_PAGES_NAME ), recordManager.pageSize, pageIos );
465 
466         // Deserialize the page now
467         long position = 0L;
468 
469         // The revision
470         long revision = recordManager.readLong( pageIos, position );
471         position += RecordManager.LONG_SIZE;
472 
473         // The number of elements in the page
474         int nbElems = recordManager.readInt( pageIos, position );
475         position += RecordManager.INT_SIZE;
476 
477         // The size of the data containing the keys and values
478         // Reads the bytes containing all the keys and values, if we have some
479         // We read  big blob of data into  ByteBuffer, then we will process
480         // this ByteBuffer
481         ByteBuffer byteBuffer = recordManager.readBytes( pageIos, position );
482 
483         // Now, deserialize the data block. If the number of elements
484         // is positive, it's a Leaf, otherwise it's a Node
485         // Note that only a leaf can have 0 elements, and it's the root page then.
486         if ( nbElems >= 0 )
487         {
488             // It's a leaf, process it as we may have sub-btrees
489             checkBtreeLeaf( recordManager, btreeInfo, checkedPages, nbElems, revision, byteBuffer, pageIos );
490         }
491         else
492         {
493             // It's a node
494             long[] children = checkBtreeNode( recordManager, btreeInfo, checkedPages, -nbElems, revision, byteBuffer,
495                 pageIos );
496 
497             for ( int pos = 0; pos <= -nbElems; pos++ )
498             {
499                 // Recursively check the children
500                 checkBtreePage( recordManager, btreeInfo, checkedPages, children[pos] );
501             }
502         }
503     }
504 
505 
506     /**
507      * Check the Btree info page
508      * @throws ClassNotFoundException 
509      */
510     private static <K, V> BtreeInfo<K, V> checkBtreeInfo( RecordManager recordManager, Map<String, int[]> checkedPages,
511         long btreeInfoOffset, long btreeRevision ) throws IOException
512     {
513         BtreeInfo<K, V> btreeInfo = new BtreeInfo<K, V>();
514 
515         PageIO[] btreeInfoPagesIos = recordManager.readPageIOs( btreeInfoOffset, Long.MAX_VALUE );
516 
517         long dataPos = 0L;
518 
519         // The B-tree page size
520         recordManager.readInt( btreeInfoPagesIos, dataPos );
521         dataPos += RecordManager.INT_SIZE;
522 
523         // The tree name
524         ByteBuffer btreeNameBytes = recordManager.readBytes( btreeInfoPagesIos, dataPos );
525         dataPos += RecordManager.INT_SIZE + btreeNameBytes.limit();
526         String btreeName = Strings.utf8ToString( btreeNameBytes );
527 
528         // The keySerializer FQCN
529         ByteBuffer keySerializerBytes = recordManager.readBytes( btreeInfoPagesIos, dataPos );
530 
531         if ( keySerializerBytes != null )
532         {
533             String keySerializerFqcn = Strings.utf8ToString( keySerializerBytes );
534 
535             btreeInfo.keySerializer = getSerializer( keySerializerFqcn );
536         }
537 
538         dataPos += RecordManager.INT_SIZE + keySerializerBytes.limit();
539 
540         // The valueSerialier FQCN
541         ByteBuffer valueSerializerBytes = recordManager.readBytes( btreeInfoPagesIos, dataPos );
542 
543         if ( valueSerializerBytes != null )
544         {
545             String valueSerializerFqcn = Strings.utf8ToString( valueSerializerBytes );
546 
547             btreeInfo.valueSerializer = getSerializer( valueSerializerFqcn );
548         }
549 
550         dataPos += RecordManager.INT_SIZE + valueSerializerBytes.limit();
551 
552         // The B-tree allowDuplicates flag
553         recordManager.readInt( btreeInfoPagesIos, dataPos );
554         dataPos += RecordManager.INT_SIZE;
555 
556         // update the checkedPages
557         if ( !RecordManager.COPIED_PAGE_BTREE_NAME.equals( btreeName )
558             && !RecordManager.BTREE_OF_BTREES_NAME.equals( btreeName ) )
559         {
560             //btreeName = btreeName + "<" + btreeRevision + ">";
561         }
562 
563         btreeInfo.btreeName = btreeName;
564 
565         // Update the checkedPages
566         int[] checkedPagesArray = checkedPages.get( btreeName );
567 
568         if ( checkedPagesArray == null )
569         {
570             // Add the new name in the checkedPage name if it's not already there
571             checkedPagesArray = createPageArray( recordManager );
572             checkedPages.put( btreeName, checkedPagesArray );
573         }
574 
575         updateCheckedPages( checkedPagesArray, recordManager.pageSize, btreeInfoPagesIos );
576         updateCheckedPages( checkedPages.get( GLOBAL_PAGES_NAME ), recordManager.pageSize, btreeInfoPagesIos );
577 
578         return btreeInfo;
579     }
580 
581 
582     /**
583      * Get back the serializer instance
584      */
585     @SuppressWarnings("unchecked")
586     private static <T> ElementSerializer<T> getSerializer( String serializerFqcn )
587     {
588         try
589         {
590             Class<?> serializerClass = Class.forName( serializerFqcn );
591             ElementSerializer<T> serializer = null;
592 
593             try
594             {
595                 serializer = ( ElementSerializer<T> ) serializerClass.getDeclaredField( "INSTANCE" ).get( null );
596             }
597             catch ( NoSuchFieldException e )
598             {
599                 // ignore
600             }
601 
602             if ( serializer == null )
603             {
604                 serializer = ( ElementSerializer<T> ) serializerClass.newInstance();
605             }
606 
607             return serializer;
608         }
609         catch ( Exception e )
610         {
611             throw new InvalidBTreeException( "Error : " + e.getMessage() );
612         }
613     }
614 
615 
616     /**
617      * Check the Btree of Btrees rootPage
618      */
619     private static <K, V> void checkBtreeOfBtreesPage( RecordManager recordManager, Map<String, int[]> checkedPages,
620         long pageOffset ) throws Exception
621     {
622         PageIO[] pageIos = recordManager.readPageIOs( pageOffset, Long.MAX_VALUE );
623 
624         // Update the checkedPages array
625         updateCheckedPages( checkedPages.get( RecordManager.BTREE_OF_BTREES_NAME ), recordManager.pageSize, pageIos );
626         updateCheckedPages( checkedPages.get( GLOBAL_PAGES_NAME ), recordManager.pageSize, pageIos );
627 
628         // Deserialize the page now
629         long position = 0L;
630 
631         // The revision
632         long revision = recordManager.readLong( pageIos, position );
633         position += RecordManager.LONG_SIZE;
634 
635         // The number of elements in the page
636         int nbElems = recordManager.readInt( pageIos, position );
637         position += RecordManager.INT_SIZE;
638 
639         // The size of the data containing the keys and values
640         // Reads the bytes containing all the keys and values, if we have some
641         // We read  big blob of data into  ByteBuffer, then we will process
642         // this ByteBuffer
643         ByteBuffer byteBuffer = recordManager.readBytes( pageIos, position );
644 
645         // Now, deserialize the data block. If the number of elements
646         // is positive, it's a Leaf, otherwise it's a Node
647         // Note that only a leaf can have 0 elements, and it's the root page then.
648         if ( nbElems >= 0 )
649         {
650             // It's a leaf, process it as we may have sub-btrees
651             checkBtreeOfBtreesLeaf( recordManager, checkedPages, nbElems, revision, byteBuffer, pageIos );
652         }
653         else
654         {
655             // It's a node
656             long[] children = checkBtreeOfBtreesNode( recordManager, checkedPages, -nbElems, revision, byteBuffer,
657                 pageIos );
658 
659             for ( int pos = 0; pos <= -nbElems; pos++ )
660             {
661                 // Recursively check the children
662                 checkBtreeOfBtreesPage( recordManager, checkedPages, children[pos] );
663             }
664         }
665     }
666 
667 
668     /**
669      * Check a Btree of Btrees leaf. It contains <revision, name> -> offset.
670      */
671     private static <K, V> void checkBtreeOfBtreesLeaf( RecordManager recordManager, Map<String, int[]> checkedPages,
672         int nbElems, long revision, ByteBuffer byteBuffer, PageIO[] pageIos ) throws Exception
673     {
674         // Read each key and value
675         for ( int i = 0; i < nbElems; i++ )
676         {
677             try
678             {
679                 // Read the number of values
680                 int nbValues = byteBuffer.getInt();
681 
682                 if ( nbValues != 1 )
683                 {
684                     throw new InvalidBTreeException( "We should have only one value for a BOB " + nbValues );
685                 }
686 
687                 // This is a normal value
688                 // First, the value, which is an offset, which length should be 12
689                 int valueLength = byteBuffer.getInt();
690 
691                 if ( valueLength != RecordManager.LONG_SIZE + RecordManager.INT_SIZE )
692                 {
693                     throw new InvalidBTreeException( "The BOB value length is invalid " + valueLength );
694                 }
695 
696                 // Second, the offset length, which should be 8
697                 int offsetLength = byteBuffer.getInt();
698 
699                 if ( offsetLength != RecordManager.LONG_SIZE )
700                 {
701                     throw new InvalidBTreeException( "The BOB value offset length is invalid " + offsetLength );
702                 }
703 
704                 // Then the offset
705                 long btreeOffset = byteBuffer.getLong();
706 
707                 checkOffset( recordManager, btreeOffset );
708 
709                 // Now, process the key
710                 // First the key length
711                 int keyLength = byteBuffer.getInt();
712 
713                 // The length should be at least 12 bytes long
714                 if ( keyLength < RecordManager.LONG_SIZE + RecordManager.INT_SIZE )
715                 {
716                     throw new InvalidBTreeException( "The BOB key length is invalid " + keyLength );
717                 }
718 
719                 // Read the revision
720                 long btreeRevision = byteBuffer.getLong();
721 
722                 // read the btreeName
723                 int btreeNameLength = byteBuffer.getInt();
724 
725                 // The length should be equals to the btreeRevision + btreeNameLength + 4
726                 if ( keyLength != RecordManager.LONG_SIZE + RecordManager.INT_SIZE + btreeNameLength )
727                 {
728                     throw new InvalidBTreeException( "The BOB key length is not the expected value " +
729                         ( RecordManager.LONG_SIZE + RecordManager.INT_SIZE + btreeNameLength ) + ", expected "
730                         + keyLength );
731                 }
732 
733                 byte[] bytes = new byte[btreeNameLength];
734                 byteBuffer.get( bytes );
735                 String btreeName = Strings.utf8ToString( bytes );
736 
737                 // Add the new name in the checkedPage name if it's not already there
738                 int[] btreePagesArray = createPageArray( recordManager );
739                 checkedPages.put( btreeName, btreePagesArray );
740 
741                 // Now, we can check the Btree we just found
742                 checkBtree( recordManager, btreeOffset, checkedPages );
743 
744                 //System.out.println( "read <" + btreeName + "," + btreeRevision + "> : 0x" + Long.toHexString( btreeOffset ) );
745             }
746             catch ( BufferUnderflowException bue )
747             {
748                 throw new InvalidBTreeException( "The BOB leaf byte buffer is too short : " + bue.getMessage() );
749             }
750         }
751     }
752 
753 
754     /**
755      * Check a Btree leaf.
756      */
757     private static <K, V> void checkBtreeLeaf( RecordManager recordManager, BtreeInfo<K, V> btreeInfo,
758         Map<String, int[]> checkedPages, int nbElems, long revision, ByteBuffer byteBuffer, PageIO[] pageIos )
759         throws Exception
760     {
761         // Read each key and value
762         for ( int i = 0; i < nbElems; i++ )
763         {
764             try
765             {
766                 // Read the number of values
767                 int nbValues = byteBuffer.getInt();
768 
769                 if ( nbValues < 0 )
770                 {
771                     // This is a sub-btree. Read the offset
772                     long subBtreeOffset = byteBuffer.getLong();
773 
774                     // And process the sub-btree
775                     checkBtree( recordManager, subBtreeOffset, checkedPages );
776 
777                     // Now, process the key
778                     // The key length
779                     byteBuffer.getInt();
780 
781                     // The key itself
782                     btreeInfo.keySerializer.deserialize( byteBuffer );
783                 }
784                 else
785                 {
786                     // just deserialize the keys and values
787                     // The value
788                     byteBuffer.getInt();
789                     btreeInfo.valueSerializer.deserialize( byteBuffer );
790 
791                     // the key
792                     byteBuffer.getInt();
793 
794                     btreeInfo.keySerializer.deserialize( byteBuffer );
795                 }
796             }
797             catch ( BufferUnderflowException bue )
798             {
799                 throw new InvalidBTreeException( "The BOB leaf byte buffer is too short : " + bue.getMessage() );
800             }
801         }
802     }
803 
804 
805     /**
806      * Check a Btree of Btrees Node
807      */
808     private static <K, V> long[] checkBtreeOfBtreesNode( RecordManager recordManager, Map<String, int[]> checkedPages,
809         int nbElems, long revision,
810         ByteBuffer byteBuffer, PageIO[] pageIos ) throws IOException
811     {
812         long[] children = new long[nbElems + 1];
813 
814         // Read each value
815         for ( int i = 0; i < nbElems; i++ )
816         {
817             // The offsets of the child
818             long firstOffset = LongSerializer.INSTANCE.deserialize( byteBuffer );
819 
820             checkOffset( recordManager, firstOffset );
821 
822             long lastOffset = LongSerializer.INSTANCE.deserialize( byteBuffer );
823 
824             checkOffset( recordManager, lastOffset );
825 
826             children[i] = firstOffset;
827 
828             // Read the key length
829             int keyLength = byteBuffer.getInt();
830 
831             // The length should be at least 12 bytes long
832             if ( keyLength < RecordManager.LONG_SIZE + RecordManager.INT_SIZE )
833             {
834                 throw new InvalidBTreeException( "The BOB key length is invalid " + keyLength );
835             }
836 
837             // Read the revision
838             byteBuffer.getLong();
839 
840             // read the btreeName
841             int btreeNameLength = byteBuffer.getInt();
842 
843             // The length should be equals to the btreeRevision + btreeNameLength + 4
844             if ( keyLength != RecordManager.LONG_SIZE + RecordManager.INT_SIZE + btreeNameLength )
845             {
846                 throw new InvalidBTreeException( "The BOB key length is not the expected value " +
847                     ( RecordManager.LONG_SIZE + RecordManager.INT_SIZE + btreeNameLength ) + ", expected " + keyLength );
848             }
849 
850             // Read the Btree name
851             byte[] bytes = new byte[btreeNameLength];
852             byteBuffer.get( bytes );
853         }
854 
855         // And read the last child
856         // The offsets of the child
857         long firstOffset = LongSerializer.INSTANCE.deserialize( byteBuffer );
858 
859         checkOffset( recordManager, firstOffset );
860 
861         long lastOffset = LongSerializer.INSTANCE.deserialize( byteBuffer );
862 
863         checkOffset( recordManager, lastOffset );
864 
865         children[nbElems] = firstOffset;
866 
867         // and read the last value, as it's a node
868         return children;
869     }
870 
871 
872     /**
873      * Check a Btree node.
874      */
875     private static <K, V> long[] checkBtreeNode( RecordManager recordManager, BtreeInfo<K, V> btreeInfo,
876         Map<String, int[]> checkedPages, int nbElems, long revision, ByteBuffer byteBuffer, PageIO[] pageIos )
877         throws Exception
878     {
879         long[] children = new long[nbElems + 1];
880 
881         // Read each key and value
882         for ( int i = 0; i < nbElems; i++ )
883         {
884             try
885             {
886                 // The offsets of the child
887                 long firstOffset = LongSerializer.INSTANCE.deserialize( byteBuffer );
888 
889                 checkOffset( recordManager, firstOffset );
890 
891                 long lastOffset = LongSerializer.INSTANCE.deserialize( byteBuffer );
892 
893                 checkOffset( recordManager, lastOffset );
894 
895                 children[i] = firstOffset;
896 
897                 // Now, read the key
898                 // The key lenth
899                 byteBuffer.getInt();
900 
901                 // The key itself
902                 btreeInfo.keySerializer.deserialize( byteBuffer );
903             }
904             catch ( BufferUnderflowException bue )
905             {
906                 throw new InvalidBTreeException( "The BOB leaf byte buffer is too short : " + bue.getMessage() );
907             }
908         }
909 
910         // The last child
911         // The offsets of the child
912         long firstOffset = LongSerializer.INSTANCE.deserialize( byteBuffer );
913 
914         checkOffset( recordManager, firstOffset );
915 
916         long lastOffset = LongSerializer.INSTANCE.deserialize( byteBuffer );
917 
918         checkOffset( recordManager, lastOffset );
919 
920         children[nbElems] = firstOffset;
921 
922         return children;
923     }
924 
925 
926     /**
927      * Create an array of bits for pages 
928      */
929     private static int[] createPageArray( RecordManager recordManager ) throws IOException
930     {
931         long fileSize = recordManager.fileChannel.size();
932         int pageSize = recordManager.pageSize;
933         long nbPages = ( fileSize - RecordManager.RECORD_MANAGER_HEADER_SIZE ) / pageSize;
934         int nbPageBits = ( int ) ( nbPages / 32 );
935 
936         return new int[nbPageBits + 1];
937     }
938 
939 
940     /**
941      * Update the array of seen pages.
942      */
943     private static void updateCheckedPages( int[] checkedPages, int pageSize, PageIO... pageIos )
944     {
945         for ( PageIO pageIO : pageIos )
946         {
947             long offset = pageIO.getOffset();
948 
949             setCheckedPage( rm, checkedPages, offset );
950         }
951     }
952 
953 
954     /**
955      * Check the offset to be sure it's a valid one :
956      * <ul>
957      * <li>It's >= 0</li>
958      * <li>It's below the end of the file</li>
959      * <li>It's a multiple of the pageSize
960      * </ul>
961      */
962     private static void checkOffset( RecordManager recordManager, long offset ) throws IOException
963     {
964         if ( ( offset == RecordManager.NO_PAGE ) ||
965             ( ( ( offset - RecordManager.RECORD_MANAGER_HEADER_SIZE ) % recordManager.pageSize ) != 0 ) ||
966             ( offset > recordManager.fileChannel.size() ) )
967         {
968             throw new InvalidBTreeException( "Invalid Offset : " + offset );
969         }
970     }
971 
972 
973     /**
974      * Check the free pages
975      */
976     private static void checkFreePages( RecordManager recordManager, Map<String, int[]> checkedPages )
977         throws IOException
978     {
979         if ( recordManager.firstFreePage == RecordManager.NO_PAGE )
980         {
981             return;
982         }
983 
984         // Now, read all the free pages
985         long currentOffset = recordManager.firstFreePage;
986         long fileSize = recordManager.fileChannel.size();
987 
988         while ( currentOffset != RecordManager.NO_PAGE )
989         {
990             if ( currentOffset > fileSize )
991             {
992                 System.out.println( "Wrong free page offset, above file size : " + currentOffset );
993                 return;
994             }
995 
996             try
997             {
998                 PageIO pageIo = recordManager.fetchPage( currentOffset );
999 
1000                 if ( currentOffset != pageIo.getOffset() )
1001                 {
1002                     System.out.println( "PageIO offset is incorrect : " + currentOffset + "-"
1003                         + pageIo.getOffset() );
1004                     return;
1005                 }
1006 
1007                 setCheckedPage( recordManager, checkedPages.get( GLOBAL_PAGES_NAME ), currentOffset );
1008                 setCheckedPage( recordManager, checkedPages.get( FREE_PAGES_NAME ), currentOffset );
1009 
1010                 long newOffset = pageIo.getNextPage();
1011                 currentOffset = newOffset;
1012             }
1013             catch ( IOException ioe )
1014             {
1015                 throw new InvalidBTreeException( "Cannot fetch page at : " + currentOffset );
1016             }
1017         }
1018     }
1019 
1020 
1021     /**
1022      * Update the ChekcedPages array
1023      */
1024     private static void setCheckedPage( RecordManager recordManager, int[] checkedPages, long offset )
1025     {
1026         int pageNumber = ( int ) offset / recordManager.pageSize;
1027         int nbBitsPage = ( RecordManager.INT_SIZE << 3 );
1028         long pageMask = checkedPages[pageNumber / nbBitsPage];
1029         long mask = 1L << pageNumber % nbBitsPage;
1030 
1031         if ( ( pageMask & mask ) != 0 )
1032         {
1033             //throw new InvalidBTreeException( "The page " + offset + " has already been referenced" );
1034         }
1035 
1036         pageMask |= mask;
1037 
1038         checkedPages[pageNumber / nbBitsPage] |= pageMask;
1039     }
1040 
1041 
1042     /**
1043      * Output the pages that has been seen ('1') and those which has not been seen ('0'). The '.' represent non-pages
1044      * at the end of the file.
1045      */
1046     private static void dumpCheckedPages( RecordManager recordManager, Map<String, int[]> checkedPages )
1047         throws IOException
1048     {
1049         // First dump the global array
1050         int[] globalArray = checkedPages.get( GLOBAL_PAGES_NAME );
1051         String result = dumpPageArray( recordManager, globalArray );
1052 
1053         String dump = String.format( "%1$-40s : %2$s", GLOBAL_PAGES_NAME, result );
1054         System.out.println( dump );
1055 
1056         // The free pages array
1057         int[] freePagesArray = checkedPages.get( FREE_PAGES_NAME );
1058         result = dumpPageArray( recordManager, freePagesArray );
1059 
1060         dump = String.format( "%1$-40s : %2$s", FREE_PAGES_NAME, result );
1061         System.out.println( dump );
1062 
1063         // The B-tree of B-trees pages array
1064         int[] btreeOfBtreesArray = checkedPages.get( RecordManager.BTREE_OF_BTREES_NAME );
1065         result = dumpPageArray( recordManager, btreeOfBtreesArray );
1066 
1067         dump = String.format( "%1$-40s : %2$s", RecordManager.BTREE_OF_BTREES_NAME, result );
1068         System.out.println( dump );
1069 
1070         // The Copied page B-tree pages array
1071         int[] copiedPagesArray = checkedPages.get( RecordManager.COPIED_PAGE_BTREE_NAME );
1072         result = dumpPageArray( recordManager, copiedPagesArray );
1073 
1074         dump = String.format( "%1$-40s : %2$s", RecordManager.COPIED_PAGE_BTREE_NAME, result );
1075         System.out.println( dump );
1076 
1077         // And now, all the other btree arrays
1078         for ( String btreeName : checkedPages.keySet() )
1079         {
1080             // Don't do the array we have already processed
1081             if ( knownPagesArrays.contains( btreeName ) )
1082             {
1083                 continue;
1084             }
1085 
1086             int[] btreePagesArray = checkedPages.get( btreeName );
1087             result = dumpPageArray( recordManager, btreePagesArray );
1088 
1089             dump = String.format( "%1$-40s : %2$s", btreeName, result );
1090             System.out.println( dump );
1091         }
1092     }
1093 
1094 
1095     /**
1096      * @see #getPageOffsets()
1097      */
1098     public static List<Long> getFreePages() throws IOException
1099     {
1100         return getPageOffsets( FREE_PAGES_NAME );
1101     }
1102 
1103     
1104     /**
1105      * @see #getPageOffsets()
1106      */
1107     public static List<Long> getGlobalPages() throws IOException
1108     {
1109         return getPageOffsets( GLOBAL_PAGES_NAME );
1110     }
1111 
1112     
1113     /**
1114      * Gives a list of offsets of pages from the page array associated wit the given name.
1115      * 
1116      * This method should always be called after calling check() method.
1117      * 
1118      * @return a list of offsets
1119      * @throws IOException
1120      */
1121     public static List<Long> getPageOffsets( String pageArrayName ) throws IOException
1122     {
1123         List<Long> lst = new ArrayList<Long>();
1124         
1125         int[] fparry = checkedPages.get( pageArrayName );
1126 
1127         long nbPagesChecked = 0; // the 0th page will always be of RM header
1128         long fileSize = rm.fileChannel.size();
1129         long nbPages = ( fileSize - RecordManager.RECORD_MANAGER_HEADER_SIZE ) / rm.pageSize;
1130 
1131         for ( int checkedPage : fparry )
1132         {
1133             for ( int j = 0; j < 32; j++ )
1134             {
1135 
1136                 if ( nbPagesChecked > nbPages + 1 )
1137                 {
1138                     break;
1139                 }
1140                 else
1141                 {
1142                     int mask = ( checkedPage & ( 1 << j ) );
1143                     if ( mask != 0 )
1144                     {
1145                         lst.add( nbPagesChecked * rm.pageSize);
1146                     }
1147                 }
1148                 
1149                 nbPagesChecked++;
1150             }
1151         }
1152         
1153         return lst;
1154     }
1155     
1156     
1157     /**
1158      * Process a page array
1159      */
1160     private static String dumpPageArray( RecordManager recordManager, int[] checkedPages ) throws IOException
1161     {
1162         StringBuilder sb = new StringBuilder();
1163         int i = -1;
1164         int nbPagesChecked = 0;
1165         long fileSize = recordManager.fileChannel.size();
1166         long nbPages = ( fileSize - RecordManager.RECORD_MANAGER_HEADER_SIZE ) / recordManager.pageSize;
1167 
1168         for ( int checkedPage : checkedPages )
1169         {
1170             if ( i == 0 )
1171             {
1172                 sb.append( " " );
1173                 i++;
1174             }
1175             else
1176             {
1177                 i = 0;
1178             }
1179 
1180             sb.append( "[" ).append( i ).append( "] " );
1181 
1182             for ( int j = 0; j < 32; j++ )
1183             {
1184                 if ( nbPagesChecked >= nbPages + 1 )
1185                 {
1186                     sb.append( "." );
1187                 }
1188                 else
1189                 {
1190                     if ( ( checkedPage & ( 1 << j ) ) == 0 )
1191                     {
1192                         sb.append( "0" );
1193                     }
1194                     else
1195                     {
1196                         sb.append( "1" );
1197                     }
1198                 }
1199 
1200                 nbPagesChecked++;
1201             }
1202         }
1203 
1204         return sb.toString();
1205     }
1206 
1207 
1208     /**
1209      * The entry point method
1210      */
1211     public void start() throws Exception
1212     {
1213         if ( !checkFilePresence() )
1214         {
1215             return;
1216         }
1217 
1218         if ( !loadRm() )
1219         {
1220             return;
1221         }
1222 
1223         boolean stop = false;
1224 
1225         while ( !stop )
1226         {
1227             System.out.println( "Choose an option:" );
1228             System.out.println( "n - Print Number of BTrees" );
1229             System.out.println( "b - Print BTree Names" );
1230             System.out.println( "i - Inspect BTree" );
1231             System.out.println( "c - Check Free Pages" );
1232             System.out.println( "s - Get database file size" );
1233             System.out.println( "d - Dump RecordManager" );
1234             System.out.println( "r - Reload RecordManager" );
1235             System.out.println( "o - Read page at offset" );
1236             System.out.println( "q - Quit" );
1237 
1238             char c = readOption();
1239 
1240             switch ( c )
1241             {
1242                 case 'n':
1243                     printNumberOfBTrees();
1244                     break;
1245 
1246                 case 'b':
1247                     printBTreeNames();
1248                     break;
1249 
1250                 case 'i':
1251                     inspectBTree();
1252                     break;
1253 
1254                 case 'c':
1255                     long fileSize = rm.fileChannel.size();
1256                     long nbPages = fileSize / rm.pageSize;
1257                     int nbPageBits = ( int ) ( nbPages / RecordManager.INT_SIZE );
1258 
1259                     Map<String, int[]> checkedPages = new HashMap<String, int[]>( 2 );
1260 
1261                     // The global page array
1262                     checkedPages.put( GLOBAL_PAGES_NAME, new int[nbPageBits + 1] );
1263 
1264                     // The freePages array
1265                     checkedPages.put( FREE_PAGES_NAME, new int[nbPageBits + 1] );
1266 
1267                     checkFreePages( rm, checkedPages );
1268                     break;
1269 
1270                 case 's':
1271                     printFileSize();
1272                     break;
1273 
1274                 case 'd':
1275                     check( rm );
1276                     break;
1277 
1278                 case 'r':
1279                     loadRm();
1280                     break;
1281 
1282                 case 'o':
1283                     readPageAt();
1284                     break;
1285                 case 'q':
1286                     stop = true;
1287                     break;
1288 
1289                 default:
1290                     System.out.println( "Invalid option" );
1291                     //c = readOption( br );
1292                     break;
1293             }
1294         }
1295 
1296         try
1297         {
1298             rm.close();
1299             br.close();
1300         }
1301         catch ( Exception e )
1302         {
1303             //ignore
1304         }
1305     }
1306 
1307 
1308     /**
1309      * Read the user's interaction
1310      */
1311     private String readLine()
1312     {
1313         try
1314         {
1315             String line = br.readLine();
1316 
1317             if ( line != null )
1318             {
1319                 return line.trim();
1320             }
1321             else
1322             {
1323                 return "";
1324             }
1325         }
1326         catch ( Exception e )
1327         {
1328             throw new RuntimeException( e );
1329         }
1330     }
1331 
1332 
1333     /**
1334      * Process the input and get back the selected choice
1335      */
1336     private char readOption()
1337     {
1338         try
1339         {
1340             String s = br.readLine();
1341 
1342             if ( ( s == null ) || ( s.length() == 0 ) )
1343             {
1344                 return ' ';
1345             }
1346 
1347             return s.charAt( 0 );
1348         }
1349         catch ( Exception e )
1350         {
1351             throw new RuntimeException( e );
1352         }
1353     }
1354 
1355     
1356     private void readPageAt() throws IOException
1357     {
1358         System.out.println();
1359         System.out.print( "Offset: " );
1360         
1361         String s = readLine();
1362         
1363         long offset = -1;
1364         
1365         try
1366         {
1367             offset = Long.parseLong( s.trim() );
1368         }
1369         catch( Exception e )
1370         {
1371             offset = -1;
1372         }
1373         
1374         if( offset < 0 || offset > (rm.fileChannel.size() - rm.DEFAULT_PAGE_SIZE) )
1375         {
1376             System.out.println( "Invalid offset " + s );
1377             return;
1378         }
1379         
1380         PageIO io = rm.fetchPage( offset );
1381 
1382         List<Long> ll = new ArrayList<Long>();
1383         ll.add( offset );
1384         
1385         do
1386         {
1387 //            System.out.println( "Next Page: " + next );
1388 //            System.out.println( "Size: " + io.getSize() );
1389 //            ByteBuffer data = io.getData();
1390             
1391             long next = io.getNextPage();
1392             ll.add( next );
1393             if ( next == -1 )
1394             {
1395                 break;
1396             }
1397             
1398             io = rm.fetchPage( next );
1399         }
1400         while( true );
1401         
1402         int i = 0;
1403         for ( ; i < ll.size() - 2; i++ )
1404         {
1405             System.out.print( ll.get( i ) + " --> ");
1406         }
1407         
1408         System.out.println( ll.get( i ) );
1409     }
1410 
1411     
1412     /**
1413      * Main method
1414      */
1415     public static void main( String[] args ) throws Exception
1416     {
1417 
1418         if ( args.length == 0 )
1419         {
1420             System.out.println( "Usage java MavibotInspector <db-file-path>" );
1421             System.exit( 0 );
1422         }
1423         
1424         File f = new File( args[0] );
1425 
1426         MavibotInspector mi = new MavibotInspector( f );
1427         mi.start();
1428     }
1429 }
1430 
1431 /**
1432  * A class used to store some information about the Btree 
1433  */
1434 final class BtreeInfo<K, V>
1435 {
1436     // The btree name
1437     /* no qualifier */String btreeName;
1438 
1439     // The key serializer
1440     /* no qualifier */ElementSerializer<K> keySerializer;
1441 
1442     // The value serializer
1443     /* no qualifier */ElementSerializer<V> valueSerializer;
1444 
1445 
1446     public String toString()
1447     {
1448         StringBuilder sb = new StringBuilder();
1449 
1450         sb.append( "B-tree Info :" );
1451         sb.append( "\n    name              : " ).append( btreeName );
1452         sb.append( "\n    key serializer    : " ).append( keySerializer.getClass().getName() );
1453         sb.append( "\n    value serializer  : " ).append( valueSerializer.getClass().getName() );
1454 
1455         return sb.toString();
1456     }
1457 }