]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add the experimental tmstmpvfs.c extension that adds a timestamp and other
authordrh <>
Mon, 5 Jan 2026 17:28:33 +0000 (17:28 +0000)
committerdrh <>
Mon, 5 Jan 2026 17:28:33 +0000 (17:28 +0000)
debugging information on each page of a database as that page is written,
if the database is configured for exactly 16 bytes of reserve space.

FossilOrigin-Name: 703e593dfc92676a2d44c67ca8282a78a1943b9c1e3f2447c0a9917d70a383c4

ext/misc/tmstmpvfs.c [new file with mode: 0644]
manifest
manifest.tags
manifest.uuid

diff --git a/ext/misc/tmstmpvfs.c b/ext/misc/tmstmpvfs.c
new file mode 100644 (file)
index 0000000..661a2f6
--- /dev/null
@@ -0,0 +1,684 @@
+/*
+** 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 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.
+**
+** 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.
+**
+** 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.
+**
+** 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;".
+**
+** Note that SQLite allows the number of reserve-bytes to be
+** increased but not decreased.  So if a database file already
+** has a reserve-bytes value greater than 16, there is no way to
+** activate timestamping on that database, other than to dump
+** and restore the database file.  Note also that other extensions
+** might also make use of the reserve-bytes.  Timestamping will
+** be incompatible with those other extensions.
+**
+** 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, this module is a no-op.
+**
+** The timestamp layout is as follows:
+**
+**   bytes 0-5     Milliseconds since the Unix Epoch
+**   bytes 6-9     WAL frame number
+**   bytes 10-12   Lower 24 bits of Salt-1
+**   byte  13      bit 0 (0x01) set if last frame of a transaction
+**   bytes 14,15   Reserved for future expansion
+**
+** For transactions that occur in rollback mode, bytes 6-15 are all
+** zero.
+*/
+#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 <string.h>
+#include <assert.h>
+
+
+/*
+** Forward declaration of objects used by this utility
+*/
+typedef struct sqlite3_vfs TmstmpVfs;
+typedef struct TmstmpFile TmstmpFile;
+
+/*
+** 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
+
+/* 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))
+
+/* 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 */
+  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 */
+  TmstmpFile *pPartner;  /* DB->WAL or WAL->DB mapping */
+  sqlite3_int64 iOfst;   /* Offset of last WAL frame header */
+  sqlite3_vfs *pSubVfs;  /* Underlying VFS */
+};
+
+
+/*
+** 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 */
+};
+
+/*
+** SQL function:    tmstmp_init(SCHEMANAME)
+**
+** This SQL functions invokes:
+**
+**   sqlite3_file_control(db, SCHEMANAME, SQLITE_FCNTL_RESERVE_BYTE, &n);
+**
+** In order to set the reserve bytes value to 16, so that tmstmpvfs will
+** operation.  This interface is undocumented, apart from this comment.
+** Usage example:
+**
+**    SELECT tmstmp_init('main'); VACUUM;
+*/
+static void tmstmpInitFunc(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  int nByte = TMSTMP_RESERVE;
+  const char *zSchemaName = (const char*)sqlite3_value_text(argv[0]);
+  sqlite3 *db = sqlite3_context_db_handle(context);
+  sqlite3_file_control(db, zSchemaName, SQLITE_FCNTL_RESERVE_BYTES, &nByte);
+}
+
+
+/*
+** Close a connection
+*/
+static int tmstmpClose(sqlite3_file *pFile){
+  TmstmpFile *p = (TmstmpFile *)pFile;
+  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
+   && p->isDb
+   && iOfst==0
+   && iAmt>=100
+  ){
+    p->hasCorrectReserve = (((u8*)zBuf)[20]==TMSTMP_RESERVE);
+    if( p->pPartner ){
+      p->pPartner->hasCorrectReserve = p->hasCorrectReserve;
+    }
+  }
+  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;
+      memcpy(&p->iFrame, zBuf, 4);
+      memcpy(&p->salt1, zBuf+8, 4);
+      memcpy(&x, zBuf+4, 4);
+      p->isCommit = (x!=0);
+      p->iOfst = iOfst;
+    }else if( iAmt>=512 && iOfst==p->iOfst+24 ){
+      sqlite3_uint64 tm = 0;
+      int rc;
+      unsigned char *s = (unsigned char*)zBuf+iAmt-TMSTMP_RESERVE;
+      p->pSubVfs->xCurrentTimeInt64(p->pSubVfs, &tm);
+      memset(s, 0, TMSTMP_RESERVE);
+      s[0] = (tm>>40)&0xff;
+      s[1] = (tm>>32)&0xff;
+      s[2] = (tm>>24)&0xff;
+      s[3] = (tm>>16)&0xff;
+      s[4] = (tm>>8)&0xff;
+      s[5] = tm&0xff;
+      memcpy(&s[9], &p->salt1, 4);
+      memcpy(&s[6], &p->iFrame, 4);
+      s[13] = p->isCommit ? 1 : 0;
+    }
+  }else if( p->pPartner==0 ){
+    /* Writing into a database in rollback mode */
+    sqlite3_uint64 tm = 0;
+    unsigned char *s = (unsigned char*)zBuf+iAmt-TMSTMP_RESERVE;
+    p->pSubVfs->xCurrentTimeInt64(p->pSubVfs, &tm);
+    memset(s, 0, TMSTMP_RESERVE);
+    s[0] = (tm>>40)&0xff;
+    s[1] = (tm>>32)&0xff;
+    s[2] = (tm>>24)&0xff;
+    s[3] = (tm>>16)&0xff;
+    s[4] = (tm>>8)&0xff;
+    s[5] = tm&0xff;
+  }
+  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);
+  if( rc==SQLITE_OK
+   && op==SQLITE_FCNTL_VFSNAME
+   && p->hasCorrectReserve
+  ){
+    *(char**)pArg = sqlite3_mprintf("tmstmp/%z", *(char**)pArg);
+  }
+  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{
+    p->isDb = 1;
+  }
+
+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);
+    sqlite3_cancel_auto_extension((void(*)(void))tmstmpRegisterFunc);
+  }
+  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 */
+#ifdef SQLITE_TMSTMPVFS_INIT_FUNCNAME
+  rc = sqlite3_create_function(db, SQLITE_TMSTMPVFS_INIT_FUNCTNAME, 1,
+                               SQLITE_UTF8, 0, tmstmpInitFunc, 0, 0);
+  if( rc ) return rc;
+#endif
+  rc = tmstmpRegisterVfs();
+  if( rc==SQLITE_OK ) rc = SQLITE_OK_LOAD_PERMANENTLY;
+  return rc;
+}
+#endif /* !defined(SQLITE_TMSTMPVFS_STATIC) */
index bdb09cfb8f58aea2586b8e2d3e26b5186ce48f53..0877dc60564171b32533f72c29bf9b4111468aa7 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Fix\san\suninitialized\svar\suse\sreported\sin\s[forum:88561a4a1e|forum\spost\s88561a4a1e].
-D 2026-01-04T11:05:05.639
+C Add\sthe\sexperimental\stmstmpvfs.c\sextension\sthat\sadds\sa\stimestamp\sand\sother\ndebugging\sinformation\son\seach\spage\sof\sa\sdatabase\sas\sthat\spage\sis\swritten,\nif\sthe\sdatabase\sis\sconfigured\sfor\sexactly\s16\sbytes\sof\sreserve\sspace.
+D 2026-01-05T17:28:33.434
 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -402,6 +402,7 @@ F ext/misc/sqlite3_stdio.h 27a4ecea47e61bc9574ccdf2806f468afe23af2f95028c9b689bf
 F ext/misc/stmt.c b090086cd6bd6281c21271d38d576eeffe662f0e6b67536352ce32bbaa438321
 F ext/misc/stmtrand.c 59cffa5d8e158943ff1ce078956d8e208e8c04e67307e8f249dece2436dcb7fc
 F ext/misc/templatevtab.c 10f15b165b95423ddef593bc5dcb915ec4eb5e0f1066d585e5435a368b8bc22b
