diff --git a/apps/susidns/src/css.css b/apps/susidns/src/css.css index 14ab494b23ba9cd8265b9061b284c2e1a4b64b8f..f3a57d4efee29a1f0ae4861a8f04c16ffb07deee 100644 --- a/apps/susidns/src/css.css +++ b/apps/susidns/src/css.css @@ -66,11 +66,11 @@ li { } tr.list1 { - background-color:#E0E0E0; + background-color:#E8E8EC; } tr.list0 { - background-color:white; + background-color:#F0F0F4; } p.messages { diff --git a/apps/susidns/src/java/src/i2p/susi/dns/NamingServiceBean.java b/apps/susidns/src/java/src/i2p/susi/dns/NamingServiceBean.java index cabdb0f5365e95ab32422e72a8a96f844538520f..a1d905ccfd6e802953756f070d8405079296f91e 100644 --- a/apps/susidns/src/java/src/i2p/susi/dns/NamingServiceBean.java +++ b/apps/susidns/src/java/src/i2p/susi/dns/NamingServiceBean.java @@ -183,7 +183,12 @@ public class NamingServiceBean extends AddressbookBean } } String destination = entry.getValue().toBase64(); - list.addLast( new AddressBean( name, destination ) ); + if (destination != null) { + list.addLast( new AddressBean( name, destination ) ); + } else { + // delete it too? + System.err.println("Bad entry " + name + " in database " + service.getName()); + } } AddressBean array[] = list.toArray(new AddressBean[list.size()]); Arrays.sort( array, sorter ); diff --git a/core/java/src/net/i2p/client/naming/BlockfileNamingService.java b/core/java/src/net/i2p/client/naming/BlockfileNamingService.java index 448f10f2e85e3aac7aab473c308e7eab225cc39b..d7a55bd396fb0f4946f78b015f150f3d7f04a2fb 100644 --- a/core/java/src/net/i2p/client/naming/BlockfileNamingService.java +++ b/core/java/src/net/i2p/client/naming/BlockfileNamingService.java @@ -75,6 +75,7 @@ public class BlockfileNamingService extends DummyNamingService { private final BlockFile _bf; private final RandomAccessFile _raf; private final List<String> _lists; + private final List<InvalidEntry> _invalid; private volatile boolean _isClosed; private static final Serializer _infoSerializer = new PropertiesSerializer(); @@ -101,6 +102,7 @@ public class BlockfileNamingService extends DummyNamingService { public BlockfileNamingService(I2PAppContext context) { super(context); _lists = new ArrayList(); + _invalid = new ArrayList(); BlockFile bf = null; RandomAccessFile raf = null; File f = new File(_context.getRouterDir(), HOSTS_DB); @@ -375,8 +377,10 @@ public class BlockfileNamingService extends DummyNamingService { try { DestEntry de = getEntry(list, key); if (de != null) { + if (!validate(key, de, listname)) + continue; d = de.dest; - if (storedOptions != null) + if (storedOptions != null && de.props != null) storedOptions.putAll(de.props); break; } @@ -384,6 +388,7 @@ public class BlockfileNamingService extends DummyNamingService { break; } } + deleteInvalid(); } if (d != null) putCache(hostname, d); @@ -553,6 +558,7 @@ public class BlockfileNamingService extends DummyNamingService { iter = sl.iterator(); Map<String, Destination> rv = new HashMap(); for (int i = 0; i < skip && iter.hasNext(); i++) { + // don't bother validating here iter.next(); } for (int i = 0; i < limit && iter.hasNext(); ) { @@ -566,6 +572,8 @@ public class BlockfileNamingService extends DummyNamingService { } } DestEntry de = (DestEntry) iter.next(); + if (!validate(key, de, listname)) + continue; if (search != null && key.indexOf(search) < 0) continue; rv.put(key, de.dest); @@ -578,6 +586,8 @@ public class BlockfileNamingService extends DummyNamingService { } catch (RuntimeException re) { _log.error("DB lookup error", re); return Collections.EMPTY_MAP; + } finally { + deleteInvalid(); } } } @@ -619,6 +629,60 @@ public class BlockfileNamingService extends DummyNamingService { ////////// End NamingService API + /** + * Continuously validate anything we read in. + * Queue anything invalid to be removed at the end of the operation. + * Caller must sync! + * @return valid + */ + private boolean validate(String key, DestEntry de, String listname) { + if (key == null) + return false; + // de.props may be null + // publickey check is a quick proxy to detect dest deserialization failure + boolean rv = key.length() > 0 && + de != null && + de.dest != null && + de.dest.getPublicKey() != null; + if (!rv) + _invalid.add(new InvalidEntry(key, listname)); + return rv; + } + + /** + * Remove and log all invalid entries queued by validate() + * while scanning in lookup() or getEntries(). + * We delete in the order detected, as an error may be corrupting later entries in the skiplist. + * Caller must sync! + */ + private void deleteInvalid() { + if (_invalid.isEmpty()) + return; + _log.error("Removing " + _invalid.size() + " corrupt entries from database"); + for (InvalidEntry ie : _invalid) { + String key = ie.key; + String list = ie.list; + try { + SkipList sl = _bf.getIndex(list, _stringSerializer, _destSerializer); + if (sl == null) { + _log.error("No list found to remove corrupt \"" + key + "\" from database " + list); + continue; + } + // this will often return null since it was corrupt + boolean success = removeEntry(sl, key) != null; + if (success) + _log.error("Removed corrupt \"" + key + "\" from database " + list); + else + _log.error("May have Failed to remove corrupt \"" + key + "\" from database " + list); + } catch (RuntimeException re) { + _log.error("Error while removing corrupt \"" + key + "\" from database " + list, re); + } catch (IOException ioe) { + _log.error("Error while removing corrput \"" + key + "\" from database " + list, ioe); + } + } + _invalid.clear(); + } + private void dumpDB() { synchronized(_bf) { if (_isClosed) @@ -634,6 +698,8 @@ public class BlockfileNamingService extends DummyNamingService { for (SkipIterator iter = sl.iterator(); iter.hasNext(); ) { String key = (String) iter.nextKey(); DestEntry de = (DestEntry) iter.next(); + if (!validate(key, de, list)) + continue; _log.error("DB " + list + " key " + key + " val " + de); i++; } @@ -643,6 +709,7 @@ public class BlockfileNamingService extends DummyNamingService { break; } } + deleteInvalid(); } } @@ -661,6 +728,11 @@ public class BlockfileNamingService extends DummyNamingService { } } + /** for logging errors in the static serializers below */ + private static void logError(String msg, Throwable t) { + I2PAppContext.getGlobalContext().logManager().getLog(BlockfileNamingService.class).error(msg, t); + } + private class Shutdown implements Runnable { public void run() { close(); @@ -691,24 +763,33 @@ public class BlockfileNamingService extends DummyNamingService { /** * Used for the values in the header skiplist + * Take care not to throw on any error. + * This means that some things will fail with no indication other than the log, + * but if we threw a RuntimeException we would prevent access to entries later in + * the SkipSpan. */ private static class PropertiesSerializer implements Serializer { + /** + * A format error on the properties is non-fatal (returns an empty properties) + */ public byte[] getBytes(Object o) { Properties p = (Properties) o; try { return DataHelper.toProperties(p); } catch (DataFormatException dfe) { - return null; + logError("DB Write Fail - properties too big?", dfe); + // null properties is a two-byte length of 0. + return new byte[2]; } } + /** returns null on error */ public Object construct(byte[] b) { Properties rv = new Properties(); try { DataHelper.fromProperties(b, 0, rv); - } catch (IOException ioe) { - return null; } catch (DataFormatException dfe) { + logError("DB Read Fail", dfe); return null; } return rv; @@ -734,22 +815,38 @@ public class BlockfileNamingService extends DummyNamingService { /** * Used for the values in the addressbook skiplists + * Take care not to throw on any error. + * This means that some things will fail with no indication other than the log, + * but if we threw a RuntimeException we would prevent access to entries later in + * the SkipSpan. */ private static class DestEntrySerializer implements Serializer { + + /** + * A format error on the properties is non-fatal (only the properties are lost) + * A format error on the destination is fatal + */ public byte[] getBytes(Object o) { DestEntry de = (DestEntry) o; ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); try { - DataHelper.writeProperties(baos, de.props); + try { + DataHelper.writeProperties(baos, de.props, true, false); // UTF-8, unsorted + } catch (DataFormatException dfe) { + logError("DB Write Fail - properties too big?", dfe); + // null properties is a two-byte length of 0. + baos.write(new byte[2]); + } de.dest.writeBytes(baos); } catch (IOException ioe) { - return null; + logError("DB Write Fail", ioe); } catch (DataFormatException dfe) { - return null; + logError("DB Write Fail", dfe); } return baos.toByteArray(); } + /** returns null on error */ public Object construct(byte[] b) { DestEntry rv = new DestEntry(); Destination dest = new Destination(); @@ -759,14 +856,29 @@ public class BlockfileNamingService extends DummyNamingService { rv.props = DataHelper.readProperties(bais); dest.readBytes(bais); } catch (IOException ioe) { + logError("DB Read Fail", ioe); return null; } catch (DataFormatException dfe) { + logError("DB Read Fail", dfe); return null; } return rv; } } + /** + * Used to store entries that need deleting + */ + private static class InvalidEntry { + public final String key; + public final String list; + + public InvalidEntry(String k, String l) { + key = k; + list = l; + } + } + public static void main(String[] args) { BlockfileNamingService bns = new BlockfileNamingService(I2PAppContext.getGlobalContext()); List<String> names = null; diff --git a/core/java/src/net/i2p/data/DataHelper.java b/core/java/src/net/i2p/data/DataHelper.java index eea636286f1e3d3b3184149f82c757d7736be9d2..932913ebd1e8047264b9e6a9d15b2370301846e8 100644 --- a/core/java/src/net/i2p/data/DataHelper.java +++ b/core/java/src/net/i2p/data/DataHelper.java @@ -54,8 +54,16 @@ import net.i2p.util.Translate; * @author jrandom */ public class DataHelper { - private final static byte EQUAL_BYTES[] = "=".getBytes(); // in UTF-8 - private final static byte SEMICOLON_BYTES[] = ";".getBytes(); // in UTF-8 + private static final byte EQUAL_BYTES[]; + private static final byte SEMICOLON_BYTES[]; + static { + try { + EQUAL_BYTES = "=".getBytes("UTF-8"); + SEMICOLON_BYTES = ";".getBytes("UTF-8"); + } catch (UnsupportedEncodingException uee) { + throw new RuntimeException("no utf8!?"); + } + } /** Read a mapping from the stream, as defined by the I2P data structure spec, * and store it into a Properties object. @@ -80,7 +88,7 @@ public class DataHelper { long size = readLong(rawStream, 2); byte data[] = new byte[(int) size]; int read = read(rawStream, data); - if (read != size) throw new DataFormatException("Not enough data to read the properties"); + if (read != size) throw new DataFormatException("Not enough data to read the properties, expected " + size + " but got " + read); ByteArrayInputStream in = new ByteArrayInputStream(data); byte eqBuf[] = new byte[EQUAL_BYTES.length]; byte semiBuf[] = new byte[SEMICOLON_BYTES.length]; @@ -251,22 +259,32 @@ public class DataHelper { * @param target returned Properties * @return new offset */ - public static int fromProperties(byte source[], int offset, Properties target) throws DataFormatException, IOException { + public static int fromProperties(byte source[], int offset, Properties target) throws DataFormatException { int size = (int)fromLong(source, offset, 2); offset += 2; ByteArrayInputStream in = new ByteArrayInputStream(source, offset, size); byte eqBuf[] = new byte[EQUAL_BYTES.length]; byte semiBuf[] = new byte[SEMICOLON_BYTES.length]; while (in.available() > 0) { - String key = readString(in); - int read = read(in, eqBuf); - if ((read != eqBuf.length) || (!eq(eqBuf, EQUAL_BYTES))) { - throw new DataFormatException("Bad key"); + String key; + try { + key = readString(in); + int read = read(in, eqBuf); + if ((read != eqBuf.length) || (!eq(eqBuf, EQUAL_BYTES))) { + throw new DataFormatException("Bad key"); + } + } catch (IOException ioe) { + throw new DataFormatException("Bad key", ioe); } - String val = readString(in); - read = read(in, semiBuf); - if ((read != semiBuf.length) || (!eq(semiBuf, SEMICOLON_BYTES))) { - throw new DataFormatException("Bad value"); + String val; + try { + val = readString(in); + int read = read(in, semiBuf); + if ((read != semiBuf.length) || (!eq(semiBuf, SEMICOLON_BYTES))) { + throw new DataFormatException("Bad value"); + } + } catch (IOException ioe) { + throw new DataFormatException("Bad value", ioe); } target.put(key, val); } diff --git a/core/java/src/net/metanotion/io/RAIFile.java b/core/java/src/net/metanotion/io/RAIFile.java index c947a7291d43092933b46a830eda4ccb45e38727..0988896e62bcdaddfa7bd3edff971f76a7de0210 100644 --- a/core/java/src/net/metanotion/io/RAIFile.java +++ b/core/java/src/net/metanotion/io/RAIFile.java @@ -82,6 +82,17 @@ public class RAIFile implements RandomAccessInterface, DataInput, DataOutput { public int readUnsignedByte() throws IOException { return delegate.readUnsignedByte(); } public int readUnsignedShort() throws IOException { return delegate.readUnsignedShort(); } + /** + * I2P + * @throws IOException if the read value is negative + */ + public int readUnsignedInt() throws IOException { + int rv = readInt(); + if (rv < 0) + throw new IOException("Negative value for unsigned int: " + rv); + return rv; + } + /** Read a UTF encoded string I would delegate here. But Java's read/writeUTF combo suck. A signed 2 byte length is not enough. diff --git a/core/java/src/net/metanotion/io/RandomAccessInterface.java b/core/java/src/net/metanotion/io/RandomAccessInterface.java index 227e36c66618478af4ca730da990798e77c87825..953d006985482a1028e34d18e8a2365dc7d5b98b 100644 --- a/core/java/src/net/metanotion/io/RandomAccessInterface.java +++ b/core/java/src/net/metanotion/io/RandomAccessInterface.java @@ -56,6 +56,8 @@ public interface RandomAccessInterface { public short readShort() throws IOException; public int readUnsignedByte() throws IOException; public int readUnsignedShort() throws IOException; + // I2P + public int readUnsignedInt() throws IOException; public String readUTF() throws IOException; public int skipBytes(int n) throws IOException; diff --git a/core/java/src/net/metanotion/io/block/BlockFile.java b/core/java/src/net/metanotion/io/block/BlockFile.java index 12f46b34c2134de00e63a0d628debc97c89fb08d..0d31eecbba402e7a0e05c7030df9a2a52aa36004 100644 --- a/core/java/src/net/metanotion/io/block/BlockFile.java +++ b/core/java/src/net/metanotion/io/block/BlockFile.java @@ -42,10 +42,12 @@ import net.metanotion.io.data.IntBytes; import net.metanotion.io.data.LongBytes; import net.metanotion.io.data.NullBytes; import net.metanotion.io.data.StringBytes; - import net.metanotion.io.block.index.BSkipList; import net.metanotion.util.skiplist.SkipList; +import net.i2p.I2PAppContext; +import net.i2p.util.Log; + class CorruptFileException extends IOException { } class BadFileFormatException extends IOException { } class BadVersionException extends IOException { } @@ -53,14 +55,15 @@ class BadVersionException extends IOException { } public class BlockFile { public static final long PAGESIZE = 1024; public static final long OFFSET_MOUNTED = 20; + public static final Log log = I2PAppContext.getGlobalContext().logManager().getLog(BlockFile.class); public RandomAccessInterface file; private long magicBytes = 0x3141deadbeef0100L; private long fileLen = PAGESIZE * 2; private int freeListStart = 0; - private short mounted = 0; - public short spanSize = 16; + private int mounted = 0; + public int spanSize = 16; private BSkipList metaIndex = null; private HashMap openIndices = new HashMap(); @@ -84,9 +87,9 @@ public class BlockFile { file.seek(0); magicBytes = file.readLong(); fileLen = file.readLong(); - freeListStart = file.readInt(); - mounted = file.readShort(); - spanSize = file.readShort(); + freeListStart = file.readUnsignedInt(); + mounted = file.readUnsignedShort(); + spanSize = file.readUnsignedShort(); } public static void main(String args[]) { @@ -125,7 +128,7 @@ public class BlockFile { } BlockFile.pageSeek(this.file, curNextPage); curPage = curNextPage; - curNextPage = this.file.readInt(); + curNextPage = this.file.readUnsignedInt(); pageCounter = 4; len = ((int) BlockFile.PAGESIZE) - pageCounter; } @@ -149,7 +152,7 @@ public class BlockFile { if(len <= 0) { BlockFile.pageSeek(this.file, curNextPage); curPage = curNextPage; - curNextPage = this.file.readInt(); + curNextPage = this.file.readUnsignedInt(); pageCounter = 4; len = ((int) BlockFile.PAGESIZE) - pageCounter; } diff --git a/core/java/src/net/metanotion/io/block/FreeListBlock.java b/core/java/src/net/metanotion/io/block/FreeListBlock.java index aec2a933490495d2444d29214d4cd02e1adfe739..0ad32e588399cacf1b58684cc8304b90968fd469 100644 --- a/core/java/src/net/metanotion/io/block/FreeListBlock.java +++ b/core/java/src/net/metanotion/io/block/FreeListBlock.java @@ -43,12 +43,12 @@ public class FreeListBlock { this.file = file; this.page = startPage; BlockFile.pageSeek(file, startPage); - nextPage = file.readInt(); - len = file.readInt(); + nextPage = file.readUnsignedInt(); + len = file.readUnsignedInt(); if(len > 0) { branches = new int[len]; for(int i=0;i<len;i++) { - branches[i] = file.readInt(); + branches[i] = file.readUnsignedInt(); } } } diff --git a/core/java/src/net/metanotion/io/block/index/BSkipLevels.java b/core/java/src/net/metanotion/io/block/index/BSkipLevels.java index 3e42b0c7c6fae0f584b4f7b2113524660e98e4c2..d774bbf527a67e92855fcb6da8a48ce7a5eda0bc 100644 --- a/core/java/src/net/metanotion/io/block/index/BSkipLevels.java +++ b/core/java/src/net/metanotion/io/block/index/BSkipLevels.java @@ -50,15 +50,15 @@ public class BSkipLevels extends SkipLevels { bsl.levelHash.put(new Integer(this.levelPage), this); - int maxLen = bf.file.readShort(); - int nonNull = bf.file.readShort(); - spanPage = bf.file.readInt(); + int maxLen = bf.file.readUnsignedShort(); + int nonNull = bf.file.readUnsignedShort(); + spanPage = bf.file.readUnsignedInt(); bottom = (BSkipSpan) bsl.spanHash.get(new Integer(spanPage)); this.levels = new BSkipLevels[maxLen]; int lp; for(int i=0;i<nonNull;i++) { - lp = bf.file.readInt(); + lp = bf.file.readUnsignedInt(); if(lp != 0) { levels[i] = (BSkipLevels) bsl.levelHash.get(new Integer(lp)); if(levels[i] == null) { diff --git a/core/java/src/net/metanotion/io/block/index/BSkipList.java b/core/java/src/net/metanotion/io/block/index/BSkipList.java index e7c8741e073b1f0b5492e658557c196b52990731..2e8fff93cb3cc78cce6813e7ab457c6da1da6985 100644 --- a/core/java/src/net/metanotion/io/block/index/BSkipList.java +++ b/core/java/src/net/metanotion/io/block/index/BSkipList.java @@ -59,10 +59,10 @@ public class BSkipList extends SkipList { this.bf = bf; BlockFile.pageSeek(bf.file, skipPage); - firstSpanPage = bf.file.readInt(); - firstLevelPage = bf.file.readInt(); - size = bf.file.readInt(); - spans = bf.file.readInt(); + firstSpanPage = bf.file.readUnsignedInt(); + firstLevelPage = bf.file.readUnsignedInt(); + size = bf.file.readUnsignedInt(); + spans = bf.file.readUnsignedInt(); //System.out.println(size + " " + spans); this.fileOnly = fileOnly; diff --git a/core/java/src/net/metanotion/io/block/index/BSkipSpan.java b/core/java/src/net/metanotion/io/block/index/BSkipSpan.java index 9ade78b0282dda9676eca7eb6f02b160fb155860..d0be46f55e62745b5c38949d4f1923e342cee280 100644 --- a/core/java/src/net/metanotion/io/block/index/BSkipSpan.java +++ b/core/java/src/net/metanotion/io/block/index/BSkipSpan.java @@ -74,7 +74,7 @@ public class BSkipSpan extends SkipSpan { int next; while(curPage != 0) { BlockFile.pageSeek(bf.file, curPage); - next = bf.file.readInt(); + next = bf.file.readUnsignedInt(); bf.freePage(curPage); curPage = next; } @@ -83,6 +83,13 @@ public class BSkipSpan extends SkipSpan { } public void flush() { + fflush(); + } + + /** + * I2P - avoid super.flush() + */ + private void fflush() { try { BlockFile.pageSeek(bf.file, page); bf.file.writeInt(overflowPage); @@ -111,11 +118,27 @@ public class BSkipSpan extends SkipSpan { } BlockFile.pageSeek(bf.file, curNextPage[0]); curPage = curNextPage[0]; - curNextPage[0] = bf.file.readInt(); + curNextPage[0] = bf.file.readUnsignedInt(); pageCounter[0] = 4; } + // Drop bad entry without throwing exception + if (keys[i] == null || vals[i] == null) { + BlockFile.log.error("Dropping null data in entry " + i + " page " + curPage + + " key=" + this.keys[i] + " val=" + this.vals[i]); + nKeys--; + i--; + continue; + } keyData = this.keySer.getBytes(keys[i]); valData = this.valSer.getBytes(vals[i]); + // Drop bad entry without throwing exception + if (keyData.length > 65535 || valData.length > 65535) { + BlockFile.log.error("Dropping huge data in entry " + i + " page " + curPage + + " keylen=" + keyData.length + " vallen=" + valData.length); + nKeys--; + i--; + continue; + } pageCounter[0] += 4; bf.file.writeShort(keyData.length); bf.file.writeShort(valData.length); @@ -123,8 +146,11 @@ public class BSkipSpan extends SkipSpan { curPage = bf.writeMultiPageData(valData, curPage, pageCounter, curNextPage); } BlockFile.pageSeek(bf.file, this.page); - this.overflowPage = bf.file.readInt(); + this.overflowPage = bf.file.readUnsignedInt(); } catch (IOException ioe) { throw new RuntimeException("Error writing to database", ioe); } + // FIXME can't get there from here + //bsl.size -= fail; + //bsl.flush(); } private static void load(BSkipSpan bss, BlockFile bf, BSkipList bsl, int spanPage, Serializer key, Serializer val) throws IOException { @@ -146,11 +172,11 @@ public class BSkipSpan extends SkipSpan { BlockFile.pageSeek(bf.file, spanPage); - bss.overflowPage = bf.file.readInt(); - bss.prevPage = bf.file.readInt(); - bss.nextPage = bf.file.readInt(); - bss.spanSize = bf.file.readShort(); - bss.nKeys = bf.file.readShort(); + bss.overflowPage = bf.file.readUnsignedInt(); + bss.prevPage = bf.file.readUnsignedInt(); + bss.nextPage = bf.file.readUnsignedInt(); + bss.spanSize = bf.file.readUnsignedShort(); + bss.nKeys = bf.file.readUnsignedShort(); } /** @@ -158,6 +184,15 @@ public class BSkipSpan extends SkipSpan { * Load the whole span's keys and values into memory */ protected void loadData() throws IOException { + loadData(true); + } + + /** + * I2P - second half of load() + * Load the whole span's keys and values into memory + * @param flushOnError set to false if you are going to flush anyway + */ + protected void loadData(boolean flushOnError) throws IOException { this.keys = new Comparable[this.spanSize]; this.vals = new Object[this.spanSize]; @@ -168,15 +203,16 @@ public class BSkipSpan extends SkipSpan { int[] pageCounter = new int[1]; pageCounter[0] = 16; // System.out.println("Span Load " + sz + " nKeys " + nKeys + " page " + curPage); + int fail = 0; for(int i=0;i<this.nKeys;i++) { if((pageCounter[0] + 4) > BlockFile.PAGESIZE) { BlockFile.pageSeek(this.bf.file, curNextPage[0]); curPage = curNextPage[0]; - curNextPage[0] = this.bf.file.readInt(); + curNextPage[0] = this.bf.file.readUnsignedInt(); pageCounter[0] = 4; } - ksz = this.bf.file.readShort(); - vsz = this.bf.file.readShort(); + ksz = this.bf.file.readUnsignedShort(); + vsz = this.bf.file.readUnsignedShort(); pageCounter[0] +=4; byte[] k = new byte[ksz]; byte[] v = new byte[vsz]; @@ -185,8 +221,24 @@ public class BSkipSpan extends SkipSpan { // System.out.println("i=" + i + ", Page " + curPage + ", offset " + pageCounter[0] + " ksz " + ksz + " vsz " + vsz); this.keys[i] = (Comparable) this.keySer.construct(k); this.vals[i] = this.valSer.construct(v); + // Drop bad entry without throwing exception + if (this.keys[i] == null || this.vals[i] == null) { + BlockFile.log.error("Null deserialized data in entry " + i + " page " + curPage + + " key=" + this.keys[i] + " val=" + this.vals[i]); + fail++; + nKeys--; + i--; + continue; + } + } + if (fail > 0) { + BlockFile.log.error("Repairing corruption of " + fail + " entries"); + if (flushOnError) + fflush(); + // FIXME can't get there from here + //bsl.size -= fail; + //bsl.flush(); } - } protected BSkipSpan() { } diff --git a/core/java/src/net/metanotion/io/block/index/IBSkipSpan.java b/core/java/src/net/metanotion/io/block/index/IBSkipSpan.java index 315b180de749441e424ffe32d41cf3a63db6e31e..5ca72b87916ac9d7986c2cd834853f0e53a317fb 100644 --- a/core/java/src/net/metanotion/io/block/index/IBSkipSpan.java +++ b/core/java/src/net/metanotion/io/block/index/IBSkipSpan.java @@ -114,12 +114,16 @@ public class IBSkipSpan extends BSkipSpan { curNextPage[0] = this.overflowPage; int[] pageCounter = new int[1]; pageCounter[0] = 16; - ksz = this.bf.file.readShort(); + ksz = this.bf.file.readUnsignedShort(); this.bf.file.skipBytes(2); //vsz pageCounter[0] +=4; byte[] k = new byte[ksz]; curPage = this.bf.readMultiPageData(k, curPage, pageCounter, curNextPage); this.firstKey = (Comparable) this.keySer.construct(k); + if (this.firstKey == null) { + BlockFile.log.error("Null deserialized first key in page " + curPage); + repair(1); + } if (DEBUG) System.err.println("Loaded header for page " + this.page + " containing " + this.nKeys + '/' + this.spanSize + " first key: " + this.firstKey); } @@ -153,16 +157,17 @@ public class IBSkipSpan extends BSkipSpan { curNextPage[0] = this.overflowPage; int[] pageCounter = new int[1]; pageCounter[0] = 16; + int fail = 0; //System.out.println("Span Load " + sz + " nKeys " + nKeys + " page " + curPage); for(int i=0;i<this.nKeys;i++) { if((pageCounter[0] + 4) > BlockFile.PAGESIZE) { BlockFile.pageSeek(this.bf.file, curNextPage[0]); curPage = curNextPage[0]; - curNextPage[0] = this.bf.file.readInt(); + curNextPage[0] = this.bf.file.readUnsignedInt(); pageCounter[0] = 4; } - ksz = this.bf.file.readShort(); - vsz = this.bf.file.readShort(); + ksz = this.bf.file.readUnsignedShort(); + vsz = this.bf.file.readUnsignedShort(); pageCounter[0] +=4; byte[] k = new byte[ksz]; byte[] v = new byte[vsz]; @@ -170,20 +175,49 @@ public class IBSkipSpan extends BSkipSpan { curPage = this.bf.readMultiPageData(v, curPage, pageCounter, curNextPage); //System.out.println("i=" + i + ", Page " + curPage + ", offset " + pageCounter[0] + " ksz " + ksz + " vsz " + vsz); Comparable ckey = (Comparable) this.keySer.construct(k); + if (ckey == null) { + BlockFile.log.error("Null deserialized key in entry " + i + " page " + curPage); + fail++; + continue; + } int diff = ckey.compareTo(key); if (diff == 0) { //System.err.println("Found " + key + " at " + i + " (first: " + this.firstKey + ')'); - return this.valSer.construct(v); + Object rv = this.valSer.construct(v); + if (rv == null) { + BlockFile.log.error("Null deserialized value in entry " + i + " page " + curPage + + " key=" + ckey); + fail++; + } + if (fail > 0) + repair(fail); + return rv; } if (diff > 0) { //System.err.println("NOT Found " + key + " at " + i + " (first: " + this.firstKey + " current: " + ckey + ')'); + if (fail > 0) + repair(fail); return null; } } //System.err.println("NOT Found " + key + " at end (first: " + this.firstKey + ')'); + if (fail > 0) + repair(fail); return null; } + private void repair(int fail) { + try { + loadData(false); + if (this.nKeys > 0) + this.firstKey = this.keys[0]; + flush(); + BlockFile.log.error("Repaired corruption of " + fail + " entries"); + } catch (IOException ioe) { + BlockFile.log.error("Failed to repair corruption of " + fail + " entries", ioe); + } + } + protected IBSkipSpan() { } public IBSkipSpan(BlockFile bf, BSkipList bsl, int spanPage, Serializer key, Serializer val) throws IOException {