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