xref: /sqlite-3.40.0/src/test_multiplex.c (revision cc4e19be)
18a922f75Sshaneh /*
28a922f75Sshaneh ** 2010 October 28
38a922f75Sshaneh **
48a922f75Sshaneh ** The author disclaims copyright to this source code.  In place of
58a922f75Sshaneh ** a legal notice, here is a blessing:
68a922f75Sshaneh **
78a922f75Sshaneh **    May you do good and not evil.
88a922f75Sshaneh **    May you find forgiveness for yourself and forgive others.
98a922f75Sshaneh **    May you share freely, never taking more than you give.
108a922f75Sshaneh **
118a922f75Sshaneh *************************************************************************
128a922f75Sshaneh **
138a922f75Sshaneh ** This file contains a VFS "shim" - a layer that sits in between the
148a922f75Sshaneh ** pager and the real VFS.
158a922f75Sshaneh **
168a922f75Sshaneh ** This particular shim enforces a multiplex system on DB files.
178a922f75Sshaneh ** This shim shards/partitions a single DB file into smaller
188a922f75Sshaneh ** "chunks" such that the total DB file size may exceed the maximum
198a922f75Sshaneh ** file size of the underlying file system.
208a922f75Sshaneh **
218a922f75Sshaneh */
228a922f75Sshaneh #include "sqlite3.h"
238a922f75Sshaneh #include <string.h>
248a922f75Sshaneh #include <assert.h>
25d50deeebSshaneh #include "test_multiplex.h"
26d50deeebSshaneh 
2778c4de4cSshaneh #ifndef SQLITE_CORE
2878c4de4cSshaneh   #define SQLITE_CORE 1  /* Disable the API redefinition in sqlite3ext.h */
2978c4de4cSshaneh #endif
3078c4de4cSshaneh #include "sqlite3ext.h"
3178c4de4cSshaneh 
32bb201344Sshaneh /*
333801b65dSshaneh ** These should be defined to be the same as the values in
343801b65dSshaneh ** sqliteInt.h.  They are defined seperately here so that
353801b65dSshaneh ** the multiplex VFS shim can be built as a loadable
363801b65dSshaneh ** module.
373801b65dSshaneh */
383801b65dSshaneh #define UNUSED_PARAMETER(x) (void)(x)
393801b65dSshaneh #define MAX_PAGE_SIZE       0x10000
403801b65dSshaneh #define DEFAULT_SECTOR_SIZE 0x1000
413801b65dSshaneh 
423801b65dSshaneh /*
43bb201344Sshaneh ** For a build without mutexes, no-op the mutex calls.
44bb201344Sshaneh */
45bb201344Sshaneh #if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0
46bb201344Sshaneh #define sqlite3_mutex_alloc(X)    ((sqlite3_mutex*)8)
47bb201344Sshaneh #define sqlite3_mutex_free(X)
48bb201344Sshaneh #define sqlite3_mutex_enter(X)
49bb201344Sshaneh #define sqlite3_mutex_try(X)      SQLITE_OK
50bb201344Sshaneh #define sqlite3_mutex_leave(X)
51bb201344Sshaneh #define sqlite3_mutex_held(X)     ((void)(X),1)
52bb201344Sshaneh #define sqlite3_mutex_notheld(X)  ((void)(X),1)
53bb201344Sshaneh #endif /* SQLITE_THREADSAFE==0 */
54bb201344Sshaneh 
55bb201344Sshaneh 
568a922f75Sshaneh /************************ Shim Definitions ******************************/
578a922f75Sshaneh 
58d9523b97Sshaneh #define SQLITE_MULTIPLEX_VFS_NAME "multiplex"
59d9523b97Sshaneh 
60050d09abSshaneh /* This is the limit on the chunk size.  It may be changed by calling
6178c4de4cSshaneh ** the xFileControl() interface.  It will be rounded up to a
623801b65dSshaneh ** multiple of MAX_PAGE_SIZE.  We default it here to 1GB.
63050d09abSshaneh */
643801b65dSshaneh #define SQLITE_MULTIPLEX_CHUNK_SIZE (MAX_PAGE_SIZE*16384)
65c27fa4b0Sshaneh 
66050d09abSshaneh /* Default limit on number of chunks.  Care should be taken
67050d09abSshaneh ** so that values for chunks numbers fit in the SQLITE_MULTIPLEX_EXT_FMT
68050d09abSshaneh ** format specifier. It may be changed by calling
69d50deeebSshaneh ** the xFileControl() interface.
70050d09abSshaneh */
718a922f75Sshaneh #define SQLITE_MULTIPLEX_MAX_CHUNKS 32
72050d09abSshaneh 
73050d09abSshaneh /* If SQLITE_MULTIPLEX_EXT_OVWR is defined, the
74050d09abSshaneh ** last SQLITE_MULTIPLEX_EXT_SZ characters of the
75050d09abSshaneh ** filename will be overwritten, otherwise, the
76050d09abSshaneh ** multiplex extension is simply appended to the filename.
77050d09abSshaneh ** Ex.  (undefined) test.db -> test.db01
78050d09abSshaneh **      (defined)   test.db -> test.01
79050d09abSshaneh ** Chunk 0 does not have a modified extension.
80050d09abSshaneh */
81050d09abSshaneh #define SQLITE_MULTIPLEX_EXT_FMT    "%02d"
82050d09abSshaneh #define SQLITE_MULTIPLEX_EXT_SZ     2
838a922f75Sshaneh 
848a922f75Sshaneh /************************ Object Definitions ******************************/
858a922f75Sshaneh 
868a922f75Sshaneh /* Forward declaration of all object types */
878a922f75Sshaneh typedef struct multiplexGroup multiplexGroup;
888a922f75Sshaneh typedef struct multiplexConn multiplexConn;
898a922f75Sshaneh 
908a922f75Sshaneh /*
918a922f75Sshaneh ** A "multiplex group" is a collection of files that collectively
928a922f75Sshaneh ** makeup a single SQLite DB file.  This allows the size of the DB
938a922f75Sshaneh ** to exceed the limits imposed by the file system.
948a922f75Sshaneh **
958a922f75Sshaneh ** There is an instance of the following object for each defined multiplex
968a922f75Sshaneh ** group.
978a922f75Sshaneh */
988a922f75Sshaneh struct multiplexGroup {
99050d09abSshaneh   sqlite3_file **pReal;            /* Handles to each chunk */
100bb201344Sshaneh   char *bOpen;                     /* array of bools - 0 if chunk not opened */
1018a922f75Sshaneh   char *zName;                     /* Base filename of this group */
1028a922f75Sshaneh   int nName;                       /* Length of base filename */
1038a922f75Sshaneh   int flags;                       /* Flags used for original opening */
104d50deeebSshaneh   int nChunkSize;                  /* Chunk size used for this group */
105d50deeebSshaneh   int nMaxChunks;                  /* Max number of chunks for this group */
106d50deeebSshaneh   int bEnabled;                    /* TRUE to use Multiplex VFS for this file */
1078a922f75Sshaneh   multiplexGroup *pNext, *pPrev;   /* Doubly linked list of all group objects */
1088a922f75Sshaneh };
1098a922f75Sshaneh 
1108a922f75Sshaneh /*
1118a922f75Sshaneh ** An instance of the following object represents each open connection
1128a922f75Sshaneh ** to a file that is multiplex'ed.  This object is a
1138a922f75Sshaneh ** subclass of sqlite3_file.  The sqlite3_file object for the underlying
1148a922f75Sshaneh ** VFS is appended to this structure.
1158a922f75Sshaneh */
1168a922f75Sshaneh struct multiplexConn {
1178a922f75Sshaneh   sqlite3_file base;              /* Base class - must be first */
1188a922f75Sshaneh   multiplexGroup *pGroup;         /* The underlying group of files */
1198a922f75Sshaneh };
1208a922f75Sshaneh 
1218a922f75Sshaneh /************************* Global Variables **********************************/
1228a922f75Sshaneh /*
1238a922f75Sshaneh ** All global variables used by this file are containing within the following
1248a922f75Sshaneh ** gMultiplex structure.
1258a922f75Sshaneh */
1268a922f75Sshaneh static struct {
1278a922f75Sshaneh   /* The pOrigVfs is the real, original underlying VFS implementation.
1288a922f75Sshaneh   ** Most operations pass-through to the real VFS.  This value is read-only
1298a922f75Sshaneh   ** during operation.  It is only modified at start-time and thus does not
1308a922f75Sshaneh   ** require a mutex.
1318a922f75Sshaneh   */
1328a922f75Sshaneh   sqlite3_vfs *pOrigVfs;
1338a922f75Sshaneh 
1348a922f75Sshaneh   /* The sThisVfs is the VFS structure used by this shim.  It is initialized
1358a922f75Sshaneh   ** at start-time and thus does not require a mutex
1368a922f75Sshaneh   */
1378a922f75Sshaneh   sqlite3_vfs sThisVfs;
1388a922f75Sshaneh 
1398a922f75Sshaneh   /* The sIoMethods defines the methods used by sqlite3_file objects
1408a922f75Sshaneh   ** associated with this shim.  It is initialized at start-time and does
1418a922f75Sshaneh   ** not require a mutex.
1428a922f75Sshaneh   **
1438a922f75Sshaneh   ** When the underlying VFS is called to open a file, it might return
1448a922f75Sshaneh   ** either a version 1 or a version 2 sqlite3_file object.  This shim
1458a922f75Sshaneh   ** has to create a wrapper sqlite3_file of the same version.  Hence
1468a922f75Sshaneh   ** there are two I/O method structures, one for version 1 and the other
1478a922f75Sshaneh   ** for version 2.
1488a922f75Sshaneh   */
1498a922f75Sshaneh   sqlite3_io_methods sIoMethodsV1;
1508a922f75Sshaneh   sqlite3_io_methods sIoMethodsV2;
1518a922f75Sshaneh 
152fd1552f2Sshaneh   /* True when this shim has been initialized.
1538a922f75Sshaneh   */
1548a922f75Sshaneh   int isInitialized;
1558a922f75Sshaneh 
1568a922f75Sshaneh   /* For run-time access any of the other global data structures in this
1578a922f75Sshaneh   ** shim, the following mutex must be held.
1588a922f75Sshaneh   */
1598a922f75Sshaneh   sqlite3_mutex *pMutex;
1608a922f75Sshaneh 
1618a922f75Sshaneh   /* List of multiplexGroup objects.
1628a922f75Sshaneh   */
1638a922f75Sshaneh   multiplexGroup *pGroups;
1648a922f75Sshaneh 
165b5830294Sshaneh   /* Storage for temp file names.  Allocated during
166b5830294Sshaneh   ** initialization to the max pathname of the underlying VFS.
167b5830294Sshaneh   */
168b5830294Sshaneh   char *zName;
169b5830294Sshaneh 
1708a922f75Sshaneh } gMultiplex;
1718a922f75Sshaneh 
1728a922f75Sshaneh /************************* Utility Routines *********************************/
1738a922f75Sshaneh /*
1748a922f75Sshaneh ** Acquire and release the mutex used to serialize access to the
1758a922f75Sshaneh ** list of multiplexGroups.
1768a922f75Sshaneh */
1778a922f75Sshaneh static void multiplexEnter(void){ sqlite3_mutex_enter(gMultiplex.pMutex); }
1788a922f75Sshaneh static void multiplexLeave(void){ sqlite3_mutex_leave(gMultiplex.pMutex); }
1798a922f75Sshaneh 
1803801b65dSshaneh /*
1813801b65dSshaneh ** Compute a string length that is limited to what can be stored in
1823801b65dSshaneh ** lower 30 bits of a 32-bit signed integer.
1833801b65dSshaneh **
1843801b65dSshaneh ** The value returned will never be negative.  Nor will it ever be greater
1853801b65dSshaneh ** than the actual length of the string.  For very long strings (greater
1863801b65dSshaneh ** than 1GiB) the value returned might be less than the true string length.
1873801b65dSshaneh */
188*cc4e19beSshaneh static int multiplexStrlen30(const char *z){
1893801b65dSshaneh   const char *z2 = z;
1903801b65dSshaneh   if( z==0 ) return 0;
1913801b65dSshaneh   while( *z2 ){ z2++; }
1923801b65dSshaneh   return 0x3fffffff & (int)(z2 - z);
1933801b65dSshaneh }
1943801b65dSshaneh 
195*cc4e19beSshaneh /*
196*cc4e19beSshaneh ** Create a temporary file name in zBuf.  zBuf must be big enough to
197*cc4e19beSshaneh ** hold at pOrigVfs->mxPathname characters.  This function departs
198*cc4e19beSshaneh ** from the traditional temporary name generation in the os_win
199*cc4e19beSshaneh ** and os_unix VFS in several ways, but is necessary so that
200*cc4e19beSshaneh ** the file name is known for temporary files (like those used
201*cc4e19beSshaneh ** during vacuum.)
202*cc4e19beSshaneh **
203*cc4e19beSshaneh ** N.B. This routine assumes your underlying VFS is ok with using
204*cc4e19beSshaneh ** "/" as a directory seperator.  This is the default for UNIXs
205*cc4e19beSshaneh ** and is allowed (even mixed) for most versions of Windows.
206*cc4e19beSshaneh */
207*cc4e19beSshaneh static int multiplexGetTempname(sqlite3_vfs *pOrigVfs, int nBuf, char *zBuf){
208*cc4e19beSshaneh   static char zChars[] =
209*cc4e19beSshaneh     "abcdefghijklmnopqrstuvwxyz"
210*cc4e19beSshaneh     "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
211*cc4e19beSshaneh     "0123456789";
212*cc4e19beSshaneh   int i,j;
213*cc4e19beSshaneh   int attempts = 0;
214*cc4e19beSshaneh   int exists = 0;
215*cc4e19beSshaneh   int rc = SQLITE_ERROR;
216*cc4e19beSshaneh 
217*cc4e19beSshaneh   /* Check that the output buffer is large enough for
218*cc4e19beSshaneh   ** pVfs->mxPathname characters.
219*cc4e19beSshaneh   */
220*cc4e19beSshaneh   if( pOrigVfs->mxPathname <= nBuf ){
221*cc4e19beSshaneh 
222*cc4e19beSshaneh     /* sqlite3_temp_directory should always be less than
223*cc4e19beSshaneh     ** pVfs->mxPathname characters.
224*cc4e19beSshaneh     */
225*cc4e19beSshaneh     sqlite3_snprintf(pOrigVfs->mxPathname,
226*cc4e19beSshaneh                      zBuf,
227*cc4e19beSshaneh                      "%s/",
228*cc4e19beSshaneh                      sqlite3_temp_directory ? sqlite3_temp_directory : ".");
229*cc4e19beSshaneh 
230*cc4e19beSshaneh     /* Check that the output buffer is large enough for the temporary file
231*cc4e19beSshaneh     ** name.
232*cc4e19beSshaneh     */
233*cc4e19beSshaneh     j = multiplexStrlen30(zBuf);
234*cc4e19beSshaneh     if( (j + 8 + 1 + 3 + 1) <= nBuf ){
235*cc4e19beSshaneh       /* Make 3 attempts to generate a unique name. */
236*cc4e19beSshaneh       do {
237*cc4e19beSshaneh         attempts++;
238*cc4e19beSshaneh         sqlite3_randomness(8, &zBuf[j]);
239*cc4e19beSshaneh         for(i=0; i<8; i++){
240*cc4e19beSshaneh           zBuf[j+i] = (char)zChars[ ((unsigned char)zBuf[j+i])%(sizeof(zChars)-1) ];
241*cc4e19beSshaneh         }
242*cc4e19beSshaneh         memcpy(&zBuf[j+i], ".tmp", 5);
243*cc4e19beSshaneh         rc = pOrigVfs->xAccess(pOrigVfs, zBuf, SQLITE_ACCESS_EXISTS, &exists);
244*cc4e19beSshaneh       } while ( (rc==SQLITE_OK) && exists && (attempts<3) );
245*cc4e19beSshaneh       if( rc==SQLITE_OK && exists ){
246*cc4e19beSshaneh         rc = SQLITE_ERROR;
247*cc4e19beSshaneh       }
248*cc4e19beSshaneh     }
249*cc4e19beSshaneh   }
250*cc4e19beSshaneh 
251*cc4e19beSshaneh   return rc;
252*cc4e19beSshaneh }
253*cc4e19beSshaneh 
2548a922f75Sshaneh /* Translate an sqlite3_file* that is really a multiplexGroup* into
2558a922f75Sshaneh ** the sqlite3_file* for the underlying original VFS.
2568a922f75Sshaneh */
2578a922f75Sshaneh static sqlite3_file *multiplexSubOpen(multiplexConn *pConn, int iChunk, int *rc, int *pOutFlags){
2588a922f75Sshaneh   multiplexGroup *pGroup = pConn->pGroup;
2598a922f75Sshaneh   sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;        /* Real VFS */
260d50deeebSshaneh   if( iChunk<pGroup->nMaxChunks ){
2618a922f75Sshaneh     sqlite3_file *pSubOpen = pGroup->pReal[iChunk];    /* Real file descriptor */
2628a922f75Sshaneh     if( !pGroup->bOpen[iChunk] ){
263050d09abSshaneh       memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1);
264050d09abSshaneh       if( iChunk ){
265050d09abSshaneh #ifdef SQLITE_MULTIPLEX_EXT_OVWR
266050d09abSshaneh         sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ, SQLITE_MULTIPLEX_EXT_FMT, iChunk);
267050d09abSshaneh #else
268050d09abSshaneh         sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1, gMultiplex.zName+pGroup->nName, SQLITE_MULTIPLEX_EXT_FMT, iChunk);
269050d09abSshaneh #endif
270050d09abSshaneh       }
271050d09abSshaneh       *rc = pOrigVfs->xOpen(pOrigVfs, gMultiplex.zName, pSubOpen, pGroup->flags, pOutFlags);
2728a922f75Sshaneh       if( *rc==SQLITE_OK ){
2738a922f75Sshaneh         pGroup->bOpen[iChunk] = -1;
2748a922f75Sshaneh         return pSubOpen;
2758a922f75Sshaneh       }
2768a922f75Sshaneh       return NULL;
2778a922f75Sshaneh     }
2788a922f75Sshaneh     *rc = SQLITE_OK;
2798a922f75Sshaneh     return pSubOpen;
2808a922f75Sshaneh   }
281b5830294Sshaneh   *rc = SQLITE_FULL;
2828a922f75Sshaneh   return NULL;
2838a922f75Sshaneh }
2848a922f75Sshaneh 
2853801b65dSshaneh /*
2863801b65dSshaneh ** This is the implementation of the multiplex_control() SQL function.
2873801b65dSshaneh */
288d50deeebSshaneh static void multiplexControlFunc(
289d50deeebSshaneh   sqlite3_context *context,
290d50deeebSshaneh   int argc,
291d50deeebSshaneh   sqlite3_value **argv
292d50deeebSshaneh ){
29378c4de4cSshaneh   int rc = SQLITE_OK;
294d8ce22bbSshaneh   sqlite3 *db = sqlite3_context_db_handle(context);
295d8ce22bbSshaneh   int op;
296d8ce22bbSshaneh   int iVal;
297d8ce22bbSshaneh 
298d8ce22bbSshaneh   if( !db || argc!=2 ){
299d8ce22bbSshaneh     rc = SQLITE_ERROR;
300d8ce22bbSshaneh   }else{
301d8ce22bbSshaneh     /* extract params */
302d8ce22bbSshaneh     op = sqlite3_value_int(argv[0]);
303d8ce22bbSshaneh     iVal = sqlite3_value_int(argv[1]);
304d8ce22bbSshaneh     /* map function op to file_control op */
30578c4de4cSshaneh     switch( op ){
30678c4de4cSshaneh       case 1:
30778c4de4cSshaneh         op = MULTIPLEX_CTRL_ENABLE;
30878c4de4cSshaneh         break;
30978c4de4cSshaneh       case 2:
31078c4de4cSshaneh         op = MULTIPLEX_CTRL_SET_CHUNK_SIZE;
31178c4de4cSshaneh         break;
31278c4de4cSshaneh       case 3:
31378c4de4cSshaneh         op = MULTIPLEX_CTRL_SET_MAX_CHUNKS;
31478c4de4cSshaneh         break;
31578c4de4cSshaneh       default:
3163801b65dSshaneh         rc = SQLITE_NOTFOUND;
31778c4de4cSshaneh         break;
31878c4de4cSshaneh     }
319d8ce22bbSshaneh   }
32078c4de4cSshaneh   if( rc==SQLITE_OK ){
321d8ce22bbSshaneh     rc = sqlite3_file_control(db, 0, op, &iVal);
322d9523b97Sshaneh   }
3233801b65dSshaneh   sqlite3_result_error_code(context, rc);
324d9523b97Sshaneh }
325d9523b97Sshaneh 
326d9523b97Sshaneh /*
3273801b65dSshaneh ** This is the entry point to register the auto-extension for the
3283801b65dSshaneh ** multiplex_control() function.
329d9523b97Sshaneh */
330d50deeebSshaneh static int multiplexFuncInit(
33178c4de4cSshaneh   sqlite3 *db,
33278c4de4cSshaneh   char **pzErrMsg,
33378c4de4cSshaneh   const sqlite3_api_routines *pApi
334d50deeebSshaneh ){
335ac039688Sshaneh   int rc;
336ac039688Sshaneh   rc = sqlite3_create_function(db, "multiplex_control", 2, SQLITE_ANY,
337d8ce22bbSshaneh       0, multiplexControlFunc, 0, 0);
338ac039688Sshaneh   return rc;
339d9523b97Sshaneh }
340d9523b97Sshaneh 
3418a922f75Sshaneh /************************* VFS Method Wrappers *****************************/
342b5830294Sshaneh 
3438a922f75Sshaneh /*
3448a922f75Sshaneh ** This is the xOpen method used for the "multiplex" VFS.
3458a922f75Sshaneh **
3468a922f75Sshaneh ** Most of the work is done by the underlying original VFS.  This method
3478a922f75Sshaneh ** simply links the new file into the appropriate multiplex group if it is a
3488a922f75Sshaneh ** file that needs to be tracked.
3498a922f75Sshaneh */
3508a922f75Sshaneh static int multiplexOpen(
3518a922f75Sshaneh   sqlite3_vfs *pVfs,         /* The multiplex VFS */
3528a922f75Sshaneh   const char *zName,         /* Name of file to be opened */
3538a922f75Sshaneh   sqlite3_file *pConn,       /* Fill in this file descriptor */
3548a922f75Sshaneh   int flags,                 /* Flags to control the opening */
3558a922f75Sshaneh   int *pOutFlags             /* Flags showing results of opening */
3568a922f75Sshaneh ){
357*cc4e19beSshaneh   int rc = SQLITE_OK;                            /* Result code */
3588a922f75Sshaneh   multiplexConn *pMultiplexOpen;                 /* The new multiplex file descriptor */
3598a922f75Sshaneh   multiplexGroup *pGroup;                        /* Corresponding multiplexGroup object */
3608a922f75Sshaneh   sqlite3_file *pSubOpen;                        /* Real file descriptor */
3618a922f75Sshaneh   sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */
362*cc4e19beSshaneh   int nName;
3638a922f75Sshaneh   int i;
364050d09abSshaneh   int sz;
3658a922f75Sshaneh 
3668a922f75Sshaneh   UNUSED_PARAMETER(pVfs);
3678a922f75Sshaneh 
3688a922f75Sshaneh   /* We need to create a group structure and manage
3698a922f75Sshaneh   ** access to this group of files.
3708a922f75Sshaneh   */
3718a922f75Sshaneh   multiplexEnter();
3728a922f75Sshaneh   pMultiplexOpen = (multiplexConn*)pConn;
373*cc4e19beSshaneh 
374*cc4e19beSshaneh   /* If the second argument to this function is NULL, generate a
375*cc4e19beSshaneh   ** temporary file name to use.  This will be handled by the
376*cc4e19beSshaneh   ** original xOpen method.  We just need to allocate space for
377*cc4e19beSshaneh   ** it.
378*cc4e19beSshaneh   */
379*cc4e19beSshaneh   if( !zName ){
380*cc4e19beSshaneh     rc = multiplexGetTempname(pOrigVfs, pOrigVfs->mxPathname, gMultiplex.zName);
381*cc4e19beSshaneh     zName = gMultiplex.zName;
382*cc4e19beSshaneh   }
383*cc4e19beSshaneh 
384*cc4e19beSshaneh   if( rc==SQLITE_OK ){
385050d09abSshaneh     /* allocate space for group */
386*cc4e19beSshaneh     nName = multiplexStrlen30(zName);
387050d09abSshaneh     sz = sizeof(multiplexGroup)                                /* multiplexGroup */
388d50deeebSshaneh        + (sizeof(sqlite3_file *)*SQLITE_MULTIPLEX_MAX_CHUNKS)  /* pReal[] */
389d50deeebSshaneh        + (pOrigVfs->szOsFile*SQLITE_MULTIPLEX_MAX_CHUNKS)      /* *pReal */
390d50deeebSshaneh        + SQLITE_MULTIPLEX_MAX_CHUNKS                           /* bOpen[] */
391050d09abSshaneh        + nName + 1;                                            /* zName */
392050d09abSshaneh #ifndef SQLITE_MULTIPLEX_EXT_OVWR
393050d09abSshaneh     sz += SQLITE_MULTIPLEX_EXT_SZ;
394050d09abSshaneh     assert(nName+SQLITE_MULTIPLEX_EXT_SZ < pOrigVfs->mxPathname);
395050d09abSshaneh #else
396050d09abSshaneh     assert(nName >= SQLITE_MULTIPLEX_EXT_SZ);
397050d09abSshaneh     assert(nName < pOrigVfs->mxPathname);
398050d09abSshaneh #endif
399050d09abSshaneh     pGroup = sqlite3_malloc( sz );
4008a922f75Sshaneh     if( pGroup==0 ){
4018a922f75Sshaneh       rc=SQLITE_NOMEM;
402*cc4e19beSshaneh     }
403*cc4e19beSshaneh   }
404*cc4e19beSshaneh 
405*cc4e19beSshaneh   if( rc==SQLITE_OK ){
406050d09abSshaneh     /* assign pointers to extra space allocated */
407050d09abSshaneh     char *p = (char *)&pGroup[1];
4088a922f75Sshaneh     pMultiplexOpen->pGroup = pGroup;
409050d09abSshaneh     memset(pGroup, 0, sz);
410c27fa4b0Sshaneh     pGroup->bEnabled = -1;
411d50deeebSshaneh     pGroup->nChunkSize = SQLITE_MULTIPLEX_CHUNK_SIZE;
412d50deeebSshaneh     pGroup->nMaxChunks = SQLITE_MULTIPLEX_MAX_CHUNKS;
413050d09abSshaneh     pGroup->pReal = (sqlite3_file **)p;
414d50deeebSshaneh     p += (sizeof(sqlite3_file *)*pGroup->nMaxChunks);
415d50deeebSshaneh     for(i=0; i<pGroup->nMaxChunks; i++){
416050d09abSshaneh       pGroup->pReal[i] = (sqlite3_file *)p;
417050d09abSshaneh       p += pOrigVfs->szOsFile;
4188a922f75Sshaneh     }
419d50deeebSshaneh     /* bOpen[] vals should all be zero from memset above */
420050d09abSshaneh     pGroup->bOpen = p;
421d50deeebSshaneh     p += pGroup->nMaxChunks;
422050d09abSshaneh     pGroup->zName = p;
423050d09abSshaneh     /* save off base filename, name length, and original open flags  */
4248a922f75Sshaneh     memcpy(pGroup->zName, zName, nName+1);
4258a922f75Sshaneh     pGroup->nName = nName;
4268a922f75Sshaneh     pGroup->flags = flags;
4278a922f75Sshaneh     pSubOpen = multiplexSubOpen(pMultiplexOpen, 0, &rc, pOutFlags);
4288a922f75Sshaneh     if( pSubOpen ){
429c27fa4b0Sshaneh       /* if this file is already larger than chunk size, disable
430c27fa4b0Sshaneh       ** the multiplex feature.
431c27fa4b0Sshaneh       */
432c27fa4b0Sshaneh       sqlite3_int64 sz;
433c27fa4b0Sshaneh       int rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz);
434c27fa4b0Sshaneh       if( (rc2==SQLITE_OK) && (sz>pGroup->nChunkSize) ){
435c27fa4b0Sshaneh         pGroup->bEnabled = 0;
436c27fa4b0Sshaneh       }
4378a922f75Sshaneh       if( pSubOpen->pMethods->iVersion==1 ){
4388a922f75Sshaneh         pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV1;
4398a922f75Sshaneh       }else{
4408a922f75Sshaneh         pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV2;
4418a922f75Sshaneh       }
4428a922f75Sshaneh       /* place this group at the head of our list */
4438a922f75Sshaneh       pGroup->pNext = gMultiplex.pGroups;
4448a922f75Sshaneh       if( gMultiplex.pGroups ) gMultiplex.pGroups->pPrev = pGroup;
4458a922f75Sshaneh       gMultiplex.pGroups = pGroup;
4468a922f75Sshaneh     }else{
4478a922f75Sshaneh       sqlite3_free(pGroup);
4488a922f75Sshaneh     }
4498a922f75Sshaneh   }
4508a922f75Sshaneh   multiplexLeave();
4518a922f75Sshaneh   return rc;
4528a922f75Sshaneh }
4538a922f75Sshaneh 
454b5830294Sshaneh /*
455b5830294Sshaneh ** This is the xDelete method used for the "multiplex" VFS.
456b5830294Sshaneh ** It attempts to delete the filename specified, as well
457050d09abSshaneh ** as additional files with the SQLITE_MULTIPLEX_EXT_FMT extension.
458b5830294Sshaneh */
459b5830294Sshaneh static int multiplexDelete(
460b5830294Sshaneh   sqlite3_vfs *pVfs,         /* The multiplex VFS */
461b5830294Sshaneh   const char *zName,         /* Name of file to delete */
462b5830294Sshaneh   int syncDir
463b5830294Sshaneh ){
464b5830294Sshaneh   sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */
465b5830294Sshaneh   int rc = SQLITE_OK;
4663801b65dSshaneh   int nName = multiplexStrlen30(zName);
467b5830294Sshaneh   int i;
468b5830294Sshaneh 
469b5830294Sshaneh   UNUSED_PARAMETER(pVfs);
470b5830294Sshaneh 
471b5830294Sshaneh   multiplexEnter();
472b5830294Sshaneh   memcpy(gMultiplex.zName, zName, nName+1);
473d50deeebSshaneh   for(i=0; i<SQLITE_MULTIPLEX_MAX_CHUNKS; i++){
474b5830294Sshaneh     int rc2;
475b5830294Sshaneh     int exists = 0;
476050d09abSshaneh     if( i ){
477050d09abSshaneh #ifdef SQLITE_MULTIPLEX_EXT_OVWR
4783801b65dSshaneh         sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1,
4793801b65dSshaneh             gMultiplex.zName+nName-SQLITE_MULTIPLEX_EXT_SZ,
4803801b65dSshaneh             SQLITE_MULTIPLEX_EXT_FMT, i);
481050d09abSshaneh #else
4823801b65dSshaneh         sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1,
4833801b65dSshaneh             gMultiplex.zName+nName,
4843801b65dSshaneh             SQLITE_MULTIPLEX_EXT_FMT, i);
485050d09abSshaneh #endif
486050d09abSshaneh     }
4873801b65dSshaneh     rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName,
4883801b65dSshaneh         SQLITE_ACCESS_EXISTS, &exists);
489b5830294Sshaneh     if( rc2==SQLITE_OK && exists ){
490b5830294Sshaneh       /* if it exists, delete it */
491d50deeebSshaneh       rc2 = pOrigVfs->xDelete(pOrigVfs, gMultiplex.zName, syncDir);
492b5830294Sshaneh       if( rc2!=SQLITE_OK ) rc = rc2;
493b5830294Sshaneh     }else{
494b5830294Sshaneh       /* stop at first "gap" */
495b5830294Sshaneh       break;
496b5830294Sshaneh     }
497b5830294Sshaneh   }
498b5830294Sshaneh   multiplexLeave();
499b5830294Sshaneh   return rc;
500b5830294Sshaneh }
501b5830294Sshaneh 
502d50deeebSshaneh static int multiplexAccess(sqlite3_vfs *a, const char *b, int c, int *d){
503d50deeebSshaneh   return gMultiplex.pOrigVfs->xAccess(gMultiplex.pOrigVfs, b, c, d);
504fffadc52Sdan }
505d50deeebSshaneh static int multiplexFullPathname(sqlite3_vfs *a, const char *b, int c, char *d){
506d50deeebSshaneh   return gMultiplex.pOrigVfs->xFullPathname(gMultiplex.pOrigVfs, b, c, d);
507fffadc52Sdan }
508fffadc52Sdan static void *multiplexDlOpen(sqlite3_vfs *a, const char *b){
509fffadc52Sdan   return gMultiplex.pOrigVfs->xDlOpen(gMultiplex.pOrigVfs, b);
510fffadc52Sdan }
511fffadc52Sdan static void multiplexDlError(sqlite3_vfs *a, int b, char *c){
512fffadc52Sdan   gMultiplex.pOrigVfs->xDlError(gMultiplex.pOrigVfs, b, c);
513fffadc52Sdan }
514fffadc52Sdan static void (*multiplexDlSym(sqlite3_vfs *a, void *b, const char *c))(void){
515fffadc52Sdan   return gMultiplex.pOrigVfs->xDlSym(gMultiplex.pOrigVfs, b, c);
516fffadc52Sdan }
517fffadc52Sdan static void multiplexDlClose(sqlite3_vfs *a, void *b){
518fffadc52Sdan   gMultiplex.pOrigVfs->xDlClose(gMultiplex.pOrigVfs, b);
519fffadc52Sdan }
520fffadc52Sdan static int multiplexRandomness(sqlite3_vfs *a, int b, char *c){
521fffadc52Sdan   return gMultiplex.pOrigVfs->xRandomness(gMultiplex.pOrigVfs, b, c);
522fffadc52Sdan }
523fffadc52Sdan static int multiplexSleep(sqlite3_vfs *a, int b){
524fffadc52Sdan   return gMultiplex.pOrigVfs->xSleep(gMultiplex.pOrigVfs, b);
525fffadc52Sdan }
526fffadc52Sdan static int multiplexCurrentTime(sqlite3_vfs *a, double *b){
527fffadc52Sdan   return gMultiplex.pOrigVfs->xCurrentTime(gMultiplex.pOrigVfs, b);
528fffadc52Sdan }
529fffadc52Sdan static int multiplexGetLastError(sqlite3_vfs *a, int b, char *c){
530fffadc52Sdan   return gMultiplex.pOrigVfs->xGetLastError(gMultiplex.pOrigVfs, b, c);
531fffadc52Sdan }
532fffadc52Sdan static int multiplexCurrentTimeInt64(sqlite3_vfs *a, sqlite3_int64 *b){
533fffadc52Sdan   return gMultiplex.pOrigVfs->xCurrentTimeInt64(gMultiplex.pOrigVfs, b);
534fffadc52Sdan }
535fffadc52Sdan 
5368a922f75Sshaneh /************************ I/O Method Wrappers *******************************/
5378a922f75Sshaneh 
5388a922f75Sshaneh /* xClose requests get passed through to the original VFS.
5398a922f75Sshaneh ** We loop over all open chunk handles and close them.
5408a922f75Sshaneh ** The group structure for this file is unlinked from
5418a922f75Sshaneh ** our list of groups and freed.
5428a922f75Sshaneh */
5438a922f75Sshaneh static int multiplexClose(sqlite3_file *pConn){
5448a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
5458a922f75Sshaneh   multiplexGroup *pGroup = p->pGroup;
5468a922f75Sshaneh   int rc = SQLITE_OK;
5478a922f75Sshaneh   int i;
5488a922f75Sshaneh   multiplexEnter();
549b5830294Sshaneh   /* close any open handles */
550d50deeebSshaneh   for(i=0; i<pGroup->nMaxChunks; i++){
5518a922f75Sshaneh     if( pGroup->bOpen[i] ){
5528a922f75Sshaneh       sqlite3_file *pSubOpen = pGroup->pReal[i];
5538a922f75Sshaneh       int rc2 = pSubOpen->pMethods->xClose(pSubOpen);
5548a922f75Sshaneh       if( rc2!=SQLITE_OK ) rc = rc2;
5558a922f75Sshaneh       pGroup->bOpen[i] = 0;
5568a922f75Sshaneh     }
5578a922f75Sshaneh   }
558b5830294Sshaneh   /* remove from linked list */
5598a922f75Sshaneh   if( pGroup->pNext ) pGroup->pNext->pPrev = pGroup->pPrev;
5608a922f75Sshaneh   if( pGroup->pPrev ){
5618a922f75Sshaneh     pGroup->pPrev->pNext = pGroup->pNext;
5628a922f75Sshaneh   }else{
5638a922f75Sshaneh     gMultiplex.pGroups = pGroup->pNext;
5648a922f75Sshaneh   }
5658a922f75Sshaneh   sqlite3_free(pGroup);
5668a922f75Sshaneh   multiplexLeave();
5678a922f75Sshaneh   return rc;
5688a922f75Sshaneh }
5698a922f75Sshaneh 
5708a922f75Sshaneh /* Pass xRead requests thru to the original VFS after
5718a922f75Sshaneh ** determining the correct chunk to operate on.
572fd1552f2Sshaneh ** Break up reads across chunk boundaries.
5738a922f75Sshaneh */
5748a922f75Sshaneh static int multiplexRead(
5758a922f75Sshaneh   sqlite3_file *pConn,
5768a922f75Sshaneh   void *pBuf,
5778a922f75Sshaneh   int iAmt,
5788a922f75Sshaneh   sqlite3_int64 iOfst
5798a922f75Sshaneh ){
5808a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
581d50deeebSshaneh   multiplexGroup *pGroup = p->pGroup;
5828a922f75Sshaneh   int rc = SQLITE_OK;
5838a922f75Sshaneh   multiplexEnter();
584c27fa4b0Sshaneh   if( !pGroup->bEnabled ){
585c27fa4b0Sshaneh     sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
586c27fa4b0Sshaneh     rc = ( !pSubOpen ) ? SQLITE_IOERR_READ : pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst);
587c27fa4b0Sshaneh   }else{
5888a922f75Sshaneh     while( iAmt > 0 ){
589d50deeebSshaneh       int i = (int)(iOfst / pGroup->nChunkSize);
5908a922f75Sshaneh       sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL);
5918a922f75Sshaneh       if( pSubOpen ){
592d50deeebSshaneh         int extra = ((int)(iOfst % pGroup->nChunkSize) + iAmt) - pGroup->nChunkSize;
5938a922f75Sshaneh         if( extra<0 ) extra = 0;
5948a922f75Sshaneh         iAmt -= extra;
595d50deeebSshaneh         rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst % pGroup->nChunkSize);
5968a922f75Sshaneh         if( rc!=SQLITE_OK ) break;
5978a922f75Sshaneh         pBuf = (char *)pBuf + iAmt;
5988a922f75Sshaneh         iOfst += iAmt;
5998a922f75Sshaneh         iAmt = extra;
6008a922f75Sshaneh       }else{
6018a922f75Sshaneh         rc = SQLITE_IOERR_READ;
6028a922f75Sshaneh         break;
6038a922f75Sshaneh       }
6048a922f75Sshaneh     }
605c27fa4b0Sshaneh   }
6068a922f75Sshaneh   multiplexLeave();
6078a922f75Sshaneh   return rc;
6088a922f75Sshaneh }
6098a922f75Sshaneh 
6108a922f75Sshaneh /* Pass xWrite requests thru to the original VFS after
6118a922f75Sshaneh ** determining the correct chunk to operate on.
612fd1552f2Sshaneh ** Break up writes across chunk boundaries.
6138a922f75Sshaneh */
6148a922f75Sshaneh static int multiplexWrite(
6158a922f75Sshaneh   sqlite3_file *pConn,
6168a922f75Sshaneh   const void *pBuf,
6178a922f75Sshaneh   int iAmt,
6188a922f75Sshaneh   sqlite3_int64 iOfst
6198a922f75Sshaneh ){
6208a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
621d50deeebSshaneh   multiplexGroup *pGroup = p->pGroup;
6228a922f75Sshaneh   int rc = SQLITE_OK;
6238a922f75Sshaneh   multiplexEnter();
624c27fa4b0Sshaneh   if( !pGroup->bEnabled ){
625c27fa4b0Sshaneh     sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
626c27fa4b0Sshaneh     rc = ( !pSubOpen ) ? SQLITE_IOERR_WRITE : pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst);
627c27fa4b0Sshaneh   }else{
6288a922f75Sshaneh     while( iAmt > 0 ){
629d50deeebSshaneh       int i = (int)(iOfst / pGroup->nChunkSize);
6308a922f75Sshaneh       sqlite3_file *pSubOpen = multiplexSubOpen(p, i, &rc, NULL);
6318a922f75Sshaneh       if( pSubOpen ){
632d50deeebSshaneh         int extra = ((int)(iOfst % pGroup->nChunkSize) + iAmt) - pGroup->nChunkSize;
6338a922f75Sshaneh         if( extra<0 ) extra = 0;
6348a922f75Sshaneh         iAmt -= extra;
635d50deeebSshaneh         rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst % pGroup->nChunkSize);
6368a922f75Sshaneh         if( rc!=SQLITE_OK ) break;
6378a922f75Sshaneh         pBuf = (char *)pBuf + iAmt;
6388a922f75Sshaneh         iOfst += iAmt;
6398a922f75Sshaneh         iAmt = extra;
6408a922f75Sshaneh       }else{
6418a922f75Sshaneh         rc = SQLITE_IOERR_WRITE;
6428a922f75Sshaneh         break;
6438a922f75Sshaneh       }
6448a922f75Sshaneh     }
645c27fa4b0Sshaneh   }
6468a922f75Sshaneh   multiplexLeave();
6478a922f75Sshaneh   return rc;
6488a922f75Sshaneh }
6498a922f75Sshaneh 
6508a922f75Sshaneh /* Pass xTruncate requests thru to the original VFS after
6518a922f75Sshaneh ** determining the correct chunk to operate on.  Delete any
6528a922f75Sshaneh ** chunks above the truncate mark.
6538a922f75Sshaneh */
6548a922f75Sshaneh static int multiplexTruncate(sqlite3_file *pConn, sqlite3_int64 size){
6558a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
6568a922f75Sshaneh   multiplexGroup *pGroup = p->pGroup;
6578a922f75Sshaneh   int rc = SQLITE_OK;
658c27fa4b0Sshaneh   multiplexEnter();
659c27fa4b0Sshaneh   if( !pGroup->bEnabled ){
660c27fa4b0Sshaneh     sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
661c27fa4b0Sshaneh     rc = ( !pSubOpen ) ? SQLITE_IOERR_TRUNCATE : pSubOpen->pMethods->xTruncate(pSubOpen, size);
662c27fa4b0Sshaneh   }else{
6638a922f75Sshaneh     int rc2;
6648a922f75Sshaneh     int i;
6658a922f75Sshaneh     sqlite3_file *pSubOpen;
6668a922f75Sshaneh     sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */
667050d09abSshaneh     memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1);
6688a922f75Sshaneh     /* delete the chunks above the truncate limit */
669d50deeebSshaneh     for(i=(int)(size / pGroup->nChunkSize)+1; i<pGroup->nMaxChunks; i++){
670fd1552f2Sshaneh       /* close any open chunks before deleting them */
6718a922f75Sshaneh       if( pGroup->bOpen[i] ){
6728a922f75Sshaneh         pSubOpen = pGroup->pReal[i];
6738a922f75Sshaneh         rc2 = pSubOpen->pMethods->xClose(pSubOpen);
6748a922f75Sshaneh         if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE;
6750596beecSshaneh         pGroup->bOpen[i] = 0;
6768a922f75Sshaneh       }
677050d09abSshaneh #ifdef SQLITE_MULTIPLEX_EXT_OVWR
6783801b65dSshaneh       sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1,
6793801b65dSshaneh           gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ,
6803801b65dSshaneh           SQLITE_MULTIPLEX_EXT_FMT, i);
681050d09abSshaneh #else
6823801b65dSshaneh       sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1,
6833801b65dSshaneh           gMultiplex.zName+pGroup->nName,
6843801b65dSshaneh           SQLITE_MULTIPLEX_EXT_FMT, i);
685050d09abSshaneh #endif
686d50deeebSshaneh       rc2 = pOrigVfs->xDelete(pOrigVfs, gMultiplex.zName, 0);
6878a922f75Sshaneh       if( rc2!=SQLITE_OK ) rc = SQLITE_IOERR_TRUNCATE;
6888a922f75Sshaneh     }
689d50deeebSshaneh     pSubOpen = multiplexSubOpen(p, (int)(size / pGroup->nChunkSize), &rc2, NULL);
6908a922f75Sshaneh     if( pSubOpen ){
691d50deeebSshaneh       rc2 = pSubOpen->pMethods->xTruncate(pSubOpen, size % pGroup->nChunkSize);
6928a922f75Sshaneh       if( rc2!=SQLITE_OK ) rc = rc2;
6938a922f75Sshaneh     }else{
6948a922f75Sshaneh       rc = SQLITE_IOERR_TRUNCATE;
6958a922f75Sshaneh     }
696c27fa4b0Sshaneh   }
6978a922f75Sshaneh   multiplexLeave();
6988a922f75Sshaneh   return rc;
6998a922f75Sshaneh }
7008a922f75Sshaneh 
7018a922f75Sshaneh /* Pass xSync requests through to the original VFS without change
7028a922f75Sshaneh */
7038a922f75Sshaneh static int multiplexSync(sqlite3_file *pConn, int flags){
7048a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
7058a922f75Sshaneh   multiplexGroup *pGroup = p->pGroup;
7068a922f75Sshaneh   int rc = SQLITE_OK;
7078a922f75Sshaneh   int i;
7088a922f75Sshaneh   multiplexEnter();
709d50deeebSshaneh   for(i=0; i<pGroup->nMaxChunks; i++){
7108a922f75Sshaneh     /* if we don't have it open, we don't need to sync it */
7118a922f75Sshaneh     if( pGroup->bOpen[i] ){
7128a922f75Sshaneh       sqlite3_file *pSubOpen = pGroup->pReal[i];
7138a922f75Sshaneh       int rc2 = pSubOpen->pMethods->xSync(pSubOpen, flags);
7148a922f75Sshaneh       if( rc2!=SQLITE_OK ) rc = rc2;
7158a922f75Sshaneh     }
7168a922f75Sshaneh   }
7178a922f75Sshaneh   multiplexLeave();
7188a922f75Sshaneh   return rc;
7198a922f75Sshaneh }
7208a922f75Sshaneh 
7218a922f75Sshaneh /* Pass xFileSize requests through to the original VFS.
7228a922f75Sshaneh ** Aggregate the size of all the chunks before returning.
7238a922f75Sshaneh */
7248a922f75Sshaneh static int multiplexFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){
7258a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
7268a922f75Sshaneh   multiplexGroup *pGroup = p->pGroup;
7278a922f75Sshaneh   int rc = SQLITE_OK;
7288a922f75Sshaneh   int rc2;
7298a922f75Sshaneh   int i;
7308a922f75Sshaneh   multiplexEnter();
731c27fa4b0Sshaneh   if( !pGroup->bEnabled ){
732c27fa4b0Sshaneh     sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
733c27fa4b0Sshaneh     rc = ( !pSubOpen ) ? SQLITE_IOERR_FSTAT : pSubOpen->pMethods->xFileSize(pSubOpen, pSize);
734c27fa4b0Sshaneh   }else{
7358a922f75Sshaneh     *pSize = 0;
736d50deeebSshaneh     for(i=0; i<pGroup->nMaxChunks; i++){
7378a922f75Sshaneh       sqlite3_file *pSubOpen = NULL;
7388a922f75Sshaneh       /* if not opened already, check to see if the chunk exists */
7398a922f75Sshaneh       if( pGroup->bOpen[i] ){
7408a922f75Sshaneh         pSubOpen = pGroup->pReal[i];
7418a922f75Sshaneh       }else{
7428a922f75Sshaneh         sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */
7438a922f75Sshaneh         int exists = 0;
744050d09abSshaneh         memcpy(gMultiplex.zName, pGroup->zName, pGroup->nName+1);
745050d09abSshaneh         if( i ){
746050d09abSshaneh #ifdef SQLITE_MULTIPLEX_EXT_OVWR
7473801b65dSshaneh           sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1,
7483801b65dSshaneh               gMultiplex.zName+pGroup->nName-SQLITE_MULTIPLEX_EXT_SZ,
7493801b65dSshaneh               SQLITE_MULTIPLEX_EXT_FMT, i);
750050d09abSshaneh #else
7513801b65dSshaneh           sqlite3_snprintf(SQLITE_MULTIPLEX_EXT_SZ+1,
7523801b65dSshaneh               gMultiplex.zName+pGroup->nName,
7533801b65dSshaneh               SQLITE_MULTIPLEX_EXT_FMT, i);
754050d09abSshaneh #endif
755050d09abSshaneh         }
7563801b65dSshaneh         rc2 = pOrigVfs->xAccess(pOrigVfs, gMultiplex.zName,
7573801b65dSshaneh             SQLITE_ACCESS_EXISTS, &exists);
7588a922f75Sshaneh         if( rc2==SQLITE_OK && exists){
7598a922f75Sshaneh           /* if it exists, open it */
7608a922f75Sshaneh           pSubOpen = multiplexSubOpen(p, i, &rc, NULL);
7618a922f75Sshaneh         }else{
7628a922f75Sshaneh           /* stop at first "gap" */
7638a922f75Sshaneh           break;
7648a922f75Sshaneh         }
7658a922f75Sshaneh       }
7668a922f75Sshaneh       if( pSubOpen ){
767fd1552f2Sshaneh         sqlite3_int64 sz;
7688a922f75Sshaneh         rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz);
7698a922f75Sshaneh         if( rc2!=SQLITE_OK ){
7708a922f75Sshaneh           rc = rc2;
7718a922f75Sshaneh         }else{
772d50deeebSshaneh           if( sz>pGroup->nChunkSize ){
7738fcfc344Sshaneh             rc = SQLITE_IOERR_FSTAT;
7748fcfc344Sshaneh           }
7758a922f75Sshaneh           *pSize += sz;
7768a922f75Sshaneh         }
7778a922f75Sshaneh       }else{
7788a922f75Sshaneh         break;
7798a922f75Sshaneh       }
7808a922f75Sshaneh     }
781c27fa4b0Sshaneh   }
7828a922f75Sshaneh   multiplexLeave();
7838a922f75Sshaneh   return rc;
7848a922f75Sshaneh }
7858a922f75Sshaneh 
7868a922f75Sshaneh /* Pass xLock requests through to the original VFS unchanged.
7878a922f75Sshaneh */
7888a922f75Sshaneh static int multiplexLock(sqlite3_file *pConn, int lock){
7898a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
7908a922f75Sshaneh   int rc;
7918a922f75Sshaneh   sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
7928a922f75Sshaneh   if( pSubOpen ){
7938a922f75Sshaneh     return pSubOpen->pMethods->xLock(pSubOpen, lock);
7948a922f75Sshaneh   }
7958a922f75Sshaneh   return SQLITE_BUSY;
7968a922f75Sshaneh }
7978a922f75Sshaneh 
7988a922f75Sshaneh /* Pass xUnlock requests through to the original VFS unchanged.
7998a922f75Sshaneh */
8008a922f75Sshaneh static int multiplexUnlock(sqlite3_file *pConn, int lock){
8018a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
8028a922f75Sshaneh   int rc;
8038a922f75Sshaneh   sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
8048a922f75Sshaneh   if( pSubOpen ){
8058a922f75Sshaneh     return pSubOpen->pMethods->xUnlock(pSubOpen, lock);
8068a922f75Sshaneh   }
8078a922f75Sshaneh   return SQLITE_IOERR_UNLOCK;
8088a922f75Sshaneh }
8098a922f75Sshaneh 
8108a922f75Sshaneh /* Pass xCheckReservedLock requests through to the original VFS unchanged.
8118a922f75Sshaneh */
8128a922f75Sshaneh static int multiplexCheckReservedLock(sqlite3_file *pConn, int *pResOut){
8138a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
8148a922f75Sshaneh   int rc;
8158a922f75Sshaneh   sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
8168a922f75Sshaneh   if( pSubOpen ){
8178a922f75Sshaneh     return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut);
8188a922f75Sshaneh   }
8198a922f75Sshaneh   return SQLITE_IOERR_CHECKRESERVEDLOCK;
8208a922f75Sshaneh }
8218a922f75Sshaneh 
822c27fa4b0Sshaneh /* Pass xFileControl requests through to the original VFS unchanged,
823c27fa4b0Sshaneh ** except for any MULTIPLEX_CTRL_* requests here.
8248a922f75Sshaneh */
8258a922f75Sshaneh static int multiplexFileControl(sqlite3_file *pConn, int op, void *pArg){
8268a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
827d50deeebSshaneh   multiplexGroup *pGroup = p->pGroup;
828d50deeebSshaneh   int rc = SQLITE_ERROR;
8298a922f75Sshaneh   sqlite3_file *pSubOpen;
830d50deeebSshaneh 
831d50deeebSshaneh   if( !gMultiplex.isInitialized ) return SQLITE_MISUSE;
832d50deeebSshaneh   switch( op ){
833d50deeebSshaneh     case MULTIPLEX_CTRL_ENABLE:
834d50deeebSshaneh       if( pArg ) {
835d50deeebSshaneh         int bEnabled = *(int *)pArg;
836d50deeebSshaneh         pGroup->bEnabled = bEnabled;
837d50deeebSshaneh         rc = SQLITE_OK;
838d50deeebSshaneh       }
839d50deeebSshaneh       break;
840d50deeebSshaneh     case MULTIPLEX_CTRL_SET_CHUNK_SIZE:
841d50deeebSshaneh       if( pArg ) {
842d50deeebSshaneh         int nChunkSize = *(int *)pArg;
84378c4de4cSshaneh         if( nChunkSize<1 ){
844d50deeebSshaneh           rc = SQLITE_MISUSE;
845d50deeebSshaneh         }else{
8463801b65dSshaneh           /* Round up to nearest multiple of MAX_PAGE_SIZE. */
8473801b65dSshaneh           nChunkSize = (nChunkSize + (MAX_PAGE_SIZE-1));
8483801b65dSshaneh           nChunkSize &= ~(MAX_PAGE_SIZE-1);
849d50deeebSshaneh           pGroup->nChunkSize = nChunkSize;
850d50deeebSshaneh           rc = SQLITE_OK;
851d50deeebSshaneh         }
852d50deeebSshaneh       }
853d50deeebSshaneh       break;
854d50deeebSshaneh     case MULTIPLEX_CTRL_SET_MAX_CHUNKS:
855d50deeebSshaneh       if( pArg ) {
856d50deeebSshaneh         int nMaxChunks = *(int *)pArg;
85778c4de4cSshaneh         if(( nMaxChunks<1 ) || ( nMaxChunks>SQLITE_MULTIPLEX_MAX_CHUNKS )){
858d50deeebSshaneh           rc = SQLITE_MISUSE;
859d50deeebSshaneh         }else{
860d50deeebSshaneh           pGroup->nMaxChunks = nMaxChunks;
861d50deeebSshaneh           rc = SQLITE_OK;
862d50deeebSshaneh         }
863d50deeebSshaneh       }
864d50deeebSshaneh       break;
865ac039688Sshaneh     case SQLITE_FCNTL_SIZE_HINT:
866ac039688Sshaneh     case SQLITE_FCNTL_CHUNK_SIZE:
867ac039688Sshaneh       /* no-op these */
868ac039688Sshaneh       rc = SQLITE_OK;
869ac039688Sshaneh       break;
870d50deeebSshaneh     default:
8718a922f75Sshaneh       pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
8728a922f75Sshaneh       if( pSubOpen ){
873d50deeebSshaneh         rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg);
8748a922f75Sshaneh       }
875d50deeebSshaneh       break;
876d50deeebSshaneh   }
877d50deeebSshaneh   return rc;
8788a922f75Sshaneh }
8798a922f75Sshaneh 
8808a922f75Sshaneh /* Pass xSectorSize requests through to the original VFS unchanged.
8818a922f75Sshaneh */
8828a922f75Sshaneh static int multiplexSectorSize(sqlite3_file *pConn){
8838a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
8848a922f75Sshaneh   int rc;
8858a922f75Sshaneh   sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
8868a922f75Sshaneh   if( pSubOpen ){
8878a922f75Sshaneh     return pSubOpen->pMethods->xSectorSize(pSubOpen);
8888a922f75Sshaneh   }
8893801b65dSshaneh   return DEFAULT_SECTOR_SIZE;
8908a922f75Sshaneh }
8918a922f75Sshaneh 
8928a922f75Sshaneh /* Pass xDeviceCharacteristics requests through to the original VFS unchanged.
8938a922f75Sshaneh */
8948a922f75Sshaneh static int multiplexDeviceCharacteristics(sqlite3_file *pConn){
8958a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
8968a922f75Sshaneh   int rc;
8978a922f75Sshaneh   sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
8988a922f75Sshaneh   if( pSubOpen ){
8998a922f75Sshaneh     return pSubOpen->pMethods->xDeviceCharacteristics(pSubOpen);
9008a922f75Sshaneh   }
9018a922f75Sshaneh   return 0;
9028a922f75Sshaneh }
9038a922f75Sshaneh 
9048a922f75Sshaneh /* Pass xShmMap requests through to the original VFS unchanged.
9058a922f75Sshaneh */
9068a922f75Sshaneh static int multiplexShmMap(
9078a922f75Sshaneh   sqlite3_file *pConn,            /* Handle open on database file */
9088a922f75Sshaneh   int iRegion,                    /* Region to retrieve */
9098a922f75Sshaneh   int szRegion,                   /* Size of regions */
9108a922f75Sshaneh   int bExtend,                    /* True to extend file if necessary */
9118a922f75Sshaneh   void volatile **pp              /* OUT: Mapped memory */
9128a922f75Sshaneh ){
9138a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
9148a922f75Sshaneh   int rc;
9158a922f75Sshaneh   sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
9168a922f75Sshaneh   if( pSubOpen ){
9178a922f75Sshaneh     return pSubOpen->pMethods->xShmMap(pSubOpen, iRegion, szRegion, bExtend, pp);
9188a922f75Sshaneh   }
9198a922f75Sshaneh   return SQLITE_IOERR;
9208a922f75Sshaneh }
9218a922f75Sshaneh 
9228a922f75Sshaneh /* Pass xShmLock requests through to the original VFS unchanged.
9238a922f75Sshaneh */
9248a922f75Sshaneh static int multiplexShmLock(
9258a922f75Sshaneh   sqlite3_file *pConn,       /* Database file holding the shared memory */
9268a922f75Sshaneh   int ofst,                  /* First lock to acquire or release */
9278a922f75Sshaneh   int n,                     /* Number of locks to acquire or release */
9288a922f75Sshaneh   int flags                  /* What to do with the lock */
9298a922f75Sshaneh ){
9308a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
9318a922f75Sshaneh   int rc;
9328a922f75Sshaneh   sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
9338a922f75Sshaneh   if( pSubOpen ){
9348a922f75Sshaneh     return pSubOpen->pMethods->xShmLock(pSubOpen, ofst, n, flags);
9358a922f75Sshaneh   }
9368a922f75Sshaneh   return SQLITE_BUSY;
9378a922f75Sshaneh }
9388a922f75Sshaneh 
9398a922f75Sshaneh /* Pass xShmBarrier requests through to the original VFS unchanged.
9408a922f75Sshaneh */
9418a922f75Sshaneh static void multiplexShmBarrier(sqlite3_file *pConn){
9428a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
9438a922f75Sshaneh   int rc;
9448a922f75Sshaneh   sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
9458a922f75Sshaneh   if( pSubOpen ){
9468a922f75Sshaneh     pSubOpen->pMethods->xShmBarrier(pSubOpen);
9478a922f75Sshaneh   }
9488a922f75Sshaneh }
9498a922f75Sshaneh 
9508a922f75Sshaneh /* Pass xShmUnmap requests through to the original VFS unchanged.
9518a922f75Sshaneh */
9528a922f75Sshaneh static int multiplexShmUnmap(sqlite3_file *pConn, int deleteFlag){
9538a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
9548a922f75Sshaneh   int rc;
9558a922f75Sshaneh   sqlite3_file *pSubOpen = multiplexSubOpen(p, 0, &rc, NULL);
9568a922f75Sshaneh   if( pSubOpen ){
9578a922f75Sshaneh     return pSubOpen->pMethods->xShmUnmap(pSubOpen, deleteFlag);
9588a922f75Sshaneh   }
9598a922f75Sshaneh   return SQLITE_OK;
9608a922f75Sshaneh }
9618a922f75Sshaneh 
9628a922f75Sshaneh /************************** Public Interfaces *****************************/
9638a922f75Sshaneh /*
96478c4de4cSshaneh ** CAPI: Initialize the multiplex VFS shim - sqlite3_multiplex_initialize()
96578c4de4cSshaneh **
96678c4de4cSshaneh ** Use the VFS named zOrigVfsName as the VFS that does the actual work.
96778c4de4cSshaneh ** Use the default if zOrigVfsName==NULL.
9688a922f75Sshaneh **
9698a922f75Sshaneh ** The multiplex VFS shim is named "multiplex".  It will become the default
9708a922f75Sshaneh ** VFS if makeDefault is non-zero.
9718a922f75Sshaneh **
9728a922f75Sshaneh ** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once
9738a922f75Sshaneh ** during start-up.
9748a922f75Sshaneh */
9758a922f75Sshaneh int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault){
9768a922f75Sshaneh   sqlite3_vfs *pOrigVfs;
9778a922f75Sshaneh   if( gMultiplex.isInitialized ) return SQLITE_MISUSE;
9788a922f75Sshaneh   pOrigVfs = sqlite3_vfs_find(zOrigVfsName);
9798a922f75Sshaneh   if( pOrigVfs==0 ) return SQLITE_ERROR;
9808a922f75Sshaneh   assert( pOrigVfs!=&gMultiplex.sThisVfs );
9818a922f75Sshaneh   gMultiplex.pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
9828a922f75Sshaneh   if( !gMultiplex.pMutex ){
9838a922f75Sshaneh     return SQLITE_NOMEM;
9848a922f75Sshaneh   }
985b5830294Sshaneh   gMultiplex.zName = sqlite3_malloc(pOrigVfs->mxPathname);
986b5830294Sshaneh   if( !gMultiplex.zName ){
987b5830294Sshaneh     sqlite3_mutex_free(gMultiplex.pMutex);
988b5830294Sshaneh     return SQLITE_NOMEM;
989b5830294Sshaneh   }
9908a922f75Sshaneh   gMultiplex.pGroups = NULL;
9918a922f75Sshaneh   gMultiplex.isInitialized = 1;
9928a922f75Sshaneh   gMultiplex.pOrigVfs = pOrigVfs;
9938a922f75Sshaneh   gMultiplex.sThisVfs = *pOrigVfs;
9948a922f75Sshaneh   gMultiplex.sThisVfs.szOsFile += sizeof(multiplexConn);
995d9523b97Sshaneh   gMultiplex.sThisVfs.zName = SQLITE_MULTIPLEX_VFS_NAME;
9968a922f75Sshaneh   gMultiplex.sThisVfs.xOpen = multiplexOpen;
997b5830294Sshaneh   gMultiplex.sThisVfs.xDelete = multiplexDelete;
998fffadc52Sdan   gMultiplex.sThisVfs.xAccess = multiplexAccess;
999fffadc52Sdan   gMultiplex.sThisVfs.xFullPathname = multiplexFullPathname;
1000fffadc52Sdan   gMultiplex.sThisVfs.xDlOpen = multiplexDlOpen;
1001fffadc52Sdan   gMultiplex.sThisVfs.xDlError = multiplexDlError;
1002fffadc52Sdan   gMultiplex.sThisVfs.xDlSym = multiplexDlSym;
1003fffadc52Sdan   gMultiplex.sThisVfs.xDlClose = multiplexDlClose;
1004fffadc52Sdan   gMultiplex.sThisVfs.xRandomness = multiplexRandomness;
1005fffadc52Sdan   gMultiplex.sThisVfs.xSleep = multiplexSleep;
1006fffadc52Sdan   gMultiplex.sThisVfs.xCurrentTime = multiplexCurrentTime;
1007fffadc52Sdan   gMultiplex.sThisVfs.xGetLastError = multiplexGetLastError;
1008fffadc52Sdan   gMultiplex.sThisVfs.xCurrentTimeInt64 = multiplexCurrentTimeInt64;
1009fffadc52Sdan 
10108a922f75Sshaneh   gMultiplex.sIoMethodsV1.iVersion = 1;
10118a922f75Sshaneh   gMultiplex.sIoMethodsV1.xClose = multiplexClose;
10128a922f75Sshaneh   gMultiplex.sIoMethodsV1.xRead = multiplexRead;
10138a922f75Sshaneh   gMultiplex.sIoMethodsV1.xWrite = multiplexWrite;
10148a922f75Sshaneh   gMultiplex.sIoMethodsV1.xTruncate = multiplexTruncate;
10158a922f75Sshaneh   gMultiplex.sIoMethodsV1.xSync = multiplexSync;
10168a922f75Sshaneh   gMultiplex.sIoMethodsV1.xFileSize = multiplexFileSize;
10178a922f75Sshaneh   gMultiplex.sIoMethodsV1.xLock = multiplexLock;
10188a922f75Sshaneh   gMultiplex.sIoMethodsV1.xUnlock = multiplexUnlock;
10198a922f75Sshaneh   gMultiplex.sIoMethodsV1.xCheckReservedLock = multiplexCheckReservedLock;
10208a922f75Sshaneh   gMultiplex.sIoMethodsV1.xFileControl = multiplexFileControl;
10218a922f75Sshaneh   gMultiplex.sIoMethodsV1.xSectorSize = multiplexSectorSize;
10228a922f75Sshaneh   gMultiplex.sIoMethodsV1.xDeviceCharacteristics = multiplexDeviceCharacteristics;
10238a922f75Sshaneh   gMultiplex.sIoMethodsV2 = gMultiplex.sIoMethodsV1;
10248a922f75Sshaneh   gMultiplex.sIoMethodsV2.iVersion = 2;
10258a922f75Sshaneh   gMultiplex.sIoMethodsV2.xShmMap = multiplexShmMap;
10268a922f75Sshaneh   gMultiplex.sIoMethodsV2.xShmLock = multiplexShmLock;
10278a922f75Sshaneh   gMultiplex.sIoMethodsV2.xShmBarrier = multiplexShmBarrier;
10288a922f75Sshaneh   gMultiplex.sIoMethodsV2.xShmUnmap = multiplexShmUnmap;
10298a922f75Sshaneh   sqlite3_vfs_register(&gMultiplex.sThisVfs, makeDefault);
1030d50deeebSshaneh 
1031d50deeebSshaneh   sqlite3_auto_extension((void*)multiplexFuncInit);
1032d50deeebSshaneh 
10338a922f75Sshaneh   return SQLITE_OK;
10348a922f75Sshaneh }
10358a922f75Sshaneh 
10368a922f75Sshaneh /*
103778c4de4cSshaneh ** CAPI: Shutdown the multiplex system - sqlite3_multiplex_shutdown()
10388a922f75Sshaneh **
10398a922f75Sshaneh ** All SQLite database connections must be closed before calling this
10408a922f75Sshaneh ** routine.
10418a922f75Sshaneh **
10428a922f75Sshaneh ** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once while
10438a922f75Sshaneh ** shutting down in order to free all remaining multiplex groups.
10448a922f75Sshaneh */
10458a922f75Sshaneh int sqlite3_multiplex_shutdown(void){
10468a922f75Sshaneh   if( gMultiplex.isInitialized==0 ) return SQLITE_MISUSE;
10478a922f75Sshaneh   if( gMultiplex.pGroups ) return SQLITE_MISUSE;
10488a922f75Sshaneh   gMultiplex.isInitialized = 0;
1049b5830294Sshaneh   sqlite3_free(gMultiplex.zName);
10508a922f75Sshaneh   sqlite3_mutex_free(gMultiplex.pMutex);
10518a922f75Sshaneh   sqlite3_vfs_unregister(&gMultiplex.sThisVfs);
10528a922f75Sshaneh   memset(&gMultiplex, 0, sizeof(gMultiplex));
10538a922f75Sshaneh   return SQLITE_OK;
10548a922f75Sshaneh }
10558a922f75Sshaneh 
10568a922f75Sshaneh /***************************** Test Code ***********************************/
10578a922f75Sshaneh #ifdef SQLITE_TEST
10588a922f75Sshaneh #include <tcl.h>
10593801b65dSshaneh extern const char *sqlite3TestErrorName(int);
10608a922f75Sshaneh 
10618a922f75Sshaneh 
10628a922f75Sshaneh /*
10638a922f75Sshaneh ** tclcmd: sqlite3_multiplex_initialize NAME MAKEDEFAULT
10648a922f75Sshaneh */
10658a922f75Sshaneh static int test_multiplex_initialize(
10668a922f75Sshaneh   void * clientData,
10678a922f75Sshaneh   Tcl_Interp *interp,
10688a922f75Sshaneh   int objc,
10698a922f75Sshaneh   Tcl_Obj *CONST objv[]
10708a922f75Sshaneh ){
10718a922f75Sshaneh   const char *zName;              /* Name of new multiplex VFS */
10728a922f75Sshaneh   int makeDefault;                /* True to make the new VFS the default */
10738a922f75Sshaneh   int rc;                         /* Value returned by multiplex_initialize() */
10748a922f75Sshaneh 
10758a922f75Sshaneh   UNUSED_PARAMETER(clientData);
10768a922f75Sshaneh 
10778a922f75Sshaneh   /* Process arguments */
10788a922f75Sshaneh   if( objc!=3 ){
10798a922f75Sshaneh     Tcl_WrongNumArgs(interp, 1, objv, "NAME MAKEDEFAULT");
10808a922f75Sshaneh     return TCL_ERROR;
10818a922f75Sshaneh   }
10828a922f75Sshaneh   zName = Tcl_GetString(objv[1]);
10838a922f75Sshaneh   if( Tcl_GetBooleanFromObj(interp, objv[2], &makeDefault) ) return TCL_ERROR;
10848a922f75Sshaneh   if( zName[0]=='\0' ) zName = 0;
10858a922f75Sshaneh 
10868a922f75Sshaneh   /* Call sqlite3_multiplex_initialize() */
10878a922f75Sshaneh   rc = sqlite3_multiplex_initialize(zName, makeDefault);
10888a922f75Sshaneh   Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
10898a922f75Sshaneh 
10908a922f75Sshaneh   return TCL_OK;
10918a922f75Sshaneh }
10928a922f75Sshaneh 
10938a922f75Sshaneh /*
10948a922f75Sshaneh ** tclcmd: sqlite3_multiplex_shutdown
10958a922f75Sshaneh */
10968a922f75Sshaneh static int test_multiplex_shutdown(
10978a922f75Sshaneh   void * clientData,
10988a922f75Sshaneh   Tcl_Interp *interp,
10998a922f75Sshaneh   int objc,
11008a922f75Sshaneh   Tcl_Obj *CONST objv[]
11018a922f75Sshaneh ){
11028a922f75Sshaneh   int rc;                         /* Value returned by multiplex_shutdown() */
11038a922f75Sshaneh 
11048a922f75Sshaneh   UNUSED_PARAMETER(clientData);
11058a922f75Sshaneh 
11068a922f75Sshaneh   if( objc!=1 ){
11078a922f75Sshaneh     Tcl_WrongNumArgs(interp, 1, objv, "");
11088a922f75Sshaneh     return TCL_ERROR;
11098a922f75Sshaneh   }
11108a922f75Sshaneh 
11118a922f75Sshaneh   /* Call sqlite3_multiplex_shutdown() */
11128a922f75Sshaneh   rc = sqlite3_multiplex_shutdown();
11138a922f75Sshaneh   Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
11148a922f75Sshaneh 
11158a922f75Sshaneh   return TCL_OK;
11168a922f75Sshaneh }
11178a922f75Sshaneh 
11188a922f75Sshaneh /*
11198a922f75Sshaneh ** tclcmd:  sqlite3_multiplex_dump
11208a922f75Sshaneh */
11218a922f75Sshaneh static int test_multiplex_dump(
11228a922f75Sshaneh   void * clientData,
11238a922f75Sshaneh   Tcl_Interp *interp,
11248a922f75Sshaneh   int objc,
11258a922f75Sshaneh   Tcl_Obj *CONST objv[]
11268a922f75Sshaneh ){
11278a922f75Sshaneh   Tcl_Obj *pResult;
11288a922f75Sshaneh   Tcl_Obj *pGroupTerm;
11298a922f75Sshaneh   multiplexGroup *pGroup;
11308a922f75Sshaneh   int i;
11318a922f75Sshaneh   int nChunks = 0;
11328a922f75Sshaneh 
11338a922f75Sshaneh   UNUSED_PARAMETER(clientData);
11348a922f75Sshaneh   UNUSED_PARAMETER(objc);
11358a922f75Sshaneh   UNUSED_PARAMETER(objv);
11368a922f75Sshaneh 
11378a922f75Sshaneh   pResult = Tcl_NewObj();
11388a922f75Sshaneh   multiplexEnter();
11398a922f75Sshaneh   for(pGroup=gMultiplex.pGroups; pGroup; pGroup=pGroup->pNext){
11408a922f75Sshaneh     pGroupTerm = Tcl_NewObj();
11418a922f75Sshaneh 
11428a922f75Sshaneh     pGroup->zName[pGroup->nName] = '\0';
11438a922f75Sshaneh     Tcl_ListObjAppendElement(interp, pGroupTerm,
11448a922f75Sshaneh           Tcl_NewStringObj(pGroup->zName, -1));
11458a922f75Sshaneh     Tcl_ListObjAppendElement(interp, pGroupTerm,
11468a922f75Sshaneh           Tcl_NewIntObj(pGroup->nName));
11478a922f75Sshaneh     Tcl_ListObjAppendElement(interp, pGroupTerm,
11488a922f75Sshaneh           Tcl_NewIntObj(pGroup->flags));
11498a922f75Sshaneh 
11508a922f75Sshaneh     /* count number of chunks with open handles */
1151d50deeebSshaneh     for(i=0; i<pGroup->nMaxChunks; i++){
11528a922f75Sshaneh       if( pGroup->bOpen[i] ) nChunks++;
11538a922f75Sshaneh     }
11548a922f75Sshaneh     Tcl_ListObjAppendElement(interp, pGroupTerm,
11558a922f75Sshaneh           Tcl_NewIntObj(nChunks));
11568a922f75Sshaneh 
11578a922f75Sshaneh     Tcl_ListObjAppendElement(interp, pGroupTerm,
1158d50deeebSshaneh           Tcl_NewIntObj(pGroup->nChunkSize));
11598a922f75Sshaneh     Tcl_ListObjAppendElement(interp, pGroupTerm,
1160d50deeebSshaneh           Tcl_NewIntObj(pGroup->nMaxChunks));
11618a922f75Sshaneh 
11628a922f75Sshaneh     Tcl_ListObjAppendElement(interp, pResult, pGroupTerm);
11638a922f75Sshaneh   }
11648a922f75Sshaneh   multiplexLeave();
11658a922f75Sshaneh   Tcl_SetObjResult(interp, pResult);
11668a922f75Sshaneh   return TCL_OK;
11678a922f75Sshaneh }
11688a922f75Sshaneh 
11698a922f75Sshaneh /*
1170d50deeebSshaneh ** Tclcmd: test_multiplex_control HANDLE DBNAME SUB-COMMAND ?INT-VALUE?
1171d50deeebSshaneh */
1172d50deeebSshaneh static int test_multiplex_control(
1173d50deeebSshaneh   ClientData cd,
1174d50deeebSshaneh   Tcl_Interp *interp,
1175d50deeebSshaneh   int objc,
1176d50deeebSshaneh   Tcl_Obj *CONST objv[]
1177d50deeebSshaneh ){
1178d50deeebSshaneh   int rc;                         /* Return code from file_control() */
1179d50deeebSshaneh   int idx;                        /* Index in aSub[] */
1180d50deeebSshaneh   Tcl_CmdInfo cmdInfo;            /* Command info structure for HANDLE */
1181d50deeebSshaneh   sqlite3 *db;                    /* Underlying db handle for HANDLE */
1182d50deeebSshaneh   int iValue = 0;
1183d50deeebSshaneh   void *pArg = 0;
1184d50deeebSshaneh 
1185d50deeebSshaneh   struct SubCommand {
1186d50deeebSshaneh     const char *zName;
1187d50deeebSshaneh     int op;
1188d50deeebSshaneh     int argtype;
1189d50deeebSshaneh   } aSub[] = {
1190d50deeebSshaneh     { "enable",       MULTIPLEX_CTRL_ENABLE,           1 },
1191d50deeebSshaneh     { "chunk_size",   MULTIPLEX_CTRL_SET_CHUNK_SIZE,   1 },
1192d50deeebSshaneh     { "max_chunks",   MULTIPLEX_CTRL_SET_MAX_CHUNKS,   1 },
1193d50deeebSshaneh     { 0, 0, 0 }
1194d50deeebSshaneh   };
1195d50deeebSshaneh 
1196c27fa4b0Sshaneh   if( objc!=5 ){
1197c27fa4b0Sshaneh     Tcl_WrongNumArgs(interp, 1, objv, "HANDLE DBNAME SUB-COMMAND INT-VALUE");
1198d50deeebSshaneh     return TCL_ERROR;
1199d50deeebSshaneh   }
1200d50deeebSshaneh 
1201d50deeebSshaneh   if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &cmdInfo) ){
1202d50deeebSshaneh     Tcl_AppendResult(interp, "expected database handle, got \"", 0);
1203d50deeebSshaneh     Tcl_AppendResult(interp, Tcl_GetString(objv[1]), "\"", 0);
1204d50deeebSshaneh     return TCL_ERROR;
1205d50deeebSshaneh   }else{
1206d50deeebSshaneh     db = *(sqlite3 **)cmdInfo.objClientData;
1207d50deeebSshaneh   }
1208d50deeebSshaneh 
1209d50deeebSshaneh   rc = Tcl_GetIndexFromObjStruct(
1210d50deeebSshaneh       interp, objv[3], aSub, sizeof(aSub[0]), "sub-command", 0, &idx
1211d50deeebSshaneh   );
1212d50deeebSshaneh   if( rc!=TCL_OK ) return rc;
1213d50deeebSshaneh 
1214d50deeebSshaneh   switch( aSub[idx].argtype ){
1215d50deeebSshaneh     case 1:
1216d50deeebSshaneh       if( Tcl_GetIntFromObj(interp, objv[4], &iValue) ){
1217d50deeebSshaneh         return TCL_ERROR;
1218d50deeebSshaneh       }
1219d50deeebSshaneh       pArg = (void *)&iValue;
1220d50deeebSshaneh       break;
1221d50deeebSshaneh     default:
1222d50deeebSshaneh       Tcl_WrongNumArgs(interp, 4, objv, "SUB-COMMAND");
1223d50deeebSshaneh       return TCL_ERROR;
1224d50deeebSshaneh   }
1225d50deeebSshaneh 
1226d50deeebSshaneh   rc = sqlite3_file_control(db, Tcl_GetString(objv[2]), aSub[idx].op, pArg);
1227d50deeebSshaneh   Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_STATIC);
1228d50deeebSshaneh   return (rc==SQLITE_OK) ? TCL_OK : TCL_ERROR;
1229d50deeebSshaneh }
1230d50deeebSshaneh 
1231d50deeebSshaneh /*
12328a922f75Sshaneh ** This routine registers the custom TCL commands defined in this
12338a922f75Sshaneh ** module.  This should be the only procedure visible from outside
12348a922f75Sshaneh ** of this module.
12358a922f75Sshaneh */
12368a922f75Sshaneh int Sqlitemultiplex_Init(Tcl_Interp *interp){
12378a922f75Sshaneh   static struct {
12388a922f75Sshaneh      char *zName;
12398a922f75Sshaneh      Tcl_ObjCmdProc *xProc;
12408a922f75Sshaneh   } aCmd[] = {
12418a922f75Sshaneh     { "sqlite3_multiplex_initialize", test_multiplex_initialize },
12428a922f75Sshaneh     { "sqlite3_multiplex_shutdown", test_multiplex_shutdown },
12438a922f75Sshaneh     { "sqlite3_multiplex_dump", test_multiplex_dump },
1244d50deeebSshaneh     { "sqlite3_multiplex_control", test_multiplex_control },
12458a922f75Sshaneh   };
12468a922f75Sshaneh   int i;
12478a922f75Sshaneh 
12488a922f75Sshaneh   for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
12498a922f75Sshaneh     Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
12508a922f75Sshaneh   }
12518a922f75Sshaneh 
12528a922f75Sshaneh   return TCL_OK;
12538a922f75Sshaneh }
12548a922f75Sshaneh #endif
1255