/ Check-in [68833626]
Login
SQLite training in Houston TX on 2019-11-05 (details)
Part of the 2019 Tcl Conference

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Fix the crash-followed-by-corruption bug revealed by savepoint4.test. This is actually the same bug as was fixed by (6043). The fix was not entirely correct. (CVS 6047)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 688336266f0aa5630f4f550ae3787a64f39f9cfa
User & Date: danielk1977 2008-12-20 08:39:57
Context
2008-12-20
13:18
Do not use long long constants in code. Ticket #3547. (CVS 6048) check-in: 51b3bfc3 user: drh tags: trunk
08:39
Fix the crash-followed-by-corruption bug revealed by savepoint4.test. This is actually the same bug as was fixed by (6043). The fix was not entirely correct. (CVS 6047) check-in: 68833626 user: danielk1977 tags: trunk
02:14
Specify type "void" in the parameter list of functions that take no parameters. Tickets #3545 and #3546. (CVS 6046) check-in: c2228bd1 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
....
1138
1139
1140
1141
1142
1143
1144

1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156

1157
1158
1159
1160
1161
1162
1163
....
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
....
1215
1216
1217
1218
1219
1220
1221
1222
1223


1224
1225
1226
1227
1228
1229
1230
....
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
....
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
....
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
....
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
** 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.518 2008/12/19 16:31:11 danielk1977 Exp $
*/
#ifndef SQLITE_OMIT_DISKIO
#include "sqliteInt.h"

