]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Extend CLI resource manager usage. Improve interface to it for clarity and simplicity.
authorlarrybr <larrybr@noemail.net>
Sat, 13 May 2023 16:54:12 +0000 (16:54 +0000)
committerlarrybr <larrybr@noemail.net>
Sat, 13 May 2023 16:54:12 +0000 (16:54 +0000)
FossilOrigin-Name: fd379f22926d55d52176b34b20e6dda2cd1218adaaed446e4945c38a5efe0fb1

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

diff --cc manifest
index f6ac686c3c9be5b54815101a7b24122ac39827d4,a3ebde84d19e00812ab2ae225fea9b97e5feba96..0cf46cf6477239e5dee525a462de53c1297d10d5
+++ b/manifest
@@@ -1,5 -1,5 +1,5 @@@
- C CLI\scloser\sto\sdoing\sfull\scleanup\son\serror\sexits.\sNeeds\stesting\swith\sOOM\ssimulation.
- D 2023-05-13T07:23:14.350
 -C Mention\sthat\ssqlite3_mutex_free()\saccepts\sNULL\sas\sa\sno-op.
 -D 2023-05-13T15:34:15.805
++C Extend\sCLI\sresource\smanager\susage.\sImprove\sinterface\sto\sit\sfor\sclarity\sand\ssimplicity.
++D 2023-05-13T16:54:12.397
  F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
  F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
  F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@@ -637,14 -633,11 +637,14 @@@ F src/pragma.h e690a356c18e98414d2e870e
  F src/prepare.c 6350675966bd0e7ac3a464af9dbfe26db6f0d4237f4e1f1acdb17b12ad371e6e
  F src/printf.c b9320cdbeca0b336c3f139fd36dd121e4167dd62b35fbe9ccaa9bab44c0af38d
  F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
- F src/resmanage.c 3495deb8d4e7499eec30c1de4c4ce85a610a611f42fd74a24584416f0d1f9aba
- F src/resmanage.h 9f937b7d2334b15f5ed2c3f38d707fec5062b414ecd2483d788a4325855eb3fd
++F src/resmanage.c 3b3a76d32e45471356e83dc59b67f5ea537b3381df09f550c1a9810648769799
++F src/resmanage.h 161405806c22fe80b6aa786a0f8b0962f21cd25c1966c8aaafaf497e563c84d4
  F src/resolve.c 3e53e02ce87c9582bd7e7d22f13f4094a271678d9dc72820fa257a2abb5e4032
  F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
  F src/select.c 12aa3168be4ff175702fe0ebeaf544312be22d275d378a28e7b2fad32d552d36
- F src/shell.c.in d5761d88d2f47ba75c778f0383573f7def7eb6ba4248c301eda09f40606e9c42
 -F src/shell.c.in 1e18312f58d365042036fc9d19dcef416074f783702b168f07814332c2268ee0
++F src/shell.c.in 498b7653702e8a4391fe6c69fcd535069e7eb8033c07a2481cf817eee79882ca
 +F src/shext_linkage.h 27dcf7624df05b2a7a6d367834339a6db3636f3035157f641f7db2ec499f8f6d
- F src/sqlite.h.in 27ca1d4b2eda8feee468af5735182390e8fe4696522751eec0136d17323201ad
+ F src/sqlite.h.in c14a4471fcd897a03631ac7ad3d05505e895e7b6419ec5b96cae9bc4df7a9fc6
  F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
  F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4
  F src/sqliteInt.h 6e08039aa944e874d3878c4f12bbd78cea797c988dad147adc9ffb1c2b179402
@@@ -2080,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 ce25a07950e10e5f0c33f179f9b7d307a73b23ad859b5a97e5c6d7bc9e68b254 f06c16a8b0e7a15ce4f7d99af3376a1bf1bfbfc0fdc048b079418ae74c619d6b
- R 383a924fa91f5b4145d9f04d112422b7
 -P 67001887044da71ab50c07d8345bd950a0a1f9688cf9cd6966312ea163cf6e17
 -R f137c75703c92c2af2a3b7644ec04694
