001: package com.quadcap.sql.file;
002:
003: /* Copyright 1997 - 2003 Quadcap Software. All rights reserved.
004: *
005: * This software is distributed under the Quadcap Free Software License.
006: * This software may be used or modified for any purpose, personal or
007: * commercial. Open Source redistributions are permitted. Commercial
008: * redistribution of larger works derived from, or works which bundle
009: * this software requires a "Commercial Redistribution License"; see
010: * http://www.quadcap.com/purchase.
011: *
012: * Redistributions qualify as "Open Source" under one of the following terms:
013: *
014: * Redistributions are made at no charge beyond the reasonable cost of
015: * materials and delivery.
016: *
017: * Redistributions are accompanied by a copy of the Source Code or by an
018: * irrevocable offer to provide a copy of the Source Code for up to three
019: * years at the cost of materials and delivery. Such redistributions
020: * must allow further use, modification, and redistribution of the Source
021: * Code under substantially the same terms as this license.
022: *
023: * Redistributions of source code must retain the copyright notices as they
024: * appear in each source code file, these license terms, and the
025: * disclaimer/limitation of liability set forth as paragraph 6 below.
026: *
027: * Redistributions in binary form must reproduce this Copyright Notice,
028: * these license terms, and the disclaimer/limitation of liability set
029: * forth as paragraph 6 below, in the documentation and/or other materials
030: * provided with the distribution.
031: *
032: * The Software is provided on an "AS IS" basis. No warranty is
033: * provided that the Software is free of defects, or fit for a
034: * particular purpose.
035: *
036: * Limitation of Liability. Quadcap Software shall not be liable
037: * for any damages suffered by the Licensee or any third party resulting
038: * from use of the Software.
039: */
040:
041: import java.io.ByteArrayOutputStream;
042: import java.io.File;
043: import java.io.FileInputStream;
044: import java.io.IOException;
045: import java.io.InputStream;
046: import java.io.OutputStream;
047: import java.io.PrintWriter;
048:
049: import java.util.Properties;
050:
051: import com.quadcap.util.ConfigNumber;
052: import com.quadcap.util.Debug;
053: import com.quadcap.util.Util;
054:
055: import com.quadcap.util.collections.LongMap;
056:
057: import com.quadcap.crypto.KeyFactory;
058: import com.quadcap.crypto.SymmetricKey;
059:
060: import com.quadcap.io.MutableByteArrayInputStream;
061:
062: import com.quadcap.sql.io.ObjectInputStream;
063: import com.quadcap.sql.io.ObjectOutputStream;
064:
065: import com.quadcap.sql.Version;
066:
067: /**
068: * This class is used to create, modify, or read a blocked file. The blocked
069: * file is accessed as a number of fixed size blocks, which are accessed
070: * through a cache of a size specified by the class user.
071: *
072: * @author Stan Bailes
073: */
074:
075: public class BlockFile implements PageManager, SegmentManager {
076: /** The underlying store used by the cache to access the file. */
077: BlockStore store;
078:
079: /** The cache of file blocks. */
080: BlockCache cache;
081:
082: /** For main-memory mode, our objects */
083: LongMap memoryObjects = null;
084: long memObjectCount = 100000;
085: boolean inMemory;
086:
087: static final int REF_SIZE = 8;
088:
089: static final int MIN_SUBPAGE = 64;
090:
091: static final int MAX_PAGESHIFT = 16;
092:
093: int maxPageShift = 0; // default
094:
095: // Block root;
096: // root block format:
097: // 0: uint16 magic (4c)
098: // 2: uint8 major version
099: // 3: uint8 minor version
100: // 4: uint32 blockSize
101: static final int oBLOCKSIZE = 4;
102:
103: // 8: uint64 freelist
104: static final int oFREELIST = 8;
105:
106: // 16: uint64 lastBlock
107: static final int oLASTBLOCK = 16;
108:
109: // 24: uint64 streams[4]
110: static final int oSTREAM_START = 24;
111: static final int MAX_STREAM = 4;
112:
113: // 56: byte[20] hash password
114: static final int oHASH_PASSWD_start = oSTREAM_START
115: + (REF_SIZE * MAX_STREAM);
116: static final int oHASH_PASSWD_len = 20;
117: // 76: uint64 subroots[16]
118: static final int oSUBPAGE_ROOT = oHASH_PASSWD_start
119: + oHASH_PASSWD_len;
120: static final int oHEADER_SIZE = (oSUBPAGE_ROOT + (MAX_PAGESHIFT * REF_SIZE));
121:
122: int blockSize;
123:
124: /** Cached copy of largest allocated block number. */
125: long lastBlock = 0;
126:
127: boolean readOnly;
128:
129: PageManager[] pageManagers = new PageManager[MAX_PAGESHIFT];
130: Object fileLock = new Object();
131:
132: MutableByteArrayInputStream mbis = new MutableByteArrayInputStream();
133: ObjectInputStream ois = new ObjectInputStream(mbis);
134: ByteArrayOutputStream bos = new ByteArrayOutputStream();
135: ObjectOutputStream oos;
136:
137: /**
138: * Create a new BlockFile, attached to the specified file. For
139: * an existing BlockFile, use the blocksize with which the file was
140: * created -- for a new file, use the specified blocksize.
141: *
142: * @param filename the file to operate on.
143: * @param blockSize the block size to use if we're creating a new file.
144: * @param mode "r" (read only) or "rw" (read-write) access.
145: */
146: public BlockFile(String filename, String mode, Properties props,
147: int blocksize, int cacheSize) throws IOException {
148: //#ifndef RELEASE
149: if (Trace.bit(4)) {
150: Debug.println("BlockFile(" + filename + ", " + mode
151: + ", blockSize = " + blocksize + ", cacheSize = "
152: + cacheSize + ")");
153: }
154: //#endif
155: this .inMemory = props.getProperty("memoryOnly", "")
156: .equalsIgnoreCase("true");
157: pageManagers[0] = this ;
158: int psize = blocksize;
159: maxPageShift = 0;
160: while (psize > MIN_SUBPAGE) {
161: maxPageShift++;
162: psize >>= 1;
163: }
164: this .readOnly = mode.equalsIgnoreCase("r");
165: this .store = makeStore(props, filename, blocksize, mode);
166: this .blockSize = store.blockSize();
167: this .cache = new BlockCache();
168: this .oos = new ObjectOutputStream(bos);
169: if (inMemory) {
170: this .memoryObjects = new LongMap(8888);
171: }
172: cache.setLock(fileLock);
173: cache.setReadOnly(readOnly);
174: cache.init(store, cacheSize);
175: Block root = getBlock(0);
176: try {
177: this .lastBlock = root.readLong(oLASTBLOCK);
178: } finally {
179: root.decrRefCount();
180: }
181: }
182:
183: //#ifdef DEBUG
184: public String signature() throws IOException {
185: StringBuffer sb = new StringBuffer("{");
186: for (long blk = 0; blk <= lastBlock; blk++) {
187: if (blk > 0)
188: sb.append(",");
189: Block b = getBlock(blk);
190: try {
191: sb.append(b.signature());
192: } finally {
193: b.decrRefCount();
194: }
195: }
196: sb.append("}");
197: return sb.toString();
198: }
199:
200: //#endif
201:
202: protected BlockStore makeStore(Properties props, String filename,
203: int blockSize, String mode) throws IOException {
204: BlockStore s;
205: File f = null;
206: boolean enc = false;
207: boolean nullStore = props.getProperty("cacheOnly", "")
208: .equalsIgnoreCase("true");
209: if (inMemory || nullStore) {
210: if (inMemory) {
211: s = new MemoryBlockStore();
212: } else {
213: s = new NullStore();
214: }
215: } else {
216: filename = new File(filename).getAbsolutePath();
217: f = new File(filename);
218: boolean exists = f.exists();
219: if (!exists && readOnly) {
220: throw new IOException("Not found: " + filename);
221: }
222: String u = props.getProperty("user", props.getProperty(
223: "jdbc.user", ""));
224: String p = props.getProperty("passwd", props.getProperty(
225: "jdbc.passwd", ""));
226: /*{com.quadcap.sql.Datafile-conn.xml-70}
227: * <config-var>
228: * <config-name>encrypt</config-name>
229: * <config-dflt>false</config-dflt>
230: * <config-desc>On database creation (i.e., datafile doesn't yet
231: * exist and 'create=true'), if
232: * <code>encrypt == <b>true</b></code>, then a low-level
233: * block encryption algorithm (the default is Rijndael)
234: * will be selected, seeded with the values of the
235: * <code>user</code> and <code>passwd</code> properties for
236: * this, connecting, user. The database magic code will
237: * be changed from <code> 04 0c </code> to <code> 0e 0c </code>
238: * to signal the encrypted content.
239: * </config-desc>
240: * </config-var>
241: */
242: boolean encrypt = props.getProperty("encrypt", "")
243: .equalsIgnoreCase("true");
244: enc = (exists && isEncrypted(f)) || (!exists && encrypt);
245: if (enc) {
246: s = new EncryptedBlockStore();
247: } else {
248: s = new BlockStore();
249: }
250: }
251: s.init(f, mode, blockSize, fileLock);
252: if (enc) {
253: String u = props.getProperty("user", props.getProperty(
254: "jdbc.user", ""));
255: String p = props.getProperty("passwd", props.getProperty(
256: "jdbc.passwd", ""));
257: SymmetricKey key = KeyFactory.createSymmetricKey(u + "/"
258: + p);
259: s.setKey(key);
260: }
261: return s;
262: }
263:
264: private boolean isEncrypted(File f) throws IOException {
265: FileInputStream fi = new FileInputStream(f);
266: try {
267: return fi.read() == 0x0e;
268: } finally {
269: fi.close();
270: }
271: }
272:
273: public long getUserBlock(int stream) throws IOException {
274: synchronized (fileLock) {
275: if (stream > MAX_STREAM) {
276: throw new IOException("Invalid stream: " + stream);
277: }
278: Block root = getBlock(0);
279: long blockNum;
280: int offset = oSTREAM_START + (stream * REF_SIZE);
281: try {
282: blockNum = root.readLong(offset);
283: if (blockNum == 0) {
284: blockNum = newPage();
285: root.writeLong(offset, blockNum);
286: }
287: } finally {
288: root.decrRefCount();
289: }
290: return blockNum;
291: }
292: }
293:
294: /**
295: * Flush the cache and close the underlying file.
296: */
297: public void close() throws IOException {
298: synchronized (fileLock) {
299: flush(false);
300: store.close();
301: }
302: }
303:
304: public final void flush(boolean fastSync) throws IOException {
305: synchronized (fileLock) {
306: //#ifdef DEBUG
307: if (Trace.bit(24)) {
308: Debug.println(toString() + ".flush(" + fastSync + ")");
309: }
310: //#endif
311: if (!readOnly) {
312: cache.flush();
313: if (!fastSync) {
314: store.flush();
315: }
316: }
317: }
318: }
319:
320: public String getName() {
321: return store.getName();
322: }
323:
324: public void setLog(Log log) {
325: store.setLog(log);
326: }
327:
328: Log getLog() {
329: return store.getLog();
330: }
331:
332: /**
333: * Locate the specified block and return the cache entry associated
334: * with that block. It needs to be the case that the cache entry is
335: * "locked" while this returned object is active, so maybe the
336: * <b>Block</b> class needs a <b>finalize()</b> routine.
337: * @param i the block number
338: * @return the cache-handle of the block.
339: */
340: public Page getPage(long i) throws IOException {
341: synchronized (fileLock) {
342: if (i > lastBlock) {
343: //#ifdef DEBUG
344: Debug.println(toString() + ".getPage(" + i
345: + "): Bad Block " + " vs lastBlock = "
346: + lastBlock);
347: //#endif
348: throw new IOException("BlockFile(" + store
349: + "), Bad block: " + i + " ("
350: + SubPageManager.toString(i) + ")");
351: }
352: return (Page) cache.getCacheable(i);
353: }
354: }
355:
356: public Block getBlock(long i) throws IOException {
357: return (Block) getPage(i);
358: }
359:
360: public void restoreBlock(long b, byte[] buf, int off)
361: throws IOException {
362: ensureLastBlock(b);
363: store.restore(b, buf, off);
364: }
365:
366: final void ensureLastBlock(long i) throws IOException {
367: if (i > lastBlock) {
368: lastBlock = i;
369: Block root = getBlock(0);
370: try {
371: root.writeLong(oLASTBLOCK, i);
372: } finally {
373: root.decrRefCount();
374: }
375: }
376: }
377:
378: /**
379: * Return the block size used in this file.
380: */
381: public final int getPageSize() {
382: return this .blockSize;
383: }
384:
385: /**
386: * Return a RandomAccess object which references the specified block
387: */
388: public RandomAccess getStream(long blockRef) throws IOException {
389: PageManager p = getPageManagerForPage(blockRef);
390: return new BlockAccess(p, blockRef);
391: }
392:
393: /**
394: * Return the head of the free list, or grow the tail of the file.
395: * @return the number of an available block.
396: */
397: public long newPage() throws IOException {
398: synchronized (fileLock) {
399: Block root = getBlock(0);
400: long ref;
401: try {
402: ref = root.readLong(oFREELIST);
403: if (ref < 0 || ref > lastBlock) {
404: throw new IOException(this + "newPage(), bad ref: "
405: + ref + ", lastBlock = " + lastBlock);
406: }
407: if (ref != 0) {
408: Block newPage = getBlock(ref);
409: try {
410: root.writeLong(oFREELIST, newPage.readLong(0));
411: //newPage.writeLong(0, 0);
412: newPage.clear();
413: } finally {
414: newPage.decrRefCount();
415: }
416: } else {
417: ref = root.readLong(oLASTBLOCK);
418: Block newPage = getBlock(ref);
419: try {
420: newPage.clear();
421: } finally {
422: newPage.decrRefCount();
423: }
424: lastBlock = ref + 1;
425: root.writeLong(oLASTBLOCK, lastBlock);
426: }
427: } finally {
428: root.decrRefCount();
429: }
430: //#ifdef DEBUG
431: if (Trace.bit(5)) {
432: Debug.println(toString() + ".newPage() = " + ref + "");
433: }
434: //#endif
435: return ref;
436: }
437: }
438:
439: /**
440: * Add this block to the free list.
441: *
442: * @param ref the number of the now free block.
443: */
444: public void freePage(long ref) throws IOException {
445: PageManager p = getPageManagerForPage(ref);
446: if (p != this ) {
447: p.freePage(ref);
448: } else
449: synchronized (fileLock) {
450: //#ifdef DEBUG
451: if (Trace.bit(5)) {
452: Debug
453: .println(toString() + ".freePage(" + ref
454: + ")");
455: }
456: //#endif
457: if (ref <= 0 || ref > lastBlock) {
458: throw new IOException("bad ref");
459: }
460: Block blk = getBlock(ref);
461: try {
462: blk.getDataAndReset();
463:
464: Block root = getBlock(0);
465: try {
466: long freeList = root.readLong(oFREELIST);
467: blk.writeLong(0, freeList);
468: root.writeLong(oFREELIST, ref);
469: } finally {
470: root.decrRefCount();
471: }
472: } finally {
473: blk.decrRefCount();
474: }
475: }
476: }
477:
478: static int getPageOffset(PageManager pm, long ref) {
479: int psize = pm.getPageSize();
480: int pnum = (int) (SubPageManager.fExt(ref,
481: SubPageManager.PAGE_NUM));
482: return pnum * psize;
483: }
484:
485: static long getPageBlock(long ref) {
486: return SubPageManager.pageBlock(ref);
487: }
488:
489: static final boolean quick = true;
490:
491: /**
492: * Create a new segment with the specified bytes value and return
493: * its reference
494: *
495: * @param buf the value to write to the new segment
496: * @return the segment id
497: */
498: public long putBytes(byte[] buf) throws IOException {
499: PageManager pm = getPageManagerForLen(buf.length);
500: long seg = pm.newPage();
501: if (quick && (buf.length + 8) < pm.getPageSize()) {
502: int off = getPageOffset(pm, seg);
503: long blk = getPageBlock(seg);
504: synchronized (fileLock) {
505: Block b = getBlock(blk);
506: try {
507: b.writeLong(off, buf.length);
508: b.write(off + 8, buf, 0, buf.length);
509: } finally {
510: b.decrRefCount();
511: }
512: }
513: } else {
514: synchronized (fileLock) {
515: ba.init(pm, seg);
516: ba.resize(buf.length);
517: ba.write(0, buf, 0, buf.length);
518: }
519: }
520: // Debug.println("BlockFile.putBytes(" + SubPageManager.toString(seg) + "): " +
521: // Util.strBytes(buf, 0, 16));
522: // Debug.println(Util.stackTrace());
523: return seg;
524: }
525:
526: BlockAccess ba = new BlockAccess();
527:
528: /**
529: * Return the segment as a byte array
530: *
531: * @param seg the segment id
532: * @return the value of the segment's bytes
533: */
534: public byte[] getBytes(long seg) throws IOException {
535: PageManager pm = getPageManagerForPage(seg);
536: byte[] buf = null;
537: int off = getPageOffset(pm, seg);
538: long blk = getPageBlock(seg);
539: synchronized (fileLock) {
540: if (quick) {
541: Block b = getBlock(blk);
542: try {
543: int len = (int) b.readLong(off);
544: if (len + 8 < pm.getPageSize()) {
545: buf = new byte[len];
546: b.read(off + 8, buf, 0, len);
547: }
548: } finally {
549: b.decrRefCount();
550: }
551: }
552: if (buf == null) {
553: ba.init(pm, seg);
554: buf = new byte[(int) (ba.size())];
555: ba.read(0, buf, 0, buf.length);
556: }
557: }
558: // Debug.println("BlockFile.getBytes(" +
559: // SubPageManager.toString(seg) + "): " +
560: // Util.strBytes(buf, 0, 16) + " @ " + Util.stackTrace());
561: return buf;
562: }
563:
564: /**
565: * Update a segment with new value bytes.
566: *
567: * @param buf the value to write to the segment
568: * @param seg the segment id
569: */
570: public void updateBytes(long seg, byte[] buf) throws IOException {
571: // Debug.println("BlockFile.updateBytes(" + SubPageManager.toString(seg) +
572: // "): " + Util.strBytes(buf, 0, 16));
573: // Debug.println(Util.stackTrace());
574: PageManager pm = getPageManagerForPage(seg);
575: synchronized (fileLock) {
576: if (quick && (buf.length + 8) < pm.getPageSize()) {
577: Block b = getBlock(getPageBlock(seg));
578: try {
579: int off = getPageOffset(pm, seg);
580: long oldLen = b.readLong(off);
581: if ((oldLen + 8) < pm.getPageSize()) {
582: b.writeLong(off, buf.length);
583: b.write(off + 8, buf, 0, buf.length);
584: return;
585: }
586: } finally {
587: b.decrRefCount();
588: }
589: }
590:
591: ba.init(pm, seg);
592: if (ba.size() != buf.length) {
593: ba.resize(buf.length);
594: }
595: ba.write(0, buf, 0, buf.length);
596: }
597: }
598:
599: /**
600: * Destroy the stream with the specified root page and free up the
601: * storage it was using.
602: *
603: * @param page the root page of the region
604: *
605: * @exception IOException if the page number isn't valid, or if another
606: * error is detected trying to access the region.
607: */
608: public void freeStream(long page) throws IOException {
609: synchronized (fileLock) {
610: getStream(page).resize(0);
611: freePage(page);
612: }
613: }
614:
615: public void freeSegment(long seg) throws IOException {
616: freeStream(seg);
617: }
618:
619: /**
620: * Return a new input stream, reading from the region with the specified
621: * root block.
622: *
623: * @param block the root block of the region
624: * @return an InputStream bound to the region.
625: * @exception IOException if the block number isn't valid, or if another
626: * error is detected trying to access the region.
627: */
628: public RandomAccessInputStream getInputStream(long block)
629: throws IOException {
630: return new RandomAccessInputStream(getStream(block));
631: }
632:
633: /**
634: * Return a new output stream, writing to the region with the specified
635: * root block.
636: *
637: * @param block the root block of the region
638: * @return an OutputStream bound to the region.
639: * @exception IOException if the block number isn't valid, or if another
640: * error is detected trying to access the region.
641: */
642: public RandomAccessOutputStream getOutputStream(long block)
643: throws IOException {
644: return new RandomAccessOutputStream(getStream(block));
645: }
646:
647: /**
648: * Return the specified sub-page manager
649: */
650: public PageManager getPageManager(int i) throws IOException {
651: if (i < 0 || i > maxPageShift) {
652: throw new IOException("Bad page shift: " + i);
653: }
654: PageManager pm = pageManagers[i];
655: if (pm == null) {
656: pageManagers[i] = pm = new SubPageManager(this , i, 0);
657: }
658: return pm;
659: }
660:
661: public PageManager getPageManagerForPage(long page)
662: throws IOException {
663: return getPageManager(SubPageManager.pageShift(page));
664: }
665:
666: public PageManager getPageManagerForLen(long len)
667: throws IOException {
668: int pageShift = 0;
669: int siz = blockSize >> 1;
670: while (len < siz && pageShift < maxPageShift - 1) {
671: siz >>= 1;
672: pageShift++;
673: }
674: if (pageShift > 0) {
675: pageShift--;
676: }
677: return getPageManager(pageShift);
678: }
679:
680: public void revert() throws IOException {
681: cache.revert();
682: Block root = getBlock(0);
683: try {
684: this .lastBlock = root.readLong(oLASTBLOCK);
685: } finally {
686: root.decrRefCount();
687: }
688: store.setLength(this .lastBlock * blockSize);
689: }
690:
691: public void showCache(PrintWriter os) {
692: cache.show(os);
693: }
694:
695: public String toString() {
696: return "BlockFile(" + store + ")";
697: }
698:
699: public void clearModified() throws IOException {
700: store.clearModified();
701: }
702:
703: public long getSize() {
704: return lastBlock * blockSize;
705: }
706:
707: public Object getLock() {
708: return fileLock;
709: }
710:
711: public int getBlockSize() {
712: return blockSize;
713: }
714:
715: /**
716: * Return the specified persistent object from the store
717: * @param ref the block number of the object's root
718: * @return the object
719: * @exception IOException may be thrown
720: */
721: public Object getObject(long ref) throws IOException {
722: Object ret = null;
723: synchronized (fileLock) {
724: if (inMemory) {
725: ret = memoryObjects.get(ref);
726: } else {
727: try {
728: byte[] buf = getBytes(ref);
729: mbis.reset(buf);
730: ret = ois.readObject();
731: } catch (ClassNotFoundException e) {
732: throw new DatafileException(e);
733: }
734: }
735: }
736: //Debug.println(this + ".getObject(" + ref + "): " + ret);
737: return ret;
738: }
739:
740: /**
741: * Write a new object to the store and return its reference
742: * @param obj the object
743: * @return the block number of the object's root
744: * @exception IOException may be thrown
745: */
746: public long putObject(Object obj) throws IOException {
747: synchronized (fileLock) {
748: if (inMemory) {
749: memoryObjects.put(++memObjectCount, obj);
750: //Debug.println(this + ".put(" + obj + ") = " + memObjectCount);
751: return memObjectCount;
752: }
753: bos.reset();
754: oos.writeObject(obj);
755: oos.flush();
756: byte[] buf = bos.toByteArray();
757: long ret = putBytes(buf);
758: return ret;
759: }
760: }
761:
762: /**
763: * Write a new version of a persistent object to the store.
764: * @param blockNum the address of the object's root page in the store
765: * @param obj the new object value
766: *
767: * @exception IOException may be thrown
768: */
769: public void updateObject(long seg, Object obj) throws IOException {
770: //Debug.println(this + ".updateObject(" + seg + "): " + obj);
771: synchronized (fileLock) {
772: if (inMemory) {
773: memoryObjects.put(seg, obj);
774: } else {
775: bos.reset();
776: oos.writeObject(obj);
777: oos.flush();
778: byte[] buf = bos.toByteArray();
779: updateBytes(seg, buf);
780: }
781: }
782: }
783:
784: /**
785: * Remove an object from the store
786: * @param ref the block number of the object's root
787: *
788: * @exception IOException may be thrown
789: */
790: public void removeObject(long ref) throws IOException {
791: //Debug.println(toString() + ".removeObject(" + ref + ")");
792: synchronized (fileLock) {
793: if (inMemory) {
794: memoryObjects.remove(ref);
795: } else {
796: freeSegment(ref);
797: }
798: }
799: }
800:
801: //#ifdef DEBUG
802: public void dump() throws IOException {
803: Block root = getBlock(0);
804: try {
805: Debug.println("Magic: "
806: + Integer.toHexString(root.readShort(0)));
807: Debug.println("Major version: " + root.readByte(2));
808: Debug.println("Minor version: " + root.readByte(3));
809: Debug.println("Block Size: " + root.readInt(oBLOCKSIZE));
810: Debug.println("FreeList: " + root.readLong(oFREELIST));
811: java.util.BitSet free = new java.util.BitSet();
812: for (long ref = root.readLong(oFREELIST); ref != 0;) {
813: free.set((int) ref);
814: Block newPage = getBlock(ref);
815: try {
816: ref = newPage.readLong(0);
817: } finally {
818: newPage.decrRefCount();
819: }
820: }
821: int first = -2;
822: int last = -2;
823: for (int i = free.nextSetBit(0); i >= 0; i = free
824: .nextSetBit(i + 1)) {
825: if (i != last + 1) {
826: if (first >= 0) {
827: if (first == last) {
828: Debug.println("Free: " + first);
829: } else {
830: Debug.println("Free: " + first + " - "
831: + last);
832: }
833: }
834: first = i;
835: last = i;
836: }
837: }
838: if (first >= 0) {
839: if (first == last) {
840: Debug.println("Free: " + first);
841: } else {
842: Debug.println("Free: " + first + " - " + last);
843: }
844: }
845: long lastBlock_ = root.readLong(oLASTBLOCK);
846: Debug
847: .println("Last Block: "
848: + root.readLong(oLASTBLOCK));
849: long waste = free.cardinality() * blockSize;
850: long total = lastBlock_ * blockSize;
851: double pct = 100 * ((double) waste) / ((double) total);
852: String pcts = java.text.NumberFormat.getInstance().format(
853: pct);
854: Debug.println("Fragmentation: " + free.cardinality()
855: + " of " + lastBlock_ + " blocks = " + pcts
856: + "% waste");
857: Debug
858: .println("Last Block: "
859: + root.readLong(oLASTBLOCK));
860: for (int i = 0; i < MAX_STREAM; i++) {
861: Debug.println("Stream " + i + ": "
862: + root.readLong(oSTREAM_START + i * REF_SIZE));
863: }
864: byte[] hp = new byte[oHASH_PASSWD_len];
865: root.read(oHASH_PASSWD_start, hp, 0, oHASH_PASSWD_len);
866: Debug.println("Hash Passwd: " + Util.hexBytes(hp));
867: for (int i = 0; i < 16; i++) {
868: Debug.println("PM "
869: + i
870: + " root: "
871: + SubPageManager
872: .toString(root.readLong(oSUBPAGE_ROOT
873: + i * REF_SIZE)));
874: }
875: } finally {
876: root.decrRefCount();
877: }
878: }
879:
880: public static void main(String[] args) {
881: try {
882: BlockFile f = new BlockFile(args[0], "r", new Properties(),
883: 8192, 256);
884: f.dump();
885: } catch (Throwable t) {
886: Debug.print(t);
887: }
888: }
889:
890: //#endif
891: }
|