private static RC readMasterJournal(VirtualFile pJrnl, byte[] zMaster, uint nMaster) { int len = 0; // Length in bytes of master journal name long szJ = 0; // Total size in bytes of journal file pJrnl uint cksum = 0; // MJ checksum value read from journal zMaster[0] = 0; var aMagic = new byte[8]; // A buffer to hold the magic header RC rc; if (RC.OK != (rc = pJrnl.FileSize(ref szJ)) || szJ < 16 || RC.OK != (rc = pJrnl.ReadByte((int)(szJ - 16), ref len)) || len >= nMaster || RC.OK != (rc = pJrnl.ReadByte(szJ - 12, ref cksum)) || RC.OK != (rc = pJrnl.Read(aMagic, 8, szJ - 8)) || ArrayEx.Compare(aMagic, aJournalMagic, 8) != 0 || RC.OK != (rc = pJrnl.Read(zMaster, len, (long)(szJ - 16 - len)))) { return(rc); } // See if the checksum matches the master journal name for (var u = 0; u < len; u++) { cksum -= zMaster[u]; } if (cksum != 0) { // If the checksum doesn't add up, then one or more of the disk sectors containing the master journal filename is corrupted. This means // definitely roll back, so just return SQLITE.OK and report a (nul) master-journal filename. len = 0; } if (len == 0) { zMaster[0] = 0; } return(RC.OK); }
internal RC lockBtree() { Debug.Assert(MutexEx.Held(this.Mutex)); Debug.Assert(this.Page1 == null); var rc = this.Pager.SharedLock(); if (rc != RC.OK) { return(rc); } MemPage pPage1 = null; // Page 1 of the database file rc = btreeGetPage(1, ref pPage1, 0); if (rc != RC.OK) { return(rc); } // Do some checking to help insure the file we opened really is a valid database file. Pgno nPageHeader; // Number of pages in the database according to hdr var nPage = nPageHeader = ConvertEx.Get4(pPage1.Data, 28); // Number of pages in the database Pgno nPageFile; // Number of pages in the database file this.Pager.GetPageCount(out nPageFile); if (nPage == 0 || ArrayEx.Compare(pPage1.Data, 24, pPage1.Data, 92, 4) != 0) { nPage = nPageFile; } if (nPage > 0) { var page1 = pPage1.Data; rc = RC.NOTADB; if (ArrayEx.Compare(page1, Btree.zMagicHeader, 16) != 0) { goto page1_init_failed; } #if SQLITE_OMIT_WAL if (page1[18] > 1) { this.ReadOnly = true; } if (page1[19] > 1) { this.Schema.file_format = page1[19]; goto page1_init_failed; } #else if (page1[18] > 2) { pBt.readOnly = true; } if (page1[19] > 2) { goto page1_init_failed; } /* If the write version is set to 2, this database should be accessed ** in WAL mode. If the log is not already open, open it now. Then ** return SQLITE_OK and return without populating BtShared.pPage1. ** The caller detects this and calls this function again. This is ** required as the version of page 1 currently in the page1 buffer ** may not be the latest version - there may be a newer one in the log ** file. */ if (page1[19] == 2 && pBt.doNotUseWAL == false) { int isOpen = 0; rc = sqlite3PagerOpenWal(pBt.pPager, ref isOpen); if (rc != SQLITE_OK) { goto page1_init_failed; } else if (isOpen == 0) { releasePage(pPage1); return(SQLITE_OK); } rc = SQLITE_NOTADB; } #endif // The maximum embedded fraction must be exactly 25%. And the minimum embedded fraction must be 12.5% for both leaf-data and non-leaf-data. // The original design allowed these amounts to vary, but as of version 3.6.0, we require them to be fixed. if (ArrayEx.Compare(page1, 21, "\x0040\x0020\x0020", 3) != 0) // "\100\040\040" { goto page1_init_failed; } var pageSize = (uint)((page1[16] << 8) | (page1[17] << 16)); if (((pageSize - 1) & pageSize) != 0 || pageSize > Pager.SQLITE_MAX_PAGE_SIZE || pageSize <= 256) { goto page1_init_failed; } Debug.Assert((pageSize & 7) == 0); var usableSize = pageSize - page1[20]; if (pageSize != this.PageSize) { // After reading the first page of the database assuming a page size of BtShared.pageSize, we have discovered that the page-size is // actually pageSize. Unlock the database, leave pBt.pPage1 at zero and return SQLITE_OK. The caller will call this function // again with the correct page-size. pPage1.releasePage(); this.UsableSize = usableSize; this.PageSize = pageSize; rc = this.Pager.SetPageSize(ref this.PageSize, (int)(pageSize - usableSize)); return(rc); } if ((this.DB.flags & sqlite3b.SQLITE.RecoveryMode) == 0 && nPage > nPageFile) { rc = SysEx.SQLITE_CORRUPT_BKPT(); goto page1_init_failed; } if (usableSize < 480) { goto page1_init_failed; } this.PageSize = pageSize; this.UsableSize = usableSize; #if !SQLITE_OMIT_AUTOVACUUM this.AutoVacuum = (ConvertEx.Get4(page1, 36 + 4 * 4) != 0); this.IncrVacuum = (ConvertEx.Get4(page1, 36 + 7 * 4) != 0); #endif } // maxLocal is the maximum amount of payload to store locally for a cell. Make sure it is small enough so that at least minFanout // cells can will fit on one page. We assume a 10-byte page header. Besides the payload, the cell must store: // 2-byte pointer to the cell // 4-byte child pointer // 9-byte nKey value // 4-byte nData value // 4-byte overflow page pointer // So a cell consists of a 2-byte pointer, a header which is as much as 17 bytes long, 0 to N bytes of payload, and an optional 4 byte overflow page pointer. this.MaxLocal = (ushort)((this.UsableSize - 12) * 64 / 255 - 23); this.MinLocal = (ushort)((this.UsableSize - 12) * 32 / 255 - 23); this.MaxLeaf = (ushort)(this.UsableSize - 35); this.MinLeaf = (ushort)((this.UsableSize - 12) * 32 / 255 - 23); Debug.Assert(this.MaxLeaf + 23 <= Btree.MX_CELL_SIZE(this)); this.Page1 = pPage1; this.Pages = nPage; return(RC.OK); page1_init_failed: pPage1.releasePage(); this.Page1 = null; 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 readJournalHdr(int isHot, long journalSize, out uint pNRec, out Pgno pDbSize) { var aMagic = new byte[8]; // A buffer to hold the magic header Debug.Assert(this.jfd.IsOpen); // Journal file must be open. pNRec = 0; pDbSize = 0; // Advance Pager.journalOff to the start of the next sector. If the journal file is too small for there to be a header stored at this // point, return SQLITE_DONE. this.journalOff = journalHdrOffset(); if (this.journalOff + JOURNAL_HDR_SZ(this) > journalSize) { return(RC.DONE); } var iHdrOff = this.journalOff; // Offset of journal header being read // Read in the first 8 bytes of the journal header. If they do not match the magic string found at the start of each journal header, return // SQLITE_DONE. If an IO error occurs, return an error code. Otherwise, proceed. RC rc; if (isHot != 0 || iHdrOff != this.journalHdr) { rc = this.jfd.Read(aMagic, aMagic.Length, iHdrOff); if (rc != RC.OK) { return(rc); } if (ArrayEx.Compare(aMagic, aJournalMagic, aMagic.Length) != 0) { return(RC.DONE); } } // Read the first three 32-bit fields of the journal header: The nRec field, the checksum-initializer and the database size at the start // of the transaction. Return an error code if anything goes wrong. if (RC.OK != (rc = this.jfd.ReadByte(iHdrOff + 8, ref pNRec)) || RC.OK != (rc = this.jfd.ReadByte(iHdrOff + 12, ref this.cksumInit)) || RC.OK != (rc = this.jfd.ReadByte(iHdrOff + 16, ref pDbSize))) { return(rc); } if (this.journalOff == 0) { // Read the page-size and sector-size journal header fields. uint iPageSize = 0; // Page-size field of journal header uint iSectorSize = 0; // Sector-size field of journal header if (RC.OK != (rc = this.jfd.ReadByte(iHdrOff + 20, ref iSectorSize)) || RC.OK != (rc = this.jfd.ReadByte(iHdrOff + 24, ref iPageSize))) { return(rc); } // Versions of SQLite prior to 3.5.8 set the page-size field of the journal header to zero. In this case, assume that the Pager.pageSize // variable is already set to the correct page size. if (iPageSize == 0) { iPageSize = (uint)this.pageSize; } // Check that the values read from the page-size and sector-size fields are within range. To be 'in range', both values need to be a power // of two greater than or equal to 512 or 32, and not greater than their respective compile time maximum limits. if (iPageSize < 512 || iSectorSize < 32 || iPageSize > SQLITE_MAX_PAGE_SIZE || iSectorSize > MAX_SECTOR_SIZE || ((iPageSize - 1) & iPageSize) != 0 || ((iSectorSize - 1) & iSectorSize) != 0) { // If the either the page-size or sector-size in the journal-header is invalid, then the process that wrote the journal-header must have // crashed before the header was synced. In this case stop reading the journal file here. return(RC.DONE); } // Update the page-size to match the value read from the journal. Use a testcase() macro to make sure that malloc failure within // PagerSetPagesize() is tested. rc = SetPageSize(ref iPageSize, -1); // Update the assumed sector-size to match the value used by the process that created this journal. If this journal was // created by a process other than this one, then this routine is being called from within pager_playback(). The local value // of Pager.sectorSize is restored at the end of that routine. this.sectorSize = iSectorSize; } this.journalOff += (int)JOURNAL_HDR_SZ(this); return(rc); }