Exemple #1
0
        // was:sqlite3PagerDontWrite
        public static void DontWrite(PgHdr pPg)
        {
            var pPager = pPg.Pager;

            if ((pPg.Flags & PgHdr.PGHDR.DIRTY) != 0 && pPager.nSavepoint == 0)
            {
                PAGERTRACE("DONT_WRITE page {0} of {1}", pPg.ID, PAGERID(pPager));
                SysEx.IOTRACE("CLEAN {0:x} {1}", pPager.GetHashCode(), pPg.ID);
                pPg.Flags |= PgHdr.PGHDR.DONT_WRITE;
                pager_set_pagehash(pPg);
            }
        }
Exemple #2
0
        private RC pagerLockDb(VFSLOCK eLock)
        {
            var rc = RC.OK;

            Debug.Assert(eLock == VFSLOCK.SHARED || eLock == VFSLOCK.RESERVED || eLock == VFSLOCK.EXCLUSIVE);
            if (this.eLock < eLock || this.eLock == VFSLOCK.UNKNOWN)
            {
                rc = this.fd.Lock(eLock);
                if (rc == RC.OK && (this.eLock != VFSLOCK.UNKNOWN || eLock == VFSLOCK.EXCLUSIVE))
                {
                    this.eLock = eLock;
                    SysEx.IOTRACE("LOCK {0:x} {1}", this.GetHashCode(), eLock);
                }
            }
            return(rc);
        }
Exemple #3
0
        private RC pagerUnlockDb(VFSLOCK eLock)
        {
            var rc = RC.OK;

            Debug.Assert(!this.exclusiveMode || this.eLock == eLock);
            Debug.Assert(eLock == VFSLOCK.NO || eLock == VFSLOCK.SHARED);
            Debug.Assert(eLock != VFSLOCK.NO || !this.pagerUseWal());
            if (this.fd.IsOpen)
            {
                Debug.Assert(this.eLock >= eLock);
                rc = this.fd.Unlock(eLock);
                if (this.eLock != VFSLOCK.UNKNOWN)
                {
                    this.eLock = eLock;
                }
                SysEx.IOTRACE("UNLOCK {0:x} {1}", this.GetHashCode(), eLock);
            }
            return(rc);
        }
Exemple #4
0
        private RC zeroJournalHdr(int doTruncate)
        {
            var rc = RC.OK;

            Debug.Assert(this.jfd.IsOpen);
            if (this.journalOff != 0)
            {
                var iLimit = this.journalSizeLimit; // Local cache of jsl
                SysEx.IOTRACE("JZEROHDR {0:x}", this.GetHashCode());
                if (doTruncate != 0 || iLimit == 0)
                {
                    rc = this.jfd.Truncate(0);
                }
                else
                {
                    var zeroHdr = new byte[28];
                    rc = this.jfd.Write(zeroHdr, zeroHdr.Length, 0);
                }
                if (rc == RC.OK && !this.noSync)
                {
                    rc = this.jfd.Sync(VirtualFile.SYNC.DATAONLY | this.syncFlags);
                }
                // At this point the transaction is committed but the write lock is still held on the file. If there is a size limit configured for
                // the persistent journal and the journal file currently consumes more space than that limit allows for, truncate it now. There is no need
                // to sync the file following this operation.
                if (rc == RC.OK && iLimit > 0)
                {
                    long sz = 0;
                    rc = this.jfd.FileSize(ref sz);
                    if (rc == RC.OK && sz > iLimit)
                    {
                        rc = this.jfd.Truncate(iLimit);
                    }
                }
            }
            return(rc);
        }
