Repurpose the SQLITE_TESTCTRL_FAULT_INSTALL test-control to register a
callback to be invoked by sqlite3FaultSim().  That test-control has been
unused since 2008-06-20 and was never used in any official release.

FossilOrigin-Name: 0d43a7ad9abe821e33e0bf83a997aa4461b1e3f2
diff --git a/src/global.c b/src/global.c
index 1ee3f64..2c14b58 100644
--- a/src/global.c
+++ b/src/global.c
@@ -173,15 +173,22 @@
    0,                         /* isMutexInit */
    0,                         /* isMallocInit */
    0,                         /* isPCacheInit */
-   0,                         /* pInitMutex */
    0,                         /* nRefInitMutex */
+   0,                         /* pInitMutex */
    0,                         /* xLog */
    0,                         /* pLogArg */
-   0,                         /* bLocaltimeFault */
 #ifdef SQLITE_ENABLE_SQLLOG
    0,                         /* xSqllog */
-   0                          /* pSqllogArg */
+   0,                         /* pSqllogArg */
 #endif
+#ifdef SQLITE_VDBE_COVERAGE
+   0,                         /* xVdbeBranch */
+   0,                         /* pVbeBranchArg */
+#endif
+#ifndef SQLITE_OMIT_BUILTIN_TEST
+   0,                         /* xTestCallback */
+#endif
+   0                          /* bLocaltimeFault */
 };
 
 /*
diff --git a/src/main.c b/src/main.c
index 65521f4..b3bc66e 100644
--- a/src/main.c
+++ b/src/main.c
@@ -3115,6 +3115,23 @@
     }
 
     /*
+    **  sqlite3_test_control(FAULT_INSTALL, xCallback)
+    **
+    ** Arrange to invoke xCallback() whenever sqlite3FaultSim() is called,
+    ** if xCallback is not NULL.
+    **
+    ** As a test of the fault simulator mechanism itself, sqlite3FaultSim(0)
+    ** is called immediately after installing the new callback and the return
+    ** value from sqlite3FaultSim(0) becomes the return from
+    ** sqlite3_test_control().
+    */
+    case SQLITE_TESTCTRL_FAULT_INSTALL: {
+      sqlite3Config.xTestCallback = va_arg(ap, int(*)(int));
+      rc = sqlite3FaultSim(0);
+      break;
+    }
+
+    /*
     **  sqlite3_test_control(BENIGN_MALLOC_HOOKS, xBegin, xEnd)
     **
     ** Register hooks to call to indicate which malloc() failures 
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 4bd65a6..bb8763c 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -2709,11 +2709,10 @@
   int isMutexInit;                  /* True after mutexes are initialized */
   int isMallocInit;                 /* True after malloc is initialized */
   int isPCacheInit;                 /* True after malloc is initialized */
-  sqlite3_mutex *pInitMutex;        /* Mutex used by sqlite3_initialize() */
   int nRefInitMutex;                /* Number of users of pInitMutex */
+  sqlite3_mutex *pInitMutex;        /* Mutex used by sqlite3_initialize() */
   void (*xLog)(void*,int,const char*); /* Function for logging */
   void *pLogArg;                       /* First argument to xLog() */
-  int bLocaltimeFault;              /* True to fail localtime() calls */
 #ifdef SQLITE_ENABLE_SQLLOG
   void(*xSqllog)(void*,sqlite3*,const char*, int);
   void *pSqllogArg;
@@ -2725,6 +2724,10 @@
   void (*xVdbeBranch)(void*,int iSrcLine,u8 eThis,u8 eMx);  /* Callback */
   void *pVdbeBranchArg;                                     /* 1st argument */
 #endif
