Beispiel #1
0
        internal void assemblePage(int nCell, byte[] apCell, int[] aSize)
        {
            Debug.Assert(this.NOverflows == 0);
            Debug.Assert(MutexEx.Held(this.Shared.Mutex));
            Debug.Assert(nCell >= 0 && nCell <= (int)Btree.MX_CELL(this.Shared) && (int)Btree.MX_CELL(this.Shared) <= 10921);
            Debug.Assert(Pager.IsPageWriteable(this.DbPage));
            // Check that the page has just been zeroed by zeroPage()
            Debug.Assert(this.Cells == 0);
            //
            var data    = this.Data;                   // Pointer to data for pPage
            int hdr     = this.HeaderOffset;           // Offset of header on pPage
            var nUsable = (int)this.Shared.UsableSize; // Usable size of page

            Debug.Assert(ConvertEx.Get2nz(data, hdr + 5) == nUsable);
            var pCellptr = this.CellOffset + nCell * 2; // Address of next cell pointer
            var cellbody = nUsable;                     // Address of next cell body

            for (var i = nCell - 1; i >= 0; i--)
            {
                var sz = (ushort)aSize[i];
                pCellptr -= 2;
                cellbody -= sz;
                ConvertEx.Put2(data, pCellptr, cellbody);
                Buffer.BlockCopy(apCell, 0, data, cellbody, sz);
            }
            ConvertEx.Put2(data, hdr + 3, nCell);
            ConvertEx.Put2(data, hdr + 5, cellbody);
            this.FreeBytes -= (ushort)(nCell * 2 + nUsable - cellbody);
            this.Cells      = (ushort)nCell;
        }
Beispiel #2
0
        internal void zeroPage(int flags)
        {
            var data = this.Data;
            var pBt  = this.Shared;
            var hdr  = this.HeaderOffset;

            Debug.Assert(Pager.GetPageID(this.DbPage) == this.ID);
            Debug.Assert(Pager.sqlite3PagerGetExtra <MemPage>(this.DbPage) == this);
            Debug.Assert(Pager.sqlite3PagerGetData(this.DbPage) == data);
            Debug.Assert(Pager.IsPageWriteable(this.DbPage));
            Debug.Assert(MutexEx.Held(pBt.Mutex));
            if (pBt.SecureDelete)
            {
                Array.Clear(data, hdr, (int)(pBt.UsableSize - hdr));
            }
            data[hdr] = (byte)flags;
            var first = (ushort)(hdr + 8 + 4 * ((flags & Btree.PTF_LEAF) == 0 ? 1 : 0));

            Array.Clear(data, hdr + 1, 4);
            data[hdr + 7] = 0;
            ConvertEx.Put2(data, hdr + 5, pBt.UsableSize);
            this.FreeBytes = (ushort)(pBt.UsableSize - first);
            decodeFlags(flags);
            this.HeaderOffset = hdr;
            this.CellOffset   = first;
            this.NOverflows   = 0;
            Debug.Assert(pBt.PageSize >= 512 && pBt.PageSize <= 65536);
            this.MaskPage = (ushort)(pBt.PageSize - 1);
            this.Cells    = 0;
            this.HasInit  = true;
        }
