Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Fix a couple of potential corruption problems in pager.c. (CVS 6143) |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
5a39525ba3e65f1c6df3cf23fbfb57f6 |
User & Date: | danielk1977 2009-01-08 17:50:46.000 |
Context
2009-01-08
| ||
17:57 | Avoid an 'invalid cast' warning in test_osinst.c. (CVS 6144) (check-in: 931f3a21bd user: danielk1977 tags: trunk) | |
17:50 | Fix a couple of potential corruption problems in pager.c. (CVS 6143) (check-in: 5a39525ba3 user: danielk1977 tags: trunk) | |
15:24 | Add a test script for ticket #2565. Change the assert() in pager.c into a testcase() macro. (CVS 6142) (check-in: 1e53e382e5 user: drh tags: trunk) | |
Changes
Changes to src/pager.c.
︙ | ︙ | |||
14 15 16 17 18 19 20 | ** The pager is used to access a database disk file. It implements ** atomic commit and rollback through the use of a journal file that ** is separate from the database file. The pager also implements file ** locking to prevent two processes from writing the same database ** file simultaneously, or one process from reading the database while ** another is writing. ** | | | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | ** The pager is used to access a database disk file. It implements ** atomic commit and rollback through the use of a journal file that ** is separate from the database file. The pager also implements file ** locking to prevent two processes from writing the same database ** file simultaneously, or one process from reading the database while ** another is writing. ** ** @(#) $Id: pager.c,v 1.542 2009/01/08 17:50:46 danielk1977 Exp $ */ #ifndef SQLITE_OMIT_DISKIO #include "sqliteInt.h" /* ** Macros for troubleshooting. Normally turned off */ |
︙ | ︙ | |||
576 577 578 579 580 581 582 | ** --------------------------------------- ** 0 0 ** 512 512 ** 100 512 ** 2000 2048 ** */ | | > > > | | 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 | ** --------------------------------------- ** 0 0 ** 512 512 ** 100 512 ** 2000 2048 ** */ static i64 journalHdrOffset(Pager *pPager){ i64 offset = 0; i64 c = pPager->journalOff; if( c ){ offset = ((c-1)/JOURNAL_HDR_SZ(pPager) + 1) * JOURNAL_HDR_SZ(pPager); } assert( offset%JOURNAL_HDR_SZ(pPager)==0 ); assert( offset>=c ); assert( (offset-c)<JOURNAL_HDR_SZ(pPager) ); return offset; } static void seekJournalHdr(Pager *pPager){ pPager->journalOff = journalHdrOffset(pPager); } /* ** Write zeros over the header of the journal file. This has the ** effect of invalidating the journal file and committing the ** transaction. */ |
︙ | ︙ | |||
2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 | if( pPager->needSync ){ assert( !pPager->tempFile ); if( pPager->journalMode!=PAGER_JOURNALMODE_MEMORY ){ int iDc = sqlite3OsDeviceCharacteristics(pPager->fd); assert( pPager->journalOpen ); if( 0==(iDc&SQLITE_IOCAP_SAFE_APPEND) ){ /* Write the nRec value into the journal file header. If in ** full-synchronous mode, sync the journal first. This ensures that ** all data has really hit the disk before nRec is updated to mark ** it as a candidate for rollback. ** ** This is not required if the persistent media supports the ** SAFE_APPEND property. Because in this case it is not possible ** for garbage data to be appended to the file, the nRec field ** is populated with 0xFFFFFFFF when the journal header is written ** and never needs to be updated. */ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < | 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 | if( pPager->needSync ){ assert( !pPager->tempFile ); if( pPager->journalMode!=PAGER_JOURNALMODE_MEMORY ){ int iDc = sqlite3OsDeviceCharacteristics(pPager->fd); assert( pPager->journalOpen ); if( 0==(iDc&SQLITE_IOCAP_SAFE_APPEND) ){ i64 jrnlOff = journalHdrOffset(pPager); u8 zMagic[8]; /* This block deals with an obscure problem. If the last connection ** that wrote to this database was operating in persistent-journal ** mode, then the journal file may at this point actually be larger ** than Pager.journalOff bytes. If the next thing in the journal ** file happens to be a journal-header (written as part of the ** previous connections transaction), and a crash or power-failure ** occurs after nRec is updated but before this connection writes ** anything else to the journal file (or commits/rolls back its ** transaction), then SQLite may become confused when doing the ** hot-journal rollback following recovery. It may roll back all ** of this connections data, then proceed to rolling back the old, ** out-of-date data that follows it. Database corruption. ** ** To work around this, if the journal file does appear to contain ** a valid header following Pager.journalOff, then write a 0x00 ** byte to the start of it to prevent it from being recognized. */ rc = sqlite3OsRead(pPager->jfd, zMagic, 8, jrnlOff); if( rc==SQLITE_OK && 0==memcmp(zMagic, aJournalMagic, 8) ){ static const u8 zerobyte = 0; rc = sqlite3OsWrite(pPager->jfd, &zerobyte, 1, jrnlOff); } if( rc!=SQLITE_OK && rc!=SQLITE_IOERR_SHORT_READ ){ return rc; } /* Write the nRec value into the journal file header. If in ** full-synchronous mode, sync the journal first. This ensures that ** all data has really hit the disk before nRec is updated to mark ** it as a candidate for rollback. ** ** This is not required if the persistent media supports the ** SAFE_APPEND property. Because in this case it is not possible ** for garbage data to be appended to the file, the nRec field ** is populated with 0xFFFFFFFF when the journal header is written ** and never needs to be updated. */ if( pPager->fullSync && 0==(iDc&SQLITE_IOCAP_SEQUENTIAL) ){ PAGERTRACE(("SYNC journal of %d\n", PAGERID(pPager))); IOTRACE(("JSYNC %p\n", pPager)) rc = sqlite3OsSync(pPager->jfd, pPager->sync_flags); if( rc!=0 ) return rc; } |
︙ | ︙ | |||
2875 2876 2877 2878 2879 2880 2881 | pPager->journalOpen = 1; pPager->journalStarted = 0; pPager->journalOff = 0; pPager->setMaster = 0; pPager->journalHdr = 0; /* Playback and delete the journal. Drop the database write | | > > | 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 | pPager->journalOpen = 1; pPager->journalStarted = 0; pPager->journalOff = 0; pPager->setMaster = 0; pPager->journalHdr = 0; /* Playback and delete the journal. Drop the database write ** lock and reacquire the read lock. Purge the cache before ** playing back the hot-journal so that we don't end up with */ sqlite3PcacheClear(pPager->pPCache); rc = pager_playback(pPager, 1); if( rc!=SQLITE_OK ){ rc = pager_error(pPager, rc); goto failed; } assert(pPager->state==PAGER_SHARED || (pPager->exclusiveMode && pPager->state>PAGER_SHARED) |
︙ | ︙ |
Added test/crash8.test.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | # 2009 January 8 # # 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 test verifies a couple of specific potential data corruption # scenarios involving crashes or power failures. # # $Id: crash8.test,v 1.1 2009/01/08 17:50:46 danielk1977 Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl ifcapable !crashtest { finish_test return } do_test crash8-1.1 { execsql { CREATE TABLE t1(a, b); CREATE INDEX i1 ON t1(a, b); INSERT INTO t1 VALUES(1, randstr(1000,1000)); INSERT INTO t1 VALUES(2, randstr(1000,1000)); INSERT INTO t1 VALUES(3, randstr(1000,1000)); INSERT INTO t1 VALUES(4, randstr(1000,1000)); INSERT INTO t1 VALUES(5, randstr(1000,1000)); INSERT INTO t1 VALUES(6, randstr(1000,1000)); CREATE TABLE t2(a, b); CREATE TABLE t3(a, b); CREATE TABLE t4(a, b); CREATE TABLE t5(a, b); CREATE TABLE t6(a, b); CREATE TABLE t7(a, b); CREATE TABLE t8(a, b); CREATE TABLE t9(a, b); CREATE TABLE t10(a, b); PRAGMA integrity_check } } {ok} # Potential corruption scenario 1. A second process opens the database # and modifies a large portion of it. It then opens a second transaction # and modifies a small part of the database, but crashes before it commits # the transaction. # # When the first process accessed the database again, it was rolling back # the aborted transaction, but was not purging its in-memory cache (which # was loaded before the second process made its first, successful, # modification). Producing an inconsistent cache. # do_test crash8-1.2 { crashsql -delay 2 -file test.db { PRAGMA cache_size = 10; UPDATE t1 SET b = randstr(1000,1000); INSERT INTO t9 VALUES(1, 2); } } {1 {child process exited abnormally}} do_test crash8-1.3 { execsql {PRAGMA integrity_check} } {ok} # Potential corruption scenario 2. The second process, operating in # persistent-journal mode, makes a large change to the database file # with a small in-memory cache. Such that more than one journal-header # was written to the file. It then opens a second transaction and makes # a smaller change that requires only a single journal-header to be # written to the journal file. The second change is such that the # journal content written to the persistent journal file exactly overwrites # the first journal-header and set of subsequent records written by the # first, successful, change. The second process crashes before it can # commit its second change. # # When the first process accessed the database again, it was rolling back # the second aborted transaction, then continuing to rollback the second # and subsequent journal-headers written by the first, successful, change. # Database corruption. # do_test crash8.2.1 { crashsql -delay 2 -file test.db { PRAGMA journal_mode = persist; PRAGMA cache_size = 10; UPDATE t1 SET b = randstr(1000,1000); PRAGMA cache_size = 100; BEGIN; INSERT INTO t2 VALUES('a', 'b'); INSERT INTO t3 VALUES('a', 'b'); INSERT INTO t4 VALUES('a', 'b'); INSERT INTO t5 VALUES('a', 'b'); INSERT INTO t6 VALUES('a', 'b'); INSERT INTO t7 VALUES('a', 'b'); INSERT INTO t8 VALUES('a', 'b'); INSERT INTO t9 VALUES('a', 'b'); INSERT INTO t10 VALUES('a', 'b'); COMMIT; } } {1 {child process exited abnormally}} do_test crash8-2.3 { execsql {PRAGMA integrity_check} } {ok} finish_test |