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_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] 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_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 \ 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 $tchksum] 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 234