]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
CLI closer to doing full cleanup on error exits. Needs testing with OOM simulation.
authorlarrybr <larrybr@noemail.net>
Sat, 13 May 2023 07:23:14 +0000 (07:23 +0000)
committerlarrybr <larrybr@noemail.net>
Sat, 13 May 2023 07:23:14 +0000 (07:23 +0000)
FossilOrigin-Name: 8751f93fa505a514d8ab7eae4f9093310ee60b90046f4632e80858001781cb31

1  2 
manifest
manifest.uuid
src/resmanage.c
src/resmanage.h
src/shell.c.in

diff --cc manifest
index 9b21651f1032b63f6d6c087910113d751c22c417,20ce1b4b84468c195a03a9b35b4fe2e13d29d621..f6ac686c3c9be5b54815101a7b24122ac39827d4
+++ b/manifest
@@@ -1,5 -1,5 +1,5 @@@
- C CLI\sresource\smanagement\sapplied\sto\sabout\s30%\sof\sshell\ssource.\sA\sfew\sminor\sflaws\sfixed.\sComments\sbegin\sto\sdescribe\sOOM\sresponses.
- D 2023-05-12T21:21:37.123
 -C Fix\sharmless\scompiler\swarning\scaused\sby\s[0772ddf56713d013].
 -D 2023-05-12T19:06:00.854
++C CLI\scloser\sto\sdoing\sfull\scleanup\son\serror\sexits.\sNeeds\stesting\swith\sOOM\ssimulation.
++D 2023-05-13T07:23:14.350
  F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
  F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
  F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@@ -637,13 -633,10 +637,13 @@@ F src/pragma.h e690a356c18e98414d2e870e
  F src/prepare.c 6350675966bd0e7ac3a464af9dbfe26db6f0d4237f4e1f1acdb17b12ad371e6e
  F src/printf.c b9320cdbeca0b336c3f139fd36dd121e4167dd62b35fbe9ccaa9bab44c0af38d
  F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
- F src/resmanage.c e013c6a7703cfa11db74fd6269e9cecd5eb5ae371c3702bff0ddd1205e5a555a
- F src/resmanage.h 4f4c0359cee4b3cdfff404851c5a877dfdd075ffdc8206c289fc05ea39a9c685
++F src/resmanage.c 3495deb8d4e7499eec30c1de4c4ce85a610a611f42fd74a24584416f0d1f9aba
++F src/resmanage.h 9f937b7d2334b15f5ed2c3f38d707fec5062b414ecd2483d788a4325855eb3fd
  F src/resolve.c 3e53e02ce87c9582bd7e7d22f13f4094a271678d9dc72820fa257a2abb5e4032
  F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
  F src/select.c 12aa3168be4ff175702fe0ebeaf544312be22d275d378a28e7b2fad32d552d36
- F src/shell.c.in 924a3467c33c4cc6211c1f35d534f57f88d0e79005370982aeb2428d0951d6f0
 -F src/shell.c.in 1e18312f58d365042036fc9d19dcef416074f783702b168f07814332c2268ee0
++F src/shell.c.in d5761d88d2f47ba75c778f0383573f7def7eb6ba4248c301eda09f40606e9c42
 +F src/shext_linkage.h 27dcf7624df05b2a7a6d367834339a6db3636f3035157f641f7db2ec499f8f6d
  F src/sqlite.h.in 27ca1d4b2eda8feee468af5735182390e8fe4696522751eec0136d17323201ad
  F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
  F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4
@@@ -2079,8 -2069,8 +2080,8 @@@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a9
  F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
  F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
  F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
- P efdcf1093b4a327da36b5854cff32a8244244302a5f979859c1398e8d191fc6b 0772ddf56713d013cd1bd44f9c75977ca14f852e3a8f038b0a6b9814f6519d79 504effa89b48c0dc6cd12b3eaf6017be3f32017c601af17759a3cc185346d868
- R 54bc1318f5a495f2da0644115fe872c7
 -P 559cb1c6de384fd14160db005acaf1858b8b018d3d40d7f786a56a49d3cdbd59
 -R 4b606a426eae6c5f8f90a511d40b128f
 -U drh
 -Z 4e1ae7be32b97c6dbec7b8961e4a74ee
++P ce25a07950e10e5f0c33f179f9b7d307a73b23ad859b5a97e5c6d7bc9e68b254 f06c16a8b0e7a15ce4f7d99af3376a1bf1bfbfc0fdc048b079418ae74c619d6b
++R 383a924fa91f5b4145d9f04d112422b7
 +U larrybr
- Z d6d73699c601b55c7b4dec78c2a1df9f
++Z 69b83efe69b378ac6c729ccaf8b045c5
  # Remove this line to create a well-formed Fossil manifest.
diff --cc manifest.uuid
index 6f5fcbf80b1fc1b3ff4dcf4f2cfcddd9aab4b40f,4291b084319317bfc3a546100af3122d19dc80e9..889ced5443ae2ea81c19db03845e16e2992b9eb4
@@@ -1,1 -1,1 +1,1 @@@
- ce25a07950e10e5f0c33f179f9b7d307a73b23ad859b5a97e5c6d7bc9e68b254
 -f06c16a8b0e7a15ce4f7d99af3376a1bf1bfbfc0fdc048b079418ae74c619d6b