+#ifndef SQLITE_OMIT_BUILTIN_TEST
+  int (*xTestCallback)(int);        /* Invoked by sqlite3FaultSim() */
+#endif
+  int bLocaltimeFault;              /* True to fail localtime() calls */
 };
 
 /*
@@ -3026,6 +3029,12 @@
 Btree *sqlite3DbNameToBtree(sqlite3*,const char*);
 int sqlite3CodeOnce(Parse *);
 
+#ifdef SQLITE_OMIT_BUILTIN_TEST
+# define sqlite3FaultSim(X) SQLITE_OK
+#else
+  int sqlite3FaultSim(int);
+#endif
+
 Bitvec *sqlite3BitvecCreate(u32);
 int sqlite3BitvecTest(Bitvec*, u32);
 int sqlite3BitvecSet(Bitvec*, u32);
diff --git a/src/test2.c b/src/test2.c
index d130e9d..58f271f 100644
--- a/src/test2.c
+++ b/src/test2.c
@@ -568,7 +568,90 @@
   rc = sqlite3_test_control(SQLITE_TESTCTRL_PENDING_BYTE, pbyte);
   Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
   return TCL_OK;
-}  
+}
+
+/*
+** The sqlite3FaultSim() callback:
+*/
+static Tcl_Interp *faultSimInterp = 0;
+static int faultSimScriptSize = 0;
+static char *faultSimScript;
+static int faultSimCallback(int x){
+  char zInt[30];
+  int i;
+  int isNeg;
+  int rc;
+  if( x==0 ){
+    memcpy(faultSimScript+faultSimScriptSize, "0", 2);
+  }else{
+    /* Convert x to text without using any sqlite3 routines */
+    if( x<0 ){
+      isNeg = 1;
+      x = -x;
+    }else{
+      isNeg = 0;
+    }
+    zInt[sizeof(zInt)-1] = 0;
+    for(i=sizeof(zInt)-2; i>0 && x>0; i--, x /= 10){
+      zInt[i] = (x%10) + '0';
+    }
+    if( isNeg ) zInt[i--] = '-';
+    memcpy(faultSimScript+faultSimScriptSize, zInt+i+1, sizeof(zInt)-i);
+  }
+  rc = Tcl_Eval(faultSimInterp, faultSimScript);
+  if( rc ){
+    fprintf(stderr, "fault simulator script failed: [%s]", faultSimScript);
+    rc = SQLITE_ERROR;
+  }else{
+    rc = atoi(Tcl_GetStringResult(faultSimInterp));
+  }
+  Tcl_ResetResult(faultSimInterp);
+  return rc;
+}
+
+/*
+** sqlite3_test_control_fault_install SCRIPT
+**
+** Arrange to invoke SCRIPT with the integer argument to sqlite3FaultSim()
+** appended, whenever sqlite3FaultSim() is called.  Or, if SCRIPT is the
+** empty string, cancel the sqlite3FaultSim() callback.
+*/
+static int faultInstallCmd(
+  void *NotUsed,
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int argc,              /* Number of arguments */
+  const char **argv      /* Text of each argument */
+){
+  const char *zScript;
+  int nScript;
+  int rc;
+  if( argc!=1 && argc!=2 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+                     " SCRIPT\"", (void*)0);
+  }
+  zScript = argc==2 ? argv[1] : "";
+  nScript = (int)strlen(zScript);
+  if( faultSimScript ){
+    free(faultSimScript);
+    faultSimScript = 0;
+  }
+  if( nScript==0 ){
+    rc = sqlite3_test_control(SQLITE_TESTCTRL_FAULT_INSTALL, 0);
+  }else{
+    faultSimScript = malloc( nScript+100 );
+    if( faultSimScript==0 ){
+      Tcl_AppendResult(interp, "out of memory", (void*)0);
+      return SQLITE_ERROR;
+    }
+    memcpy(faultSimScript, zScript, nScript);
+    faultSimScript[nScript] = ' ';
+    faultSimScriptSize = nScript+1;
+    faultSimInterp = interp;
+    rc = sqlite3_test_control(SQLITE_TESTCTRL_FAULT_INSTALL, faultSimCallback);
+  }
+  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
+  return SQLITE_OK;
+}
 
 /*
 ** sqlite3BitvecBuiltinTest SIZE PROGRAM
@@ -638,7 +721,8 @@
     { "fake_big_file",           (Tcl_CmdProc*)fake_big_file       },
 #endif
     { "sqlite3BitvecBuiltinTest",(Tcl_CmdProc*)testBitvecBuiltinTest     },
-    { "sqlite3_test_control_pending_byte", (Tcl_CmdProc*)testPendingByte },
+    { "sqlite3_test_control_pending_byte",  (Tcl_CmdProc*)testPendingByte },
+    { "sqlite3_test_control_fault_install", (Tcl_CmdProc*)faultInstallCmd },
   };
   int i;
   for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
diff --git a/src/util.c b/src/util.c
index 577d552..4fe07a8 100644
--- a/src/util.c
+++ b/src/util.c
@@ -31,6 +31,24 @@
 }
 #endif
 
+/*
+** Give a callback to the test harness that can be used to simulate faults
+** in places where it is difficult or expensive to do so purely by means
+** of inputs.
+**
+** The intent of the integer argument is to let the fault simulator know
+** which of multiple sqlite3FaultSim() calls has been hit.
+**
+** Return whatever integer value the test callback returns, or return
+** SQLITE_OK if no test callback is installed.
+*/
+#ifndef SQLITE_OMIT_BUILTIN_TEST
+int sqlite3FaultSim(int iTest){
+  int (*xCallback)(int) = sqlite3GlobalConfig.xTestCallback;
+  return xCallback ? xCallback(iTest) : SQLITE_OK;
+}
+#endif
+
 #ifndef SQLITE_OMIT_FLOATING_POINT
 /*
 ** Return true if the floating point value is Not a Number (NaN).