|
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
|
|