Exemple #5
0
        private static RC pager_write(PgHdr pPg)
        {
            var pData  = pPg.Data;
            var pPager = pPg.Pager;
            var rc     = RC.OK;

            // This routine is not called unless a write-transaction has already been started. The journal file may or may not be open at this point.
            // It is never called in the ERROR state.
            Debug.Assert(pPager.eState == PAGER.WRITER_LOCKED || pPager.eState == PAGER.WRITER_CACHEMOD || pPager.eState == PAGER.WRITER_DBMOD);
            Debug.Assert(pPager.assert_pager_state());
            // If an error has been previously detected, report the same error again. This should not happen, but the check provides robustness.
            if (Check.NEVER(pPager.errCode) != RC.OK)
            {
                return(pPager.errCode);
            }
            // Higher-level routines never call this function if database is not writable.  But check anyway, just for robustness.
            if (Check.NEVER(pPager.readOnly))
            {
                return(RC.PERM);
            }
#if SQLITE_CHECK_PAGES
            CHECK_PAGE(pPg);
#endif
            // The journal file needs to be opened. Higher level routines have already obtained the necessary locks to begin the write-transaction, but the
            // rollback journal might not yet be open. Open it now if this is the case.
            //
            // This is done before calling sqlite3PcacheMakeDirty() on the page. Otherwise, if it were done after calling sqlite3PcacheMakeDirty(), then
            // an error might occur and the pager would end up in WRITER_LOCKED state with pages marked as dirty in the cache.
            if (pPager.eState == PAGER.WRITER_LOCKED)
            {
                rc = pPager.pager_open_journal();
                if (rc != RC.OK)
                {
                    return(rc);
                }
            }
            Debug.Assert(pPager.eState >= PAGER.WRITER_CACHEMOD);
            Debug.Assert(pPager.assert_pager_state());
            // Mark the page as dirty.  If the page has already been written to the journal then we can return right away.
            PCache.MakePageDirty(pPg);
            if (pageInJournal(pPg) && !subjRequiresPage(pPg))
            {
                Debug.Assert(!pPager.pagerUseWal());
            }
            else
            {
                // The transaction journal now exists and we have a RESERVED or an EXCLUSIVE lock on the main database file.  Write the current page to
                // the transaction journal if it is not there already.
                if (!pageInJournal(pPg) && !pPager.pagerUseWal())
                {
                    Debug.Assert(!pPager.pagerUseWal());
                    if (pPg.ID <= pPager.dbOrigSize && pPager.jfd.IsOpen)
                    {
                        var iOff = pPager.journalOff;
                        // We should never write to the journal file the page that contains the database locks.  The following Debug.Assert verifies that we do not.
                        Debug.Assert(pPg.ID != ((VirtualFile.PENDING_BYTE / (pPager.pageSize)) + 1));
                        Debug.Assert(pPager.journalHdr <= pPager.journalOff);
                        byte[] pData2 = null;
                        if (CODEC2(pPager, pData, pPg.ID, codec_ctx.ENCRYPT_READ_CTX, ref pData2))
                        {
                            return(RC.NOMEM);
                        }
                        var cksum = pPager.pager_cksum(pData2);
                        // Even if an IO or diskfull error occurred while journalling the page in the block above, set the need-sync flag for the page.
                        // Otherwise, when the transaction is rolled back, the logic in playback_one_page() will think that the page needs to be restored
                        // in the database file. And if an IO error occurs while doing so, then corruption may follow.
                        pPg.Flags |= PgHdr.PGHDR.NEED_SYNC;
                        rc         = pPager.jfd.WriteByte(iOff, pPg.ID);
                        if (rc != RC.OK)
                        {
                            return(rc);
                        }
                        rc = pPager.jfd.Write(pData2, pPager.pageSize, iOff + 4);
                        if (rc != RC.OK)
                        {
                            return(rc);
                        }
                        rc = pPager.jfd.WriteByte(iOff + pPager.pageSize + 4, cksum);
                        if (rc != RC.OK)
                        {
                            return(rc);
                        }
                        SysEx.IOTRACE("JOUT {0:x} {1} {2,11} {3}", pPager.GetHashCode(), pPg.ID, pPager.journalOff, pPager.pageSize);
                        PAGERTRACE("JOURNAL {0} page {1} needSync={2} hash({3,08:x})", PAGERID(pPager), pPg.ID, (pPg.Flags & PgHdr.PGHDR.NEED_SYNC) != 0 ? 1 : 0, pager_pagehash(pPg));
                        pPager.journalOff += 8 + pPager.pageSize;
                        pPager.nRec++;
                        Debug.Assert(pPager.pInJournal != null);
                        rc = pPager.pInJournal.Set(pPg.ID);
                        Debug.Assert(rc == RC.OK || rc == RC.NOMEM);
                        rc |= pPager.addToSavepointBitvecs(pPg.ID);
                        if (rc != RC.OK)
                        {
                            Debug.Assert(rc == RC.NOMEM);
                            return(rc);
                        }
                    }
                    else
                    {
                        if (pPager.eState != PAGER.WRITER_DBMOD)
                        {
                            pPg.Flags |= PgHdr.PGHDR.NEED_SYNC;
                        }
                        PAGERTRACE("APPEND {0} page {1} needSync={2}", PAGERID(pPager), pPg.ID, (pPg.Flags & PgHdr.PGHDR.NEED_SYNC) != 0 ? 1 : 0);
                    }
                }
                // If the statement journal is open and the page is not in it, then write the current page to the statement journal.  Note that
                // the statement journal format differs from the standard journal format in that it omits the checksums and the header.
                if (subjRequiresPage(pPg))
                {
                    rc = subjournalPage(pPg);
                }
            }
            // Update the database size and return.
            if (pPager.dbSize < pPg.ID)
            {
                pPager.dbSize = pPg.ID;
            }
            return(rc);
        }
