#include "sqlite3.h"
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include "test_multiplex.h"
#ifndef SQLITE_CORE
#define SQLITE_CORE 1
#endif
#include "sqlite3ext.h"
#define UNUSED_PARAMETER(x) (void)(x)
#define MAX_PAGE_SIZE 0x10000
#define DEFAULT_SECTOR_SIZE 0x1000
#define MX_CHUNK_NUMBER 299
#define SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET 400
#define SQLITE_MULTIPLEX_WAL_8_3_OFFSET 700
#ifndef SQLITE_MULTIPLEX_VFS_NAME
# define SQLITE_MULTIPLEX_VFS_NAME "multiplex"
#endif
#ifndef SQLITE_MULTIPLEX_CHUNK_SIZE
# define SQLITE_MULTIPLEX_CHUNK_SIZE 2147418112
#endif
#ifndef SQLITE_MULTIPLEX_MAX_CHUNKS
# define SQLITE_MULTIPLEX_MAX_CHUNKS 12
#endif
typedef struct multiplexGroup multiplexGroup;
typedef struct multiplexConn multiplexConn;
struct multiplexGroup {
struct multiplexReal {
sqlite3_file *p;
char *z;
} *aReal;
int nReal;
char *zName;
int nName;
int flags;
unsigned int szChunk;
unsigned char bEnabled;
unsigned char bTruncate;
};
struct multiplexConn {
sqlite3_file base;
multiplexGroup *pGroup;
};
static struct {
sqlite3_vfs *pOrigVfs;
sqlite3_vfs sThisVfs;
sqlite3_io_methods sIoMethodsV1;
sqlite3_io_methods sIoMethodsV2;
int isInitialized;
} gMultiplex;
static int multiplexStrlen30(const char *z){
const char *z2 = z;
if( z==0 ) return 0;
while( *z2 ){ z2++; }
return 0x3fffffff & (int)(z2 - z);
}
static void multiplexFilename(
const char *zBase,
int nBase,
int flags,
int iChunk,
char *zOut
){
int n = nBase;
memcpy(zOut, zBase, n+1);
if( iChunk!=0 && iChunk<=MX_CHUNK_NUMBER ){
#ifdef SQLITE_ENABLE_8_3_NAMES
int i;
for(i=n-1; i>0 && i>=n-4 && zOut[i]!='.'; i--){}
if( i>=n-4 ) n = i+1;
if( flags & SQLITE_OPEN_MAIN_JOURNAL ){
iChunk += SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET;
}else if( flags & SQLITE_OPEN_WAL ){
iChunk += SQLITE_MULTIPLEX_WAL_8_3_OFFSET;
}
#endif
sqlite3_snprintf(4,&zOut[n],"%03d",iChunk);
n += 3;
}
assert( zOut[n]=='\0' );
zOut[n+1] = '\0';
}
static int multiplexSubFilename(multiplexGroup *pGroup, int iChunk){
if( iChunk>=pGroup->nReal ){
struct multiplexReal *p;
p = sqlite3_realloc64(pGroup->aReal, (iChunk+1)*sizeof(*p));
if( p==0 ){
return SQLITE_NOMEM;
}
memset(&p[pGroup->nReal], 0, sizeof(p[0])*(iChunk+1-pGroup->nReal));
pGroup->aReal = p;
pGroup->nReal = iChunk+1;
}
if( pGroup->zName && pGroup->aReal[iChunk].z==0 ){
char *z;
int n = pGroup->nName;
z = sqlite3_malloc64( n+5 );
if( z==0 ){
return SQLITE_NOMEM;
}
multiplexFilename(pGroup->zName, pGroup->nName, pGroup->flags, iChunk, z);
pGroup->aReal[iChunk].z = (char*)sqlite3_create_filename(z,"","",0,0);
sqlite3_free(z);
if( pGroup->aReal[iChunk].z==0 ) return SQLITE_NOMEM;
}
return SQLITE_OK;
}
static sqlite3_file *multiplexSubOpen(
multiplexGroup *pGroup,
int iChunk,
int *rc,
int *pOutFlags,
int createFlag
){
sqlite3_file *pSubOpen = 0;
sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;
#ifdef SQLITE_ENABLE_8_3_NAMES
if( iChunk>=SQLITE_MULTIPLEX_JOURNAL_8_3_OFFSET ){
sqlite3_log(SQLITE_FULL, "multiplexed chunk overflow: %s", pGroup->zName);
*rc = SQLITE_FULL;
return 0;
}
#endif
*rc = multiplexSubFilename(pGroup, iChunk);
if( (*rc)==SQLITE_OK && (pSubOpen = pGroup->aReal[iChunk].p)==0 ){
int flags, bExists;
flags = pGroup->flags;
if( createFlag ){
flags |= SQLITE_OPEN_CREATE;
}else if( iChunk==0 ){
}else if( pGroup->aReal[iChunk].z==0 ){
return 0;
}else{
*rc = pOrigVfs->xAccess(pOrigVfs, pGroup->aReal[iChunk].z,
SQLITE_ACCESS_EXISTS, &bExists);
if( *rc || !bExists ){
if( *rc ){
sqlite3_log(*rc, "multiplexor.xAccess failure on %s",
pGroup->aReal[iChunk].z);
}
return 0;
}
flags &= ~SQLITE_OPEN_CREATE;
}
pSubOpen = sqlite3_malloc64( pOrigVfs->szOsFile );
if( pSubOpen==0 ){
*rc = SQLITE_IOERR_NOMEM;
return 0;
}
pGroup->aReal[iChunk].p = pSubOpen;
*rc = pOrigVfs->xOpen(pOrigVfs, pGroup->aReal[iChunk].z, pSubOpen,
flags, pOutFlags);
if( (*rc)!=SQLITE_OK ){
sqlite3_log(*rc, "multiplexor.xOpen failure on %s",
pGroup->aReal[iChunk].z);
sqlite3_free(pSubOpen);
pGroup->aReal[iChunk].p = 0;
return 0;
}
}
return pSubOpen;
}
static sqlite3_int64 multiplexSubSize(
multiplexGroup *pGroup,
int iChunk,
int *rc
){
sqlite3_file *pSub;
sqlite3_int64 sz = 0;
if( *rc ) return 0;
pSub = multiplexSubOpen(pGroup, iChunk, rc, NULL, 0);
if( pSub==0 ) return 0;
*rc = pSub->pMethods->xFileSize(pSub, &sz);
return sz;
}
static void multiplexControlFunc(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
int rc = SQLITE_OK;
sqlite3 *db = sqlite3_context_db_handle(context);
int op = 0;
int iVal;
if( !db || argc!=2 ){
rc = SQLITE_ERROR;
}else{
op = sqlite3_value_int(argv[0]);
iVal = sqlite3_value_int(argv[1]);
switch( op ){
case 1:
op = MULTIPLEX_CTRL_ENABLE;
break;
case 2:
op = MULTIPLEX_CTRL_SET_CHUNK_SIZE;
break;
case 3:
op = MULTIPLEX_CTRL_SET_MAX_CHUNKS;
break;
default:
rc = SQLITE_NOTFOUND;
break;
}
}
if( rc==SQLITE_OK ){
rc = sqlite3_file_control(db, 0, op, &iVal);
}
sqlite3_result_error_code(context, rc);
}
static int multiplexFuncInit(
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
int rc;
rc = sqlite3_create_function(db, "multiplex_control", 2, SQLITE_ANY,
0, multiplexControlFunc, 0, 0);
return rc;
}
static void multiplexSubClose(
multiplexGroup *pGroup,
int iChunk,
sqlite3_vfs *pOrigVfs
){
sqlite3_file *pSubOpen = pGroup->aReal[iChunk].p;
if( pSubOpen ){
pSubOpen->pMethods->xClose(pSubOpen);
if( pOrigVfs && pGroup->aReal[iChunk].z ){
pOrigVfs->xDelete(pOrigVfs, pGroup->aReal[iChunk].z, 0);
}
sqlite3_free(pGroup->aReal[iChunk].p);
}
sqlite3_free_filename(pGroup->aReal[iChunk].z);
memset(&pGroup->aReal[iChunk], 0, sizeof(pGroup->aReal[iChunk]));
}
static void multiplexFreeComponents(multiplexGroup *pGroup){
int i;
for(i=0; i<pGroup->nReal; i++){ multiplexSubClose(pGroup, i, 0); }
sqlite3_free(pGroup->aReal);
pGroup->aReal = 0;
pGroup->nReal = 0;
}
static int multiplexOpen(
sqlite3_vfs *pVfs,
const char *zName,
sqlite3_file *pConn,
int flags,
int *pOutFlags
){
int rc = SQLITE_OK;
multiplexConn *pMultiplexOpen;
multiplexGroup *pGroup = 0;
sqlite3_file *pSubOpen = 0;
sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;
int nName = 0;
int sz = 0;
char *zToFree = 0;
UNUSED_PARAMETER(pVfs);
memset(pConn, 0, pVfs->szOsFile);
assert( zName || (flags & SQLITE_OPEN_DELETEONCLOSE) );
pMultiplexOpen = (multiplexConn*)pConn;
if( rc==SQLITE_OK ){
nName = zName ? multiplexStrlen30(zName) : 0;
sz = sizeof(multiplexGroup)
+ nName + 1;
pGroup = sqlite3_malloc64( sz );
if( pGroup==0 ){
rc = SQLITE_NOMEM;
}
}
if( rc==SQLITE_OK ){
const char *zUri = (flags & SQLITE_OPEN_URI) ? zName : 0;
memset(pGroup, 0, sz);
pMultiplexOpen->pGroup = pGroup;
pGroup->bEnabled = (unsigned char)-1;
pGroup->bTruncate = (unsigned char)sqlite3_uri_boolean(zUri, "truncate",
(flags & SQLITE_OPEN_MAIN_DB)==0);
pGroup->szChunk = (int)sqlite3_uri_int64(zUri, "chunksize",
SQLITE_MULTIPLEX_CHUNK_SIZE);
pGroup->szChunk = (pGroup->szChunk+0xffff)&~0xffff;
if( zName ){
char *p = (char *)&pGroup[1];
pGroup->zName = p;
memcpy(pGroup->zName, zName, nName+1);
pGroup->nName = nName;
}
if( pGroup->bEnabled ){
#ifndef SQLITE_OMIT_WSD
extern int sqlite3PendingByte;
#else
int sqlite3PendingByte = 0x40000000;
#endif
while( (sqlite3PendingByte % pGroup->szChunk)>=(pGroup->szChunk-65536) ){
pGroup->szChunk += 65536;
}
}
pGroup->flags = (flags & ~SQLITE_OPEN_URI);
rc = multiplexSubFilename(pGroup, 1);
if( rc==SQLITE_OK ){
pSubOpen = multiplexSubOpen(pGroup, 0, &rc, pOutFlags, 0);
if( pSubOpen==0 && rc==SQLITE_OK ) rc = SQLITE_CANTOPEN;
}
if( rc==SQLITE_OK ){
sqlite3_int64 sz64;
rc = pSubOpen->pMethods->xFileSize(pSubOpen, &sz64);
if( rc==SQLITE_OK && zName ){
int bExists;
if( flags & SQLITE_OPEN_SUPER_JOURNAL ){
pGroup->bEnabled = 0;
}else
if( sz64==0 ){
if( flags & SQLITE_OPEN_MAIN_JOURNAL ){
int iChunk = 1;
do {
rc = pOrigVfs->xAccess(pOrigVfs,
pGroup->aReal[iChunk].z, SQLITE_ACCESS_EXISTS, &bExists
);
if( rc==SQLITE_OK && bExists ){
rc = pOrigVfs->xDelete(pOrigVfs, pGroup->aReal[iChunk].z, 0);
if( rc==SQLITE_OK ){
rc = multiplexSubFilename(pGroup, ++iChunk);
}
}
}while( rc==SQLITE_OK && bExists );
}
}else{
rc = pOrigVfs->xAccess(pOrigVfs, pGroup->aReal[1].z,
SQLITE_ACCESS_EXISTS, &bExists);
bExists = multiplexSubSize(pGroup, 1, &rc)>0;
if( rc==SQLITE_OK && bExists && sz64==(sz64&0xffff0000) && sz64>0
&& sz64!=pGroup->szChunk ){
pGroup->szChunk = (int)sz64;
}else if( rc==SQLITE_OK && !bExists && sz64>pGroup->szChunk ){
pGroup->bEnabled = 0;
}
}
}
}
if( rc==SQLITE_OK ){
if( pSubOpen->pMethods->iVersion==1 ){
pConn->pMethods = &gMultiplex.sIoMethodsV1;
}else{
pConn->pMethods = &gMultiplex.sIoMethodsV2;
}
}else{
multiplexFreeComponents(pGroup);
sqlite3_free(pGroup);
}
}
sqlite3_free(zToFree);
return rc;
}
static int multiplexDelete(
sqlite3_vfs *pVfs,
const char *zName,
int syncDir
){
int rc;
sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;
rc = pOrigVfs->xDelete(pOrigVfs, zName, syncDir);
if( rc==SQLITE_OK ){
int nName = (int)strlen(zName);
char *z;
z = sqlite3_malloc64(nName + 5);
if( z==0 ){
rc = SQLITE_IOERR_NOMEM;
}else{
int iChunk = 0;
int bExists;
do{
multiplexFilename(zName, nName, SQLITE_OPEN_MAIN_JOURNAL, ++iChunk, z);
rc = pOrigVfs->xAccess(pOrigVfs, z, SQLITE_ACCESS_EXISTS, &bExists);
}while( rc==SQLITE_OK && bExists );
while( rc==SQLITE_OK && iChunk>1 ){
multiplexFilename(zName, nName, SQLITE_OPEN_MAIN_JOURNAL, --iChunk, z);
rc = pOrigVfs->xDelete(pOrigVfs, z, syncDir);
}
if( rc==SQLITE_OK ){
iChunk = 0;
do{
multiplexFilename(zName, nName, SQLITE_OPEN_WAL, ++iChunk, z);
rc = pOrigVfs->xAccess(pOrigVfs, z, SQLITE_ACCESS_EXISTS, &bExists);
}while( rc==SQLITE_OK && bExists );
while( rc==SQLITE_OK && iChunk>1 ){
multiplexFilename(zName, nName, SQLITE_OPEN_WAL, --iChunk, z);
rc = pOrigVfs->xDelete(pOrigVfs, z, syncDir);
}
}
}
sqlite3_free(z);
}
return rc;
}
static int multiplexAccess(sqlite3_vfs *a, const char *b, int c, int *d){
return gMultiplex.pOrigVfs->xAccess(gMultiplex.pOrigVfs, b, c, d);
}
static int multiplexFullPathname(sqlite3_vfs *a, const char *b, int c, char *d){
return gMultiplex.pOrigVfs->xFullPathname(gMultiplex.pOrigVfs, b, c, d);
}
static void *multiplexDlOpen(sqlite3_vfs *a, const char *b){
return gMultiplex.pOrigVfs->xDlOpen(gMultiplex.pOrigVfs, b);
}
static void multiplexDlError(sqlite3_vfs *a, int b, char *c){
gMultiplex.pOrigVfs->xDlError(gMultiplex.pOrigVfs, b, c);
}
static void (*multiplexDlSym(sqlite3_vfs *a, void *b, const char *c))(void){
return gMultiplex.pOrigVfs->xDlSym(gMultiplex.pOrigVfs, b, c);
}
static void multiplexDlClose(sqlite3_vfs *a, void *b){
gMultiplex.pOrigVfs->xDlClose(gMultiplex.pOrigVfs, b);
}
static int multiplexRandomness(sqlite3_vfs *a, int b, char *c){
return gMultiplex.pOrigVfs->xRandomness(gMultiplex.pOrigVfs, b, c);
}
static int multiplexSleep(sqlite3_vfs *a, int b){
return gMultiplex.pOrigVfs->xSleep(gMultiplex.pOrigVfs, b);
}
static int multiplexCurrentTime(sqlite3_vfs *a, double *b){
return gMultiplex.pOrigVfs->xCurrentTime(gMultiplex.pOrigVfs, b);
}
static int multiplexGetLastError(sqlite3_vfs *a, int b, char *c){
if( gMultiplex.pOrigVfs->xGetLastError ){
return gMultiplex.pOrigVfs->xGetLastError(gMultiplex.pOrigVfs, b, c);
}else{
return 0;
}
}
static int multiplexCurrentTimeInt64(sqlite3_vfs *a, sqlite3_int64 *b){
return gMultiplex.pOrigVfs->xCurrentTimeInt64(gMultiplex.pOrigVfs, b);
}
static int multiplexClose(sqlite3_file *pConn){
multiplexConn *p = (multiplexConn*)pConn;
multiplexGroup *pGroup = p->pGroup;
int rc = SQLITE_OK;
multiplexFreeComponents(pGroup);
sqlite3_free(pGroup);
return rc;
}
static int multiplexRead(
sqlite3_file *pConn,
void *pBuf,
int iAmt,
sqlite3_int64 iOfst
){
multiplexConn *p = (multiplexConn*)pConn;
multiplexGroup *pGroup = p->pGroup;
int rc = SQLITE_OK;
if( !pGroup->bEnabled ){
sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);
if( pSubOpen==0 ){
rc = SQLITE_IOERR_READ;
}else{
rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt, iOfst);
}
}else{
while( iAmt > 0 ){
int i = (int)(iOfst / pGroup->szChunk);
sqlite3_file *pSubOpen;
pSubOpen = multiplexSubOpen(pGroup, i, &rc, NULL, 1);
if( pSubOpen ){
int extra = ((int)(iOfst % pGroup->szChunk) + iAmt) - pGroup->szChunk;
if( extra<0 ) extra = 0;
iAmt -= extra;
rc = pSubOpen->pMethods->xRead(pSubOpen, pBuf, iAmt,
iOfst % pGroup->szChunk);
if( rc!=SQLITE_OK ) break;
pBuf = (char *)pBuf + iAmt;
iOfst += iAmt;
iAmt = extra;
}else{
rc = SQLITE_IOERR_READ;
break;
}
}
}
return rc;
}
static int multiplexWrite(
sqlite3_file *pConn,
const void *pBuf,
int iAmt,
sqlite3_int64 iOfst
){
multiplexConn *p = (multiplexConn*)pConn;
multiplexGroup *pGroup = p->pGroup;
int rc = SQLITE_OK;
if( !pGroup->bEnabled ){
sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);
if( pSubOpen==0 ){
rc = SQLITE_IOERR_WRITE;
}else{
rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt, iOfst);
}
}else{
while( rc==SQLITE_OK && iAmt>0 ){
int i = (int)(iOfst / pGroup->szChunk);
sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, i, &rc, NULL, 1);
if( pSubOpen ){
int extra = ((int)(iOfst % pGroup->szChunk) + iAmt) -
pGroup->szChunk;
if( extra<0 ) extra = 0;
iAmt -= extra;
rc = pSubOpen->pMethods->xWrite(pSubOpen, pBuf, iAmt,
iOfst % pGroup->szChunk);
pBuf = (char *)pBuf + iAmt;
iOfst += iAmt;
iAmt = extra;
}
}
}
return rc;
}
static int multiplexTruncate(sqlite3_file *pConn, sqlite3_int64 size){
multiplexConn *p = (multiplexConn*)pConn;
multiplexGroup *pGroup = p->pGroup;
int rc = SQLITE_OK;
if( !pGroup->bEnabled ){
sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);
if( pSubOpen==0 ){
rc = SQLITE_IOERR_TRUNCATE;
}else{
rc = pSubOpen->pMethods->xTruncate(pSubOpen, size);
}
}else{
int i;
int iBaseGroup = (int)(size / pGroup->szChunk);
sqlite3_file *pSubOpen;
sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;
for(i = pGroup->nReal-1; i>iBaseGroup && rc==SQLITE_OK; i--){
if( pGroup->bTruncate ){
multiplexSubClose(pGroup, i, pOrigVfs);
}else{
pSubOpen = multiplexSubOpen(pGroup, i, &rc, 0, 0);
if( pSubOpen ){
rc = pSubOpen->pMethods->xTruncate(pSubOpen, 0);
}
}
}
if( rc==SQLITE_OK ){
pSubOpen = multiplexSubOpen(pGroup, iBaseGroup, &rc, 0, 0);
if( pSubOpen ){
rc = pSubOpen->pMethods->xTruncate(pSubOpen, size % pGroup->szChunk);
}
}
if( rc ) rc = SQLITE_IOERR_TRUNCATE;
}
return rc;
}
static int multiplexSync(sqlite3_file *pConn, int flags){
multiplexConn *p = (multiplexConn*)pConn;
multiplexGroup *pGroup = p->pGroup;
int rc = SQLITE_OK;
int i;
for(i=0; i<pGroup->nReal; i++){
sqlite3_file *pSubOpen = pGroup->aReal[i].p;
if( pSubOpen ){
int rc2 = pSubOpen->pMethods->xSync(pSubOpen, flags);
if( rc2!=SQLITE_OK ) rc = rc2;
}
}
return rc;
}
static int multiplexFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){
multiplexConn *p = (multiplexConn*)pConn;
multiplexGroup *pGroup = p->pGroup;
int rc = SQLITE_OK;
int i;
if( !pGroup->bEnabled ){
sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);
if( pSubOpen==0 ){
rc = SQLITE_IOERR_FSTAT;
}else{
rc = pSubOpen->pMethods->xFileSize(pSubOpen, pSize);
}
}else{
*pSize = 0;
for(i=0; rc==SQLITE_OK; i++){
sqlite3_int64 sz = multiplexSubSize(pGroup, i, &rc);
if( sz==0 ) break;
*pSize = i*(sqlite3_int64)pGroup->szChunk + sz;
}
}
return rc;
}
static int multiplexLock(sqlite3_file *pConn, int lock){
multiplexConn *p = (multiplexConn*)pConn;
int rc;
sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
if( pSubOpen ){
return pSubOpen->pMethods->xLock(pSubOpen, lock);
}
return SQLITE_BUSY;
}
static int multiplexUnlock(sqlite3_file *pConn, int lock){
multiplexConn *p = (multiplexConn*)pConn;
int rc;
sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
if( pSubOpen ){
return pSubOpen->pMethods->xUnlock(pSubOpen, lock);
}
return SQLITE_IOERR_UNLOCK;
}
static int multiplexCheckReservedLock(sqlite3_file *pConn, int *pResOut){
multiplexConn *p = (multiplexConn*)pConn;
int rc;
sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
if( pSubOpen ){
return pSubOpen->pMethods->xCheckReservedLock(pSubOpen, pResOut);
}
return SQLITE_IOERR_CHECKRESERVEDLOCK;
}
static int multiplexFileControl(sqlite3_file *pConn, int op, void *pArg){
multiplexConn *p = (multiplexConn*)pConn;
multiplexGroup *pGroup = p->pGroup;
int rc = SQLITE_ERROR;
sqlite3_file *pSubOpen;
if( !gMultiplex.isInitialized ) return SQLITE_MISUSE;
switch( op ){
case MULTIPLEX_CTRL_ENABLE:
if( pArg ) {
int bEnabled = *(int *)pArg;
pGroup->bEnabled = (unsigned char)bEnabled;
rc = SQLITE_OK;
}
break;
case MULTIPLEX_CTRL_SET_CHUNK_SIZE:
if( pArg ) {
unsigned int szChunk = *(unsigned*)pArg;
if( szChunk<1 ){
rc = SQLITE_MISUSE;
}else{
szChunk = (szChunk + (MAX_PAGE_SIZE-1));
szChunk &= ~(MAX_PAGE_SIZE-1);
pGroup->szChunk = szChunk;
rc = SQLITE_OK;
}
}
break;
case MULTIPLEX_CTRL_SET_MAX_CHUNKS:
rc = SQLITE_OK;
break;
case SQLITE_FCNTL_SIZE_HINT:
case SQLITE_FCNTL_CHUNK_SIZE:
rc = SQLITE_OK;
break;
case SQLITE_FCNTL_PRAGMA: {
char **aFcntl = (char**)pArg;
if( aFcntl[1] && sqlite3_strnicmp(aFcntl[1],"multiplex_",10)==0 ){
sqlite3_int64 sz = 0;
(void)multiplexFileSize(pConn, &sz);
if( sqlite3_stricmp(aFcntl[1],"multiplex_truncate")==0 ){
if( aFcntl[2] && aFcntl[2][0] ){
if( sqlite3_stricmp(aFcntl[2], "on")==0
|| sqlite3_stricmp(aFcntl[2], "1")==0 ){
pGroup->bTruncate = 1;
}else
if( sqlite3_stricmp(aFcntl[2], "off")==0
|| sqlite3_stricmp(aFcntl[2], "0")==0 ){
pGroup->bTruncate = 0;
}
}
aFcntl[0] = sqlite3_mprintf(pGroup->bTruncate ? "on" : "off");
rc = SQLITE_OK;
break;
}
if( sqlite3_stricmp(aFcntl[1],"multiplex_enabled")==0 ){
aFcntl[0] = sqlite3_mprintf("%d", pGroup->bEnabled!=0);
rc = SQLITE_OK;
break;
}
if( sqlite3_stricmp(aFcntl[1],"multiplex_chunksize")==0
&& pGroup->bEnabled
){
aFcntl[0] = sqlite3_mprintf("%u", pGroup->szChunk);
rc = SQLITE_OK;
break;
}
if( sqlite3_stricmp(aFcntl[1],"multiplex_filecount")==0 ){
int n = 0;
int ii;
for(ii=0; ii<pGroup->nReal; ii++){
if( pGroup->aReal[ii].p!=0 ) n++;
}
aFcntl[0] = sqlite3_mprintf("%d", n);
rc = SQLITE_OK;
break;
}
}
}
default:
pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL, 0);
if( pSubOpen ){
rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg);
if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){
*(char**)pArg = sqlite3_mprintf("multiplex/%z", *(char**)pArg);
}
}
break;
}
return rc;
}
static int multiplexSectorSize(sqlite3_file *pConn){
multiplexConn *p = (multiplexConn*)pConn;
int rc;
sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
if( pSubOpen && pSubOpen->pMethods->xSectorSize ){
return pSubOpen->pMethods->xSectorSize(pSubOpen);
}
return DEFAULT_SECTOR_SIZE;
}
static int multiplexDeviceCharacteristics(sqlite3_file *pConn){
multiplexConn *p = (multiplexConn*)pConn;
int rc;
sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
if( pSubOpen ){
return pSubOpen->pMethods->xDeviceCharacteristics(pSubOpen);
}
return 0;
}
static int multiplexShmMap(
sqlite3_file *pConn,
int iRegion,
int szRegion,
int bExtend,
void volatile **pp
){
multiplexConn *p = (multiplexConn*)pConn;
int rc;
sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
if( pSubOpen ){
return pSubOpen->pMethods->xShmMap(pSubOpen, iRegion, szRegion, bExtend,pp);
}
return SQLITE_IOERR;
}
static int multiplexShmLock(
sqlite3_file *pConn,
int ofst,
int n,
int flags
){
multiplexConn *p = (multiplexConn*)pConn;
int rc;
sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
if( pSubOpen ){
return pSubOpen->pMethods->xShmLock(pSubOpen, ofst, n, flags);
}
return SQLITE_BUSY;
}
static void multiplexShmBarrier(sqlite3_file *pConn){
multiplexConn *p = (multiplexConn*)pConn;
int rc;
sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
if( pSubOpen ){
pSubOpen->pMethods->xShmBarrier(pSubOpen);
}
}
static int multiplexShmUnmap(sqlite3_file *pConn, int deleteFlag){
multiplexConn *p = (multiplexConn*)pConn;
int rc;
sqlite3_file *pSubOpen = multiplexSubOpen(p->pGroup, 0, &rc, NULL, 0);
if( pSubOpen ){
return pSubOpen->pMethods->xShmUnmap(pSubOpen, deleteFlag);
}
return SQLITE_OK;
}
int sqlite3_multiplex_initialize(const char *zOrigVfsName, int makeDefault){
sqlite3_vfs *pOrigVfs;
if( gMultiplex.isInitialized ) return SQLITE_MISUSE;
pOrigVfs = sqlite3_vfs_find(zOrigVfsName);
if( pOrigVfs==0 ) return SQLITE_ERROR;
assert( pOrigVfs!=&gMultiplex.sThisVfs );
gMultiplex.isInitialized = 1;
gMultiplex.pOrigVfs = pOrigVfs;
gMultiplex.sThisVfs = *pOrigVfs;
gMultiplex.sThisVfs.szOsFile += sizeof(multiplexConn);
gMultiplex.sThisVfs.zName = SQLITE_MULTIPLEX_VFS_NAME;
gMultiplex.sThisVfs.xOpen = multiplexOpen;
gMultiplex.sThisVfs.xDelete = multiplexDelete;
gMultiplex.sThisVfs.xAccess = multiplexAccess;
gMultiplex.sThisVfs.xFullPathname = multiplexFullPathname;
gMultiplex.sThisVfs.xDlOpen = multiplexDlOpen;
gMultiplex.sThisVfs.xDlError = multiplexDlError;
gMultiplex.sThisVfs.xDlSym = multiplexDlSym;
gMultiplex.sThisVfs.xDlClose = multiplexDlClose;
gMultiplex.sThisVfs.xRandomness = multiplexRandomness;
gMultiplex.sThisVfs.xSleep = multiplexSleep;
gMultiplex.sThisVfs.xCurrentTime = multiplexCurrentTime;
gMultiplex.sThisVfs.xGetLastError = multiplexGetLastError;
gMultiplex.sThisVfs.xCurrentTimeInt64 = multiplexCurrentTimeInt64;
gMultiplex.sIoMethodsV1.iVersion = 1;
gMultiplex.sIoMethodsV1.xClose = multiplexClose;
gMultiplex.sIoMethodsV1.xRead = multiplexRead;
gMultiplex.sIoMethodsV1.xWrite = multiplexWrite;
gMultiplex.sIoMethodsV1.xTruncate = multiplexTruncate;
gMultiplex.sIoMethodsV1.xSync = multiplexSync;
gMultiplex.sIoMethodsV1.xFileSize = multiplexFileSize;
gMultiplex.sIoMethodsV1.xLock = multiplexLock;
gMultiplex.sIoMethodsV1.xUnlock = multiplexUnlock;
gMultiplex.sIoMethodsV1.xCheckReservedLock = multiplexCheckReservedLock;
gMultiplex.sIoMethodsV1.xFileControl = multiplexFileControl;
gMultiplex.sIoMethodsV1.xSectorSize = multiplexSectorSize;
gMultiplex.sIoMethodsV1.xDeviceCharacteristics =
multiplexDeviceCharacteristics;
gMultiplex.sIoMethodsV2 = gMultiplex.sIoMethodsV1;
gMultiplex.sIoMethodsV2.iVersion = 2;
gMultiplex.sIoMethodsV2.xShmMap = multiplexShmMap;
gMultiplex.sIoMethodsV2.xShmLock = multiplexShmLock;
gMultiplex.sIoMethodsV2.xShmBarrier = multiplexShmBarrier;
gMultiplex.sIoMethodsV2.xShmUnmap = multiplexShmUnmap;
sqlite3_vfs_register(&gMultiplex.sThisVfs, makeDefault);
sqlite3_auto_extension((void(*)(void))multiplexFuncInit);
return SQLITE_OK;
}
int sqlite3_multiplex_shutdown(int eForce){
int rc = SQLITE_OK;
if( gMultiplex.isInitialized==0 ) return SQLITE_MISUSE;
gMultiplex.isInitialized = 0;
sqlite3_vfs_unregister(&gMultiplex.sThisVfs);
memset(&gMultiplex, 0, sizeof(gMultiplex));
return rc;
}
#ifdef SQLITE_TEST
#if defined(INCLUDE_SQLITE_TCL_H)
# include "sqlite_tcl.h"
#else
# include "tcl.h"
# ifndef SQLITE_TCLAPI
# define SQLITE_TCLAPI
# endif
#endif
extern const char *sqlite3ErrName(int);
static int SQLITE_TCLAPI test_multiplex_initialize(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
const char *zName;
int makeDefault;
int rc;
UNUSED_PARAMETER(clientData);
if( objc!=3 ){
Tcl_WrongNumArgs(interp, 1, objv, "NAME MAKEDEFAULT");
return TCL_ERROR;
}
zName = Tcl_GetString(objv[1]);
if( Tcl_GetBooleanFromObj(interp, objv[2], &makeDefault) ) return TCL_ERROR;
if( zName[0]=='\0' ) zName = 0;
rc = sqlite3_multiplex_initialize(zName, makeDefault);
Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
return TCL_OK;
}
static int SQLITE_TCLAPI test_multiplex_shutdown(
void * clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
int rc;
UNUSED_PARAMETER(clientData);
if( objc==2 && strcmp(Tcl_GetString(objv[1]),"-force")!=0 ){
objc = 3;
}
if( (objc!=1 && objc!=2) ){
Tcl_WrongNumArgs(interp, 1, objv, "?-force?");
return TCL_ERROR;
}
rc = sqlite3_multiplex_shutdown(objc==2);
Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
return TCL_OK;
}
static int SQLITE_TCLAPI test_multiplex_control(
ClientData cd,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
int rc;
int idx;
Tcl_CmdInfo cmdInfo;
sqlite3 *db;
int iValue = 0;
void *pArg = 0;
struct SubCommand {
const char *zName;
int op;
int argtype;
} aSub[] = {
{ "enable", MULTIPLEX_CTRL_ENABLE, 1 },
{ "chunk_size", MULTIPLEX_CTRL_SET_CHUNK_SIZE, 1 },
{ "max_chunks", MULTIPLEX_CTRL_SET_MAX_CHUNKS, 1 },
{ 0, 0, 0 }
};
if( objc!=5 ){
Tcl_WrongNumArgs(interp, 1, objv, "HANDLE DBNAME SUB-COMMAND INT-VALUE");
return TCL_ERROR;
}
if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &cmdInfo) ){
Tcl_AppendResult(interp, "expected database handle, got \"", 0);
Tcl_AppendResult(interp, Tcl_GetString(objv[1]), "\"", 0);
return TCL_ERROR;
}else{
db = *(sqlite3 **)cmdInfo.objClientData;
}
rc = Tcl_GetIndexFromObjStruct(
interp, objv[3], aSub, sizeof(aSub[0]), "sub-command", 0, &idx
);
if( rc!=TCL_OK ) return rc;
switch( aSub[idx].argtype ){
case 1:
if( Tcl_GetIntFromObj(interp, objv[4], &iValue) ){
return TCL_ERROR;
}
pArg = (void *)&iValue;
break;
default:
Tcl_WrongNumArgs(interp, 4, objv, "SUB-COMMAND");
return TCL_ERROR;
}
rc = sqlite3_file_control(db, Tcl_GetString(objv[2]), aSub[idx].op, pArg);
Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_STATIC);
return (rc==SQLITE_OK) ? TCL_OK : TCL_ERROR;
}
int Sqlitemultiplex_Init(Tcl_Interp *interp){
static struct {
char *zName;
Tcl_ObjCmdProc *xProc;
} aCmd[] = {
{ "sqlite3_multiplex_initialize", test_multiplex_initialize },
{ "sqlite3_multiplex_shutdown", test_multiplex_shutdown },
{ "sqlite3_multiplex_control", test_multiplex_control },
};
int i;
for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
Tcl_CreateObjCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
}
return TCL_OK;
}
#endif