Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | 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. |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
251a7590ff4f65f59a1c871892533e4e |
User & Date: | drh 2015-07-02 16:17:30.545 |
Context
2015-07-02
| ||
16:29 | Fix a (harmless) shadowed local variable definition in the integrity_check logic. (check-in: 3a26a919fd user: drh tags: trunk) | |
16:17 | 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. (check-in: 251a7590ff user: drh tags: trunk) | |
15:52 | Remove "#ifdef SQLITE_ENABLE_FTS5" from individual fts5 source files. Add a single "#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5)" to fts5.c. (check-in: 7819002ed8 user: dan tags: trunk) | |
Changes
Changes to src/btree.c.
︙ | ︙ | |||
8928 8929 8930 8931 8932 8933 8934 | ** 3. Check the integrity of overflow pages. ** 4. Recursively call checkTreePage on all children. ** 5. Verify that the depth of all children is the same. */ static int checkTreePage( IntegrityCk *pCheck, /* Context for the sanity check */ int iPage, /* Page number of the page to check */ | | | | > > | > > > | | > > > | > > | > | > | < < < < > > | < < > > | > > > > | > > > > > | > > > > > > > > > > > > > > > > > > > > > > | < > > > > > > > > > | | > | > > | > < < | | < | > > | > > | < | | < < > | > | < | | < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < | < | < < > | < < < < | | < < < < < < < < < < | > > > > | | | < < < < | < < < < < < < < | | | < < < < < > > | | > > > > | | | | | | 8928 8929 8930 8931 8932 8933 8934 8935 8936 8937 8938 8939 8940 8941 8942 8943 8944 8945 8946 8947 8948 8949 8950 8951 8952 8953 8954 8955 8956 8957 8958 8959 8960 8961 8962 8963 8964 8965 8966 8967 8968 8969 8970 8971 8972 8973 8974 8975 8976 8977 8978 8979 8980 8981 8982 8983 8984 8985 8986 8987 8988 8989 8990 8991 8992 8993 8994 8995 8996 8997 8998 8999 9000 9001 9002 9003 9004 9005 9006 9007 9008 9009 9010 9011 9012 9013 9014 9015 9016 9017 9018 9019 9020 9021 9022 9023 9024 9025 9026 9027 9028 9029 9030 9031 9032 9033 9034 9035 9036 9037 9038 9039 9040 9041 9042 9043 9044 9045 9046 9047 9048 9049 9050 9051 9052 9053 9054 9055 9056 9057 9058 9059 9060 9061 9062 9063 9064 9065 9066 9067 9068 9069 9070 9071 9072 9073 9074 9075 9076 9077 9078 9079 9080 9081 9082 9083 9084 9085 9086 9087 9088 9089 9090 9091 9092 9093 9094 9095 9096 9097 9098 9099 9100 9101 9102 9103 9104 9105 9106 9107 9108 9109 9110 9111 9112 9113 9114 9115 9116 9117 9118 9119 9120 9121 9122 9123 9124 9125 9126 9127 9128 9129 9130 9131 9132 9133 9134 9135 9136 9137 9138 9139 9140 9141 9142 9143 9144 9145 9146 9147 9148 9149 9150 9151 9152 9153 9154 9155 9156 9157 9158 9159 9160 9161 9162 9163 9164 9165 9166 | ** 3. Check the integrity of overflow pages. ** 4. Recursively call checkTreePage on all children. ** 5. Verify that the depth of all children is the same. */ static int checkTreePage( IntegrityCk *pCheck, /* Context for the sanity check */ int iPage, /* Page number of the page to check */ i64 *piMinKey, /* Write minimum integer primary key here */ i64 maxKey /* Error if integer primary key greater than this */ ){ 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; const char *saved_zPfx = pCheck->zPfx; int saved_v1 = pCheck->v1; int saved_v2 = pCheck->v2; /* Check that the page exists */ pBt = pCheck->pBt; usableSize = pBt->usableSize; if( iPage==0 ) return 0; if( checkRef(pCheck, iPage) ) return 0; pCheck->zPfx = "Page %d: "; pCheck->v1 = iPage; if( (rc = btreeGetPage(pBt, (Pgno)iPage, &pPage, 0))!=0 ){ checkAppendMsg(pCheck, "unable to get the page. error code=%d", rc); goto end_of_check; } /* Clear MemPage.isInit to make sure the corruption detection code in ** btreeInitPage() is executed. */ pPage->isInit = 0; if( (rc = btreeInitPage(pPage))!=0 ){ assert( rc==SQLITE_CORRUPT ); /* The only possible error from InitPage */ checkAppendMsg(pCheck, "btreeInitPage() returns error code %d", rc); goto end_of_check; } data = pPage->aData; hdr = pPage->hdrOffset; /* Set up for cell analysis */ pCheck->zPfx = "On tree page %d cell %d: "; 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 cell size */ pCheck->v2 = i; 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; } 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); } #endif checkList(pCheck, 0, pgnoOvfl, nPage); } 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, &maxKey, maxKey); keyCanBeEqual = 0; if( d2!=depth ){ checkAppendMsg(pCheck, "Child page depth differs"); depth = d2; } }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 */ pCheck->zPfx = 0; 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)); } } /* 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. */ i = get2byte(&data[hdr+1]); while( i>0 ){ int size, j; assert( i<=usableSize-4 ); /* Enforced by btreeInitPage() */ size = get2byte(&data[i+2]); assert( i+size<=usableSize ); /* Enforced by btreeInitPage() */ btreeHeapInsert(heap, (i<<16)|(i+size-1)); /* EVIDENCE-OF: R-58208-19414 The first 2 bytes of a freeblock are a ** big-endian integer which is the offset in the b-tree page of the next ** freeblock in the chain, or zero if the freeblock is the last on the ** chain. */ j = get2byte(&data[i]); /* EVIDENCE-OF: R-06866-39125 Freeblocks are always connected in order of ** increasing offset. */ assert( j==0 || j>i+size ); /* Enforced by btreeInitPage() */ assert( j<=usableSize-4 ); /* Enforced by btreeInitPage() */ i = j; } /* 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); while( btreeHeapPull(heap,&x) ){ if( (prev&0xffff)+1>(x>>16) ){ checkAppendMsg(pCheck, "Multiple uses for byte %u of page %d", x>>16, iPage); break; }else{ nFrag += (x>>16) - (prev&0xffff) - 1; prev = x; } } 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 && nFrag!=data[hdr+7] ){ checkAppendMsg(pCheck, "Fragmentation of %d bytes reported as %d on page %d", nFrag, data[hdr+7], iPage); } } end_of_check: releasePage(pPage); pCheck->zPfx = saved_zPfx; pCheck->v1 = saved_v1; |
︙ | ︙ | |||
9188 9189 9190 9191 9192 9193 9194 | Btree *p, /* The btree to be checked */ int *aRoot, /* An array of root pages numbers for individual trees */ int nRoot, /* Number of entries in aRoot[] */ int mxErr, /* Stop reporting errors after this many */ int *pnErr /* Write number of errors seen to this variable */ ){ Pgno i; | < > > | 9187 9188 9189 9190 9191 9192 9193 9194 9195 9196 9197 9198 9199 9200 9201 9202 9203 9204 9205 | Btree *p, /* The btree to be checked */ int *aRoot, /* An array of root pages numbers for individual trees */ int nRoot, /* Number of entries in aRoot[] */ int mxErr, /* Stop reporting errors after this many */ int *pnErr /* Write number of errors seen to this variable */ ){ Pgno i; 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 ); assert( (nRef = sqlite3PagerRefcount(pBt->pPager))>=0 ); sCheck.pBt = pBt; sCheck.pPager = pBt->pPager; sCheck.nPage = btreePagecount(sCheck.pBt); |
︙ | ︙ | |||
9235 9236 9237 9238 9239 9240 9241 9242 9243 9244 9245 9246 9247 9248 | sCheck.zPfx = "Main freelist: "; checkList(&sCheck, 1, get4byte(&pBt->pPage1->aData[32]), get4byte(&pBt->pPage1->aData[36])); sCheck.zPfx = 0; /* Check all the tables. */ for(i=0; (int)i<nRoot && sCheck.mxErr; i++){ if( aRoot[i]==0 ) continue; #ifndef SQLITE_OMIT_AUTOVACUUM if( pBt->autoVacuum && aRoot[i]>1 ){ checkPtrmap(&sCheck, aRoot[i], PTRMAP_ROOTPAGE, 0); } #endif | > > > | > | 9235 9236 9237 9238 9239 9240 9241 9242 9243 9244 9245 9246 9247 9248 9249 9250 9251 9252 9253 9254 9255 9256 9257 9258 9259 9260 9261 | sCheck.zPfx = "Main freelist: "; checkList(&sCheck, 1, get4byte(&pBt->pPage1->aData[32]), get4byte(&pBt->pPage1->aData[36])); sCheck.zPfx = 0; /* 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], ¬Used, LARGEST_INT64); } pBt->db->flags = savedDbFlags; /* Make sure every page in the file is referenced */ for(i=1; i<=sCheck.nPage && sCheck.mxErr; i++){ #ifdef SQLITE_OMIT_AUTOVACUUM if( getPageReferenced(&sCheck, i)==0 ){ checkAppendMsg(&sCheck, "Page %d is never used", i); |
︙ | ︙ |
Changes to test/corrupt2.test.
︙ | ︙ | |||
245 246 247 248 249 250 251 | db2 eval {SELECT rowid FROM t1} { set result [db2 eval {pragma integrity_check}] break } set result } {{*** in database main *** On tree page 2 cell 0: 2nd reference to page 10 | < | 245 246 247 248 249 250 251 252 253 254 255 256 257 258 | db2 eval {SELECT rowid FROM t1} { set result [db2 eval {pragma integrity_check}] break } set result } {{*** in database main *** On tree page 2 cell 0: 2nd reference to page 10 Page 4 is never used}} db2 close proc corruption_test {args} { set A(-corrupt) {} set A(-sqlprep) {} |
︙ | ︙ |
Changes to test/corrupt7.test.
︙ | ︙ | |||
61 62 63 64 65 66 67 | hexio_get_int [hexio_read test.db 20 1] } 0 ;# Unused bytes per page is 0 integrity_check corrupt7-1.4 # Deliberately corrupt some of the cell offsets in the btree page # on page 2 of the database. | < < < < < | | | | | | < < < < < < < < < < < < < < < | | | | | | | | < | 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | hexio_get_int [hexio_read test.db 20 1] } 0 ;# Unused bytes per page is 0 integrity_check corrupt7-1.4 # Deliberately corrupt some of the cell offsets in the btree page # on page 2 of the database. do_test corrupt7-2.1 { db close hexio_write test.db 1062 FF sqlite3 db test.db db eval {PRAGMA integrity_check(1)} } {{*** in database main *** On tree page 2 cell 15: Offset 65457 out of range 945..1020}} do_test corrupt7-2.2 { db close hexio_write test.db 1062 04 sqlite3 db test.db db eval {PRAGMA integrity_check(1)} } {{*** in database main *** On tree page 2 cell 15: Offset 1201 out of range 945..1020}} # The code path that was causing the buffer overrun that this test # case was checking for was removed. # #do_test corrupt7-3.1 { # execsql { # DROP TABLE t1; |
︙ | ︙ |
Changes to test/corruptE.test.
︙ | ︙ | |||
10 11 12 13 14 15 16 | #*********************************************************************** # This file implements regression tests for SQLite library. # # This file implements tests to make sure SQLite does not crash or # segfault if it sees a corrupt database file. It specifcally # focuses on rowid order corruption. # | < | 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #*********************************************************************** # This file implements regression tests for SQLite library. # # This file implements tests to make sure SQLite does not crash or # segfault if it sees a corrupt database file. It specifcally # focuses on rowid order corruption. # set testdir [file dirname $argv0] source $testdir/tester.tcl # Do not use a codec for tests in this file, as the database file is # manipulated directly using tcl scripts (using the [hexio_write] command). # |
︙ | ︙ | |||
75 76 77 78 79 80 81 | forcecopy test.bu test.db # insert corrupt byte(s) hexio_write test.db 2041 [format %02x 0x2e] sqlite3 db test.db | | < < | < < | < | < < < | < | < < | < | < < < < < < < < < < | < < | < | 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | forcecopy test.bu test.db # insert corrupt byte(s) hexio_write test.db 2041 [format %02x 0x2e] sqlite3 db test.db catchsql {PRAGMA integrity_check} } {/ out of order/} do_test corruptE-2.2 { db close forcecopy test.bu test.db # insert corrupt byte(s) hexio_write test.db 2047 [format %02x 0x84] sqlite3 db test.db catchsql {PRAGMA integrity_check} } {/ Extends off end of page/} do_test corruptE-2.3 { db close forcecopy test.bu test.db # insert corrupt byte(s) hexio_write test.db 7420 [format %02x 0xa8] hexio_write test.db 10459 [format %02x 0x8d] sqlite3 db test.db catchsql {PRAGMA integrity_check} } {/out of order/} do_test corruptE-2.4 { db close forcecopy test.bu test.db # insert corrupt byte(s) hexio_write test.db 10233 [format %02x 0xd0] sqlite3 db test.db catchsql {PRAGMA integrity_check} } {/out of order/} set tests [list {10233 0xd0} \ {941 0x42} \ {2041 0xd0} \ {2042 0x1f} \ {2274 0x75} \ {3267 0xf2} \ {5113 0x36} \ {10233 0x84} \ {10234 0x74} \ {10239 0x41} \ {11273 0x28} \ {11461 0xe6} \ {12297 0xd7} \ {13303 0x53} ] set tc 1 foreach test $tests { do_test corruptE-3.$tc { db close forcecopy test.bu test.db # insert corrupt byte(s) hexio_write test.db [lindex $test 0] [format %02x [lindex $test 1]] sqlite3 db test.db catchsql {PRAGMA integrity_check} } {/out of order/} incr tc 1 } finish_test |