Beispiel #3
0
        internal RC defragmentPage()
        {
            Debug.Assert(Pager.IsPageWriteable(this.DbPage));
            Debug.Assert(this.Shared != null);
            Debug.Assert(this.Shared.UsableSize <= Pager.SQLITE_MAX_PAGE_SIZE);
            Debug.Assert(this.NOverflows == 0);
            Debug.Assert(MutexEx.Held(this.Shared.Mutex));
            var temp       = this.Shared.Pager.sqlite3PagerTempSpace(); // Temp area for cell content
            var data       = this.Data;                                 // The page data
            var hdr        = this.HeaderOffset;                         // Offset to the page header
            var cellOffset = this.CellOffset;                           // Offset to the cell pointer array
            var nCell      = this.Cells;                                // Number of cells on the page

            Debug.Assert(nCell == ConvertEx.Get2(data, hdr + 3));
            var usableSize = (int)this.Shared.UsableSize;        // Number of usable bytes on a page
            var cbrk       = (int)ConvertEx.Get2(data, hdr + 5); // Offset to the cell content area

            Buffer.BlockCopy(data, cbrk, temp, cbrk, usableSize - cbrk);
            cbrk = usableSize;
            var iCellFirst = cellOffset + 2 * nCell; // First allowable cell index
            var iCellLast  = usableSize - 4;         // Last possible cell index

            for (var i = 0; i < nCell; i++)
            {
                var pAddr = cellOffset + i * 2;          // The i-th cell pointer
                var pc    = ConvertEx.Get2(data, pAddr); // Address of a i-th cell
#if !SQLITE_ENABLE_OVERSIZE_CELL_CHECK
                // These conditions have already been verified in btreeInitPage() if SQLITE_ENABLE_OVERSIZE_CELL_CHECK is defined
                if (pc < iCellFirst || pc > iCellLast)
                {
                    return(SysEx.SQLITE_CORRUPT_BKPT());
                }
#endif
                Debug.Assert(pc >= iCellFirst && pc <= iCellLast);
                var size = cellSizePtr(temp, pc); // Size of a cell
                cbrk -= size;
                if (cbrk < iCellFirst || pc + size > usableSize)
                {
                    return(SysEx.SQLITE_CORRUPT_BKPT());
                }
                Debug.Assert(cbrk + size <= usableSize && cbrk >= iCellFirst);
                Buffer.BlockCopy(temp, pc, data, cbrk, size);
                ConvertEx.Put2(data, pAddr, cbrk);
            }
            Debug.Assert(cbrk >= iCellFirst);
            ConvertEx.Put2(data, hdr + 5, cbrk);
            data[hdr + 1] = 0;
            data[hdr + 2] = 0;
            data[hdr + 7] = 0;
            var addr = cellOffset + 2 * nCell; // Offset of first byte after cell pointer array
            Array.Clear(data, addr, cbrk - addr);
            Debug.Assert(Pager.IsPageWriteable(this.DbPage));
            return(cbrk - iCellFirst != this.FreeBytes ? SysEx.SQLITE_CORRUPT_BKPT() : RC.OK);
        }
Beispiel #4
0
        internal void insertCell(int i, byte[] pCell, int sz, byte[] pTemp, Pgno iChild, ref RC pRC)
        {
            var nSkip = (iChild != 0 ? 4 : 0);

            if (pRC != RC.OK)
            {
                return;
            }
            Debug.Assert(i >= 0 && i <= this.Cells + this.NOverflows);
            Debug.Assert(this.Cells <= Btree.MX_CELL(this.Shared) && Btree.MX_CELL(this.Shared) <= 10921);
            Debug.Assert(this.NOverflows <= this.Overflows.Length);
            Debug.Assert(MutexEx.Held(this.Shared.Mutex));
            // The cell should normally be sized correctly.  However, when moving a malformed cell from a leaf page to an interior page, if the cell size
            // wanted to be less than 4 but got rounded up to 4 on the leaf, then size might be less than 8 (leaf-size + pointer) on the interior node.  Hence
            // the term after the || in the following assert().
            Debug.Assert(sz == cellSizePtr(pCell) || (sz == 8 && iChild > 0));
            if (this.NOverflows != 0 || sz + 2 > this.FreeBytes)
            {
                if (pTemp != null)
                {
                    Buffer.BlockCopy(pCell, nSkip, pTemp, nSkip, sz - nSkip);
                    pCell = pTemp;
                }
                if (iChild != 0)
                {
                    ConvertEx.Put4L(pCell, iChild);
                }
                var j = this.NOverflows++;
                Debug.Assert(j < this.Overflows.Length);
                this.Overflows[j].Cell  = pCell;
                this.Overflows[j].Index = (ushort)i;
            }
            else
            {
                var rc = Pager.Write(this.DbPage);
                if (rc != RC.OK)
                {
                    pRC = rc;
                    return;
                }
                Debug.Assert(Pager.IsPageWriteable(this.DbPage));
                var data       = this.Data;                   // The content of the whole page
                var cellOffset = (int)this.CellOffset;        // Address of first cell pointer in data[]
                var end        = cellOffset + 2 * this.Cells; // First byte past the last cell pointer in data[]
                var ins        = cellOffset + 2 * i;          // Index in data[] where new cell pointer is inserted
                int idx        = 0;                           // Where to write new cell content in data[]
                rc = allocateSpace(sz, ref idx);
                if (rc != RC.OK)
                {
                    pRC = rc;
                    return;
                }
                // The allocateSpace() routine guarantees the following two properties if it returns success
                Debug.Assert(idx >= end + 2);
                Debug.Assert(idx + sz <= (int)this.Shared.UsableSize);
                this.Cells++;
                this.FreeBytes -= (ushort)(2 + sz);
                Buffer.BlockCopy(pCell, nSkip, data, idx + nSkip, sz - nSkip);
                if (iChild != 0)
                {
                    ConvertEx.Put4L(data, (uint)idx, iChild);
                }
                for (var j = end; j > ins; j -= 2)
                {
                    data[j + 0] = data[j - 2];
                    data[j + 1] = data[j - 1];
                }
                ConvertEx.Put2(data, ins, idx);
                ConvertEx.Put2(data, this.HeaderOffset + 3, this.Cells);
#if !SQLITE_OMIT_AUTOVACUUM
                if (this.Shared.AutoVacuum)
                {
                    // The cell may contain a pointer to an overflow page. If so, write the entry for the overflow page into the pointer map.
                    ptrmapPutOvflPtr(pCell, ref pRC);
                }
#endif
            }
        }
