/ Check-in [5a39525b]
Login

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 | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 5a39525ba3e65f1c6df3cf23fbfb57f6a0bf4b62
User & Date: danielk1977 2009-01-08 17:50:46
Context
2009-01-08
17:57
Avoid an 'invalid cast' warning in test_osinst.c. (CVS 6144) check-in: 931f3a21 user: danielk1977 tags: trunk
17:50
Fix a couple of potential corruption problems in pager.c. (CVS 6143) check-in: 5a39525b 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: 1e53e382 user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/pager.c.

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
...
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591



592
593
594
595
596
597
598
599
....
2483
2484
2485
2486
2487
2488
2489





























2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
....
2875
2876
2877
2878
2879
2880
2881
2882

2883

2884
2885
2886
2887
2888
2889
2890
** 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.541 2009/01/08 15:24:02 drh Exp $
*/
#ifndef SQLITE_OMIT_DISKIO
#include "sqliteInt.h"

/*
** Macros for troubleshooting.  Normally turned off
*/
................................................................................
** ---------------------------------------
** 0                         0
** 512                       512
** 100                       512
** 2000                      2048
** 
*/
static void seekJournalHdr(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) );



  pPager->journalOff = offset;
}

/*
** Write zeros over the header of the journal file.  This has the
** effect of invalidating the journal file and committing the
** transaction.
*/
................................................................................
  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.
        */
        i64 jrnlOff;
        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;
        }

................................................................................
      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.

      */

      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)







|







 







|








>
>
>
|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>











<







 







|
>

>







14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
...
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
....
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
....
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
** 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
*/
................................................................................
** ---------------------------------------
** 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.
*/
................................................................................
  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;
        }

................................................................................
      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