SQLite4
Check-in [1512aee6f6]
Not logged in

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

Overview
Comment:Combine the bt database and shm headers.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 1512aee6f68f507882b8a2a35722d63694199c1f
User & Date: dan 2013-11-20 19:23:35
Context
2013-11-21
18:46
Add code to free pages and blocks. check-in: 1a0d07f113 user: dan tags: trunk
2013-11-20
19:23
Combine the bt database and shm headers. check-in: 1512aee6f6 user: dan tags: trunk
2013-11-16
20:35
Add test code to run bt checkpoints in a background thread. Various fixes and tweaks to support this. check-in: 439684c450 user: dan tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/btInt.h.

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
#ifndef MAX
# define MAX(a,b) (((a)>(b))?(a):(b))
#endif

/* By default pages are 1024 bytes in size. */
#define BT_DEFAULT_PGSZ 1024

/*
** Database header fields.
*/
typedef struct BtDbhdr BtDbhdr;
struct BtDbhdr {
  u32 pgsz;                       /* Page size in bytes */
  u32 nPg;                        /* Number of pages in database */
  u32 cookie;                     /* User cookie value (SQL schema cookie) */
  u32 padding;                    /* Unused */
  u32 aCksum[2];                  /* Checksum over other fields */
};

/*************************************************************************
** Interface to bt_pager.c functionality.
*/
typedef struct BtPage BtPage;
typedef struct BtPager BtPager;

/*







<
<
<
<
<
<
<
<
<
<
<
<







35
36
37
38
39
40
41












42
43
44
45
46
47
48
#ifndef MAX
# define MAX(a,b) (((a)>(b))?(a):(b))
#endif

/* By default pages are 1024 bytes in size. */
#define BT_DEFAULT_PGSZ 1024













/*************************************************************************
** Interface to bt_pager.c functionality.
*/
typedef struct BtPage BtPage;
typedef struct BtPager BtPager;

/*

Changes to src/bt_log.c.

22
23
24
25
26
27
28
29
30
31

32
33


34
35
36
37
38
39
40
..
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
...
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
...
352
353
354
355
356
357
358

359
360
361
362
363
364
365
366
367
368
369
370

371
372
373
374
375
376
377
...
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
...
800
801
802
803
804
805
806
807
808
809
810

811
812
813

814
815
816
817
818



819
820
821
822
823
824
825
826


827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
...
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
....
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
....
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
....
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
....
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
....
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
#define BT_WAL_MAGIC   0xBEE1CA62
#define BT_WAL_VERSION 0x00000001

/* Wrap the log around if there is a block of this many free frames at
** the start of the file.  */
#define BT_NWRAPLOG    100

typedef struct BtWalHdr BtWalHdr;
typedef struct BtShmHdr BtShmHdr;
typedef struct BtCkptHdr BtCkptHdr;

typedef struct BtFrameHdr BtFrameHdr;
typedef struct BtShm BtShm;



/*
** WAL file header. All u32 fields are stored in big-endian order on
** disk. A single WAL file may contain two of these headers - one at
** byte offset 0 and the other at offset <sector-size> (the start of
** the second disk sector in the file, according to the xSectorSize
** VFS method).
................................................................................
  u32 pgno;                       /* Page number of this frame */
  u32 iNext;                      /* Next frame pointer */
  u32 nPg;                        /* For commit frames, size of db file */
  u32 aCksum[2];                  /* Frame checksum */
};

#define BT_FRAME_COMMIT 0x80000000










/*
** Shared memory header. Shared memory begins with two copies of
** this structure. All fields are stored in machine byte-order.
*/
struct BtShmHdr {
  u32 aLog[6];                    /* First/last frames for each log region */
  int nSector;                    /* Sector size assumed for WAL file */
  int iHashSide;                  /* Hash table side for region (c) of log */
  u32 aFrameCksum[2];             /* Checksum of previous frame */
  u32 iNextFrame;                 /* Location to write next log frame to */


  /* The three "db-header" values */
  u32 iCookie;                    /* User cookie value */
  int pgsz;                       /* Size of database pages in bytes */
  int nPg;                        /* Number of pages in db at last commit */