Exemple #6
0
        private RC pager_write_pagelist(PgHdr pList)
        {
            var rc = RC.OK;

            // This function is only called for rollback pagers in WRITER_DBMOD state.
            Debug.Assert(!pagerUseWal());
            Debug.Assert(this.eState == PAGER.WRITER_DBMOD);
            Debug.Assert(this.eLock == VFSLOCK.EXCLUSIVE);
            // If the file is a temp-file has not yet been opened, open it now. It is not possible for rc to be other than SQLITE.OK if this branch
            // is taken, as pager_wait_on_lock() is a no-op for temp-files.
            if (!this.fd.IsOpen)
            {
                Debug.Assert(this.tempFile && rc == RC.OK);
                rc = pagerOpentemp(ref this.fd, this.vfsFlags);
            }
            // Before the first write, give the VFS a hint of what the final file size will be.
            Debug.Assert(rc != RC.OK || this.fd.IsOpen);
            if (rc == RC.OK && this.dbSize > this.dbHintSize)
            {
                long szFile = this.pageSize * (long)this.dbSize;
                this.fd.SetFileControl(VirtualFile.FCNTL.SIZE_HINT, ref szFile);
                this.dbHintSize = this.dbSize;
            }
            while (rc == RC.OK && pList)
            {
                var pgno = pList.ID;
                // If there are dirty pages in the page cache with page numbers greater than Pager.dbSize, this means sqlite3PagerTruncateImage() was called to
                // make the file smaller (presumably by auto-vacuum code). Do not write any such pages to the file.
                // Also, do not write out any page that has the PGHDR_DONT_WRITE flag set (set by sqlite3PagerDontWrite()).
                if (pList.ID <= this.dbSize && 0 == (pList.Flags & PgHdr.PGHDR.DONT_WRITE))
                {
                    Debug.Assert((pList.Flags & PgHdr.PGHDR.NEED_SYNC) == 0);
                    if (pList.ID == 1)
                    {
                        pager_write_changecounter(pList);
                    }
                    // Encode the database
                    byte[] pData = null; // Data to write
                    if (CODEC2(this, pList.Data, pgno, codec_ctx.ENCRYPT_WRITE_CTX, ref pData))
                    {
                        return(RC.NOMEM);
                    }
                    // Write out the page data.
                    long offset = (pList.ID - 1) * (long)this.pageSize;   // Offset to write
                    rc = this.fd.Write(pData, this.pageSize, offset);
                    // If page 1 was just written, update Pager.dbFileVers to match the value now stored in the database file. If writing this
                    // page caused the database file to grow, update dbFileSize.
                    if (pgno == 1)
                    {
                        Buffer.BlockCopy(pData, 24, this.dbFileVers, 0, this.dbFileVers.Length);
                    }
                    if (pgno > this.dbFileSize)
                    {
                        this.dbFileSize = pgno;
                    }
                    // Update any backup objects copying the contents of this pager.
                    if (this.pBackup != null)
                    {
                        this.pBackup.sqlite3BackupUpdate(pgno, pList.Data);
                    }
                    PAGERTRACE("STORE {0} page {1} hash({2,08:x})", PAGERID(this), pgno, pager_pagehash(pList));
                    SysEx.IOTRACE("PGOUT {0:x} {1}", this.GetHashCode(), pgno);
                }
                else
                {
                    PAGERTRACE("NOSTORE {0} page {1}", PAGERID(this), pgno);
                }
                pager_set_pagehash(pList);
                pList = pList.Dirtys;
            }
            return(rc);
        }
