#include "sqlite3recover.h"
#include <assert.h>
#include <string.h>
#ifndef SQLITE_OMIT_VIRTUALTABLE
#ifdef _WIN32
__declspec(dllexport)
#endif
int sqlite3_dbdata_init(sqlite3*, char**, const sqlite3_api_routines*);
typedef unsigned int u32;
typedef unsigned char u8;
typedef sqlite3_int64 i64;
typedef struct RecoverTable RecoverTable;
typedef struct RecoverColumn RecoverColumn;
struct RecoverTable {
u32 iRoot;
char *zTab;
int nCol;
RecoverColumn *aCol;
int bIntkey;
int iRowidBind;
RecoverTable *pNext;
};
struct RecoverColumn {
int iField;
int iBind;
int bIPK;
char *zCol;
int eHidden;
};
#define RECOVER_EHIDDEN_NONE 0
#define RECOVER_EHIDDEN_HIDDEN 1
#define RECOVER_EHIDDEN_VIRTUAL 2
#define RECOVER_EHIDDEN_STORED 3
typedef struct RecoverBitmap RecoverBitmap;
struct RecoverBitmap {
i64 nPg;
u32 aElem[1];
};
typedef struct RecoverStateW1 RecoverStateW1;
struct RecoverStateW1 {
sqlite3_stmt *pTbls;
sqlite3_stmt *pSel;
sqlite3_stmt *pInsert;
int nInsert;
RecoverTable *pTab;
int nMax;
sqlite3_value **apVal;
int nVal;
int bHaveRowid;
i64 iRowid;
i64 iPrevPage;
int iPrevCell;
};
typedef struct RecoverStateLAF RecoverStateLAF;
struct RecoverStateLAF {
RecoverBitmap *pUsed;
i64 nPg;
sqlite3_stmt *pAllAndParent;
sqlite3_stmt *pMapInsert;
sqlite3_stmt *pMaxField;
sqlite3_stmt *pUsedPages;
sqlite3_stmt *pFindRoot;
sqlite3_stmt *pInsert;
sqlite3_stmt *pAllPage;
sqlite3_stmt *pPageData;
sqlite3_value **apVal;
int nMaxField;
};
struct sqlite3_recover {
sqlite3 *dbIn;
char *zDb;
char *zUri;
void *pSqlCtx;
int (*xSql)(void*,const char*);
char *zStateDb;
char *zLostAndFound;
int bFreelistCorrupt;
int bRecoverRowid;
int bSlowIndexes;
int pgsz;
int detected_pgsz;
int nReserve;
u8 *pPage1Disk;
u8 *pPage1Cache;
int errCode;
char *zErrMsg;
int eState;
int bCloseTransaction;
RecoverStateW1 w1;
RecoverStateLAF laf;
sqlite3 *dbOut;
sqlite3_stmt *pGetPage;
RecoverTable *pTblList;
};
#define RECOVER_STATE_INIT 0
#define RECOVER_STATE_WRITING 1
#define RECOVER_STATE_LOSTANDFOUND1 2
#define RECOVER_STATE_LOSTANDFOUND2 3
#define RECOVER_STATE_LOSTANDFOUND3 4
#define RECOVER_STATE_SCHEMA2 5
#define RECOVER_STATE_DONE 6
typedef struct RecoverGlobal RecoverGlobal;
struct RecoverGlobal {
const sqlite3_io_methods *pMethods;
sqlite3_recover *p;
};
static RecoverGlobal recover_g;
#define RECOVER_MUTEX_ID SQLITE_MUTEX_STATIC_APP2
#define RECOVER_ROWID_DEFAULT 1
#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0
# define recoverEnterMutex()
# define recoverLeaveMutex()
#else
static void recoverEnterMutex(void){
sqlite3_mutex_enter(sqlite3_mutex_alloc(RECOVER_MUTEX_ID));
}
static void recoverLeaveMutex(void){
sqlite3_mutex_leave(sqlite3_mutex_alloc(RECOVER_MUTEX_ID));
}
#endif
#if SQLITE_THREADSAFE+0>=1 && defined(SQLITE_DEBUG)
static void recoverAssertMutexHeld(void){
assert( sqlite3_mutex_held(sqlite3_mutex_alloc(RECOVER_MUTEX_ID)) );
}
#else
# define recoverAssertMutexHeld()
#endif
static int recoverStrlen(const char *zStr){
if( zStr==0 ) return 0;
return (int)(strlen(zStr)&0x7fffffff);
}
static void *recoverMalloc(sqlite3_recover *p, i64 nByte){
void *pRet = 0;
assert( nByte>0 );
if( p->errCode==SQLITE_OK ){
pRet = sqlite3_malloc64(nByte);
if( pRet ){
memset(pRet, 0, nByte);
}else{
p->errCode = SQLITE_NOMEM;
}
}
return pRet;
}
static int recoverError(
sqlite3_recover *p,
int errCode,
const char *zFmt, ...
){
char *z = 0;
va_list ap;
va_start(ap, zFmt);
if( zFmt ){
z = sqlite3_vmprintf(zFmt, ap);
va_end(ap);
}
sqlite3_free(p->zErrMsg);
p->zErrMsg = z;
p->errCode = errCode;
return errCode;
}
static RecoverBitmap *recoverBitmapAlloc(sqlite3_recover *p, i64 nPg){
int nElem = (nPg+1+31) / 32;
int nByte = sizeof(RecoverBitmap) + nElem*sizeof(u32);
RecoverBitmap *pRet = (RecoverBitmap*)recoverMalloc(p, nByte);
if( pRet ){
pRet->nPg = nPg;
}
return pRet;
}
static void recoverBitmapFree(RecoverBitmap *pMap){
sqlite3_free(pMap);
}
static void recoverBitmapSet(RecoverBitmap *pMap, i64 iPg){
if( iPg<=pMap->nPg ){
int iElem = (iPg / 32);
int iBit = (iPg % 32);
pMap->aElem[iElem] |= (((u32)1) << iBit);
}
}
static int recoverBitmapQuery(RecoverBitmap *pMap, i64 iPg){
int ret = 1;
if( iPg<=pMap->nPg && iPg>0 ){
int iElem = (iPg / 32);
int iBit = (iPg % 32);
ret = (pMap->aElem[iElem] & (((u32)1) << iBit)) ? 1 : 0;
}
return ret;
}
static int recoverDbError(sqlite3_recover *p, sqlite3 *db){
return recoverError(p, sqlite3_errcode(db), "%s", sqlite3_errmsg(db));
}
static sqlite3_stmt *recoverPrepare(
sqlite3_recover *p,
sqlite3 *db,
const char *zSql
){
sqlite3_stmt *pStmt = 0;
if( p->errCode==SQLITE_OK ){
if( sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0) ){
recoverDbError(p, db);
}
}
return pStmt;
}
static sqlite3_stmt *recoverPreparePrintf(
sqlite3_recover *p,
sqlite3 *db,
const char *zFmt, ...
){
sqlite3_stmt *pStmt = 0;
if( p->errCode==SQLITE_OK ){
va_list ap;
char *z;
va_start(ap, zFmt);
z = sqlite3_vmprintf(zFmt, ap);
va_end(ap);
if( z==0 ){
p->errCode = SQLITE_NOMEM;
}else{
pStmt = recoverPrepare(p, db, z);
sqlite3_free(z);
}
}
return pStmt;
}
static sqlite3_stmt *recoverReset(sqlite3_recover *p, sqlite3_stmt *pStmt){
int rc = sqlite3_reset(pStmt);
if( rc!=SQLITE_OK && rc!=SQLITE_CONSTRAINT && p->errCode==SQLITE_OK ){
recoverDbError(p, sqlite3_db_handle(pStmt));
}
return pStmt;
}
static void recoverFinalize(sqlite3_recover *p, sqlite3_stmt *pStmt){
sqlite3 *db = sqlite3_db_handle(pStmt);
int rc = sqlite3_finalize(pStmt);
if( rc!=SQLITE_OK && p->errCode==SQLITE_OK ){
recoverDbError(p, db);
}
}
static int recoverExec(sqlite3_recover *p, sqlite3 *db, const char *zSql){
if( p->errCode==SQLITE_OK ){
int rc = sqlite3_exec(db, zSql, 0, 0, 0);
if( rc ){
recoverDbError(p, db);
}
}
return p->errCode;
}
static void recoverBindValue(
sqlite3_recover *p,
sqlite3_stmt *pStmt,
int iBind,
sqlite3_value *pVal
){
if( p->errCode==SQLITE_OK ){
int rc = sqlite3_bind_value(pStmt, iBind, pVal);
if( rc ) recoverError(p, rc, 0);
}
}
static char *recoverMPrintf(sqlite3_recover *p, const char *zFmt, ...){
va_list ap;
char *z;
va_start(ap, zFmt);
z = sqlite3_vmprintf(zFmt, ap);
va_end(ap);
if( p->errCode==SQLITE_OK ){
if( z==0 ) p->errCode = SQLITE_NOMEM;
}else{
sqlite3_free(z);
z = 0;
}
return z;
}
static i64 recoverPageCount(sqlite3_recover *p){
i64 nPg = 0;
if( p->errCode==SQLITE_OK ){
sqlite3_stmt *pStmt = 0;
pStmt = recoverPreparePrintf(p, p->dbIn, "PRAGMA %Q.page_count", p->zDb);
if( pStmt ){
sqlite3_step(pStmt);
nPg = sqlite3_column_int64(pStmt, 0);
}
recoverFinalize(p, pStmt);
}
return nPg;
}
static void recoverReadI32(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
const unsigned char *pBlob;
int nBlob;
int iInt;
assert( argc==2 );
nBlob = sqlite3_value_bytes(argv[0]);
pBlob = (const unsigned char*)sqlite3_value_blob(argv[0]);
iInt = sqlite3_value_int(argv[1]) & 0xFFFF;
if( (iInt+1)*4<=nBlob ){
const unsigned char *a = &pBlob[iInt*4];
i64 iVal = ((i64)a[0]<<24)
+ ((i64)a[1]<<16)
+ ((i64)a[2]<< 8)
+ ((i64)a[3]<< 0);
sqlite3_result_int64(context, iVal);
}
}
static void recoverPageIsUsed(
sqlite3_context *pCtx,
int nArg,
sqlite3_value **apArg
){
sqlite3_recover *p = (sqlite3_recover*)sqlite3_user_data(pCtx);
i64 pgno = sqlite3_value_int64(apArg[0]);
assert( nArg==1 );
sqlite3_result_int(pCtx, recoverBitmapQuery(p->laf.pUsed, pgno));
}
static void recoverGetPage(
sqlite3_context *pCtx,
int nArg,
sqlite3_value **apArg
){
sqlite3_recover *p = (sqlite3_recover*)sqlite3_user_data(pCtx);
i64 pgno = sqlite3_value_int64(apArg[0]);
sqlite3_stmt *pStmt = 0;
assert( nArg==1 );
if( pgno==0 ){
i64 nPg = recoverPageCount(p);
sqlite3_result_int64(pCtx, nPg);
return;
}else{
if( p->pGetPage==0 ){
pStmt = p->pGetPage = recoverPreparePrintf(
p, p->dbIn, "SELECT data FROM sqlite_dbpage(%Q) WHERE pgno=?", p->zDb
);
}else if( p->errCode==SQLITE_OK ){
pStmt = p->pGetPage;
}
if( pStmt ){
sqlite3_bind_int64(pStmt, 1, pgno);
if( SQLITE_ROW==sqlite3_step(pStmt) ){
const u8 *aPg;
int nPg;
assert( p->errCode==SQLITE_OK );
aPg = sqlite3_column_blob(pStmt, 0);
nPg = sqlite3_column_bytes(pStmt, 0);
if( pgno==1 && nPg==p->pgsz && 0==memcmp(p->pPage1Cache, aPg, nPg) ){
aPg = p->pPage1Disk;
}
sqlite3_result_blob(pCtx, aPg, nPg-p->nReserve, SQLITE_TRANSIENT);
}
recoverReset(p, pStmt);
}
}
if( p->errCode ){
if( p->zErrMsg ) sqlite3_result_error(pCtx, p->zErrMsg, -1);
sqlite3_result_error_code(pCtx, p->errCode);
}
}
static const char *recoverUnusedString(
const char *z,
const char *zA, const char *zB,
char *zBuf
){
unsigned i = 0;
if( strstr(z, zA)==0 ) return zA;
if( strstr(z, zB)==0 ) return zB;
do{
sqlite3_snprintf(20,zBuf,"(%s%u)", zA, i++);
}while( strstr(z,zBuf)!=0 );
return zBuf;
}
static void recoverEscapeCrnl(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
const char *zText = (const char*)sqlite3_value_text(argv[0]);
(void)argc;
if( zText && zText[0]=='\'' ){
int nText = sqlite3_value_bytes(argv[0]);
int i;
char zBuf1[20];
char zBuf2[20];
const char *zNL = 0;
const char *zCR = 0;
int nCR = 0;
int nNL = 0;
for(i=0; zText[i]; i++){
if( zNL==0 && zText[i]=='\n' ){
zNL = recoverUnusedString(zText, "\\n", "\\012", zBuf1);
nNL = (int)strlen(zNL);
}
if( zCR==0 && zText[i]=='\r' ){
zCR = recoverUnusedString(zText, "\\r", "\\015", zBuf2);
nCR = (int)strlen(zCR);
}
}
if( zNL || zCR ){
int iOut = 0;
i64 nMax = (nNL > nCR) ? nNL : nCR;
i64 nAlloc = nMax * nText + (nMax+64)*2;
char *zOut = (char*)sqlite3_malloc64(nAlloc);
if( zOut==0 ){
sqlite3_result_error_nomem(context);
return;
}
if( zNL && zCR ){
memcpy(&zOut[iOut], "replace(replace(", 16);
iOut += 16;
}else{
memcpy(&zOut[iOut], "replace(", 8);
iOut += 8;
}
for(i=0; zText[i]; i++){
if( zText[i]=='\n' ){
memcpy(&zOut[iOut], zNL, nNL);
iOut += nNL;
}else if( zText[i]=='\r' ){
memcpy(&zOut[iOut], zCR, nCR);
iOut += nCR;
}else{
zOut[iOut] = zText[i];
iOut++;
}
}
if( zNL ){
memcpy(&zOut[iOut], ",'", 2); iOut += 2;
memcpy(&zOut[iOut], zNL, nNL); iOut += nNL;
memcpy(&zOut[iOut], "', char(10))", 12); iOut += 12;
}
if( zCR ){
memcpy(&zOut[iOut], ",'", 2); iOut += 2;
memcpy(&zOut[iOut], zCR, nCR); iOut += nCR;
memcpy(&zOut[iOut], "', char(13))", 12); iOut += 12;
}
sqlite3_result_text(context, zOut, iOut, SQLITE_TRANSIENT);
sqlite3_free(zOut);
return;
}
}
sqlite3_result_value(context, argv[0]);
}
static int recoverCacheSchema(sqlite3_recover *p){
return recoverExec(p, p->dbOut,
"WITH RECURSIVE pages(p) AS ("
" SELECT 1"
" UNION"
" SELECT child FROM sqlite_dbptr('getpage()'), pages WHERE pgno=p"
")"
"INSERT INTO recovery.schema SELECT"
" max(CASE WHEN field=0 THEN value ELSE NULL END),"
" max(CASE WHEN field=1 THEN value ELSE NULL END),"
" max(CASE WHEN field=2 THEN value ELSE NULL END),"
" max(CASE WHEN field=3 THEN value ELSE NULL END),"
" max(CASE WHEN field=4 THEN value ELSE NULL END)"
"FROM sqlite_dbdata('getpage()') WHERE pgno IN ("
" SELECT p FROM pages"
") GROUP BY pgno, cell"
);
}
static void recoverSqlCallback(sqlite3_recover *p, const char *zSql){
if( p->errCode==SQLITE_OK && p->xSql ){
int res = p->xSql(p->pSqlCtx, zSql);
if( res ){
recoverError(p, SQLITE_ERROR, "callback returned an error - %d", res);
}
}
}
static void recoverTransferSettings(sqlite3_recover *p){
const char *aPragma[] = {
"encoding",
"page_size",
"auto_vacuum",
"user_version",
"application_id"
};
int ii;
if( p->errCode==SQLITE_OK ){
sqlite3 *db2 = 0;
int rc = sqlite3_open("", &db2);
if( rc!=SQLITE_OK ){
recoverDbError(p, db2);
return;
}
for(ii=0; ii<(int)(sizeof(aPragma)/sizeof(aPragma[0])); ii++){
const char *zPrag = aPragma[ii];
sqlite3_stmt *p1 = 0;
p1 = recoverPreparePrintf(p, p->dbIn, "PRAGMA %Q.%s", p->zDb, zPrag);
if( p->errCode==SQLITE_OK && sqlite3_step(p1)==SQLITE_ROW ){
const char *zArg = (const char*)sqlite3_column_text(p1, 0);
char *z2 = recoverMPrintf(p, "PRAGMA %s = %Q", zPrag, zArg);
recoverSqlCallback(p, z2);
recoverExec(p, db2, z2);
sqlite3_free(z2);
if( zArg==0 ){
recoverError(p, SQLITE_NOMEM, 0);
}
}
recoverFinalize(p, p1);
}
recoverExec(p, db2, "CREATE TABLE t1(a); DROP TABLE t1;");
if( p->errCode==SQLITE_OK ){
sqlite3 *db = p->dbOut;
sqlite3_backup *pBackup = sqlite3_backup_init(db, "main", db2, "main");
if( pBackup ){
sqlite3_backup_step(pBackup, -1);
p->errCode = sqlite3_backup_finish(pBackup);
}else{
recoverDbError(p, db);
}
}
sqlite3_close(db2);
}
}
static int recoverOpenOutput(sqlite3_recover *p){
struct Func {
const char *zName;
int nArg;
void (*xFunc)(sqlite3_context*,int,sqlite3_value **);
} aFunc[] = {
{ "getpage", 1, recoverGetPage },
{ "page_is_used", 1, recoverPageIsUsed },
{ "read_i32", 2, recoverReadI32 },
{ "escape_crnl", 1, recoverEscapeCrnl },
};
const int flags = SQLITE_OPEN_URI|SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE;
sqlite3 *db = 0;
int ii;
assert( p->dbOut==0 );
if( sqlite3_open_v2(p->zUri, &db, flags, 0) ){
recoverDbError(p, db);
}
if( p->errCode==SQLITE_OK ){
p->errCode = sqlite3_dbdata_init(db, 0, 0);
}
for(ii=0;
p->errCode==SQLITE_OK && ii<(int)(sizeof(aFunc)/sizeof(aFunc[0]));
ii++){
p->errCode = sqlite3_create_function(db, aFunc[ii].zName,
aFunc[ii].nArg, SQLITE_UTF8, (void*)p, aFunc[ii].xFunc, 0, 0
);
}
p->dbOut = db;
return p->errCode;
}
static void recoverOpenRecovery(sqlite3_recover *p){
char *zSql = recoverMPrintf(p, "ATTACH %Q AS recovery;", p->zStateDb);
recoverExec(p, p->dbOut, zSql);
recoverExec(p, p->dbOut,
"PRAGMA writable_schema = 1;"
"CREATE TABLE recovery.map(pgno INTEGER PRIMARY KEY, parent INT);"
"CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);"
);
sqlite3_free(zSql);
}
static void recoverAddTable(
sqlite3_recover *p,
const char *zName,
i64 iRoot
){
sqlite3_stmt *pStmt = recoverPreparePrintf(p, p->dbOut,
"PRAGMA table_xinfo(%Q)", zName
);
if( pStmt ){
int iPk = -1;
int iBind = 1;
RecoverTable *pNew = 0;
int nCol = 0;
int nName = recoverStrlen(zName);
int nByte = 0;
while( sqlite3_step(pStmt)==SQLITE_ROW ){
nCol++;
nByte += (sqlite3_column_bytes(pStmt, 1)+1);
}
nByte += sizeof(RecoverTable) + nCol*sizeof(RecoverColumn) + nName+1;
recoverReset(p, pStmt);
pNew = recoverMalloc(p, nByte);
if( pNew ){
int i = 0;
int iField = 0;
char *csr = 0;
pNew->aCol = (RecoverColumn*)&pNew[1];
pNew->zTab = csr = (char*)&pNew->aCol[nCol];
pNew->nCol = nCol;
pNew->iRoot = iRoot;
memcpy(csr, zName, nName);
csr += nName+1;
for(i=0; sqlite3_step(pStmt)==SQLITE_ROW; i++){
int iPKF = sqlite3_column_int(pStmt, 5);
int n = sqlite3_column_bytes(pStmt, 1);
const char *z = (const char*)sqlite3_column_text(pStmt, 1);
const char *zType = (const char*)sqlite3_column_text(pStmt, 2);
int eHidden = sqlite3_column_int(pStmt, 6);
if( iPk==-1 && iPKF==1 && !sqlite3_stricmp("integer", zType) ) iPk = i;
if( iPKF>1 ) iPk = -2;
pNew->aCol[i].zCol = csr;
pNew->aCol[i].eHidden = eHidden;
if( eHidden==RECOVER_EHIDDEN_VIRTUAL ){
pNew->aCol[i].iField = -1;
}else{
pNew->aCol[i].iField = iField++;
}
if( eHidden!=RECOVER_EHIDDEN_VIRTUAL
&& eHidden!=RECOVER_EHIDDEN_STORED
){
pNew->aCol[i].iBind = iBind++;
}
memcpy(csr, z, n);
csr += (n+1);
}
pNew->pNext = p->pTblList;
p->pTblList = pNew;
pNew->bIntkey = 1;
}
recoverFinalize(p, pStmt);
pStmt = recoverPreparePrintf(p, p->dbOut, "PRAGMA index_xinfo(%Q)", zName);
while( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){
int iField = sqlite3_column_int(pStmt, 0);
int iCol = sqlite3_column_int(pStmt, 1);
assert( iCol<pNew->nCol );
pNew->aCol[iCol].iField = iField;
pNew->bIntkey = 0;
iPk = -2;
}
recoverFinalize(p, pStmt);
if( p->errCode==SQLITE_OK ){
if( iPk>=0 ){
pNew->aCol[iPk].bIPK = 1;
}else if( pNew->bIntkey ){
pNew->iRowidBind = iBind++;
}
}
}
}
static int recoverWriteSchema1(sqlite3_recover *p){
sqlite3_stmt *pSelect = 0;
sqlite3_stmt *pTblname = 0;
pSelect = recoverPrepare(p, p->dbOut,
"WITH dbschema(rootpage, name, sql, tbl, isVirtual, isIndex) AS ("
" SELECT rootpage, name, sql, "
" type='table', "
" sql LIKE 'create virtual%',"
" (type='index' AND (sql LIKE '%unique%' OR ?1))"
" FROM recovery.schema"
")"
"SELECT rootpage, tbl, isVirtual, name, sql"
" FROM dbschema "
" WHERE tbl OR isIndex"
" ORDER BY tbl DESC, name=='sqlite_sequence' DESC"
);
pTblname = recoverPrepare(p, p->dbOut,
"SELECT name FROM sqlite_schema "
"WHERE type='table' ORDER BY rowid DESC LIMIT 1"
);
if( pSelect ){
sqlite3_bind_int(pSelect, 1, p->bSlowIndexes);
while( sqlite3_step(pSelect)==SQLITE_ROW ){
i64 iRoot = sqlite3_column_int64(pSelect, 0);
int bTable = sqlite3_column_int(pSelect, 1);
int bVirtual = sqlite3_column_int(pSelect, 2);
const char *zName = (const char*)sqlite3_column_text(pSelect, 3);
const char *zSql = (const char*)sqlite3_column_text(pSelect, 4);
char *zFree = 0;
int rc = SQLITE_OK;
if( bVirtual ){
zSql = (const char*)(zFree = recoverMPrintf(p,
"INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)",
zName, zName, zSql
));
}
rc = sqlite3_exec(p->dbOut, zSql, 0, 0, 0);
if( rc==SQLITE_OK ){
recoverSqlCallback(p, zSql);
if( bTable && !bVirtual ){
if( SQLITE_ROW==sqlite3_step(pTblname) ){
const char *zTbl = (const char*)sqlite3_column_text(pTblname, 0);
recoverAddTable(p, zTbl, iRoot);
}
recoverReset(p, pTblname);
}
}else if( rc!=SQLITE_ERROR ){
recoverDbError(p, p->dbOut);
}
sqlite3_free(zFree);
}
}
recoverFinalize(p, pSelect);
recoverFinalize(p, pTblname);
return p->errCode;
}
static int recoverWriteSchema2(sqlite3_recover *p){
sqlite3_stmt *pSelect = 0;
pSelect = recoverPrepare(p, p->dbOut,
p->bSlowIndexes ?
"SELECT rootpage, sql FROM recovery.schema "
" WHERE type!='table' AND type!='index'"
:
"SELECT rootpage, sql FROM recovery.schema "
" WHERE type!='table' AND (type!='index' OR sql NOT LIKE '%unique%')"
);
if( pSelect ){
while( sqlite3_step(pSelect)==SQLITE_ROW ){
const char *zSql = (const char*)sqlite3_column_text(pSelect, 1);
int rc = sqlite3_exec(p->dbOut, zSql, 0, 0, 0);
if( rc==SQLITE_OK ){
recoverSqlCallback(p, zSql);
}else if( rc!=SQLITE_ERROR ){
recoverDbError(p, p->dbOut);
}
}
}
recoverFinalize(p, pSelect);
return p->errCode;
}
static sqlite3_stmt *recoverInsertStmt(
sqlite3_recover *p,
RecoverTable *pTab,
int nField
){
sqlite3_stmt *pRet = 0;
const char *zSep = "";
const char *zSqlSep = "";
char *zSql = 0;
char *zFinal = 0;
char *zBind = 0;
int ii;
int bSql = p->xSql ? 1 : 0;
if( nField<=0 ) return 0;
assert( nField<=pTab->nCol );
zSql = recoverMPrintf(p, "INSERT OR IGNORE INTO %Q(", pTab->zTab);
if( pTab->iRowidBind ){
assert( pTab->bIntkey );
zSql = recoverMPrintf(p, "%z_rowid_", zSql);
if( bSql ){
zBind = recoverMPrintf(p, "%zquote(?%d)", zBind, pTab->iRowidBind);
}else{
zBind = recoverMPrintf(p, "%z?%d", zBind, pTab->iRowidBind);
}
zSqlSep = "||', '||";
zSep = ", ";
}
for(ii=0; ii<nField; ii++){
int eHidden = pTab->aCol[ii].eHidden;
if( eHidden!=RECOVER_EHIDDEN_VIRTUAL
&& eHidden!=RECOVER_EHIDDEN_STORED
){
assert( pTab->aCol[ii].iField>=0 && pTab->aCol[ii].iBind>=1 );
zSql = recoverMPrintf(p, "%z%s%Q", zSql, zSep, pTab->aCol[ii].zCol);
if( bSql ){
zBind = recoverMPrintf(p,
"%z%sescape_crnl(quote(?%d))", zBind, zSqlSep, pTab->aCol[ii].iBind
);
zSqlSep = "||', '||";
}else{
zBind = recoverMPrintf(p, "%z%s?%d", zBind, zSep, pTab->aCol[ii].iBind);
}
zSep = ", ";
}
}
if( bSql ){
zFinal = recoverMPrintf(p, "SELECT %Q || ') VALUES (' || %s || ')'",
zSql, zBind
);
}else{
zFinal = recoverMPrintf(p, "%s) VALUES (%s)", zSql, zBind);
}
pRet = recoverPrepare(p, p->dbOut, zFinal);
sqlite3_free(zSql);
sqlite3_free(zBind);
sqlite3_free(zFinal);
return pRet;
}
static RecoverTable *recoverFindTable(sqlite3_recover *p, u32 iRoot){
RecoverTable *pRet = 0;
for(pRet=p->pTblList; pRet && pRet->iRoot!=iRoot; pRet=pRet->pNext);
return pRet;
}
static char *recoverLostAndFoundCreate(
sqlite3_recover *p,
int nField
){
char *zTbl = 0;
sqlite3_stmt *pProbe = 0;
int ii = 0;
pProbe = recoverPrepare(p, p->dbOut,
"SELECT 1 FROM sqlite_schema WHERE name=?"
);
for(ii=-1; zTbl==0 && p->errCode==SQLITE_OK && ii<1000; ii++){
int bFail = 0;
if( ii<0 ){
zTbl = recoverMPrintf(p, "%s", p->zLostAndFound);
}else{
zTbl = recoverMPrintf(p, "%s_%d", p->zLostAndFound, ii);
}
if( p->errCode==SQLITE_OK ){
sqlite3_bind_text(pProbe, 1, zTbl, -1, SQLITE_STATIC);
if( SQLITE_ROW==sqlite3_step(pProbe) ){
bFail = 1;
}
recoverReset(p, pProbe);
}
if( bFail ){
sqlite3_clear_bindings(pProbe);
sqlite3_free(zTbl);
zTbl = 0;
}
}
recoverFinalize(p, pProbe);
if( zTbl ){
const char *zSep = 0;
char *zField = 0;
char *zSql = 0;
zSep = "rootpgno INTEGER, pgno INTEGER, nfield INTEGER, id INTEGER, ";
for(ii=0; p->errCode==SQLITE_OK && ii<nField; ii++){
zField = recoverMPrintf(p, "%z%sc%d", zField, zSep, ii);
zSep = ", ";
}
zSql = recoverMPrintf(p, "CREATE TABLE %s(%s)", zTbl, zField);
sqlite3_free(zField);
recoverExec(p, p->dbOut, zSql);
recoverSqlCallback(p, zSql);
sqlite3_free(zSql);
}else if( p->errCode==SQLITE_OK ){
recoverError(
p, SQLITE_ERROR, "failed to create %s output table", p->zLostAndFound
);
}
return zTbl;
}
static sqlite3_stmt *recoverLostAndFoundInsert(
sqlite3_recover *p,
const char *zTab,
int nField
){
int nTotal = nField + 4;
int ii;
char *zBind = 0;
sqlite3_stmt *pRet = 0;
if( p->xSql==0 ){
for(ii=0; ii<nTotal; ii++){
zBind = recoverMPrintf(p, "%z%s?", zBind, zBind?", ":"", ii);
}
pRet = recoverPreparePrintf(
p, p->dbOut, "INSERT INTO %s VALUES(%s)", zTab, zBind
);
}else{
const char *zSep = "";
for(ii=0; ii<nTotal; ii++){
zBind = recoverMPrintf(p, "%z%squote(?)", zBind, zSep);
zSep = "|| ', ' ||";
}
pRet = recoverPreparePrintf(
p, p->dbOut, "SELECT 'INSERT INTO %s VALUES(' || %s || ')'", zTab, zBind
);
}
sqlite3_free(zBind);
return pRet;
}
static int recoverLostAndFoundFindRoot(
sqlite3_recover *p,
i64 iPg,
i64 *piRoot
){
RecoverStateLAF *pLaf = &p->laf;
if( pLaf->pFindRoot==0 ){
pLaf->pFindRoot = recoverPrepare(p, p->dbOut,
"WITH RECURSIVE p(pgno) AS ("
" SELECT ?"
" UNION"
" SELECT parent FROM recovery.map AS m, p WHERE m.pgno=p.pgno"
") "
"SELECT p.pgno FROM p, recovery.map m WHERE m.pgno=p.pgno "
" AND m.parent IS NULL"
);
}
if( p->errCode==SQLITE_OK ){
sqlite3_bind_int64(pLaf->pFindRoot, 1, iPg);
if( sqlite3_step(pLaf->pFindRoot)==SQLITE_ROW ){
*piRoot = sqlite3_column_int64(pLaf->pFindRoot, 0);
}else{
*piRoot = iPg;
}
recoverReset(p, pLaf->pFindRoot);
}
return p->errCode;
}
static void recoverLostAndFoundOnePage(sqlite3_recover *p, i64 iPage){
RecoverStateLAF *pLaf = &p->laf;
sqlite3_value **apVal = pLaf->apVal;
sqlite3_stmt *pPageData = pLaf->pPageData;
sqlite3_stmt *pInsert = pLaf->pInsert;
int nVal = -1;
int iPrevCell = 0;
i64 iRoot = 0;
int bHaveRowid = 0;
i64 iRowid = 0;
int ii = 0;
if( recoverLostAndFoundFindRoot(p, iPage, &iRoot) ) return;
sqlite3_bind_int64(pPageData, 1, iPage);
while( p->errCode==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPageData) ){
int iCell = sqlite3_column_int64(pPageData, 0);
int iField = sqlite3_column_int64(pPageData, 1);
if( iPrevCell!=iCell && nVal>=0 ){
sqlite3_bind_int64(pInsert, 1, iRoot);
sqlite3_bind_int64(pInsert, 2, iPage);
sqlite3_bind_int(pInsert, 3, nVal);
if( bHaveRowid ){
sqlite3_bind_int64(pInsert, 4, iRowid);
}
for(ii=0; ii<nVal; ii++){
recoverBindValue(p, pInsert, 5+ii, apVal[ii]);
}
if( sqlite3_step(pInsert)==SQLITE_ROW ){
recoverSqlCallback(p, (const char*)sqlite3_column_text(pInsert, 0));
}
recoverReset(p, pInsert);
for(ii=0; ii<nVal; ii++){
sqlite3_value_free(apVal[ii]);
apVal[ii] = 0;
}
sqlite3_clear_bindings(pInsert);
bHaveRowid = 0;
nVal = -1;
}
if( iCell<0 ) break;
if( iField<0 ){
assert( nVal==-1 );
iRowid = sqlite3_column_int64(pPageData, 2);
bHaveRowid = 1;
nVal = 0;
}else if( iField<pLaf->nMaxField ){
sqlite3_value *pVal = sqlite3_column_value(pPageData, 2);
apVal[iField] = sqlite3_value_dup(pVal);
assert( iField==nVal || (nVal==-1 && iField==0) );
nVal = iField+1;
if( apVal[iField]==0 ){
recoverError(p, SQLITE_NOMEM, 0);
}
}
iPrevCell = iCell;
}
recoverReset(p, pPageData);
for(ii=0; ii<nVal; ii++){
sqlite3_value_free(apVal[ii]);
apVal[ii] = 0;
}
}
static int recoverLostAndFound3Step(sqlite3_recover *p){
RecoverStateLAF *pLaf = &p->laf;
if( p->errCode==SQLITE_OK ){
if( pLaf->pInsert==0 ){
return SQLITE_DONE;
}else{
if( p->errCode==SQLITE_OK ){
int res = sqlite3_step(pLaf->pAllPage);
if( res==SQLITE_ROW ){
i64 iPage = sqlite3_column_int64(pLaf->pAllPage, 0);
if( recoverBitmapQuery(pLaf->pUsed, iPage)==0 ){
recoverLostAndFoundOnePage(p, iPage);
}
}else{
recoverReset(p, pLaf->pAllPage);
return SQLITE_DONE;
}
}
}
}
return SQLITE_OK;
}
static void recoverLostAndFound3Init(sqlite3_recover *p){
RecoverStateLAF *pLaf = &p->laf;
if( pLaf->nMaxField>0 ){
char *zTab = 0;
zTab = recoverLostAndFoundCreate(p, pLaf->nMaxField);
pLaf->pInsert = recoverLostAndFoundInsert(p, zTab, pLaf->nMaxField);
sqlite3_free(zTab);
pLaf->pAllPage = recoverPreparePrintf(p, p->dbOut,
"WITH RECURSIVE seq(ii) AS ("
" SELECT 1 UNION ALL SELECT ii+1 FROM seq WHERE ii<%lld"
")"
"SELECT ii FROM seq" , p->laf.nPg
);
pLaf->pPageData = recoverPrepare(p, p->dbOut,
"SELECT cell, field, value "
"FROM sqlite_dbdata('getpage()') d WHERE d.pgno=? "
"UNION ALL "
"SELECT -1, -1, -1"
);
pLaf->apVal = (sqlite3_value**)recoverMalloc(p,
pLaf->nMaxField*sizeof(sqlite3_value*)
);
}
}
static int recoverWriteDataInit(sqlite3_recover *p){
RecoverStateW1 *p1 = &p->w1;
RecoverTable *pTbl = 0;
int nByte = 0;
assert( p1->nMax==0 );
for(pTbl=p->pTblList; pTbl; pTbl=pTbl->pNext){
if( pTbl->nCol>p1->nMax ) p1->nMax = pTbl->nCol;
}
nByte = sizeof(sqlite3_value*) * (p1->nMax+1);
p1->apVal = (sqlite3_value**)recoverMalloc(p, nByte);
if( p1->apVal==0 ) return p->errCode;
p1->pTbls = recoverPrepare(p, p->dbOut,
"SELECT rootpage FROM recovery.schema "
" WHERE type='table' AND (sql NOT LIKE 'create virtual%')"
" ORDER BY (tbl_name='sqlite_sequence') ASC"
);
p1->pSel = recoverPrepare(p, p->dbOut,
"WITH RECURSIVE pages(page) AS ("
" SELECT ?1"
" UNION"
" SELECT child FROM sqlite_dbptr('getpage()'), pages "
" WHERE pgno=page"
") "
"SELECT page, cell, field, value "
"FROM sqlite_dbdata('getpage()') d, pages p WHERE p.page=d.pgno "
"UNION ALL "
"SELECT 0, 0, 0, 0"
);
return p->errCode;
}
static void recoverWriteDataCleanup(sqlite3_recover *p){
RecoverStateW1 *p1 = &p->w1;
int ii;
for(ii=0; ii<p1->nVal; ii++){
sqlite3_value_free(p1->apVal[ii]);
}
sqlite3_free(p1->apVal);
recoverFinalize(p, p1->pInsert);
recoverFinalize(p, p1->pTbls);
recoverFinalize(p, p1->pSel);
memset(p1, 0, sizeof(*p1));
}
static int recoverWriteDataStep(sqlite3_recover *p){
RecoverStateW1 *p1 = &p->w1;
sqlite3_stmt *pSel = p1->pSel;
sqlite3_value **apVal = p1->apVal;
if( p->errCode==SQLITE_OK && p1->pTab==0 ){
if( sqlite3_step(p1->pTbls)==SQLITE_ROW ){
i64 iRoot = sqlite3_column_int64(p1->pTbls, 0);
p1->pTab = recoverFindTable(p, iRoot);
recoverFinalize(p, p1->pInsert);
p1->pInsert = 0;
if( p1->pTab==0 ) return p->errCode;
if( sqlite3_stricmp("sqlite_sequence", p1->pTab->zTab)==0 ){
recoverExec(p, p->dbOut, "DELETE FROM sqlite_sequence");
recoverSqlCallback(p, "DELETE FROM sqlite_sequence");
}
sqlite3_bind_int64(pSel, 1, iRoot);
p1->nVal = 0;
p1->bHaveRowid = 0;
p1->iPrevPage = -1;
p1->iPrevCell = -1;
}else{
return SQLITE_DONE;
}
}
assert( p->errCode!=SQLITE_OK || p1->pTab );
if( p->errCode==SQLITE_OK && sqlite3_step(pSel)==SQLITE_ROW ){
RecoverTable *pTab = p1->pTab;
i64 iPage = sqlite3_column_int64(pSel, 0);
int iCell = sqlite3_column_int(pSel, 1);
int iField = sqlite3_column_int(pSel, 2);
sqlite3_value *pVal = sqlite3_column_value(pSel, 3);
int bNewCell = (p1->iPrevPage!=iPage || p1->iPrevCell!=iCell);
assert( bNewCell==0 || (iField==-1 || iField==0) );
assert( bNewCell || iField==p1->nVal || p1->nVal==pTab->nCol );
if( bNewCell ){
int ii = 0;
if( p1->nVal>=0 ){
if( p1->pInsert==0 || p1->nVal!=p1->nInsert ){
recoverFinalize(p, p1->pInsert);
p1->pInsert = recoverInsertStmt(p, pTab, p1->nVal);
p1->nInsert = p1->nVal;
}
if( p1->nVal>0 ){
sqlite3_stmt *pInsert = p1->pInsert;
for(ii=0; ii<pTab->nCol; ii++){
RecoverColumn *pCol = &pTab->aCol[ii];
int iBind = pCol->iBind;
if( iBind>0 ){
if( pCol->bIPK ){
sqlite3_bind_int64(pInsert, iBind, p1->iRowid);
}else if( pCol->iField<p1->nVal ){
recoverBindValue(p, pInsert, iBind, apVal[pCol->iField]);
}
}
}
if( p->bRecoverRowid && pTab->iRowidBind>0 && p1->bHaveRowid ){
sqlite3_bind_int64(pInsert, pTab->iRowidBind, p1->iRowid);
}
if( SQLITE_ROW==sqlite3_step(pInsert) ){
const char *z = (const char*)sqlite3_column_text(pInsert, 0);
recoverSqlCallback(p, z);
}
recoverReset(p, pInsert);
assert( p->errCode || pInsert );
if( pInsert ) sqlite3_clear_bindings(pInsert);
}
}
for(ii=0; ii<p1->nVal; ii++){
sqlite3_value_free(apVal[ii]);
apVal[ii] = 0;
}
p1->nVal = -1;
p1->bHaveRowid = 0;
}
if( iPage!=0 ){
if( iField<0 ){
p1->iRowid = sqlite3_column_int64(pSel, 3);
assert( p1->nVal==-1 );
p1->nVal = 0;
p1->bHaveRowid = 1;
}else if( iField<pTab->nCol ){
assert( apVal[iField]==0 );
apVal[iField] = sqlite3_value_dup( pVal );
if( apVal[iField]==0 ){
recoverError(p, SQLITE_NOMEM, 0);
}
p1->nVal = iField+1;
}
p1->iPrevCell = iCell;
p1->iPrevPage = iPage;
}
}else{
recoverReset(p, pSel);
p1->pTab = 0;
}
return p->errCode;
}
static void recoverLostAndFound1Init(sqlite3_recover *p){
RecoverStateLAF *pLaf = &p->laf;
sqlite3_stmt *pStmt = 0;
assert( p->laf.pUsed==0 );
pLaf->nPg = recoverPageCount(p);
pLaf->pUsed = recoverBitmapAlloc(p, pLaf->nPg);
pStmt = recoverPrepare(
p, p->dbOut,
"WITH trunk(pgno) AS ("
" SELECT read_i32(getpage(1), 8) AS x WHERE x>0"
" UNION"
" SELECT read_i32(getpage(trunk.pgno), 0) AS x FROM trunk WHERE x>0"
"),"
"trunkdata(pgno, data) AS ("
" SELECT pgno, getpage(pgno) FROM trunk"
"),"
"freelist(data, n, freepgno) AS ("
" SELECT data, min(16384, read_i32(data, 1)-1), pgno FROM trunkdata"
" UNION ALL"
" SELECT data, n-1, read_i32(data, 2+n) FROM freelist WHERE n>=0"
"),"
""
"roots(r) AS ("
" SELECT 1 UNION ALL"
" SELECT rootpage FROM recovery.schema WHERE rootpage>0"
"),"
"used(page) AS ("
" SELECT r FROM roots"
" UNION"
" SELECT child FROM sqlite_dbptr('getpage()'), used "
" WHERE pgno=page"
") "
"SELECT page FROM used"
" UNION ALL "
"SELECT freepgno FROM freelist WHERE NOT ?"
);
if( pStmt ) sqlite3_bind_int(pStmt, 1, p->bFreelistCorrupt);
pLaf->pUsedPages = pStmt;
}
static int recoverLostAndFound1Step(sqlite3_recover *p){
RecoverStateLAF *pLaf = &p->laf;
int rc = p->errCode;
if( rc==SQLITE_OK ){
rc = sqlite3_step(pLaf->pUsedPages);
if( rc==SQLITE_ROW ){
i64 iPg = sqlite3_column_int64(pLaf->pUsedPages, 0);
recoverBitmapSet(pLaf->pUsed, iPg);
rc = SQLITE_OK;
}else{
recoverFinalize(p, pLaf->pUsedPages);
pLaf->pUsedPages = 0;
}
}
return rc;
}
static void recoverLostAndFound2Init(sqlite3_recover *p){
RecoverStateLAF *pLaf = &p->laf;
assert( p->laf.pAllAndParent==0 );
assert( p->laf.pMapInsert==0 );
assert( p->laf.pMaxField==0 );
assert( p->laf.nMaxField==0 );
pLaf->pMapInsert = recoverPrepare(p, p->dbOut,
"INSERT OR IGNORE INTO recovery.map(pgno, parent) VALUES(?, ?)"
);
pLaf->pAllAndParent = recoverPreparePrintf(p, p->dbOut,
"WITH RECURSIVE seq(ii) AS ("
" SELECT 1 UNION ALL SELECT ii+1 FROM seq WHERE ii<%lld"
")"
"SELECT pgno, child FROM sqlite_dbptr('getpage()') "
" UNION ALL "
"SELECT NULL, ii FROM seq", p->laf.nPg
);
pLaf->pMaxField = recoverPreparePrintf(p, p->dbOut,
"SELECT max(field)+1 FROM sqlite_dbdata('getpage') WHERE pgno = ?"
);
}
static int recoverLostAndFound2Step(sqlite3_recover *p){
RecoverStateLAF *pLaf = &p->laf;
if( p->errCode==SQLITE_OK ){
int res = sqlite3_step(pLaf->pAllAndParent);
if( res==SQLITE_ROW ){
i64 iChild = sqlite3_column_int(pLaf->pAllAndParent, 1);
if( recoverBitmapQuery(pLaf->pUsed, iChild)==0 ){
sqlite3_bind_int64(pLaf->pMapInsert, 1, iChild);
sqlite3_bind_value(pLaf->pMapInsert, 2,
sqlite3_column_value(pLaf->pAllAndParent, 0)
);
sqlite3_step(pLaf->pMapInsert);
recoverReset(p, pLaf->pMapInsert);
sqlite3_bind_int64(pLaf->pMaxField, 1, iChild);
if( SQLITE_ROW==sqlite3_step(pLaf->pMaxField) ){
int nMax = sqlite3_column_int(pLaf->pMaxField, 0);
if( nMax>pLaf->nMaxField ) pLaf->nMaxField = nMax;
}
recoverReset(p, pLaf->pMaxField);
}
}else{
recoverFinalize(p, pLaf->pAllAndParent);
pLaf->pAllAndParent =0;
return SQLITE_DONE;
}
}
return p->errCode;
}
static void recoverLostAndFoundCleanup(sqlite3_recover *p){
recoverBitmapFree(p->laf.pUsed);
p->laf.pUsed = 0;
sqlite3_finalize(p->laf.pUsedPages);
sqlite3_finalize(p->laf.pAllAndParent);
sqlite3_finalize(p->laf.pMapInsert);
sqlite3_finalize(p->laf.pMaxField);
sqlite3_finalize(p->laf.pFindRoot);
sqlite3_finalize(p->laf.pInsert);
sqlite3_finalize(p->laf.pAllPage);
sqlite3_finalize(p->laf.pPageData);
p->laf.pUsedPages = 0;
p->laf.pAllAndParent = 0;
p->laf.pMapInsert = 0;
p->laf.pMaxField = 0;
p->laf.pFindRoot = 0;
p->laf.pInsert = 0;
p->laf.pAllPage = 0;
p->laf.pPageData = 0;
sqlite3_free(p->laf.apVal);
p->laf.apVal = 0;
}
static void recoverFinalCleanup(sqlite3_recover *p){
RecoverTable *pTab = 0;
RecoverTable *pNext = 0;
recoverWriteDataCleanup(p);
recoverLostAndFoundCleanup(p);
for(pTab=p->pTblList; pTab; pTab=pNext){
pNext = pTab->pNext;
sqlite3_free(pTab);
}
p->pTblList = 0;
sqlite3_finalize(p->pGetPage);
p->pGetPage = 0;
sqlite3_file_control(p->dbIn, p->zDb, SQLITE_FCNTL_RESET_CACHE, 0);
{
#ifndef NDEBUG
int res =
#endif
sqlite3_close(p->dbOut);
assert( res==SQLITE_OK );
}
p->dbOut = 0;
}
static u32 recoverGetU16(const u8 *a){
return (((u32)a[0])<<8) + ((u32)a[1]);
}
static u32 recoverGetU32(const u8 *a){
return (((u32)a[0])<<24) + (((u32)a[1])<<16) + (((u32)a[2])<<8) + ((u32)a[3]);
}
static int recoverGetVarint(const u8 *a, i64 *pVal){
sqlite3_uint64 u = 0;
int i;
for(i=0; i<8; i++){
u = (u<<7) + (a[i]&0x7f);
if( (a[i]&0x80)==0 ){ *pVal = (sqlite3_int64)u; return i+1; }
}
u = (u<<8) + (a[i]&0xff);
*pVal = (sqlite3_int64)u;
return 9;
}
static int recoverIsValidPage(u8 *aTmp, const u8 *a, int n){
u8 *aUsed = aTmp;
int nFrag = 0;
int nActual = 0;
int iFree = 0;
int nCell = 0;
int iCellOff = 0;
int iContent = 0;
int eType = 0;
int ii = 0;
eType = (int)a[0];
if( eType!=0x02 && eType!=0x05 && eType!=0x0A && eType!=0x0D ) return 0;
iFree = (int)recoverGetU16(&a[1]);
nCell = (int)recoverGetU16(&a[3]);
iContent = (int)recoverGetU16(&a[5]);
if( iContent==0 ) iContent = 65536;
nFrag = (int)a[7];
if( iContent>n ) return 0;
memset(aUsed, 0, n);
memset(aUsed, 0xFF, iContent);
if( iFree && iFree<=iContent ) return 0;
while( iFree ){
int iNext = 0;
int nByte = 0;
if( iFree>(n-4) ) return 0;
iNext = recoverGetU16(&a[iFree]);
nByte = recoverGetU16(&a[iFree+2]);
if( iFree+nByte>n || nByte<4 ) return 0;
if( iNext && iNext<iFree+nByte ) return 0;
memset(&aUsed[iFree], 0xFF, nByte);
iFree = iNext;
}
if( eType==0x02 || eType==0x05 ){
iCellOff = 12;
}else{
iCellOff = 8;
}
if( (iCellOff + 2*nCell)>iContent ) return 0;
for(ii=0; ii<nCell; ii++){
int iByte;
i64 nPayload = 0;
int nByte = 0;
int iOff = recoverGetU16(&a[iCellOff + 2*ii]);
if( iOff<iContent || iOff>n ){
return 0;
}
if( eType==0x05 || eType==0x02 ) nByte += 4;
nByte += recoverGetVarint(&a[iOff+nByte], &nPayload);
if( eType==0x0D ){
i64 dummy = 0;
nByte += recoverGetVarint(&a[iOff+nByte], &dummy);
}
if( eType!=0x05 ){
int X = (eType==0x0D) ? n-35 : (((n-12)*64/255)-23);
int M = ((n-12)*32/255)-23;
int K = M+((nPayload-M)%(n-4));
if( nPayload<X ){
nByte += nPayload;
}else if( K<=X ){
nByte += K+4;
}else{
nByte += M+4;
}
}
if( iOff+nByte>n ){
return 0;
}
for(iByte=iOff; iByte<(iOff+nByte); iByte++){
if( aUsed[iByte]!=0 ){
return 0;
}
aUsed[iByte] = 0xFF;
}
}
nActual = 0;
for(ii=0; ii<n; ii++){
if( aUsed[ii]==0 ) nActual++;
}
return (nActual==nFrag);
}
static int recoverVfsClose(sqlite3_file*);
static int recoverVfsRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
static int recoverVfsWrite(sqlite3_file*, const void*, int, sqlite3_int64);
static int recoverVfsTruncate(sqlite3_file*, sqlite3_int64 size);
static int recoverVfsSync(sqlite3_file*, int flags);
static int recoverVfsFileSize(sqlite3_file*, sqlite3_int64 *pSize);
static int recoverVfsLock(sqlite3_file*, int);
static int recoverVfsUnlock(sqlite3_file*, int);
static int recoverVfsCheckReservedLock(sqlite3_file*, int *pResOut);
static int recoverVfsFileControl(sqlite3_file*, int op, void *pArg);
static int recoverVfsSectorSize(sqlite3_file*);
static int recoverVfsDeviceCharacteristics(sqlite3_file*);
static int recoverVfsShmMap(sqlite3_file*, int, int, int, void volatile**);
static int recoverVfsShmLock(sqlite3_file*, int offset, int n, int flags);
static void recoverVfsShmBarrier(sqlite3_file*);
static int recoverVfsShmUnmap(sqlite3_file*, int deleteFlag);
static int recoverVfsFetch(sqlite3_file*, sqlite3_int64, int, void**);
static int recoverVfsUnfetch(sqlite3_file *pFd, sqlite3_int64 iOff, void *p);
static sqlite3_io_methods recover_methods = {
2,
recoverVfsClose,
recoverVfsRead,
recoverVfsWrite,
recoverVfsTruncate,
recoverVfsSync,
recoverVfsFileSize,
recoverVfsLock,
recoverVfsUnlock,
recoverVfsCheckReservedLock,
recoverVfsFileControl,
recoverVfsSectorSize,
recoverVfsDeviceCharacteristics,
recoverVfsShmMap,
recoverVfsShmLock,
recoverVfsShmBarrier,
recoverVfsShmUnmap,
recoverVfsFetch,
recoverVfsUnfetch
};
static int recoverVfsClose(sqlite3_file *pFd){
assert( pFd->pMethods!=&recover_methods );
return pFd->pMethods->xClose(pFd);
}
static void recoverPutU16(u8 *a, u32 v){
a[0] = (v>>8) & 0x00FF;
a[1] = (v>>0) & 0x00FF;
}
static void recoverPutU32(u8 *a, u32 v){
a[0] = (v>>24) & 0x00FF;
a[1] = (v>>16) & 0x00FF;
a[2] = (v>>8) & 0x00FF;
a[3] = (v>>0) & 0x00FF;
}
static int recoverVfsDetectPagesize(
sqlite3_recover *p,
sqlite3_file *pFd,
u32 nReserve,
i64 nSz
){
int rc = SQLITE_OK;
const int nMin = 512;
const int nMax = 65536;
const int nMaxBlk = 4;
u32 pgsz = 0;
int iBlk = 0;
u8 *aPg = 0;
u8 *aTmp = 0;
int nBlk = 0;
aPg = (u8*)sqlite3_malloc(2*nMax);
if( aPg==0 ) return SQLITE_NOMEM;
aTmp = &aPg[nMax];
nBlk = (nSz+nMax-1)/nMax;
if( nBlk>nMaxBlk ) nBlk = nMaxBlk;
do {
for(iBlk=0; rc==SQLITE_OK && iBlk<nBlk; iBlk++){
int nByte = (nSz>=((iBlk+1)*nMax)) ? nMax : (nSz % nMax);
memset(aPg, 0, nMax);
rc = pFd->pMethods->xRead(pFd, aPg, nByte, iBlk*nMax);
if( rc==SQLITE_OK ){
int pgsz2;
for(pgsz2=(pgsz ? pgsz*2 : nMin); pgsz2<=nMax; pgsz2=pgsz2*2){
int iOff;
for(iOff=0; iOff<nMax; iOff+=pgsz2){
if( recoverIsValidPage(aTmp, &aPg[iOff], pgsz2-nReserve) ){
pgsz = pgsz2;
break;
}
}
}
}
}
if( pgsz>(u32)p->detected_pgsz ){
p->detected_pgsz = pgsz;
p->nReserve = nReserve;
}
if( nReserve==0 ) break;
nReserve = 0;
}while( 1 );
p->detected_pgsz = pgsz;
sqlite3_free(aPg);
return rc;
}
static int recoverVfsRead(sqlite3_file *pFd, void *aBuf, int nByte, i64 iOff){
int rc = SQLITE_OK;
if( pFd->pMethods==&recover_methods ){
pFd->pMethods = recover_g.pMethods;
rc = pFd->pMethods->xRead(pFd, aBuf, nByte, iOff);
if( nByte==16 ){
sqlite3_randomness(16, aBuf);
}else
if( rc==SQLITE_OK && iOff==0 && nByte>=108 ){
const int aPreserve[] = {32, 36, 52, 60, 64, 68};
u8 aHdr[108] = {
0x53, 0x51, 0x4c, 0x69, 0x74, 0x65, 0x20, 0x66,
0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x33, 0x00,
0xFF, 0xFF, 0x01, 0x01, 0x00, 0x40, 0x20, 0x20,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
0x00, 0x00, 0x10, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x2e, 0x5b, 0x30,
0x0D, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00
};
u8 *a = (u8*)aBuf;
u32 pgsz = recoverGetU16(&a[16]);
u32 nReserve = a[20];
u32 enc = recoverGetU32(&a[56]);
u32 dbsz = 0;
i64 dbFileSize = 0;
int ii;
sqlite3_recover *p = recover_g.p;
if( pgsz==0x01 ) pgsz = 65536;
rc = pFd->pMethods->xFileSize(pFd, &dbFileSize);
if( rc==SQLITE_OK && p->detected_pgsz==0 ){
rc = recoverVfsDetectPagesize(p, pFd, nReserve, dbFileSize);
}
if( p->detected_pgsz ){
pgsz = p->detected_pgsz;
nReserve = p->nReserve;
}
if( pgsz ){
dbsz = dbFileSize / pgsz;
}
if( enc!=SQLITE_UTF8 && enc!=SQLITE_UTF16BE && enc!=SQLITE_UTF16LE ){
enc = SQLITE_UTF8;
}
sqlite3_free(p->pPage1Cache);
p->pPage1Cache = 0;
p->pPage1Disk = 0;
p->pgsz = nByte;
p->pPage1Cache = (u8*)recoverMalloc(p, nByte*2);
if( p->pPage1Cache ){
p->pPage1Disk = &p->pPage1Cache[nByte];
memcpy(p->pPage1Disk, aBuf, nByte);
aHdr[18] = a[18];
aHdr[19] = a[19];
recoverPutU32(&aHdr[28], dbsz);
recoverPutU32(&aHdr[56], enc);
recoverPutU16(&aHdr[105], pgsz-nReserve);
if( pgsz==65536 ) pgsz = 1;
recoverPutU16(&aHdr[16], pgsz);
aHdr[20] = nReserve;
for(ii=0; ii<(int)(sizeof(aPreserve)/sizeof(aPreserve[0])); ii++){
memcpy(&aHdr[aPreserve[ii]], &a[aPreserve[ii]], 4);
}
memcpy(aBuf, aHdr, sizeof(aHdr));
memset(&((u8*)aBuf)[sizeof(aHdr)], 0, nByte-sizeof(aHdr));
memcpy(p->pPage1Cache, aBuf, nByte);
}else{
rc = p->errCode;
}
}
pFd->pMethods = &recover_methods;
}else{
rc = pFd->pMethods->xRead(pFd, aBuf, nByte, iOff);
}
return rc;
}
#define RECOVER_VFS_WRAPPER(code) \
int rc = SQLITE_OK; \
if( pFd->pMethods==&recover_methods ){ \
pFd->pMethods = recover_g.pMethods; \
rc = code; \
pFd->pMethods = &recover_methods; \
}else{ \
rc = code; \
} \
return rc;
static int recoverVfsWrite(
sqlite3_file *pFd, const void *aBuf, int nByte, i64 iOff
){
RECOVER_VFS_WRAPPER (
pFd->pMethods->xWrite(pFd, aBuf, nByte, iOff)
);
}
static int recoverVfsTruncate(sqlite3_file *pFd, sqlite3_int64 size){
RECOVER_VFS_WRAPPER (
pFd->pMethods->xTruncate(pFd, size)
);
}
static int recoverVfsSync(sqlite3_file *pFd, int flags){
RECOVER_VFS_WRAPPER (
pFd->pMethods->xSync(pFd, flags)
);
}
static int recoverVfsFileSize(sqlite3_file *pFd, sqlite3_int64 *pSize){
RECOVER_VFS_WRAPPER (
pFd->pMethods->xFileSize(pFd, pSize)
);
}
static int recoverVfsLock(sqlite3_file *pFd, int eLock){
RECOVER_VFS_WRAPPER (
pFd->pMethods->xLock(pFd, eLock)
);
}
static int recoverVfsUnlock(sqlite3_file *pFd, int eLock){
RECOVER_VFS_WRAPPER (
pFd->pMethods->xUnlock(pFd, eLock)
);
}
static int recoverVfsCheckReservedLock(sqlite3_file *pFd, int *pResOut){
RECOVER_VFS_WRAPPER (
pFd->pMethods->xCheckReservedLock(pFd, pResOut)
);
}
static int recoverVfsFileControl(sqlite3_file *pFd, int op, void *pArg){
RECOVER_VFS_WRAPPER (
(pFd->pMethods ? pFd->pMethods->xFileControl(pFd, op, pArg) : SQLITE_NOTFOUND)
);
}
static int recoverVfsSectorSize(sqlite3_file *pFd){
RECOVER_VFS_WRAPPER (
pFd->pMethods->xSectorSize(pFd)
);
}
static int recoverVfsDeviceCharacteristics(sqlite3_file *pFd){
RECOVER_VFS_WRAPPER (
pFd->pMethods->xDeviceCharacteristics(pFd)
);
}
static int recoverVfsShmMap(
sqlite3_file *pFd, int iPg, int pgsz, int bExtend, void volatile **pp
){
RECOVER_VFS_WRAPPER (
pFd->pMethods->xShmMap(pFd, iPg, pgsz, bExtend, pp)
);
}
static int recoverVfsShmLock(sqlite3_file *pFd, int offset, int n, int flags){
RECOVER_VFS_WRAPPER (
pFd->pMethods->xShmLock(pFd, offset, n, flags)
);
}
static void recoverVfsShmBarrier(sqlite3_file *pFd){
if( pFd->pMethods==&recover_methods ){
pFd->pMethods = recover_g.pMethods;
pFd->pMethods->xShmBarrier(pFd);
pFd->pMethods = &recover_methods;
}else{
pFd->pMethods->xShmBarrier(pFd);
}
}
static int recoverVfsShmUnmap(sqlite3_file *pFd, int deleteFlag){
RECOVER_VFS_WRAPPER (
pFd->pMethods->xShmUnmap(pFd, deleteFlag)
);
}
static int recoverVfsFetch(
sqlite3_file *pFd,
sqlite3_int64 iOff,
int iAmt,
void **pp
){
(void)pFd;
(void)iOff;
(void)iAmt;
*pp = 0;
return SQLITE_OK;
}
static int recoverVfsUnfetch(sqlite3_file *pFd, sqlite3_int64 iOff, void *p){
(void)pFd;
(void)iOff;
(void)p;
return SQLITE_OK;
}
static void recoverInstallWrapper(sqlite3_recover *p){
sqlite3_file *pFd = 0;
assert( recover_g.pMethods==0 );
recoverAssertMutexHeld();
sqlite3_file_control(p->dbIn, p->zDb, SQLITE_FCNTL_FILE_POINTER, (void*)&pFd);
assert( pFd==0 || pFd->pMethods!=&recover_methods );
if( pFd && pFd->pMethods ){
int iVersion = 1 + (pFd->pMethods->iVersion>1 && pFd->pMethods->xShmMap!=0);
recover_g.pMethods = pFd->pMethods;
recover_g.p = p;
recover_methods.iVersion = iVersion;
pFd->pMethods = &recover_methods;
}
}
static void recoverUninstallWrapper(sqlite3_recover *p){
sqlite3_file *pFd = 0;
recoverAssertMutexHeld();
sqlite3_file_control(p->dbIn, p->zDb,SQLITE_FCNTL_FILE_POINTER,(void*)&pFd);
if( pFd && pFd->pMethods ){
pFd->pMethods = recover_g.pMethods;
recover_g.pMethods = 0;
recover_g.p = 0;
}
}
static void recoverStep(sqlite3_recover *p){
assert( p && p->errCode==SQLITE_OK );
switch( p->eState ){
case RECOVER_STATE_INIT:
recoverSqlCallback(p, "BEGIN");
recoverSqlCallback(p, "PRAGMA writable_schema = on");
recoverEnterMutex();
recoverInstallWrapper(p);
recoverOpenOutput(p);
sqlite3_file_control(p->dbIn, p->zDb, SQLITE_FCNTL_RESET_CACHE, 0);
recoverExec(p, p->dbIn, "PRAGMA writable_schema = on");
recoverExec(p, p->dbIn, "BEGIN");
if( p->errCode==SQLITE_OK ) p->bCloseTransaction = 1;
recoverExec(p, p->dbIn, "SELECT 1 FROM sqlite_schema");
recoverTransferSettings(p);
recoverOpenRecovery(p);
recoverCacheSchema(p);
recoverUninstallWrapper(p);
recoverLeaveMutex();
recoverExec(p, p->dbOut, "BEGIN");
recoverWriteSchema1(p);
p->eState = RECOVER_STATE_WRITING;
break;
case RECOVER_STATE_WRITING: {
if( p->w1.pTbls==0 ){
recoverWriteDataInit(p);
}
if( SQLITE_DONE==recoverWriteDataStep(p) ){
recoverWriteDataCleanup(p);
if( p->zLostAndFound ){
p->eState = RECOVER_STATE_LOSTANDFOUND1;
}else{
p->eState = RECOVER_STATE_SCHEMA2;
}
}
break;
}
case RECOVER_STATE_LOSTANDFOUND1: {
if( p->laf.pUsed==0 ){
recoverLostAndFound1Init(p);
}
if( SQLITE_DONE==recoverLostAndFound1Step(p) ){
p->eState = RECOVER_STATE_LOSTANDFOUND2;
}
break;
}
case RECOVER_STATE_LOSTANDFOUND2: {
if( p->laf.pAllAndParent==0 ){
recoverLostAndFound2Init(p);
}
if( SQLITE_DONE==recoverLostAndFound2Step(p) ){
p->eState = RECOVER_STATE_LOSTANDFOUND3;
}
break;
}
case RECOVER_STATE_LOSTANDFOUND3: {
if( p->laf.pInsert==0 ){
recoverLostAndFound3Init(p);
}
if( SQLITE_DONE==recoverLostAndFound3Step(p) ){
p->eState = RECOVER_STATE_SCHEMA2;
}
break;
}
case RECOVER_STATE_SCHEMA2: {
int rc = SQLITE_OK;
recoverWriteSchema2(p);
p->eState = RECOVER_STATE_DONE;
recoverExec(p, p->dbOut, "COMMIT");
rc = sqlite3_exec(p->dbIn, "END", 0, 0, 0);
if( p->errCode==SQLITE_OK ) p->errCode = rc;
recoverSqlCallback(p, "PRAGMA writable_schema = off");
recoverSqlCallback(p, "COMMIT");
p->eState = RECOVER_STATE_DONE;
recoverFinalCleanup(p);
break;
};
case RECOVER_STATE_DONE: {
break;
};
}
}
sqlite3_recover *recoverInit(
sqlite3* db,
const char *zDb,
const char *zUri,
int (*xSql)(void*, const char*),
void *pSqlCtx
){
sqlite3_recover *pRet = 0;
int nDb = 0;
int nUri = 0;
int nByte = 0;
if( zDb==0 ){ zDb = "main"; }
nDb = recoverStrlen(zDb);
nUri = recoverStrlen(zUri);
nByte = sizeof(sqlite3_recover) + nDb+1 + nUri+1;
pRet = (sqlite3_recover*)sqlite3_malloc(nByte);
if( pRet ){
memset(pRet, 0, nByte);
pRet->dbIn = db;
pRet->zDb = (char*)&pRet[1];
pRet->zUri = &pRet->zDb[nDb+1];
memcpy(pRet->zDb, zDb, nDb);
if( nUri>0 && zUri ) memcpy(pRet->zUri, zUri, nUri);
pRet->xSql = xSql;
pRet->pSqlCtx = pSqlCtx;
pRet->bRecoverRowid = RECOVER_ROWID_DEFAULT;
}
return pRet;
}
sqlite3_recover *sqlite3_recover_init(
sqlite3* db,
const char *zDb,
const char *zUri
){
return recoverInit(db, zDb, zUri, 0, 0);
}
sqlite3_recover *sqlite3_recover_init_sql(
sqlite3* db,
const char *zDb,
int (*xSql)(void*, const char*),
void *pSqlCtx
){
return recoverInit(db, zDb, 0, xSql, pSqlCtx);
}
const char *sqlite3_recover_errmsg(sqlite3_recover *p){
return (p && p->errCode!=SQLITE_NOMEM) ? p->zErrMsg : "out of memory";
}
int sqlite3_recover_errcode(sqlite3_recover *p){
return p ? p->errCode : SQLITE_NOMEM;
}
int sqlite3_recover_config(sqlite3_recover *p, int op, void *pArg){
int rc = SQLITE_OK;
if( p==0 ){
rc = SQLITE_NOMEM;
}else if( p->eState!=RECOVER_STATE_INIT ){
rc = SQLITE_MISUSE;
}else{
switch( op ){
case 789:
sqlite3_free(p->zStateDb);
p->zStateDb = recoverMPrintf(p, "%s", (char*)pArg);
break;
case SQLITE_RECOVER_LOST_AND_FOUND: {
const char *zArg = (const char*)pArg;
sqlite3_free(p->zLostAndFound);
if( zArg ){
p->zLostAndFound = recoverMPrintf(p, "%s", zArg);
}else{
p->zLostAndFound = 0;
}
break;
}
case SQLITE_RECOVER_FREELIST_CORRUPT:
p->bFreelistCorrupt = *(int*)pArg;
break;
case SQLITE_RECOVER_ROWIDS:
p->bRecoverRowid = *(int*)pArg;
break;
case SQLITE_RECOVER_SLOWINDEXES:
p->bSlowIndexes = *(int*)pArg;
break;
default:
rc = SQLITE_NOTFOUND;
break;
}
}
return rc;
}
int sqlite3_recover_step(sqlite3_recover *p){
if( p==0 ) return SQLITE_NOMEM;
if( p->errCode==SQLITE_OK ) recoverStep(p);
if( p->eState==RECOVER_STATE_DONE && p->errCode==SQLITE_OK ){
return SQLITE_DONE;
}
return p->errCode;
}
int sqlite3_recover_run(sqlite3_recover *p){
while( SQLITE_OK==sqlite3_recover_step(p) );
return sqlite3_recover_errcode(p);
}
int sqlite3_recover_finish(sqlite3_recover *p){
int rc;
if( p==0 ){
rc = SQLITE_NOMEM;
}else{
recoverFinalCleanup(p);
if( p->bCloseTransaction && sqlite3_get_autocommit(p->dbIn)==0 ){
rc = sqlite3_exec(p->dbIn, "END", 0, 0, 0);
if( p->errCode==SQLITE_OK ) p->errCode = rc;
}
rc = p->errCode;
sqlite3_free(p->zErrMsg);
sqlite3_free(p->zStateDb);
sqlite3_free(p->zLostAndFound);
sqlite3_free(p->pPage1Cache);
sqlite3_free(p);
}
return rc;
}
#endif