]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add the checkfreelist extension to the command-line shell.
authordrh <drh@noemail.net>
Wed, 11 Oct 2017 18:26:26 +0000 (18:26 +0000)
committerdrh <drh@noemail.net>
Wed, 11 Oct 2017 18:26:26 +0000 (18:26 +0000)
FossilOrigin-Name: 48418f2ed5ab1cb270776166141ce32ed3ebf22ed4e33a66a204d4fde9d11f52

ext/misc/checkfreelist.c
manifest
manifest.uuid
src/shell.c
src/shell.c.in

index 345f26c2236f682d71f1a8f29285cf9959aa59fb..cd2801e0403bbcdb4191d92f2a8ce734e11c5803 100644 (file)
@@ -223,7 +223,7 @@ static void checkfreelist_function(
   sqlite3 *db = sqlite3_context_db_handle(pCtx);
 
   assert( nArg==1 );
-  zDb = sqlite3_value_text(apArg[0]);
+  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);
index 7a6b18c2305afd4888d88bf456a5104f9e258bb3..c2dd3cb1eb2965efb302b29d8134a78e092d218e 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Check\sthat\sthe\sleaf\scount\son\seach\sfreelist\strunk\spage\sis\sin\srange\sas\spart\sof\ncheckfreelist\sprocessing.
-D 2017-10-11T18:21:44.429
+C Add\sthe\scheckfreelist\sextension\sto\sthe\scommand-line\sshell.
+D 2017-10-11T18:26:26.636
 F Makefile.in 05d02ce8606a9e46cd413d0bb46873fe597e5e41f52c4110241c11e60adff018
 F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
 F Makefile.msc 148d7cd36e556f5c257232cd93c71a1dd32c880d964c7d714990d677cd094589
@@ -259,7 +259,7 @@ F ext/misc/README.md 8e008c8d2b02e09096b31dfba033253ac27c6c06a18aa5826e299fa7601
 F ext/misc/amatch.c 6db4607cb17c54b853a2d7c7c36046d004853f65b9b733e6f019d543d5dfae87
 F ext/misc/anycollseq.c 5ffdfde9829eeac52219136ad6aa7cd9a4edb3b15f4f2532de52f4a22525eddb
 F ext/misc/carray.c ed96c218ea940b85c9a274c4d9c59fe9491c299147a38a8bba537687bd6c6005
-F ext/misc/checkfreelist.c fc46557e73a6233bd698815d3963acc44bf4dba0ca9c91c90be361cca49d6b3e
+F ext/misc/checkfreelist.c 0abb84b4545016d57ba1a2aa8884c72c73ed838968909858c03bc1f38fb6b054
 F ext/misc/closure.c 0d2a038df8fbae7f19de42e7c7d71f2e4dc88704
 F ext/misc/completion.c 52c3f01523e3e387eb321b4739a89d1fe47cbe6025aa1f2d8d3685e9e365df0f
 F ext/misc/compress.c 122faa92d25033d6c3f07c39231de074ab3d2e83
@@ -461,8 +461,8 @@ F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384
 F src/resolve.c 4324a94573b1e29286f8121e4881db59eaedc014afeb274c8d3e07ed282e0e20
 F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac
 F src/select.c 42aca61e739c405ddd8a1b702977a7743c7d52a94885f7c5596bd7e73e6bff18
