// 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); } }
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); }
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); }
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); }
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); }
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); }
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); }
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); }