Exemple #7
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 #8
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);
        }
Exemple #9
0
        public RC Get(Pgno pgno, ref DbPage ppPage, byte noContent)
        {
            Debug.Assert(eState >= PAGER.READER);
            Debug.Assert(assert_pager_state());
            if (pgno == 0)
            {
                return(SysEx.SQLITE_CORRUPT_BKPT());
            }
            // If the pager is in the error state, return an error immediately.  Otherwise, request the page from the PCache layer.
            var   rc  = (errCode != RC.OK ? errCode : pPCache.FetchPage(pgno, 1, ref ppPage));
            PgHdr pPg = null;

            if (rc != RC.OK)
            {
                // Either the call to sqlite3PcacheFetch() returned an error or the pager was already in the error-state when this function was called.
                // Set pPg to 0 and jump to the exception handler.  */
                pPg = null;
                goto pager_get_err;
            }
            Debug.Assert((ppPage).ID == pgno);
            Debug.Assert((ppPage).Pager == this || (ppPage).Pager == null);
            if ((ppPage).Pager != null && 0 == noContent)
            {
                // In this case the pcache already contains an initialized copy of the page. Return without further ado.
                Debug.Assert(pgno <= PAGER_MAX_PGNO && pgno != PAGER_MJ_PGNO(this));
                return(RC.OK);
            }
            else
            {
                // The pager cache has created a new page. Its content needs to be initialized.
                pPg       = ppPage;
                pPg.Pager = this;
                pPg.Extra = _memPageBuilder;
                // The maximum page number is 2^31. Return SQLITE_CORRUPT if a page number greater than this, or the unused locking-page, is requested.
                if (pgno > PAGER_MAX_PGNO || pgno == PAGER_MJ_PGNO(this))
                {
                    rc = SysEx.SQLITE_CORRUPT_BKPT();
                    goto pager_get_err;
                }
                if (
#if SQLITE_OMIT_MEMORYDB
                    1 == MEMDB
#else
                    memDb != 0
#endif
                    || dbSize < pgno || noContent != 0 || !fd.IsOpen)
                {
                    if (pgno > mxPgno)
                    {
                        rc = RC.FULL;
                        goto pager_get_err;
                    }
                    if (noContent != 0)
                    {
                        // Failure to set the bits in the InJournal bit-vectors is benign. It merely means that we might do some extra work to journal a
                        // page that does not need to be journaled.  Nevertheless, be sure to test the case where a malloc error occurs while trying to set
                        // a bit in a bit vector.
                        MallocEx.sqlite3BeginBenignMalloc();
                        if (pgno <= dbOrigSize)
                        {
                            pInJournal.Set(pgno);
                        }
                        addToSavepointBitvecs(pgno);
                        MallocEx.sqlite3EndBenignMalloc();
                    }
                    Array.Clear(pPg.Data, 0, pageSize);
                    SysEx.IOTRACE("ZERO {0:x} {1}\n", this.GetHashCode(), pgno);
                }