/* ** 2013-10-09 ** ** 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 file contains the implementation of an SQLite vfs wrapper for ** unix that generates per-database log files of all disk activity. */ /* ** This module contains code for a wrapper VFS that causes a log of ** most VFS calls to be written into a file on disk. ** ** Each database connection creates a separate log file in the same ** directory as the original database and named after the original ** database. A unique suffix is added to avoid name collisions. ** Separate log files are used so that concurrent processes do not ** try to write log operations to the same file at the same instant, ** resulting in overwritten or comingled log text. ** ** Each individual log file records operations by a single database ** connection on both the original database and its associated rollback ** journal. ** ** The log files are in the comma-separated-value (CSV) format. The ** log files can be imported into an SQLite database using the ".import" ** command of the SQLite command-line shell for analysis. ** ** One technique for using this module is to append the text of this ** module to the end of a standard "sqlite3.c" amalgamation file then ** add the following compile-time options: ** ** -DSQLITE_EXTRA_INIT=sqlite3_register_vfslog ** -DSQLITE_USE_FCNTL_TRACE ** ** The first compile-time option causes the sqlite3_register_vfslog() ** function, defined below, to be invoked when SQLite is initialized. ** That causes this custom VFS to become the default VFS for all ** subsequent connections. The SQLITE_USE_FCNTL_TRACE option causes ** the SQLite core to issue extra sqlite3_file_control() operations ** with SQLITE_FCNTL_TRACE to give some indication of what is going ** on in the core. */ #include "sqlite3.h" #include #include #include #if SQLITE_OS_UNIX # include #endif /* ** Forward declaration of objects used by this utility */ typedef struct VLogLog VLogLog; typedef struct VLogVfs VLogVfs; typedef struct VLogFile VLogFile; /* There is a pair (an array of size 2) of the following objects for ** each database file being logged. The first contains the filename ** and is used to log I/O with the main database. The second has ** a NULL filename and is used to log I/O for the journal. Both ** out pointers are the same. */ struct VLogLog { VLogLog *pNext; /* Next in a list of all active logs */ VLogLog **ppPrev; /* Pointer to this in the list */ int nRef; /* Number of references to this object */ int nFilename; /* Length of zFilename in bytes */ char *zFilename; /* Name of database file. NULL for journal */ FILE *out; /* Write information here */ }; struct VLogVfs { sqlite3_vfs base; /* VFS methods */ sqlite3_vfs *pVfs; /* Parent VFS */ }; struct VLogFile { sqlite3_file base; /* IO methods */ sqlite3_file *pReal; /* Underlying file handle */ VLogLog *pLog; /* The log file for this file */ }; #define REALVFS(p) (((VLogVfs*)(p))->pVfs) /* ** Methods for VLogFile */ static int vlogClose(sqlite3_file*); static int vlogRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); static int vlogWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst); static int vlogTruncate(sqlite3_file*, sqlite3_int64 size); static int vlogSync(sqlite3_file*, int flags); static int vlogFileSize(sqlite3_file*, sqlite3_int64 *pSize); static int vlogLock(sqlite3_file*, int); static int vlogUnlock(sqlite3_file*, int); static int vlogCheckReservedLock(sqlite3_file*, int *pResOut); static int vlogFileControl(sqlite3_file*, int op, void *pArg); static int vlogSectorSize(sqlite3_file*); static int vlogDeviceCharacteristics(sqlite3_file*); /* ** Methods for VLogVfs */ static int vlogOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *); static int vlogDelete(sqlite3_vfs*, const char *zName, int syncDir); static int vlogAccess(sqlite3_vfs*, const char *zName, int flags, int *); static int vlogFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut); static void *vlogDlOpen(sqlite3_vfs*, const char *zFilename); static void vlogDlError(sqlite3_vfs*, int nByte, char *zErrMsg); static void (*vlogDlSym(sqlite3_vfs *pVfs, void *p, const char*zSym))(void); static void vlogDlClose(sqlite3_vfs*, void*); static int vlogRandomness(sqlite3_vfs*, int nByte, char *zOut); static int vlogSleep(sqlite3_vfs*, int microseconds); static int vlogCurrentTime(sqlite3_vfs*, double*); static int vlogGetLastError(sqlite3_vfs*, int, char *); static int vlogCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*); static VLogVfs vlog_vfs = { { 1, /* iVersion */ 0, /* szOsFile (set by register_vlog()) */ 1024, /* mxPathname */ 0, /* pNext */ "vfslog", /* zName */ 0, /* pAppData */ vlogOpen, /* xOpen */ vlogDelete, /* xDelete */ vlogAccess, /* xAccess */ vlogFullPathname, /* xFullPathname */ vlogDlOpen, /* xDlOpen */ vlogDlError, /* xDlError */ vlogDlSym, /* xDlSym */ vlogDlClose, /* xDlClose */ vlogRandomness, /* xRandomness */ vlogSleep, /* xSleep */ vlogCurrentTime, /* xCurrentTime */ vlogGetLastError, /* xGetLastError */ vlogCurrentTimeInt64 /* xCurrentTimeInt64 */ }, 0 }; static sqlite3_io_methods vlog_io_methods = { 1, /* iVersion */ vlogClose, /* xClose */ vlogRead, /* xRead */ vlogWrite, /* xWrite */ vlogTruncate, /* xTruncate */ vlogSync, /* xSync */ vlogFileSize, /* xFileSize */ vlogLock, /* xLock */ vlogUnlock, /* xUnlock */ vlogCheckReservedLock, /* xCheckReservedLock */ vlogFileControl, /* xFileControl */ vlogSectorSize, /* xSectorSize */ vlogDeviceCharacteristics, /* xDeviceCharacteristics */ 0, /* xShmMap */ 0, /* xShmLock */ 0, /* xShmBarrier */ 0 /* xShmUnmap */ }; #if SQLITE_OS_UNIX && !defined(NO_GETTOD) #include static sqlite3_uint64 vlog_time(){ struct timeval sTime; gettimeofday(&sTime, 0); return sTime.tv_usec + (sqlite3_uint64)sTime.tv_sec * 1000000; } #elif SQLITE_OS_WIN #include #include static sqlite3_uint64 vlog_time(){ FILETIME ft; sqlite3_uint64 u64time = 0; GetSystemTimeAsFileTime(&ft); u64time |= ft.dwHighDateTime; u64time <<= 32; u64time |= ft.dwLowDateTime; /* ft is 100-nanosecond intervals, we want microseconds */ return u64time /(sqlite3_uint64)10; } #else static sqlite3_uint64 vlog_time(){ return 0; } #endif /* ** Write a message to the log file */ static void vlogLogPrint( VLogLog *pLog, /* The log file to write into */ sqlite3_int64 tStart, /* Start time of system call */ sqlite3_int64 tElapse, /* Elapse time of system call */ const char *zOp, /* Type of system call */ sqlite3_int64 iArg1, /* First argument */ sqlite3_int64 iArg2, /* Second argument */ const char *zArg3, /* Third argument */ int iRes /* Result */ ){ char z1[40], z2[40], z3[2000]; if( pLog==0 ) return; if( iArg1>=0 ){ sqlite3_snprintf(sizeof(z1), z1, "%lld", iArg1); }else{ z1[0] = 0; } if( iArg2>=0 ){ sqlite3_snprintf(sizeof(z2), z2, "%lld", iArg2); }else{ z2[0] = 0; } if( zArg3 ){ sqlite3_snprintf(sizeof(z3), z3, "\"%.*w\"", sizeof(z3)-4, zArg3); }else{ z3[0] = 0; } fprintf(pLog->out,"%lld,%lld,%s,%d,%s,%s,%s,%d\n", tStart, tElapse, zOp, pLog->zFilename==0, z1, z2, z3, iRes); } /* ** List of all active log connections. Protected by the master mutex. */ static VLogLog *allLogs = 0; /* ** Close a VLogLog object */ static void vlogLogClose(VLogLog *p){ if( p ){ sqlite3_mutex *pMutex; p->nRef--; if( p->nRef>0 || p->zFilename==0 ) return; pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER); sqlite3_mutex_enter(pMutex); *p->ppPrev = p->pNext; if( p->pNext ) p->pNext->ppPrev = p->ppPrev; sqlite3_mutex_leave(pMutex); fclose(p->out); sqlite3_free(p); } } /* ** Open a VLogLog object on the given file */ static VLogLog *vlogLogOpen(const char *zFilename){ int nName = (int)strlen(zFilename); int isJournal = 0; sqlite3_mutex *pMutex; VLogLog *pLog, *pTemp; sqlite3_int64 tNow = 0; if( nName>4 && strcmp(zFilename+nName-4,"-wal")==0 ){ return 0; /* Do not log wal files */ }else if( nName>8 && strcmp(zFilename+nName-8,"-journal")==0 ){ nName -= 8; isJournal = 1; }else if( nName>12 && sqlite3_strglob("-mj??????9??", zFilename+nName-12)==0 ){ return 0; /* Do not log master journal files */ } pTemp = sqlite3_malloc( sizeof(*pLog)*2 + nName + 60 ); if( pTemp==0 ) return 0; pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER); sqlite3_mutex_enter(pMutex); for(pLog=allLogs; pLog; pLog=pLog->pNext){ if( pLog->nFilename==nName && !memcmp(pLog->zFilename, zFilename, nName) ){ break; } } if( pLog==0 ){ pLog = pTemp; pTemp = 0; memset(pLog, 0, sizeof(*pLog)*2); pLog->zFilename = (char*)&pLog[2]; tNow = vlog_time(); sqlite3_snprintf(nName+60, pLog->zFilename, "%.*s-debuglog-%lld", nName, zFilename, tNow); pLog->out = fopen(pLog->zFilename, "a"); if( pLog->out==0 ){ sqlite3_mutex_leave(pMutex); sqlite3_free(pLog); return 0; } pLog->nFilename = nName; pLog[1].out = pLog[0].out; pLog->ppPrev = &allLogs; if( allLogs ) allLogs->ppPrev = &pLog->pNext; pLog->pNext = allLogs; allLogs = pLog; } sqlite3_mutex_leave(pMutex); if( pTemp ){ sqlite3_free(pTemp); }else{ #if SQLITE_OS_UNIX char zHost[200]; zHost[0] = 0; gethostname(zHost, sizeof(zHost)-1); zHost[sizeof(zHost)-1] = 0; vlogLogPrint(pLog, tNow, 0, "IDENT", getpid(), -1, zHost, 0); #endif } if( pLog && isJournal ) pLog++; pLog->nRef++; return pLog; } /* ** Close an vlog-file. */ static int vlogClose(sqlite3_file *pFile){ sqlite3_uint64 tStart, tElapse; int rc = SQLITE_OK; VLogFile *p = (VLogFile *)pFile; tStart = vlog_time(); if( p->pReal->pMethods ){ rc = p->pReal->pMethods->xClose(p->pReal); } tElapse = vlog_time() - tStart; vlogLogPrint(p->pLog, tStart, tElapse, "CLOSE", -1, -1, 0, rc); vlogLogClose(p->pLog); return rc; } /* ** Compute signature for a block of content. ** ** For blocks of 16 or fewer bytes, the signature is just a hex dump of ** the entire block. ** ** For blocks of more than 16 bytes, the signature is a hex dump of the ** first 8 bytes followed by a 64-bit has of the entire block. */ static void vlogSignature(unsigned char *p, int n, char *zCksum){ unsigned int s0 = 0, s1 = 0; unsigned int *pI; int i; if( n<=16 ){ for(i=0; ipReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst); tElapse = vlog_time() - tStart; if( rc==SQLITE_OK ){ vlogSignature(zBuf, iAmt, zSig); }else{ zSig[0] = 0; } vlogLogPrint(p->pLog, tStart, tElapse, "READ", iAmt, iOfst, zSig, rc); if( rc==SQLITE_OK && p->pLog && p->pLog->zFilename && iOfst<=24 && iOfst+iAmt>=28 ){ unsigned char *x = ((unsigned char*)zBuf)+(24-iOfst); unsigned iCtr, nFree = -1; char *zFree = 0; char zStr[12]; iCtr = bigToNative(x); if( iOfst+iAmt>=40 ){ zFree = zStr; sqlite3_snprintf(sizeof(zStr), zStr, "%d", bigToNative(x+8)); nFree = bigToNative(x+12); } vlogLogPrint(p->pLog, tStart, 0, "CHNGCTR-READ", iCtr, nFree, zFree, 0); } return rc; } /* ** Write data to an vlog-file. */ static int vlogWrite( sqlite3_file *pFile, const void *z, int iAmt, sqlite_int64 iOfst ){ int rc; sqlite3_uint64 tStart, tElapse; VLogFile *p = (VLogFile *)pFile; char zSig[40]; tStart = vlog_time(); vlogSignature((unsigned char*)z, iAmt, zSig); rc = p->pReal->pMethods->xWrite(p->pReal, z, iAmt, iOfst); tElapse = vlog_time() - tStart; vlogLogPrint(p->pLog, tStart, tElapse, "WRITE", iAmt, iOfst, zSig, rc); if( rc==SQLITE_OK && p->pLog && p->pLog->zFilename && iOfst<=24 && iOfst+iAmt>=28 ){ unsigned char *x = ((unsigned char*)z)+(24-iOfst); unsigned iCtr, nFree = -1; char *zFree = 0; char zStr[12]; iCtr = bigToNative(x); if( iOfst+iAmt>=40 ){ zFree = zStr; sqlite3_snprintf(sizeof(zStr), zStr, "%d", bigToNative(x+8)); nFree = bigToNative(x+12); } vlogLogPrint(p->pLog, tStart, 0, "CHNGCTR-WRITE", iCtr, nFree, zFree, 0); } return rc; } /* ** Truncate an vlog-file. */ static int vlogTruncate(sqlite3_file *pFile, sqlite_int64 size){ int rc; sqlite3_uint64 tStart, tElapse; VLogFile *p = (VLogFile *)pFile; tStart = vlog_time(); rc = p->pReal->pMethods->xTruncate(p->pReal, size); tElapse = vlog_time() - tStart; vlogLogPrint(p->pLog, tStart, tElapse, "TRUNCATE", size, -1, 0, rc); return rc; } /* ** Sync an vlog-file. */ static int vlogSync(sqlite3_file *pFile, int flags){ int rc; sqlite3_uint64 tStart, tElapse; VLogFile *p = (VLogFile *)pFile; tStart = vlog_time(); rc = p->pReal->pMethods->xSync(p->pReal, flags); tElapse = vlog_time() - tStart; vlogLogPrint(p->pLog, tStart, tElapse, "SYNC", flags, -1, 0, rc); return rc; } /* ** Return the current file-size of an vlog-file. */ static int vlogFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ int rc; sqlite3_uint64 tStart, tElapse; VLogFile *p = (VLogFile *)pFile; tStart = vlog_time(); rc = p->pReal->pMethods->xFileSize(p->pReal, pSize); tElapse = vlog_time() - tStart; vlogLogPrint(p->pLog, tStart, tElapse, "FILESIZE", *pSize, -1, 0, rc); return rc; } /* ** Lock an vlog-file. */ static int vlogLock(sqlite3_file *pFile, int eLock){ int rc; sqlite3_uint64 tStart, tElapse; VLogFile *p = (VLogFile *)pFile; tStart = vlog_time(); rc = p->pReal->pMethods->xLock(p->pReal, eLock); tElapse = vlog_time() - tStart; vlogLogPrint(p->pLog, tStart, tElapse, "LOCK", eLock, -1, 0, rc); return rc; } /* ** Unlock an vlog-file. */ static int vlogUnlock(sqlite3_file *pFile, int eLock){ int rc; sqlite3_uint64 tStart; VLogFile *p = (VLogFile *)pFile; tStart = vlog_time(); vlogLogPrint(p->pLog, tStart, 0, "UNLOCK", eLock, -1, 0, 0); rc = p->pReal->pMethods->xUnlock(p->pReal, eLock); return rc; } /* ** Check if another file-handle holds a RESERVED lock on an vlog-file. */ static int vlogCheckReservedLock(sqlite3_file *pFile, int *pResOut){ int rc; sqlite3_uint64 tStart, tElapse; VLogFile *p = (VLogFile *)pFile; tStart = vlog_time(); rc = p->pReal->pMethods->xCheckReservedLock(p->pReal, pResOut); tElapse = vlog_time() - tStart; vlogLogPrint(p->pLog, tStart, tElapse, "CHECKRESERVEDLOCK", *pResOut, -1, "", rc); return rc; } /* ** File control method. For custom operations on an vlog-file. */ static int vlogFileControl(sqlite3_file *pFile, int op, void *pArg){ VLogFile *p = (VLogFile *)pFile; sqlite3_uint64 tStart, tElapse; int rc; tStart = vlog_time(); rc = p->pReal->pMethods->xFileControl(p->pReal, op, pArg); if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){ *(char**)pArg = sqlite3_mprintf("vlog/%z", *(char**)pArg); } tElapse = vlog_time() - tStart; if( op==SQLITE_FCNTL_TRACE ){ vlogLogPrint(p->pLog, tStart, tElapse, "TRACE", op, -1, pArg, rc); }else if( op==SQLITE_FCNTL_PRAGMA ){ const char **azArg = (const char **)pArg; vlogLogPrint(p->pLog, tStart, tElapse, "FILECONTROL", op, -1, azArg[1], rc); }else if( op==SQLITE_FCNTL_SIZE_HINT ){ sqlite3_int64 sz = *(sqlite3_int64*)pArg; vlogLogPrint(p->pLog, tStart, tElapse, "FILECONTROL", op, sz, 0, rc); }else{ vlogLogPrint(p->pLog, tStart, tElapse, "FILECONTROL", op, -1, 0, rc); } return rc; } /* ** Return the sector-size in bytes for an vlog-file. */ static int vlogSectorSize(sqlite3_file *pFile){ int rc; sqlite3_uint64 tStart, tElapse; VLogFile *p = (VLogFile *)pFile; tStart = vlog_time(); rc = p->pReal->pMethods->xSectorSize(p->pReal); tElapse = vlog_time() - tStart; vlogLogPrint(p->pLog, tStart, tElapse, "SECTORSIZE", -1, -1, 0, rc); return rc; } /* ** Return the device characteristic flags supported by an vlog-file. */ static int vlogDeviceCharacteristics(sqlite3_file *pFile){ int rc; sqlite3_uint64 tStart, tElapse; VLogFile *p = (VLogFile *)pFile; tStart = vlog_time(); rc = p->pReal->pMethods->xDeviceCharacteristics(p->pReal); tElapse = vlog_time() - tStart; vlogLogPrint(p->pLog, tStart, tElapse, "DEVCHAR", -1, -1, 0, rc); return rc; } /* ** Open an vlog file handle. */ static int vlogOpen( sqlite3_vfs *pVfs, const char *zName, sqlite3_file *pFile, int flags, int *pOutFlags ){ int rc; sqlite3_uint64 tStart, tElapse; sqlite3_int64 iArg2; VLogFile *p = (VLogFile*)pFile; p->pReal = (sqlite3_file*)&p[1]; if( (flags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_MAIN_JOURNAL))!=0 ){ p->pLog = vlogLogOpen(zName); }else{ p->pLog = 0; } tStart = vlog_time(); rc = REALVFS(pVfs)->xOpen(REALVFS(pVfs), zName, p->pReal, flags, pOutFlags); tElapse = vlog_time() - tStart; iArg2 = pOutFlags ? *pOutFlags : -1; vlogLogPrint(p->pLog, tStart, tElapse, "OPEN", flags, iArg2, 0, rc); if( rc==SQLITE_OK ){ pFile->pMethods = &vlog_io_methods; }else{ if( p->pLog ) vlogLogClose(p->pLog); p->pLog = 0; } return rc; } /* ** 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 vlogDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ int rc; sqlite3_uint64 tStart, tElapse; VLogLog *pLog; tStart = vlog_time(); rc = REALVFS(pVfs)->xDelete(REALVFS(pVfs), zPath, dirSync); tElapse = vlog_time() - tStart; pLog = vlogLogOpen(zPath); vlogLogPrint(pLog, tStart, tElapse, "DELETE", dirSync, -1, 0, rc); vlogLogClose(pLog); return rc; } /* ** Test for access permissions. Return true if the requested permission ** is available, or false otherwise. */ static int vlogAccess( sqlite3_vfs *pVfs, const char *zPath, int flags, int *pResOut ){ int rc; sqlite3_uint64 tStart, tElapse; VLogLog *pLog; tStart = vlog_time(); rc = REALVFS(pVfs)->xAccess(REALVFS(pVfs), zPath, flags, pResOut); tElapse = vlog_time() - tStart; pLog = vlogLogOpen(zPath); vlogLogPrint(pLog, tStart, tElapse, "ACCESS", flags, *pResOut, 0, rc); vlogLogClose(pLog); return rc; } /* ** 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 vlogFullPathname( sqlite3_vfs *pVfs, const char *zPath, int nOut, char *zOut ){ return REALVFS(pVfs)->xFullPathname(REALVFS(pVfs), zPath, nOut, zOut); } /* ** Open the dynamic library located at zPath and return a handle. */ static void *vlogDlOpen(sqlite3_vfs *pVfs, const char *zPath){ return REALVFS(pVfs)->xDlOpen(REALVFS(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 vlogDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){ REALVFS(pVfs)->xDlError(REALVFS(pVfs), nByte, zErrMsg); } /* ** Return a pointer to the symbol zSymbol in the dynamic library pHandle. */ static void (*vlogDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void){ return REALVFS(pVfs)->xDlSym(REALVFS(pVfs), p, zSym); } /* ** Close the dynamic library handle pHandle. */ static void vlogDlClose(sqlite3_vfs *pVfs, void *pHandle){ REALVFS(pVfs)->xDlClose(REALVFS(pVfs), pHandle); } /* ** Populate the buffer pointed to by zBufOut with nByte bytes of ** random data. */ static int vlogRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ return REALVFS(pVfs)->xRandomness(REALVFS(pVfs), nByte, zBufOut); } /* ** Sleep for nMicro microseconds. Return the number of microseconds ** actually slept. */ static int vlogSleep(sqlite3_vfs *pVfs, int nMicro){ return REALVFS(pVfs)->xSleep(REALVFS(pVfs), nMicro); } /* ** Return the current time as a Julian Day number in *pTimeOut. */ static int vlogCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){ return REALVFS(pVfs)->xCurrentTime(REALVFS(pVfs), pTimeOut); } static int vlogGetLastError(sqlite3_vfs *pVfs, int a, char *b){ return REALVFS(pVfs)->xGetLastError(REALVFS(pVfs), a, b); } static int vlogCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){ return REALVFS(pVfs)->xCurrentTimeInt64(REALVFS(pVfs), p); } /* ** Register debugvfs as the default VFS for this process. */ int sqlite3_register_vfslog(const char *zArg){ vlog_vfs.pVfs = sqlite3_vfs_find(0); vlog_vfs.base.szOsFile = sizeof(VLogFile) + vlog_vfs.pVfs->szOsFile; return sqlite3_vfs_register(&vlog_vfs.base, 1); }