]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add a test (and fix) for possible corruption if malloc() fails during a CREATE INDEX...
authordanielk1977 <danielk1977@noemail.net>
Fri, 18 Jan 2008 13:42:54 +0000 (13:42 +0000)
committerdanielk1977 <danielk1977@noemail.net>
Fri, 18 Jan 2008 13:42:54 +0000 (13:42 +0000)
FossilOrigin-Name: 65245d9904db19568d5092926b27f0c193c9ef0b

manifest
manifest.uuid
src/pager.c
src/test6.c
test/crash5.test [new file with mode: 0644]

index c57960cca08c2b07c3a77a73a88443b3c6128759..796a47072168752c010e576d48cb00770e7e187f 100644 (file)
--- 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
index dd61cedae120ce00bac9fb6b3c9b3e7ea2c23cbb..b69cb1fabe5898a883a511af79f74a129b6773f4 100644 (file)
@@ -1 +1 @@
-87534dfff9e7a37c624a83c79f4074f29ff16c9e
\ No newline at end of file
+65245d9904db19568d5092926b27f0c193c9ef0b
\ No newline at end of file
index c59fb7b9263f17e2aff12f2ff123b4be2059d590..5b81b70f0dc4e02d165e80639e930846eff20e0d 100644 (file)
@@ -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;
     }
index 39bbc072e16faa6479ad0d2d5e7e44abb0fb0973..2e8c4f69ddf82a6c341f9e94995f3efd4d305251 100644 (file)
@@ -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 (file)
index 0000000..de5a791
--- /dev/null
@@ -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
+