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 */
87typedef struct multiplexGroup multiplexGroup;
88typedef 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*/
98struct 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*/
116struct 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*/
126static 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*/
177static void multiplexEnter(void){ sqlite3_mutex_enter(gMultiplex.pMutex); }
178static 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*/
188int 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*/
198static 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*/
229static 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*/
271static 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*/
291static 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*/
384static 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
427static int multiplexAccess(sqlite3_vfs *a, const char *b, int c, int *d){
428  return gMultiplex.pOrigVfs->xAccess(gMultiplex.pOrigVfs, b, c, d);
429}
430static int multiplexFullPathname(sqlite3_vfs *a, const char *b, int c, char *d){
431  return gMultiplex.pOrigVfs->xFullPathname(gMultiplex.pOrigVfs, b, c, d);
432}
433static void *multiplexDlOpen(sqlite3_vfs *a, const char *b){
434  return gMultiplex.pOrigVfs->xDlOpen(gMultiplex.pOrigVfs, b);
435}
436static void multiplexDlError(sqlite3_vfs *a, int b, char *c){
437  gMultiplex.pOrigVfs->xDlError(gMultiplex.pOrigVfs, b, c);
438}
439static void (*multiplexDlSym(sqlite3_vfs *a, void *b, const char *c))(void){
440  return gMultiplex.pOrigVfs->xDlSym(gMultiplex.pOrigVfs, b, c);
441}
442static void multiplexDlClose(sqlite3_vfs *a, void *b){
443  gMultiplex.pOrigVfs->xDlClose(gMultiplex.pOrigVfs, b);
444}
445static int multiplexRandomness(sqlite3_vfs *a, int b, char *c){
446  return gMultiplex.pOrigVfs->xRandomness(gMultiplex.pOrigVfs, b, c);
447}
448static int multiplexSleep(sqlite3_vfs *a, int b){
449  return gMultiplex.pOrigVfs->xSleep(gMultiplex.pOrigVfs, b);
450}
451static int multiplexCurrentTime(sqlite3_vfs *a, double *b){
452  return gMultiplex.pOrigVfs->xCurrentTime(gMultiplex.pOrigVfs, b);
453}
454static int multiplexGetLastError(sqlite3_vfs *a, int b, char *c){
455  return gMultiplex.pOrigVfs->xGetLastError(gMultiplex.pOrigVfs, b, c);
456}
457static 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*/
468static 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*/
499static 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*/
539static 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*/
579static 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*/
628static 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*/
649static 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*/
713static 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*/
725static 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*/
737static 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*/
750static 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*/
807static 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*/
819static 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*/
831static 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*/
849static 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*/
866static 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*/
877static 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*/
900int 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*/
970int 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>
984extern const char *sqlite3TestErrorName(int);
985
986
987/*
988** tclcmd: sqlite3_multiplex_initialize NAME MAKEDEFAULT
989*/
990static 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*/
1021static 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*/
1046static 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*/
1097static 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*/
1161int 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