Team Ai
Modelpublic

AryaWu/sqlite

sourceHugging Faceupdated 10mo agoView on Hugging Face
0likes
test_multiplex.c1370 linesDownload Raw Back to src
1/*2** 2010 October 283**4** The author disclaims copyright to this source code.  In place of5** a legal notice, here is a blessing:6**7**    May you do good and not evil.8**    May you find forgiveness for yourself and forgive others.9**    May you share freely, never taking more than you give.10**11*************************************************************************12**13** This file contains a VFS "shim" - a layer that sits in between the14** pager and the real VFS - that breaks up a very large database file15** into two or more smaller files on disk.  This is useful, for example,16** in order to support large, multi-gigabyte databases on older filesystems17** that limit the maximum file size to 2 GiB.18**19** USAGE:20**21** Compile this source file and link it with your application.  Then22** at start-time, invoke the following procedure:23**24**   int sqlite3_multiplex_initialize(25**      const char *zOrigVfsName,    // The underlying real VFS26**      int makeDefault              // True to make multiplex the default VFS27**   );28**29** The procedure call above will create and register a new VFS shim named30** "multiplex".  The multiplex VFS will use the VFS named by zOrigVfsName to31** do the actual disk I/O.  (The zOrigVfsName parameter may be NULL, in32** which case the default VFS at the moment sqlite3_multiplex_initialize()33** is called will be used as the underlying real VFS.)34**35** If the makeDefault parameter is TRUE then multiplex becomes the new36** default VFS.  Otherwise, you can use the multiplex VFS by specifying37** "multiplex" as the 4th parameter to sqlite3_open_v2() or by employing38** URI filenames and adding "vfs=multiplex" as a parameter to the filename39** URI.40**41** The multiplex VFS allows databases up to 32 GiB in size.  But it splits42** the files up into smaller pieces, so that they will work even on43** filesystems that do not support large files.  The default chunk size44** is 2147418112 bytes (which is 64KiB less than 2GiB) but this can be45** changed at compile-time by defining the SQLITE_MULTIPLEX_CHUNK_SIZE46** macro.  Use the "chunksize=NNNN" query parameter with a URI filename47** in order to select an alternative chunk size for individual connections48** at run-time.49*/50#include "sqlite3.h"51#include <string.h>52#include <assert.h>53#include <stdlib.h>54#include "test_multiplex.h"55 56#ifndef SQLITE_CORE57  #define SQLITE_CORE 1  /* Disable the API redefinition in sqlite3ext.h */58#endif59#include "sqlite3ext.h"60 61/*62** These should be defined to be the same as the values in63** sqliteInt.h.  They are defined separately here so that64** the multiplex VFS shim can be built as a loadable65** module.66*/67#define UNUSED_PARAMETER(x) (void)(x)68#define MAX_PAGE_SIZE       0x1000069#define DEFAULT_SECTOR_SIZE 0x100070 71/* Maximum chunk number */72#define MX_CHUNK_NUMBER 29973 74/* First chunk for rollback journal files */75#define SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET 40076#define SQLITE_MULTIPLEX_WAL_8_3_OFFSET 70077 78 79/************************ Shim Definitions ******************************/80 81#ifndef SQLITE_MULTIPLEX_VFS_NAME82# define SQLITE_MULTIPLEX_VFS_NAME "multiplex"83#endif84 85/* This is the limit on the chunk size.  It may be changed by calling86** the xFileControl() interface.  It will be rounded up to a87** multiple of MAX_PAGE_SIZE.  We default it here to 2GiB less 64KiB.88*/89#ifndef SQLITE_MULTIPLEX_CHUNK_SIZE90# define SQLITE_MULTIPLEX_CHUNK_SIZE 214741811291#endif92 93/* This used to be the default limit on number of chunks, but94** it is no longer enforced. There is currently no limit to the95** number of chunks.96**97** May be changed by calling the xFileControl() interface.98*/99#ifndef SQLITE_MULTIPLEX_MAX_CHUNKS100# define SQLITE_MULTIPLEX_MAX_CHUNKS 12101#endif102 103/************************ Object Definitions ******************************/104 105/* Forward declaration of all object types */106typedef struct multiplexGroup multiplexGroup;107typedef struct multiplexConn multiplexConn;108 109/*110** A "multiplex group" is a collection of files that collectively111** makeup a single SQLite DB file.  This allows the size of the DB112** to exceed the limits imposed by the file system.113**114** There is an instance of the following object for each defined multiplex115** group.116*/117struct multiplexGroup {118  struct multiplexReal {           /* For each chunk */119    sqlite3_file *p;                  /* Handle for the chunk */120    char *z;                          /* Name of this chunk */121  } *aReal;                        /* list of all chunks */122  int nReal;                       /* Number of chunks */123  char *zName;                     /* Base filename of this group */124  int nName;                       /* Length of base filename */125  int flags;                       /* Flags used for original opening */126  unsigned int szChunk;            /* Chunk size used for this group */127  unsigned char bEnabled;          /* TRUE to use Multiplex VFS for this file */128  unsigned char bTruncate;         /* TRUE to enable truncation of databases */129};130 131/*132** An instance of the following object represents each open connection133** to a file that is multiplex'ed.  This object is a134** subclass of sqlite3_file.  The sqlite3_file object for the underlying135** VFS is appended to this structure.136*/137struct multiplexConn {138  sqlite3_file base;              /* Base class - must be first */139  multiplexGroup *pGroup;         /* The underlying group of files */140};141 142/************************* Global Variables **********************************/143/*144** All global variables used by this file are containing within the following145** gMultiplex structure.146*/147static struct {148  /* The pOrigVfs is the real, original underlying VFS implementation.149  ** Most operations pass-through to the real VFS.  This value is read-only150  ** during operation.  It is only modified at start-time and thus does not151  ** require a mutex.152  */153  sqlite3_vfs *pOrigVfs;154 155  /* The sThisVfs is the VFS structure used by this shim.  It is initialized156  ** at start-time and thus does not require a mutex157  */158  sqlite3_vfs sThisVfs;159 160  /* The sIoMethods defines the methods used by sqlite3_file objects161  ** associated with this shim.  It is initialized at start-time and does162  ** not require a mutex.163  **164  ** When the underlying VFS is called to open a file, it might return165  ** either a version 1 or a version 2 sqlite3_file object.  This shim166  ** has to create a wrapper sqlite3_file of the same version.  Hence167  ** there are two I/O method structures, one for version 1 and the other168  ** for version 2.169  */170  sqlite3_io_methods sIoMethodsV1;171  sqlite3_io_methods sIoMethodsV2;172 173  /* True when this shim has been initialized.174  */175  int isInitialized;176} gMultiplex;177 178/************************* Utility Routines *********************************/179/*180** Compute a string length that is limited to what can be stored in181** lower 30 bits of a 32-bit signed integer.182**183** The value returned will never be negative.  Nor will it ever be greater184** than the actual length of the string.  For very long strings (greater185** than 1GiB) the value returned might be less than the true string length.186*/187static int multiplexStrlen30(const char *z){188  const char *z2 = z;189  if( z==0 ) return 0;190  while( *z2 ){ z2++; }191  return 0x3fffffff & (int)(z2 - z);192}193 194/*195** Generate the file-name for chunk iChunk of the group with base name196** zBase. The file-name is written to buffer zOut before returning. Buffer197** zOut must be allocated by the caller so that it is at least (nBase+5)198** bytes in size, where nBase is the length of zBase, not including the199** nul-terminator.200**201** If iChunk is 0 (or 400 - the number for the first journal file chunk),202** the output is a copy of the input string. Otherwise, if203** SQLITE_ENABLE_8_3_NAMES is not defined or the input buffer does not contain204** a "." character, then the output is a copy of the input string with the205** three-digit zero-padded decimal representation if iChunk appended to it.206** For example:207**208**   zBase="test.db", iChunk=4  ->  zOut="test.db004"209**210** Or, if SQLITE_ENABLE_8_3_NAMES is defined and the input buffer contains211** a "." character, then everything after the "." is replaced by the212** three-digit representation of iChunk.213**214**   zBase="test.db", iChunk=4  ->  zOut="test.004"215**216** The output buffer string is terminated by 2 0x00 bytes. This makes it safe217** to pass to sqlite3_uri_parameter() and similar.218*/219static void multiplexFilename(220  const char *zBase,              /* Filename for chunk 0 */221  int nBase,                      /* Size of zBase in bytes (without \0) */222  int flags,                      /* Flags used to open file */223  int iChunk,                     /* Chunk to generate filename for */224  char *zOut                      /* Buffer to write generated name to */225){226  int n = nBase;227  memcpy(zOut, zBase, n+1);228  if( iChunk!=0 && iChunk<=MX_CHUNK_NUMBER ){229#ifdef SQLITE_ENABLE_8_3_NAMES230    int i;231    for(i=n-1; i>0 && i>=n-4 && zOut[i]!='.'; i--){}232    if( i>=n-4 ) n = i+1;233    if( flags & SQLITE_OPEN_MAIN_JOURNAL ){234      /* The extensions on overflow files for main databases are 001, 002,235      ** 003 and so forth.  To avoid name collisions, add 400 to the236      ** extensions of journal files so that they are 401, 402, 403, ....237      */238      iChunk += SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET;239    }else if( flags & SQLITE_OPEN_WAL ){240      /* To avoid name collisions, add 700 to the241      ** extensions of WAL files so that they are 701, 702, 703, ....242      */243      iChunk += SQLITE_MULTIPLEX_WAL_8_3_OFFSET;244    }245#endif246    sqlite3_snprintf(4,&zOut[n],"%03d",iChunk);247    n += 3;248  }249 250  assert( zOut[n]=='\0' );251  zOut[n+1] = '\0';252}253 254/* Compute the filename for the iChunk-th chunk255*/256static int multiplexSubFilename(multiplexGroup *pGroup, int iChunk){257  if( iChunk>=pGroup->nReal ){258    struct multiplexReal *p;259    p = sqlite3_realloc64(pGroup->aReal, (iChunk+1)*sizeof(*p));260    if( p==0 ){261      return SQLITE_NOMEM;262    }263    memset(&p[pGroup->nReal], 0, sizeof(p[0])*(iChunk+1-pGroup->nReal));264    pGroup->aReal = p;265    pGroup->nReal = iChunk+1;266  }267  if( pGroup->zName && pGroup->aReal[iChunk].z==0 ){268    char *z;269    int n = pGroup->nName;270    z = sqlite3_malloc64( n+5 );271    if( z==0 ){272      return SQLITE_NOMEM;273    }274    multiplexFilename(pGroup->zName, pGroup->nName, pGroup->flags, iChunk, z);275    pGroup->aReal[iChunk].z = (char*)sqlite3_create_filename(z,"","",0,0);276    sqlite3_free(z);277    if( pGroup->aReal[iChunk].z==0 ) return SQLITE_NOMEM;278  }279  return SQLITE_OK;280}281 282/* Translate an sqlite3_file* that is really a multiplexGroup* into283** the sqlite3_file* for the underlying original VFS.284**285** For chunk 0, the pGroup->flags determines whether or not a new file286** is created if it does not already exist.  For chunks 1 and higher, the287** file is created only if createFlag is 1.288*/289static sqlite3_file *multiplexSubOpen(290  multiplexGroup *pGroup,    /* The multiplexor group */291  int iChunk,                /* Which chunk to open.  0==original file */292  int *rc,                   /* Result code in and out */293  int *pOutFlags,            /* Output flags */294  int createFlag             /* True to create if iChunk>0 */295){296  sqlite3_file *pSubOpen = 0;297  sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;        /* Real VFS */298 299#ifdef SQLITE_ENABLE_8_3_NAMES300  /* If JOURNAL_8_3_OFFSET is set to (say) 400, then any overflow files are301  ** part of a database journal are named db.401, db.402, and so on. A302  ** database may therefore not grow to larger than 400 chunks. Attempting303  ** to open chunk 401 indicates the database is full. */304  if( iChunk>=SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET ){305    sqlite3_log(SQLITE_FULL, "multiplexed chunk overflow: %s", pGroup->zName);306    *rc = SQLITE_FULL;307    return 0;308  }309#endif310 311  *rc = multiplexSubFilename(pGroup, iChunk);312  if( (*rc)==SQLITE_OK && (pSubOpen = pGroup->aReal[iChunk].p)==0 ){313    int flags, bExists;314    flags = pGroup->flags;315    if( createFlag ){316      flags |= SQLITE_OPEN_CREATE;317    }else if( iChunk==0 ){318      /* Fall through */319    }else if( pGroup->aReal[iChunk].z==0 ){320      return 0;321    }else{322      *rc = pOrigVfs->xAccess(pOrigVfs, pGroup->aReal[iChunk].z,323                              SQLITE_ACCESS_EXISTS, &bExists);324     if( *rc || !bExists ){325        if( *rc ){326          sqlite3_log(*rc, "multiplexor.xAccess failure on %s",327                      pGroup->aReal[iChunk].z);328        }329        return 0;330      }331      flags &= ~SQLITE_OPEN_CREATE;332    }333    pSubOpen = sqlite3_malloc64( pOrigVfs->szOsFile );334    if( pSubOpen==0 ){335      *rc = SQLITE_IOERR_NOMEM;336      return 0;337    }338    pGroup->aReal[iChunk].p = pSubOpen;339    *rc = pOrigVfs->xOpen(pOrigVfs, pGroup->aReal[iChunk].z, pSubOpen,340                          flags, pOutFlags);341    if( (*rc)!=SQLITE_OK ){342      sqlite3_log(*rc, "multiplexor.xOpen failure on %s",343                  pGroup->aReal[iChunk].z);344      sqlite3_free(pSubOpen);345      pGroup->aReal[iChunk].p = 0;346      return 0;347    }348  }349  return pSubOpen;350}351 352/*353** Return the size, in bytes, of chunk number iChunk.  If that chunk354** does not exist, then return 0.  This function does not distinguish between355** non-existent files and zero-length files.356*/357static sqlite3_int64 multiplexSubSize(358  multiplexGroup *pGroup,    /* The multiplexor group */359  int iChunk,                /* Which chunk to open.  0==original file */360  int *rc                    /* Result code in and out */361){362  sqlite3_file *pSub;363  sqlite3_int64 sz = 0;364 365  if( *rc ) return 0;366  pSub = multiplexSubOpen(pGroup, iChunk, rc, NULL, 0);367  if( pSub==0 ) return 0;368  *rc = pSub->pMethods->xFileSize(pSub, &sz);369  return sz;370}  371 372/*373** This is the implementation of the multiplex_control() SQL function.374*/375static void multiplexControlFunc(376  sqlite3_context *context,377  int argc,378  sqlite3_value **argv379){380  int rc = SQLITE_OK;381  sqlite3 *db = sqlite3_context_db_handle(context);382  int op = 0;383  int iVal;384 385  if( !db || argc!=2 ){386    rc = SQLITE_ERROR;387  }else{388    /* extract params */389    op = sqlite3_value_int(argv[0]);390    iVal = sqlite3_value_int(argv[1]);391    /* map function op to file_control op */392    switch( op ){393      case 1:394        op = MULTIPLEX_CTRL_ENABLE;395        break;396      case 2:397        op = MULTIPLEX_CTRL_SET_CHUNK_SIZE;398        break;399      case 3:400        op = MULTIPLEX_CTRL_SET_MAX_CHUNKS;401        break;402      default:403        rc = SQLITE_NOTFOUND;404        break;405    }406  }407  if( rc==SQLITE_OK ){408    rc = sqlite3_file_control(db, 0, op, &iVal);409  }410  sqlite3_result_error_code(context, rc);411}412 413/*414** This is the entry point to register the auto-extension for the415** multiplex_control() function.416*/417static int multiplexFuncInit(418  sqlite3 *db,419  char **pzErrMsg,420  const sqlite3_api_routines *pApi421){422  int rc;423  rc = sqlite3_create_function(db, "multiplex_control", 2, SQLITE_ANY,424      0, multiplexControlFunc, 0, 0);425  return rc;426}427 428/*429** Close a single sub-file in the connection group.430*/431static void multiplexSubClose(432  multiplexGroup *pGroup,433  int iChunk,434  sqlite3_vfs *pOrigVfs435){436  sqlite3_file *pSubOpen = pGroup->aReal[iChunk].p;437  if( pSubOpen ){438    pSubOpen->pMethods->xClose(pSubOpen);439    if( pOrigVfs && pGroup->aReal[iChunk].z ){440      pOrigVfs->xDelete(pOrigVfs, pGroup->aReal[iChunk].z, 0);441    }442    sqlite3_free(pGroup->aReal[iChunk].p);443  }444  sqlite3_free_filename(pGroup->aReal[iChunk].z);445  memset(&pGroup->aReal[iChunk], 0, sizeof(pGroup->aReal[iChunk]));446}447 448/*449** Deallocate memory held by a multiplexGroup450*/451static void multiplexFreeComponents(multiplexGroup *pGroup){452  int i;453  for(i=0; i<pGroup->nReal; i++){ multiplexSubClose(pGroup, i, 0); }454  sqlite3_free(pGroup->aReal);455  pGroup->aReal = 0;456  pGroup->nReal = 0;457}458 459 460/************************* VFS Method Wrappers *****************************/461 462/*463** This is the xOpen method used for the "multiplex" VFS.464**465** Most of the work is done by the underlying original VFS.  This method466** simply links the new file into the appropriate multiplex group if it is a467** file that needs to be tracked.468*/469static int multiplexOpen(470  sqlite3_vfs *pVfs,         /* The multiplex VFS */471  const char *zName,         /* Name of file to be opened */472  sqlite3_file *pConn,       /* Fill in this file descriptor */473  int flags,                 /* Flags to control the opening */474  int *pOutFlags             /* Flags showing results of opening */475){476  int rc = SQLITE_OK;                  /* Result code */477  multiplexConn *pMultiplexOpen;       /* The new multiplex file descriptor */478  multiplexGroup *pGroup = 0;          /* Corresponding multiplexGroup object */479  sqlite3_file *pSubOpen = 0;                    /* Real file descriptor */480  sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */481  int nName = 0;482  int sz = 0;483  char *zToFree = 0;484 485  UNUSED_PARAMETER(pVfs);486  memset(pConn, 0, pVfs->szOsFile);487  assert( zName || (flags & SQLITE_OPEN_DELETEONCLOSE) );488 489  /* We need to create a group structure and manage490  ** access to this group of files.491  */492  pMultiplexOpen = (multiplexConn*)pConn;493 494  if( rc==SQLITE_OK ){495    /* allocate space for group */496    nName = zName ? multiplexStrlen30(zName) : 0;497    sz = sizeof(multiplexGroup)                             /* multiplexGroup */498       + nName + 1;                                         /* zName */499    pGroup = sqlite3_malloc64( sz );500    if( pGroup==0 ){501      rc = SQLITE_NOMEM;502    }503  }504 505  if( rc==SQLITE_OK ){506    const char *zUri = (flags & SQLITE_OPEN_URI) ? zName : 0;507    /* assign pointers to extra space allocated */508    memset(pGroup, 0, sz);509    pMultiplexOpen->pGroup = pGroup;510    pGroup->bEnabled = (unsigned char)-1;511    pGroup->bTruncate = (unsigned char)sqlite3_uri_boolean(zUri, "truncate",512                                   (flags & SQLITE_OPEN_MAIN_DB)==0);513    pGroup->szChunk = (int)sqlite3_uri_int64(zUri, "chunksize",514                                        SQLITE_MULTIPLEX_CHUNK_SIZE);515    pGroup->szChunk = (pGroup->szChunk+0xffff)&~0xffff;516    if( zName ){517      char *p = (char *)&pGroup[1];518      pGroup->zName = p;519      memcpy(pGroup->zName, zName, nName+1);520      pGroup->nName = nName;521    }522    if( pGroup->bEnabled ){523      /* Make sure that the chunksize is such that the pending byte does not524      ** falls at the end of a chunk.  A region of up to 64K following525      ** the pending byte is never written, so if the pending byte occurs526      ** near the end of a chunk, that chunk will be too small. */527#ifndef SQLITE_OMIT_WSD528      extern int sqlite3PendingByte;529#else530      int sqlite3PendingByte = 0x40000000;531#endif532      while( (sqlite3PendingByte % pGroup->szChunk)>=(pGroup->szChunk-65536) ){533        pGroup->szChunk += 65536;534      }535    }536    pGroup->flags = (flags & ~SQLITE_OPEN_URI);537    rc = multiplexSubFilename(pGroup, 1);538    if( rc==SQLITE_OK ){539      pSubOpen = multiplexSubOpen(pGroup, 0, &rc, pOutFlags, 0);540      if( pSubOpen==0 && rc==SQLITE_OK ) rc = SQLITE_CANTOPEN;541    }542    if( rc==SQLITE_OK ){543      sqlite3_int64 sz64;544 545      rc = pSubOpen->pMethods->xFileSize(pSubOpen, &sz64);546      if( rc==SQLITE_OK && zName ){547        int bExists;548        if( flags & SQLITE_OPEN_SUPER_JOURNAL ){549          pGroup->bEnabled = 0;550        }else551        if( sz64==0 ){552          if( flags & SQLITE_OPEN_MAIN_JOURNAL ){553            /* If opening a main journal file and the first chunk is zero554            ** bytes in size, delete any subsequent chunks from the555            ** file-system. */556            int iChunk = 1;557            do {558              rc = pOrigVfs->xAccess(pOrigVfs,559                  pGroup->aReal[iChunk].z, SQLITE_ACCESS_EXISTS, &bExists560              );561              if( rc==SQLITE_OK && bExists ){562                rc = pOrigVfs->xDelete(pOrigVfs, pGroup->aReal[iChunk].z, 0);563                if( rc==SQLITE_OK ){564                  rc = multiplexSubFilename(pGroup, ++iChunk);565                }566              }567            }while( rc==SQLITE_OK && bExists );568          }569        }else{570          /* If the first overflow file exists and if the size of the main file571          ** is different from the chunk size, that means the chunk size is set572          ** set incorrectly.  So fix it.573          **574          ** Or, if the first overflow file does not exist and the main file is575          ** larger than the chunk size, that means the chunk size is too small.576          ** But we have no way of determining the intended chunk size, so577          ** just disable the multiplexor all together.578          */579          rc = pOrigVfs->xAccess(pOrigVfs, pGroup->aReal[1].z,580              SQLITE_ACCESS_EXISTS, &bExists);581          bExists = multiplexSubSize(pGroup, 1, &rc)>0;582          if( rc==SQLITE_OK && bExists && sz64==(sz64&0xffff0000) && sz64>0583              && sz64!=pGroup->szChunk ){584            pGroup->szChunk = (int)sz64;585          }else if( rc==SQLITE_OK && !bExists && sz64>pGroup->szChunk ){586            pGroup->bEnabled = 0;587          }588        }589      }590    }591 592    if( rc==SQLITE_OK ){593      if( pSubOpen->pMethods->iVersion==1 ){594        pConn->pMethods = &gMultiplex.sIoMethodsV1;595      }else{596        pConn->pMethods = &gMultiplex.sIoMethodsV2;597      }598    }else{599      multiplexFreeComponents(pGroup);600      sqlite3_free(pGroup);601    }602  }603  sqlite3_free(zToFree);604  return rc;605}606 607/*608** This is the xDelete method used for the "multiplex" VFS.609** It attempts to delete the filename specified.610*/611static int multiplexDelete(612  sqlite3_vfs *pVfs,         /* The multiplex VFS */613  const char *zName,         /* Name of file to delete */614  int syncDir615){616  int rc;617  sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */618  rc = pOrigVfs->xDelete(pOrigVfs, zName, syncDir);619  if( rc==SQLITE_OK ){620    /* If the main chunk was deleted successfully, also delete any subsequent621    ** chunks - starting with the last (highest numbered).622    */623    int nName = (int)strlen(zName);624    char *z;625    z = sqlite3_malloc64(nName + 5);626    if( z==0 ){627      rc = SQLITE_IOERR_NOMEM;628    }else{629      int iChunk = 0;630      int bExists;631      do{632        multiplexFilename(zName, nName, SQLITE_OPEN_MAIN_JOURNAL, ++iChunk, z);633        rc = pOrigVfs->xAccess(pOrigVfs, z, SQLITE_ACCESS_EXISTS, &bExists);634      }while( rc==SQLITE_OK && bExists );635      while( rc==SQLITE_OK && iChunk>1 ){636        multiplexFilename(zName, nName, SQLITE_OPEN_MAIN_JOURNAL, --iChunk, z);637        rc = pOrigVfs->xDelete(pOrigVfs, z, syncDir);638      }639      if( rc==SQLITE_OK ){640        iChunk = 0;641        do{642          multiplexFilename(zName, nName, SQLITE_OPEN_WAL, ++iChunk, z);643          rc = pOrigVfs->xAccess(pOrigVfs, z, SQLITE_ACCESS_EXISTS, &bExists);644        }while( rc==SQLITE_OK && bExists );645        while( rc==SQLITE_OK && iChunk>1 ){646          multiplexFilename(zName, nName, SQLITE_OPEN_WAL, --iChunk, z);647          rc = pOrigVfs->xDelete(pOrigVfs, z, syncDir);648        }649      }650    }651    sqlite3_free(z);652  }653  return rc;654}655 656static int multiplexAccess(sqlite3_vfs *a, const char *b, int c, int *d){657  return gMultiplex.pOrigVfs->xAccess(gMultiplex.pOrigVfs, b, c, d);658}659static int multiplexFullPathname(sqlite3_vfs *a, const char *b, int c, char *d){660  return gMultiplex.pOrigVfs->xFullPathname(gMultiplex.pOrigVfs, b, c, d);661}662static void *multiplexDlOpen(sqlite3_vfs *a, const char *b){663  return gMultiplex.pOrigVfs->xDlOpen(gMultiplex.pOrigVfs, b);664}665static void multiplexDlError(sqlite3_vfs *a, int b, char *c){666  gMultiplex.pOrigVfs->xDlError(gMultiplex.pOrigVfs, b, c);667}668static void (*multiplexDlSym(sqlite3_vfs *a, void *b, const char *c))(void){669  return gMultiplex.pOrigVfs->xDlSym(gMultiplex.pOrigVfs, b, c);670}671static void multiplexDlClose(sqlite3_vfs *a, void *b){672  gMultiplex.pOrigVfs->xDlClose(gMultiplex.pOrigVfs, b);673}674static int multiplexRandomness(sqlite3_vfs *a, int b, char *c){675  return gMultiplex.pOrigVfs->xRandomness(gMultiplex.pOrigVfs, b, c);676}677static int multiplexSleep(sqlite3_vfs *a, int b){678  return gMultiplex.pOrigVfs->xSleep(gMultiplex.pOrigVfs, b);679}680static int multiplexCurrentTime(sqlite3_vfs *a, double *b){681  return gMultiplex.pOrigVfs->xCurrentTime(gMultiplex.pOrigVfs, b);682}683static int multiplexGetLastError(sqlite3_vfs *a, int b, char *c){684  if( gMultiplex.pOrigVfs->xGetLastError ){685    return gMultiplex.pOrigVfs->xGetLastError(gMultiplex.pOrigVfs, b, c);686  }else{687    return 0;688  }689}690static int multiplexCurrentTimeInt64(sqlite3_vfs *a, sqlite3_int64 *b){691  return gMultiplex.pOrigVfs->xCurrentTimeInt64(gMultiplex.pOrigVfs, b);692}693 694/************************ I/O Method Wrappers *******************************/695 696/* xClose requests get passed through to the original VFS.697** We loop over all open chunk handles and close them.698** The group structure for this file is unlinked from699** our list of groups and freed.700*/701static int multiplexClose(sqlite3_file *pConn){702  multiplexConn *p = (multiplexConn*)pConn;703  multiplexGroup *pGroup = p->pGroup;704  int rc = SQLITE_OK;705  multiplexFreeComponents(pGroup);706  sqlite3_free(pGroup);707  return rc;708}709 710/* Pass xRead requests thru to the original VFS after711** determining the correct chunk to operate on.712** Break up reads across chunk boundaries.713*/714static int multiplexRead(715  sqlite3_file *pConn,716  void *pBuf,717  int iAmt,718  sqlite3_int64 iOfst719){720  multiplexConn *p = (multiplexConn*)pConn;721  multiplexGroup *pGroup = p->pGroup;722  int rc = SQLITE_OK;723  if( !pGroup->bEnabled ){724    sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);725    if( pSubOpen==0 ){726      rc = SQLITE_IOERR_READ;727    }else{728      rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst);729    }730  }else{731    while( iAmt > 0 ){732      int i = (int)(iOfst / pGroup->szChunk);733      sqlite3_file *pSubOpen;734      pSubOpen = multiplexSubOpen(pGroup, i, &rc, NULL, 1);735      if( pSubOpen ){736        int extra = ((int)(iOfst % pGroup->szChunk) + iAmt) - pGroup->szChunk;737        if( extra<0 ) extra = 0;738        iAmt -= extra;739        rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt,740                                       iOfst % pGroup->szChunk);741        if( rc!=SQLITE_OK ) break;742        pBuf = (char *)pBuf + iAmt;743        iOfst += iAmt;744        iAmt = extra;745      }else{746        rc = SQLITE_IOERR_READ;747        break;748      }749    }750  }751 752  return rc;753}754 755/* Pass xWrite requests thru to the original VFS after756** determining the correct chunk to operate on.757** Break up writes across chunk boundaries.758*/759static int multiplexWrite(760  sqlite3_file *pConn,761  const void *pBuf,762  int iAmt,763  sqlite3_int64 iOfst764){765  multiplexConn *p = (multiplexConn*)pConn;766  multiplexGroup *pGroup = p->pGroup;767  int rc = SQLITE_OK;768  if( !pGroup->bEnabled ){769    sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);770    if( pSubOpen==0 ){771      rc = SQLITE_IOERR_WRITE;772    }else{773      rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst);774    }775  }else{776    while( rc==SQLITE_OK && iAmt>0 ){777      int i = (int)(iOfst / pGroup->szChunk);778      sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, i, &rc, NULL, 1);779      if( pSubOpen ){780        int extra = ((int)(iOfst % pGroup->szChunk) + iAmt) -781                    pGroup->szChunk;782        if( extra<0 ) extra = 0;783        iAmt -= extra;784        rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt,785                                        iOfst % pGroup->szChunk);786        pBuf = (char *)pBuf + iAmt;787        iOfst += iAmt;788        iAmt = extra;789      }790    }791  }792  return rc;793}794 795/* Pass xTruncate requests thru to the original VFS after796** determining the correct chunk to operate on.  Delete any797** chunks above the truncate mark.798*/799static int multiplexTruncate(sqlite3_file *pConn, sqlite3_int64 size){800  multiplexConn *p = (multiplexConn*)pConn;801  multiplexGroup *pGroup = p->pGroup;802  int rc = SQLITE_OK;803  if( !pGroup->bEnabled ){804    sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);805    if( pSubOpen==0 ){806      rc = SQLITE_IOERR_TRUNCATE;807    }else{808      rc = pSubOpen->pMethods->xTruncate(pSubOpen, size);809    }810  }else{811    int i;812    int iBaseGroup = (int)(size / pGroup->szChunk);813    sqlite3_file *pSubOpen;814    sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */815    /* delete the chunks above the truncate limit */816    for(i = pGroup->nReal-1; i>iBaseGroup && rc==SQLITE_OK; i--){817      if( pGroup->bTruncate ){818        multiplexSubClose(pGroup, i, pOrigVfs);819      }else{820        pSubOpen = multiplexSubOpen(pGroup, i, &rc, 0, 0);821        if( pSubOpen ){822          rc = pSubOpen->pMethods->xTruncate(pSubOpen, 0);823        }824      }825    }826    if( rc==SQLITE_OK ){827      pSubOpen = multiplexSubOpen(pGroup, iBaseGroup, &rc, 0, 0);828      if( pSubOpen ){829        rc = pSubOpen->pMethods->xTruncate(pSubOpen, size % pGroup->szChunk);830      }831    }832    if( rc ) rc = SQLITE_IOERR_TRUNCATE;833  }834  return rc;835}836 837/* Pass xSync requests through to the original VFS without change838*/839static int multiplexSync(sqlite3_file *pConn, int flags){840  multiplexConn *p = (multiplexConn*)pConn;841  multiplexGroup *pGroup = p->pGroup;842  int rc = SQLITE_OK;843  int i;844  for(i=0; i<pGroup->nReal; i++){845    sqlite3_file *pSubOpen = pGroup->aReal[i].p;846    if( pSubOpen ){847      int rc2 = pSubOpen->pMethods->xSync(pSubOpen, flags);848      if( rc2!=SQLITE_OK ) rc = rc2;849    }850  }851  return rc;852}853 854/* Pass xFileSize requests through to the original VFS.855** Aggregate the size of all the chunks before returning.856*/857static int multiplexFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){858  multiplexConn *p = (multiplexConn*)pConn;859  multiplexGroup *pGroup = p->pGroup;860  int rc = SQLITE_OK;861  int i;862  if( !pGroup->bEnabled ){863    sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);864    if( pSubOpen==0 ){865      rc = SQLITE_IOERR_FSTAT;866    }else{867      rc = pSubOpen->pMethods->xFileSize(pSubOpen, pSize);868    }869  }else{870    *pSize = 0;871    for(i=0; rc==SQLITE_OK; i++){872      sqlite3_int64 sz = multiplexSubSize(pGroup, i, &rc);873      if( sz==0 ) break;874      *pSize = i*(sqlite3_int64)pGroup->szChunk + sz;875    }876  }877  return rc;878}879 880/* Pass xLock requests through to the original VFS unchanged.881*/882static int multiplexLock(sqlite3_file *pConn, int lock){883  multiplexConn *p = (multiplexConn*)pConn;884  int rc;885  sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);886  if( pSubOpen ){887    return pSubOpen->pMethods->xLock(pSubOpen, lock);888  }889  return SQLITE_BUSY;890}891 892/* Pass xUnlock requests through to the original VFS unchanged.893*/894static int multiplexUnlock(sqlite3_file *pConn, int lock){895  multiplexConn *p = (multiplexConn*)pConn;896  int rc;897  sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);898  if( pSubOpen ){899    return pSubOpen->pMethods->xUnlock(pSubOpen, lock);900  }901  return SQLITE_IOERR_UNLOCK;902}903 904/* Pass xCheckReservedLock requests through to the original VFS unchanged.905*/906static int multiplexCheckReservedLock(sqlite3_file *pConn, int *pResOut){907  multiplexConn *p = (multiplexConn*)pConn;908  int rc;909  sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);910  if( pSubOpen ){911    return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut);912  }913  return SQLITE_IOERR_CHECKRESERVEDLOCK;914}915 916/* Pass xFileControl requests through to the original VFS unchanged,917** except for any MULTIPLEX_CTRL_* requests here.918*/919static int multiplexFileControl(sqlite3_file *pConn, int op, void *pArg){920  multiplexConn *p = (multiplexConn*)pConn;921  multiplexGroup *pGroup = p->pGroup;922  int rc = SQLITE_ERROR;923  sqlite3_file *pSubOpen;924 925  if( !gMultiplex.isInitialized ) return SQLITE_MISUSE;926  switch( op ){927    case MULTIPLEX_CTRL_ENABLE:928      if( pArg ) {929        int bEnabled = *(int *)pArg;930        pGroup->bEnabled = (unsigned char)bEnabled;931        rc = SQLITE_OK;932      }933      break;934    case MULTIPLEX_CTRL_SET_CHUNK_SIZE:935      if( pArg ) {936        unsigned int szChunk = *(unsigned*)pArg;937        if( szChunk<1 ){938          rc = SQLITE_MISUSE;939        }else{940          /* Round up to nearest multiple of MAX_PAGE_SIZE. */941          szChunk = (szChunk + (MAX_PAGE_SIZE-1));942          szChunk &= ~(MAX_PAGE_SIZE-1);943          pGroup->szChunk = szChunk;944          rc = SQLITE_OK;945        }946      }947      break;948    case MULTIPLEX_CTRL_SET_MAX_CHUNKS:949      rc = SQLITE_OK;950      break;951    case SQLITE_FCNTL_SIZE_HINT:952    case SQLITE_FCNTL_CHUNK_SIZE:953      /* no-op these */954      rc = SQLITE_OK;955      break;956    case SQLITE_FCNTL_PRAGMA: {957      char **aFcntl = (char**)pArg;958      /*959      ** EVIDENCE-OF: R-29875-31678 The argument to the SQLITE_FCNTL_PRAGMA960      ** file control is an array of pointers to strings (char**) in which the961      ** second element of the array is the name of the pragma and the third962      ** element is the argument to the pragma or NULL if the pragma has no963      ** argument.964      */965      if( aFcntl[1] && sqlite3_strnicmp(aFcntl[1],"multiplex_",10)==0 ){966        sqlite3_int64 sz = 0;967        (void)multiplexFileSize(pConn, &sz);968        /*969        ** PRAGMA multiplex_truncate=BOOLEAN;970        ** PRAGMA multiplex_truncate;971        **972        ** Turn the multiplexor truncate feature on or off.  Return either973        ** "on" or "off" to indicate the new setting.  If the BOOLEAN argument974        ** is omitted, just return the current value for the truncate setting.975        */976        if( sqlite3_stricmp(aFcntl[1],"multiplex_truncate")==0 ){977          if( aFcntl[2] && aFcntl[2][0] ){978            if( sqlite3_stricmp(aFcntl[2], "on")==0979             || sqlite3_stricmp(aFcntl[2], "1")==0 ){980              pGroup->bTruncate = 1;981            }else982            if( sqlite3_stricmp(aFcntl[2], "off")==0983             || sqlite3_stricmp(aFcntl[2], "0")==0 ){984              pGroup->bTruncate = 0;985            }986          }987          /* EVIDENCE-OF: R-27806-26076 The handler for an SQLITE_FCNTL_PRAGMA988          ** file control can optionally make the first element of the char**989          ** argument point to a string obtained from sqlite3_mprintf() or the990          ** equivalent and that string will become the result of the pragma991          ** or the error message if the pragma fails.992          */993          aFcntl[0] = sqlite3_mprintf(pGroup->bTruncate ? "on" : "off");994          rc = SQLITE_OK;995          break;996        }997        /*998        ** PRAGMA multiplex_enabled;999        **1000        ** Return 0 or 1 depending on whether the multiplexor is enabled or1001        ** disabled, respectively.1002        */1003        if( sqlite3_stricmp(aFcntl[1],"multiplex_enabled")==0 ){1004          aFcntl[0] = sqlite3_mprintf("%d", pGroup->bEnabled!=0);1005          rc = SQLITE_OK;1006          break;1007        }1008        /*1009        ** PRAGMA multiplex_chunksize;1010        **1011        ** Return the chunksize for the multiplexor, or no-op if the1012        ** multiplexor is not active.1013        */1014        if( sqlite3_stricmp(aFcntl[1],"multiplex_chunksize")==01015         && pGroup->bEnabled1016        ){1017          aFcntl[0] = sqlite3_mprintf("%u", pGroup->szChunk);1018          rc = SQLITE_OK;1019          break;1020        }1021        /*1022        ** PRAGMA multiplex_filecount;1023        **1024        ** Return the number of disk files currently in use by the1025        ** multiplexor.  This should be the total database size size1026        ** divided by the chunksize and rounded up.1027        */1028        if( sqlite3_stricmp(aFcntl[1],"multiplex_filecount")==0 ){1029          int n = 0;1030          int ii;1031          for(ii=0; ii<pGroup->nReal; ii++){1032            if( pGroup->aReal[ii].p!=0 ) n++;1033          }1034          aFcntl[0] = sqlite3_mprintf("%d", n);1035          rc = SQLITE_OK;1036          break;1037        }1038      }1039      /* If the multiplexor does not handle the pragma, pass it through1040      ** into the default case. */1041    }1042    default:1043      pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);1044      if( pSubOpen ){1045        rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg);1046        if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){1047         *(char**)pArg = sqlite3_mprintf("multiplex/%z", *(char**)pArg);1048        }1049      }1050      break;1051  }1052  return rc;1053}1054 1055/* Pass xSectorSize requests through to the original VFS unchanged.1056*/1057static int multiplexSectorSize(sqlite3_file *pConn){1058  multiplexConn *p = (multiplexConn*)pConn;1059  int rc;1060  sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);1061  if( pSubOpen && pSubOpen->pMethods->xSectorSize ){1062    return pSubOpen->pMethods->xSectorSize(pSubOpen);1063  }1064  return DEFAULT_SECTOR_SIZE;1065}1066 1067/* Pass xDeviceCharacteristics requests through to the original VFS unchanged.1068*/1069static int multiplexDeviceCharacteristics(sqlite3_file *pConn){1070  multiplexConn *p = (multiplexConn*)pConn;1071  int rc;1072  sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);1073  if( pSubOpen ){1074    return pSubOpen->pMethods->xDeviceCharacteristics(pSubOpen);1075  }1076  return 0;1077}1078 1079/* Pass xShmMap requests through to the original VFS unchanged.1080*/1081static int multiplexShmMap(1082  sqlite3_file *pConn,            /* Handle open on database file */1083  int iRegion,                    /* Region to retrieve */1084  int szRegion,                   /* Size of regions */1085  int bExtend,                    /* True to extend file if necessary */1086  void volatile **pp              /* OUT: Mapped memory */1087){1088  multiplexConn *p = (multiplexConn*)pConn;1089  int rc;1090  sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);1091  if( pSubOpen ){1092    return pSubOpen->pMethods->xShmMap(pSubOpen, iRegion, szRegion, bExtend,pp);1093  }1094  return SQLITE_IOERR;1095}1096 1097/* Pass xShmLock requests through to the original VFS unchanged.1098*/1099static int multiplexShmLock(1100  sqlite3_file *pConn,       /* Database file holding the shared memory */1101  int ofst,                  /* First lock to acquire or release */1102  int n,                     /* Number of locks to acquire or release */1103  int flags                  /* What to do with the lock */1104){1105  multiplexConn *p = (multiplexConn*)pConn;1106  int rc;1107  sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);1108  if( pSubOpen ){1109    return pSubOpen->pMethods->xShmLock(pSubOpen, ofst, n, flags);1110  }1111  return SQLITE_BUSY;1112}1113 1114/* Pass xShmBarrier requests through to the original VFS unchanged.1115*/1116static void multiplexShmBarrier(sqlite3_file *pConn){1117  multiplexConn *p = (multiplexConn*)pConn;1118  int rc;1119  sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);1120  if( pSubOpen ){1121    pSubOpen->pMethods->xShmBarrier(pSubOpen);1122  }1123}1124 1125/* Pass xShmUnmap requests through to the original VFS unchanged.1126*/1127static int multiplexShmUnmap(sqlite3_file *pConn, int deleteFlag){1128  multiplexConn *p = (multiplexConn*)pConn;1129  int rc;1130  sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);1131  if( pSubOpen ){1132    return pSubOpen->pMethods->xShmUnmap(pSubOpen, deleteFlag);1133  }1134  return SQLITE_OK;1135}1136 1137/************************** Public Interfaces *****************************/1138/*1139** CAPI: Initialize the multiplex VFS shim - sqlite3_multiplex_initialize()1140**1141** Use the VFS named zOrigVfsName as the VFS that does the actual work.1142** Use the default if zOrigVfsName==NULL.1143**1144** The multiplex VFS shim is named "multiplex".  It will become the default1145** VFS if makeDefault is non-zero.1146**1147** THIS ROUTINE IS NOT THREADSAFE.  Call this routine exactly once1148** during start-up.1149*/1150int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault){1151  sqlite3_vfs *pOrigVfs;1152  if( gMultiplex.isInitialized ) return SQLITE_MISUSE;1153  pOrigVfs = sqlite3_vfs_find(zOrigVfsName);1154  if( pOrigVfs==0 ) return SQLITE_ERROR;1155  assert( pOrigVfs!=&gMultiplex.sThisVfs );1156  gMultiplex.isInitialized = 1;1157  gMultiplex.pOrigVfs = pOrigVfs;1158  gMultiplex.sThisVfs = *pOrigVfs;1159  gMultiplex.sThisVfs.szOsFile += sizeof(multiplexConn);1160  gMultiplex.sThisVfs.zName = SQLITE_MULTIPLEX_VFS_NAME;1161  gMultiplex.sThisVfs.xOpen = multiplexOpen;1162  gMultiplex.sThisVfs.xDelete = multiplexDelete;1163  gMultiplex.sThisVfs.xAccess = multiplexAccess;1164  gMultiplex.sThisVfs.xFullPathname = multiplexFullPathname;1165  gMultiplex.sThisVfs.xDlOpen = multiplexDlOpen;1166  gMultiplex.sThisVfs.xDlError = multiplexDlError;1167  gMultiplex.sThisVfs.xDlSym = multiplexDlSym;1168  gMultiplex.sThisVfs.xDlClose = multiplexDlClose;1169  gMultiplex.sThisVfs.xRandomness = multiplexRandomness;1170  gMultiplex.sThisVfs.xSleep = multiplexSleep;1171  gMultiplex.sThisVfs.xCurrentTime = multiplexCurrentTime;1172  gMultiplex.sThisVfs.xGetLastError = multiplexGetLastError;1173  gMultiplex.sThisVfs.xCurrentTimeInt64 = multiplexCurrentTimeInt64;1174 1175  gMultiplex.sIoMethodsV1.iVersion = 1;1176  gMultiplex.sIoMethodsV1.xClose = multiplexClose;1177  gMultiplex.sIoMethodsV1.xRead = multiplexRead;1178  gMultiplex.sIoMethodsV1.xWrite = multiplexWrite;1179  gMultiplex.sIoMethodsV1.xTruncate = multiplexTruncate;1180  gMultiplex.sIoMethodsV1.xSync = multiplexSync;1181  gMultiplex.sIoMethodsV1.xFileSize = multiplexFileSize;1182  gMultiplex.sIoMethodsV1.xLock = multiplexLock;1183  gMultiplex.sIoMethodsV1.xUnlock = multiplexUnlock;1184  gMultiplex.sIoMethodsV1.xCheckReservedLock = multiplexCheckReservedLock;1185  gMultiplex.sIoMethodsV1.xFileControl = multiplexFileControl;1186  gMultiplex.sIoMethodsV1.xSectorSize = multiplexSectorSize;1187  gMultiplex.sIoMethodsV1.xDeviceCharacteristics =1188                                            multiplexDeviceCharacteristics;1189  gMultiplex.sIoMethodsV2 = gMultiplex.sIoMethodsV1;1190  gMultiplex.sIoMethodsV2.iVersion = 2;1191  gMultiplex.sIoMethodsV2.xShmMap = multiplexShmMap;1192  gMultiplex.sIoMethodsV2.xShmLock = multiplexShmLock;1193  gMultiplex.sIoMethodsV2.xShmBarrier = multiplexShmBarrier;1194  gMultiplex.sIoMethodsV2.xShmUnmap = multiplexShmUnmap;1195  sqlite3_vfs_register(&gMultiplex.sThisVfs, makeDefault);1196 1197  sqlite3_auto_extension((void(*)(void))multiplexFuncInit);1198 1199  return SQLITE_OK;1200}

Showing the first 1,200 of 1370 lines. Download the file for the rest.