Beispiel #5
0
        internal RC allocateSpace(int nByte, ref int pIdx)
        {
            var hdr  = this.HeaderOffset;
            var data = this.Data;

            Debug.Assert(Pager.IsPageWriteable(this.DbPage));
            Debug.Assert(this.Shared != null);
            Debug.Assert(MutexEx.Held(this.Shared.Mutex));
            Debug.Assert(nByte >= 0);  // Minimum cell size is 4
            Debug.Assert(this.FreeBytes >= nByte);
            Debug.Assert(this.NOverflows == 0);
            var usableSize = this.Shared.UsableSize; // Usable size of the page

            Debug.Assert(nByte < usableSize - 8);
            var nFrag = data[hdr + 7]; // Number of fragmented bytes on pPage

            Debug.Assert(this.CellOffset == hdr + 12 - 4 * this.Leaf);
            var gap = this.CellOffset + 2 * this.Cells; // First byte of gap between cell pointers and cell content
            var top = ConvertEx.Get2nz(data, hdr + 5);  // First byte of cell content area

            if (gap > top)
            {
                return(SysEx.SQLITE_CORRUPT_BKPT());
            }
            if (nFrag >= 60)
            {
                // Always defragment highly fragmented pages
                var rc = defragmentPage();
                if (rc != RC.OK)
                {
                    return(rc);
                }
                top = ConvertEx.Get2nz(data, hdr + 5);
            }
            else if (gap + 2 <= top)
            {
                // Search the freelist looking for a free slot big enough to satisfy the request. The allocation is made from the first free slot in
                // the list that is large enough to accomadate it.
                int pc;
                for (var addr = hdr + 1; (pc = ConvertEx.Get2(data, addr)) > 0; addr = pc)
                {
                    if (pc > usableSize - 4 || pc < addr + 4)
                    {
                        return(SysEx.SQLITE_CORRUPT_BKPT());
                    }
                    var size = ConvertEx.Get2(data, pc + 2); // Size of free slot
                    if (size >= nByte)
                    {
                        var x = size - nByte;
                        if (x < 4)
                        {
                            // Remove the slot from the free-list. Update the number of fragmented bytes within the page.
                            data[addr + 0] = data[pc + 0];
                            data[addr + 1] = data[pc + 1];
                            data[hdr + 7]  = (byte)(nFrag + x);
                        }
                        else if (size + pc > usableSize)
                        {
                            return(SysEx.SQLITE_CORRUPT_BKPT());
                        }
                        else
                        {
                            // The slot remains on the free-list. Reduce its size to account for the portion used by the new allocation.
                            ConvertEx.Put2(data, pc + 2, x);
                        }
                        pIdx = pc + x;
                        return(RC.OK);
                    }
                }
            }
            // Check to make sure there is enough space in the gap to satisfy the allocation.  If not, defragment.
            if (gap + 2 + nByte > top)
            {
                var rc = defragmentPage();
                if (rc != RC.OK)
                {
                    return(rc);
                }
                top = ConvertEx.Get2nz(data, hdr + 5);
                Debug.Assert(gap + nByte <= top);
            }
            // Allocate memory from the gap in between the cell pointer array and the cell content area.  The btreeInitPage() call has already
            // validated the freelist.  Given that the freelist is valid, there is no way that the allocation can extend off the end of the page.
            // The Debug.Assert() below verifies the previous sentence.
            top -= nByte;
            ConvertEx.Put2(data, hdr + 5, top);
            Debug.Assert(top + nByte <= (int)this.Shared.UsableSize);
            pIdx = top;
            return(RC.OK);
        }