++P 8751f93fa505a514d8ab7eae4f9093310ee60b90046f4632e80858001781cb31 603d9ad5012ca8996783996d7b8cd6a1aabf12b21604a2ccc137f4c2d99427b9
++R 90a259a6a8128da5d9e2c168718fd7cd
  U larrybr
- Z 69b83efe69b378ac6c729ccaf8b045c5
 -Z a68f3963d62b5a0e832809880b122d2b
++Z bcc2e382aa2ea964989bed4339357d06
  # Remove this line to create a well-formed Fossil manifest.
diff --cc manifest.uuid
index 889ced5443ae2ea81c19db03845e16e2992b9eb4,08988c19c46f6da95f6aaf0d251bbc7bc69931cb..17264b230f44b6d89ef27954c1ea919ff71e4a3f
@@@ -1,1 -1,1 +1,1 @@@
- 8751f93fa505a514d8ab7eae4f9093310ee60b90046f4632e80858001781cb31
 -603d9ad5012ca8996783996d7b8cd6a1aabf12b21604a2ccc137f4c2d99427b9
++fd379f22926d55d52176b34b20e6dda2cd1218adaaed446e4945c38a5efe0fb1
diff --cc src/resmanage.c
index f0f79bf39c7068e03b9442621c2564d57c508655,0000000000000000000000000000000000000000..7dd9883de6ae58a77b936c9d6acadbef249d4798
mode 100644,000000..100644
--- /dev/null
@@@ -1,339 -1,0 +1,341 @@@
- static ResourceMark exit_mark = 0;
- #ifndef SHELL_OMIT_LONGJMP
- static jmp_buf *p_exit_ripper = 0;
- #endif
 +/*
 +** 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 */
-   fprintf(stderr, "Auto-freed %d resources.\n", holder_free(exit_mark));
++static RipStackDest *pRipStack = 0;
 +
 +/* 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){
++  RipStackDest *pRSD = pRipStack;
 +  int nFreed;
 +  if( zMoan ){
 +    fprintf(stderr, "Error: Terminating due to %s.\n", zMoan);
 +  }
-   if( p_exit_ripper!=0 ){
-     longjmp(*p_exit_ripper, errCode);
++  fprintf(stderr, "Auto-freed %d resources.\n",
++          holder_free( (pRSD)? pRSD->resDest : 0 ));
++  pRipStack = (pRSD)? pRSD->pPrev : 0;
 +#ifndef SHELL_OMIT_LONGJMP
- #ifndef SHELL_OMIT_LONGJMP
++  if( pRSD!=0 ){
++    longjmp(pRSD->exeDest, 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 );
 +      }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
 +/* 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;
 +}
 +
- void register_exit_ripper(jmp_buf *pjb, ResourceMark rip_mark){
-   exit_mark = rip_mark;
-   p_exit_ripper = pjb;
 +/* Record a resource stack and call stack rip-to position */
- /* 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;
++void register_exit_ripper(RipStackDest *pRSD){
++  assert(pRSD!=0);
++  pRSD->pPrev = pRipStack;
++  pRSD->resDest = holder_mark();
++  pRipStack = pRSD;
 +}
- #endif
++/* Undo register_exit_ripper effect, back to previous state. */
++void forget_exit_ripper(RipStackDest *pRSD){
++  if( pRSD==0 ) pRipStack = 0;
++  else{
++    pRipStack = pRSD->pPrev;
++    pRSD->pPrev = 0;
++  }
 +}
diff --cc src/resmanage.h
index b083d1724e8f2b36412de719218ad3133867977f,0000000000000000000000000000000000000000..4401a553511cc1b5b852540bf017349d8c170b82
mode 100644,000000..100644
--- /dev/null
@@@ -1,165 -1,0 +1,171 @@@
- # 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
 +/*
 +** 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>
- #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.
 +#endif
 +#include <stdio.h>
 +#include <assert.h>
 +
 +#ifdef __cplusplus
 +extern "C" {
 +#endif
 +
 +#include "sqlite3.h"
 +
 +/* Type used for marking/conveying positions within a held-resource stack */
 +typedef unsigned short ResourceMark;
 +typedef unsigned short ResourceCount;
 +
