restore_jrnl.tcl 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  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. package require sqlite3
  18. set parm_error 0
  19. set fix_chksums 0
  20. set dump_pages 0
  21. set db_name ""
  22. for {set i 0} {$i<$argc} {incr i} {
  23. if {[lindex $argv $i] == "-fix_chksums"} {
  24. set fix_chksums -1
  25. } elseif {[lindex $argv $i] == "-dump_pages"} {
  26. set dump_pages -1
  27. } elseif {$db_name == ""} {
  28. set db_name [lindex $argv $i]
  29. set jrnl_name $db_name-journal
  30. } else {
  31. set parm_error -1
  32. }
  33. }
  34. if {$parm_error || $db_name == ""} {
  35. puts "USAGE: restore_jrnl.tcl \[-fix_chksums\] \[-dump_pages\] db_name"
  36. puts "Example: restore_jrnl.tcl foo.sqlite"
  37. return
  38. }
  39. # is there a way to determine this?
  40. set sectsz 512
  41. # Copy file $from into $to
  42. #
  43. proc copy_file {from to} {
  44. file copy -force $from $to
  45. }
  46. # Execute some SQL
  47. #
  48. proc catchsql {sql} {
  49. set rc [catch {uplevel [list db eval $sql]} msg]
  50. list $rc $msg
  51. }
  52. # Perform a test
  53. #
  54. proc do_test {name cmd expected} {
  55. puts -nonewline "$name ..."
  56. set res [uplevel $cmd]
  57. if {$res eq $expected} {
  58. puts Ok
  59. } else {
  60. puts Error
  61. puts " Got: $res"
  62. puts " Expected: $expected"
  63. }
  64. }
  65. # Calc checksum nonce from journal page data.
  66. #
  67. proc calc_nonce {jrnl_pgno} {
  68. global sectsz
  69. global db_pgsz
  70. global jrnl_name
  71. set jrnl_pg_offset [expr $sectsz+((4+$db_pgsz+4)*$jrnl_pgno)]
  72. set nonce [hexio_get_int [hexio_read $jrnl_name [expr $jrnl_pg_offset+4+$db_pgsz] 4]]
  73. for {set i [expr $db_pgsz-200]} {$i>0} {set i [expr $i-200]} {
  74. set byte [hexio_get_int [hexio_read $jrnl_name [expr $jrnl_pg_offset+4+$i] 1]]
  75. set nonce [expr $nonce-$byte]
  76. }
  77. return $nonce
  78. }
  79. # Calc checksum from journal page data.
  80. #
  81. proc calc_chksum {jrnl_pgno} {
  82. global sectsz
  83. global db_pgsz
  84. global jrnl_name
  85. global nonce
  86. set jrnl_pg_offset [expr $sectsz+((4+$db_pgsz+4)*$jrnl_pgno)]
  87. set chksum $nonce
  88. for {set i [expr $db_pgsz-200]} {$i>0} {set i [expr $i-200]} {
  89. set byte [hexio_get_int [hexio_read $jrnl_name [expr $jrnl_pg_offset+4+$i] 1]]
  90. set chksum [expr $chksum+$byte]
  91. }
  92. return $chksum
  93. }
  94. # Print journal page data in hex dump form
  95. #
  96. proc dump_jrnl_page {jrnl_pgno} {
  97. global sectsz
  98. global db_pgsz
  99. global jrnl_name
  100. # print a header block for the page
  101. puts [string repeat "-" 79]
  102. set jrnl_pg_offset [expr $sectsz+((4+$db_pgsz+4)*$jrnl_pgno)]
  103. set db_pgno [hexio_get_int [hexio_read $jrnl_name [expr $jrnl_pg_offset] 4]]
  104. set chksum [hexio_get_int [hexio_read $jrnl_name [expr $jrnl_pg_offset+4+$db_pgsz] 4]]
  105. set nonce [calc_nonce $jrnl_pgno]
  106. puts [ format {jrnl_pg_offset: %08x (%d) jrnl_pgno: %d db_pgno: %d} \
  107. $jrnl_pg_offset $jrnl_pg_offset \
  108. $jrnl_pgno $db_pgno]
  109. puts [ format {nonce: %08x chksum: %08x} \
  110. $nonce $chksum]
  111. # now hex dump the data
  112. # This is derived from the Tcler's WIKI
  113. set fid [open $jrnl_name r]
  114. fconfigure $fid -translation binary
  115. seek $fid [expr $jrnl_pg_offset+4]
  116. set data [read $fid $db_pgsz]
  117. close $fid
  118. for {set addr 0} {$addr<$db_pgsz} {set addr [expr $addr+16]} {
  119. # get 16 bytes of data
  120. set s [string range $data $addr [expr $addr+16]]
  121. # Convert the data to hex and to characters.
  122. binary scan $s H*@0a* hex ascii
  123. # Replace non-printing characters in the data.
  124. regsub -all -- {[^[:graph:] ]} $ascii {.} ascii
  125. # Split the 16 bytes into two 8-byte chunks
  126. regexp -- {(.{16})(.{0,16})} $hex -> hex1 hex2
  127. # Convert the hex to pairs of hex digits
  128. regsub -all -- {..} $hex1 {& } hex1
  129. regsub -all -- {..} $hex2 {& } hex2
  130. # Print the hex and ascii data
  131. puts [ format {%08x %-24s %-24s %-16s} \
  132. $addr $hex1 $hex2 $ascii ]
  133. }
  134. }
  135. # Setup for the tests. Make a backup copy of the files.
  136. #
  137. if [file exist $db_name.org] {
  138. puts "ERROR: during back-up: $db_name.org exists already."
  139. return;
  140. }
  141. if [file exist $jrnl_name.org] {
  142. puts "ERROR: during back-up: $jrnl_name.org exists already."
  143. return
  144. }
  145. copy_file $db_name $db_name.org
  146. copy_file $jrnl_name $jrnl_name.org
  147. set db_fsize [file size $db_name]
  148. set db_pgsz [hexio_get_int [hexio_read $db_name 16 2]]
  149. set db_npage [expr {$db_fsize / $db_pgsz}]
  150. set jrnl_fsize [file size $jrnl_name]
  151. set jrnl_npage [expr {($jrnl_fsize - $sectsz) / (4 + $db_pgsz + 4)}]
  152. # calculate checksum nonce for first page
  153. set nonce [calc_nonce 0]
  154. # verify all the pages in the journal use the same nonce
  155. for {set i 1} {$i<$jrnl_npage} {incr i} {
  156. set tnonce [calc_nonce $i]
  157. if {$tnonce != $nonce} {
  158. puts "WARNING: different nonces: 0=$nonce $i=$tnonce"
  159. if {$fix_chksums } {
  160. set jrnl_pg_offset [expr $sectsz+((4+$db_pgsz+4)*$i)]
  161. set tchksum [calc_chksum $i]
  162. hexio_write $jrnl_name [expr $jrnl_pg_offset+4+$db_pgsz] [format %08x $tchksum]
  163. puts "INFO: fixing chksum: $i=$tchksum"
  164. }
  165. }
  166. }
  167. # verify all the page numbers in the journal
  168. for {set i 0} {$i<$jrnl_npage} {incr i} {
  169. set jrnl_pg_offset [expr $sectsz+((4+$db_pgsz+4)*$i)]
  170. set db_pgno [hexio_get_int [hexio_read $jrnl_name $jrnl_pg_offset 4]]
  171. if {$db_pgno < 1} {
  172. puts "WARNING: page number < 1: $i=$db_pgno"
  173. }
  174. if {$db_pgno >= $db_npage} {
  175. puts "WARNING: page number >= $db_npage: $i=$db_pgno"
  176. }
  177. }
  178. # dump page data
  179. if {$dump_pages} {
  180. for {set i 0} {$i<$jrnl_npage} {incr i} {
  181. dump_jrnl_page $i
  182. }
  183. }
  184. # write the 8 byte magic string
  185. hexio_write $jrnl_name 0 d9d505f920a163d7
  186. # write -1 for number of records
  187. hexio_write $jrnl_name 8 ffffffff
  188. # write 00 for checksum nonce
  189. hexio_write $jrnl_name 12 [format %08x $nonce]
  190. # write page count
  191. hexio_write $jrnl_name 16 [format %08x $db_npage]
  192. # write sector size
  193. hexio_write $jrnl_name 20 [format %08x $sectsz]
  194. # write page size
  195. hexio_write $jrnl_name 24 [format %08x $db_pgsz]
  196. # check the integrity of the database with the patched journal
  197. sqlite3 db $db_name
  198. do_test restore_jrnl-1.0 {
  199. catchsql {PRAGMA integrity_check}
  200. } {0 ok}
  201. db close