SQLite4
Check-in [93af0d7d05]
Not logged in

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

Overview
Comment:Add missing calls to xSync(). Fix a problem with recovering wrapped logs.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 93af0d7d05c2f0022b420a37f0a706720b3d0a02
User & Date: dan 2013-11-08 17:50:48
Context
2013-11-08
20:18
Fix a robustness related problem with wrapped logs. check-in: 7dd7b942fd user: dan tags: trunk
17:50
Add missing calls to xSync(). Fix a problem with recovering wrapped logs. check-in: 93af0d7d05 user: dan tags: trunk
2013-11-07
20:22
Begin adding tests to check that bt database transactions are robust in the face of system failure. check-in: 5d57889261 user: dan tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to lsm-test/lsmtest2.c.

432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
...
475
476
477
478
479
480
481
482
483
484
485

486
487
488
489
490
491
492

/*
** This test verifies that if a system crash occurs while committing a
** transaction to the log file, no earlier transactions are lost or damaged.
*/
static void crash_test2b(int bCompress, int *pRc){
  const char *DBNAME = "testdb.bt";
  const DatasourceDefn defn = {TEST_DATASOURCE_RANDOM, 12, 16, 1000, 1000};

  const int nIter = 200;
  const int nInsert = 20;

  int i;
  int iDot = 0;
  Datasource *pData;
  CksumDb *pCksumDb;
  TestDb *pDb;

................................................................................
      testrc = tdb_write(pDb, pKey, nKey, pVal, nVal);
      if( testrc ) break;
    }
    tdb_close(pDb);

    /* Check that no data was lost when the system crashed. */
    testCompareCksumBtdb(DBNAME, 
      testCksumArrayGet(pCksumDb, 100 + iIns),
      testCksumArrayGet(pCksumDb, 100 + iIns + 1),
      pRc
    );

  }

  testDatasourceFree(pData);
  testCksumArrayFree(pCksumDb);
}

/*







|


|







 







|
|


>







432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
...
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493

/*
** This test verifies that if a system crash occurs while committing a
** transaction to the log file, no earlier transactions are lost or damaged.
*/
static void crash_test2b(int bCompress, int *pRc){
  const char *DBNAME = "testdb.bt";
  const DatasourceDefn defn = {TEST_DATASOURCE_RANDOM, 12, 16, 800, 800};

  const int nIter = 200;
  const int nInsert = 200;

  int i;
  int iDot = 0;
  Datasource *pData;
  CksumDb *pCksumDb;
  TestDb *pDb;

................................................................................
      testrc = tdb_write(pDb, pKey, nKey, pVal, nVal);
      if( testrc ) break;
    }
    tdb_close(pDb);

    /* Check that no data was lost when the system crashed. */
    testCompareCksumBtdb(DBNAME, 
      testCksumArrayGet(pCksumDb, 100 + iIns - (testrc==0)),
      testCksumArrayGet(pCksumDb, 100 + iIns + 1 - (testrc==0)),
      pRc
    );

  }

  testDatasourceFree(pData);
  testCksumArrayFree(pCksumDb);
}

