提交 cd833fcb authored 作者: Thomas Mueller's avatar Thomas Mueller

MVStore: read-only encrypted files with an old read version

上级 b7fe8ef5
...@@ -56,7 +56,6 @@ TestMVStoreDataLoss ...@@ -56,7 +56,6 @@ TestMVStoreDataLoss
TransactionStore: TransactionStore:
MVStore: MVStore:
- in-place compact (first, move chunks to end of file, then move to start)
- automated 'kill process' and 'power failure' test - automated 'kill process' and 'power failure' test
- update checkstyle - update checkstyle
- maybe split database into multiple files, to speed up compact - maybe split database into multiple files, to speed up compact
...@@ -76,17 +75,20 @@ MVStore: ...@@ -76,17 +75,20 @@ MVStore:
- maybe let a chunk point to a list of potential next chunks - maybe let a chunk point to a list of potential next chunks
(so no fixed location header is needed), similar to a skip list (so no fixed location header is needed), similar to a skip list
- support stores that span multiple files (chunks stored in other files) - support stores that span multiple files (chunks stored in other files)
- triggers (can be implemented with a custom map) - triggers (can be implemented with a custom map);
maybe implement database indexing with triggers
- store number of write operations per page (maybe defragment - store number of write operations per page (maybe defragment
if much different than count) if much different than count)
- r-tree: nearest neighbor search - r-tree: nearest neighbor search
- support maps without values (just existence of the key) - support maps without values (just existence of the key)
- support maps without keys (counted b-tree features) - support maps without keys (counted b-tree features)
- use a small object cache (StringCache), test on Android - use a small object value cache (StringCache), test on Android
- dump values (using a callback) for default serialization
- MVStoreTool.dump: dump values (using a callback)
- map split / merge (fast if no overlap) - map split / merge (fast if no overlap)
- StreamStore optimization: avoid copying bytes (use case: partitioning)
- MVStoreTool.shrink to shrink a store (create, copy, rename, delete) - StreamStore optimization: avoid copying bytes in memory
- Feature shrink a store (create, copy, rename, delete)
and for MVStore on Windows, auto-detect renamed file and for MVStore on Windows, auto-detect renamed file
- ensure data is overwritten eventually if the system doesn't have a timer - ensure data is overwritten eventually if the system doesn't have a timer
- SSD-friendly write (always in blocks of 4 MB / 1 second?) - SSD-friendly write (always in blocks of 4 MB / 1 second?)
...@@ -94,8 +96,7 @@ MVStore: ...@@ -94,8 +96,7 @@ MVStore:
- implement a sharded map (in one store, multiple stores) - implement a sharded map (in one store, multiple stores)
to support concurrent updates and writes, and very large maps to support concurrent updates and writes, and very large maps
- implement an off-heap file system - implement an off-heap file system
- remove change cursor, or add support for writing to branches - add support for writing to branches
- support pluggable logging or remove log
- maybe add an optional finalizer and exit hook - maybe add an optional finalizer and exit hook
to store committed changes to store committed changes
- to save space when persisting very small transactions, - to save space when persisting very small transactions,
...@@ -119,7 +120,6 @@ MVStore: ...@@ -119,7 +120,6 @@ MVStore:
- to save space for small chunks, combine the last partial - to save space for small chunks, combine the last partial
block with the header block with the header
- off-heap storage (with lower default retention time) - off-heap storage (with lower default retention time)
- object value cache for default serialization
- temporary file storage - temporary file storage
- simple rollback method (rollback to last committed version) - simple rollback method (rollback to last committed version)
- MVMap to implement SortedMap, then NavigableMap - MVMap to implement SortedMap, then NavigableMap
...@@ -483,10 +483,10 @@ public class MVStore { ...@@ -483,10 +483,10 @@ public class MVStore {
} }
try { try {
if (readOnly) { if (readOnly) {
openFile(); openFile(true);
} else if (!openFile()) { } else if (!openFile(false)) {
readOnly = true; readOnly = true;
openFile(); openFile(true);
} }
} finally { } finally {
if (filePassword != null) { if (filePassword != null) {
...@@ -500,7 +500,7 @@ public class MVStore { ...@@ -500,7 +500,7 @@ public class MVStore {
rollbackTo(rollback); rollbackTo(rollback);
} }
this.lastCommittedVersion = currentVersion; this.lastCommittedVersion = currentVersion;
// setWriteDelay starts the thread, but only if // setWriteDelay starts the thread, but only if
// the parameter is different than the current value // the parameter is different than the current value
setWriteDelay(1000); setWriteDelay(1000);
...@@ -510,18 +510,17 @@ public class MVStore { ...@@ -510,18 +510,17 @@ public class MVStore {
* Try to open the file in read or write mode. * Try to open the file in read or write mode.
* *
* @return if opening the file was successful, and false if the file could * @return if opening the file was successful, and false if the file could
* not be opened in write mode because the write file format it too * not be opened in write mode because the write file format is too
* high (in which case the file can be opened in read-only mode) * high (in which case the file can be opened in read-only mode)
* @throw IllegalStateException if the file could not be opened * @throw IllegalStateException if the file could not be opened
* because of an IOException or file format error * because of an IOException or file format error
*/ */
private boolean openFile() { private boolean openFile(boolean readOnly) {
IllegalStateException exception; IllegalStateException exception;
try { try {
log("file open");
FilePath f = FilePath.get(fileName); FilePath f = FilePath.get(fileName);
if (f.exists() && !f.canWrite()) { if (f.exists() && !f.canWrite()) {
readOnly = true; return false;
} }
file = f.open(readOnly ? "r" : "rw"); file = f.open(readOnly ? "r" : "rw");
if (filePassword != null) { if (filePassword != null) {
...@@ -564,8 +563,7 @@ public class MVStore { ...@@ -564,8 +563,7 @@ public class MVStore {
"The file format {0} is larger than the supported format {1}", "The file format {0} is larger than the supported format {1}",
formatRead, FORMAT_READ); formatRead, FORMAT_READ);
} }
if (formatWrite > FORMAT_WRITE) { if (formatWrite > FORMAT_WRITE && !readOnly) {
readOnly = true;
file.close(); file.close();
return false; return false;
} }
...@@ -573,7 +571,7 @@ public class MVStore { ...@@ -573,7 +571,7 @@ public class MVStore {
readMeta(); readMeta();
} }
} }
exception = null; return true;
} catch (IOException e) { } catch (IOException e) {
exception = DataUtils.newIllegalStateException( exception = DataUtils.newIllegalStateException(
DataUtils.ERROR_READING_FAILED, DataUtils.ERROR_READING_FAILED,
...@@ -581,18 +579,14 @@ public class MVStore { ...@@ -581,18 +579,14 @@ public class MVStore {
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
exception = e; exception = e;
} }
if (exception != null) { try {
try { closeFile(false);
closeFile(false); } catch (Exception e2) {
} catch (Exception e2) { // ignore
// ignore
}
throw exception;
} }
return true; throw exception;
} }
private void readMeta() { private void readMeta() {
Chunk header = readChunkHeader(rootChunkStart); Chunk header = readChunkHeader(rootChunkStart);
lastChunkId = header.id; lastChunkId = header.id;
...@@ -775,7 +769,6 @@ public class MVStore { ...@@ -775,7 +769,6 @@ public class MVStore {
if (shrinkIfPossible) { if (shrinkIfPossible) {
shrinkFileIfPossible(0); shrinkFileIfPossible(0);
} }
log("file close");
if (fileLock != null) { if (fileLock != null) {
fileLock.release(); fileLock.release();
fileLock = null; fileLock = null;
...@@ -1229,7 +1222,7 @@ public class MVStore { ...@@ -1229,7 +1222,7 @@ public class MVStore {
* free space between chunks. This might temporarily double the file size. * free space between chunks. This might temporarily double the file size.
* Chunks are overwritten irrespective of the current retention time. Before * Chunks are overwritten irrespective of the current retention time. Before
* overwriting chunks and before resizing the file, syncFile() is called. * overwriting chunks and before resizing the file, syncFile() is called.
* *
* @return if anything was written * @return if anything was written
*/ */
public synchronized boolean compactMoveChunks() { public synchronized boolean compactMoveChunks() {
...@@ -1290,11 +1283,11 @@ public class MVStore { ...@@ -1290,11 +1283,11 @@ public class MVStore {
meta.put("chunk." + c.id, c.asString()); meta.put("chunk." + c.id, c.asString());
} }
boolean oldReuse = reuseSpace; boolean oldReuse = reuseSpace;
// update the metadata (store at the end of the file) // update the metadata (store at the end of the file)
reuseSpace = false; reuseSpace = false;
store(); store();
syncFile(); syncFile();
// now re-use the empty space // now re-use the empty space
...@@ -1333,7 +1326,7 @@ public class MVStore { ...@@ -1333,7 +1326,7 @@ public class MVStore {
return true; return true;
} }
/** /**
* Force all changes to be written to the file. The default implementation * Force all changes to be written to the file. The default implementation
* calls FileChannel.force(true). * calls FileChannel.force(true).
...@@ -1416,7 +1409,6 @@ public class MVStore { ...@@ -1416,7 +1409,6 @@ public class MVStore {
if (move != null && moved + c.maxLengthLive > averageMaxLength) { if (move != null && moved + c.maxLengthLive > averageMaxLength) {
break; break;
} }
log(" chunk " + c.id + " " + c.getFillRate() + "% full; prio=" + c.collectPriority);
moved += c.maxLengthLive; moved += c.maxLengthLive;
move = c; move = c;
} }
...@@ -1476,7 +1468,6 @@ public class MVStore { ...@@ -1476,7 +1468,6 @@ public class MVStore {
} else { } else {
Chunk c = getChunk(p.getPos()); Chunk c = getChunk(p.getPos());
if (old.contains(c)) { if (old.contains(c)) {
log(" move key:" + k + " chunk:" + c.id);
Object value = map.remove(k); Object value = map.remove(k);
map.put(k, value); map.put(k, value);
} }
...@@ -1568,16 +1559,6 @@ public class MVStore { ...@@ -1568,16 +1559,6 @@ public class MVStore {
} }
} }
/**
* Log the string, if logging is enabled.
*
* @param string the string to log
*/
void log(String string) {
// TODO logging
// System.out.println(string);
}
Compressor getCompressor() { Compressor getCompressor() {
return compressor; return compressor;
} }
...@@ -2051,7 +2032,7 @@ public class MVStore { ...@@ -2051,7 +2032,7 @@ public class MVStore {
// ignore // ignore
} }
} }
/** /**
* Set the maximum delay in milliseconds to store committed changes (for * Set the maximum delay in milliseconds to store committed changes (for
* file-based stores). * file-based stores).
......
...@@ -102,9 +102,9 @@ public class FilePathCrypt extends FilePathWrapper { ...@@ -102,9 +102,9 @@ public class FilePathCrypt extends FilePathWrapper {
} }
/** /**
* Convert a char array to a byte array. The char array is cleared after * Convert a char array to a byte array, in UTF-16 format. The char array is
* use. * not cleared after use (this must be done by the caller).
* *
* @param passwordChars the password characters * @param passwordChars the password characters
* @return the byte array * @return the byte array
*/ */
...@@ -117,7 +117,6 @@ public class FilePathCrypt extends FilePathWrapper { ...@@ -117,7 +117,6 @@ public class FilePathCrypt extends FilePathWrapper {
password[i + i] = (byte) (c >>> 8); password[i + i] = (byte) (c >>> 8);
password[i + i + 1] = (byte) c; password[i + i + 1] = (byte) c;
} }
Arrays.fill(passwordChars, (char) 0);
return password; return password;
} }
......
...@@ -47,6 +47,7 @@ public class TestMVStore extends TestBase { ...@@ -47,6 +47,7 @@ public class TestMVStore extends TestBase {
public void test() throws Exception { public void test() throws Exception {
FileUtils.deleteRecursive(getBaseDir(), true); FileUtils.deleteRecursive(getBaseDir(), true);
FileUtils.createDirectories(getBaseDir()); FileUtils.createDirectories(getBaseDir());
testNewerWriteVersion();
testCompactFully(); testCompactFully();
testBackgroundExceptionListener(); testBackgroundExceptionListener();
testOldVersion(); testOldVersion();
...@@ -91,6 +92,35 @@ public class TestMVStore extends TestBase { ...@@ -91,6 +92,35 @@ public class TestMVStore extends TestBase {
// longer running tests // longer running tests
testLargerThan2G(); testLargerThan2G();
} }
private void testNewerWriteVersion() throws Exception {
String fileName = getBaseDir() + "/testNewerWriteVersion.h3";
FileUtils.delete(fileName);
char[] passwordChars;
passwordChars = "007".toCharArray();
MVStore s = new MVStore.Builder().
encryptionKey(passwordChars).
fileName(fileName).
open();
Map<String, String> header = s.getFileHeader();
assertEquals("1", header.get("format"));
header.put("formatRead", "1");
header.put("format", "2");
MVMap<Integer, String> m = s.openMap("data");
m.put(0, "Hello World");
s.store();
s.close();
passwordChars = "007".toCharArray();
s = new MVStore.Builder().
encryptionKey(passwordChars).
fileName(fileName).
open();
assertTrue(s.isReadOnly());
m = s.openMap("data");
assertEquals("Hello World", m.get(0));
s.close();
}
private void testCompactFully() throws Exception { private void testCompactFully() throws Exception {
String fileName = getBaseDir() + "/testCompactFully.h3"; String fileName = getBaseDir() + "/testCompactFully.h3";
...@@ -343,6 +373,14 @@ public class TestMVStore extends TestBase { ...@@ -343,6 +373,14 @@ public class TestMVStore extends TestBase {
m = s.openMap("test"); m = s.openMap("test");
assertEquals("Hello", m.get(1)); assertEquals("Hello", m.get(1));
s.close(); s.close();
FileUtils.setReadOnly(fileName);
passwordChars = "007".toCharArray();
s = new MVStore.Builder().
fileName(fileName).
encryptionKey(passwordChars).open();
assertTrue(s.isReadOnly());
FileUtils.delete(fileName); FileUtils.delete(fileName);
assertFalse(FileUtils.exists(fileName)); assertFalse(FileUtils.exists(fileName));
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论