  u32 aCksum[2];                  /* Object checksum */
};

/*
** A single instance of this structure follows the two BtShmHdr structures 
** in shared memory.
**
................................................................................
  assert( aLog[2]==0 || pHdr->iNextFrame<aLog[2] ||  pHdr->iNextFrame>aLog[3] );
  assert( aLog[4]==0 || pHdr->iNextFrame<aLog[4] ||  pHdr->iNextFrame>aLog[5] );
}
#else
#define btDebugCheckSnapshot(x)
#endif

#ifndef NDEBUG
static void btDebugCheckHash(BtLock *pLock){
}
#else
#define btDebugCheckHash(x)
#endif

#ifndef NDEBUG
static void btDebugLogSafepoint(BtLock *pLock, u32 iSafe){
#if BT_PAGE_DEBUG
  static int nCall = 0;
  fprintf(stderr, "%d:%d: checkpoint safepoint=%d\n",
      pLock->iDebugId, nCall++, (int)iSafe
  );
................................................................................
  fflush(stderr);
#endif
}
#endif

#ifndef NDEBUG
#include <ctype.h>

static void binToStr(u8 *pIn, int nIn, u8 *pOut, int nOut){
  int i;
  int nCopy = MIN(nIn, (nOut-1));
  for(i=0; i<nCopy; i++){
    if( isprint(pIn[i]) ){
      pOut[i] = pIn[i];
    }else{
      pOut[i] = '.';
    }
  }
  pOut[i] = '\0';
}

void sqlite4BtDebugKV(
    BtLock *pLock, const char *zStr, u8 *pK, int nK, u8 *pV, int nV
){
#if BT_VAL_DEBUG
  u8 aKBuf[40];
  u8 aVBuf[40];
  static int nCall = 0;
................................................................................

  btLogHashInsert(pLog, pHdr->pgno, iFrame);
  if( pHdr->nPg!=0 ){
    FrameRecoverCtx *pFRC = (FrameRecoverCtx*)pCtx;
    pFRC->iLast = iFrame;
    pFRC->iNextFrame = pHdr->iNext;
    memcpy(pLog->snapshot.aFrameCksum, pHdr->aCksum, sizeof(pHdr->aCksum));
    pLog->snapshot.nPg = pHdr->nPg;
  }

#if 0
  fprintf(stderr, "recovered frame=%d pgno=%d\n", iFrame, pHdr->pgno);
  fflush(stderr);
#endif
  return 0;
................................................................................
    }
  }

  aLog[5] = iLast;
  return btLogHashRollback(pLog, btLogFrameHash(pLog, iLast), iLast);
}

static int btLogReadDbhdr(BtLog *pLog, BtDbhdr *pHdr){
  BtLock *p = pLog->pLock;
  int rc;                         /* Return code */
  i64 nByte;                      /* Size of database file in byte */


  rc = p->pVfs->xSize(p->pFd, &nByte);
  if( rc==SQLITE4_OK && nByte>0 ){

    rc = p->pVfs->xRead(p->pFd, 0, pHdr, sizeof(BtDbhdr));
    if( rc==SQLITE4_OK ){
      u32 aCksum[2];
      btLogChecksum(1, pHdr, offsetof(BtDbhdr, aCksum), 0, aCksum);
      if( aCksum[0]==pHdr->aCksum[0] && aCksum[1]==pHdr->aCksum[1] ){



        return SQLITE4_OK;
      }
    }
  }

  memset(pHdr, 0, sizeof(BtDbhdr));
  pHdr->pgsz = BT_DEFAULT_PGSZ;
  pHdr->nPg = 1;


  return rc;
}

static int btLogUpdateDbhdr(BtLog *pLog, u8 *aData){
  BtDbhdr dbhdr;

  dbhdr.cookie = pLog->snapshot.iCookie;
  dbhdr.nPg = pLog->snapshot.nPg;
  dbhdr.pgsz = pLog->snapshot.pgsz;
  dbhdr.padding = 0;
  btLogChecksum(1, &dbhdr, offsetof(BtDbhdr, aCksum), 0, dbhdr.aCksum);

  assert( dbhdr.pgsz>0 );
  memcpy(aData, &dbhdr, sizeof(BtDbhdr));

  return SQLITE4_OK;
}


/*
** Run log recovery. In other words, read the log file from disk and 
................................................................................
      /* One or more transactions were recovered from the log file. */
      BtShm *pShm = btLogShm(pLog);
      pShm->ckpt.iWalHdr = (iSlot<<2) + pHdr->iCnt;
      pShm->ckpt.iFirstRead = pHdr->iFirstFrame;
      pShm->ckpt.iFirstRecover = pHdr->iFirstFrame;
      rc = btLogRollbackRecovery(pLog, &ctx);
      pLog->snapshot.iNextFrame = ctx.iNextFrame;
      pLog->snapshot.pgsz = pHdr->nPgsz;
      assert( pShm->ckpt.iFirstRead>0 );
    }
  }

  if( rc==SQLITE4_OK && ctx.iLast==0 ){
    /* No transactions were recovered from the log file. */
    BtDbhdr dbhdr;            /* Database header */
    btLogZeroSnapshot(pLog);

    /* Read the database file header to obtail values required
    ** by the snapshot.  */
    rc = btLogReadDbhdr(pLog, &dbhdr);
    pLog->snapshot.nPg = dbhdr.nPg;
    pLog->snapshot.pgsz = dbhdr.pgsz;
    pLog->snapshot.iCookie = dbhdr.cookie;
  }

  if( rc==SQLITE4_OK ){
    btDebugTopology(
        pLog->pLock, "recovered", pLog->snapshot.iHashSide, pLog->snapshot.aLog
    );
  }
................................................................................

  /* Update the wal index hash tables with the (pgno -> iFrame) record. 
  ** If this is a commit frame, update the nPg field as well. */
  if( rc==SQLITE4_OK ){
    if( iFrame==1 ){
      pLog->snapshot.iHashSide = (pLog->snapshot.iHashSide+1) % 2;
    }
    if( nPg ) pLog->snapshot.nPg = nPg;

    rc = btLogHashInsert(pLog, pgno, iFrame);
  }

  /* Update the private copy of the shm-header */
  btDebugCheckSnapshot(&pLog->snapshot);
  BtShmHdr hdr;
................................................................................
  }

  /* If this is a commit frame and the size of the database has changed,
  ** ensure that the log file contains at least one copy of page 1 written
  ** since the last checkpoint. This is required as a future checkpoint
  ** will need to update the nPg field in the database header located on
  ** page 1. */
  if( nPg && nPg!=pLog->snapshot.nPg ){
    BtPager *pPager = (BtPager *)(pLog->pLock);
    BtPage *pOne = 0;
    rc = sqlite4BtPageGet(pPager, 1, &pOne);
    if( rc==SQLITE4_OK ){
      rc = sqlite4BtLogWrite(pLog, 1, sqlite4BtPageData(pOne), 0);
      sqlite4BtPageRelease(pOne);
    }
................................................................................
    u32 *aPgno = 0;               /* Array of page numbers to checkpoint */
    int nPgno;                    /* Number of entries in aPgno[] */
    int i;                        /* Used to loop through aPgno[] */
    u8 *aBuf;                     /* Buffer to load page data into */
    u32 iFirstRead;               /* First frame not checkpointed */

    rc = btLogSnapshot(pLog, &pLog->snapshot);
    pgsz = pLog->snapshot.pgsz;

    if( rc==SQLITE4_OK ){
      /* Allocate space to load log data into */
      aBuf = sqlite4_malloc(pLock->pEnv, pgsz);
      if( aBuf==0 ) rc = btErrorBkpt(SQLITE4_NOMEM);
    }
    
................................................................................
  return rc;
}

/*
** Return the database page size in bytes.
*/
int sqlite4BtLogPagesize(BtLog *pLog){
  return pLog->snapshot.pgsz;
}

/*
** Return the number of pages in the database at last commit.
*/
int sqlite4BtLogPagecount(BtLog *pLog){
  return (pLog->snapshot.nPg==1 ? 2 : pLog->snapshot.nPg);
}

/*
** Return the current value of the user cookie.
*/
u32 sqlite4BtLogCookie(BtLog *pLog){
  return pLog->snapshot.iCookie;
}


/*
** Set the value of the user cookie.
*/
int sqlite4BtLogSetCookie(BtLog *pLog, u32 iCookie){
................................................................................
  int rc;

  rc = sqlite4BtPageGet(pPager, 1, &pOne);
  if( rc==SQLITE4_OK ){
    rc = sqlite4BtPageWrite(pOne);
  }
  if( rc==SQLITE4_OK ){
    pLog->snapshot.iCookie = iCookie;
    btLogUpdateDbhdr(pLog, sqlite4BtPageData(pOne));
  }
  sqlite4BtPageRelease(pOne);

  return rc;
}









<
<

>


>
>







 







>
>
>
>
>
>
>
>
>











>

<
|
<
<
<







 







<
<
<
<
<
<
<







 







>












>







 







|







 







|



>



>
|


<
|
>
>
>





|


>
>




|
|
|
|
|
|
|
|
|
<







 







|






<




|
<
<
<







 







|







 







|







 







|







 







|






|






|







 







|








22
23
24
25
26
27
28


29
30
31
32
33
34
35
36
37
38
39
40
41
..
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
...
295
296
297
298
299
300
301







302
303
304
305
306
307
308
...
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
...
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
...
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820

821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847

848
849
850
851
852
853
854
...
907
908
909
910
911
912
913
914
915
916
917
918
919
920

921
922
923
924
925



926
927
928
929
930
931
932
....
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
....
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
....
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
....
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
....
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
#define BT_WAL_MAGIC   0xBEE1CA62
#define BT_WAL_VERSION 0x00000001

/* Wrap the log around if there is a block of this many free frames at
** the start of the file.  */
#define BT_NWRAPLOG    100



typedef struct BtCkptHdr BtCkptHdr;
typedef struct BtDbHdrCksum BtDbHdrCksum;
typedef struct BtFrameHdr BtFrameHdr;
typedef struct BtShm BtShm;
typedef struct BtShmHdr BtShmHdr;
typedef struct BtWalHdr BtWalHdr;

/*
** WAL file header. All u32 fields are stored in big-endian order on
** disk. A single WAL file may contain two of these headers - one at
** byte offset 0 and the other at offset <sector-size> (the start of
** the second disk sector in the file, according to the xSectorSize
** VFS method).
................................................................................
  u32 pgno;                       /* Page number of this frame */
  u32 iNext;                      /* Next frame pointer */
  u32 nPg;                        /* For commit frames, size of db file */
  u32 aCksum[2];                  /* Frame checksum */
};

#define BT_FRAME_COMMIT 0x80000000

/*
** A database header with checksum fields.
*/
struct BtDbHdrCksum {
  BtDbHdr hdr;
  u32 aCksum[2];
};


/*
** Shared memory header. Shared memory begins with two copies of
** this structure. All fields are stored in machine byte-order.
*/
struct BtShmHdr {
  u32 aLog[6];                    /* First/last frames for each log region */
  int nSector;                    /* Sector size assumed for WAL file */
  int iHashSide;                  /* Hash table side for region (c) of log */
  u32 aFrameCksum[2];             /* Checksum of previous frame */
  u32 iNextFrame;                 /* Location to write next log frame to */
  BtDbHdr dbhdr;                  /* Cached db-header values */


  u32 padding;



  u32 aCksum[2];                  /* Object checksum */
};

/*
** A single instance of this structure follows the two BtShmHdr structures 
** in shared memory.
**
................................................................................
  assert( aLog[2]==0 || pHdr->iNextFrame<aLog[2] ||  pHdr->iNextFrame>aLog[3] );
  assert( aLog[4]==0 || pHdr->iNextFrame<aLog[4] ||  pHdr->iNextFrame>aLog[5] );
}
#else
#define btDebugCheckSnapshot(x)
#endif








#ifndef NDEBUG
static void btDebugLogSafepoint(BtLock *pLock, u32 iSafe){
#if BT_PAGE_DEBUG
  static int nCall = 0;
  fprintf(stderr, "%d:%d: checkpoint safepoint=%d\n",
      pLock->iDebugId, nCall++, (int)iSafe
  );
................................................................................
  fflush(stderr);
#endif
}
#endif

#ifndef NDEBUG
#include <ctype.h>
#if BT_VAL_DEBUG
static void binToStr(u8 *pIn, int nIn, u8 *pOut, int nOut){
  int i;
  int nCopy = MIN(nIn, (nOut-1));
  for(i=0; i<nCopy; i++){
    if( isprint(pIn[i]) ){
      pOut[i] = pIn[i];
    }else{
      pOut[i] = '.';
    }
  }
  pOut[i] = '\0';
}
#endif
void sqlite4BtDebugKV(
    BtLock *pLock, const char *zStr, u8 *pK, int nK, u8 *pV, int nV
){
#if BT_VAL_DEBUG
  u8 aKBuf[40];
  u8 aVBuf[40];
  static int nCall = 0;
................................................................................

  btLogHashInsert(pLog, pHdr->pgno, iFrame);
  if( pHdr->nPg!=0 ){
    FrameRecoverCtx *pFRC = (FrameRecoverCtx*)pCtx;
    pFRC->iLast = iFrame;
    pFRC->iNextFrame = pHdr->iNext;
    memcpy(pLog->snapshot.aFrameCksum, pHdr->aCksum, sizeof(pHdr->aCksum));
    pLog->snapshot.dbhdr.nPg = pHdr->nPg;
  }

#if 0
  fprintf(stderr, "recovered frame=%d pgno=%d\n", iFrame, pHdr->pgno);
  fflush(stderr);
#endif
  return 0;
................................................................................
    }
  }

  aLog[5] = iLast;
  return btLogHashRollback(pLog, btLogFrameHash(pLog, iLast), iLast);
}

static int btLogReadDbhdr(BtLog *pLog, BtDbHdr *pHdr){
  BtLock *p = pLog->pLock;
  int rc;                         /* Return code */
  i64 nByte;                      /* Size of database file in byte */


  rc = p->pVfs->xSize(p->pFd, &nByte);
  if( rc==SQLITE4_OK && nByte>0 ){
    BtDbHdrCksum hdr;
    rc = p->pVfs->xRead(p->pFd, 0, &hdr, sizeof(BtDbHdrCksum));
    if( rc==SQLITE4_OK ){
      u32 aCksum[2];

      btLogChecksum(1, (u8*)&hdr, offsetof(BtDbHdrCksum, aCksum), 0, aCksum);

      if( aCksum[0]==hdr.aCksum[0] && aCksum[1]==hdr.aCksum[1] ){
        memcpy(pHdr, &hdr, sizeof(BtDbHdr));
        return SQLITE4_OK;
      }
    }
  }

  memset(pHdr, 0, sizeof(BtDbHdr));
  pHdr->pgsz = BT_DEFAULT_PGSZ;
  pHdr->nPg = 1;
  pHdr->iRoot = 2;

  return rc;
}

static int btLogUpdateDbhdr(BtLog *pLog, u8 *aData){
  BtDbHdrCksum hdr;

  memcpy(&hdr.hdr, &pLog->snapshot.dbhdr, sizeof(BtDbHdr));

  btLogChecksum(1, (u8*)&hdr, offsetof(BtDbHdrCksum, aCksum), 0, hdr.aCksum);

  assert( hdr.hdr.iRoot=2 );
  assert( hdr.hdr.pgsz>0 );
  memcpy(aData, &hdr, sizeof(BtDbHdrCksum));


  return SQLITE4_OK;
}


