Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Better detection and handling of corrupt database files. (CVS 1922) |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
8f5b199e845fa7ae3444ef69bd840716 |
User & Date: | drh 2004-08-30 16:52:18.000 |
Context
2004-08-31
| ||
00:52 | Combine the implementation of LIKE and GLOB into a single parameterized function. (CVS 1923) (check-in: 0a47c8f86d user: drh tags: trunk) | |
2004-08-30
| ||
16:52 | Better detection and handling of corrupt database files. (CVS 1922) (check-in: 8f5b199e84 user: drh tags: trunk) | |
14:58 | Documentation updates (CVS 1921) (check-in: 9322c439c5 user: drh tags: trunk) | |
Changes
Changes to src/btree.c.
1 2 3 4 5 6 7 8 9 10 11 | /* ** 2004 April 6 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* | | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /* ** 2004 April 6 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** $Id: btree.c,v 1.183 2004/08/30 16:52:18 drh Exp $ ** ** This file implements a external (disk-based) database using BTrees. ** For a detailed discussion of BTrees, refer to ** ** Donald E. Knuth, THE ART OF COMPUTER PROGRAMMING, Volume 3: ** "Sorting And Searching", pages 473-480. Addison-Wesley ** Publishing Company, Reading, Massachusetts. |
︙ | ︙ | |||
818 819 820 821 822 823 824 | int nFree; /* Number of unused bytes on the page */ int top; /* First byte of the cell content area */ assert( pPage->pBt!=0 ); assert( pParent==0 || pParent->pBt==pPage->pBt ); assert( pPage->pgno==sqlite3pager_pagenumber(pPage->aData) ); assert( pPage->aData == &((unsigned char*)pPage)[-pPage->pBt->pageSize] ); | | | > > > > > > > > > > > > | > | > > > | > > > | > > > | 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 | int nFree; /* Number of unused bytes on the page */ int top; /* First byte of the cell content area */ assert( pPage->pBt!=0 ); assert( pParent==0 || pParent->pBt==pPage->pBt ); assert( pPage->pgno==sqlite3pager_pagenumber(pPage->aData) ); assert( pPage->aData == &((unsigned char*)pPage)[-pPage->pBt->pageSize] ); if( pPage->pParent!=pParent && (pPage->pParent!=0 || pPage->isInit) ){ /* The parent page should never change unless the file is corrupt */ return SQLITE_CORRUPT; /* bkpt-CORRUPT */ } if( pPage->isInit ) return SQLITE_OK; if( pPage->pParent==0 && pParent!=0 ){ pPage->pParent = pParent; sqlite3pager_ref(pParent->aData); } hdr = pPage->hdrOffset; data = pPage->aData; decodeFlags(pPage, data[hdr]); pPage->nOverflow = 0; pPage->idxShift = 0; usableSize = pPage->pBt->usableSize; pPage->cellOffset = cellOffset = hdr + 12 - 4*pPage->leaf; top = get2byte(&data[hdr+5]); pPage->nCell = get2byte(&data[hdr+3]); if( pPage->nCell>MX_CELL ){ /* To many cells for a single page. The page must be corrupt */ return SQLITE_CORRUPT; /* bkpt-CORRUPT */ } if( pPage->nCell==0 && pParent!=0 && pParent->pgno!=1 ){ /* All pages must have at least one cell, except for root pages */ return SQLITE_CORRUPT; /* bkpt-CORRUPT */ } /* Compute the total free space on the page */ pc = get2byte(&data[hdr+1]); nFree = data[hdr+7] + top - (cellOffset + 2*pPage->nCell); i = 0; while( pc>0 ){ int next, size; if( pc>usableSize-4 ){ /* Free block is off the page */ return SQLITE_CORRUPT; /* bkpt-CORRUPT */ } if( i++>SQLITE_MAX_PAGE_SIZE/4 ){ /* The free block list forms an infinite loop */ return SQLITE_CORRUPT; /* bkpt-CORRUPT */ } next = get2byte(&data[pc]); size = get2byte(&data[pc+2]); if( next>0 && next<=pc+size+3 ){ /* Free blocks must be in accending order */ return SQLITE_CORRUPT; /* bkpt-CORRUPT */ } nFree += size; pc = next; } pPage->nFree = nFree; if( nFree>=usableSize ){ /* Free space cannot exceed total page size */ return SQLITE_CORRUPT; /* bkpt-CORRUPT */ } pPage->isInit = 1; pageIntegrity(pPage); return SQLITE_OK; } /* |
︙ | ︙ | |||
918 919 920 921 922 923 924 925 926 927 928 929 930 931 | static int getAndInitPage( Btree *pBt, /* The database file */ Pgno pgno, /* Number of the page to get */ MemPage **ppPage, /* Write the page pointer here */ MemPage *pParent /* Parent of the page */ ){ int rc; rc = getPage(pBt, pgno, ppPage); if( rc==SQLITE_OK && (*ppPage)->isInit==0 ){ rc = initPage(*ppPage, pParent); } return rc; } | > > > | 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 | static int getAndInitPage( Btree *pBt, /* The database file */ Pgno pgno, /* Number of the page to get */ MemPage **ppPage, /* Write the page pointer here */ MemPage *pParent /* Parent of the page */ ){ int rc; if( pgno==0 ){ return SQLITE_CORRUPT; /* bkpt-CORRUPT */ } rc = getPage(pBt, pgno, ppPage); if( rc==SQLITE_OK && (*ppPage)->isInit==0 ){ rc = initPage(*ppPage, pParent); } return rc; } |
︙ | ︙ | |||
1786 1787 1788 1789 1790 1791 1792 | offset -= ovflSize; } sqlite3pager_unref(aPayload); } } if( amt>0 ){ | | | 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 | offset -= ovflSize; } sqlite3pager_unref(aPayload); } } if( amt>0 ){ return SQLITE_CORRUPT; /* bkpt-CORRUPT */ } return SQLITE_OK; } /* ** Read part of the key associated with cursor pCur. Exactly ** "amt" bytes will be transfered into pBuf[]. The transfer |
︙ | ︙ | |||
1927 1928 1929 1930 1931 1932 1933 | pOldPage = pCur->pPage; pOldPage->idxShift = 0; releasePage(pOldPage); pCur->pPage = pNewPage; pCur->idx = 0; pCur->info.nSize = 0; if( pNewPage->nCell<1 ){ | | | 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 | pOldPage = pCur->pPage; pOldPage->idxShift = 0; releasePage(pOldPage); pCur->pPage = pNewPage; pCur->idx = 0; pCur->info.nSize = 0; if( pNewPage->nCell<1 ){ return SQLITE_CORRUPT; /* bkpt-CORRUPT */ } return SQLITE_OK; } /* ** Return true if the page is the virtual root of its table. ** |
︙ | ︙ | |||
2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 | if( k==0 ){ /* The trunk has no leaves. So extract the trunk page itself and ** use it as the newly allocated page */ *pPgno = get4byte(&pPage1->aData[32]); memcpy(&pPage1->aData[32], &pTrunk->aData[0], 4); *ppPage = pTrunk; TRACE(("ALLOCATE: %d trunk - %d free pages left\n", *pPgno, n-1)); }else{ /* Extract a leaf from the trunk */ int closest; unsigned char *aData = pTrunk->aData; if( nearby>0 ){ int i, dist; closest = 0; dist = get4byte(&aData[8]) - nearby; if( dist<0 ) dist = -dist; for(i=1; i<k; i++){ int d2 = get4byte(&aData[8+i*4]) - nearby; if( d2<0 ) d2 = -d2; if( d2<dist ) closest = i; } }else{ closest = 0; } *pPgno = get4byte(&aData[8+closest*4]); TRACE(("ALLOCATE: %d was leaf %d of %d on trunk %d: %d more free pages\n", *pPgno, closest+1, k, pTrunk->pgno, n-1)); if( closest<k-1 ){ memcpy(&aData[8+closest*4], &aData[4+k*4], 4); } put4byte(&aData[4], k-1); rc = getPage(pBt, *pPgno, ppPage); | > > > > > > > | 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 | if( k==0 ){ /* The trunk has no leaves. So extract the trunk page itself and ** use it as the newly allocated page */ *pPgno = get4byte(&pPage1->aData[32]); memcpy(&pPage1->aData[32], &pTrunk->aData[0], 4); *ppPage = pTrunk; TRACE(("ALLOCATE: %d trunk - %d free pages left\n", *pPgno, n-1)); }else if( k>pBt->usableSize/4 - 8 ){ /* Value of k is out of range. Database corruption */ return SQLITE_CORRUPT; /* bkpt-CORRUPT */ }else{ /* Extract a leaf from the trunk */ int closest; unsigned char *aData = pTrunk->aData; if( nearby>0 ){ int i, dist; closest = 0; dist = get4byte(&aData[8]) - nearby; if( dist<0 ) dist = -dist; for(i=1; i<k; i++){ int d2 = get4byte(&aData[8+i*4]) - nearby; if( d2<0 ) d2 = -d2; if( d2<dist ) closest = i; } }else{ closest = 0; } *pPgno = get4byte(&aData[8+closest*4]); if( *pPgno>sqlite3pager_pagecount(pBt->pPager) ){ /* Free page off the end of the file */ return SQLITE_CORRUPT; /* bkpt-CORRUPT */ } TRACE(("ALLOCATE: %d was leaf %d of %d on trunk %d: %d more free pages\n", *pPgno, closest+1, k, pTrunk->pgno, n-1)); if( closest<k-1 ){ memcpy(&aData[8+closest*4], &aData[4+k*4], 4); } put4byte(&aData[4], k-1); rc = getPage(pBt, *pPgno, ppPage); |
︙ | ︙ | |||
2461 2462 2463 2464 2465 2466 2467 | }else{ /* Other free pages already exist. Retrive the first trunk page ** of the freelist and find out how many leaves it has. */ MemPage *pTrunk; rc = getPage(pBt, get4byte(&pPage1->aData[32]), &pTrunk); if( rc ) return rc; k = get4byte(&pTrunk->aData[4]); | | | 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 | }else{ /* Other free pages already exist. Retrive the first trunk page ** of the freelist and find out how many leaves it has. */ MemPage *pTrunk; rc = getPage(pBt, get4byte(&pPage1->aData[32]), &pTrunk); if( rc ) return rc; k = get4byte(&pTrunk->aData[4]); if( k>=pBt->usableSize/4 - 8 ){ /* The trunk is full. Turn the page being freed into a new ** trunk page with no leaves. */ rc = sqlite3pager_write(pPage->aData); if( rc ) return rc; put4byte(pPage->aData, pTrunk->pgno); put4byte(&pPage->aData[4], 0); put4byte(&pPage1->aData[32], pPage->pgno); |
︙ | ︙ | |||
3561 3562 3563 3564 3565 3566 3567 | int szNext; int notUsed; unsigned char tempCell[MX_CELL_SIZE]; assert( !pPage->leafData ); getTempCursor(pCur, &leafCur); rc = sqlite3BtreeNext(&leafCur, ¬Used); if( rc!=SQLITE_OK ){ | | > > | 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 | int szNext; int notUsed; unsigned char tempCell[MX_CELL_SIZE]; assert( !pPage->leafData ); getTempCursor(pCur, &leafCur); rc = sqlite3BtreeNext(&leafCur, ¬Used); if( rc!=SQLITE_OK ){ if( rc!=SQLITE_NOMEM ){ rc = SQLITE_CORRUPT; /* bkpt-CORRUPT */ } return rc; } rc = sqlite3pager_write(leafCur.pPage->aData); if( rc ) return rc; TRACE(("DELETE: table=%d delete internal from %d replace from leaf %d\n", pCur->pgnoRoot, pPage->pgno, leafCur.pPage->pgno)); dropCell(pPage, pCur->idx, cellSizePtr(pPage, pCell)); |
︙ | ︙ | |||
4037 4038 4039 4040 4041 4042 4043 | if( sqlite3pager_get(pCheck->pPager, (Pgno)iPage, (void**)&pOvfl) ){ sprintf(zMsg, "failed to get page %d", iPage); checkAppendMsg(pCheck, zContext, zMsg); break; } if( isFreeList ){ int n = get4byte(&pOvfl[4]); | > > > > > | | | | > | 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 | if( sqlite3pager_get(pCheck->pPager, (Pgno)iPage, (void**)&pOvfl) ){ sprintf(zMsg, "failed to get page %d", iPage); checkAppendMsg(pCheck, zContext, zMsg); break; } if( isFreeList ){ int n = get4byte(&pOvfl[4]); if( n>=pCheck->pBt->usableSize/4-8 ){ sprintf(zMsg, "freelist leaf count too big on page %d", iPage); checkAppendMsg(pCheck, zContext, zMsg); N--; }else{ for(i=0; i<n; i++){ checkRef(pCheck, get4byte(&pOvfl[8+i*4]), zContext); } N -= n; } } iPage = get4byte(pOvfl); sqlite3pager_unref(pOvfl); } } /* |
︙ | ︙ |
Changes to src/pager.c.
︙ | ︙ | |||
14 15 16 17 18 19 20 | ** The pager is used to access a database disk file. It implements ** atomic commit and rollback through the use of a journal file that ** is separate from the database file. The pager also implements file ** locking to prevent two processes from writing the same database ** file simultaneously, or one process from reading the database while ** another is writing. ** | | | 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | ** The pager is used to access a database disk file. It implements ** atomic commit and rollback through the use of a journal file that ** is separate from the database file. The pager also implements file ** locking to prevent two processes from writing the same database ** file simultaneously, or one process from reading the database while ** another is writing. ** ** @(#) $Id: pager.c,v 1.161 2004/08/30 16:52:18 drh Exp $ */ #include "os.h" /* Must be first to enable large file support */ #include "sqliteInt.h" #include "pager.h" #include <assert.h> #include <string.h> |
︙ | ︙ | |||
1304 1305 1306 1307 1308 1309 1310 | } pPager->journalOff = szJ; end_stmt_playback: if( rc!=SQLITE_OK ){ pPager->errMask |= PAGER_ERR_CORRUPT; | | | 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 | } pPager->journalOff = szJ; end_stmt_playback: if( rc!=SQLITE_OK ){ pPager->errMask |= PAGER_ERR_CORRUPT; rc = SQLITE_CORRUPT; /* bkpt-CORRUPT */ }else{ pPager->journalOff = szJ; /* pager_reload_cache(pPager); */ } return rc; } |
︙ | ︙ | |||
2878 2879 2880 2881 2882 2883 2884 | rc = rc2; if( rc3 ) rc = rc3; } }else{ rc = pager_playback(pPager); } if( rc!=SQLITE_OK ){ | | | 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 | rc = rc2; if( rc3 ) rc = rc3; } }else{ rc = pager_playback(pPager); } if( rc!=SQLITE_OK ){ rc = SQLITE_CORRUPT; /* bkpt-CORRUPT */ pPager->errMask |= PAGER_ERR_CORRUPT; } pPager->dbSize = -1; return rc; } /* |
︙ | ︙ |
Changes to src/vdbemem.c.
︙ | ︙ | |||
628 629 630 631 632 633 634 | if( (flags & MEM_Str) ){ assert( pMem->enc==SQLITE_UTF8 || pMem->enc==SQLITE_UTF16BE || pMem->enc==SQLITE_UTF16LE ); /* If the string is UTF-8 encoded and nul terminated, then pMem->n | | > > > | > | 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 | if( (flags & MEM_Str) ){ assert( pMem->enc==SQLITE_UTF8 || pMem->enc==SQLITE_UTF16BE || pMem->enc==SQLITE_UTF16LE ); /* If the string is UTF-8 encoded and nul terminated, then pMem->n ** must be the length of the string. (Later:) If the database file ** has been corrupted, '\000' characters might have been inserted ** into the middle of the string. In that case, the strlen() might ** be less. */ if( pMem->enc==SQLITE_UTF8 && (flags & MEM_Term) ){ assert( strlen(pMem->z)<=pMem->n ); assert( pMem->z[pMem->n]==0 ); } } }else{ /* Cannot define a string subtype for non-string objects */ assert( (pMem->flags & (MEM_Static|MEM_Dyn|MEM_Ephem|MEM_Short))==0 ); assert( pMem->xDel==0 ); } |
︙ | ︙ |
Added test/corrupt.test.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 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 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | # 2004 August 30 # # The author disclaims copyright to this source code. In place of # a legal notice, here is a blessing: # # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** # 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. # # $Id: corrupt.test,v 1.1 2004/08/30 16:52:19 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl # Construct a large database for testing. # do_test corrupt-1.1 { execsql { BEGIN; CREATE TABLE t1(x); INSERT INTO t1 VALUES(randstr(10,100)); INSERT INTO t1 VALUES(randstr(10,100)); INSERT INTO t1 VALUES(randstr(10,100)); INSERT INTO t1 SELECT x || randstr(5,10) FROM t1; INSERT INTO t1 SELECT x || randstr(5,10) FROM t1; INSERT INTO t1 SELECT x || randstr(5,10) FROM t1; INSERT INTO t1 SELECT x || randstr(5,10) FROM t1; INSERT INTO t1 VALUES(randstr(2100,3000)); INSERT INTO t1 SELECT x || randstr(5,10) FROM t1; INSERT INTO t1 SELECT x || randstr(5,10) FROM t1; INSERT INTO t1 SELECT x || randstr(5,10) FROM t1; INSERT INTO t1 SELECT x || randstr(5,10) FROM t1; CREATE INDEX t1i1 ON t1(x); CREATE TABLE t2 AS SELECT * FROM t1; DELETE FROM t2 WHERE rowid%5!=0; COMMIT; PRAGMA integrity_check; } } {ok} # Copy a file # proc copy_file {from to} { set f [open $from] fconfigure $f -translation binary set t [open $to w] fconfigure $t -translation binary puts -nonewline $t [read $f [file size $from]] close $t close $f } # Setup for the tests. copy_file test.db test.bu set fsize [file size test.db] set junk "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" while {[string length $junk]<256} {append junk $junk} set junk [string range $junk 0 255] for {set i [expr {1*256}]} {$i<$fsize-256} {incr i 256} { set tn [expr {$i/256}] db close copy_file test.bu test.db set fd [open test.db r+] fconfigure $fd -translation binary seek $fd $i puts -nonewline $fd $junk close $fd sqlite3 db test.db do_test corrupt-2.$tn.1 { sqlite3 db test.db catchsql {SELECT count(*) FROM sqlite_master} set x {} } {} do_test corrupt-2.$tn.2 { catchsql {SELECT count(*) FROM t1} set x {} } {} do_test corrupt-2.$tn.3 { catchsql {SELECT count(*) FROM t1 WHERE x>'abcdef'} set x {} } {} do_test corrupt-2.$tn.4 { catchsql {SELECT count(*) FROM t2} set x {} } {} do_test corrupt-2.$tn.5 { catchsql {CREATE TABLE t3 AS SELECT * FROM t1} set x {} } {} do_test corrupt-2.$tn.6 { catchsql {DROP TABLE t1} set x {} } {} if {$tn==724} btree_breakpoint do_test corrupt-2.$tn.7 { catchsql {PRAGMA integrity_check} set x {} } {} } finish_test |
Changes to test/quick.test.
1 2 3 4 5 6 7 8 9 10 11 12 | # 2001 September 15 # # The author disclaims copyright to this source code. In place of # a legal notice, here is a blessing: # # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** # This file runs all tests. # | | < > > | | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | # 2001 September 15 # # The author disclaims copyright to this source code. In place of # a legal notice, here is a blessing: # # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** # This file runs all tests. # # $Id: quick.test,v 1.29 2004/08/30 16:52:19 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl rename finish_test really_finish_test proc finish_test {} {} set ISQUICK 1 set EXCLUDE { all.test btree2.test corrupt.test crash.test malloc.test memleak.test misuse.test quick.test utf16.test } if {[sqlite3 -has-codec]} { # lappend EXCLUDE \ # conflict.test } |
︙ | ︙ |