mirror of
https://github.com/sqlite/sqlite.git
synced 2025-08-07 02:42:48 +03:00
Smaller and faster PRAGMA integrity_check that also does a better job of
detecting errors. Some output text describing discovered file corruption has changed for clarity. FossilOrigin-Name: 251a7590ff4f65f59a1c871892533e4e2c544515
This commit is contained in:
262
src/btree.c
262
src/btree.c
@@ -8932,20 +8932,30 @@ static int btreeHeapPull(u32 *aHeap, u32 *pOut){
|
||||
static int checkTreePage(
|
||||
IntegrityCk *pCheck, /* Context for the sanity check */
|
||||
int iPage, /* Page number of the page to check */
|
||||
i64 *pnParentMinKey,
|
||||
i64 *pnParentMaxKey
|
||||
i64 *piMinKey, /* Write minimum integer primary key here */
|
||||
i64 maxKey /* Error if integer primary key greater than this */
|
||||
){
|
||||
MemPage *pPage = 0;
|
||||
int i, rc, depth, d2, pgno, cnt;
|
||||
int hdr, cellStart;
|
||||
int nCell;
|
||||
u8 *data;
|
||||
BtShared *pBt;
|
||||
int usableSize;
|
||||
u32 *heap = 0;
|
||||
MemPage *pPage = 0; /* The page being analyzed */
|
||||
int i; /* Loop counter */
|
||||
int rc; /* Result code from subroutine call */
|
||||
int depth = -1, d2; /* Depth of a subtree */
|
||||
int pgno; /* Page number */
|
||||
int nFrag; /* Number of fragmented bytes on the page */
|
||||
int hdr; /* Offset to the page header */
|
||||
int cellStart; /* Offset to the start of the cell pointer array */
|
||||
int nCell; /* Number of cells */
|
||||
int doCoverageCheck = 1; /* True if cell coverage checking should be done */
|
||||
int keyCanBeEqual = 1; /* True if IPK can be equal to maxKey
|
||||
** False if IPK must be strictly less than maxKey */
|
||||
u8 *data; /* Page content */
|
||||
u8 *pCell; /* Cell content */
|
||||
u8 *pCellIdx; /* Next element of the cell pointer array */
|
||||
BtShared *pBt; /* The BtShared object that owns pPage */
|
||||
u32 pc; /* Address of a cell */
|
||||
u32 usableSize; /* Usable size of the page */
|
||||
u32 contentOffset; /* Offset to the start of the cell content area */
|
||||
u32 *heap = 0; /* Min-heap used for checking cell coverage */
|
||||
u32 x, prev = 0;
|
||||
i64 nMinKey = 0;
|
||||
i64 nMaxKey = 0;
|
||||
const char *saved_zPfx = pCheck->zPfx;
|
||||
int saved_v1 = pCheck->v1;
|
||||
int saved_v2 = pCheck->v2;
|
||||
@@ -8961,7 +8971,6 @@ static int checkTreePage(
|
||||
if( (rc = btreeGetPage(pBt, (Pgno)iPage, &pPage, 0))!=0 ){
|
||||
checkAppendMsg(pCheck,
|
||||
"unable to get the page. error code=%d", rc);
|
||||
depth = -1;
|
||||
goto end_of_check;
|
||||
}
|
||||
|
||||
@@ -8972,41 +8981,85 @@ static int checkTreePage(
|
||||
assert( rc==SQLITE_CORRUPT ); /* The only possible error from InitPage */
|
||||
checkAppendMsg(pCheck,
|
||||
"btreeInitPage() returns error code %d", rc);
|
||||
depth = -1;
|
||||
goto end_of_check;
|
||||
}
|
||||
data = pPage->aData;
|
||||
hdr = pPage->hdrOffset;
|
||||
|
||||
/* Check out all the cells.
|
||||
*/
|
||||
depth = 0;
|
||||
/* Set up for cell analysis */
|
||||
pCheck->zPfx = "On tree page %d cell %d: ";
|
||||
for(i=0; i<pPage->nCell && pCheck->mxErr; i++){
|
||||
u8 *pCell;
|
||||
u32 sz;
|
||||
contentOffset = get2byteNotZero(&data[hdr+5]);
|
||||
assert( contentOffset<=usableSize ); /* Enforced by btreeInitPage() */
|
||||
|
||||
/* EVIDENCE-OF: R-37002-32774 The two-byte integer at offset 3 gives the
|
||||
** number of cells on the page. */
|
||||
nCell = get2byte(&data[hdr+3]);
|
||||
assert( pPage->nCell==nCell );
|
||||
|
||||
/* EVIDENCE-OF: R-23882-45353 The cell pointer array of a b-tree page
|
||||
** immediately follows the b-tree page header. */
|
||||
cellStart = hdr + 12 - 4*pPage->leaf;
|
||||
assert( pPage->aCellIdx==&data[cellStart] );
|
||||
pCellIdx = &data[cellStart + 2*(nCell-1)];
|
||||
|
||||
if( !pPage->leaf ){
|
||||
/* Analyze the right-child page of internal pages */
|
||||
pgno = get4byte(&data[hdr+8]);
|
||||
#ifndef SQLITE_OMIT_AUTOVACUUM
|
||||
if( pBt->autoVacuum ){
|
||||
pCheck->zPfx = "On page %d at right child: ";
|
||||
checkPtrmap(pCheck, pgno, PTRMAP_BTREE, iPage);
|
||||
}
|
||||
#endif
|
||||
depth = checkTreePage(pCheck, pgno, &maxKey, maxKey);
|
||||
keyCanBeEqual = 0;
|
||||
}else{
|
||||
/* For leaf pages, the coverage check will occur in the same loop
|
||||
** as the other cell checks, so initialize the heap. */
|
||||
heap = pCheck->heap;
|
||||
heap[0] = 0;
|
||||
btreeHeapInsert(heap, contentOffset-1);
|
||||
}
|
||||
|
||||
/* EVIDENCE-OF: R-02776-14802 The cell pointer array consists of K 2-byte
|
||||
** integer offsets to the cell contents. */
|
||||
for(i=nCell-1; i>=0 && pCheck->mxErr; i--){
|
||||
CellInfo info;
|
||||
|
||||
/* Check payload overflow pages
|
||||
*/
|
||||
/* Check cell size */
|
||||
pCheck->v2 = i;
|
||||
pCell = findCell(pPage,i);
|
||||
pPage->xParseCell(pPage, pCell, &info);
|
||||
sz = info.nPayload;
|
||||
/* For intKey pages, check that the keys are in order.
|
||||
*/
|
||||
if( pPage->intKey ){
|
||||
if( i==0 ){
|
||||
nMinKey = nMaxKey = info.nKey;
|
||||
}else if( info.nKey <= nMaxKey ){
|
||||
checkAppendMsg(pCheck,
|
||||
"Rowid %lld out of order (previous was %lld)", info.nKey, nMaxKey);
|
||||
}
|
||||
nMaxKey = info.nKey;
|
||||
assert( pCellIdx==&data[cellStart + i*2] );
|
||||
pc = get2byteAligned(pCellIdx);
|
||||
pCellIdx -= 2;
|
||||
if( pc<contentOffset || pc>usableSize-4 ){
|
||||
checkAppendMsg(pCheck, "Offset %d out of range %d..%d",
|
||||
pc, contentOffset, usableSize-4);
|
||||
doCoverageCheck = 0;
|
||||
continue;
|
||||
}
|
||||
if( (sz>info.nLocal)
|
||||
&& (&pCell[info.iOverflow]<=&pPage->aData[pBt->usableSize])
|
||||
){
|
||||
int nPage = (sz - info.nLocal + usableSize - 5)/(usableSize - 4);
|
||||
Pgno pgnoOvfl = get4byte(&pCell[info.iOverflow]);
|
||||
pCell = &data[pc];
|
||||
pPage->xParseCell(pPage, pCell, &info);
|
||||
if( pc+info.nSize>usableSize ){
|
||||
checkAppendMsg(pCheck, "Extends off end of page");
|
||||
doCoverageCheck = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check for integer primary key out of range */
|
||||
if( pPage->intKey ){
|
||||
if( keyCanBeEqual ? (info.nKey > maxKey) : (info.nKey >= maxKey) ){
|
||||
checkAppendMsg(pCheck, "Rowid %lld out of order", info.nKey);
|
||||
}
|
||||
maxKey = info.nKey;
|
||||
}
|
||||
|
||||
/* Check the content overflow list */
|
||||
if( info.nPayload>info.nLocal ){
|
||||
int nPage; /* Number of pages on the overflow chain */
|
||||
Pgno pgnoOvfl; /* First page of the overflow chain */
|
||||
assert( pc + info.iOverflow <= usableSize );
|
||||
nPage = (info.nPayload - info.nLocal + usableSize - 5)/(usableSize - 4);
|
||||
pgnoOvfl = get4byte(&pCell[info.iOverflow]);
|
||||
#ifndef SQLITE_OMIT_AUTOVACUUM
|
||||
if( pBt->autoVacuum ){
|
||||
checkPtrmap(pCheck, pgnoOvfl, PTRMAP_OVERFLOW1, iPage);
|
||||
@@ -9015,107 +9068,50 @@ static int checkTreePage(
|
||||
checkList(pCheck, 0, pgnoOvfl, nPage);
|
||||
}
|
||||
|
||||
/* Check sanity of left child page.
|
||||
*/
|
||||
if( !pPage->leaf ){
|
||||
/* Check sanity of left child page for internal pages */
|
||||
pgno = get4byte(pCell);
|
||||
#ifndef SQLITE_OMIT_AUTOVACUUM
|
||||
if( pBt->autoVacuum ){
|
||||
checkPtrmap(pCheck, pgno, PTRMAP_BTREE, iPage);
|
||||
}
|
||||
#endif
|
||||
d2 = checkTreePage(pCheck, pgno, &nMinKey, i==0?NULL:&nMaxKey);
|
||||
if( i>0 && d2!=depth ){
|
||||
d2 = checkTreePage(pCheck, pgno, &maxKey, maxKey);
|
||||
keyCanBeEqual = 0;
|
||||
if( d2!=depth ){
|
||||
checkAppendMsg(pCheck, "Child page depth differs");
|
||||
depth = d2;
|
||||
}
|
||||
depth = d2;
|
||||
}
|
||||
}
|
||||
|
||||
if( !pPage->leaf ){
|
||||
pgno = get4byte(&pPage->aData[pPage->hdrOffset+8]);
|
||||
pCheck->zPfx = "On page %d at right child: ";
|
||||
#ifndef SQLITE_OMIT_AUTOVACUUM
|
||||
if( pBt->autoVacuum ){
|
||||
checkPtrmap(pCheck, pgno, PTRMAP_BTREE, iPage);
|
||||
}
|
||||
#endif
|
||||
d2 = checkTreePage(pCheck, pgno, NULL, !pPage->nCell?NULL:&nMaxKey);
|
||||
if( d2!=depth && iPage!=1 ){
|
||||
checkAppendMsg(pCheck, "Child page depth differs");
|
||||
}
|
||||
}
|
||||
|
||||
/* For intKey leaf pages, check that the min/max keys are in order
|
||||
** with any left/parent/right pages.
|
||||
*/
|
||||
pCheck->zPfx = "Page %d: ";
|
||||
if( pPage->leaf && pPage->intKey ){
|
||||
/* if we are a left child page */
|
||||
if( pnParentMinKey ){
|
||||
/* if we are the left most child page */
|
||||
if( !pnParentMaxKey ){
|
||||
if( nMaxKey > *pnParentMinKey ){
|
||||
checkAppendMsg(pCheck,
|
||||
"Rowid %lld out of order (max larger than parent min of %lld)",
|
||||
nMaxKey, *pnParentMinKey);
|
||||
}
|
||||
}else{
|
||||
if( nMinKey <= *pnParentMinKey ){
|
||||
checkAppendMsg(pCheck,
|
||||
"Rowid %lld out of order (min less than parent min of %lld)",
|
||||
nMinKey, *pnParentMinKey);
|
||||
}
|
||||
if( nMaxKey > *pnParentMaxKey ){
|
||||
checkAppendMsg(pCheck,
|
||||
"Rowid %lld out of order (max larger than parent max of %lld)",
|
||||
nMaxKey, *pnParentMaxKey);
|
||||
}
|
||||
*pnParentMinKey = nMaxKey;
|
||||
}
|
||||
/* else if we're a right child page */
|
||||
} else if( pnParentMaxKey ){
|
||||
if( nMinKey <= *pnParentMaxKey ){
|
||||
checkAppendMsg(pCheck,
|
||||
"Rowid %lld out of order (min less than parent max of %lld)",
|
||||
nMinKey, *pnParentMaxKey);
|
||||
}
|
||||
}else{
|
||||
/* Populate the coverage-checking heap for leaf pages */
|
||||
btreeHeapInsert(heap, (pc<<16)|(pc+info.nSize-1));
|
||||
}
|
||||
}
|
||||
*piMinKey = maxKey;
|
||||
|
||||
/* Check for complete coverage of the page
|
||||
*/
|
||||
data = pPage->aData;
|
||||
hdr = pPage->hdrOffset;
|
||||
heap = pCheck->heap;
|
||||
heap[0] = 0;
|
||||
pCheck->zPfx = 0;
|
||||
{
|
||||
int contentOffset = get2byteNotZero(&data[hdr+5]);
|
||||
assert( contentOffset<=usableSize ); /* Enforced by btreeInitPage() */
|
||||
btreeHeapInsert(heap, contentOffset-1);
|
||||
/* EVIDENCE-OF: R-37002-32774 The two-byte integer at offset 3 gives the
|
||||
** number of cells on the page. */
|
||||
nCell = get2byte(&data[hdr+3]);
|
||||
/* EVIDENCE-OF: R-23882-45353 The cell pointer array of a b-tree page
|
||||
** immediately follows the b-tree page header. */
|
||||
cellStart = hdr + 12 - 4*pPage->leaf;
|
||||
/* EVIDENCE-OF: R-02776-14802 The cell pointer array consists of K 2-byte
|
||||
** integer offsets to the cell contents. */
|
||||
for(i=nCell-1; i>=0; i--){
|
||||
u32 pc = get2byteAligned(&data[cellStart+i*2]);
|
||||
u32 size = pPage->xCellSize(pPage, &data[pc]);
|
||||
if( (int)(pc+size-1)>=usableSize ){
|
||||
pCheck->zPfx = 0;
|
||||
checkAppendMsg(pCheck,
|
||||
"Corruption detected in cell %d on page %d",i,iPage);
|
||||
}else{
|
||||
if( doCoverageCheck && pCheck->mxErr>0 ){
|
||||
/* For leaf pages, the min-heap has already been initialized and the
|
||||
** cells have already been inserted. But for internal pages, that has
|
||||
** not yet been done, so do it now */
|
||||
if( !pPage->leaf ){
|
||||
heap = pCheck->heap;
|
||||
heap[0] = 0;
|
||||
btreeHeapInsert(heap, contentOffset-1);
|
||||
for(i=nCell-1; i>=0; i--){
|
||||
u32 pc = get2byteAligned(&data[cellStart+i*2]);
|
||||
u32 size = pPage->xCellSize(pPage, &data[pc]);
|
||||
btreeHeapInsert(heap, (pc<<16)|(pc+size-1));
|
||||
}
|
||||
}
|
||||
/* EVIDENCE-OF: R-20690-50594 The second field of the b-tree page header
|
||||
/* Add the freeblocks to the min-heap
|
||||
**
|
||||
** EVIDENCE-OF: R-20690-50594 The second field of the b-tree page header
|
||||
** is the offset of the first freeblock, or zero if there are no
|
||||
** freeblocks on the page. */
|
||||
** freeblocks on the page.
|
||||
*/
|
||||
i = get2byte(&data[hdr+1]);
|
||||
while( i>0 ){
|
||||
int size, j;
|
||||
@@ -9134,7 +9130,10 @@ static int checkTreePage(
|
||||
assert( j<=usableSize-4 ); /* Enforced by btreeInitPage() */
|
||||
i = j;
|
||||
}
|
||||
cnt = 0;
|
||||
/* Analyze the min-heap looking for overlap between cells and/or
|
||||
** freeblocks, and counting the number of untracked bytes in nFrag.
|
||||
*/
|
||||
nFrag = 0;
|
||||
assert( heap[0]>0 );
|
||||
assert( (heap[1]>>16)==0 );
|
||||
btreeHeapPull(heap,&prev);
|
||||
@@ -9144,20 +9143,20 @@ static int checkTreePage(
|
||||
"Multiple uses for byte %u of page %d", x>>16, iPage);
|
||||
break;
|
||||
}else{
|
||||
cnt += (x>>16) - (prev&0xffff) - 1;
|
||||
nFrag += (x>>16) - (prev&0xffff) - 1;
|
||||
prev = x;
|
||||
}
|
||||
}
|
||||
cnt += usableSize - (prev&0xffff) - 1;
|
||||
nFrag += usableSize - (prev&0xffff) - 1;
|
||||
/* EVIDENCE-OF: R-43263-13491 The total number of bytes in all fragments
|
||||
** is stored in the fifth field of the b-tree page header.
|
||||
** EVIDENCE-OF: R-07161-27322 The one-byte integer at offset 7 gives the
|
||||
** number of fragmented free bytes within the cell content area.
|
||||
*/
|
||||
if( heap[0]==0 && cnt!=data[hdr+7] ){
|
||||
if( heap[0]==0 && nFrag!=data[hdr+7] ){
|
||||
checkAppendMsg(pCheck,
|
||||
"Fragmentation of %d bytes reported as %d on page %d",
|
||||
cnt, data[hdr+7], iPage);
|
||||
nFrag, data[hdr+7], iPage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9192,10 +9191,11 @@ char *sqlite3BtreeIntegrityCheck(
|
||||
int *pnErr /* Write number of errors seen to this variable */
|
||||
){
|
||||
Pgno i;
|
||||
VVA_ONLY( int nRef );
|
||||
IntegrityCk sCheck;
|
||||
BtShared *pBt = p->pBt;
|
||||
int savedDbFlags = pBt->db->flags;
|
||||
char zErr[100];
|
||||
VVA_ONLY( int nRef );
|
||||
|
||||
sqlite3BtreeEnter(p);
|
||||
assert( p->inTrans>TRANS_NONE && pBt->inTransaction>TRANS_NONE );
|
||||
@@ -9239,15 +9239,19 @@ char *sqlite3BtreeIntegrityCheck(
|
||||
|
||||
/* Check all the tables.
|
||||
*/
|
||||
testcase( pBt->db->flags & SQLITE_CellSizeCk );
|
||||
pBt->db->flags &= ~SQLITE_CellSizeCk;
|
||||
for(i=0; (int)i<nRoot && sCheck.mxErr; i++){
|
||||
i64 notUsed;
|
||||
if( aRoot[i]==0 ) continue;
|
||||
#ifndef SQLITE_OMIT_AUTOVACUUM
|
||||
if( pBt->autoVacuum && aRoot[i]>1 ){
|
||||
checkPtrmap(&sCheck, aRoot[i], PTRMAP_ROOTPAGE, 0);
|
||||
}
|
||||
#endif
|
||||
checkTreePage(&sCheck, aRoot[i], NULL, NULL);
|
||||
checkTreePage(&sCheck, aRoot[i], ¬Used, LARGEST_INT64);
|
||||
}
|
||||
pBt->db->flags = savedDbFlags;
|
||||
|
||||
/* Make sure every page in the file is referenced
|
||||
*/
|
||||
|
Reference in New Issue
Block a user