Beispiel #6
0
        internal RC freeSpace(int start, int size)
        {
            var data = this.Data;

            Debug.Assert(this.Shared != null);
            Debug.Assert(Pager.IsPageWriteable(this.DbPage));
            Debug.Assert(start >= this.HeaderOffset + 6 + this.ChildPtrSize);
            Debug.Assert((start + size) <= (int)this.Shared.UsableSize);
            Debug.Assert(MutexEx.Held(this.Shared.Mutex));
            Debug.Assert(size >= 0);   // Minimum cell size is 4
            if (this.Shared.SecureDelete)
            {
                // Overwrite deleted information with zeros when the secure_delete option is enabled
                Array.Clear(data, start, size);
            }
            // Add the space back into the linked list of freeblocks.  Note that even though the freeblock list was checked by btreeInitPage(),
            // btreeInitPage() did not detect overlapping cells or freeblocks that overlapped cells.   Nor does it detect when the
            // cell content area exceeds the value in the page header.  If these situations arise, then subsequent insert operations might corrupt
            // the freelist.  So we do need to check for corruption while scanning the freelist.
            var hdr   = this.HeaderOffset;
            var addr  = hdr + 1;
            var iLast = (int)this.Shared.UsableSize - 4; // Largest possible freeblock offset

            Debug.Assert(start <= iLast);
            int pbegin;

            while ((pbegin = ConvertEx.Get2(data, addr)) < start && pbegin > 0)
            {
                if (pbegin < addr + 4)
                {
                    return(SysEx.SQLITE_CORRUPT_BKPT());
                }
                addr = pbegin;
            }
            if (pbegin > iLast)
            {
                return(SysEx.SQLITE_CORRUPT_BKPT());
            }
            Debug.Assert(pbegin > addr || pbegin == 0);
            ConvertEx.Put2(data, addr, start);
            ConvertEx.Put2(data, start, pbegin);
            ConvertEx.Put2(data, start + 2, size);
            this.FreeBytes = (ushort)(this.FreeBytes + size);
            // Coalesce adjacent free blocks
            addr = hdr + 1;
            while ((pbegin = ConvertEx.Get2(data, addr)) > 0)
            {
                Debug.Assert(pbegin > addr);
                Debug.Assert(pbegin <= (int)this.Shared.UsableSize - 4);
                var pnext = ConvertEx.Get2(data, pbegin);
                var psize = ConvertEx.Get2(data, pbegin + 2);
                if (pbegin + psize + 3 >= pnext && pnext > 0)
                {
                    var frag = pnext - (pbegin + psize);
                    if ((frag < 0) || (frag > (int)data[hdr + 7]))
                    {
                        return(SysEx.SQLITE_CORRUPT_BKPT());
                    }
                    data[hdr + 7] -= (byte)frag;
                    var x = ConvertEx.Get2(data, pnext);
                    ConvertEx.Put2(data, pbegin, x);
                    x = pnext + ConvertEx.Get2(data, pnext + 2) - pbegin;
                    ConvertEx.Put2(data, pbegin + 2, x);
                }
                else
                {
                    addr = pbegin;
                }
            }
            // If the cell content area begins with a freeblock, remove it.
            if (data[hdr + 1] == data[hdr + 5] && data[hdr + 2] == data[hdr + 6])
            {
                pbegin = ConvertEx.Get2(data, hdr + 1);
                ConvertEx.Put2(data, hdr + 1, ConvertEx.Get2(data, pbegin));
                var top = ConvertEx.Get2(data, hdr + 5) + ConvertEx.Get2(data, pbegin + 2);
                ConvertEx.Put2(data, hdr + 5, top);
            }
            Debug.Assert(Pager.IsPageWriteable(this.DbPage));
            return(RC.OK);
        }