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 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 btreeInitPage() { Debug.Assert(this.Shared != null); Debug.Assert(MutexEx.Held(this.Shared.Mutex)); Debug.Assert(this.ID == Pager.GetPageID(this.DbPage)); Debug.Assert(this == Pager.sqlite3PagerGetExtra <MemPage>(this.DbPage)); Debug.Assert(this.Data == Pager.sqlite3PagerGetData(this.DbPage)); if (!this.HasInit) { var pBt = this.Shared; // The main btree structure var hdr = this.HeaderOffset; // Offset to beginning of page header var data = this.Data; if (decodeFlags(data[hdr]) != 0) { return(SysEx.SQLITE_CORRUPT_BKPT()); } Debug.Assert(pBt.PageSize >= 512 && pBt.PageSize <= 65536); this.MaskPage = (ushort)(pBt.PageSize - 1); this.NOverflows = 0; var usableSize = (int)pBt.UsableSize; // Amount of usable space on each page ushort cellOffset; // Offset from start of page to first cell pointer this.CellOffset = (cellOffset = (ushort)(hdr + 12 - 4 * this.Leaf)); var top = ConvertEx.Get2nz(data, hdr + 5); // First byte of the cell content area this.Cells = (ushort)(ConvertEx.Get2(data, hdr + 3)); if (this.Cells > Btree.MX_CELL(pBt)) { // To many cells for a single page. The page must be corrupt return(SysEx.SQLITE_CORRUPT_BKPT()); } // A malformed database page might cause us to read past the end of page when parsing a cell. // The following block of code checks early to see if a cell extends past the end of a page boundary and causes SQLITE_CORRUPT to be // returned if it does. var iCellFirst = cellOffset + 2 * this.Cells; // First allowable cell or freeblock offset var iCellLast = usableSize - 4; // Last possible cell or freeblock offset #if SQLITE_ENABLE_OVERSIZE_CELL_CHECK if (pPage.leaf == 0) { iCellLast--; } for (var i = 0; i < pPage.nCell; i++) { pc = (ushort)ConvertEx.get2byte(data, cellOffset + i * 2); if (pc < iCellFirst || pc > iCellLast) { return(SysEx.SQLITE_CORRUPT_BKPT()); } var sz = cellSizePtr(pPage, data, pc); if (pc + sz > usableSize) { return(SysEx.SQLITE_CORRUPT_BKPT()); } } if (pPage.leaf == 0) { iCellLast++; } #endif // Compute the total free space on the page var pc = (ushort)ConvertEx.Get2(data, hdr + 1); // Address of a freeblock within pPage.aData[] var nFree = (ushort)(data[hdr + 7] + top); // Number of unused bytes on the page while (pc > 0) { if (pc < iCellFirst || pc > iCellLast) { // Start of free block is off the page return(SysEx.SQLITE_CORRUPT_BKPT()); } var next = (ushort)ConvertEx.Get2(data, pc); var size = (ushort)ConvertEx.Get2(data, pc + 2); if ((next > 0 && next <= pc + size + 3) || pc + size > usableSize) { // Free blocks must be in ascending order. And the last byte of the free-block must lie on the database page. return(SysEx.SQLITE_CORRUPT_BKPT()); } nFree = (ushort)(nFree + size); pc = next; } // At this point, nFree contains the sum of the offset to the start of the cell-content area plus the number of free bytes within // the cell-content area. If this is greater than the usable-size of the page, then the page must be corrupted. This check also // serves to verify that the offset to the start of the cell-content area, according to the page header, lies within the page. if (nFree > usableSize) { return(SysEx.SQLITE_CORRUPT_BKPT()); } this.FreeBytes = (ushort)(nFree - iCellFirst); this.HasInit = true; } return(RC.OK); }