forked from I2P_Developers/i2p.i2p
- Better serializer error handling and logging
- Automatic corruption repair in blockfile - Automatic removal of bad entries in BFNS - Use unsigned shorts to extend max lengths to 65535 - Check max length - Throw IOE on negative ints - Tweak fromProperties() exceptions - Fix DataHelper encoding issues (ticket #436) - CSS tweaks
This commit is contained in:
@ -66,11 +66,11 @@ li {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tr.list1 {
|
tr.list1 {
|
||||||
background-color:#E0E0E0;
|
background-color:#E8E8EC;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.list0 {
|
tr.list0 {
|
||||||
background-color:white;
|
background-color:#F0F0F4;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.messages {
|
p.messages {
|
||||||
|
@ -183,7 +183,12 @@ public class NamingServiceBean extends AddressbookBean
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
String destination = entry.getValue().toBase64();
|
String destination = entry.getValue().toBase64();
|
||||||
|
if (destination != null) {
|
||||||
list.addLast( new AddressBean( name, destination ) );
|
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()]);
|
AddressBean array[] = list.toArray(new AddressBean[list.size()]);
|
||||||
Arrays.sort( array, sorter );
|
Arrays.sort( array, sorter );
|
||||||
|
@ -75,6 +75,7 @@ public class BlockfileNamingService extends DummyNamingService {
|
|||||||
private final BlockFile _bf;
|
private final BlockFile _bf;
|
||||||
private final RandomAccessFile _raf;
|
private final RandomAccessFile _raf;
|
||||||
private final List<String> _lists;
|
private final List<String> _lists;
|
||||||
|
private final List<InvalidEntry> _invalid;
|
||||||
private volatile boolean _isClosed;
|
private volatile boolean _isClosed;
|
||||||
|
|
||||||
private static final Serializer _infoSerializer = new PropertiesSerializer();
|
private static final Serializer _infoSerializer = new PropertiesSerializer();
|
||||||
@ -101,6 +102,7 @@ public class BlockfileNamingService extends DummyNamingService {
|
|||||||
public BlockfileNamingService(I2PAppContext context) {
|
public BlockfileNamingService(I2PAppContext context) {
|
||||||
super(context);
|
super(context);
|
||||||
_lists = new ArrayList();
|
_lists = new ArrayList();
|
||||||
|
_invalid = new ArrayList();
|
||||||
BlockFile bf = null;
|
BlockFile bf = null;
|
||||||
RandomAccessFile raf = null;
|
RandomAccessFile raf = null;
|
||||||
File f = new File(_context.getRouterDir(), HOSTS_DB);
|
File f = new File(_context.getRouterDir(), HOSTS_DB);
|
||||||
@ -375,8 +377,10 @@ public class BlockfileNamingService extends DummyNamingService {
|
|||||||
try {
|
try {
|
||||||
DestEntry de = getEntry(list, key);
|
DestEntry de = getEntry(list, key);
|
||||||
if (de != null) {
|
if (de != null) {
|
||||||
|
if (!validate(key, de, listname))
|
||||||
|
continue;
|
||||||
d = de.dest;
|
d = de.dest;
|
||||||
if (storedOptions != null)
|
if (storedOptions != null && de.props != null)
|
||||||
storedOptions.putAll(de.props);
|
storedOptions.putAll(de.props);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -384,6 +388,7 @@ public class BlockfileNamingService extends DummyNamingService {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
deleteInvalid();
|
||||||
}
|
}
|
||||||
if (d != null)
|
if (d != null)
|
||||||
putCache(hostname, d);
|
putCache(hostname, d);
|
||||||
@ -553,6 +558,7 @@ public class BlockfileNamingService extends DummyNamingService {
|
|||||||
iter = sl.iterator();
|
iter = sl.iterator();
|
||||||
Map<String, Destination> rv = new HashMap();
|
Map<String, Destination> rv = new HashMap();
|
||||||
for (int i = 0; i < skip && iter.hasNext(); i++) {
|
for (int i = 0; i < skip && iter.hasNext(); i++) {
|
||||||
|
// don't bother validating here
|
||||||
iter.next();
|
iter.next();
|
||||||
}
|
}
|
||||||
for (int i = 0; i < limit && iter.hasNext(); ) {
|
for (int i = 0; i < limit && iter.hasNext(); ) {
|
||||||
@ -566,6 +572,8 @@ public class BlockfileNamingService extends DummyNamingService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DestEntry de = (DestEntry) iter.next();
|
DestEntry de = (DestEntry) iter.next();
|
||||||
|
if (!validate(key, de, listname))
|
||||||
|
continue;
|
||||||
if (search != null && key.indexOf(search) < 0)
|
if (search != null && key.indexOf(search) < 0)
|
||||||
continue;
|
continue;
|
||||||
rv.put(key, de.dest);
|
rv.put(key, de.dest);
|
||||||
@ -578,6 +586,8 @@ public class BlockfileNamingService extends DummyNamingService {
|
|||||||
} catch (RuntimeException re) {
|
} catch (RuntimeException re) {
|
||||||
_log.error("DB lookup error", re);
|
_log.error("DB lookup error", re);
|
||||||
return Collections.EMPTY_MAP;
|
return Collections.EMPTY_MAP;
|
||||||
|
} finally {
|
||||||
|
deleteInvalid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -619,6 +629,60 @@ public class BlockfileNamingService extends DummyNamingService {
|
|||||||
|
|
||||||
////////// End NamingService API
|
////////// 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() {
|
private void dumpDB() {
|
||||||
synchronized(_bf) {
|
synchronized(_bf) {
|
||||||
if (_isClosed)
|
if (_isClosed)
|
||||||
@ -634,6 +698,8 @@ public class BlockfileNamingService extends DummyNamingService {
|
|||||||
for (SkipIterator iter = sl.iterator(); iter.hasNext(); ) {
|
for (SkipIterator iter = sl.iterator(); iter.hasNext(); ) {
|
||||||
String key = (String) iter.nextKey();
|
String key = (String) iter.nextKey();
|
||||||
DestEntry de = (DestEntry) iter.next();
|
DestEntry de = (DestEntry) iter.next();
|
||||||
|
if (!validate(key, de, list))
|
||||||
|
continue;
|
||||||
_log.error("DB " + list + " key " + key + " val " + de);
|
_log.error("DB " + list + " key " + key + " val " + de);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
@ -643,6 +709,7 @@ public class BlockfileNamingService extends DummyNamingService {
|
|||||||
break;
|
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 {
|
private class Shutdown implements Runnable {
|
||||||
public void run() {
|
public void run() {
|
||||||
close();
|
close();
|
||||||
@ -691,24 +763,33 @@ public class BlockfileNamingService extends DummyNamingService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for the values in the header skiplist
|
* 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 {
|
private static class PropertiesSerializer implements Serializer {
|
||||||
|
/**
|
||||||
|
* A format error on the properties is non-fatal (returns an empty properties)
|
||||||
|
*/
|
||||||
public byte[] getBytes(Object o) {
|
public byte[] getBytes(Object o) {
|
||||||
Properties p = (Properties) o;
|
Properties p = (Properties) o;
|
||||||
try {
|
try {
|
||||||
return DataHelper.toProperties(p);
|
return DataHelper.toProperties(p);
|
||||||
} catch (DataFormatException dfe) {
|
} 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) {
|
public Object construct(byte[] b) {
|
||||||
Properties rv = new Properties();
|
Properties rv = new Properties();
|
||||||
try {
|
try {
|
||||||
DataHelper.fromProperties(b, 0, rv);
|
DataHelper.fromProperties(b, 0, rv);
|
||||||
} catch (IOException ioe) {
|
|
||||||
return null;
|
|
||||||
} catch (DataFormatException dfe) {
|
} catch (DataFormatException dfe) {
|
||||||
|
logError("DB Read Fail", dfe);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return rv;
|
return rv;
|
||||||
@ -734,22 +815,38 @@ public class BlockfileNamingService extends DummyNamingService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for the values in the addressbook skiplists
|
* 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 {
|
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) {
|
public byte[] getBytes(Object o) {
|
||||||
DestEntry de = (DestEntry) o;
|
DestEntry de = (DestEntry) o;
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
|
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
|
||||||
try {
|
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);
|
de.dest.writeBytes(baos);
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
return null;
|
logError("DB Write Fail", ioe);
|
||||||
} catch (DataFormatException dfe) {
|
} catch (DataFormatException dfe) {
|
||||||
return null;
|
logError("DB Write Fail", dfe);
|
||||||
}
|
}
|
||||||
return baos.toByteArray();
|
return baos.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** returns null on error */
|
||||||
public Object construct(byte[] b) {
|
public Object construct(byte[] b) {
|
||||||
DestEntry rv = new DestEntry();
|
DestEntry rv = new DestEntry();
|
||||||
Destination dest = new Destination();
|
Destination dest = new Destination();
|
||||||
@ -759,14 +856,29 @@ public class BlockfileNamingService extends DummyNamingService {
|
|||||||
rv.props = DataHelper.readProperties(bais);
|
rv.props = DataHelper.readProperties(bais);
|
||||||
dest.readBytes(bais);
|
dest.readBytes(bais);
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
|
logError("DB Read Fail", ioe);
|
||||||
return null;
|
return null;
|
||||||
} catch (DataFormatException dfe) {
|
} catch (DataFormatException dfe) {
|
||||||
|
logError("DB Read Fail", dfe);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return rv;
|
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) {
|
public static void main(String[] args) {
|
||||||
BlockfileNamingService bns = new BlockfileNamingService(I2PAppContext.getGlobalContext());
|
BlockfileNamingService bns = new BlockfileNamingService(I2PAppContext.getGlobalContext());
|
||||||
List<String> names = null;
|
List<String> names = null;
|
||||||
|
@ -54,8 +54,16 @@ import net.i2p.util.Translate;
|
|||||||
* @author jrandom
|
* @author jrandom
|
||||||
*/
|
*/
|
||||||
public class DataHelper {
|
public class DataHelper {
|
||||||
private final static byte EQUAL_BYTES[] = "=".getBytes(); // in UTF-8
|
private static final byte EQUAL_BYTES[];
|
||||||
private final static byte SEMICOLON_BYTES[] = ";".getBytes(); // in UTF-8
|
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,
|
/** Read a mapping from the stream, as defined by the I2P data structure spec,
|
||||||
* and store it into a Properties object.
|
* and store it into a Properties object.
|
||||||
@ -80,7 +88,7 @@ public class DataHelper {
|
|||||||
long size = readLong(rawStream, 2);
|
long size = readLong(rawStream, 2);
|
||||||
byte data[] = new byte[(int) size];
|
byte data[] = new byte[(int) size];
|
||||||
int read = read(rawStream, data);
|
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);
|
ByteArrayInputStream in = new ByteArrayInputStream(data);
|
||||||
byte eqBuf[] = new byte[EQUAL_BYTES.length];
|
byte eqBuf[] = new byte[EQUAL_BYTES.length];
|
||||||
byte semiBuf[] = new byte[SEMICOLON_BYTES.length];
|
byte semiBuf[] = new byte[SEMICOLON_BYTES.length];
|
||||||
@ -251,23 +259,33 @@ public class DataHelper {
|
|||||||
* @param target returned Properties
|
* @param target returned Properties
|
||||||
* @return new offset
|
* @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);
|
int size = (int)fromLong(source, offset, 2);
|
||||||
offset += 2;
|
offset += 2;
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream(source, offset, size);
|
ByteArrayInputStream in = new ByteArrayInputStream(source, offset, size);
|
||||||
byte eqBuf[] = new byte[EQUAL_BYTES.length];
|
byte eqBuf[] = new byte[EQUAL_BYTES.length];
|
||||||
byte semiBuf[] = new byte[SEMICOLON_BYTES.length];
|
byte semiBuf[] = new byte[SEMICOLON_BYTES.length];
|
||||||
while (in.available() > 0) {
|
while (in.available() > 0) {
|
||||||
String key = readString(in);
|
String key;
|
||||||
|
try {
|
||||||
|
key = readString(in);
|
||||||
int read = read(in, eqBuf);
|
int read = read(in, eqBuf);
|
||||||
if ((read != eqBuf.length) || (!eq(eqBuf, EQUAL_BYTES))) {
|
if ((read != eqBuf.length) || (!eq(eqBuf, EQUAL_BYTES))) {
|
||||||
throw new DataFormatException("Bad key");
|
throw new DataFormatException("Bad key");
|
||||||
}
|
}
|
||||||
String val = readString(in);
|
} catch (IOException ioe) {
|
||||||
read = read(in, semiBuf);
|
throw new DataFormatException("Bad key", ioe);
|
||||||
|
}
|
||||||
|
String val;
|
||||||
|
try {
|
||||||
|
val = readString(in);
|
||||||
|
int read = read(in, semiBuf);
|
||||||
if ((read != semiBuf.length) || (!eq(semiBuf, SEMICOLON_BYTES))) {
|
if ((read != semiBuf.length) || (!eq(semiBuf, SEMICOLON_BYTES))) {
|
||||||
throw new DataFormatException("Bad value");
|
throw new DataFormatException("Bad value");
|
||||||
}
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
throw new DataFormatException("Bad value", ioe);
|
||||||
|
}
|
||||||
target.put(key, val);
|
target.put(key, val);
|
||||||
}
|
}
|
||||||
return offset + size;
|
return offset + size;
|
||||||
|
@ -82,6 +82,17 @@ public class RAIFile implements RandomAccessInterface, DataInput, DataOutput {
|
|||||||
public int readUnsignedByte() throws IOException { return delegate.readUnsignedByte(); }
|
public int readUnsignedByte() throws IOException { return delegate.readUnsignedByte(); }
|
||||||
public int readUnsignedShort() throws IOException { return delegate.readUnsignedShort(); }
|
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
|
/** Read a UTF encoded string
|
||||||
I would delegate here. But Java's read/writeUTF combo suck.
|
I would delegate here. But Java's read/writeUTF combo suck.
|
||||||
A signed 2 byte length is not enough.
|
A signed 2 byte length is not enough.
|
||||||
|
@ -56,6 +56,8 @@ public interface RandomAccessInterface {
|
|||||||
public short readShort() throws IOException;
|
public short readShort() throws IOException;
|
||||||
public int readUnsignedByte() throws IOException;
|
public int readUnsignedByte() throws IOException;
|
||||||
public int readUnsignedShort() throws IOException;
|
public int readUnsignedShort() throws IOException;
|
||||||
|
// I2P
|
||||||
|
public int readUnsignedInt() throws IOException;
|
||||||
public String readUTF() throws IOException;
|
public String readUTF() throws IOException;
|
||||||
public int skipBytes(int n) throws IOException;
|
public int skipBytes(int n) throws IOException;
|
||||||
|
|
||||||
|
@ -42,10 +42,12 @@ import net.metanotion.io.data.IntBytes;
|
|||||||
import net.metanotion.io.data.LongBytes;
|
import net.metanotion.io.data.LongBytes;
|
||||||
import net.metanotion.io.data.NullBytes;
|
import net.metanotion.io.data.NullBytes;
|
||||||
import net.metanotion.io.data.StringBytes;
|
import net.metanotion.io.data.StringBytes;
|
||||||
|
|
||||||
import net.metanotion.io.block.index.BSkipList;
|
import net.metanotion.io.block.index.BSkipList;
|
||||||
import net.metanotion.util.skiplist.SkipList;
|
import net.metanotion.util.skiplist.SkipList;
|
||||||
|
|
||||||
|
import net.i2p.I2PAppContext;
|
||||||
|
import net.i2p.util.Log;
|
||||||
|
|
||||||
class CorruptFileException extends IOException { }
|
class CorruptFileException extends IOException { }
|
||||||
class BadFileFormatException extends IOException { }
|
class BadFileFormatException extends IOException { }
|
||||||
class BadVersionException extends IOException { }
|
class BadVersionException extends IOException { }
|
||||||
@ -53,14 +55,15 @@ class BadVersionException extends IOException { }
|
|||||||
public class BlockFile {
|
public class BlockFile {
|
||||||
public static final long PAGESIZE = 1024;
|
public static final long PAGESIZE = 1024;
|
||||||
public static final long OFFSET_MOUNTED = 20;
|
public static final long OFFSET_MOUNTED = 20;
|
||||||
|
public static final Log log = I2PAppContext.getGlobalContext().logManager().getLog(BlockFile.class);
|
||||||
|
|
||||||
public RandomAccessInterface file;
|
public RandomAccessInterface file;
|
||||||
|
|
||||||
private long magicBytes = 0x3141deadbeef0100L;
|
private long magicBytes = 0x3141deadbeef0100L;
|
||||||
private long fileLen = PAGESIZE * 2;
|
private long fileLen = PAGESIZE * 2;
|
||||||
private int freeListStart = 0;
|
private int freeListStart = 0;
|
||||||
private short mounted = 0;
|
private int mounted = 0;
|
||||||
public short spanSize = 16;
|
public int spanSize = 16;
|
||||||
|
|
||||||
private BSkipList metaIndex = null;
|
private BSkipList metaIndex = null;
|
||||||
private HashMap openIndices = new HashMap();
|
private HashMap openIndices = new HashMap();
|
||||||
@ -84,9 +87,9 @@ public class BlockFile {
|
|||||||
file.seek(0);
|
file.seek(0);
|
||||||
magicBytes = file.readLong();
|
magicBytes = file.readLong();
|
||||||
fileLen = file.readLong();
|
fileLen = file.readLong();
|
||||||
freeListStart = file.readInt();
|
freeListStart = file.readUnsignedInt();
|
||||||
mounted = file.readShort();
|
mounted = file.readUnsignedShort();
|
||||||
spanSize = file.readShort();
|
spanSize = file.readUnsignedShort();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String args[]) {
|
public static void main(String args[]) {
|
||||||
@ -125,7 +128,7 @@ public class BlockFile {
|
|||||||
}
|
}
|
||||||
BlockFile.pageSeek(this.file, curNextPage);
|
BlockFile.pageSeek(this.file, curNextPage);
|
||||||
curPage = curNextPage;
|
curPage = curNextPage;
|
||||||
curNextPage = this.file.readInt();
|
curNextPage = this.file.readUnsignedInt();
|
||||||
pageCounter = 4;
|
pageCounter = 4;
|
||||||
len = ((int) BlockFile.PAGESIZE) - pageCounter;
|
len = ((int) BlockFile.PAGESIZE) - pageCounter;
|
||||||
}
|
}
|
||||||
@ -149,7 +152,7 @@ public class BlockFile {
|
|||||||
if(len <= 0) {
|
if(len <= 0) {
|
||||||
BlockFile.pageSeek(this.file, curNextPage);
|
BlockFile.pageSeek(this.file, curNextPage);
|
||||||
curPage = curNextPage;
|
curPage = curNextPage;
|
||||||
curNextPage = this.file.readInt();
|
curNextPage = this.file.readUnsignedInt();
|
||||||
pageCounter = 4;
|
pageCounter = 4;
|
||||||
len = ((int) BlockFile.PAGESIZE) - pageCounter;
|
len = ((int) BlockFile.PAGESIZE) - pageCounter;
|
||||||
}
|
}
|
||||||
|
@ -43,12 +43,12 @@ public class FreeListBlock {
|
|||||||
this.file = file;
|
this.file = file;
|
||||||
this.page = startPage;
|
this.page = startPage;
|
||||||
BlockFile.pageSeek(file, startPage);
|
BlockFile.pageSeek(file, startPage);
|
||||||
nextPage = file.readInt();
|
nextPage = file.readUnsignedInt();
|
||||||
len = file.readInt();
|
len = file.readUnsignedInt();
|
||||||
if(len > 0) {
|
if(len > 0) {
|
||||||
branches = new int[len];
|
branches = new int[len];
|
||||||
for(int i=0;i<len;i++) {
|
for(int i=0;i<len;i++) {
|
||||||
branches[i] = file.readInt();
|
branches[i] = file.readUnsignedInt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,15 +50,15 @@ public class BSkipLevels extends SkipLevels {
|
|||||||
|
|
||||||
bsl.levelHash.put(new Integer(this.levelPage), this);
|
bsl.levelHash.put(new Integer(this.levelPage), this);
|
||||||
|
|
||||||
int maxLen = bf.file.readShort();
|
int maxLen = bf.file.readUnsignedShort();
|
||||||
int nonNull = bf.file.readShort();
|
int nonNull = bf.file.readUnsignedShort();
|
||||||
spanPage = bf.file.readInt();
|
spanPage = bf.file.readUnsignedInt();
|
||||||
bottom = (BSkipSpan) bsl.spanHash.get(new Integer(spanPage));
|
bottom = (BSkipSpan) bsl.spanHash.get(new Integer(spanPage));
|
||||||
|
|
||||||
this.levels = new BSkipLevels[maxLen];
|
this.levels = new BSkipLevels[maxLen];
|
||||||
int lp;
|
int lp;
|
||||||
for(int i=0;i<nonNull;i++) {
|
for(int i=0;i<nonNull;i++) {
|
||||||
lp = bf.file.readInt();
|
lp = bf.file.readUnsignedInt();
|
||||||
if(lp != 0) {
|
if(lp != 0) {
|
||||||
levels[i] = (BSkipLevels) bsl.levelHash.get(new Integer(lp));
|
levels[i] = (BSkipLevels) bsl.levelHash.get(new Integer(lp));
|
||||||
if(levels[i] == null) {
|
if(levels[i] == null) {
|
||||||
|
@ -59,10 +59,10 @@ public class BSkipList extends SkipList {
|
|||||||
this.bf = bf;
|
this.bf = bf;
|
||||||
|
|
||||||
BlockFile.pageSeek(bf.file, skipPage);
|
BlockFile.pageSeek(bf.file, skipPage);
|
||||||
firstSpanPage = bf.file.readInt();
|
firstSpanPage = bf.file.readUnsignedInt();
|
||||||
firstLevelPage = bf.file.readInt();
|
firstLevelPage = bf.file.readUnsignedInt();
|
||||||
size = bf.file.readInt();
|
size = bf.file.readUnsignedInt();
|
||||||
spans = bf.file.readInt();
|
spans = bf.file.readUnsignedInt();
|
||||||
//System.out.println(size + " " + spans);
|
//System.out.println(size + " " + spans);
|
||||||
|
|
||||||
this.fileOnly = fileOnly;
|
this.fileOnly = fileOnly;
|
||||||
|
@ -74,7 +74,7 @@ public class BSkipSpan extends SkipSpan {
|
|||||||
int next;
|
int next;
|
||||||
while(curPage != 0) {
|
while(curPage != 0) {
|
||||||
BlockFile.pageSeek(bf.file, curPage);
|
BlockFile.pageSeek(bf.file, curPage);
|
||||||
next = bf.file.readInt();
|
next = bf.file.readUnsignedInt();
|
||||||
bf.freePage(curPage);
|
bf.freePage(curPage);
|
||||||
curPage = next;
|
curPage = next;
|
||||||
}
|
}
|
||||||
@ -83,6 +83,13 @@ public class BSkipSpan extends SkipSpan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void flush() {
|
public void flush() {
|
||||||
|
fflush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* I2P - avoid super.flush()
|
||||||
|
*/
|
||||||
|
private void fflush() {
|
||||||
try {
|
try {
|
||||||
BlockFile.pageSeek(bf.file, page);
|
BlockFile.pageSeek(bf.file, page);
|
||||||
bf.file.writeInt(overflowPage);
|
bf.file.writeInt(overflowPage);
|
||||||
@ -111,11 +118,27 @@ public class BSkipSpan extends SkipSpan {
|
|||||||
}
|
}
|
||||||
BlockFile.pageSeek(bf.file, curNextPage[0]);
|
BlockFile.pageSeek(bf.file, curNextPage[0]);
|
||||||
curPage = curNextPage[0];
|
curPage = curNextPage[0];
|
||||||
curNextPage[0] = bf.file.readInt();
|
curNextPage[0] = bf.file.readUnsignedInt();
|
||||||
pageCounter[0] = 4;
|
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]);
|
keyData = this.keySer.getBytes(keys[i]);
|
||||||
valData = this.valSer.getBytes(vals[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;
|
pageCounter[0] += 4;
|
||||||
bf.file.writeShort(keyData.length);
|
bf.file.writeShort(keyData.length);
|
||||||
bf.file.writeShort(valData.length);
|
bf.file.writeShort(valData.length);
|
||||||
@ -123,8 +146,11 @@ public class BSkipSpan extends SkipSpan {
|
|||||||
curPage = bf.writeMultiPageData(valData, curPage, pageCounter, curNextPage);
|
curPage = bf.writeMultiPageData(valData, curPage, pageCounter, curNextPage);
|
||||||
}
|
}
|
||||||
BlockFile.pageSeek(bf.file, this.page);
|
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); }
|
} 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 {
|
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);
|
BlockFile.pageSeek(bf.file, spanPage);
|
||||||
|
|
||||||
bss.overflowPage = bf.file.readInt();
|
bss.overflowPage = bf.file.readUnsignedInt();
|
||||||
bss.prevPage = bf.file.readInt();
|
bss.prevPage = bf.file.readUnsignedInt();
|
||||||
bss.nextPage = bf.file.readInt();
|
bss.nextPage = bf.file.readUnsignedInt();
|
||||||
bss.spanSize = bf.file.readShort();
|
bss.spanSize = bf.file.readUnsignedShort();
|
||||||
bss.nKeys = bf.file.readShort();
|
bss.nKeys = bf.file.readUnsignedShort();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -158,6 +184,15 @@ public class BSkipSpan extends SkipSpan {
|
|||||||
* Load the whole span's keys and values into memory
|
* Load the whole span's keys and values into memory
|
||||||
*/
|
*/
|
||||||
protected void loadData() throws IOException {
|
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.keys = new Comparable[this.spanSize];
|
||||||
this.vals = new Object[this.spanSize];
|
this.vals = new Object[this.spanSize];
|
||||||
|
|
||||||
@ -168,15 +203,16 @@ public class BSkipSpan extends SkipSpan {
|
|||||||
int[] pageCounter = new int[1];
|
int[] pageCounter = new int[1];
|
||||||
pageCounter[0] = 16;
|
pageCounter[0] = 16;
|
||||||
// System.out.println("Span Load " + sz + " nKeys " + nKeys + " page " + curPage);
|
// System.out.println("Span Load " + sz + " nKeys " + nKeys + " page " + curPage);
|
||||||
|
int fail = 0;
|
||||||
for(int i=0;i<this.nKeys;i++) {
|
for(int i=0;i<this.nKeys;i++) {
|
||||||
if((pageCounter[0] + 4) > BlockFile.PAGESIZE) {
|
if((pageCounter[0] + 4) > BlockFile.PAGESIZE) {
|
||||||
BlockFile.pageSeek(this.bf.file, curNextPage[0]);
|
BlockFile.pageSeek(this.bf.file, curNextPage[0]);
|
||||||
curPage = curNextPage[0];
|
curPage = curNextPage[0];
|
||||||
curNextPage[0] = this.bf.file.readInt();
|
curNextPage[0] = this.bf.file.readUnsignedInt();
|
||||||
pageCounter[0] = 4;
|
pageCounter[0] = 4;
|
||||||
}
|
}
|
||||||
ksz = this.bf.file.readShort();
|
ksz = this.bf.file.readUnsignedShort();
|
||||||
vsz = this.bf.file.readShort();
|
vsz = this.bf.file.readUnsignedShort();
|
||||||
pageCounter[0] +=4;
|
pageCounter[0] +=4;
|
||||||
byte[] k = new byte[ksz];
|
byte[] k = new byte[ksz];
|
||||||
byte[] v = new byte[vsz];
|
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);
|
// System.out.println("i=" + i + ", Page " + curPage + ", offset " + pageCounter[0] + " ksz " + ksz + " vsz " + vsz);
|
||||||
this.keys[i] = (Comparable) this.keySer.construct(k);
|
this.keys[i] = (Comparable) this.keySer.construct(k);
|
||||||
this.vals[i] = this.valSer.construct(v);
|
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() { }
|
protected BSkipSpan() { }
|
||||||
|
@ -114,12 +114,16 @@ public class IBSkipSpan extends BSkipSpan {
|
|||||||
curNextPage[0] = this.overflowPage;
|
curNextPage[0] = this.overflowPage;
|
||||||
int[] pageCounter = new int[1];
|
int[] pageCounter = new int[1];
|
||||||
pageCounter[0] = 16;
|
pageCounter[0] = 16;
|
||||||
ksz = this.bf.file.readShort();
|
ksz = this.bf.file.readUnsignedShort();
|
||||||
this.bf.file.skipBytes(2); //vsz
|
this.bf.file.skipBytes(2); //vsz
|
||||||
pageCounter[0] +=4;
|
pageCounter[0] +=4;
|
||||||
byte[] k = new byte[ksz];
|
byte[] k = new byte[ksz];
|
||||||
curPage = this.bf.readMultiPageData(k, curPage, pageCounter, curNextPage);
|
curPage = this.bf.readMultiPageData(k, curPage, pageCounter, curNextPage);
|
||||||
this.firstKey = (Comparable) this.keySer.construct(k);
|
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)
|
if (DEBUG)
|
||||||
System.err.println("Loaded header for page " + this.page + " containing " + this.nKeys + '/' + this.spanSize + " first key: " + this.firstKey);
|
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;
|
curNextPage[0] = this.overflowPage;
|
||||||
int[] pageCounter = new int[1];
|
int[] pageCounter = new int[1];
|
||||||
pageCounter[0] = 16;
|
pageCounter[0] = 16;
|
||||||
|
int fail = 0;
|
||||||
//System.out.println("Span Load " + sz + " nKeys " + nKeys + " page " + curPage);
|
//System.out.println("Span Load " + sz + " nKeys " + nKeys + " page " + curPage);
|
||||||
for(int i=0;i<this.nKeys;i++) {
|
for(int i=0;i<this.nKeys;i++) {
|
||||||
if((pageCounter[0] + 4) > BlockFile.PAGESIZE) {
|
if((pageCounter[0] + 4) > BlockFile.PAGESIZE) {
|
||||||
BlockFile.pageSeek(this.bf.file, curNextPage[0]);
|
BlockFile.pageSeek(this.bf.file, curNextPage[0]);
|
||||||
curPage = curNextPage[0];
|
curPage = curNextPage[0];
|
||||||
curNextPage[0] = this.bf.file.readInt();
|
curNextPage[0] = this.bf.file.readUnsignedInt();
|
||||||
pageCounter[0] = 4;
|
pageCounter[0] = 4;
|
||||||
}
|
}
|
||||||
ksz = this.bf.file.readShort();
|
ksz = this.bf.file.readUnsignedShort();
|
||||||
vsz = this.bf.file.readShort();
|
vsz = this.bf.file.readUnsignedShort();
|
||||||
pageCounter[0] +=4;
|
pageCounter[0] +=4;
|
||||||
byte[] k = new byte[ksz];
|
byte[] k = new byte[ksz];
|
||||||
byte[] v = new byte[vsz];
|
byte[] v = new byte[vsz];
|
||||||
@ -170,20 +175,49 @@ public class IBSkipSpan extends BSkipSpan {
|
|||||||
curPage = this.bf.readMultiPageData(v, curPage, pageCounter, curNextPage);
|
curPage = this.bf.readMultiPageData(v, curPage, pageCounter, curNextPage);
|
||||||
//System.out.println("i=" + i + ", Page " + curPage + ", offset " + pageCounter[0] + " ksz " + ksz + " vsz " + vsz);
|
//System.out.println("i=" + i + ", Page " + curPage + ", offset " + pageCounter[0] + " ksz " + ksz + " vsz " + vsz);
|
||||||
Comparable ckey = (Comparable) this.keySer.construct(k);
|
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);
|
int diff = ckey.compareTo(key);
|
||||||
if (diff == 0) {
|
if (diff == 0) {
|
||||||
//System.err.println("Found " + key + " at " + i + " (first: " + this.firstKey + ')');
|
//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) {
|
if (diff > 0) {
|
||||||
//System.err.println("NOT Found " + key + " at " + i + " (first: " + this.firstKey + " current: " + ckey + ')');
|
//System.err.println("NOT Found " + key + " at " + i + " (first: " + this.firstKey + " current: " + ckey + ')');
|
||||||
|
if (fail > 0)
|
||||||
|
repair(fail);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//System.err.println("NOT Found " + key + " at end (first: " + this.firstKey + ')');
|
//System.err.println("NOT Found " + key + " at end (first: " + this.firstKey + ')');
|
||||||
|
if (fail > 0)
|
||||||
|
repair(fail);
|
||||||
return null;
|
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() { }
|
protected IBSkipSpan() { }
|
||||||
|
|
||||||
public IBSkipSpan(BlockFile bf, BSkipList bsl, int spanPage, Serializer key, Serializer val) throws IOException {
|
public IBSkipSpan(BlockFile bf, BSkipList bsl, int spanPage, Serializer key, Serializer val) throws IOException {
|
||||||
|
Reference in New Issue
Block a user