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);
        }
Exemple #2
0
        // 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);
 }
Exemple #5
0
        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);
        }
Exemple #7
0
        // 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;
        }
Exemple #8
0
        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);
        }
Exemple #9
0
        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);
        }
Exemple #10
0
        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);
        }
Exemple #11
0
        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);
        }
Exemple #12
0
        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);
        }