SQLite

Artifact [e6eb20c4]
Login

Artifact e6eb20c469bcdb96f297f2241860bccabf9f036bfa7f3d47bcc6ca1191b108dc:


/*
** 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.
**
*************************************************************************
**
*/


#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 */