Fossil SCM

fossil-scm / extsrc / tmstmpvfs.c
Blame History Raw 1043 lines
1
/*
2
** 2026-01-05
3
**
4
** The author disclaims copyright to this source code. In place of
5
** a legal notice, here is a blessing:
6
**
7
** May you do good and not evil.
8
** May you find forgiveness for yourself and forgive others.
9
** May you share freely, never taking more than you give.
10
**
11
******************************************************************************
12
**
13
** This file implements a VFS shim that writes a timestamp and other tracing
14
** information into 16 byts of reserved space at the end of each page of the
15
** database file.
16
**
17
** The VFS also tries to generate log-files with names of the form:
18
**
19
** $(DATABASE)-tmstmp/$(TIME)-$(PID)-$(ID)
20
**
21
** Log files are only generated if directory $(DATABASE)-tmstmp exists.
22
** The name of each log file is the current ISO8601 time in milliseconds,
23
** the process ID, and a random 32-bit value (to disambiguate multiple
24
** connections from the same process) separated by dashes. The log file
25
** contains 16-bytes records for various events, such as opening or close
26
** of the database or WAL file, writes to the WAL file, checkpoints, and
27
** similar. The logfile is only generated if the connection attempts to
28
** modify the database. There is a separate log file for each open database
29
** connection.
30
**
31
** COMPILING
32
**
33
** To build this extension as a separately loaded shared library or
34
** DLL, use compiler command-lines similar to the following:
35
**
36
** (linux) gcc -fPIC -shared tmstmpvfs.c -o tmstmpvfs.so
37
** (mac) clang -fPIC -dynamiclib tmstmpvfs.c -o tmstmpvfs.dylib
38
** (windows) cl tmstmpvfs.c -link -dll -out:tmstmpvfs.dll
39
**
40
** You may want to add additional compiler options, of course,
41
** according to the needs of your project.
42
**
43
** Another option is to statically link both SQLite and this extension
44
** into your application. If both this file and "sqlite3.c" are statically
45
** linked, and if "sqlite3.c" is compiled with an option like:
46
**
47
** -DSQLITE_EXTRA_INIT=sqlite3_register_tmstmpvfs
48
**
49
** Then SQLite will use the tmstmp VFS by default throughout your
50
** application.
51
**
52
** LOADING
53
**
54
** To load this extension as a shared library, you first have to
55
** bring up a dummy SQLite database connection to use as the argument
56
** to the sqlite3_load_extension() API call. Then you invoke the
57
** sqlite3_load_extension() API and shutdown the dummy database
58
** connection. All subsequent database connections that are opened
59
** will include this extension. For example:
60
**
61
** sqlite3 *db;
62
** sqlite3_open(":memory:", &db);
63
** sqlite3_load_extension(db, "./tmstmpvfs");
64
** sqlite3_close(db);
65
**
66
** Tmstmpvfs is a VFS Shim. When loaded, "tmstmpvfs" becomes the new
67
** default VFS and it uses the prior default VFS as the next VFS
68
** down in the stack. This is normally what you want. However, in
69
** complex situations where multiple VFS shims are being loaded,
70
** it might be important to ensure that tmstmpvfs is loaded in the
71
** correct order so that it sequences itself into the default VFS
72
** Shim stack in the right order.
73
**
74
** When running the CLI, you can load this extension at invocation by
75
** adding a command-line option like this: "--vfs ./tmstmpvfs.so".
76
** The --vfs option usually specifies the symbolic name of a built-in VFS.
77
** But if the argument to --vfs is not a built-in VFS but is instead the
78
** name of a file, the CLI tries to load that file as an extension. Note
79
** that the full name of the extension file must be provided, including
80
** the ".so" or ".dylib" or ".dll" suffix.
81
**
82
** An application can see if the tmstmpvfs is being used by examining
83
** the results from SQLITE_FCNTL_VFSNAME (or the .vfsname command in
84
** the CLI). If the answer include "tmstmp", then this VFS is being
85
** used.
86
**
87
** USING
88
**
89
** Open database connections using the sqlite3_open() or
90
** sqlite3_open_v2() interfaces, as normal. Ordinary database files
91
** (without a timestamp) will operate normally.
92
**
93
** Timestamping only works on databases that have a reserve-bytes
94
** value of exactly 16. The default value for reserve-bytes is 0.
95
** Hence, newly created database files will omit the timestamp by
96
** default. To create a database that includes a timestamp, change
97
** the reserve-bytes value to 16 by running:
98
**
99
** int n = 16;
100
** sqlite3_file_control(db, 0, SQLITE_FCNTL_RESERVE_BYTES, &n);
101
**
102
** If you do this immediately after creating a new database file,
103
** before anything else has been written into the file, then that
104
** might be all that you need to do. Otherwise, the API call
105
** above should be followed by:
106
**
107
** sqlite3_exec(db, "VACUUM", 0, 0, 0);
108
**
109
** It never hurts to run the VACUUM, even if you don't need it.
110
**
111
** From the CLI, use the ".filectrl reserve_bytes 16" command,
112
** followed by "VACUUM;".
113
**
114
** SQLite allows the number of reserve-bytes to be increased, but
115
** not decreased. If you want to restore the reserve-bytes to 0
116
** (to disable tmstmpvfs), the easiest approach is to use VACUUM INTO
117
** with a URI filename as the argument and include "reserve=0" query
118
** parameter on the URI. Example:
119
**
120
** VACUUM INTO 'file:notimestamps.db?reserve=0';
121
**
122
** Then switch over to using the new database file. The reserve=0 query
123
** parameter only works on SQLite 3.52.0 and later.
124
**
125
** IMPLEMENTATION NOTES
126
**
127
** The timestamp information is stored in the last 16 bytes of each page.
128
** This module only operates if the "bytes of reserved space on each page"
129
** value at offset 20 the SQLite database header is exactly 16. If
130
** the reserved-space value is not 16, no timestamp information is added
131
** to database pages. Some, but not all, logfile entries will be made
132
** still, but the size of the logs will be greatly reduced.
133
**
134
** The timestamp layout is as follows:
135
**
136
** bytes 0,1 Zero. Reserved for future expansion
137
** bytes 2-7 Milliseconds since the Unix Epoch
138
** bytes 8-11 WAL frame number
139
** bytes 12 0: WAL write 2: rollback write
140
** bytes 13-15 Lower 24 bits of Salt-1
141
**
142
** For transactions that occur in rollback mode, only the timestamp
143
** in bytes 2-7 and byte 12 are non-zero. Byte 12 is set to 2 for
144
** rollback writes.
145
**
146
** The 16-byte tag is added to each database page when the content
147
** is written into the database file itself. This shim does not make
148
** any changes to the page as it is written to the WAL file, since
149
** that would mess up the WAL checksum.
150
**
151
** LOGGING
152
**
153
** An open database connection that attempts to write to the database
154
** will create a log file if a directory name $(DATABASE)-tmstmp exists.
155
** The name of the log file is:
156
**
157
** $(TIME)-$(PID)-$(RANDOM)
158
**
159
** Where TIME is an ISO 8601 date in milliseconds with no punctuation,
160
** PID is the process ID, and RANDOM is a 32-bit random number expressed
161
** as hexadecimal.
162
**
163
** The log consists of 16-byte records. Each record consists of five
164
** unsigned integers:
165
**
166
** 1 1 6 4 4 <--- bytes
167
** op a1 ts a2 a3
168
**
169
** The meanings of the a1-a3 values depend on op. ts is the timestamp
170
** in milliseconds since the unix epoch (1970-01-01 00:00:00).
171
** Opcodes are defined by the ELOG_* #defines below.
172
**
173
** ELOG_OPEN_DB "Open a connection to the database file"
174
** op = 0x01
175
** a2 = process-ID
176
**
177
** ELOG_OPEN_WAL "Open a connection to the -wal file"
178
** op = 0x02
179
** a2 = process-ID
180
**
181
** ELOG_WAL_PAGE "New page added to the WAL file"
182
** op = 0x03
183
** a1 = 1 if last page of a txn. 0 otherwise.
184
** a2 = page number in the DB file
185
** a3 = frame number in the WAL file
186
**
187
** ELOG_DB_PAGE "Database page updated using rollback mode"
188
** op = 0x04
189
** a2 = page number in the DB file
190
**
191
** ELOG_CKPT_START "Start of a checkpoint operation"
192
** op = 0x05
193
**
194
** ELOG_CKPT_PAGE "Page xfer from WAL to database"
195
** op = 0x06
196
** a2 = database page number
197
** a3 = frame number in the WAL file
198
**
199
** ELOG_CKPT_END "Start of a checkpoint operation"
200
** op = 0x07
201
**
202
** ELOG_WAL_RESET "WAL file header overwritten"
203
** op = 0x08
204
** a3 = Salt1 value
205
**
206
** ELOG_CLOSE_WAL "Close the WAL file connection"
207
** op = 0x0e
208
**
209
** ELOG_CLOSE_DB "Close the DB connection"
210
** op = 0x0f
211
**
212
** VIEWING TIMESTAMPS AND LOGS
213
**
214
** The command-line utility at tool/showtmlog.c will read and display
215
** the content of one or more tmstmpvfs.c log files. If all of the
216
** log files are stored in directory $(DATABASE)-tmstmp, then you can
217
** view them all using a command like shown below (with an extra "?"
218
** inserted on the wildcard to avoid closing the C-language comment
219
** that contains this text):
220
**
221
** showtmlog $(DATABASE)-tmstmp/?*
222
**
223
** The command-line utility at tools/showdb.c can be used to show the
224
** timestamps on pages of a database file, using a command like this:
225
**
226
** showdb --tmstmp $(DATABASE) pgidx
227
*
228
** The command above shows the timestamp and the intended use of every
229
** pages in the database, in human-readable form. If you also add
230
** the --csv option to the command above, then the command generates
231
** a Comma-Separated-Value (CSV) file as output, which contains a
232
** decoding of the complete timestamp tag on each page of the database.
233
** This CVS file can be easily imported into another SQLite database
234
** using a CLI command like the following:
235
**
236
** .import --csv '|showdb --tmstmp -csv orig.db pgidx' ts_table
237
**
238
** In the command above, the database containing the timestamps is
239
** "orig.db" and the content is imported into a new table named "ts_table".
240
** The "ts_table" is created automatically, using the column names found
241
** in the first line of the CSV file. All columns of the automatically
242
** created ts_table are of type TEXT. It might make more sense to
243
** create the table yourself, using more sensible datatypes, like this:
244
**
245
** CREATE TABLE ts_table (
246
** pgno INT, -- page number
247
** tm REAL, -- seconds since 1970-01-01
248
** frame INT, -- WAL frame number
249
** flg INT, -- flag (tag byte 12)
250
** salt INT, -- WAL salt (tag bytes 13-15)
251
** parent INT, -- Parent page number
252
** child INT, -- Index of this page in its parent
253
** ovfl INT, -- Index of this page on the overflow chain
254
** txt TEXT -- Description of this page
255
** );
256
**
257
** Then import using:
258
**
259
** .import --csv --skip 1 '|showdb --tmstmp --csv orig.db pgidx' ts_table
260
**
261
** Note the addition of the "--skip 1" option on ".import" to bypass the
262
** first line of the CSV file that contains the column names.
263
**
264
** Both programs "showdb" and "showtmlog" can be built by running
265
** "make showtmlog showdb" from the top-level of a recent SQLite
266
** source tree.
267
*/
268
#if defined(SQLITE_AMALGAMATION) && !defined(SQLITE_TMSTMPVFS_STATIC)
269
# define SQLITE_TMSTMPVFS_STATIC
270
#endif
271
#ifdef SQLITE_TMSTMPVFS_STATIC
272
# include "sqlite3.h"
273
#else
274
# include "sqlite3ext.h"
275
SQLITE_EXTENSION_INIT1
276
#endif
277
#include <string.h>
278
#include <assert.h>
279
#include <stdio.h>
280
281
/*
282
** Forward declaration of objects used by this utility
283
*/
284
typedef struct sqlite3_vfs TmstmpVfs;
285
typedef struct TmstmpFile TmstmpFile;
286
typedef struct TmstmpLog TmstmpLog;
287
288
/*
289
** Bytes of reserved space used by this extension
290
*/
291
#define TMSTMP_RESERVE 16
292
293
/*
294
** The magic number used to identify TmstmpFile objects
295
*/
296
#define TMSTMP_MAGIC 0x2a87b72d
297
298
/*
299
** Useful datatype abbreviations
300
*/
301
#if !defined(SQLITE_AMALGAMATION)
302
typedef unsigned char u8;
303
typedef unsigned int u32;
304
#endif
305
306
/*
307
** Current process id
308
*/
309
#if defined(_WIN32)
310
# include <windows.h>
311
# define GETPID (u32)GetCurrentProcessId()
312
#else
313
# include <unistd.h>
314
# define GETPID (u32)getpid()
315
#endif
316
317
/* Access to a lower-level VFS that (might) implement dynamic loading,
318
** access to randomness, etc.
319
*/
320
#define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData))
321
#define ORIGFILE(p) ((sqlite3_file*)(((TmstmpFile*)(p))+1))
322
323
/* Information for the tmstmp log file. */
324
struct TmstmpLog {
325
char *zLogname; /* Log filename */
326
FILE *log; /* Open log file */
327
int n; /* Bytes of a[] used */
328
unsigned char a[16*6]; /* Buffered header for the log */
329
};
330
331
/* An open WAL or DB file */
332
struct TmstmpFile {
333
sqlite3_file base; /* IO methods */
334
u32 uMagic; /* Magic number for sanity checking */
335
u32 salt1; /* Last WAL salt-1 value */
336
u32 iFrame; /* Last WAL frame number */
337
u32 pgno; /* Current page number */
338
u32 pgsz; /* Size of each page, in bytes */
339
u8 isWal; /* True if this is a WAL file */
340
u8 isDb; /* True if this is a DB file */
341
u8 isCommit; /* Last WAL frame header was a transaction commit */
342
u8 hasCorrectReserve; /* File has the correct reserve size */
343
u8 inCkpt; /* True if in a checkpoint */
344
TmstmpLog *pLog; /* Log file */
345
TmstmpFile *pPartner; /* DB->WAL or WAL->DB mapping */
346
sqlite3_int64 iOfst; /* Offset of last WAL frame header */
347
sqlite3_vfs *pSubVfs; /* Underlying VFS */
348
};
349
350
/*
351
** Event log opcodes
352
*/
353
#define ELOG_OPEN_DB 0x01
354
#define ELOG_OPEN_WAL 0x02
355
#define ELOG_WAL_PAGE 0x03
356
#define ELOG_DB_PAGE 0x04
357
#define ELOG_CKPT_START 0x05
358
#define ELOG_CKPT_PAGE 0x06
359
#define ELOG_CKPT_DONE 0x07
360
#define ELOG_WAL_RESET 0x08
361
#define ELOG_CLOSE_WAL 0x0e
362
#define ELOG_CLOSE_DB 0x0f
363
364
/*
365
** Methods for TmstmpFile
366
*/
367
static int tmstmpClose(sqlite3_file*);
368
static int tmstmpRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
369
static int tmstmpWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst);
370
static int tmstmpTruncate(sqlite3_file*, sqlite3_int64 size);
371
static int tmstmpSync(sqlite3_file*, int flags);
372
static int tmstmpFileSize(sqlite3_file*, sqlite3_int64 *pSize);
373
static int tmstmpLock(sqlite3_file*, int);
374
static int tmstmpUnlock(sqlite3_file*, int);
375
static int tmstmpCheckReservedLock(sqlite3_file*, int *pResOut);
376
static int tmstmpFileControl(sqlite3_file*, int op, void *pArg);
377
static int tmstmpSectorSize(sqlite3_file*);
378
static int tmstmpDeviceCharacteristics(sqlite3_file*);
379
static int tmstmpShmMap(sqlite3_file*, int iPg, int pgsz, int, void volatile**);
380
static int tmstmpShmLock(sqlite3_file*, int offset, int n, int flags);
381
static void tmstmpShmBarrier(sqlite3_file*);
382
static int tmstmpShmUnmap(sqlite3_file*, int deleteFlag);
383
static int tmstmpFetch(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp);
384
static int tmstmpUnfetch(sqlite3_file*, sqlite3_int64 iOfst, void *p);
385
386
/*
387
** Methods for TmstmpVfs
388
*/
389
static int tmstmpOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
390
static int tmstmpDelete(sqlite3_vfs*, const char *zName, int syncDir);
391
static int tmstmpAccess(sqlite3_vfs*, const char *zName, int flags, int *);
392
static int tmstmpFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut);
393
static void *tmstmpDlOpen(sqlite3_vfs*, const char *zFilename);
394
static void tmstmpDlError(sqlite3_vfs*, int nByte, char *zErrMsg);
395
static void (*tmstmpDlSym(sqlite3_vfs *pVfs, void *p, const char*zSym))(void);
396
static void tmstmpDlClose(sqlite3_vfs*, void*);
397
static int tmstmpRandomness(sqlite3_vfs*, int nByte, char *zOut);
398
static int tmstmpSleep(sqlite3_vfs*, int microseconds);
399
static int tmstmpCurrentTime(sqlite3_vfs*, double*);
400
static int tmstmpGetLastError(sqlite3_vfs*, int, char *);
401
static int tmstmpCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*);
402
static int tmstmpSetSystemCall(sqlite3_vfs*, const char*,sqlite3_syscall_ptr);
403
static sqlite3_syscall_ptr tmstmpGetSystemCall(sqlite3_vfs*, const char *z);
404
static const char *tmstmpNextSystemCall(sqlite3_vfs*, const char *zName);
405
406
static sqlite3_vfs tmstmp_vfs = {
407
3, /* iVersion (set when registered) */
408
0, /* szOsFile (set when registered) */
409
1024, /* mxPathname */
410
0, /* pNext */
411
"tmstmpvfs", /* zName */
412
0, /* pAppData (set when registered) */
413
tmstmpOpen, /* xOpen */
414
tmstmpDelete, /* xDelete */
415
tmstmpAccess, /* xAccess */
416
tmstmpFullPathname, /* xFullPathname */
417
tmstmpDlOpen, /* xDlOpen */
418
tmstmpDlError, /* xDlError */
419
tmstmpDlSym, /* xDlSym */
420
tmstmpDlClose, /* xDlClose */
421
tmstmpRandomness, /* xRandomness */
422
tmstmpSleep, /* xSleep */
423
tmstmpCurrentTime, /* xCurrentTime */
424
tmstmpGetLastError, /* xGetLastError */
425
tmstmpCurrentTimeInt64, /* xCurrentTimeInt64 */
426
tmstmpSetSystemCall, /* xSetSystemCall */
427
tmstmpGetSystemCall, /* xGetSystemCall */
428
tmstmpNextSystemCall /* xNextSystemCall */
429
};
430
431
static const sqlite3_io_methods tmstmp_io_methods = {
432
3, /* iVersion */
433
tmstmpClose, /* xClose */
434
tmstmpRead, /* xRead */
435
tmstmpWrite, /* xWrite */
436
tmstmpTruncate, /* xTruncate */
437
tmstmpSync, /* xSync */
438
tmstmpFileSize, /* xFileSize */
439
tmstmpLock, /* xLock */
440
tmstmpUnlock, /* xUnlock */
441
tmstmpCheckReservedLock, /* xCheckReservedLock */
442
tmstmpFileControl, /* xFileControl */
443
tmstmpSectorSize, /* xSectorSize */
444
tmstmpDeviceCharacteristics, /* xDeviceCharacteristics */
445
tmstmpShmMap, /* xShmMap */
446
tmstmpShmLock, /* xShmLock */
447
tmstmpShmBarrier, /* xShmBarrier */
448
tmstmpShmUnmap, /* xShmUnmap */
449
tmstmpFetch, /* xFetch */
450
tmstmpUnfetch /* xUnfetch */
451
};
452
453
/*
454
** Write a 6-byte millisecond timestamp into aOut[]
455
*/
456
static void tmstmpPutTS(TmstmpFile *p, unsigned char *aOut){
457
sqlite3_uint64 tm = 0;
458
p->pSubVfs->xCurrentTimeInt64(p->pSubVfs, (sqlite3_int64*)&tm);
459
tm -= 210866760000000LL;
460
aOut[0] = (tm>>40)&0xff;
461
aOut[1] = (tm>>32)&0xff;
462
aOut[2] = (tm>>24)&0xff;
463
aOut[3] = (tm>>16)&0xff;
464
aOut[4] = (tm>>8)&0xff;
465
aOut[5] = tm&0xff;
466
}
467
468
/*
469
** Read a 32-bit big-endian unsigned integer and return it.
470
*/
471
static u32 tmstmpGetU32(const unsigned char *a){
472
return (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3];
473
}
474
475
/* Write a 32-bit integer as big-ending into a[]
476
*/
477
static void tmstmpPutU32(u32 v, unsigned char *a){
478
a[0] = (v>>24) & 0xff;
479
a[1] = (v>>16) & 0xff;
480
a[2] = (v>>8) & 0xff;
481
a[3] = v & 0xff;
482
}
483
484
/* Free a TmstmpLog object */
485
static void tmstmpLogFree(TmstmpLog *pLog){
486
if( pLog==0 ) return;
487
if( pLog->log ) fclose(pLog->log);
488
sqlite3_free(pLog->zLogname);
489
sqlite3_free(pLog);
490
}
491
492
/* Flush log content. Open the file if necessary. Return the
493
** number of errors. */
494
static int tmstmpLogFlush(TmstmpFile *p){
495
TmstmpLog *pLog = p->pLog;
496
assert( pLog!=0 );
497
if( pLog->log==0 ){
498
pLog->log = fopen(pLog->zLogname, "wb");
499
if( pLog->log==0 ){
500
tmstmpLogFree(pLog);
501
p->pLog = 0;
502
return 1;
503
}
504
}
505
(void)fwrite(pLog->a, pLog->n, 1, pLog->log);
506
fflush(pLog->log);
507
pLog->n = 0;
508
return 0;
509
}
510
511
/*
512
** Write a record onto the event log
513
*/
514
static void tmstmpEvent(
515
TmstmpFile *p,
516
u8 op,
517
u8 a1,
518
u32 a2,
519
u32 a3,
520
u8 *pTS
521
){
522
unsigned char *a;
523
TmstmpLog *pLog;
524
if( p->isWal ){
525
p = p->pPartner;
526
assert( p!=0 );
527
assert( p->isDb );
528
}
529
pLog = p->pLog;
530
if( pLog==0 ) return;
531
if( pLog->n >= (int)sizeof(pLog->a) ){
532
if( tmstmpLogFlush(p) ) return;
533
}
534
a = pLog->a + pLog->n;
535
a[0] = op;
536
a[1] = a1;
537
if( pTS ){
538
memcpy(a+2, pTS, 6);
539
}else{
540
tmstmpPutTS(p, a+2);
541
}
542
tmstmpPutU32(a2, a+8);
543
tmstmpPutU32(a3, a+12);
544
pLog->n += 16;
545
if( pLog->log || (op>=ELOG_WAL_PAGE && op<=ELOG_WAL_RESET) ){
546
(void)tmstmpLogFlush(p);
547
}
548
}
549
550
/*
551
** Close a connection
552
*/
553
static int tmstmpClose(sqlite3_file *pFile){
554
TmstmpFile *p = (TmstmpFile *)pFile;
555
if( p->hasCorrectReserve ){
556
tmstmpEvent(p, p->isDb ? ELOG_CLOSE_DB : ELOG_CLOSE_WAL, 0, 0, 0, 0);
557
}
558
tmstmpLogFree(p->pLog);
559
if( p->pPartner ){
560
assert( p->pPartner->pPartner==p );
561
p->pPartner->pPartner = 0;
562
p->pPartner = 0;
563
}
564
pFile = ORIGFILE(pFile);
565
return pFile->pMethods->xClose(pFile);
566
}
567
568
/*
569
** Read bytes from a file
570
*/
571
static int tmstmpRead(
572
sqlite3_file *pFile,
573
void *zBuf,
574
int iAmt,
575
sqlite_int64 iOfst
576
){
577
int rc;
578
TmstmpFile *p = (TmstmpFile*)pFile;
579
pFile = ORIGFILE(pFile);
580
rc = pFile->pMethods->xRead(pFile, zBuf, iAmt, iOfst);
581
if( rc!=SQLITE_OK ) return rc;
582
if( p->isDb
583
&& iOfst==0
584
&& iAmt>=100
585
){
586
const unsigned char *a = (unsigned char*)zBuf;
587
p->hasCorrectReserve = (a[20]==TMSTMP_RESERVE);
588
p->pgsz = (a[16]<<8) + a[17];
589
if( p->pgsz==1 ) p->pgsz = 65536;
590
if( p->pPartner ){
591
p->pPartner->hasCorrectReserve = p->hasCorrectReserve;
592
p->pPartner->pgsz = p->pgsz;
593
}
594
}
595
if( p->isWal
596
&& p->inCkpt
597
&& iAmt>=512 && iAmt<=65535 && (iAmt&(iAmt-1))==0
598
){
599
p->pPartner->iFrame = (iOfst-56)/(p->pgsz+24) + 1;
600
}
601
return rc;
602
}
603
604
/*
605
** Write data to a tmstmp-file.
606
*/
607
static int tmstmpWrite(
608
sqlite3_file *pFile,
609
const void *zBuf,
610
int iAmt,
611
sqlite_int64 iOfst
612
){
613
TmstmpFile *p = (TmstmpFile*)pFile;
614
sqlite3_file *pSub = ORIGFILE(pFile);
615
if( !p->hasCorrectReserve ){
616
/* The database does not have the correct reserve size. No-op */
617
}else if( p->isWal ){
618
/* Writing into a WAL file */
619
if( iAmt==24 ){
620
/* A frame header */
621
u32 x = 0;
622
p->iFrame = (iOfst - 32)/(p->pgsz+24)+1;
623
p->pgno = tmstmpGetU32((const u8*)zBuf);
624
p->salt1 = tmstmpGetU32(((const u8*)zBuf)+8);
625
memcpy(&x, ((const u8*)zBuf)+4, 4);
626
p->isCommit = (x!=0);
627
p->iOfst = iOfst;
628
}else if( iAmt>=512 && iOfst==p->iOfst+24 ){
629
unsigned char s[TMSTMP_RESERVE];
630
memset(s, 0, TMSTMP_RESERVE);
631
tmstmpPutTS(p, s+2);
632
tmstmpEvent(p, ELOG_WAL_PAGE, p->isCommit, p->pgno, p->iFrame, s+2);
633
}else if( iAmt==32 && iOfst==0 ){
634
p->salt1 = tmstmpGetU32(((const u8*)zBuf)+16);
635
tmstmpEvent(p, ELOG_WAL_RESET, 0, 0, p->salt1, 0);
636
}
637
}else if( p->inCkpt ){
638
unsigned char *s = (unsigned char*)zBuf+iAmt-TMSTMP_RESERVE;
639
memset(s, 0, TMSTMP_RESERVE);
640
tmstmpPutTS(p, s+2);
641
tmstmpPutU32(p->iFrame, s+8);
642
tmstmpPutU32(p->pPartner->salt1 & 0xffffff, s+12);
643
assert( p->pgsz>0 );
644
tmstmpEvent(p, ELOG_CKPT_PAGE, 0, (iOfst/p->pgsz)+1, p->iFrame, 0);
645
}else if( p->pPartner==0 ){
646
/* Writing into a database in rollback mode */
647
unsigned char *s = (unsigned char*)zBuf+iAmt-TMSTMP_RESERVE;
648
memset(s, 0, TMSTMP_RESERVE);
649
tmstmpPutTS(p, s+2);
650
s[12] = 2;
651
assert( p->pgsz>0 );
652
tmstmpEvent(p, ELOG_DB_PAGE, 0, (u32)(iOfst/p->pgsz)+1, 0, s+2);
653
}
654
return pSub->pMethods->xWrite(pSub,zBuf,iAmt,iOfst);
655
}
656
657
/*
658
** Truncate a tmstmp-file.
659
*/
660
static int tmstmpTruncate(sqlite3_file *pFile, sqlite_int64 size){
661
pFile = ORIGFILE(pFile);
662
return pFile->pMethods->xTruncate(pFile, size);
663
}
664
665
/*
666
** Sync a tmstmp-file.
667
*/
668
static int tmstmpSync(sqlite3_file *pFile, int flags){
669
pFile = ORIGFILE(pFile);
670
return pFile->pMethods->xSync(pFile, flags);
671
}
672
673
/*
674
** Return the current file-size of a tmstmp-file.
675
*/
676
static int tmstmpFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
677
TmstmpFile *p = (TmstmpFile *)pFile;
678
pFile = ORIGFILE(p);
679
return pFile->pMethods->xFileSize(pFile, pSize);
680
}
681
682
/*
683
** Lock a tmstmp-file.
684
*/
685
static int tmstmpLock(sqlite3_file *pFile, int eLock){
686
pFile = ORIGFILE(pFile);
687
return pFile->pMethods->xLock(pFile, eLock);
688
}
689
690
/*
691
** Unlock a tmstmp-file.
692
*/
693
static int tmstmpUnlock(sqlite3_file *pFile, int eLock){
694
pFile = ORIGFILE(pFile);
695
return pFile->pMethods->xUnlock(pFile, eLock);
696
}
697
698
/*
699
** Check if another file-handle holds a RESERVED lock on a tmstmp-file.
700
*/
701
static int tmstmpCheckReservedLock(sqlite3_file *pFile, int *pResOut){
702
pFile = ORIGFILE(pFile);
703
return pFile->pMethods->xCheckReservedLock(pFile, pResOut);
704
}
705
706
/*
707
** File control method. For custom operations on a tmstmp-file.
708
*/
709
static int tmstmpFileControl(sqlite3_file *pFile, int op, void *pArg){
710
int rc;
711
TmstmpFile *p = (TmstmpFile*)pFile;
712
pFile = ORIGFILE(pFile);
713
rc = pFile->pMethods->xFileControl(pFile, op, pArg);
714
switch( op ){
715
case SQLITE_FCNTL_VFSNAME: {
716
if( p->hasCorrectReserve && rc==SQLITE_OK ){
717
*(char**)pArg = sqlite3_mprintf("tmstmp/%z", *(char**)pArg);
718
}
719
break;
720
}
721
case SQLITE_FCNTL_CKPT_START: {
722
p->inCkpt = 1;
723
assert( p->isDb );
724
assert( p->pPartner!=0 );
725
p->pPartner->inCkpt = 1;
726
if( p->hasCorrectReserve ){
727
tmstmpEvent(p, ELOG_CKPT_START, 0, 0, 0, 0);
728
}
729
rc = SQLITE_OK;
730
break;
731
}
732
case SQLITE_FCNTL_CKPT_DONE: {
733
p->inCkpt = 0;
734
assert( p->isDb );
735
assert( p->pPartner!=0 );
736
p->pPartner->inCkpt = 0;
737
if( p->hasCorrectReserve ){
738
tmstmpEvent(p, ELOG_CKPT_DONE, 0, 0, 0, 0);
739
}
740
rc = SQLITE_OK;
741
break;
742
}
743
}
744
return rc;
745
}
746
747
/*
748
** Return the sector-size in bytes for a tmstmp-file.
749
*/
750
static int tmstmpSectorSize(sqlite3_file *pFile){
751
pFile = ORIGFILE(pFile);
752
return pFile->pMethods->xSectorSize(pFile);
753
}
754
755
/*
756
** Return the device characteristic flags supported by a tmstmp-file.
757
*/
758
static int tmstmpDeviceCharacteristics(sqlite3_file *pFile){
759
int devchar = 0;
760
pFile = ORIGFILE(pFile);
761
devchar = pFile->pMethods->xDeviceCharacteristics(pFile);
762
return (devchar & ~SQLITE_IOCAP_SUBPAGE_READ);
763
}
764
765
/* Create a shared memory file mapping */
766
static int tmstmpShmMap(
767
sqlite3_file *pFile,
768
int iPg,
769
int pgsz,
770
int bExtend,
771
void volatile **pp
772
){
773
pFile = ORIGFILE(pFile);
774
return pFile->pMethods->xShmMap(pFile,iPg,pgsz,bExtend,pp);
775
}
776
777
/* Perform locking on a shared-memory segment */
778
static int tmstmpShmLock(sqlite3_file *pFile, int offset, int n, int flags){
779
pFile = ORIGFILE(pFile);
780
return pFile->pMethods->xShmLock(pFile,offset,n,flags);
781
}
782
783
/* Memory barrier operation on shared memory */
784
static void tmstmpShmBarrier(sqlite3_file *pFile){
785
pFile = ORIGFILE(pFile);
786
pFile->pMethods->xShmBarrier(pFile);
787
}
788
789
/* Unmap a shared memory segment */
790
static int tmstmpShmUnmap(sqlite3_file *pFile, int deleteFlag){
791
pFile = ORIGFILE(pFile);
792
return pFile->pMethods->xShmUnmap(pFile,deleteFlag);
793
}
794
795
/* Fetch a page of a memory-mapped file */
796
static int tmstmpFetch(
797
sqlite3_file *pFile,
798
sqlite3_int64 iOfst,
799
int iAmt,
800
void **pp
801
){
802
pFile = ORIGFILE(pFile);
803
return pFile->pMethods->xFetch(pFile, iOfst, iAmt, pp);
804
}
805
806
/* Release a memory-mapped page */
807
static int tmstmpUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){
808
pFile = ORIGFILE(pFile);
809
return pFile->pMethods->xUnfetch(pFile, iOfst, pPage);
810
}
811
812
813
/*
814
** Open a tmstmp file handle.
815
*/
816
static int tmstmpOpen(
817
sqlite3_vfs *pVfs,
818
const char *zName,
819
sqlite3_file *pFile,
820
int flags,
821
int *pOutFlags
822
){
823
TmstmpFile *p, *pDb;
824
sqlite3_file *pSubFile;
825
sqlite3_vfs *pSubVfs;
826
int rc;
827
828
pSubVfs = ORIGVFS(pVfs);
829
if( (flags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_WAL))==0 ){
830
/* If the file is not a persistent database or a WAL file, then
831
** bypass the timestamp logic all together */
832
return pSubVfs->xOpen(pSubVfs, zName, pFile, flags, pOutFlags);
833
}
834
if( (flags & SQLITE_OPEN_WAL)!=0 ){
835
pDb = (TmstmpFile*)sqlite3_database_file_object(zName);
836
if( pDb==0
837
|| pDb->uMagic!=TMSTMP_MAGIC
838
|| !pDb->isDb
839
|| pDb->pPartner!=0
840
){
841
return pSubVfs->xOpen(pSubVfs, zName, pFile, flags, pOutFlags);
842
}
843
}else{
844
pDb = 0;
845
}
846
p = (TmstmpFile*)pFile;
847
memset(p, 0, sizeof(*p));
848
pSubFile = ORIGFILE(pFile);
849
pFile->pMethods = &tmstmp_io_methods;
850
p->pSubVfs = pSubVfs;
851
p->uMagic = TMSTMP_MAGIC;
852
rc = pSubVfs->xOpen(pSubVfs, zName, pSubFile, flags, pOutFlags);
853
if( rc ) goto tmstmp_open_done;
854
if( pDb!=0 ){
855
p->isWal = 1;
856
p->pPartner = pDb;
857
pDb->pPartner = p;
858
}else{
859
u32 r2;
860
u32 pid;
861
TmstmpLog *pLog;
862
sqlite3_uint64 r1; /* Milliseconds since 1970-01-01 */
863
sqlite3_uint64 days; /* Days since 1970-01-01 */
864
sqlite3_uint64 sod; /* Start of date specified by r1 */
865
sqlite3_uint64 z; /* Days since 0000-03-01 */
866
sqlite3_uint64 era; /* 400-year era */
867
int h; /* hour */
868
int m; /* minute */
869
int s; /* second */
870
int f; /* millisecond */
871
int Y; /* year */
872
int M; /* month */
873
int D; /* day */
874
int y; /* year assuming March is first month */
875
unsigned int doe; /* day of 400-year era */
876
unsigned int yoe; /* year of 400-year era */
877
unsigned int doy; /* day of year */
878
unsigned int mp; /* month with March==0 */
879
880
p->isDb = 1;
881
r1 = 0;
882
pLog = sqlite3_malloc64( sizeof(TmstmpLog) );
883
if( pLog==0 ){
884
pSubFile->pMethods->xClose(pSubFile);
885
rc = SQLITE_NOMEM;
886
goto tmstmp_open_done;
887
}
888
memset(pLog, 0, sizeof(pLog[0]));
889
p->pLog = pLog;
890
p->pSubVfs->xCurrentTimeInt64(p->pSubVfs, (sqlite3_int64*)&r1);
891
r1 -= 210866760000000LL;
892
days = r1/86400000;
893
sod = (r1%86400000)/1000;
894
f = (int)(r1%1000);
895
896
h = sod/3600;
897
m = (sod%3600)/60;
898
s = sod%60;
899
z = days + 719468;
900
era = z/146097;
901
doe = (unsigned)(z - era*146097);
902
yoe = (doe - doe/1460 + doe/36524 - doe/146096)/365;
903
y = (int)yoe + era*400;
904
doy = doe - (365*yoe + yoe/4 - yoe/100);
905
mp = (5*doy + 2)/153;
906
D = doy - (153*mp + 2)/5 + 1;
907
M = mp + (mp<10 ? 3 : -9);
908
Y = y + (M <=2);
909
sqlite3_randomness(sizeof(r2), &r2);
910
pid = GETPID;
911
pLog->zLogname = sqlite3_mprintf(
912
"%s-tmstmp/%04d%02d%02dT%02d%02d%02d%03d-%08d-%08x",
913
zName, Y, M, D, h, m, s, f, pid, r2);
914
}
915
tmstmpEvent(p, p->isWal ? ELOG_OPEN_WAL : ELOG_OPEN_DB, 0, GETPID, 0, 0);
916
917
tmstmp_open_done:
918
if( rc ) pFile->pMethods = 0;
919
return rc;
920
}
921
922
/*
923
** All VFS interfaces other than xOpen are passed down into the Sub-VFS.
924
*/
925
static int tmstmpDelete(sqlite3_vfs *p, const char *zName, int syncDir){
926
sqlite3_vfs *pSub = ORIGVFS(p);
927
return pSub->xDelete(pSub,zName,syncDir);
928
}
929
static int tmstmpAccess(sqlite3_vfs *p, const char *zName, int flags, int *pR){
930
sqlite3_vfs *pSub = ORIGVFS(p);
931
return pSub->xAccess(pSub,zName,flags,pR);
932
}
933
static int tmstmpFullPathname(sqlite3_vfs*p,const char *zName,int n,char *zOut){
934
sqlite3_vfs *pSub = ORIGVFS(p);
935
return pSub->xFullPathname(pSub,zName,n,zOut);
936
}
937
static void *tmstmpDlOpen(sqlite3_vfs *p, const char *zFilename){
938
sqlite3_vfs *pSub = ORIGVFS(p);
939
return pSub->xDlOpen(pSub,zFilename);
940
}
941
static void tmstmpDlError(sqlite3_vfs *p, int nByte, char *zErrMsg){
942
sqlite3_vfs *pSub = ORIGVFS(p);
943
return pSub->xDlError(pSub,nByte,zErrMsg);
944
}
945
static void(*tmstmpDlSym(sqlite3_vfs *p, void *pDl, const char *zSym))(void){
946
sqlite3_vfs *pSub = ORIGVFS(p);
947
return pSub->xDlSym(pSub,pDl,zSym);
948
}
949
static void tmstmpDlClose(sqlite3_vfs *p, void *pDl){
950
sqlite3_vfs *pSub = ORIGVFS(p);
951
return pSub->xDlClose(pSub,pDl);
952
}
953
static int tmstmpRandomness(sqlite3_vfs *p, int nByte, char *zOut){
954
sqlite3_vfs *pSub = ORIGVFS(p);
955
return pSub->xRandomness(pSub,nByte,zOut);
956
}
957
static int tmstmpSleep(sqlite3_vfs *p, int microseconds){
958
sqlite3_vfs *pSub = ORIGVFS(p);
959
return pSub->xSleep(pSub,microseconds);
960
}
961
static int tmstmpCurrentTime(sqlite3_vfs *p, double *prNow){
962
sqlite3_vfs *pSub = ORIGVFS(p);
963
return pSub->xCurrentTime(pSub,prNow);
964
}
965
static int tmstmpGetLastError(sqlite3_vfs *p, int a, char *b){
966
sqlite3_vfs *pSub = ORIGVFS(p);
967
return pSub->xGetLastError(pSub,a,b);
968
}
969
static int tmstmpCurrentTimeInt64(sqlite3_vfs *p, sqlite3_int64 *piNow){
970
sqlite3_vfs *pSub = ORIGVFS(p);
971
return pSub->xCurrentTimeInt64(pSub,piNow);
972
}
973
static int tmstmpSetSystemCall(sqlite3_vfs *p, const char *zName,
974
sqlite3_syscall_ptr x){
975
sqlite3_vfs *pSub = ORIGVFS(p);
976
return pSub->xSetSystemCall(pSub,zName,x);
977
}
978
static sqlite3_syscall_ptr tmstmpGetSystemCall(sqlite3_vfs *p, const char *z){
979
sqlite3_vfs *pSub = ORIGVFS(p);
980
return pSub->xGetSystemCall(pSub,z);
981
}
982
static const char *tmstmpNextSystemCall(sqlite3_vfs *p, const char *zName){
983
sqlite3_vfs *pSub = ORIGVFS(p);
984
return pSub->xNextSystemCall(pSub,zName);
985
}
986
987
/*
988
** Register the tmstmp VFS as the default VFS for the system.
989
*/
990
static int tmstmpRegisterVfs(void){
991
int rc = SQLITE_OK;
992
sqlite3_vfs *pOrig = sqlite3_vfs_find(0);
993
if( pOrig==0 ) return SQLITE_ERROR;
994
if( pOrig==&tmstmp_vfs ) return SQLITE_OK;
995
tmstmp_vfs.iVersion = pOrig->iVersion;
996
tmstmp_vfs.pAppData = pOrig;
997
tmstmp_vfs.szOsFile = pOrig->szOsFile + sizeof(TmstmpFile);
998
rc = sqlite3_vfs_register(&tmstmp_vfs, 1);
999
return rc;
1000
}
1001
1002
#if defined(SQLITE_TMSTMPVFS_STATIC)
1003
/* This variant of the initializer runs when the extension is
1004
** statically linked.
1005
*/
1006
int sqlite3_register_tmstmpvfs(const char *NotUsed){
1007
(void)NotUsed;
1008
return tmstmpRegisterVfs();
1009
}
1010
int sqlite3_unregister_tmstmpvfs(void){
1011
if( sqlite3_vfs_find("tmstmpvfs") ){
1012
sqlite3_vfs_unregister(&tmstmp_vfs);
1013
}
1014
return SQLITE_OK;
1015
}
1016
#endif /* defined(SQLITE_TMSTMPVFS_STATIC */
1017
1018
#if !defined(SQLITE_TMSTMPVFS_STATIC)
1019
/* This variant of the initializer function is used when the
1020
** extension is shared library to be loaded at run-time.
1021
*/
1022
#ifdef _WIN32
1023
__declspec(dllexport)
1024
#endif
1025
/*
1026
** This routine is called by sqlite3_load_extension() when the
1027
** extension is first loaded.
1028
***/
1029
int sqlite3_tmstmpvfs_init(
1030
sqlite3 *db,
1031
char **pzErrMsg,
1032
const sqlite3_api_routines *pApi
1033
){
1034
int rc;
1035
SQLITE_EXTENSION_INIT2(pApi);
1036
(void)pzErrMsg; /* not used */
1037
(void)db; /* not used */
1038
rc = tmstmpRegisterVfs();
1039
if( rc==SQLITE_OK ) rc = SQLITE_OK_LOAD_PERMANENTLY;
1040
return rc;
1041
}
1042
#endif /* !defined(SQLITE_TMSTMPVFS_STATIC) */
1043

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button