xref: /sqlite-3.40.0/src/test_multiplex.c (revision 6da7cc9b)
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
14a12b6fa3Sdrh ** pager and the real VFS - that breaks up a very large database file
15a12b6fa3Sdrh ** into two or more smaller files on disk.  This is useful, for example,
16a12b6fa3Sdrh ** in order to support large, multi-gigabyte databases on older filesystems
17a12b6fa3Sdrh ** that limit the maximum file size to 2 GiB.
188a922f75Sshaneh **
19a12b6fa3Sdrh ** USAGE:
208a922f75Sshaneh **
21a12b6fa3Sdrh ** Compile this source file and link it with your application.  Then
22a12b6fa3Sdrh ** at start-time, invoke the following procedure:
23a12b6fa3Sdrh **
24a12b6fa3Sdrh **   int sqlite3_multiplex_initialize(
25a12b6fa3Sdrh **      const char *zOrigVfsName,    // The underlying real VFS
26a12b6fa3Sdrh **      int makeDefault              // True to make multiplex the default VFS
27a12b6fa3Sdrh **   );
28a12b6fa3Sdrh **
29a12b6fa3Sdrh ** The procedure call above will create and register a new VFS shim named
30a12b6fa3Sdrh ** "multiplex".  The multiplex VFS will use the VFS named by zOrigVfsName to
31a12b6fa3Sdrh ** do the actual disk I/O.  (The zOrigVfsName parameter may be NULL, in
32a12b6fa3Sdrh ** which case the default VFS at the moment sqlite3_multiplex_initialize()
33a12b6fa3Sdrh ** is called will be used as the underlying real VFS.)
34a12b6fa3Sdrh **
35a12b6fa3Sdrh ** If the makeDefault parameter is TRUE then multiplex becomes the new
36a12b6fa3Sdrh ** default VFS.  Otherwise, you can use the multiplex VFS by specifying
37a12b6fa3Sdrh ** "multiplex" as the 4th parameter to sqlite3_open_v2() or by employing
38a12b6fa3Sdrh ** URI filenames and adding "vfs=multiplex" as a parameter to the filename
39a12b6fa3Sdrh ** URI.
40a12b6fa3Sdrh **
41a12b6fa3Sdrh ** The multiplex VFS allows databases up to 32 GiB in size.  But it splits
42ad2148daSdrh ** the files up into smaller pieces, so that they will work even on
43ad2148daSdrh ** filesystems that do not support large files.  The default chunk size
44ad2148daSdrh ** is 2147418112 bytes (which is 64KiB less than 2GiB) but this can be
45ad2148daSdrh ** changed at compile-time by defining the SQLITE_MULTIPLEX_CHUNK_SIZE
46ad2148daSdrh ** macro.  Use the "chunksize=NNNN" query parameter with a URI filename
47ad2148daSdrh ** in order to select an alternative chunk size for individual connections
48ad2148daSdrh ** at run-time.
498a922f75Sshaneh */
508a922f75Sshaneh #include "sqlite3.h"
518a922f75Sshaneh #include <string.h>
528a922f75Sshaneh #include <assert.h>
536f25e89dSdrh #include <stdlib.h>
54d50deeebSshaneh #include "test_multiplex.h"
55d50deeebSshaneh 
5678c4de4cSshaneh #ifndef SQLITE_CORE
5778c4de4cSshaneh   #define SQLITE_CORE 1  /* Disable the API redefinition in sqlite3ext.h */
5878c4de4cSshaneh #endif
5978c4de4cSshaneh #include "sqlite3ext.h"
6078c4de4cSshaneh 
61bb201344Sshaneh /*
623801b65dSshaneh ** These should be defined to be the same as the values in
6348864df9Smistachkin ** sqliteInt.h.  They are defined separately here so that
643801b65dSshaneh ** the multiplex VFS shim can be built as a loadable
653801b65dSshaneh ** module.
663801b65dSshaneh */
673801b65dSshaneh #define UNUSED_PARAMETER(x) (void)(x)
683801b65dSshaneh #define MAX_PAGE_SIZE       0x10000
693801b65dSshaneh #define DEFAULT_SECTOR_SIZE 0x1000
703801b65dSshaneh 
715fb96aafSdrh /* Maximum chunk number */
725fb96aafSdrh #define MX_CHUNK_NUMBER 299
735fb96aafSdrh 
7443a6d4bdSdrh /* First chunk for rollback journal files */
7527e69643Sdan #define SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET 400
765fb96aafSdrh #define SQLITE_MULTIPLEX_WAL_8_3_OFFSET 700
7727e69643Sdan 
78bb201344Sshaneh 
798a922f75Sshaneh /************************ Shim Definitions ******************************/
808a922f75Sshaneh 
816f25e89dSdrh #ifndef SQLITE_MULTIPLEX_VFS_NAME
82d9523b97Sshaneh # define SQLITE_MULTIPLEX_VFS_NAME "multiplex"
836f25e89dSdrh #endif
84d9523b97Sshaneh 
85050d09abSshaneh /* This is the limit on the chunk size.  It may be changed by calling
8678c4de4cSshaneh ** the xFileControl() interface.  It will be rounded up to a
876f25e89dSdrh ** multiple of MAX_PAGE_SIZE.  We default it here to 2GiB less 64KiB.
88050d09abSshaneh */
896f25e89dSdrh #ifndef SQLITE_MULTIPLEX_CHUNK_SIZE
906f25e89dSdrh # define SQLITE_MULTIPLEX_CHUNK_SIZE 2147418112
916f25e89dSdrh #endif
92c27fa4b0Sshaneh 
9373795becSdrh /* This used to be the default limit on number of chunks, but
9473795becSdrh ** it is no longer enforced. There is currently no limit to the
9573795becSdrh ** number of chunks.
9673795becSdrh **
9773795becSdrh ** May be changed by calling the xFileControl() interface.
98050d09abSshaneh */
996f25e89dSdrh #ifndef SQLITE_MULTIPLEX_MAX_CHUNKS
10073795becSdrh # define SQLITE_MULTIPLEX_MAX_CHUNKS 12
1016f25e89dSdrh #endif
102050d09abSshaneh 
1038a922f75Sshaneh /************************ Object Definitions ******************************/
1048a922f75Sshaneh 
1058a922f75Sshaneh /* Forward declaration of all object types */
1068a922f75Sshaneh typedef struct multiplexGroup multiplexGroup;
1078a922f75Sshaneh typedef struct multiplexConn multiplexConn;
1088a922f75Sshaneh 
1098a922f75Sshaneh /*
1108a922f75Sshaneh ** A "multiplex group" is a collection of files that collectively
1118a922f75Sshaneh ** makeup a single SQLite DB file.  This allows the size of the DB
1128a922f75Sshaneh ** to exceed the limits imposed by the file system.
1138a922f75Sshaneh **
1148a922f75Sshaneh ** There is an instance of the following object for each defined multiplex
1158a922f75Sshaneh ** group.
1168a922f75Sshaneh */
1178a922f75Sshaneh struct multiplexGroup {
118f3717af4Sdrh   struct multiplexReal {           /* For each chunk */
119f3717af4Sdrh     sqlite3_file *p;                  /* Handle for the chunk */
120f3717af4Sdrh     char *z;                          /* Name of this chunk */
121f3717af4Sdrh   } *aReal;                        /* list of all chunks */
122f3717af4Sdrh   int nReal;                       /* Number of chunks */
1238a922f75Sshaneh   char *zName;                     /* Base filename of this group */
1248a922f75Sshaneh   int nName;                       /* Length of base filename */
1258a922f75Sshaneh   int flags;                       /* Flags used for original opening */
126e6deb204Sdrh   unsigned int szChunk;            /* Chunk size used for this group */
127e7d9f13dSdrh   unsigned char bEnabled;          /* TRUE to use Multiplex VFS for this file */
128e7d9f13dSdrh   unsigned char bTruncate;         /* TRUE to enable truncation of databases */
1298a922f75Sshaneh };
1308a922f75Sshaneh 
1318a922f75Sshaneh /*
1328a922f75Sshaneh ** An instance of the following object represents each open connection
1338a922f75Sshaneh ** to a file that is multiplex'ed.  This object is a
1348a922f75Sshaneh ** subclass of sqlite3_file.  The sqlite3_file object for the underlying
1358a922f75Sshaneh ** VFS is appended to this structure.
1368a922f75Sshaneh */
1378a922f75Sshaneh struct multiplexConn {
1388a922f75Sshaneh   sqlite3_file base;              /* Base class - must be first */
1398a922f75Sshaneh   multiplexGroup *pGroup;         /* The underlying group of files */
1408a922f75Sshaneh };
1418a922f75Sshaneh 
1428a922f75Sshaneh /************************* Global Variables **********************************/
1438a922f75Sshaneh /*
1448a922f75Sshaneh ** All global variables used by this file are containing within the following
1458a922f75Sshaneh ** gMultiplex structure.
1468a922f75Sshaneh */
1478a922f75Sshaneh static struct {
1488a922f75Sshaneh   /* The pOrigVfs is the real, original underlying VFS implementation.
1498a922f75Sshaneh   ** Most operations pass-through to the real VFS.  This value is read-only
1508a922f75Sshaneh   ** during operation.  It is only modified at start-time and thus does not
1518a922f75Sshaneh   ** require a mutex.
1528a922f75Sshaneh   */
1538a922f75Sshaneh   sqlite3_vfs *pOrigVfs;
1548a922f75Sshaneh 
1558a922f75Sshaneh   /* The sThisVfs is the VFS structure used by this shim.  It is initialized
1568a922f75Sshaneh   ** at start-time and thus does not require a mutex
1578a922f75Sshaneh   */
1588a922f75Sshaneh   sqlite3_vfs sThisVfs;
1598a922f75Sshaneh 
1608a922f75Sshaneh   /* The sIoMethods defines the methods used by sqlite3_file objects
1618a922f75Sshaneh   ** associated with this shim.  It is initialized at start-time and does
1628a922f75Sshaneh   ** not require a mutex.
1638a922f75Sshaneh   **
1648a922f75Sshaneh   ** When the underlying VFS is called to open a file, it might return
1658a922f75Sshaneh   ** either a version 1 or a version 2 sqlite3_file object.  This shim
1668a922f75Sshaneh   ** has to create a wrapper sqlite3_file of the same version.  Hence
1678a922f75Sshaneh   ** there are two I/O method structures, one for version 1 and the other
1688a922f75Sshaneh   ** for version 2.
1698a922f75Sshaneh   */
1708a922f75Sshaneh   sqlite3_io_methods sIoMethodsV1;
1718a922f75Sshaneh   sqlite3_io_methods sIoMethodsV2;
1728a922f75Sshaneh 
173fd1552f2Sshaneh   /* True when this shim has been initialized.
1748a922f75Sshaneh   */
1758a922f75Sshaneh   int isInitialized;
1768a922f75Sshaneh } gMultiplex;
1778a922f75Sshaneh 
1788a922f75Sshaneh /************************* Utility Routines *********************************/
1798a922f75Sshaneh /*
1803801b65dSshaneh ** Compute a string length that is limited to what can be stored in
1813801b65dSshaneh ** lower 30 bits of a 32-bit signed integer.
1823801b65dSshaneh **
1833801b65dSshaneh ** The value returned will never be negative.  Nor will it ever be greater
1843801b65dSshaneh ** than the actual length of the string.  For very long strings (greater
1853801b65dSshaneh ** than 1GiB) the value returned might be less than the true string length.
1863801b65dSshaneh */
multiplexStrlen30(const char * z)187cc4e19beSshaneh static int multiplexStrlen30(const char *z){
1883801b65dSshaneh   const char *z2 = z;
1893801b65dSshaneh   if( z==0 ) return 0;
1903801b65dSshaneh   while( *z2 ){ z2++; }
1913801b65dSshaneh   return 0x3fffffff & (int)(z2 - z);
1923801b65dSshaneh }
1933801b65dSshaneh 
194e712b582Sdan /*
195e712b582Sdan ** Generate the file-name for chunk iChunk of the group with base name
196e712b582Sdan ** zBase. The file-name is written to buffer zOut before returning. Buffer
197730f85a3Sdan ** zOut must be allocated by the caller so that it is at least (nBase+5)
198e712b582Sdan ** bytes in size, where nBase is the length of zBase, not including the
199e712b582Sdan ** nul-terminator.
200730f85a3Sdan **
201730f85a3Sdan ** If iChunk is 0 (or 400 - the number for the first journal file chunk),
202730f85a3Sdan ** the output is a copy of the input string. Otherwise, if
203730f85a3Sdan ** SQLITE_ENABLE_8_3_NAMES is not defined or the input buffer does not contain
204730f85a3Sdan ** a "." character, then the output is a copy of the input string with the
205730f85a3Sdan ** three-digit zero-padded decimal representation if iChunk appended to it.
206730f85a3Sdan ** For example:
207730f85a3Sdan **
208730f85a3Sdan **   zBase="test.db", iChunk=4  ->  zOut="test.db004"
209730f85a3Sdan **
210730f85a3Sdan ** Or, if SQLITE_ENABLE_8_3_NAMES is defined and the input buffer contains
211730f85a3Sdan ** a "." character, then everything after the "." is replaced by the
212730f85a3Sdan ** three-digit representation of iChunk.
213730f85a3Sdan **
214730f85a3Sdan **   zBase="test.db", iChunk=4  ->  zOut="test.004"
215730f85a3Sdan **
216730f85a3Sdan ** The output buffer string is terminated by 2 0x00 bytes. This makes it safe
217730f85a3Sdan ** to pass to sqlite3_uri_parameter() and similar.
218e712b582Sdan */
multiplexFilename(const char * zBase,int nBase,int flags,int iChunk,char * zOut)219e712b582Sdan static void multiplexFilename(
220e712b582Sdan   const char *zBase,              /* Filename for chunk 0 */
221e712b582Sdan   int nBase,                      /* Size of zBase in bytes (without \0) */
222e712b582Sdan   int flags,                      /* Flags used to open file */
223e712b582Sdan   int iChunk,                     /* Chunk to generate filename for */
224e712b582Sdan   char *zOut                      /* Buffer to write generated name to */
225e712b582Sdan ){
226e712b582Sdan   int n = nBase;
227730f85a3Sdan   memcpy(zOut, zBase, n+1);
2285fb96aafSdrh   if( iChunk!=0 && iChunk<=MX_CHUNK_NUMBER ){
229e712b582Sdan #ifdef SQLITE_ENABLE_8_3_NAMES
230e712b582Sdan     int i;
231e712b582Sdan     for(i=n-1; i>0 && i>=n-4 && zOut[i]!='.'; i--){}
232e712b582Sdan     if( i>=n-4 ) n = i+1;
2335fb96aafSdrh     if( flags & SQLITE_OPEN_MAIN_JOURNAL ){
234e712b582Sdan       /* The extensions on overflow files for main databases are 001, 002,
235e712b582Sdan       ** 003 and so forth.  To avoid name collisions, add 400 to the
236e712b582Sdan       ** extensions of journal files so that they are 401, 402, 403, ....
237e712b582Sdan       */
238e712b582Sdan       iChunk += SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET;
2395fb96aafSdrh     }else if( flags & SQLITE_OPEN_WAL ){
2405fb96aafSdrh       /* To avoid name collisions, add 700 to the
2415fb96aafSdrh       ** extensions of WAL files so that they are 701, 702, 703, ....
2425fb96aafSdrh       */
2435fb96aafSdrh       iChunk += SQLITE_MULTIPLEX_WAL_8_3_OFFSET;
244e712b582Sdan     }
245e712b582Sdan #endif
246e712b582Sdan     sqlite3_snprintf(4,&zOut[n],"%03d",iChunk);
247730f85a3Sdan     n += 3;
248e712b582Sdan   }
249730f85a3Sdan 
250730f85a3Sdan   assert( zOut[n]=='\0' );
251730f85a3Sdan   zOut[n+1] = '\0';
252e712b582Sdan }
253e712b582Sdan 
254f3717af4Sdrh /* Compute the filename for the iChunk-th chunk
255f3717af4Sdrh */
multiplexSubFilename(multiplexGroup * pGroup,int iChunk)256f3717af4Sdrh static int multiplexSubFilename(multiplexGroup *pGroup, int iChunk){
257f3717af4Sdrh   if( iChunk>=pGroup->nReal ){
258f3717af4Sdrh     struct multiplexReal *p;
259f3cdcdccSdrh     p = sqlite3_realloc64(pGroup->aReal, (iChunk+1)*sizeof(*p));
260f3717af4Sdrh     if( p==0 ){
261f3717af4Sdrh       return SQLITE_NOMEM;
262f3717af4Sdrh     }
263f3717af4Sdrh     memset(&p[pGroup->nReal], 0, sizeof(p[0])*(iChunk+1-pGroup->nReal));
264f3717af4Sdrh     pGroup->aReal = p;
265f3717af4Sdrh     pGroup->nReal = iChunk+1;
266f3717af4Sdrh   }
2675b1626aaSdrh   if( pGroup->zName && pGroup->aReal[iChunk].z==0 ){
268f3717af4Sdrh     char *z;
269f3717af4Sdrh     int n = pGroup->nName;
2704defdddcSdrh     z = sqlite3_malloc64( n+5 );
271f3717af4Sdrh     if( z==0 ){
272f3717af4Sdrh       return SQLITE_NOMEM;
273f3717af4Sdrh     }
274e712b582Sdan     multiplexFilename(pGroup->zName, pGroup->nName, pGroup->flags, iChunk, z);
275*6da7cc9bSdrh     pGroup->aReal[iChunk].z = (char*)sqlite3_create_filename(z,"","",0,0);
2764defdddcSdrh     sqlite3_free(z);
2774defdddcSdrh     if( pGroup->aReal[iChunk].z==0 ) return SQLITE_NOMEM;
278f3717af4Sdrh   }
279f3717af4Sdrh   return SQLITE_OK;
280f3717af4Sdrh }
281f3717af4Sdrh 
2828a922f75Sshaneh /* Translate an sqlite3_file* that is really a multiplexGroup* into
2838a922f75Sshaneh ** the sqlite3_file* for the underlying original VFS.
284e7d9f13dSdrh **
285e7d9f13dSdrh ** For chunk 0, the pGroup->flags determines whether or not a new file
286e7d9f13dSdrh ** is created if it does not already exist.  For chunks 1 and higher, the
287e7d9f13dSdrh ** file is created only if createFlag is 1.
2888a922f75Sshaneh */
multiplexSubOpen(multiplexGroup * pGroup,int iChunk,int * rc,int * pOutFlags,int createFlag)2898efdb732Sdrh static sqlite3_file *multiplexSubOpen(
290e7d9f13dSdrh   multiplexGroup *pGroup,    /* The multiplexor group */
291e7d9f13dSdrh   int iChunk,                /* Which chunk to open.  0==original file */
292e7d9f13dSdrh   int *rc,                   /* Result code in and out */
293e7d9f13dSdrh   int *pOutFlags,            /* Output flags */
294e7d9f13dSdrh   int createFlag             /* True to create if iChunk>0 */
2958efdb732Sdrh ){
296f3717af4Sdrh   sqlite3_file *pSubOpen = 0;
2978a922f75Sshaneh   sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;        /* Real VFS */
29827e69643Sdan 
29927e69643Sdan #ifdef SQLITE_ENABLE_8_3_NAMES
30043a6d4bdSdrh   /* If JOURNAL_8_3_OFFSET is set to (say) 400, then any overflow files are
30143a6d4bdSdrh   ** part of a database journal are named db.401, db.402, and so on. A
30243a6d4bdSdrh   ** database may therefore not grow to larger than 400 chunks. Attempting
30343a6d4bdSdrh   ** to open chunk 401 indicates the database is full. */
30427e69643Sdan   if( iChunk>=SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET ){
305689453dbSdrh     sqlite3_log(SQLITE_FULL, "multiplexed chunk overflow: %s", pGroup->zName);
30627e69643Sdan     *rc = SQLITE_FULL;
30727e69643Sdan     return 0;
30827e69643Sdan   }
30927e69643Sdan #endif
31027e69643Sdan 
311f3717af4Sdrh   *rc = multiplexSubFilename(pGroup, iChunk);
312f3717af4Sdrh   if( (*rc)==SQLITE_OK && (pSubOpen = pGroup->aReal[iChunk].p)==0 ){
31306999667Sdrh     int flags, bExists;
314e7d9f13dSdrh     flags = pGroup->flags;
315e7d9f13dSdrh     if( createFlag ){
316e7d9f13dSdrh       flags |= SQLITE_OPEN_CREATE;
3178c24a369Sdrh     }else if( iChunk==0 ){
3188c24a369Sdrh       /* Fall through */
319e7d9f13dSdrh     }else if( pGroup->aReal[iChunk].z==0 ){
320e7d9f13dSdrh       return 0;
321e7d9f13dSdrh     }else{
322e7d9f13dSdrh       *rc = pOrigVfs->xAccess(pOrigVfs, pGroup->aReal[iChunk].z,
323e7d9f13dSdrh                               SQLITE_ACCESS_EXISTS, &bExists);
3241779efd4Sdrh      if( *rc || !bExists ){
3251779efd4Sdrh         if( *rc ){
3261779efd4Sdrh           sqlite3_log(*rc, "multiplexor.xAccess failure on %s",
3271779efd4Sdrh                       pGroup->aReal[iChunk].z);
3281779efd4Sdrh         }
3291779efd4Sdrh         return 0;
3301779efd4Sdrh       }
331e7d9f13dSdrh       flags &= ~SQLITE_OPEN_CREATE;
332e7d9f13dSdrh     }
333f3cdcdccSdrh     pSubOpen = sqlite3_malloc64( pOrigVfs->szOsFile );
334f3717af4Sdrh     if( pSubOpen==0 ){
3358c24a369Sdrh       *rc = SQLITE_IOERR_NOMEM;
336f3717af4Sdrh       return 0;
337050d09abSshaneh     }
338f3717af4Sdrh     pGroup->aReal[iChunk].p = pSubOpen;
33906999667Sdrh     *rc = pOrigVfs->xOpen(pOrigVfs, pGroup->aReal[iChunk].z, pSubOpen,
340e7d9f13dSdrh                           flags, pOutFlags);
34106999667Sdrh     if( (*rc)!=SQLITE_OK ){
3421779efd4Sdrh       sqlite3_log(*rc, "multiplexor.xOpen failure on %s",
3431779efd4Sdrh                   pGroup->aReal[iChunk].z);
344f3717af4Sdrh       sqlite3_free(pSubOpen);
345f3717af4Sdrh       pGroup->aReal[iChunk].p = 0;
346f3717af4Sdrh       return 0;
347f3717af4Sdrh     }
348f3717af4Sdrh   }
3498a922f75Sshaneh   return pSubOpen;
3508a922f75Sshaneh }
3518a922f75Sshaneh 
3523801b65dSshaneh /*
353e7d9f13dSdrh ** Return the size, in bytes, of chunk number iChunk.  If that chunk
354e7d9f13dSdrh ** does not exist, then return 0.  This function does not distingish between
355e7d9f13dSdrh ** non-existant files and zero-length files.
356e7d9f13dSdrh */
multiplexSubSize(multiplexGroup * pGroup,int iChunk,int * rc)357e7d9f13dSdrh static sqlite3_int64 multiplexSubSize(
358e7d9f13dSdrh   multiplexGroup *pGroup,    /* The multiplexor group */
359e7d9f13dSdrh   int iChunk,                /* Which chunk to open.  0==original file */
360e7d9f13dSdrh   int *rc                    /* Result code in and out */
361e7d9f13dSdrh ){
362e7d9f13dSdrh   sqlite3_file *pSub;
363e7d9f13dSdrh   sqlite3_int64 sz = 0;
364e7d9f13dSdrh 
365b04f3c15Sdrh   if( *rc ) return 0;
366e7d9f13dSdrh   pSub = multiplexSubOpen(pGroup, iChunk, rc, NULL, 0);
367e7d9f13dSdrh   if( pSub==0 ) return 0;
368e7d9f13dSdrh   *rc = pSub->pMethods->xFileSize(pSub, &sz);
369e7d9f13dSdrh   return sz;
370e7d9f13dSdrh }
371e7d9f13dSdrh 
372e7d9f13dSdrh /*
3733801b65dSshaneh ** This is the implementation of the multiplex_control() SQL function.
3743801b65dSshaneh */
multiplexControlFunc(sqlite3_context * context,int argc,sqlite3_value ** argv)375d50deeebSshaneh static void multiplexControlFunc(
376d50deeebSshaneh   sqlite3_context *context,
377d50deeebSshaneh   int argc,
378d50deeebSshaneh   sqlite3_value **argv
379d50deeebSshaneh ){
38078c4de4cSshaneh   int rc = SQLITE_OK;
381d8ce22bbSshaneh   sqlite3 *db = sqlite3_context_db_handle(context);
3827bb22ac7Smistachkin   int op = 0;
383d8ce22bbSshaneh   int iVal;
384d8ce22bbSshaneh 
385d8ce22bbSshaneh   if( !db || argc!=2 ){
386d8ce22bbSshaneh     rc = SQLITE_ERROR;
387d8ce22bbSshaneh   }else{
388d8ce22bbSshaneh     /* extract params */
389d8ce22bbSshaneh     op = sqlite3_value_int(argv[0]);
390d8ce22bbSshaneh     iVal = sqlite3_value_int(argv[1]);
391d8ce22bbSshaneh     /* map function op to file_control op */
39278c4de4cSshaneh     switch( op ){
39378c4de4cSshaneh       case 1:
39478c4de4cSshaneh         op = MULTIPLEX_CTRL_ENABLE;
39578c4de4cSshaneh         break;
39678c4de4cSshaneh       case 2:
39778c4de4cSshaneh         op = MULTIPLEX_CTRL_SET_CHUNK_SIZE;
39878c4de4cSshaneh         break;
39978c4de4cSshaneh       case 3:
40078c4de4cSshaneh         op = MULTIPLEX_CTRL_SET_MAX_CHUNKS;
40178c4de4cSshaneh         break;
40278c4de4cSshaneh       default:
4033801b65dSshaneh         rc = SQLITE_NOTFOUND;
40478c4de4cSshaneh         break;
40578c4de4cSshaneh     }
406d8ce22bbSshaneh   }
40778c4de4cSshaneh   if( rc==SQLITE_OK ){
408d8ce22bbSshaneh     rc = sqlite3_file_control(db, 0, op, &iVal);
409d9523b97Sshaneh   }
4103801b65dSshaneh   sqlite3_result_error_code(context, rc);
411d9523b97Sshaneh }
412d9523b97Sshaneh 
413d9523b97Sshaneh /*
4143801b65dSshaneh ** This is the entry point to register the auto-extension for the
4153801b65dSshaneh ** multiplex_control() function.
416d9523b97Sshaneh */
multiplexFuncInit(sqlite3 * db,char ** pzErrMsg,const sqlite3_api_routines * pApi)417d50deeebSshaneh static int multiplexFuncInit(
41878c4de4cSshaneh   sqlite3 *db,
41978c4de4cSshaneh   char **pzErrMsg,
42078c4de4cSshaneh   const sqlite3_api_routines *pApi
421d50deeebSshaneh ){
422ac039688Sshaneh   int rc;
423ac039688Sshaneh   rc = sqlite3_create_function(db, "multiplex_control", 2, SQLITE_ANY,
424d8ce22bbSshaneh       0, multiplexControlFunc, 0, 0);
425ac039688Sshaneh   return rc;
426d9523b97Sshaneh }
427d9523b97Sshaneh 
428f3717af4Sdrh /*
429f3717af4Sdrh ** Close a single sub-file in the connection group.
430f3717af4Sdrh */
multiplexSubClose(multiplexGroup * pGroup,int iChunk,sqlite3_vfs * pOrigVfs)431f3717af4Sdrh static void multiplexSubClose(
432f3717af4Sdrh   multiplexGroup *pGroup,
433f3717af4Sdrh   int iChunk,
434f3717af4Sdrh   sqlite3_vfs *pOrigVfs
435f3717af4Sdrh ){
436f3717af4Sdrh   sqlite3_file *pSubOpen = pGroup->aReal[iChunk].p;
437f3717af4Sdrh   if( pSubOpen ){
438f3717af4Sdrh     pSubOpen->pMethods->xClose(pSubOpen);
4392be25bffSdrh     if( pOrigVfs && pGroup->aReal[iChunk].z ){
4402be25bffSdrh       pOrigVfs->xDelete(pOrigVfs, pGroup->aReal[iChunk].z, 0);
4412be25bffSdrh     }
442f3717af4Sdrh     sqlite3_free(pGroup->aReal[iChunk].p);
443f3717af4Sdrh   }
4444defdddcSdrh   sqlite3_free_filename(pGroup->aReal[iChunk].z);
445f3717af4Sdrh   memset(&pGroup->aReal[iChunk], 0, sizeof(pGroup->aReal[iChunk]));
446f3717af4Sdrh }
447f3717af4Sdrh 
448f3717af4Sdrh /*
449f3717af4Sdrh ** Deallocate memory held by a multiplexGroup
450f3717af4Sdrh */
multiplexFreeComponents(multiplexGroup * pGroup)451f3717af4Sdrh static void multiplexFreeComponents(multiplexGroup *pGroup){
452f3717af4Sdrh   int i;
453f3717af4Sdrh   for(i=0; i<pGroup->nReal; i++){ multiplexSubClose(pGroup, i, 0); }
454f3717af4Sdrh   sqlite3_free(pGroup->aReal);
455f3717af4Sdrh   pGroup->aReal = 0;
456f3717af4Sdrh   pGroup->nReal = 0;
457f3717af4Sdrh }
458f3717af4Sdrh 
459f3717af4Sdrh 
4608a922f75Sshaneh /************************* VFS Method Wrappers *****************************/
461b5830294Sshaneh 
4628a922f75Sshaneh /*
4638a922f75Sshaneh ** This is the xOpen method used for the "multiplex" VFS.
4648a922f75Sshaneh **
4658a922f75Sshaneh ** Most of the work is done by the underlying original VFS.  This method
4668a922f75Sshaneh ** simply links the new file into the appropriate multiplex group if it is a
4678a922f75Sshaneh ** file that needs to be tracked.
4688a922f75Sshaneh */
multiplexOpen(sqlite3_vfs * pVfs,const char * zName,sqlite3_file * pConn,int flags,int * pOutFlags)4698a922f75Sshaneh static int multiplexOpen(
4708a922f75Sshaneh   sqlite3_vfs *pVfs,         /* The multiplex VFS */
4718a922f75Sshaneh   const char *zName,         /* Name of file to be opened */
4728a922f75Sshaneh   sqlite3_file *pConn,       /* Fill in this file descriptor */
4738a922f75Sshaneh   int flags,                 /* Flags to control the opening */
4748a922f75Sshaneh   int *pOutFlags             /* Flags showing results of opening */
4758a922f75Sshaneh ){
476cc4e19beSshaneh   int rc = SQLITE_OK;                  /* Result code */
4778a922f75Sshaneh   multiplexConn *pMultiplexOpen;       /* The new multiplex file descriptor */
478fc30b042Sdrh   multiplexGroup *pGroup = 0;          /* Corresponding multiplexGroup object */
4790a0ca697Sdrh   sqlite3_file *pSubOpen = 0;                    /* Real file descriptor */
4808a922f75Sshaneh   sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */
481fc30b042Sdrh   int nName = 0;
482fc30b042Sdrh   int sz = 0;
483f3717af4Sdrh   char *zToFree = 0;
4848a922f75Sshaneh 
4858a922f75Sshaneh   UNUSED_PARAMETER(pVfs);
486f3717af4Sdrh   memset(pConn, 0, pVfs->szOsFile);
4875b1626aaSdrh   assert( zName || (flags & SQLITE_OPEN_DELETEONCLOSE) );
4888a922f75Sshaneh 
4898a922f75Sshaneh   /* We need to create a group structure and manage
4908a922f75Sshaneh   ** access to this group of files.
4918a922f75Sshaneh   */
4928a922f75Sshaneh   pMultiplexOpen = (multiplexConn*)pConn;
493cc4e19beSshaneh 
494cc4e19beSshaneh   if( rc==SQLITE_OK ){
495050d09abSshaneh     /* allocate space for group */
4965b1626aaSdrh     nName = zName ? multiplexStrlen30(zName) : 0;
497050d09abSshaneh     sz = sizeof(multiplexGroup)                             /* multiplexGroup */
498050d09abSshaneh        + nName + 1;                                         /* zName */
499f3cdcdccSdrh     pGroup = sqlite3_malloc64( sz );
5008a922f75Sshaneh     if( pGroup==0 ){
5018a922f75Sshaneh       rc = SQLITE_NOMEM;
502cc4e19beSshaneh     }
503cc4e19beSshaneh   }
504cc4e19beSshaneh 
505cc4e19beSshaneh   if( rc==SQLITE_OK ){
506c02a43afSdrh     const char *zUri = (flags & SQLITE_OPEN_URI) ? zName : 0;
507050d09abSshaneh     /* assign pointers to extra space allocated */
508050d09abSshaneh     memset(pGroup, 0, sz);
5095b1626aaSdrh     pMultiplexOpen->pGroup = pGroup;
51027b2f053Smistachkin     pGroup->bEnabled = (unsigned char)-1;
51177fac879Smistachkin     pGroup->bTruncate = (unsigned char)sqlite3_uri_boolean(zUri, "truncate",
51292913720Sdrh                                    (flags & SQLITE_OPEN_MAIN_DB)==0);
5137da5fcb0Sdrh     pGroup->szChunk = (int)sqlite3_uri_int64(zUri, "chunksize",
51492913720Sdrh                                         SQLITE_MULTIPLEX_CHUNK_SIZE);
51592913720Sdrh     pGroup->szChunk = (pGroup->szChunk+0xffff)&~0xffff;
5165b1626aaSdrh     if( zName ){
5175b1626aaSdrh       char *p = (char *)&pGroup[1];
518050d09abSshaneh       pGroup->zName = p;
5198a922f75Sshaneh       memcpy(pGroup->zName, zName, nName+1);
5208a922f75Sshaneh       pGroup->nName = nName;
5215b1626aaSdrh     }
52278c0eafbSdrh     if( pGroup->bEnabled ){
52392913720Sdrh       /* Make sure that the chunksize is such that the pending byte does not
52478c0eafbSdrh       ** falls at the end of a chunk.  A region of up to 64K following
52578c0eafbSdrh       ** the pending byte is never written, so if the pending byte occurs
52678c0eafbSdrh       ** near the end of a chunk, that chunk will be too small. */
5270420b74aSdan #ifndef SQLITE_OMIT_WSD
52878c0eafbSdrh       extern int sqlite3PendingByte;
5290420b74aSdan #else
5300420b74aSdan       int sqlite3PendingByte = 0x40000000;
5310420b74aSdan #endif
53278c0eafbSdrh       while( (sqlite3PendingByte % pGroup->szChunk)>=(pGroup->szChunk-65536) ){
53378c0eafbSdrh         pGroup->szChunk += 65536;
53478c0eafbSdrh       }
53578c0eafbSdrh     }
5364b2f45beSdan     pGroup->flags = (flags & ~SQLITE_OPEN_URI);
5370a0ca697Sdrh     rc = multiplexSubFilename(pGroup, 1);
5380a0ca697Sdrh     if( rc==SQLITE_OK ){
539e7d9f13dSdrh       pSubOpen = multiplexSubOpen(pGroup, 0, &rc, pOutFlags, 0);
540e7d9f13dSdrh       if( pSubOpen==0 && rc==SQLITE_OK ) rc = SQLITE_CANTOPEN;
5410a0ca697Sdrh     }
54231b21295Sdan     if( rc==SQLITE_OK ){
5438e18922fSmistachkin       sqlite3_int64 sz64;
5440a0ca697Sdrh 
5458e18922fSmistachkin       rc = pSubOpen->pMethods->xFileSize(pSubOpen, &sz64);
54631b21295Sdan       if( rc==SQLITE_OK && zName ){
547e712b582Sdan         int bExists;
548ccb2113aSdrh         if( flags & SQLITE_OPEN_SUPER_JOURNAL ){
54963c088e7Sdan           pGroup->bEnabled = 0;
55063c088e7Sdan         }else
5518e18922fSmistachkin         if( sz64==0 ){
552e712b582Sdan           if( flags & SQLITE_OPEN_MAIN_JOURNAL ){
553e712b582Sdan             /* If opening a main journal file and the first chunk is zero
554e712b582Sdan             ** bytes in size, delete any subsequent chunks from the
555e712b582Sdan             ** file-system. */
556e712b582Sdan             int iChunk = 1;
557e712b582Sdan             do {
558e712b582Sdan               rc = pOrigVfs->xAccess(pOrigVfs,
559e712b582Sdan                   pGroup->aReal[iChunk].z, SQLITE_ACCESS_EXISTS, &bExists
560e712b582Sdan               );
561e712b582Sdan               if( rc==SQLITE_OK && bExists ){
562e712b582Sdan                 rc = pOrigVfs->xDelete(pOrigVfs, pGroup->aReal[iChunk].z, 0);
563e712b582Sdan                 if( rc==SQLITE_OK ){
564e712b582Sdan                   rc = multiplexSubFilename(pGroup, ++iChunk);
565e712b582Sdan                 }
566e712b582Sdan               }
567e712b582Sdan             }while( rc==SQLITE_OK && bExists );
568e712b582Sdan           }
569e712b582Sdan         }else{
5700a0ca697Sdrh           /* If the first overflow file exists and if the size of the main file
5710a0ca697Sdrh           ** is different from the chunk size, that means the chunk size is set
5720a0ca697Sdrh           ** set incorrectly.  So fix it.
5730a0ca697Sdrh           **
5740a0ca697Sdrh           ** Or, if the first overflow file does not exist and the main file is
5750a0ca697Sdrh           ** larger than the chunk size, that means the chunk size is too small.
5760a0ca697Sdrh           ** But we have no way of determining the intended chunk size, so
5770a0ca697Sdrh           ** just disable the multiplexor all togethre.
5780a0ca697Sdrh           */
57931b21295Sdan           rc = pOrigVfs->xAccess(pOrigVfs, pGroup->aReal[1].z,
580e712b582Sdan               SQLITE_ACCESS_EXISTS, &bExists);
581e7d9f13dSdrh           bExists = multiplexSubSize(pGroup, 1, &rc)>0;
5828e18922fSmistachkin           if( rc==SQLITE_OK && bExists && sz64==(sz64&0xffff0000) && sz64>0
5838e18922fSmistachkin               && sz64!=pGroup->szChunk ){
5848e18922fSmistachkin             pGroup->szChunk = (int)sz64;
5858e18922fSmistachkin           }else if( rc==SQLITE_OK && !bExists && sz64>pGroup->szChunk ){
586c27fa4b0Sshaneh             pGroup->bEnabled = 0;
587c27fa4b0Sshaneh           }
5880a0ca697Sdrh         }
58931b21295Sdan       }
590e712b582Sdan     }
5910a0ca697Sdrh 
59231b21295Sdan     if( rc==SQLITE_OK ){
5938a922f75Sshaneh       if( pSubOpen->pMethods->iVersion==1 ){
5940c52f5a2Sdrh         pConn->pMethods = &gMultiplex.sIoMethodsV1;
5958a922f75Sshaneh       }else{
5960c52f5a2Sdrh         pConn->pMethods = &gMultiplex.sIoMethodsV2;
5978a922f75Sshaneh       }
5988a922f75Sshaneh     }else{
599f3717af4Sdrh       multiplexFreeComponents(pGroup);
6008a922f75Sshaneh       sqlite3_free(pGroup);
6018a922f75Sshaneh     }
6028a922f75Sshaneh   }
603f3717af4Sdrh   sqlite3_free(zToFree);
6048a922f75Sshaneh   return rc;
6058a922f75Sshaneh }
6068a922f75Sshaneh 
607b5830294Sshaneh /*
608b5830294Sshaneh ** This is the xDelete method used for the "multiplex" VFS.
609f3717af4Sdrh ** It attempts to delete the filename specified.
610b5830294Sshaneh */
multiplexDelete(sqlite3_vfs * pVfs,const char * zName,int syncDir)611b5830294Sshaneh static int multiplexDelete(
612b5830294Sshaneh   sqlite3_vfs *pVfs,         /* The multiplex VFS */
613b5830294Sshaneh   const char *zName,         /* Name of file to delete */
614b5830294Sshaneh   int syncDir
615b5830294Sshaneh ){
616e712b582Sdan   int rc;
617b5830294Sshaneh   sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */
618e712b582Sdan   rc = pOrigVfs->xDelete(pOrigVfs, zName, syncDir);
619e712b582Sdan   if( rc==SQLITE_OK ){
620e712b582Sdan     /* If the main chunk was deleted successfully, also delete any subsequent
621e712b582Sdan     ** chunks - starting with the last (highest numbered).
622e712b582Sdan     */
62383cc1392Sdrh     int nName = (int)strlen(zName);
624e712b582Sdan     char *z;
625f3cdcdccSdrh     z = sqlite3_malloc64(nName + 5);
626e712b582Sdan     if( z==0 ){
627e712b582Sdan       rc = SQLITE_IOERR_NOMEM;
628e712b582Sdan     }else{
629e712b582Sdan       int iChunk = 0;
630e712b582Sdan       int bExists;
631e712b582Sdan       do{
632e712b582Sdan         multiplexFilename(zName, nName, SQLITE_OPEN_MAIN_JOURNAL, ++iChunk, z);
633e712b582Sdan         rc = pOrigVfs->xAccess(pOrigVfs, z, SQLITE_ACCESS_EXISTS, &bExists);
634e712b582Sdan       }while( rc==SQLITE_OK && bExists );
635e712b582Sdan       while( rc==SQLITE_OK && iChunk>1 ){
636e712b582Sdan         multiplexFilename(zName, nName, SQLITE_OPEN_MAIN_JOURNAL, --iChunk, z);
637e712b582Sdan         rc = pOrigVfs->xDelete(pOrigVfs, z, syncDir);
638e712b582Sdan       }
6394aced72aSdan       if( rc==SQLITE_OK ){
640c611ee94Sdrh         iChunk = 0;
641c611ee94Sdrh         do{
642c611ee94Sdrh           multiplexFilename(zName, nName, SQLITE_OPEN_WAL, ++iChunk, z);
643c611ee94Sdrh           rc = pOrigVfs->xAccess(pOrigVfs, z, SQLITE_ACCESS_EXISTS, &bExists);
644c611ee94Sdrh         }while( rc==SQLITE_OK && bExists );
645c611ee94Sdrh         while( rc==SQLITE_OK && iChunk>1 ){
646c611ee94Sdrh           multiplexFilename(zName, nName, SQLITE_OPEN_WAL, --iChunk, z);
647c611ee94Sdrh           rc = pOrigVfs->xDelete(pOrigVfs, z, syncDir);
648c611ee94Sdrh         }
649e712b582Sdan       }
6504aced72aSdan     }
651e712b582Sdan     sqlite3_free(z);
652e712b582Sdan   }
653e712b582Sdan   return rc;
654b5830294Sshaneh }
655b5830294Sshaneh 
multiplexAccess(sqlite3_vfs * a,const char * b,int c,int * d)656d50deeebSshaneh static int multiplexAccess(sqlite3_vfs *a, const char *b, int c, int *d){
657d50deeebSshaneh   return gMultiplex.pOrigVfs->xAccess(gMultiplex.pOrigVfs, b, c, d);
658fffadc52Sdan }
multiplexFullPathname(sqlite3_vfs * a,const char * b,int c,char * d)659d50deeebSshaneh static int multiplexFullPathname(sqlite3_vfs *a, const char *b, int c, char *d){
660d50deeebSshaneh   return gMultiplex.pOrigVfs->xFullPathname(gMultiplex.pOrigVfs, b, c, d);
661fffadc52Sdan }
multiplexDlOpen(sqlite3_vfs * a,const char * b)662fffadc52Sdan static void *multiplexDlOpen(sqlite3_vfs *a, const char *b){
663fffadc52Sdan   return gMultiplex.pOrigVfs->xDlOpen(gMultiplex.pOrigVfs, b);
664fffadc52Sdan }
multiplexDlError(sqlite3_vfs * a,int b,char * c)665fffadc52Sdan static void multiplexDlError(sqlite3_vfs *a, int b, char *c){
666fffadc52Sdan   gMultiplex.pOrigVfs->xDlError(gMultiplex.pOrigVfs, b, c);
667fffadc52Sdan }
multiplexDlSym(sqlite3_vfs * a,void * b,const char * c)668fffadc52Sdan static void (*multiplexDlSym(sqlite3_vfs *a, void *b, const char *c))(void){
669fffadc52Sdan   return gMultiplex.pOrigVfs->xDlSym(gMultiplex.pOrigVfs, b, c);
670fffadc52Sdan }
multiplexDlClose(sqlite3_vfs * a,void * b)671fffadc52Sdan static void multiplexDlClose(sqlite3_vfs *a, void *b){
672fffadc52Sdan   gMultiplex.pOrigVfs->xDlClose(gMultiplex.pOrigVfs, b);
673fffadc52Sdan }
multiplexRandomness(sqlite3_vfs * a,int b,char * c)674fffadc52Sdan static int multiplexRandomness(sqlite3_vfs *a, int b, char *c){
675fffadc52Sdan   return gMultiplex.pOrigVfs->xRandomness(gMultiplex.pOrigVfs, b, c);
676fffadc52Sdan }
multiplexSleep(sqlite3_vfs * a,int b)677fffadc52Sdan static int multiplexSleep(sqlite3_vfs *a, int b){
678fffadc52Sdan   return gMultiplex.pOrigVfs->xSleep(gMultiplex.pOrigVfs, b);
679fffadc52Sdan }
multiplexCurrentTime(sqlite3_vfs * a,double * b)680fffadc52Sdan static int multiplexCurrentTime(sqlite3_vfs *a, double *b){
681fffadc52Sdan   return gMultiplex.pOrigVfs->xCurrentTime(gMultiplex.pOrigVfs, b);
682fffadc52Sdan }
multiplexGetLastError(sqlite3_vfs * a,int b,char * c)683fffadc52Sdan static int multiplexGetLastError(sqlite3_vfs *a, int b, char *c){
68472cd360cSdrh   if( gMultiplex.pOrigVfs->xGetLastError ){
685fffadc52Sdan     return gMultiplex.pOrigVfs->xGetLastError(gMultiplex.pOrigVfs, b, c);
68672cd360cSdrh   }else{
68772cd360cSdrh     return 0;
68872cd360cSdrh   }
689fffadc52Sdan }
multiplexCurrentTimeInt64(sqlite3_vfs * a,sqlite3_int64 * b)690fffadc52Sdan static int multiplexCurrentTimeInt64(sqlite3_vfs *a, sqlite3_int64 *b){
691fffadc52Sdan   return gMultiplex.pOrigVfs->xCurrentTimeInt64(gMultiplex.pOrigVfs, b);
692fffadc52Sdan }
693fffadc52Sdan 
6948a922f75Sshaneh /************************ I/O Method Wrappers *******************************/
6958a922f75Sshaneh 
6968a922f75Sshaneh /* xClose requests get passed through to the original VFS.
6978a922f75Sshaneh ** We loop over all open chunk handles and close them.
6988a922f75Sshaneh ** The group structure for this file is unlinked from
6998a922f75Sshaneh ** our list of groups and freed.
7008a922f75Sshaneh */
multiplexClose(sqlite3_file * pConn)7018a922f75Sshaneh static int multiplexClose(sqlite3_file *pConn){
7028a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
7038a922f75Sshaneh   multiplexGroup *pGroup = p->pGroup;
7048a922f75Sshaneh   int rc = SQLITE_OK;
705f3717af4Sdrh   multiplexFreeComponents(pGroup);
7068a922f75Sshaneh   sqlite3_free(pGroup);
7078a922f75Sshaneh   return rc;
7088a922f75Sshaneh }
7098a922f75Sshaneh 
7108a922f75Sshaneh /* Pass xRead requests thru to the original VFS after
7118a922f75Sshaneh ** determining the correct chunk to operate on.
712fd1552f2Sshaneh ** Break up reads across chunk boundaries.
7138a922f75Sshaneh */
multiplexRead(sqlite3_file * pConn,void * pBuf,int iAmt,sqlite3_int64 iOfst)7148a922f75Sshaneh static int multiplexRead(
7158a922f75Sshaneh   sqlite3_file *pConn,
7168a922f75Sshaneh   void *pBuf,
7178a922f75Sshaneh   int iAmt,
7188a922f75Sshaneh   sqlite3_int64 iOfst
7198a922f75Sshaneh ){
7208a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
721d50deeebSshaneh   multiplexGroup *pGroup = p->pGroup;
7228a922f75Sshaneh   int rc = SQLITE_OK;
723c27fa4b0Sshaneh   if( !pGroup->bEnabled ){
724e7d9f13dSdrh     sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);
725f3717af4Sdrh     if( pSubOpen==0 ){
7268efdb732Sdrh       rc = SQLITE_IOERR_READ;
7278efdb732Sdrh     }else{
7288efdb732Sdrh       rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst);
7298efdb732Sdrh     }
730c27fa4b0Sshaneh   }else{
7318a922f75Sshaneh     while( iAmt > 0 ){
732e6deb204Sdrh       int i = (int)(iOfst / pGroup->szChunk);
73395a5bcbbSdrh       sqlite3_file *pSubOpen;
73495a5bcbbSdrh       pSubOpen = multiplexSubOpen(pGroup, i, &rc, NULL, 1);
7358a922f75Sshaneh       if( pSubOpen ){
736e6deb204Sdrh         int extra = ((int)(iOfst % pGroup->szChunk) + iAmt) - pGroup->szChunk;
7378a922f75Sshaneh         if( extra<0 ) extra = 0;
7388a922f75Sshaneh         iAmt -= extra;
7398efdb732Sdrh         rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt,
740e6deb204Sdrh                                        iOfst % pGroup->szChunk);
7418a922f75Sshaneh         if( rc!=SQLITE_OK ) break;
7428a922f75Sshaneh         pBuf = (char *)pBuf + iAmt;
7438a922f75Sshaneh         iOfst += iAmt;
7448a922f75Sshaneh         iAmt = extra;
7458a922f75Sshaneh       }else{
7468a922f75Sshaneh         rc = SQLITE_IOERR_READ;
7478a922f75Sshaneh         break;
7488a922f75Sshaneh       }
7498a922f75Sshaneh     }
750c27fa4b0Sshaneh   }
75148c06f32Sdan 
7528a922f75Sshaneh   return rc;
7538a922f75Sshaneh }
7548a922f75Sshaneh 
7558a922f75Sshaneh /* Pass xWrite requests thru to the original VFS after
7568a922f75Sshaneh ** determining the correct chunk to operate on.
757fd1552f2Sshaneh ** Break up writes across chunk boundaries.
7588a922f75Sshaneh */
multiplexWrite(sqlite3_file * pConn,const void * pBuf,int iAmt,sqlite3_int64 iOfst)7598a922f75Sshaneh static int multiplexWrite(
7608a922f75Sshaneh   sqlite3_file *pConn,
7618a922f75Sshaneh   const void *pBuf,
7628a922f75Sshaneh   int iAmt,
7638a922f75Sshaneh   sqlite3_int64 iOfst
7648a922f75Sshaneh ){
7658a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
766d50deeebSshaneh   multiplexGroup *pGroup = p->pGroup;
7678a922f75Sshaneh   int rc = SQLITE_OK;
768c27fa4b0Sshaneh   if( !pGroup->bEnabled ){
769e7d9f13dSdrh     sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);
7708efdb732Sdrh     if( pSubOpen==0 ){
7718efdb732Sdrh       rc = SQLITE_IOERR_WRITE;
7728efdb732Sdrh     }else{
7738efdb732Sdrh       rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst);
7748efdb732Sdrh     }
775c27fa4b0Sshaneh   }else{
77627e69643Sdan     while( rc==SQLITE_OK && iAmt>0 ){
777e6deb204Sdrh       int i = (int)(iOfst / pGroup->szChunk);
778e7d9f13dSdrh       sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, i, &rc, NULL, 1);
7798a922f75Sshaneh       if( pSubOpen ){
780e6deb204Sdrh         int extra = ((int)(iOfst % pGroup->szChunk) + iAmt) -
781e6deb204Sdrh                     pGroup->szChunk;
7828a922f75Sshaneh         if( extra<0 ) extra = 0;
7838a922f75Sshaneh         iAmt -= extra;
7848efdb732Sdrh         rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt,
785e6deb204Sdrh                                         iOfst % pGroup->szChunk);
7868a922f75Sshaneh         pBuf = (char *)pBuf + iAmt;
7878a922f75Sshaneh         iOfst += iAmt;
7888a922f75Sshaneh         iAmt = extra;
7898a922f75Sshaneh       }
7908a922f75Sshaneh     }
791c27fa4b0Sshaneh   }
7928a922f75Sshaneh   return rc;
7938a922f75Sshaneh }
7948a922f75Sshaneh 
7958a922f75Sshaneh /* Pass xTruncate requests thru to the original VFS after
7968a922f75Sshaneh ** determining the correct chunk to operate on.  Delete any
7978a922f75Sshaneh ** chunks above the truncate mark.
7988a922f75Sshaneh */
multiplexTruncate(sqlite3_file * pConn,sqlite3_int64 size)7998a922f75Sshaneh static int multiplexTruncate(sqlite3_file *pConn, sqlite3_int64 size){
8008a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
8018a922f75Sshaneh   multiplexGroup *pGroup = p->pGroup;
8028a922f75Sshaneh   int rc = SQLITE_OK;
803c27fa4b0Sshaneh   if( !pGroup->bEnabled ){
804e7d9f13dSdrh     sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);
8058efdb732Sdrh     if( pSubOpen==0 ){
8068efdb732Sdrh       rc = SQLITE_IOERR_TRUNCATE;
8078efdb732Sdrh     }else{
8088efdb732Sdrh       rc = pSubOpen->pMethods->xTruncate(pSubOpen, size);
8098efdb732Sdrh     }
810e7d9f13dSdrh   }else{
8118a922f75Sshaneh     int i;
812e7d9f13dSdrh     int iBaseGroup = (int)(size / pGroup->szChunk);
8138a922f75Sshaneh     sqlite3_file *pSubOpen;
8148a922f75Sshaneh     sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */
8158a922f75Sshaneh     /* delete the chunks above the truncate limit */
816e7d9f13dSdrh     for(i = pGroup->nReal-1; i>iBaseGroup && rc==SQLITE_OK; i--){
817e7d9f13dSdrh       if( pGroup->bTruncate ){
818f3717af4Sdrh         multiplexSubClose(pGroup, i, pOrigVfs);
8198a922f75Sshaneh       }else{
820e7d9f13dSdrh         pSubOpen = multiplexSubOpen(pGroup, i, &rc, 0, 0);
821e7d9f13dSdrh         if( pSubOpen ){
822e7d9f13dSdrh           rc = pSubOpen->pMethods->xTruncate(pSubOpen, 0);
8238a922f75Sshaneh         }
824c27fa4b0Sshaneh       }
825e7d9f13dSdrh     }
826e7d9f13dSdrh     if( rc==SQLITE_OK ){
827e7d9f13dSdrh       pSubOpen = multiplexSubOpen(pGroup, iBaseGroup, &rc, 0, 0);
828e7d9f13dSdrh       if( pSubOpen ){
829e7d9f13dSdrh         rc = pSubOpen->pMethods->xTruncate(pSubOpen, size % pGroup->szChunk);
830e7d9f13dSdrh       }
831e7d9f13dSdrh     }
832e7d9f13dSdrh     if( rc ) rc = SQLITE_IOERR_TRUNCATE;
833e7d9f13dSdrh   }
8348a922f75Sshaneh   return rc;
8358a922f75Sshaneh }
8368a922f75Sshaneh 
8378a922f75Sshaneh /* Pass xSync requests through to the original VFS without change
8388a922f75Sshaneh */
multiplexSync(sqlite3_file * pConn,int flags)8398a922f75Sshaneh static int multiplexSync(sqlite3_file *pConn, int flags){
8408a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
8418a922f75Sshaneh   multiplexGroup *pGroup = p->pGroup;
8428a922f75Sshaneh   int rc = SQLITE_OK;
8438a922f75Sshaneh   int i;
844f3717af4Sdrh   for(i=0; i<pGroup->nReal; i++){
845f3717af4Sdrh     sqlite3_file *pSubOpen = pGroup->aReal[i].p;
846f3717af4Sdrh     if( pSubOpen ){
8478a922f75Sshaneh       int rc2 = pSubOpen->pMethods->xSync(pSubOpen, flags);
8488a922f75Sshaneh       if( rc2!=SQLITE_OK ) rc = rc2;
8498a922f75Sshaneh     }
8508a922f75Sshaneh   }
8518a922f75Sshaneh   return rc;
8528a922f75Sshaneh }
8538a922f75Sshaneh 
8548a922f75Sshaneh /* Pass xFileSize requests through to the original VFS.
8558a922f75Sshaneh ** Aggregate the size of all the chunks before returning.
8568a922f75Sshaneh */
multiplexFileSize(sqlite3_file * pConn,sqlite3_int64 * pSize)8578a922f75Sshaneh static int multiplexFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){
8588a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
8598a922f75Sshaneh   multiplexGroup *pGroup = p->pGroup;
8608a922f75Sshaneh   int rc = SQLITE_OK;
8618a922f75Sshaneh   int i;
862c27fa4b0Sshaneh   if( !pGroup->bEnabled ){
863e7d9f13dSdrh     sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);
8648efdb732Sdrh     if( pSubOpen==0 ){
8655b1626aaSdrh       rc = SQLITE_IOERR_FSTAT;
8668efdb732Sdrh     }else{
8678efdb732Sdrh       rc = pSubOpen->pMethods->xFileSize(pSubOpen, pSize);
8688efdb732Sdrh     }
869c27fa4b0Sshaneh   }else{
8708a922f75Sshaneh     *pSize = 0;
8715b1626aaSdrh     for(i=0; rc==SQLITE_OK; i++){
872e7d9f13dSdrh       sqlite3_int64 sz = multiplexSubSize(pGroup, i, &rc);
873e7d9f13dSdrh       if( sz==0 ) break;
8742be25bffSdrh       *pSize = i*(sqlite3_int64)pGroup->szChunk + sz;
8758a922f75Sshaneh     }
8768a922f75Sshaneh   }
8778a922f75Sshaneh   return rc;
8788a922f75Sshaneh }
8798a922f75Sshaneh 
8808a922f75Sshaneh /* Pass xLock requests through to the original VFS unchanged.
8818a922f75Sshaneh */
multiplexLock(sqlite3_file * pConn,int lock)8828a922f75Sshaneh static int multiplexLock(sqlite3_file *pConn, int lock){
8838a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
8848a922f75Sshaneh   int rc;
885e7d9f13dSdrh   sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
8868a922f75Sshaneh   if( pSubOpen ){
8878a922f75Sshaneh     return pSubOpen->pMethods->xLock(pSubOpen, lock);
8888a922f75Sshaneh   }
8898a922f75Sshaneh   return SQLITE_BUSY;
8908a922f75Sshaneh }
8918a922f75Sshaneh 
8928a922f75Sshaneh /* Pass xUnlock requests through to the original VFS unchanged.
8938a922f75Sshaneh */
multiplexUnlock(sqlite3_file * pConn,int lock)8948a922f75Sshaneh static int multiplexUnlock(sqlite3_file *pConn, int lock){
8958a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
8968a922f75Sshaneh   int rc;
897e7d9f13dSdrh   sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
8988a922f75Sshaneh   if( pSubOpen ){
8998a922f75Sshaneh     return pSubOpen->pMethods->xUnlock(pSubOpen, lock);
9008a922f75Sshaneh   }
9018a922f75Sshaneh   return SQLITE_IOERR_UNLOCK;
9028a922f75Sshaneh }
9038a922f75Sshaneh 
9048a922f75Sshaneh /* Pass xCheckReservedLock requests through to the original VFS unchanged.
9058a922f75Sshaneh */
multiplexCheckReservedLock(sqlite3_file * pConn,int * pResOut)9068a922f75Sshaneh static int multiplexCheckReservedLock(sqlite3_file *pConn, int *pResOut){
9078a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
9088a922f75Sshaneh   int rc;
909e7d9f13dSdrh   sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
9108a922f75Sshaneh   if( pSubOpen ){
9118a922f75Sshaneh     return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut);
9128a922f75Sshaneh   }
9138a922f75Sshaneh   return SQLITE_IOERR_CHECKRESERVEDLOCK;
9148a922f75Sshaneh }
9158a922f75Sshaneh 
916c27fa4b0Sshaneh /* Pass xFileControl requests through to the original VFS unchanged,
917c27fa4b0Sshaneh ** except for any MULTIPLEX_CTRL_* requests here.
9188a922f75Sshaneh */
multiplexFileControl(sqlite3_file * pConn,int op,void * pArg)9198a922f75Sshaneh static int multiplexFileControl(sqlite3_file *pConn, int op, void *pArg){
9208a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
921d50deeebSshaneh   multiplexGroup *pGroup = p->pGroup;
922d50deeebSshaneh   int rc = SQLITE_ERROR;
9238a922f75Sshaneh   sqlite3_file *pSubOpen;
924d50deeebSshaneh 
925d50deeebSshaneh   if( !gMultiplex.isInitialized ) return SQLITE_MISUSE;
926d50deeebSshaneh   switch( op ){
927d50deeebSshaneh     case MULTIPLEX_CTRL_ENABLE:
928d50deeebSshaneh       if( pArg ) {
929d50deeebSshaneh         int bEnabled = *(int *)pArg;
93077fac879Smistachkin         pGroup->bEnabled = (unsigned char)bEnabled;
931d50deeebSshaneh         rc = SQLITE_OK;
932d50deeebSshaneh       }
933d50deeebSshaneh       break;
934d50deeebSshaneh     case MULTIPLEX_CTRL_SET_CHUNK_SIZE:
935d50deeebSshaneh       if( pArg ) {
936e6deb204Sdrh         unsigned int szChunk = *(unsigned*)pArg;
937e6deb204Sdrh         if( szChunk<1 ){
938d50deeebSshaneh           rc = SQLITE_MISUSE;
939d50deeebSshaneh         }else{
9403801b65dSshaneh           /* Round up to nearest multiple of MAX_PAGE_SIZE. */
941e6deb204Sdrh           szChunk = (szChunk + (MAX_PAGE_SIZE-1));
942e6deb204Sdrh           szChunk &= ~(MAX_PAGE_SIZE-1);
943e6deb204Sdrh           pGroup->szChunk = szChunk;
944d50deeebSshaneh           rc = SQLITE_OK;
945d50deeebSshaneh         }
946d50deeebSshaneh       }
947d50deeebSshaneh       break;
948d50deeebSshaneh     case MULTIPLEX_CTRL_SET_MAX_CHUNKS:
949d50deeebSshaneh       rc = SQLITE_OK;
950d50deeebSshaneh       break;
951ac039688Sshaneh     case SQLITE_FCNTL_SIZE_HINT:
952ac039688Sshaneh     case SQLITE_FCNTL_CHUNK_SIZE:
953ac039688Sshaneh       /* no-op these */
954ac039688Sshaneh       rc = SQLITE_OK;
955ac039688Sshaneh       break;
9563b8fea9eSdrh     case SQLITE_FCNTL_PRAGMA: {
9573b8fea9eSdrh       char **aFcntl = (char**)pArg;
9588dd7a6a9Sdrh       /*
9598dd7a6a9Sdrh       ** EVIDENCE-OF: R-29875-31678 The argument to the SQLITE_FCNTL_PRAGMA
9608dd7a6a9Sdrh       ** file control is an array of pointers to strings (char**) in which the
9618dd7a6a9Sdrh       ** second element of the array is the name of the pragma and the third
9628dd7a6a9Sdrh       ** element is the argument to the pragma or NULL if the pragma has no
9638dd7a6a9Sdrh       ** argument.
9648dd7a6a9Sdrh       */
96537bbcb48Sdrh       if( aFcntl[1] && sqlite3_strnicmp(aFcntl[1],"multiplex_",10)==0 ){
96637bbcb48Sdrh         sqlite3_int64 sz = 0;
96737bbcb48Sdrh         (void)multiplexFileSize(pConn, &sz);
96837bbcb48Sdrh         /*
96937bbcb48Sdrh         ** PRAGMA multiplex_truncate=BOOLEAN;
97037bbcb48Sdrh         ** PRAGMA multiplex_truncate;
97137bbcb48Sdrh         **
97237bbcb48Sdrh         ** Turn the multiplexor truncate feature on or off.  Return either
97337bbcb48Sdrh         ** "on" or "off" to indicate the new setting.  If the BOOLEAN argument
97437bbcb48Sdrh         ** is omitted, just return the current value for the truncate setting.
97537bbcb48Sdrh         */
97637bbcb48Sdrh         if( sqlite3_stricmp(aFcntl[1],"multiplex_truncate")==0 ){
9773b8fea9eSdrh           if( aFcntl[2] && aFcntl[2][0] ){
9783b8fea9eSdrh             if( sqlite3_stricmp(aFcntl[2], "on")==0
9793b8fea9eSdrh              || sqlite3_stricmp(aFcntl[2], "1")==0 ){
9803b8fea9eSdrh               pGroup->bTruncate = 1;
9813b8fea9eSdrh             }else
9823b8fea9eSdrh             if( sqlite3_stricmp(aFcntl[2], "off")==0
9833b8fea9eSdrh              || sqlite3_stricmp(aFcntl[2], "0")==0 ){
9843b8fea9eSdrh               pGroup->bTruncate = 0;
9853b8fea9eSdrh             }
9863b8fea9eSdrh           }
9878dd7a6a9Sdrh           /* EVIDENCE-OF: R-27806-26076 The handler for an SQLITE_FCNTL_PRAGMA
9888dd7a6a9Sdrh           ** file control can optionally make the first element of the char**
9898dd7a6a9Sdrh           ** argument point to a string obtained from sqlite3_mprintf() or the
9908dd7a6a9Sdrh           ** equivalent and that string will become the result of the pragma
9918dd7a6a9Sdrh           ** or the error message if the pragma fails.
9928dd7a6a9Sdrh           */
9933b8fea9eSdrh           aFcntl[0] = sqlite3_mprintf(pGroup->bTruncate ? "on" : "off");
9943b8fea9eSdrh           rc = SQLITE_OK;
9953b8fea9eSdrh           break;
9963b8fea9eSdrh         }
99737bbcb48Sdrh         /*
99837bbcb48Sdrh         ** PRAGMA multiplex_enabled;
99937bbcb48Sdrh         **
100037bbcb48Sdrh         ** Return 0 or 1 depending on whether the multiplexor is enabled or
100137bbcb48Sdrh         ** disabled, respectively.
100237bbcb48Sdrh         */
100337bbcb48Sdrh         if( sqlite3_stricmp(aFcntl[1],"multiplex_enabled")==0 ){
100437bbcb48Sdrh           aFcntl[0] = sqlite3_mprintf("%d", pGroup->bEnabled!=0);
100537bbcb48Sdrh           rc = SQLITE_OK;
100637bbcb48Sdrh           break;
100737bbcb48Sdrh         }
100837bbcb48Sdrh         /*
100937bbcb48Sdrh         ** PRAGMA multiplex_chunksize;
101037bbcb48Sdrh         **
101137bbcb48Sdrh         ** Return the chunksize for the multiplexor, or no-op if the
101237bbcb48Sdrh         ** multiplexor is not active.
101337bbcb48Sdrh         */
101437bbcb48Sdrh         if( sqlite3_stricmp(aFcntl[1],"multiplex_chunksize")==0
101537bbcb48Sdrh          && pGroup->bEnabled
101637bbcb48Sdrh         ){
101737bbcb48Sdrh           aFcntl[0] = sqlite3_mprintf("%u", pGroup->szChunk);
101837bbcb48Sdrh           rc = SQLITE_OK;
101937bbcb48Sdrh           break;
102037bbcb48Sdrh         }
102137bbcb48Sdrh         /*
102237bbcb48Sdrh         ** PRAGMA multiplex_filecount;
102337bbcb48Sdrh         **
102437bbcb48Sdrh         ** Return the number of disk files currently in use by the
102537bbcb48Sdrh         ** multiplexor.  This should be the total database size size
102637bbcb48Sdrh         ** divided by the chunksize and rounded up.
102737bbcb48Sdrh         */
102837bbcb48Sdrh         if( sqlite3_stricmp(aFcntl[1],"multiplex_filecount")==0 ){
102937bbcb48Sdrh           int n = 0;
103037bbcb48Sdrh           int ii;
103137bbcb48Sdrh           for(ii=0; ii<pGroup->nReal; ii++){
103237bbcb48Sdrh             if( pGroup->aReal[ii].p!=0 ) n++;
103337bbcb48Sdrh           }
103437bbcb48Sdrh           aFcntl[0] = sqlite3_mprintf("%d", n);
103537bbcb48Sdrh           rc = SQLITE_OK;
103637bbcb48Sdrh           break;
103737bbcb48Sdrh         }
103837bbcb48Sdrh       }
10393b8fea9eSdrh       /* If the multiplexor does not handle the pragma, pass it through
10403b8fea9eSdrh       ** into the default case. */
10413b8fea9eSdrh     }
1042d50deeebSshaneh     default:
1043e7d9f13dSdrh       pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);
10448a922f75Sshaneh       if( pSubOpen ){
1045d50deeebSshaneh         rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg);
1046de60fc2dSdrh         if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){
1047de60fc2dSdrh          *(char**)pArg = sqlite3_mprintf("multiplex/%z", *(char**)pArg);
1048de60fc2dSdrh         }
10498a922f75Sshaneh       }
1050d50deeebSshaneh       break;
1051d50deeebSshaneh   }
1052d50deeebSshaneh   return rc;
10538a922f75Sshaneh }
10548a922f75Sshaneh 
10558a922f75Sshaneh /* Pass xSectorSize requests through to the original VFS unchanged.
10568a922f75Sshaneh */
multiplexSectorSize(sqlite3_file * pConn)10578a922f75Sshaneh static int multiplexSectorSize(sqlite3_file *pConn){
10588a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
10598a922f75Sshaneh   int rc;
1060e7d9f13dSdrh   sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
1061c7f94629Sdrh   if( pSubOpen && pSubOpen->pMethods->xSectorSize ){
10628a922f75Sshaneh     return pSubOpen->pMethods->xSectorSize(pSubOpen);
10638a922f75Sshaneh   }
10643801b65dSshaneh   return DEFAULT_SECTOR_SIZE;
10658a922f75Sshaneh }
10668a922f75Sshaneh 
10678a922f75Sshaneh /* Pass xDeviceCharacteristics requests through to the original VFS unchanged.
10688a922f75Sshaneh */
multiplexDeviceCharacteristics(sqlite3_file * pConn)10698a922f75Sshaneh static int multiplexDeviceCharacteristics(sqlite3_file *pConn){
10708a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
10718a922f75Sshaneh   int rc;
1072e7d9f13dSdrh   sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
10738a922f75Sshaneh   if( pSubOpen ){
10748a922f75Sshaneh     return pSubOpen->pMethods->xDeviceCharacteristics(pSubOpen);
10758a922f75Sshaneh   }
10768a922f75Sshaneh   return 0;
10778a922f75Sshaneh }
10788a922f75Sshaneh 
10798a922f75Sshaneh /* Pass xShmMap requests through to the original VFS unchanged.
10808a922f75Sshaneh */
multiplexShmMap(sqlite3_file * pConn,int iRegion,int szRegion,int bExtend,void volatile ** pp)10818a922f75Sshaneh static int multiplexShmMap(
10828a922f75Sshaneh   sqlite3_file *pConn,            /* Handle open on database file */
10838a922f75Sshaneh   int iRegion,                    /* Region to retrieve */
10848a922f75Sshaneh   int szRegion,                   /* Size of regions */
10858a922f75Sshaneh   int bExtend,                    /* True to extend file if necessary */
10868a922f75Sshaneh   void volatile **pp              /* OUT: Mapped memory */
10878a922f75Sshaneh ){
10888a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
10898a922f75Sshaneh   int rc;
1090e7d9f13dSdrh   sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
10918a922f75Sshaneh   if( pSubOpen ){
10928a922f75Sshaneh     return pSubOpen->pMethods->xShmMap(pSubOpen, iRegion, szRegion, bExtend,pp);
10938a922f75Sshaneh   }
10948a922f75Sshaneh   return SQLITE_IOERR;
10958a922f75Sshaneh }
10968a922f75Sshaneh 
10978a922f75Sshaneh /* Pass xShmLock requests through to the original VFS unchanged.
10988a922f75Sshaneh */
multiplexShmLock(sqlite3_file * pConn,int ofst,int n,int flags)10998a922f75Sshaneh static int multiplexShmLock(
11008a922f75Sshaneh   sqlite3_file *pConn,       /* Database file holding the shared memory */
11018a922f75Sshaneh   int ofst,                  /* First lock to acquire or release */
11028a922f75Sshaneh   int n,                     /* Number of locks to acquire or release */
11038a922f75Sshaneh   int flags                  /* What to do with the lock */
11048a922f75Sshaneh ){
11058a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
11068a922f75Sshaneh   int rc;
1107e7d9f13dSdrh   sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
11088a922f75Sshaneh   if( pSubOpen ){
11098a922f75Sshaneh     return pSubOpen->pMethods->xShmLock(pSubOpen, ofst, n, flags);
11108a922f75Sshaneh   }
11118a922f75Sshaneh   return SQLITE_BUSY;
11128a922f75Sshaneh }
11138a922f75Sshaneh 
11148a922f75Sshaneh /* Pass xShmBarrier requests through to the original VFS unchanged.
11158a922f75Sshaneh */
multiplexShmBarrier(sqlite3_file * pConn)11168a922f75Sshaneh static void multiplexShmBarrier(sqlite3_file *pConn){
11178a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
11188a922f75Sshaneh   int rc;
1119e7d9f13dSdrh   sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
11208a922f75Sshaneh   if( pSubOpen ){
11218a922f75Sshaneh     pSubOpen->pMethods->xShmBarrier(pSubOpen);
11228a922f75Sshaneh   }
11238a922f75Sshaneh }
11248a922f75Sshaneh 
11258a922f75Sshaneh /* Pass xShmUnmap requests through to the original VFS unchanged.
11268a922f75Sshaneh */
multiplexShmUnmap(sqlite3_file * pConn,int deleteFlag)11278a922f75Sshaneh static int multiplexShmUnmap(sqlite3_file *pConn, int deleteFlag){
11288a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
11298a922f75Sshaneh   int rc;
1130e7d9f13dSdrh   sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
11318a922f75Sshaneh   if( pSubOpen ){
11328a922f75Sshaneh     return pSubOpen->pMethods->xShmUnmap(pSubOpen, deleteFlag);
11338a922f75Sshaneh   }
11348a922f75Sshaneh   return SQLITE_OK;
11358a922f75Sshaneh }
11368a922f75Sshaneh 
11378a922f75Sshaneh /************************** Public Interfaces *****************************/
11388a922f75Sshaneh /*
113978c4de4cSshaneh ** CAPI: Initialize the multiplex VFS shim - sqlite3_multiplex_initialize()
114078c4de4cSshaneh **
114178c4de4cSshaneh ** Use the VFS named zOrigVfsName as the VFS that does the actual work.
114278c4de4cSshaneh ** Use the default if zOrigVfsName==NULL.
11438a922f75Sshaneh **
11448a922f75Sshaneh ** The multiplex VFS shim is named "multiplex".  It will become the default
11458a922f75Sshaneh ** VFS if makeDefault is non-zero.
11468a922f75Sshaneh **
11478a922f75Sshaneh ** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once
11488a922f75Sshaneh ** during start-up.
11498a922f75Sshaneh */
sqlite3_multiplex_initialize(const char * zOrigVfsName,int makeDefault)11508a922f75Sshaneh int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault){
11518a922f75Sshaneh   sqlite3_vfs *pOrigVfs;
11528a922f75Sshaneh   if( gMultiplex.isInitialized ) return SQLITE_MISUSE;
11538a922f75Sshaneh   pOrigVfs = sqlite3_vfs_find(zOrigVfsName);
11548a922f75Sshaneh   if( pOrigVfs==0 ) return SQLITE_ERROR;
11558a922f75Sshaneh   assert( pOrigVfs!=&gMultiplex.sThisVfs );
11568a922f75Sshaneh   gMultiplex.isInitialized = 1;
11578a922f75Sshaneh   gMultiplex.pOrigVfs = pOrigVfs;
11588a922f75Sshaneh   gMultiplex.sThisVfs = *pOrigVfs;
11598a922f75Sshaneh   gMultiplex.sThisVfs.szOsFile += sizeof(multiplexConn);
1160d9523b97Sshaneh   gMultiplex.sThisVfs.zName = SQLITE_MULTIPLEX_VFS_NAME;
11618a922f75Sshaneh   gMultiplex.sThisVfs.xOpen = multiplexOpen;
1162b5830294Sshaneh   gMultiplex.sThisVfs.xDelete = multiplexDelete;
1163fffadc52Sdan   gMultiplex.sThisVfs.xAccess = multiplexAccess;
1164fffadc52Sdan   gMultiplex.sThisVfs.xFullPathname = multiplexFullPathname;
1165fffadc52Sdan   gMultiplex.sThisVfs.xDlOpen = multiplexDlOpen;
1166fffadc52Sdan   gMultiplex.sThisVfs.xDlError = multiplexDlError;
1167fffadc52Sdan   gMultiplex.sThisVfs.xDlSym = multiplexDlSym;
1168fffadc52Sdan   gMultiplex.sThisVfs.xDlClose = multiplexDlClose;
1169fffadc52Sdan   gMultiplex.sThisVfs.xRandomness = multiplexRandomness;
1170fffadc52Sdan   gMultiplex.sThisVfs.xSleep = multiplexSleep;
1171fffadc52Sdan   gMultiplex.sThisVfs.xCurrentTime = multiplexCurrentTime;
1172fffadc52Sdan   gMultiplex.sThisVfs.xGetLastError = multiplexGetLastError;
1173fffadc52Sdan   gMultiplex.sThisVfs.xCurrentTimeInt64 = multiplexCurrentTimeInt64;
1174fffadc52Sdan 
11758a922f75Sshaneh   gMultiplex.sIoMethodsV1.iVersion = 1;
11768a922f75Sshaneh   gMultiplex.sIoMethodsV1.xClose = multiplexClose;
11778a922f75Sshaneh   gMultiplex.sIoMethodsV1.xRead = multiplexRead;
11788a922f75Sshaneh   gMultiplex.sIoMethodsV1.xWrite = multiplexWrite;
11798a922f75Sshaneh   gMultiplex.sIoMethodsV1.xTruncate = multiplexTruncate;
11808a922f75Sshaneh   gMultiplex.sIoMethodsV1.xSync = multiplexSync;
11818a922f75Sshaneh   gMultiplex.sIoMethodsV1.xFileSize = multiplexFileSize;
11828a922f75Sshaneh   gMultiplex.sIoMethodsV1.xLock = multiplexLock;
11838a922f75Sshaneh   gMultiplex.sIoMethodsV1.xUnlock = multiplexUnlock;
11848a922f75Sshaneh   gMultiplex.sIoMethodsV1.xCheckReservedLock = multiplexCheckReservedLock;
11858a922f75Sshaneh   gMultiplex.sIoMethodsV1.xFileControl = multiplexFileControl;
11868a922f75Sshaneh   gMultiplex.sIoMethodsV1.xSectorSize = multiplexSectorSize;
11878efdb732Sdrh   gMultiplex.sIoMethodsV1.xDeviceCharacteristics =
11888efdb732Sdrh                                             multiplexDeviceCharacteristics;
11898a922f75Sshaneh   gMultiplex.sIoMethodsV2 = gMultiplex.sIoMethodsV1;
11908a922f75Sshaneh   gMultiplex.sIoMethodsV2.iVersion = 2;
11918a922f75Sshaneh   gMultiplex.sIoMethodsV2.xShmMap = multiplexShmMap;
11928a922f75Sshaneh   gMultiplex.sIoMethodsV2.xShmLock = multiplexShmLock;
11938a922f75Sshaneh   gMultiplex.sIoMethodsV2.xShmBarrier = multiplexShmBarrier;
11948a922f75Sshaneh   gMultiplex.sIoMethodsV2.xShmUnmap = multiplexShmUnmap;
11958a922f75Sshaneh   sqlite3_vfs_register(&gMultiplex.sThisVfs, makeDefault);
1196d50deeebSshaneh 
119732c83c8bSdrh   sqlite3_auto_extension((void(*)(void))multiplexFuncInit);
1198d50deeebSshaneh 
11998a922f75Sshaneh   return SQLITE_OK;
12008a922f75Sshaneh }
12018a922f75Sshaneh 
12028a922f75Sshaneh /*
120378c4de4cSshaneh ** CAPI: Shutdown the multiplex system - sqlite3_multiplex_shutdown()
12048a922f75Sshaneh **
12058a922f75Sshaneh ** All SQLite database connections must be closed before calling this
12068a922f75Sshaneh ** routine.
12078a922f75Sshaneh **
12088a922f75Sshaneh ** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once while
12098a922f75Sshaneh ** shutting down in order to free all remaining multiplex groups.
12108a922f75Sshaneh */
sqlite3_multiplex_shutdown(int eForce)1211a1a8298cSdrh int sqlite3_multiplex_shutdown(int eForce){
1212a1a8298cSdrh   int rc = SQLITE_OK;
12138a922f75Sshaneh   if( gMultiplex.isInitialized==0 ) return SQLITE_MISUSE;
12148a922f75Sshaneh   gMultiplex.isInitialized = 0;
12158a922f75Sshaneh   sqlite3_vfs_unregister(&gMultiplex.sThisVfs);
12168a922f75Sshaneh   memset(&gMultiplex, 0, sizeof(gMultiplex));
1217a1a8298cSdrh   return rc;
12188a922f75Sshaneh }
12198a922f75Sshaneh 
12208a922f75Sshaneh /***************************** Test Code ***********************************/
12218a922f75Sshaneh #ifdef SQLITE_TEST
122252b1dbb5Smistachkin #if defined(INCLUDE_SQLITE_TCL_H)
122352b1dbb5Smistachkin #  include "sqlite_tcl.h"
122452b1dbb5Smistachkin #else
122552b1dbb5Smistachkin #  include "tcl.h"
12267617e4a8Smistachkin #  ifndef SQLITE_TCLAPI
12277617e4a8Smistachkin #    define SQLITE_TCLAPI
12287617e4a8Smistachkin #  endif
122952b1dbb5Smistachkin #endif
1230e84d8d32Smistachkin extern const char *sqlite3ErrName(int);
12318a922f75Sshaneh 
12328a922f75Sshaneh 
12338a922f75Sshaneh /*
12348a922f75Sshaneh ** tclcmd: sqlite3_multiplex_initialize NAME MAKEDEFAULT
12358a922f75Sshaneh */
test_multiplex_initialize(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])12367617e4a8Smistachkin static int SQLITE_TCLAPI test_multiplex_initialize(
12378a922f75Sshaneh   void * clientData,
12388a922f75Sshaneh   Tcl_Interp *interp,
12398a922f75Sshaneh   int objc,
12408a922f75Sshaneh   Tcl_Obj *CONST objv[]
12418a922f75Sshaneh ){
12428a922f75Sshaneh   const char *zName;              /* Name of new multiplex VFS */
12438a922f75Sshaneh   int makeDefault;                /* True to make the new VFS the default */
12448a922f75Sshaneh   int rc;                         /* Value returned by multiplex_initialize() */
12458a922f75Sshaneh 
12468a922f75Sshaneh   UNUSED_PARAMETER(clientData);
12478a922f75Sshaneh 
12488a922f75Sshaneh   /* Process arguments */
12498a922f75Sshaneh   if( objc!=3 ){
12508a922f75Sshaneh     Tcl_WrongNumArgs(interp, 1, objv, "NAME MAKEDEFAULT");
12518a922f75Sshaneh     return TCL_ERROR;
12528a922f75Sshaneh   }
12538a922f75Sshaneh   zName = Tcl_GetString(objv[1]);
12548a922f75Sshaneh   if( Tcl_GetBooleanFromObj(interp, objv[2], &makeDefault) ) return TCL_ERROR;
12558a922f75Sshaneh   if( zName[0]=='\0' ) zName = 0;
12568a922f75Sshaneh 
12578a922f75Sshaneh   /* Call sqlite3_multiplex_initialize() */
12588a922f75Sshaneh   rc = sqlite3_multiplex_initialize(zName, makeDefault);
1259e84d8d32Smistachkin   Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
12608a922f75Sshaneh 
12618a922f75Sshaneh   return TCL_OK;
12628a922f75Sshaneh }
12638a922f75Sshaneh 
12648a922f75Sshaneh /*
12658a922f75Sshaneh ** tclcmd: sqlite3_multiplex_shutdown
12668a922f75Sshaneh */
test_multiplex_shutdown(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])12677617e4a8Smistachkin static int SQLITE_TCLAPI test_multiplex_shutdown(
12688a922f75Sshaneh   void * clientData,
12698a922f75Sshaneh   Tcl_Interp *interp,
12708a922f75Sshaneh   int objc,
12718a922f75Sshaneh   Tcl_Obj *CONST objv[]
12728a922f75Sshaneh ){
12738a922f75Sshaneh   int rc;                         /* Value returned by multiplex_shutdown() */
12748a922f75Sshaneh 
12758a922f75Sshaneh   UNUSED_PARAMETER(clientData);
12768a922f75Sshaneh 
1277a1a8298cSdrh   if( objc==2 && strcmp(Tcl_GetString(objv[1]),"-force")!=0 ){
1278a1a8298cSdrh     objc = 3;
1279a1a8298cSdrh   }
1280a1a8298cSdrh   if( (objc!=1 && objc!=2) ){
1281a1a8298cSdrh     Tcl_WrongNumArgs(interp, 1, objv, "?-force?");
12828a922f75Sshaneh     return TCL_ERROR;
12838a922f75Sshaneh   }
12848a922f75Sshaneh 
12858a922f75Sshaneh   /* Call sqlite3_multiplex_shutdown() */
1286a1a8298cSdrh   rc = sqlite3_multiplex_shutdown(objc==2);
1287e84d8d32Smistachkin   Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
12888a922f75Sshaneh 
12898a922f75Sshaneh   return TCL_OK;
12908a922f75Sshaneh }
12918a922f75Sshaneh 
12928a922f75Sshaneh /*
1293d50deeebSshaneh ** Tclcmd: test_multiplex_control HANDLE DBNAME SUB-COMMAND ?INT-VALUE?
1294d50deeebSshaneh */
test_multiplex_control(ClientData cd,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])12957617e4a8Smistachkin static int SQLITE_TCLAPI test_multiplex_control(
1296d50deeebSshaneh   ClientData cd,
1297d50deeebSshaneh   Tcl_Interp *interp,
1298d50deeebSshaneh   int objc,
1299d50deeebSshaneh   Tcl_Obj *CONST objv[]
1300d50deeebSshaneh ){
1301d50deeebSshaneh   int rc;                         /* Return code from file_control() */
1302d50deeebSshaneh   int idx;                        /* Index in aSub[] */
1303d50deeebSshaneh   Tcl_CmdInfo cmdInfo;            /* Command info structure for HANDLE */
1304d50deeebSshaneh   sqlite3 *db;                    /* Underlying db handle for HANDLE */
1305d50deeebSshaneh   int iValue = 0;
1306d50deeebSshaneh   void *pArg = 0;
1307d50deeebSshaneh 
1308d50deeebSshaneh   struct SubCommand {
1309d50deeebSshaneh     const char *zName;
1310d50deeebSshaneh     int op;
1311d50deeebSshaneh     int argtype;
1312d50deeebSshaneh   } aSub[] = {
1313d50deeebSshaneh     { "enable",       MULTIPLEX_CTRL_ENABLE,           1 },
1314d50deeebSshaneh     { "chunk_size",   MULTIPLEX_CTRL_SET_CHUNK_SIZE,   1 },
1315d50deeebSshaneh     { "max_chunks",   MULTIPLEX_CTRL_SET_MAX_CHUNKS,   1 },
1316d50deeebSshaneh     { 0, 0, 0 }
1317d50deeebSshaneh   };
1318d50deeebSshaneh 
1319c27fa4b0Sshaneh   if( objc!=5 ){
1320c27fa4b0Sshaneh     Tcl_WrongNumArgs(interp, 1, objv, "HANDLE DBNAME SUB-COMMAND INT-VALUE");
1321d50deeebSshaneh     return TCL_ERROR;
1322d50deeebSshaneh   }
1323d50deeebSshaneh 
1324d50deeebSshaneh   if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &cmdInfo) ){
1325d50deeebSshaneh     Tcl_AppendResult(interp, "expected database handle, got \"", 0);
1326d50deeebSshaneh     Tcl_AppendResult(interp, Tcl_GetString(objv[1]), "\"", 0);
1327d50deeebSshaneh     return TCL_ERROR;
1328d50deeebSshaneh   }else{
1329d50deeebSshaneh     db = *(sqlite3 **)cmdInfo.objClientData;
1330d50deeebSshaneh   }
1331d50deeebSshaneh 
1332d50deeebSshaneh   rc = Tcl_GetIndexFromObjStruct(
1333d50deeebSshaneh       interp, objv[3], aSub, sizeof(aSub[0]), "sub-command", 0, &idx
1334d50deeebSshaneh   );
1335d50deeebSshaneh   if( rc!=TCL_OK ) return rc;
1336d50deeebSshaneh 
1337d50deeebSshaneh   switch( aSub[idx].argtype ){
1338d50deeebSshaneh     case 1:
1339d50deeebSshaneh       if( Tcl_GetIntFromObj(interp, objv[4], &iValue) ){
1340d50deeebSshaneh         return TCL_ERROR;
1341d50deeebSshaneh       }
1342d50deeebSshaneh       pArg = (void *)&iValue;
1343d50deeebSshaneh       break;
1344d50deeebSshaneh     default:
1345d50deeebSshaneh       Tcl_WrongNumArgs(interp, 4, objv, "SUB-COMMAND");
1346d50deeebSshaneh       return TCL_ERROR;
1347d50deeebSshaneh   }
1348d50deeebSshaneh 
1349d50deeebSshaneh   rc = sqlite3_file_control(db, Tcl_GetString(objv[2]), aSub[idx].op, pArg);
1350e84d8d32Smistachkin   Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
1351d50deeebSshaneh   return (rc==SQLITE_OK) ? TCL_OK : TCL_ERROR;
1352d50deeebSshaneh }
1353d50deeebSshaneh 
1354d50deeebSshaneh /*
13558a922f75Sshaneh ** This routine registers the custom TCL commands defined in this
13568a922f75Sshaneh ** module.  This should be the only procedure visible from outside
13578a922f75Sshaneh ** of this module.
13588a922f75Sshaneh */
Sqlitemultiplex_Init(Tcl_Interp * interp)13598a922f75Sshaneh int Sqlitemultiplex_Init(Tcl_Interp *interp){
13608a922f75Sshaneh   static struct {
13618a922f75Sshaneh      char *zName;
13628a922f75Sshaneh      Tcl_ObjCmdProc *xProc;
13638a922f75Sshaneh   } aCmd[] = {
13648a922f75Sshaneh     { "sqlite3_multiplex_initialize", test_multiplex_initialize },
13658a922f75Sshaneh     { "sqlite3_multiplex_shutdown", test_multiplex_shutdown },
1366d50deeebSshaneh     { "sqlite3_multiplex_control", test_multiplex_control },
13678a922f75Sshaneh   };
13688a922f75Sshaneh   int i;
13698a922f75Sshaneh 
13708a922f75Sshaneh   for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
13718a922f75Sshaneh     Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
13728a922f75Sshaneh   }
13738a922f75Sshaneh 
13748a922f75Sshaneh   return TCL_OK;
13758a922f75Sshaneh }
13768a922f75Sshaneh #endif
1377