internal RC modifyPagePointer(Pgno iFrom, Pgno iTo, PTRMAP eType) { Debug.Assert(MutexEx.Held(this.Shared.Mutex)); Debug.Assert(Pager.IsPageWriteable(this.DbPage)); if (eType == PTRMAP.OVERFLOW2) { // The pointer is always the first 4 bytes of the page in this case. if (ConvertEx.Get4(this.Data) != iFrom) { return(SysEx.SQLITE_CORRUPT_BKPT()); } ConvertEx.Put4L(this.Data, iTo); } else { var isInitOrig = this.HasInit; btreeInitPage(); var nCell = this.Cells; int i; for (i = 0; i < nCell; i++) { var pCell = FindCell(i); if (eType == PTRMAP.OVERFLOW1) { var info = new CellInfo(); btreeParseCellPtr(pCell, ref info); if (info.iOverflow != 0) { if (iFrom == ConvertEx.Get4(this.Data, pCell + info.iOverflow)) { ConvertEx.Put4(this.Data, pCell + info.iOverflow, (int)iTo); break; } } } else { if (ConvertEx.Get4(this.Data, pCell) == iFrom) { ConvertEx.Put4(this.Data, pCell, (int)iTo); break; } } } if (i == nCell) { if (eType != PTRMAP.BTREE || ConvertEx.Get4(this.Data, this.HeaderOffset + 8) != iFrom) { return(SysEx.SQLITE_CORRUPT_BKPT()); } ConvertEx.Put4L(this.Data, (uint)this.HeaderOffset + 8, iTo); } this.HasInit = isInitOrig; } return(RC.OK); }
static void checkPtrmap(IntegrityCk check, Pid childID, PTRMAP type, Pid parentID, string context) { PTRMAP ptrmapType = 0; Pid ptrmapParentID = 0; RC rc = ptrmapGet(check.Bt, childID, ref ptrmapType, ref ptrmapParentID); if (rc != RC.OK) { if (rc == RC.NOMEM || rc == RC.IOERR_NOMEM) check.MallocFailed = true; checkAppendMsg(check, context, "Failed to read ptrmap key=%d", childID); return; } if (ptrmapType != type || ptrmapParentID != parentID) checkAppendMsg(check, context, "Bad ptr map entry key=%d expected=(%d,%d) got=(%d,%d)", childID, type, parentID, ptrmapType, ptrmapParentID); }
static RC ptrmapGet(BtShared bt, Pid key, ref PTRMAP type, ref Pid id) { Debug.Assert(MutexEx.Held(bt.Mutex)); var page = (IPage)new PgHdr(); // The pointer map page var ptrmapIdx = (Pid)PTRMAP_PAGENO(bt, key); // Pointer map page index var rc = bt.Pager.Acquire(ptrmapIdx, ref page, false); if (rc != RC.OK) return rc; var ptrmap = Pager.GetData(page); // Pointer map page data var offset = (int)PTRMAP_PTROFFSET(ptrmapIdx, key); // Offset of entry in pointer map if (offset < 0) { Pager.Unref(page); return SysEx.CORRUPT_BKPT(); } Debug.Assert(offset <= (int)bt.UsableSize - 5); Debug.Assert(type != 0); type = (PTRMAP)ptrmap[offset]; id = ConvertEx.Get4(ptrmap, offset + 1); Pager.Unref(page); if ((byte)type < 1 || (byte)type > 5) return SysEx.CORRUPT_BKPT(); return RC.OK; }
static void ptrmapPut(BtShared bt, Pid key, PTRMAP type, Pid parent, ref RC rcRef) { if (rcRef != RC.OK) return; Debug.Assert(MutexEx.Held(bt.Mutex)); // The master-journal page number must never be used as a pointer map page Debug.Assert(!PTRMAP_ISPAGE(bt, PENDING_BYTE_PAGE(bt))); Debug.Assert(bt.AutoVacuum); if (key == 0) { rcRef = SysEx.CORRUPT_BKPT(); return; } var ptrmapIdx = PTRMAP_PAGENO(bt, key); // The pointer map page number var page = (IPage)new PgHdr(); // The pointer map page var rc = bt.Pager.Acquire(ptrmapIdx, ref page, false); if (rc != RC.OK) { rcRef = rc; return; } var offset = (int)PTRMAP_PTROFFSET(ptrmapIdx, key); // Offset in pointer map page if (offset < 0) { rcRef = SysEx.CORRUPT_BKPT(); goto ptrmap_exit; } Debug.Assert(offset <= (int)bt.UsableSize - 5); var ptrmap = Pager.GetData(page); // The pointer map page if (type != (PTRMAP)ptrmap[offset] || ConvertEx.Get4(ptrmap, offset + 1) != parent) { TRACE("PTRMAP_UPDATE: %d->(%d,%d)\n", key, type, parent); rcRef = rc = Pager.Write(page); if (rc == RC.OK) { ptrmap[offset] = (byte)type; ConvertEx.Put4(ptrmap, offset + 1, parent); } } ptrmap_exit: Pager.Unref(page); }
static RC relocatePage(BtShared bt, MemPage page, PTRMAP type, Pid ptrPageID, Pid freePageID, bool isCommit) { Debug.Assert(type == PTRMAP.OVERFLOW2 || type == PTRMAP.OVERFLOW1 || type == PTRMAP.BTREE || type == PTRMAP.ROOTPAGE); Debug.Assert(MutexEx.Held(bt.Mutex)); Debug.Assert(page.Bt == bt); // Move page iDbPage from its current location to page number iFreePage var lastID = page.ID; TRACE("AUTOVACUUM: Moving %d to free page %d (ptr page %d type %d)\n", lastID, freePageID, ptrPageID, type); Pager pager = bt.Pager; var rc = pager.Movepage(page.DBPage, freePageID, isCommit); if (rc != RC.OK) return rc; page.ID = freePageID; // If pDbPage was a btree-page, then it may have child pages and/or cells that point to overflow pages. The pointer map entries for all these // pages need to be changed. // // If pDbPage is an overflow page, then the first 4 bytes may store a pointer to a subsequent overflow page. If this is the case, then // the pointer map needs to be updated for the subsequent overflow page. if (type == PTRMAP.BTREE || type == PTRMAP.ROOTPAGE) { rc = setChildPtrmaps(page); if (rc != RC.OK) return rc; } else { Pid nextOvfl = ConvertEx.Get4(page.Data); if (nextOvfl != 0) { ptrmapPut(bt, nextOvfl, PTRMAP.OVERFLOW2, freePageID, ref rc); if (rc != RC.OK) return rc; } } // Fix the database pointer on page iPtrPage that pointed at iDbPage so that it points at iFreePage. Also fix the pointer map entry for iPtrPage. if (type != PTRMAP.ROOTPAGE) { var ptrPage = new MemPage(); // The page that contains a pointer to pDbPage rc = btreeGetPage(bt, ptrPageID, ref ptrPage, false); if (rc != RC.OK) return rc; rc = Pager.Write(ptrPage.DBPage); if (rc != RC.OK) { releasePage(ptrPage); return rc; } rc = modifyPagePointer(ptrPage, lastID, freePageID, type); releasePage(ptrPage); if (rc == RC.OK) ptrmapPut(bt, freePageID, type, ptrPageID, ref rc); } return rc; }
static RC modifyPagePointer(MemPage page, Pid from, Pid to, PTRMAP type) { Debug.Assert(MutexEx.Held(page.Bt.Mutex)); Debug.Assert(Pager.Iswriteable(page.DBPage)); if (type == PTRMAP.OVERFLOW2) { // The pointer is always the first 4 bytes of the page in this case. if (ConvertEx.Get4(page.Data) != from) return SysEx.CORRUPT_BKPT(); ConvertEx.Put4(page.Data, to); } else { var isInitOrig = page.IsInit; btreeInitPage(page); ushort cells = page.Cells; uint i; for (i = 0U; i < cells; i++) { uint cell_ = findCell(page, i); if (type == PTRMAP.OVERFLOW1) { var info = new CellInfo(); btreeParseCellPtr(page, cell_, ref info); if (info.Overflow != 0 && //cell + info.Overflow + 3 <= page.Data + page.MaskPage && from == ConvertEx.Get4(page.Data, cell_ + info.Overflow)) { ConvertEx.Put4(page.Data, cell_ + info.Overflow, (int)to); break; } } else if (ConvertEx.Get4(page.Data, cell_) == from) { ConvertEx.Put4(page.Data, cell_, (int)to); break; } } if (i == cells) { if (type != PTRMAP.BTREE || ConvertEx.Get4(page.Data, page.HdrOffset + 8) != from) return SysEx.CORRUPT_BKPT(); ConvertEx.Put4(page.Data, page.HdrOffset + 8, to); } page.IsInit = isInitOrig; } return RC.OK; }
internal static RC incrVacuumStep(BtShared pBt, Pgno nFin, Pgno iLastPg) { Pgno nFreeList; // Number of pages still on the free-list Debug.Assert(MutexEx.Held(pBt.Mutex)); Debug.Assert(iLastPg > nFin); if (!PTRMAP_ISPAGE(pBt, iLastPg) && iLastPg != PENDING_BYTE_PAGE(pBt)) { PTRMAP eType = 0; Pgno iPtrPage = 0; nFreeList = ConvertEx.Get4(pBt.Page1.Data, 36); if (nFreeList == 0) { return(RC.DONE); } var rc = pBt.ptrmapGet(iLastPg, ref eType, ref iPtrPage); if (rc != RC.OK) { return(rc); } if (eType == PTRMAP.ROOTPAGE) { return(SysEx.SQLITE_CORRUPT_BKPT()); } if (eType == PTRMAP.FREEPAGE) { if (nFin == 0) { // Remove the page from the files free-list. This is not required if nFin is non-zero. In that case, the free-list will be // truncated to zero after this function returns, so it doesn't matter if it still contains some garbage entries. Pgno iFreePg = 0; var pFreePg = new MemPage(); rc = pBt.allocateBtreePage(ref pFreePg, ref iFreePg, iLastPg, 1); if (rc != RC.OK) { return(rc); } Debug.Assert(iFreePg == iLastPg); pFreePg.releasePage(); } } else { Pgno iFreePg = 0; // Index of free page to move pLastPg to var pLastPg = new MemPage(); rc = pBt.btreeGetPage(iLastPg, ref pLastPg, 0); if (rc != RC.OK) { return(rc); } // If nFin is zero, this loop runs exactly once and page pLastPg is swapped with the first free page pulled off the free list. // On the other hand, if nFin is greater than zero, then keep looping until a free-page located within the first nFin pages of the file is found. do { var pFreePg = new MemPage(); rc = pBt.allocateBtreePage(ref pFreePg, ref iFreePg, 0, 0); if (rc != RC.OK) { pLastPg.releasePage(); return(rc); } pFreePg.releasePage(); } while (nFin != 0 && iFreePg > nFin); Debug.Assert(iFreePg < iLastPg); rc = Pager.Write(pLastPg.DbPage); if (rc == RC.OK) { rc = relocatePage(pBt, pLastPg, eType, iPtrPage, iFreePg, (nFin != 0) ? 1 : 0); } pLastPg.releasePage(); if (rc != RC.OK) { return(rc); } } } if (nFin == 0) { iLastPg--; while (iLastPg == PENDING_BYTE_PAGE(pBt) || PTRMAP_ISPAGE(pBt, iLastPg)) { if (PTRMAP_ISPAGE(pBt, iLastPg)) { var pPg = new MemPage(); var rc = pBt.btreeGetPage(iLastPg, ref pPg, 0); if (rc != RC.OK) { return(rc); } rc = Pager.Write(pPg.DbPage); pPg.releasePage(); if (rc != RC.OK) { return(rc); } } iLastPg--; } pBt.Pager.TruncateImage(iLastPg); pBt.Pages = iLastPg; } return(RC.OK); }
internal static RC relocatePage(BtShared pBt, MemPage pDbPage, PTRMAP eType, Pgno iPtrPage, Pgno iFreePage, int isCommit) { var pPtrPage = new MemPage(); // The page that contains a pointer to pDbPage var iDbPage = pDbPage.ID; var pPager = pBt.Pager; Debug.Assert(eType == PTRMAP.OVERFLOW2 || eType == PTRMAP.OVERFLOW1 || eType == PTRMAP.BTREE || eType == PTRMAP.ROOTPAGE); Debug.Assert(MutexEx.Held(pBt.Mutex)); Debug.Assert(pDbPage.Shared == pBt); // Move page iDbPage from its current location to page number iFreePage Btree.TRACE("AUTOVACUUM: Moving %d to free page %d (ptr page %d type %d)\n", iDbPage, iFreePage, iPtrPage, eType); var rc = pPager.sqlite3PagerMovepage(pDbPage.DbPage, iFreePage, isCommit); if (rc != RC.OK) { return(rc); } pDbPage.ID = iFreePage; // If pDbPage was a btree-page, then it may have child pages and/or cells that point to overflow pages. The pointer map entries for all these // pages need to be changed. // If pDbPage is an overflow page, then the first 4 bytes may store a pointer to a subsequent overflow page. If this is the case, then // the pointer map needs to be updated for the subsequent overflow page. if (eType == PTRMAP.BTREE || eType == PTRMAP.ROOTPAGE) { rc = pDbPage.setChildPtrmaps(); if (rc != RC.OK) { return(rc); } } else { var nextOvfl = (Pgno)ConvertEx.Get4(pDbPage.Data); if (nextOvfl != 0) { pBt.ptrmapPut(nextOvfl, PTRMAP.OVERFLOW2, iFreePage, ref rc); if (rc != RC.OK) { return(rc); } } } // Fix the database pointer on page iPtrPage that pointed at iDbPage so that it points at iFreePage. Also fix the pointer map entry for iPtrPage. if (eType != PTRMAP.ROOTPAGE) { rc = pBt.btreeGetPage(iPtrPage, ref pPtrPage, 0); if (rc != RC.OK) { return(rc); } rc = Pager.Write(pPtrPage.DbPage); if (rc != RC.OK) { pPtrPage.releasePage(); return(rc); } rc = pPtrPage.modifyPagePointer(iDbPage, iFreePage, eType); pPtrPage.releasePage(); if (rc == RC.OK) { pBt.ptrmapPut(iFreePage, eType, iPtrPage, ref rc); } } return(rc); }
internal static RC relocatePage(BtShared pBt, MemPage pDbPage, PTRMAP eType, Pgno iPtrPage, Pgno iFreePage, int isCommit) { var pPtrPage = new MemPage(); // The page that contains a pointer to pDbPage var iDbPage = pDbPage.ID; var pPager = pBt.Pager; Debug.Assert(eType == PTRMAP.OVERFLOW2 || eType == PTRMAP.OVERFLOW1 || eType == PTRMAP.BTREE || eType == PTRMAP.ROOTPAGE); Debug.Assert(MutexEx.Held(pBt.Mutex)); Debug.Assert(pDbPage.Shared == pBt); // Move page iDbPage from its current location to page number iFreePage Btree.TRACE("AUTOVACUUM: Moving %d to free page %d (ptr page %d type %d)\n", iDbPage, iFreePage, iPtrPage, eType); var rc = pPager.sqlite3PagerMovepage(pDbPage.DbPage, iFreePage, isCommit); if (rc != RC.OK) return rc; pDbPage.ID = iFreePage; // If pDbPage was a btree-page, then it may have child pages and/or cells that point to overflow pages. The pointer map entries for all these // pages need to be changed. // If pDbPage is an overflow page, then the first 4 bytes may store a pointer to a subsequent overflow page. If this is the case, then // the pointer map needs to be updated for the subsequent overflow page. if (eType == PTRMAP.BTREE || eType == PTRMAP.ROOTPAGE) { rc = pDbPage.setChildPtrmaps(); if (rc != RC.OK) return rc; } else { var nextOvfl = (Pgno)ConvertEx.Get4(pDbPage.Data); if (nextOvfl != 0) { pBt.ptrmapPut(nextOvfl, PTRMAP.OVERFLOW2, iFreePage, ref rc); if (rc != RC.OK) return rc; } } // Fix the database pointer on page iPtrPage that pointed at iDbPage so that it points at iFreePage. Also fix the pointer map entry for iPtrPage. if (eType != PTRMAP.ROOTPAGE) { rc = pBt.btreeGetPage(iPtrPage, ref pPtrPage, 0); if (rc != RC.OK) return rc; rc = Pager.Write(pPtrPage.DbPage); if (rc != RC.OK) { pPtrPage.releasePage(); return rc; } rc = pPtrPage.modifyPagePointer(iDbPage, iFreePage, eType); pPtrPage.releasePage(); if (rc == RC.OK) pBt.ptrmapPut(iFreePage, eType, iPtrPage, ref rc); } return rc; }
internal RC modifyPagePointer(Pgno iFrom, Pgno iTo, PTRMAP eType) { Debug.Assert(MutexEx.Held(this.Shared.Mutex)); Debug.Assert(Pager.IsPageWriteable(this.DbPage)); if (eType == PTRMAP.OVERFLOW2) { // The pointer is always the first 4 bytes of the page in this case. if (ConvertEx.Get4(this.Data) != iFrom) return SysEx.SQLITE_CORRUPT_BKPT(); ConvertEx.Put4L(this.Data, iTo); } else { var isInitOrig = this.HasInit; btreeInitPage(); var nCell = this.Cells; int i; for (i = 0; i < nCell; i++) { var pCell = FindCell(i); if (eType == PTRMAP.OVERFLOW1) { var info = new CellInfo(); btreeParseCellPtr(pCell, ref info); if (info.iOverflow != 0) if (iFrom == ConvertEx.Get4(this.Data, pCell, info.iOverflow)) { ConvertEx.Put4(this.Data, pCell + info.iOverflow, (int)iTo); break; } } else { if (ConvertEx.Get4(this.Data, pCell) == iFrom) { ConvertEx.Put4(this.Data, pCell, (int)iTo); break; } } } if (i == nCell) { if (eType != PTRMAP.BTREE || ConvertEx.Get4(this.Data, this.HeaderOffset + 8) != iFrom) return SysEx.SQLITE_CORRUPT_BKPT(); ConvertEx.Put4L(this.Data, this.HeaderOffset + 8, iTo); } this.HasInit = isInitOrig; } return RC.OK; }