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