-F src/shell.c b1c14539ae8f756a96a5604952e24fb8f2a65745290037f4f43dddfabac76e6e
-F src/shell.c.in 73d8000bb066cd7ceb9655ffdb0e19a80779e3c64506f5a1ecfa9838511bee18
+F src/shell.c ffb06532d6667bf1bb64080a316120c67249636a12f008c2f9716d6778565d57
+F src/shell.c.in 7842db264d5512520c61d0353196eeefeb65b710dd0d97d4ad9844c56e313be5
 F src/sqlite.h.in ab4f8a29d1580dfaeb6891fa1b83cff8229ba0daa56994707ceaca71495d9ab7
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h a1fd3aa82f967da436164e0728a7d6841651fd0c6e27b9044e0eb9f6c8462e47
@@ -1660,7 +1660,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 21930ef5376261d95fa325be7761d327a350d4ae6b4573c83ddb4d294dea51c4
-R f25a07255c10ac9143e97a5d9d8e737e
-U dan
-Z fe2b858df1a95da0fbc85fd6ca1ee6ea
+P 4e89406248f51d3b83d61e5472fb493f3d3b4ff2a69bf256c7e15445eeb2f3ec
+R 0bf65201c138febf2eab9baca075a19d
+U drh
+Z f892c9c71f9811efc31d97f9328db29a
index a9df98f06fab096dcef3914e17257c745b9e7b3b..eb5db275813f7d22bc8de6970c5a7dc72be23461 100644 (file)
@@ -1 +1 @@
-4e89406248f51d3b83d61e5472fb493f3d3b4ff2a69bf256c7e15445eeb2f3ec
\ No newline at end of file
+48418f2ed5ab1cb270776166141ce32ed3ebf22ed4e33a66a204d4fde9d11f52
\ No newline at end of file
index 2b77d482e629c30a88fdc98c685b10153a65a917..6c14db9fdd9f39e20301ecaa05cab6589b3f2384 100644 (file)
@@ -2156,6 +2156,307 @@ int sqlite3_completion_init(
 }
 
 /************************* End ../ext/misc/completion.c ********************/
+/************************* Begin ../ext/misc/checkfreelist.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
+*/
+
+SQLITE_EXTENSION_INIT1
+
+#ifndef SQLITE_AMALGAMATION
+# include <string.h>
+# include <stdio.h>
+# include <stdlib.h>
+# include <assert.h>
+# define ALWAYS(X)  1
+# define NEVER(X)   0
+  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);
+    int nData = 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
+
+#endif
+int sqlite3_checkfreelist_init(
+  sqlite3 *db, 
+  char **pzErrMsg, 
+  const sqlite3_api_routines *pApi
+){
+  SQLITE_EXTENSION_INIT2(pApi);
+  return cflRegister(db);
+}
+
+/************************* End ../ext/misc/checkfreelist.c ********************/
 
 #if defined(SQLITE_ENABLE_SESSION)
 /*
@@ -4246,6 +4547,7 @@ static void open_db(ShellState *p, int keepAlive){
     sqlite3_fileio_init(p->db, 0, 0);
     sqlite3_shathree_init(p->db, 0, 0);
     sqlite3_completion_init(p->db, 0, 0);
+    sqlite3_checkfreelist_init(p->db, 0, 0);
     sqlite3_create_function(p->db, "shell_add_schema", 2, SQLITE_UTF8, 0,
                             shellAddSchemaName, 0, 0);
   }
index 54a61b94563d57b67b9789d3610301d8f903327b..de170887f7b8f14ec9ab23e3a65da8ff1a2aea14 100644 (file)
@@ -796,6 +796,7 @@ static void shellAddSchemaName(
 INCLUDE ../ext/misc/shathree.c
 INCLUDE ../ext/misc/fileio.c
 INCLUDE ../ext/misc/completion.c
+INCLUDE ../ext/misc/checkfreelist.c
 
 #if defined(SQLITE_ENABLE_SESSION)
 /*
@@ -2886,6 +2887,7 @@ static void open_db(ShellState *p, int keepAlive){
     sqlite3_fileio_init(p->db, 0, 0);
     sqlite3_shathree_init(p->db, 0, 0);
     sqlite3_completion_init(p->db, 0, 0);
+    sqlite3_checkfreelist_init(p->db, 0, 0);
     sqlite3_create_function(p->db, "shell_add_schema", 2, SQLITE_UTF8, 0,
                             shellAddSchemaName, 0, 0);
   }