++8751f93fa505a514d8ab7eae4f9093310ee60b90046f4632e80858001781cb31
diff --cc src/resmanage.c
index b4ae2aecf09f43df3d77908f12756c4f4873c013,0000000000000000000000000000000000000000..f0f79bf39c7068e03b9442621c2564d57c508655
mode 100644,000000..100644
--- /dev/null
@@@ -1,302 -1,0 +1,339 @@@
-     fprintf(stderr, "Quitting due to %s, freeing %d resources.\n",
-             zMoan, numResHold);
 +/*
 +** 2023 May 8
 +**
 +** 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 file implements a simple resource management package, interface
 +** and function of which is described in resmanage.h .
 +*/
 +
 +#include <stdlib.h>
 +#include <stdio.h>
 +
 +#include <assert.h>
 +#include "resmanage.h"
 +
 +/* Track how to free various held resources. */
 +typedef enum FreeableResourceKind {
++  FRK_Indirect = 1<<15,
 +  FRK_Malloc = 0, FRK_DbConn = 1, FRK_DbStmt = 2,
 +  FRK_DbMem = 3, FRK_File = 4,
 +#if (!defined(_WIN32) && !defined(WIN32)) || !SQLITE_OS_WINRT
 +  FRK_Pipe,
 +#endif
 +#ifdef SHELL_MANAGE_TEXT
 +  FRK_Text,
 +#endif
++  FRK_AnyRef,
 +  FRK_CustomBase /* series of values for custom freers */
 +} FreeableResourceKind;
 +
 +#if defined(_WIN32) || defined(WIN32)
 +# if !SQLITE_OS_WINRT
 +#  include <io.h>
 +#  include <fcntl.h>
 +#  undef pclose
 +#  define pclose _pclose
 +# endif
 +#else
 +extern int pclose(FILE*);
 +#endif
 +
 +typedef struct ResourceHeld {
 +  union {
 +    void *p_any;
 +    void *p_malloced;
 +    sqlite3 *p_conn;
 +    sqlite3_stmt *p_stmt;
 +    void *p_s3mem;
 +    FILE *p_stream;
 +#ifdef SHELL_MANAGE_TEXT
 +    ShellText *p_text;
 +#endif
++    AnyResourceHolder *p_any_rh;
 +  } held;
 +  FreeableResourceKind frk;
 +} ResourceHeld;
 +
 +/* The held-resource stack. This is for single-threaded use only. */
 +static ResourceHeld *pResHold = 0;
 +static ResourceCount numResHold = 0;
 +static ResourceCount numResAlloc = 0;
 +
 +/* A small set of custom freers. It is linearly searched, used for
 +** layered heap-allocated (and other-allocated) data structures, so
 +** tends to have use limited to where slow things are happening.
 +*/
 +static ResourceCount numCustom = 0; /* number of the set */
 +static ResourceCount numCustomAlloc = 0; /* allocated space */
 +typedef void (*FreerFunction)(void *);
 +static FreerFunction *aCustomFreers = 0; /* content of set */
 +
 +const char *resmanage_oom_message = "out of memory, aborting";
 +
 +/* Info recorded in support of quit_moan(...) and stack-ripping */
 +static ResourceMark exit_mark = 0;
 +#ifndef SHELL_OMIT_LONGJMP
 +static jmp_buf *p_exit_ripper = 0;
 +#endif
 +
 +/* Current position of the held-resource stack */
 +ResourceMark holder_mark(){
 +  return numResHold;
 +}
 +
 +/* Strip resource stack then strip call stack (or exit.) */
 +void quit_moan(const char *zMoan, int errCode){
++  int nFreed;
 +  if( zMoan ){
-   holder_free(exit_mark);
++    fprintf(stderr, "Error: Terminating due to %s.\n", zMoan);
 +  }
- static void free_rk( ResourceHeld *pRH ){
-   if( pRH->held.p_any == 0 ) return;
++  fprintf(stderr, "Auto-freed %d resources.\n", holder_free(exit_mark));
 +#ifndef SHELL_OMIT_LONGJMP
 +  if( p_exit_ripper!=0 ){
 +    longjmp(*p_exit_ripper, errCode);
 +  } else
 +#endif
 +    exit(errCode);
 +}
 +
 +/* Free a single resource item. (ignorant of stack) */
-       }
++static int free_rk( ResourceHeld *pRH ){
++  int rv = 0;
++  if( pRH->held.p_any == 0 ) return rv;
++  if( pRH->frk & FRK_Indirect ){
++    pRH->held.p_any = *(void**)pRH->held.p_any;
++    if( pRH->held.p_any == 0 ) return rv;
++    pRH->frk &= ~FRK_Indirect;
++  }
++  ++rv;
 +  switch( pRH->frk ){
 +  case FRK_Malloc:
 +    free(pRH->held.p_malloced);
 +    break;
 +  case FRK_DbConn:
 +    sqlite3_close_v2(pRH->held.p_conn);
 +    break;
 +  case FRK_DbStmt:
 +    sqlite3_clear_bindings(pRH->held.p_stmt);
 +    sqlite3_finalize(pRH->held.p_stmt);
 +    break;
 +  case FRK_DbMem:
 +    sqlite3_free(pRH->held.p_s3mem);
 +    break;
 +  case FRK_File:
 +    fclose(pRH->held.p_stream);
 +    break;
 +#if (!defined(_WIN32) && !defined(WIN32)) || !SQLITE_OS_WINRT
 +  case FRK_Pipe:
 +    pclose(pRH->held.p_stream);
 +    break;
 +#endif
 +#ifdef SHELL_MANAGE_TEXT
 +  case FRK_Text:
 +    freeText(pRH->held.p_text);
 +    break;
 +#endif
++  case FRK_AnyRef:
++    (pRH->held.p_any_rh->its_freer)(pRH->held.p_any_rh->pAny);
++    break;
 +  default:
 +    {
 +      int ck = pRH->frk - FRK_CustomBase;
 +      assert(ck>=0);
 +      if( ck < numCustom ){
 +        aCustomFreers[ck]( pRH->held.p_any );
- /* a reference to a ShellText object, (storage for which not managed) */
- static void text_holder(ShellText *pt){
++      }else --rv;
 +    }
 +  }
 +  pRH->held.p_any = 0;
++  return rv;
 +}
 +
 +/* Take back a held resource pointer, leaving held as NULL. (no-op) */
 +void* take_held(ResourceMark mark, ResourceCount offset){
 +  return swap_held(mark,offset,0);
 +}
 +
 +/* Swap a held resource pointer for a new one. */
 +void* swap_held(ResourceMark mark, ResourceCount offset, void *pNew){
 +  ResourceCount rix = mark + offset;
 +  assert(rix < numResHold && numResHold > 0);
 +  if( rix < numResHold && numResHold > 0 ){
 +    void *rv = pResHold[rix].held.p_any;
 +    pResHold[rix].held.p_any = pNew;
 +    return rv;
 +  }else return 0;
 +}
 +
 +/* Reserve room for at least count additional holders, with no chance
 +** of an OOM abrupt exit. This is used internally only by this package.
 +** The return is either 1 for success, or 0 indicating an OOM failure.
 + */
 +static int more_holders_try(ResourceCount count){
 +  ResourceHeld *prh;
 +  ResourceCount newAlloc = numResHold + count;
 +  if( newAlloc < numResHold ) return 0; /* Overflow, sorry. */
 +  if( newAlloc <= numResAlloc ) return 1;
 +  prh = (ResourceHeld*)realloc(pResHold, newAlloc*sizeof(ResourceHeld));
 +  if( prh == 0 ) return 0;
 +  pResHold = prh;
 +  numResAlloc = newAlloc;
 +  return 1;
 +}
 +
++/* Lose one or more holders. */
++void* pop_holder(void){
++  assert(numResHold>0);
++  if( numResHold>0 ) return pResHold[--numResHold].held.p_any;
++  return 0;
++}
++void pop_holders(ResourceCount num){
++  assert(numResHold>=num);
++  if( numResHold>=num ) numResHold -= num;
++  else numResHold = 0;
++}
++
 +/* Shared resource-stack pushing code */
 +static void res_hold(void *pv, FreeableResourceKind frk){
 +  ResourceHeld rh = { pv, frk };
 +  if( numResHold == numResAlloc ){
 +    ResourceCount nrn = (ResourceCount)((numResAlloc>>2) + 5);
 +    if( !more_holders_try(nrn) ){
 +      free_rk(&rh);
 +      quit_moan(resmanage_oom_message,1);
 +    }
 +  }
 +  pResHold[numResHold++] = rh;
 +}
 +
 +/* Hold a NULL (Assure swap_held() cannot fail.) */
 +void more_holders(ResourceCount more){
 +  if( !more_holders_try(more) ) quit_moan(resmanage_oom_message,1);
 +}
 +
 +/* Hold anything in the malloc() heap */
 +void* mmem_holder(void *pm){
 +  res_hold(pm, FRK_Malloc);
 +  return pm;
 +}
 +/* Hold a C string in the malloc() heap */
 +char* mstr_holder(char *z){
 +  res_hold(z, FRK_Malloc);
 +  return z;
 +}
 +/* Hold a C string in the SQLite heap */
 +char* sstr_holder(char *z){
 +  res_hold(z, FRK_DbMem);
 +  return z;
 +}
++/* Hold a C string in the SQLite heap, reference to */
++void sstr_ptr_holder(char **pz){
++  assert(pz!=0);
++  res_hold(pz, FRK_DbMem|FRK_Indirect);
++}
 +/* Hold an open C runtime FILE */
 +void file_holder(FILE *pf){
 +  res_hold(pf, FRK_File);
 +}
 +#if (!defined(_WIN32) && !defined(WIN32)) || !SQLITE_OS_WINRT
 +/* Hold an open C runtime pipe */
 +void pipe_holder(FILE *pp){
 +  res_hold(pp, FRK_Pipe);
 +}
 +#endif
 +#ifdef SHELL_MANAGE_TEXT
-     --numResHold;
-     if( pResHold[numResHold].held.p_any!=0 ){
-       free_rk(&pResHold[numResHold]);
-       ++rv;
-     }
++/* a ShellText object, reference to (storage for which not managed) */
++static void text_ref_holder(ShellText *pt){
 +  res_hold(pt, FRK_Text);
 +}
 +#endif
 +/* Hold anything together with arbitrary freeing function */
 +void* any_holder(void *pm, void (*its_freer)(void*)){
 +  int i = 0;
 +  while( i < numCustom ){
 +    if( its_freer == aCustomFreers[i] ) break;
 +    ++i;
 +  }
 +  if( i == numCustom ){
 +    size_t ncf = numCustom + 2;
 +    FreerFunction *pcf;
 +    pcf = (FreerFunction *)realloc(aCustomFreers, ncf*sizeof(FreerFunction));
 +    if( pcf!=0 ){
 +      assert(ncf < (1<<16));
 +      numCustomAlloc = (ResourceCount)ncf;
 +      aCustomFreers = pcf;
 +      aCustomFreers[numCustom++] = its_freer;
 +    }else{
 +      quit_moan(resmanage_oom_message,1);
 +    }
 +  }
 +  res_hold(pm, i + FRK_CustomBase);
 +  return pm;
 +}
 +/* Hold some SQLite-allocated memory */
 +void* smem_holder(void *pm){
 +  res_hold(pm, FRK_DbMem);
 +  return pm;
 +}
 +/* Hold a SQLite database "connection" */
 +void conn_holder(sqlite3 *pdb){
 +  res_hold(pdb, FRK_DbConn);
 +}
 +/* Hold a SQLite prepared statement */
 +void stmt_holder(sqlite3_stmt *pstmt){
 +  res_hold(pstmt, FRK_DbStmt);
 +}
++/* Hold a SQLite prepared statement, reference to */
++void stmt_ptr_holder(sqlite3_stmt **ppstmt){
++  assert(ppstmt!=0);
++  res_hold(ppstmt, FRK_DbStmt|FRK_Indirect);
++}
++/* Hold a reference to an AnyResourceHolder (in stack frame) */
++void any_ref_holder(AnyResourceHolder *parh){
++  assert(parh!=0 && parh->its_freer!=0);
++  res_hold(parh, FRK_AnyRef);
++}
 +
 +/* Free all held resources in excess of given resource stack mark,
 +** then return how many needed freeing. */
 +int holder_free(ResourceMark mark){
 +  int rv = 0;
 +  while( numResHold > mark ){
++    rv += free_rk(&pResHold[--numResHold]);
 +  }
 +  if( mark==0 ){
 +    if( numResAlloc>0 ){
 +      free(pResHold);
 +      pResHold = 0;
 +      numResAlloc = 0;
 +    }
 +    if( numCustomAlloc>0 ){
 +      free(aCustomFreers);
 +      aCustomFreers = 0;
 +      numCustom = 0;
 +      numCustomAlloc = 0;
 +    }
 +  }
 +  return rv;
 +}
 +
 +#ifndef SHELL_OMIT_LONGJMP
 +/* Record a resource stack and call stack rip-to position */
 +void register_exit_ripper(jmp_buf *pjb, ResourceMark rip_mark){
 +  exit_mark = rip_mark;
 +  p_exit_ripper = pjb;
 +}
 +/* Undo register_exit_ripper effect, back to default state. */
 +void forget_exit_ripper(jmp_buf *pjb){
 +  exit_mark = 0;
 +  assert(p_exit_ripper == pjb);
 +  p_exit_ripper = 0;
 +}
 +#endif
diff --cc src/resmanage.h
index bb276de61140c0c4b6e086c0c3188b02f5c135a3,0000000000000000000000000000000000000000..b083d1724e8f2b36412de719218ad3133867977f
mode 100644,000000..100644
--- /dev/null
@@@ -1,132 -1,0 +1,165 @@@
- /* Type used for marking positions within a held-resource stack */
 +/*
 +** 2023 May 8
 +**
 +** 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 file declares the interface of a simple resource management package
 +** which supports freeing of resources upon an abrupt, program-initiated
 +** termination of a function from somewhere in a call tree. The package is
 +** designed to be used with setjmp()/longjmp() to effect a pattern similar
 +** to the try/throw/catch available in some programming languages. (But see
 +** below regarding usage when the setmp()/longjmp() pair is unavailable.)
 +**
 +** The general scheme is that routines whose pre-return code might be
 +** bypassed, thereby leaking resources, do not rely on pre-return code
 +** to release locally held resources. Instead, they give ownership of
 +** such resources to this package, via xxxx_holder(...) calls, and use
 +** its holder_mark() and holder_free(...) functions to release locally
 +** acquired resources.
 +**
 +** For environments where setmp()/longjmp() are unavailable, (indicated by
 +** SHELL_OMIT_LONGJMP defined), the package substitutes a process exit for
 +** resumption of execution at a chosen code location. The resources in the
 +** package's held resource stack are still released. And the ability to
 +** free locally acquired resources as functions return is retained. This
 +** can simplify early function exit logic, but a body of code relying on
 +** process exit to ease error handling is unsuitable for use as a called
 +** routine within a larger application. That use is most of the reason for
 +** this package's existence.
 +*/
 +
 +#ifndef RES_MANAGE_H
 +# define RES_MANAGE_H
 +
 +#ifndef SHELL_OMIT_LONGJMP
 +# include <setjmp.h>
 +# define RIP_STATE(jb) jmp_buf jb
 +# define RIP_TO_HERE(jb) setjmp(jb)
 +#else
 +# define RIP_STATE(jb)
 +# define RIP_TO_HERE(jb) 0
 +#endif
 +#include <stdio.h>
 +#include <assert.h>
 +
 +#ifdef __cplusplus
 +extern "C" {
 +#endif
 +
 +#include "sqlite3.h"
 +
- /* Routines for holding resources on held-resource stack together
++/* Type used for marking/conveying positions within a held-resource stack */
 +typedef unsigned short ResourceMark;
 +typedef unsigned short ResourceCount;
 +
++/* Type used for generic free functions (to fib about their signature.) */
++typedef void (*GenericFreer)(void*);
++
 +/* Current position of the held-resource stack */
 +extern ResourceMark holder_mark();
 +
 +/* Assure no allocation failure for some more xxx_holder() calls.
 +** Note that this call may fail with an OOM abrupt exit. */
 +extern void more_holders(ResourceCount more);
 +
- /* a reference to a ShellText object, (storage for which not managed) */
- static void text_holder(ShellText *);
++/* Lose one or more holders, without freeing anything. */
++extern void* pop_holder(void);
++extern void pop_holders(ResourceCount num);
++
++/*
++** Routines for holding resources on held-resource stack together
 +** with enough information for them to be freed by this package.
 +*/
++
++/* xxxx_holder(...)
++** The referenced objects are directly freed; they are stored in
++** the heap with lifetime not bound to the caller's activation.
++*/
 +/* anything together with arbitrary freeing function */
 +extern void* any_holder(void *pm, void (*its_freer)(void*));
 +/* anything in the malloc() heap */
 +extern void* mmem_holder(void *pm);
 +/* a C string in the malloc() heap */
 +extern char* mstr_holder(char *z);
 +/* some SQLite-allocated memory */
 +extern void* smem_holder(void *pm);
 +/* a C string in the SQLite heap */
 +extern char* sstr_holder(char *z);
 +/* a SQLite database "connection" */
 +extern void conn_holder(sqlite3 *pdb);
 +/* a SQLite prepared statement */
 +extern void stmt_holder(sqlite3_stmt *pstmt);
 +/* an open C runtime FILE */
 +extern void file_holder(FILE *);
 +#if (!defined(_WIN32) && !defined(WIN32)) || !SQLITE_OS_WINRT
 +/* an open C runtime pipe */
 +extern void pipe_holder(FILE *);
 +#endif
++
++/* xxxx_ptr_holder(...) or xxxx_ref_holder(...)
++** The referenced objects are indirectly freed; they are stored
++** in the caller's stack frame, with lifetime limited to the
++** the caller's activation. It is a grave error to use these
++** then fail to call holder_free() before returning. For abrupt
++** exits, this condition is met because holder_free() is called
++** by the abrupt exiter before the execution stack is stripped.
++*/
++typedef struct AnyResourceHolder {
++  void *pAny;
++  GenericFreer its_freer;
++} AnyResourceHolder;
++
++/* a reference to an AnyResourceHolder (whose storage is not managed) */
++extern void any_ref_holder(AnyResourceHolder *parh);
++/* a C string in the SQLite heap, reference to */
++extern void sstr_ptr_holder(char **pz);
++/* a SQLite prepared statement, reference to */
++extern void stmt_ptr_holder(sqlite3_stmt **ppstmt);
 +#ifdef SHELL_MANAGE_TEXT
++/* a ShellText object, reference to (storage for which not managed) */
++static void text_ref_holder(ShellText *);
 +#endif
 +
 +/* Take back a held resource pointer, leaving held as NULL. (no-op) */
 +extern void* take_held(ResourceMark mark, ResourceCount offset);
 +
 +/* Swap a held resource pointer for a new one. */
 +extern void* swap_held(ResourceMark mark, ResourceCount offset, void *pNew);
 +
 +/* Free all held resources in excess of given resource stack mark.
 +** Return count of number actually freed (rather than being 0.) */
 +extern int holder_free(ResourceMark mark);
 +
 +#ifndef SHELL_OMIT_LONGJMP
 +/* Remember a longjmp() destination together with a resource stack
 +** mark which determines how far the resource stack may be stripped.
 +** This info determines how far the execution and resource stacks
 +** will be stripped back should quit_moan(...) be called.
 +*/
 +extern void register_exit_ripper(jmp_buf *pjb, ResourceMark rip_mark);
 +/* Forget whatever register_exit_ripper() has been recorded. */
 +extern void forget_exit_ripper(jmp_buf *pjb);
 +#else
 +#define register_exit_ripper(jb, rm)
 +#define forget_exit_ripper()
 +#endif
 +
 +/* Strip resource stack and execute previously registered longjmp() as
 +** previously prepared by register_exit_ripper() call. Or, if no such
 +** prep done (or possible), strip the whole stack and exit the process.
 +*/
 +extern void quit_moan(const char *zMoan, int errCode);
 +
 +/* What the complaint will be for OOM failures and abrupt exits. */
 +extern const char *resmanage_oom_message;
 +
 +#ifdef __cplusplus
 +}  /* End of the 'extern "C"' block */
 +#endif
 +#endif /* RES_MANAGE_H */
diff --cc src/shell.c.in
index 72496ae665710145b7d310af9366fca46b57ae7c,ccaed896d92b818254fcb5bfae0f4f8a79b126c9..fef2bb43a9a131f023ff321d539e803866728b25
@@@ -506,19 -469,15 +506,29 @@@ static int console_utf8 = 0
  */
  static int stdout_is_console = 1;
  
++/*
++** This statically allocated variable is used to strip a resource
++** stack upon abrupt exits (involving OOM or -safe mode violations.
++*/
++static ResourceMark main_resource_mark = 0;
++
++/* Avoid redundant atexit() registration. */
++static u8 atexit_registered = 0;
++
  /*
  ** The following is the open SQLite database.  We make a pointer
  ** to this database a static variable so that it can be accessed
  ** by the SIGINT handler to interrupt database processing.
  */
  static sqlite3 *globalDb = 0;
 +/*
 +** Mutex used to access *globalDb from main thread or ^C handler.
 +*/
 +static sqlite3_mutex *pGlobalDbLock = 0;
 +
  /*
 -** True if an interrupt (Control-C) has been received.
 +** Greater than 0 if an interrupt (Control-C) has been received.
  */
  static volatile int seenInterrupt = 0;
  
@@@ -813,24 -760,13 +823,37 @@@ void utf8_printf(FILE *out, const char 
  # define raw_printf fprintf
  #endif
  
 -/* Indicate out-of-memory and exit. */
 -static void shell_out_of_memory(void){
 -  raw_printf(stderr,"Error: out of memory\n");
 +/*
- ** Provide a way for embedding apps to handle OOM condition their way.
- ** This is crude, and using it will undoubtedly leak memory and handles.
- ** The alternative is extensive recoding of the shell, (not for today.)
- ** This could become a longjump(...) (paired with app's setjump(...).)
++** Provide a way for embedding apps to handle OOM or other shell-fatal
++** conditions their way. A plain exit() call will undoubtedly leak
++** memory and handles. The default scheme, when SHELL_TERMINATE is
++** not defined, uses setjmp()/longjmp() execution stack ripping
++** together with a resource stack which can be popped to release
++** resources automatically. One replacement could be:
++# define SHELL_TERMINATE(why) \
++  fprintf(stderr, "Error: Terminating due to %s\n", why);\
+   exit(1);
 +*/
- #ifndef SHELL_OOM_EXIT
- # define SHELL_OOM_EXIT exit(1)
++
++/*
++** Provide an abrupt exit/termination for routines that cannot proceed
++** or whose failure means the CLI cannot usefully continue.
++*/
++static void shell_terminate(const char *zWhy){
++#ifndef SHELL_TERMINATE
++  quit_moan(zWhy, 2);
++#else
++  holder_free(exit_mark);
++  SHELL_TERMINATE(zWhy);
 +#endif
- /* Indicate out-of-memory and exit. */
++}
++
++/* Indicate out-of-memory and terminate. */
 +static void shell_out_of_memory(void){
-   raw_printf(STD_ERR,"Error: out of memory\n");
-   sqlite3_mutex_free(pGlobalDbLock);
-   pGlobalDbLock = 0;
-   SHELL_OOM_EXIT;
++  shell_terminate("out of memory");
  }
  
--/* Check a pointer to see if it is NULL.  If it is NULL, exit with an
++/* Check a pointer to see if it is NULL.  If so, terminate with an
  ** out-of-memory error.
  */
  static void shell_check_oom(const void *p){
@@@ -985,80 -913,10 +1008,81 @@@ static FILE * openChrSource(const char 
  }
  
  /*
 -** This routine reads a line of text from FILE in, stores
 -** the text in memory obtained from malloc() and returns a pointer
 -** to the text.  NULL is returned at end of file, or if malloc()
 -** fails.
 +** Arrange for shell input from either a FILE or a string.
 +** For applicable invariants, see strLineGet(...) which is
 +** the only modifier of this struct. (3rd and 4th members)
 +** All other members are simply initialized to select the
 +** input stream and track input sources, set by whatever
 +** routines redirect the input.
 +*/
 +typedef struct InSource {
 +  FILE *inFile;     /* Will be 0 when input is to be taken from string.  */
 +  char *zStrIn;     /* Will be 0 when no input is available from string. */
 +  int iReadOffset;  /* Offset into zStrIn where next "read" to be done.  */
 +  int lineno;       /* How many lines have been read from this source    */
 +  const char *zSourceSay; /* For complaints, keep a name for this source */
 +  struct InSource *pFrom; /* and redirect tracking to aid unraveling.    */
 +} InSource;
 +#define INSOURCE_STR_REDIR(str, tagTo, isFrom) {0, str, 0, 0, tagTo, isFrom}
 +#define INSOURCE_FILE_REDIR(fh, tagTo, isFrom) {fh, 0, 0, 0, tagTo, isFrom}
 +#define INSOURCE_IS_INTERACTIVE(pIS) \
 +  ((pIS)==&termInSource && stdin_is_interactive )
++#define INSOURCE_IS_INVOKEARG(pIS) \
++  ((pIS)==&cmdInSource)
 +/* This instance's address is taken as part of interactive input test. */
 +static InSource termInSource = { 0, 0, 0, 0, "<terminal>", 0};
 +static InSource stdInSource = { 0, 0, 0, 0, "<stdin>", 0};
 +static InSource cmdInSource = { 0, 0, 0, 0, "<cmdLine>", 0};
 +static void init_std_inputs(FILE *pIn ){
 +  termInSource.inFile = pIn;
 +  stdInSource.inFile = pIn;
 +  cmdInSource.lineno = 0;
 +}
 +static void set_invocation_cmd(char *zDo){
 +  cmdInSource.iReadOffset = 0;
 +  cmdInSource.zStrIn = zDo;
 +  ++cmdInSource.lineno;
 +}
 +
 +static char *strLineGet(char *zBuf, int ncMax, InSource *pInSrc){
 +  if( pInSrc->inFile!=0 ){
 +    char *zRet = fgets(zBuf, ncMax, pInSrc->inFile );
 +    if( zRet!=0 ){
 +      int iRead = strlen30(zRet);
 +      if( iRead>0 && zRet[iRead-1]=='\n' ) ++pInSrc->lineno;
 +      /* Consider: record line length to avoid rescan for it. */
 +      return zRet;
 +    }
 +  }
 +  else if( pInSrc->zStrIn!=0 ){
 +    char *zBegin = pInSrc->zStrIn + pInSrc->iReadOffset;
 +    if( *zBegin!=0 ){
 +      int iTake = 0;
 +      char c;
 +      ncMax -= 1;
 +      while( iTake<ncMax && (c=zBegin[iTake])!=0 ){
 +        zBuf[iTake++] = c;
 +        if( c=='\n' ){
 +          ++pInSrc->lineno;
 +          break;
 +        }
 +      }
 +      if( ncMax>=0 ) zBuf[iTake] = 0;
 +      pInSrc->iReadOffset += iTake;
 +      /* Consider: record line length to avoid rescan for it. */
 +      return zBuf;
 +    }
 +  }
 +  return 0;
 +}
 +
 +/*
 +** This routine reads a line of text from designated stream source,
 +** stores the text in memory obtained from malloc() and returns a
 +** pointer to the text.  NULL is returned at end of file.
 +** There will be no return if malloc() fails.
 +**
 +** The trailing newline (or other line-end chars) are stripped.
  **
  ** If zLine is not NULL then it is a malloced buffer returned from
  ** a previous call to this routine that may be reused.
@@@ -1308,6 -1103,6 +1332,8 @@@ static char* takeText(ShellText *p)
  **
  ** If the third argument, quote, is not '\0', then it is used as a
  ** quote character for zAppend.
++**
++** This routine can abruptly exit under OOM conditions.
  */
  static void appendText(ShellText *p, const char *zAppend, char quote){
    i64 len;
@@@ -1381,18 -1173,13 +1407,18 @@@ static char *shellFakeSchema
    char cQuote;
    char *zDiv = "(";
    int nRow = 0;
 -
 -  zSql = sqlite3_mprintf("PRAGMA \"%w\".table_info=%Q;",
 -                         zSchema ? zSchema : "main", zName);
 +  int rc;
 +  char *rv = 0;
 +  ResourceMark rm_mark = holder_mark();
 +  char *zSql = smprintf("PRAGMA \"%w\".table_info=%Q;",
 +                        zSchema ? zSchema : "main", zName);
    shell_check_oom(zSql);
 -  sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
 +  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
    sqlite3_free(zSql);
 +  if( rc==SQLITE_NOMEM ) shell_out_of_memory();
 +  stmt_holder(pStmt);
    initText(&s);
-   text_holder(&s);
++  text_ref_holder(&s);
    if( zSchema ){
      cQuote = quoteChar(zSchema);
      if( cQuote && sqlite3_stricmp(zSchema,"temp")==0 ) cQuote = 0;
@@@ -2063,19 -1634,10 +2089,16 @@@ static void shellPutsFunc
  
  /*
  ** If in safe mode, print an error message described by the arguments
- ** and "exit" without returning to the caller. This "exit" will occur
- ** immediately, as a process exit, for normal shell builds. When the
- ** shell is built with options to use it embedded, control returns to
- ** the caller of the shell's main, "do shell things" entry point.
 -** and exit immediately.
++** and arrange for a semi-abrupt exit. Unsafe actions are blocked
++** by their doers calling this and not acting if 1 is returned.
++** The process_input() routine detects the semi-abrupt exit, (which
++** is indicated by the external shell state member, shellAbruptExit
++** being set non-zero), and aborts any further input processing.
 +**
- ** It is an error, (perhaps with only minor effect such as memory leak),
- ** for a dot-command to call this function while it holds resources in
- ** need of freeing. Instead, it should be called before acquiring them.
- **
- ** The return is true if failing, 0 otherwise.
++** The return is 1 if failing for a forbidded unsafe act, 0 otherwise.
  */
 -static void failIfSafeMode(
 -  ShellState *p,
 +static int failIfSafeMode(
 +  ShellExState *psx,
    const char *zErrMsg,
    ...
  ){
@@@ -2147,13 -1689,8 +2170,13 @@@ static void editFunc
  
    if( argc==2 ){
      zEditor = (const char*)sqlite3_value_text(argv[1]);
 +    /* If that failed for OOM, just pretend it is not there. */
    }else{
      zEditor = getenv("VISUAL");
-     /* Note that this code NOT threadsafe due to use of the getenv()
++    /* Note that this code is NOT threadsafe due to use of the getenv()
 +    ** result. Because the shell is single-threaded, this is fine.
 +    ** But if the CLI is called as a subroutine in a multi-threaded
 +    ** program, adjustments may have to be made. */
    }
    if( zEditor==0 ){
      sqlite3_result_error(context, "no editor for edit()", -1);
@@@ -5027,38 -4255,34 +5050,36 @@@ static int shell_exec
    }
  
  #ifndef SQLITE_OMIT_VIRTUALTABLE
 -  if( pArg->expert.pExpert ){
 -    rc = expertHandleSQL(pArg, zSql, pzErrMsg);
 -    return expertFinish(pArg, (rc!=SQLITE_OK), pzErrMsg);
 +  if( psi->expert.pExpert ){
 +    rc = expertHandleSQL(psi, zSql, pzErrMsg);
 +    return expertFinish(psi, (rc!=SQLITE_OK), pzErrMsg);
    }
  #endif
-   stmt_holder(pStmt);    /* offset 0 */
-   stmt_holder(pExplain); /* offset 1 */
-   sstr_holder(zEQP);     /* offset 2 */
 -
++  stmt_ptr_holder(&pStmt);    /* offset 0 */
++  stmt_ptr_holder(&pExplain); /* offset 1 */
++  sstr_ptr_holder(&zEQP);     /* offset 2 */
    while( zSql[0] && (SQLITE_OK == rc) ){
      static const char *zStmtSql;
      rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover);
      if( SQLITE_OK != rc ){
 +      if( rc==SQLITE_NOMEM ) shell_out_of_memory();
        if( pzErrMsg ){
--        *pzErrMsg = save_err_msg(db, "in prepare", rc, zSql);
-         shell_check_oom(*pzErrMsg);
++        shell_check_oom(*pzErrMsg = save_err_msg(db, "in prepare", rc, zSql));
        }
      }else{
        if( !pStmt ){
          /* this happens for a comment or white-space */
 -        zSql = zLeftover;
 -        while( IsSpace(zSql[0]) ) zSql++;
 +        zSql = skipWhite(zLeftover);
          continue;
        }
-       swap_held(mark, 0, pStmt);
        zStmtSql = sqlite3_sql(pStmt);
        if( zStmtSql==0 ) zStmtSql = "";
 -      while( IsSpace(zStmtSql[0]) ) zStmtSql++;
 +      else zStmtSql = skipWhite(zStmtSql);
  
        /* save off the prepared statment handle and reset row count */
 -      if( pArg ){
 -        pArg->pStmt = pStmt;
 -        pArg->cnt = 0;
 +      if( psx ){
 +        psi->pStmt = pStmt;
 +        psx->resultCount = 0;
        }
  
        /* Show the EXPLAIN QUERY PLAN if .eqp is on */
          int triggerEQP = 0;
          disable_debug_trace_modes();
          sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, -1, &triggerEQP);
 -        if( pArg->autoEQP>=AUTOEQP_trigger ){
 +        if( psi->autoEQP>=AUTOEQP_trigger ){
            sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 1, 0);
          }
 -        zEQP = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", zStmtSql);
 +        zEQP = smprintf("EXPLAIN QUERY PLAN %s", zStmtSql);
          shell_check_oom(zEQP);
-         swap_held(mark, 2, zEQP);
          rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
          if( rc==SQLITE_OK ){
-           swap_held(mark, 1, pExplain);
            while( sqlite3_step(pExplain)==SQLITE_ROW ){
              const char *zEQPLine = (const char*)sqlite3_column_text(pExplain,3);
              int iEqpId = sqlite3_column_int(pExplain, 0);
              int iParentId = sqlite3_column_int(pExplain, 1);
              if( zEQPLine==0 ) zEQPLine = "";
 -            if( zEQPLine[0]=='-' ) eqp_render(pArg, 0);
 -            eqp_append(pArg, iEqpId, iParentId, zEQPLine);
 +            if( zEQPLine[0]=='-' ) eqp_render(psi, 0);
 +            eqp_append(psi, iEqpId, iParentId, zEQPLine);
            }
 -          eqp_render(pArg, 0);
 +          eqp_render(psi, 0);
          }
          sqlite3_finalize(pExplain);
-         swap_held(mark, 1, 0);
++        pExplain = 0;
          sqlite3_free(zEQP);
-         swap_held(mark, 2, 0);
 -        if( pArg->autoEQP>=AUTOEQP_full ){
++        zEQP = 0;
 +        if( psi->autoEQP>=AUTOEQP_full ){
            /* Also do an EXPLAIN for ".eqp full" mode */
 -          zEQP = sqlite3_mprintf("EXPLAIN %s", zStmtSql);
 +          zEQP = smprintf("EXPLAIN %s", zStmtSql);
            shell_check_oom(zEQP);
-           swap_held(mark, 2, zEQP);
            rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
            if( rc==SQLITE_OK ){
-             swap_held(mark, 1, pExplain);
 -            pArg->cMode = MODE_Explain;
 -            explain_data_prepare(pArg, pExplain);
 -            exec_prepared_stmt(pArg, pExplain);
 -            explain_data_delete(pArg);
 +            explain_data_prepare(psi, pExplain);
 +            psi->cMode = MODE_Explain;
 +#if SHELL_DATAIO_EXT
 +            {
 +              ExportHandler *pexSave = psi->pActiveExporter;
 +              psi->pActiveExporter = psi->pFreeformExporter;
 +              exec_prepared_stmt(psx, pExplain);
 +              psi->pActiveExporter = pexSave;
 +            }
 +#else
 +            exec_prepared_stmt(psx, pExplain);
 +#endif
 +            explain_data_delete(psi);
            }
            sqlite3_finalize(pExplain);
-           swap_held(mark, 1, 0);
++          pExplain = 0;
            sqlite3_free(zEQP);
-           swap_held(mark, 2, 0);
++          zEQP = 0;
          }
 -        if( pArg->autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){
 +        if( psi->autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){
            sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 0, 0);
            /* Reprepare pStmt before reactiving trace modes */
            sqlite3_finalize(pStmt);
++          pStmt = 0;
            sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
-           swap_held(mark, 0, pStmt);
 -          if( pArg ) pArg->pStmt = pStmt;
 +          if( psx ) psi->pStmt = pStmt;
          }
          restore_debug_trace_modes();
        }
        ** copy of the error message. Otherwise, set zSql to point to the
        ** next statement to execute. */
        rc2 = sqlite3_finalize(pStmt);
-       swap_held(mark, 0, 0);
++      pStmt = 0;
        if( rc!=SQLITE_NOMEM ) rc = rc2;
        if( rc==SQLITE_OK ){
 -        zSql = zLeftover;
 -        while( IsSpace(zSql[0]) ) zSql++;
 +        zSql = skipWhite(zLeftover);
        }else if( pzErrMsg ){
--        *pzErrMsg = save_err_msg(db, "stepping", rc, 0);
++        shell_check_oom(*pzErrMsg = save_err_msg(db, "stepping", rc, 0));
        }
  
        /* clear saved stmt handle */
  */
  static void freeColumnList(char **azCol){
    int i;
--  for(i=1; azCol[i]; i++){
--    sqlite3_free(azCol[i]);
++  if( azCol!=0 ){
++    for(i=1; azCol[i]; i++){
++      sqlite3_free(azCol[i]);
++    }
++    /* azCol[0] is a static string */
++    sqlite3_free(azCol);
    }
--  /* azCol[0] is a static string */
--  sqlite3_free(azCol);
  }
  
  /*
  ** name of the rowid column.
  **
  ** The first regular column in the table is azCol[1].  The list is terminated
--** by an entry with azCol[i]==0.
++** by an entry with azCol[i]==0 for i>0. This is an invariant, maintained
++** as the list is grown so that freeColumnList() can always deal with it.
++**
++** This function can exit abruptly under OOM conditions.
  */
 -static char **tableColumnList(ShellState *p, const char *zTab){
 +static char **tableColumnList(sqlite3 *db, const char *zTab, int preserveRowid){
    char **azCol = 0;
    sqlite3_stmt *pStmt;
    char *zSql;
    int nAlloc = 0;
    int nPK = 0;       /* Number of PRIMARY KEY columns seen */
    int isIPK = 0;     /* True if one PRIMARY KEY column of type INTEGER */
 -  int preserveRowid = ShellHasFlag(p, SHFLG_PreserveRowid);
++  ResourceMark mark = holder_mark();
++  AnyResourceHolder arh = { 0, (GenericFreer)freeColumnList };
    int rc;
  
 -  zSql = sqlite3_mprintf("PRAGMA table_info=%Q", zTab);
 +  zSql = smprintf("PRAGMA table_info=%Q", zTab);
    shell_check_oom(zSql);
 -  rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 +  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
    sqlite3_free(zSql);
    if( rc ) return 0;
++  stmt_holder(pStmt);   /* offset 0 */
++  any_ref_holder(&arh); /* offset 1 */
    while( sqlite3_step(pStmt)==SQLITE_ROW ){
      if( nCol>=nAlloc-2 ){
++      int nAllocPrev = nAlloc;
        nAlloc = nAlloc*2 + nCol + 10;
        azCol = sqlite3_realloc(azCol, nAlloc*sizeof(azCol[0]));
        shell_check_oom(azCol);
++      memset(azCol+nAllocPrev, 0, (nAlloc-nAllocPrev)*sizeof(azCol[0]));
++      arh.pAny = azCol;
      }
 -    azCol[++nCol] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 1));
 +    azCol[++nCol] = smprintf("%s", sqlite3_column_text(pStmt, 1));
      shell_check_oom(azCol[nCol]);
      if( sqlite3_column_int(pStmt, 5) ){
        nPK++;
        }
      }
    }
--  sqlite3_finalize(pStmt);
++  swap_held(mark, 1, 0); /* Now that it's built, save it from takedown. */
++  holder_free(mark);
    if( azCol==0 ) return 0;
--  azCol[0] = 0;
--  azCol[nCol+1] = 0;
++  any_ref_holder(&arh);  /* offset 0 */
++  /* azCol[0] = 0; azCol[nCol+1] = 0; -- Done by memset() above. */
  
    /* The decision of whether or not a rowid really needs to be preserved
    ** is tricky.  We never need to preserve a rowid for a WITHOUT ROWID table
      ** there is a "pk" entry in "PRAGMA index_list".  There will be
      ** no "pk" index if the PRIMARY KEY really is an alias for the ROWID.
      */
 -    zSql = sqlite3_mprintf("SELECT 1 FROM pragma_index_list(%Q)"
 -                           " WHERE origin='pk'", zTab);
 +    zSql = smprintf("SELECT 1 FROM pragma_index_list(%Q)"
 +                    " WHERE origin='pk'", zTab);
      shell_check_oom(zSql);
 -    rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
 +    rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
      sqlite3_free(zSql);
      if( rc ){
--      freeColumnList(azCol);
++      holder_free(mark);
        return 0;
      }
++    stmt_holder(pStmt);
      rc = sqlite3_step(pStmt);
--    sqlite3_finalize(pStmt);
      preserveRowid = rc==SQLITE_ROW;
    }
    if( preserveRowid ){
        }
      }
    }
++  swap_held(mark, 0, 0); /* Save built list from takedown (again.) */
++  holder_free(mark);
    return azCol;
  }
  
@@@ -5326,10 -4534,9 +5358,11 @@@ static int dump_callback(void *pArg, in
    const char *zTable;
    const char *zType;
    const char *zSql;
 -  ShellState *p = (ShellState *)pArg;
 +  ShellInState *psi = (ShellInState *)pArg;
 +  ShellExState *psx = XSS(psi);
    int dataOnly;
    int noSys;
++  ResourceMark mark = holder_mark();
  
    UNUSED_PARAMETER(azNotUsed);
    if( nArg!=3 || azArg==0 ) return 0;
    }
  
    if( cli_strcmp(zType, "table")==0 ){
++    AnyResourceHolder arh = { 0, (GenericFreer)freeColumnList };
      ShellText sSelect;
      ShellText sTable;
      char **azCol;
      int i;
 -    char *savedDestTable;
 +    const char *savedDestTable;
      int savedMode;
 +    int preserveRowid = (psi->shellFlgs & SHFLG_PreserveRowid)!=0;
  
 -    azCol = tableColumnList(p, zTable);
 +    azCol = tableColumnList(DBI(psi), zTable, preserveRowid);
      if( azCol==0 ){
 -      p->nErr++;
 +      psi->nErr++;
        return 0;
      }
++    /* Hereafter, resource management is used (for a column list.) */
++    arh.pAny = azCol;
++    any_ref_holder(&arh);
  
      /* Always quote the table name, even if it appears to be pure ascii,
      ** in case it is a keyword. Ex:  INSERT INTO "table" ... */
      initText(&sTable);
++    text_ref_holder(&sTable);
      appendText(&sTable, zTable, quoteChar(zTable));
      /* If preserving the rowid, add a column list after the table name.
      ** In other words:  "INSERT INTO tab(rowid,a,b,c,...) VALUES(...)"
  
      /* Build an appropriate SELECT statement */
      initText(&sSelect);
++    text_ref_holder(&sSelect);
      appendText(&sSelect, "SELECT ", 0);
      if( azCol[0] ){
        appendText(&sSelect, azCol[0], 0);
          appendText(&sSelect, ",", 0);
        }
      }
--    freeColumnList(azCol);
      appendText(&sSelect, " FROM ", 0);
      appendText(&sSelect, zTable, quoteChar(zTable));
  
 -    savedDestTable = p->zDestTable;
 -    savedMode = p->mode;
 -    p->zDestTable = sTable.z;
 -    p->mode = p->cMode = MODE_Insert;
 -    rc = shell_exec(p, sSelect.z, 0);
 +    savedDestTable = psx->zDestTable;
 +    savedMode = psi->mode;
 +    psx->zDestTable = sTable.z;
 +    psi->mode = psi->cMode = MODE_Insert;
 +    rc = shell_exec(psx, sSelect.z, 0);
      if( (rc&0xff)==SQLITE_CORRUPT ){
 -      raw_printf(p->out, "/****** CORRUPTION ERROR *******/\n");
 -      toggleSelectOrder(p->db);
 -      shell_exec(p, sSelect.z, 0);
 -      toggleSelectOrder(p->db);
 -    }
 -    p->zDestTable = savedDestTable;
 -    p->mode = savedMode;
 -    freeText(&sTable);
 -    freeText(&sSelect);
 -    if( rc ) p->nErr++;
 +      raw_printf(psi->out, "/****** CORRUPTION ERROR *******/\n");
 +      toggleSelectOrder(DBX(psx));
 +      shell_exec(psx, sSelect.z, 0);
 +      toggleSelectOrder(DBX(psx));
 +    }
 +    psx->zDestTable = savedDestTable;
 +    psi->mode = savedMode;
-     freeText(&sTable);
-     freeText(&sSelect);
 +    if( rc ) psi->nErr++;
++    holder_free(mark);
    }
    return 0;
  }
@@@ -5447,8 -4655,8 +5484,11 @@@ static int run_schema_dump_query
    const char *zQuery
  ){
    int rc;
++  ResourceMark mark = holder_mark();
    char *zErr = 0;
 -  rc = sqlite3_exec(p->db, zQuery, dump_callback, p, &zErr);
++
++  sstr_ptr_holder(&zErr);
 +  rc = sqlite3_exec(DBI(psi), zQuery, dump_callback, psi, &zErr);
    if( rc==SQLITE_CORRUPT ){
      char *zQ2;
      int len = strlen30(zQuery);
        sqlite3_free(zErr);
        zErr = 0;
      }
--    zQ2 = malloc( len+100 );
--    if( zQ2==0 ) return rc;
++    shell_check_oom(zQ2 = malloc( len+100 ));
++    mmem_holder(zQ2);
      sqlite3_snprintf(len+100, zQ2, "%s ORDER BY rowid DESC", zQuery);
 -    rc = sqlite3_exec(p->db, zQ2, dump_callback, p, &zErr);
 +    rc = sqlite3_exec(DBI(psi), zQ2, dump_callback, psi, &zErr);
      if( rc ){
 -      utf8_printf(p->out, "/****** ERROR: %s ******/\n", zErr);
 +      utf8_printf(psi->out, "/****** ERROR: %s ******/\n", zErr);
      }else{
        rc = SQLITE_CORRUPT;
      }
--    sqlite3_free(zErr);
--    free(zQ2);
    }
++ bail:
++  holder_free(mark);
    return rc;
  }
  
 +/* Configure help text generation to have coalesced secondary help lines
 + * with trailing newlines on all help lines. This allow help text to be
 + * representable as an array of two C-strings per dot-command.
 + */
 +DISPATCH_CONFIG[
 +  HELP_COALESCE=1
 +];
 +#define HELP_TEXT_FMTP ".%s"
 +#define HELP_TEXT_FMTS "%s"
 +/* Above HELP_COALESCE config and HELP_TEXT_FMT PP vars must track.
 + * Alternative is 0, ".%s\n" and "%s\n" .
 + */
 +
 +/* Forward references */
 +static int showHelp(FILE *out, const char *zPattern, ShellExState *);
 +static DotCmdRC process_input(ShellInState *psx);
 +static DotCommand *builtInCommand(int ix);
 +
  /*
 -** Text of help messages.
 +** Read the content of file zName into memory obtained from sqlite3_malloc64()
 +** and return a pointer to the buffer. The caller is responsible for freeing
 +** the memory.
 +**
 +** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes
 +** read.
  **
 -** The help text for each individual command begins with a line that starts
 -** with ".".  Subsequent lines are supplemental information.
 +** For convenience, a nul-terminator byte is always appended to the data read
 +** from the file before the buffer is returned. This byte is not included in
 +** the final value of (*pnByte), if applicable.
 +**
 +** NULL is returned if any error is encountered. The final value of *pnByte
 +** is undefined in this case.
+ **
 -** There must be two or more spaces between the end of the command and the
 -** start of the description of what that command does.
++** This function always returns; no abrubt OOM exits are taken.
  */
 -static const char *(azHelp[]) = {
 -#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE) \
 -  && !defined(SQLITE_SHELL_FIDDLE)
 -  ".archive ...             Manage SQL archives",
 -  "   Each command must have exactly one of the following options:",
 -  "     -c, --create               Create a new archive",
 -  "     -u, --update               Add or update files with changed mtime",
 -  "     -i, --insert               Like -u but always add even if unchanged",
 -  "     -r, --remove               Remove files from archive",
 -  "     -t, --list                 List contents of archive",
 -  "     -x, --extract              Extract files from archive",
 -  "   Optional arguments:",
 -  "     -v, --verbose              Print each filename as it is processed",
 -  "     -f FILE, --file FILE       Use archive FILE (default is current db)",
 -  "     -a FILE, --append FILE     Open FILE using the apndvfs VFS",
 -  "     -C DIR, --directory DIR    Read/extract files from directory DIR",
 -  "     -g, --glob                 Use glob matching for names in archive",
 -  "     -n, --dryrun               Show the SQL that would have occurred",
 -  "   Examples:",
 -  "     .ar -cf ARCHIVE foo bar  # Create ARCHIVE from files foo and bar",
 -  "     .ar -tf ARCHIVE          # List members of ARCHIVE",
 -  "     .ar -xvf ARCHIVE         # Verbosely extract files from ARCHIVE",
 -  "   See also:",
 -  "      http://sqlite.org/cli.html#sqlite_archive_support",
 -#endif
 -#ifndef SQLITE_OMIT_AUTHORIZATION
 -  ".auth ON|OFF             Show authorizer callbacks",
 -#endif
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".backup ?DB? FILE        Backup DB (default \"main\") to FILE",
 -  "   Options:",
 -  "       --append            Use the appendvfs",
 -  "       --async             Write to FILE without journal and fsync()",
 -#endif
 -  ".bail on|off             Stop after hitting an error.  Default OFF",
 -  ".binary on|off           Turn binary output on or off.  Default OFF",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".cd DIRECTORY            Change the working directory to DIRECTORY",
 +static char *readFile(const char *zName, int *pnByte){
 +  FILE *in = fopen(zName, "rb");
 +  long nIn;
 +  size_t nRead;
 +  char *pBuf;
 +  int rc;
 +  if( in==0 ) return 0;
 +  rc = fseek(in, 0, SEEK_END);
 +  if( rc!=0 ){
 +    raw_printf(stderr, "Error: '%s' not seekable\n", zName);
 +    fclose(in);
 +    return 0;
 +  }
 +  nIn = ftell(in);
 +  rewind(in);
 +  pBuf = sqlite3_malloc64( nIn+1 );
 +  if( pBuf==0 ){
 +    raw_printf(stderr, "Error: out of memory\n");
 +    fclose(in);
 +    return 0;
 +  }
 +  nRead = fread(pBuf, nIn, 1, in);
 +  fclose(in);
 +  if( nRead!=1 ){
 +    sqlite3_free(pBuf);
 +    raw_printf(stderr, "Error: cannot read '%s'\n", zName);
 +    return 0;
 +  }
 +  pBuf[nIn] = 0;
 +  if( pnByte ) *pnByte = nIn;
 +  return pBuf;
 +}
 +
 +#if defined(SQLITE_ENABLE_SESSION)
 +/*
 +** Close a single OpenSession object and release all of its associated
 +** resources.
 +*/
 +static void session_close(OpenSession *pSession){
 +  int i;
 +  sqlite3session_delete(pSession->p);
 +  sqlite3_free(pSession->zName);
 +  for(i=0; i<pSession->nFilter; i++){
 +    sqlite3_free(pSession->azFilter[i]);
 +  }
 +  sqlite3_free(pSession->azFilter);
 +  memset(pSession, 0, sizeof(OpenSession));
 +}
  #endif
 -  ".changes on|off          Show number of rows changed by SQL",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".check GLOB              Fail if output since .testcase does not match",
 -  ".clone NEWDB             Clone data into NEWDB from the existing database",
 -#endif
 -  ".connection [close] [#]  Open or close an auxiliary database connection",
 -  ".databases               List names and files of attached databases",
 -  ".dbconfig ?op? ?val?     List or change sqlite3_db_config() options",
 -#if SQLITE_SHELL_HAVE_RECOVER
 -  ".dbinfo ?DB?             Show status information about the database",
 -#endif
 -  ".dump ?OBJECTS?          Render database content as SQL",
 -  "   Options:",
 -  "     --data-only            Output only INSERT statements",
 -  "     --newlines             Allow unescaped newline characters in output",
 -  "     --nosys                Omit system tables (ex: \"sqlite_stat1\")",
 -  "     --preserve-rowids      Include ROWID values in the output",
 -  "   OBJECTS is a LIKE pattern for tables, indexes, triggers or views to dump",
 -  "   Additional LIKE patterns can be given in subsequent arguments",
 -  ".echo on|off             Turn command echo on or off",
 -  ".eqp on|off|full|...     Enable or disable automatic EXPLAIN QUERY PLAN",
 -  "   Other Modes:",
 -#ifdef SQLITE_DEBUG
 -  "      test                  Show raw EXPLAIN QUERY PLAN output",
 -  "      trace                 Like \"full\" but enable \"PRAGMA vdbe_trace\"",
 -#endif
 -  "      trigger               Like \"full\" but also show trigger bytecode",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".excel                   Display the output of next command in spreadsheet",
 -  "   --bom                   Put a UTF8 byte-order mark on intermediate file",
 -#endif
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".exit ?CODE?             Exit this program with return-code CODE",
 -#endif
 -  ".expert                  EXPERIMENTAL. Suggest indexes for queries",
 -  ".explain ?on|off|auto?   Change the EXPLAIN formatting mode.  Default: auto",
 -  ".filectrl CMD ...        Run various sqlite3_file_control() operations",
 -  "   --schema SCHEMA         Use SCHEMA instead of \"main\"",
 -  "   --help                  Show CMD details",
 -  ".fullschema ?--indent?   Show schema and the content of sqlite_stat tables",
 -  ".headers on|off          Turn display of headers on or off",
 -  ".help ?-all? ?PATTERN?   Show help text for PATTERN",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".import FILE TABLE       Import data from FILE into TABLE",
 -  "   Options:",
 -  "     --ascii               Use \\037 and \\036 as column and row separators",
 -  "     --csv                 Use , and \\n as column and row separators",
 -  "     --skip N              Skip the first N rows of input",
 -  "     --schema S            Target table to be S.TABLE",
 -  "     -v                    \"Verbose\" - increase auxiliary output",
 -  "   Notes:",
 -  "     *  If TABLE does not exist, it is created.  The first row of input",
 -  "        determines the column names.",
 -  "     *  If neither --csv or --ascii are used, the input mode is derived",
 -  "        from the \".mode\" output mode",
 -  "     *  If FILE begins with \"|\" then it is a command that generates the",
 -  "        input text.",
 -#endif
 -#ifndef SQLITE_OMIT_TEST_CONTROL
 -  ",imposter INDEX TABLE    Create imposter table TABLE on index INDEX",
 -#endif
 -  ".indexes ?TABLE?         Show names of indexes",
 -  "                           If TABLE is specified, only show indexes for",
 -  "                           tables matching TABLE using the LIKE operator.",
 -#ifdef SQLITE_ENABLE_IOTRACE
 -  ",iotrace FILE            Enable I/O diagnostic logging to FILE",
 -#endif
 -  ".limit ?LIMIT? ?VAL?     Display or change the value of an SQLITE_LIMIT",
 -  ".lint OPTIONS            Report potential schema issues.",
 -  "     Options:",
 -  "        fkey-indexes     Find missing foreign key indexes",
 -#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE)
 -  ".load FILE ?ENTRY?       Load an extension library",
 -#endif
 -#if !defined(SQLITE_SHELL_FIDDLE)
 -  ".log FILE|on|off         Turn logging on or off.  FILE can be stderr/stdout",
 +
 +/*
 +** Close all OpenSession objects and release all associated resources.
 +*/
 +#if defined(SQLITE_ENABLE_SESSION)
 +static void session_close_all(ShellInState *psi, int i){
 +  int j;
 +  struct AuxDb *pAuxDb = i<0 ? psi->pAuxDb : &psi->aAuxDb[i];
 +  for(j=0; j<pAuxDb->nSession; j++){
 +    session_close(&pAuxDb->aSession[j]);
 +  }
 +  pAuxDb->nSession = 0;
 +}
  #else
 -  ".log on|off              Turn logging on or off.",
 -#endif
 -  ".mode MODE ?OPTIONS?     Set output mode",
 -  "   MODE is one of:",
 -  "     ascii       Columns/rows delimited by 0x1F and 0x1E",
 -  "     box         Tables using unicode box-drawing characters",
 -  "     csv         Comma-separated values",
 -  "     column      Output in columns.  (See .width)",
 -  "     html        HTML <table> code",
 -  "     insert      SQL insert statements for TABLE",
 -  "     json        Results in a JSON array",
 -  "     line        One value per line",
 -  "     list        Values delimited by \"|\"",
 -  "     markdown    Markdown table format",
 -  "     qbox        Shorthand for \"box --wrap 60 --quote\"",
 -  "     quote       Escape answers as for SQL",
 -  "     table       ASCII-art table",
 -  "     tabs        Tab-separated values",
 -  "     tcl         TCL list elements",
 -  "   OPTIONS: (for columnar modes or insert mode):",
 -  "     --wrap N       Wrap output lines to no longer than N characters",
 -  "     --wordwrap B   Wrap or not at word boundaries per B (on/off)",
 -  "     --ww           Shorthand for \"--wordwrap 1\"",
 -  "     --quote        Quote output text as SQL literals",
 -  "     --noquote      Do not quote output text",
 -  "     TABLE          The name of SQL table used for \"insert\" mode",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".nonce STRING            Suspend safe mode for one command if nonce matches",
 -#endif
 -  ".nullvalue STRING        Use STRING in place of NULL values",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".once ?OPTIONS? ?FILE?   Output for the next SQL command only to FILE",
 -  "     If FILE begins with '|' then open as a pipe",
 -  "       --bom  Put a UTF8 byte-order mark at the beginning",
 -  "       -e     Send output to the system text editor",
 -  "       -x     Send output as CSV to a spreadsheet (same as \".excel\")",
 -  /* Note that .open is (partially) available in WASM builds but is
 -  ** currently only intended to be used by the fiddle tool, not
 -  ** end users, so is "undocumented." */
 -  ".open ?OPTIONS? ?FILE?   Close existing database and reopen FILE",
 -  "     Options:",
 -  "        --append        Use appendvfs to append database to the end of FILE",
 -#endif
 -#ifndef SQLITE_OMIT_DESERIALIZE
 -  "        --deserialize   Load into memory using sqlite3_deserialize()",
 -  "        --hexdb         Load the output of \"dbtotxt\" as an in-memory db",
 -  "        --maxsize N     Maximum size for --hexdb or --deserialized database",
 -#endif
 -  "        --new           Initialize FILE to an empty database",
 -  "        --nofollow      Do not follow symbolic links",
 -  "        --readonly      Open FILE readonly",
 -  "        --zip           FILE is a ZIP archive",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".output ?FILE?           Send output to FILE or stdout if FILE is omitted",
 -  "   If FILE begins with '|' then open it as a pipe.",
 -  "   Options:",
 -  "     --bom                 Prefix output with a UTF8 byte-order mark",
 -  "     -e                    Send output to the system text editor",
 -  "     -x                    Send output as CSV to a spreadsheet",
 -#endif
 -  ".parameter CMD ...       Manage SQL parameter bindings",
 -  "   clear                   Erase all bindings",
 -  "   init                    Initialize the TEMP table that holds bindings",
 -  "   list                    List the current parameter bindings",
 -  "   set PARAMETER VALUE     Given SQL parameter PARAMETER a value of VALUE",
 -  "                           PARAMETER should start with one of: $ : @ ?",
 -  "   unset PARAMETER         Remove PARAMETER from the binding table",
 -  ".print STRING...         Print literal STRING",
 -#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
 -  ".progress N              Invoke progress handler after every N opcodes",
 -  "   --limit N                 Interrupt after N progress callbacks",
 -  "   --once                    Do no more than one progress interrupt",
 -  "   --quiet|-q                No output except at interrupts",
 -  "   --reset                   Reset the count for each input and interrupt",
 -#endif
 -  ".prompt MAIN CONTINUE    Replace the standard prompts",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".quit                    Stop interpreting input stream, exit if primary.",
 -  ".read FILE               Read input from FILE or command output",
 -  "    If FILE begins with \"|\", it is a command that generates the input.",
 -#endif
 -#if SQLITE_SHELL_HAVE_RECOVER
 -  ".recover                 Recover as much data as possible from corrupt db.",
 -  "   --ignore-freelist        Ignore pages that appear to be on db freelist",
 -  "   --lost-and-found TABLE   Alternative name for the lost-and-found table",
 -  "   --no-rowids              Do not attempt to recover rowid values",
 -  "                            that are not also INTEGER PRIMARY KEYs",
 -#endif
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ".restore ?DB? FILE       Restore content of DB (default \"main\") from FILE",
 -  ".save ?OPTIONS? FILE     Write database to FILE (an alias for .backup ...)",
 +# define session_close_all(X,Y)
  #endif
 -  ".scanstats on|off|est    Turn sqlite3_stmt_scanstatus() metrics on or off",
 -  ".schema ?PATTERN?        Show the CREATE statements matching PATTERN",
 -  "   Options:",
 -  "      --indent             Try to pretty-print the schema",
 -  "      --nosys              Omit objects whose names start with \"sqlite_\"",
 -  ",selftest ?OPTIONS?      Run tests defined in the SELFTEST table",
 -  "    Options:",
 -  "       --init               Create a new SELFTEST table",
 -  "       -v                   Verbose output",
 -  ".separator COL ?ROW?     Change the column and row separators",
 +
 +/*
 +** Implementation of the xFilter function for an open session.  Omit
 +** any tables named by ".session filter" but let all other table through.
 +*/
  #if defined(SQLITE_ENABLE_SESSION)
 -  ".session ?NAME? CMD ...  Create or control sessions",
 -  "   Subcommands:",
 -  "     attach TABLE             Attach TABLE",
 -  "     changeset FILE           Write a changeset into FILE",
 -  "     close                    Close one session",
 -  "     enable ?BOOLEAN?         Set or query the enable bit",
 -  "     filter GLOB...           Reject tables matching GLOBs",
 -  "     indirect ?BOOLEAN?       Mark or query the indirect status",
 -  "     isempty                  Query whether the session is empty",
 -  "     list                     List currently open session names",
 -  "     open DB NAME             Open a new session on DB",
 -  "     patchset FILE            Write a patchset into FILE",
 -  "   If ?NAME? is omitted, the first defined session is used.",
 -#endif
 -  ".sha3sum ...             Compute a SHA3 hash of database content",
 -  "    Options:",
 -  "      --schema              Also hash the sqlite_schema table",
 -  "      --sha3-224            Use the sha3-224 algorithm",
 -  "      --sha3-256            Use the sha3-256 algorithm (default)",
 -  "      --sha3-384            Use the sha3-384 algorithm",
 -  "      --sha3-512            Use the sha3-512 algorithm",
 -  "    Any other argument is a LIKE pattern for tables to hash",
 -#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
 -  ".shell CMD ARGS...       Run CMD ARGS... in a system shell",
 -#endif
 -  ".show                    Show the current values for various settings",
 -  ".stats ?ARG?             Show stats or turn stats on or off",
 -  "   off                      Turn off automatic stat display",
 -  "   on                       Turn on automatic stat display",
 -  "   stmt                     Show statement stats",
 -  "   vmstep                   Show the virtual machine step count only",
 -#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
 -  ".system CMD ARGS...      Run CMD ARGS... in a system shell",
 -#endif
 -  ".tables ?TABLE?          List names of tables matching LIKE pattern TABLE",
 -#ifndef SQLITE_SHELL_FIDDLE
 -  ",testcase NAME           Begin redirecting output to 'testcase-out.txt'",
 -#endif
 -  ",testctrl CMD ...        Run various sqlite3_test_control() operations",
 -  "                           Run \".testctrl\" with no arguments for details",
 -  ".timeout MS              Try opening locked tables for MS milliseconds",
 -  ".timer on|off            Turn SQL timer on or off",
 -#ifndef SQLITE_OMIT_TRACE
 -  ".trace ?OPTIONS?         Output each SQL statement as it is run",
 -  "    FILE                    Send output to FILE",
 -  "    stdout                  Send output to stdout",
 -  "    stderr                  Send output to stderr",
 -  "    off                     Disable tracing",
 -  "    --expanded              Expand query parameters",
 -#ifdef SQLITE_ENABLE_NORMALIZE
 -  "    --normalized            Normal the SQL statements",
 +static int session_filter(void *pCtx, const char *zTab){
 +  OpenSession *pSession = (OpenSession*)pCtx;
 +  int i;
 +  for(i=0; i<pSession->nFilter; i++){
 +    if( sqlite3_strglob(pSession->azFilter[i], zTab)==0 ) return 0;
 +  }
 +  return 1;
 +}
  #endif
 -  "    --plain                 Show SQL as it is input",
 -  "    --stmt                  Trace statement execution (SQLITE_TRACE_STMT)",
 -  "    --profile               Profile statements (SQLITE_TRACE_PROFILE)",
 -  "    --row                   Trace each row (SQLITE_TRACE_ROW)",
 -  "    --close                 Trace connection close (SQLITE_TRACE_CLOSE)",
 -#endif /* SQLITE_OMIT_TRACE */
 -#ifdef SQLITE_DEBUG
 -  ".unmodule NAME ...       Unregister virtual table modules",
 -  "    --allexcept             Unregister everything except those named",
 +
 +#if SHELL_DYNAMIC_EXTENSION
 +static int notify_subscribers(ShellInState *psi, NoticeKind nk, void *pvs) {
 +  int six = 0;
 +  int rcFlags = 0;
 +  ShellExState *psx = XSS(psi);
 +  while( six < psi->numSubscriptions ){
 +    struct EventSubscription *pes = psi->pSubscriptions + six++;
 +    rcFlags |= pes->eventHandler(pes->pvUserData, nk, pvs, psx);
 +  }
 +  return rcFlags;
 +}
  #endif
 -  ".version                 Show source, library and compiler versions",
 -  ".vfsinfo ?AUX?           Information about the top-level VFS",
 -  ".vfslist                 List all available VFSes",
 -  ".vfsname ?AUX?           Print the name of the VFS stack",
 -  ".width NUM1 NUM2 ...     Set minimum column widths for columnar output",
 -  "     Negative values right-justify",
 -};
  
  /*
 -** Output help text.
 -**
 -** zPattern describes the set of commands for which help text is provided.
 -** If zPattern is NULL, then show all commands, but only give a one-line
 -** description of each.
 +** Try to deduce the type of file for zName based on its content.  Return
 +** one of the SHELL_OPEN_* constants.
  **
 -** Return the number of matches.
 +** If the file does not exist or is empty but its name looks like a ZIP
 +** archive and the dfltZip flag is true, then assume it is a ZIP archive.
 +** Otherwise, assume an ordinary database regardless of the filename if
 +** the type cannot be determined from content.
  */
 -static int showHelp(FILE *out, const char *zPattern){
 -  int i = 0;
 -  int j = 0;
 -  int n = 0;
 -  char *zPat;
 -  if( zPattern==0
 -   || zPattern[0]=='0'
 -   || cli_strcmp(zPattern,"-a")==0
 -   || cli_strcmp(zPattern,"-all")==0
 -   || cli_strcmp(zPattern,"--all")==0
 -  ){
 -    enum HelpWanted { HW_NoCull = 0, HW_SummaryOnly = 1, HW_Undoc = 2 };
 -    enum HelpHave { HH_Undoc = 2, HH_Summary = 1, HH_More = 0 };
 -    /* Show all or most commands
 -    ** *zPattern==0   => summary of documented commands only
 -    ** *zPattern=='0' => whole help for undocumented commands
 -    ** Otherwise      => whole help for documented commands
 -    */
 -    enum HelpWanted hw = HW_SummaryOnly;
 -    enum HelpHave hh = HH_More;
 -    if( zPattern!=0 ){
 -      hw = (*zPattern=='0')? HW_NoCull|HW_Undoc : HW_NoCull;
 -    }
 -    for(i=0; i<ArraySize(azHelp); i++){
 -      switch( azHelp[i][0] ){
 -      case ',':
 -        hh = HH_Summary|HH_Undoc;
 -        break;
 -      case '.':
 -        hh = HH_Summary;
 -        break;
 -      default:
 -        hh &= ~HH_Summary;
 -        break;
 -      }
 -      if( ((hw^hh)&HH_Undoc)==0 ){
 -        if( (hh&HH_Summary)!=0 ){
 -          utf8_printf(out, ".%s\n", azHelp[i]+1);
 -          ++n;
 -        }else if( (hw&HW_SummaryOnly)==0 ){
 -          utf8_printf(out, "%s\n", azHelp[i]);
 -        }
 -      }
 -    }
 -  }else{
 -    /* Seek documented commands for which zPattern is an exact prefix */
 -    zPat = sqlite3_mprintf(".%s*", zPattern);
 -    shell_check_oom(zPat);
 -    for(i=0; i<ArraySize(azHelp); i++){
 -      if( sqlite3_strglob(zPat, azHelp[i])==0 ){
 -        utf8_printf(out, "%s\n", azHelp[i]);
 -        j = i+1;
 -        n++;
 -      }
 -    }
 -    sqlite3_free(zPat);
 -    if( n ){
 -      if( n==1 ){
 -        /* when zPattern is a prefix of exactly one command, then include
 -        ** the details of that command, which should begin at offset j */
 -        while( j<ArraySize(azHelp)-1 && azHelp[j][0]==' ' ){
 -          utf8_printf(out, "%s\n", azHelp[j]);
 -          j++;
 -        }
 -      }
 -      return n;
 -    }
 -    /* Look for documented commands that contain zPattern anywhere.
 -    ** Show complete text of all documented commands that match. */
 -    zPat = sqlite3_mprintf("%%%s%%", zPattern);
 -    shell_check_oom(zPat);
 -    for(i=0; i<ArraySize(azHelp); i++){
 -      if( azHelp[i][0]==',' ){
 -        while( i<ArraySize(azHelp)-1 && azHelp[i+1][0]==' ' ) ++i;
 -        continue;
 -      }
 -      if( azHelp[i][0]=='.' ) j = i;
 -      if( sqlite3_strlike(zPat, azHelp[i], 0)==0 ){
 -        utf8_printf(out, "%s\n", azHelp[j]);
 -        while( j<ArraySize(azHelp)-1 && azHelp[j+1][0]==' ' ){
 -          j++;
 -          utf8_printf(out, "%s\n", azHelp[j]);
 -        }
 -        i = j;
 -        n++;
 -      }
 -    }
 -    sqlite3_free(zPat);
 -  }
 -  return n;
 -}
 -
 -/* Forward reference */
 -static int process_input(ShellState *p);
 -
 -/*
 -** Read the content of file zName into memory obtained from sqlite3_malloc64()
 -** and return a pointer to the buffer. The caller is responsible for freeing
 -** the memory.
 -**
 -** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes
 -** read.
 -**
 -** For convenience, a nul-terminator byte is always appended to the data read
 -** from the file before the buffer is returned. This byte is not included in
 -** the final value of (*pnByte), if applicable.
 -**
 -** NULL is returned if any error is encountered. The final value of *pnByte
 -** is undefined in this case.
 -*/
 -static char *readFile(const char *zName, int *pnByte){
 -  FILE *in = fopen(zName, "rb");
 -  long nIn;
 -  size_t nRead;
 -  char *pBuf;
 -  int rc;
 -  if( in==0 ) return 0;
 -  rc = fseek(in, 0, SEEK_END);
 -  if( rc!=0 ){
 -    raw_printf(stderr, "Error: '%s' not seekable\n", zName);
 -    fclose(in);
 -    return 0;
 -  }
 -  nIn = ftell(in);
 -  rewind(in);
 -  pBuf = sqlite3_malloc64( nIn+1 );
 -  if( pBuf==0 ){
 -    raw_printf(stderr, "Error: out of memory\n");
 -    fclose(in);
 -    return 0;
 -  }
 -  nRead = fread(pBuf, nIn, 1, in);
 -  fclose(in);
 -  if( nRead!=1 ){
 -    sqlite3_free(pBuf);
 -    raw_printf(stderr, "Error: cannot read '%s'\n", zName);
 -    return 0;
 -  }
 -  pBuf[nIn] = 0;
 -  if( pnByte ) *pnByte = nIn;
 -  return pBuf;
 -}
 -
 -#if defined(SQLITE_ENABLE_SESSION)
 -/*
 -** Close a single OpenSession object and release all of its associated
 -** resources.
 -*/
 -static void session_close(OpenSession *pSession){
 -  int i;
 -  sqlite3session_delete(pSession->p);
 -  sqlite3_free(pSession->zName);
 -  for(i=0; i<pSession->nFilter; i++){
 -    sqlite3_free(pSession->azFilter[i]);
 -  }
 -  sqlite3_free(pSession->azFilter);
 -  memset(pSession, 0, sizeof(OpenSession));
 -}
 -#endif
 -
 -/*
 -** Close all OpenSession objects and release all associated resources.
 -*/
 -#if defined(SQLITE_ENABLE_SESSION)
 -static void session_close_all(ShellState *p, int i){
 -  int j;
 -  struct AuxDb *pAuxDb = i<0 ? p->pAuxDb : &p->aAuxDb[i];
 -  for(j=0; j<pAuxDb->nSession; j++){
 -    session_close(&pAuxDb->aSession[j]);
 -  }
 -  pAuxDb->nSession = 0;
 -}
 -#else
 -# define session_close_all(X,Y)
 -#endif
 -
 -/*
 -** Implementation of the xFilter function for an open session.  Omit
 -** any tables named by ".session filter" but let all other table through.
 -*/
 -#if defined(SQLITE_ENABLE_SESSION)
 -static int session_filter(void *pCtx, const char *zTab){
 -  OpenSession *pSession = (OpenSession*)pCtx;
 -  int i;
 -  for(i=0; i<pSession->nFilter; i++){
 -    if( sqlite3_strglob(pSession->azFilter[i], zTab)==0 ) return 0;
 -  }
 -  return 1;
 -}
 -#endif
 -
 -/*
 -** Try to deduce the type of file for zName based on its content.  Return
 -** one of the SHELL_OPEN_* constants.
 -**
 -** If the file does not exist or is empty but its name looks like a ZIP
 -** archive and the dfltZip flag is true, then assume it is a ZIP archive.
 -** Otherwise, assume an ordinary database regardless of the filename if
 -** the type cannot be determined from content.
 -*/
 -int deduceDatabaseType(const char *zName, int dfltZip){
 +u8 deduceDatabaseType(const char *zName, int dfltZip){
    FILE *f = fopen(zName, "rb");
    size_t n;
 -  int rc = SHELL_OPEN_UNSPEC;
 +  u8 rc = SHELL_OPEN_UNSPEC;
    char zBuf[100];
    if( f==0 ){
      if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
  /*
  ** Reconstruct an in-memory database using the output from the "dbtotxt"
  ** program.  Read content from the file in p->aAuxDb[].zDbFilename.
 -** If p->aAuxDb[].zDbFilename is 0, then read from standard input.
 +** If p->aAuxDb[].zDbFilename is 0, then read from the present input.
  */
 -static unsigned char *readHexDb(ShellState *p, int *pnData){
 +static unsigned char *readHexDb(ShellInState *psi, int *pnData){
++  ResourceMark mark = holder_mark();
    unsigned char *a = 0;
 -  int nLine;
    int n = 0;
    int pgsz = 0;
    int iOffset = 0;
    n = (n+pgsz-1)&~(pgsz-1);  /* Round n up to the next multiple of pgsz */
    a = sqlite3_malloc( n ? n : 1 );
    shell_check_oom(a);
++  smem_holder(a); /* offset 0 */
    memset(a, 0, n);
    if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){
 -    utf8_printf(stderr, "invalid pagesize\n");
 +    utf8_printf(STD_ERR, "invalid pagesize\n");
      goto readHexDb_error;
    }
 -  for(nLine++; fgets(zLine, sizeof(zLine), in)!=0; nLine++){
 +  while( strLineGet(zLine,sizeof(zLine), psi->pInSource)!=0 ){
      rc = sscanf(zLine, "| page %d offset %d", &j, &k);
      if( rc==2 ){
        iOffset = k;
        }
      }
    }
 -  *pnData = n;
 -  if( in!=p->in ){
 -    fclose(in);
 -  }else{
 -    p->lineno = nLine;
 +  *pnData = n; /* Record success and size. */
++  swap_held(mark, 0, 0);
 + readHexDb_cleanup:
 +  if( psi->pInSource==&inRedir ){
 +    fclose( inRedir.inFile );
 +    psi->pInSource = inRedir.pFrom;
    }
++  holder_free(mark);
    return a;
  
 -readHexDb_error:
 -  if( in!=p->in ){
 -    fclose(in);
 -  }else{
 -    while( fgets(zLine, sizeof(zLine), p->in)!=0 ){
 -      nLine++;
 -      if(cli_strncmp(zLine, "| end ", 6)==0 ) break;
 + readHexDb_error:
 +  nlError = psi->pInSource->lineno;
 +  if( psi->pInSource!=&inRedir ){
 +    /* Since taking input inline, consume through its end marker. */
 +    while( strLineGet(zLine, sizeof(zLine), psi->pInSource)!=0 ){
 +      if(cli_strncmp(zLine, zEndMarker, 6)==0 ) break;
      }
 -    p->lineno = nLine;
    }
--  sqlite3_free(a);
 -  utf8_printf(stderr,"Error on line %d of --hexdb input\n", nLine);
 -  return 0;
 +  a = 0;
 +  utf8_printf(STD_ERR,"Error on line %d within --hexdb input\n", nlError);
 +  goto readHexDb_cleanup;
  }
  #endif /* SQLITE_OMIT_DESERIALIZE */
  
@@@ -5749,7 -5326,7 +5794,7 @@@ static void shellUSleepFunc
    sqlite3_result_int(context, sleep);
  }
  
--/* Flags for open_db().
++/* Flags for open_db(). ToDo: Conform comments to code or vice-versa.
  **
  ** The default behavior of open_db() is to exit(1) if the database fails to
  ** open.  The OPEN_DB_KEEPALIVE flag changes that so that it prints an error
@@@ -5808,21 -5381,20 +5853,21 @@@ static void open_db(ShellExState *psx, 
          break;
        }
      }
 -    globalDb = p->db;
 -    if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){
 -      utf8_printf(stderr,"Error: unable to open database \"%s\": %s\n",
 -          zDbFilename, sqlite3_errmsg(p->db));
 +    globalDb = DBX(psx);
 +    if( DBX(psx)==0 || SQLITE_OK!=sqlite3_errcode(DBX(psx)) ){
 +      const char *zWhy = (DBX(psx)==0)? "(?)" : sqlite3_errmsg(DBX(psx));
 +      utf8_printf(STD_ERR,"Error: unable to open database \"%s\": %s\n",
 +          zDbFilename, zWhy);
++      sqlite3_close(DBX(psx));
++      if( bail_on_error && !stdin_is_interactive ){
++        shell_terminate("-bail used in batch mode.");
++      }
        if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){
--        exit(1);
++        shell_terminate("with OPEN_DB_KEEPALIVE.");
        }
-       sqlite3_close(DBX(psx));
 -      sqlite3_close(p->db);
 -      sqlite3_open(":memory:", &p->db);
 -      if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){
 -        utf8_printf(stderr,
 -          "Also: unable to open substitute in-memory database.\n"
 -        );
 -        exit(1);
 +      sqlite3_open(":memory:", &DBX(psx));
 +      if( DBX(psx)==0 || SQLITE_OK!=sqlite3_errcode(DBX(psx)) ){
-         utf8_printf(stderr,
-           "Also: unable to open substitute in-memory database.\n"
-         );
-         exit(1);
++        shell_terminate("Also: unable to open substitute in-memory database.");
        }else{
          utf8_printf(stderr,
            "Notice: using substitute in-memory database instead of \"%s\"\n",
@@@ -15268,486 -11480,160 +15313,488 @@@ static int runOneSqlLine(ShellExState *
    return 0;
  }
  
 -static void echo_group_input(ShellState *p, const char *zDo){
 -  if( ShellHasFlag(p, SHFLG_Echo) ) utf8_printf(p->out, "%s\n", zDo);
 +#if SHELL_EXTENDED_PARSING
 +/* Resumable line classsifier for dot-commands
 +**
 +** Determines if a dot-command is open, having either an unclosed
 +** quoted argument or an escape sequence opener ('\') at its end.
 +**
 +** The FSM design/behavior assumes/requires that a terminating '\'
 +** is not part of the character sequence being classified -- that
 +** it represents an escaped newline which is removed as physical
 +** lines are spliced to accumulate logical lines.
 +**
 +** The line or added line-portion is passed as zCmd.
 +** The pScanState pointer must reference an (opaque) DCmd_ScanState,
 +** which must be set to DCSS_Start to initialize the scanner state.
 +** Resumed scanning should always be done with zCmd logically just
 +** past the last non-0 char of the text previously passed in, with
 +** any previously scanned, trailing newline escape first trimmed.
 +** Returns are: 0 => not open (aka complete), 1 => is open (incomplete)
 +** The following macros may be applied to the scan state:
 +*/
 +#define DCSS_InDarkArg(dcss) (((dcss)&argPosMask)==inDqArg)
 +#define DCSS_EndEscaped(dcss) (((dcss)&endEscaped)!=0)
 +#define DCSS_IsOpen(dcss) (((dcss)&isOpenMask)!=0)
 +typedef enum {
 +  DCSS_Start = 0,
 +  twixtArgs = 0, inSqArg = 1, inDarkArg = 2, inDqArg = 3, /* ordered */
 +  endEscaped = 4, /* bit used */
 +  argPosMask = 3, /* bits used */
 +  isOpenMask = 1|4 /* bit test */
 +} DCmd_ScanState;
 +
 +static void dot_command_scan(char *zCmd, DCmd_ScanState *pScanState,
 +                             SCAN_TRACKER_REFTYPE pst){
 +  DCmd_ScanState ss = *pScanState & ~endEscaped;
 +  char c = (ss&isOpenMask)? 1 : *zCmd++;
 +  while( c!=0 ){
 +    switch( ss ){
 +    case twixtArgs:
 +      CONTINUE_PROMPT_AWAITC(pst, 0);
 +      while( IsSpace(c) ){
 +        if( (c=*zCmd++)==0 ) goto atEnd;
 +      }
 +      switch( c ){
 +      case '\\':
 +        if( *zCmd==0 ){
 +          ss |= endEscaped;
 +          goto atEnd;
 +        }else goto inDark;
 +      case '\'': ss = inSqArg; goto inSq;
 +      case '"': ss = inDqArg; goto inDq;
 +      default: ss = inDarkArg; goto inDark;
 +      }
 +    inSq:
 +    case inSqArg:
 +      CONTINUE_PROMPT_AWAITC(pst, '\'');
 +      while( (c=*zCmd++)!='\'' ){
 +        if( c==0 ) goto atEnd;
 +        if( c=='\\' && *zCmd==0 ){
 +          ss |= endEscaped;
 +          goto atEnd;
 +        }
 +      }
 +      ss = twixtArgs;
 +      c = *zCmd++;
 +      continue;
 +    inDq:
 +    case inDqArg:
 +      CONTINUE_PROMPT_AWAITC(pst, '"');
 +      do {
 +        if( (c=*zCmd++)==0 ) goto atEnd;
 +        if( c=='\\' ){
 +          if( (c=*zCmd++)==0 ){
 +            ss |= endEscaped;
 +            goto atEnd;
 +          }
 +          if( (c=*zCmd++)==0 ) goto atEnd;
 +        }
 +      } while( c!='"' );
 +      ss = twixtArgs;
 +      c = *zCmd++;
 +      continue;
 +    inDark:
 +    case inDarkArg:
 +      CONTINUE_PROMPT_AWAITC(pst, 0);
 +      while( !IsSpace(c) ){
 +        if( c=='\\' && *zCmd==0 ){
 +          ss |= endEscaped;
 +          goto atEnd;
 +        }
 +        if( (c=*zCmd++)==0 ) goto atEnd;
 +      }
 +      ss = twixtArgs;
 +      c = *zCmd++;
 +      continue;
 +    case endEscaped: case isOpenMask: default:
 +      ; /* Not reachable, but quiet compilers unable to see this. */
 +    }
 +  }
 + atEnd:
 +  *pScanState = ss;
  }
 +#else
 +# define dot_command_scan(x,y,z)
 +#endif
  
 -#ifdef SQLITE_SHELL_FIDDLE
 +/* Utility functions for process_input. */
 +
 +#if SHELL_EXTENDED_PARSING
  /*
 -** Alternate one_input_line() impl for wasm mode. This is not in the primary
 -** impl because we need the global shellState and cannot access it from that
 -** function without moving lots of code around (creating a larger/messier diff).
 +** Process dot-command line with its scan state to:
 +** 1. Setup for requested line-splicing; and
 +** 2. Say whether it is complete.
 +** The last two out parameters are the line's length, which may be
 +** adjusted, and the char to be used for joining a subsequent line.
 +** This is broken out of process_input() mainly for readability.
 +** The return is TRUE for dot-command ready to run, else false.
  */
 -static char *one_input_line(FILE *in, char *zPrior, int isContinuation){
 -  /* Parse the next line from shellState.wasm.zInput. */
 -  const char *zBegin = shellState.wasm.zPos;
 -  const char *z = zBegin;
 -  char *zLine = 0;
 -  i64 nZ = 0;
 -
 -  UNUSED_PARAMETER(in);
 -  UNUSED_PARAMETER(isContinuation);
 -  if(!z || !*z){
 +static int line_join_done(DCmd_ScanState dcss, char *zLine,
 +                          i64 *pnLength, char *pcLE){
 +  /* It is ready only if has no open argument or escaped newline. */
 +  int bOpen = DCSS_IsOpen(dcss);
 +  if( !DCSS_EndEscaped(dcss) ){
 +    *pcLE = '\n';
 +    return !bOpen;
 +  }else{
 +    *pcLE = (bOpen || DCSS_InDarkArg(dcss))? 0 : ' ';
 +    /* Swallow the trailing escape character. */
 +    zLine[--*pnLength] = 0;
      return 0;
    }
 -  while(*z && isspace(*z)) ++z;
 -  zBegin = z;
 -  for(; *z && '\n'!=*z; ++nZ, ++z){}
 -  if(nZ>0 && '\r'==zBegin[nZ-1]){
 -    --nZ;
 +}
 +#endif
 +
 +/*
 +** Grow the accumulation line buffer to accommodate ncNeed chars.
 +** In/out parameters pz and pna reference the buffer and its size.
 +** The buffer must eventually be sqlite3_free()'ed by the caller.
 +*/
 +static void grow_line_buffer(char **pz, i64 *pna, int ncNeed){
 +
 +  if( ncNeed > *pna ){
 +    *pna += *pna + (*pna>>1) + 100;
 +    *pz = sqlite3_realloc(*pz, *pna);
 +    shell_check_oom(*pz);
    }
 -  shellState.wasm.zPos = z;
 -  zLine = realloc(zPrior, nZ+1);
 -  shell_check_oom(zLine);
 -  memcpy(zLine, zBegin, nZ);
 -  zLine[nZ] = 0;
 -  return zLine;
  }
 -#endif /* SQLITE_SHELL_FIDDLE */
  
  /*
 -** Read input from *in and process it.  If *in==0 then input
 -** is interactive - the user is typing it it.  Otherwise, input
 -** is coming from a file or device.  A prompt is issued and history
 -** is saved only if input is interactive.  An interrupt signal will
 -** cause this routine to exit immediately, unless input is interactive.
 +** Read input from designated source (p->pInSource) and process it.
 +** If pInSource==0 then input is interactive - the user is typing it.
 +** Otherwise, input is coming from a file, stream device or string.
 +** Prompts issue and history is saved only for interactive input.
 +** An interrupt signal will cause this routine to exit immediately,
 +** with "exit demanded" code returned, unless input is interactive.
  **
 -** Return the number of errors.
 -*/
 -static int process_input(ShellState *p){
 -  char *zLine = 0;          /* A single input line */
 -  char *zSql = 0;           /* Accumulated SQL text */
 -  i64 nLine;                /* Length of current line */
 -  i64 nSql = 0;             /* Bytes of zSql[] used */
 -  i64 nAlloc = 0;           /* Allocated zSql[] space */
 -  int rc;                   /* Error code */
 -  int errCnt = 0;           /* Number of errors seen */
 -  i64 startline = 0;        /* Line number for start of current input */
 -  QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */
 -
 -  if( p->inputNesting==MAX_INPUT_NESTING ){
 -    /* This will be more informative in a later version. */
 -    utf8_printf(stderr,"Input nesting limit (%d) reached at line %d."
 -                " Check recursion.\n", MAX_INPUT_NESTING, p->lineno);
 -    return 1;
 -  }
 -  ++p->inputNesting;
 -  p->lineno = 0;
 -  CONTINUE_PROMPT_RESET;
 -  while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){
 -    fflush(p->out);
 -    zLine = one_input_line(p->in, zLine, nSql>0);
 -    if( zLine==0 ){
 -      /* End of input */
 -      if( p->in==0 && stdin_is_interactive ) printf("\n");
 -      break;
 -    }
 -    if( seenInterrupt ){
 -      if( p->in!=0 ) break;
 -      seenInterrupt = 0;
 -    }
 -    p->lineno++;
 -    if( QSS_INPLAIN(qss)
 -        && line_is_command_terminator(zLine)
 -        && line_is_complete(zSql, nSql) ){
 -      memcpy(zLine,";",2);
 -    }
 -    qss = quickscan(zLine, qss, CONTINUE_PROMPT_PSTATE);
 -    if( QSS_PLAINWHITE(qss) && nSql==0 ){
 -      /* Just swallow single-line whitespace */
 -      echo_group_input(p, zLine);
 -      qss = QSS_Start;
 -      continue;
 -    }
 -    if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){
 -      CONTINUE_PROMPT_RESET;
 -      echo_group_input(p, zLine);
 -      if( zLine[0]=='.' ){
 -        rc = do_meta_command(zLine, p);
 -        if( rc==2 ){ /* exit requested */
 +** Returns are the post-execute values of enum DotCmdRC:
 +**   DCR_Ok, DCR_Return, DCR_Exit, DCR_Abort
 +** each of which may be bit-wise or'ed with DCR_Error.
 +*/
 +static DotCmdRC process_input(ShellInState *psi){
 +  char *zLineInput = 0;  /* a line-at-a-time input buffer or usable result */
 +  char *zLineAccum = 0;  /* accumulation buffer, used for multi-line input */
 +  /* Above two pointers could be local to the group handling loop, but are
 +   * not so that the number of memory allocations can be reduced. They are
 +   * reused from one incoming group to another, realloc()'ed as needed. */
 +  i64 naAccum = 0;       /* tracking how big zLineAccum buffer has become */
 +  /* Some flags for ending the overall group processing loop, always 1 or 0 */
 +  u8 bInputEnd=0, bInterrupted=0;
 +  /* Termination kind: DCR_Ok, DCR_Error, DCR_Return, DCR_Exit, DCR_Abort,
 +   * the greatest of whichever is applicable */
-   u8 termKind = DCR_Ok;
++  u8 termKind = (XSS(psi)->shellAbruptExit==0)? DCR_Ok : DCR_Exit;
++  /* Flag to indicate input from shell invocation argument. */
++  u8 bInvokeArg = INSOURCE_IS_INVOKEARG(psi->pInSource);
 +  /* Flag to affect prompting and interrupt action */
 +  u8 bInteractive = INSOURCE_IS_INTERACTIVE(psi->pInSource);
 +  int nErrors = 0;       /* count of errors during execution or its prep */
 +
 +  /* Block overly-recursive or absurdly nested input redirects. */
 +  if( psi->inputNesting>=MAX_INPUT_NESTING ){
 +    InSource *pInSrc = psi->pInSource->pFrom;
 +    const char *zLead = "Input nesting limit ("
 +      SHELL_STRINGIFY(MAX_INPUT_NESTING)") reached,";
 +    int i = 3;
 +    assert(pInSrc!=0 && MAX_INPUT_NESTING>0);
 +    while( i-->0 && pInSrc!=0 ){
 +      utf8_printf(STD_ERR,
 +                  "%s from line %d of \"%s\"",
 +                  zLead, pInSrc->lineno, pInSrc->zSourceSay);
 +      zLead = (i%2==0)? "\n" : "";
 +      pInSrc=pInSrc->pFrom;
 +    }
 +    utf8_printf(STD_ERR, " ...\nError: Check recursion.\n");
 +    return DCR_Error;
 +  }
 +  ++psi->inputNesting;
 +
 +  /* line-group processing loop (per SQL block, dot-command or comment) */
 +  while( !bInputEnd && termKind==DCR_Ok && !bInterrupted ){
 +#if SHELL_DYNAMIC_EXTENSION
 +    ScriptSupport *pSS = psi->script;
 +#endif
 +    int nGroupLines = 0;  /* count of lines belonging to this group */
 +    i64 ncLineIn = 0;     /* how many (non-zero) chars are in zLineInput  */
 +    i64 ncLineAcc = 0;    /* how many (non-zero) chars are in zLineAccum  */
 +    i64 iLastLine = 0;    /* index of last accumulated line start */
 +    /* Initialize resumable scanner(s). */
 +    SqlScanState sqScanState = SSS_Start; /* for SQL scan */
 +#if SHELL_EXTENDED_PARSING
 +    DCmd_ScanState dcScanState = DCSS_Start;  /* for dot-command scan */
 +    int nLeadWhite = 0; /* skips over initial whitespace to . or # */
 +    char cLineEnd = '\n'; /* May be swallowed or replaced with space. */
 +#else
 +# define nLeadWhite 0     /* For legacy parsing, no white before . or # . */
 +# define cLineEnd '\n'    /* For legacy parsing, this always joins lines. */
 +#endif
 +    /* An ordered enum to record kind of incoming line group. Its ordering
 +     * means than a value greater than Comment implies something runnable.
 +     */
 +    enum { Tbd = 0, Eof, Comment, Sql, Cmd
 +#if SHELL_DYNAMIC_EXTENSION
 +      , Script
 +#endif
 +    } inKind = Tbd;
 +    /* An enum signifying the group disposition state */
 +    enum {
 +      Incoming, Runnable, Dumpable, Erroneous, Ignore
 +    } disposition = Incoming;
 +    char **pzLineUse = &zLineInput;   /* ref line to be processed */
 +    i64 *pncLineUse = &ncLineIn;      /* ref that line's char count */
 +    int iStartline = 0;               /* starting line number of group */
 +
 +    seenInterrupt = 0;
 +    fflush(psi->out);
 +    CONTINUE_PROMPT_RESET;
 +    zLineInput = one_input_line(psi->pInSource, zLineInput,
 +                                nGroupLines>0, &shellPrompts);
 +    if( zLineInput==0 ){
 +      bInputEnd = 1;
 +      inKind = Eof;
 +      disposition = Ignore;
 +      if( bInteractive ) printf("\n");
 +    }else{
 +      ++nGroupLines;
 +      iStartline = psi->pInSource->lineno;
 +      ncLineIn = strlen30(zLineInput);
 +      if( seenInterrupt ){
 +        if( psi->pInSource!=0 ) break;
 +        bInterrupted = 1; /* This will be honored, or not, later. */
 +        seenInterrupt = 0;
 +        disposition = Dumpable;
 +      }
 +      /* Classify and check for single-line dispositions, prep for more. */
 +#if SHELL_EXTENDED_PARSING
 +      nLeadWhite = (SHEXT_PARSING(psi))
 +        ? skipWhite(zLineInput)-zLineInput
 +        : 0; /* Disallow leading whitespace for . or # in legacy mode. */
 +#endif
 +#if SHELL_DYNAMIC_EXTENSION
 +      if( pSS && pSS->pMethods->isScriptLeader(pSS, zLineInput+nLeadWhite) ){
 +        inKind = Script;
 +      }else
 +#endif
 +      {
 +        switch( zLineInput[nLeadWhite] ){
 +        case '.':
 +          inKind = Cmd;
 +          dot_command_scan(zLineInput+nLeadWhite, &dcScanState,
 +                           CONTINUE_PROMPT_PSTATE);
 +          break;
 +        case '#':
 +          inKind = Comment;
 +          break;
 +        default:
 +          /* Might be SQL, or a swallowable whole SQL comment. */
 +          sql_prescan(zLineInput, &sqScanState, CONTINUE_PROMPT_PSTATE);
 +          if( SSS_PLAINWHITE(sqScanState) ){
 +            /* It's either all blank or a whole SQL comment. Swallowable. */
 +            inKind = Comment;
 +          }else{
 +            /* Something dark, not a # comment or dot-command. Must be SQL. */
 +            inKind = Sql;
 +          }
            break;
 -        }else if( rc ){
 -          errCnt++;
          }
        }
 -      qss = QSS_Start;
 -      continue;
 -    }
 -    /* No single-line dispositions remain; accumulate line(s). */
 -    nLine = strlen(zLine);
 -    if( nSql+nLine+2>=nAlloc ){
 -      /* Grow buffer by half-again increments when big. */
 -      nAlloc = nSql+(nSql>>1)+nLine+100;
 -      zSql = realloc(zSql, nAlloc);
 -      shell_check_oom(zSql);
 -    }
 -    if( nSql==0 ){
 -      i64 i;
 -      for(i=0; zLine[i] && IsSpace(zLine[i]); i++){}
 -      assert( nAlloc>0 && zSql!=0 );
 -      memcpy(zSql, zLine+i, nLine+1-i);
 -      startline = p->lineno;
 -      nSql = nLine-i;
 -    }else{
 -      zSql[nSql++] = '\n';
 -      memcpy(zSql+nSql, zLine, nLine+1);
 -      nSql += nLine;
 -    }
 -    if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){
 -      echo_group_input(p, zSql);
 -      errCnt += runOneSqlLine(p, zSql, p->in, startline);
 -      CONTINUE_PROMPT_RESET;
 -      nSql = 0;
 -      if( p->outCount ){
 -        output_reset(p);
 -        p->outCount = 0;
 -      }else{
 -        clearTempFile(p);
 +    } /* end read/classify initial group input line */
 +
 +    /* Here, if not at end of input, the initial line of group is in, and
 +     * it has been scanned and classified. Next, do the processing needed
 +     * to recognize whether the initial line or accumulated group so far
 +     * is complete such that it may be run, and perform joining of more
 +     * lines into the group while it is not so complete. This loop ends
 +     * with the input group line(s) ready to be run, or if the input ends
 +     * before it is ready, with the group marked as erroneous.
 +     */
 +    while( disposition==Incoming ){
 +      PROMPTS_UPDATE(inKind == Sql || inKind == Cmd);
 +      /* Check whether more to accumulate, or ready for final disposition. */
 +      switch( inKind ){
 +      case Comment:
 +        disposition = Dumpable;
 +      case Cmd:
 +#if SHELL_EXTENDED_PARSING
 +        if( SHEXT_PARSING(psi) ){
 +          if( line_join_done(dcScanState, *pzLineUse, pncLineUse, &cLineEnd) ){
 +            disposition = Runnable;
 +          }
 +        }else
 +#endif
 +          disposition = Runnable; /* Legacy, any dot-command line is ready. */
 +        break;
 +#if SHELL_DYNAMIC_EXTENSION
 +      case Script:
 +        if( pSS==0
 +            || pSS->pMethods->scriptIsComplete(pSS, *pzLineUse+nLeadWhite, 0) ){
 +          disposition = Runnable;
 +        }
 +        break;
 +#endif
 +      case Sql:
 +        /* Check to see if it is complete and ready to run. */
 +        if( SSS_SEMITERM(sqScanState) && 1==sqlite3_complete(*pzLineUse)){
 +          disposition = Runnable;
 +        }else if( SSS_PLAINWHITE(sqScanState) ){
 +          /* It is a leading single-line or multi-line comment. */
 +          disposition = Runnable;
 +          inKind = Comment;
 +        }else{
 +          char *zT = line_is_command_terminator(zLineInput);
 +          if( zT!=0  ){
 +            /* Last line is a lone go or / -- prep for running it. */
 +            if( nGroupLines>1 ){
 +              disposition = Runnable;
 +              memcpy(*pzLineUse+iLastLine,";\n",3);
 +              *pncLineUse = iLastLine + 2;
 +            }else{
 +              /* Unless nothing preceded it, then dump it. */
 +              disposition = Dumpable;
 +            }
 +          }
 +        }
 +        break;
 +      case Tbd: case Eof: default: assert(0); /* Not reachable */
 +      } /* end switch on inKind */
 +      /* Collect and accumulate more input if group not yet complete. */
 +      if( disposition==Incoming ){
 +        if( nGroupLines==1 ){
 +          grow_line_buffer(&zLineAccum, &naAccum, ncLineIn+2);
 +          /* Copy line just input */
 +          memcpy(zLineAccum, zLineInput, ncLineIn);
 +          zLineAccum[ncLineIn] = 0;
 +          ncLineAcc = ncLineIn;
 +          pzLineUse = &zLineAccum;
 +          pncLineUse = &ncLineAcc;
 +        }
 +        /* Read in next line of group, (if available.) */
 +        zLineInput = one_input_line(psi->pInSource, zLineInput,
 +                                    nGroupLines>0, &shellPrompts);
 +        if( zLineInput==0 ){
 +          bInputEnd = 1;
-           if( inKind==Sql && psi->pInSource==&cmdInSource ){
++          if( inKind==Sql && bInvokeArg ){
 +            /* As a special dispensation, SQL arguments on the command line
 +            ** do not need to end with ';' (or a lone go.) */
 +            if( nGroupLines>1 ){
 +              grow_line_buffer(&zLineAccum, &naAccum, ncLineAcc+2);
 +            }
 +            strcpy( zLineAccum+ncLineAcc, ";" );
 +            if( 1==sqlite3_complete(*pzLineUse) ){
 +              zLineAccum[ncLineAcc] = 0;
 +              disposition = Runnable;
 +              continue;
 +            }
 +          }
 +          disposition = Erroneous;
 +          inKind = Eof;
 +          if( bInteractive ) printf("\n");
 +          continue;
 +        }
 +        ++nGroupLines;
 +        ncLineIn = strlen30(zLineInput);
 +        /* Scan line just input (if needed) and append to accumulation. */
 +        switch( inKind ){
 +        case Cmd:
 +          dot_command_scan(zLineInput, &dcScanState, CONTINUE_PROMPT_PSTATE);
 +          break;
 +        case Sql:
 +          sql_prescan(zLineInput, &sqScanState, CONTINUE_PROMPT_PSTATE);
 +          break;
 +        default:
 +          break;
 +        }
 +        grow_line_buffer(&zLineAccum, &naAccum, ncLineAcc+ncLineIn+2);
 +        /* Join lines as setup by exam of previous line(s). */
 +        if( cLineEnd!=0 ) zLineAccum[ncLineAcc++] = cLineEnd;
 +#if SHELL_EXTENDED_PARSING
 +        cLineEnd = '\n'; /* reset to default after use */
 +#endif
 +        memcpy(zLineAccum+ncLineAcc, zLineInput, ncLineIn);
 +        iLastLine = ncLineAcc;
 +        ncLineAcc += ncLineIn;
 +        zLineAccum[ncLineAcc] = 0;
 +      } /* end glom another line */
 +    } /* end group collection loop */
 +    /* Here, the group is fully collected or known to be incomplete forever. */
 +    CONTINUE_PROMPT_RESET;
 +    switch( disposition ){
 +    case Dumpable:
 +      echo_group_input(psi, *pzLineUse);
 +#if SHELL_DYNAMIC_EXTENSION
 +      if( inKind==Script && pSS!=0 ) pSS->pMethods->resetCompletionScan(pSS);
 +#endif
 +      break;
 +    case Runnable:
 +      switch( inKind ){
 +      case Sql:
 +        echo_group_input(psi, *pzLineUse);
 +        nErrors += runOneSqlLine(XSS(psi), *pzLineUse,
 +                                 INSOURCE_IS_INTERACTIVE(psi->pInSource),
 +                                 iStartline);
 +        break;
 +      case Cmd: {
 +        DotCmdRC dcr;
 +        echo_group_input(psi, *pzLineUse);
 +        dcr = do_dot_command(*pzLineUse+nLeadWhite, XSS(psi));
 +        nErrors += (dcr & DCR_Error);
 +        dcr &= ~DCR_Error;
 +        if( dcr > termKind ) termKind = dcr;
 +        break;
 +      }
 +#if SHELL_DYNAMIC_EXTENSION
 +      case Script: {
 +        char *zErr = 0;
 +        DotCmdRC dcr;
 +        assert(pSS!=0);
 +        /* Consider: Should echo flag be honored here? */
 +        pSS->pMethods->resetCompletionScan(pSS);
 +        dcr = pSS->pMethods->runScript(pSS, *pzLineUse+nLeadWhite,
 +                                       XSS(psi), &zErr);
 +        if( dcr!=DCR_Ok || zErr!=0 ){
 +          /* Future: Handle errors more informatively and like dot commands. */
 +          nErrors += (dcr!=DCR_Ok);
 +          if( zErr!=0 ){
 +            utf8_printf(STD_ERR, "Error: %s\n", zErr);
 +            sqlite3_free(zErr);
 +          }
 +        }
 +        break;
 +      }
 +#endif
 +      default:
 +        assert(inKind!=Tbd);
 +        break;
 +      }
 +      if( XSS(psi)->shellAbruptExit!=0 ){
 +        termKind = DCR_Exit;
        }
 -      p->bSafeMode = p->bSafeModePersist;
 -      qss = QSS_Start;
 -    }else if( nSql && QSS_PLAINWHITE(qss) ){
 -      echo_group_input(p, zSql);
 -      nSql = 0;
 -      qss = QSS_Start;
 +      break;
 +    case Erroneous:
 +      utf8_printf(STD_ERR, "Error: Input incomplete at line %d of \"%s\"\n",
 +                  psi->pInSource->lineno, psi->pInSource->zSourceSay);
 +#if SHELL_DYNAMIC_EXTENSION
 +      if( inKind==Script && pSS!=0 ) pSS->pMethods->resetCompletionScan(pSS);
 +#endif
 +      ++nErrors;
 +      break;
 +    case Ignore:
 +      break;
 +    default: assert(0);
      }
 +    if( bail_on_error && nErrors>0 && termKind==DCR_Ok ) termKind = DCR_Error;
 +  } /* end group consume/prep/(run, dump or complain) loop */
 +
 +  /* Cleanup and determine return value based on flags and error count. */
 +  free(zLineInput); /* Allocated via malloc() by readline or equivalents. */
 +  sqlite3_free(zLineAccum);
 +
 +  /* Translate DCR_Return because it has been done here, not to propagate
 +   * unless input is from shell invocation argument. */
 +  if( termKind==DCR_Return && psi->pInSource!=&cmdInSource ){
 +    termKind = DCR_Ok;
    }
 -  if( nSql ){
 -    /* This may be incomplete. Let the SQL parser deal with that. */
 -    echo_group_input(p, zSql);
 -    errCnt += runOneSqlLine(p, zSql, p->in, startline);
 -    CONTINUE_PROMPT_RESET;
 -  }
 -  free(zSql);
 -  free(zLine);
 -  --p->inputNesting;
 -  return errCnt>0;
 +  return termKind|(nErrors>0);
  }
  
  /*
@@@ -16106,369 -11941,6 +16153,361 @@@ static void sayAbnormalExit(void)
    if( seenInterrupt ) fprintf(stderr, "Program interrupted.\n");
  }
  
-   ResourceMark buffMark; /* where to grab held array pointer */
 +/* A vector of command strings collected from . */
 +typedef struct CmdArgs {
 +  /* Array is malloc'ed, but not its elements except by wmain()'s futzing. */
 +  char **azCmd;  /* the strings */
 +  int nCmd;      /* how many collected */
-           void *vaz;
-           if( pca->nCmd == 0 ){
-             pca->buffMark = holder_mark();
-             more_holders(1);
-           }
-           vaz = realloc(pca->azCmd, sizeof(pca->azCmd[0])*(pca->nCmd+1));
 +} CmdArgs;
 +/* Data collected during args scanning. */
 +typedef struct ArgsData {
 +  int readStdin;         /* whether stdin will be read */
 +  int nOptsEnd;          /* where -- seen, else argc */
 +  const char *zInitFile; /* specified init file */
 +  const char *zVfs;      /* -vfs command-line option */
 +  short bQuiet;          /* -quiet option */
 +} ArgsData;
 +/*
 +** Perform CLI invocation argument processing.
 +** This code is collected here for convenience, to declutter main()
 +** and to make this processing a little simpler to understand.
 +*/
 +static int scanInvokeArgs(int argc, char **argv, int pass, ShellInState *psi,
 +                          CmdArgs *pca, ArgsData *pad){
 +  int rc = 0;
 +  DotCmdRC drc;
 +  int i;
 +  if( pass==1 ){
 +    for(i=1; i<argc && rc<2; i++){
 +      char *z = argv[i];
 +      if( z[0]!='-' || i>pad->nOptsEnd ){
 +        if( psi->aAuxDb->zDbFilename==0 ){
 +          psi->aAuxDb->zDbFilename = z;
 +        }else{
-           if( pca->nCmd == 0 ) mmem_holder(vaz);
-           else swap_held(pca->buffMark, 0, vaz);
++          void *vaz = realloc(pca->azCmd, sizeof(pca->azCmd[0])*(pca->nCmd+1));
 +          shell_check_oom(vaz);
 +          pca->azCmd = (char**)vaz;
 +          pca->azCmd[pca->nCmd++] = z;
 +          /* Excesss arguments are interpreted as SQL (or dot-commands)
 +          ** and mean that nothing is to be read from stdin. */
 +          pad->readStdin = 0;
 +        }
 +        continue;
 +      }
 +      if( z[1]=='-' ) z++;
 +      if( cli_strcmp(z, "-")==0 ){
 +        pad->nOptsEnd = i;
 +        continue;
 +      }else if( cli_strcmp(z,"-separator")==0
 +       || cli_strcmp(z,"-nullvalue")==0
 +       || cli_strcmp(z,"-newline")==0
 +       || cli_strcmp(z,"-cmd")==0
 +      ){
 +        (void)cmdline_option_value(argc, argv, ++i);
 +        /* Will pickup value on next pass. */
 +      }else if( cli_strcmp(z,"-init")==0 ){
 +        pad->zInitFile = cmdline_option_value(argc, argv, ++i);
 +      }else if( cli_strcmp(z,"-batch")==0 ){
 +        /* Need to check for batch mode here to so we can avoid printing
 +        ** informational messages (like from process_sqliterc) before
 +        ** we do the actual processing of arguments later in a second pass.
 +        */
 +        stdin_is_interactive = 0;
 +      }else if( cli_strcmp(z,"-heap")==0 ){
 +#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5)
 +        const char *zSize;
 +        sqlite3_int64 szHeap;
 +
 +        zSize = cmdline_option_value(argc, argv, ++i);
 +        szHeap = integerValue(zSize);
 +        if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000;
 +        verify_uninitialized();
 +        sqlite3_config(SQLITE_CONFIG_HEAP,malloc((int)szHeap),(int)szHeap, 64);
 +#else
 +        (void)cmdline_option_value(argc, argv, ++i);
 +#endif
 +      }else if( cli_strcmp(z,"-pagecache")==0 ){
 +        sqlite3_int64 n, sz;
 +        void *pvCache = 0;
 +        sz = integerValue(cmdline_option_value(argc,argv,++i));
 +        if( sz>70000 ) sz = 70000;
 +        if( sz<0 ) sz = 0;
 +        n = integerValue(cmdline_option_value(argc,argv,++i));
 +        if( sz>0 && n>0 && 0xffffffffffffLL/sz<n ){
 +          n = 0xffffffffffffLL/sz;
 +        }
 +        verify_uninitialized();
 +        if( n>0 && sz>0 ) pvCache = malloc(n*sz);
 +        shell_check_oom(pvCache);
 +        sqlite3_config(SQLITE_CONFIG_PAGECACHE, pvCache, sz, n);
 +        psi->shellFlgs |= SHFLG_Pagecache;
 +      }else if( cli_strcmp(z,"-lookaside")==0 ){
 +        int n, sz;
 +        sz = (int)integerValue(cmdline_option_value(argc,argv,++i));
 +        if( sz<0 ) sz = 0;
 +        n = (int)integerValue(cmdline_option_value(argc,argv,++i));
 +        if( n<0 ) n = 0;
 +        verify_uninitialized();
 +        sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, n);
 +        if( sz*n==0 ) psi->shellFlgs &= ~SHFLG_Lookaside;
 +      }else if( cli_strcmp(z,"-threadsafe")==0 ){
 +        int n;
 +        n = (int)integerValue(cmdline_option_value(argc,argv,++i));
 +        verify_uninitialized();
 +        switch( n ){
 +           case 0:  sqlite3_config(SQLITE_CONFIG_SINGLETHREAD);  break;
 +           case 2:  sqlite3_config(SQLITE_CONFIG_MULTITHREAD);   break;
 +           default: sqlite3_config(SQLITE_CONFIG_SERIALIZED);    break;
 +        }
 +#ifdef SQLITE_ENABLE_VFSTRACE
 +      }else if( cli_strcmp(z,"-vfstrace")==0 ){
 +        extern int vfstrace_register(
 +           const char *zTraceName,
 +           const char *zOldVfsName,
 +           int (*xOut)(const char*,void*),
 +           void *pOutArg,
 +           int makeDefault
 +        );
 +        vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,STD_ERR,1);
 +#endif
 +#ifdef SQLITE_ENABLE_MULTIPLEX
 +      }else if( cli_strcmp(z,"-multiplex")==0 ){
 +        extern int sqlite3_multiple_initialize(const char*,int);
 +        sqlite3_multiplex_initialize(0, 1);
 +#endif
 +      }else if( cli_strcmp(z,"-mmap")==0 ){
 +        sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
 +        verify_uninitialized();
 +        sqlite3_config(SQLITE_CONFIG_MMAP_SIZE, sz, sz);
 +#ifdef SQLITE_ENABLE_SORTER_REFERENCES
 +      }else if( cli_strcmp(z,"-sorterref")==0 ){
 +        sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
 +        verify_uninitialized();
 +        sqlite3_config(SQLITE_CONFIG_SORTERREF_SIZE, (int)sz);
 +#endif
 +      }else if( cli_strcmp(z,"-vfs")==0 ){
 +        pad->zVfs = cmdline_option_value(argc, argv, ++i);
 +#ifdef SQLITE_HAVE_ZLIB
 +      }else if( cli_strcmp(z,"-zip")==0 ){
 +        psi->openMode = SHELL_OPEN_ZIPFILE;
 +#endif
 +      }else if( cli_strcmp(z,"-append")==0 ){
 +        psi->openMode = SHELL_OPEN_APPENDVFS;
 +#ifndef SQLITE_OMIT_DESERIALIZE
 +      }else if( cli_strcmp(z,"-deserialize")==0 ){
 +        psi->openMode = SHELL_OPEN_DESERIALIZE;
 +      }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){
 +        psi->szMax = integerValue(argv[++i]);
 +#endif
 +      }else if( cli_strcmp(z,"-readonly")==0 ){
 +        psi->openMode = SHELL_OPEN_READONLY;
 +      }else if( cli_strcmp(z,"-nofollow")==0 ){
 +        psi->openFlags = SQLITE_OPEN_NOFOLLOW;
 +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
 +      }else if( cli_strncmp(z, "-A",2)==0 ){
 +        /* All remaining command-line arguments are passed to the ".archive"
 +        ** command, so ignore them */
 +        break;
 +#endif
 +      }else if( cli_strcmp(z, "-memtrace")==0 ){
 +        sqlite3MemTraceActivate(STD_ERR);
 +      }else if( cli_strcmp(z,"-bail")==0 ){
 +        bail_on_error = 1;
 +#if SHELL_EXTENSIONS
 +      }else if( cli_strcmp(z,"-shxopts")==0 ){
 +        psi->bExtendedDotCmds = (u8)integerValue(argv[++i]);
 +#endif
 +      }else if( cli_strcmp(z,"-nonce")==0 ){
 +        free(psi->zNonce);
 +        psi->zNonce = strdup(argv[++i]);
 +        shell_check_oom(psi->zNonce);
 +      }else if( cli_strcmp(z,"-quiet")==0 ){
 +        pad->bQuiet = (int)integerValue(cmdline_option_value(argc,argv,++i));
 +      }else if( cli_strcmp(z,"-unsafe-testing")==0 ){
 +        psi->shellFlgs |= SHFLG_TestingMode;
 +      }else if( cli_strcmp(z,"-safe")==0 ){
 +        /* catch this on the second pass (Unsafe is fine on invocation.) */
 +      }
 +    }
 +  }else if( pass==2 ){
 +    for(i=1; i<argc && rc<2; i++){
 +      char *z = argv[i];
 +      char *zModeSet = 0;
 +      if( z[0]!='-' || i>=pad->nOptsEnd ) continue;
 +      if( z[1]=='-' ){ z++; }
 +      if( cli_strcmp(z,"-init")==0 ){
 +        i++;
 +      }else if( cli_strcmp(z,"-html")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-list")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-quote")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-line")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-column")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-json")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-markdown")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-table")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-box")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-csv")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-ascii")==0 ){
 +        zModeSet = z;
 +      }else if( cli_strcmp(z,"-tabs")==0 ){
 +        zModeSet = z;
 +#ifdef SQLITE_HAVE_ZLIB
 +      }else if( cli_strcmp(z,"-zip")==0 ){
 +        psi->openMode = SHELL_OPEN_ZIPFILE;
 +#endif
 +      }else if( cli_strcmp(z,"-append")==0 ){
 +        psi->openMode = SHELL_OPEN_APPENDVFS;
 +#ifndef SQLITE_OMIT_DESERIALIZE
 +      }else if( cli_strcmp(z,"-deserialize")==0 ){
 +        psi->openMode = SHELL_OPEN_DESERIALIZE;
 +      }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){
 +        psi->szMax = integerValue(argv[++i]);
 +#endif
 +      }else if( cli_strcmp(z,"-readonly")==0 ){
 +        psi->openMode = SHELL_OPEN_READONLY;
 +      }else if( cli_strcmp(z,"-nofollow")==0 ){
 +        psi->openFlags |= SQLITE_OPEN_NOFOLLOW;
 +      }else if( cli_strcmp(z,"-separator")==0 ){
 +        sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator,
 +                         "%s",cmdline_option_value(argc,argv,++i));
 +      }else if( cli_strcmp(z,"-newline")==0 ){
 +        sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator,
 +                         "%s",cmdline_option_value(argc,argv,++i));
 +      }else if( cli_strcmp(z,"-nullvalue")==0 ){
 +        sqlite3_snprintf(sizeof(psi->nullValue), psi->nullValue,
 +                         "%s",cmdline_option_value(argc,argv,++i));
 +      }else if( cli_strcmp(z,"-header")==0 ){
 +        psi->showHeader = 1;
 +        ShellSetFlagI(psi, SHFLG_HeaderSet);
 +      }else if( cli_strcmp(z,"-noheader")==0 ){
 +        psi->showHeader = 0;
 +        ShellSetFlagI(psi, SHFLG_HeaderSet);
 +      }else if( cli_strcmp(z,"-echo")==0 ){
 +        ShellSetFlagI(psi, SHFLG_Echo);
 +      }else if( cli_strcmp(z,"-eqp")==0 ){
 +        psi->autoEQP = AUTOEQP_on;
 +      }else if( cli_strcmp(z,"-eqpfull")==0 ){
 +        psi->autoEQP = AUTOEQP_full;
 +      }else if( cli_strcmp(z,"-stats")==0 ){
 +        psi->statsOn = 1;
 +      }else if( cli_strcmp(z,"-scanstats")==0 ){
 +        psi->scanstatsOn = 1;
 +      }else if( cli_strcmp(z,"-backslash")==0 ){
 +        /* Undocumented command-line option: -backslash
 +        ** Causes C-style backslash escapes to be evaluated in SQL statements
 +        ** prior to sending the SQL into SQLite.  Useful for injecting crazy
 +        ** bytes in the middle of SQL statements for testing and debugging.
 +        */
 +        ShellSetFlagI(psi, SHFLG_Backslash);
 +      }else if( cli_strcmp(z,"-bail")==0 ){
 +        /* No-op.  The bail_on_error flag should already be set. */
 +#if SHELL_EXTENSIONS
 +      }else if( cli_strcmp(z,"-shxopts")==0 ){
 +        i++; /* Handled on first pass. */
 +#endif
 +      }else if( cli_strcmp(z,"-version")==0 ){
 +        fprintf(STD_OUT, "%s %s\n", sqlite3_libversion(), sqlite3_sourceid());
 +        rc = 2;
 +      }else if( cli_strcmp(z,"-interactive")==0 ){
 +        stdin_is_interactive = 1;
 +      }else if( cli_strcmp(z,"-batch")==0 ){
 +        stdin_is_interactive = 0;
 +      }else if( cli_strcmp(z,"-utf8")==0 ){
 +#if SHELL_WIN_UTF8_OPT
 +        console_utf8 = 1;
 +#endif /* SHELL_WIN_UTF8_OPT */
 +      }else if( cli_strcmp(z,"-heap")==0 ){
 +        i++;
 +      }else if( cli_strcmp(z,"-pagecache")==0 ){
 +        i+=2;
 +      }else if( cli_strcmp(z,"-lookaside")==0 ){
 +        i+=2;
 +      }else if( cli_strcmp(z,"-threadsafe")==0 ){
 +        i+=2;
 +      }else if( cli_strcmp(z,"-nonce")==0 ){
 +        i+=2;
 +      }else if( cli_strcmp(z,"-mmap")==0 ){
 +        i++;
 +      }else if( cli_strcmp(z,"-memtrace")==0 ){
 +        i++;
 +#ifdef SQLITE_ENABLE_SORTER_REFERENCES
 +      }else if( cli_strcmp(z,"-sorterref")==0 ){
 +        i++;
 +#endif
 +      }else if( cli_strcmp(z,"-vfs")==0 ){
 +        i++;
 +#ifdef SQLITE_ENABLE_VFSTRACE
 +      }else if( cli_strcmp(z,"-vfstrace")==0 ){
 +        i++;
 +#endif
 +#ifdef SQLITE_ENABLE_MULTIPLEX
 +      }else if( cli_strcmp(z,"-multiplex")==0 ){
 +        i++;
 +#endif
 +      }else if( cli_strcmp(z,"-help")==0 ){
 +        usage(1);
 +      }else if( cli_strcmp(z,"-cmd")==0 ){
 +        /* Run commands that follow -cmd first and separately from commands
 +        ** that simply appear on the command-line.  This seems goofy.  It would
 +        ** be better if all commands ran in the order that they appear.  But
 +        ** we retain the goofy behavior for historical compatibility. */
 +        if( i==argc-1 ) break; /* Pretend specified command is empty. */
 +        set_invocation_cmd(cmdline_option_value(argc,argv,++i));
 +        drc = process_input(psi);
 +        rc = (drc>2)? 2 : drc;
 +        if( rc>0 ){
 +          break;
 +        }
 +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
 +      }else if( cli_strncmp(z, "-A", 2)==0 ){
 +        if( pca->nCmd>0 ){
 +          utf8_printf(STD_ERR, "Error: cannot mix regular SQL or dot-commands"
 +                              " with \"%s\"\n", z);
 +          rc = 1;
 +          break;
 +        }
 +        open_db(XSS(psi), OPEN_DB_ZIPFILE);
 +        if( z[2] ){
 +          argv[i] = &z[2];
 +          drc = arDotCommand(XSS(psi), 1, argv+(i-1), argc-(i-1));
 +        }else{
 +          drc = arDotCommand(XSS(psi), 1, argv+i, argc-i);
 +        }
 +        rc = (drc>2)? 2 : drc;
 +        pad->readStdin = 0;
 +        break;
 +#endif
 +      }else if( cli_strcmp(z,"-safe")==0 ){
 +        psi->bSafeMode = psi->bSafeModeFuture = 1;
 +      }else if( cli_strcmp(z,"-unsafe-testing")==0 ){
 +        /* Acted upon in first pass. */
 +      }else if( cli_strcmp(z,"-quiet")==0 ){
 +        ++i;
 +      }else{
 +        utf8_printf(STD_ERR,"%s: Error: unknown option: %s\n", Argv0, z);
 +        raw_printf(STD_ERR,"Use -help for a list of options.\n");
 +        rc = 2;
 +      }
 +      if( zModeSet!=0 ){
 +        char *azModeCmd[] = { ".mode", zModeSet+1 };
 +        modeCommand(azModeCmd, 2, XSS(psi), 0);
 +        psi->cMode = psi->mode;
 +      }
 +    }
 +  }
 +  return rc;
 +}
 +
  #ifndef SQLITE_SHELL_IS_UTF8
  #  if (defined(_WIN32) || defined(WIN32)) \
     && (defined(_MSC_VER) || (defined(UNICODE) && defined(__GNUC__)))
@@@ -16499,36 -11963,27 +16538,36 @@@ int SQLITE_CDECL SHELL_MAIN(int argc, w
  #ifdef SQLITE_DEBUG
    sqlite3_int64 mem_main_enter = 0;
  #endif
 -  char *zErrMsg = 0;
  #ifdef SQLITE_SHELL_FIDDLE
 -#  define data shellState
 +#  define datai shellStateI
 +#  define datax shellStateX
  #else
 -  ShellState data;
 +  ShellInState datai;
 +  ShellExState datax;
  #endif
 +#if SHELL_DATAIO_EXT
 +  BuiltInFFExporter ffExporter = BI_FF_EXPORTER_INIT( &datai );
 +  BuiltInCMExporter cmExporter = BI_CM_EXPORTER_INIT( &datai );
 +#endif
 +  RIP_STATE(exit_jb);
    const char *zInitFile = 0;
 -  int i;
 +  int bQuiet = 0; /* for testing, to suppress banner and history actions */
 +  int i, aec;
    int rc = 0;
 +  DotCmdRC drc = DCR_Ok;
    int warnInmemoryDb = 0;
-   /* azCmd, nCmd, buffMark */
-   CmdArgs cmdArgs = {0,0,0};
 -  int readStdin = 1;
 -  int nCmd = 0;
++  /* azCmd, nCmd */
++  CmdArgs cmdArgs = {0,0};
 +  /* readStdin, nOptsEnd, zInitFile, zVfs, bQuiet */
 +  ArgsData argsData = { 1, argc, 0,0,0 };
    int nOptsEnd = argc;
 -  char **azCmd = 0;
 -  const char *zVfs = 0;           /* Value of -vfs command-line option */
  #if !SQLITE_SHELL_IS_UTF8
    char **argvToFree = 0;
    int argcToFree = 0;
  #endif
-   ResourceMark entry_mark = holder_mark();
 -  setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
++  main_resource_mark = holder_mark();
  
 +  setvbuf(STD_ERR, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
  #ifdef SQLITE_SHELL_FIDDLE
    stdin_is_interactive = 0;
    stdout_is_console = 1;
    stdin_is_interactive = isatty(0);
    stdout_is_console = isatty(1);
  #endif
++  if( atexit_registered==0 ){
  #if SHELL_WIN_UTF8_OPT
--  atexit(console_restore); /* Needs revision for CLI as library call */
++    atexit(console_restore); /* Needs revision for CLI as library call */
  #endif
--  atexit(sayAbnormalExit);
++    atexit(sayAbnormalExit);
++  }
  #ifdef SQLITE_DEBUG
    mem_main_enter = sqlite3_memory_used();
  #endif
      exit(1);
    }
  #endif
 -  main_init(&data);
 +  main_init(&datai,&datax);
 +#if SHELL_DATAIO_EXT
 +  datai.pFreeformExporter = (ExportHandler*)&ffExporter;
 +  datai.pColumnarExporter = (ExportHandler*)&cmExporter;
 +  datai.pActiveExporter = (ExportHandler*)&ffExporter;
 +#endif
  
 -  /* On Windows, we must translate command-line arguments into UTF-8.
 -  ** The SQLite memory allocator subsystem has to be enabled in order to
 -  ** do this.  But we want to run an sqlite3_shutdown() afterwards so that
 -  ** subsequent sqlite3_config() calls will work.  So copy all results into
 -  ** memory that does not come from the SQLite memory allocator.
 +  /* From here on, within the true clause of this next test, various
 +  ** heap allocations are made which may fail, resulting in an abrupt
 +  ** shell exit. Such an exit happens in 1 of 2 ways: A held resource
 +  ** stack and the call stack are ripped back to this point; or just
 +  ** the held resource stack is ripped back and a process exit occurs.
    */
-   register_exit_ripper(&exit_jb, entry_mark);
++  register_exit_ripper(&exit_jb, main_resource_mark);
 +  if( 0==RIP_TO_HERE(exit_jb) ){
 +
 +    /* On Windows, we must translate command-line arguments into UTF-8.
 +    ** The SQLite memory allocator subsystem has to be enabled in order to
 +    ** do this.  But we want to run an sqlite3_shutdown() afterwards so that
 +    ** subsequent sqlite3_config() calls will work.  So copy all results into
 +    ** memory that does not come from the SQLite memory allocator.
 +    */
  #if !SQLITE_SHELL_IS_UTF8
 -  sqlite3_initialize();
 -  argvToFree = malloc(sizeof(argv[0])*argc*2);
 -  shell_check_oom(argvToFree);
 -  argcToFree = argc;
 -  argv = argvToFree + argc;
 -  for(i=0; i<argc; i++){
 -    char *z = sqlite3_win32_unicode_to_utf8(wargv[i]);
 -    i64 n;
 -    shell_check_oom(z);
 -    n = strlen(z);
 -    argv[i] = malloc( n+1 );
 -    shell_check_oom(argv[i]);
 -    memcpy(argv[i], z, n+1);
 -    argvToFree[i] = argv[i];
 -    sqlite3_free(z);
 -  }
 -  sqlite3_shutdown();
 +    sqlite3_initialize();
 +    argvToFree = malloc(sizeof(argv[0])*argc*2);
 +    shell_check_oom(argvToFree);
 +    argcToFree = argc;
 +    argv = argvToFree + argc;
 +    for(i=0; i<argc; i++){
 +      char *z = sqlite3_win32_unicode_to_utf8(wargv[i]);
 +      i64 n;
 +      shell_check_oom(z);
 +      n = strlen(z);
 +      argv[i] = malloc( n+1 );
 +      shell_check_oom(argv[i]);
 +      memcpy(argv[i], z, n+1);
 +      argvToFree[i] = argv[i];
 +      sqlite3_free(z);
 +    }
 +    sqlite3_shutdown();
  #endif
  
 -  assert( argc>=1 && argv && argv[0] );
 -  Argv0 = argv[0];
 +    assert( argc>=1 && argv && argv[0] );
 +    Argv0 = argv[0];
 +#if SHELL_DYNAMIC_EXTENSION
 +    initStartupDir();
 +    if( isExtendedBasename(Argv0) ){
 +      datai.bExtendedDotCmds = SHELL_ALL_EXTENSIONS;
 +    }
 +#endif
  
  #ifdef SQLITE_SHELL_DBNAME_PROC
 -  {
 -    /* If the SQLITE_SHELL_DBNAME_PROC macro is defined, then it is the name
 -    ** of a C-function that will provide the name of the database file.  Use
 -    ** this compile-time option to embed this shell program in larger
 -    ** applications. */
 -    extern void SQLITE_SHELL_DBNAME_PROC(const char**);
 -    SQLITE_SHELL_DBNAME_PROC(&data.pAuxDb->zDbFilename);
 -    warnInmemoryDb = 0;
 -  }
 +    {
 +      /* If the SQLITE_SHELL_DBNAME_PROC macro is defined, then it is the name
 +      ** of a C-function that will provide the name of the database file.  Use
 +      ** this compile-time option to embed this shell program in larger
 +      ** applications. */
 +      extern void SQLITE_SHELL_DBNAME_PROC(const char**);
 +      SQLITE_SHELL_DBNAME_PROC(&datai.pAuxDb->zDbFilename);
 +      warnInmemoryDb = 0;
 +    }
  #endif
  
 -  /* Do an initial pass through the command-line argument to locate
 -  ** the name of the database file, the name of the initialization file,
 -  ** the size of the alternative malloc heap,
 -  ** and the first command to execute.
 -  */
 +    /* Do an initial pass through the command-line argument to locate
 +    ** the name of the database file, the name of the initialization file,
 +    ** the size of the alternative malloc heap,
 +    ** and the first command to execute.
 +    */
  #ifndef SQLITE_SHELL_FIDDLE
 -  verify_uninitialized();
 -#endif
 -  for(i=1; i<argc; i++){
 -    char *z;
 -    z = argv[i];
 -    if( z[0]!='-' || i>nOptsEnd ){
 -      if( data.aAuxDb->zDbFilename==0 ){
 -        data.aAuxDb->zDbFilename = z;
 -      }else{
 -        /* Excesss arguments are interpreted as SQL (or dot-commands) and
 -        ** mean that nothing is read from stdin */
 -        readStdin = 0;
 -        nCmd++;
 -        azCmd = realloc(azCmd, sizeof(azCmd[0])*nCmd);
 -        shell_check_oom(azCmd);
 -        azCmd[nCmd-1] = z;
 -      }
 -      continue;
 -    }
 -    if( z[1]=='-' ) z++;
 -    if( cli_strcmp(z, "-")==0 ){
 -      nOptsEnd = i;
 -      continue;
 -    }else if( cli_strcmp(z,"-separator")==0
 -     || cli_strcmp(z,"-nullvalue")==0
 -     || cli_strcmp(z,"-newline")==0
 -     || cli_strcmp(z,"-cmd")==0
 -    ){
 -      (void)cmdline_option_value(argc, argv, ++i);
 -    }else if( cli_strcmp(z,"-init")==0 ){
 -      zInitFile = cmdline_option_value(argc, argv, ++i);
 -    }else if( cli_strcmp(z,"-batch")==0 ){
 -      /* Need to check for batch mode here to so we can avoid printing
 -      ** informational messages (like from process_sqliterc) before
 -      ** we do the actual processing of arguments later in a second pass.
 -      */
 -      stdin_is_interactive = 0;
 -    }else if( cli_strcmp(z,"-heap")==0 ){
 -#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5)
 -      const char *zSize;
 -      sqlite3_int64 szHeap;
 -
 -      zSize = cmdline_option_value(argc, argv, ++i);
 -      szHeap = integerValue(zSize);
 -      if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000;
 -      verify_uninitialized();
 -      sqlite3_config(SQLITE_CONFIG_HEAP, malloc((int)szHeap), (int)szHeap, 64);
 -#else
 -      (void)cmdline_option_value(argc, argv, ++i);
 -#endif
 -    }else if( cli_strcmp(z,"-pagecache")==0 ){
 -      sqlite3_int64 n, sz;
 -      sz = integerValue(cmdline_option_value(argc,argv,++i));
 -      if( sz>70000 ) sz = 70000;
 -      if( sz<0 ) sz = 0;
 -      n = integerValue(cmdline_option_value(argc,argv,++i));
 -      if( sz>0 && n>0 && 0xffffffffffffLL/sz<n ){
 -        n = 0xffffffffffffLL/sz;
 -      }
 -      verify_uninitialized();
 -      sqlite3_config(SQLITE_CONFIG_PAGECACHE,
 -                    (n>0 && sz>0) ? malloc(n*sz) : 0, sz, n);
 -      data.shellFlgs |= SHFLG_Pagecache;
 -    }else if( cli_strcmp(z,"-lookaside")==0 ){
 -      int n, sz;
 -      sz = (int)integerValue(cmdline_option_value(argc,argv,++i));
 -      if( sz<0 ) sz = 0;
 -      n = (int)integerValue(cmdline_option_value(argc,argv,++i));
 -      if( n<0 ) n = 0;
 -      verify_uninitialized();
 -      sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, n);
 -      if( sz*n==0 ) data.shellFlgs &= ~SHFLG_Lookaside;
 -    }else if( cli_strcmp(z,"-threadsafe")==0 ){
 -      int n;
 -      n = (int)integerValue(cmdline_option_value(argc,argv,++i));
 -      verify_uninitialized();
 -      switch( n ){
 -         case 0:  sqlite3_config(SQLITE_CONFIG_SINGLETHREAD);  break;
 -         case 2:  sqlite3_config(SQLITE_CONFIG_MULTITHREAD);   break;
 -         default: sqlite3_config(SQLITE_CONFIG_SERIALIZED);    break;
 -      }
 -#ifdef SQLITE_ENABLE_VFSTRACE
 -    }else if( cli_strcmp(z,"-vfstrace")==0 ){
 -      extern int vfstrace_register(
 -         const char *zTraceName,
 -         const char *zOldVfsName,
 -         int (*xOut)(const char*,void*),
 -         void *pOutArg,
 -         int makeDefault
 -      );
 -      vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,stderr,1);
 -#endif
 -#ifdef SQLITE_ENABLE_MULTIPLEX
 -    }else if( cli_strcmp(z,"-multiplex")==0 ){
 -      extern int sqlite3_multiple_initialize(const char*,int);
 -      sqlite3_multiplex_initialize(0, 1);
 -#endif
 -    }else if( cli_strcmp(z,"-mmap")==0 ){
 -      sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
 -      verify_uninitialized();
 -      sqlite3_config(SQLITE_CONFIG_MMAP_SIZE, sz, sz);
 -#if defined(SQLITE_ENABLE_SORTER_REFERENCES)
 -    }else if( cli_strcmp(z,"-sorterref")==0 ){
 -      sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
 -      verify_uninitialized();
 -      sqlite3_config(SQLITE_CONFIG_SORTERREF_SIZE, (int)sz);
 -#endif
 -    }else if( cli_strcmp(z,"-vfs")==0 ){
 -      zVfs = cmdline_option_value(argc, argv, ++i);
 -#ifdef SQLITE_HAVE_ZLIB
 -    }else if( cli_strcmp(z,"-zip")==0 ){
 -      data.openMode = SHELL_OPEN_ZIPFILE;
 -#endif
 -    }else if( cli_strcmp(z,"-append")==0 ){
 -      data.openMode = SHELL_OPEN_APPENDVFS;
 -#ifndef SQLITE_OMIT_DESERIALIZE
 -    }else if( cli_strcmp(z,"-deserialize")==0 ){
 -      data.openMode = SHELL_OPEN_DESERIALIZE;
 -    }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){
 -      data.szMax = integerValue(argv[++i]);
 -#endif
 -    }else if( cli_strcmp(z,"-readonly")==0 ){
 -      data.openMode = SHELL_OPEN_READONLY;
 -    }else if( cli_strcmp(z,"-nofollow")==0 ){
 -      data.openFlags = SQLITE_OPEN_NOFOLLOW;
 -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
 -    }else if( cli_strncmp(z, "-A",2)==0 ){
 -      /* All remaining command-line arguments are passed to the ".archive"
 -      ** command, so ignore them */
 -      break;
 +    verify_uninitialized();
  #endif
 -    }else if( cli_strcmp(z, "-memtrace")==0 ){
 -      sqlite3MemTraceActivate(stderr);
 -    }else if( cli_strcmp(z,"-bail")==0 ){
 -      bail_on_error = 1;
 -    }else if( cli_strcmp(z,"-nonce")==0 ){
 -      free(data.zNonce);
 -      data.zNonce = strdup(argv[++i]);
 -    }else if( cli_strcmp(z,"-unsafe-testing")==0 ){
 -      ShellSetFlag(&data,SHFLG_TestingMode);
 -    }else if( cli_strcmp(z,"-safe")==0 ){
 -      /* no-op - catch this on the second pass */
 -    }
 -  }
 +    i = scanInvokeArgs(argc, argv, 1, &datai, &cmdArgs, &argsData);
  #ifndef SQLITE_SHELL_FIDDLE
 -  verify_uninitialized();
 +    verify_uninitialized();
  #endif
  
 -
  #ifdef SQLITE_SHELL_INIT_PROC
 -  {
 -    /* If the SQLITE_SHELL_INIT_PROC macro is defined, then it is the name
 -    ** of a C-function that will perform initialization actions on SQLite that
 -    ** occur just before or after sqlite3_initialize(). Use this compile-time
 -    ** option to embed this shell program in larger applications. */
 -    extern void SQLITE_SHELL_INIT_PROC(void);
 -    SQLITE_SHELL_INIT_PROC();
 -  }
 +    {
 +      /* If the SQLITE_SHELL_INIT_PROC macro is defined, then it is the name
 +      ** of a C-function that will perform initialization actions on SQLite that
 +      ** occur just before or after sqlite3_initialize(). Use this compile-time
 +      ** option to embed this shell program in larger applications. */
 +      extern void SQLITE_SHELL_INIT_PROC(void);
 +      SQLITE_SHELL_INIT_PROC();
 +    }
  #else
 -  /* All the sqlite3_config() calls have now been made. So it is safe
 -  ** to call sqlite3_initialize() and process any command line -vfs option. */
 -  sqlite3_initialize();
 +    /* All the sqlite3_config() calls have now been made. So it is safe
 +    ** to call sqlite3_initialize() and process any command line -vfs option. */
 +    sqlite3_initialize();
  #endif
  
 -  if( zVfs ){
 -    sqlite3_vfs *pVfs = sqlite3_vfs_find(zVfs);
 -    if( pVfs ){
 -      sqlite3_vfs_register(pVfs, 1);
 -    }else{
 -      utf8_printf(stderr, "no such VFS: \"%s\"\n", zVfs);
 -      exit(1);
 +    /* Create a mutex for thread-safe query execution interruption. */
-     pGlobalDbLock = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
-     atexit(zapGlobalDbLock);
++    if( pGlobalDbLock==0 ){
++      pGlobalDbLock = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+     }
 -  }
++    if( atexit_registered==0 ){
++      atexit(zapGlobalDbLock);
++      ++atexit_registered;
++    }
 +    /* Register the control-C (SIGINT) handler.
 +    ** Make sure we have a valid signal handler early, before anything
 +    ** is done that might take long. */
 +#ifdef SIGINT
 +    signal(SIGINT, interrupt_handler);
 +#elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE)
 +    SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
 +#endif
  
 -  if( data.pAuxDb->zDbFilename==0 ){
 +    if( argsData.zVfs ){
 +      sqlite3_vfs *pVfs = sqlite3_vfs_find(argsData.zVfs);
 +      if( pVfs ){
 +        sqlite3_vfs_register(pVfs, 1);
 +      }else{
 +        utf8_printf(STD_ERR, "no such VFS: \"%s\"\n", argsData.zVfs);
 +        rc = 1;
 +        goto shell_bail;
 +      }
 +    }
 +
 +    if( datai.pAuxDb->zDbFilename==0 ){
  #ifndef SQLITE_OMIT_MEMORYDB
 -    data.pAuxDb->zDbFilename = ":memory:";
 -    warnInmemoryDb = argc==1;
 +      datai.pAuxDb->zDbFilename = ":memory:";
 +      warnInmemoryDb = argc==1;
  #else
 -    utf8_printf(stderr,"%s: Error: no database filename specified\n", Argv0);
 -    return 1;
 +      utf8_printf(STD_ERR,"%s: Error: no database filename specified\n", Argv0);
 +      rc = 1;
 +      goto shell_bail;
  #endif
 -  }
 -  data.out = stdout;
 +    }
 +    datai.out = STD_OUT;
  #ifndef SQLITE_SHELL_FIDDLE
 -  sqlite3_appendvfs_init(0,0,0);
 +    sqlite3_appendvfs_init(0,0,0);
  #endif
  
 -  /* Go ahead and open the database file if it already exists.  If the
 -  ** file does not exist, delay opening it.  This prevents empty database
 -  ** files from being created if a user mistypes the database name argument
 -  ** to the sqlite command-line tool.
 -  */
 -  if( access(data.pAuxDb->zDbFilename, 0)==0 ){
 -    open_db(&data, 0);
 -  }
 +    /* Go ahead and open the database file if it already exists.  If the
 +    ** file does not exist, delay opening it.  This prevents empty database
 +    ** files from being created if a user mistypes the database name argument
 +    ** to the sqlite command-line tool.
 +    */
 +    if( access(datai.pAuxDb->zDbFilename, 0)==0 ){
 +      open_db(&datax, 0);
 +    }
  
 -  /* Process the initialization file if there is one.  If no -init option
 -  ** is given on the command line, look for a file named ~/.sqliterc and
 -  ** try to process it.
 -  */
 -  process_sqliterc(&data,zInitFile);
 +    /* Process the initialization file if there is one.  If no -init option
 +    ** is given on the command line, look for a file named ~/.sqliterc and
 +    ** try to process it, without any quitting or bail-on-error.
 +    */
 +    process_sqliterc(&datai,argsData.zInitFile);
 +
 +    /* Make a second pass through the command-line argument and set
 +    ** options.  This second pass is delayed until after the initialization
 +    ** file is processed so that the command-line arguments will override
 +    ** settings in the initialization file.
 +    */
 +    rc = scanInvokeArgs(argc, argv, 2, &datai, &cmdArgs, &argsData);
 +    if( rc>0 ){
 +      goto shell_bail;
 +    }
  
 -  /* Make a second pass through the command-line argument and set
 -  ** options.  This second pass is delayed until after the initialization
 -  ** file is processed so that the command-line arguments will override
 -  ** settings in the initialization file.
 -  */
 -  for(i=1; i<argc; i++){
 -    char *z = argv[i];
 -    if( z[0]!='-' || i>=nOptsEnd ) continue;
 -    if( z[1]=='-' ){ z++; }
 -    if( cli_strcmp(z,"-init")==0 ){
 -      i++;
 -    }else if( cli_strcmp(z,"-html")==0 ){
 -      data.mode = MODE_Html;
 -    }else if( cli_strcmp(z,"-list")==0 ){
 -      data.mode = MODE_List;
 -    }else if( cli_strcmp(z,"-quote")==0 ){
 -      data.mode = MODE_Quote;
 -      sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Comma);
 -      sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, SEP_Row);
 -    }else if( cli_strcmp(z,"-line")==0 ){
 -      data.mode = MODE_Line;
 -    }else if( cli_strcmp(z,"-column")==0 ){
 -      data.mode = MODE_Column;
 -    }else if( cli_strcmp(z,"-json")==0 ){
 -      data.mode = MODE_Json;
 -    }else if( cli_strcmp(z,"-markdown")==0 ){
 -      data.mode = MODE_Markdown;
 -    }else if( cli_strcmp(z,"-table")==0 ){
 -      data.mode = MODE_Table;
 -    }else if( cli_strcmp(z,"-box")==0 ){
 -      data.mode = MODE_Box;
 -    }else if( cli_strcmp(z,"-csv")==0 ){
 -      data.mode = MODE_Csv;
 -      memcpy(data.colSeparator,",",2);
 -#ifdef SQLITE_HAVE_ZLIB
 -    }else if( cli_strcmp(z,"-zip")==0 ){
 -      data.openMode = SHELL_OPEN_ZIPFILE;
 -#endif
 -    }else if( cli_strcmp(z,"-append")==0 ){
 -      data.openMode = SHELL_OPEN_APPENDVFS;
 -#ifndef SQLITE_OMIT_DESERIALIZE
 -    }else if( cli_strcmp(z,"-deserialize")==0 ){
 -      data.openMode = SHELL_OPEN_DESERIALIZE;
 -    }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){
 -      data.szMax = integerValue(argv[++i]);
 -#endif
 -    }else if( cli_strcmp(z,"-readonly")==0 ){
 -      data.openMode = SHELL_OPEN_READONLY;
 -    }else if( cli_strcmp(z,"-nofollow")==0 ){
 -      data.openFlags |= SQLITE_OPEN_NOFOLLOW;
 -    }else if( cli_strcmp(z,"-ascii")==0 ){
 -      data.mode = MODE_Ascii;
 -      sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Unit);
 -      sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Record);
 -    }else if( cli_strcmp(z,"-tabs")==0 ){
 -      data.mode = MODE_List;
 -      sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Tab);
 -      sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Row);
 -    }else if( cli_strcmp(z,"-separator")==0 ){
 -      sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,
 -                       "%s",cmdline_option_value(argc,argv,++i));
 -    }else if( cli_strcmp(z,"-newline")==0 ){
 -      sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,
 -                       "%s",cmdline_option_value(argc,argv,++i));
 -    }else if( cli_strcmp(z,"-nullvalue")==0 ){
 -      sqlite3_snprintf(sizeof(data.nullValue), data.nullValue,
 -                       "%s",cmdline_option_value(argc,argv,++i));
 -    }else if( cli_strcmp(z,"-header")==0 ){
 -      data.showHeader = 1;
 -      ShellSetFlag(&data, SHFLG_HeaderSet);
 -     }else if( cli_strcmp(z,"-noheader")==0 ){
 -      data.showHeader = 0;
 -      ShellSetFlag(&data, SHFLG_HeaderSet);
 -    }else if( cli_strcmp(z,"-echo")==0 ){
 -      ShellSetFlag(&data, SHFLG_Echo);
 -    }else if( cli_strcmp(z,"-eqp")==0 ){
 -      data.autoEQP = AUTOEQP_on;
 -    }else if( cli_strcmp(z,"-eqpfull")==0 ){
 -      data.autoEQP = AUTOEQP_full;
 -    }else if( cli_strcmp(z,"-stats")==0 ){
 -      data.statsOn = 1;
 -    }else if( cli_strcmp(z,"-scanstats")==0 ){
 -      data.scanstatsOn = 1;
 -    }else if( cli_strcmp(z,"-backslash")==0 ){
 -      /* Undocumented command-line option: -backslash
 -      ** Causes C-style backslash escapes to be evaluated in SQL statements
 -      ** prior to sending the SQL into SQLite.  Useful for injecting
 -      ** crazy bytes in the middle of SQL statements for testing and debugging.
 -      */
 -      ShellSetFlag(&data, SHFLG_Backslash);
 -    }else if( cli_strcmp(z,"-bail")==0 ){
 -      /* No-op.  The bail_on_error flag should already be set. */
 -    }else if( cli_strcmp(z,"-version")==0 ){
 -      printf("%s %s\n", sqlite3_libversion(), sqlite3_sourceid());
 -      return 0;
 -    }else if( cli_strcmp(z,"-interactive")==0 ){
 -      stdin_is_interactive = 1;
 -    }else if( cli_strcmp(z,"-batch")==0 ){
 -      stdin_is_interactive = 0;
 -    }else if( cli_strcmp(z,"-utf8")==0 ){
  #if SHELL_WIN_UTF8_OPT
 -      console_utf8 = 1;
 -#endif /* SHELL_WIN_UTF8_OPT */
 -    }else if( cli_strcmp(z,"-heap")==0 ){
 -      i++;
 -    }else if( cli_strcmp(z,"-pagecache")==0 ){
 -      i+=2;
 -    }else if( cli_strcmp(z,"-lookaside")==0 ){
 -      i+=2;
 -    }else if( cli_strcmp(z,"-threadsafe")==0 ){
 -      i+=2;
 -    }else if( cli_strcmp(z,"-nonce")==0 ){
 -      i += 2;
 -    }else if( cli_strcmp(z,"-mmap")==0 ){
 -      i++;
 -    }else if( cli_strcmp(z,"-memtrace")==0 ){
 -      i++;
 -#ifdef SQLITE_ENABLE_SORTER_REFERENCES
 -    }else if( cli_strcmp(z,"-sorterref")==0 ){
 -      i++;
 -#endif
 -    }else if( cli_strcmp(z,"-vfs")==0 ){
 -      i++;
 -#ifdef SQLITE_ENABLE_VFSTRACE
 -    }else if( cli_strcmp(z,"-vfstrace")==0 ){
 -      i++;
 -#endif
 -#ifdef SQLITE_ENABLE_MULTIPLEX
 -    }else if( cli_strcmp(z,"-multiplex")==0 ){
 -      i++;
 -#endif
 -    }else if( cli_strcmp(z,"-help")==0 ){
 -      usage(1);
 -    }else if( cli_strcmp(z,"-cmd")==0 ){
 -      /* Run commands that follow -cmd first and separately from commands
 -      ** that simply appear on the command-line.  This seems goofy.  It would
 -      ** be better if all commands ran in the order that they appear.  But
 -      ** we retain the goofy behavior for historical compatibility. */
 -      if( i==argc-1 ) break;
 -      z = cmdline_option_value(argc,argv,++i);
 -      if( z[0]=='.' ){
 -        rc = do_meta_command(z, &data);
 -        if( rc && bail_on_error ) return rc==2 ? 0 : rc;
 -      }else{
 -        open_db(&data, 0);
 -        rc = shell_exec(&data, z, &zErrMsg);
 -        if( zErrMsg!=0 ){
 -          utf8_printf(stderr,"Error: %s\n", zErrMsg);
 -          if( bail_on_error ) return rc!=0 ? rc : 1;
 -        }else if( rc!=0 ){
 -          utf8_printf(stderr,"Error: unable to process SQL \"%s\"\n", z);
 -          if( bail_on_error ) return rc;
 -        }
 -      }
 -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
 -    }else if( cli_strncmp(z, "-A", 2)==0 ){
 -      if( nCmd>0 ){
 -        utf8_printf(stderr, "Error: cannot mix regular SQL or dot-commands"
 -                            " with \"%s\"\n", z);
 -        return 1;
 -      }
 -      open_db(&data, OPEN_DB_ZIPFILE);
 -      if( z[2] ){
 -        argv[i] = &z[2];
 -        arDotCommand(&data, 1, argv+(i-1), argc-(i-1));
 -      }else{
 -        arDotCommand(&data, 1, argv+i, argc-i);
 -      }
 -      readStdin = 0;
 -      break;
 -#endif
 -    }else if( cli_strcmp(z,"-safe")==0 ){
 -      data.bSafeMode = data.bSafeModePersist = 1;
 -    }else if( cli_strcmp(z,"-unsafe-testing")==0 ){
 -      /* Acted upon in first pass. */
 +    if( console_utf8 && stdin_is_interactive ){
 +      console_prepare();
      }else{
 -      utf8_printf(stderr,"%s: Error: unknown option: %s\n", Argv0, z);
 -      raw_printf(stderr,"Use -help for a list of options.\n");
 -      return 1;
 +      setBinaryMode(stdin, 0);
 +      console_utf8 = 0;
      }
 -    data.cMode = data.mode;
 -  }
 -#if SHELL_WIN_UTF8_OPT
 -  if( console_utf8 && stdin_is_interactive ){
 -    console_prepare();
 -  }else{
 -    setBinaryMode(stdin, 0);
 -    console_utf8 = 0;
 -  }
  #endif
  
 -  if( !readStdin ){
 -    /* Run all arguments that do not begin with '-' as if they were separate
 -    ** command-line inputs, except for the argToSkip argument which contains
 -    ** the database filename.
 -    */
 -    for(i=0; i<nCmd; i++){
 -      if( azCmd[i][0]=='.' ){
 -        rc = do_meta_command(azCmd[i], &data);
 -        if( rc ){
 -          free(azCmd);
 -          return rc==2 ? 0 : rc;
 -        }
 -      }else{
 -        open_db(&data, 0);
 -        echo_group_input(&data, azCmd[i]);
 -        rc = shell_exec(&data, azCmd[i], &zErrMsg);
 -        if( zErrMsg || rc ){
 -          if( zErrMsg!=0 ){
 -            utf8_printf(stderr,"Error: %s\n", zErrMsg);
 -          }else{
 -            utf8_printf(stderr,"Error: unable to process SQL: %s\n", azCmd[i]);
 -          }
 -          sqlite3_free(zErrMsg);
 -          free(azCmd);
 -          return rc!=0 ? rc : 1;
 -        }
 -      }
 -    }
 -  }else{
 -    /* Run commands received from standard input
 -    */
 -    if( stdin_is_interactive ){
 -      char *zHome;
 -      char *zHistory;
 -      int nHistory;
 -      printf(
 -        "SQLite version %s %.19s\n" /*extra-version-info*/
 -        "Enter \".help\" for usage hints.\n",
 -        sqlite3_libversion(), sqlite3_sourceid()
 -      );
 -      if( warnInmemoryDb ){
 -        printf("Connected to a ");
 -        printBold("transient in-memory database");
 -        printf(".\nUse \".open FILENAME\" to reopen on a "
 -               "persistent database.\n");
 -      }
 -      zHistory = getenv("SQLITE_HISTORY");
 -      if( zHistory ){
 -        zHistory = strdup(zHistory);
 -      }else if( (zHome = find_home_dir(0))!=0 ){
 -        nHistory = strlen30(zHome) + 20;
 -        if( (zHistory = malloc(nHistory))!=0 ){
 -          sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome);
 +    if( !argsData.readStdin ){
 +      /* Run all arguments that are not the DB name or do not begin with '-'
 +      ** as if they were separate command-line inputs. */
 +      for(i=0; i<cmdArgs.nCmd && rc<2; i++){
 +        set_invocation_cmd(cmdArgs.azCmd[i]);
 +        drc = process_input(&datai);
 +        rc = (drc>2)? 2 : drc;
 +        if( rc>0 ){
 +          goto shell_bail;
          }
        }
 -      if( zHistory ){ shell_read_history(zHistory); }
 +    }else{
 +      /* Run commands received from standard input
 +      */
 +      if( stdin_is_interactive ){
 +        char *zHome;
 +        char *zHistory = 0;
 +        if( argsData.bQuiet ){
 +          /* bQuiet is almost like normal interactive, but quieter
 +          ** and avoids history keeping and line editor completions. */
 +          mainPrompt[0] = 0;
 +          continuePrompt[0] = 0;
 +        }else{
 +          fprintf(STD_OUT,
 +            "SQLite version %s %.19s\n" /*extra-version-info*/
 +            "Enter \".help\" for usage hints.\n",
 +            sqlite3_libversion(), sqlite3_sourceid()
 +          );
 +          if( warnInmemoryDb ){
 +            fprintf(STD_OUT, "Connected to a ");
 +            printBold("transient in-memory database");
 +            fprintf(STD_OUT, ".\nUse \".open FILENAME\" to reopen on a "
 +                   "persistent database.\n");
 +          }
 +          zHistory = getenv("SQLITE_HISTORY");
 +          if( zHistory ){
 +            zHistory = strdup(zHistory);
 +          }else if( (zHome = find_home_dir(0))!=0 ){
 +            int nHistory = strlen30(zHome) + 20;
 +            if( (zHistory = malloc(nHistory))!=0 ){
 +              sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome);
 +            }
 +          }
 +          if( zHistory ){ shell_read_history(zHistory); }
  #if HAVE_READLINE || HAVE_EDITLINE
 -      rl_attempted_completion_function = readline_completion;
 +          rl_attempted_completion_function = readline_completion;
  #elif HAVE_LINENOISE
 -      linenoiseSetCompletionCallback(linenoise_completion);
 +          linenoiseSetCompletionCallback(linenoise_completion);
  #endif
 -      data.in = 0;
 -      rc = process_input(&data);
 -      if( zHistory ){
 -        shell_stifle_history(2000);
 -        shell_write_history(zHistory);
 -        free(zHistory);
 +        }
 +        datai.pInSource = &termInSource; /* read from stdin interactively */
 +        drc = process_input(&datai);
 +        rc = (drc>2)? 2 : drc;
 +        if( !bQuiet ){
 +          if( zHistory ){
 +            shell_stifle_history(2000);
 +            shell_write_history(zHistory);
 +            free(zHistory);
 +          }
 +        }
 +      }else{
 +        datai.pInSource = &stdInSource; /* read from stdin without prompts */
 +        drc = process_input(&datai);
 +        rc = (drc>2)? 2 : drc;
        }
 -    }else{
 -      data.in = stdin;
 -      rc = process_input(&data);
      }
-   holder_free(entry_mark);
 +  }else{
 +    /* An abrupt, stack-ripping exit arrives here. */
 +  }
 + shell_bail:
++  /* All users of resource managment should have left its stack as
++  ** it was near the beginning of shell execution. Verify this. */
++  assert(main_resource_mark==holder_mark());
++  if( cmdArgs.azCmd!=0 ){
++    free(cmdArgs.azCmd);
++    cmdArgs.azCmd = 0;
+   }
  #ifndef SQLITE_SHELL_FIDDLE
    /* In WASM mode we have to leave the db state in place so that
 -  ** client code can "push" SQL into it after this call returns. */
 -  free(azCmd);
 -  set_table_name(&data, 0);
 -  if( data.db ){
 -    session_close_all(&data, -1);
 -    close_db(data.db);
 +  ** client code can "push" SQL into it after this call returns.
 +  ** For that build, just bypass freeing all acquired resources.
 +  */
 +  set_table_name(&datax, 0);
 +  if( datax.dbUser ){
 +    session_close_all(&datai, -1);
 +# if SHELL_DYNAMIC_EXTENSION
 +    notify_subscribers(&datai, NK_DbAboutToClose, datax.dbUser);
 +# endif
 +    close_db(datax.dbUser);
    }
- # ifdef SQLITE_DEBUG
-   /* Do this redundantly with atexit() to aid memory leak reporting. */
 -  for(i=0; i<ArraySize(data.aAuxDb); i++){
 -    sqlite3_free(data.aAuxDb[i].zFreeOnClose);
 -    if( data.aAuxDb[i].db ){
 -      session_close_all(&data, i);
 -      close_db(data.aAuxDb[i].db);
++  /* Do this redundantly with atexit() to aid memory leak reporting,
++  ** or if CLI is embedded, to get it done before return. */
 +  sqlite3_mutex_free(pGlobalDbLock);
 +  pGlobalDbLock = 0;
- # endif
 +  for(i=0; i<ArraySize(datai.aAuxDb); i++){
 +    sqlite3_free(datai.aAuxDb[i].zFreeOnClose);
 +    if( datai.aAuxDb[i].db ){
 +      session_close_all(&datai, i);
 +      close_db(datai.aAuxDb[i].db);
      }
    }
    find_home_dir(1);