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