xref: /sqlite-3.40.0/src/test_multiplex.c (revision ee68ccfb)
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 
713801b65dSshaneh /*
72bb201344Sshaneh ** For a build without mutexes, no-op the mutex calls.
73bb201344Sshaneh */
74bb201344Sshaneh #if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0
75bb201344Sshaneh #define sqlite3_mutex_alloc(X)    ((sqlite3_mutex*)8)
76bb201344Sshaneh #define sqlite3_mutex_free(X)
77bb201344Sshaneh #define sqlite3_mutex_enter(X)
78bb201344Sshaneh #define sqlite3_mutex_try(X)      SQLITE_OK
79bb201344Sshaneh #define sqlite3_mutex_leave(X)
80bb201344Sshaneh #define sqlite3_mutex_held(X)     ((void)(X),1)
81bb201344Sshaneh #define sqlite3_mutex_notheld(X)  ((void)(X),1)
82bb201344Sshaneh #endif /* SQLITE_THREADSAFE==0 */
83bb201344Sshaneh 
845fb96aafSdrh /* Maximum chunk number */
855fb96aafSdrh #define MX_CHUNK_NUMBER 299
865fb96aafSdrh 
8743a6d4bdSdrh /* First chunk for rollback journal files */
8827e69643Sdan #define SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET 400
895fb96aafSdrh #define SQLITE_MULTIPLEX_WAL_8_3_OFFSET 700
9027e69643Sdan 
91bb201344Sshaneh 
928a922f75Sshaneh /************************ Shim Definitions ******************************/
938a922f75Sshaneh 
946f25e89dSdrh #ifndef SQLITE_MULTIPLEX_VFS_NAME
95d9523b97Sshaneh # define SQLITE_MULTIPLEX_VFS_NAME "multiplex"
966f25e89dSdrh #endif
97d9523b97Sshaneh 
98050d09abSshaneh /* This is the limit on the chunk size.  It may be changed by calling
9978c4de4cSshaneh ** the xFileControl() interface.  It will be rounded up to a
1006f25e89dSdrh ** multiple of MAX_PAGE_SIZE.  We default it here to 2GiB less 64KiB.
101050d09abSshaneh */
1026f25e89dSdrh #ifndef SQLITE_MULTIPLEX_CHUNK_SIZE
1036f25e89dSdrh # define SQLITE_MULTIPLEX_CHUNK_SIZE 2147418112
1046f25e89dSdrh #endif
105c27fa4b0Sshaneh 
10673795becSdrh /* This used to be the default limit on number of chunks, but
10773795becSdrh ** it is no longer enforced. There is currently no limit to the
10873795becSdrh ** number of chunks.
10973795becSdrh **
11073795becSdrh ** May be changed by calling the xFileControl() interface.
111050d09abSshaneh */
1126f25e89dSdrh #ifndef SQLITE_MULTIPLEX_MAX_CHUNKS
11373795becSdrh # define SQLITE_MULTIPLEX_MAX_CHUNKS 12
1146f25e89dSdrh #endif
115050d09abSshaneh 
1168a922f75Sshaneh /************************ Object Definitions ******************************/
1178a922f75Sshaneh 
1188a922f75Sshaneh /* Forward declaration of all object types */
1198a922f75Sshaneh typedef struct multiplexGroup multiplexGroup;
1208a922f75Sshaneh typedef struct multiplexConn multiplexConn;
1218a922f75Sshaneh 
1228a922f75Sshaneh /*
1238a922f75Sshaneh ** A "multiplex group" is a collection of files that collectively
1248a922f75Sshaneh ** makeup a single SQLite DB file.  This allows the size of the DB
1258a922f75Sshaneh ** to exceed the limits imposed by the file system.
1268a922f75Sshaneh **
1278a922f75Sshaneh ** There is an instance of the following object for each defined multiplex
1288a922f75Sshaneh ** group.
1298a922f75Sshaneh */
1308a922f75Sshaneh struct multiplexGroup {
131f3717af4Sdrh   struct multiplexReal {           /* For each chunk */
132f3717af4Sdrh     sqlite3_file *p;                  /* Handle for the chunk */
133f3717af4Sdrh     char *z;                          /* Name of this chunk */
134f3717af4Sdrh   } *aReal;                        /* list of all chunks */
135f3717af4Sdrh   int nReal;                       /* Number of chunks */
1368a922f75Sshaneh   char *zName;                     /* Base filename of this group */
1378a922f75Sshaneh   int nName;                       /* Length of base filename */
1388a922f75Sshaneh   int flags;                       /* Flags used for original opening */
139e6deb204Sdrh   unsigned int szChunk;            /* Chunk size used for this group */
140e7d9f13dSdrh   unsigned char bEnabled;          /* TRUE to use Multiplex VFS for this file */
141e7d9f13dSdrh   unsigned char bTruncate;         /* TRUE to enable truncation of databases */
1428a922f75Sshaneh   multiplexGroup *pNext, *pPrev;   /* Doubly linked list of all group objects */
1438a922f75Sshaneh };
1448a922f75Sshaneh 
1458a922f75Sshaneh /*
1468a922f75Sshaneh ** An instance of the following object represents each open connection
1478a922f75Sshaneh ** to a file that is multiplex'ed.  This object is a
1488a922f75Sshaneh ** subclass of sqlite3_file.  The sqlite3_file object for the underlying
1498a922f75Sshaneh ** VFS is appended to this structure.
1508a922f75Sshaneh */
1518a922f75Sshaneh struct multiplexConn {
1528a922f75Sshaneh   sqlite3_file base;              /* Base class - must be first */
1538a922f75Sshaneh   multiplexGroup *pGroup;         /* The underlying group of files */
1548a922f75Sshaneh };
1558a922f75Sshaneh 
1568a922f75Sshaneh /************************* Global Variables **********************************/
1578a922f75Sshaneh /*
1588a922f75Sshaneh ** All global variables used by this file are containing within the following
1598a922f75Sshaneh ** gMultiplex structure.
1608a922f75Sshaneh */
1618a922f75Sshaneh static struct {
1628a922f75Sshaneh   /* The pOrigVfs is the real, original underlying VFS implementation.
1638a922f75Sshaneh   ** Most operations pass-through to the real VFS.  This value is read-only
1648a922f75Sshaneh   ** during operation.  It is only modified at start-time and thus does not
1658a922f75Sshaneh   ** require a mutex.
1668a922f75Sshaneh   */
1678a922f75Sshaneh   sqlite3_vfs *pOrigVfs;
1688a922f75Sshaneh 
1698a922f75Sshaneh   /* The sThisVfs is the VFS structure used by this shim.  It is initialized
1708a922f75Sshaneh   ** at start-time and thus does not require a mutex
1718a922f75Sshaneh   */
1728a922f75Sshaneh   sqlite3_vfs sThisVfs;
1738a922f75Sshaneh 
1748a922f75Sshaneh   /* The sIoMethods defines the methods used by sqlite3_file objects
1758a922f75Sshaneh   ** associated with this shim.  It is initialized at start-time and does
1768a922f75Sshaneh   ** not require a mutex.
1778a922f75Sshaneh   **
1788a922f75Sshaneh   ** When the underlying VFS is called to open a file, it might return
1798a922f75Sshaneh   ** either a version 1 or a version 2 sqlite3_file object.  This shim
1808a922f75Sshaneh   ** has to create a wrapper sqlite3_file of the same version.  Hence
1818a922f75Sshaneh   ** there are two I/O method structures, one for version 1 and the other
1828a922f75Sshaneh   ** for version 2.
1838a922f75Sshaneh   */
1848a922f75Sshaneh   sqlite3_io_methods sIoMethodsV1;
1858a922f75Sshaneh   sqlite3_io_methods sIoMethodsV2;
1868a922f75Sshaneh 
187fd1552f2Sshaneh   /* True when this shim has been initialized.
1888a922f75Sshaneh   */
1898a922f75Sshaneh   int isInitialized;
1908a922f75Sshaneh 
1918a922f75Sshaneh   /* For run-time access any of the other global data structures in this
1928a922f75Sshaneh   ** shim, the following mutex must be held.
1938a922f75Sshaneh   */
1948a922f75Sshaneh   sqlite3_mutex *pMutex;
1958a922f75Sshaneh 
1968a922f75Sshaneh   /* List of multiplexGroup objects.
1978a922f75Sshaneh   */
1988a922f75Sshaneh   multiplexGroup *pGroups;
1998a922f75Sshaneh } gMultiplex;
2008a922f75Sshaneh 
2018a922f75Sshaneh /************************* Utility Routines *********************************/
2028a922f75Sshaneh /*
2038a922f75Sshaneh ** Acquire and release the mutex used to serialize access to the
2048a922f75Sshaneh ** list of multiplexGroups.
2058a922f75Sshaneh */
2068a922f75Sshaneh static void multiplexEnter(void){ sqlite3_mutex_enter(gMultiplex.pMutex); }
2078a922f75Sshaneh static void multiplexLeave(void){ sqlite3_mutex_leave(gMultiplex.pMutex); }
2088a922f75Sshaneh 
2093801b65dSshaneh /*
2103801b65dSshaneh ** Compute a string length that is limited to what can be stored in
2113801b65dSshaneh ** lower 30 bits of a 32-bit signed integer.
2123801b65dSshaneh **
2133801b65dSshaneh ** The value returned will never be negative.  Nor will it ever be greater
2143801b65dSshaneh ** than the actual length of the string.  For very long strings (greater
2153801b65dSshaneh ** than 1GiB) the value returned might be less than the true string length.
2163801b65dSshaneh */
217cc4e19beSshaneh static int multiplexStrlen30(const char *z){
2183801b65dSshaneh   const char *z2 = z;
2193801b65dSshaneh   if( z==0 ) return 0;
2203801b65dSshaneh   while( *z2 ){ z2++; }
2213801b65dSshaneh   return 0x3fffffff & (int)(z2 - z);
2223801b65dSshaneh }
2233801b65dSshaneh 
224e712b582Sdan /*
225e712b582Sdan ** Generate the file-name for chunk iChunk of the group with base name
226e712b582Sdan ** zBase. The file-name is written to buffer zOut before returning. Buffer
227730f85a3Sdan ** zOut must be allocated by the caller so that it is at least (nBase+5)
228e712b582Sdan ** bytes in size, where nBase is the length of zBase, not including the
229e712b582Sdan ** nul-terminator.
230730f85a3Sdan **
231730f85a3Sdan ** If iChunk is 0 (or 400 - the number for the first journal file chunk),
232730f85a3Sdan ** the output is a copy of the input string. Otherwise, if
233730f85a3Sdan ** SQLITE_ENABLE_8_3_NAMES is not defined or the input buffer does not contain
234730f85a3Sdan ** a "." character, then the output is a copy of the input string with the
235730f85a3Sdan ** three-digit zero-padded decimal representation if iChunk appended to it.
236730f85a3Sdan ** For example:
237730f85a3Sdan **
238730f85a3Sdan **   zBase="test.db", iChunk=4  ->  zOut="test.db004"
239730f85a3Sdan **
240730f85a3Sdan ** Or, if SQLITE_ENABLE_8_3_NAMES is defined and the input buffer contains
241730f85a3Sdan ** a "." character, then everything after the "." is replaced by the
242730f85a3Sdan ** three-digit representation of iChunk.
243730f85a3Sdan **
244730f85a3Sdan **   zBase="test.db", iChunk=4  ->  zOut="test.004"
245730f85a3Sdan **
246730f85a3Sdan ** The output buffer string is terminated by 2 0x00 bytes. This makes it safe
247730f85a3Sdan ** to pass to sqlite3_uri_parameter() and similar.
248e712b582Sdan */
249e712b582Sdan static void multiplexFilename(
250e712b582Sdan   const char *zBase,              /* Filename for chunk 0 */
251e712b582Sdan   int nBase,                      /* Size of zBase in bytes (without \0) */
252e712b582Sdan   int flags,                      /* Flags used to open file */
253e712b582Sdan   int iChunk,                     /* Chunk to generate filename for */
254e712b582Sdan   char *zOut                      /* Buffer to write generated name to */
255e712b582Sdan ){
256e712b582Sdan   int n = nBase;
257730f85a3Sdan   memcpy(zOut, zBase, n+1);
2585fb96aafSdrh   if( iChunk!=0 && iChunk<=MX_CHUNK_NUMBER ){
259e712b582Sdan #ifdef SQLITE_ENABLE_8_3_NAMES
260e712b582Sdan     int i;
261e712b582Sdan     for(i=n-1; i>0 && i>=n-4 && zOut[i]!='.'; i--){}
262e712b582Sdan     if( i>=n-4 ) n = i+1;
2635fb96aafSdrh     if( flags & SQLITE_OPEN_MAIN_JOURNAL ){
264e712b582Sdan       /* The extensions on overflow files for main databases are 001, 002,
265e712b582Sdan       ** 003 and so forth.  To avoid name collisions, add 400 to the
266e712b582Sdan       ** extensions of journal files so that they are 401, 402, 403, ....
267e712b582Sdan       */
268e712b582Sdan       iChunk += SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET;
2695fb96aafSdrh     }else if( flags & SQLITE_OPEN_WAL ){
2705fb96aafSdrh       /* To avoid name collisions, add 700 to the
2715fb96aafSdrh       ** extensions of WAL files so that they are 701, 702, 703, ....
2725fb96aafSdrh       */
2735fb96aafSdrh       iChunk += SQLITE_MULTIPLEX_WAL_8_3_OFFSET;
274e712b582Sdan     }
275e712b582Sdan #endif
276e712b582Sdan     sqlite3_snprintf(4,&zOut[n],"%03d",iChunk);
277730f85a3Sdan     n += 3;
278e712b582Sdan   }
279730f85a3Sdan 
280730f85a3Sdan   assert( zOut[n]=='\0' );
281730f85a3Sdan   zOut[n+1] = '\0';
282e712b582Sdan }
283e712b582Sdan 
284f3717af4Sdrh /* Compute the filename for the iChunk-th chunk
285f3717af4Sdrh */
286f3717af4Sdrh static int multiplexSubFilename(multiplexGroup *pGroup, int iChunk){
287f3717af4Sdrh   if( iChunk>=pGroup->nReal ){
288f3717af4Sdrh     struct multiplexReal *p;
289f3717af4Sdrh     p = sqlite3_realloc(pGroup->aReal, (iChunk+1)*sizeof(*p));
290f3717af4Sdrh     if( p==0 ){
291f3717af4Sdrh       return SQLITE_NOMEM;
292f3717af4Sdrh     }
293f3717af4Sdrh     memset(&p[pGroup->nReal], 0, sizeof(p[0])*(iChunk+1-pGroup->nReal));
294f3717af4Sdrh     pGroup->aReal = p;
295f3717af4Sdrh     pGroup->nReal = iChunk+1;
296f3717af4Sdrh   }
2975b1626aaSdrh   if( pGroup->zName && pGroup->aReal[iChunk].z==0 ){
298f3717af4Sdrh     char *z;
299f3717af4Sdrh     int n = pGroup->nName;
300730f85a3Sdan     pGroup->aReal[iChunk].z = z = sqlite3_malloc( n+5 );
301f3717af4Sdrh     if( z==0 ){
302f3717af4Sdrh       return SQLITE_NOMEM;
303f3717af4Sdrh     }
304e712b582Sdan     multiplexFilename(pGroup->zName, pGroup->nName, pGroup->flags, iChunk, z);
305f3717af4Sdrh   }
306f3717af4Sdrh   return SQLITE_OK;
307f3717af4Sdrh }
308f3717af4Sdrh 
3098a922f75Sshaneh /* Translate an sqlite3_file* that is really a multiplexGroup* into
3108a922f75Sshaneh ** the sqlite3_file* for the underlying original VFS.
311e7d9f13dSdrh **
312e7d9f13dSdrh ** For chunk 0, the pGroup->flags determines whether or not a new file
313e7d9f13dSdrh ** is created if it does not already exist.  For chunks 1 and higher, the
314e7d9f13dSdrh ** file is created only if createFlag is 1.
3158a922f75Sshaneh */
3168efdb732Sdrh static sqlite3_file *multiplexSubOpen(
317e7d9f13dSdrh   multiplexGroup *pGroup,    /* The multiplexor group */
318e7d9f13dSdrh   int iChunk,                /* Which chunk to open.  0==original file */
319e7d9f13dSdrh   int *rc,                   /* Result code in and out */
320e7d9f13dSdrh   int *pOutFlags,            /* Output flags */
321e7d9f13dSdrh   int createFlag             /* True to create if iChunk>0 */
3228efdb732Sdrh ){
323f3717af4Sdrh   sqlite3_file *pSubOpen = 0;
3248a922f75Sshaneh   sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;        /* Real VFS */
32527e69643Sdan 
32627e69643Sdan #ifdef SQLITE_ENABLE_8_3_NAMES
32743a6d4bdSdrh   /* If JOURNAL_8_3_OFFSET is set to (say) 400, then any overflow files are
32843a6d4bdSdrh   ** part of a database journal are named db.401, db.402, and so on. A
32943a6d4bdSdrh   ** database may therefore not grow to larger than 400 chunks. Attempting
33043a6d4bdSdrh   ** to open chunk 401 indicates the database is full. */
33127e69643Sdan   if( iChunk>=SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET ){
332689453dbSdrh     sqlite3_log(SQLITE_FULL, "multiplexed chunk overflow: %s", pGroup->zName);
33327e69643Sdan     *rc = SQLITE_FULL;
33427e69643Sdan     return 0;
33527e69643Sdan   }
33627e69643Sdan #endif
33727e69643Sdan 
338f3717af4Sdrh   *rc = multiplexSubFilename(pGroup, iChunk);
339f3717af4Sdrh   if( (*rc)==SQLITE_OK && (pSubOpen = pGroup->aReal[iChunk].p)==0 ){
34006999667Sdrh     int flags, bExists;
341e7d9f13dSdrh     flags = pGroup->flags;
342e7d9f13dSdrh     if( createFlag ){
343e7d9f13dSdrh       flags |= SQLITE_OPEN_CREATE;
3448c24a369Sdrh     }else if( iChunk==0 ){
3458c24a369Sdrh       /* Fall through */
346e7d9f13dSdrh     }else if( pGroup->aReal[iChunk].z==0 ){
347e7d9f13dSdrh       return 0;
348e7d9f13dSdrh     }else{
349e7d9f13dSdrh       *rc = pOrigVfs->xAccess(pOrigVfs, pGroup->aReal[iChunk].z,
350e7d9f13dSdrh                               SQLITE_ACCESS_EXISTS, &bExists);
3511779efd4Sdrh      if( *rc || !bExists ){
3521779efd4Sdrh         if( *rc ){
3531779efd4Sdrh           sqlite3_log(*rc, "multiplexor.xAccess failure on %s",
3541779efd4Sdrh                       pGroup->aReal[iChunk].z);
3551779efd4Sdrh         }
3561779efd4Sdrh         return 0;
3571779efd4Sdrh       }
358e7d9f13dSdrh       flags &= ~SQLITE_OPEN_CREATE;
359e7d9f13dSdrh     }
360f3717af4Sdrh     pSubOpen = sqlite3_malloc( pOrigVfs->szOsFile );
361f3717af4Sdrh     if( pSubOpen==0 ){
3628c24a369Sdrh       *rc = SQLITE_IOERR_NOMEM;
363f3717af4Sdrh       return 0;
364050d09abSshaneh     }
365f3717af4Sdrh     pGroup->aReal[iChunk].p = pSubOpen;
36606999667Sdrh     *rc = pOrigVfs->xOpen(pOrigVfs, pGroup->aReal[iChunk].z, pSubOpen,
367e7d9f13dSdrh                           flags, pOutFlags);
36806999667Sdrh     if( (*rc)!=SQLITE_OK ){
3691779efd4Sdrh       sqlite3_log(*rc, "multiplexor.xOpen failure on %s",
3701779efd4Sdrh                   pGroup->aReal[iChunk].z);
371f3717af4Sdrh       sqlite3_free(pSubOpen);
372f3717af4Sdrh       pGroup->aReal[iChunk].p = 0;
373f3717af4Sdrh       return 0;
374f3717af4Sdrh     }
375f3717af4Sdrh   }
3768a922f75Sshaneh   return pSubOpen;
3778a922f75Sshaneh }
3788a922f75Sshaneh 
3793801b65dSshaneh /*
380e7d9f13dSdrh ** Return the size, in bytes, of chunk number iChunk.  If that chunk
381e7d9f13dSdrh ** does not exist, then return 0.  This function does not distingish between
382e7d9f13dSdrh ** non-existant files and zero-length files.
383e7d9f13dSdrh */
384e7d9f13dSdrh static sqlite3_int64 multiplexSubSize(
385e7d9f13dSdrh   multiplexGroup *pGroup,    /* The multiplexor group */
386e7d9f13dSdrh   int iChunk,                /* Which chunk to open.  0==original file */
387e7d9f13dSdrh   int *rc                    /* Result code in and out */
388e7d9f13dSdrh ){
389e7d9f13dSdrh   sqlite3_file *pSub;
390e7d9f13dSdrh   sqlite3_int64 sz = 0;
391e7d9f13dSdrh 
392b04f3c15Sdrh   if( *rc ) return 0;
393e7d9f13dSdrh   pSub = multiplexSubOpen(pGroup, iChunk, rc, NULL, 0);
394e7d9f13dSdrh   if( pSub==0 ) return 0;
395e7d9f13dSdrh   *rc = pSub->pMethods->xFileSize(pSub, &sz);
396e7d9f13dSdrh   return sz;
397e7d9f13dSdrh }
398e7d9f13dSdrh 
399e7d9f13dSdrh /*
4003801b65dSshaneh ** This is the implementation of the multiplex_control() SQL function.
4013801b65dSshaneh */
402d50deeebSshaneh static void multiplexControlFunc(
403d50deeebSshaneh   sqlite3_context *context,
404d50deeebSshaneh   int argc,
405d50deeebSshaneh   sqlite3_value **argv
406d50deeebSshaneh ){
40778c4de4cSshaneh   int rc = SQLITE_OK;
408d8ce22bbSshaneh   sqlite3 *db = sqlite3_context_db_handle(context);
409d8ce22bbSshaneh   int op;
410d8ce22bbSshaneh   int iVal;
411d8ce22bbSshaneh 
412d8ce22bbSshaneh   if( !db || argc!=2 ){
413d8ce22bbSshaneh     rc = SQLITE_ERROR;
414d8ce22bbSshaneh   }else{
415d8ce22bbSshaneh     /* extract params */
416d8ce22bbSshaneh     op = sqlite3_value_int(argv[0]);
417d8ce22bbSshaneh     iVal = sqlite3_value_int(argv[1]);
418d8ce22bbSshaneh     /* map function op to file_control op */
41978c4de4cSshaneh     switch( op ){
42078c4de4cSshaneh       case 1:
42178c4de4cSshaneh         op = MULTIPLEX_CTRL_ENABLE;
42278c4de4cSshaneh         break;
42378c4de4cSshaneh       case 2:
42478c4de4cSshaneh         op = MULTIPLEX_CTRL_SET_CHUNK_SIZE;
42578c4de4cSshaneh         break;
42678c4de4cSshaneh       case 3:
42778c4de4cSshaneh         op = MULTIPLEX_CTRL_SET_MAX_CHUNKS;
42878c4de4cSshaneh         break;
42978c4de4cSshaneh       default:
4303801b65dSshaneh         rc = SQLITE_NOTFOUND;
43178c4de4cSshaneh         break;
43278c4de4cSshaneh     }
433d8ce22bbSshaneh   }
43478c4de4cSshaneh   if( rc==SQLITE_OK ){
435d8ce22bbSshaneh     rc = sqlite3_file_control(db, 0, op, &iVal);
436d9523b97Sshaneh   }
4373801b65dSshaneh   sqlite3_result_error_code(context, rc);
438d9523b97Sshaneh }
439d9523b97Sshaneh 
440d9523b97Sshaneh /*
4413801b65dSshaneh ** This is the entry point to register the auto-extension for the
4423801b65dSshaneh ** multiplex_control() function.
443d9523b97Sshaneh */
444d50deeebSshaneh static int multiplexFuncInit(
44578c4de4cSshaneh   sqlite3 *db,
44678c4de4cSshaneh   char **pzErrMsg,
44778c4de4cSshaneh   const sqlite3_api_routines *pApi
448d50deeebSshaneh ){
449ac039688Sshaneh   int rc;
450ac039688Sshaneh   rc = sqlite3_create_function(db, "multiplex_control", 2, SQLITE_ANY,
451d8ce22bbSshaneh       0, multiplexControlFunc, 0, 0);
452ac039688Sshaneh   return rc;
453d9523b97Sshaneh }
454d9523b97Sshaneh 
455f3717af4Sdrh /*
456f3717af4Sdrh ** Close a single sub-file in the connection group.
457f3717af4Sdrh */
458f3717af4Sdrh static void multiplexSubClose(
459f3717af4Sdrh   multiplexGroup *pGroup,
460f3717af4Sdrh   int iChunk,
461f3717af4Sdrh   sqlite3_vfs *pOrigVfs
462f3717af4Sdrh ){
463f3717af4Sdrh   sqlite3_file *pSubOpen = pGroup->aReal[iChunk].p;
464f3717af4Sdrh   if( pSubOpen ){
465f3717af4Sdrh     pSubOpen->pMethods->xClose(pSubOpen);
4662be25bffSdrh     if( pOrigVfs && pGroup->aReal[iChunk].z ){
4672be25bffSdrh       pOrigVfs->xDelete(pOrigVfs, pGroup->aReal[iChunk].z, 0);
4682be25bffSdrh     }
469f3717af4Sdrh     sqlite3_free(pGroup->aReal[iChunk].p);
470f3717af4Sdrh   }
471f3717af4Sdrh   sqlite3_free(pGroup->aReal[iChunk].z);
472f3717af4Sdrh   memset(&pGroup->aReal[iChunk], 0, sizeof(pGroup->aReal[iChunk]));
473f3717af4Sdrh }
474f3717af4Sdrh 
475f3717af4Sdrh /*
476f3717af4Sdrh ** Deallocate memory held by a multiplexGroup
477f3717af4Sdrh */
478f3717af4Sdrh static void multiplexFreeComponents(multiplexGroup *pGroup){
479f3717af4Sdrh   int i;
480f3717af4Sdrh   for(i=0; i<pGroup->nReal; i++){ multiplexSubClose(pGroup, i, 0); }
481f3717af4Sdrh   sqlite3_free(pGroup->aReal);
482f3717af4Sdrh   pGroup->aReal = 0;
483f3717af4Sdrh   pGroup->nReal = 0;
484f3717af4Sdrh }
485f3717af4Sdrh 
486f3717af4Sdrh 
4878a922f75Sshaneh /************************* VFS Method Wrappers *****************************/
488b5830294Sshaneh 
4898a922f75Sshaneh /*
4908a922f75Sshaneh ** This is the xOpen method used for the "multiplex" VFS.
4918a922f75Sshaneh **
4928a922f75Sshaneh ** Most of the work is done by the underlying original VFS.  This method
4938a922f75Sshaneh ** simply links the new file into the appropriate multiplex group if it is a
4948a922f75Sshaneh ** file that needs to be tracked.
4958a922f75Sshaneh */
4968a922f75Sshaneh static int multiplexOpen(
4978a922f75Sshaneh   sqlite3_vfs *pVfs,         /* The multiplex VFS */
4988a922f75Sshaneh   const char *zName,         /* Name of file to be opened */
4998a922f75Sshaneh   sqlite3_file *pConn,       /* Fill in this file descriptor */
5008a922f75Sshaneh   int flags,                 /* Flags to control the opening */
5018a922f75Sshaneh   int *pOutFlags             /* Flags showing results of opening */
5028a922f75Sshaneh ){
503cc4e19beSshaneh   int rc = SQLITE_OK;                  /* Result code */
5048a922f75Sshaneh   multiplexConn *pMultiplexOpen;       /* The new multiplex file descriptor */
505fc30b042Sdrh   multiplexGroup *pGroup = 0;          /* Corresponding multiplexGroup object */
5060a0ca697Sdrh   sqlite3_file *pSubOpen = 0;                    /* Real file descriptor */
5078a922f75Sshaneh   sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */
508fc30b042Sdrh   int nName = 0;
509fc30b042Sdrh   int sz = 0;
510f3717af4Sdrh   char *zToFree = 0;
5118a922f75Sshaneh 
5128a922f75Sshaneh   UNUSED_PARAMETER(pVfs);
513f3717af4Sdrh   memset(pConn, 0, pVfs->szOsFile);
5145b1626aaSdrh   assert( zName || (flags & SQLITE_OPEN_DELETEONCLOSE) );
5158a922f75Sshaneh 
5168a922f75Sshaneh   /* We need to create a group structure and manage
5178a922f75Sshaneh   ** access to this group of files.
5188a922f75Sshaneh   */
5198a922f75Sshaneh   multiplexEnter();
5208a922f75Sshaneh   pMultiplexOpen = (multiplexConn*)pConn;
521cc4e19beSshaneh 
522cc4e19beSshaneh   if( rc==SQLITE_OK ){
523050d09abSshaneh     /* allocate space for group */
5245b1626aaSdrh     nName = zName ? multiplexStrlen30(zName) : 0;
525050d09abSshaneh     sz = sizeof(multiplexGroup)                             /* multiplexGroup */
526050d09abSshaneh        + nName + 1;                                         /* zName */
527050d09abSshaneh     pGroup = sqlite3_malloc( sz );
5288a922f75Sshaneh     if( pGroup==0 ){
5298a922f75Sshaneh       rc = SQLITE_NOMEM;
530cc4e19beSshaneh     }
531cc4e19beSshaneh   }
532cc4e19beSshaneh 
533cc4e19beSshaneh   if( rc==SQLITE_OK ){
534c02a43afSdrh     const char *zUri = (flags & SQLITE_OPEN_URI) ? zName : 0;
535050d09abSshaneh     /* assign pointers to extra space allocated */
536050d09abSshaneh     memset(pGroup, 0, sz);
5375b1626aaSdrh     pMultiplexOpen->pGroup = pGroup;
538c27fa4b0Sshaneh     pGroup->bEnabled = -1;
539c02a43afSdrh     pGroup->bTruncate = sqlite3_uri_boolean(zUri, "truncate",
54092913720Sdrh                                    (flags & SQLITE_OPEN_MAIN_DB)==0);
5417da5fcb0Sdrh     pGroup->szChunk = (int)sqlite3_uri_int64(zUri, "chunksize",
54292913720Sdrh                                         SQLITE_MULTIPLEX_CHUNK_SIZE);
54392913720Sdrh     pGroup->szChunk = (pGroup->szChunk+0xffff)&~0xffff;
5445b1626aaSdrh     if( zName ){
5455b1626aaSdrh       char *p = (char *)&pGroup[1];
546050d09abSshaneh       pGroup->zName = p;
5478a922f75Sshaneh       memcpy(pGroup->zName, zName, nName+1);
5488a922f75Sshaneh       pGroup->nName = nName;
5495b1626aaSdrh     }
55078c0eafbSdrh     if( pGroup->bEnabled ){
55192913720Sdrh       /* Make sure that the chunksize is such that the pending byte does not
55278c0eafbSdrh       ** falls at the end of a chunk.  A region of up to 64K following
55378c0eafbSdrh       ** the pending byte is never written, so if the pending byte occurs
55478c0eafbSdrh       ** near the end of a chunk, that chunk will be too small. */
5550420b74aSdan #ifndef SQLITE_OMIT_WSD
55678c0eafbSdrh       extern int sqlite3PendingByte;
5570420b74aSdan #else
5580420b74aSdan       int sqlite3PendingByte = 0x40000000;
5590420b74aSdan #endif
56078c0eafbSdrh       while( (sqlite3PendingByte % pGroup->szChunk)>=(pGroup->szChunk-65536) ){
56178c0eafbSdrh         pGroup->szChunk += 65536;
56278c0eafbSdrh       }
56378c0eafbSdrh     }
5648a922f75Sshaneh     pGroup->flags = flags;
5650a0ca697Sdrh     rc = multiplexSubFilename(pGroup, 1);
5660a0ca697Sdrh     if( rc==SQLITE_OK ){
567e7d9f13dSdrh       pSubOpen = multiplexSubOpen(pGroup, 0, &rc, pOutFlags, 0);
568e7d9f13dSdrh       if( pSubOpen==0 && rc==SQLITE_OK ) rc = SQLITE_CANTOPEN;
5690a0ca697Sdrh     }
57031b21295Sdan     if( rc==SQLITE_OK ){
571c27fa4b0Sshaneh       sqlite3_int64 sz;
5720a0ca697Sdrh 
57331b21295Sdan       rc = pSubOpen->pMethods->xFileSize(pSubOpen, &sz);
57431b21295Sdan       if( rc==SQLITE_OK && zName ){
575e712b582Sdan         int bExists;
576e712b582Sdan         if( sz==0 ){
577e712b582Sdan           if( flags & SQLITE_OPEN_MAIN_JOURNAL ){
578e712b582Sdan             /* If opening a main journal file and the first chunk is zero
579e712b582Sdan             ** bytes in size, delete any subsequent chunks from the
580e712b582Sdan             ** file-system. */
581e712b582Sdan             int iChunk = 1;
582e712b582Sdan             do {
583e712b582Sdan               rc = pOrigVfs->xAccess(pOrigVfs,
584e712b582Sdan                   pGroup->aReal[iChunk].z, SQLITE_ACCESS_EXISTS, &bExists
585e712b582Sdan               );
586e712b582Sdan               if( rc==SQLITE_OK && bExists ){
587e712b582Sdan                 rc = pOrigVfs->xDelete(pOrigVfs, pGroup->aReal[iChunk].z, 0);
588e712b582Sdan                 if( rc==SQLITE_OK ){
589e712b582Sdan                   rc = multiplexSubFilename(pGroup, ++iChunk);
590e712b582Sdan                 }
591e712b582Sdan               }
592e712b582Sdan             }while( rc==SQLITE_OK && bExists );
593e712b582Sdan           }
594e712b582Sdan         }else{
5950a0ca697Sdrh           /* If the first overflow file exists and if the size of the main file
5960a0ca697Sdrh           ** is different from the chunk size, that means the chunk size is set
5970a0ca697Sdrh           ** set incorrectly.  So fix it.
5980a0ca697Sdrh           **
5990a0ca697Sdrh           ** Or, if the first overflow file does not exist and the main file is
6000a0ca697Sdrh           ** larger than the chunk size, that means the chunk size is too small.
6010a0ca697Sdrh           ** But we have no way of determining the intended chunk size, so
6020a0ca697Sdrh           ** just disable the multiplexor all togethre.
6030a0ca697Sdrh           */
60431b21295Sdan           rc = pOrigVfs->xAccess(pOrigVfs, pGroup->aReal[1].z,
605e712b582Sdan               SQLITE_ACCESS_EXISTS, &bExists);
606e7d9f13dSdrh           bExists = multiplexSubSize(pGroup, 1, &rc)>0;
607e712b582Sdan           if( rc==SQLITE_OK && bExists  && sz==(sz&0xffff0000) && sz>0
608e6deb204Sdrh               && sz!=pGroup->szChunk ){
6097da5fcb0Sdrh             pGroup->szChunk = (int)sz;
610e712b582Sdan           }else if( rc==SQLITE_OK && !bExists && sz>pGroup->szChunk ){
611c27fa4b0Sshaneh             pGroup->bEnabled = 0;
612c27fa4b0Sshaneh           }
6130a0ca697Sdrh         }
61431b21295Sdan       }
615e712b582Sdan     }
6160a0ca697Sdrh 
61731b21295Sdan     if( rc==SQLITE_OK ){
6188a922f75Sshaneh       if( pSubOpen->pMethods->iVersion==1 ){
6198a922f75Sshaneh         pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV1;
6208a922f75Sshaneh       }else{
6218a922f75Sshaneh         pMultiplexOpen->base.pMethods = &gMultiplex.sIoMethodsV2;
6228a922f75Sshaneh       }
6238a922f75Sshaneh       /* place this group at the head of our list */
6248a922f75Sshaneh       pGroup->pNext = gMultiplex.pGroups;
6258a922f75Sshaneh       if( gMultiplex.pGroups ) gMultiplex.pGroups->pPrev = pGroup;
6268a922f75Sshaneh       gMultiplex.pGroups = pGroup;
6278a922f75Sshaneh     }else{
628f3717af4Sdrh       multiplexFreeComponents(pGroup);
6298a922f75Sshaneh       sqlite3_free(pGroup);
6308a922f75Sshaneh     }
6318a922f75Sshaneh   }
6328a922f75Sshaneh   multiplexLeave();
633f3717af4Sdrh   sqlite3_free(zToFree);
6348a922f75Sshaneh   return rc;
6358a922f75Sshaneh }
6368a922f75Sshaneh 
637b5830294Sshaneh /*
638b5830294Sshaneh ** This is the xDelete method used for the "multiplex" VFS.
639f3717af4Sdrh ** It attempts to delete the filename specified.
640b5830294Sshaneh */
641b5830294Sshaneh static int multiplexDelete(
642b5830294Sshaneh   sqlite3_vfs *pVfs,         /* The multiplex VFS */
643b5830294Sshaneh   const char *zName,         /* Name of file to delete */
644b5830294Sshaneh   int syncDir
645b5830294Sshaneh ){
646e712b582Sdan   int rc;
647b5830294Sshaneh   sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */
648e712b582Sdan   rc = pOrigVfs->xDelete(pOrigVfs, zName, syncDir);
649e712b582Sdan   if( rc==SQLITE_OK ){
650e712b582Sdan     /* If the main chunk was deleted successfully, also delete any subsequent
651e712b582Sdan     ** chunks - starting with the last (highest numbered).
652e712b582Sdan     */
65383cc1392Sdrh     int nName = (int)strlen(zName);
654e712b582Sdan     char *z;
655730f85a3Sdan     z = sqlite3_malloc(nName + 5);
656e712b582Sdan     if( z==0 ){
657e712b582Sdan       rc = SQLITE_IOERR_NOMEM;
658e712b582Sdan     }else{
659e712b582Sdan       int iChunk = 0;
660e712b582Sdan       int bExists;
661e712b582Sdan       do{
662e712b582Sdan         multiplexFilename(zName, nName, SQLITE_OPEN_MAIN_JOURNAL, ++iChunk, z);
663e712b582Sdan         rc = pOrigVfs->xAccess(pOrigVfs, z, SQLITE_ACCESS_EXISTS, &bExists);
664e712b582Sdan       }while( rc==SQLITE_OK && bExists );
665e712b582Sdan       while( rc==SQLITE_OK && iChunk>1 ){
666e712b582Sdan         multiplexFilename(zName, nName, SQLITE_OPEN_MAIN_JOURNAL, --iChunk, z);
667e712b582Sdan         rc = pOrigVfs->xDelete(pOrigVfs, z, syncDir);
668e712b582Sdan       }
6694aced72aSdan       if( rc==SQLITE_OK ){
670c611ee94Sdrh         iChunk = 0;
671c611ee94Sdrh         do{
672c611ee94Sdrh           multiplexFilename(zName, nName, SQLITE_OPEN_WAL, ++iChunk, z);
673c611ee94Sdrh           rc = pOrigVfs->xAccess(pOrigVfs, z, SQLITE_ACCESS_EXISTS, &bExists);
674c611ee94Sdrh         }while( rc==SQLITE_OK && bExists );
675c611ee94Sdrh         while( rc==SQLITE_OK && iChunk>1 ){
676c611ee94Sdrh           multiplexFilename(zName, nName, SQLITE_OPEN_WAL, --iChunk, z);
677c611ee94Sdrh           rc = pOrigVfs->xDelete(pOrigVfs, z, syncDir);
678c611ee94Sdrh         }
679e712b582Sdan       }
6804aced72aSdan     }
681e712b582Sdan     sqlite3_free(z);
682e712b582Sdan   }
683e712b582Sdan   return rc;
684b5830294Sshaneh }
685b5830294Sshaneh 
686d50deeebSshaneh static int multiplexAccess(sqlite3_vfs *a, const char *b, int c, int *d){
687d50deeebSshaneh   return gMultiplex.pOrigVfs->xAccess(gMultiplex.pOrigVfs, b, c, d);
688fffadc52Sdan }
689d50deeebSshaneh static int multiplexFullPathname(sqlite3_vfs *a, const char *b, int c, char *d){
690d50deeebSshaneh   return gMultiplex.pOrigVfs->xFullPathname(gMultiplex.pOrigVfs, b, c, d);
691fffadc52Sdan }
692fffadc52Sdan static void *multiplexDlOpen(sqlite3_vfs *a, const char *b){
693fffadc52Sdan   return gMultiplex.pOrigVfs->xDlOpen(gMultiplex.pOrigVfs, b);
694fffadc52Sdan }
695fffadc52Sdan static void multiplexDlError(sqlite3_vfs *a, int b, char *c){
696fffadc52Sdan   gMultiplex.pOrigVfs->xDlError(gMultiplex.pOrigVfs, b, c);
697fffadc52Sdan }
698fffadc52Sdan static void (*multiplexDlSym(sqlite3_vfs *a, void *b, const char *c))(void){
699fffadc52Sdan   return gMultiplex.pOrigVfs->xDlSym(gMultiplex.pOrigVfs, b, c);
700fffadc52Sdan }
701fffadc52Sdan static void multiplexDlClose(sqlite3_vfs *a, void *b){
702fffadc52Sdan   gMultiplex.pOrigVfs->xDlClose(gMultiplex.pOrigVfs, b);
703fffadc52Sdan }
704fffadc52Sdan static int multiplexRandomness(sqlite3_vfs *a, int b, char *c){
705fffadc52Sdan   return gMultiplex.pOrigVfs->xRandomness(gMultiplex.pOrigVfs, b, c);
706fffadc52Sdan }
707fffadc52Sdan static int multiplexSleep(sqlite3_vfs *a, int b){
708fffadc52Sdan   return gMultiplex.pOrigVfs->xSleep(gMultiplex.pOrigVfs, b);
709fffadc52Sdan }
710fffadc52Sdan static int multiplexCurrentTime(sqlite3_vfs *a, double *b){
711fffadc52Sdan   return gMultiplex.pOrigVfs->xCurrentTime(gMultiplex.pOrigVfs, b);
712fffadc52Sdan }
713fffadc52Sdan static int multiplexGetLastError(sqlite3_vfs *a, int b, char *c){
714fffadc52Sdan   return gMultiplex.pOrigVfs->xGetLastError(gMultiplex.pOrigVfs, b, c);
715fffadc52Sdan }
716fffadc52Sdan static int multiplexCurrentTimeInt64(sqlite3_vfs *a, sqlite3_int64 *b){
717fffadc52Sdan   return gMultiplex.pOrigVfs->xCurrentTimeInt64(gMultiplex.pOrigVfs, b);
718fffadc52Sdan }
719fffadc52Sdan 
7208a922f75Sshaneh /************************ I/O Method Wrappers *******************************/
7218a922f75Sshaneh 
7228a922f75Sshaneh /* xClose requests get passed through to the original VFS.
7238a922f75Sshaneh ** We loop over all open chunk handles and close them.
7248a922f75Sshaneh ** The group structure for this file is unlinked from
7258a922f75Sshaneh ** our list of groups and freed.
7268a922f75Sshaneh */
7278a922f75Sshaneh static int multiplexClose(sqlite3_file *pConn){
7288a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
7298a922f75Sshaneh   multiplexGroup *pGroup = p->pGroup;
7308a922f75Sshaneh   int rc = SQLITE_OK;
7318a922f75Sshaneh   multiplexEnter();
732f3717af4Sdrh   multiplexFreeComponents(pGroup);
733b5830294Sshaneh   /* remove from linked list */
7348a922f75Sshaneh   if( pGroup->pNext ) pGroup->pNext->pPrev = pGroup->pPrev;
7358a922f75Sshaneh   if( pGroup->pPrev ){
7368a922f75Sshaneh     pGroup->pPrev->pNext = pGroup->pNext;
7378a922f75Sshaneh   }else{
7388a922f75Sshaneh     gMultiplex.pGroups = pGroup->pNext;
7398a922f75Sshaneh   }
7408a922f75Sshaneh   sqlite3_free(pGroup);
7418a922f75Sshaneh   multiplexLeave();
7428a922f75Sshaneh   return rc;
7438a922f75Sshaneh }
7448a922f75Sshaneh 
7458a922f75Sshaneh /* Pass xRead requests thru to the original VFS after
7468a922f75Sshaneh ** determining the correct chunk to operate on.
747fd1552f2Sshaneh ** Break up reads across chunk boundaries.
7488a922f75Sshaneh */
7498a922f75Sshaneh static int multiplexRead(
7508a922f75Sshaneh   sqlite3_file *pConn,
7518a922f75Sshaneh   void *pBuf,
7528a922f75Sshaneh   int iAmt,
7538a922f75Sshaneh   sqlite3_int64 iOfst
7548a922f75Sshaneh ){
7558a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
756d50deeebSshaneh   multiplexGroup *pGroup = p->pGroup;
7578a922f75Sshaneh   int rc = SQLITE_OK;
758*ee68ccfbSdrh   int nMutex = 0;
759*ee68ccfbSdrh   multiplexEnter(); nMutex++;
760c27fa4b0Sshaneh   if( !pGroup->bEnabled ){
761e7d9f13dSdrh     sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);
762*ee68ccfbSdrh     multiplexLeave(); nMutex--;
763f3717af4Sdrh     if( pSubOpen==0 ){
7648efdb732Sdrh       rc = SQLITE_IOERR_READ;
7658efdb732Sdrh     }else{
7668efdb732Sdrh       rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst);
7678efdb732Sdrh     }
768c27fa4b0Sshaneh   }else{
7698a922f75Sshaneh     while( iAmt > 0 ){
770e6deb204Sdrh       int i = (int)(iOfst / pGroup->szChunk);
771*ee68ccfbSdrh       if( nMutex==0 ){ multiplexEnter(); nMutex++; }
772e7d9f13dSdrh       sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, i, &rc, NULL, 1);
773*ee68ccfbSdrh       multiplexLeave(); nMutex--;
7748a922f75Sshaneh       if( pSubOpen ){
775e6deb204Sdrh         int extra = ((int)(iOfst % pGroup->szChunk) + iAmt) - pGroup->szChunk;
7768a922f75Sshaneh         if( extra<0 ) extra = 0;
7778a922f75Sshaneh         iAmt -= extra;
7788efdb732Sdrh         rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt,
779e6deb204Sdrh                                        iOfst % pGroup->szChunk);
7808a922f75Sshaneh         if( rc!=SQLITE_OK ) break;
7818a922f75Sshaneh         pBuf = (char *)pBuf + iAmt;
7828a922f75Sshaneh         iOfst += iAmt;
7838a922f75Sshaneh         iAmt = extra;
7848a922f75Sshaneh       }else{
7858a922f75Sshaneh         rc = SQLITE_IOERR_READ;
7868a922f75Sshaneh         break;
7878a922f75Sshaneh       }
7888a922f75Sshaneh     }
789c27fa4b0Sshaneh   }
790*ee68ccfbSdrh   assert( nMutex==0 || nMutex==1 );
791*ee68ccfbSdrh   if( nMutex ) multiplexLeave();
7928a922f75Sshaneh   return rc;
7938a922f75Sshaneh }
7948a922f75Sshaneh 
7958a922f75Sshaneh /* Pass xWrite requests thru to the original VFS after
7968a922f75Sshaneh ** determining the correct chunk to operate on.
797fd1552f2Sshaneh ** Break up writes across chunk boundaries.
7988a922f75Sshaneh */
7998a922f75Sshaneh static int multiplexWrite(
8008a922f75Sshaneh   sqlite3_file *pConn,
8018a922f75Sshaneh   const void *pBuf,
8028a922f75Sshaneh   int iAmt,
8038a922f75Sshaneh   sqlite3_int64 iOfst
8048a922f75Sshaneh ){
8058a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
806d50deeebSshaneh   multiplexGroup *pGroup = p->pGroup;
8078a922f75Sshaneh   int rc = SQLITE_OK;
8088a922f75Sshaneh   multiplexEnter();
809c27fa4b0Sshaneh   if( !pGroup->bEnabled ){
810e7d9f13dSdrh     sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);
8118efdb732Sdrh     if( pSubOpen==0 ){
8128efdb732Sdrh       rc = SQLITE_IOERR_WRITE;
8138efdb732Sdrh     }else{
8148efdb732Sdrh       rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst);
8158efdb732Sdrh     }
816c27fa4b0Sshaneh   }else{
81727e69643Sdan     while( rc==SQLITE_OK && iAmt>0 ){
818e6deb204Sdrh       int i = (int)(iOfst / pGroup->szChunk);
819e7d9f13dSdrh       sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, i, &rc, NULL, 1);
8208a922f75Sshaneh       if( pSubOpen ){
821e6deb204Sdrh         int extra = ((int)(iOfst % pGroup->szChunk) + iAmt) -
822e6deb204Sdrh                     pGroup->szChunk;
8238a922f75Sshaneh         if( extra<0 ) extra = 0;
8248a922f75Sshaneh         iAmt -= extra;
8258efdb732Sdrh         rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt,
826e6deb204Sdrh                                         iOfst % pGroup->szChunk);
8278a922f75Sshaneh         pBuf = (char *)pBuf + iAmt;
8288a922f75Sshaneh         iOfst += iAmt;
8298a922f75Sshaneh         iAmt = extra;
8308a922f75Sshaneh       }
8318a922f75Sshaneh     }
832c27fa4b0Sshaneh   }
8338a922f75Sshaneh   multiplexLeave();
8348a922f75Sshaneh   return rc;
8358a922f75Sshaneh }
8368a922f75Sshaneh 
8378a922f75Sshaneh /* Pass xTruncate requests thru to the original VFS after
8388a922f75Sshaneh ** determining the correct chunk to operate on.  Delete any
8398a922f75Sshaneh ** chunks above the truncate mark.
8408a922f75Sshaneh */
8418a922f75Sshaneh static int multiplexTruncate(sqlite3_file *pConn, sqlite3_int64 size){
8428a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
8438a922f75Sshaneh   multiplexGroup *pGroup = p->pGroup;
8448a922f75Sshaneh   int rc = SQLITE_OK;
845c27fa4b0Sshaneh   multiplexEnter();
846c27fa4b0Sshaneh   if( !pGroup->bEnabled ){
847e7d9f13dSdrh     sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);
8488efdb732Sdrh     if( pSubOpen==0 ){
8498efdb732Sdrh       rc = SQLITE_IOERR_TRUNCATE;
8508efdb732Sdrh     }else{
8518efdb732Sdrh       rc = pSubOpen->pMethods->xTruncate(pSubOpen, size);
8528efdb732Sdrh     }
853e7d9f13dSdrh   }else{
8548a922f75Sshaneh     int i;
855e7d9f13dSdrh     int iBaseGroup = (int)(size / pGroup->szChunk);
8568a922f75Sshaneh     sqlite3_file *pSubOpen;
8578a922f75Sshaneh     sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */
8588a922f75Sshaneh     /* delete the chunks above the truncate limit */
859e7d9f13dSdrh     for(i = pGroup->nReal-1; i>iBaseGroup && rc==SQLITE_OK; i--){
860e7d9f13dSdrh       if( pGroup->bTruncate ){
861f3717af4Sdrh         multiplexSubClose(pGroup, i, pOrigVfs);
8628a922f75Sshaneh       }else{
863e7d9f13dSdrh         pSubOpen = multiplexSubOpen(pGroup, i, &rc, 0, 0);
864e7d9f13dSdrh         if( pSubOpen ){
865e7d9f13dSdrh           rc = pSubOpen->pMethods->xTruncate(pSubOpen, 0);
8668a922f75Sshaneh         }
867c27fa4b0Sshaneh       }
868e7d9f13dSdrh     }
869e7d9f13dSdrh     if( rc==SQLITE_OK ){
870e7d9f13dSdrh       pSubOpen = multiplexSubOpen(pGroup, iBaseGroup, &rc, 0, 0);
871e7d9f13dSdrh       if( pSubOpen ){
872e7d9f13dSdrh         rc = pSubOpen->pMethods->xTruncate(pSubOpen, size % pGroup->szChunk);
873e7d9f13dSdrh       }
874e7d9f13dSdrh     }
875e7d9f13dSdrh     if( rc ) rc = SQLITE_IOERR_TRUNCATE;
876e7d9f13dSdrh   }
8778a922f75Sshaneh   multiplexLeave();
8788a922f75Sshaneh   return rc;
8798a922f75Sshaneh }
8808a922f75Sshaneh 
8818a922f75Sshaneh /* Pass xSync requests through to the original VFS without change
8828a922f75Sshaneh */
8838a922f75Sshaneh static int multiplexSync(sqlite3_file *pConn, int flags){
8848a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
8858a922f75Sshaneh   multiplexGroup *pGroup = p->pGroup;
8868a922f75Sshaneh   int rc = SQLITE_OK;
8878a922f75Sshaneh   int i;
8888a922f75Sshaneh   multiplexEnter();
889f3717af4Sdrh   for(i=0; i<pGroup->nReal; i++){
890f3717af4Sdrh     sqlite3_file *pSubOpen = pGroup->aReal[i].p;
891f3717af4Sdrh     if( pSubOpen ){
8928a922f75Sshaneh       int rc2 = pSubOpen->pMethods->xSync(pSubOpen, flags);
8938a922f75Sshaneh       if( rc2!=SQLITE_OK ) rc = rc2;
8948a922f75Sshaneh     }
8958a922f75Sshaneh   }
8968a922f75Sshaneh   multiplexLeave();
8978a922f75Sshaneh   return rc;
8988a922f75Sshaneh }
8998a922f75Sshaneh 
9008a922f75Sshaneh /* Pass xFileSize requests through to the original VFS.
9018a922f75Sshaneh ** Aggregate the size of all the chunks before returning.
9028a922f75Sshaneh */
9038a922f75Sshaneh static int multiplexFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){
9048a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
9058a922f75Sshaneh   multiplexGroup *pGroup = p->pGroup;
9068a922f75Sshaneh   int rc = SQLITE_OK;
9078a922f75Sshaneh   int i;
9088a922f75Sshaneh   multiplexEnter();
909c27fa4b0Sshaneh   if( !pGroup->bEnabled ){
910e7d9f13dSdrh     sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);
9118efdb732Sdrh     if( pSubOpen==0 ){
9125b1626aaSdrh       rc = SQLITE_IOERR_FSTAT;
9138efdb732Sdrh     }else{
9148efdb732Sdrh       rc = pSubOpen->pMethods->xFileSize(pSubOpen, pSize);
9158efdb732Sdrh     }
916c27fa4b0Sshaneh   }else{
9178a922f75Sshaneh     *pSize = 0;
9185b1626aaSdrh     for(i=0; rc==SQLITE_OK; i++){
919e7d9f13dSdrh       sqlite3_int64 sz = multiplexSubSize(pGroup, i, &rc);
920e7d9f13dSdrh       if( sz==0 ) break;
9212be25bffSdrh       *pSize = i*(sqlite3_int64)pGroup->szChunk + sz;
9228a922f75Sshaneh     }
9238a922f75Sshaneh   }
9248a922f75Sshaneh   multiplexLeave();
9258a922f75Sshaneh   return rc;
9268a922f75Sshaneh }
9278a922f75Sshaneh 
9288a922f75Sshaneh /* Pass xLock requests through to the original VFS unchanged.
9298a922f75Sshaneh */
9308a922f75Sshaneh static int multiplexLock(sqlite3_file *pConn, int lock){
9318a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
9328a922f75Sshaneh   int rc;
933e7d9f13dSdrh   sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
9348a922f75Sshaneh   if( pSubOpen ){
9358a922f75Sshaneh     return pSubOpen->pMethods->xLock(pSubOpen, lock);
9368a922f75Sshaneh   }
9378a922f75Sshaneh   return SQLITE_BUSY;
9388a922f75Sshaneh }
9398a922f75Sshaneh 
9408a922f75Sshaneh /* Pass xUnlock requests through to the original VFS unchanged.
9418a922f75Sshaneh */
9428a922f75Sshaneh static int multiplexUnlock(sqlite3_file *pConn, int lock){
9438a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
9448a922f75Sshaneh   int rc;
945e7d9f13dSdrh   sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
9468a922f75Sshaneh   if( pSubOpen ){
9478a922f75Sshaneh     return pSubOpen->pMethods->xUnlock(pSubOpen, lock);
9488a922f75Sshaneh   }
9498a922f75Sshaneh   return SQLITE_IOERR_UNLOCK;
9508a922f75Sshaneh }
9518a922f75Sshaneh 
9528a922f75Sshaneh /* Pass xCheckReservedLock requests through to the original VFS unchanged.
9538a922f75Sshaneh */
9548a922f75Sshaneh static int multiplexCheckReservedLock(sqlite3_file *pConn, int *pResOut){
9558a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
9568a922f75Sshaneh   int rc;
957e7d9f13dSdrh   sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
9588a922f75Sshaneh   if( pSubOpen ){
9598a922f75Sshaneh     return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut);
9608a922f75Sshaneh   }
9618a922f75Sshaneh   return SQLITE_IOERR_CHECKRESERVEDLOCK;
9628a922f75Sshaneh }
9638a922f75Sshaneh 
964c27fa4b0Sshaneh /* Pass xFileControl requests through to the original VFS unchanged,
965c27fa4b0Sshaneh ** except for any MULTIPLEX_CTRL_* requests here.
9668a922f75Sshaneh */
9678a922f75Sshaneh static int multiplexFileControl(sqlite3_file *pConn, int op, void *pArg){
9688a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
969d50deeebSshaneh   multiplexGroup *pGroup = p->pGroup;
970d50deeebSshaneh   int rc = SQLITE_ERROR;
9718a922f75Sshaneh   sqlite3_file *pSubOpen;
972d50deeebSshaneh 
973d50deeebSshaneh   if( !gMultiplex.isInitialized ) return SQLITE_MISUSE;
974d50deeebSshaneh   switch( op ){
975d50deeebSshaneh     case MULTIPLEX_CTRL_ENABLE:
976d50deeebSshaneh       if( pArg ) {
977d50deeebSshaneh         int bEnabled = *(int *)pArg;
978d50deeebSshaneh         pGroup->bEnabled = bEnabled;
979d50deeebSshaneh         rc = SQLITE_OK;
980d50deeebSshaneh       }
981d50deeebSshaneh       break;
982d50deeebSshaneh     case MULTIPLEX_CTRL_SET_CHUNK_SIZE:
983d50deeebSshaneh       if( pArg ) {
984e6deb204Sdrh         unsigned int szChunk = *(unsigned*)pArg;
985e6deb204Sdrh         if( szChunk<1 ){
986d50deeebSshaneh           rc = SQLITE_MISUSE;
987d50deeebSshaneh         }else{
9883801b65dSshaneh           /* Round up to nearest multiple of MAX_PAGE_SIZE. */
989e6deb204Sdrh           szChunk = (szChunk + (MAX_PAGE_SIZE-1));
990e6deb204Sdrh           szChunk &= ~(MAX_PAGE_SIZE-1);
991e6deb204Sdrh           pGroup->szChunk = szChunk;
992d50deeebSshaneh           rc = SQLITE_OK;
993d50deeebSshaneh         }
994d50deeebSshaneh       }
995d50deeebSshaneh       break;
996d50deeebSshaneh     case MULTIPLEX_CTRL_SET_MAX_CHUNKS:
997d50deeebSshaneh       rc = SQLITE_OK;
998d50deeebSshaneh       break;
999ac039688Sshaneh     case SQLITE_FCNTL_SIZE_HINT:
1000ac039688Sshaneh     case SQLITE_FCNTL_CHUNK_SIZE:
1001ac039688Sshaneh       /* no-op these */
1002ac039688Sshaneh       rc = SQLITE_OK;
1003ac039688Sshaneh       break;
1004d50deeebSshaneh     default:
1005e7d9f13dSdrh       pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);
10068a922f75Sshaneh       if( pSubOpen ){
1007d50deeebSshaneh         rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg);
1008de60fc2dSdrh         if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){
1009de60fc2dSdrh          *(char**)pArg = sqlite3_mprintf("multiplex/%z", *(char**)pArg);
1010de60fc2dSdrh         }
10118a922f75Sshaneh       }
1012d50deeebSshaneh       break;
1013d50deeebSshaneh   }
1014d50deeebSshaneh   return rc;
10158a922f75Sshaneh }
10168a922f75Sshaneh 
10178a922f75Sshaneh /* Pass xSectorSize requests through to the original VFS unchanged.
10188a922f75Sshaneh */
10198a922f75Sshaneh static int multiplexSectorSize(sqlite3_file *pConn){
10208a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
10218a922f75Sshaneh   int rc;
1022e7d9f13dSdrh   sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
1023c7f94629Sdrh   if( pSubOpen && pSubOpen->pMethods->xSectorSize ){
10248a922f75Sshaneh     return pSubOpen->pMethods->xSectorSize(pSubOpen);
10258a922f75Sshaneh   }
10263801b65dSshaneh   return DEFAULT_SECTOR_SIZE;
10278a922f75Sshaneh }
10288a922f75Sshaneh 
10298a922f75Sshaneh /* Pass xDeviceCharacteristics requests through to the original VFS unchanged.
10308a922f75Sshaneh */
10318a922f75Sshaneh static int multiplexDeviceCharacteristics(sqlite3_file *pConn){
10328a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
10338a922f75Sshaneh   int rc;
1034e7d9f13dSdrh   sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
10358a922f75Sshaneh   if( pSubOpen ){
10368a922f75Sshaneh     return pSubOpen->pMethods->xDeviceCharacteristics(pSubOpen);
10378a922f75Sshaneh   }
10388a922f75Sshaneh   return 0;
10398a922f75Sshaneh }
10408a922f75Sshaneh 
10418a922f75Sshaneh /* Pass xShmMap requests through to the original VFS unchanged.
10428a922f75Sshaneh */
10438a922f75Sshaneh static int multiplexShmMap(
10448a922f75Sshaneh   sqlite3_file *pConn,            /* Handle open on database file */
10458a922f75Sshaneh   int iRegion,                    /* Region to retrieve */
10468a922f75Sshaneh   int szRegion,                   /* Size of regions */
10478a922f75Sshaneh   int bExtend,                    /* True to extend file if necessary */
10488a922f75Sshaneh   void volatile **pp              /* OUT: Mapped memory */
10498a922f75Sshaneh ){
10508a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
10518a922f75Sshaneh   int rc;
1052e7d9f13dSdrh   sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
10538a922f75Sshaneh   if( pSubOpen ){
10548a922f75Sshaneh     return pSubOpen->pMethods->xShmMap(pSubOpen, iRegion, szRegion, bExtend,pp);
10558a922f75Sshaneh   }
10568a922f75Sshaneh   return SQLITE_IOERR;
10578a922f75Sshaneh }
10588a922f75Sshaneh 
10598a922f75Sshaneh /* Pass xShmLock requests through to the original VFS unchanged.
10608a922f75Sshaneh */
10618a922f75Sshaneh static int multiplexShmLock(
10628a922f75Sshaneh   sqlite3_file *pConn,       /* Database file holding the shared memory */
10638a922f75Sshaneh   int ofst,                  /* First lock to acquire or release */
10648a922f75Sshaneh   int n,                     /* Number of locks to acquire or release */
10658a922f75Sshaneh   int flags                  /* What to do with the lock */
10668a922f75Sshaneh ){
10678a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
10688a922f75Sshaneh   int rc;
1069e7d9f13dSdrh   sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
10708a922f75Sshaneh   if( pSubOpen ){
10718a922f75Sshaneh     return pSubOpen->pMethods->xShmLock(pSubOpen, ofst, n, flags);
10728a922f75Sshaneh   }
10738a922f75Sshaneh   return SQLITE_BUSY;
10748a922f75Sshaneh }
10758a922f75Sshaneh 
10768a922f75Sshaneh /* Pass xShmBarrier requests through to the original VFS unchanged.
10778a922f75Sshaneh */
10788a922f75Sshaneh static void multiplexShmBarrier(sqlite3_file *pConn){
10798a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
10808a922f75Sshaneh   int rc;
1081e7d9f13dSdrh   sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
10828a922f75Sshaneh   if( pSubOpen ){
10838a922f75Sshaneh     pSubOpen->pMethods->xShmBarrier(pSubOpen);
10848a922f75Sshaneh   }
10858a922f75Sshaneh }
10868a922f75Sshaneh 
10878a922f75Sshaneh /* Pass xShmUnmap requests through to the original VFS unchanged.
10888a922f75Sshaneh */
10898a922f75Sshaneh static int multiplexShmUnmap(sqlite3_file *pConn, int deleteFlag){
10908a922f75Sshaneh   multiplexConn *p = (multiplexConn*)pConn;
10918a922f75Sshaneh   int rc;
1092e7d9f13dSdrh   sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
10938a922f75Sshaneh   if( pSubOpen ){
10948a922f75Sshaneh     return pSubOpen->pMethods->xShmUnmap(pSubOpen, deleteFlag);
10958a922f75Sshaneh   }
10968a922f75Sshaneh   return SQLITE_OK;
10978a922f75Sshaneh }
10988a922f75Sshaneh 
10998a922f75Sshaneh /************************** Public Interfaces *****************************/
11008a922f75Sshaneh /*
110178c4de4cSshaneh ** CAPI: Initialize the multiplex VFS shim - sqlite3_multiplex_initialize()
110278c4de4cSshaneh **
110378c4de4cSshaneh ** Use the VFS named zOrigVfsName as the VFS that does the actual work.
110478c4de4cSshaneh ** Use the default if zOrigVfsName==NULL.
11058a922f75Sshaneh **
11068a922f75Sshaneh ** The multiplex VFS shim is named "multiplex".  It will become the default
11078a922f75Sshaneh ** VFS if makeDefault is non-zero.
11088a922f75Sshaneh **
11098a922f75Sshaneh ** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once
11108a922f75Sshaneh ** during start-up.
11118a922f75Sshaneh */
11128a922f75Sshaneh int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault){
11138a922f75Sshaneh   sqlite3_vfs *pOrigVfs;
11148a922f75Sshaneh   if( gMultiplex.isInitialized ) return SQLITE_MISUSE;
11158a922f75Sshaneh   pOrigVfs = sqlite3_vfs_find(zOrigVfsName);
11168a922f75Sshaneh   if( pOrigVfs==0 ) return SQLITE_ERROR;
11178a922f75Sshaneh   assert( pOrigVfs!=&gMultiplex.sThisVfs );
11188a922f75Sshaneh   gMultiplex.pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
11198a922f75Sshaneh   if( !gMultiplex.pMutex ){
11208a922f75Sshaneh     return SQLITE_NOMEM;
11218a922f75Sshaneh   }
11228a922f75Sshaneh   gMultiplex.pGroups = NULL;
11238a922f75Sshaneh   gMultiplex.isInitialized = 1;
11248a922f75Sshaneh   gMultiplex.pOrigVfs = pOrigVfs;
11258a922f75Sshaneh   gMultiplex.sThisVfs = *pOrigVfs;
11268a922f75Sshaneh   gMultiplex.sThisVfs.szOsFile += sizeof(multiplexConn);
1127d9523b97Sshaneh   gMultiplex.sThisVfs.zName = SQLITE_MULTIPLEX_VFS_NAME;
11288a922f75Sshaneh   gMultiplex.sThisVfs.xOpen = multiplexOpen;
1129b5830294Sshaneh   gMultiplex.sThisVfs.xDelete = multiplexDelete;
1130fffadc52Sdan   gMultiplex.sThisVfs.xAccess = multiplexAccess;
1131fffadc52Sdan   gMultiplex.sThisVfs.xFullPathname = multiplexFullPathname;
1132fffadc52Sdan   gMultiplex.sThisVfs.xDlOpen = multiplexDlOpen;
1133fffadc52Sdan   gMultiplex.sThisVfs.xDlError = multiplexDlError;
1134fffadc52Sdan   gMultiplex.sThisVfs.xDlSym = multiplexDlSym;
1135fffadc52Sdan   gMultiplex.sThisVfs.xDlClose = multiplexDlClose;
1136fffadc52Sdan   gMultiplex.sThisVfs.xRandomness = multiplexRandomness;
1137fffadc52Sdan   gMultiplex.sThisVfs.xSleep = multiplexSleep;
1138fffadc52Sdan   gMultiplex.sThisVfs.xCurrentTime = multiplexCurrentTime;
1139fffadc52Sdan   gMultiplex.sThisVfs.xGetLastError = multiplexGetLastError;
1140fffadc52Sdan   gMultiplex.sThisVfs.xCurrentTimeInt64 = multiplexCurrentTimeInt64;
1141fffadc52Sdan 
11428a922f75Sshaneh   gMultiplex.sIoMethodsV1.iVersion = 1;
11438a922f75Sshaneh   gMultiplex.sIoMethodsV1.xClose = multiplexClose;
11448a922f75Sshaneh   gMultiplex.sIoMethodsV1.xRead = multiplexRead;
11458a922f75Sshaneh   gMultiplex.sIoMethodsV1.xWrite = multiplexWrite;
11468a922f75Sshaneh   gMultiplex.sIoMethodsV1.xTruncate = multiplexTruncate;
11478a922f75Sshaneh   gMultiplex.sIoMethodsV1.xSync = multiplexSync;
11488a922f75Sshaneh   gMultiplex.sIoMethodsV1.xFileSize = multiplexFileSize;
11498a922f75Sshaneh   gMultiplex.sIoMethodsV1.xLock = multiplexLock;
11508a922f75Sshaneh   gMultiplex.sIoMethodsV1.xUnlock = multiplexUnlock;
11518a922f75Sshaneh   gMultiplex.sIoMethodsV1.xCheckReservedLock = multiplexCheckReservedLock;
11528a922f75Sshaneh   gMultiplex.sIoMethodsV1.xFileControl = multiplexFileControl;
11538a922f75Sshaneh   gMultiplex.sIoMethodsV1.xSectorSize = multiplexSectorSize;
11548efdb732Sdrh   gMultiplex.sIoMethodsV1.xDeviceCharacteristics =
11558efdb732Sdrh                                             multiplexDeviceCharacteristics;
11568a922f75Sshaneh   gMultiplex.sIoMethodsV2 = gMultiplex.sIoMethodsV1;
11578a922f75Sshaneh   gMultiplex.sIoMethodsV2.iVersion = 2;
11588a922f75Sshaneh   gMultiplex.sIoMethodsV2.xShmMap = multiplexShmMap;
11598a922f75Sshaneh   gMultiplex.sIoMethodsV2.xShmLock = multiplexShmLock;
11608a922f75Sshaneh   gMultiplex.sIoMethodsV2.xShmBarrier = multiplexShmBarrier;
11618a922f75Sshaneh   gMultiplex.sIoMethodsV2.xShmUnmap = multiplexShmUnmap;
11628a922f75Sshaneh   sqlite3_vfs_register(&gMultiplex.sThisVfs, makeDefault);
1163d50deeebSshaneh 
1164d50deeebSshaneh   sqlite3_auto_extension((void*)multiplexFuncInit);
1165d50deeebSshaneh 
11668a922f75Sshaneh   return SQLITE_OK;
11678a922f75Sshaneh }
11688a922f75Sshaneh 
11698a922f75Sshaneh /*
117078c4de4cSshaneh ** CAPI: Shutdown the multiplex system - sqlite3_multiplex_shutdown()
11718a922f75Sshaneh **
11728a922f75Sshaneh ** All SQLite database connections must be closed before calling this
11738a922f75Sshaneh ** routine.
11748a922f75Sshaneh **
11758a922f75Sshaneh ** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once while
11768a922f75Sshaneh ** shutting down in order to free all remaining multiplex groups.
11778a922f75Sshaneh */
11788a922f75Sshaneh int sqlite3_multiplex_shutdown(void){
11798a922f75Sshaneh   if( gMultiplex.isInitialized==0 ) return SQLITE_MISUSE;
11808a922f75Sshaneh   if( gMultiplex.pGroups ) return SQLITE_MISUSE;
11818a922f75Sshaneh   gMultiplex.isInitialized = 0;
11828a922f75Sshaneh   sqlite3_mutex_free(gMultiplex.pMutex);
11838a922f75Sshaneh   sqlite3_vfs_unregister(&gMultiplex.sThisVfs);
11848a922f75Sshaneh   memset(&gMultiplex, 0, sizeof(gMultiplex));
11858a922f75Sshaneh   return SQLITE_OK;
11868a922f75Sshaneh }
11878a922f75Sshaneh 
11888a922f75Sshaneh /***************************** Test Code ***********************************/
11898a922f75Sshaneh #ifdef SQLITE_TEST
11908a922f75Sshaneh #include <tcl.h>
1191e84d8d32Smistachkin extern const char *sqlite3ErrName(int);
11928a922f75Sshaneh 
11938a922f75Sshaneh 
11948a922f75Sshaneh /*
11958a922f75Sshaneh ** tclcmd: sqlite3_multiplex_initialize NAME MAKEDEFAULT
11968a922f75Sshaneh */
11978a922f75Sshaneh static int test_multiplex_initialize(
11988a922f75Sshaneh   void * clientData,
11998a922f75Sshaneh   Tcl_Interp *interp,
12008a922f75Sshaneh   int objc,
12018a922f75Sshaneh   Tcl_Obj *CONST objv[]
12028a922f75Sshaneh ){
12038a922f75Sshaneh   const char *zName;              /* Name of new multiplex VFS */
12048a922f75Sshaneh   int makeDefault;                /* True to make the new VFS the default */
12058a922f75Sshaneh   int rc;                         /* Value returned by multiplex_initialize() */
12068a922f75Sshaneh 
12078a922f75Sshaneh   UNUSED_PARAMETER(clientData);
12088a922f75Sshaneh 
12098a922f75Sshaneh   /* Process arguments */
12108a922f75Sshaneh   if( objc!=3 ){
12118a922f75Sshaneh     Tcl_WrongNumArgs(interp, 1, objv, "NAME MAKEDEFAULT");
12128a922f75Sshaneh     return TCL_ERROR;
12138a922f75Sshaneh   }
12148a922f75Sshaneh   zName = Tcl_GetString(objv[1]);
12158a922f75Sshaneh   if( Tcl_GetBooleanFromObj(interp, objv[2], &makeDefault) ) return TCL_ERROR;
12168a922f75Sshaneh   if( zName[0]=='\0' ) zName = 0;
12178a922f75Sshaneh 
12188a922f75Sshaneh   /* Call sqlite3_multiplex_initialize() */
12198a922f75Sshaneh   rc = sqlite3_multiplex_initialize(zName, makeDefault);
1220e84d8d32Smistachkin   Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
12218a922f75Sshaneh 
12228a922f75Sshaneh   return TCL_OK;
12238a922f75Sshaneh }
12248a922f75Sshaneh 
12258a922f75Sshaneh /*
12268a922f75Sshaneh ** tclcmd: sqlite3_multiplex_shutdown
12278a922f75Sshaneh */
12288a922f75Sshaneh static int test_multiplex_shutdown(
12298a922f75Sshaneh   void * clientData,
12308a922f75Sshaneh   Tcl_Interp *interp,
12318a922f75Sshaneh   int objc,
12328a922f75Sshaneh   Tcl_Obj *CONST objv[]
12338a922f75Sshaneh ){
12348a922f75Sshaneh   int rc;                         /* Value returned by multiplex_shutdown() */
12358a922f75Sshaneh 
12368a922f75Sshaneh   UNUSED_PARAMETER(clientData);
12378a922f75Sshaneh 
12388a922f75Sshaneh   if( objc!=1 ){
12398a922f75Sshaneh     Tcl_WrongNumArgs(interp, 1, objv, "");
12408a922f75Sshaneh     return TCL_ERROR;
12418a922f75Sshaneh   }
12428a922f75Sshaneh 
12438a922f75Sshaneh   /* Call sqlite3_multiplex_shutdown() */
12448a922f75Sshaneh   rc = sqlite3_multiplex_shutdown();
1245e84d8d32Smistachkin   Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
12468a922f75Sshaneh 
12478a922f75Sshaneh   return TCL_OK;
12488a922f75Sshaneh }
12498a922f75Sshaneh 
12508a922f75Sshaneh /*
12518a922f75Sshaneh ** tclcmd:  sqlite3_multiplex_dump
12528a922f75Sshaneh */
12538a922f75Sshaneh static int test_multiplex_dump(
12548a922f75Sshaneh   void * clientData,
12558a922f75Sshaneh   Tcl_Interp *interp,
12568a922f75Sshaneh   int objc,
12578a922f75Sshaneh   Tcl_Obj *CONST objv[]
12588a922f75Sshaneh ){
12598a922f75Sshaneh   Tcl_Obj *pResult;
12608a922f75Sshaneh   Tcl_Obj *pGroupTerm;
12618a922f75Sshaneh   multiplexGroup *pGroup;
12628a922f75Sshaneh   int i;
12638a922f75Sshaneh   int nChunks = 0;
12648a922f75Sshaneh 
12658a922f75Sshaneh   UNUSED_PARAMETER(clientData);
12668a922f75Sshaneh   UNUSED_PARAMETER(objc);
12678a922f75Sshaneh   UNUSED_PARAMETER(objv);
12688a922f75Sshaneh 
12698a922f75Sshaneh   pResult = Tcl_NewObj();
12708a922f75Sshaneh   multiplexEnter();
12718a922f75Sshaneh   for(pGroup=gMultiplex.pGroups; pGroup; pGroup=pGroup->pNext){
12728a922f75Sshaneh     pGroupTerm = Tcl_NewObj();
12738a922f75Sshaneh 
12745b1626aaSdrh     if( pGroup->zName ){
12758a922f75Sshaneh       pGroup->zName[pGroup->nName] = '\0';
12768a922f75Sshaneh       Tcl_ListObjAppendElement(interp, pGroupTerm,
12778a922f75Sshaneh           Tcl_NewStringObj(pGroup->zName, -1));
12785b1626aaSdrh     }else{
12795b1626aaSdrh       Tcl_ListObjAppendElement(interp, pGroupTerm, Tcl_NewObj());
12805b1626aaSdrh     }
12818a922f75Sshaneh     Tcl_ListObjAppendElement(interp, pGroupTerm,
12828a922f75Sshaneh           Tcl_NewIntObj(pGroup->nName));
12838a922f75Sshaneh     Tcl_ListObjAppendElement(interp, pGroupTerm,
12848a922f75Sshaneh           Tcl_NewIntObj(pGroup->flags));
12858a922f75Sshaneh 
12868a922f75Sshaneh     /* count number of chunks with open handles */
1287f3717af4Sdrh     for(i=0; i<pGroup->nReal; i++){
1288f3717af4Sdrh       if( pGroup->aReal[i].p!=0 ) nChunks++;
12898a922f75Sshaneh     }
12908a922f75Sshaneh     Tcl_ListObjAppendElement(interp, pGroupTerm,
12918a922f75Sshaneh           Tcl_NewIntObj(nChunks));
12928a922f75Sshaneh 
12938a922f75Sshaneh     Tcl_ListObjAppendElement(interp, pGroupTerm,
1294e6deb204Sdrh           Tcl_NewIntObj(pGroup->szChunk));
12958a922f75Sshaneh     Tcl_ListObjAppendElement(interp, pGroupTerm,
1296f3717af4Sdrh           Tcl_NewIntObj(pGroup->nReal));
12978a922f75Sshaneh 
12988a922f75Sshaneh     Tcl_ListObjAppendElement(interp, pResult, pGroupTerm);
12998a922f75Sshaneh   }
13008a922f75Sshaneh   multiplexLeave();
13018a922f75Sshaneh   Tcl_SetObjResult(interp, pResult);
13028a922f75Sshaneh   return TCL_OK;
13038a922f75Sshaneh }
13048a922f75Sshaneh 
13058a922f75Sshaneh /*
1306d50deeebSshaneh ** Tclcmd: test_multiplex_control HANDLE DBNAME SUB-COMMAND ?INT-VALUE?
1307d50deeebSshaneh */
1308d50deeebSshaneh static int test_multiplex_control(
1309d50deeebSshaneh   ClientData cd,
1310d50deeebSshaneh   Tcl_Interp *interp,
1311d50deeebSshaneh   int objc,
1312d50deeebSshaneh   Tcl_Obj *CONST objv[]
1313d50deeebSshaneh ){
1314d50deeebSshaneh   int rc;                         /* Return code from file_control() */
1315d50deeebSshaneh   int idx;                        /* Index in aSub[] */
1316d50deeebSshaneh   Tcl_CmdInfo cmdInfo;            /* Command info structure for HANDLE */
1317d50deeebSshaneh   sqlite3 *db;                    /* Underlying db handle for HANDLE */
1318d50deeebSshaneh   int iValue = 0;
1319d50deeebSshaneh   void *pArg = 0;
1320d50deeebSshaneh 
1321d50deeebSshaneh   struct SubCommand {
1322d50deeebSshaneh     const char *zName;
1323d50deeebSshaneh     int op;
1324d50deeebSshaneh     int argtype;
1325d50deeebSshaneh   } aSub[] = {
1326d50deeebSshaneh     { "enable",       MULTIPLEX_CTRL_ENABLE,           1 },
1327d50deeebSshaneh     { "chunk_size",   MULTIPLEX_CTRL_SET_CHUNK_SIZE,   1 },
1328d50deeebSshaneh     { "max_chunks",   MULTIPLEX_CTRL_SET_MAX_CHUNKS,   1 },
1329d50deeebSshaneh     { 0, 0, 0 }
1330d50deeebSshaneh   };
1331d50deeebSshaneh 
1332c27fa4b0Sshaneh   if( objc!=5 ){
1333c27fa4b0Sshaneh     Tcl_WrongNumArgs(interp, 1, objv, "HANDLE DBNAME SUB-COMMAND INT-VALUE");
1334d50deeebSshaneh     return TCL_ERROR;
1335d50deeebSshaneh   }
1336d50deeebSshaneh 
1337d50deeebSshaneh   if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &cmdInfo) ){
1338d50deeebSshaneh     Tcl_AppendResult(interp, "expected database handle, got \"", 0);
1339d50deeebSshaneh     Tcl_AppendResult(interp, Tcl_GetString(objv[1]), "\"", 0);
1340d50deeebSshaneh     return TCL_ERROR;
1341d50deeebSshaneh   }else{
1342d50deeebSshaneh     db = *(sqlite3 **)cmdInfo.objClientData;
1343d50deeebSshaneh   }
1344d50deeebSshaneh 
1345d50deeebSshaneh   rc = Tcl_GetIndexFromObjStruct(
1346d50deeebSshaneh       interp, objv[3], aSub, sizeof(aSub[0]), "sub-command", 0, &idx
1347d50deeebSshaneh   );
1348d50deeebSshaneh   if( rc!=TCL_OK ) return rc;
1349d50deeebSshaneh 
1350d50deeebSshaneh   switch( aSub[idx].argtype ){
1351d50deeebSshaneh     case 1:
1352d50deeebSshaneh       if( Tcl_GetIntFromObj(interp, objv[4], &iValue) ){
1353d50deeebSshaneh         return TCL_ERROR;
1354d50deeebSshaneh       }
1355d50deeebSshaneh       pArg = (void *)&iValue;
1356d50deeebSshaneh       break;
1357d50deeebSshaneh     default:
1358d50deeebSshaneh       Tcl_WrongNumArgs(interp, 4, objv, "SUB-COMMAND");
1359d50deeebSshaneh       return TCL_ERROR;
1360d50deeebSshaneh   }
1361d50deeebSshaneh 
1362d50deeebSshaneh   rc = sqlite3_file_control(db, Tcl_GetString(objv[2]), aSub[idx].op, pArg);
1363e84d8d32Smistachkin   Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
1364d50deeebSshaneh   return (rc==SQLITE_OK) ? TCL_OK : TCL_ERROR;
1365d50deeebSshaneh }
1366d50deeebSshaneh 
1367d50deeebSshaneh /*
13688a922f75Sshaneh ** This routine registers the custom TCL commands defined in this
13698a922f75Sshaneh ** module.  This should be the only procedure visible from outside
13708a922f75Sshaneh ** of this module.
13718a922f75Sshaneh */
13728a922f75Sshaneh int Sqlitemultiplex_Init(Tcl_Interp *interp){
13738a922f75Sshaneh   static struct {
13748a922f75Sshaneh      char *zName;
13758a922f75Sshaneh      Tcl_ObjCmdProc *xProc;
13768a922f75Sshaneh   } aCmd[] = {
13778a922f75Sshaneh     { "sqlite3_multiplex_initialize", test_multiplex_initialize },
13788a922f75Sshaneh     { "sqlite3_multiplex_shutdown", test_multiplex_shutdown },
13798a922f75Sshaneh     { "sqlite3_multiplex_dump", test_multiplex_dump },
1380d50deeebSshaneh     { "sqlite3_multiplex_control", test_multiplex_control },
13818a922f75Sshaneh   };
13828a922f75Sshaneh   int i;
13838a922f75Sshaneh 
13848a922f75Sshaneh   for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
13858a922f75Sshaneh     Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
13868a922f75Sshaneh   }
13878a922f75Sshaneh 
13888a922f75Sshaneh   return TCL_OK;
13898a922f75Sshaneh }
13908a922f75Sshaneh #endif
1391