From: danielk1977 Date: Fri, 18 Jan 2008 13:42:54 +0000 (+0000) Subject: Add a test (and fix) for possible corruption if malloc() fails during a CREATE INDEX... X-Git-Tag: version-3.6.10~1452 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a98d7b47975e218820ddc3a8b6a16fc98fe047f4;p=thirdparty%2Fsqlite.git Add a test (and fix) for possible corruption if malloc() fails during a CREATE INDEX statement, the application continues with the transaction, then crashes. (CVS 4725) FossilOrigin-Name: 65245d9904db19568d5092926b27f0c193c9ef0b --- diff --git a/manifest b/manifest index c57960cca0..796a470721 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sa\scouple\sof\smissing\scalls\sto\spagerLeave().\s(CVS\s4724) -D 2008-01-18T11:33:16 +C Add\sa\stest\s(and\sfix)\sfor\spossible\scorruption\sif\smalloc()\sfails\sduring\sa\sCREATE\sINDEX\sstatement,\sthe\sapplication\scontinues\swith\sthe\stransaction,\sthen\scrashes.\s(CVS\s4725) +D 2008-01-18T13:42:55 F Makefile.arm-wince-mingw32ce-gcc ac5f7b2cef0cd850d6f755ba6ee4ab961b1fadf7 F Makefile.in 30789bf70614bad659351660d76b8e533f3340e9 F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654 @@ -124,7 +124,7 @@ F src/os_unix.c e4daef7628f690fa2b188af3632fb18f96525946 F src/os_unix.h 5768d56d28240d3fe4537fac08cc85e4fb52279e F src/os_win.c c832d528ea774c7094d887749d71884984c9034c F src/os_win.h 41a946bea10f61c158ce8645e7646b29d44f122b -F src/pager.c cf40ab8f57884ce1b5a355c0e42cdbaceaa42de0 +F src/pager.c a55923e2de46b81868da9e6e6f348e2978d477b9 F src/pager.h f504f7ae84060fee0416a853e368d3d113c3d6fa F src/parse.y 2ae06e8d3190faace49c5b82e7cea1fc60d084a1 F src/pragma.c 155315ee3e6a861a0060ba4d184dfffd08ebbc03 @@ -145,7 +145,7 @@ F src/test2.c 77b34303883b9d722c65a6879bb0163a400e3789 F src/test3.c df62cd5c971dc1ae0be0a1842f9df3390934e7a6 F src/test4.c c2c0f5dc907f1346f5d4b65eb5799f11eb9e4071 F src/test5.c 3a6a5717a149d7ca2e6d14f5be72cf7555d54dc4 -F src/test6.c f40e41e1808d743e995a5016dc9a56702d1632bd +F src/test6.c 8944b6482be3a54505c4f14339392448e4cabe66 F src/test7.c acec2256c7c2d279db5a8b5fa1a2a68fcc942c67 F src/test8.c 6399d2f0561f1f65785c63e94f2cdd36fb248872 F src/test9.c b46c8fe02ac7cca1a7316436d8d38d50c66f4b2f @@ -243,6 +243,7 @@ F test/crash.test 1b6ac8410689ff78028887f445062dc897c9ac89 F test/crash2.test 26d7a4c5520201e5de2c696ea51ab946b59dc0e9 F test/crash3.test 0b09687ae1a3ccbcefdfaeb4b963e26e36255d76 F test/crash4.test 02ff4f15c149ca1e88a5c299b4896c84d9450c3b +F test/crash5.test 0a20b3c310323f1c6595e2866e238aa4c467eef3 F test/crashtest1.c 09c1c7d728ccf4feb9e481671e29dda5669bbcc2 F test/createtab.test 199cf68f44e5d9e87a0b8afc7130fdeb4def3272 F test/date.test 51734f3798f338e3f75107aff5a057ae0ff7006c @@ -606,7 +607,7 @@ F www/tclsqlite.tcl 8be95ee6dba05eabcd27a9d91331c803f2ce2130 F www/vdbe.tcl 87a31ace769f20d3627a64fa1fade7fed47b90d0 F www/version3.tcl 890248cf7b70e60c383b0e84d77d5132b3ead42b F www/whentouse.tcl fc46eae081251c3c181bd79c5faef8195d7991a5 -P 251c78a982a33194a052897c37a2a79ae9654452 -R c0ed04846e95ed3c4b78c2b81c8b73fe +P 87534dfff9e7a37c624a83c79f4074f29ff16c9e +R cbf1e7869193895b2025968111b8f3b5 U danielk1977 -Z a0c52f569e9f496fa4f45cbe77dad52f +Z 9aca5a3c5a8b6ed139614576f4b62520 diff --git a/manifest.uuid b/manifest.uuid index dd61cedae1..b69cb1fabe 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -87534dfff9e7a37c624a83c79f4074f29ff16c9e \ No newline at end of file +65245d9904db19568d5092926b27f0c193c9ef0b \ No newline at end of file diff --git a/src/pager.c b/src/pager.c index c59fb7b926..5b81b70f0d 100644 --- a/src/pager.c +++ b/src/pager.c @@ -18,7 +18,7 @@ ** file simultaneously, or one process from reading the database while ** another is writing. ** -** @(#) $Id: pager.c,v 1.401 2008/01/18 11:33:16 danielk1977 Exp $ +** @(#) $Id: pager.c,v 1.402 2008/01/18 13:42:55 danielk1977 Exp $ */ #ifndef SQLITE_OMIT_DISKIO #include "sqliteInt.h" @@ -5052,6 +5052,13 @@ int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno){ ** Pager.aInJournal bit has been set. This needs to be remedied by loading ** the page into the pager-cache and setting the PgHdr.needSync flag. ** + ** If the attempt to load the page into the page-cache fails, (due + ** to a malloc() or IO failure), clear the bit in the aInJournal[] + ** array. Otherwise, if the page is loaded and written again in + ** this transaction, it may be written to the database file before + ** it is synced into the journal file. This way, it may end up in + ** the journal file twice, but that is not a problem. + ** ** The sqlite3PagerGet() call may cause the journal to sync. So make ** sure the Pager.needSync flag is set too. */ @@ -5060,6 +5067,9 @@ int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno){ assert( pPager->needSync ); rc = sqlite3PagerGet(pPager, needSyncPgno, &pPgHdr); if( rc!=SQLITE_OK ){ + if( pPager->aInJournal && (int)needSyncPgno<=pPager->origDbSize ){ + pPager->aInJournal[needSyncPgno/8] &= ~(1<<(needSyncPgno&7)); + } pagerLeave(pPager); return rc; } diff --git a/src/test6.c b/src/test6.c index 39bbc072e1..2e8c4f69dd 100644 --- a/src/test6.c +++ b/src/test6.c @@ -155,6 +155,16 @@ static CrashGlobal g = {0, 0, SQLITE_DEFAULT_SECTOR_SIZE, 0, 0}; */ static int sqlite3CrashTestEnable = 0; +static void *crash_malloc(int nByte){ + return (void *)Tcl_Alloc((size_t)nByte); +} +static void crash_free(void *p){ + Tcl_Free(p); +} +static void *crash_realloc(void *p, int n){ + return (void *)Tcl_Realloc(p, (size_t)n); +} + /* ** Flush the write-list as if xSync() had been called on file handle ** pFile. If isCrash is true, simulate a crash. @@ -263,7 +273,7 @@ static int writeListSync(CrashFile *pFile, int isCrash){ ); } #endif - sqlite3_free(pWrite); + crash_free(pWrite); break; } case 2: { /* Do nothing */ @@ -290,7 +300,7 @@ static int writeListSync(CrashFile *pFile, int isCrash){ ); #endif - zGarbage = sqlite3_malloc(g.iSectorSize); + zGarbage = crash_malloc(g.iSectorSize); if( zGarbage ){ sqlite3_int64 i; for(i=iFirst; rc==SQLITE_OK && i<=iLast; i++){ @@ -299,7 +309,7 @@ static int writeListSync(CrashFile *pFile, int isCrash){ pRealFile, zGarbage, g.iSectorSize, i*g.iSectorSize ); } - sqlite3_free(zGarbage); + crash_free(zGarbage); }else{ rc = SQLITE_NOMEM; } @@ -338,10 +348,11 @@ static int writeListAppend( assert((zBuf && nBuf) || (!nBuf && !zBuf)); - pNew = (WriteBuffer *)sqlite3MallocZero(sizeof(WriteBuffer) + nBuf); + pNew = (WriteBuffer *)crash_malloc(sizeof(WriteBuffer) + nBuf); if( pNew==0 ){ fprintf(stderr, "out of memory in the crash simulator\n"); } + memset(pNew, 0, sizeof(WriteBuffer)+nBuf); pNew->iOffset = iOffset; pNew->nBuf = nBuf; pNew->pFile = (CrashFile *)pFile; @@ -407,7 +418,7 @@ static int cfWrite( while( pCrash->iSize>pCrash->nData ){ u8 *zNew; int nNew = (pCrash->nData*2) + 4096; - zNew = sqlite3_realloc(pCrash->zData, nNew); + zNew = crash_realloc(pCrash->zData, nNew); if( !zNew ){ return SQLITE_NOMEM; } @@ -549,7 +560,7 @@ static int cfOpen( } if( rc==SQLITE_OK ){ pWrapper->nData = (4096 + pWrapper->iSize); - pWrapper->zData = sqlite3_malloc(pWrapper->nData); + pWrapper->zData = crash_malloc(pWrapper->nData); if( pWrapper->zData ){ memset(pWrapper->zData, 0, pWrapper->nData); rc = sqlite3OsRead(pReal, pWrapper->zData, pWrapper->iSize, 0); diff --git a/test/crash5.test b/test/crash5.test new file mode 100644 index 0000000000..de5a7912f7 --- /dev/null +++ b/test/crash5.test @@ -0,0 +1,111 @@ + +# 2007 Aug 13 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# This file tests aspects of recovery from a malloc() failure +# in a CREATE INDEX statement. +# +# $Id: crash5.test,v 1.1 2008/01/18 13:42:55 danielk1977 Exp $ + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# Only run these tests if memory debugging is turned on. +# +ifcapable !memdebug||!crashtest||!memorymanage { + puts "Skipping crash5 tests: not compiled with -DSQLITE_MEMDEBUG..." + finish_test + return +} + +db close + +for {set ii 0} {$ii < 10} {incr ii} { + for {set jj 50} {$jj < 100} {incr jj} { + + # Set up the database so that it is an auto-vacuum database + # containing a single table (root page 3) with a single row. + # The row has an overflow page (page 4). + file delete -force test.db test.db-journal + sqlite3 db test.db + set c [string repeat 3 1500] + db eval { + pragma auto_vacuum = 1; + CREATE TABLE t1(a, b, c); + INSERT INTO t1 VALUES('1111111111', '2222222222', $c); + } + db close + + do_test crash5-$ii.$jj.1 { + crashsql -delay 1 -file test.db-journal -seed $ii -tclbody [join [list \ + [list set iFail $jj] { + sqlite3_crashparams 0 /Users/dan/Desktop/sqlite/bld/test.db-journal + + # Begin a transaction and evaluate a "CREATE INDEX" statement + # with the iFail'th malloc() set to fail. This operation will + # have to move the current contents of page 4 (the overflow + # page) to make room for the new root page. The bug is that + # if malloc() fails at a particular point in sqlite3PagerMovepage(), + # sqlite mistakenly thinks that the page being moved (page 4) has + # been safely synced into the journal. If the page is written + # to later in the transaction, it may be written out to the database + # before the relevant part of the journal has been synced. + # + db eval BEGIN + sqlite3_memdebug_fail $iFail -repeat 0 + catch {db eval { CREATE UNIQUE INDEX i1 ON t1(a); }} msg + # puts "$n $msg ac=[sqlite3_get_autocommit db]" + + # If the transaction is still active (it may not be if the malloc() + # failure occured in the OS layer), write to the database. Make sure + # page 4 is among those written. + # + if {![sqlite3_get_autocommit db]} { + db eval { + DELETE FROM t1; -- This will put page 4 on the free list. + INSERT INTO t1 VALUES('111111111', '2222222222', '33333333'); + INSERT INTO t1 SELECT * FROM t1; -- 2 + INSERT INTO t1 SELECT * FROM t1; -- 4 + INSERT INTO t1 SELECT * FROM t1; -- 8 + INSERT INTO t1 SELECT * FROM t1; -- 16 + INSERT INTO t1 SELECT * FROM t1; -- 32 + INSERT INTO t1 SELECT * FROM t1 WHERE rowid%2; -- 48 + } + } + + # If the right malloc() failed during the 'CREATE INDEX' above and + # the transaction was not rolled back, then the sqlite cache now + # has a dirty page 4 that it incorrectly believes is already safely + # in the synced part of the journal file. When + # sqlite3_release_memory() is called sqlite tries to free memory + # by writing page 4 out to the db file. If it crashes later on, + # before syncing the journal... Corruption! + # + sqlite3_crashparams 1 /Users/dan/Desktop/sqlite/bld/test.db-journal + sqlite3_release_memory 8092 + }]] {} + expr 1 + } {1} + + sqlite3 db test.db + do_test crash5-$ii.$jj.2 { + db eval {pragma integrity_check} + } {ok} + do_test crash5-$ii.$jj.3 { + db eval {SELECT * FROM t1} + } [list 1111111111 2222222222 $::c] + db close + } +} + + +finish_test +