Ejemplo n.º 1
0
        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);
        }
Ejemplo n.º 2
0
        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);
        }
Ejemplo n.º 3
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);
        }
Ejemplo n.º 4
0
        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);
        }