/*
** Macros for troubleshooting.  Normally turned off
*/
................................................................................
** false for the statement journal.  The main rollback journal uses
** checksums - the statement journal does not.
*/
static int pager_playback_one_page(
  Pager *pPager,                /* The pager being played back */
  int isMainJrnl,               /* 1 -> main journal. 0 -> sub-journal. */
  i64 offset,                   /* Offset of record to playback */

  Bitvec *pDone                 /* Bitvec of pages already played back */
){
  int rc;
  PgHdr *pPg;                   /* An existing page in the cache */
  Pgno pgno;                    /* The page number of a page in journal */
  u32 cksum;                    /* Checksum used for sanity checking */
  u8 *aData = (u8 *)pPager->pTmpSpace;   /* Temp storage for a page */
  sqlite3_file *jfd = (isMainJrnl ? pPager->jfd : pPager->sjfd);

  /* The temp storage must be allocated at this point */
  assert( aData );
  assert( isMainJrnl || pDone );


  rc = read32bits(jfd, offset, &pgno);
  if( rc!=SQLITE_OK ) return rc;
  rc = sqlite3OsRead(jfd, aData, pPager->pageSize, offset+4);
  if( rc!=SQLITE_OK ) return rc;
  pPager->journalOff += pPager->pageSize + 4;

................................................................................
  if( pgno>(Pgno)pPager->dbSize || sqlite3BitvecTest(pDone, pgno) ){
    return SQLITE_OK;
  }
  if( isMainJrnl ){
    rc = read32bits(jfd, offset+pPager->pageSize+4, &cksum);
    if( rc ) return rc;
    pPager->journalOff += 4;
    if( !pDone && pager_cksum(pPager, aData)!=cksum ){
      return SQLITE_DONE;
    }
  }
  if( pDone && (rc = sqlite3BitvecSet(pDone, pgno)) ){
    return rc;
  }

................................................................................
  ** the page is marked as needSync==0.
  **
  ** 2008-04-14:  When attempting to vacuum a corrupt database file, it
  ** is possible to fail a statement on a database that does not yet exist.
  ** Do not attempt to write if database file has never been opened.
  */
  pPg = pager_lookup(pPager, pgno);
  PAGERTRACE4("PLAYBACK %d page %d hash(%08x)\n",
               PAGERID(pPager), pgno, pager_datahash(pPager->pageSize, aData));


  if( (pPager->state>=PAGER_EXCLUSIVE)
   && (pPg==0 || 0==(pPg->flags&PGHDR_NEED_SYNC))
   && (pPager->fd->pMethods)
  ){
    i64 ofst = (pgno-1)*(i64)pPager->pageSize;
    rc = sqlite3OsWrite(pPager->fd, aData, pPager->pageSize, ofst);
  }
................................................................................
    */
    void *pData;
    pData = pPg->pData;
    memcpy(pData, aData, pPager->pageSize);
    if( pPager->xReiniter ){
      pPager->xReiniter(pPg);
    }
    if( isMainJrnl && (!pDone || pPager->journalOff<=pPager->journalHdr) ){
      /* If the contents of this page were just restored from the main 
      ** journal file, then its content must be as they were when the 
      ** transaction was first opened. In this case we can mark the page
      ** as clean, since there will be no need to write it out to the.
      **
      ** There is one exception to this rule. If the page is being rolled
      ** back as part of a savepoint (or statement) rollback from an 
................................................................................
        goto end_playback;
      }
    }

    /* Copy original pages out of the journal and back into the database file.
    */
    for(u=0; u<nRec; u++){
      rc = pager_playback_one_page(pPager, 1, pPager->journalOff, 0);
      if( rc!=SQLITE_OK ){
        if( rc==SQLITE_DONE ){
          rc = SQLITE_OK;
          pPager->journalOff = szJ;
          break;
        }else{
          /* If we are unable to rollback, then the database is probably
................................................................................
  ** cleared.
  */
  szJ = pPager->journalOff;
  if( pSavepoint ){
    iHdrOff = pSavepoint->iHdrOffset ? pSavepoint->iHdrOffset : szJ;
    pPager->journalOff = pSavepoint->iOffset;
    while( rc==SQLITE_OK && pPager->journalOff<iHdrOff ){
      rc = pager_playback_one_page(pPager, 1, pPager->journalOff, pDone);
      assert( rc!=SQLITE_DONE );
    }
  }else{
    pPager->journalOff = 0;
  }
  while( rc==SQLITE_OK && pPager->journalOff<szJ ){
    u32 nJRec;         /* Number of Journal Records */
................................................................................
    u32 dummy;
    rc = readJournalHdr(pPager, szJ, &nJRec, &dummy);
    assert( rc!=SQLITE_DONE );
    if( nJRec==0 ){
      nJRec = (szJ - pPager->journalOff) / (pPager->pageSize+8);
    }
    for(ii=0; rc==SQLITE_OK && ii<nJRec && pPager->journalOff<szJ; ii++){
      rc = pager_playback_one_page(pPager, 1, pPager->journalOff, pDone);
      assert( rc!=SQLITE_DONE );
    }
  }
  assert( rc!=SQLITE_OK || pPager->journalOff==szJ );

  /* Now roll back pages from the sub-journal. */
  if( pSavepoint ){
    for(ii=pSavepoint->iSubRec; rc==SQLITE_OK && ii<pPager->stmtNRec; ii++){
      i64 offset = ii*(4+pPager->pageSize);
      rc = pager_playback_one_page(pPager, 0, offset, pDone);
      assert( rc!=SQLITE_DONE );
    }
  }

  sqlite3BitvecDestroy(pDone);
  if( rc==SQLITE_OK ){
    pPager->journalOff = szJ;







|







 







>












>







 







|







 







|
|
>
>







 







|







 







|







 







|







 







|









|







14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
....
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
....
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
....
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
....
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
....
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
....
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
....
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
** 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.519 2008/12/20 08:39:57 danielk1977 Exp $
*/
#ifndef SQLITE_OMIT_DISKIO
#include "sqliteInt.h"

/*
** Macros for troubleshooting.  Normally turned off
*/
................................................................................
** false for the statement journal.  The main rollback journal uses
** checksums - the statement journal does not.
*/
static int pager_playback_one_page(
  Pager *pPager,                /* The pager being played back */
  int isMainJrnl,               /* 1 -> main journal. 0 -> sub-journal. */
  i64 offset,                   /* Offset of record to playback */
  int isSavepnt,                /* True for a savepoint rollback */
  Bitvec *pDone                 /* Bitvec of pages already played back */
){
  int rc;
  PgHdr *pPg;                   /* An existing page in the cache */
  Pgno pgno;                    /* The page number of a page in journal */
  u32 cksum;                    /* Checksum used for sanity checking */
  u8 *aData = (u8 *)pPager->pTmpSpace;   /* Temp storage for a page */
  sqlite3_file *jfd = (isMainJrnl ? pPager->jfd : pPager->sjfd);

  /* The temp storage must be allocated at this point */
  assert( aData );
  assert( isMainJrnl || pDone );
  assert( isSavepnt || pDone==0 );

  rc = read32bits(jfd, offset, &pgno);
  if( rc!=SQLITE_OK ) return rc;
  rc = sqlite3OsRead(jfd, aData, pPager->pageSize, offset+4);
  if( rc!=SQLITE_OK ) return rc;
  pPager->journalOff += pPager->pageSize + 4;

................................................................................
  if( pgno>(Pgno)pPager->dbSize || sqlite3BitvecTest(pDone, pgno) ){
    return SQLITE_OK;
  }
  if( isMainJrnl ){
    rc = read32bits(jfd, offset+pPager->pageSize+4, &cksum);
    if( rc ) return rc;
    pPager->journalOff += 4;
    if( !isSavepnt && pager_cksum(pPager, aData)!=cksum ){
      return SQLITE_DONE;
    }
  }
  if( pDone && (rc = sqlite3BitvecSet(pDone, pgno)) ){
    return rc;
  }

................................................................................
  ** the page is marked as needSync==0.
  **
  ** 2008-04-14:  When attempting to vacuum a corrupt database file, it
  ** is possible to fail a statement on a database that does not yet exist.
  ** Do not attempt to write if database file has never been opened.
  */
  pPg = pager_lookup(pPager, pgno);
  PAGERTRACE5("PLAYBACK %d page %d hash(%08x) %s\n",
               PAGERID(pPager), pgno, pager_datahash(pPager->pageSize, aData),
               (isMainJrnl?"main-journal":"sub-journal")
  );
  if( (pPager->state>=PAGER_EXCLUSIVE)
   && (pPg==0 || 0==(pPg->flags&PGHDR_NEED_SYNC))
   && (pPager->fd->pMethods)
  ){
    i64 ofst = (pgno-1)*(i64)pPager->pageSize;
    rc = sqlite3OsWrite(pPager->fd, aData, pPager->pageSize, ofst);
  }
................................................................................
    */
    void *pData;
    pData = pPg->pData;
    memcpy(pData, aData, pPager->pageSize);
    if( pPager->xReiniter ){
      pPager->xReiniter(pPg);
    }
    if( isMainJrnl && (!isSavepnt || pPager->journalOff<=pPager->journalHdr) ){
      /* If the contents of this page were just restored from the main 
      ** journal file, then its content must be as they were when the 
      ** transaction was first opened. In this case we can mark the page
      ** as clean, since there will be no need to write it out to the.
      **
      ** There is one exception to this rule. If the page is being rolled
      ** back as part of a savepoint (or statement) rollback from an 
................................................................................
        goto end_playback;
      }
    }

    /* Copy original pages out of the journal and back into the database file.
    */
    for(u=0; u<nRec; u++){
      rc = pager_playback_one_page(pPager, 1, pPager->journalOff, 0, 0);
      if( rc!=SQLITE_OK ){
        if( rc==SQLITE_DONE ){
          rc = SQLITE_OK;
          pPager->journalOff = szJ;
          break;
        }else{
          /* If we are unable to rollback, then the database is probably
................................................................................
  ** cleared.
  */
  szJ = pPager->journalOff;
  if( pSavepoint ){
    iHdrOff = pSavepoint->iHdrOffset ? pSavepoint->iHdrOffset : szJ;
    pPager->journalOff = pSavepoint->iOffset;
    while( rc==SQLITE_OK && pPager->journalOff<iHdrOff ){
      rc = pager_playback_one_page(pPager, 1, pPager->journalOff, 1, pDone);
      assert( rc!=SQLITE_DONE );
    }
  }else{
    pPager->journalOff = 0;
  }
  while( rc==SQLITE_OK && pPager->journalOff<szJ ){
    u32 nJRec;         /* Number of Journal Records */
................................................................................
    u32 dummy;
    rc = readJournalHdr(pPager, szJ, &nJRec, &dummy);
    assert( rc!=SQLITE_DONE );
    if( nJRec==0 ){
      nJRec = (szJ - pPager->journalOff) / (pPager->pageSize+8);
    }
    for(ii=0; rc==SQLITE_OK && ii<nJRec && pPager->journalOff<szJ; ii++){
      rc = pager_playback_one_page(pPager, 1, pPager->journalOff, 1, pDone);
      assert( rc!=SQLITE_DONE );
    }
  }
  assert( rc!=SQLITE_OK || pPager->journalOff==szJ );

  /* Now roll back pages from the sub-journal. */
  if( pSavepoint ){
    for(ii=pSavepoint->iSubRec; rc==SQLITE_OK && ii<pPager->stmtNRec; ii++){
      i64 offset = ii*(4+pPager->pageSize);
      rc = pager_playback_one_page(pPager, 0, offset, 1, pDone);
      assert( rc!=SQLITE_DONE );
    }
  }

  sqlite3BitvecDestroy(pDone);
  if( rc==SQLITE_OK ){
    pPager->journalOff = szJ;

Changes to test/savepoint4.test.

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
#
#    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.
#
#***********************************************************************
#
# $Id: savepoint4.test,v 1.2 2008/12/19 18:45:53 danielk1977 Exp $

set testdir [file dirname $argv0]
source $testdir/tester.tcl


proc signature {} {
  return [db eval {SELECT count(*), md5sum(x) FROM t1}]
}

set ITERATIONS 3

expr srand(0)

do_test savepoint4-1 {
  execsql {
    PRAGMA cache_size=10;
    BEGIN;
    CREATE TABLE t1(x TEXT);







|









|
<







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
#
#    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.
#
#***********************************************************************
#
# $Id: savepoint4.test,v 1.3 2008/12/20 08:39:57 danielk1977 Exp $

set testdir [file dirname $argv0]
source $testdir/tester.tcl


proc signature {} {
  return [db eval {SELECT count(*), md5sum(x) FROM t1}]
}

set ITERATIONS 25

expr srand(0)

do_test savepoint4-1 {
  execsql {
    PRAGMA cache_size=10;
    BEGIN;
    CREATE TABLE t1(x TEXT);