internal static RC sqlite3BtreeIncrVacuum(Btree p) { var pBt = p.Shared; p.sqlite3BtreeEnter(); Debug.Assert(pBt.InTransaction == TRANS.WRITE && p.InTransaction == TRANS.WRITE); RC rc; if (!pBt.AutoVacuum) { rc = RC.DONE; } else { Btree.invalidateAllOverflowCache(pBt); rc = incrVacuumStep(pBt, 0, pBt.btreePagecount()); if (rc == RC.OK) { rc = Pager.Write(pBt.Page1.DbPage); ConvertEx.Put4(pBt.Page1.Data, 28, pBt.Pages); } } p.sqlite3BtreeLeave(); return(rc); }
// Write a 32-bit integer into the given file descriptor. Return SQLITE.OK on success or an error code is something goes wrong. public RC WriteByte(long offset, uint val) { var ac = new byte[4]; ConvertEx.Put4(ac, val); return(this.Write(ac, 4, offset)); }
internal static RC autoVacuumCommit(BtShared pBt) { var rc = RC.OK; var pPager = pBt.Pager; #if DEBUG var nRef = pPager.RefCount; #else var nRef = 0; #endif Debug.Assert(MutexEx.Held(pBt.Mutex)); Btree.invalidateAllOverflowCache(pBt); Debug.Assert(pBt.AutoVacuum); if (!pBt.IncrVacuum) { var nOrig = pBt.btreePagecount(); // Database size before freeing if (PTRMAP_ISPAGE(pBt, nOrig) || nOrig == PENDING_BYTE_PAGE(pBt)) { // It is not possible to create a database for which the final page is either a pointer-map page or the pending-byte page. If one // is encountered, this indicates corruption. return(SysEx.SQLITE_CORRUPT_BKPT()); } var nFree = (Pgno)ConvertEx.Get4(pBt.Page1.Data, 36); // Number of pages on the freelist initially var nEntry = (int)pBt.UsableSize / 5; // Number of entries on one ptrmap page var nPtrmap = (Pgno)((nFree - nOrig + PTRMAP_PAGENO(pBt, nOrig) + (Pgno)nEntry) / nEntry); // Number of PtrMap pages to be freed var nFin = nOrig - nFree - nPtrmap; // Number of pages in database after autovacuuming if (nOrig > PENDING_BYTE_PAGE(pBt) && nFin < PENDING_BYTE_PAGE(pBt)) { nFin--; } while (PTRMAP_ISPAGE(pBt, nFin) || nFin == PENDING_BYTE_PAGE(pBt)) { nFin--; } if (nFin > nOrig) { return(SysEx.SQLITE_CORRUPT_BKPT()); } for (var iFree = nOrig; iFree > nFin && rc == RC.OK; iFree--) { rc = incrVacuumStep(pBt, nFin, iFree); } if ((rc == RC.DONE || rc == RC.OK) && nFree > 0) { rc = Pager.Write(pBt.Page1.DbPage); ConvertEx.Put4(pBt.Page1.Data, 32, 0); ConvertEx.Put4(pBt.Page1.Data, 36, 0); ConvertEx.Put4(pBt.Page1.Data, 28, nFin); pBt.Pager.TruncateImage(nFin); pBt.Pages = nFin; } if (rc != RC.OK) { pPager.Rollback(); } } Debug.Assert(nRef == pPager.RefCount); 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, (uint)this.HeaderOffset + 8, iTo); } this.HasInit = isInitOrig; } return(RC.OK); }
private static void pager_write_changecounter(PgHdr pPg) { // Increment the value just read and write it back to byte 24. uint change_counter = ConvertEx.Get4(pPg.Pager.dbFileVers, 0) + 1; ConvertEx.Put4(pPg.Data, 24, change_counter); // Also store the SQLite version number in bytes 96..99 and in bytes 92..95 store the change counter for which the version number // is valid. ConvertEx.Put4(pPg.Data, 92, change_counter); ConvertEx.Put4(pPg.Data, 96, SysEx.SQLITE_VERSION_NUMBER); }
internal RC newDatabase() { Debug.Assert(MutexEx.Held(this.Mutex)); if (this.Pages > 0) { return(RC.OK); } var pP1 = this.Page1; Debug.Assert(pP1 != null); var data = pP1.Data; var rc = Pager.Write(pP1.DbPage); if (rc != RC.OK) { return(rc); } Buffer.BlockCopy(Btree.zMagicHeader, 0, data, 0, 16); Debug.Assert(Btree.zMagicHeader.Length == 16); data[16] = (byte)((this.PageSize >> 8) & 0xff); data[17] = (byte)((this.PageSize >> 16) & 0xff); data[18] = 1; data[19] = 1; Debug.Assert(this.UsableSize <= this.PageSize && this.UsableSize + 255 >= this.PageSize); data[20] = (byte)(this.PageSize - this.UsableSize); data[21] = 64; data[22] = 32; data[23] = 32; pP1.zeroPage(Btree.PTF_INTKEY | Btree.PTF_LEAF | Btree.PTF_LEAFDATA); this.PageSizeFixed = true; #if !SQLITE_OMIT_AUTOVACUUM Debug.Assert(this.AutoVacuum == true || this.AutoVacuum == false); Debug.Assert(this.IncrVacuum == true || this.IncrVacuum == false); ConvertEx.Put4(data, 36 + 4 * 4, this.AutoVacuum ? 1 : 0); ConvertEx.Put4(data, 36 + 7 * 4, this.IncrVacuum ? 1 : 0); #endif this.Pages = 1; data[31] = 1; return(RC.OK); }
// was:sqlite3BtreeBeginTrans public RC BeginTrans(byte wrflag) { var shared = this.Shared; var rc = RC.OK; sqlite3BtreeEnter(); btreeIntegrity(); // If the btree is already in a write-transaction, or it is already in a read-transaction and a read-transaction is requested, this is a no-op. if (this.InTransaction == TRANS.WRITE || (this.InTransaction == TRANS.READ && wrflag == 0)) goto trans_begun; // Write transactions are not possible on a read-only database if (shared.ReadOnly && wrflag != 0) { rc = RC.READONLY; goto trans_begun; } #if !SQLITE_OMIT_SHARED_CACHE // If another database handle has already opened a write transaction on this shared-btree structure and a second write transaction is // requested, return SQLITE_LOCKED. sqlite3b sharedDB = null; if ((wrflag != 0 && shared.InTransaction == TRANS.WRITE) || shared.IsPending) sharedDB = shared.Writer.DB; else if (wrflag > 1) for (var @lock = shared.Locks; @lock != null; @lock = @lock.Next) if (@lock.Tree != this) { sharedDB = @lock.Tree.DB; break; } if (sharedDB != null) { sqlite3b.sqlite3ConnectionBlocked(this.DB, sharedDB); rc = RC.LOCKED_SHAREDCACHE; goto trans_begun; } #endif // Any read-only or read-write transaction implies a read-lock on page 1. So if some other shared-cache client already has a write-lock // on page 1, the transaction cannot be opened. */ rc = querySharedCacheTableLock(MASTER_ROOT, LOCK.READ); if (rc != RC.OK) goto trans_begun; shared.InitiallyEmpty = shared.Pages == 0; do { // Call lockBtree() until either pBt.pPage1 is populated or lockBtree() returns something other than SQLITE_OK. lockBtree() // may return SQLITE_OK but leave pBt.pPage1 set to 0 if after reading page 1 it discovers that the page-size of the database // file is not pBt.pageSize. In this case lockBtree() will update pBt.pageSize to the page-size of the file on disk. while (shared.Page1 == null && (rc = shared.lockBtree()) == RC.OK) ; if (rc == RC.OK && wrflag != 0) { if (shared.ReadOnly) rc = RC.READONLY; else { rc = shared.Pager.Begin(wrflag > 1, this.DB.sqlite3TempInMemory()); if (rc == RC.OK) rc = shared.newDatabase(); } } if (rc != RC.OK) shared.unlockBtreeIfUnused(); } while (((int)rc & 0xFF) == (int)RC.BUSY && shared.InTransaction == TRANS.NONE && btreeInvokeBusyHandler(shared) != 0); if (rc == RC.OK) { if (this.InTransaction == TRANS.NONE) { shared.Transactions++; #if !SQLITE_OMIT_SHARED_CACHE if (Sharable) { Debug.Assert(Locks.Tree == this && Locks.TableID == 1); Locks.Lock = LOCK.READ; Locks.Next = shared.Locks; shared.Locks = Locks; } #endif } this.InTransaction = (wrflag != 0 ? TRANS.WRITE : TRANS.READ); if (this.InTransaction > shared.InTransaction) shared.InTransaction = this.InTransaction; if (wrflag != 0) { var pPage1 = shared.Page1; #if !SQLITE_OMIT_SHARED_CACHE Debug.Assert(shared.Writer == null); shared.Writer = this; shared.IsExclusive = (wrflag > 1); #endif // If the db-size header field is incorrect (as it may be if an old client has been writing the database file), update it now. Doing // this sooner rather than later means the database size can safely re-read the database size from page 1 if a savepoint or transaction // rollback occurs within the transaction. if (shared.Pages != ConvertEx.Get4(pPage1.Data, 28)) { rc = Pager.Write(pPage1.DbPage); if (rc == RC.OK) ConvertEx.Put4(pPage1.Data, 28, shared.Pages); } } } trans_begun: if (rc == RC.OK && wrflag != 0) // This call makes sure that the pager has the correct number of open savepoints. If the second parameter is greater than 0 and // the sub-journal is not already open, then it will be opened here. rc = shared.Pager.OpenSavepoint(this.DB.nSavepoint); btreeIntegrity(); sqlite3BtreeLeave(); return rc; }
internal RC fillInCell(byte[] pCell, byte[] pKey, long nKey, byte[] pData, int nData, int nZero, ref int pnSize) { Debug.Assert(MutexEx.Held(this.Shared.Mutex)); // pPage is not necessarily writeable since pCell might be auxiliary buffer space that is separate from the pPage buffer area // TODO -- Determine if the following Assert is needed under c# //Debug.Assert( pCell < pPage.aData || pCell >= &pPage.aData[pBt.pageSize] || sqlite3PagerIswriteable(pPage.pDbPage) ); // Fill in the header. var nHeader = 0; if (this.Leaf == 0) { nHeader += 4; } if (this.HasData != 0) { nHeader += (int)ConvertEx.PutVariant9(pCell, (uint)nHeader, (int)(nData + nZero)); } else { nData = nZero = 0; } nHeader += ConvertEx.PutVariant9L(pCell, (uint)nHeader, (ulong)nKey); var info = new CellInfo(); btreeParseCellPtr(pCell, ref info); Debug.Assert(info.nHeader == nHeader); Debug.Assert(info.nKey == nKey); Debug.Assert(info.nData == (uint)(nData + nZero)); // Fill in the payload var nPayload = nData + nZero; byte[] pSrc; int nSrc; if (this.HasIntKey) { pSrc = pData; nSrc = nData; nData = 0; } else { if (Check.NEVER(nKey > 0x7fffffff || pKey == null)) { return(SysEx.SQLITE_CORRUPT_BKPT()); } nPayload += (int)nKey; pSrc = pKey; nSrc = (int)nKey; } pnSize = info.nSize; var spaceLeft = (int)info.nLocal; var pPayload = pCell; var pPayloadIndex = nHeader; var pPrior = pCell; var pPriorIndex = (int)info.iOverflow; var pBt = this.Shared; Pgno pgnoOvfl = 0; MemPage pToRelease = null; while (nPayload > 0) { if (spaceLeft == 0) { #if !SQLITE_OMIT_AUTOVACUUM var pgnoPtrmap = pgnoOvfl; // Overflow page pointer-map entry page if (pBt.AutoVacuum) { do { pgnoOvfl++; }while (PTRMAP_ISPAGE(pBt, pgnoOvfl) || pgnoOvfl == PENDING_BYTE_PAGE(pBt)); } #endif MemPage pOvfl = null; var rc = pBt.allocateBtreePage(ref pOvfl, ref pgnoOvfl, pgnoOvfl, 0); #if !SQLITE_OMIT_AUTOVACUUM // If the database supports auto-vacuum, and the second or subsequent overflow page is being allocated, add an entry to the pointer-map for that page now. // If this is the first overflow page, then write a partial entry to the pointer-map. If we write nothing to this pointer-map slot, // then the optimistic overflow chain processing in clearCell() may misinterpret the uninitialised values and delete the // wrong pages from the database. if (pBt.AutoVacuum && rc == RC.OK) { var eType = (pgnoPtrmap != 0 ? PTRMAP.OVERFLOW2 : PTRMAP.OVERFLOW1); pBt.ptrmapPut(pgnoOvfl, eType, pgnoPtrmap, ref rc); if (rc != RC.OK) { pOvfl.releasePage(); } } #endif if (rc != RC.OK) { pToRelease.releasePage(); return(rc); } // If pToRelease is not zero than pPrior points into the data area of pToRelease. Make sure pToRelease is still writeable. Debug.Assert(pToRelease == null || Pager.IsPageWriteable(pToRelease.DbPage)); // If pPrior is part of the data area of pPage, then make sure pPage is still writeable // TODO -- Determine if the following Assert is needed under c# //Debug.Assert( pPrior < pPage.aData || pPrior >= &pPage.aData[pBt.pageSize] || sqlite3PagerIswriteable(pPage.pDbPage) ); ConvertEx.Put4L(pPrior, (uint)pPriorIndex, pgnoOvfl); pToRelease.releasePage(); pToRelease = pOvfl; pPrior = pOvfl.Data; pPriorIndex = 0; ConvertEx.Put4(pPrior, 0); pPayload = pOvfl.Data; pPayloadIndex = 4; spaceLeft = (int)pBt.UsableSize - 4; } var n = nPayload; if (n > spaceLeft) { n = spaceLeft; } // If pToRelease is not zero than pPayload points into the data area of pToRelease. Make sure pToRelease is still writeable. Debug.Assert(pToRelease == null || Pager.IsPageWriteable(pToRelease.DbPage)); // If pPayload is part of the data area of pPage, then make sure pPage is still writeable // TODO -- Determine if the following Assert is needed under c# //Debug.Assert( pPayload < pPage.aData || pPayload >= &pPage.aData[pBt.pageSize] || sqlite3PagerIswriteable(pPage.pDbPage) ); var pSrcIndex = 0; if (nSrc > 0) { if (n > nSrc) { n = nSrc; } Debug.Assert(pSrc != null); Buffer.BlockCopy(pSrc, pSrcIndex, pPayload, pPayloadIndex, n); } else { var pZeroBlob = MallocEx.sqlite3Malloc(n); Buffer.BlockCopy(pZeroBlob, 0, pPayload, pPayloadIndex, n); } nPayload -= n; pPayloadIndex += n; pSrcIndex += n; nSrc -= n; spaceLeft -= n; if (nSrc == 0) { nSrc = nData; pSrc = pData; } } pToRelease.releasePage(); return(RC.OK); }
internal RC freePage2(MemPage pMemPage, Pgno iPage) { MemPage pTrunk = null; // Free-list trunk page var pPage1 = this.Page1; // Local reference to page 1 Debug.Assert(MutexEx.Held(this.Mutex)); Debug.Assert(iPage > 1); Debug.Assert(pMemPage == null || pMemPage.ID == iPage); MemPage pPage; // Page being freed. May be NULL. if (pMemPage != null) { pPage = pMemPage; Pager.AddPageRef(pPage.DbPage); } else { pPage = btreePageLookup(iPage); } // Increment the free page count on pPage1 var rc = Pager.Write(pPage1.DbPage); if (rc != RC.OK) { goto freepage_out; } var nFree = (int)ConvertEx.Get4(pPage1.Data, 36); // Initial number of pages on free-list ConvertEx.Put4(pPage1.Data, 36, nFree + 1); if (this.SecureDelete) { // If the secure_delete option is enabled, then always fully overwrite deleted information with zeros. if ((pPage == null && ((rc = btreeGetPage(iPage, ref pPage, 0)) != RC.OK)) || ((rc = Pager.Write(pPage.DbPage)) != RC.OK)) { goto freepage_out; } Array.Clear(pPage.Data, 0, (int)pPage.Shared.PageSize); } // If the database supports auto-vacuum, write an entry in the pointer-map to indicate that the page is free. #if !SQLITE_OMIT_AUTOVACUUM if (this.AutoVacuum) #else if (false) #endif { ptrmapPut(iPage, PTRMAP.FREEPAGE, 0, ref rc); if (rc != RC.OK) { goto freepage_out; } } // Now manipulate the actual database free-list structure. There are two possibilities. If the free-list is currently empty, or if the first // trunk page in the free-list is full, then this page will become a new free-list trunk page. Otherwise, it will become a leaf of the // first trunk page in the current free-list. This block tests if it is possible to add the page as a new free-list leaf. Pgno iTrunk = 0; // Page number of free-list trunk page if (nFree != 0) { uint nLeaf; // Initial number of leaf cells on trunk page iTrunk = (Pgno)ConvertEx.Get4(pPage1.Data, 32); // Page number of free-list trunk page rc = btreeGetPage(iTrunk, ref pTrunk, 0); if (rc != RC.OK) { goto freepage_out; } nLeaf = ConvertEx.Get4(pTrunk.Data, 4); Debug.Assert(this.UsableSize > 32); if (nLeaf > (uint)this.UsableSize / 4 - 2) { rc = SysEx.SQLITE_CORRUPT_BKPT(); goto freepage_out; } if (nLeaf < (uint)this.UsableSize / 4 - 8) { // In this case there is room on the trunk page to insert the page being freed as a new leaf. // Note: that the trunk page is not really full until it contains usableSize/4 - 2 entries, not usableSize/4 - 8 entries as we have // coded. But due to a coding error in versions of SQLite prior to 3.6.0, databases with freelist trunk pages holding more than // usableSize/4 - 8 entries will be reported as corrupt. In order to maintain backwards compatibility with older versions of SQLite, // we will continue to restrict the number of entries to usableSize/4 - 8 for now. At some point in the future (once everyone has upgraded // to 3.6.0 or later) we should consider fixing the conditional above to read "usableSize/4-2" instead of "usableSize/4-8". rc = Pager.Write(pTrunk.DbPage); if (rc == RC.OK) { ConvertEx.Put4(pTrunk.Data, 4, nLeaf + 1); ConvertEx.Put4(pTrunk.Data, (uint)(8 + nLeaf * 4), iPage); if (pPage != null && !this.SecureDelete) { Pager.DontWrite(pPage.DbPage); } rc = btreeSetHasContent(iPage); } Btree.TRACE("FREE-PAGE: %d leaf on trunk page %d\n", iPage, pTrunk.ID); goto freepage_out; } } // If control flows to this point, then it was not possible to add the the page being freed as a leaf page of the first trunk in the free-list. // Possibly because the free-list is empty, or possibly because the first trunk in the free-list is full. Either way, the page being freed // will become the new first trunk page in the free-list. if (pPage == null && (rc = btreeGetPage(iPage, ref pPage, 0)) != RC.OK) { goto freepage_out; } rc = Pager.Write(pPage.DbPage); if (rc != RC.OK) { goto freepage_out; } ConvertEx.Put4L(pPage.Data, iTrunk); ConvertEx.Put4(pPage.Data, 4, 0); ConvertEx.Put4(pPage1.Data, 32, iPage); Btree.TRACE("FREE-PAGE: %d new trunk page replacing %d\n", pPage.ID, iTrunk); freepage_out: if (pPage != null) { pPage.HasInit = false; } pPage.releasePage(); pTrunk.releasePage(); return(rc); }
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); }
private RC syncJournal(int newHdr) { var rc = RC.OK; Debug.Assert(this.eState == PAGER.WRITER_CACHEMOD || this.eState == PAGER.WRITER_DBMOD); Debug.Assert(assert_pager_state()); Debug.Assert(!pagerUseWal()); rc = sqlite3PagerExclusiveLock(); if (rc != RC.OK) { return(rc); } if (!this.noSync) { Debug.Assert(!this.tempFile); if (this.jfd.IsOpen && this.journalMode != JOURNALMODE.MEMORY) { var iDc = this.fd.DeviceCharacteristics; Debug.Assert(this.jfd.IsOpen); if (0 == (iDc & VirtualFile.IOCAP.SAFE_APPEND)) { // This block deals with an obscure problem. If the last connection that wrote to this database was operating in persistent-journal // mode, then the journal file may at this point actually be larger than Pager.journalOff bytes. If the next thing in the journal // file happens to be a journal-header (written as part of the previous connection's transaction), and a crash or power-failure // occurs after nRec is updated but before this connection writes anything else to the journal file (or commits/rolls back its // transaction), then SQLite may become confused when doing the hot-journal rollback following recovery. It may roll back all // of this connections data, then proceed to rolling back the old, out-of-date data that follows it. Database corruption. // To work around this, if the journal file does appear to contain a valid header following Pager.journalOff, then write a 0x00 // byte to the start of it to prevent it from being recognized. // Variable iNextHdrOffset is set to the offset at which this problematic header will occur, if it exists. aMagic is used // as a temporary buffer to inspect the first couple of bytes of the potential journal header. var zHeader = new byte[aJournalMagic.Length + 4]; aJournalMagic.CopyTo(zHeader, 0); ConvertEx.Put4(zHeader, aJournalMagic.Length, this.nRec); var iNextHdrOffset = journalHdrOffset(); var aMagic = new byte[8]; rc = this.jfd.Read(aMagic, 8, iNextHdrOffset); if (rc == RC.OK && 0 == ArrayEx.Compare(aMagic, aJournalMagic, 8)) { var zerobyte = new byte[1]; rc = this.jfd.Write(zerobyte, 1, iNextHdrOffset); } if (rc != RC.OK && rc != RC.IOERR_SHORT_READ) { return(rc); } // Write the nRec value into the journal file header. If in full-synchronous mode, sync the journal first. This ensures that // all data has really hit the disk before nRec is updated to mark it as a candidate for rollback. // This is not required if the persistent media supports the SAFE_APPEND property. Because in this case it is not possible // for garbage data to be appended to the file, the nRec field is populated with 0xFFFFFFFF when the journal header is written // and never needs to be updated. if (this.fullSync && 0 == (iDc & VirtualFile.IOCAP.SEQUENTIAL)) { PAGERTRACE("SYNC journal of {0}", PAGERID(this)); SysEx.IOTRACE("JSYNC {0:x}", this.GetHashCode()); rc = this.jfd.Sync(this.syncFlags); if (rc != RC.OK) { return(rc); } } SysEx.IOTRACE("JHDR {0:x} {1,11}", this.GetHashCode(), this.journalHdr); rc = this.jfd.Write(zHeader, zHeader.Length, this.journalHdr); if (rc != RC.OK) { return(rc); } } if (0 == (iDc & VirtualFile.IOCAP.SEQUENTIAL)) { PAGERTRACE("SYNC journal of {0}", PAGERID(this)); SysEx.IOTRACE("JSYNC {0:x}", this.GetHashCode()); rc = this.jfd.Sync(this.syncFlags | (this.syncFlags == VirtualFile.SYNC.FULL ? VirtualFile.SYNC.DATAONLY : 0)); if (rc != RC.OK) { return(rc); } } this.journalHdr = this.journalOff; if (newHdr != 0 && 0 == (iDc & VirtualFile.IOCAP.SAFE_APPEND)) { this.nRec = 0; rc = writeJournalHdr(); if (rc != RC.OK) { return(rc); } } } else { this.journalHdr = this.journalOff; } } // Unless the pager is in noSync mode, the journal file was just successfully synced. Either way, clear the PGHDR_NEED_SYNC flag on all pages. this.pPCache.ClearSyncFlags(); this.eState = PAGER.WRITER_DBMOD; Debug.Assert(assert_pager_state()); return(RC.OK); }
private RC writeJournalHdr() { Debug.Assert(this.jfd.IsOpen); // Journal file must be open. var nHeader = (uint)this.pageSize; // Size of buffer pointed to by zHeader if (nHeader > JOURNAL_HDR_SZ(this)) { nHeader = JOURNAL_HDR_SZ(this); } // If there are active savepoints and any of them were created since the most recent journal header was written, update the // PagerSavepoint.iHdrOffset fields now. for (var ii = 0; ii < this.nSavepoint; ii++) { if (this.aSavepoint[ii].iHdrOffset == 0) { this.aSavepoint[ii].iHdrOffset = this.journalOff; } } this.journalHdr = this.journalOff = journalHdrOffset(); // Write the nRec Field - the number of page records that follow this journal header. Normally, zero is written to this value at this time. // After the records are added to the journal (and the journal synced, if in full-sync mode), the zero is overwritten with the true number // of records (see syncJournal()). // A faster alternative is to write 0xFFFFFFFF to the nRec field. When reading the journal this value tells SQLite to assume that the // rest of the journal file contains valid page records. This assumption is dangerous, as if a failure occurred whilst writing to the journal // file it may contain some garbage data. There are two scenarios where this risk can be ignored: // * When the pager is in no-sync mode. Corruption can follow a power failure in this case anyway. // * When the SQLITE_IOCAP_SAFE_APPEND flag is set. This guarantees that garbage data is never appended to the journal file. Debug.Assert(this.fd.IsOpen || this.noSync); var zHeader = this.pTmpSpace; // Temporary space used to build header if (this.noSync || (this.journalMode == JOURNALMODE.MEMORY) || (this.fd.DeviceCharacteristics & VirtualFile.IOCAP.SAFE_APPEND) != 0) { aJournalMagic.CopyTo(zHeader, 0); ConvertEx.Put4(zHeader, aJournalMagic.Length, 0xffffffff); } else { Array.Clear(zHeader, 0, aJournalMagic.Length + 4); } // The random check-hash initialiser long i64Temp = 0; UtilEx.MakeRandomness(sizeof(long), ref i64Temp); this.cksumInit = (Pgno)i64Temp; ConvertEx.Put4(zHeader, aJournalMagic.Length + 4, this.cksumInit); // The initial database size ConvertEx.Put4(zHeader, aJournalMagic.Length + 8, this.dbOrigSize); // The assumed sector size for this process ConvertEx.Put4(zHeader, aJournalMagic.Length + 12, this.sectorSize); // The page size ConvertEx.Put4(zHeader, aJournalMagic.Length + 16, (uint)this.pageSize); // Initializing the tail of the buffer is not necessary. Everything works find if the following memset() is omitted. But initializing // the memory prevents valgrind from complaining, so we are willing to take the performance hit. Array.Clear(zHeader, aJournalMagic.Length + 20, (int)nHeader - aJournalMagic.Length + 20); // In theory, it is only necessary to write the 28 bytes that the journal header consumes to the journal file here. Then increment the // Pager.journalOff variable by JOURNAL_HDR_SZ so that the next record is written to the following sector (leaving a gap in the file // that will be implicitly filled in by the OS). // However it has been discovered that on some systems this pattern can be significantly slower than contiguously writing data to the file, // even if that means explicitly writing data to the block of (JOURNAL_HDR_SZ - 28) bytes that will not be used. So that is what // is done. // The loop is required here in case the sector-size is larger than the database page size. Since the zHeader buffer is only Pager.pageSize // bytes in size, more than one call to sqlite3OsWrite() may be required to populate the entire journal header sector. RC rc = RC.OK; // Return code for (uint nWrite = 0; rc == RC.OK && nWrite < JOURNAL_HDR_SZ(this); nWrite += nHeader) { SysEx.IOTRACE("JHDR {0:x} {1,11} {2}", this.GetHashCode(), this.journalHdr, nHeader); rc = this.jfd.Write(zHeader, (int)nHeader, this.journalOff); Debug.Assert(this.journalHdr <= this.journalOff); this.journalOff += (int)nHeader; } return(rc); }