/*

Changes to lsm-test/lsmtest_tdb4.c.

98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
...
130
131
132
133
134
135
136
137

138
139
140
141
142
143
144
...
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
...
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
...
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250

static int btVfsRead(bt_file *pFile, sqlite4_int64 iOff, void *pBuf, int nBuf){
  BtFile *p = (BtFile*)pFile;
  if( p->pBt->bCrash ) return SQLITE4_IOERR;
  return p->pBt->pVfs->xRead(p->pFile, iOff, pBuf, nBuf);
}

static int btFlushSectors(BtFile *p){
  sqlite4_int64 iSz;
  int rc;
  int i;
  u8 *aTmp = 0;

  rc = p->pBt->pVfs->xSize(p->pFile, &iSz);
  for(i=0; rc==SQLITE4_OK && i<p->nSector; i++){
................................................................................
        }else if( iOpt==3 ){
          if( aTmp==0 ) aTmp = testMalloc(p->nSectorSize);
          aWrite = aTmp;
          testPrngArray(i*13, (u32*)aWrite, nWrite/sizeof(u32));
        }

#if 0
fprintf(stderr, "handle sector %d with %s\n", i, 

    iOpt==1 ? "rollback" : iOpt==2 ? "write" : "omit"
);
fflush(stderr);
#endif

        if( aWrite ){
          rc = p->pBt->pVfs->xWrite(p->pFile, iSOff, aWrite, nWrite);
................................................................................
  int iSector;                    /* Current sector */
  int iLast;                      /* Last sector affected */

  if( p->nSectorSize==0 ){
    p->nSectorSize = p->pBt->pVfs->xSectorSize(p->pFile);
    if( p->nSectorSize<512 ) p->nSectorSize = 512;
  }
  iLast = (iOff+nBuf) / p->nSectorSize;
  iFirst = iOff / p->nSectorSize;

  rc = p->pBt->pVfs->xSize(p->pFile, &iSz);
  for(iSector=iFirst; rc==SQLITE4_OK && iSector<=iLast; iSector++){
    int nRead;
    sqlite4_int64 iSOff = iSector * p->nSectorSize;
    u8 *aBuf = testMalloc(p->nSectorSize);
................................................................................
  BtDb *pBt = p->pBt;

  if( p->pBt->bCrash ) return SQLITE4_IOERR;
  if( pBt->nCrashSync ){
    pBt->nCrashSync--;
    pBt->bCrash = (pBt->nCrashSync==0);
    if( pBt->bCrash ){
      btFlushSectors(pBt->apFile[0]);
      btFlushSectors(pBt->apFile[1]);
      rc = SQLITE4_IOERR;
    }else{
      btFlushSectors(p);
    }
  }

  if( rc==SQLITE4_OK ){
    rc = p->pBt->pVfs->xSync(p->pFile);
  }
  return rc;
................................................................................
  return p->pBt->pVfs->xSectorSize(p->pFile);
}

static int btVfsClose(bt_file *pFile){
  BtFile *p = (BtFile*)pFile;
  int rc;
  assert( p->pBt->apFile[0]==p || p->pBt->apFile[1]==p );
  btFlushSectors(p);
  testFree(p->apSector);
  rc = p->pBt->pVfs->xClose(p->pFile);
  testFree(p);
  return rc;
}

