improvements and simplification of cipher_migrate implementation

This commit is contained in:
Stephen Lombardo 2018-09-19 17:11:53 -04:00
parent cf8df7e2a8
commit 1e601879cd
3 changed files with 141 additions and 173 deletions

View File

@ -43,7 +43,7 @@
void *sqlite3PagerGetCodec(Pager*); void *sqlite3PagerGetCodec(Pager*);
int sqlite3pager_is_mj_pgno(Pager*, Pgno); int sqlite3pager_is_mj_pgno(Pager*, Pgno);
void sqlite3pager_error(Pager*, int); void sqlite3pager_error(Pager*, int);
int sqlite3pager_truncate(Pager*, Pgno); void sqlite3pager_reset(Pager *pPager);
#if !defined (SQLCIPHER_CRYPTO_CC) \ #if !defined (SQLCIPHER_CRYPTO_CC) \
&& !defined (SQLCIPHER_CRYPTO_LIBTOMCRYPT) \ && !defined (SQLCIPHER_CRYPTO_LIBTOMCRYPT) \

View File

@ -1216,66 +1216,59 @@ cleanup:
} }
int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) {
u32 meta; int i, pass_sz, keyspec_sz, nRes, user_version, upgrade_from, rc, oflags;
int i, password_sz, key_sz, saved_flags, saved_nChange, saved_nTotalChange, nRes, user_version = 0, upgrade_from = 0, rc = 0;
u8 saved_mTrace;
int (*saved_xTrace)(u32,void*,void*,void*); /* Saved db->xTrace */
Db *pDb = 0; Db *pDb = 0;
sqlite3 *db = ctx->pBt->db; sqlite3 *db = ctx->pBt->db;
const char *db_filename = sqlite3_db_filename(db, "main"); const char *db_filename = sqlite3_db_filename(db, "main");
char *migrated_db_filename = sqlite3_mprintf("%s-migrated", db_filename);
char *v1_pragmas = "PRAGMA cipher_use_hmac = OFF; PRAGMA kdf_iter = 4000; PRAGMA cipher_page_size = 1024; PRAGMA cipher_hmac_algorithm = HMAC_SHA1; PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1;"; char *v1_pragmas = "PRAGMA cipher_use_hmac = OFF; PRAGMA kdf_iter = 4000; PRAGMA cipher_page_size = 1024; PRAGMA cipher_hmac_algorithm = HMAC_SHA1; PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1;";
char *v2_pragmas = "PRAGMA kdf_iter = 4000; PRAGMA cipher_page_size = 1024; PRAGMA cipher_hmac_algorithm = HMAC_SHA1; PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1;"; char *v2_pragmas = "PRAGMA kdf_iter = 4000; PRAGMA cipher_page_size = 1024; PRAGMA cipher_hmac_algorithm = HMAC_SHA1; PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1;";
char *v3_pragmas = "PRAGMA kdf_iter = 64000; PRAGMA cipher_page_size = 1024; PRAGMA cipher_hmac_algorithm = HMAC_SHA1; PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1;"; char *v3_pragmas = "PRAGMA kdf_iter = 64000; PRAGMA cipher_page_size = 1024; PRAGMA cipher_hmac_algorithm = HMAC_SHA1; PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1;";
char *set_user_version, *key; char *set_user_version = NULL, *pass = NULL, *attach_command = NULL, *migrated_db_filename = NULL, *keyspec = NULL;
Btree *pDest = NULL, *pSrc = NULL; Btree *pDest = NULL, *pSrc = NULL;
static const unsigned char aCopy[] = { const char* commands[4];
BTREE_SCHEMA_VERSION, 1, /* Add one to the old schema cookie */ sqlite3_file *srcfile, *destfile;
BTREE_DEFAULT_CACHE_SIZE, 0, /* Preserve the default page cache size */
BTREE_TEXT_ENCODING, 0, /* Preserve the text encoding */
BTREE_USER_VERSION, 0, /* Preserve the user version */
BTREE_APPLICATION_ID, 0, /* Preserve the application id */
};
rc = user_version = upgrade_from = 0; rc = user_version = upgrade_from = 0;
key_sz = ctx->read_ctx->pass_sz + 1; if(!db_filename || sqlite3Strlen30(db_filename) < 1)
key = sqlcipher_malloc(key_sz); goto exit; /* exit immediately if this is an in memory database */
memset(key, 0, key_sz);
memcpy(key, ctx->read_ctx->pass, ctx->read_ctx->pass_sz);
if(db_filename){ /* pull the provided password / key material off the current codec context */
const char* commands[4]; pass_sz = ctx->read_ctx->pass_sz;
char *attach_command = sqlite3_mprintf("ATTACH DATABASE '%s-migrated' as migrate KEY '%q';", pass = sqlcipher_malloc(pass_sz+1);
db_filename, key); memset(pass, 0, pass_sz+1);
memcpy(pass, ctx->read_ctx->pass, pass_sz);
int rc = sqlcipher_check_connection(db_filename, key, ctx->read_ctx->pass_sz, "", &user_version); /* Version 4 - current, no upgrade required, so exit immediately */
rc = sqlcipher_check_connection(db_filename, pass, pass_sz, "", &user_version);
if(rc == SQLITE_OK){ if(rc == SQLITE_OK){
CODEC_TRACE("No upgrade required - exiting\n"); CODEC_TRACE("No upgrade required - exiting\n");
goto exit; goto exit;
} }
/* Version 3 - check for 64k with hmac format and 1024 page size */ /* Version 3 - check for 64k with hmac format and 1024 page size */
rc = sqlcipher_check_connection(db_filename, key, ctx->read_ctx->pass_sz, v3_pragmas, &user_version); rc = sqlcipher_check_connection(db_filename, pass, pass_sz, v3_pragmas, &user_version);
if(rc == SQLITE_OK) { if(rc == SQLITE_OK) {
CODEC_TRACE("Version 3 format found\n"); CODEC_TRACE("Version 3 format found\n");
upgrade_from = 3; upgrade_from = 3;
} }
/* Version 2 - check for 4k with hmac format and 1024 page size */ /* Version 2 - check for 4k with hmac format and 1024 page size */
rc = sqlcipher_check_connection(db_filename, key, ctx->read_ctx->pass_sz, v2_pragmas, &user_version); rc = sqlcipher_check_connection(db_filename, pass, pass_sz, v2_pragmas, &user_version);
if(rc == SQLITE_OK) { if(rc == SQLITE_OK) {
CODEC_TRACE("Version 2 format found\n"); CODEC_TRACE("Version 2 format found\n");
upgrade_from = 2; upgrade_from = 2;
} }
/* Version 1 - check no HMAC, 4k KDF, and 1024 page size */ /* Version 1 - check no HMAC, 4k KDF, and 1024 page size */
rc = sqlcipher_check_connection(db_filename, key, ctx->read_ctx->pass_sz, v1_pragmas, &user_version); rc = sqlcipher_check_connection(db_filename, pass, pass_sz, v1_pragmas, &user_version);
if(rc == SQLITE_OK) { if(rc == SQLITE_OK) {
CODEC_TRACE("Version 1 format found\n"); CODEC_TRACE("Version 1 format found\n");
upgrade_from = 1; upgrade_from = 1;
} }
migrated_db_filename = sqlite3_mprintf("%s-migrated", db_filename);
attach_command = sqlite3_mprintf("ATTACH DATABASE '%s' as migrate KEY '%q';", migrated_db_filename, pass);
set_user_version = sqlite3_mprintf("PRAGMA migrate.user_version = %d;", user_version); set_user_version = sqlite3_mprintf("PRAGMA migrate.user_version = %d;", user_version);
switch(upgrade_from) { switch(upgrade_from) {
case 1: case 1:
@ -1299,14 +1292,9 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) {
rc = sqlite3_exec(db, commands[i], NULL, NULL, NULL); rc = sqlite3_exec(db, commands[i], NULL, NULL, NULL);
if(rc != SQLITE_OK){ if(rc != SQLITE_OK){
CODEC_TRACE("migration step %d failed error code %d\n", i, rc); CODEC_TRACE("migration step %d failed error code %d\n", i, rc);
break; goto handle_error;
} }
} }
sqlite3_free(attach_command);
sqlite3_free(set_user_version);
sqlcipher_free(key, key_sz);
if(rc == SQLITE_OK){
if( !db->autoCommit ){ if( !db->autoCommit ){
CODEC_TRACE("cannot migrate from within a transaction"); CODEC_TRACE("cannot migrate from within a transaction");
@ -1317,93 +1305,72 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) {
goto handle_error; goto handle_error;
} }
/* Save the current value of the database flags so that it can be
** restored before returning. Then set the writable-schema flag, and
** disable CHECK and foreign key constraints. */
saved_flags = db->flags;
saved_nChange = db->nChange;
saved_nTotalChange = db->nTotalChange;
saved_xTrace = db->xTrace;
saved_mTrace = db->mTrace;
db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks;
db->mDbFlags |= DBFLAG_PreferBuiltin | DBFLAG_Vacuum;
db->flags &= ~(SQLITE_ForeignKeys | SQLITE_ReverseOrder);
db->xTrace = 0;
db->mTrace = 0;
pDest = db->aDb[0].pBt; pDest = db->aDb[0].pBt;
pDb = &(db->aDb[db->nDb-1]); pDb = &(db->aDb[db->nDb-1]);
pSrc = pDb->pBt; pSrc = pDb->pBt;
nRes = sqlite3BtreeGetOptimalReserve(pSrc); nRes = sqlite3BtreeGetOptimalReserve(pSrc);
rc = sqlite3BtreeSetPageSize(pDest, default_page_size, nRes, 0); rc = sqlite3BtreeSetPageSize(pDest, default_page_size, nRes, 0);
if( rc!=SQLITE_OK ) goto handle_error; CODEC_TRACE("set btree page size to %d res %d rc %d\n", default_page_size, nRes, rc);
CODEC_TRACE("set BTree page size to %d res %d rc %d\n", default_page_size, nRes, rc);
rc = sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL);
if( rc!=SQLITE_OK ) goto handle_error; if( rc!=SQLITE_OK ) goto handle_error;
sqlite3pager_truncate(pDest->pBt->pPager, 0); sqlite3CodecGetKey(db, db->nDb - 1, (void**)&keyspec, &keyspec_sz);
sqlite3CodecAttach(db, 0, keyspec, keyspec_sz);
rc = sqlite3BtreeBeginTrans(pSrc, 2, 0);
if( rc!=SQLITE_OK ) goto handle_error;
rc = sqlite3BtreeBeginTrans(pDest, 2, 0); #if defined(_WIN32) || defined(SQLITE_OS_WINRT)
if( rc!=SQLITE_OK ) goto handle_error; if(!MoveFileExA(migrated_db_filename, db_filename, MOVEFILE_REPLACE_EXISTING)) {
rc = SQLITE_ERROR;
assert( 1==sqlite3BtreeIsInTrans(pDest) ); CODEC_TRACE("error occurred while renaming %d\n", rc);
assert( 1==sqlite3BtreeIsInTrans(pSrc) ); goto handle_error;
CODEC_TRACE("started transactions\n");
sqlite3CodecGetKey(db, db->nDb - 1, (void**)&key, &password_sz);
sqlite3CodecAttach(db, 0, key, password_sz);
ctx = (codec_ctx*) sqlite3PagerGetCodec(pDest->pBt->pPager);
ctx->skip_read_hmac = 1;
for(i=0; i<ArraySize(aCopy); i+=2){
sqlite3BtreeGetMeta(pSrc, aCopy[i], &meta);
rc = sqlite3BtreeUpdateMeta(pDest, aCopy[i], meta+aCopy[i+1]);
CODEC_TRACE("applied metadata %d %d\n",i, rc);
if( NEVER(rc!=SQLITE_OK) ) goto handle_error;
} }
CODEC_TRACE("finished applied metadata\n"); #else
if ((rc = rename(migrated_db_filename, db_filename)) != 0) {
CODEC_TRACE("error occurred while renaming %d\n", rc);
goto handle_error;
}
#endif
CODEC_TRACE("renamed migration database to main database: %d\n", rc);
rc = sqlite3BtreeCopyFile(pDest, pSrc); srcfile = sqlite3PagerFile(pSrc->pBt->pPager);
ctx->skip_read_hmac = 0; destfile = sqlite3PagerFile(pDest->pBt->pPager);
sqlite3OsClose(srcfile);
sqlite3OsClose(destfile);
rc = sqlite3OsOpen(db->pVfs, migrated_db_filename, srcfile, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, &oflags);
CODEC_TRACE("reopened migration database: %d\n", rc);
if( rc!=SQLITE_OK ) goto handle_error; if( rc!=SQLITE_OK ) goto handle_error;
CODEC_TRACE("copied btree %d\n", rc);
rc = sqlite3BtreeCommit(pDest); rc = sqlite3OsOpen(db->pVfs, db_filename, destfile, SQLITE_OPEN_READWRITE, &oflags);
CODEC_TRACE("reopened main database: %d\n", rc);
if( rc!=SQLITE_OK ) goto handle_error; if( rc!=SQLITE_OK ) goto handle_error;
CODEC_TRACE("committed destination transaction %d\n", rc);
db->flags = saved_flags; sqlite3pager_reset(pDest->pBt->pPager);
db->nChange = saved_nChange; CODEC_TRACE("reset pager\n");
db->nTotalChange = saved_nTotalChange;
db->xTrace = saved_xTrace; rc = sqlite3BtreeClose(pSrc);
db->mTrace = saved_mTrace; CODEC_TRACE("closed src btree %d\n", rc);
db->autoCommit = 1; if( rc!=SQLITE_OK ) goto handle_error;
sqlite3BtreeClose(pDb->pBt);
pDb->pBt = 0; pDb->pBt = NULL;
pDb->pSchema = 0; pDb->pSchema = NULL;
sqlite3ResetAllSchemasOfConnection(db); sqlite3ResetAllSchemasOfConnection(db);
db->pVfs->xDelete(db->pVfs, migrated_db_filename, 0);
sqlite3_free(migrated_db_filename);
} else {
CODEC_TRACE("*** migration failure** error code: %d\n", rc);
}
} rc = sqlite3OsDelete(db->pVfs, migrated_db_filename, 0);
if( rc!=SQLITE_OK ) goto handle_error;
goto exit; goto exit;
handle_error: handle_error:
if(pDest) sqlite3BtreeRollback(pDest, SQLITE_OK, 0); CODEC_TRACE("An error occurred attempting to migrate the database - last error %d\n", rc);
CODEC_TRACE("An error occurred attempting to migrate the database\n");
rc = SQLITE_ERROR; rc = SQLITE_ERROR;
exit: exit:
if(pass) sqlcipher_free(pass, pass_sz);
if(attach_command) sqlcipher_free(attach_command, sqlite3Strlen30(attach_command));
if(migrated_db_filename) sqlcipher_free(migrated_db_filename, sqlite3Strlen30(migrated_db_filename));
if(set_user_version) sqlcipher_free(set_user_version, sqlite3Strlen30(set_user_version));
return rc; return rc;
} }

View File

@ -7714,9 +7714,10 @@ void sqlite3pager_error(Pager *pPager, int error) {
setGetterMethod(pPager); setGetterMethod(pPager);
} }
int sqlite3pager_truncate(Pager *pPager, Pgno nPage){ void sqlite3pager_reset(Pager *pPager){
return pager_truncate(pPager, nPage); pager_reset(pPager);
} }
#endif #endif
/* END SQLCIPHER */ /* END SQLCIPHER */