mirror of
https://github.com/tursodatabase/libsql.git
synced 2024-12-16 02:08:49 +00:00
311 lines
8.4 KiB
C
311 lines
8.4 KiB
C
/*
|
|
** 2017 October 11
|
|
**
|
|
** 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.
|
|
**
|
|
*************************************************************************
|
|
**
|
|
** This module exports a single C function:
|
|
**
|
|
** int sqlite3_check_freelist(sqlite3 *db, const char *zDb);
|
|
**
|
|
** This function checks the free-list in database zDb (one of "main",
|
|
** "temp", etc.) and reports any errors by invoking the sqlite3_log()
|
|
** function. It returns SQLITE_OK if successful, or an SQLite error
|
|
** code otherwise. It is not an error if the free-list is corrupted but
|
|
** no IO or OOM errors occur.
|
|
**
|
|
** If this file is compiled and loaded as an SQLite loadable extension,
|
|
** it adds an SQL function "checkfreelist" to the database handle, to
|
|
** be invoked as follows:
|
|
**
|
|
** SELECT checkfreelist(<database-name>);
|
|
**
|
|
** This function performs the same checks as sqlite3_check_freelist(),
|
|
** except that it returns all error messages as a single text value,
|
|
** separated by newline characters. If the freelist is not corrupted
|
|
** in any way, an empty string is returned.
|
|
**
|
|
** To compile this module for use as an SQLite loadable extension:
|
|
**
|
|
** gcc -Os -fPIC -shared checkfreelist.c -o checkfreelist.so
|
|
*/
|
|
|
|
#include "sqlite3ext.h"
|
|
SQLITE_EXTENSION_INIT1
|
|
|
|
#ifndef SQLITE_AMALGAMATION
|
|
# include <string.h>
|
|
# include <stdio.h>
|
|
# include <stdlib.h>
|
|
# include <assert.h>
|
|
# if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST)
|
|
# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1
|
|
# endif
|
|
# if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS)
|
|
# define ALWAYS(X) (1)
|
|
# define NEVER(X) (0)
|
|
# elif !defined(NDEBUG)
|
|
# define ALWAYS(X) ((X)?1:(assert(0),0))
|
|
# define NEVER(X) ((X)?(assert(0),1):0)
|
|
# else
|
|
# define ALWAYS(X) (X)
|
|
# define NEVER(X) (X)
|
|
# endif
|
|
typedef unsigned char u8;
|
|
typedef unsigned short u16;
|
|
typedef unsigned int u32;
|
|
#define get4byte(x) ( \
|
|
((u32)((x)[0])<<24) + \
|
|
((u32)((x)[1])<<16) + \
|
|
((u32)((x)[2])<<8) + \
|
|
((u32)((x)[3])) \
|
|
)
|
|
#endif
|
|
|
|
/*
|
|
** Execute a single PRAGMA statement and return the integer value returned
|
|
** via output parameter (*pnOut).
|
|
**
|
|
** The SQL statement passed as the third argument should be a printf-style
|
|
** format string containing a single "%s" which will be replace by the
|
|
** value passed as the second argument. e.g.
|
|
**
|
|
** sqlGetInteger(db, "main", "PRAGMA %s.page_count", pnOut)
|
|
**
|
|
** executes "PRAGMA main.page_count" and stores the results in (*pnOut).
|
|
*/
|
|
static int sqlGetInteger(
|
|
sqlite3 *db, /* Database handle */
|
|
const char *zDb, /* Database name ("main", "temp" etc.) */
|
|
const char *zFmt, /* SQL statement format */
|
|
u32 *pnOut /* OUT: Integer value */
|
|
){
|
|
int rc, rc2;
|
|
char *zSql;
|
|
sqlite3_stmt *pStmt = 0;
|
|
int bOk = 0;
|
|
|
|
zSql = sqlite3_mprintf(zFmt, zDb);
|
|
if( zSql==0 ){
|
|
rc = SQLITE_NOMEM;
|
|
}else{
|
|
rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
|
|
sqlite3_free(zSql);
|
|
}
|
|
|
|
if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
|
|
*pnOut = (u32)sqlite3_column_int(pStmt, 0);
|
|
bOk = 1;
|
|
}
|
|
|
|
rc2 = sqlite3_finalize(pStmt);
|
|
if( rc==SQLITE_OK ) rc = rc2;
|
|
if( rc==SQLITE_OK && bOk==0 ) rc = SQLITE_ERROR;
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
** Argument zFmt must be a printf-style format string and must be
|
|
** followed by its required arguments. If argument pzOut is NULL,
|
|
** then the results of printf()ing the format string are passed to
|
|
** sqlite3_log(). Otherwise, they are appended to the string
|
|
** at (*pzOut).
|
|
*/
|
|
static int checkFreelistError(char **pzOut, const char *zFmt, ...){
|
|
int rc = SQLITE_OK;
|
|
char *zErr = 0;
|
|
va_list ap;
|
|
|
|
va_start(ap, zFmt);
|
|
zErr = sqlite3_vmprintf(zFmt, ap);
|
|
if( zErr==0 ){
|
|
rc = SQLITE_NOMEM;
|
|
}else{
|
|
if( pzOut ){
|
|
*pzOut = sqlite3_mprintf("%s%z%s", *pzOut?"\n":"", *pzOut, zErr);
|
|
if( *pzOut==0 ) rc = SQLITE_NOMEM;
|
|
}else{
|
|
sqlite3_log(SQLITE_ERROR, "checkfreelist: %s", zErr);
|
|
}
|
|
sqlite3_free(zErr);
|
|
}
|
|
va_end(ap);
|
|
return rc;
|
|
}
|
|
|
|
static int checkFreelist(
|
|
sqlite3 *db,
|
|
const char *zDb,
|
|
char **pzOut
|
|
){
|
|
/* This query returns one row for each page on the free list. Each row has
|
|
** two columns - the page number and page content. */
|
|
const char *zTrunk =
|
|
"WITH freelist_trunk(i, d, n) AS ("
|
|
"SELECT 1, NULL, sqlite_readint32(data, 32) "
|
|
"FROM sqlite_dbpage(:1) WHERE pgno=1 "
|
|
"UNION ALL "
|
|
"SELECT n, data, sqlite_readint32(data) "
|
|
"FROM freelist_trunk, sqlite_dbpage(:1) WHERE pgno=n "
|
|
")"
|
|
"SELECT i, d FROM freelist_trunk WHERE i!=1;";
|
|
|
|
int rc, rc2; /* Return code */
|
|
sqlite3_stmt *pTrunk = 0; /* Compilation of zTrunk */
|
|
u32 nPage = 0; /* Number of pages in db */
|
|
u32 nExpected = 0; /* Expected number of free pages */
|
|
u32 nFree = 0; /* Number of pages on free list */
|
|
|
|
if( zDb==0 ) zDb = "main";
|
|
|
|
if( (rc = sqlGetInteger(db, zDb, "PRAGMA %s.page_count", &nPage))
|
|
|| (rc = sqlGetInteger(db, zDb, "PRAGMA %s.freelist_count", &nExpected))
|
|
){
|
|
return rc;
|
|
}
|
|
|
|
rc = sqlite3_prepare_v2(db, zTrunk, -1, &pTrunk, 0);
|
|
if( rc!=SQLITE_OK ) return rc;
|
|
sqlite3_bind_text(pTrunk, 1, zDb, -1, SQLITE_STATIC);
|
|
while( rc==SQLITE_OK && sqlite3_step(pTrunk)==SQLITE_ROW ){
|
|
u32 i;
|
|
u32 iTrunk = (u32)sqlite3_column_int(pTrunk, 0);
|
|
const u8 *aData = (const u8*)sqlite3_column_blob(pTrunk, 1);
|
|
u32 nData = (u32)sqlite3_column_bytes(pTrunk, 1);
|
|
u32 iNext = get4byte(&aData[0]);
|
|
u32 nLeaf = get4byte(&aData[4]);
|
|
|
|
if( nLeaf>((nData/4)-2-6) ){
|
|
rc = checkFreelistError(pzOut,
|
|
"leaf count out of range (%d) on trunk page %d",
|
|
(int)nLeaf, (int)iTrunk
|
|
);
|
|
nLeaf = (nData/4) - 2 - 6;
|
|
}
|
|
|
|
nFree += 1+nLeaf;
|
|
if( iNext>nPage ){
|
|
rc = checkFreelistError(pzOut,
|
|
"trunk page %d is out of range", (int)iNext
|
|
);
|
|
}
|
|
|
|
for(i=0; rc==SQLITE_OK && i<nLeaf; i++){
|
|
u32 iLeaf = get4byte(&aData[8 + 4*i]);
|
|
if( iLeaf==0 || iLeaf>nPage ){
|
|
rc = checkFreelistError(pzOut,
|
|
"leaf page %d is out of range (child %d of trunk page %d)",
|
|
(int)iLeaf, (int)i, (int)iTrunk
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if( rc==SQLITE_OK && nFree!=nExpected ){
|
|
rc = checkFreelistError(pzOut,
|
|
"free-list count mismatch: actual=%d header=%d",
|
|
(int)nFree, (int)nExpected
|
|
);
|
|
}
|
|
|
|
rc2 = sqlite3_finalize(pTrunk);
|
|
if( rc==SQLITE_OK ) rc = rc2;
|
|
return rc;
|
|
}
|
|
|
|
int sqlite3_check_freelist(sqlite3 *db, const char *zDb){
|
|
return checkFreelist(db, zDb, 0);
|
|
}
|
|
|
|
static void checkfreelist_function(
|
|
sqlite3_context *pCtx,
|
|
int nArg,
|
|
sqlite3_value **apArg
|
|
){
|
|
const char *zDb;
|
|
int rc;
|
|
char *zOut = 0;
|
|
sqlite3 *db = sqlite3_context_db_handle(pCtx);
|
|
|
|
assert( nArg==1 );
|
|
zDb = (const char*)sqlite3_value_text(apArg[0]);
|
|
rc = checkFreelist(db, zDb, &zOut);
|
|
if( rc==SQLITE_OK ){
|
|
sqlite3_result_text(pCtx, zOut?zOut:"ok", -1, SQLITE_TRANSIENT);
|
|
}else{
|
|
sqlite3_result_error_code(pCtx, rc);
|
|
}
|
|
|
|
sqlite3_free(zOut);
|
|
}
|
|
|
|
/*
|
|
** An SQL function invoked as follows:
|
|
**
|
|
** sqlite_readint32(BLOB) -- Decode 32-bit integer from start of blob
|
|
*/
|
|
static void readint_function(
|
|
sqlite3_context *pCtx,
|
|
int nArg,
|
|
sqlite3_value **apArg
|
|
){
|
|
const u8 *zBlob;
|
|
int nBlob;
|
|
int iOff = 0;
|
|
u32 iRet = 0;
|
|
|
|
if( nArg!=1 && nArg!=2 ){
|
|
sqlite3_result_error(
|
|
pCtx, "wrong number of arguments to function sqlite_readint32()", -1
|
|
);
|
|
return;
|
|
}
|
|
if( nArg==2 ){
|
|
iOff = sqlite3_value_int(apArg[1]);
|
|
}
|
|
|
|
zBlob = sqlite3_value_blob(apArg[0]);
|
|
nBlob = sqlite3_value_bytes(apArg[0]);
|
|
|
|
if( nBlob>=(iOff+4) ){
|
|
iRet = get4byte(&zBlob[iOff]);
|
|
}
|
|
|
|
sqlite3_result_int64(pCtx, (sqlite3_int64)iRet);
|
|
}
|
|
|
|
/*
|
|
** Register the SQL functions.
|
|
*/
|
|
static int cflRegister(sqlite3 *db){
|
|
int rc = sqlite3_create_function(
|
|
db, "sqlite_readint32", -1, SQLITE_UTF8, 0, readint_function, 0, 0
|
|
);
|
|
if( rc!=SQLITE_OK ) return rc;
|
|
rc = sqlite3_create_function(
|
|
db, "checkfreelist", 1, SQLITE_UTF8, 0, checkfreelist_function, 0, 0
|
|
);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
** Extension load function.
|
|
*/
|
|
#ifdef _WIN32
|
|
__declspec(dllexport)
|
|
#endif
|
|
int sqlite3_checkfreelist_init(
|
|
sqlite3 *db,
|
|
char **pzErrMsg,
|
|
const sqlite3_api_routines *pApi
|
|
){
|
|
SQLITE_EXTENSION_INIT2(pApi);
|
|
return cflRegister(db);
|
|
}
|