static int btVfsUnlink(sqlite4_env *pEnv, bt_env *pVfs, const char *zFile){







|







 







|
>







 







|







 







|
|


|







 







|







98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
...
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
...
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
...
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
...
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251

static int btVfsRead(bt_file *pFile, sqlite4_int64 iOff, void *pBuf, int nBuf){
  BtFile *p = (BtFile*)pFile;
  if( p->pBt->bCrash ) return SQLITE4_IOERR;
  return p->pBt->pVfs->xRead(p->pFile, iOff, pBuf, nBuf);
}

static int btFlushSectors(BtFile *p, int iFile){
  sqlite4_int64 iSz;
  int rc;
  int i;
  u8 *aTmp = 0;

  rc = p->pBt->pVfs->xSize(p->pFile, &iSz);
  for(i=0; rc==SQLITE4_OK && i<p->nSector; i++){
................................................................................
        }else if( iOpt==3 ){
          if( aTmp==0 ) aTmp = testMalloc(p->nSectorSize);
          aWrite = aTmp;
          testPrngArray(i*13, (u32*)aWrite, nWrite/sizeof(u32));
        }

#if 0
fprintf(stderr, "handle sector %d of %s with %s\n", i, 
    iFile==0 ? "db" : "log",
    iOpt==1 ? "rollback" : iOpt==2 ? "write" : "omit"
);
fflush(stderr);
#endif

        if( aWrite ){
          rc = p->pBt->pVfs->xWrite(p->pFile, iSOff, aWrite, nWrite);
................................................................................
  int iSector;                    /* Current sector */
  int iLast;                      /* Last sector affected */

  if( p->nSectorSize==0 ){
    p->nSectorSize = p->pBt->pVfs->xSectorSize(p->pFile);
    if( p->nSectorSize<512 ) p->nSectorSize = 512;
  }
  iLast = (iOff+nBuf-1) / p->nSectorSize;
  iFirst = iOff / p->nSectorSize;

  rc = p->pBt->pVfs->xSize(p->pFile, &iSz);
  for(iSector=iFirst; rc==SQLITE4_OK && iSector<=iLast; iSector++){
    int nRead;
    sqlite4_int64 iSOff = iSector * p->nSectorSize;
    u8 *aBuf = testMalloc(p->nSectorSize);
................................................................................
  BtDb *pBt = p->pBt;

  if( p->pBt->bCrash ) return SQLITE4_IOERR;
  if( pBt->nCrashSync ){
    pBt->nCrashSync--;
    pBt->bCrash = (pBt->nCrashSync==0);
    if( pBt->bCrash ){
      btFlushSectors(pBt->apFile[0], 0);
      btFlushSectors(pBt->apFile[1], 1);
      rc = SQLITE4_IOERR;
    }else{
      btFlushSectors(p, 0);
    }
  }

  if( rc==SQLITE4_OK ){
    rc = p->pBt->pVfs->xSync(p->pFile);
  }
  return rc;
................................................................................
  return p->pBt->pVfs->xSectorSize(p->pFile);
}

static int btVfsClose(bt_file *pFile){
  BtFile *p = (BtFile*)pFile;
  int rc;
  assert( p->pBt->apFile[0]==p || p->pBt->apFile[1]==p );
  btFlushSectors(p, 0);
  testFree(p->apSector);
  rc = p->pBt->pVfs->xClose(p->pFile);
  testFree(p);
  return rc;
}

static int btVfsUnlink(sqlite4_env *pEnv, bt_env *pVfs, const char *zFile){

Changes to src/bt_log.c.

240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
...
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
...
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400




















401
402
403
404
405
406
407
...
484
485
486
487
488
489
490


491
492
493
494
495
496
497
...
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
...
657
658
659
660
661
662
663
664


665
666

667
668
669
670
671
672
673
...
686
687
688
689
690
691
692

693
694
695
696
697
698
699
...
951
952
953
954
955
956
957


958
959
960
961
962
963
964
...
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
....
1717
1718
1719
1720
1721
1722
1723





1724
1725
1726
1727
1728
1729
1730
....
1748
1749
1750
1751
1752
1753
1754



1755
1756
1757
1758
1759
1760
1761
  u32 *aOut                       /* OUT: Final checksum value output */
){
  assert( (nByte&0x00000007)==4 && nByte>=8 );
  btLogChecksum(nativeCksum, a, 8, aIn, aOut);
  btLogChecksum(nativeCksum, &a[4], nByte-4, aOut, aOut);
}

#define BT_PAGE_DEBUG 1
#define BT_VAL_DEBUG  0

static void btDebugTopology(BtLock *pLock, char *zStr, int iSide, u32 *aLog){
#if BT_PAGE_DEBUG
  fprintf(stderr, "%d:%s: (side=%d) %d..%d  %d..%d  %d..%d\n", 
      pLock->iDebugId, zStr, iSide,
      (int)aLog[0], (int)aLog[1], (int)aLog[2], 
................................................................................
#if BT_VAL_DEBUG
  u8 aKBuf[40];
  u8 aVBuf[40];
  static int nCall = 0;

  binToStr(pK, nK, aKBuf, sizeof(aKBuf));
  binToStr(pV, nV, aVBuf, sizeof(aVBuf));
  fprintf(stderr, "%d:%d: %s \"%s\" -> \"%s\"\n", 
      pLock->iDebugId, nCall++, zStr, aKBuf, aVBuf
  );

  fflush(stderr);
#endif
}
#endif

................................................................................
  fprintf(stderr, "%d:%d: Search log for page %d (safe=%d) - frame %d\n", 
      pLock->iDebugId, nCall++, (int)pgno, (int)iSafe, (int)iFrame
  );
  fflush(stderr);
#endif
}

static void btDebugSetPgno(
    BtLock *pLock, int iHash, u32 *aPgno, int iFrame, int iZero, u32 pgno
){
#if BT_PAGE_DEBUG
  static int nCall = 0;
  fprintf(stderr, "%d:%d: Set iHash=%d aPgno=%p iFrame=%d iZero=%d pgno=%d\n",
      pLock->iDebugId, nCall++, iHash,
      (void*)aPgno, iFrame, iZero, (int)pgno
  );
  fflush(stderr);
#endif
}





















/*
** Ensure that shared-memory chunk iChunk is mapped and available in
** the BtLog.apShm[] array. If an error occurs, return an SQLite4 error
** code. Otherwise, SQLITE4_OK.
*/
static int btLogMapShm(BtLog *pLog, int iChunk){
................................................................................
    u32 aCksum[2];
    btLogChecksum(1, (u8*)pHdr, offsetof(BtWalHdr, aCksum), 0, aCksum);
    if( pHdr->iMagic!=BT_WAL_MAGIC 
     || aCksum[0]!=pHdr->aCksum[0] 
     || aCksum[1]!=pHdr->aCksum[1] 
    ){
      rc = SQLITE4_NOTFOUND;


    }
  }
  return rc;
}

/*
** This function is used as part of recovery. It reads the contents of
................................................................................
  rc = btLogFindHash(pLog, iSide, iHash, &aHash, &aPgno, &iZero);

  /* Update the hash table */
  if( rc==SQLITE4_OK ){
    int iSlot;
    int nCollide = HASHTABLE_NSLOT*2;
    aPgno[iFrame-iZero] = pgno;
btDebugSetPgno(pLog->pLock, iHash, aPgno, iFrame, iZero, pgno);

    if( iFrame==iZero ){
      memset(aHash, 0, sizeof(ht_slot) * HASHTABLE_NSLOT);
    }

    for(iSlot=btLogHashKey(pLog,pgno); ; iSlot=btLogHashNext(pLog, iSlot)){
      if( aHash[iSlot]==0 ){
................................................................................
  rc = btLogFindHash(pLog, iSide, iHash, &aHash, &aPgno, &iZero);
  if( rc==SQLITE4_OK ){
    int i;
    ht_slot iMax;
    iMax = (iFrame - iZero) + 1;

    for(i=0; i<HASHTABLE_NSLOT; i++){
      if( aHash[i]>iMax ) aHash[i] = 0;


    }
    memset(&aPgno[iMax], 0, (nPgno-iMax)*sizeof(u32));

  }

  return rc;
}


/*
................................................................................

static int btLogRecoverFrame(
  BtLog *pLog,                    /* Log module handle */
  void *pCtx,                     /* woints to type u32 - pgno of last commit*/
  u32 iFrame,                     /* Frame number */
  BtFrameHdr *pHdr                /* Frame header */
){

  if( btLogIsEmpty(pLog) ){
    /* This is the first frame recovered. It is therefore both the first
    ** and last frame of log region (c).  */
    pLog->snapshot.aLog[4] = iFrame;
    pLog->snapshot.aLog[5] = iFrame;
  }else{
    u32 iExpect = pLog->snapshot.aLog[5]+1;
................................................................................
}

static int btLogWriteHeader(BtLog *pLog, int iHdr, BtWalHdr *pHdr){
  int rc;                         /* Return code */
  i64 iOff;                       /* File offset to write to */
  assert( iHdr==0 || iHdr==1 );



  /* Calculate a checksum for the header */
  btLogChecksum(1, (u8*)pHdr, offsetof(BtWalHdr, aCksum), 0, pHdr->aCksum);

  /* Write the object to disk */
  iOff = iHdr * pLog->snapshot.nSector;
  rc = btLogWriteData(pLog, iOff, (u8*)pHdr, sizeof(BtWalHdr));

................................................................................

  rc = btLogFindHash(pLog, iSide, iHash, &aHash, &aPgno, &iZero);
  if( rc==SQLITE4_OK ){
    int nCollide = HASHTABLE_NSLOT*2;
    int iSlot;
    u32 iFrame = 0;
    
    iSlot=btLogHashKey(pLog, pgno); 
    for( ; aHash[iSlot]; iSlot=btLogHashNext(pLog, iSlot)){
      if( aPgno[aHash[iSlot]-1]==pgno ){
        u32 iCandidate = iZero + aHash[iSlot] - 1;
        if( iCandidate<=iHi ) iFrame = iCandidate;
      }
      if( (nCollide--)==0 ) return btErrorBkpt(SQLITE4_CORRUPT);
    }
................................................................................
          }
          btDebugCkptPage(pLog->pLock, pgno, aBuf, pgsz);
          rc = pVfs->xWrite(pFd, iOff, aBuf, pgsz);
        }else if( rc==SQLITE4_NOTFOUND ){
          rc = SQLITE4_OK;
        }
      }






      /* Update the first field of the checkpoint-header. This tells readers
      ** that they need not consider anything that in the log before this
      ** point (since the data has already been copied into the database
      ** file).  */
      if( rc==SQLITE4_OK ){
        assert( iFirstRead>0 );
................................................................................
        hdr.nSector = pLog->snapshot.nSector;
        hdr.nPgsz = pgsz;
        hdr.iFirstFrame = iFirstRead;

        hdr.iSalt1 = fhdr.aCksum[0];
        hdr.iSalt2 = fhdr.aCksum[1];
        rc = btLogWriteHeader(pLog, iSlot, &hdr);



        if( rc==SQLITE4_OK ){
          pShm->ckpt.iWalHdr = (iSlot<<2) + hdr.iCnt;
        }
      }

      /* Update the second field of the checkpoint header. This tells future
      ** writers that it is now safe to recycle pages before this point







|







 







|
|







 







|
|



|
|





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







 







>
>







 







|







 







|
>
>
|
<
>







 







>







 







>
>







 







|







 







>
>
>
>
>







 







>
>
>







240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
...
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
...
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
...
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
...
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
...
679
680
681
682
683
684
685
686
687
688
689

690
691
692
693
694
695
696
697
...
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
...
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
....
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
....
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
....
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
  u32 *aOut                       /* OUT: Final checksum value output */
){
  assert( (nByte&0x00000007)==4 && nByte>=8 );
  btLogChecksum(nativeCksum, a, 8, aIn, aOut);
  btLogChecksum(nativeCksum, &a[4], nByte-4, aOut, aOut);
}

#define BT_PAGE_DEBUG 0
#define BT_VAL_DEBUG  0

static void btDebugTopology(BtLock *pLock, char *zStr, int iSide, u32 *aLog){
#if BT_PAGE_DEBUG
  fprintf(stderr, "%d:%s: (side=%d) %d..%d  %d..%d  %d..%d\n", 
      pLock->iDebugId, zStr, iSide,
      (int)aLog[0], (int)aLog[1], (int)aLog[2], 
................................................................................
#if BT_VAL_DEBUG
  u8 aKBuf[40];
  u8 aVBuf[40];
  static int nCall = 0;

  binToStr(pK, nK, aKBuf, sizeof(aKBuf));
  binToStr(pV, nV, aVBuf, sizeof(aVBuf));
  fprintf(stderr, "%d:%d: %s \"%s\" -> \"%s\" (%d bytes)\n", 
      pLock->iDebugId, nCall++, zStr, aKBuf, aVBuf, nV
  );

  fflush(stderr);
#endif
}
#endif

................................................................................
  fprintf(stderr, "%d:%d: Search log for page %d (safe=%d) - frame %d\n", 
      pLock->iDebugId, nCall++, (int)pgno, (int)iSafe, (int)iFrame
  );
  fflush(stderr);
#endif
}

static void btDebugSetPgno(BtLock *pLock, 
    int iHash, int iSide, u32 *aPgno, int iFrame, int iZero, u32 pgno
){
#if BT_PAGE_DEBUG
  static int nCall = 0;
  fprintf(stderr, "%d:%d: Set iHash=%d/%d aPgno=%p iFrame=%d iZero=%d pgno=%d\n"
      , pLock->iDebugId, nCall++, iHash, iSide,
      (void*)aPgno, iFrame, iZero, (int)pgno
  );
  fflush(stderr);
#endif
}

#ifndef NDEBUG
static void btDebugLogHeader(
    BtLock *pLock, const char *z, BtWalHdr *pHdr, int iHdr
){
#if BT_PAGE_DEBUG
  static int nCall = 0;
  fprintf(stderr, "%d:%d: %s log-header %d: (iCnt=%d iFirstFrame=%d)\n",
      pLock->iDebugId, nCall++, z, iHdr,
      (int)pHdr->iCnt, (int)pHdr->iFirstFrame
  );
  fflush(stderr);
#endif
}
#endif


#ifdef NDEBUG
# define btDebugLogHeader(a,b,c,d)
#endif

/*
** Ensure that shared-memory chunk iChunk is mapped and available in
** the BtLog.apShm[] array. If an error occurs, return an SQLite4 error
** code. Otherwise, SQLITE4_OK.
*/
static int btLogMapShm(BtLog *pLog, int iChunk){
................................................................................
    u32 aCksum[2];
    btLogChecksum(1, (u8*)pHdr, offsetof(BtWalHdr, aCksum), 0, aCksum);
    if( pHdr->iMagic!=BT_WAL_MAGIC 
     || aCksum[0]!=pHdr->aCksum[0] 
     || aCksum[1]!=pHdr->aCksum[1] 
    ){
      rc = SQLITE4_NOTFOUND;
    }else{
      btDebugLogHeader(pLog->pLock, "read", pHdr, iOff!=0);
    }
  }
  return rc;
}

/*
** This function is used as part of recovery. It reads the contents of
................................................................................
  rc = btLogFindHash(pLog, iSide, iHash, &aHash, &aPgno, &iZero);

  /* Update the hash table */
  if( rc==SQLITE4_OK ){
    int iSlot;
    int nCollide = HASHTABLE_NSLOT*2;
    aPgno[iFrame-iZero] = pgno;
    btDebugSetPgno(pLog->pLock, iHash, iSide, aPgno, iFrame, iZero, pgno);

    if( iFrame==iZero ){
      memset(aHash, 0, sizeof(ht_slot) * HASHTABLE_NSLOT);
    }

    for(iSlot=btLogHashKey(pLog,pgno); ; iSlot=btLogHashNext(pLog, iSlot)){
      if( aHash[iSlot]==0 ){
................................................................................
  rc = btLogFindHash(pLog, iSide, iHash, &aHash, &aPgno, &iZero);
  if( rc==SQLITE4_OK ){
    int i;
    ht_slot iMax;
    iMax = (iFrame - iZero) + 1;

    for(i=0; i<HASHTABLE_NSLOT; i++){
      if( aHash[i]>iMax ){
        aPgno[aHash[i]-1] = 0;
        aHash[i] = 0;
      }

    }
  }

  return rc;
}


/*
................................................................................

static int btLogRecoverFrame(
  BtLog *pLog,                    /* Log module handle */
  void *pCtx,                     /* woints to type u32 - pgno of last commit*/
  u32 iFrame,                     /* Frame number */
  BtFrameHdr *pHdr                /* Frame header */
){

  if( btLogIsEmpty(pLog) ){
    /* This is the first frame recovered. It is therefore both the first
    ** and last frame of log region (c).  */
    pLog->snapshot.aLog[4] = iFrame;
    pLog->snapshot.aLog[5] = iFrame;
  }else{
    u32 iExpect = pLog->snapshot.aLog[5]+1;
................................................................................
}

static int btLogWriteHeader(BtLog *pLog, int iHdr, BtWalHdr *pHdr){
  int rc;                         /* Return code */
  i64 iOff;                       /* File offset to write to */
  assert( iHdr==0 || iHdr==1 );

  btDebugLogHeader(pLog->pLock, "write", pHdr, iHdr);

  /* Calculate a checksum for the header */
  btLogChecksum(1, (u8*)pHdr, offsetof(BtWalHdr, aCksum), 0, pHdr->aCksum);

  /* Write the object to disk */
  iOff = iHdr * pLog->snapshot.nSector;
  rc = btLogWriteData(pLog, iOff, (u8*)pHdr, sizeof(BtWalHdr));

................................................................................

  rc = btLogFindHash(pLog, iSide, iHash, &aHash, &aPgno, &iZero);
  if( rc==SQLITE4_OK ){
    int nCollide = HASHTABLE_NSLOT*2;
    int iSlot;
    u32 iFrame = 0;
    
    iSlot = btLogHashKey(pLog, pgno); 
    for( ; aHash[iSlot]; iSlot=btLogHashNext(pLog, iSlot)){
      if( aPgno[aHash[iSlot]-1]==pgno ){
        u32 iCandidate = iZero + aHash[iSlot] - 1;
        if( iCandidate<=iHi ) iFrame = iCandidate;
      }
      if( (nCollide--)==0 ) return btErrorBkpt(SQLITE4_CORRUPT);
    }
................................................................................
          }
          btDebugCkptPage(pLog->pLock, pgno, aBuf, pgsz);
          rc = pVfs->xWrite(pFd, iOff, aBuf, pgsz);
        }else if( rc==SQLITE4_NOTFOUND ){
          rc = SQLITE4_OK;
        }
      }

      /* Sync the database file to disk. */
      if( rc==SQLITE4_OK ){
        rc = btLogSyncFile(pLog, pLog->pLock->pFd);
      }

      /* Update the first field of the checkpoint-header. This tells readers
      ** that they need not consider anything that in the log before this
      ** point (since the data has already been copied into the database
      ** file).  */
      if( rc==SQLITE4_OK ){
        assert( iFirstRead>0 );
................................................................................
        hdr.nSector = pLog->snapshot.nSector;
        hdr.nPgsz = pgsz;
        hdr.iFirstFrame = iFirstRead;

        hdr.iSalt1 = fhdr.aCksum[0];
        hdr.iSalt2 = fhdr.aCksum[1];
        rc = btLogWriteHeader(pLog, iSlot, &hdr);
        if( rc==SQLITE4_OK ){
          rc = btLogSyncFile(pLog, pLog->pFd);
        }
        if( rc==SQLITE4_OK ){
          pShm->ckpt.iWalHdr = (iSlot<<2) + hdr.iCnt;
        }
      }

      /* Update the second field of the checkpoint header. This tells future
      ** writers that it is now safe to recycle pages before this point