++/* Type used to record a possible succession of recovery destinations */
++typedef struct RipStackDest {
++  struct RipStackDest *pPrev;
++  ResourceMark resDest;
++#ifndef SHELL_OMIT_LONGJMP
++  jmp_buf exeDest;
++#endif
++} RipStackDest;
++#define RIP_STACK_DEST_INIT {0}
++
++/* This macro effects stack mark and potential rip-back, keeping needed
++** details in a RipStackDest object (independent of SHELL_OMIT_LONGJMP.) */
++#ifndef SHELL_OMIT_LONGJMP
++# define RIP_TO_HERE(RSD) (RSD.resDest=holder_mark(), setjmp(RSD.exeDest))
++#else
++# define RIP_TO_HERE(RSD) (RSD.resDest=holder_mark(), 0)
++#endif
++
 +/* 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);
 +
 +/* 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);
 +
- extern void register_exit_ripper(jmp_buf *pjb, ResourceMark rip_mark);
++/* Remember execution and resource stack postion/state. This determines
++** how far these stacks may be stripped should quit_moan(...) be called.
 +*/
- extern void forget_exit_ripper(jmp_buf *pjb);
- #else
- #define register_exit_ripper(jb, rm)
- #define forget_exit_ripper()
- #endif
++extern void register_exit_ripper(RipStackDest *);
 +/* Forget whatever register_exit_ripper() has been recorded. */
++extern void forget_exit_ripper(RipStackDest *);
 +
 +/* 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 fef2bb43a9a131f023ff321d539e803866728b25,ccaed896d92b818254fcb5bfae0f4f8a79b126c9..6cc019333da7055a8554e011e9380488f6612e60
@@@ -16153,361 -11941,6 +16153,374 @@@ static void sayAbnormalExit(void)
    if( seenInterrupt ) fprintf(stderr, "Program interrupted.\n");
  }
  
- /* A vector of command strings collected from . */
++/* A vector of command strings collected from the command line. */
 +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 */
++  u8 bArgsHeld;  /* whether "the strings" are owned by this object */
 +} 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;
++
++static void freeCmdArgs(CmdArgs *pca){
++  int i;
++  if( !pca ) return;
++  if( pca->bArgsHeld ){
++    for( i=0; i<pca->nCmd; ++i ){
++      free(pca->azCmd[i]);
++    }
++  }
++  free(pca->azCmd);
++  pca->azCmd = 0;
++  pca->nCmd = 0;
++}
 +/*
 +** 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{
 +          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__)))
@@@ -16538,36 -11963,27 +16551,34 @@@ 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
-   RIP_STATE(exit_jb);
 +#if SHELL_DATAIO_EXT
 +  BuiltInFFExporter ffExporter = BI_FF_EXPORTER_INIT( &datai );
 +  BuiltInCMExporter cmExporter = BI_CM_EXPORTER_INIT( &datai );
 +#endif
++  RipStackDest mainRipDest = RIP_STACK_DEST_INIT;
    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 */
-   CmdArgs cmdArgs = {0,0};
 -  int readStdin = 1;
 -  int nCmd = 0;
 -  int nOptsEnd = argc;
 -  char **azCmd = 0;
 -  const char *zVfs = 0;           /* Value of -vfs command-line option */
++  /* azCmd, nCmd, bArgsHeld */
++  CmdArgs cmdArgs = {0,0,0};
 +  /* readStdin, nOptsEnd, zInitFile, zVfs, bQuiet */
 +  ArgsData argsData = { 1, argc, 0,0,0 };
-   int nOptsEnd = argc;
  #if !SQLITE_SHELL_IS_UTF8
--  char **argvToFree = 0;
--  int argcToFree = 0;
++  CmdArgs argsUtf8 = {0,0,1};
++  AnyResourceHolder caRH = {&argsUtf8, freeCmdArgs};
  #endif
-   main_resource_mark = holder_mark();
 -  setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
  
 +  setvbuf(STD_ERR, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
  #ifdef SQLITE_SHELL_FIDDLE
    stdin_is_interactive = 0;
    stdout_is_console = 1;
      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, main_resource_mark);