+F ext/misc/tmstmpvfs.c 742c76f7e6705c547a66932186decf3097a5c3e36a57b84582c64c95994f54f8
 F ext/misc/totype.c ba11aac3c0b52c685bd25aa4e0f80c41c624fb1cc5ab763250e09ddc762bc3a8
 F ext/misc/uint.c 327afc166058acf566f33a15bf47c869d2d3564612644d9ff81a23efc8b36039
 F ext/misc/unionvtab.c 716d385256d5fb4beea31b0efede640807e423e85c9784d21d22f0cce010a785
@@ -2189,8 +2190,11 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee
 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
 F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c
-P f60e863e0ca2d8ab853fa5f48d3cd7b062b13167fcddffc4563bde9285b92320
-R 4acf967c5b59de5937342771fb22d43f
-U stephan
-Z d4f1135862bcdc1161b210d6bfccd00f
+P 707c0f6442e946f23de061ee2753eb5994ab55d411c49b232799f309ba0f10cf
+R eebbc04e39ff5bc6f1027ece05be9308
+T *branch * timestamp-vfs
+T *sym-timestamp-vfs *
+T -sym-trunk *
+U drh
+Z edde794cfb79a18a07337a2e938e02dc
 # Remove this line to create a well-formed Fossil manifest.
index bec971799ff1b8ee641c166c7aeb22d12c785393..72032ee788ac0cbc18d68185f1617ed1fff5aa48 100644 (file)
@@ -1,2 +1,2 @@
-branch trunk
-tag trunk
+branch timestamp-vfs
+tag timestamp-vfs
index 5737a874f4b31ef9fd354cec375b1af43e240f01..5eb1b96438f443d2db2564824ebac14afc0ca616 100644 (file)
@@ -1 +1 @@
-707c0f6442e946f23de061ee2753eb5994ab55d411c49b232799f309ba0f10cf
+703e593dfc92676a2d44c67ca8282a78a1943b9c1e3f2447c0a9917d70a383c4