| OLD | NEW |
| (Empty) |
| 1 # 2010 January 7 | |
| 2 # | |
| 3 # The author disclaims copyright to this source code. In place of | |
| 4 # a legal notice, here is a blessing: | |
| 5 # | |
| 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. | |
| 9 # | |
| 10 #*********************************************************************** | |
| 11 # This file implements utility functions for SQLite library. | |
| 12 # | |
| 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. | |
| 16 # | |
| 17 | |
| 18 package require sqlite3 | |
| 19 | |
| 20 set parm_error 0 | |
| 21 set fix_chksums 0 | |
| 22 set dump_pages 0 | |
| 23 set db_name "" | |
| 24 | |
| 25 for {set i 0} {$i<$argc} {incr i} { | |
| 26 if {[lindex $argv $i] == "-fix_chksums"} { | |
| 27 set fix_chksums -1 | |
| 28 } elseif {[lindex $argv $i] == "-dump_pages"} { | |
| 29 set dump_pages -1 | |
| 30 } elseif {$db_name == ""} { | |
| 31 set db_name [lindex $argv $i] | |
| 32 set jrnl_name $db_name-journal | |
| 33 } else { | |
| 34 set parm_error -1 | |
| 35 } | |
| 36 } | |
| 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" | |
| 40 return | |
| 41 } | |
| 42 | |
| 43 # is there a way to determine this? | |
| 44 set sectsz 512 | |
| 45 | |
| 46 # Copy file $from into $to | |
| 47 # | |
| 48 proc copy_file {from to} { | |
| 49 file copy -force $from $to | |
| 50 } | |
| 51 | |
| 52 # Execute some SQL | |
| 53 # | |
| 54 proc catchsql {sql} { | |
| 55 set rc [catch {uplevel [list db eval $sql]} msg] | |
| 56 list $rc $msg | |
| 57 } | |
| 58 | |
| 59 # Perform a test | |
| 60 # | |
| 61 proc do_test {name cmd expected} { | |
| 62 puts -nonewline "$name ..." | |
| 63 set res [uplevel $cmd] | |
| 64 if {$res eq $expected} { | |
| 65 puts Ok | |
| 66 } else { | |
| 67 puts Error | |
| 68 puts " Got: $res" | |
| 69 puts " Expected: $expected" | |
| 70 } | |
| 71 } | |
| 72 | |
| 73 # Calc checksum nonce from journal page data. | |
| 74 # | |
| 75 proc calc_nonce {jrnl_pgno} { | |
| 76 global sectsz | |
| 77 global db_pgsz | |
| 78 global jrnl_name | |
| 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_pg
sz] 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] | |
| 84 } | |
| 85 return $nonce | |
| 86 } | |
| 87 | |
| 88 # Calc checksum from journal page data. | |
| 89 # | |
| 90 proc calc_chksum {jrnl_pgno} { | |
| 91 global sectsz | |
| 92 global db_pgsz | |
| 93 global jrnl_name | |
| 94 global nonce | |
| 95 set jrnl_pg_offset [expr $sectsz+((4+$db_pgsz+4)*$jrnl_pgno)] | |
| 96 set chksum $nonce | |
| 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] | |
| 100 } | |
| 101 return $chksum | |
| 102 } | |
| 103 | |
| 104 # Print journal page data in hex dump form | |
| 105 # | |
| 106 proc dump_jrnl_page {jrnl_pgno} { | |
| 107 global sectsz | |
| 108 global db_pgsz | |
| 109 global jrnl_name | |
| 110 | |
| 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_p
gsz] 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 \ | |
| 119 $jrnl_pgno $db_pgno] | |
| 120 puts [ format {nonce: %08x chksum: %08x} \ | |
| 121 $nonce $chksum] | |
| 122 | |
| 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] | |
| 129 close $fid | |
| 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]] | |
| 133 | |
| 134 # Convert the data to hex and to characters. | |
| 135 binary scan $s H*@0a* hex ascii | |
| 136 | |
| 137 # Replace non-printing characters in the data. | |
| 138 regsub -all -- {[^[:graph:] ]} $ascii {.} ascii | |
| 139 | |
| 140 # Split the 16 bytes into two 8-byte chunks | |
| 141 regexp -- {(.{16})(.{0,16})} $hex -> hex1 hex2 | |
| 142 | |
| 143 # Convert the hex to pairs of hex digits | |
| 144 regsub -all -- {..} $hex1 {& } hex1 | |
| 145 regsub -all -- {..} $hex2 {& } hex2 | |
| 146 | |
| 147 # Print the hex and ascii data | |
| 148 puts [ format {%08x %-24s %-24s %-16s} \ | |
| 149 $addr $hex1 $hex2 $ascii ] | |
| 150 } | |
| 151 } | |
| 152 | |
| 153 # Setup for the tests. Make a backup copy of the files. | |
| 154 # | |
| 155 if [file exist $db_name.org] { | |
| 156 puts "ERROR: during back-up: $db_name.org exists already." | |
| 157 return; | |
| 158 } | |
| 159 if [file exist $jrnl_name.org] { | |
| 160 puts "ERROR: during back-up: $jrnl_name.org exists already." | |
| 161 return | |
| 162 } | |
| 163 copy_file $db_name $db_name.org | |
| 164 copy_file $jrnl_name $jrnl_name.org | |
| 165 | |
| 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}] | |
| 169 | |
| 170 set jrnl_fsize [file size $jrnl_name] | |
| 171 set jrnl_npage [expr {($jrnl_fsize - $sectsz) / (4 + $db_pgsz + 4)}] | |
| 172 | |
| 173 # calculate checksum nonce for first page | |
| 174 set nonce [calc_nonce 0] | |
| 175 | |
| 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" | |
| 181 if {$fix_chksums } { | |
| 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 $tch
ksum] | |
| 185 puts "INFO: fixing chksum: $i=$tchksum" | |
| 186 } | |
| 187 } | |
| 188 } | |
| 189 | |
| 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]] | |
| 194 if {$db_pgno < 1} { | |
| 195 puts "WARNING: page number < 1: $i=$db_pgno" | |
| 196 } | |
| 197 if {$db_pgno >= $db_npage} { | |
| 198 puts "WARNING: page number >= $db_npage: $i=$db_pgno" | |
| 199 } | |
| 200 } | |
| 201 | |
| 202 # dump page data | |
| 203 if {$dump_pages} { | |
| 204 for {set i 0} {$i<$jrnl_npage} {incr i} { | |
| 205 dump_jrnl_page $i | |
| 206 } | |
| 207 } | |
| 208 | |
| 209 # write the 8 byte magic string | |
| 210 hexio_write $jrnl_name 0 d9d505f920a163d7 | |
| 211 | |
| 212 # write -1 for number of records | |
| 213 hexio_write $jrnl_name 8 ffffffff | |
| 214 | |
| 215 # write 00 for checksum nonce | |
| 216 hexio_write $jrnl_name 12 [format %08x $nonce] | |
| 217 | |
| 218 # write page count | |
| 219 hexio_write $jrnl_name 16 [format %08x $db_npage] | |
| 220 | |
| 221 # write sector size | |
| 222 hexio_write $jrnl_name 20 [format %08x $sectsz] | |
| 223 | |
| 224 # write page size | |
| 225 hexio_write $jrnl_name 24 [format %08x $db_pgsz] | |
| 226 | |
| 227 # check the integrity of the database with the patched journal | |
| 228 sqlite3 db $db_name | |
| 229 do_test restore_jrnl-1.0 { | |
| 230 catchsql {PRAGMA integrity_check} | |
| 231 } {0 ok} | |
| 232 db close | |
| 233 | |
| OLD | NEW |