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 */
multiplexEnter(void)177 static void multiplexEnter(void){ sqlite3_mutex_enter(gMultiplex.pMutex); }
multiplexLeave(void)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 */
multiplexStrlen30(const char * z)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 */
multiplexSubOpen(multiplexConn * pConn,int iChunk,int * rc,int * pOutFlags)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 */
multiplexControlFunc(sqlite3_context * context,int argc,sqlite3_value ** argv)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 */
multiplexFuncInit(sqlite3 * db,char ** pzErrMsg,const sqlite3_api_routines * pApi)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 */
multiplexOpen(sqlite3_vfs * pVfs,const char * zName,sqlite3_file * pConn,int flags,int * pOutFlags)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 */
multiplexDelete(sqlite3_vfs * pVfs,const char * zName,int syncDir)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 
multiplexAccess(sqlite3_vfs * a,const char * b,int c,int * d)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 }
multiplexFullPathname(sqlite3_vfs * a,const char * b,int c,char * d)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 }
multiplexDlOpen(sqlite3_vfs * a,const char * b)433 static void *multiplexDlOpen(sqlite3_vfs *a, const char *b){
434   return gMultiplex.pOrigVfs->xDlOpen(gMultiplex.pOrigVfs, b);
435 }
multiplexDlError(sqlite3_vfs * a,int b,char * c)436 static void multiplexDlError(sqlite3_vfs *a, int b, char *c){
437   gMultiplex.pOrigVfs->xDlError(gMultiplex.pOrigVfs, b, c);
438 }
multiplexDlSym(sqlite3_vfs * a,void * b,const char * c)439 static void (*multiplexDlSym(sqlite3_vfs *a, void *b, const char *c))(void){
440   return gMultiplex.pOrigVfs->xDlSym(gMultiplex.pOrigVfs, b, c);
441 }
multiplexDlClose(sqlite3_vfs * a,void * b)442 static void multiplexDlClose(sqlite3_vfs *a, void *b){
443   gMultiplex.pOrigVfs->xDlClose(gMultiplex.pOrigVfs, b);
444 }
multiplexRandomness(sqlite3_vfs * a,int b,char * c)445 static int multiplexRandomness(sqlite3_vfs *a, int b, char *c){
446   return gMultiplex.pOrigVfs->xRandomness(gMultiplex.pOrigVfs, b, c);
447 }
multiplexSleep(sqlite3_vfs * a,int b)448 static int multiplexSleep(sqlite3_vfs *a, int b){
449   return gMultiplex.pOrigVfs->xSleep(gMultiplex.pOrigVfs, b);
450 }
multiplexCurrentTime(sqlite3_vfs * a,double * b)451 static int multiplexCurrentTime(sqlite3_vfs *a, double *b){
452   return gMultiplex.pOrigVfs->xCurrentTime(gMultiplex.pOrigVfs, b);
453 }
multiplexGetLastError(sqlite3_vfs * a,int b,char * c)454 static int multiplexGetLastError(sqlite3_vfs *a, int b, char *c){
455   return gMultiplex.pOrigVfs->xGetLastError(gMultiplex.pOrigVfs, b, c);
456 }
multiplexCurrentTimeInt64(sqlite3_vfs * a,sqlite3_int64 * b)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 */
multiplexClose(sqlite3_file * pConn)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 */
multiplexRead(sqlite3_file * pConn,void * pBuf,int iAmt,sqlite3_int64 iOfst)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 */
multiplexWrite(sqlite3_file * pConn,const void * pBuf,int iAmt,sqlite3_int64 iOfst)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 */
multiplexTruncate(sqlite3_file * pConn,sqlite3_int64 size)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 */
multiplexSync(sqlite3_file * pConn,int flags)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 */
multiplexFileSize(sqlite3_file * pConn,sqlite3_int64 * pSize)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 */
multiplexLock(sqlite3_file * pConn,int lock)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 */
multiplexUnlock(sqlite3_file * pConn,int lock)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 */
multiplexCheckReservedLock(sqlite3_file * pConn,int * pResOut)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 */
multiplexFileControl(sqlite3_file * pConn,int op,void * pArg)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 */
multiplexSectorSize(sqlite3_file * pConn)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 */
multiplexDeviceCharacteristics(sqlite3_file * pConn)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 */
multiplexShmMap(sqlite3_file * pConn,int iRegion,int szRegion,int bExtend,void volatile ** pp)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 */
multiplexShmLock(sqlite3_file * pConn,int ofst,int n,int flags)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 */
multiplexShmBarrier(sqlite3_file * pConn)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 */
multiplexShmUnmap(sqlite3_file * pConn,int deleteFlag)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 */
sqlite3_multiplex_initialize(const char * zOrigVfsName,int makeDefault)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 */
sqlite3_multiplex_shutdown(void)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 */
test_multiplex_initialize(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])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 */
test_multiplex_shutdown(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])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 */
test_multiplex_dump(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])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 */
test_multiplex_control(ClientData cd,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])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 */
Sqlitemultiplex_Init(Tcl_Interp * interp)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