Resolve issues around database conversion.
This commit is contained in:
parent
11a709a621
commit
cc15092eb7
|
@ -6,7 +6,9 @@
|
|||
#import "OWSDatabaseConverter.h"
|
||||
#import <Curve25519Kit/Randomness.h>
|
||||
#import <SignalServiceKit/OWSStorage.h>
|
||||
#import <SignalServiceKit/YapDatabaseConnection+OWS.h>
|
||||
#import <YapDatabase/YapDatabase.h>
|
||||
#import <YapDatabase/YapDatabasePrivate.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
@ -33,37 +35,122 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
return [Randomness generateRandomBytes:30];
|
||||
}
|
||||
|
||||
- (nullable NSString *)createUnconvertedDatabase:(NSData *)passwordData
|
||||
- (void)openYapDatabase:(NSString *)databaseFilePath
|
||||
databasePassword:(NSData *)databasePassword
|
||||
databaseBlock:(void (^_Nonnull)(YapDatabase *))databaseBlock
|
||||
{
|
||||
OWSAssert(databaseFilePath.length > 0);
|
||||
OWSAssert(databasePassword.length > 0);
|
||||
OWSAssert(databaseBlock);
|
||||
|
||||
DDLogVerbose(@"openYapDatabase: %@", databaseFilePath);
|
||||
|
||||
__weak YapDatabase *_Nullable weakDatabase = nil;
|
||||
dispatch_queue_t snapshotQueue;
|
||||
dispatch_queue_t writeQueue;
|
||||
|
||||
@autoreleasepool {
|
||||
YapDatabaseOptions *options = [[YapDatabaseOptions alloc] init];
|
||||
options.corruptAction = YapDatabaseCorruptAction_Fail;
|
||||
options.cipherKeyBlock = ^{
|
||||
return databasePassword;
|
||||
};
|
||||
options.enableMultiProcessSupport = YES;
|
||||
|
||||
OWSAssert(options.cipherDefaultkdfIterNumber == 0);
|
||||
OWSAssert(options.kdfIterNumber == 0);
|
||||
OWSAssert(options.cipherPageSize == 0);
|
||||
OWSAssert(options.pragmaPageSize == 0);
|
||||
OWSAssert(options.pragmaJournalSizeLimit == 0);
|
||||
|
||||
YapDatabase *database = [[YapDatabase alloc] initWithPath:databaseFilePath
|
||||
serializer:nil
|
||||
deserializer:[OWSStorage logOnFailureDeserializer]
|
||||
options:options];
|
||||
OWSAssert(database);
|
||||
|
||||
weakDatabase = database;
|
||||
snapshotQueue = database->snapshotQueue;
|
||||
writeQueue = database->writeQueue;
|
||||
|
||||
databaseBlock(database);
|
||||
|
||||
// Close the database.
|
||||
database = nil;
|
||||
}
|
||||
|
||||
// Flush the database's queues, which may contain lingering
|
||||
// references to the database.
|
||||
dispatch_sync(snapshotQueue,
|
||||
^{
|
||||
});
|
||||
dispatch_sync(writeQueue,
|
||||
^{
|
||||
});
|
||||
|
||||
// Wait for notifications from writes to be fired.
|
||||
{
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"Database modified notifications"];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// Database modified notifications are fired on the main queue.
|
||||
// Once this block executes, the main queue has been flushed
|
||||
// and we know that all database modified notifications are
|
||||
// complete.
|
||||
[expectation fulfill];
|
||||
});
|
||||
|
||||
[self waitForExpectationsWithTimeout:5.0
|
||||
handler:^(NSError *error) {
|
||||
if (error) {
|
||||
NSLog(@"Timeout Error: %@", error);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
YapDatabase *_Nullable strongDatabase = weakDatabase;
|
||||
OWSAssert(!strongDatabase);
|
||||
}
|
||||
|
||||
- (nullable NSString *)createUnconvertedDatabase:(NSData *)databasePassword
|
||||
{
|
||||
NSString *temporaryDirectory = NSTemporaryDirectory();
|
||||
NSString *filename = [NSUUID UUID].UUIDString;
|
||||
NSString *databaseFilePath = [temporaryDirectory stringByAppendingPathComponent:filename];
|
||||
|
||||
YapDatabaseOptions *options = [[YapDatabaseOptions alloc] init];
|
||||
options.corruptAction = YapDatabaseCorruptAction_Fail;
|
||||
options.cipherKeyBlock = ^{
|
||||
return passwordData;
|
||||
};
|
||||
options.enableMultiProcessSupport = YES;
|
||||
[self openYapDatabase:databaseFilePath
|
||||
databasePassword:databasePassword
|
||||
databaseBlock:^(YapDatabase *database) {
|
||||
YapDatabaseConnection *dbConnection = database.newConnection;
|
||||
[dbConnection setObject:@(YES) forKey:@"test_key_name" inCollection:@"test_collection_name"];
|
||||
[dbConnection flushTransactionsWithCompletionQueue:dispatch_get_main_queue() completionBlock:nil];
|
||||
}];
|
||||
|
||||
OWSAssert(options.cipherDefaultkdfIterNumber == 0);
|
||||
OWSAssert(options.kdfIterNumber == 0);
|
||||
OWSAssert(options.cipherPageSize == 0);
|
||||
OWSAssert(options.pragmaPageSize == 0);
|
||||
OWSAssert(options.pragmaJournalSizeLimit == 0);
|
||||
OWSAssert([[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]);
|
||||
|
||||
YapDatabase *database = [[YapDatabase alloc] initWithPath:databaseFilePath
|
||||
serializer:nil
|
||||
deserializer:[OWSStorage logOnFailureDeserializer]
|
||||
options:options];
|
||||
OWSAssert(database);
|
||||
return database ? databaseFilePath : nil;
|
||||
[self openYapDatabase:databaseFilePath
|
||||
databasePassword:databasePassword
|
||||
databaseBlock:^(YapDatabase *database) {
|
||||
YapDatabaseConnection *dbConnection = database.newConnection;
|
||||
id _Nullable value = [dbConnection objectForKey:@"test_key_name" inCollection:@"test_collection_name"];
|
||||
OWSAssert([@(YES) isEqual:value]);
|
||||
}];
|
||||
|
||||
OWSAssert([[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]);
|
||||
|
||||
NSError *_Nullable error = nil;
|
||||
NSDictionary *fileAttributes =
|
||||
[[NSFileManager defaultManager] attributesOfItemAtPath:databaseFilePath error:&error];
|
||||
OWSAssert(fileAttributes && !error);
|
||||
DDLogVerbose(@"%@ test database file size: %@", self.logTag, fileAttributes[NSFileSize]);
|
||||
|
||||
return databaseFilePath;
|
||||
}
|
||||
|
||||
- (void)testDoesDatabaseNeedToBeConverted_Unconverted
|
||||
{
|
||||
NSData *passwordData = [self randomDatabasePassword];
|
||||
NSString *_Nullable databaseFilePath = [self createUnconvertedDatabase:passwordData];
|
||||
NSData *databasePassword = [self randomDatabasePassword];
|
||||
NSString *_Nullable databaseFilePath = [self createUnconvertedDatabase:databasePassword];
|
||||
XCTAssertTrue([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]);
|
||||
}
|
||||
|
||||
|
@ -74,10 +161,15 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
- (void)testDatabaseConversion
|
||||
{
|
||||
NSData *passwordData = [self randomDatabasePassword];
|
||||
NSString *_Nullable databaseFilePath = [self createUnconvertedDatabase:passwordData];
|
||||
NSData *databasePassword = [self randomDatabasePassword];
|
||||
NSString *_Nullable databaseFilePath = [self createUnconvertedDatabase:databasePassword];
|
||||
XCTAssertTrue([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]);
|
||||
[OWSDatabaseConverter convertDatabaseIfNecessary:databaseFilePath];
|
||||
NSError *_Nullable error =
|
||||
[OWSDatabaseConverter convertDatabaseIfNecessary:databaseFilePath databasePassword:databasePassword];
|
||||
if (error) {
|
||||
DDLogError(@"%s error: %@", __PRETTY_FUNCTION__, error);
|
||||
}
|
||||
XCTAssertNil(error);
|
||||
XCTAssertFalse([OWSDatabaseConverter doesDatabaseNeedToBeConverted:databaseFilePath]);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,8 +12,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
+ (void)convertDatabaseIfNecessary;
|
||||
+ (void)convertDatabaseIfNecessary:(NSString *)databaseFilePath;
|
||||
+ (nullable NSError *)convertDatabaseIfNecessary;
|
||||
+ (nullable NSError *)convertDatabaseIfNecessary:(NSString *)databaseFilePath
|
||||
databasePassword:(NSData *)databasePassword;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -4,42 +4,64 @@
|
|||
|
||||
#import "OWSDatabaseConverter.h"
|
||||
#import "sqlite3.h"
|
||||
#import <SignalServiceKit/NSData+hexString.h>
|
||||
#import <SignalServiceKit/OWSError.h>
|
||||
#import <SignalServiceKit/OWSFileSystem.h>
|
||||
#import <SignalServiceKit/TSStorageManager.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
const int kSqliteHeaderLength = 32;
|
||||
const NSUInteger kSqliteHeaderLength = 32;
|
||||
|
||||
@interface OWSStorage (OWSDatabaseConverter)
|
||||
|
||||
+ (YapDatabaseDeserializer)logOnFailureDeserializer;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation OWSDatabaseConverter
|
||||
|
||||
+ (NSData *)readFirstNBytesOfDatabaseFile:(NSString *)filePath byteCount:(NSUInteger)byteCount
|
||||
{
|
||||
OWSAssert(filePath.length > 0);
|
||||
|
||||
@autoreleasepool {
|
||||
NSError *error;
|
||||
// We use NSDataReadingMappedAlways instead of NSDataReadingMappedIfSafe because
|
||||
// we know the database will always exist for the duration of this instance of NSData.
|
||||
NSData *_Nullable data = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:filePath]
|
||||
options:NSDataReadingMappedAlways
|
||||
error:&error];
|
||||
if (!data || error) {
|
||||
DDLogError(@"%@ Couldn't read database file header.", self.logTag);
|
||||
// TODO: Make a convenience method (on a category of NSException?) that
|
||||
// flushes DDLog before raising a terminal exception.
|
||||
[NSException raise:@"Couldn't read database file header" format:@""];
|
||||
}
|
||||
// Pull this constant out so that we can use it in our YapDatabase fork.
|
||||
NSData *_Nullable headerData = [data subdataWithRange:NSMakeRange(0, byteCount)];
|
||||
if (!headerData || headerData.length != byteCount) {
|
||||
[NSException raise:@"Database file header has unexpected length"
|
||||
format:@"Database file header has unexpected length: %zd", headerData.length];
|
||||
}
|
||||
return [headerData copy];
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)doesDatabaseNeedToBeConverted:(NSString *)databaseFilePath
|
||||
{
|
||||
OWSAssert(databaseFilePath.length > 0);
|
||||
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]) {
|
||||
DDLogVerbose(@"%@ Skipping database conversion; no legacy database found.", self.logTag);
|
||||
return NO;
|
||||
}
|
||||
NSError *error;
|
||||
// We use NSDataReadingMappedAlways instead of NSDataReadingMappedIfSafe because
|
||||
// we know the database will always exist for the duration of this instance of NSData.
|
||||
NSData *_Nullable data = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:databaseFilePath]
|
||||
options:NSDataReadingMappedAlways
|
||||
error:&error];
|
||||
if (!data || error) {
|
||||
DDLogError(@"%@ Couldn't read legacy database file header.", self.logTag);
|
||||
// TODO: Make a convenience method (on a category of NSException?) that
|
||||
// flushes DDLog before raising a terminal exception.
|
||||
[NSException raise:@"Couldn't read legacy database file header" format:@""];
|
||||
}
|
||||
// Pull this constant out so that we can use it in our YapDatabase fork.
|
||||
NSData *_Nullable headerData = [data subdataWithRange:NSMakeRange(0, kSqliteHeaderLength)];
|
||||
if (!headerData || headerData.length != kSqliteHeaderLength) {
|
||||
[NSException raise:@"Database database file header has unexpected length"
|
||||
format:@"Database database file header has unexpected length: %zd", headerData.length];
|
||||
DDLogVerbose(@"%@ database file not found.", self.logTag);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSData *headerData = [self readFirstNBytesOfDatabaseFile:databaseFilePath byteCount:kSqliteHeaderLength];
|
||||
OWSAssert(headerData);
|
||||
|
||||
NSString *kUnencryptedHeader = @"SQLite format 3\0";
|
||||
NSData *unencryptedHeaderData = [kUnencryptedHeader dataUsingEncoding:NSUTF8StringEncoding];
|
||||
BOOL isUnencrypted = [unencryptedHeaderData
|
||||
|
@ -52,26 +74,9 @@ const int kSqliteHeaderLength = 32;
|
|||
return YES;
|
||||
}
|
||||
|
||||
+ (void)convertDatabaseIfNecessary
|
||||
+ (nullable NSError *)convertDatabaseIfNecessary
|
||||
{
|
||||
NSString *databaseFilePath = [TSStorageManager legacyDatabaseFilePath];
|
||||
[self convertDatabaseIfNecessary:databaseFilePath];
|
||||
}
|
||||
|
||||
// TODO upon failure show user error UI
|
||||
// TODO upon failure anything we need to do "back out" partial migration
|
||||
+ (void)convertDatabaseIfNecessary:(NSString *)databaseFilePath
|
||||
{
|
||||
if (![self doesDatabaseNeedToBeConverted:databaseFilePath]) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self convertDatabase:(NSString *)databaseFilePath];
|
||||
}
|
||||
|
||||
+ (nullable NSError *)convertDatabase:(NSString *)databaseFilePath
|
||||
{
|
||||
OWSAssert(databaseFilePath.length > 0);
|
||||
|
||||
NSError *error;
|
||||
NSData *_Nullable databasePassword = [OWSStorage tryToLoadDatabasePassword:&error];
|
||||
|
@ -81,7 +86,34 @@ const int kSqliteHeaderLength = 32;
|
|||
OWSErrorCodeDatabaseConversionFatalError, @"Failed to load database password"));
|
||||
}
|
||||
|
||||
// TODO:
|
||||
return [self convertDatabaseIfNecessary:databaseFilePath databasePassword:databasePassword];
|
||||
}
|
||||
|
||||
// TODO upon failure show user error UI
|
||||
// TODO upon failure anything we need to do "back out" partial migration
|
||||
+ (nullable NSError *)convertDatabaseIfNecessary:(NSString *)databaseFilePath
|
||||
databasePassword:(NSData *)databasePassword
|
||||
{
|
||||
if (![self doesDatabaseNeedToBeConverted:databaseFilePath]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [self convertDatabase:(NSString *)databaseFilePath databasePassword:databasePassword];
|
||||
}
|
||||
|
||||
+ (nullable NSError *)convertDatabase:(NSString *)databaseFilePath databasePassword:(NSData *)databasePassword
|
||||
{
|
||||
OWSAssert(databaseFilePath.length > 0);
|
||||
OWSAssert(databasePassword.length > 0);
|
||||
|
||||
NSData *headerData = [self readFirstNBytesOfDatabaseFile:databaseFilePath byteCount:kSqliteHeaderLength];
|
||||
OWSAssert(headerData);
|
||||
|
||||
const NSUInteger kSQLCipherSaltLength = 16;
|
||||
OWSAssert(headerData.length >= kSQLCipherSaltLength);
|
||||
NSData *sqlCipherSaltData = [headerData subdataWithRange:NSMakeRange(0, kSQLCipherSaltLength)];
|
||||
|
||||
// TODO: Write salt to keychain.
|
||||
|
||||
// Hello Matthew,
|
||||
//
|
||||
|
@ -181,139 +213,156 @@ const int kSqliteHeaderLength = 32;
|
|||
return OWSErrorWithCodeDescription(OWSErrorCodeDatabaseConversionFatalError, @"Failed to set SQLCipher key");
|
||||
}
|
||||
|
||||
// TODO set plaintext pragma
|
||||
// TODO modify first page
|
||||
// TODO force checkpoint
|
||||
|
||||
// -----------------------------------------------------------
|
||||
//
|
||||
// This block was derived from [Yapdatabase configureDatabase].
|
||||
{
|
||||
//
|
||||
// {
|
||||
// int status;
|
||||
//
|
||||
// // Set mandatory pragmas
|
||||
//
|
||||
|
||||
// MJK: this isn't relevant since we only migrate existing databses and never set a pageSize option.
|
||||
// if (isNewDatabaseFile && (options.pragmaPageSize > 0))
|
||||
// {
|
||||
// NSString *pragma_page_size =
|
||||
// [NSString stringWithFormat:@"PRAGMA page_size = %ld;", (long)options.pragmaPageSize];
|
||||
//
|
||||
// status = sqlite3_exec(db, [pragma_page_size UTF8String], NULL, NULL, NULL);
|
||||
// if (status != SQLITE_OK)
|
||||
// {
|
||||
// YDBLogError(@"Error setting PRAGMA page_size: %d %s", status, sqlite3_errmsg(db));
|
||||
// }
|
||||
// }
|
||||
|
||||
//
|
||||
status = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
|
||||
if (status != SQLITE_OK) {
|
||||
DDLogError(@"Error setting PRAGMA journal_mode: %d %s", status, sqlite3_errmsg(db));
|
||||
return OWSErrorWithCodeDescription(OWSErrorCodeDatabaseConversionFatalError, @"Failed to set WAL mode");
|
||||
}
|
||||
|
||||
// MJK: this isn't relevant since we only migrate existing databses
|
||||
// if (isNewDatabaseFile)
|
||||
// {
|
||||
// status = sqlite3_exec(db, "PRAGMA auto_vacuum = FULL; VACUUM;", NULL, NULL, NULL);
|
||||
// if (status != SQLITE_OK)
|
||||
// {
|
||||
// YDBLogError(@"Error setting PRAGMA auto_vacuum: %d %s", status, sqlite3_errmsg(db));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
|
||||
// TODO verify we need to do this.
|
||||
// Set synchronous to normal for THIS sqlite instance.
|
||||
//
|
||||
// This does NOT affect normal connections.
|
||||
// That is, this does NOT affect YapDatabaseConnection instances.
|
||||
// The sqlite connections of normal YapDatabaseConnection instances will follow the set pragmaSynchronous value.
|
||||
//
|
||||
// The reason we hardcode normal for this sqlite instance is because
|
||||
// it's only used to write the initial snapshot value.
|
||||
// And this doesn't need to be durable, as it is initialized to zero everytime.
|
||||
//
|
||||
// (This sqlite db is also used to perform checkpoints.
|
||||
// But a normal value won't affect these operations,
|
||||
// as they will perform sync operations whether the connection is normal or full.)
|
||||
status = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL);
|
||||
if (status != SQLITE_OK) {
|
||||
DDLogError(@"Error setting PRAGMA synchronous: %d %s", status, sqlite3_errmsg(db));
|
||||
// This isn't critical, so we can continue.
|
||||
}
|
||||
|
||||
// Set journal_size_imit.
|
||||
//
|
||||
// We only need to do set this pragma for THIS connection,
|
||||
// because it is the only connection that performs checkpoints.
|
||||
|
||||
NSInteger defaultPragmaJournalSizeLimit = 0;
|
||||
NSString *pragma_journal_size_limit =
|
||||
[NSString stringWithFormat:@"PRAGMA journal_size_limit = %ld;", (long)defaultPragmaJournalSizeLimit];
|
||||
|
||||
status = sqlite3_exec(db, [pragma_journal_size_limit UTF8String], NULL, NULL, NULL);
|
||||
if (status != SQLITE_OK) {
|
||||
DDLogError(@"Error setting PRAGMA journal_size_limit: %d %s", status, sqlite3_errmsg(db));
|
||||
// This isn't critical, so we can continue.
|
||||
}
|
||||
//
|
||||
// // Set mmap_size (if needed).
|
||||
// //
|
||||
// // This configures memory mapped I/O.
|
||||
// // OWS: we currently don't set options.pragmaMMapSize, so we can ignore this code.
|
||||
// if (options.pragmaMMapSize > 0)
|
||||
// {
|
||||
// NSString *pragma_mmap_size =
|
||||
// [NSString stringWithFormat:@"PRAGMA mmap_size = %ld;", (long)options.pragmaMMapSize];
|
||||
//
|
||||
// status = sqlite3_exec(db, [pragma_mmap_size UTF8String], NULL, NULL, NULL);
|
||||
// if (status != SQLITE_OK)
|
||||
// {
|
||||
// YDBLogError(@"Error setting PRAGMA mmap_size: %d %s", status, sqlite3_errmsg(db));
|
||||
// // This isn't critical, so we can continue.
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Disable autocheckpointing.
|
||||
//
|
||||
// YapDatabase has its own optimized checkpointing algorithm built-in.
|
||||
// It knows the state of every active connection for the database,
|
||||
// so it can invoke the checkpoint methods at the precise time in which a checkpoint can be most effective.
|
||||
sqlite3_wal_autocheckpoint(db, 0);
|
||||
|
||||
// END DB setup copied from YapDatabase
|
||||
// BEGIN SQLCipher migration
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------------------------
|
||||
//
|
||||
// SQLCipher migration
|
||||
|
||||
// if (NO)
|
||||
// {
|
||||
// int status;
|
||||
//
|
||||
// // Set mandatory pragmas
|
||||
// NSString *setPlainTextHeaderPragma =
|
||||
// [NSString stringWithFormat:@"PRAGMA cipher_plaintext_header_size = %zd;", kSqliteHeaderLength];
|
||||
//
|
||||
// status = sqlite3_exec(db, [setPlainTextHeaderPragma UTF8String], NULL, NULL, NULL);
|
||||
// if (status != SQLITE_OK) {
|
||||
// DDLogError(@"Error setting PRAGMA cipher_plaintext_header_size = %zd: status: %d, error: %s",
|
||||
// kSqliteHeaderLength,
|
||||
// status,
|
||||
// sqlite3_errmsg(db));
|
||||
// return OWSErrorWithCodeDescription(
|
||||
// OWSErrorCodeDatabaseConversionFatalError, @"Failed to set PRAGMA
|
||||
// cipher_plaintext_header_size");
|
||||
// }
|
||||
//
|
||||
// // Modify the first page, so that SQLCipher will overwrite, respecting our new cipher_plaintext_header_size
|
||||
// NSString *tableName = [NSString stringWithFormat:@"signal-migration-%@", [NSUUID new].UUIDString];
|
||||
// NSString *modificationSQL =
|
||||
// [NSString stringWithFormat:@"CREATE TABLE %@(int a); INSERT INTO %@(a) VALUES (1);", tableName, tableName];
|
||||
// status = sqlite3_exec(db, [modificationSQL UTF8String], NULL, NULL, NULL);
|
||||
// if (status != SQLITE_OK) {
|
||||
// DDLogError(@"%@ Error modifying first page: %d, error: %s", self.logTag, status, sqlite3_errmsg(db));
|
||||
// return OWSErrorWithCodeDescription(OWSErrorCodeDatabaseConversionFatalError, @"Error modifying first
|
||||
// page");
|
||||
// }
|
||||
//
|
||||
// // Force a checkpoint so that the plaintext is written to the actual DB file, not just living in the WAL.
|
||||
// // TODO do we need/want the earlier checkpoint if we're checkpointing here?
|
||||
// sqlite3_wal_autocheckpoint(db, 0);
|
||||
//
|
||||
//
|
||||
// sqlite3_close(db);
|
||||
// return nil;
|
||||
// }
|
||||
|
||||
// MJK: this isn't relevant since we only migrate existing databses and never set a pageSize option.
|
||||
// if (isNewDatabaseFile && (options.pragmaPageSize > 0))
|
||||
// {
|
||||
// NSString *pragma_page_size =
|
||||
// [NSString stringWithFormat:@"PRAGMA page_size = %ld;", (long)options.pragmaPageSize];
|
||||
//
|
||||
// status = sqlite3_exec(db, [pragma_page_size UTF8String], NULL, NULL, NULL);
|
||||
// if (status != SQLITE_OK)
|
||||
// {
|
||||
// YDBLogError(@"Error setting PRAGMA page_size: %d %s", status, sqlite3_errmsg(db));
|
||||
// }
|
||||
// }
|
||||
|
||||
//
|
||||
status = sqlite3_exec(db, "PRAGMA journal_mode = WAL;", NULL, NULL, NULL);
|
||||
if (status != SQLITE_OK) {
|
||||
DDLogError(@"Error setting PRAGMA journal_mode: %d %s", status, sqlite3_errmsg(db));
|
||||
return OWSErrorWithCodeDescription(OWSErrorCodeDatabaseConversionFatalError, @"Failed to set WAL mode");
|
||||
}
|
||||
|
||||
// MJK: this isn't relevant since we only migrate existing databses
|
||||
// if (isNewDatabaseFile)
|
||||
// {
|
||||
// status = sqlite3_exec(db, "PRAGMA auto_vacuum = FULL; VACUUM;", NULL, NULL, NULL);
|
||||
// if (status != SQLITE_OK)
|
||||
// {
|
||||
// YDBLogError(@"Error setting PRAGMA auto_vacuum: %d %s", status, sqlite3_errmsg(db));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
|
||||
// TODO verify we need to do this.
|
||||
// Set synchronous to normal for THIS sqlite instance.
|
||||
//
|
||||
// This does NOT affect normal connections.
|
||||
// That is, this does NOT affect YapDatabaseConnection instances.
|
||||
// The sqlite connections of normal YapDatabaseConnection instances will follow the set pragmaSynchronous value.
|
||||
//
|
||||
// The reason we hardcode normal for this sqlite instance is because
|
||||
// it's only used to write the initial snapshot value.
|
||||
// And this doesn't need to be durable, as it is initialized to zero everytime.
|
||||
//
|
||||
// (This sqlite db is also used to perform checkpoints.
|
||||
// But a normal value won't affect these operations,
|
||||
// as they will perform sync operations whether the connection is normal or full.)
|
||||
status = sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", NULL, NULL, NULL);
|
||||
if (status != SQLITE_OK) {
|
||||
DDLogError(@"Error setting PRAGMA synchronous: %d %s", status, sqlite3_errmsg(db));
|
||||
// This isn't critical, so we can continue.
|
||||
}
|
||||
|
||||
// Set journal_size_imit.
|
||||
//
|
||||
// We only need to do set this pragma for THIS connection,
|
||||
// because it is the only connection that performs checkpoints.
|
||||
|
||||
NSInteger defaultPragmaJournalSizeLimit = 0;
|
||||
NSString *pragma_journal_size_limit =
|
||||
[NSString stringWithFormat:@"PRAGMA journal_size_limit = %ld;", (long)defaultPragmaJournalSizeLimit];
|
||||
|
||||
status = sqlite3_exec(db, [pragma_journal_size_limit UTF8String], NULL, NULL, NULL);
|
||||
if (status != SQLITE_OK) {
|
||||
DDLogError(@"Error setting PRAGMA journal_size_limit: %d %s", status, sqlite3_errmsg(db));
|
||||
// This isn't critical, so we can continue.
|
||||
}
|
||||
//
|
||||
// // Set mmap_size (if needed).
|
||||
// //
|
||||
// // This configures memory mapped I/O.
|
||||
// // OWS: we currently don't set options.pragmaMMapSize, so we can ignore this code.
|
||||
// if (options.pragmaMMapSize > 0)
|
||||
// {
|
||||
// NSString *pragma_mmap_size =
|
||||
// [NSString stringWithFormat:@"PRAGMA mmap_size = %ld;", (long)options.pragmaMMapSize];
|
||||
//
|
||||
// status = sqlite3_exec(db, [pragma_mmap_size UTF8String], NULL, NULL, NULL);
|
||||
// if (status != SQLITE_OK)
|
||||
// {
|
||||
// YDBLogError(@"Error setting PRAGMA mmap_size: %d %s", status, sqlite3_errmsg(db));
|
||||
// // This isn't critical, so we can continue.
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Disable autocheckpointing.
|
||||
//
|
||||
// YapDatabase has its own optimized checkpointing algorithm built-in.
|
||||
// It knows the state of every active connection for the database,
|
||||
// so it can invoke the checkpoint methods at the precise time in which a checkpoint can be most effective.
|
||||
sqlite3_wal_autocheckpoint(db, 0);
|
||||
|
||||
// END DB setup copied from YapDatabase
|
||||
// BEGIN SQLCipher migration
|
||||
|
||||
NSString *setPlainTextHeaderPragma =
|
||||
[NSString stringWithFormat:@"PRAGMA cipher_plaintext_header_size = %d;", kSqliteHeaderLength];
|
||||
|
||||
status = sqlite3_exec(db, [setPlainTextHeaderPragma UTF8String], NULL, NULL, NULL);
|
||||
if (status != SQLITE_OK) {
|
||||
DDLogError(@"Error setting PRAGMA cipher_plaintext_header_size = %d: status: %d, error: %s",
|
||||
kSqliteHeaderLength,
|
||||
status,
|
||||
sqlite3_errmsg(db));
|
||||
return OWSErrorWithCodeDescription(
|
||||
OWSErrorCodeDatabaseConversionFatalError, @"Failed to set PRAGMA cipher_plaintext_header_size");
|
||||
}
|
||||
|
||||
// Modify the first page, so that SQLCipher will overwrite, respecting our new cipher_plaintext_header_size
|
||||
NSString *tableName = [NSString stringWithFormat:@"signal-migration-%@", [NSUUID new].UUIDString];
|
||||
NSString *modificationSQL =
|
||||
[NSString stringWithFormat:@"CREATE TABLE %@(int a); INSERT INTO %@(a) VALUES (1);", tableName, tableName];
|
||||
status = sqlite3_exec(db, [modificationSQL UTF8String], NULL, NULL, NULL);
|
||||
if (status != SQLITE_OK) {
|
||||
DDLogError(@"%@ Error modifying first page: %d, error: %s", self.logTag, status, sqlite3_errmsg(db));
|
||||
return OWSErrorWithCodeDescription(OWSErrorCodeDatabaseConversionFatalError, @"Error modifying first page");
|
||||
}
|
||||
|
||||
// Force a checkpoint so that the plaintext is written to the actual DB file, not just living in the WAL.
|
||||
// TODO do we need/want the earlier checkpoint if we're checkpointing here?
|
||||
sqlite3_wal_autocheckpoint(db, 0);
|
||||
// TODO set plaintext pragma
|
||||
// TODO modify first page
|
||||
// TODO force checkpoint
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue