9580a42b创建于 2025年8月4日历史提交
From d6f2f7b1d7d288bc0cb0ff998052bb8efac40f7f Mon Sep 17 00:00:00 2001
From: MartinChoo <214582617@qq.com>
Date: Wed, 23 Jul 2025 17:41:00 +0800
Subject: [PATCH 04/12] Support meta recovery

---
 src/sqlite3.c | 1156 ++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 1151 insertions(+), 5 deletions(-)

diff --git a/src/sqlite3.c b/src/sqlite3.c
index 6f423a9..dc09c3c 100644
--- a/src/sqlite3.c
+++ b/src/sqlite3.c
@@ -786,6 +786,7 @@ SQLITE_API int sqlite3_exec(
 #define SQLITE_WARNING     28   /* Warnings from sqlite3_log() */
 #define SQLITE_ROW         100  /* sqlite3_step() has another row ready */
 #define SQLITE_DONE        101  /* sqlite3_step() has finished executing */
+#define SQLITE_META_RECOVERED     66   /* meta page recovered*/
 /* end-of-error-codes */
 
 /*
@@ -57472,6 +57473,26 @@ struct PagerSavepoint {
 #endif
 };
 
+#if !defined(SQLITE_OS_UNIX) && defined(SQLITE_META_DWR)
+#undef SQLITE_META_DWR
+#endif
+
+#ifdef SQLITE_META_DWR
+static int PragmaMetaDoubleWrie(sqlite3 *db, int iDb, Parse *parse, const char *zLeft, const char *zRight);
+typedef struct MetaDwrHdr MetaDwrHdr;
+static int MetaDwrWriteHeader(Pager *pPager, MetaDwrHdr *hdr);
+static int MetaDwrUpdateMetaPages(Btree *pBt);
+static void MetaDwrPagerRelease(Pager *pPager);
+static int MetaDwrOpenFile(Pager *pPager, u8 openCreate);
+static void MetaDwrCheckVacuum(BtShared *pBt);
+static int MetaDwrRecoverAndBeginTran(Btree *pBt, int wrflag, int *pSchemaVersion);
+static int MetaDwrOpenAndCheck(Btree *pBt);
+static void MetaDwrDisable(Btree *pBt);
+#define META_HEADER_CHANGED 1
+#define META_SCHEMA_CHANGED 2
+#define META_IN_RECOVERY 1
+#define META_RECOVER_SUCCESS 2
+#endif
 /*
 ** Bits of the Pager.doNotSpill flag.  See further description below.
 */
@@ -57737,6 +57758,13 @@ struct Pager {
   Wal *pWal;                  /* Write-ahead log used by "journal_mode=wal" */
   char *zWal;                 /* File name for write-ahead log */
 #endif
+#ifdef SQLITE_META_DWR
+  u8 metaChanged;
+  sqlite3_file *metaFd;
+  MetaDwrHdr *metaHdr;
+  void *metaMapPage;
+  int (*xGetMethod)(Pager*,Pgno,DbPage**,int); /* Routine to fetch a patch */
+#endif
 };
 
 /*
@@ -58096,7 +58124,11 @@ static void setGetterMethod(Pager *pPager){
     pPager->xGet = getPageMMap;
 #endif /* SQLITE_MAX_MMAP_SIZE>0 */
   }else{
+#ifdef SQLITE_META_DWR
+    pPager->xGet = pPager->xGetMethod ? pPager->xGetMethod : getPageNormal;
+#else
     pPager->xGet = getPageNormal;
+#endif
   }
 }
 
@@ -59170,7 +59202,14 @@ static int pager_end_transaction(Pager *pPager, int hasSuper, int bCommit){
     }
     sqlite3PcacheTruncate(pPager->pPCache, pPager->dbSize);
   }
-
+#ifdef SQLITE_META_DWR
+  if (bCommit && pPager->metaChanged != 0) {
+    sqlite3BeginBenignMalloc();
+    (void)MetaDwrWriteHeader(pPager, pPager->metaHdr);
+    sqlite3EndBenignMalloc();
+    pPager->metaChanged = 0;
+  }
+#endif
   if( pagerUseWal(pPager) ){
     /* Drop the WAL write-lock, if any. Also, if the connection was in
     ** locking_mode=exclusive mode but is no longer, drop the EXCLUSIVE
@@ -61269,6 +61308,9 @@ SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager, sqlite3 *db){
     sqlite3WalClose(pPager->pWal, db, pPager->walSyncFlags, pPager->pageSize,a);
     pPager->pWal = 0;
   }
+#endif
+#ifdef SQLITE_META_DWR
+  MetaDwrPagerRelease(pPager);
 #endif
   pager_reset(pPager);
   if( MEMDB ){
@@ -62112,7 +62154,11 @@ act_like_temp_file:
     rc = sqlite3PcacheOpen(szPageDflt, nExtra, !memDb,
                        !memDb?pagerStress:0, (void *)pPager, pPager->pPCache);
   }
-
+#ifdef SQLITE_META_DWR
+  if( rc==SQLITE_OK && !memDb && !readOnly){
+    (void)MetaDwrOpenFile(pPager, 0);
+  }
+#endif
   /* If an error occurred above, free the  Pager structure and close the file.
   */
   if( rc!=SQLITE_OK ){
@@ -70005,6 +70051,10 @@ struct BtShared {
 #endif
   u8 *pTmpSpace;        /* Temp space sufficient to hold a single cell */
   int nPreformatSize;   /* Size of last cell written by TransferRow() */
+#ifdef SQLITE_META_DWR
+  u32 maxMetaPage;
+  u32 metaRecoverStatus;
+#endif
 };
 
 /*
@@ -74298,7 +74348,12 @@ trans_begun:
       rc = sqlite3PagerOpenSavepoint(pPager, p->db->nSavepoint);
     }
   }
-
+#ifdef SQLITE_META_DWR
+  if (rc == SQLITE_NOTADB || rc == SQLITE_CORRUPT) {
+    int rc1 = MetaDwrRecoverAndBeginTran(p, wrflag, pSchemaVersion);
+    rc = (rc1 == SQLITE_OK) ? SQLITE_OK : rc;
+  }
+#endif
   btreeIntegrity(p);
   sqlite3BtreeLeave(p);
   return rc;
@@ -74687,6 +74742,9 @@ SQLITE_PRIVATE int sqlite3BtreeIncrVacuum(Btree *p){
       if( rc==SQLITE_OK ){
         rc = sqlite3PagerWrite(pBt->pPage1->pDbPage);
         put4byte(&pBt->pPage1->aData[28], pBt->nPage);
+#ifdef SQLITE_META_DWR
+        MetaDwrCheckVacuum(pBt);
+#endif
       }
     }else{
       rc = SQLITE_DONE;
@@ -74771,6 +74829,9 @@ static int autoVacuumCommit(Btree *p){
       put4byte(&pBt->pPage1->aData[28], nFin);
       pBt->bDoTruncate = 1;
       pBt->nPage = nFin;
+#ifdef SQLITE_META_DWR
+      MetaDwrCheckVacuum(pBt);
+#endif
     }
     if( rc!=SQLITE_OK ){
       sqlite3PagerRollback(pPager);
@@ -74827,6 +74888,9 @@ SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zSuperJrnl){
     if( pBt->bDoTruncate ){
       sqlite3PagerTruncateImage(pBt->pPager, pBt->nPage);
     }
+#endif
+#ifdef SQLITE_META_DWR
+    (void)MetaDwrUpdateMetaPages(p);
 #endif
     rc = sqlite3PagerCommitPhaseOne(pBt->pPager, zSuperJrnl, 0);
     sqlite3BtreeLeave(p);
@@ -80898,6 +80962,11 @@ SQLITE_PRIVATE int sqlite3BtreeUpdateMeta(Btree *p, int idx, u32 iMeta){
       assert( iMeta==0 || iMeta==1 );
       pBt->incrVacuum = (u8)iMeta;
     }
+#endif
+#ifdef SQLITE_META_DWR
+    if (idx == 1 && pBt->pPager->metaFd) {
+      pBt->pPager->metaChanged = META_SCHEMA_CHANGED;
+    }
 #endif
   }
   sqlite3BtreeLeave(p);
@@ -82544,7 +82613,12 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){
           sqlite3PagerTruncateImage(pDestPager, nDestTruncate);
           rc = sqlite3PagerCommitPhaseOne(pDestPager, 0, 0);
         }
-
+      #ifdef SQLITE_META_DWR
+        if (rc == SQLITE_OK && p->pDest->pBt->pPager->metaFd) {
+          p->pDest->pBt->pPager->metaChanged = META_SCHEMA_CHANGED;
+          (void)MetaDwrUpdateMetaPages(p->pDest);
+        }
+      #endif
         /* Finish committing the transaction to the destination database. */
         if( SQLITE_OK==rc
          && SQLITE_OK==(rc = sqlite3BtreeCommitPhaseTwo(p->pDest, 0))
@@ -138957,7 +139031,12 @@ SQLITE_PRIVATE void sqlite3Pragma(
   }
 #endif
   /* END CODEC */
-
+#ifdef SQLITE_META_DWR
+  if(PragmaMetaDoubleWrie(db, iDb, pParse, zLeft, zRight)) {
+    /* PragmaMetaDoubleWrie executes internal */
+    goto pragma_out;
+  }
+#endif
   /* Locate the pragma in the lookup table */
   pPragma = pragmaLocate(zLeft);
   if( pPragma==0 ){
@@ -180709,6 +180788,10 @@ SQLITE_PRIVATE const char *sqlite3ErrStr(int rc){
       zErr = "no more rows available";
       break;
     }
+    case SQLITE_META_RECOVERED: {
+      zErr = "warning meta recover message";
+      break;
+    }
     default: {
       rc &= 0xff;
       if( ALWAYS(rc>=0) && rc<ArraySize(aMsg) && aMsg[rc]!=0 ){
@@ -254918,7 +255001,1070 @@ export_finish:
 }
 /************** End file hw_codec.c *****************************************/
 #endif
+#ifdef SQLITE_META_DWR
+#define META_DWR_MAX_PAGES 500
+#define META_DWR_MAGIC 0x234A86D9
+#define META_DWR_VERSION 0x00000001
+#define META_DWR_INVALID_ZONE 0x55
+#define META_DWR_HEADER_PAGE_SIZE 4096
+#define META_DWR_HEADER_DEFAULT_PAGE_CNT 8
+typedef struct ScanPages {
+  u32 pageCnt;
+  u32 pageBufSize;
+  u32 maxPageNo;
+  Pgno *pages;
+} ScanPages;
+
+typedef struct MetaDwrHdr {
+  u32 magic;
+  u32 version;
+  u32 dbSize;
+  u32 mxFrameInWal;
+  u32 freeListPageNo;
+  u32 freeListPageCnt;
+  u32 schemaCookie;
+  u32 pageSz;
+  u32 pageCnt;
+  u64 dbFileInode;
+  u32 reserved[12];
+  u32 checkSum;
+  u8 *zones;
+  Pgno *pages;
+  u32 pageBufSize;
+  u8 hdrValid;
+  u8 checkFileId;
+  u16 needSync;
+  i64 lastSyncTime;
+} MetaDwrHdr;
+
+#define META_VERIFIED_HDR_LEN (offsetof(MetaDwrHdr, zones))
+#define META_ZONES_LENGTH (META_DWR_MAX_PAGES * sizeof(u8))
+#define META_PAGE_NO_OFFSET (META_VERIFIED_HDR_LEN + META_ZONES_LENGTH)
+#define META_FILE_UPDATE_TIMES_PER_SYNC 100 // sync once for every 100 update
+#define META_FILE_SYNC_TIMEOUT_MS 30000     // 30 seconds
+
+static int MetaDwrHeaderSimpleCheck(Pager *pPager, MetaDwrHdr *hdr) {
+#if SQLITE_OS_UNIX
+  if (hdr->checkFileId) {
+    unixFile *fd = (unixFile *)pPager->fd;
+    if (fd == NULL || fd->pInode == NULL || pPager->pVfs == NULL) {
+      return SQLITE_INTERNAL;
+    }
+    if (fd->pInode->fileId.ino != hdr->dbFileInode) {
+      sqlite3_log(SQLITE_IOERR_DATA, "Ino mismatch file %llu dwr file %llu",
+      fd->pInode->fileId.ino, hdr->dbFileInode);
+      return SQLITE_IOERR_DATA;
+    }
+  }
+#endif
+  if (hdr->pageCnt > META_DWR_MAX_PAGES || hdr->version != META_DWR_VERSION ||
+      hdr->magic != META_DWR_MAGIC || hdr->checkSum != META_DWR_MAGIC) {
+    sqlite3_log(SQLITE_IOERR_DATA, "Meta dwr file check wrong pageCnt %u, version %u, magic %u, checkSum %u",
+                hdr->pageCnt, hdr->version, hdr->magic, hdr->checkSum);
+    return SQLITE_IOERR_DATA;
+  }
+  if (hdr->pageSz != pPager->pageSize) {
+    sqlite3_log(SQLITE_IOERR_DATA, "Meta dwr file check wrong pageSz %u-%u", hdr->pageSz, pPager->pageSize);
+    return SQLITE_IOERR_DATA;
+  }
+  return SQLITE_OK;
+}
+
+static int PragmaMetaDoubleWrie(sqlite3 *db, int iDb, Parse *parse, const char *zLeft, const char *zRight) {
+  Btree *pBt = db->aDb[iDb].pBt;
+  if (pBt == NULL || zLeft == NULL || sqlite3StrICmp(zLeft, "meta_double_write") != 0) {
+    return 0;
+  }
+  Pager *pPager = pBt->pBt->pPager;
+  if (pPager == NULL) {
+    sqlite3_log(SQLITE_WARNING_DUMP, "Invalid pager handle");
+    return 1;
+  }
+  if (zRight == NULL) {
+    Vdbe *v = sqlite3GetVdbe(parse);
+    sqlite3VdbeSetNumCols(v, 1);
+    sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "meta_double_write", SQLITE_STATIC);
+    sqlite3VdbeAddOp4(v, OP_String8, 0, 1, 0, pPager->metaFd ? "enabled" : "disabled", 0);
+    sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1);
+  } else if (strncmp(zRight, "enabled", 7) == 0) {
+    sqlite3_mutex_enter(db->mutex);
+    // only support enabled meta double write
+    int rc = MetaDwrOpenAndCheck(pBt);
+    if (rc != SQLITE_OK && rc != SQLITE_PERM) {
+      parse->nErr++;
+      parse->rc = rc;
+    }
+    sqlite3_mutex_leave(db->mutex);
+  } else if (strncmp(zRight, "disabled", 8) == 0) {
+    sqlite3_mutex_enter(db->mutex);
+    MetaDwrDisable(pBt);
+    sqlite3_mutex_leave(db->mutex);
+  }
+  return 1;
+}
+
+static int GetBtreePageNo(MemPage *pPage, void *args) {
+  ScanPages *pInfo = (ScanPages *)args;
+  // realloc buffer to store pages
+  u32 pageCnt = pInfo->pageCnt;
+  if (pageCnt == pInfo->pageBufSize) {
+    u32 memSz = sizeof(Pgno) * ROUND8(pageCnt + 1);
+    Pgno *pages = sqlite3Malloc(memSz);
+    if (pages == NULL) {
+      sqlite3_log(SQLITE_NOMEM, "GetPages alloc buffer go wrong %u", memSz);
+      return SQLITE_NOMEM;
+    }
+    if (pageCnt != 0) {
+      memcpy(pages, pInfo->pages, pageCnt * sizeof(Pgno));
+    }
+    sqlite3_free(pInfo->pages);
+    pInfo->pageBufSize = ROUND8(pageCnt + 1);
+    pInfo->pages = pages;
+  }
+  pInfo->pages[pageCnt] = pPage->pgno;
+  pInfo->pageCnt++;
+  if (pInfo->maxPageNo < pPage->pgno) {
+    pInfo->maxPageNo = pPage->pgno;
+  }
+  return 0;
+}
+
+typedef int (*ScanFn)(MemPage *pPage, void *args);
+static SQLITE_NOINLINE int ScanOverflowPages(
+    MemPage *pPage,       /* The page that contains the Cell */
+    unsigned char *pCell, /* First byte of the Cell */
+    CellInfo *pInfo,      /* Size information about the cell */
+    ScanFn fn,            /* Scan pages function */
+    void *args) {
+  BtShared *pBt;
+  Pgno ovflPgno;
+  int rc;
+  int nOvfl;
+  u32 ovflPageSize;
+
+  if (pCell + pInfo->nSize > pPage->aDataEnd) {
+    /* Cell extends past end of page */
+    return SQLITE_CORRUPT_BKPT;
+  }
+  ovflPgno = get4byte(pCell + pInfo->nSize - 4);
+  pBt = pPage->pBt;
+  assert(pBt->usableSize > 4);
+  ovflPageSize = pBt->usableSize - 4;
+  nOvfl = (pInfo->nPayload - pInfo->nLocal + ovflPageSize - 1) / ovflPageSize;
+  assert(nOvfl > 0 ||
+         (CORRUPT_DB && (pInfo->nPayload + ovflPageSize) < ovflPageSize));
+  while (nOvfl > 0) {
+    nOvfl--;
+    Pgno iNext = 0;
+    MemPage *pOvfl = 0;
+    if (ovflPgno < 2 || ovflPgno > btreePagecount(pBt)) {
+      sqlite3_log(SQLITE_WARNING_DUMP, "Ignore for ovfl page not as expect, pgno %u ovflPgno %u novfl %d payload %u local %u",
+        pPage->pgno, ovflPgno, nOvfl, pInfo->nPayload, pInfo->nLocal);
+      return SQLITE_MISUSE;
+    }
+    rc = getOverflowPage(pBt, ovflPgno, &pOvfl, &iNext);
+    if (rc)
+      return rc;
+    if (pOvfl) {
+      rc = fn(pOvfl, args);
+      if (rc) {
+        return rc;
+      }
+      sqlite3PagerUnref(pOvfl->pDbPage);
+    }
+    ovflPgno = iNext;
+  }
+  return SQLITE_OK;
+}
+
+static int ScanBtreePage(
+    BtShared *pBt, /* The BTree that contains the table */
+    Pgno pgno,     /* Page number to clear */
+    ScanFn fn,     /* Scan pages function */
+    void *args) {  /* Scan pages args */
+  MemPage *pPage;
+  int rc;
+  unsigned char *pCell;
+  int i;
+  int hdr;
+  CellInfo info;
+
+  assert(sqlite3_mutex_held(pBt->mutex));
+  if (pgno > btreePagecount(pBt)) {
+    return SQLITE_OK;
+  }
+  rc = getAndInitPage(pBt, pgno, &pPage, 0);
+  if (rc) {
+    return rc;
+  }
+  rc = fn(pPage, args);
+  if (rc) {
+    goto SCAN_PAGE_OUT;
+  }
+  hdr = pPage->hdrOffset;
+  for (i = pPage->nCell - 1; i >= 0; i--) {
+    pCell = findCell(pPage, i);
+    if (!pPage->leaf) {
+      rc = ScanBtreePage(pBt, get4byte(pCell), fn, args);
+      if (rc) {
+        goto SCAN_PAGE_OUT;
+      }
+    }
+    pPage->xParseCell(pPage, pCell, &info);
+    if (info.nLocal < info.nPayload) {
+      rc = ScanOverflowPages(pPage, pCell, &info, fn, args);
+      if (rc) {
+        goto SCAN_PAGE_OUT;
+      }
+    }
+  }
+  if (!pPage->leaf) {
+    rc = ScanBtreePage(pBt, get4byte(&pPage->aData[hdr + 8]), fn, args);
+    if (rc) {
+      goto SCAN_PAGE_OUT;
+    }
+  }
+SCAN_PAGE_OUT:
+  releasePage(pPage);
+  return rc;
+}
+
+static inline int ScanMetaPages(Btree *pBt, ScanPages *pages) {
+  return ScanBtreePage(pBt->pBt, 1, GetBtreePageNo, pages);
+}
+
+static int ReleaseMetaPages(ScanPages *pages) {
+  sqlite3_free(pages->pages);
+  pages->pages = NULL;
+  return SQLITE_OK;
+}
+
+static void InitMetaHeader(MetaDwrHdr *hdr) {
+  (void)memset(hdr, 0, META_VERIFIED_HDR_LEN);
+  hdr->magic = META_DWR_MAGIC;
+  hdr->version = META_DWR_VERSION;
+  hdr->checkSum = META_DWR_MAGIC;
+}
+
+static void MetaDwrReleaseHdr(MetaDwrHdr *hdr) {
+  if (!hdr) {
+    return;
+  }
+  sqlite3_free(hdr->zones);
+  sqlite3_free(hdr);
+}
+
+static int ExpandMetaPageBuf(MetaDwrHdr *hdr, u32 minimalPageCnt, u32 bufHasData) {
+  if (minimalPageCnt < hdr->pageBufSize && hdr->zones != NULL) {
+    return SQLITE_OK;
+  }
+  int pageBufSz = ROUND8(MAX(hdr->pageCnt, minimalPageCnt));
+  u8 *zones = (u8 *)sqlite3Malloc(pageBufSz * (sizeof(u8) + sizeof(Pgno)));
+  if (zones == NULL) {
+    return SQLITE_NOMEM_BKPT;
+  }
+  Pgno *pgnos = (Pgno *)(zones + pageBufSz);
+  if (hdr->zones != NULL) {
+    if (bufHasData && hdr->pageCnt > 0) {
+      (void)memcpy(zones, hdr->zones, hdr->pageCnt * sizeof(u8));
+      (void)memcpy(pgnos, hdr->pages, hdr->pageCnt * sizeof(Pgno));
+    }
+    sqlite3_free(hdr->zones);
+  }
+  hdr->pageBufSize = pageBufSz;
+  hdr->zones = zones;
+  hdr->pages = pgnos;
+  return SQLITE_OK;
+}
+
+static MetaDwrHdr *AllocInitMetaHeaderDwr(Pager *pPager) {
+  MetaDwrHdr *hdr = sqlite3MallocZero(sizeof(MetaDwrHdr));
+  if (hdr == NULL) {
+    return NULL;
+  }
+  InitMetaHeader(hdr);
+  int rc = ExpandMetaPageBuf(hdr, META_DWR_HEADER_DEFAULT_PAGE_CNT, 0);
+  if (rc != SQLITE_OK) {
+    MetaDwrReleaseHdr(hdr);
+    return NULL;
+  }
+  hdr->checkFileId = (pPager->pVfs != NULL && sqlite3_stricmp(pPager->pVfs->zName, "unix") == 0);
+  return hdr;
+}
+
+static void MetaDwrCloseFile(Pager *pPager) {
+  if (!pPager->metaFd) {
+    return;
+  }
+#if SQLITE_OS_UNIX
+  if (pPager->metaMapPage) {
+    osMunmap(pPager->metaMapPage, META_DWR_HEADER_PAGE_SIZE);
+    pPager->metaMapPage = NULL;
+  }
+#endif
+  if (pPager->metaHdr && pPager->metaHdr->needSync > 0) {
+    (void)sqlite3OsSync(pPager->metaFd, SQLITE_SYNC_NORMAL);
+  }
+  sqlite3OsClose(pPager->metaFd);
+}
+
+static void MetaDwrPagerRelease(Pager *pPager) {
+  MetaDwrCloseFile(pPager);
+  MetaDwrReleaseHdr(pPager->metaHdr);
+  pPager->metaHdr = NULL;
+  if (pPager->metaFd) {
+    sqlite3_free(pPager->metaFd);
+    pPager->metaFd = NULL;
+  }
+}
+
+static inline int ReadFromHdrPage(Pager *pPager, void *data, int amt, i64 offset) {
+  if (pPager->metaMapPage) {
+    (void)memcpy(data, (u8 *)pPager->metaMapPage + offset, amt);
+    return SQLITE_OK;
+  }
+  return sqlite3OsRead(pPager->metaFd, data, amt, offset);
+}
+
+static int MetaDwrReadHeader(Pager *pPager, MetaDwrHdr *hdr) {
+  i64 sz = 0;
+  int rc = sqlite3OsFileSize(pPager->metaFd, &sz);
+  if (rc != SQLITE_OK) {
+    sqlite3_log(rc, "Meta dwr file size go wrong");
+    return rc;
+  }
+  if (sz <= META_DWR_HEADER_PAGE_SIZE) {
+    rc = SQLITE_IOERR_DATA;
+    goto READ_META_OUT;
+  }
+  rc = ReadFromHdrPage(pPager, hdr, META_VERIFIED_HDR_LEN, 0);
+  if (rc != SQLITE_OK) {
+    sqlite3_log(rc, "Meta dwr file header read wrong");
+    goto READ_META_OUT;
+  }
+  rc = MetaDwrHeaderSimpleCheck(pPager, hdr);
+  if (rc != SQLITE_OK) {
+    goto READ_META_OUT;
+  }
+  // avoid realloc buffer if buf can't hold all pages
+  rc = ExpandMetaPageBuf(hdr, hdr->pageCnt, 0);
+  if (rc != SQLITE_OK) {
+    goto READ_META_OUT;
+  }
+  int zoneSize = hdr->pageCnt * sizeof(u8);
+  rc = ReadFromHdrPage(pPager, hdr->zones, zoneSize, META_VERIFIED_HDR_LEN);
+  if (rc != SQLITE_OK) {
+    goto READ_META_OUT;
+  }
+  rc = ReadFromHdrPage(pPager, hdr->pages, hdr->pageCnt * sizeof(Pgno), META_PAGE_NO_OFFSET);
+  if (rc != SQLITE_OK) {
+    goto READ_META_OUT;
+  }
+  for (u32 i = 0; i < hdr->pageCnt; i++) {
+    u8 zoneIdx = hdr->zones[i];
+    if (zoneIdx != 0 && zoneIdx != 1 && zoneIdx != META_DWR_INVALID_ZONE) {
+      sqlite3_log(SQLITE_IOERR_DATA, "Invalid zoneIdx %d", zoneIdx);
+      rc = SQLITE_IOERR_DATA;
+      break;
+    }
+  }
+READ_META_OUT:
+  if (rc == SQLITE_IOERR_DATA) {
+    InitMetaHeader(hdr);
+    rc = SQLITE_OK;
+  }
+  return rc;
+}
+
+static inline u64 CaculateMetaDwrWriteOffset(int pageSz, u32 idx, u8 zone) {
+  // 1 header page, idx correspond 2 zone pages
+  return META_DWR_HEADER_PAGE_SIZE + pageSz * (idx * 2 + zone);
+}
+
+static void MetaDwrUpdateHeaderDbInfo(BtShared *pBt) {
+  MetaDwrHdr *hdr = pBt->pPager->metaHdr;
+  // 28 offset: dbSize, freelist pageNo, freelist pages count, schema cookie
+  const u8 *dbHdrInfo = &pBt->pPage1->aData[28];
+  hdr->dbSize = sqlite3Get4byte(dbHdrInfo);
+#ifndef SQLITE_OMIT_WAL
+  if (pagerUseWal(pBt->pPager)) {
+    WalIndexHdr *pWalHdr = &pBt->pPager->pWal->hdr;
+    if (pWalHdr->isInit) {
+      hdr->mxFrameInWal = pWalHdr->mxFrame;
+      hdr->dbSize = pWalHdr->nPage;
+    }
+  } else {
+    hdr->mxFrameInWal = 0;
+  }
+#endif
+#if SQLITE_OS_UNIX
+  if (hdr->checkFileId) {
+    unixFile *fd = (unixFile *)pBt->pPager->fd;
+    if (fd == NULL || fd->pInode == NULL) {
+      sqlite3_log(SQLITE_WARNING_DUMP, "update meta header invalid fd");
+      hdr->hdrValid = 0;
+      return;
+    }
+    hdr->dbFileInode = fd->pInode->fileId.ino;
+  }
+#endif
+  hdr->freeListPageNo = sqlite3Get4byte(dbHdrInfo + 4);
+  hdr->freeListPageCnt = sqlite3Get4byte(dbHdrInfo + 8);
+  hdr->schemaCookie = sqlite3Get4byte(dbHdrInfo + 12);
+  hdr->hdrValid = 1;
+}
+
+static inline int WriteToHdrPage(Pager *pPager, const void *data, int amt, i64 offset) {
+  if (pPager->metaMapPage) {
+    (void)memcpy((u8 *)pPager->metaMapPage + offset, data, amt);
+    return SQLITE_OK;
+  }
+  return sqlite3OsWrite(pPager->metaFd, data, amt, offset);
+}
+
+static int MetaDwrWriteHeader(Pager *pPager, MetaDwrHdr *hdr) {
+  if (pPager->metaChanged == 0 || hdr == NULL || hdr->pageCnt == 0 || hdr->hdrValid == 0) {
+    return SQLITE_OK;
+  }
+  hdr->hdrValid = 0;
+  hdr->pageSz = pPager->pageSize;
+  hdr->dbSize = pPager->dbSize;
+  int rc = WriteToHdrPage(pPager, hdr, META_VERIFIED_HDR_LEN, 0);
+  if (rc != SQLITE_OK) {
+    sqlite3_log(rc, "update meta header write hdr %u wrong", META_VERIFIED_HDR_LEN);
+    return rc;
+  }
+  if (hdr->zones) {
+    int zoneSize = hdr->pageCnt * sizeof(u8);
+    rc = WriteToHdrPage(pPager, hdr->zones, zoneSize, META_VERIFIED_HDR_LEN);
+    if (rc != SQLITE_OK) {
+      sqlite3_log(rc, "update meta header write zonebuf %u wrong", zoneSize);
+      return rc;
+    }
+  }
+  if (hdr->pages) {
+    int pageBufSz = hdr->pageCnt * sizeof(Pgno);
+    rc = WriteToHdrPage(pPager, hdr->pages, pageBufSz, META_PAGE_NO_OFFSET);
+    if (rc != SQLITE_OK) {
+      sqlite3_log(rc, "update meta header write pagebuf %u wrong", pageBufSz);
+    }
+  }
+  if (rc == SQLITE_OK) {
+    u64 size = CaculateMetaDwrWriteOffset((int)pPager->pageSize, hdr->pageCnt, 0);
+    rc = sqlite3OsTruncate(pPager->metaFd, size);
+    if (rc != SQLITE_OK) {
+      sqlite3_log(rc, "update meta header truncate filesz %lu wrong", size);
+    }
+    i64 timeMs = 0;
+    sqlite3OsCurrentTimeInt64(pPager->pVfs, &timeMs);
+    if ((timeMs - hdr->lastSyncTime) > META_FILE_SYNC_TIMEOUT_MS ||
+        hdr->needSync >= META_FILE_UPDATE_TIMES_PER_SYNC) {
+      rc = sqlite3OsSync(pPager->metaFd, SQLITE_SYNC_NORMAL);
+      if (rc != SQLITE_OK) {
+        sqlite3_log(rc, "update meta header sync filesz %lu wrong", size);
+      }
+      hdr->lastSyncTime = timeMs;
+      hdr->needSync = 0;
+    } else {
+      hdr->needSync++;
+    }
+  }
+  return rc;
+}
+
+static int MetaDwrFindPageIdx(MetaDwrHdr *hdr, u32 pgno, u32 *idx) {
+  for (u32 i = 0; i < hdr->pageCnt && i < META_DWR_MAX_PAGES; i++) {
+    if (pgno == hdr->pages[i]) {
+      *idx = i;
+      return 1;
+    }
+  }
+  return 0;
+}
+
+static int MetaDwrWriteOnePage(Btree *pBt, PgHdr *pPage, MetaDwrHdr *hdr, u8 curZones, u32 idx) {
+  int rc = SQLITE_OK;
+  u8 pageExpand = 0;
+  if (hdr->pageCnt <= idx) {
+    rc = ExpandMetaPageBuf(hdr, idx + 1, 1);
+    if (rc != SQLITE_OK) {
+      return rc;
+    }
+    pageExpand = 1;
+  }
+  Pager *pPager = pBt->pBt->pPager;
+  // asume zone 0 or 1
+  u8 zone = 1 - curZones;
+  int pageSz = sqlite3BtreeGetPageSize(pBt);
+  u64 ofs = CaculateMetaDwrWriteOffset(pageSz, idx, zone);
+  void *pData;
+#if defined(SQLITE_HAS_CODEC)
+  if ((pData = sqlite3PagerCodec(pPage)) == 0)
+    return SQLITE_NOMEM;
+#else
+  pData = pPage->pData;
+#endif
+  rc = sqlite3OsWrite(pPager->metaFd, pData, pageSz, ofs);
+  if (rc != SQLITE_OK) {
+    return rc;
+  }
+  hdr->zones[idx] = zone;
+  hdr->pages[idx] = pPage->pgno;
+  if (pageExpand) {
+    hdr->pageCnt++;
+  }
+  return SQLITE_OK;
+}
+
+static int MetaDwrRestoreAllPages(Btree *pBt, const ScanPages *metaPages, MetaDwrHdr *hdr) {
+  u32 i = 0;
+  PgHdr *p = NULL;
+  int rc = SQLITE_OK;
+  for (i = 0; i < metaPages->pageCnt && i < META_DWR_MAX_PAGES; i++) {
+    Pgno pgno = metaPages->pages[i];
+    if (pgno > btreePagecount(pBt->pBt)) {
+      sqlite3_log(SQLITE_WARNING_DUMP, "pageno %d overlimit", pgno);
+      return SQLITE_CORRUPT_BKPT;
+    }
+    rc = sqlite3PagerGet(pBt->pBt->pPager, pgno, &p, 0);
+    if (rc) {
+      return rc;
+    }
+    rc = MetaDwrWriteOnePage(pBt, p, hdr, 1, i);
+    sqlite3PagerUnref(p);
+    if (rc) {
+      return rc;
+    }
+  }
+  hdr->pageCnt = metaPages->pageCnt;
+  MetaDwrUpdateHeaderDbInfo(pBt->pBt);
+  return rc;
+}
+
+static inline const char *GetMetaFilePath(Pager *pPager)
+{
+  return pPager->metaFd == NULL ? NULL : ((const char *)pPager->metaFd + ROUND8(pPager->pVfs->szOsFile));
+}
+
+static int MetaDwrCheckPerm(sqlite3_vfs *pVfs, u8 openCreate, char *metaPath) {
+  int exists = 0;
+  int rc = sqlite3OsAccess(pVfs, metaPath, SQLITE_ACCESS_EXISTS, &exists);
+  if (rc != SQLITE_OK) {
+    return rc;
+  }
+  if (!exists && !openCreate) {
+    return SQLITE_PERM;
+  }
+#ifdef OS_FEATURE
+  // check if the path have enough permission
+  rc = osAccess(metaPath, W_OK|R_OK);
+  if (rc == 0 || errno == ENOENT) {
+    return SQLITE_OK;
+  }
+  rc = SQLITE_PERM;
+  if (openCreate) {
+    sqlite3_log(SQLITE_WARNING_DUMP, "Meta double write disabled, sysno %d", errno);
+  }
+#endif
+  return rc;
+}
+
+static int MetaDwrOpenFile(Pager *pPager, u8 openCreate) {
+  if (pPager->metaFd || pPager->zFilename == NULL) {
+    return SQLITE_OK;
+  }
+  sqlite3BeginBenignMalloc();
+  sqlite3_vfs *pVfs = pPager->pVfs;
+  int size = strlen(pPager->zFilename) + sizeof("-dwr");
+  int szOsFile = ROUND8(pVfs->szOsFile);
+  sqlite3_file *metaFd = (sqlite3_file *)sqlite3MallocZero(szOsFile + size);
+  char *metaPath = (char *)metaFd + szOsFile;
+  if (metaFd == NULL) {
+    sqlite3EndBenignMalloc();
+    sqlite3_log(SQLITE_NOMEM_BKPT, "sqlite alloc memsize %d go wrong", szOsFile + size);
+    return SQLITE_NOMEM_BKPT;
+  }
+  sqlite3_snprintf(size, metaPath, "%s-dwr", pPager->zFilename);
+  int rc = MetaDwrCheckPerm(pVfs, openCreate, metaPath);
+  if (rc != SQLITE_OK) {
+    goto INIT_META_OUT;
+  }
+  u32 flags = (SQLITE_OPEN_READWRITE | SQLITE_OPEN_SUPER_JOURNAL);
+  if (openCreate) {
+    flags |= SQLITE_OPEN_CREATE;
+  }
+  rc = sqlite3OsOpen(pVfs, metaPath, metaFd, (int)flags, 0);
+  if (rc != SQLITE_OK) {
+    goto INIT_META_OUT;
+  }
+#if SQLITE_OS_UNIX
+  if (pPager->metaMapPage == NULL) {
+    sqlite3_int64 sz = META_DWR_HEADER_PAGE_SIZE;
+    sqlite3OsFileControlHint(metaFd, SQLITE_FCNTL_CHUNK_SIZE, &sz);
+    sqlite3OsFileControlHint(metaFd, SQLITE_FCNTL_SIZE_HINT, &sz);
+    void *page = osMmap(0, META_DWR_HEADER_PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
+                        ((unixFile *)metaFd)->h, 0);
+    if (page != MAP_FAILED) {
+      pPager->metaMapPage = page;
+    }
+  }
+#endif
+  pPager->metaFd = metaFd;
+INIT_META_OUT:
+  sqlite3EndBenignMalloc();
+  if (rc != SQLITE_OK && metaFd != NULL) {
+    sqlite3_free(metaFd);
+  }
+  return rc;
+}
+
+void MetaDwrCheckVacuum(BtShared *pBt) {
+  if (!pBt || !pBt->pPager->metaFd) {
+    return;
+  }
+  if (pBt->nPage < pBt->maxMetaPage) {
+    pBt->pPager->metaChanged = META_SCHEMA_CHANGED;
+  }
+}
+
+static inline u8 LocalMetaHdrValid(Pager *pPager) {
+  return pPager->metaMapPage != NULL && memcmp(pPager->metaMapPage, pPager->metaHdr,
+    META_VERIFIED_HDR_LEN) == 0;
+}
+
+static int MetaDwrLoadHdr(Pager *pPager) {
+  if (!pPager->metaHdr) {
+    pPager->metaHdr = AllocInitMetaHeaderDwr(pPager);
+    if (pPager->metaHdr == NULL) {
+      return SQLITE_NOMEM_BKPT;
+    }
+  }
+  if (LocalMetaHdrValid(pPager)) {
+    return SQLITE_OK;
+  }
+  return MetaDwrReadHeader(pPager, pPager->metaHdr);
+}
+
+static int MetaDwrLoadAndCheckMetaFile(BtShared *pBt, u8 reportErr) {
+  int rc = MetaDwrLoadHdr(pBt->pPager);
+  if (rc != SQLITE_OK) {
+    return rc;
+  }
+  MetaDwrHdr *hdr = pBt->pPager->metaHdr;
+  if (hdr->pageCnt == 0) {
+    return reportErr ? SQLITE_IOERR_DATA : SQLITE_OK;
+  }
+  // 28 offset: dbSize, freelist pageNo, freelist pages count, schema cookie
+  u8 *dbHdrInfo = &pBt->pPage1->aData[28];
+  if (hdr->dbSize != pBt->pPager->dbSize || hdr->dbSize != sqlite3Get4byte(dbHdrInfo) ||
+      hdr->freeListPageNo != sqlite3Get4byte(dbHdrInfo + 4) ||
+      hdr->freeListPageCnt != sqlite3Get4byte(dbHdrInfo + 8) ||
+      hdr->schemaCookie != sqlite3Get4byte(dbHdrInfo + 12)) {
+    sqlite3_log(SQLITE_IOERR_DATA, "Meta dwr file expect %u-%u-%u-%u-%u but gotton %u-%u-%u-%u-%u",
+                pBt->pPager->dbSize, hdr->dbSize, sqlite3Get4byte(dbHdrInfo), sqlite3Get4byte(dbHdrInfo + 4),
+                sqlite3Get4byte(dbHdrInfo + 8), sqlite3Get4byte(dbHdrInfo + 12), hdr->dbSize, hdr->freeListPageNo,
+                hdr->freeListPageCnt, hdr->schemaCookie);
+    // reinit
+    InitMetaHeader(hdr);
+    if (reportErr) {
+      return SQLITE_IOERR_DATA;
+    }
+  }
+  return SQLITE_OK;
+}
+
+static int MetaDwrReadOnePage(Pager *pPager, MetaDwrHdr *hdr, int idx, u8 *pData) {
+  u64 ofs = CaculateMetaDwrWriteOffset(pPager->pageSize, idx, hdr->zones[idx]);
+  int rc = sqlite3OsRead(pPager->metaFd, pData, pPager->pageSize, ofs);
+  CODEC1(pPager, pData, hdr->pages[idx], 3, rc = SQLITE_NOMEM_BKPT);
+  return rc;
+}
+
+static int MetaDwrRecoverHeadPage(
+    Pager *pPager, /* The pager open on the database file */
+    Pgno pgno,     /* Page number to fetch */
+    DbPage **pDbPage,
+    int flag) {
+  if (pPager->metaFd == NULL) {
+    return pgno == 1 ? SQLITE_NOTADB : SQLITE_CORRUPT;
+  }
+  sqlite3_pcache_page *pCachePage = sqlite3PcacheFetch(pPager->pPCache, pgno, 3);
+  if (pCachePage == NULL) {
+    sqlite3_log(SQLITE_NOMEM_BKPT, "Get meta page wrong %d", pgno);
+    return SQLITE_NOMEM_BKPT;
+  }
+  DbPage *pPage = sqlite3PcacheFetchFinish(pPager->pPCache, pgno, pCachePage);
+  pPage->pPager = pPager;
+  assert(pCachePage != 0);
+  int rc = MetaDwrLoadHdr(pPager);
+  if (rc != SQLITE_OK) {
+    goto RELEASE_OUT;
+  }
+  MetaDwrHdr *hdr = pPager->metaHdr;
+  u8 walChecked = 0;
+#ifndef SQLITE_OMIT_WAL
+  if (pagerUseWal(pPager)) {
+    WalIndexHdr *pWalHdr = &pPager->pWal->hdr;
+    if (pWalHdr->isInit && pWalHdr->mxFrame != 0) {
+      if (hdr->mxFrameInWal != pWalHdr->mxFrame || hdr->dbSize != pWalHdr->nPage) {
+        rc = SQLITE_NOTADB;
+        sqlite3_log(SQLITE_WARNING_DUMP, "Meta dwr wal hdr expect %u-%u but gotten %u-%u",
+                    hdr->mxFrameInWal, hdr->dbSize, pWalHdr->mxFrame, pWalHdr->nPage);
+        goto RELEASE_OUT;
+      } else {
+        walChecked = 1;
+      }
+    }
+  }
+  if (walChecked == 0) {
+    i64 size = 0;
+    rc = sqlite3OsFileSize(pPager->fd, &size);
+    if (rc != SQLITE_OK) {
+      rc = SQLITE_NOTADB;
+      sqlite3_log(SQLITE_WARNING_DUMP, "Meta dwr get db file size go wrong");
+      goto RELEASE_OUT;
+    }
+    i64 expectSz = (i64)hdr->dbSize * (i64)hdr->pageSz;
+    if (size != expectSz) {
+      rc = SQLITE_NOTADB;
+      sqlite3_log(SQLITE_WARNING_DUMP, "Meta dwr expect file size %lu but gotten size %llu",
+                  expectSz, size);
+      goto RELEASE_OUT;
+    }
+  }
+#endif
+  rc = SQLITE_NOTADB;
+  for (u32 i = 0; i < hdr->pageCnt; i++) {
+    if (hdr->pages[i] != pgno) {
+      continue;
+    }
+    rc = MetaDwrReadOnePage(pPager, hdr, i, sqlite3PagerGetData(pPage));
+    if (rc == SQLITE_OK) {
+      *pDbPage = pPage;
+      if (pPage->pgno == 1) {
+        memcpy(&pPager->dbFileVers, &((u8 *)pPage->pData)[24], sizeof(pPager->dbFileVers));
+      }
+      pager_set_pagehash(pPage);
+    }
+    break;
+  }
+RELEASE_OUT:
+  if (rc != SQLITE_OK && pPage != NULL) {
+    sqlite3PcacheDrop(pPage);
+  }
+  return rc;
+}
+
+static int MetaDwrRestoreChangedPages(Btree *pBt) {
+  Pager *pPager = pBt->pBt->pPager;
+  MetaDwrHdr *hdr = pPager->metaHdr;
+  u8 *zones = sqlite3MallocZero(hdr->pageBufSize * sizeof(u8));
+  if (zones == NULL) {
+    sqlite3_log(SQLITE_NOMEM_BKPT, "Alloc zones buffer size %u go wrong", hdr->pageBufSize * sizeof(u8));
+    return SQLITE_NOMEM_BKPT;
+  }
+  if (hdr->pageCnt > 0) {
+    memcpy(zones, hdr->zones, hdr->pageCnt * sizeof(u8));
+  }
+  u32 idx = 0;
+  PgHdr *p = 0;
+  PgHdr *pList = sqlite3PcacheDirtyList(pPager->pPCache);
+  int rc = SQLITE_OK;
+  for (p = pList; p; p = p->pDirty) {
+    if (MetaDwrFindPageIdx(hdr, p->pgno, &idx) == 0) {
+      continue;
+    }
+    rc = MetaDwrWriteOnePage(pBt, p, hdr, zones[idx], idx);
+    if (rc != SQLITE_OK) {
+      break;
+    }
+  }
+  if (rc == SQLITE_OK) {
+    MetaDwrUpdateHeaderDbInfo(pBt->pBt);
+  }
+  sqlite3_free(zones);
+  return rc;
+}
+
+static int MetaDwrUpdateMetaPages(Btree *pBt) {
+  Pager *pPager = pBt->pBt->pPager;
+  if (!pPager || !pPager->metaFd || pPager->memDb || pPager->readOnly || pBt->pBt->pPage1 == NULL) {
+    return SQLITE_OK;
+  }
+  if (pPager->metaChanged == 0) {
+    if ((pBt->pBt->pPage1->pDbPage->flags & PGHDR_DIRTY) == 0) {
+      return SQLITE_OK;
+    }
+    pPager->metaChanged = META_HEADER_CHANGED;
+  }
+  sqlite3BeginBenignMalloc();
+  int rc = MetaDwrLoadHdr(pPager);
+  if (rc != SQLITE_OK) {
+    goto UPDATE_OUT;
+  }
+  // only update header page
+  if (pPager->metaChanged == META_HEADER_CHANGED) {
+    rc = MetaDwrRestoreChangedPages(pBt);
+    goto UPDATE_OUT;
+  }
+  // update schema pages
+  ScanPages metaPages = {0};
+  rc = ScanMetaPages(pBt, &metaPages);
+  if (rc != SQLITE_OK) {
+    goto UPDATE_OUT;
+  }
+  MetaDwrHdr *hdr = pPager->metaHdr;
+  // rewrite
+  if (hdr->pageCnt == 0 || hdr->pageCnt != metaPages.pageCnt || pBt->pBt->nPage > pBt->pBt->maxMetaPage ||
+      memcmp(hdr->pages, metaPages.pages, hdr->pageCnt * sizeof(Pgno))) {
+    // if page numbers unorderred, restore all pages
+    rc = MetaDwrRestoreAllPages(pBt, &metaPages, hdr);
+  } else {
+    rc = MetaDwrRestoreChangedPages(pBt);
+  }
+  if (rc == SQLITE_OK) {
+    pBt->pBt->maxMetaPage = metaPages.maxPageNo;
+  }
+  ReleaseMetaPages(&metaPages);
+UPDATE_OUT:
+  sqlite3EndBenignMalloc();
+  return rc;
+}
+
+static int MetaDwrRecoverSinglePage(Btree *pBt, int pgno, u8 *pData) {
+  if (pgno < 1 || pBt == NULL) {
+    return SQLITE_CORRUPT_BKPT;
+  }
+  Pager *pPager = sqlite3BtreePager(pBt);
+  DbPage *pDbPage = NULL;
+  int rc = sqlite3PagerGet(pPager, pgno, &pDbPage, 0);
+  if (rc) {
+    return rc;
+  }
+  if ((rc = sqlite3PagerWrite(pDbPage)) == SQLITE_OK) {
+    memcpy(sqlite3PagerGetData(pDbPage), pData, pPager->pageSize);
+  } else {
+    sqlite3_log(rc, "Dwr recoverwrite meta page %d failed", pgno);
+  }
+  sqlite3PagerUnref(pDbPage);
+  return rc;
+}
+
+static int MetaDwrCheckMeta(Btree *pBt) {
+  int nErr = 0;
+  Pgno aRoot[2] = {0, 1}; // quick check and only check root btree
+  char *errStr = NULL;
+  int rc = sqlite3BtreeIntegrityCheck(pBt->db, pBt, &aRoot[0], 2, SQLITE_INTEGRITY_CHECK_ERROR_MAX,
+    &nErr, &errStr);
+  if (nErr == 0) {
+    assert(errStr == 0);
+    return SQLITE_OK;
+  }
+  if (rc != SQLITE_OK) {
+    sqlite3_log(rc, "Meta integrity check go wrong");
+    return rc;
+  }
+  sqlite3_log(SQLITE_WARNING_DUMP, "Meta integrity check %s", errStr);
+  sqlite3DbFree(pBt->db, errStr);
+  return SQLITE_CORRUPT;
+}
+
+static int MetaDwrBeginTrans(Btree *pBt, int wrflag) {
+  pBt->pBt->btsFlags &= ~BTS_READ_ONLY;
+  Pager *pPager = pBt->pBt->pPager;
+  void *xGetMethod = pPager->xGet;
+  pPager->xGetMethod = MetaDwrRecoverHeadPage;
+  pPager->xGet = MetaDwrRecoverHeadPage;
+  int rc = sqlite3BtreeBeginTrans(pBt, wrflag, 0);
+  pPager->xGet = xGetMethod;
+  pPager->xGetMethod = 0;
+  if (rc == SQLITE_OK) {
+    sqlite3PagerWrite(pBt->pBt->pPage1->pDbPage);
+    sqlite3_log(rc, "sqlite fix meta header");
+  }
+  return rc;
+}
+
+static int MetaDwrRecoverAndBeginTran(Btree *pBt, int wrflag, int *pSchemaVersion)
+{
+  Pager *pPager = pBt->pBt->pPager;
+  assert(sqlite3_mutex_held(pBt->pBt->mutex));
+  if (!pPager->metaFd || pBt->pBt->metaRecoverStatus || pPager->readOnly || pPager->memDb) {
+    return SQLITE_NOTADB;
+  }
+  int rc = MetaDwrLoadHdr(pPager);
+  if (rc != SQLITE_OK) {
+    sqlite3_log(rc, "MetaDwr load header failed");
+    return rc;
+  }
+  pBt->pBt->metaRecoverStatus = META_IN_RECOVERY;
+  rc = MetaDwrBeginTrans(pBt, 2);
+  if (rc != SQLITE_OK) {
+    return rc;
+  }
+  void *pData = NULL;
+  pPager->metaChanged = META_HEADER_CHANGED;
+  MetaDwrHdr *hdr = pPager->metaHdr;
+  sqlite3_log(SQLITE_WARNING_DUMP, "sqlite meta recover %u frames", hdr->pageCnt);
+  int szPage = sqlite3BtreeGetPageSize(pBt);
+  pData = sqlite3Malloc(szPage);
+  if (pData == NULL) {
+    rc = SQLITE_NOMEM;
+    sqlite3_log(rc, "Dwr malloc mem size %d failed", szPage);
+    goto DWR_RECOVER_OUT;
+  }
+  for (u32 i = 0; i < hdr->pageCnt; i++) {
+    rc = MetaDwrReadOnePage(pPager, hdr, i, pData);
+    if (rc != SQLITE_OK) {
+      sqlite3_log(rc, "Dwr read %d meta page failed ", i);
+      break;
+    }
+    rc = MetaDwrRecoverSinglePage(pBt, hdr->pages[i], pData);
+    if (rc != SQLITE_OK) {
+      sqlite3_log(rc, "Dwr recover %d meta page failed ", i);
+      break;
+    }
+  }
+DWR_RECOVER_OUT:
+  /* Close the transaction, if one was opened. */
+  if (rc == SQLITE_OK) {
+    sqlite3BtreeCommit(pBt);
+  } else {
+    (void)sqlite3BtreeRollback(pBt, SQLITE_ABORT_ROLLBACK, 0);
+  }
+  if (rc == SQLITE_OK) {
+    rc = sqlite3BtreeBeginTrans(pBt, wrflag, pSchemaVersion);
+  }
+  if (rc == SQLITE_OK) {
+    pBt->pBt->metaRecoverStatus = META_RECOVER_SUCCESS;
+  }
+  if (pData) {
+    sqlite3_free(pData);
+  }
+  return rc;
+}
+
+static int Sqlite3MetaDwrCheckRestore(Btree *pBt) {
+  Pager *pPager = pBt->pBt->pPager;
+  int rc = MetaDwrOpenFile(pPager, 1);
+  if (rc != SQLITE_OK) {
+    return rc;
+  }
+  ScanPages metaPages = {0};
+  rc = ScanMetaPages(pBt, &metaPages);
+  if (rc != SQLITE_OK || metaPages.pageCnt == 0) {
+    goto CHK_RESTORE_OUT;
+  }
+  rc = MetaDwrLoadAndCheckMetaFile(pBt->pBt, 0);
+  if (rc != SQLITE_OK) {
+    goto CHK_RESTORE_OUT;
+  }
+  MetaDwrHdr *hdr = pPager->metaHdr;
+  if (hdr->pageCnt == 0 || hdr->pageCnt != metaPages.pageCnt ||
+    memcmp(hdr->pages, metaPages.pages, hdr->pageCnt * sizeof(Pgno))) {
+    sqlite3_log(SQLITE_WARNING_DUMP, "sqlite meta restore all");
+    rc = MetaDwrRestoreAllPages(pBt, &metaPages, hdr);
+    if (rc == SQLITE_OK) {
+      pPager->metaChanged = META_SCHEMA_CHANGED;
+      rc = MetaDwrWriteHeader(pPager, hdr);
+      pPager->metaChanged = 0;
+    }
+  }
+  if (rc == SQLITE_OK) {
+    pBt->pBt->maxMetaPage = metaPages.maxPageNo;
+  }
+CHK_RESTORE_OUT:
+  ReleaseMetaPages(&metaPages);
+  return rc;
+}
+
+static inline u8 IsConnectionValidForCheck(Pager *pPager)
+{
+#if SQLITE_OS_UNIX
+    unixFile *fd = (unixFile *)pPager->fd;
+    // unix and only one connection exist
+    if (fd == NULL || fd->pInode == NULL || pPager->pVfs == NULL ||
+      sqlite3_stricmp(pPager->pVfs->zName, "unix") != 0 || fd->pInode->nRef != 1) {
+      return 0;
+    }
+    return 1;
+#else
+    return 0;
+#endif
+}
+
+static int MetaDwrOpenAndCheck(Btree *pBt)
+{
+  Pager *pPager = pBt->pBt->pPager;
+  if (pPager->memDb || pPager->readOnly || !IsConnectionValidForCheck(pPager)) {
+    return SQLITE_OK;
+  }
+#ifdef SQLITE_HAS_CODEC
+  // not support codec right now
+  if (pPager->xCodec) {
+    return SQLITE_OK;
+  }
+#endif
+  sqlite3BtreeEnter(pBt);
+  int rc = SQLITE_OK;
+  int openedTransaction = 0;
+  int tnxState = sqlite3BtreeTxnState(pBt);
+  if (tnxState == SQLITE_TXN_NONE) {
+    rc = sqlite3BtreeBeginTrans(pBt, 0, 0);
+    if (rc != SQLITE_OK) {
+      goto DWR_OPEN_OUT;
+    }
+    openedTransaction = 1;
+  }
+  rc = MetaDwrCheckMeta(pBt);
+  if (rc == SQLITE_CORRUPT || rc == SQLITE_NOTADB) {
+    // keep txn status after recover
+    rc = MetaDwrRecoverAndBeginTran(pBt, tnxState == SQLITE_TXN_WRITE ? 1 : 0, 0);
+    goto DWR_OPEN_OUT;
+  }
+  rc = Sqlite3MetaDwrCheckRestore(pBt);
+DWR_OPEN_OUT:
+  if (rc == SQLITE_OK && pBt->pBt->metaRecoverStatus == META_RECOVER_SUCCESS) {
+    rc = MetaDwrCheckMeta(pBt);
+    if (rc == SQLITE_OK) {
+      rc = SQLITE_META_RECOVERED;
+    }
+  }
+  /* Close the transaction, if one was opened. */
+  if (openedTransaction) {
+    sqlite3BtreeCommit(pBt);
+  }
+  sqlite3BtreeLeave(pBt);
+  return rc;
+}
 
+static void MetaDwrDisable(Btree *pBt)
+{
+  Pager *pPager = pBt->pBt->pPager;
+  if (pPager->metaFd == NULL || pPager->memDb || pPager->readOnly || !IsConnectionValidForCheck(pPager)) {
+    return;
+  }
+#ifdef SQLITE_HAS_CODEC
+  // not support codec right now
+  if (pPager->xCodec) {
+    return;
+  }
+#endif
+  sqlite3BtreeEnter(pBt);
+  MetaDwrCloseFile(pPager);
+  MetaDwrReleaseHdr(pPager->metaHdr);
+  pPager->metaHdr = NULL;
+  const char *metaPath = GetMetaFilePath(pPager);
+  if (metaPath != NULL) {
+    (void)osUnlink(metaPath);
+  }
+  if (pPager->metaFd) {
+    sqlite3_free(pPager->metaFd);
+    pPager->metaFd = NULL;
+  }
+  sqlite3BtreeLeave(pBt);
+}
+#endif
 #if SQLITE_OS_UNIX
 #include <unistd.h>
 #include <sys/syscall.h>
-- 
2.47.0.windows.2