summaryrefslogtreecommitdiffstats
path: root/database/sqlite/sqlite3recover.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 11:19:16 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 12:07:37 +0000
commitb485aab7e71c1625cfc27e0f92c9509f42378458 (patch)
treeae9abe108601079d1679194de237c9a435ae5b55 /database/sqlite/sqlite3recover.c
parentAdding upstream version 1.44.3. (diff)
downloadnetdata-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.c2872
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