3 # The author disclaims copyright to this source code. In place of
4 # a legal notice, here is a blessing:
6 # May you do good and not evil.
7 # May you find forgiveness for yourself and forgive others.
8 # May you share freely, never taking more than you give.
10 #***********************************************************************
11 # This file implements utility functions for SQLite library.
13 # This file attempts to restore the header of a journal.
14 # This may be useful for rolling-back the last committed
15 # transaction from a recovered journal.
18 package require sqlite3
25 for {set i
0} {$i<$argc} {incr i
} {
26 if {[lindex $argv $i] == "-fix_chksums"} {
28 } elseif
{[lindex $argv $i] == "-dump_pages"} {
30 } elseif
{$db_name == ""} {
31 set db_name
[lindex $argv $i]
32 set jrnl_name
$db_name-journal
37 if {$parm_error ||
$db_name == ""} {
38 puts "USAGE: restore_jrnl.tcl \[-fix_chksums\] \[-dump_pages\] db_name"
39 puts "Example: restore_jrnl.tcl foo.sqlite"
43 # is there a way to determine this?
46 # Copy file $from into $to
48 proc copy_file
{from to
} {
49 file copy
-force $from $to
55 set rc
[catch {uplevel [list db
eval $sql]} msg
]
61 proc do_test
{name cmd expected
} {
62 puts -nonewline "$name ..."
63 set res
[uplevel $cmd]
64 if {$res eq
$expected} {
69 puts " Expected: $expected"
73 # Calc checksum nonce from journal page data.
75 proc calc_nonce
{jrnl_pgno
} {
79 set jrnl_pg_offset
[expr $sectsz+((4+$db_pgsz+4)*$jrnl_pgno)]
80 set nonce
[hexio_get_int
[hexio_read
$jrnl_name [expr $jrnl_pg_offset+4+$db_pgsz] 4]]
81 for {set i
[expr $db_pgsz-200]} {$i>0} {set i
[expr $i-200]} {
82 set byte
[hexio_get_int
[hexio_read
$jrnl_name [expr $jrnl_pg_offset+4+$i] 1]]
83 set nonce
[expr $nonce-$byte]
88 # Calc checksum from journal page data.
90 proc calc_chksum
{jrnl_pgno
} {
95 set jrnl_pg_offset
[expr $sectsz+((4+$db_pgsz+4)*$jrnl_pgno)]
97 for {set i
[expr $db_pgsz-200]} {$i>0} {set i
[expr $i-200]} {
98 set byte
[hexio_get_int
[hexio_read
$jrnl_name [expr $jrnl_pg_offset+4+$i] 1]]
99 set chksum
[expr $chksum+$byte]
104 # Print journal page data in hex dump form
106 proc dump_jrnl_page
{jrnl_pgno
} {
111 # print a header block for the page
112 puts [string repeat
"-" 79]
113 set jrnl_pg_offset
[expr $sectsz+((4+$db_pgsz+4)*$jrnl_pgno)]
114 set db_pgno
[hexio_get_int
[hexio_read
$jrnl_name [expr $jrnl_pg_offset] 4]]
115 set chksum
[hexio_get_int
[hexio_read
$jrnl_name [expr $jrnl_pg_offset+4+$db_pgsz] 4]]
116 set nonce
[calc_nonce
$jrnl_pgno]
117 puts [ format {jrnl_pg_offset
: %08x
(%d
) jrnl_pgno
: %d db_pgno
: %d
} \
118 $jrnl_pg_offset $jrnl_pg_offset \
120 puts [ format {nonce
: %08x chksum
: %08x
} \
123 # now hex dump the data
124 # This is derived from the Tcler's WIKI
125 set fid
[open $jrnl_name r
]
126 fconfigure $fid -translation binary -encoding binary
127 seek $fid [expr $jrnl_pg_offset+4]
128 set data
[read $fid $db_pgsz]
130 for {set addr
0} {$addr<$db_pgsz} {set addr
[expr $addr+16]} {
131 # get 16 bytes of data
132 set s
[string range
$data $addr [expr $addr+16]]
134 # Convert the data to hex and to characters.
135 binary scan $s H
*@0a
* hex ascii
137 # Replace non-printing characters in the data.
138 regsub -all -- {[^
[:graph
:] ]} $ascii {.
} ascii
140 # Split the 16 bytes into two 8-byte chunks
141 regexp -- {(.
{16})(.
{0,16})} $hex -> hex1 hex2
143 # Convert the hex to pairs of hex digits
144 regsub -all -- {..
} $hex1 {& } hex1
145 regsub -all -- {..
} $hex2 {& } hex2
147 # Print the hex and ascii data
148 puts [ format {%08x
%-24s %-24s %-16s} \
149 $addr $hex1 $hex2 $ascii ]
153 # Setup for the tests. Make a backup copy of the files.
155 if [file exist
$db_name.org
] {
156 puts "ERROR: during back-up: $db_name.org exists already."
159 if [file exist
$jrnl_name.org
] {
160 puts "ERROR: during back-up: $jrnl_name.org exists already."
163 copy_file
$db_name $db_name.org
164 copy_file
$jrnl_name $jrnl_name.org
166 set db_fsize
[file size
$db_name]
167 set db_pgsz
[hexio_get_int
[hexio_read
$db_name 16 2]]
168 set db_npage
[expr {$db_fsize / $db_pgsz}]
170 set jrnl_fsize
[file size
$jrnl_name]
171 set jrnl_npage
[expr {($jrnl_fsize - $sectsz) / (4 + $db_pgsz + 4)}]
173 # calculate checksum nonce for first page
174 set nonce
[calc_nonce
0]
176 # verify all the pages in the journal use the same nonce
177 for {set i
1} {$i<$jrnl_npage} {incr i
} {
178 set tnonce
[calc_nonce
$i]
179 if {$tnonce != $nonce} {
180 puts "WARNING: different nonces: 0=$nonce $i=$tnonce"
182 set jrnl_pg_offset
[expr $sectsz+((4+$db_pgsz+4)*$i)]
183 set tchksum
[calc_chksum
$i]
184 hexio_write
$jrnl_name [expr $jrnl_pg_offset+4+$db_pgsz] [format %08x
$tchksum]
185 puts "INFO: fixing chksum: $i=$tchksum"
190 # verify all the page numbers in the journal
191 for {set i
0} {$i<$jrnl_npage} {incr i
} {
192 set jrnl_pg_offset
[expr $sectsz+((4+$db_pgsz+4)*$i)]
193 set db_pgno
[hexio_get_int
[hexio_read
$jrnl_name $jrnl_pg_offset 4]]
195 puts "WARNING: page number < 1: $i=$db_pgno"
197 if {$db_pgno >= $db_npage} {
198 puts "WARNING: page number >= $db_npage: $i=$db_pgno"
204 for {set i
0} {$i<$jrnl_npage} {incr i
} {
209 # write the 8 byte magic string
210 hexio_write
$jrnl_name 0 d9d505f920a163d7
212 # write -1 for number of records
213 hexio_write
$jrnl_name 8 ffffffff
215 # write 00 for checksum nonce
216 hexio_write
$jrnl_name 12 [format %08x
$nonce]
219 hexio_write
$jrnl_name 16 [format %08x
$db_npage]
222 hexio_write
$jrnl_name 20 [format %08x
$sectsz]
225 hexio_write
$jrnl_name 24 [format %08x
$db_pgsz]
227 # check the integrity of the database with the patched journal
229 do_test restore_jrnl-1.0
{
230 catchsql
{PRAGMA integrity_check
}