/*
** Run log recovery. In other words, read the log file from disk and 
................................................................................
      /* One or more transactions were recovered from the log file. */
      BtShm *pShm = btLogShm(pLog);
      pShm->ckpt.iWalHdr = (iSlot<<2) + pHdr->iCnt;
      pShm->ckpt.iFirstRead = pHdr->iFirstFrame;
      pShm->ckpt.iFirstRecover = pHdr->iFirstFrame;
      rc = btLogRollbackRecovery(pLog, &ctx);
      pLog->snapshot.iNextFrame = ctx.iNextFrame;
      pLog->snapshot.dbhdr.pgsz = pHdr->nPgsz;
      assert( pShm->ckpt.iFirstRead>0 );
    }
  }

  if( rc==SQLITE4_OK && ctx.iLast==0 ){
    /* No transactions were recovered from the log file. */

    btLogZeroSnapshot(pLog);

    /* Read the database file header to obtail values required
    ** by the snapshot.  */
    rc = btLogReadDbhdr(pLog, &pLog->snapshot.dbhdr);



  }

  if( rc==SQLITE4_OK ){
    btDebugTopology(
        pLog->pLock, "recovered", pLog->snapshot.iHashSide, pLog->snapshot.aLog
    );
  }
................................................................................

  /* Update the wal index hash tables with the (pgno -> iFrame) record. 
  ** If this is a commit frame, update the nPg field as well. */
  if( rc==SQLITE4_OK ){
    if( iFrame==1 ){
      pLog->snapshot.iHashSide = (pLog->snapshot.iHashSide+1) % 2;
    }
    if( nPg ) pLog->snapshot.dbhdr.nPg = nPg;

    rc = btLogHashInsert(pLog, pgno, iFrame);
  }

  /* Update the private copy of the shm-header */
  btDebugCheckSnapshot(&pLog->snapshot);
  BtShmHdr hdr;
................................................................................
  }

  /* If this is a commit frame and the size of the database has changed,
  ** ensure that the log file contains at least one copy of page 1 written
  ** since the last checkpoint. This is required as a future checkpoint
  ** will need to update the nPg field in the database header located on
  ** page 1. */
  if( nPg && nPg!=pLog->snapshot.dbhdr.nPg ){
    BtPager *pPager = (BtPager *)(pLog->pLock);
    BtPage *pOne = 0;
    rc = sqlite4BtPageGet(pPager, 1, &pOne);
    if( rc==SQLITE4_OK ){
      rc = sqlite4BtLogWrite(pLog, 1, sqlite4BtPageData(pOne), 0);
      sqlite4BtPageRelease(pOne);
    }
................................................................................
    u32 *aPgno = 0;               /* Array of page numbers to checkpoint */
    int nPgno;                    /* Number of entries in aPgno[] */
    int i;                        /* Used to loop through aPgno[] */
    u8 *aBuf;                     /* Buffer to load page data into */
    u32 iFirstRead;               /* First frame not checkpointed */

    rc = btLogSnapshot(pLog, &pLog->snapshot);
    pgsz = pLog->snapshot.dbhdr.pgsz;

    if( rc==SQLITE4_OK ){
      /* Allocate space to load log data into */
      aBuf = sqlite4_malloc(pLock->pEnv, pgsz);
      if( aBuf==0 ) rc = btErrorBkpt(SQLITE4_NOMEM);
    }
    
................................................................................
  return rc;
}

/*
** Return the database page size in bytes.
*/
int sqlite4BtLogPagesize(BtLog *pLog){
  return pLog->snapshot.dbhdr.pgsz;
}

/*
** Return the number of pages in the database at last commit.
*/
int sqlite4BtLogPagecount(BtLog *pLog){
  return (pLog->snapshot.dbhdr.nPg==1 ? 2 : pLog->snapshot.dbhdr.nPg);
}

/*
** Return the current value of the user cookie.
*/
u32 sqlite4BtLogCookie(BtLog *pLog){
  return pLog->snapshot.dbhdr.iCookie;
}


/*
** Set the value of the user cookie.
*/
int sqlite4BtLogSetCookie(BtLog *pLog, u32 iCookie){
................................................................................
  int rc;

  rc = sqlite4BtPageGet(pPager, 1, &pOne);
  if( rc==SQLITE4_OK ){
    rc = sqlite4BtPageWrite(pOne);
  }
  if( rc==SQLITE4_OK ){
    pLog->snapshot.dbhdr.iCookie = iCookie;
    btLogUpdateDbhdr(pLog, sqlite4BtPageData(pOne));
  }
  sqlite4BtPageRelease(pOne);

  return rc;
}