mirror of
https://github.com/sqlite/sqlite.git
synced 2024-12-01 01:01:28 +01:00
a959bf5311
necessary. There are no vulnerabilities here. However, adding these checks avoids unnecessary static analyzer complaints. [forum:/forumpost/ce1193be15|Forum post ce1193be15]. FossilOrigin-Name: 272a15b9f418fb0b31a9808f7c42c20cf52318035ff98935d8e8519634357e8d
576 lines
16 KiB
C
576 lines
16 KiB
C
/*
|
|
** 2016-09-07
|
|
**
|
|
** The author disclaims copyright to this source code. In place of
|
|
** a legal notice, here is a blessing:
|
|
**
|
|
** May you do good and not evil.
|
|
** May you find forgiveness for yourself and forgive others.
|
|
** May you share freely, never taking more than you give.
|
|
**
|
|
******************************************************************************
|
|
**
|
|
** This is an in-memory VFS implementation. The application supplies
|
|
** a chunk of memory to hold the database file.
|
|
**
|
|
** Because there is place to store a rollback or wal journal, the database
|
|
** must use one of journal_mode=MEMORY or journal_mode=NONE.
|
|
**
|
|
** USAGE:
|
|
**
|
|
** sqlite3_open_v2("file:/whatever?ptr=0xf05538&sz=14336&max=65536", &db,
|
|
** SQLITE_OPEN_READWRITE | SQLITE_OPEN_URI,
|
|
** "memvfs");
|
|
**
|
|
** These are the query parameters:
|
|
**
|
|
** ptr= The address of the memory buffer that holds the database.
|
|
**
|
|
** sz= The current size the database file
|
|
**
|
|
** maxsz= The maximum size of the database. In other words, the
|
|
** amount of space allocated for the ptr= buffer.
|
|
**
|
|
** freeonclose= If true, then sqlite3_free() is called on the ptr=
|
|
** value when the connection closes.
|
|
**
|
|
** The ptr= and sz= query parameters are required. If maxsz= is omitted,
|
|
** then it defaults to the sz= value. Parameter values can be in either
|
|
** decimal or hexadecimal. The filename in the URI is ignored.
|
|
*/
|
|
#include <sqlite3ext.h>
|
|
SQLITE_EXTENSION_INIT1
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
|
|
/*
|
|
** Forward declaration of objects used by this utility
|
|
*/
|
|
typedef struct sqlite3_vfs MemVfs;
|
|
typedef struct MemFile MemFile;
|
|
|
|
/* Access to a lower-level VFS that (might) implement dynamic loading,
|
|
** access to randomness, etc.
|
|
*/
|
|
#define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData))
|
|
|
|
/* An open file */
|
|
struct MemFile {
|
|
sqlite3_file base; /* IO methods */
|
|
sqlite3_int64 sz; /* Size of the file */
|
|
sqlite3_int64 szMax; /* Space allocated to aData */
|
|
unsigned char *aData; /* content of the file */
|
|
int bFreeOnClose; /* Invoke sqlite3_free() on aData at close */
|
|
};
|
|
|
|
/*
|
|
** Methods for MemFile
|
|
*/
|
|
static int memClose(sqlite3_file*);
|
|
static int memRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
|
|
static int memWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst);
|
|
static int memTruncate(sqlite3_file*, sqlite3_int64 size);
|
|
static int memSync(sqlite3_file*, int flags);
|
|
static int memFileSize(sqlite3_file*, sqlite3_int64 *pSize);
|
|
static int memLock(sqlite3_file*, int);
|
|
static int memUnlock(sqlite3_file*, int);
|
|
static int memCheckReservedLock(sqlite3_file*, int *pResOut);
|
|
static int memFileControl(sqlite3_file*, int op, void *pArg);
|
|
static int memSectorSize(sqlite3_file*);
|
|
static int memDeviceCharacteristics(sqlite3_file*);
|
|
static int memShmMap(sqlite3_file*, int iPg, int pgsz, int, void volatile**);
|
|
static int memShmLock(sqlite3_file*, int offset, int n, int flags);
|
|
static void memShmBarrier(sqlite3_file*);
|
|
static int memShmUnmap(sqlite3_file*, int deleteFlag);
|
|
static int memFetch(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp);
|
|
static int memUnfetch(sqlite3_file*, sqlite3_int64 iOfst, void *p);
|
|
|
|
/*
|
|
** Methods for MemVfs
|
|
*/
|
|
static int memOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
|
|
static int memDelete(sqlite3_vfs*, const char *zName, int syncDir);
|
|
static int memAccess(sqlite3_vfs*, const char *zName, int flags, int *);
|
|
static int memFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut);
|
|
static void *memDlOpen(sqlite3_vfs*, const char *zFilename);
|
|
static void memDlError(sqlite3_vfs*, int nByte, char *zErrMsg);
|
|
static void (*memDlSym(sqlite3_vfs *pVfs, void *p, const char*zSym))(void);
|
|
static void memDlClose(sqlite3_vfs*, void*);
|
|
static int memRandomness(sqlite3_vfs*, int nByte, char *zOut);
|
|
static int memSleep(sqlite3_vfs*, int microseconds);
|
|
static int memCurrentTime(sqlite3_vfs*, double*);
|
|
static int memGetLastError(sqlite3_vfs*, int, char *);
|
|
static int memCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*);
|
|
|
|
static sqlite3_vfs mem_vfs = {
|
|
2, /* iVersion */
|
|
0, /* szOsFile (set when registered) */
|
|
1024, /* mxPathname */
|
|
0, /* pNext */
|
|
"memvfs", /* zName */
|
|
0, /* pAppData (set when registered) */
|
|
memOpen, /* xOpen */
|
|
memDelete, /* xDelete */
|
|
memAccess, /* xAccess */
|
|
memFullPathname, /* xFullPathname */
|
|
memDlOpen, /* xDlOpen */
|
|
memDlError, /* xDlError */
|
|
memDlSym, /* xDlSym */
|
|
memDlClose, /* xDlClose */
|
|
memRandomness, /* xRandomness */
|
|
memSleep, /* xSleep */
|
|
memCurrentTime, /* xCurrentTime */
|
|
memGetLastError, /* xGetLastError */
|
|
memCurrentTimeInt64 /* xCurrentTimeInt64 */
|
|
};
|
|
|
|
static const sqlite3_io_methods mem_io_methods = {
|
|
3, /* iVersion */
|
|
memClose, /* xClose */
|
|
memRead, /* xRead */
|
|
memWrite, /* xWrite */
|
|
memTruncate, /* xTruncate */
|
|
memSync, /* xSync */
|
|
memFileSize, /* xFileSize */
|
|
memLock, /* xLock */
|
|
memUnlock, /* xUnlock */
|
|
memCheckReservedLock, /* xCheckReservedLock */
|
|
memFileControl, /* xFileControl */
|
|
memSectorSize, /* xSectorSize */
|
|
memDeviceCharacteristics, /* xDeviceCharacteristics */
|
|
memShmMap, /* xShmMap */
|
|
memShmLock, /* xShmLock */
|
|
memShmBarrier, /* xShmBarrier */
|
|
memShmUnmap, /* xShmUnmap */
|
|
memFetch, /* xFetch */
|
|
memUnfetch /* xUnfetch */
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
** Close an mem-file.
|
|
**
|
|
** The pData pointer is owned by the application, so there is nothing
|
|
** to free.
|
|
*/
|
|
static int memClose(sqlite3_file *pFile){
|
|
MemFile *p = (MemFile *)pFile;
|
|
if( p->bFreeOnClose ) sqlite3_free(p->aData);
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Read data from an mem-file.
|
|
*/
|
|
static int memRead(
|
|
sqlite3_file *pFile,
|
|
void *zBuf,
|
|
int iAmt,
|
|
sqlite_int64 iOfst
|
|
){
|
|
MemFile *p = (MemFile *)pFile;
|
|
memcpy(zBuf, p->aData+iOfst, iAmt);
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Write data to an mem-file.
|
|
*/
|
|
static int memWrite(
|
|
sqlite3_file *pFile,
|
|
const void *z,
|
|
int iAmt,
|
|
sqlite_int64 iOfst
|
|
){
|
|
MemFile *p = (MemFile *)pFile;
|
|
if( iOfst+iAmt>p->sz ){
|
|
if( iOfst+iAmt>p->szMax ) return SQLITE_FULL;
|
|
if( iOfst>p->sz ) memset(p->aData+p->sz, 0, iOfst-p->sz);
|
|
p->sz = iOfst+iAmt;
|
|
}
|
|
memcpy(p->aData+iOfst, z, iAmt);
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Truncate an mem-file.
|
|
*/
|
|
static int memTruncate(sqlite3_file *pFile, sqlite_int64 size){
|
|
MemFile *p = (MemFile *)pFile;
|
|
if( size>p->sz ){
|
|
if( size>p->szMax ) return SQLITE_FULL;
|
|
memset(p->aData+p->sz, 0, size-p->sz);
|
|
}
|
|
p->sz = size;
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Sync an mem-file.
|
|
*/
|
|
static int memSync(sqlite3_file *pFile, int flags){
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Return the current file-size of an mem-file.
|
|
*/
|
|
static int memFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
|
|
MemFile *p = (MemFile *)pFile;
|
|
*pSize = p->sz;
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Lock an mem-file.
|
|
*/
|
|
static int memLock(sqlite3_file *pFile, int eLock){
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Unlock an mem-file.
|
|
*/
|
|
static int memUnlock(sqlite3_file *pFile, int eLock){
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Check if another file-handle holds a RESERVED lock on an mem-file.
|
|
*/
|
|
static int memCheckReservedLock(sqlite3_file *pFile, int *pResOut){
|
|
*pResOut = 0;
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** File control method. For custom operations on an mem-file.
|
|
*/
|
|
static int memFileControl(sqlite3_file *pFile, int op, void *pArg){
|
|
MemFile *p = (MemFile *)pFile;
|
|
int rc = SQLITE_NOTFOUND;
|
|
if( op==SQLITE_FCNTL_VFSNAME ){
|
|
*(char**)pArg = sqlite3_mprintf("mem(%p,%lld)", p->aData, p->sz);
|
|
rc = SQLITE_OK;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
** Return the sector-size in bytes for an mem-file.
|
|
*/
|
|
static int memSectorSize(sqlite3_file *pFile){
|
|
return 1024;
|
|
}
|
|
|
|
/*
|
|
** Return the device characteristic flags supported by an mem-file.
|
|
*/
|
|
static int memDeviceCharacteristics(sqlite3_file *pFile){
|
|
return SQLITE_IOCAP_ATOMIC |
|
|
SQLITE_IOCAP_POWERSAFE_OVERWRITE |
|
|
SQLITE_IOCAP_SAFE_APPEND |
|
|
SQLITE_IOCAP_SEQUENTIAL;
|
|
}
|
|
|
|
/* Create a shared memory file mapping */
|
|
static int memShmMap(
|
|
sqlite3_file *pFile,
|
|
int iPg,
|
|
int pgsz,
|
|
int bExtend,
|
|
void volatile **pp
|
|
){
|
|
return SQLITE_IOERR_SHMMAP;
|
|
}
|
|
|
|
/* Perform locking on a shared-memory segment */
|
|
static int memShmLock(sqlite3_file *pFile, int offset, int n, int flags){
|
|
return SQLITE_IOERR_SHMLOCK;
|
|
}
|
|
|
|
/* Memory barrier operation on shared memory */
|
|
static void memShmBarrier(sqlite3_file *pFile){
|
|
return;
|
|
}
|
|
|
|
/* Unmap a shared memory segment */
|
|
static int memShmUnmap(sqlite3_file *pFile, int deleteFlag){
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/* Fetch a page of a memory-mapped file */
|
|
static int memFetch(
|
|
sqlite3_file *pFile,
|
|
sqlite3_int64 iOfst,
|
|
int iAmt,
|
|
void **pp
|
|
){
|
|
MemFile *p = (MemFile *)pFile;
|
|
*pp = (void*)(p->aData + iOfst);
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/* Release a memory-mapped page */
|
|
static int memUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Open an mem file handle.
|
|
*/
|
|
static int memOpen(
|
|
sqlite3_vfs *pVfs,
|
|
const char *zName,
|
|
sqlite3_file *pFile,
|
|
int flags,
|
|
int *pOutFlags
|
|
){
|
|
MemFile *p = (MemFile*)pFile;
|
|
memset(p, 0, sizeof(*p));
|
|
if( (flags & SQLITE_OPEN_MAIN_DB)==0 ) return SQLITE_CANTOPEN;
|
|
p->aData = (unsigned char*)sqlite3_uri_int64(zName,"ptr",0);
|
|
if( p->aData==0 ) return SQLITE_CANTOPEN;
|
|
p->sz = sqlite3_uri_int64(zName,"sz",0);
|
|
if( p->sz<0 ) return SQLITE_CANTOPEN;
|
|
p->szMax = sqlite3_uri_int64(zName,"max",p->sz);
|
|
if( p->szMax<p->sz ) return SQLITE_CANTOPEN;
|
|
p->bFreeOnClose = sqlite3_uri_boolean(zName,"freeonclose",0);
|
|
pFile->pMethods = &mem_io_methods;
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Delete the file located at zPath. If the dirSync argument is true,
|
|
** ensure the file-system modifications are synced to disk before
|
|
** returning.
|
|
*/
|
|
static int memDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
|
|
return SQLITE_IOERR_DELETE;
|
|
}
|
|
|
|
/*
|
|
** Test for access permissions. Return true if the requested permission
|
|
** is available, or false otherwise.
|
|
*/
|
|
static int memAccess(
|
|
sqlite3_vfs *pVfs,
|
|
const char *zPath,
|
|
int flags,
|
|
int *pResOut
|
|
){
|
|
*pResOut = 0;
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Populate buffer zOut with the full canonical pathname corresponding
|
|
** to the pathname in zPath. zOut is guaranteed to point to a buffer
|
|
** of at least (INST_MAX_PATHNAME+1) bytes.
|
|
*/
|
|
static int memFullPathname(
|
|
sqlite3_vfs *pVfs,
|
|
const char *zPath,
|
|
int nOut,
|
|
char *zOut
|
|
){
|
|
sqlite3_snprintf(nOut, zOut, "%s", zPath);
|
|
return SQLITE_OK;
|
|
}
|
|
|
|
/*
|
|
** Open the dynamic library located at zPath and return a handle.
|
|
*/
|
|
static void *memDlOpen(sqlite3_vfs *pVfs, const char *zPath){
|
|
return ORIGVFS(pVfs)->xDlOpen(ORIGVFS(pVfs), zPath);
|
|
}
|
|
|
|
/*
|
|
** Populate the buffer zErrMsg (size nByte bytes) with a human readable
|
|
** utf-8 string describing the most recent error encountered associated
|
|
** with dynamic libraries.
|
|
*/
|
|
static void memDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
|
|
ORIGVFS(pVfs)->xDlError(ORIGVFS(pVfs), nByte, zErrMsg);
|
|
}
|
|
|
|
/*
|
|
** Return a pointer to the symbol zSymbol in the dynamic library pHandle.
|
|
*/
|
|
static void (*memDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void){
|
|
return ORIGVFS(pVfs)->xDlSym(ORIGVFS(pVfs), p, zSym);
|
|
}
|
|
|
|
/*
|
|
** Close the dynamic library handle pHandle.
|
|
*/
|
|
static void memDlClose(sqlite3_vfs *pVfs, void *pHandle){
|
|
ORIGVFS(pVfs)->xDlClose(ORIGVFS(pVfs), pHandle);
|
|
}
|
|
|
|
/*
|
|
** Populate the buffer pointed to by zBufOut with nByte bytes of
|
|
** random data.
|
|
*/
|
|
static int memRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
|
|
return ORIGVFS(pVfs)->xRandomness(ORIGVFS(pVfs), nByte, zBufOut);
|
|
}
|
|
|
|
/*
|
|
** Sleep for nMicro microseconds. Return the number of microseconds
|
|
** actually slept.
|
|
*/
|
|
static int memSleep(sqlite3_vfs *pVfs, int nMicro){
|
|
return ORIGVFS(pVfs)->xSleep(ORIGVFS(pVfs), nMicro);
|
|
}
|
|
|
|
/*
|
|
** Return the current time as a Julian Day number in *pTimeOut.
|
|
*/
|
|
static int memCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
|
|
return ORIGVFS(pVfs)->xCurrentTime(ORIGVFS(pVfs), pTimeOut);
|
|
}
|
|
|
|
static int memGetLastError(sqlite3_vfs *pVfs, int a, char *b){
|
|
return ORIGVFS(pVfs)->xGetLastError(ORIGVFS(pVfs), a, b);
|
|
}
|
|
static int memCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){
|
|
return ORIGVFS(pVfs)->xCurrentTimeInt64(ORIGVFS(pVfs), p);
|
|
}
|
|
|
|
#ifdef MEMVFS_TEST
|
|
/*
|
|
** memvfs_from_file(FILENAME, MAXSIZE)
|
|
**
|
|
** This an SQL function used to help in testing the memvfs VFS. The
|
|
** function reads the content of a file into memory and then returns
|
|
** a URI that can be handed to ATTACH to attach the memory buffer as
|
|
** a database. Example:
|
|
**
|
|
** ATTACH memvfs_from_file('test.db',1048576) AS inmem;
|
|
**
|
|
** The optional MAXSIZE argument gives the size of the memory allocation
|
|
** used to hold the database. If omitted, it defaults to the size of the
|
|
** file on disk.
|
|
*/
|
|
#include <stdio.h>
|
|
static void memvfsFromFileFunc(
|
|
sqlite3_context *context,
|
|
int argc,
|
|
sqlite3_value **argv
|
|
){
|
|
unsigned char *p;
|
|
sqlite3_int64 sz;
|
|
sqlite3_int64 szMax;
|
|
FILE *in;
|
|
const char *zFilename = (const char*)sqlite3_value_text(argv[0]);
|
|
char *zUri;
|
|
|
|
if( zFilename==0 ) return;
|
|
in = fopen(zFilename, "rb");
|
|
if( in==0 ) return;
|
|
fseek(in, 0, SEEK_END);
|
|
szMax = sz = ftell(in);
|
|
rewind(in);
|
|
if( argc>=2 ){
|
|
szMax = sqlite3_value_int64(argv[1]);
|
|
if( szMax<sz ) szMax = sz;
|
|
}
|
|
p = sqlite3_malloc64( szMax );
|
|
if( p==0 ){
|
|
fclose(in);
|
|
sqlite3_result_error_nomem(context);
|
|
return;
|
|
}
|
|
fread(p, sz, 1, in);
|
|
fclose(in);
|
|
zUri = sqlite3_mprintf(
|
|
"file:/mem?vfs=memvfs&ptr=%lld&sz=%lld&max=%lld&freeonclose=1",
|
|
(sqlite3_int64)p, sz, szMax);
|
|
sqlite3_result_text(context, zUri, -1, sqlite3_free);
|
|
}
|
|
#endif /* MEMVFS_TEST */
|
|
|
|
#ifdef MEMVFS_TEST
|
|
/*
|
|
** memvfs_to_file(SCHEMA, FILENAME)
|
|
**
|
|
** The schema identified by SCHEMA must be a memvfs database. Write
|
|
** the content of this database into FILENAME.
|
|
*/
|
|
static void memvfsToFileFunc(
|
|
sqlite3_context *context,
|
|
int argc,
|
|
sqlite3_value **argv
|
|
){
|
|
MemFile *p = 0;
|
|
FILE *out;
|
|
int rc;
|
|
sqlite3 *db = sqlite3_context_db_handle(context);
|
|
sqlite3_vfs *pVfs = 0;
|
|
const char *zSchema = (const char*)sqlite3_value_text(argv[0]);
|
|
const char *zFilename = (const char*)sqlite3_value_text(argv[1]);
|
|
|
|
if( zFilename==0 ) return;
|
|
out = fopen(zFilename, "wb");
|
|
if( out==0 ) return;
|
|
rc = sqlite3_file_control(db, zSchema, SQLITE_FCNTL_VFS_POINTER, &pVfs);
|
|
if( rc || pVfs==0 ) return;
|
|
if( strcmp(pVfs->zName,"memvfs")!=0 ) return;
|
|
rc = sqlite3_file_control(db, zSchema, SQLITE_FCNTL_FILE_POINTER, &p);
|
|
if( rc ) return;
|
|
fwrite(p->aData, 1, (size_t)p->sz, out);
|
|
fclose(out);
|
|
}
|
|
#endif /* MEMVFS_TEST */
|
|
|
|
#ifdef MEMVFS_TEST
|
|
/* Called for each new database connection */
|
|
static int memvfsRegister(
|
|
sqlite3 *db,
|
|
char **pzErrMsg,
|
|
const struct sqlite3_api_routines *pThunk
|
|
){
|
|
sqlite3_create_function(db, "memvfs_from_file", 1, SQLITE_UTF8, 0,
|
|
memvfsFromFileFunc, 0, 0);
|
|
sqlite3_create_function(db, "memvfs_from_file", 2, SQLITE_UTF8, 0,
|
|
memvfsFromFileFunc, 0, 0);
|
|
sqlite3_create_function(db, "memvfs_to_file", 2, SQLITE_UTF8, 0,
|
|
memvfsToFileFunc, 0, 0);
|
|
return SQLITE_OK;
|
|
}
|
|
#endif /* MEMVFS_TEST */
|
|
|
|
|
|
#ifdef _WIN32
|
|
__declspec(dllexport)
|
|
#endif
|
|
/*
|
|
** This routine is called when the extension is loaded.
|
|
** Register the new VFS.
|
|
*/
|
|
int sqlite3_memvfs_init(
|
|
sqlite3 *db,
|
|
char **pzErrMsg,
|
|
const sqlite3_api_routines *pApi
|
|
){
|
|
int rc = SQLITE_OK;
|
|
SQLITE_EXTENSION_INIT2(pApi);
|
|
mem_vfs.pAppData = sqlite3_vfs_find(0);
|
|
if( mem_vfs.pAppData==0 ) return SQLITE_ERROR;
|
|
mem_vfs.szOsFile = sizeof(MemFile);
|
|
rc = sqlite3_vfs_register(&mem_vfs, 1);
|
|
#ifdef MEMVFS_TEST
|
|
if( rc==SQLITE_OK ){
|
|
rc = sqlite3_auto_extension((void(*)(void))memvfsRegister);
|
|
}
|
|
if( rc==SQLITE_OK ){
|
|
rc = memvfsRegister(db, pzErrMsg, pApi);
|
|
}
|
|
#endif
|
|
if( rc==SQLITE_OK ) rc = SQLITE_OK_LOAD_PERMANENTLY;
|
|
return rc;
|
|
}
|