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 |