Home | History | Annotate | Download | only in src
      1 /*
      2 ** 2010 October 28
      3 **
      4 ** The author disclaims copyright to this source code.  In place of
      5 ** a legal notice, here is a blessing:
      6 **
      7 **    May you do good and not evil.
      8 **    May you find forgiveness for yourself and forgive others.
      9 **    May you share freely, never taking more than you give.
     10 **
     11 *************************************************************************
     12 **
     13 ** This file contains a VFS "shim" - a layer that sits in between the
     14 ** pager and the real VFS.
     15 **
     16 ** This particular shim enforces a multiplex system on DB files.
     17 ** This shim shards/partitions a single DB file into smaller
     18 ** "chunks" such that the total DB file size may exceed the maximum
     19 ** file size of the underlying file system.
     20 **
     21 */
     22 #include "sqlite3.h"
     23 #include <string.h>
     24 #include <assert.h>
     25 #include "test_multiplex.h"
     26 
     27 #ifndef SQLITE_CORE
     28   #define SQLITE_CORE 1  /* Disable the API redefinition in sqlite3ext.h */
     29 #endif
     30 #include "sqlite3ext.h"
     31 
     32 /*
     33 ** These should be defined to be the same as the values in
     34 ** sqliteInt.h.  They are defined seperately here so that
     35 ** the multiplex VFS shim can be built as a loadable
     36 ** module.
     37 */
     38 #define UNUSED_PARAMETER(x) (void)(x)
     39 #define MAX_PAGE_SIZE       0x10000
     40 #define DEFAULT_SECTOR_SIZE 0x1000
     41 
     42 /*
     43 ** For a build without mutexes, no-op the mutex calls.
     44 */
     45 #if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0
     46 #define sqlite3_mutex_alloc(X)    ((sqlite3_mutex*)8)
     47 #define sqlite3_mutex_free(X)
     48 #define sqlite3_mutex_enter(X)
     49 #define sqlite3_mutex_try(X)      SQLITE_OK
     50 #define sqlite3_mutex_leave(X)
     51 #define sqlite3_mutex_held(X)     ((void)(X),1)
     52 #define sqlite3_mutex_notheld(X)  ((void)(X),1)
     53 #endif /* SQLITE_THREADSAFE==0 */
     54 
     55 
     56 /************************ Shim Definitions ******************************/
     57 
     58 #define SQLITE_MULTIPLEX_VFS_NAME "multiplex"
     59 
     60 /* This is the limit on the chunk size.  It may be changed by calling
     61 ** the xFileControl() interface.  It will be rounded up to a
     62 ** multiple of MAX_PAGE_SIZE.  We default it here to 1GB.
     63 */
     64 #define SQLITE_MULTIPLEX_CHUNK_SIZE (MAX_PAGE_SIZE*16384)
     65 
     66 /* Default limit on number of chunks.  Care should be taken
     67 ** so that values for chunks numbers fit in the SQLITE_MULTIPLEX_EXT_FMT
     68 ** format specifier. It may be changed by calling
     69 ** the xFileControl() interface.
     70 */
     71 #define SQLITE_MULTIPLEX_MAX_CHUNKS 32
     72 
     73 /* If SQLITE_MULTIPLEX_EXT_OVWR is defined, the
     74 ** last SQLITE_MULTIPLEX_EXT_SZ characters of the
     75 ** filename will be overwritten, otherwise, the
     76 ** multiplex extension is simply appended to the filename.
     77 ** Ex.  (undefined) test.db -> test.db01
     78 **      (defined)   test.db -> test.01
     79 ** Chunk 0 does not have a modified extension.
     80 */
     81 #define SQLITE_MULTIPLEX_EXT_FMT    "%02d"
     82 #define SQLITE_MULTIPLEX_EXT_SZ     2
     83 
     84 /************************ Object Definitions ******************************/
     85 
     86 /* Forward declaration of all object types */
     87 typedef struct multiplexGroup multiplexGroup;
     88 typedef struct multiplexConn multiplexConn;
     89 
     90 /*
     91 ** A "multiplex group" is a collection of files that collectively
     92 ** makeup a single SQLite DB file.  This allows the size of the DB
     93 ** to exceed the limits imposed by the file system.
     94 **
     95 ** There is an instance of the following object for each defined multiplex
     96 ** group.
     97 */
     98 struct multiplexGroup {
     99   sqlite3_file **pReal;            /* Handles to each chunk */
    100   char *bOpen;                     /* array of bools - 0 if chunk not opened */
    101   char *zName;                     /* Base filename of this group */
    102   int nName;                       /* Length of base filename */
    103   int flags;                       /* Flags used for original opening */
    104   int nChunkSize;                  /* Chunk size used for this group */
    105   int nMaxChunks;                  /* Max number of chunks for this group */
    106   int bEnabled;                    /* TRUE to use Multiplex VFS for this file */
    107   multiplexGroup *pNext, *pPrev;   /* Doubly linked list of all group objects */
    108 };
    109 
    110 /*
    111 ** An instance of the following object represents each open connection
    112 ** to a file that is multiplex'ed.  This object is a
    113 ** subclass of sqlite3_file.  The sqlite3_file object for the underlying
    114 ** VFS is appended to this structure.
    115 */
    116 struct multiplexConn {
    117   sqlite3_file base;              /* Base class - must be first */
    118   multiplexGroup *pGroup;         /* The underlying group of files */
    119 };
    120 
    121 /************************* Global Variables **********************************/
    122 /*
    123 ** All global variables used by this file are containing within the following
    124 ** gMultiplex structure.
    125 */
    126 static struct {
    127   /* The pOrigVfs is the real, original underlying VFS implementation.
    128   ** Most operations pass-through to the real VFS.  This value is read-only
    129   ** during operation.  It is only modified at start-time and thus does not
    130   ** require a mutex.
    131   */
    132   sqlite3_vfs *pOrigVfs;
    133 
    134   /* The sThisVfs is the VFS structure used by this shim.  It is initialized
    135   ** at start-time and thus does not require a mutex
    136   */
    137   sqlite3_vfs sThisVfs;
    138 
    139   /* The sIoMethods defines the methods used by sqlite3_file objects
    140   ** associated with this shim.  It is initialized at start-time and does
    141   ** not require a mutex.
    142   **
    143   ** When the underlying VFS is called to open a file, it might return
    144   ** either a version 1 or a version 2 sqlite3_file object.  This shim
    145   ** has to create a wrapper sqlite3_file of the same version.  Hence
    146   ** there are two I/O method structures, one for version 1 and the other
    147   ** for version 2.
    148   */
    149   sqlite3_io_methods sIoMethodsV1;
    150   sqlite3_io_methods sIoMethodsV2;
    151 
    152   /* True when this shim has been initialized.
    153   */
    154   int isInitialized;
    155 
    156   /* For run-time access any of the other global data structures in this
    157   ** shim, the following mutex must be held.
    158   */
    159   sqlite3_mutex *pMutex;
    160 
    161   /* List of multiplexGroup objects.
    162   */
    163   multiplexGroup *pGroups;
    164 
    165   /* Storage for temp file names.  Allocated during
    166   ** initialization to the max pathname of the underlying VFS.
    167   */
    168   char *zName;
    169 
    170 } gMultiplex;
    171 
    172 /************************* Utility Routines *********************************/
    173 /*
    174 ** Acquire and release the mutex used to serialize access to the
    175 ** list of multiplexGroups.
    176 */
    177 static void multiplexEnter(void){ sqlite3_mutex_enter(gMultiplex.pMutex); }
    178 static void multiplexLeave(void){ sqlite3_mutex_leave(gMultiplex.pMutex); }
    179 
    180 /*
    181 ** Compute a string length that is limited to what can be stored in
    182 ** lower 30 bits of a 32-bit signed integer.
    183 **
    184 ** The value returned will never be negative.  Nor will it ever be greater
    185 ** than the actual length of the string.  For very long strings (greater
    186 ** than 1GiB) the value returned might be less than the true string length.
    187 */
    188 int multiplexStrlen30(const char *z){
    189   const char *z2 = z;
    190   if( z==0 ) return 0;
    191   while( *z2 ){ z2++; }
    192   return 0x3fffffff & (int)(z2 - z);
    193 }
    194 
    195 /* Translate an sqlite3_file* that is really a multiplexGroup* into
    196 ** the sqlite3_file* for the underlying original VFS.
    197 */
    198 static sqlite3_file *multiplexSubOpen(multiplexConn *pConn, int iChunk, int *rc, int *pOutFlags){
    199   multiplexGroup *pGroup = pConn->pGroup;
    200   sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;        /* Real VFS */
    201   if( iChunk<pGroup->nMaxChunks ){
    202     sqlite3_file *pSubOpen = pGroup->pReal[iChunk];    /* Real file descriptor */
    203     if( !pGroup->bOpen[iChunk] ){
    204       memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1);
    205       if( iChunk ){
    206 #ifdef SQLITE_MULTIPLEX_EXT_OVWR
    207         sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, iChunk);
    208 #else
    209         sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName, SQLITE_MULTIPLEX_EXT_FMT, iChunk);
    210 #endif
    211       }
    212       *rc = pOrigVfs->xOpen(pOrigVfs, gMultiplex.zName, pSubOpen, pGroup->flags, pOutFlags);
    213       if( *rc==SQLITE_OK ){
    214         pGroup->bOpen[iChunk] = -1;
    215         return pSubOpen;
    216       }
    217       return NULL;
    218     }
    219     *rc = SQLITE_OK;
    220     return pSubOpen;
    221   }
    222   *rc = SQLITE_FULL;
    223   return NULL;
    224 }
    225 
    226 /*
    227 ** This is the implementation of the multiplex_control() SQL function.
    228 */
    229 static void multiplexControlFunc(
    230   sqlite3_context *context,
    231   int argc,
    232   sqlite3_value **argv
    233 ){
    234   int rc = SQLITE_OK;
    235   sqlite3 *db = sqlite3_context_db_handle(context);
    236   int op;
    237   int iVal;
    238 
    239   if( !db || argc!=2 ){
    240     rc = SQLITE_ERROR;
    241   }else{
    242     /* extract params */
    243     op = sqlite3_value_int(argv[0]);
    244     iVal = sqlite3_value_int(argv[1]);
    245     /* map function op to file_control op */
    246     switch( op ){
    247       case 1:
    248         op = MULTIPLEX_CTRL_ENABLE;
    249         break;
    250       case 2:
    251         op = MULTIPLEX_CTRL_SET_CHUNK_SIZE;
    252         break;
    253       case 3:
    254         op = MULTIPLEX_CTRL_SET_MAX_CHUNKS;
    255         break;
    256       default:
    257         rc = SQLITE_NOTFOUND;
    258         break;
    259     }
    260   }
    261   if( rc==SQLITE_OK ){
    262     rc = sqlite3_file_control(db, 0, op, &iVal);
    263   }
    264   sqlite3_result_error_code(context, rc);
    265 }
    266 
    267 /*
    268 ** This is the entry point to register the auto-extension for the
    269 ** multiplex_control() function.
    270 */
    271 static int multiplexFuncInit(
    272   sqlite3 *db,
    273   char **pzErrMsg,
    274   const sqlite3_api_routines *pApi
    275 ){
    276   int rc;
    277   rc = sqlite3_create_function(db, "multiplex_control", 2, SQLITE_ANY,
    278       0, multiplexControlFunc, 0, 0);
    279   return rc;
    280 }
    281 
    282 /************************* VFS Method Wrappers *****************************/
    283 
    284 /*
    285 ** This is the xOpen method used for the "multiplex" VFS.
    286 **
    287 ** Most of the work is done by the underlying original VFS.  This method
    288 ** simply links the new file into the appropriate multiplex group if it is a
    289 ** file that needs to be tracked.
    290 */
    291 static int multiplexOpen(
    292   sqlite3_vfs *pVfs,         /* The multiplex VFS */
    293   const char *zName,         /* Name of file to be opened */
    294   sqlite3_file *pConn,       /* Fill in this file descriptor */
    295   int flags,                 /* Flags to control the opening */
    296   int *pOutFlags             /* Flags showing results of opening */
    297 ){
    298   int rc;                                        /* Result code */
    299   multiplexConn *pMultiplexOpen;                 /* The new multiplex file descriptor */
    300   multiplexGroup *pGroup;                        /* Corresponding multiplexGroup object */
    301   sqlite3_file *pSubOpen;                        /* Real file descriptor */
    302   sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */
    303   int nName = multiplexStrlen30(zName);
    304   int i;
    305   int sz;
    306 
    307   UNUSED_PARAMETER(pVfs);
    308 
    309   /* We need to create a group structure and manage
    310   ** access to this group of files.
    311   */
    312   multiplexEnter();
    313   pMultiplexOpen = (multiplexConn*)pConn;
    314   /* allocate space for group */
    315   sz = sizeof(multiplexGroup)                                /* multiplexGroup */
    316      + (sizeof(sqlite3_file *)*SQLITE_MULTIPLEX_MAX_CHUNKS)  /* pReal[] */
    317      + (pOrigVfs->szOsFile*SQLITE_MULTIPLEX_MAX_CHUNKS)      /* *pReal */
    318      + SQLITE_MULTIPLEX_MAX_CHUNKS                           /* bOpen[] */
    319      + nName + 1;                                            /* zName */
    320 #ifndef SQLITE_MULTIPLEX_EXT_OVWR
    321   sz += SQLITE_MULTIPLEX_EXT_SZ;
    322   assert(nName+SQLITE_MULTIPLEX_EXT_SZ < pOrigVfs->mxPathname);
    323 #else
    324   assert(nName >= SQLITE_MULTIPLEX_EXT_SZ);
    325   assert(nName < pOrigVfs->mxPathname);
    326 #endif
    327   pGroup = sqlite3_malloc( sz );
    328   if( pGroup==0 ){
    329     rc=SQLITE_NOMEM;
    330   }else{
    331     /* assign pointers to extra space allocated */
    332     char *p = (char *)&pGroup[1];
    333     pMultiplexOpen->pGroup = pGroup;
    334     memset(pGroup, 0, sz);
    335     pGroup->bEnabled = -1;
    336     pGroup->nChunkSize = SQLITE_MULTIPLEX_CHUNK_SIZE;
    337     pGroup->nMaxChunks = SQLITE_MULTIPLEX_MAX_CHUNKS;
    338     pGroup->pReal = (sqlite3_file **)p;
    339     p += (sizeof(sqlite3_file *)*pGroup->nMaxChunks);
    340     for(i=0; i<pGroup->nMaxChunks; i++){
    341       pGroup->pReal[i] = (sqlite3_file *)p;
    342       p += pOrigVfs->szOsFile;
    343     }
    344     /* bOpen[] vals should all be zero from memset above */
    345     pGroup->bOpen = p;
    346     p += pGroup->nMaxChunks;
    347     pGroup->zName = p;
    348     /* save off base filename, name length, and original open flags  */
    349     memcpy(pGroup->zName, zName, nName+1);
    350     pGroup->nName = nName;
    351     pGroup->flags = flags;
    352     pSubOpen = multiplexSubOpen(pMultiplexOpen, 0, &rc, pOutFlags);
    353     if( pSubOpen ){
    354       /* if this file is already larger than chunk size, disable
    355       ** the multiplex feature.
    356       */
    357       sqlite3_int64 sz;
    358       int rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz);
    359       if( (rc2==SQLITE_OK) && (sz>pGroup->nChunkSize) ){
    360         pGroup->bEnabled = 0;
    361       }
    362       if( pSubOpen->pMethods->iVersion==1 ){
    363         pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV1;
    364       }else{
    365         pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV2;
    366       }
    367       /* place this group at the head of our list */
    368       pGroup->pNext = gMultiplex.pGroups;
    369       if( gMultiplex.pGroups ) gMultiplex.pGroups->pPrev = pGroup;
    370       gMultiplex.pGroups = pGroup;
    371     }else{
    372       sqlite3_free(pGroup);
    373     }
    374   }
    375   multiplexLeave();
    376   return rc;
    377 }
    378 
    379 /*
    380 ** This is the xDelete method used for the "multiplex" VFS.
    381 ** It attempts to delete the filename specified, as well
    382 ** as additional files with the SQLITE_MULTIPLEX_EXT_FMT extension.
    383 */
    384 static int multiplexDelete(
    385   sqlite3_vfs *pVfs,         /* The multiplex VFS */
    386   const char *zName,         /* Name of file to delete */
    387   int syncDir
    388 ){
    389   sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */
    390   int rc = SQLITE_OK;
    391   int nName = multiplexStrlen30(zName);
    392   int i;
    393 
    394   UNUSED_PARAMETER(pVfs);
    395 
    396   multiplexEnter();
    397   memcpy(gMultiplex.zName, zName, nName+1);
    398   for(i=0; i<SQLITE_MULTIPLEX_MAX_CHUNKS; i++){
    399     int rc2;
    400     int exists = 0;
    401     if( i ){
    402 #ifdef SQLITE_MULTIPLEX_EXT_OVWR
    403         sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1,
    404             gMultiplex.zName+nName-SQLITE_MULTIPLEX_EXT_SZ,
    405             SQLITE_MULTIPLEX_EXT_FMT, i);
    406 #else
    407         sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1,
    408             gMultiplex.zName+nName,
    409             SQLITE_MULTIPLEX_EXT_FMT, i);
    410 #endif
    411     }
    412     rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName,
    413         SQLITE_ACCESS_EXISTS, &exists);
    414     if( rc2==SQLITE_OK && exists){
    415       /* if it exists, delete it */
    416       rc2 = pOrigVfs->xDelete(pOrigVfs, gMultiplex.zName, syncDir);
    417       if( rc2!=SQLITE_OK ) rc = rc2;
    418     }else{
    419       /* stop at first "gap" */
    420       break;
    421     }
    422   }
    423   multiplexLeave();
    424   return rc;
    425 }
    426 
    427 static int multiplexAccess(sqlite3_vfs *a, const char *b, int c, int *d){
    428   return gMultiplex.pOrigVfs->xAccess(gMultiplex.pOrigVfs, b, c, d);
    429 }
    430 static int multiplexFullPathname(sqlite3_vfs *a, const char *b, int c, char *d){
    431   return gMultiplex.pOrigVfs->xFullPathname(gMultiplex.pOrigVfs, b, c, d);
    432 }
    433 static void *multiplexDlOpen(sqlite3_vfs *a, const char *b){
    434   return gMultiplex.pOrigVfs->xDlOpen(gMultiplex.pOrigVfs, b);
    435 }
    436 static void multiplexDlError(sqlite3_vfs *a, int b, char *c){
    437   gMultiplex.pOrigVfs->xDlError(gMultiplex.pOrigVfs, b, c);
    438 }
    439 static void (*multiplexDlSym(sqlite3_vfs *a, void *b, const char *c))(void){
    440   return gMultiplex.pOrigVfs->xDlSym(gMultiplex.pOrigVfs, b, c);
    441 }
    442 static void multiplexDlClose(sqlite3_vfs *a, void *b){
    443   gMultiplex.pOrigVfs->xDlClose(gMultiplex.pOrigVfs, b);
    444 }
    445 static int multiplexRandomness(sqlite3_vfs *a, int b, char *c){
    446   return gMultiplex.pOrigVfs->xRandomness(gMultiplex.pOrigVfs, b, c);
    447 }
    448 static int multiplexSleep(sqlite3_vfs *a, int b){
    449   return gMultiplex.pOrigVfs->xSleep(gMultiplex.pOrigVfs, b);
    450 }
    451 static int multiplexCurrentTime(sqlite3_vfs *a, double *b){
    452   return gMultiplex.pOrigVfs->xCurrentTime(gMultiplex.pOrigVfs, b);
    453 }
    454 static int multiplexGetLastError(sqlite3_vfs *a, int b, char *c){
    455   return gMultiplex.pOrigVfs->xGetLastError(gMultiplex.pOrigVfs, b, c);
    456 }
    457 static int multiplexCurrentTimeInt64(sqlite3_vfs *a, sqlite3_int64 *b){
    458   return gMultiplex.pOrigVfs->xCurrentTimeInt64(gMultiplex.pOrigVfs, b);
    459 }
    460 
    461 /************************ I/O Method Wrappers *******************************/
    462 
    463 /* xClose requests get passed through to the original VFS.
    464 ** We loop over all open chunk handles and close them.
    465 ** The group structure for this file is unlinked from
    466 ** our list of groups and freed.
    467 */
    468 static int multiplexClose(sqlite3_file *pConn){
    469   multiplexConn *p = (multiplexConn*)pConn;
    470   multiplexGroup *pGroup = p->pGroup;
    471   int rc = SQLITE_OK;
    472   int i;
    473   multiplexEnter();
    474   /* close any open handles */
    475   for(i=0; i<pGroup->nMaxChunks; i++){
    476     if( pGroup->bOpen[i] ){
    477       sqlite3_file *pSubOpen = pGroup->pReal[i];
    478       int rc2 = pSubOpen->pMethods->xClose(pSubOpen);
    479       if( rc2!=SQLITE_OK ) rc = rc2;
    480       pGroup->bOpen[i] = 0;
    481     }
    482   }
    483   /* remove from linked list */
    484   if( pGroup->pNext ) pGroup->pNext->pPrev = pGroup->pPrev;
    485   if( pGroup->pPrev ){
    486     pGroup->pPrev->pNext = pGroup->pNext;
    487   }else{
    488     gMultiplex.pGroups = pGroup->pNext;
    489   }
    490   sqlite3_free(pGroup);
    491   multiplexLeave();
    492   return rc;
    493 }
    494 
    495 /* Pass xRead requests thru to the original VFS after
    496 ** determining the correct chunk to operate on.
    497 ** Break up reads across chunk boundaries.
    498 */
    499 static int multiplexRead(
    500   sqlite3_file *pConn,
    501   void *pBuf,
    502   int iAmt,
    503   sqlite3_int64 iOfst
    504 ){
    505   multiplexConn *p = (multiplexConn*)pConn;
    506   multiplexGroup *pGroup = p->pGroup;
    507   int rc = SQLITE_OK;
    508   multiplexEnter();
    509   if( !pGroup->bEnabled ){
    510     sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
    511     rc = ( !pSubOpen ) ? SQLITE_IOERR_READ : pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst);
    512   }else{
    513     while( iAmt > 0 ){
    514       int i = (int)(iOfst / pGroup->nChunkSize);
    515       sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL);
    516       if( pSubOpen ){
    517         int extra = ((int)(iOfst % pGroup->nChunkSize) + iAmt) - pGroup->nChunkSize;
    518         if( extra<0 ) extra = 0;
    519         iAmt -= extra;
    520         rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst % pGroup->nChunkSize);
    521         if( rc!=SQLITE_OK ) break;
    522         pBuf = (char *)pBuf + iAmt;
    523         iOfst += iAmt;
    524         iAmt = extra;
    525       }else{
    526         rc = SQLITE_IOERR_READ;
    527         break;
    528       }
    529     }
    530   }
    531   multiplexLeave();
    532   return rc;
    533 }
    534 
    535 /* Pass xWrite requests thru to the original VFS after
    536 ** determining the correct chunk to operate on.
    537 ** Break up writes across chunk boundaries.
    538 */
    539 static int multiplexWrite(
    540   sqlite3_file *pConn,
    541   const void *pBuf,
    542   int iAmt,
    543   sqlite3_int64 iOfst
    544 ){
    545   multiplexConn *p = (multiplexConn*)pConn;
    546   multiplexGroup *pGroup = p->pGroup;
    547   int rc = SQLITE_OK;
    548   multiplexEnter();
    549   if( !pGroup->bEnabled ){
    550     sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
    551     rc = ( !pSubOpen ) ? SQLITE_IOERR_WRITE : pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst);
    552   }else{
    553     while( iAmt > 0 ){
    554       int i = (int)(iOfst / pGroup->nChunkSize);
    555       sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL);
    556       if( pSubOpen ){
    557         int extra = ((int)(iOfst % pGroup->nChunkSize) + iAmt) - pGroup->nChunkSize;
    558         if( extra<0 ) extra = 0;
    559         iAmt -= extra;
    560         rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst % pGroup->nChunkSize);
    561         if( rc!=SQLITE_OK ) break;
    562         pBuf = (char *)pBuf + iAmt;
    563         iOfst += iAmt;
    564         iAmt = extra;
    565       }else{
    566         rc = SQLITE_IOERR_WRITE;
    567         break;
    568       }
    569     }
    570   }
    571   multiplexLeave();
    572   return rc;
    573 }
    574 
    575 /* Pass xTruncate requests thru to the original VFS after
    576 ** determining the correct chunk to operate on.  Delete any
    577 ** chunks above the truncate mark.
    578 */
    579 static int multiplexTruncate(sqlite3_file *pConn, sqlite3_int64 size){
    580   multiplexConn *p = (multiplexConn*)pConn;
    581   multiplexGroup *pGroup = p->pGroup;
    582   int rc = SQLITE_OK;
    583   multiplexEnter();
    584   if( !pGroup->bEnabled ){
    585     sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
    586     rc = ( !pSubOpen ) ? SQLITE_IOERR_TRUNCATE : pSubOpen->pMethods->xTruncate(pSubOpen, size);
    587   }else{
    588     int rc2;
    589     int i;
    590     sqlite3_file *pSubOpen;
    591     sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */
    592     memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1);
    593     /* delete the chunks above the truncate limit */
    594     for(i=(int)(size / pGroup->nChunkSize)+1; i<pGroup->nMaxChunks; i++){
    595       /* close any open chunks before deleting them */
    596       if( pGroup->bOpen[i] ){
    597         pSubOpen = pGroup->pReal[i];
    598         rc2 = pSubOpen->pMethods->xClose(pSubOpen);
    599         if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE;
    600         pGroup->bOpen[i] = 0;
    601       }
    602 #ifdef SQLITE_MULTIPLEX_EXT_OVWR
    603       sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1,
    604           gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ,
    605           SQLITE_MULTIPLEX_EXT_FMT, i);
    606 #else
    607       sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1,
    608           gMultiplex.zName+pGroup->nName,
    609           SQLITE_MULTIPLEX_EXT_FMT, i);
    610 #endif
    611       rc2 = pOrigVfs->xDelete(pOrigVfs, gMultiplex.zName, 0);
    612       if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE;
    613     }
    614     pSubOpen = multiplexSubOpen(p, (int)(size / pGroup->nChunkSize), &rc2, NULL);
    615     if( pSubOpen ){
    616       rc2 = pSubOpen->pMethods->xTruncate(pSubOpen, size % pGroup->nChunkSize);
    617       if( rc2!=SQLITE_OK ) rc = rc2;
    618     }else{
    619       rc = SQLITE_IOERR_TRUNCATE;
    620     }
    621   }
    622   multiplexLeave();
    623   return rc;
    624 }
    625 
    626 /* Pass xSync requests through to the original VFS without change
    627 */
    628 static int multiplexSync(sqlite3_file *pConn, int flags){
    629   multiplexConn *p = (multiplexConn*)pConn;
    630   multiplexGroup *pGroup = p->pGroup;
    631   int rc = SQLITE_OK;
    632   int i;
    633   multiplexEnter();
    634   for(i=0; i<pGroup->nMaxChunks; i++){
    635     /* if we don't have it open, we don't need to sync it */
    636     if( pGroup->bOpen[i] ){
    637       sqlite3_file *pSubOpen = pGroup->pReal[i];
    638       int rc2 = pSubOpen->pMethods->xSync(pSubOpen, flags);
    639       if( rc2!=SQLITE_OK ) rc = rc2;
    640     }
    641   }
    642   multiplexLeave();
    643   return rc;
    644 }
    645 
    646 /* Pass xFileSize requests through to the original VFS.
    647 ** Aggregate the size of all the chunks before returning.
    648 */
    649 static int multiplexFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){
    650   multiplexConn *p = (multiplexConn*)pConn;
    651   multiplexGroup *pGroup = p->pGroup;
    652   int rc = SQLITE_OK;
    653   int rc2;
    654   int i;
    655   multiplexEnter();
    656   if( !pGroup->bEnabled ){
    657     sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
    658     rc = ( !pSubOpen ) ? SQLITE_IOERR_FSTAT : pSubOpen->pMethods->xFileSize(pSubOpen, pSize);
    659   }else{
    660     *pSize = 0;
    661     for(i=0; i<pGroup->nMaxChunks; i++){
    662       sqlite3_file *pSubOpen = NULL;
    663       /* if not opened already, check to see if the chunk exists */
    664       if( pGroup->bOpen[i] ){
    665         pSubOpen = pGroup->pReal[i];
    666       }else{
    667         sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */
    668         int exists = 0;
    669         memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1);
    670         if( i ){
    671 #ifdef SQLITE_MULTIPLEX_EXT_OVWR
    672           sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1,
    673               gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ,
    674               SQLITE_MULTIPLEX_EXT_FMT, i);
    675 #else
    676           sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1,
    677               gMultiplex.zName+pGroup->nName,
    678               SQLITE_MULTIPLEX_EXT_FMT, i);
    679 #endif
    680         }
    681         rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName,
    682             SQLITE_ACCESS_EXISTS, &exists);
    683         if( rc2==SQLITE_OK && exists){
    684           /* if it exists, open it */
    685           pSubOpen = multiplexSubOpen(p, i, &rc, NULL);
    686         }else{
    687           /* stop at first "gap" */
    688           break;
    689         }
    690       }
    691       if( pSubOpen ){
    692         sqlite3_int64 sz;
    693         rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz);
    694         if( rc2!=SQLITE_OK ){
    695           rc = rc2;
    696         }else{
    697           if( sz>pGroup->nChunkSize ){
    698             rc = SQLITE_IOERR_FSTAT;
    699           }
    700           *pSize += sz;
    701         }
    702       }else{
    703         break;
    704       }
    705     }
    706   }
    707   multiplexLeave();
    708   return rc;
    709 }
    710 
    711 /* Pass xLock requests through to the original VFS unchanged.
    712 */
    713 static int multiplexLock(sqlite3_file *pConn, int lock){
    714   multiplexConn *p = (multiplexConn*)pConn;
    715   int rc;
    716   sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
    717   if( pSubOpen ){
    718     return pSubOpen->pMethods->xLock(pSubOpen, lock);
    719   }
    720   return SQLITE_BUSY;
    721 }
    722 
    723 /* Pass xUnlock requests through to the original VFS unchanged.
    724 */
    725 static int multiplexUnlock(sqlite3_file *pConn, int lock){
    726   multiplexConn *p = (multiplexConn*)pConn;
    727   int rc;
    728   sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
    729   if( pSubOpen ){
    730     return pSubOpen->pMethods->xUnlock(pSubOpen, lock);
    731   }
    732   return SQLITE_IOERR_UNLOCK;
    733 }
    734 
    735 /* Pass xCheckReservedLock requests through to the original VFS unchanged.
    736 */
    737 static int multiplexCheckReservedLock(sqlite3_file *pConn, int *pResOut){
    738   multiplexConn *p = (multiplexConn*)pConn;
    739   int rc;
    740   sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
    741   if( pSubOpen ){
    742     return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut);
    743   }
    744   return SQLITE_IOERR_CHECKRESERVEDLOCK;
    745 }
    746 
    747 /* Pass xFileControl requests through to the original VFS unchanged,
    748 ** except for any MULTIPLEX_CTRL_* requests here.
    749 */
    750 static int multiplexFileControl(sqlite3_file *pConn, int op, void *pArg){
    751   multiplexConn *p = (multiplexConn*)pConn;
    752   multiplexGroup *pGroup = p->pGroup;
    753   int rc = SQLITE_ERROR;
    754   sqlite3_file *pSubOpen;
    755 
    756   if( !gMultiplex.isInitialized ) return SQLITE_MISUSE;
    757   switch( op ){
    758     case MULTIPLEX_CTRL_ENABLE:
    759       if( pArg ) {
    760         int bEnabled = *(int *)pArg;
    761         pGroup->bEnabled = bEnabled;
    762         rc = SQLITE_OK;
    763       }
    764       break;
    765     case MULTIPLEX_CTRL_SET_CHUNK_SIZE:
    766       if( pArg ) {
    767         int nChunkSize = *(int *)pArg;
    768         if( nChunkSize<1 ){
    769           rc = SQLITE_MISUSE;
    770         }else{
    771           /* Round up to nearest multiple of MAX_PAGE_SIZE. */
    772           nChunkSize = (nChunkSize + (MAX_PAGE_SIZE-1));
    773           nChunkSize &= ~(MAX_PAGE_SIZE-1);
    774           pGroup->nChunkSize = nChunkSize;
    775           rc = SQLITE_OK;
    776         }
    777       }
    778       break;
    779     case MULTIPLEX_CTRL_SET_MAX_CHUNKS:
    780       if( pArg ) {
    781         int nMaxChunks = *(int *)pArg;
    782         if(( nMaxChunks<1 ) || ( nMaxChunks>SQLITE_MULTIPLEX_MAX_CHUNKS )){
    783           rc = SQLITE_MISUSE;
    784         }else{
    785           pGroup->nMaxChunks = nMaxChunks;
    786           rc = SQLITE_OK;
    787         }
    788       }
    789       break;
    790     case SQLITE_FCNTL_SIZE_HINT:
    791     case SQLITE_FCNTL_CHUNK_SIZE:
    792       /* no-op these */
    793       rc = SQLITE_OK;
    794       break;
    795     default:
    796       pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
    797       if( pSubOpen ){
    798         rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg);
    799       }
    800       break;
    801   }
    802   return rc;
    803 }
    804 
    805 /* Pass xSectorSize requests through to the original VFS unchanged.
    806 */
    807 static int multiplexSectorSize(sqlite3_file *pConn){
    808   multiplexConn *p = (multiplexConn*)pConn;
    809   int rc;
    810   sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
    811   if( pSubOpen ){
    812     return pSubOpen->pMethods->xSectorSize(pSubOpen);
    813   }
    814   return DEFAULT_SECTOR_SIZE;
    815 }
    816 
    817 /* Pass xDeviceCharacteristics requests through to the original VFS unchanged.
    818 */
    819 static int multiplexDeviceCharacteristics(sqlite3_file *pConn){
    820   multiplexConn *p = (multiplexConn*)pConn;
    821   int rc;
    822   sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
    823   if( pSubOpen ){
    824     return pSubOpen->pMethods->xDeviceCharacteristics(pSubOpen);
    825   }
    826   return 0;
    827 }
    828 
    829 /* Pass xShmMap requests through to the original VFS unchanged.
    830 */
    831 static int multiplexShmMap(
    832   sqlite3_file *pConn,            /* Handle open on database file */
    833   int iRegion,                    /* Region to retrieve */
    834   int szRegion,                   /* Size of regions */
    835   int bExtend,                    /* True to extend file if necessary */
    836   void volatile **pp              /* OUT: Mapped memory */
    837 ){
    838   multiplexConn *p = (multiplexConn*)pConn;
    839   int rc;
    840   sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
    841   if( pSubOpen ){
    842     return pSubOpen->pMethods->xShmMap(pSubOpen, iRegion, szRegion, bExtend, pp);
    843   }
    844   return SQLITE_IOERR;
    845 }
    846 
    847 /* Pass xShmLock requests through to the original VFS unchanged.
    848 */
    849 static int multiplexShmLock(
    850   sqlite3_file *pConn,       /* Database file holding the shared memory */
    851   int ofst,                  /* First lock to acquire or release */
    852   int n,                     /* Number of locks to acquire or release */
    853   int flags                  /* What to do with the lock */
    854 ){
    855   multiplexConn *p = (multiplexConn*)pConn;
    856   int rc;
    857   sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
    858   if( pSubOpen ){
    859     return pSubOpen->pMethods->xShmLock(pSubOpen, ofst, n, flags);
    860   }
    861   return SQLITE_BUSY;
    862 }
    863 
    864 /* Pass xShmBarrier requests through to the original VFS unchanged.
    865 */
    866 static void multiplexShmBarrier(sqlite3_file *pConn){
    867   multiplexConn *p = (multiplexConn*)pConn;
    868   int rc;
    869   sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
    870   if( pSubOpen ){
    871     pSubOpen->pMethods->xShmBarrier(pSubOpen);
    872   }
    873 }
    874 
    875 /* Pass xShmUnmap requests through to the original VFS unchanged.
    876 */
    877 static int multiplexShmUnmap(sqlite3_file *pConn, int deleteFlag){
    878   multiplexConn *p = (multiplexConn*)pConn;
    879   int rc;
    880   sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
    881   if( pSubOpen ){
    882     return pSubOpen->pMethods->xShmUnmap(pSubOpen, deleteFlag);
    883   }
    884   return SQLITE_OK;
    885 }
    886 
    887 /************************** Public Interfaces *****************************/
    888 /*
    889 ** CAPI: Initialize the multiplex VFS shim - sqlite3_multiplex_initialize()
    890 **
    891 ** Use the VFS named zOrigVfsName as the VFS that does the actual work.
    892 ** Use the default if zOrigVfsName==NULL.
    893 **
    894 ** The multiplex VFS shim is named "multiplex".  It will become the default
    895 ** VFS if makeDefault is non-zero.
    896 **
    897 ** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once
    898 ** during start-up.
    899 */
    900 int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault){
    901   sqlite3_vfs *pOrigVfs;
    902   if( gMultiplex.isInitialized ) return SQLITE_MISUSE;
    903   pOrigVfs = sqlite3_vfs_find(zOrigVfsName);
    904   if( pOrigVfs==0 ) return SQLITE_ERROR;
    905   assert( pOrigVfs!=&gMultiplex.sThisVfs );
    906   gMultiplex.pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
    907   if( !gMultiplex.pMutex ){
    908     return SQLITE_NOMEM;
    909   }
    910   gMultiplex.zName = sqlite3_malloc(pOrigVfs->mxPathname);
    911   if( !gMultiplex.zName ){
    912     sqlite3_mutex_free(gMultiplex.pMutex);
    913     return SQLITE_NOMEM;
    914   }
    915   gMultiplex.pGroups = NULL;
    916   gMultiplex.isInitialized = 1;
    917   gMultiplex.pOrigVfs = pOrigVfs;
    918   gMultiplex.sThisVfs = *pOrigVfs;
    919   gMultiplex.sThisVfs.szOsFile += sizeof(multiplexConn);
    920   gMultiplex.sThisVfs.zName = SQLITE_MULTIPLEX_VFS_NAME;
    921   gMultiplex.sThisVfs.xOpen = multiplexOpen;
    922   gMultiplex.sThisVfs.xDelete = multiplexDelete;
    923   gMultiplex.sThisVfs.xAccess = multiplexAccess;
    924   gMultiplex.sThisVfs.xFullPathname = multiplexFullPathname;
    925   gMultiplex.sThisVfs.xDlOpen = multiplexDlOpen;
    926   gMultiplex.sThisVfs.xDlError = multiplexDlError;
    927   gMultiplex.sThisVfs.xDlSym = multiplexDlSym;
    928   gMultiplex.sThisVfs.xDlClose = multiplexDlClose;
    929   gMultiplex.sThisVfs.xRandomness = multiplexRandomness;
    930   gMultiplex.sThisVfs.xSleep = multiplexSleep;
    931   gMultiplex.sThisVfs.xCurrentTime = multiplexCurrentTime;
    932   gMultiplex.sThisVfs.xGetLastError = multiplexGetLastError;
    933   gMultiplex.sThisVfs.xCurrentTimeInt64 = multiplexCurrentTimeInt64;
    934 
    935   gMultiplex.sIoMethodsV1.iVersion = 1;
    936   gMultiplex.sIoMethodsV1.xClose = multiplexClose;
    937   gMultiplex.sIoMethodsV1.xRead = multiplexRead;
    938   gMultiplex.sIoMethodsV1.xWrite = multiplexWrite;
    939   gMultiplex.sIoMethodsV1.xTruncate = multiplexTruncate;
    940   gMultiplex.sIoMethodsV1.xSync = multiplexSync;
    941   gMultiplex.sIoMethodsV1.xFileSize = multiplexFileSize;
    942   gMultiplex.sIoMethodsV1.xLock = multiplexLock;
    943   gMultiplex.sIoMethodsV1.xUnlock = multiplexUnlock;
    944   gMultiplex.sIoMethodsV1.xCheckReservedLock = multiplexCheckReservedLock;
    945   gMultiplex.sIoMethodsV1.xFileControl = multiplexFileControl;
    946   gMultiplex.sIoMethodsV1.xSectorSize = multiplexSectorSize;
    947   gMultiplex.sIoMethodsV1.xDeviceCharacteristics = multiplexDeviceCharacteristics;
    948   gMultiplex.sIoMethodsV2 = gMultiplex.sIoMethodsV1;
    949   gMultiplex.sIoMethodsV2.iVersion = 2;
    950   gMultiplex.sIoMethodsV2.xShmMap = multiplexShmMap;
    951   gMultiplex.sIoMethodsV2.xShmLock = multiplexShmLock;
    952   gMultiplex.sIoMethodsV2.xShmBarrier = multiplexShmBarrier;
    953   gMultiplex.sIoMethodsV2.xShmUnmap = multiplexShmUnmap;
    954   sqlite3_vfs_register(&gMultiplex.sThisVfs, makeDefault);
    955 
    956   sqlite3_auto_extension((void*)multiplexFuncInit);
    957 
    958   return SQLITE_OK;
    959 }
    960 
    961 /*
    962 ** CAPI: Shutdown the multiplex system - sqlite3_multiplex_shutdown()
    963 **
    964 ** All SQLite database connections must be closed before calling this
    965 ** routine.
    966 **
    967 ** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once while
    968 ** shutting down in order to free all remaining multiplex groups.
    969 */
    970 int sqlite3_multiplex_shutdown(void){
    971   if( gMultiplex.isInitialized==0 ) return SQLITE_MISUSE;
    972   if( gMultiplex.pGroups ) return SQLITE_MISUSE;
    973   gMultiplex.isInitialized = 0;
    974   sqlite3_free(gMultiplex.zName);
    975   sqlite3_mutex_free(gMultiplex.pMutex);
    976   sqlite3_vfs_unregister(&gMultiplex.sThisVfs);
    977   memset(&gMultiplex, 0, sizeof(gMultiplex));
    978   return SQLITE_OK;
    979 }
    980 
    981 /***************************** Test Code ***********************************/
    982 #ifdef SQLITE_TEST
    983 #include <tcl.h>
    984 extern const char *sqlite3TestErrorName(int);
    985 
    986 
    987 /*
    988 ** tclcmd: sqlite3_multiplex_initialize NAME MAKEDEFAULT
    989 */
    990 static int test_multiplex_initialize(
    991   void * clientData,
    992   Tcl_Interp *interp,
    993   int objc,
    994   Tcl_Obj *CONST objv[]
    995 ){
    996   const char *zName;              /* Name of new multiplex VFS */
    997   int makeDefault;                /* True to make the new VFS the default */
    998   int rc;                         /* Value returned by multiplex_initialize() */
    999 
   1000   UNUSED_PARAMETER(clientData);
   1001 
   1002   /* Process arguments */
   1003   if( objc!=3 ){
   1004     Tcl_WrongNumArgs(interp, 1, objv, "NAME MAKEDEFAULT");
   1005     return TCL_ERROR;
   1006   }
   1007   zName = Tcl_GetString(objv[1]);
   1008   if( Tcl_GetBooleanFromObj(interp, objv[2], &makeDefault) ) return TCL_ERROR;
   1009   if( zName[0]=='\0' ) zName = 0;
   1010 
   1011   /* Call sqlite3_multiplex_initialize() */
   1012   rc = sqlite3_multiplex_initialize(zName, makeDefault);
   1013   Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
   1014 
   1015   return TCL_OK;
   1016 }
   1017 
   1018 /*
   1019 ** tclcmd: sqlite3_multiplex_shutdown
   1020 */
   1021 static int test_multiplex_shutdown(
   1022   void * clientData,
   1023   Tcl_Interp *interp,
   1024   int objc,
   1025   Tcl_Obj *CONST objv[]
   1026 ){
   1027   int rc;                         /* Value returned by multiplex_shutdown() */
   1028 
   1029   UNUSED_PARAMETER(clientData);
   1030 
   1031   if( objc!=1 ){
   1032     Tcl_WrongNumArgs(interp, 1, objv, "");
   1033     return TCL_ERROR;
   1034   }
   1035 
   1036   /* Call sqlite3_multiplex_shutdown() */
   1037   rc = sqlite3_multiplex_shutdown();
   1038   Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
   1039 
   1040   return TCL_OK;
   1041 }
   1042 
   1043 /*
   1044 ** tclcmd:  sqlite3_multiplex_dump
   1045 */
   1046 static int test_multiplex_dump(
   1047   void * clientData,
   1048   Tcl_Interp *interp,
   1049   int objc,
   1050   Tcl_Obj *CONST objv[]
   1051 ){
   1052   Tcl_Obj *pResult;
   1053   Tcl_Obj *pGroupTerm;
   1054   multiplexGroup *pGroup;
   1055   int i;
   1056   int nChunks = 0;
   1057 
   1058   UNUSED_PARAMETER(clientData);
   1059   UNUSED_PARAMETER(objc);
   1060   UNUSED_PARAMETER(objv);
   1061 
   1062   pResult = Tcl_NewObj();
   1063   multiplexEnter();
   1064   for(pGroup=gMultiplex.pGroups; pGroup; pGroup=pGroup->pNext){
   1065     pGroupTerm = Tcl_NewObj();
   1066 
   1067     pGroup->zName[pGroup->nName] = '\0';
   1068     Tcl_ListObjAppendElement(interp, pGroupTerm,
   1069           Tcl_NewStringObj(pGroup->zName, -1));
   1070     Tcl_ListObjAppendElement(interp, pGroupTerm,
   1071           Tcl_NewIntObj(pGroup->nName));
   1072     Tcl_ListObjAppendElement(interp, pGroupTerm,
   1073           Tcl_NewIntObj(pGroup->flags));
   1074 
   1075     /* count number of chunks with open handles */
   1076     for(i=0; i<pGroup->nMaxChunks; i++){
   1077       if( pGroup->bOpen[i] ) nChunks++;
   1078     }
   1079     Tcl_ListObjAppendElement(interp, pGroupTerm,
   1080           Tcl_NewIntObj(nChunks));
   1081 
   1082     Tcl_ListObjAppendElement(interp, pGroupTerm,
   1083           Tcl_NewIntObj(pGroup->nChunkSize));
   1084     Tcl_ListObjAppendElement(interp, pGroupTerm,
   1085           Tcl_NewIntObj(pGroup->nMaxChunks));
   1086 
   1087     Tcl_ListObjAppendElement(interp, pResult, pGroupTerm);
   1088   }
   1089   multiplexLeave();
   1090   Tcl_SetObjResult(interp, pResult);
   1091   return TCL_OK;
   1092 }
   1093 
   1094 /*
   1095 ** Tclcmd: test_multiplex_control HANDLE DBNAME SUB-COMMAND ?INT-VALUE?
   1096 */
   1097 static int test_multiplex_control(
   1098   ClientData cd,
   1099   Tcl_Interp *interp,
   1100   int objc,
   1101   Tcl_Obj *CONST objv[]
   1102 ){
   1103   int rc;                         /* Return code from file_control() */
   1104   int idx;                        /* Index in aSub[] */
   1105   Tcl_CmdInfo cmdInfo;            /* Command info structure for HANDLE */
   1106   sqlite3 *db;                    /* Underlying db handle for HANDLE */
   1107   int iValue = 0;
   1108   void *pArg = 0;
   1109 
   1110   struct SubCommand {
   1111     const char *zName;
   1112     int op;
   1113     int argtype;
   1114   } aSub[] = {
   1115     { "enable",       MULTIPLEX_CTRL_ENABLE,           1 },
   1116     { "chunk_size",   MULTIPLEX_CTRL_SET_CHUNK_SIZE,   1 },
   1117     { "max_chunks",   MULTIPLEX_CTRL_SET_MAX_CHUNKS,   1 },
   1118     { 0, 0, 0 }
   1119   };
   1120 
   1121   if( objc!=5 ){
   1122     Tcl_WrongNumArgs(interp, 1, objv, "HANDLE DBNAME SUB-COMMAND INT-VALUE");
   1123     return TCL_ERROR;
   1124   }
   1125 
   1126   if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &cmdInfo) ){
   1127     Tcl_AppendResult(interp, "expected database handle, got \"", 0);
   1128     Tcl_AppendResult(interp, Tcl_GetString(objv[1]), "\"", 0);
   1129     return TCL_ERROR;
   1130   }else{
   1131     db = *(sqlite3 **)cmdInfo.objClientData;
   1132   }
   1133 
   1134   rc = Tcl_GetIndexFromObjStruct(
   1135       interp, objv[3], aSub, sizeof(aSub[0]), "sub-command", 0, &idx
   1136   );
   1137   if( rc!=TCL_OK ) return rc;
   1138 
   1139   switch( aSub[idx].argtype ){
   1140     case 1:
   1141       if( Tcl_GetIntFromObj(interp, objv[4], &iValue) ){
   1142         return TCL_ERROR;
   1143       }
   1144       pArg = (void *)&iValue;
   1145       break;
   1146     default:
   1147       Tcl_WrongNumArgs(interp, 4, objv, "SUB-COMMAND");
   1148       return TCL_ERROR;
   1149   }
   1150 
   1151   rc = sqlite3_file_control(db, Tcl_GetString(objv[2]), aSub[idx].op, pArg);
   1152   Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
   1153   return (rc==SQLITE_OK) ? TCL_OK : TCL_ERROR;
   1154 }
   1155 
   1156 /*
   1157 ** This routine registers the custom TCL commands defined in this
   1158 ** module.  This should be the only procedure visible from outside
   1159 ** of this module.
   1160 */
   1161 int Sqlitemultiplex_Init(Tcl_Interp *interp){
   1162   static struct {
   1163      char *zName;
   1164      Tcl_ObjCmdProc *xProc;
   1165   } aCmd[] = {
   1166     { "sqlite3_multiplex_initialize", test_multiplex_initialize },
   1167     { "sqlite3_multiplex_shutdown", test_multiplex_shutdown },
   1168     { "sqlite3_multiplex_dump", test_multiplex_dump },
   1169     { "sqlite3_multiplex_control", test_multiplex_control },
   1170   };
   1171   int i;
   1172 
   1173   for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
   1174     Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
   1175   }
   1176 
   1177   return TCL_OK;
   1178 }
   1179 #endif
   1180