/* ** 2026-01-05 ** ** 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 implements a VFS shim that writes a timestamp and other tracing ** information into 16 byts of reserved space at the end of each page of the ** database file. The additional data is written as the page is added to ** the WAL file for databases in WAL mode, or as the database file itself ** is modified in rollback modes. ** ** The VFS also tries to generate log-files with names of the form: ** ** $(DATABASE)-tmstmp/$(TIME)-$(PID)-$(ID) ** ** Log files are only generated if directory $(DATABASE)-tmstmp exists. ** The name of each log file is the current ISO8601 time in milliseconds, ** the process ID, and a random 32-bit value (to disambiguate multiple ** connections from the same process) separated by dashes. The log file ** contains 16-bytes records for various events, such as opening or close ** of the database or WAL file, writes to the WAL file, checkpoints, and ** similar. The logfile is only generated if the connection attempts to ** modify the database. There is a separate log file for each open database ** connection. ** ** COMPILING ** ** To build this extension as a separately loaded shared library or ** DLL, use compiler command-lines similar to the following: ** ** (linux) gcc -fPIC -shared tmstmpvfs.c -o tmstmpvfs.so ** (mac) clang -fPIC -dynamiclib tmstmpvfs.c -o tmstmpvfs.dylib ** (windows) cl tmstmpvfs.c -link -dll -out:tmstmpvfs.dll ** ** You may want to add additional compiler options, of course, ** according to the needs of your project. ** ** Another option is to statically link both SQLite and this extension ** into your application. If both this file and "sqlite3.c" are statically ** linked, and if "sqlite3.c" is compiled with -DSQLITE_EXTRA_INIT= ** SQLite amalgamation "sqlite3.c" file with the option like: ** ** -DSQLITE_EXTRA_INIT=sqlite3_register_tmstmpvfs ** ** Then SQLite will use the tmstmp VFS by default throughout your ** application. ** ** LOADING ** ** To load this extension as a shared library, you first have to ** bring up a dummy SQLite database connection to use as the argument ** to the sqlite3_load_extension() API call. Then you invoke the ** sqlite3_load_extension() API and shutdown the dummy database ** connection. All subsequent database connections that are opened ** will include this extension. For example: ** ** sqlite3 *db; ** sqlite3_open(":memory:", &db); ** sqlite3_load_extension(db, "./tmstmpvfs"); ** sqlite3_close(db); ** ** Tmstmpvfs is a VFS Shim. When loaded, "tmstmpvfs" becomes the new ** default VFS and it uses the prior default VFS as the next VFS ** down in the stack. This is normally what you want. However, in ** complex situations where multiple VFS shims are being loaded, ** it might be important to ensure that tmstmpvfs is loaded in the ** correct order so that it sequences itself into the default VFS ** Shim stack in the right order. ** ** When running the CLI, you can load this extension at invocation by ** adding a command-line option like this: "--vfs ./tmstmpvfs.so". ** The --vfs option usually specifies the symbolic name of a built-in VFS. ** But if the argument to --vfs is not a built-in VFS but is instead the ** name of a file, the CLI tries to load that file as an extension. Note ** that the full name of the extension file must be provided, including ** the ".so" or ".dylib" or ".dll" suffix. ** ** An application can see if the tmstmpvfs is being used by examining ** the results from SQLITE_FCNTL_VFSNAME (or the .vfsname command in ** the CLI). If the answer include "tmstmp", then this VFS is being ** used. ** ** USING ** ** Open database connections using the sqlite3_open() or ** sqlite3_open_v2() interfaces, as normal. Ordinary database files ** (without a timestamp) will operate normally. ** ** Timestamping only works on databases that have a reserve-bytes ** value of exactly 16. The default value for reserve-bytes is 0. ** Hence, newly created database files will omit the timestamp by ** default. To create a database that includes a timestamp, change ** the reserve-bytes value to 16 by running: ** ** int n = 16; ** sqlite3_file_control(db, 0, SQLITE_FCNTL_RESERVE_BYTES, &n); ** ** If you do this immediately after creating a new database file, ** before anything else has been written into the file, then that ** might be all that you need to do. Otherwise, the API call ** above should be followed by: ** ** sqlite3_exec(db, "VACUUM", 0, 0, 0); ** ** It never hurts to run the VACUUM, even if you don't need it. ** ** From the CLI, use the ".filectrl reserve_bytes 16" command, ** followed by "VACUUM;". ** ** SQLite allows the number of reserve-bytes to be increased, but ** not decreased. If you want to restore the reserve-bytes to 0 ** (to disable tmstmpvfs), the easiest approach is to use VACUUM INTO ** with a URI filename as the argument and include "reserve=0" query ** parameter on the URI. Example: ** ** VACUUM INTO 'file:notimestamps.db?reserve=0'; ** ** Then switch over to using the new database file. The reserve=0 query ** parameter only works on SQLite 3.52.0 and later. ** ** IMPLEMENTATION NOTES ** ** The timestamp information is stored in the last 16 bytes of each page. ** This module only operates if the "bytes of reserved space on each page" ** value at offset 20 the SQLite database header is exactly 16. If ** the reserved-space value is not 16, no timestamp information is added ** to database pages. Some, but not all, logfile entries will be made ** still, but the size of the logs will be greatly reduced. ** ** The timestamp layout is as follows: ** ** bytes 0,1 Zero. Reserved for future expansion ** bytes 2-7 Milliseconds since the Unix Epoch ** bytes 8-11 WAL frame number ** bytes 12 0: WAL write 1: WAL txn 2: rollback write ** bytes 13-15 Lower 24 bits of Salt-1 ** ** For transactions that occur in rollback mode, only the timestamp ** in bytes 2-7 and byte 12 are non-zero. Byte 12 is set to 2 for ** rollback writes. ** ** LOGGING ** ** An open database connection that attempts to write to the database ** will create a log file if a directory name $(DATABASE)-tmstmp exists. ** The name of the log file is: ** ** $(TIME)-$(PID)-$(RANDOM) ** ** Where TIME is an ISO 8601 date in milliseconds with no punctuation, ** PID is the process ID, and RANDOM is a 32-bit random number expressed ** as hexadecimal. ** ** The log consists of 16-byte records. Each record consists of five ** unsigned integers: ** ** 1 1 6 4 4 <--- bytes ** op a1 ts a2 a3 ** ** The meanings of the a1-a3 values depend on op. ts is the timestamp ** in milliseconds since the unix epoch (1970-01-01 00:00:00). ** Opcodes are defined by the ELOG_* #defines below. ** ** ELOG_OPEN_DB "Open a connection to the database file" ** op = 0x01 ** a2 = process-ID ** ** ELOG_OPEN_WAL "Open a connection to the -wal file" ** op = 0x02 ** a2 = process-ID ** ** ELOG_WAL_PAGE "New page added to the WAL file" ** op = 0x03 ** a1 = 1 if last page of a txn. 0 otherwise. ** a2 = page number in the DB file ** a3 = frame number in the WAL file ** ** ELOG_DB_PAGE "Database page updated using rollback mode" ** op = 0x04 ** a2 = page number in the DB file ** ** ELOG_CKPT_START "Start of a checkpoint operation" ** op = 0x05 ** ** ELOG_CKPT_PAGE "Page xfer from WAL to database" ** op = 0x06 ** a2 = database page number ** ** ELOG_CKPT_END "Start of a checkpoint operation" ** op = 0x07 ** ** ELOG_WAL_RESET "WAL file header overwritten" ** op = 0x08 ** a3 = Salt1 value ** ** ELOG_CLOSE_WAL "Close the WAL file connection" ** op = 0x0e ** ** ELOG_CLOSE_DB "Close the DB connection" ** op = 0x0f */ #if defined(SQLITE_AMALGAMATION) && !defined(SQLITE_TMSTMPVFS_STATIC) # define SQLITE_TMSTMPVFS_STATIC #endif #ifdef SQLITE_TMSTMPVFS_STATIC # include "sqlite3.h" #else # include "sqlite3ext.h" SQLITE_EXTENSION_INIT1 #endif #include #include #include /* ** Forward declaration of objects used by this utility */ typedef struct sqlite3_vfs TmstmpVfs; typedef struct TmstmpFile TmstmpFile; typedef struct TmstmpLog TmstmpLog; /* ** Bytes of reserved space used by this extension */ #define TMSTMP_RESERVE 16 /* ** The magic number used to identify TmstmpFile objects */ #define TMSTMP_MAGIC 0x2a87b72d /* ** Useful datatype abbreviations */ #if !defined(SQLITE_AMALGAMATION) typedef unsigned char u8; typedef unsigned int u32; #endif /* ** Current process id */ #if defined(_WIN32) # include # define GETPID (u32)GetCurrentProcessId() #else # include # define GETPID (u32)getpid() #endif /* Access to a lower-level VFS that (might) implement dynamic loading, ** access to randomness, etc. */ #define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData)) #define ORIGFILE(p) ((sqlite3_file*)(((TmstmpFile*)(p))+1)) /* Information for the tmstmp log file. */ struct TmstmpLog { char *zLogname; /* Log filename */ FILE *log; /* Open log file */ int n; /* Bytes of a[] used */ unsigned char a[16*6]; /* Buffered header for the log */ }; /* An open WAL file */ struct TmstmpFile { sqlite3_file base; /* IO methods */ u32 uMagic; /* Magic number for sanity checking */ u32 salt1; /* Last WAL salt-1 value */ u32 iFrame; /* Last WAL frame number */ u32 pgno; /* Current page number */ u32 pgsz; /* Size of each page, in bytes */ u8 isWal; /* True if this is a WAL file */ u8 isDb; /* True if this is a DB file */ u8 isCommit; /* Last WAL frame header was a transaction commit */ u8 hasCorrectReserve; /* File has the correct reserve size */ u8 inCkpt; /* True if in a checkpoint */ TmstmpLog *pLog; /* Log file */ TmstmpFile *pPartner; /* DB->WAL or WAL->DB mapping */ sqlite3_int64 iOfst; /* Offset of last WAL frame header */ sqlite3_vfs *pSubVfs; /* Underlying VFS */ }; /* ** Event log opcodes */ #define ELOG_OPEN_DB 0x01 #define ELOG_OPEN_WAL 0x02 #define ELOG_WAL_PAGE 0x03 #define ELOG_DB_PAGE 0x04 #define ELOG_CKPT_START 0x05 #define ELOG_CKPT_PAGE 0x06 #define ELOG_CKPT_DONE 0x07 #define ELOG_WAL_RESET 0x08 #define ELOG_CLOSE_WAL 0x0e #define ELOG_CLOSE_DB 0x0f /* ** Methods for TmstmpFile */ static int tmstmpClose(sqlite3_file*); static int tmstmpRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); static int tmstmpWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst); static int tmstmpTruncate(sqlite3_file*, sqlite3_int64 size); static int tmstmpSync(sqlite3_file*, int flags); static int tmstmpFileSize(sqlite3_file*, sqlite3_int64 *pSize); static int tmstmpLock(sqlite3_file*, int); static int tmstmpUnlock(sqlite3_file*, int); static int tmstmpCheckReservedLock(sqlite3_file*, int *pResOut); static int tmstmpFileControl(sqlite3_file*, int op, void *pArg); static int tmstmpSectorSize(sqlite3_file*); static int tmstmpDeviceCharacteristics(sqlite3_file*); static int tmstmpShmMap(sqlite3_file*, int iPg, int pgsz, int, void volatile**); static int tmstmpShmLock(sqlite3_file*, int offset, int n, int flags); static void tmstmpShmBarrier(sqlite3_file*); static int tmstmpShmUnmap(sqlite3_file*, int deleteFlag); static int tmstmpFetch(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp); static int tmstmpUnfetch(sqlite3_file*, sqlite3_int64 iOfst, void *p); /* ** Methods for TmstmpVfs */ static int tmstmpOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *); static int tmstmpDelete(sqlite3_vfs*, const char *zName, int syncDir); static int tmstmpAccess(sqlite3_vfs*, const char *zName, int flags, int *); static int tmstmpFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut); static void *tmstmpDlOpen(sqlite3_vfs*, const char *zFilename); static void tmstmpDlError(sqlite3_vfs*, int nByte, char *zErrMsg); static void (*tmstmpDlSym(sqlite3_vfs *pVfs, void *p, const char*zSym))(void); static void tmstmpDlClose(sqlite3_vfs*, void*); static int tmstmpRandomness(sqlite3_vfs*, int nByte, char *zOut); static int tmstmpSleep(sqlite3_vfs*, int microseconds); static int tmstmpCurrentTime(sqlite3_vfs*, double*); static int tmstmpGetLastError(sqlite3_vfs*, int, char *); static int tmstmpCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*); static int tmstmpSetSystemCall(sqlite3_vfs*, const char*,sqlite3_syscall_ptr); static sqlite3_syscall_ptr tmstmpGetSystemCall(sqlite3_vfs*, const char *z); static const char *tmstmpNextSystemCall(sqlite3_vfs*, const char *zName); static sqlite3_vfs tmstmp_vfs = { 3, /* iVersion (set when registered) */ 0, /* szOsFile (set when registered) */ 1024, /* mxPathname */ 0, /* pNext */ "tmstmpvfs", /* zName */ 0, /* pAppData (set when registered) */ tmstmpOpen, /* xOpen */ tmstmpDelete, /* xDelete */ tmstmpAccess, /* xAccess */ tmstmpFullPathname, /* xFullPathname */ tmstmpDlOpen, /* xDlOpen */ tmstmpDlError, /* xDlError */ tmstmpDlSym, /* xDlSym */ tmstmpDlClose, /* xDlClose */ tmstmpRandomness, /* xRandomness */ tmstmpSleep, /* xSleep */ tmstmpCurrentTime, /* xCurrentTime */ tmstmpGetLastError, /* xGetLastError */ tmstmpCurrentTimeInt64, /* xCurrentTimeInt64 */ tmstmpSetSystemCall, /* xSetSystemCall */ tmstmpGetSystemCall, /* xGetSystemCall */ tmstmpNextSystemCall /* xNextSystemCall */ }; static const sqlite3_io_methods tmstmp_io_methods = { 3, /* iVersion */ tmstmpClose, /* xClose */ tmstmpRead, /* xRead */ tmstmpWrite, /* xWrite */ tmstmpTruncate, /* xTruncate */ tmstmpSync, /* xSync */ tmstmpFileSize, /* xFileSize */ tmstmpLock, /* xLock */ tmstmpUnlock, /* xUnlock */ tmstmpCheckReservedLock, /* xCheckReservedLock */ tmstmpFileControl, /* xFileControl */ tmstmpSectorSize, /* xSectorSize */ tmstmpDeviceCharacteristics, /* xDeviceCharacteristics */ tmstmpShmMap, /* xShmMap */ tmstmpShmLock, /* xShmLock */ tmstmpShmBarrier, /* xShmBarrier */ tmstmpShmUnmap, /* xShmUnmap */ tmstmpFetch, /* xFetch */ tmstmpUnfetch /* xUnfetch */ }; /* ** Write a 6-byte millisecond timestamp into aOut[] */ static void tmstmpPutTS(TmstmpFile *p, unsigned char *aOut){ sqlite3_uint64 tm = 0; p->pSubVfs->xCurrentTimeInt64(p->pSubVfs, (sqlite3_int64*)&tm); tm -= 210866760000000LL; aOut[0] = (tm>>40)&0xff; aOut[1] = (tm>>32)&0xff; aOut[2] = (tm>>24)&0xff; aOut[3] = (tm>>16)&0xff; aOut[4] = (tm>>8)&0xff; aOut[5] = tm&0xff; } /* ** Read a 32-bit big-endian unsigned integer and return it. */ static u32 tmstmpGetU32(const unsigned char *a){ return (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3]; } /* Write a 32-bit integer as big-ending into a[] */ static void tmstmpPutU32(u32 v, unsigned char *a){ a[0] = (v>>24) & 0xff; a[1] = (v>>16) & 0xff; a[2] = (v>>8) & 0xff; a[3] = v & 0xff; } /* Free a TmstmpLog object */ static void tmstmpLogFree(TmstmpLog *pLog){ if( pLog==0 ) return; sqlite3_free(pLog->zLogname); sqlite3_free(pLog); } /* Flush log content. Open the file if necessary. Return the ** number of errors. */ static int tmstmpLogFlush(TmstmpFile *p){ TmstmpLog *pLog = p->pLog; assert( pLog!=0 ); if( pLog->log==0 ){ pLog->log = fopen(pLog->zLogname, "wb"); if( pLog->log==0 ){ tmstmpLogFree(pLog); p->pLog = 0; return 1; } } (void)fwrite(pLog->a, pLog->n, 1, pLog->log); pLog->n = 0; return 0; } /* ** Write a record onto the event log */ static void tmstmpEvent( TmstmpFile *p, u8 op, u8 a1, u32 a2, u32 a3 ){ unsigned char *a; TmstmpLog *pLog; if( p->isWal ){ p = p->pPartner; assert( p!=0 ); assert( p->isDb ); } pLog = p->pLog; if( pLog==0 ) return; if( pLog->n >= (int)sizeof(pLog->a) ){ if( tmstmpLogFlush(p) ) return; } a = pLog->a + pLog->n; a[0] = op; a[1] = a1; tmstmpPutTS(p, a+2); tmstmpPutU32(a2, a+8); tmstmpPutU32(a3, a+12); pLog->n += 16; if( pLog->log || (op>=ELOG_WAL_PAGE && op<=ELOG_WAL_RESET) ){ (void)tmstmpLogFlush(p); } } /* ** Close a connection */ static int tmstmpClose(sqlite3_file *pFile){ TmstmpFile *p = (TmstmpFile *)pFile; if( p->hasCorrectReserve ){ tmstmpEvent(p, p->isDb ? ELOG_CLOSE_DB : ELOG_CLOSE_WAL, 0, 0, 0); } tmstmpLogFree(p->pLog); if( p->pPartner ){ assert( p->pPartner->pPartner==p ); p->pPartner->pPartner = 0; p->pPartner = 0; } pFile = ORIGFILE(pFile); return pFile->pMethods->xClose(pFile); } /* ** Read bytes from a file */ static int tmstmpRead( sqlite3_file *pFile, void *zBuf, int iAmt, sqlite_int64 iOfst ){ int rc; TmstmpFile *p = (TmstmpFile*)pFile; pFile = ORIGFILE(pFile); rc = pFile->pMethods->xRead(pFile, zBuf, iAmt, iOfst); if( rc!=SQLITE_OK ) return rc; if( p->isDb && iOfst==0 && iAmt>=100 ){ const unsigned char *a = (unsigned char*)zBuf; p->hasCorrectReserve = (a[20]==TMSTMP_RESERVE); p->pgsz = (a[16]<<8) + a[17]; if( p->pgsz==1 ) p->pgsz = 65536; if( p->pPartner ){ p->pPartner->hasCorrectReserve = p->hasCorrectReserve; p->pPartner->pgsz = p->pgsz; } } return rc; } /* ** Write data to a tmstmp-file. */ static int tmstmpWrite( sqlite3_file *pFile, const void *zBuf, int iAmt, sqlite_int64 iOfst ){ TmstmpFile *p = (TmstmpFile*)pFile; sqlite3_file *pSub = ORIGFILE(pFile); if( !p->hasCorrectReserve ){ /* The database does not have the correct reserve size. No-op */ }else if( p->isWal ){ /* Writing into a WAL file */ if( iAmt==24 ){ /* A frame header */ u32 x = 0; p->iFrame = (iOfst - 32)/(p->pgsz+24); p->pgno = tmstmpGetU32((const u8*)zBuf); p->salt1 = tmstmpGetU32(((const u8*)zBuf)+8); memcpy(&x, ((const u8*)zBuf)+4, 4); p->isCommit = (x!=0); p->iOfst = iOfst; }else if( iAmt>=512 && iOfst==p->iOfst+24 ){ unsigned char *s = (unsigned char*)zBuf+iAmt-TMSTMP_RESERVE; memset(s, 0, TMSTMP_RESERVE); tmstmpPutTS(p, s+2); tmstmpPutU32(p->iFrame, s+8); tmstmpPutU32(p->salt1, s+12); s[12] = p->isCommit ? 1 : 0; tmstmpEvent(p, ELOG_WAL_PAGE, s[12], p->pgno, p->iFrame); }else if( iAmt==32 && iOfst==0 ){ u32 salt1 = tmstmpGetU32(((const u8*)zBuf)+16); tmstmpEvent(p, ELOG_WAL_RESET, 0, 0, salt1); } }else if( p->inCkpt ){ assert( p->pgsz>0 ); tmstmpEvent(p, ELOG_CKPT_PAGE, 0, (iOfst/p->pgsz)+1, 0); }else if( p->pPartner==0 ){ /* Writing into a database in rollback mode */ unsigned char *s = (unsigned char*)zBuf+iAmt-TMSTMP_RESERVE; memset(s, 0, TMSTMP_RESERVE); tmstmpPutTS(p, s+2); s[12] = 2; assert( p->pgsz>0 ); tmstmpEvent(p, ELOG_DB_PAGE, 0, (u32)(iOfst/p->pgsz), 0); } return pSub->pMethods->xWrite(pSub,zBuf,iAmt,iOfst); } /* ** Truncate a tmstmp-file. */ static int tmstmpTruncate(sqlite3_file *pFile, sqlite_int64 size){ pFile = ORIGFILE(pFile); return pFile->pMethods->xTruncate(pFile, size); } /* ** Sync a tmstmp-file. */ static int tmstmpSync(sqlite3_file *pFile, int flags){ pFile = ORIGFILE(pFile); return pFile->pMethods->xSync(pFile, flags); } /* ** Return the current file-size of a tmstmp-file. */ static int tmstmpFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ TmstmpFile *p = (TmstmpFile *)pFile; pFile = ORIGFILE(p); return pFile->pMethods->xFileSize(pFile, pSize); } /* ** Lock a tmstmp-file. */ static int tmstmpLock(sqlite3_file *pFile, int eLock){ pFile = ORIGFILE(pFile); return pFile->pMethods->xLock(pFile, eLock); } /* ** Unlock a tmstmp-file. */ static int tmstmpUnlock(sqlite3_file *pFile, int eLock){ pFile = ORIGFILE(pFile); return pFile->pMethods->xUnlock(pFile, eLock); } /* ** Check if another file-handle holds a RESERVED lock on a tmstmp-file. */ static int tmstmpCheckReservedLock(sqlite3_file *pFile, int *pResOut){ pFile = ORIGFILE(pFile); return pFile->pMethods->xCheckReservedLock(pFile, pResOut); } /* ** File control method. For custom operations on a tmstmp-file. */ static int tmstmpFileControl(sqlite3_file *pFile, int op, void *pArg){ int rc; TmstmpFile *p = (TmstmpFile*)pFile; pFile = ORIGFILE(pFile); rc = pFile->pMethods->xFileControl(pFile, op, pArg); switch( op ){ case SQLITE_FCNTL_VFSNAME: { if( p->hasCorrectReserve && rc==SQLITE_OK ){ *(char**)pArg = sqlite3_mprintf("tmstmp/%z", *(char**)pArg); } break; } case SQLITE_FCNTL_CKPT_START: { p->inCkpt = 1; assert( p->isDb ); assert( p->pPartner!=0 ); p->pPartner->inCkpt = 1; if( p->hasCorrectReserve ){ tmstmpEvent(p, ELOG_CKPT_START, 0, 0, 0); } rc = SQLITE_OK; break; } case SQLITE_FCNTL_CKPT_DONE: { p->inCkpt = 0; assert( p->isDb ); assert( p->pPartner!=0 ); p->pPartner->inCkpt = 0; if( p->hasCorrectReserve ){ tmstmpEvent(p, ELOG_CKPT_DONE, 0, 0, 0); } rc = SQLITE_OK; break; } } return rc; } /* ** Return the sector-size in bytes for a tmstmp-file. */ static int tmstmpSectorSize(sqlite3_file *pFile){ pFile = ORIGFILE(pFile); return pFile->pMethods->xSectorSize(pFile); } /* ** Return the device characteristic flags supported by a tmstmp-file. */ static int tmstmpDeviceCharacteristics(sqlite3_file *pFile){ int devchar = 0; pFile = ORIGFILE(pFile); devchar = pFile->pMethods->xDeviceCharacteristics(pFile); return (devchar & ~SQLITE_IOCAP_SUBPAGE_READ); } /* Create a shared memory file mapping */ static int tmstmpShmMap( sqlite3_file *pFile, int iPg, int pgsz, int bExtend, void volatile **pp ){ pFile = ORIGFILE(pFile); return pFile->pMethods->xShmMap(pFile,iPg,pgsz,bExtend,pp); } /* Perform locking on a shared-memory segment */ static int tmstmpShmLock(sqlite3_file *pFile, int offset, int n, int flags){ pFile = ORIGFILE(pFile); return pFile->pMethods->xShmLock(pFile,offset,n,flags); } /* Memory barrier operation on shared memory */ static void tmstmpShmBarrier(sqlite3_file *pFile){ pFile = ORIGFILE(pFile); pFile->pMethods->xShmBarrier(pFile); } /* Unmap a shared memory segment */ static int tmstmpShmUnmap(sqlite3_file *pFile, int deleteFlag){ pFile = ORIGFILE(pFile); return pFile->pMethods->xShmUnmap(pFile,deleteFlag); } /* Fetch a page of a memory-mapped file */ static int tmstmpFetch( sqlite3_file *pFile, sqlite3_int64 iOfst, int iAmt, void **pp ){ pFile = ORIGFILE(pFile); return pFile->pMethods->xFetch(pFile, iOfst, iAmt, pp); } /* Release a memory-mapped page */ static int tmstmpUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){ pFile = ORIGFILE(pFile); return pFile->pMethods->xUnfetch(pFile, iOfst, pPage); } /* ** Open a tmstmp file handle. */ static int tmstmpOpen( sqlite3_vfs *pVfs, const char *zName, sqlite3_file *pFile, int flags, int *pOutFlags ){ TmstmpFile *p, *pDb; sqlite3_file *pSubFile; sqlite3_vfs *pSubVfs; int rc; pSubVfs = ORIGVFS(pVfs); if( (flags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_WAL))==0 ){ /* If the file is not a persistent database or a WAL file, then ** bypass the timestamp logic all together */ return pSubVfs->xOpen(pSubVfs, zName, pFile, flags, pOutFlags); } if( (flags & SQLITE_OPEN_WAL)!=0 ){ pDb = (TmstmpFile*)sqlite3_database_file_object(zName); if( pDb==0 || pDb->uMagic!=TMSTMP_MAGIC || !pDb->isDb || pDb->pPartner!=0 ){ return pSubVfs->xOpen(pSubVfs, zName, pFile, flags, pOutFlags); } }else{ pDb = 0; } p = (TmstmpFile*)pFile; memset(p, 0, sizeof(*p)); pSubFile = ORIGFILE(pFile); pFile->pMethods = &tmstmp_io_methods; p->pSubVfs = pSubVfs; p->uMagic = TMSTMP_MAGIC; rc = pSubVfs->xOpen(pSubVfs, zName, pSubFile, flags, pOutFlags); if( rc ) goto tmstmp_open_done; if( pDb!=0 ){ p->isWal = 1; p->pPartner = pDb; pDb->pPartner = p; }else{ u32 r2; u32 pid; TmstmpLog *pLog; sqlite3_uint64 r1; /* Milliseconds since 1970-01-01 */ sqlite3_uint64 days; /* Days since 1970-01-01 */ sqlite3_uint64 sod; /* Start of date specified by ms */ sqlite3_uint64 z; /* Days since 0000-03-01 */ sqlite3_uint64 era; /* 400-year era */ int h; /* hour */ int m; /* minute */ int s; /* second */ int f; /* millisecond */ int Y; /* year */ int M; /* month */ int D; /* day */ int y; /* year assuming March is first month */ unsigned int doe; /* day of 400-year era */ unsigned int yoe; /* year of 400-year era */ unsigned int doy; /* day of year */ unsigned int mp; /* month with March==0 */ p->isDb = 1; r1 = 0; pLog = sqlite3_malloc64( sizeof(TmstmpLog) ); if( pLog==0 ){ return SQLITE_NOMEM; } memset(pLog, 0, sizeof(pLog[0])); p->pLog = pLog; p->pSubVfs->xCurrentTimeInt64(p->pSubVfs, (sqlite3_int64*)&r1); r1 -= 210866760000000LL; days = r1/86400000; sod = (r1%86400000)/1000; f = (int)(r1%1000); h = sod/3600; m = (sod%3600)/60; s = sod%60; z = days + 719468; era = z/147097; doe = (unsigned)(z - era*146097); yoe = (doe - doe/1460 + doe/36524 - doe/146096)/365; y = (int)yoe + era*400; doy = doe - (365*yoe + yoe/4 - yoe/100); mp = (5*doy + 2)/153; D = doy - (153*mp + 2)/5 + 1; M = mp + (mp<10 ? 3 : -9); Y = y + (M <=2); sqlite3_randomness(sizeof(r2), &r2); pid = GETPID; pLog->zLogname = sqlite3_mprintf( "%s-tmstmp/%04d%02d%02dT%02d%02d%02d%03d-%08d-%08x", zName, Y, M, D, h, m, s, f, pid, r2); } tmstmpEvent(p, p->isWal ? ELOG_OPEN_WAL : ELOG_OPEN_DB, 0, GETPID, 0); tmstmp_open_done: if( rc ) pFile->pMethods = 0; return rc; } /* ** All VFS interfaces other than xOpen are passed down into the Sub-VFS. */ static int tmstmpDelete(sqlite3_vfs *p, const char *zName, int syncDir){ sqlite3_vfs *pSub = ORIGVFS(p); return pSub->xDelete(pSub,zName,syncDir); } static int tmstmpAccess(sqlite3_vfs *p, const char *zName, int flags, int *pR){ sqlite3_vfs *pSub = ORIGVFS(p); return pSub->xAccess(pSub,zName,flags,pR); } static int tmstmpFullPathname(sqlite3_vfs*p,const char *zName,int n,char *zOut){ sqlite3_vfs *pSub = ORIGVFS(p); return pSub->xFullPathname(pSub,zName,n,zOut); } static void *tmstmpDlOpen(sqlite3_vfs *p, const char *zFilename){ sqlite3_vfs *pSub = ORIGVFS(p); return pSub->xDlOpen(pSub,zFilename); } static void tmstmpDlError(sqlite3_vfs *p, int nByte, char *zErrMsg){ sqlite3_vfs *pSub = ORIGVFS(p); return pSub->xDlError(pSub,nByte,zErrMsg); } static void(*tmstmpDlSym(sqlite3_vfs *p, void *pDl, const char *zSym))(void){ sqlite3_vfs *pSub = ORIGVFS(p); return pSub->xDlSym(pSub,pDl,zSym); } static void tmstmpDlClose(sqlite3_vfs *p, void *pDl){ sqlite3_vfs *pSub = ORIGVFS(p); return pSub->xDlClose(pSub,pDl); } static int tmstmpRandomness(sqlite3_vfs *p, int nByte, char *zOut){ sqlite3_vfs *pSub = ORIGVFS(p); return pSub->xRandomness(pSub,nByte,zOut); } static int tmstmpSleep(sqlite3_vfs *p, int microseconds){ sqlite3_vfs *pSub = ORIGVFS(p); return pSub->xSleep(pSub,microseconds); } static int tmstmpCurrentTime(sqlite3_vfs *p, double *prNow){ sqlite3_vfs *pSub = ORIGVFS(p); return pSub->xCurrentTime(pSub,prNow); } static int tmstmpGetLastError(sqlite3_vfs *p, int a, char *b){ sqlite3_vfs *pSub = ORIGVFS(p); return pSub->xGetLastError(pSub,a,b); } static int tmstmpCurrentTimeInt64(sqlite3_vfs *p, sqlite3_int64 *piNow){ sqlite3_vfs *pSub = ORIGVFS(p); return pSub->xCurrentTimeInt64(pSub,piNow); } static int tmstmpSetSystemCall(sqlite3_vfs *p, const char *zName, sqlite3_syscall_ptr x){ sqlite3_vfs *pSub = ORIGVFS(p); return pSub->xSetSystemCall(pSub,zName,x); } static sqlite3_syscall_ptr tmstmpGetSystemCall(sqlite3_vfs *p, const char *z){ sqlite3_vfs *pSub = ORIGVFS(p); return pSub->xGetSystemCall(pSub,z); } static const char *tmstmpNextSystemCall(sqlite3_vfs *p, const char *zName){ sqlite3_vfs *pSub = ORIGVFS(p); return pSub->xNextSystemCall(pSub,zName); } /* ** Register the tmstmp VFS as the default VFS for the system. */ static int tmstmpRegisterVfs(void){ int rc = SQLITE_OK; sqlite3_vfs *pOrig = sqlite3_vfs_find(0); if( pOrig==0 ) return SQLITE_ERROR; if( pOrig==&tmstmp_vfs ) return SQLITE_OK; tmstmp_vfs.iVersion = pOrig->iVersion; tmstmp_vfs.pAppData = pOrig; tmstmp_vfs.szOsFile = pOrig->szOsFile + sizeof(TmstmpFile); rc = sqlite3_vfs_register(&tmstmp_vfs, 1); return rc; } #if defined(SQLITE_TMSTMPVFS_STATIC) /* This variant of the initializer runs when the extension is ** statically linked. */ int sqlite3_register_tmstmpvfs(const char *NotUsed){ (void)NotUsed; return tmstmpRegisterVfs(); } int sqlite3_unregister_tmstmpvfs(void){ if( sqlite3_vfs_find("tmstmpvfs") ){ sqlite3_vfs_unregister(&tmstmp_vfs); } return SQLITE_OK; } #endif /* defined(SQLITE_TMSTMPVFS_STATIC */ #if !defined(SQLITE_TMSTMPVFS_STATIC) /* This variant of the initializer function is used when the ** extension is shared library to be loaded at run-time. */ #ifdef _WIN32 __declspec(dllexport) #endif /* ** This routine is called by sqlite3_load_extension() when the ** extension is first loaded. ***/ int sqlite3_tmstmpvfs_init( sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi ){ int rc; SQLITE_EXTENSION_INIT2(pApi); (void)pzErrMsg; /* not used */ (void)db; /* not used */ rc = tmstmpRegisterVfs(); if( rc==SQLITE_OK ) rc = SQLITE_OK_LOAD_PERMANENTLY; return rc; } #endif /* !defined(SQLITE_TMSTMPVFS_STATIC) */