diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 11:19:16 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 12:07:37 +0000 |
commit | b485aab7e71c1625cfc27e0f92c9509f42378458 (patch) | |
tree | ae9abe108601079d1679194de237c9a435ae5b55 /database/sqlite/sqlite3recover.c | |
parent | Adding upstream version 1.44.3. (diff) | |
download | netdata-b485aab7e71c1625cfc27e0f92c9509f42378458.tar.xz netdata-b485aab7e71c1625cfc27e0f92c9509f42378458.zip |
Adding upstream version 1.45.3+dfsg.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'database/sqlite/sqlite3recover.c')
-rw-r--r-- | database/sqlite/sqlite3recover.c | 2872 |
1 files changed, 0 insertions, 2872 deletions
diff --git a/database/sqlite/sqlite3recover.c b/database/sqlite/sqlite3recover.c deleted file mode 100644 index 3dae0b7a..00000000 --- a/database/sqlite/sqlite3recover.c +++ /dev/null @@ -1,2872 +0,0 @@ -/* -** 2022-08-27 -** -** 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. -** -************************************************************************* -** -*/ - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wsign-compare" -#include "sqlite3recover.h" -#include <assert.h> -#include <string.h> - -#ifndef SQLITE_OMIT_VIRTUALTABLE - -/* -** Declaration for public API function in file dbdata.c. This may be called -** with NULL as the final two arguments to register the sqlite_dbptr and -** sqlite_dbdata virtual tables with a database handle. -*/ -#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; - -/* -** When recovering rows of data that can be associated with table -** definitions recovered from the sqlite_schema table, each table is -** represented by an instance of the following object. -** -** iRoot: -** The root page in the original database. Not necessarily (and usually -** not) the same in the recovered database. -** -** zTab: -** Name of the table. -** -** nCol/aCol[]: -** aCol[] is an array of nCol columns. In the order in which they appear -** in the table. -** -** bIntkey: -** Set to true for intkey tables, false for WITHOUT ROWID. -** -** iRowidBind: -** Each column in the aCol[] array has associated with it the index of -** the bind parameter its values will be bound to in the INSERT statement -** used to construct the output database. If the table does has a rowid -** but not an INTEGER PRIMARY KEY column, then iRowidBind contains the -** index of the bind paramater to which the rowid value should be bound. -** Otherwise, it contains -1. If the table does contain an INTEGER PRIMARY -** KEY column, then the rowid value should be bound to the index associated -** with the column. -** -** pNext: -** All RecoverTable objects used by the recovery operation are allocated -** and populated as part of creating the recovered database schema in -** the output database, before any non-schema data are recovered. They -** are then stored in a singly-linked list linked by this variable beginning -** at sqlite3_recover.pTblList. -*/ -struct RecoverTable { - u32 iRoot; /* Root page in original database */ - char *zTab; /* Name of table */ - int nCol; /* Number of columns in table */ - RecoverColumn *aCol; /* Array of columns */ - int bIntkey; /* True for intkey, false for without rowid */ - int iRowidBind; /* If >0, bind rowid to INSERT here */ - RecoverTable *pNext; -}; - -/* -** Each database column is represented by an instance of the following object -** stored in the RecoverTable.aCol[] array of the associated table. -** -** iField: -** The index of the associated field within database records. Or -1 if -** there is no associated field (e.g. for virtual generated columns). -** -** iBind: -** The bind index of the INSERT statement to bind this columns values -** to. Or 0 if there is no such index (iff (iField<0)). -** -** bIPK: -** True if this is the INTEGER PRIMARY KEY column. -** -** zCol: -** Name of column. -** -** eHidden: -** A RECOVER_EHIDDEN_* constant value (see below for interpretation of each). -*/ -struct RecoverColumn { - int iField; /* Field in record on disk */ - int iBind; /* Binding to use in INSERT */ - int bIPK; /* True for IPK column */ - char *zCol; - int eHidden; -}; - -#define RECOVER_EHIDDEN_NONE 0 /* Normal database column */ -#define RECOVER_EHIDDEN_HIDDEN 1 /* Column is __HIDDEN__ */ -#define RECOVER_EHIDDEN_VIRTUAL 2 /* Virtual generated column */ -#define RECOVER_EHIDDEN_STORED 3 /* Stored generated column */ - -/* -** Bitmap object used to track pages in the input database. Allocated -** and manipulated only by the following functions: -** -** recoverBitmapAlloc() -** recoverBitmapFree() -** recoverBitmapSet() -** recoverBitmapQuery() -** -** nPg: -** Largest page number that may be stored in the bitmap. The range -** of valid keys is 1 to nPg, inclusive. -** -** aElem[]: -** Array large enough to contain a bit for each key. For key value -** iKey, the associated bit is the bit (iKey%32) of aElem[iKey/32]. -** In other words, the following is true if bit iKey is set, or -** false if it is clear: -** -** (aElem[iKey/32] & (1 << (iKey%32))) ? 1 : 0 -*/ -typedef struct RecoverBitmap RecoverBitmap; -struct RecoverBitmap { - i64 nPg; /* Size of bitmap */ - u32 aElem[1]; /* Array of 32-bit bitmasks */ -}; - -/* -** State variables (part of the sqlite3_recover structure) used while -** recovering data for tables identified in the recovered schema (state -** RECOVER_STATE_WRITING). -*/ -typedef struct RecoverStateW1 RecoverStateW1; -struct RecoverStateW1 { - sqlite3_stmt *pTbls; - sqlite3_stmt *pSel; - sqlite3_stmt *pInsert; - int nInsert; - - RecoverTable *pTab; /* Table currently being written */ - int nMax; /* Max column count in any schema table */ - sqlite3_value **apVal; /* Array of nMax values */ - int nVal; /* Number of valid entries in apVal[] */ - int bHaveRowid; - i64 iRowid; - i64 iPrevPage; - int iPrevCell; -}; - -/* -** State variables (part of the sqlite3_recover structure) used while -** recovering data destined for the lost and found table (states -** RECOVER_STATE_LOSTANDFOUND[123]). -*/ -typedef struct RecoverStateLAF RecoverStateLAF; -struct RecoverStateLAF { - RecoverBitmap *pUsed; - i64 nPg; /* Size of db in pages */ - sqlite3_stmt *pAllAndParent; - sqlite3_stmt *pMapInsert; - sqlite3_stmt *pMaxField; - sqlite3_stmt *pUsedPages; - sqlite3_stmt *pFindRoot; - sqlite3_stmt *pInsert; /* INSERT INTO lost_and_found ... */ - sqlite3_stmt *pAllPage; - sqlite3_stmt *pPageData; - sqlite3_value **apVal; - int nMaxField; -}; - -/* -** Main recover handle structure. -*/ -struct sqlite3_recover { - /* Copies of sqlite3_recover_init[_sql]() parameters */ - sqlite3 *dbIn; /* Input database */ - char *zDb; /* Name of input db ("main" etc.) */ - char *zUri; /* URI for output database */ - void *pSqlCtx; /* SQL callback context */ - int (*xSql)(void*,const char*); /* Pointer to SQL callback function */ - - /* Values configured by sqlite3_recover_config() */ - char *zStateDb; /* State database to use (or NULL) */ - char *zLostAndFound; /* Name of lost-and-found table (or NULL) */ - int bFreelistCorrupt; /* SQLITE_RECOVER_FREELIST_CORRUPT setting */ - int bRecoverRowid; /* SQLITE_RECOVER_ROWIDS setting */ - int bSlowIndexes; /* SQLITE_RECOVER_SLOWINDEXES setting */ - - int pgsz; - int detected_pgsz; - int nReserve; - u8 *pPage1Disk; - u8 *pPage1Cache; - - /* Error code and error message */ - int errCode; /* For sqlite3_recover_errcode() */ - char *zErrMsg; /* For sqlite3_recover_errmsg() */ - - int eState; - int bCloseTransaction; - - /* Variables used with eState==RECOVER_STATE_WRITING */ - RecoverStateW1 w1; - - /* Variables used with states RECOVER_STATE_LOSTANDFOUND[123] */ - RecoverStateLAF laf; - - /* Fields used within sqlite3_recover_run() */ - sqlite3 *dbOut; /* Output database */ - sqlite3_stmt *pGetPage; /* SELECT against input db sqlite_dbdata */ - RecoverTable *pTblList; /* List of tables recovered from schema */ -}; - -/* -** The various states in which an sqlite3_recover object may exist: -** -** RECOVER_STATE_INIT: -** The object is initially created in this state. sqlite3_recover_step() -** has yet to be called. This is the only state in which it is permitted -** to call sqlite3_recover_config(). -** -** RECOVER_STATE_WRITING: -** -** RECOVER_STATE_LOSTANDFOUND1: -** State to populate the bitmap of pages used by other tables or the -** database freelist. -** -** RECOVER_STATE_LOSTANDFOUND2: -** Populate the recovery.map table - used to figure out a "root" page -** for each lost page from in the database from which records are -** extracted. -** -** RECOVER_STATE_LOSTANDFOUND3: -** Populate the lost-and-found table itself. -*/ -#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 - - -/* -** Global variables used by this extension. -*/ -typedef struct RecoverGlobal RecoverGlobal; -struct RecoverGlobal { - const sqlite3_io_methods *pMethods; - sqlite3_recover *p; -}; -static RecoverGlobal recover_g; - -/* -** Use this static SQLite mutex to protect the globals during the -** first call to sqlite3_recover_step(). -*/ -#define RECOVER_MUTEX_ID SQLITE_MUTEX_STATIC_APP2 - - -/* -** Default value for SQLITE_RECOVER_ROWIDS (sqlite3_recover.bRecoverRowid). -*/ -#define RECOVER_ROWID_DEFAULT 1 - -/* -** Mutex handling: -** -** recoverEnterMutex() - Enter the recovery mutex -** recoverLeaveMutex() - Leave the recovery mutex -** recoverAssertMutexHeld() - Assert that the recovery mutex is held -*/ -#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 - - -/* -** Like strlen(). But handles NULL pointer arguments. -*/ -static int recoverStrlen(const char *zStr){ - if( zStr==0 ) return 0; - return (int)(strlen(zStr)&0x7fffffff); -} - -/* -** This function is a no-op if the recover handle passed as the first -** argument already contains an error (if p->errCode!=SQLITE_OK). -** -** Otherwise, an attempt is made to allocate, zero and return a buffer nByte -** bytes in size. If successful, a pointer to the new buffer is returned. Or, -** if an OOM error occurs, NULL is returned and the handle error code -** (p->errCode) set to SQLITE_NOMEM. -*/ -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; -} - -/* -** Set the error code and error message for the recover handle passed as -** the first argument. The error code is set to the value of parameter -** errCode. -** -** Parameter zFmt must be a printf() style formatting string. The handle -** error message is set to the result of using any trailing arguments for -** parameter substitutions in the formatting string. -** -** For example: -** -** recoverError(p, SQLITE_ERROR, "no such table: %s", zTablename); -*/ -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; -} - - -/* -** This function is a no-op if p->errCode is initially other than SQLITE_OK. -** In this case it returns NULL. -** -** Otherwise, an attempt is made to allocate and return a bitmap object -** large enough to store a bit for all page numbers between 1 and nPg, -** inclusive. The bitmap is initially zeroed. -*/ -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; -} - -/* -** Free a bitmap object allocated by recoverBitmapAlloc(). -*/ -static void recoverBitmapFree(RecoverBitmap *pMap){ - sqlite3_free(pMap); -} - -/* -** Set the bit associated with page iPg in bitvec 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); - } -} - -/* -** Query bitmap object pMap for the state of the bit associated with page -** iPg. Return 1 if it is set, or 0 otherwise. -*/ -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; -} - -/* -** Set the recover handle error to the error code and message returned by -** calling sqlite3_errcode() and sqlite3_errmsg(), respectively, on database -** handle db. -*/ -static int recoverDbError(sqlite3_recover *p, sqlite3 *db){ - return recoverError(p, sqlite3_errcode(db), "%s", sqlite3_errmsg(db)); -} - -/* -** This function is a no-op if recover handle p already contains an error -** (if p->errCode!=SQLITE_OK). -** -** Otherwise, it attempts to prepare the SQL statement in zSql against -** database handle db. If successful, the statement handle is returned. -** Or, if an error occurs, NULL is returned and an error left in the -** recover handle. -*/ -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; -} - -/* -** This function is a no-op if recover handle p already contains an error -** (if p->errCode!=SQLITE_OK). -** -** Otherwise, argument zFmt is used as a printf() style format string, -** along with any trailing arguments, to create an SQL statement. This -** SQL statement is prepared against database handle db and, if successful, -** the statment handle returned. Or, if an error occurs - either during -** the printf() formatting or when preparing the resulting SQL - an -** error code and message are left in the recover handle. -*/ -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; -} - -/* -** Reset SQLite statement handle pStmt. If the call to sqlite3_reset() -** indicates that an error occurred, and there is not already an error -** in the recover handle passed as the first argument, set the error -** code and error message appropriately. -** -** This function returns a copy of the statement handle pointer passed -** as the second argument. -*/ -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; -} - -/* -** Finalize SQLite statement handle pStmt. If the call to sqlite3_reset() -** indicates that an error occurred, and there is not already an error -** in the recover handle passed as the first argument, set the error -** code and error message appropriately. -*/ -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); - } -} - -/* -** This function is a no-op if recover handle p already contains an error -** (if p->errCode!=SQLITE_OK). A copy of p->errCode is returned in this -** case. -** -** Otherwise, execute SQL script zSql. If successful, return SQLITE_OK. -** Or, if an error occurs, leave an error code and message in the recover -** handle and return a copy of the error code. -*/ -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; -} - -/* -** Bind the value pVal to parameter iBind of statement pStmt. Leave an -** error in the recover handle passed as the first argument if an error -** (e.g. an OOM) occurs. -*/ -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); - } -} - -/* -** This function is a no-op if recover handle p already contains an error -** (if p->errCode!=SQLITE_OK). NULL is returned in this case. -** -** Otherwise, an attempt is made to interpret zFmt as a printf() style -** formatting string and the result of using the trailing arguments for -** parameter substitution with it written into a buffer obtained from -** sqlite3_malloc(). If successful, a pointer to the buffer is returned. -** It is the responsibility of the caller to eventually free the buffer -** using sqlite3_free(). -** -** Or, if an error occurs, an error code and message is left in the recover -** handle and NULL returned. -*/ -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; -} - -/* -** This function is a no-op if recover handle p already contains an error -** (if p->errCode!=SQLITE_OK). Zero is returned in this case. -** -** Otherwise, execute "PRAGMA page_count" against the input database. If -** successful, return the integer result. Or, if an error occurs, leave an -** error code and error message in the sqlite3_recover handle and return -** zero. -*/ -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; -} - -/* -** Implementation of SQL scalar function "read_i32". The first argument to -** this function must be a blob. The second a non-negative integer. This -** function reads and returns a 32-bit big-endian integer from byte -** offset (4*<arg2>) of the blob. -** -** SELECT read_i32(<blob>, <idx>) -*/ -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); - } -} - -/* -** Implementation of SQL scalar function "page_is_used". This function -** is used as part of the procedure for locating orphan rows for the -** lost-and-found table, and it depends on those routines having populated -** the sqlite3_recover.laf.pUsed variable. -** -** The only argument to this function is a page-number. It returns true -** if the page has already been used somehow during data recovery, or false -** otherwise. -** -** SELECT page_is_used(<pgno>); -*/ -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)); -} - -/* -** The implementation of a user-defined SQL function invoked by the -** sqlite_dbdata and sqlite_dbptr virtual table modules to access pages -** of the database being recovered. -** -** This function always takes a single integer argument. If the argument -** is zero, then the value returned is the number of pages in the db being -** recovered. If the argument is greater than zero, it is a page number. -** The value returned in this case is an SQL blob containing the data for -** the identified page of the db being recovered. e.g. -** -** SELECT getpage(0); -- return number of pages in db -** SELECT getpage(4); -- return page 4 of db as a blob of data -*/ -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); - } -} - -/* -** Find a string that is not found anywhere in z[]. Return a pointer -** to that string. -** -** Try to use zA and zB first. If both of those are already found in z[] -** then make up some string and store it in the buffer zBuf. -*/ -static const char *recoverUnusedString( - const char *z, /* Result must not appear anywhere in z */ - const char *zA, const char *zB, /* Try these first */ - char *zBuf /* Space to store a generated string */ -){ - 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; -} - -/* -** Implementation of scalar SQL function "escape_crnl". The argument passed to -** this function is the output of built-in function quote(). If the first -** character of the input is "'", indicating that the value passed to quote() -** was a text value, then this function searches the input for "\n" and "\r" -** characters and adds a wrapper similar to the following: -** -** replace(replace(<input>, '\n', char(10), '\r', char(13)); -** -** Or, if the first character of the input is not "'", then a copy of the input -** is returned. -*/ -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]); -} - -/* -** This function is a no-op if recover handle p already contains an error -** (if p->errCode!=SQLITE_OK). A copy of the error code is returned in -** this case. -** -** Otherwise, attempt to populate temporary table "recovery.schema" with the -** parts of the database schema that can be extracted from the input database. -** -** If no error occurs, SQLITE_OK is returned. Otherwise, an error code -** and error message are left in the recover handle and a copy of the -** error code returned. It is not considered an error if part of all of -** the database schema cannot be recovered due to corruption. -*/ -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" - ); -} - -/* -** If this recover handle is not in SQL callback mode (i.e. was not created -** using sqlite3_recover_init_sql()) of if an error has already occurred, -** this function is a no-op. Otherwise, issue a callback with SQL statement -** zSql as the parameter. -** -** If the callback returns non-zero, set the recover handle error code to -** the value returned (so that the caller will abandon processing). -*/ -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); - } - } -} - -/* -** Transfer the following settings from the input database to the output -** database: -** -** + page-size, -** + auto-vacuum settings, -** + database encoding, -** + user-version (PRAGMA user_version), and -** + application-id (PRAGMA application_id), and -*/ -static void recoverTransferSettings(sqlite3_recover *p){ - const char *aPragma[] = { - "encoding", - "page_size", - "auto_vacuum", - "user_version", - "application_id" - }; - int ii; - - /* Truncate the output database to 0 pages in size. This is done by - ** opening a new, empty, temp db, then using the backup API to clobber - ** any existing output db with a copy of it. */ - 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); - } -} - -/* -** This function is a no-op if recover handle p already contains an error -** (if p->errCode!=SQLITE_OK). A copy of the error code is returned in -** this case. -** -** Otherwise, an attempt is made to open the output database, attach -** and create the schema of the temporary database used to store -** intermediate data, and to register all required user functions and -** virtual table modules with the output handle. -** -** If no error occurs, SQLITE_OK is returned. Otherwise, an error code -** and error message are left in the recover handle and a copy of the -** error code returned. -*/ -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; /* New database handle */ - int ii; /* For iterating through aFunc[] */ - - assert( p->dbOut==0 ); - - if( sqlite3_open_v2(p->zUri, &db, flags, 0) ){ - recoverDbError(p, db); - } - - /* Register the sqlite_dbdata and sqlite_dbptr virtual table modules. - ** These two are registered with the output database handle - this - ** module depends on the input handle supporting the sqlite_dbpage - ** virtual table only. */ - if( p->errCode==SQLITE_OK ){ - p->errCode = sqlite3_dbdata_init(db, 0, 0); - } - - /* Register the custom user-functions with the output handle. */ - 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; -} - -/* -** Attach the auxiliary database 'recovery' to the output database handle. -** This temporary database is used during the recovery process and then -** discarded. -*/ -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); -} - - -/* -** This function is a no-op if recover handle p already contains an error -** (if p->errCode!=SQLITE_OK). -** -** Otherwise, argument zName must be the name of a table that has just been -** created in the output database. This function queries the output db -** for the schema of said table, and creates a RecoverTable object to -** store the schema in memory. The new RecoverTable object is linked into -** the list at sqlite3_recover.pTblList. -** -** Parameter iRoot must be the root page of table zName in the INPUT -** database. -*/ -static void recoverAddTable( - sqlite3_recover *p, - const char *zName, /* Name of table created in output db */ - i64 iRoot /* Root page of same table in INPUT db */ -){ - 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++; - } - } - } -} - -/* -** This function is called after recoverCacheSchema() has cached those parts -** of the input database schema that could be recovered in temporary table -** "recovery.schema". This function creates in the output database copies -** of all parts of that schema that must be created before the tables can -** be populated. Specifically, this means: -** -** * all tables that are not VIRTUAL, and -** * UNIQUE indexes. -** -** If the recovery handle uses SQL callbacks, then callbacks containing -** the associated "CREATE TABLE" and "CREATE INDEX" statements are made. -** -** Additionally, records are added to the sqlite_schema table of the -** output database for any VIRTUAL tables. The CREATE VIRTUAL TABLE -** records are written directly to sqlite_schema, not actually executed. -** If the handle is in SQL callback mode, then callbacks are invoked -** with equivalent SQL statements. -*/ -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; -} - -/* -** This function is called after the output database has been populated. It -** adds all recovered schema elements that were not created in the output -** database by recoverWriteSchema1() - everything except for tables and -** UNIQUE indexes. Specifically: -** -** * views, -** * triggers, -** * non-UNIQUE indexes. -** -** If the recover handle is in SQL callback mode, then equivalent callbacks -** are issued to create the schema elements. -*/ -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; -} - -/* -** This function is a no-op if recover handle p already contains an error -** (if p->errCode!=SQLITE_OK). In this case it returns NULL. -** -** Otherwise, if the recover handle is configured to create an output -** database (was created by sqlite3_recover_init()), then this function -** prepares and returns an SQL statement to INSERT a new record into table -** pTab, assuming the first nField fields of a record extracted from disk -** are valid. -** -** For example, if table pTab is: -** -** CREATE TABLE name(a, b GENERATED ALWAYS AS (a+1) STORED, c, d, e); -** -** And nField is 4, then the SQL statement prepared and returned is: -** -** INSERT INTO (a, c, d) VALUES (?1, ?2, ?3); -** -** In this case even though 4 values were extracted from the input db, -** only 3 are written to the output, as the generated STORED column -** cannot be written. -** -** If the recover handle is in SQL callback mode, then the SQL statement -** prepared is such that evaluating it returns a single row containing -** a single text value - itself an SQL statement similar to the above, -** except with SQL literals in place of the variables. For example: -** -** SELECT 'INSERT INTO (a, c, d) VALUES (' -** || quote(?1) || ', ' -** || quote(?2) || ', ' -** || quote(?3) || ')'; -** -** In either case, it is the responsibility of the caller to eventually -** free the statement handle using sqlite3_finalize(). -*/ -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; -} - - -/* -** Search the list of RecoverTable objects at p->pTblList for one that -** has root page iRoot in the input database. If such an object is found, -** return a pointer to it. Otherwise, return NULL. -*/ -static RecoverTable *recoverFindTable(sqlite3_recover *p, u32 iRoot){ - RecoverTable *pRet = 0; - for(pRet=p->pTblList; pRet && pRet->iRoot!=iRoot; pRet=pRet->pNext); - return pRet; -} - -/* -** This function attempts to create a lost and found table within the -** output db. If successful, it returns a pointer to a buffer containing -** the name of the new table. It is the responsibility of the caller to -** eventually free this buffer using sqlite3_free(). -** -** If an error occurs, NULL is returned and an error code and error -** message left in the recover handle. -*/ -static char *recoverLostAndFoundCreate( - sqlite3_recover *p, /* Recover object */ - int nField /* Number of column fields in new table */ -){ - 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; -} - -/* -** Synthesize and prepare an INSERT statement to write to the lost_and_found -** table in the output database. The name of the table is zTab, and it has -** nField c* fields. -*/ -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; -} - -/* -** Input database page iPg contains data that will be written to the -** lost-and-found table of the output database. This function attempts -** to identify the root page of the tree that page iPg belonged to. -** If successful, it sets output variable (*piRoot) to the page number -** of the root page and returns SQLITE_OK. Otherwise, if an error occurs, -** an SQLite error code is returned and the final value of *piRoot -** undefined. -*/ -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; -} - -/* -** Recover data from page iPage of the input database and write it to -** the lost-and-found table in the output database. -*/ -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 ){ - /* Insert the new row */ - sqlite3_bind_int64(pInsert, 1, iRoot); /* rootpgno */ - sqlite3_bind_int64(pInsert, 2, iPage); /* pgno */ - sqlite3_bind_int(pInsert, 3, nVal); /* nfield */ - if( bHaveRowid ){ - sqlite3_bind_int64(pInsert, 4, iRowid); /* id */ - } - 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); - - /* Discard the accumulated row data */ - 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; - } -} - -/* -** Perform one step (sqlite3_recover_step()) of work for the connection -** passed as the only argument, which is guaranteed to be in -** RECOVER_STATE_LOSTANDFOUND3 state - during which the lost-and-found -** table of the output database is populated with recovered data that can -** not be assigned to any recovered schema object. -*/ -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; -} - -/* -** Initialize resources required in RECOVER_STATE_LOSTANDFOUND3 -** state - during which the lost-and-found table of the output database -** is populated with recovered data that can not be assigned to any -** recovered schema object. -*/ -static void recoverLostAndFound3Init(sqlite3_recover *p){ - RecoverStateLAF *pLaf = &p->laf; - - if( pLaf->nMaxField>0 ){ - char *zTab = 0; /* Name of lost_and_found table */ - - 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*) - ); - } -} - -/* -** Initialize resources required in RECOVER_STATE_WRITING state - during which -** tables recovered from the schema of the input database are populated with -** recovered data. -*/ -static int recoverWriteDataInit(sqlite3_recover *p){ - RecoverStateW1 *p1 = &p->w1; - RecoverTable *pTbl = 0; - int nByte = 0; - - /* Figure out the maximum number of columns for any table in the schema */ - assert( p1->nMax==0 ); - for(pTbl=p->pTblList; pTbl; pTbl=pTbl->pNext){ - if( pTbl->nCol>p1->nMax ) p1->nMax = pTbl->nCol; - } - - /* Allocate an array of (sqlite3_value*) in which to accumulate the values - ** that will be written to the output database in a single row. */ - nByte = sizeof(sqlite3_value*) * (p1->nMax+1); - p1->apVal = (sqlite3_value**)recoverMalloc(p, nByte); - if( p1->apVal==0 ) return p->errCode; - - /* Prepare the SELECT to loop through schema tables (pTbls) and the SELECT - ** to loop through cells that appear to belong to a single table (pSel). */ - 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; -} - -/* -** Clean up resources allocated by recoverWriteDataInit() (stuff in -** sqlite3_recover.w1). -*/ -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)); -} - -/* -** Perform one step (sqlite3_recover_step()) of work for the connection -** passed as the only argument, which is guaranteed to be in -** RECOVER_STATE_WRITING state - during which tables recovered from the -** schema of the input database are populated with recovered data. -*/ -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 this table is unknown, return early. The caller will invoke this - ** function again and it will move on to the next table. */ - if( p1->pTab==0 ) return p->errCode; - - /* If this is the sqlite_sequence table, delete any rows added by - ** earlier INSERT statements on tables with AUTOINCREMENT primary - ** keys before recovering its contents. The p1->pTbls SELECT statement - ** is rigged to deliver "sqlite_sequence" last of all, so we don't - ** worry about it being modified after it is recovered. */ - if( sqlite3_stricmp("sqlite_sequence", p1->pTab->zTab)==0 ){ - recoverExec(p, p->dbOut, "DELETE FROM sqlite_sequence"); - recoverSqlCallback(p, "DELETE FROM sqlite_sequence"); - } - - /* Bind the root page of this table within the original database to - ** SELECT statement p1->pSel. The SELECT statement will then iterate - ** through cells that look like they belong to table pTab. */ - 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; -} - -/* -** Initialize resources required by sqlite3_recover_step() in -** RECOVER_STATE_LOSTANDFOUND1 state - during which the set of pages not -** already allocated to a recovered schema element is determined. -*/ -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); - - /* Prepare a statement to iterate through all pages that are part of any tree - ** in the recoverable part of the input database schema to the bitmap. And, - ** if !p->bFreelistCorrupt, add all pages that appear to be part of the - ** freelist. */ - 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; -} - -/* -** Perform one step (sqlite3_recover_step()) of work for the connection -** passed as the only argument, which is guaranteed to be in -** RECOVER_STATE_LOSTANDFOUND1 state - during which the set of pages not -** already allocated to a recovered schema element is determined. -*/ -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; -} - -/* -** Initialize resources required by RECOVER_STATE_LOSTANDFOUND2 -** state - during which the pages identified in RECOVER_STATE_LOSTANDFOUND1 -** are sorted into sets that likely belonged to the same database tree. -*/ -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 = ?" - ); -} - -/* -** Perform one step (sqlite3_recover_step()) of work for the connection -** passed as the only argument, which is guaranteed to be in -** RECOVER_STATE_LOSTANDFOUND2 state - during which the pages identified -** in RECOVER_STATE_LOSTANDFOUND1 are sorted into sets that likely belonged -** to the same database tree. -*/ -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; -} - -/* -** Free all resources allocated as part of sqlite3_recover_step() calls -** in one of the RECOVER_STATE_LOSTANDFOUND[123] states. -*/ -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; -} - -/* -** Free all resources allocated as part of sqlite3_recover_step() calls. -*/ -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; -} - -/* -** Decode and return an unsigned 16-bit big-endian integer value from -** buffer a[]. -*/ -static u32 recoverGetU16(const u8 *a){ - return (((u32)a[0])<<8) + ((u32)a[1]); -} - -/* -** Decode and return an unsigned 32-bit big-endian integer value from -** buffer a[]. -*/ -static u32 recoverGetU32(const u8 *a){ - return (((u32)a[0])<<24) + (((u32)a[1])<<16) + (((u32)a[2])<<8) + ((u32)a[3]); -} - -/* -** Decode an SQLite varint from buffer a[]. Write the decoded value to (*pVal) -** and return the number of bytes consumed. -*/ -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; -} - -/* -** The second argument points to a buffer n bytes in size. If this buffer -** or a prefix thereof appears to contain a well-formed SQLite b-tree page, -** return the page-size in bytes. Otherwise, if the buffer does not -** appear to contain a well-formed b-tree page, return 0. -*/ -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; /* Number of cells on page */ - int iCellOff = 0; /* Offset of cell array in page */ - 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); - - /* Follow the free-list. This is the same format for all b-tree pages. */ - 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; - } - - /* Run through the cells */ - 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, /* iVersion */ - 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); -} - -/* -** Write value v to buffer a[] as a 16-bit big-endian unsigned integer. -*/ -static void recoverPutU16(u8 *a, u32 v){ - a[0] = (v>>8) & 0x00FF; - a[1] = (v>>0) & 0x00FF; -} - -/* -** Write value v to buffer a[] as a 32-bit big-endian unsigned integer. -*/ -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; -} - -/* -** Detect the page-size of the database opened by file-handle pFd by -** searching the first part of the file for a well-formed SQLite b-tree -** page. If parameter nReserve is non-zero, then as well as searching for -** a b-tree page with zero reserved bytes, this function searches for one -** with nReserve reserved bytes at the end of it. -** -** If successful, set variable p->detected_pgsz to the detected page-size -** in bytes and return SQLITE_OK. Or, if no error occurs but no valid page -** can be found, return SQLITE_OK but leave p->detected_pgsz set to 0. Or, -** if an error occurs (e.g. an IO or OOM error), then an SQLite error code -** is returned. The final value of p->detected_pgsz is undefined in this -** case. -*/ -static int recoverVfsDetectPagesize( - sqlite3_recover *p, /* Recover handle */ - sqlite3_file *pFd, /* File-handle open on input database */ - u32 nReserve, /* Possible nReserve value */ - i64 nSz /* Size of database file in bytes */ -){ - 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; -} - -/* -** The xRead() method of the wrapper VFS. This is used to intercept calls -** to read page 1 of the input database. -*/ -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 ){ - /* Ensure that the database has a valid header file. The only fields - ** that really matter to recovery are: - ** - ** + Database page size (16-bits at offset 16) - ** + Size of db in pages (32-bits at offset 28) - ** + Database encoding (32-bits at offset 56) - ** - ** Also preserved are: - ** - ** + first freelist page (32-bits at offset 32) - ** + size of freelist (32-bits at offset 36) - ** + the wal-mode flags (16-bits at offset 18) - ** - ** We also try to preserve the auto-vacuum, incr-value, user-version - ** and application-id fields - all 32 bit quantities at offsets - ** 52, 60, 64 and 68. All other fields are set to known good values. - ** - ** Byte offset 105 should also contain the page-size as a 16-bit - ** integer. - */ - 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; -} - -/* -** Used to make sqlite3_io_methods wrapper methods less verbose. -*/ -#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; - -/* -** Methods of the wrapper VFS. All methods except for xRead() and xClose() -** simply uninstall the sqlite3_io_methods wrapper, invoke the equivalent -** method on the lower level VFS, then reinstall the wrapper before returning. -** Those that return an integer value use the RECOVER_VFS_WRAPPER macro. -*/ -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; -} - -/* -** Install the VFS wrapper around the file-descriptor open on the input -** database for recover handle p. Mutex RECOVER_MUTEX_ID must be held -** when this function is called. -*/ -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; - } -} - -/* -** Uninstall the VFS wrapper that was installed around the file-descriptor open -** on the input database for recover handle p. Mutex RECOVER_MUTEX_ID must be -** held when this function is called. -*/ -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; - } -} - -/* -** This function does the work of a single sqlite3_recover_step() call. It -** is guaranteed that the handle is not in an error state when this -** function is called. -*/ -static void recoverStep(sqlite3_recover *p){ - assert( p && p->errCode==SQLITE_OK ); - switch( p->eState ){ - case RECOVER_STATE_INIT: - /* This is the very first call to sqlite3_recover_step() on this object. - */ - recoverSqlCallback(p, "BEGIN"); - recoverSqlCallback(p, "PRAGMA writable_schema = on"); - - recoverEnterMutex(); - recoverInstallWrapper(p); - - /* Open the output database. And register required virtual tables and - ** user functions with the new handle. */ - recoverOpenOutput(p); - - /* Open transactions on both the input and output databases. */ - 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; - - /* If no error has occurred, commit the write transaction on the output - ** database. Regardless of whether or not an error has occurred, make - ** an attempt to end the read transaction on the input database. */ - 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: { - /* no-op */ - break; - }; - } -} - - -/* -** This is a worker function that does the heavy lifting for both init -** functions: -** -** sqlite3_recover_init() -** sqlite3_recover_init_sql() -** -** All this function does is allocate space for the recover handle and -** take copies of the input parameters. All the real work is done within -** sqlite3_recover_run(). -*/ -sqlite3_recover *recoverInit( - sqlite3* db, - const char *zDb, - const char *zUri, /* Output URI for _recover_init() */ - int (*xSql)(void*, const char*),/* SQL callback for _recover_init_sql() */ - void *pSqlCtx /* Context arg for _recover_init_sql() */ -){ - 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; -} - -/* -** Initialize a recovery handle that creates a new database containing -** the recovered data. -*/ -sqlite3_recover *sqlite3_recover_init( - sqlite3* db, - const char *zDb, - const char *zUri -){ - return recoverInit(db, zDb, zUri, 0, 0); -} - -/* -** Initialize a recovery handle that returns recovered data in the -** form of SQL statements via a callback. -*/ -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); -} - -/* -** Return the handle error message, if any. -*/ -const char *sqlite3_recover_errmsg(sqlite3_recover *p){ - return (p && p->errCode!=SQLITE_NOMEM) ? p->zErrMsg : "out of memory"; -} - -/* -** Return the handle error code. -*/ -int sqlite3_recover_errcode(sqlite3_recover *p){ - return p ? p->errCode : SQLITE_NOMEM; -} - -/* -** Configure the handle. -*/ -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: - /* This undocumented magic configuration option is used to set the - ** name of the auxiliary database that is ATTACH-ed to the database - ** connection and used to hold state information during the - ** recovery process. This option is for debugging use only and - ** is subject to change or removal at any time. */ - 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; -} - -/* -** Do a unit of work towards the recovery job. Return SQLITE_OK if -** no error has occurred but database recovery is not finished, SQLITE_DONE -** if database recovery has been successfully completed, or an SQLite -** error code if an error has occurred. -*/ -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; -} - -/* -** Do the configured recovery operation. Return SQLITE_OK if successful, or -** else an SQLite error code. -*/ -int sqlite3_recover_run(sqlite3_recover *p){ - while( SQLITE_OK==sqlite3_recover_step(p) ); - return sqlite3_recover_errcode(p); -} - - -/* -** Free all resources associated with the recover handle passed as the only -** argument. The results of using a handle with any sqlite3_recover_** -** API function after it has been passed to this function are undefined. -** -** A copy of the value returned by the first call made to sqlite3_recover_run() -** on this handle is returned, or SQLITE_OK if sqlite3_recover_run() has -** not been called on this handle. -*/ -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 /* ifndef SQLITE_OMIT_VIRTUALTABLE */ -#pragma GCC diagnostic pop |