internal static void pageReinit(DbPage pData) { var pPage = Pager.sqlite3PagerGetExtra <MemPage>(pData); Debug.Assert(Pager.GetPageRefCount(pData) > 0); if (pPage.HasInit) { Debug.Assert(MutexEx.Held(pPage.Shared.Mutex)); pPage.HasInit = false; if (Pager.GetPageRefCount(pData) > 1) { // pPage might not be a btree page; it might be an overflow page or ptrmap page or a free page. In those cases, the following // call to btreeInitPage() will likely return SQLITE_CORRUPT. But no harm is done by this. And it is very important that // btreeInitPage() be called on every btree page so we make the call for every page that comes in for re-initing. pPage.btreeInitPage(); } } }
internal RC clearCell(int pCell) { Debug.Assert(MutexEx.Held(this.Shared.Mutex)); var info = new CellInfo(); btreeParseCellPtr(pCell, ref info); if (info.iOverflow == 0) { return(RC.OK); // No overflow pages. Return without doing anything } var pBt = this.Shared; var ovflPgno = (Pgno)ConvertEx.Get4(this.Data, pCell + info.iOverflow); Debug.Assert(pBt.UsableSize > 4); var ovflPageSize = (uint)(pBt.UsableSize - 4); var nOvfl = (int)((info.nPayload - info.nLocal + ovflPageSize - 1) / ovflPageSize); Debug.Assert(ovflPgno == 0 || nOvfl > 0); RC rc; while (nOvfl-- != 0) { Pgno iNext = 0; MemPage pOvfl = null; if (ovflPgno < 2 || ovflPgno > pBt.btreePagecount()) { // 0 is not a legal page number and page 1 cannot be an overflow page. Therefore if ovflPgno<2 or past the end of the file the database must be corrupt. return(SysEx.SQLITE_CORRUPT_BKPT()); } if (nOvfl != 0) { rc = pBt.getOverflowPage(ovflPgno, out pOvfl, out iNext); if (rc != RC.OK) { return(rc); } } if ((pOvfl != null || ((pOvfl = pBt.btreePageLookup(ovflPgno)) != null)) && Pager.GetPageRefCount(pOvfl.DbPage) != 1) { // There is no reason any cursor should have an outstanding reference to an overflow page belonging to a cell that is being deleted/updated. // So if there exists more than one reference to this page, then it must not really be an overflow page and the database must be corrupt. // It is helpful to detect this before calling freePage2(), as freePage2() may zero the page contents if secure-delete mode is // enabled. If this 'overflow' page happens to be a page that the caller is iterating through or using in some other way, this // can be problematic. rc = SysEx.SQLITE_CORRUPT_BKPT(); } else { rc = pBt.freePage2(pOvfl, ovflPgno); } if (pOvfl != null) { Pager.Unref(pOvfl.DbPage); } if (rc != RC.OK) { return(rc); } ovflPgno = iNext; } return(RC.OK); }
internal RC allocateBtreePage(ref MemPage ppPage, ref Pgno pPgno, Pgno nearby, byte exact) { MemPage pTrunk = null; MemPage pPrevTrunk = null; Debug.Assert(MutexEx.Held(this.Mutex)); var pPage1 = this.Page1; var mxPage = btreePagecount(); // Total size of the database file var n = ConvertEx.Get4(pPage1.Data, 36); // Number of pages on the freelist if (n >= mxPage) { return(SysEx.SQLITE_CORRUPT_BKPT()); } RC rc; if (n > 0) { // There are pages on the freelist. Reuse one of those pages. Pgno iTrunk; byte searchList = 0; // If the free-list must be searched for 'nearby' // If the 'exact' parameter was true and a query of the pointer-map shows that the page 'nearby' is somewhere on the free-list, then the entire-list will be searched for that page. #if !SQLITE_OMIT_AUTOVACUUM if (exact != 0 && nearby <= mxPage) { Debug.Assert(nearby > 0); Debug.Assert(this.AutoVacuum); PTRMAP eType = 0; uint dummy0 = 0; rc = ptrmapGet(nearby, ref eType, ref dummy0); if (rc != RC.OK) { return(rc); } if (eType == PTRMAP.FREEPAGE) { searchList = 1; } pPgno = nearby; } #endif // Decrement the free-list count by 1. Set iTrunk to the index of the first free-list trunk page. iPrevTrunk is initially 1. rc = Pager.Write(pPage1.DbPage); if (rc != RC.OK) { return(rc); } ConvertEx.Put4(pPage1.Data, 36, n - 1); // The code within this loop is run only once if the 'searchList' variable is not true. Otherwise, it runs once for each trunk-page on the // free-list until the page 'nearby' is located. do { pPrevTrunk = pTrunk; iTrunk = (pPrevTrunk != null ? ConvertEx.Get4(pPrevTrunk.Data, 0) : ConvertEx.Get4(pPage1.Data, 32)); rc = (iTrunk > mxPage ? SysEx.SQLITE_CORRUPT_BKPT() : btreeGetPage(iTrunk, ref pTrunk, 0)); if (rc != RC.OK) { pTrunk = null; goto end_allocate_page; } var k = ConvertEx.Get4(pTrunk.Data, 4); // # of leaves on this trunk page if (k == 0 && searchList == 0) { // The trunk has no leaves and the list is not being searched. So extract the trunk page itself and use it as the newly allocated page Debug.Assert(pPrevTrunk == null); rc = Pager.Write(pTrunk.DbPage); if (rc != RC.OK) { goto end_allocate_page; } pPgno = iTrunk; Buffer.BlockCopy(pTrunk.Data, 0, pPage1.Data, 32, 4); ppPage = pTrunk; pTrunk = null; Btree.TRACE("ALLOCATE: %d trunk - %d free pages left\n", pPgno, n - 1); } else if (k > (uint)(this.UsableSize / 4 - 2)) { // Value of k is out of range. Database corruption rc = SysEx.SQLITE_CORRUPT_BKPT(); goto end_allocate_page; #if !SQLITE_OMIT_AUTOVACUUM } else if (searchList != 0 && nearby == iTrunk) { // The list is being searched and this trunk page is the page to allocate, regardless of whether it has leaves. Debug.Assert(pPgno == iTrunk); ppPage = pTrunk; searchList = 0; rc = Pager.Write(pTrunk.DbPage); if (rc != RC.OK) { goto end_allocate_page; } if (k == 0) { if (pPrevTrunk == null) { pPage1.Data[32 + 0] = pTrunk.Data[0 + 0]; pPage1.Data[32 + 1] = pTrunk.Data[0 + 1]; pPage1.Data[32 + 2] = pTrunk.Data[0 + 2]; pPage1.Data[32 + 3] = pTrunk.Data[0 + 3]; } else { rc = Pager.Write(pPrevTrunk.DbPage); if (rc != RC.OK) { goto end_allocate_page; } pPrevTrunk.Data[0 + 0] = pTrunk.Data[0 + 0]; pPrevTrunk.Data[0 + 1] = pTrunk.Data[0 + 1]; pPrevTrunk.Data[0 + 2] = pTrunk.Data[0 + 2]; pPrevTrunk.Data[0 + 3] = pTrunk.Data[0 + 3]; } } else { // The trunk page is required by the caller but it contains pointers to free-list leaves. The first leaf becomes a trunk page in this case. var pNewTrunk = new MemPage(); var iNewTrunk = (Pgno)ConvertEx.Get4(pTrunk.Data, 8); if (iNewTrunk > mxPage) { rc = SysEx.SQLITE_CORRUPT_BKPT(); goto end_allocate_page; } rc = btreeGetPage(iNewTrunk, ref pNewTrunk, 0); if (rc != RC.OK) { goto end_allocate_page; } rc = Pager.Write(pNewTrunk.DbPage); if (rc != RC.OK) { pNewTrunk.releasePage(); goto end_allocate_page; } pNewTrunk.Data[0 + 0] = pTrunk.Data[0 + 0]; pNewTrunk.Data[0 + 1] = pTrunk.Data[0 + 1]; pNewTrunk.Data[0 + 2] = pTrunk.Data[0 + 2]; pNewTrunk.Data[0 + 3] = pTrunk.Data[0 + 3]; ConvertEx.Put4(pNewTrunk.Data, 4, (uint)(k - 1)); Buffer.BlockCopy(pTrunk.Data, 12, pNewTrunk.Data, 8, (int)(k - 1) * 4); pNewTrunk.releasePage(); if (pPrevTrunk == null) { Debug.Assert(Pager.IsPageWriteable(pPage1.DbPage)); ConvertEx.Put4(pPage1.Data, 32, iNewTrunk); } else { rc = Pager.Write(pPrevTrunk.DbPage); if (rc != RC.OK) { goto end_allocate_page; } ConvertEx.Put4(pPrevTrunk.Data, 0, iNewTrunk); } } pTrunk = null; Btree.TRACE("ALLOCATE: %d trunk - %d free pages left\n", pPgno, n - 1); #endif } else if (k > 0) { // Extract a leaf from the trunk uint closest; var aData = pTrunk.Data; if (nearby > 0) { closest = 0; var dist = Math.Abs((int)(ConvertEx.Get4(aData, 8) - nearby)); for (uint i = 1; i < k; i++) { int dist2 = Math.Abs((int)(ConvertEx.Get4(aData, 8 + i * 4) - nearby)); if (dist2 < dist) { closest = i; dist = dist2; } } } else { closest = 0; } // var iPage = (Pgno)ConvertEx.Get4(aData, 8 + closest * 4); if (iPage > mxPage) { rc = SysEx.SQLITE_CORRUPT_BKPT(); goto end_allocate_page; } if (searchList == 0 || iPage == nearby) { pPgno = iPage; Btree.TRACE("ALLOCATE: %d was leaf %d of %d on trunk %d" + ": %d more free pages\n", pPgno, closest + 1, k, pTrunk.ID, n - 1); rc = Pager.Write(pTrunk.DbPage); if (rc != RC.OK) { goto end_allocate_page; } if (closest < k - 1) { Buffer.BlockCopy(aData, (int)(4 + k * 4), aData, 8 + (int)closest * 4, 4); } ConvertEx.Put4(aData, 4, (k - 1)); var noContent = (!btreeGetHasContent(pPgno) ? 1 : 0); rc = btreeGetPage(pPgno, ref ppPage, noContent); if (rc == RC.OK) { rc = Pager.Write((ppPage).DbPage); if (rc != RC.OK) { ppPage.releasePage(); } } searchList = 0; } } pPrevTrunk.releasePage(); pPrevTrunk = null; } while (searchList != 0); } else { // There are no pages on the freelist, so create a new page at the end of the file rc = Pager.Write(this.Page1.DbPage); if (rc != RC.OK) { return(rc); } this.Pages++; if (this.Pages == MemPage.PENDING_BYTE_PAGE(this)) { this.Pages++; } #if !SQLITE_OMIT_AUTOVACUUM if (this.AutoVacuum && MemPage.PTRMAP_ISPAGE(this, this.Pages)) { // If pPgno refers to a pointer-map page, allocate two new pages at the end of the file instead of one. The first allocated page // becomes a new pointer-map page, the second is used by the caller. MemPage pPg = null; Btree.TRACE("ALLOCATE: %d from end of file (pointer-map page)\n", pPgno); Debug.Assert(this.Pages != MemPage.PENDING_BYTE_PAGE(this)); rc = btreeGetPage(this.Pages, ref pPg, 1); if (rc == RC.OK) { rc = Pager.Write(pPg.DbPage); pPg.releasePage(); } if (rc != RC.OK) { return(rc); } this.Pages++; if (this.Pages == MemPage.PENDING_BYTE_PAGE(this)) { this.Pages++; } } #endif ConvertEx.Put4(this.Page1.Data, 28, this.Pages); pPgno = this.Pages; Debug.Assert(pPgno != MemPage.PENDING_BYTE_PAGE(this)); rc = btreeGetPage(pPgno, ref ppPage, 1); if (rc != RC.OK) { return(rc); } rc = Pager.Write((ppPage).DbPage); if (rc != RC.OK) { ppPage.releasePage(); } Btree.TRACE("ALLOCATE: %d from end of file\n", pPgno); } Debug.Assert(pPgno != MemPage.PENDING_BYTE_PAGE(this)); end_allocate_page: pTrunk.releasePage(); pPrevTrunk.releasePage(); if (rc == RC.OK) { if (Pager.GetPageRefCount((ppPage).DbPage) > 1) { ppPage.releasePage(); return(SysEx.SQLITE_CORRUPT_BKPT()); } (ppPage).HasInit = false; } else { ppPage = null; } Debug.Assert(rc != RC.OK || Pager.IsPageWriteable((ppPage).DbPage)); return(rc); }