#include "sqlite3.h"
#include <assert.h>
#include <string.h>
#define BLOCKSIZE 512
#define BLOBSIZE 10485760
#define FS_VFS_NAME "fs"
typedef struct fs_real_file fs_real_file;
struct fs_real_file {
sqlite3_file *pFile;
const char *zName;
int nDatabase;
int nJournal;
int nBlob;
int nRef;
fs_real_file *pNext;
fs_real_file **ppThis;
};
typedef struct fs_file fs_file;
struct fs_file {
sqlite3_file base;
int eType;
fs_real_file *pReal;
};
typedef struct tmp_file tmp_file;
struct tmp_file {
sqlite3_file base;
int nSize;
int nAlloc;
char *zAlloc;
};
#define DATABASE_FILE 1
#define JOURNAL_FILE 2
static int fsClose(sqlite3_file*);
static int fsRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
static int fsWrite(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst);
static int fsTruncate(sqlite3_file*, sqlite3_int64 size);
static int fsSync(sqlite3_file*, int flags);
static int fsFileSize(sqlite3_file*, sqlite3_int64 *pSize);
static int fsLock(sqlite3_file*, int);
static int fsUnlock(sqlite3_file*, int);
static int fsCheckReservedLock(sqlite3_file*, int *pResOut);
static int fsFileControl(sqlite3_file*, int op, void *pArg);
static int fsSectorSize(sqlite3_file*);
static int fsDeviceCharacteristics(sqlite3_file*);
static int tmpClose(sqlite3_file*);
static int tmpRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
static int tmpWrite(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst);
static int tmpTruncate(sqlite3_file*, sqlite3_int64 size);
static int tmpSync(sqlite3_file*, int flags);
static int tmpFileSize(sqlite3_file*, sqlite3_int64 *pSize);
static int tmpLock(sqlite3_file*, int);
static int tmpUnlock(sqlite3_file*, int);
static int tmpCheckReservedLock(sqlite3_file*, int *pResOut);
static int tmpFileControl(sqlite3_file*, int op, void *pArg);
static int tmpSectorSize(sqlite3_file*);
static int tmpDeviceCharacteristics(sqlite3_file*);
static int fsOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
static int fsDelete(sqlite3_vfs*, const char *zName, int syncDir);
static int fsAccess(sqlite3_vfs*, const char *zName, int flags, int *);
static int fsFullPathname(sqlite3_vfs*, const char *zName, int nOut,char *zOut);
static void *fsDlOpen(sqlite3_vfs*, const char *zFilename);
static void fsDlError(sqlite3_vfs*, int nByte, char *zErrMsg);
static void (*fsDlSym(sqlite3_vfs*,void*, const char *zSymbol))(void);
static void fsDlClose(sqlite3_vfs*, void*);
static int fsRandomness(sqlite3_vfs*, int nByte, char *zOut);
static int fsSleep(sqlite3_vfs*, int microseconds);
static int fsCurrentTime(sqlite3_vfs*, double*);
typedef struct fs_vfs_t fs_vfs_t;
struct fs_vfs_t {
sqlite3_vfs base;
fs_real_file *pFileList;
sqlite3_vfs *pParent;
};
static fs_vfs_t fs_vfs = {
{
1,
0,
0,
0,
FS_VFS_NAME,
0,
fsOpen,
fsDelete,
fsAccess,
fsFullPathname,
fsDlOpen,
fsDlError,
fsDlSym,
fsDlClose,
fsRandomness,
fsSleep,
fsCurrentTime,
0
},
0,
0
};
static sqlite3_io_methods fs_io_methods = {
1,
fsClose,
fsRead,
fsWrite,
fsTruncate,
fsSync,
fsFileSize,
fsLock,
fsUnlock,
fsCheckReservedLock,
fsFileControl,
fsSectorSize,
fsDeviceCharacteristics,
0,
0,
0,
0
};
static sqlite3_io_methods tmp_io_methods = {
1,
tmpClose,
tmpRead,
tmpWrite,
tmpTruncate,
tmpSync,
tmpFileSize,
tmpLock,
tmpUnlock,
tmpCheckReservedLock,
tmpFileControl,
tmpSectorSize,
tmpDeviceCharacteristics,
0,
0,
0,
0
};
#define MIN(x,y) ((x)<(y)?(x):(y))
#define MAX(x,y) ((x)>(y)?(x):(y))
static int tmpClose(sqlite3_file *pFile){
tmp_file *pTmp = (tmp_file *)pFile;
sqlite3_free(pTmp->zAlloc);
return SQLITE_OK;
}
static int tmpRead(
sqlite3_file *pFile,
void *zBuf,
int iAmt,
sqlite_int64 iOfst
){
tmp_file *pTmp = (tmp_file *)pFile;
if( (iAmt+iOfst)>pTmp->nSize ){
return SQLITE_IOERR_SHORT_READ;
}
memcpy(zBuf, &pTmp->zAlloc[iOfst], iAmt);
return SQLITE_OK;
}
static int tmpWrite(
sqlite3_file *pFile,
const void *zBuf,
int iAmt,
sqlite_int64 iOfst
){
tmp_file *pTmp = (tmp_file *)pFile;
if( (iAmt+iOfst)>pTmp->nAlloc ){
int nNew = (int)(2*(iAmt+iOfst+pTmp->nAlloc));
char *zNew = sqlite3_realloc(pTmp->zAlloc, nNew);
if( !zNew ){
return SQLITE_NOMEM;
}
pTmp->zAlloc = zNew;
pTmp->nAlloc = nNew;
}
memcpy(&pTmp->zAlloc[iOfst], zBuf, iAmt);
pTmp->nSize = (int)MAX(pTmp->nSize, iOfst+iAmt);
return SQLITE_OK;
}
static int tmpTruncate(sqlite3_file *pFile, sqlite_int64 size){
tmp_file *pTmp = (tmp_file *)pFile;
pTmp->nSize = (int)MIN(pTmp->nSize, size);
return SQLITE_OK;
}
static int tmpSync(sqlite3_file *pFile, int flags){
return SQLITE_OK;
}
static int tmpFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
tmp_file *pTmp = (tmp_file *)pFile;
*pSize = pTmp->nSize;
return SQLITE_OK;
}
static int tmpLock(sqlite3_file *pFile, int eLock){
return SQLITE_OK;
}
static int tmpUnlock(sqlite3_file *pFile, int eLock){
return SQLITE_OK;
}
static int tmpCheckReservedLock(sqlite3_file *pFile, int *pResOut){
*pResOut = 0;
return SQLITE_OK;
}
static int tmpFileControl(sqlite3_file *pFile, int op, void *pArg){
return SQLITE_OK;
}
static int tmpSectorSize(sqlite3_file *pFile){
return 0;
}
static int tmpDeviceCharacteristics(sqlite3_file *pFile){
return 0;
}
static int fsClose(sqlite3_file *pFile){
int rc = SQLITE_OK;
fs_file *p = (fs_file *)pFile;
fs_real_file *pReal = p->pReal;
pReal->nRef--;
assert(pReal->nRef>=0);
if( pReal->nRef==0 ){
*pReal->ppThis = pReal->pNext;
if( pReal->pNext ){
pReal->pNext->ppThis = pReal->ppThis;
}
rc = pReal->pFile->pMethods->xClose(pReal->pFile);
sqlite3_free(pReal);
}
return rc;
}
static int fsRead(
sqlite3_file *pFile,
void *zBuf,
int iAmt,
sqlite_int64 iOfst
){
int rc = SQLITE_OK;
fs_file *p = (fs_file *)pFile;
fs_real_file *pReal = p->pReal;
sqlite3_file *pF = pReal->pFile;
if( (p->eType==DATABASE_FILE && (iAmt+iOfst)>pReal->nDatabase)
|| (p->eType==JOURNAL_FILE && (iAmt+iOfst)>pReal->nJournal)
){
rc = SQLITE_IOERR_SHORT_READ;
}else if( p->eType==DATABASE_FILE ){
rc = pF->pMethods->xRead(pF, zBuf, iAmt, iOfst+BLOCKSIZE);
}else{
int iRem = iAmt;
int iBuf = 0;
int ii = (int)iOfst;
while( iRem>0 && rc==SQLITE_OK ){
int iRealOff = pReal->nBlob - BLOCKSIZE*((ii/BLOCKSIZE)+1) + ii%BLOCKSIZE;
int iRealAmt = MIN(iRem, BLOCKSIZE - (iRealOff%BLOCKSIZE));
rc = pF->pMethods->xRead(pF, &((char *)zBuf)[iBuf], iRealAmt, iRealOff);
ii += iRealAmt;
iBuf += iRealAmt;
iRem -= iRealAmt;
}
}
return rc;
}
static int fsWrite(
sqlite3_file *pFile,
const void *zBuf,
int iAmt,
sqlite_int64 iOfst
){
int rc = SQLITE_OK;
fs_file *p = (fs_file *)pFile;
fs_real_file *pReal = p->pReal;
sqlite3_file *pF = pReal->pFile;
if( p->eType==DATABASE_FILE ){
if( (iAmt+iOfst+BLOCKSIZE)>(pReal->nBlob-pReal->nJournal) ){
rc = SQLITE_FULL;
}else{
rc = pF->pMethods->xWrite(pF, zBuf, iAmt, iOfst+BLOCKSIZE);
if( rc==SQLITE_OK ){
pReal->nDatabase = (int)MAX(pReal->nDatabase, iAmt+iOfst);
}
}
}else{
int iRem = iAmt;
int iBuf = 0;
int ii = (int)iOfst;
while( iRem>0 && rc==SQLITE_OK ){
int iRealOff = pReal->nBlob - BLOCKSIZE*((ii/BLOCKSIZE)+1) + ii%BLOCKSIZE;
int iRealAmt = MIN(iRem, BLOCKSIZE - (iRealOff%BLOCKSIZE));
if( iRealOff<(pReal->nDatabase+BLOCKSIZE) ){
rc = SQLITE_FULL;
}else{
rc = pF->pMethods->xWrite(pF, &((char *)zBuf)[iBuf], iRealAmt,iRealOff);
ii += iRealAmt;
iBuf += iRealAmt;
iRem -= iRealAmt;
}
}
if( rc==SQLITE_OK ){
pReal->nJournal = (int)MAX(pReal->nJournal, iAmt+iOfst);
}
}
return rc;
}
static int fsTruncate(sqlite3_file *pFile, sqlite_int64 size){
fs_file *p = (fs_file *)pFile;
fs_real_file *pReal = p->pReal;
if( p->eType==DATABASE_FILE ){
pReal->nDatabase = (int)MIN(pReal->nDatabase, size);
}else{
pReal->nJournal = (int)MIN(pReal->nJournal, size);
}
return SQLITE_OK;
}
static int fsSync(sqlite3_file *pFile, int flags){
fs_file *p = (fs_file *)pFile;
fs_real_file *pReal = p->pReal;
sqlite3_file *pRealFile = pReal->pFile;
int rc = SQLITE_OK;
if( p->eType==DATABASE_FILE ){
unsigned char zSize[4];
zSize[0] = (pReal->nDatabase&0xFF000000)>>24;
zSize[1] = (unsigned char)((pReal->nDatabase&0x00FF0000)>>16);
zSize[2] = (pReal->nDatabase&0x0000FF00)>>8;
zSize[3] = (pReal->nDatabase&0x000000FF);
rc = pRealFile->pMethods->xWrite(pRealFile, zSize, 4, 0);
}
if( rc==SQLITE_OK ){
rc = pRealFile->pMethods->xSync(pRealFile, flags&(~SQLITE_SYNC_DATAONLY));
}
return rc;
}
static int fsFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
fs_file *p = (fs_file *)pFile;
fs_real_file *pReal = p->pReal;
if( p->eType==DATABASE_FILE ){
*pSize = pReal->nDatabase;
}else{
*pSize = pReal->nJournal;
}
return SQLITE_OK;
}
static int fsLock(sqlite3_file *pFile, int eLock){
return SQLITE_OK;
}
static int fsUnlock(sqlite3_file *pFile, int eLock){
return SQLITE_OK;
}
static int fsCheckReservedLock(sqlite3_file *pFile, int *pResOut){
*pResOut = 0;
return SQLITE_OK;
}
static int fsFileControl(sqlite3_file *pFile, int op, void *pArg){
if( op==SQLITE_FCNTL_PRAGMA ) return SQLITE_NOTFOUND;
return SQLITE_OK;
}
static int fsSectorSize(sqlite3_file *pFile){
return BLOCKSIZE;
}
static int fsDeviceCharacteristics(sqlite3_file *pFile){
return 0;
}
static int fsOpen(
sqlite3_vfs *pVfs,
const char *zName,
sqlite3_file *pFile,
int flags,
int *pOutFlags
){
fs_vfs_t *pFsVfs = (fs_vfs_t *)pVfs;
fs_file *p = (fs_file *)pFile;
fs_real_file *pReal = 0;
int eType;
int nName;
int rc = SQLITE_OK;
if( 0==(flags&(SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_MAIN_JOURNAL)) ){
tmp_file *p2 = (tmp_file *)pFile;
memset(p2, 0, sizeof(*p2));
p2->base.pMethods = &tmp_io_methods;
return SQLITE_OK;
}
eType = ((flags&(SQLITE_OPEN_MAIN_DB))?DATABASE_FILE:JOURNAL_FILE);
p->base.pMethods = &fs_io_methods;
p->eType = eType;
assert(strlen("-journal")==8);
nName = (int)strlen(zName)-((eType==JOURNAL_FILE)?8:0);
pReal=pFsVfs->pFileList;
for(; pReal && strncmp(pReal->zName, zName, nName); pReal=pReal->pNext);
if( !pReal ){
int real_flags = (flags&~(SQLITE_OPEN_MAIN_DB))|SQLITE_OPEN_TEMP_DB;
sqlite3_int64 size;
sqlite3_file *pRealFile;
sqlite3_vfs *pParent = pFsVfs->pParent;
assert(eType==DATABASE_FILE);
pReal = (fs_real_file *)sqlite3_malloc(sizeof(*pReal)+pParent->szOsFile);
if( !pReal ){
rc = SQLITE_NOMEM;
goto open_out;
}
memset(pReal, 0, sizeof(*pReal)+pParent->szOsFile);
pReal->zName = zName;
pReal->pFile = (sqlite3_file *)(&pReal[1]);
rc = pParent->xOpen(pParent, zName, pReal->pFile, real_flags, pOutFlags);
if( rc!=SQLITE_OK ){
goto open_out;
}
pRealFile = pReal->pFile;
rc = pRealFile->pMethods->xFileSize(pRealFile, &size);
if( rc!=SQLITE_OK ){
goto open_out;
}
if( size==0 ){
rc = pRealFile->pMethods->xWrite(pRealFile, "\0", 1, BLOBSIZE-1);
pReal->nBlob = BLOBSIZE;
}else{
unsigned char zS[4];
pReal->nBlob = (int)size;
rc = pRealFile->pMethods->xRead(pRealFile, zS, 4, 0);
pReal->nDatabase = (zS[0]<<24)+(zS[1]<<16)+(zS[2]<<8)+zS[3];
if( rc==SQLITE_OK ){
rc = pRealFile->pMethods->xRead(pRealFile, zS, 4, pReal->nBlob-4);
if( zS[0] || zS[1] || zS[2] || zS[3] ){
pReal->nJournal = pReal->nBlob;
}
}
}
if( rc==SQLITE_OK ){
pReal->pNext = pFsVfs->pFileList;
if( pReal->pNext ){
pReal->pNext->ppThis = &pReal->pNext;
}
pReal->ppThis = &pFsVfs->pFileList;
pFsVfs->pFileList = pReal;
}
}
open_out:
if( pReal ){
if( rc==SQLITE_OK ){
p->pReal = pReal;
pReal->nRef++;
}else{
if( pReal->pFile->pMethods ){
pReal->pFile->pMethods->xClose(pReal->pFile);
}
sqlite3_free(pReal);
}
}
return rc;
}
static int fsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
int rc = SQLITE_OK;
fs_vfs_t *pFsVfs = (fs_vfs_t *)pVfs;
fs_real_file *pReal;
sqlite3_file *pF;
int nName = (int)strlen(zPath) - 8;
assert(strlen("-journal")==8);
assert(strcmp("-journal", &zPath[nName])==0);
pReal = pFsVfs->pFileList;
for(; pReal && strncmp(pReal->zName, zPath, nName); pReal=pReal->pNext);
if( pReal ){
pF = pReal->pFile;
rc = pF->pMethods->xWrite(pF, "\0\0\0\0", 4, pReal->nBlob-BLOCKSIZE);
if( rc==SQLITE_OK ){
pReal->nJournal = 0;
}
}
return rc;
}
static int fsAccess(
sqlite3_vfs *pVfs,
const char *zPath,
int flags,
int *pResOut
){
fs_vfs_t *pFsVfs = (fs_vfs_t *)pVfs;
fs_real_file *pReal;
int isJournal = 0;
int nName = (int)strlen(zPath);
if( flags!=SQLITE_ACCESS_EXISTS ){
sqlite3_vfs *pParent = ((fs_vfs_t *)pVfs)->pParent;
return pParent->xAccess(pParent, zPath, flags, pResOut);
}
assert(strlen("-journal")==8);
if( nName>8 && strcmp("-journal", &zPath[nName-8])==0 ){
nName -= 8;
isJournal = 1;
}
pReal = pFsVfs->pFileList;
for(; pReal && strncmp(pReal->zName, zPath, nName); pReal=pReal->pNext);
*pResOut = (pReal && (!isJournal || pReal->nJournal>0));
return SQLITE_OK;
}
static int fsFullPathname(
sqlite3_vfs *pVfs,
const char *zPath,
int nOut,
char *zOut
){
sqlite3_vfs *pParent = ((fs_vfs_t *)pVfs)->pParent;
return pParent->xFullPathname(pParent, zPath, nOut, zOut);
}
static void *fsDlOpen(sqlite3_vfs *pVfs, const char *zPath){
sqlite3_vfs *pParent = ((fs_vfs_t *)pVfs)->pParent;
return pParent->xDlOpen(pParent, zPath);
}
static void fsDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
sqlite3_vfs *pParent = ((fs_vfs_t *)pVfs)->pParent;
pParent->xDlError(pParent, nByte, zErrMsg);
}
static void (*fsDlSym(sqlite3_vfs *pVfs, void *pH, const char *zSym))(void){
sqlite3_vfs *pParent = ((fs_vfs_t *)pVfs)->pParent;
return pParent->xDlSym(pParent, pH, zSym);
}
static void fsDlClose(sqlite3_vfs *pVfs, void *pHandle){
sqlite3_vfs *pParent = ((fs_vfs_t *)pVfs)->pParent;
pParent->xDlClose(pParent, pHandle);
}
static int fsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
sqlite3_vfs *pParent = ((fs_vfs_t *)pVfs)->pParent;
return pParent->xRandomness(pParent, nByte, zBufOut);
}
static int fsSleep(sqlite3_vfs *pVfs, int nMicro){
sqlite3_vfs *pParent = ((fs_vfs_t *)pVfs)->pParent;
return pParent->xSleep(pParent, nMicro);
}
static int fsCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
sqlite3_vfs *pParent = ((fs_vfs_t *)pVfs)->pParent;
return pParent->xCurrentTime(pParent, pTimeOut);
}
int fs_register(void){
if( fs_vfs.pParent ) return SQLITE_OK;
fs_vfs.pParent = sqlite3_vfs_find(0);
fs_vfs.base.mxPathname = fs_vfs.pParent->mxPathname;
fs_vfs.base.szOsFile = MAX(sizeof(tmp_file), sizeof(fs_file));
return sqlite3_vfs_register(&fs_vfs.base, 0);
}
#ifdef SQLITE_TEST
int SqlitetestOnefile_Init() {return fs_register();}
#endif