Home | History | Annotate | Download | only in tool
      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