-   if( 0==RIP_TO_HERE(exit_jb) ){
++  register_exit_ripper(&mainRipDest);
++  if( 0==RIP_TO_HERE(mainRipDest) ){
++    main_resource_mark = mainRipDest.resDest;
 +
 +    /* 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;
++    argsUtf8.azCmd = malloc(sizeof(argv[0])*argc);
++    shell_check_oom(argsUtf8.azCmd);
++    argsUtf8.nCmd = 0;
++    any_ref_holder(&caRH);
++    ++main_resource_mark;
 +    for(i=0; i<argc; i++){
 +      char *z = sqlite3_win32_unicode_to_utf8(wargv[i]);
 +      i64 n;
 +      shell_check_oom(z);
++      ++main_resource_mark;
++      sstr_holder(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);
++      argsUtf8.azCmd[i] = malloc( n+1 );
++      if( argsUtf8.azCmd[i] ) ++argsUtf8.nCmd;
++      else shell_out_of_memory();
++      sqlite3_free(pop_holder());
++      --main_resource_mark;
++      memcpy(argsUtf8.azCmd[i], z, n+1);
 +    }
 +    sqlite3_shutdown();
++    argv = argsUtf8.azCmd;
  #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. */
 +    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( 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( data.pAuxDb->zDbFilename==0 ){
 +    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);
      }
 +  }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
      }
    }
    find_home_dir(1);
 -  output_reset(&data);
 -  data.doXdgOpen = 0;
 -  clearTempFile(&data);
 -#if !SQLITE_SHELL_IS_UTF8
 -  for(i=0; i<argcToFree; i++) free(argvToFree[i]);
 -  free(argvToFree);
 -#endif
 -  free(data.colWidth);
 -  free(data.zNonce);
 -  /* Clear the global data structure so that valgrind will detect memory
 -  ** leaks */
 -  memset(&data, 0, sizeof(data));
 -#ifdef SQLITE_DEBUG
 +  output_reset(&datai);
 +  datai.doXdgOpen = 0;
 +  clearTempFile(&datai);
 +  sqlite3_free(datai.zEditor);
 +# if SHELL_DYNAMIC_EXTENSION
 +  notify_subscribers(&datai, NK_DbAboutToClose, datax.dbShell);
 +  /* It is necessary that the shell DB be closed after the user DBs.
 +   * This is because loaded extensions are held by the shell DB and
 +   * are therefor (automatically) unloaded when it is closed. */
 +  notify_subscribers(&datai, NK_ExtensionUnload, datax.dbShell);
 +  /* Forcefully unsubscribe any extension which ignored above or did
 +   * not unsubscribe upon getting above event. */
 +  unsubscribe_extensions(&datai);
 +  /* This must be done before any extensions unload. */
 +  free_all_shext_tracking(&datai);
 +  /* Unload extensions and free the DB used for dealing with them. */
 +  sqlite3_close(datax.dbShell);
 +  /* This notification can only reach statically linked extensions. */
 +  notify_subscribers(&datai, NK_ShutdownImminent, 0);
 +  /* Forcefull unsubscribe static extension event listeners. */
 +  subscribe_events(&datax, 0, &datax, NK_Unsubscribe, 0);
- # endif
- # if !SQLITE_SHELL_IS_UTF8
-   for(i=0; i<argcToFree; i++) free(argvToFree[i]);
-   free(argvToFree);
 +# endif
 +  free(datax.pSpecWidths);
 +  free(datai.zNonce);
 +  for(i=0; i<datai.nSavedModes; ++i) sqlite3_free(datai.pModeStack[i]);
 +# if SHELL_DATAIO_EXT
 +  cmExporter.pMethods->destruct((ExportHandler*)&cmExporter);
 +  ffExporter.pMethods->destruct((ExportHandler*)&ffExporter);
 +# endif
 +  aec = datax.shellAbruptExit;
 +  /* Clear shell state objects so that valgrind detects real memory leaks. */
 +  memset(&datai, 0, sizeof(datai));
 +  memset(&datax, 0, sizeof(datax));
 +# ifdef SQLITE_DEBUG
    if( sqlite3_memory_used()>mem_main_enter ){
      utf8_printf(stderr, "Memory leaked: %u bytes\n",
                  (unsigned int)(sqlite3_memory_used()-mem_main_enter));