| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | #include "sqlite3.h" |
| | #include <assert.h> |
| | #include <stdio.h> |
| | #include <stdlib.h> |
| | #include <stdarg.h> |
| | #include <string.h> |
| |
|
| | typedef struct ScrubState ScrubState; |
| | typedef unsigned char u8; |
| | typedef unsigned short u16; |
| | typedef unsigned int u32; |
| |
|
| |
|
| | |
| | struct ScrubState { |
| | const char *zSrcFile; |
| | const char *zDestFile; |
| | int rcErr; |
| | char *zErr; |
| | sqlite3 *dbSrc; |
| | sqlite3_file *pSrc; |
| | sqlite3 *dbDest; |
| | sqlite3_file *pDest; |
| | u32 szPage; |
| | u32 szUsable; |
| | u32 nPage; |
| | u32 iLastPage; |
| | u8 *page1; |
| | }; |
| |
|
| | |
| | static void scrubBackupErr(ScrubState *p, const char *zFormat, ...){ |
| | va_list ap; |
| | sqlite3_free(p->zErr); |
| | va_start(ap, zFormat); |
| | p->zErr = sqlite3_vmprintf(zFormat, ap); |
| | va_end(ap); |
| | if( p->rcErr==0 ) p->rcErr = SQLITE_ERROR; |
| | } |
| |
|
| | |
| | static u8 *scrubBackupAllocPage(ScrubState *p){ |
| | u8 *pPage; |
| | if( p->rcErr ) return 0; |
| | pPage = sqlite3_malloc( p->szPage ); |
| | if( pPage==0 ) p->rcErr = SQLITE_NOMEM; |
| | return pPage; |
| | } |
| |
|
| | |
| | |
| | |
| | static u8 *scrubBackupRead(ScrubState *p, int pgno, u8 *pBuf){ |
| | int rc; |
| | sqlite3_int64 iOff; |
| | u8 *pOut = pBuf; |
| | if( p->rcErr ) return 0; |
| | if( pOut==0 ){ |
| | pOut = scrubBackupAllocPage(p); |
| | if( pOut==0 ) return 0; |
| | } |
| | iOff = (pgno-1)*(sqlite3_int64)p->szPage; |
| | rc = p->pSrc->pMethods->xRead(p->pSrc, pOut, p->szPage, iOff); |
| | if( rc!=SQLITE_OK ){ |
| | if( pBuf==0 ) sqlite3_free(pOut); |
| | pOut = 0; |
| | scrubBackupErr(p, "read failed for page %d", pgno); |
| | p->rcErr = SQLITE_IOERR; |
| | } |
| | return pOut; |
| | } |
| |
|
| | |
| | static void scrubBackupWrite(ScrubState *p, int pgno, const u8 *pData){ |
| | int rc; |
| | sqlite3_int64 iOff; |
| | if( p->rcErr ) return; |
| | iOff = (pgno-1)*(sqlite3_int64)p->szPage; |
| | rc = p->pDest->pMethods->xWrite(p->pDest, pData, p->szPage, iOff); |
| | if( rc!=SQLITE_OK ){ |
| | scrubBackupErr(p, "write failed for page %d", pgno); |
| | p->rcErr = SQLITE_IOERR; |
| | } |
| | if( (u32)pgno>p->iLastPage ) p->iLastPage = pgno; |
| | } |
| |
|
| | |
| | static sqlite3_stmt *scrubBackupPrepare( |
| | ScrubState *p, |
| | sqlite3 *db, |
| | const char *zSql |
| | ){ |
| | sqlite3_stmt *pStmt; |
| | if( p->rcErr ) return 0; |
| | p->rcErr = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); |
| | if( p->rcErr ){ |
| | scrubBackupErr(p, "SQL error \"%s\" on \"%s\"", |
| | sqlite3_errmsg(db), zSql); |
| | sqlite3_finalize(pStmt); |
| | return 0; |
| | } |
| | return pStmt; |
| | } |
| |
|
| |
|
| | |
| | static void scrubBackupOpenSrc(ScrubState *p){ |
| | sqlite3_stmt *pStmt; |
| | int rc; |
| | |
| | p->rcErr = sqlite3_open_v2(p->zSrcFile, &p->dbSrc, |
| | SQLITE_OPEN_READWRITE | |
| | SQLITE_OPEN_URI | SQLITE_OPEN_PRIVATECACHE, 0); |
| | if( p->rcErr ){ |
| | scrubBackupErr(p, "cannot open source database: %s", |
| | sqlite3_errmsg(p->dbSrc)); |
| | return; |
| | } |
| | p->rcErr = sqlite3_exec(p->dbSrc, "SELECT 1 FROM sqlite_schema; BEGIN;", |
| | 0, 0, 0); |
| | if( p->rcErr ){ |
| | scrubBackupErr(p, |
| | "cannot start a read transaction on the source database: %s", |
| | sqlite3_errmsg(p->dbSrc)); |
| | return; |
| | } |
| | rc = sqlite3_wal_checkpoint_v2(p->dbSrc, "main", SQLITE_CHECKPOINT_FULL, |
| | 0, 0); |
| | if( rc ){ |
| | scrubBackupErr(p, "cannot checkpoint the source database"); |
| | return; |
| | } |
| | pStmt = scrubBackupPrepare(p, p->dbSrc, "PRAGMA page_size"); |
| | if( pStmt==0 ) return; |
| | rc = sqlite3_step(pStmt); |
| | if( rc==SQLITE_ROW ){ |
| | p->szPage = sqlite3_column_int(pStmt, 0); |
| | }else{ |
| | scrubBackupErr(p, "unable to determine the page size"); |
| | } |
| | sqlite3_finalize(pStmt); |
| | if( p->rcErr ) return; |
| | pStmt = scrubBackupPrepare(p, p->dbSrc, "PRAGMA page_count"); |
| | if( pStmt==0 ) return; |
| | rc = sqlite3_step(pStmt); |
| | if( rc==SQLITE_ROW ){ |
| | p->nPage = sqlite3_column_int(pStmt, 0); |
| | }else{ |
| | scrubBackupErr(p, "unable to determine the size of the source database"); |
| | } |
| | sqlite3_finalize(pStmt); |
| | sqlite3_file_control(p->dbSrc, "main", SQLITE_FCNTL_FILE_POINTER, &p->pSrc); |
| | if( p->pSrc==0 || p->pSrc->pMethods==0 ){ |
| | scrubBackupErr(p, "cannot get the source file handle"); |
| | p->rcErr = SQLITE_ERROR; |
| | } |
| | } |
| |
|
| | |
| | static void scrubBackupOpenDest(ScrubState *p){ |
| | sqlite3_stmt *pStmt; |
| | int rc; |
| | char *zSql; |
| | if( p->rcErr ) return; |
| | p->rcErr = sqlite3_open_v2(p->zDestFile, &p->dbDest, |
| | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | |
| | SQLITE_OPEN_URI | SQLITE_OPEN_PRIVATECACHE, 0); |
| | if( p->rcErr ){ |
| | scrubBackupErr(p, "cannot open destination database: %s", |
| | sqlite3_errmsg(p->dbDest)); |
| | return; |
| | } |
| | zSql = sqlite3_mprintf("PRAGMA page_size(%u);", p->szPage); |
| | if( zSql==0 ){ |
| | p->rcErr = SQLITE_NOMEM; |
| | return; |
| | } |
| | p->rcErr = sqlite3_exec(p->dbDest, zSql, 0, 0, 0); |
| | sqlite3_free(zSql); |
| | if( p->rcErr ){ |
| | scrubBackupErr(p, |
| | "cannot set the page size on the destination database: %s", |
| | sqlite3_errmsg(p->dbDest)); |
| | return; |
| | } |
| | sqlite3_exec(p->dbDest, "PRAGMA journal_mode=OFF;", 0, 0, 0); |
| | p->rcErr = sqlite3_exec(p->dbDest, "BEGIN EXCLUSIVE;", 0, 0, 0); |
| | if( p->rcErr ){ |
| | scrubBackupErr(p, |
| | "cannot start a write transaction on the destination database: %s", |
| | sqlite3_errmsg(p->dbDest)); |
| | return; |
| | } |
| | pStmt = scrubBackupPrepare(p, p->dbDest, "PRAGMA page_count;"); |
| | if( pStmt==0 ) return; |
| | rc = sqlite3_step(pStmt); |
| | if( rc!=SQLITE_ROW ){ |
| | scrubBackupErr(p, "cannot measure the size of the destination"); |
| | }else if( sqlite3_column_int(pStmt, 0)>1 ){ |
| | scrubBackupErr(p, "destination database is not empty - holds %d pages", |
| | sqlite3_column_int(pStmt, 0)); |
| | } |
| | sqlite3_finalize(pStmt); |
| | sqlite3_file_control(p->dbDest, "main", SQLITE_FCNTL_FILE_POINTER, &p->pDest); |
| | if( p->pDest==0 || p->pDest->pMethods==0 ){ |
| | scrubBackupErr(p, "cannot get the destination file handle"); |
| | p->rcErr = SQLITE_ERROR; |
| | } |
| | } |
| |
|
| | |
| | static u32 scrubBackupInt32(const u8 *a){ |
| | u32 v = a[3]; |
| | v += ((u32)a[2])<<8; |
| | v += ((u32)a[1])<<16; |
| | v += ((u32)a[0])<<24; |
| | return v; |
| | } |
| |
|
| | |
| | static u32 scrubBackupInt16(const u8 *a){ |
| | return (a[0]<<8) + a[1]; |
| | } |
| |
|
| | |
| | |
| | |
| | static int scrubBackupVarint(const u8 *z, sqlite3_int64 *pVal){ |
| | sqlite3_int64 v = 0; |
| | int i; |
| | for(i=0; i<8; i++){ |
| | v = (v<<7) + (z[i]&0x7f); |
| | if( (z[i]&0x80)==0 ){ *pVal = v; return i+1; } |
| | } |
| | v = (v<<8) + (z[i]&0xff); |
| | *pVal = v; |
| | return 9; |
| | } |
| |
|
| | |
| | |
| | |
| | static int scrubBackupVarintSize(const u8 *z){ |
| | int i; |
| | for(i=0; i<8; i++){ |
| | if( (z[i]&0x80)==0 ){ return i+1; } |
| | } |
| | return 9; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | static void scrubBackupFreelist(ScrubState *p, int pgno, u32 nFree){ |
| | u8 *a, *aBuf; |
| | u32 n, mx; |
| |
|
| | if( p->rcErr ) return; |
| | aBuf = scrubBackupAllocPage(p); |
| | if( aBuf==0 ) return; |
| | |
| | while( pgno && nFree){ |
| | a = scrubBackupRead(p, pgno, aBuf); |
| | if( a==0 ) break; |
| | n = scrubBackupInt32(&a[4]); |
| | mx = p->szUsable/4 - 2; |
| | if( n<mx ){ |
| | memset(&a[n*4+8], 0, 4*(mx-n)); |
| | } |
| | scrubBackupWrite(p, pgno, a); |
| | pgno = scrubBackupInt32(a); |
| | #if 0 |
| | |
| | |
| | |
| | |
| | for(i=0; i<n && nFree; i++){ |
| | u32 iLeaf = scrubBackupInt32(&a[i*4+8]); |
| | if( aZero==0 ){ |
| | aZero = scrubBackupAllocPage(p); |
| | if( aZero==0 ){ pgno = 0; break; } |
| | memset(aZero, 0, p->szPage); |
| | } |
| | scrubBackupWrite(p, iLeaf, aZero); |
| | nFree--; |
| | } |
| | #endif |
| | } |
| | sqlite3_free(aBuf); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | static void scrubBackupOverflow(ScrubState *p, int pgno, u32 nByte){ |
| | u8 *a, *aBuf; |
| |
|
| | aBuf = scrubBackupAllocPage(p); |
| | if( aBuf==0 ) return; |
| | while( nByte>0 && pgno!=0 ){ |
| | a = scrubBackupRead(p, pgno, aBuf); |
| | if( a==0 ) break; |
| | if( nByte >= (p->szUsable)-4 ){ |
| | nByte -= (p->szUsable) - 4; |
| | }else{ |
| | u32 x = (p->szUsable - 4) - nByte; |
| | u32 i = p->szUsable - x; |
| | memset(&a[i], 0, x); |
| | nByte = 0; |
| | } |
| | scrubBackupWrite(p, pgno, a); |
| | pgno = scrubBackupInt32(a); |
| | } |
| | sqlite3_free(aBuf); |
| | } |
| | |
| |
|
| | |
| | |
| | |
| | |
| | static void scrubBackupBtree(ScrubState *p, int pgno, int iDepth){ |
| | u8 *a; |
| | u32 i, n, pc; |
| | u32 nCell; |
| | u32 nPrefix; |
| | u32 szHdr; |
| | u32 iChild; |
| | u8 *aTop; |
| | u8 *aCell; |
| | u32 x, y; |
| | int ln = 0; |
| |
|
| | |
| | if( p->rcErr ) return; |
| | if( iDepth>50 ){ |
| | scrubBackupErr(p, "corrupt: b-tree too deep at page %d", pgno); |
| | return; |
| | } |
| | if( pgno==1 ){ |
| | a = p->page1; |
| | }else{ |
| | a = scrubBackupRead(p, pgno, 0); |
| | if( a==0 ) return; |
| | } |
| | nPrefix = pgno==1 ? 100 : 0; |
| | aTop = &a[nPrefix]; |
| | szHdr = 8 + 4*(aTop[0]==0x02 || aTop[0]==0x05); |
| | aCell = aTop + szHdr; |
| | nCell = scrubBackupInt16(&aTop[3]); |
| |
|
| | |
| | |
| | x = scrubBackupInt16(&aTop[5]); |
| | if( x>p->szUsable ){ ln=__LINE__; goto btree_corrupt; } |
| | y = szHdr + nPrefix + nCell*2; |
| | if( y>x ){ ln=__LINE__; goto btree_corrupt; } |
| | if( y<x ) memset(a+y, 0, x-y); |
| |
|
| | |
| | pc = scrubBackupInt16(&aTop[1]); |
| | if( pc>0 && pc<x ){ ln=__LINE__; goto btree_corrupt; } |
| | while( pc ){ |
| | if( pc>(p->szUsable)-4 ){ ln=__LINE__; goto btree_corrupt; } |
| | n = scrubBackupInt16(&a[pc+2]); |
| | if( pc+n>(p->szUsable) ){ ln=__LINE__; goto btree_corrupt; } |
| | if( n>4 ) memset(&a[pc+4], 0, n-4); |
| | x = scrubBackupInt16(&a[pc]); |
| | if( x<pc+4 && x>0 ){ ln=__LINE__; goto btree_corrupt; } |
| | pc = x; |
| | } |
| |
|
| | |
| | scrubBackupWrite(p, pgno, a); |
| |
|
| | |
| | for(i=0; i<nCell; i++){ |
| | u32 X, M, K, nLocal; |
| | sqlite3_int64 P; |
| | pc = scrubBackupInt16(&aCell[i*2]); |
| | if( pc <= szHdr ){ ln=__LINE__; goto btree_corrupt; } |
| | if( pc > p->szUsable-3 ){ ln=__LINE__; goto btree_corrupt; } |
| | if( aTop[0]==0x05 || aTop[0]==0x02 ){ |
| | if( pc+4 > p->szUsable ){ ln=__LINE__; goto btree_corrupt; } |
| | iChild = scrubBackupInt32(&a[pc]); |
| | pc += 4; |
| | scrubBackupBtree(p, iChild, iDepth+1); |
| | if( aTop[0]==0x05 ) continue; |
| | } |
| | pc += scrubBackupVarint(&a[pc], &P); |
| | if( pc >= p->szUsable ){ ln=__LINE__; goto btree_corrupt; } |
| | if( aTop[0]==0x0d ){ |
| | X = p->szUsable - 35; |
| | }else{ |
| | X = ((p->szUsable - 12)*64/255) - 23; |
| | } |
| | if( P<=X ){ |
| | |
| | continue; |
| | } |
| | M = ((p->szUsable - 12)*32/255)-23; |
| | K = M + ((P-M)%(p->szUsable-4)); |
| | if( aTop[0]==0x0d ){ |
| | pc += scrubBackupVarintSize(&a[pc]); |
| | if( pc > (p->szUsable-4) ){ ln=__LINE__; goto btree_corrupt; } |
| | } |
| | nLocal = K<=X ? K : M; |
| | if( pc+nLocal > p->szUsable-4 ){ ln=__LINE__; goto btree_corrupt; } |
| | iChild = scrubBackupInt32(&a[pc+nLocal]); |
| | scrubBackupOverflow(p, iChild, (u32)(P-nLocal)); |
| | } |
| |
|
| | |
| | if( aTop[0]==0x05 || aTop[0]==0x02 ){ |
| | iChild = scrubBackupInt32(&aTop[8]); |
| | scrubBackupBtree(p, iChild, iDepth+1); |
| | } |
| |
|
| | |
| | if( pgno>1 ) sqlite3_free(a); |
| | return; |
| |
|
| | btree_corrupt: |
| | scrubBackupErr(p, "corruption on page %d of source database (errid=%d)", |
| | pgno, ln); |
| | if( pgno>1 ) sqlite3_free(a); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | static void scrubBackupPtrmap(ScrubState *p){ |
| | u32 pgno = 2; |
| | u32 J = p->szUsable/5; |
| | u32 iLock = (1073742335/p->szPage)+1; |
| | u8 *a, *pBuf; |
| | if( p->rcErr ) return; |
| | pBuf = scrubBackupAllocPage(p); |
| | if( pBuf==0 ) return; |
| | while( pgno<=p->nPage ){ |
| | a = scrubBackupRead(p, pgno, pBuf); |
| | if( a==0 ) break; |
| | scrubBackupWrite(p, pgno, a); |
| | pgno += J+1; |
| | if( pgno==iLock ) pgno++; |
| | } |
| | sqlite3_free(pBuf); |
| | } |
| |
|
| | int sqlite3_scrub_backup( |
| | const char *zSrcFile, |
| | const char *zDestFile, |
| | char **pzErr |
| | ){ |
| | ScrubState s; |
| | u32 n, i; |
| | sqlite3_stmt *pStmt; |
| |
|
| | memset(&s, 0, sizeof(s)); |
| | s.zSrcFile = zSrcFile; |
| | s.zDestFile = zDestFile; |
| |
|
| | |
| | scrubBackupOpenSrc(&s); |
| | scrubBackupOpenDest(&s); |
| |
|
| | |
| | s.page1 = scrubBackupRead(&s, 1, 0); |
| | if( s.page1==0 ) goto scrub_abort; |
| | s.szUsable = s.szPage - s.page1[20]; |
| |
|
| | |
| | n = scrubBackupInt32(&s.page1[36]); |
| | i = scrubBackupInt32(&s.page1[32]); |
| | if( n ) scrubBackupFreelist(&s, i, n); |
| |
|
| | |
| | n = scrubBackupInt32(&s.page1[52]); |
| | if( n ) scrubBackupPtrmap(&s); |
| |
|
| | |
| | scrubBackupBtree(&s, 1, 0); |
| | pStmt = scrubBackupPrepare(&s, s.dbSrc, |
| | "SELECT rootpage FROM sqlite_schema WHERE coalesce(rootpage,0)>0"); |
| | if( pStmt==0 ) goto scrub_abort; |
| | while( sqlite3_step(pStmt)==SQLITE_ROW ){ |
| | i = (u32)sqlite3_column_int(pStmt, 0); |
| | scrubBackupBtree(&s, i, 0); |
| | } |
| | sqlite3_finalize(pStmt); |
| |
|
| | |
| | |
| | |
| | |
| | |
| | if( s.iLastPage<s.nPage ){ |
| | u8 *aZero = scrubBackupAllocPage(&s); |
| | if( aZero ){ |
| | memset(aZero, 0, s.szPage); |
| | scrubBackupWrite(&s, s.nPage, aZero); |
| | sqlite3_free(aZero); |
| | } |
| | } |
| |
|
| | scrub_abort: |
| | |
| | |
| | sqlite3_close(s.dbDest); |
| |
|
| | |
| | sqlite3_exec(s.dbSrc, "COMMIT;", 0, 0, 0); |
| | sqlite3_close(s.dbSrc); |
| | sqlite3_free(s.page1); |
| | if( pzErr ){ |
| | *pzErr = s.zErr; |
| | }else{ |
| | sqlite3_free(s.zErr); |
| | } |
| | return s.rcErr; |
| | } |
| |
|
| | #ifdef SCRUB_STANDALONE |
| | |
| | static void errorLogCallback(void *pNotUsed, int iErr, const char *zMsg){ |
| | const char *zType; |
| | switch( iErr&0xff ){ |
| | case SQLITE_WARNING: zType = "WARNING"; break; |
| | case SQLITE_NOTICE: zType = "NOTICE"; break; |
| | default: zType = "ERROR"; break; |
| | } |
| | fprintf(stderr, "%s: %s\n", zType, zMsg); |
| | } |
| |
|
| | |
| | int main(int argc, char **argv){ |
| | char *zErr = 0; |
| | int rc; |
| | if( argc!=3 ){ |
| | fprintf(stderr,"Usage: %s SOURCE DESTINATION\n", argv[0]); |
| | exit(1); |
| | } |
| | sqlite3_config(SQLITE_CONFIG_LOG, errorLogCallback, 0); |
| | rc = sqlite3_scrub_backup(argv[1], argv[2], &zErr); |
| | if( rc==SQLITE_NOMEM ){ |
| | fprintf(stderr, "%s: out of memory\n", argv[0]); |
| | exit(1); |
| | } |
| | if( zErr ){ |
| | fprintf(stderr, "%s: %s\n", argv[0], zErr); |
| | sqlite3_free(zErr); |
| | exit(1); |
| | } |
| | return 0; |
| | } |
| | #endif |
| |
|