1 2(********************************************************************) 3(* *) 4(* savehd7.sd7 Save a harddisk which has hardware errors. *) 5(* Copyright (C) 2006, 2009 Thomas Mertes *) 6(* *) 7(* This program is free software; you can redistribute it and/or *) 8(* modify it under the terms of the GNU General Public License as *) 9(* published by the Free Software Foundation; either version 2 of *) 10(* the License, or (at your option) any later version. *) 11(* *) 12(* This program is distributed in the hope that it will be useful, *) 13(* but WITHOUT ANY WARRANTY; without even the implied warranty of *) 14(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *) 15(* GNU General Public License for more details. *) 16(* *) 17(* You should have received a copy of the GNU General Public *) 18(* License along with this program; if not, write to the *) 19(* Free Software Foundation, Inc., 51 Franklin Street, *) 20(* Fifth Floor, Boston, MA 02110-1301, USA. *) 21(* *) 22(********************************************************************) 23 24 25$ include "seed7_05.s7i"; 26 include "stdio.s7i"; 27 include "osfiles.s7i"; 28 include "keybd.s7i"; 29 include "bigint.s7i"; 30 include "bigfile.s7i"; 31 include "bigrat.s7i"; 32 include "line.s7i"; 33 34const string: dataFileName is "savehd7.dat"; 35const string: logFileName is "savehd7.log"; 36 37var file: log is STD_NULL; 38 39const type: phaseType is new enum 40 NONE, COPY, REREAD, IMPROVE, EXAMINE, FIX, DONE 41 end enum; 42 43const array string: phaseTypeStr is [] 44 ("none", "copy", "reread", "improve", "examine", "fix", "done"); 45 46const func string: str (in phaseType: phase) is 47 return phaseTypeStr[succ(ord(phase))]; 48 49const func phaseType: (attr phaseType) parse (in string: stri) is func 50 result 51 var phaseType: phase is phaseType.first; 52 begin 53 while str(phase) <> stri do 54 incr(phase); 55 end while; 56 end func; 57 58enable_io(phaseType); 59 60const type: areaHashType is hash [bigInteger] bigInteger; 61 62const type: stateType is new struct 63 var string: stateFileName is ""; 64 var string: inFileName is ""; 65 var bigInteger: inFileSize is -1_; 66 var string: outFileName is ""; 67 var phaseType: phase is NONE; 68 var integer: skipSize is 2 ** 20; 69 var integer: chunkSize is 2 ** 14; 70 var integer: blockSize is 512; 71 var bigInteger: rereadPosition is 1_; 72 var integer: rereadRunsToDo is 0; 73 var bigInteger: maximumOfBadBytes is 0_; 74 var bigInteger: badBytesToProcess is 0_; 75 var bigInteger: badAreaToProcess is 0_; 76 var bigInteger: sizeOfBadAreaToProcess is 0_; 77 var bigInteger: blockToProcess is 0_; 78 var areaHashType: badAreas is areaHashType.value; 79 var bigInteger: sumOfBadBytes is 0_; 80 var bigInteger: badBytesInUnprocessedAreas is 0_; 81 var string: emptyBlock is ""; 82 end struct; 83 84 85const proc: showProgress (in stateType: state, in bigInteger: bytesToProcess, 86 in bigInteger: bytesProcessed, in bigInteger: bytesDone) is func 87 local 88 var bigRational: percentProgress is 0_/1_; 89 var bigRational: percentDone is 0_/1_; 90 var bigRational: percentFixed is 0_/1_; 91 var bigRational: percentBadBlocks is 0_/1_; 92 begin 93 percentProgress := bytesProcessed * 100_ / bytesToProcess; 94 percentDone := (bytesDone - state.sumOfBadBytes) * 100_ / state.inFileSize; 95 percentBadBlocks := state.sumOfBadBytes * 100_ / state.inFileSize; 96 if state.maximumOfBadBytes <> 0_ then 97 percentFixed := (state.maximumOfBadBytes - state.sumOfBadBytes) * 100_ / state.maximumOfBadBytes; 98 end if; 99 write(state.phase rpad 7 <& " "); 100 write(percentProgress digits 4 lpad 9 <& "% "); 101 write(percentDone digits 4 lpad 9 <& "% "); 102 write(percentBadBlocks digits 4 lpad 9 <& "% "); 103 write(percentFixed digits 4 lpad 9 <& "% "); 104 write(state.sumOfBadBytes lpad 12 <& " \r"); 105 flush(OUT); 106 (* 107 writeln(log, "bytesToProcess=" <& bytesToProcess <& 108 " bytesProcessed=" <& bytesProcessed <& 109 " bytesDone=" <& bytesDone); 110 write(log, state.phase rpad 7 <& " "); 111 write(log, percentProgress digits 4 lpad 9 <& "% "); 112 write(log, percentDone digits 4 lpad 9 <& "% "); 113 write(log, percentBadBlocks digits 4 lpad 9 <& "% "); 114 write(log, percentFixed digits 4 lpad 9 <& "% "); 115 writeln(log, state.sumOfBadBytes lpad 12); 116 *) 117 end func; 118 119 120const func stateType: loadState (in string: stateFileName) is func 121 result 122 var stateType: state is stateType.value; 123 local 124 var file: stateFile is STD_NULL; 125 var string: headerLine is ""; 126 var bigInteger: position is 0_; 127 var bigInteger: badAreaSize is 0_; 128 begin 129 stateFile := open(stateFileName, "r"); 130 if stateFile <> STD_NULL then 131 headerLine := getln(stateFile); 132 if headerLine = "Savehd7 Version 2.1" then 133 state.stateFileName := stateFileName; 134 state.inFileName := getln(stateFile); 135 state.outFileName := getln(stateFile); 136 readln(stateFile, state.phase); 137 readln(stateFile, state.skipSize); 138 readln(stateFile, state.chunkSize); 139 readln(stateFile, state.blockSize); 140 readln(stateFile, state.rereadPosition); 141 readln(stateFile, state.rereadRunsToDo); 142 readln(stateFile, state.maximumOfBadBytes); 143 readln(stateFile, state.badBytesToProcess); 144 readln(stateFile, state.badAreaToProcess); 145 readln(stateFile, state.sizeOfBadAreaToProcess); 146 readln(stateFile, state.blockToProcess); 147 while succeeds(read(stateFile, position)) do 148 readln(stateFile, badAreaSize); 149 # writeln(literal(getln(stateFile))); 150 state.badAreas @:= [position] badAreaSize; 151 # writeln(position <& " " <& badAreaSize); 152 state.sumOfBadBytes +:= badAreaSize; 153 end while; 154 end if; 155 close(stateFile); 156 end if; 157 end func; 158 159 160const proc: saveState (in stateType: state) is func 161 local 162 var file: stateFile is STD_NULL; 163 var bigInteger: position is 0_; 164 var bigInteger: badAreaSize is 0_; 165 begin 166 stateFile := open(state.stateFileName, "w"); 167 if stateFile <> STD_NULL then 168 writeln(stateFile, "Savehd7 Version 2.1"); 169 writeln(stateFile, state.inFileName); 170 writeln(stateFile, state.outFileName); 171 writeln(stateFile, state.phase); 172 writeln(stateFile, state.skipSize); 173 writeln(stateFile, state.chunkSize); 174 writeln(stateFile, state.blockSize); 175 writeln(stateFile, state.rereadPosition); 176 writeln(stateFile, state.rereadRunsToDo); 177 writeln(stateFile, state.maximumOfBadBytes); 178 writeln(stateFile, state.badBytesToProcess); 179 writeln(stateFile, state.badAreaToProcess); 180 writeln(stateFile, state.sizeOfBadAreaToProcess); 181 writeln(stateFile, state.blockToProcess); 182 for position range sort(keys(state.badAreas)) do 183 badAreaSize := state.badAreas[position]; 184 writeln(stateFile, position <& " " <& badAreaSize); 185 end for; 186 close(stateFile); 187 end if; 188 end func; 189 190 191const proc: checkSumOfBadBytes (in stateType: state) is func 192 local 193 var bigInteger: badAreaSize is 0_; 194 var bigInteger: sumOfBadBytes is 0_; 195 begin 196 for badAreaSize range state.badAreas do 197 sumOfBadBytes +:= badAreaSize; 198 end for; 199 if sumOfBadBytes <> state.sumOfBadBytes then 200 writeln(log, " ***** SumOfBadBytes " <& state.sumOfBadBytes <& 201 " not correct (" <& sumOfBadBytes <& ")"); 202 end if; 203 end func; 204 205 206const func bigInteger: countBadBytesInAreasForward (in stateType: state, 207 in bigInteger: startPosition) is func 208 result 209 var bigInteger: badBytesInAreasForward is 0_; 210 local 211 var bigInteger: position is 0_; 212 var bigInteger: badAreaSize is 0_; 213 begin 214 for badAreaSize key position range state.badAreas do 215 if position >= startPosition then 216 badBytesInAreasForward +:= badAreaSize; 217 end if; 218 end for; 219 end func; 220 221 222const proc: listBadAreas (in stateType: state) is func 223 local 224 var bigInteger: position is 0_; 225 var bigInteger: badAreaSize is 0_; 226 begin 227 for position range sort(keys(state.badAreas)) do 228 badAreaSize := state.badAreas[position]; 229 writeln(" " <& position <& " " <& badAreaSize); 230 end for; 231 end func; 232 233 234const func boolean: confirmSave (inout stateType: state) is func 235 result 236 var boolean: confirmed is FALSE; 237 local 238 var bigInteger: outFileSize is -1_; 239 var bigInteger: bytesProcessed is 0_; 240 var bigInteger: halveBadAreaSize is 0_; 241 var boolean: finished is FALSE; 242 var boolean: proceed is TRUE; 243 var string: command is ""; 244 begin 245 if state.stateFileName <> "" then 246 writeln; 247 writeln("Conditions to save the partition:"); 248 writeln(" Input file name: " <& state.inFileName); 249 if fileOpenSucceeds(state.inFileName) then 250 state.inFileSize := bigFileSize(state.inFileName); 251 writeln(" Input file size: " <& state.inFileSize); 252 else 253 state.inFileSize := -1_; 254 end if; 255 writeln(" Output file name: " <& state.outFileName); 256 if fileOpenSucceeds(state.outFileName) then 257 outFileSize := bigFileSize(state.outFileName); 258 writeln(" Output file size: " <& outFileSize); 259 end if; 260 if state.phase >= REREAD and state.rereadPosition > 1_ then 261 writeln(" Rereaded: " <& pred(state.rereadPosition)); 262 end if; 263 if state.inFileSize = -1_ then 264 writeln(" ***** Input file not existing or not accessible"); 265 else 266 # writeln("outFileSize=" <& outFileSize <& " inFileSize=" <& state.inFileSize); 267 write(" State: "); 268 if outFileSize = -1_ then 269 writeln("Nothing saved"); 270 state.phase := COPY; 271 elsif state.phase = COPY or state.inFileSize <> outFileSize then 272 writeln("Copy - " <& 273 outFileSize * 100_ / state.inFileSize digits 4 <& "% done"); 274 state.phase := COPY; 275 elsif state.phase = REREAD then 276 writeln("Reread #" <& state.rereadRunsToDo <& " - " <& 277 pred(state.rereadPosition) * 100_ / state.inFileSize digits 4 <& "% done"); 278 state.phase := REREAD; 279 elsif state.phase = IMPROVE or state.phase = FIX then 280 if state.badAreaToProcess <= state.inFileSize and state.badBytesToProcess <> 0_ then 281 state.badBytesInUnprocessedAreas := countBadBytesInAreasForward(state, 282 state.badAreaToProcess + state.sizeOfBadAreaToProcess); 283 if state.blockToProcess >= state.badAreaToProcess then 284 bytesProcessed := state.badBytesToProcess - 285 (state.badBytesInUnprocessedAreas + 286 (state.blockToProcess - state.badAreaToProcess) + 287 bigInteger(state.blockSize)); 288 else 289 bytesProcessed := state.badBytesToProcess - state.badBytesInUnprocessedAreas; 290 end if; 291 if state.phase = IMPROVE then 292 write("Improve - "); 293 else 294 write("Fix - "); 295 end if; 296 writeln(bytesProcessed * 100_ / state.badBytesToProcess digits 4 <& "% done"); 297 else 298 writeln("Done"); 299 finished := TRUE; 300 end if; 301 elsif state.phase = EXAMINE then 302 halveBadAreaSize := state.sizeOfBadAreaToProcess div 303 bigInteger(state.blockSize) div 2_ * 304 bigInteger(state.blockSize); 305 state.badBytesInUnprocessedAreas := countBadBytesInAreasForward(state, 306 state.badAreaToProcess + state.sizeOfBadAreaToProcess) + halveBadAreaSize; 307 if state.blockToProcess >= state.badAreaToProcess + halveBadAreaSize then 308 bytesProcessed := state.badBytesToProcess - 309 (state.badBytesInUnprocessedAreas + (state.badAreaToProcess + 310 state.sizeOfBadAreaToProcess - state.blockToProcess)); 311 else 312 bytesProcessed := state.badBytesToProcess - 313 (state.badBytesInUnprocessedAreas + 314 (state.blockToProcess - state.badAreaToProcess) + 315 bigInteger(state.blockSize)); 316 end if; 317 writeln("Examine - " <& 318 bytesProcessed * 100_ / state.badBytesToProcess digits 4 <& "% done"); 319 state.phase := EXAMINE; 320 else 321 writeln("Done"); 322 finished := TRUE; 323 end if; 324 if state.sumOfBadBytes <> 0_ then 325 writeln(" Total bad bytes: " <& state.sumOfBadBytes); 326 writeln; 327 write("Should the bad areas be listed (Y/N/Q)? "); 328 command := upper(getln(IN)); 329 if command = "Y" then 330 writeln; 331 writeln("List of bad areas:"); 332 writeln(" position size"); 333 listBadAreas(state); 334 elsif command = "Q" then 335 proceed := FALSE; 336 end if; 337 end if; 338 if proceed and not finished then 339 writeln; 340 write("Should the save "); 341 if outFileSize = -1_ then 342 write("start"); 343 else 344 write("continue"); 345 end if; 346 write(" (type 'Yes' to confirm)? "); 347 command := getln(IN); 348 proceed := upper(command) <> "Q"; 349 if command = "Yes" then 350 confirmed := TRUE; 351 state.emptyBlock := "\0;" mult state.blockSize; 352 end if; 353 end if; 354 end if; 355 end if; 356 if proceed and not confirmed then 357 if state.stateFileName <> "" then 358 writeln; 359 write("Should a different partition be saved (Y/N/Q)? "); 360 command := upper(getln(IN)); 361 else 362 command := "Y"; 363 end if; 364 if command = "Y" then 365 state := stateType.value; 366 state.stateFileName := dataFileName; 367 writeln; 368 writeln("Please enter the conditions to save the partition:"); 369 write(" Input file name: "); 370 state.inFileName := getln(IN); 371 if state.inFileName <> "" then 372 repeat 373 write(" Output file name: "); 374 state.outFileName := getln(IN); 375 if fileOpenSucceeds(state.outFileName) then 376 writeln(" ***** Output file already exists"); 377 end if; 378 until state.outFileName = "" or not fileOpenSucceeds(state.outFileName); 379 if state.outFileName <> "" then 380 confirmed := confirmSave(state); 381 if confirmed then 382 saveState(state); 383 if not fileOpenSucceeds(state.stateFileName) then 384 writeln(" ***** Unable to write state file: " <& 385 state.stateFileName); 386 confirmed := FALSE; 387 else 388 if fileType(logFileName) = FILE_REGULAR then 389 removeFile(logFileName); 390 end if; 391 state.phase := COPY; 392 end if; 393 end if; 394 end if; 395 end if; 396 end if; 397 end if; 398 end func; 399 400 401const proc: nextPhase (inout stateType: state) is func 402 begin 403 case state.phase of 404 when {COPY}: 405 incr(state.phase); 406 when {REREAD}: 407 decr(state.rereadRunsToDo); 408 when {IMPROVE}: 409 incr(state.phase); 410 when {EXAMINE}: 411 incr(state.phase); 412 when {FIX}: 413 incr(state.phase); 414 end case; 415 if state.phase = REREAD then 416 if state.rereadRunsToDo > 0 then 417 state.rereadPosition := 1_; 418 else 419 incr(state.phase); 420 end if; 421 end if; 422 case state.phase of 423 when {IMPROVE, EXAMINE, FIX}: 424 state.badBytesToProcess := state.sumOfBadBytes; 425 state.badAreaToProcess := 0_; 426 state.sizeOfBadAreaToProcess := 0_; 427 state.blockToProcess := 0_; 428 when {DONE}: 429 writeln(state.phase rpad 7 <& " "); 430 writeln; 431 writeln("Saving finished"); 432 end case; 433 end func; 434 435 436const func bigInteger: bigLength (in string: stri) is 437 return bigInteger(length(stri)); 438 439 440const func bigInteger: afterMaximumBadArea (in stateType: state) is func 441 result 442 var bigInteger: maximumPosition is 0_ 443 local 444 var bigInteger: position is 0_; 445 var bigInteger: badAreaSize is 0_; 446 begin 447 for badAreaSize key position range state.badAreas do 448 if position + badAreaSize > maximumPosition then 449 maximumPosition := position + badAreaSize; 450 end if; 451 end for; 452 end func; 453 454 455const func bigInteger: searchPossibleAreaCombine (in stateType: state, 456 in bigInteger: newAreaPosition, in bigInteger: newAreaSize) is func 457 result 458 var bigInteger: positionFound is -1_; 459 local 460 var bigInteger: position is 0_; 461 var bigInteger: badAreaSize is 0_; 462 begin 463 for badAreaSize key position range state.badAreas do 464 if (position <> newAreaPosition or badAreaSize <> newAreaSize) and 465 newAreaPosition + newAreaSize >= position and 466 newAreaPosition <= position + badAreaSize then 467 positionFound := position; 468 end if; 469 end for; 470 end func; 471 472 473const proc: combineBadAreas (inout stateType: state, in bigInteger: oldAreaPosition, 474 inout bigInteger: newAreaPosition, inout bigInteger: newAreaSize) is func 475 local 476 var bigInteger: badAreaSize is 0_; 477 begin 478 badAreaSize := state.badAreas[oldAreaPosition]; 479 if newAreaPosition < oldAreaPosition then 480 excl(state.badAreas, oldAreaPosition); 481 state.sumOfBadBytes -:= badAreaSize; 482 if newAreaPosition + newAreaSize <= oldAreaPosition + badAreaSize then 483 newAreaSize := badAreaSize + (oldAreaPosition - newAreaPosition); 484 end if; 485 state.badAreas @:= [newAreaPosition] newAreaSize; 486 state.sumOfBadBytes +:= newAreaSize; 487 writeln(log, "Bad area at " <& oldAreaPosition <& 488 " enlarged to new position " <& newAreaPosition <& 489 " with new size " <& newAreaSize); 490 elsif newAreaPosition + newAreaSize > oldAreaPosition + badAreaSize then 491 state.sumOfBadBytes -:= badAreaSize; 492 newAreaSize +:= newAreaPosition - oldAreaPosition; 493 newAreaPosition := oldAreaPosition; 494 state.badAreas[oldAreaPosition] := newAreaSize; 495 state.sumOfBadBytes +:= newAreaSize; 496 writeln(log, "Bad area at " <& oldAreaPosition <& 497 " enlarged to size " <& newAreaSize); 498 else 499 writeln(log, " ***** Bad area at " <& newAreaPosition <& 500 " with size " <& newAreaSize <& " not handled"); 501 end if; 502 end func; 503 504 505const proc: addBadArea (inout stateType: state, 506 in var bigInteger: newAreaPosition, in var bigInteger: newAreaSize) is func 507 local 508 var bigInteger: positionFound is 0_; 509 begin 510 positionFound := searchPossibleAreaCombine(state, newAreaPosition, newAreaSize); 511 if positionFound <> -1_ then 512 repeat 513 combineBadAreas(state, positionFound, newAreaPosition, newAreaSize); 514 positionFound := searchPossibleAreaCombine(state, newAreaPosition, newAreaSize); 515 until positionFound = -1_; 516 elsif newAreaPosition not in state.badAreas then 517 state.badAreas @:= [newAreaPosition] newAreaSize; 518 state.sumOfBadBytes +:= newAreaSize; 519 writeln(log, "New bad area found at " <& newAreaPosition <& 520 " with size " <& newAreaSize); 521 else 522 writeln(log, "Bad area at " <& newAreaPosition <& 523 " with size " <& newAreaSize <& " already present"); 524 end if; 525 if state.sumOfBadBytes > state.maximumOfBadBytes then 526 state.maximumOfBadBytes := state.sumOfBadBytes; 527 end if; 528 end func; 529 530 531const func bigInteger: searchPossibleAreaShrink (in stateType: state, 532 in bigInteger: okayAreaPosition, in bigInteger: okayAreaSize) is func 533 result 534 var bigInteger: positionFound is -1_; 535 local 536 var bigInteger: position is 0_; 537 var bigInteger: badAreaSize is 0_; 538 begin 539 for badAreaSize key position range state.badAreas do 540 if okayAreaPosition + okayAreaSize > position and 541 okayAreaPosition < position + badAreaSize then 542 positionFound := position; 543 end if; 544 end for; 545 end func; 546 547 548const proc: shrinkBadAreas (inout stateType: state, in bigInteger: oldAreaPosition, 549 in bigInteger: okayAreaPosition, in bigInteger: okayAreaSize) is func 550 local 551 var bigInteger: badAreaSize is 0_; 552 var bigInteger: badAreaSizeReduction is 0_; 553 begin 554 badAreaSize := state.badAreas[oldAreaPosition]; 555 if okayAreaPosition <= oldAreaPosition then 556 excl(state.badAreas, oldAreaPosition); 557 state.sumOfBadBytes -:= badAreaSize; 558 badAreaSizeReduction := okayAreaPosition - oldAreaPosition + okayAreaSize; 559 if badAreaSize > badAreaSizeReduction then 560 badAreaSize -:= badAreaSizeReduction; 561 state.badAreas @:= [okayAreaPosition + okayAreaSize] badAreaSize; 562 state.sumOfBadBytes +:= badAreaSize; 563 writeln(log, "Bad area at " <& oldAreaPosition <& 564 " shrunk to new position " <& okayAreaPosition + okayAreaSize <& 565 " with new size " <& badAreaSize); 566 else 567 writeln(log, "Bad area at " <& oldAreaPosition <& 568 " with size " <& badAreaSize <& " removed"); 569 end if; 570 else 571 state.badAreas[oldAreaPosition] := okayAreaPosition - oldAreaPosition; 572 if okayAreaPosition + okayAreaSize < oldAreaPosition + badAreaSize then 573 state.badAreas @:= [okayAreaPosition + okayAreaSize] 574 oldAreaPosition - okayAreaPosition + badAreaSize - okayAreaSize; 575 state.sumOfBadBytes -:= okayAreaSize; 576 writeln(log, "Bad area at " <& oldAreaPosition <& 577 " splited to area with size " <& state.badAreas[oldAreaPosition] <& 578 " and area at " <& okayAreaPosition + okayAreaSize <& 579 " with size " <& state.badAreas[okayAreaPosition + okayAreaSize]); 580 else 581 state.sumOfBadBytes -:= oldAreaPosition + badAreaSize - okayAreaPosition; 582 writeln(log, "Bad area at " <& oldAreaPosition <& 583 " with size " <& badAreaSize <& 584 " shrunk to size " <& state.badAreas[oldAreaPosition]); 585 end if; 586 end if; 587 end func; 588 589 590const proc: removeBadArea (inout stateType: state, 591 in bigInteger: okayAreaPosition, in bigInteger: okayAreaSize) is func 592 local 593 var bigInteger: positionFound is 0_; 594 begin 595 positionFound := searchPossibleAreaShrink(state, okayAreaPosition, okayAreaSize); 596 if positionFound <> -1_ then 597 repeat 598 shrinkBadAreas(state, positionFound, okayAreaPosition, okayAreaSize); 599 positionFound := searchPossibleAreaShrink(state, okayAreaPosition, okayAreaSize); 600 until positionFound = -1_; 601 end if; 602 end func; 603 604 605const proc: copyFile (inout stateType: state) is func 606 local 607 var file: inFile is STD_NULL; 608 var file: outFile is STD_NULL; 609 var bigInteger: currPosition is 1_; 610 var string: chunkContent is ""; 611 var bigInteger: missingBytes is 0_; 612 begin 613 inFile := open(state.inFileName, "r"); 614 outFile := open(state.outFileName, "r+"); 615 if outFile = STD_NULL then 616 outFile := open(state.outFileName, "w"); 617 end if; 618 if inFile <> STD_NULL and outFile <> STD_NULL then 619 writeln(log, "Start copy from " <& state.inFileName <& " to " <& state.outFileName); 620 currPosition := bigLength(outFile) + 1_; 621 if afterMaximumBadArea(state) > currPosition then 622 currPosition := afterMaximumBadArea(state); 623 end if; 624 showProgress(state, state.inFileSize, pred(currPosition), pred(currPosition)); 625 while currPosition <= state.inFileSize and not keypressed(KEYBOARD) do 626 seek(inFile, currPosition); 627 chunkContent := gets(inFile, state.chunkSize); 628 if length(chunkContent) <> 0 then 629 seek(outFile, currPosition); 630 write(outFile, chunkContent); 631 end if; 632 if length(chunkContent) <> state.chunkSize and 633 currPosition + bigLength(chunkContent) <= state.inFileSize then 634 if currPosition + bigInteger(state.chunkSize) > state.inFileSize then 635 missingBytes := succ(state.inFileSize) - currPosition - bigLength(chunkContent); 636 else 637 missingBytes := bigInteger(state.chunkSize) - bigLength(chunkContent); 638 end if; 639 seek(outFile, currPosition + bigLength(chunkContent)); 640 write(outFile, "\0;" mult ord(missingBytes)); 641 addBadArea(state, currPosition + bigLength(chunkContent), missingBytes); 642 if state.skipSize > state.chunkSize then 643 if currPosition + bigInteger(state.skipSize) > state.inFileSize then 644 missingBytes := succ(state.inFileSize) - currPosition; 645 else 646 missingBytes := bigInteger(state.skipSize); 647 end if; 648 missingBytes -:= bigInteger(state.chunkSize); 649 if missingBytes > 0_ then 650 seek(outFile, currPosition + bigInteger(state.chunkSize)); 651 write(outFile, "\0;" mult ord(missingBytes)); 652 addBadArea(state, currPosition + bigInteger(state.chunkSize), missingBytes); 653 end if; 654 currPosition +:= bigInteger(state.skipSize); 655 else 656 currPosition +:= bigInteger(state.chunkSize); 657 end if; 658 showProgress(state, state.inFileSize, pred(currPosition), pred(currPosition)); 659 saveState(state); 660 else 661 currPosition +:= bigInteger(state.chunkSize); 662 showProgress(state, state.inFileSize, pred(currPosition), pred(currPosition)); 663 end if; 664 end while; 665 666 if currPosition > state.inFileSize then 667 showProgress(state, state.inFileSize, state.inFileSize, state.inFileSize); 668 writeln(log, "Stop copy from " <& state.inFileName <& " to " <& state.outFileName); 669 nextPhase(state); 670 else 671 writeln; 672 writeln; 673 writeln("Copy paused - To continue restart the program"); 674 writeln(log, "Pause copy from " <& state.inFileName <& " to " <& state.outFileName); 675 end if; 676 close(inFile); 677 close(outFile); 678 end if; 679 end func; 680 681 682const func string: repairBlock (inout stateType: state, in bigInteger: blockPosition, 683 in string: sourceBlock, in string: destinationBlock) is func 684 result 685 var string: repairedBlock is ""; 686 begin 687 repairedBlock := destinationBlock; 688 if sourceBlock = destinationBlock then 689 removeBadArea(state, blockPosition, bigLength(sourceBlock)); 690 if destinationBlock <> state.emptyBlock then 691 writeln(log, "fix block " <& blockPosition <& 692 " (length " <& length(sourceBlock) <& "), which was not empty before"); 693 end if; 694 elsif destinationBlock = state.emptyBlock then 695 repairedBlock := sourceBlock & 696 "\0;" mult (length(destinationBlock) - length(sourceBlock)); 697 removeBadArea(state, blockPosition, bigLength(sourceBlock)); 698 writeln(log, "fix block " <& blockPosition <& 699 " (length " <& length(sourceBlock) <& "), which was empty before"); 700 elsif sourceBlock = "" then 701 writeln(log, "leave block " <& blockPosition <& 702 " (length " <& length(sourceBlock) <& ") unchanged, which is empty now"); 703 else 704 writeln(log, "block " <& blockPosition <& 705 " (length " <& length(sourceBlock) <& ") different and not empty in both cases"); 706 addBadArea(state, blockPosition, bigLength(sourceBlock)); 707 end if; 708 # checkSumOfBadBytes(state); 709 end func; 710 711 712const proc: repairChunk (inout stateType: state, inout file: outFile, 713 in bigInteger: chunkPosition, in string: sourceChunk, 714 in string: destinationChunk) is func 715 local 716 var string: repairedChunk is ""; 717 var integer: index is 0; 718 var string: sourceBlock is ""; 719 var string: destinationBlock is ""; 720 begin 721 repairedChunk := destinationChunk; 722 for index range 1 to length(sourceChunk) step state.blockSize do 723 sourceBlock := sourceChunk[index len state.blockSize]; 724 destinationBlock := repairedChunk[index len state.blockSize]; 725 destinationBlock := repairBlock(state, 726 chunkPosition + bigInteger(index) - 1_, 727 sourceBlock, destinationBlock); 728 repairedChunk := repairedChunk[.. pred(index)] & 729 destinationBlock & repairedChunk[index + state.blockSize ..]; 730 end for; 731 if repairedChunk <> destinationChunk then 732 seek(outFile, chunkPosition); 733 write(outFile, repairedChunk); 734 end if; 735 state.rereadPosition := chunkPosition + bigLength(sourceChunk); 736 # checkSumOfBadBytes(state); 737 saveState(state); 738 end func; 739 740 741const proc: rereadFile (inout stateType: state) is func 742 local 743 var file: inFile is STD_NULL; 744 var file: outFile is STD_NULL; 745 var bigInteger: currPosition is 1_; 746 var string: sourceChunk is ""; 747 var string: destinationChunk is ""; 748 begin 749 inFile := open(state.inFileName, "r"); 750 outFile := open(state.outFileName, "r+"); 751 if inFile <> STD_NULL and outFile <> STD_NULL then 752 writeln(log, "Start reread from " <& state.inFileName <& " to " <& state.outFileName); 753 currPosition := state.rereadPosition; 754 showProgress(state, state.inFileSize, pred(currPosition), state.inFileSize); 755 while currPosition <= state.inFileSize and not keypressed(KEYBOARD) do 756 seek(inFile, currPosition); 757 sourceChunk := gets(inFile, state.chunkSize); 758 if length(sourceChunk) <> 0 then 759 seek(outFile, currPosition); 760 destinationChunk := gets(outFile, length(sourceChunk)); 761 else 762 destinationChunk := ""; 763 end if; 764 if sourceChunk <> destinationChunk then 765 repairChunk(state, outFile, currPosition, sourceChunk, destinationChunk); 766 currPosition +:= bigInteger(state.skipSize); 767 else 768 currPosition +:= bigInteger(state.chunkSize); 769 end if; 770 showProgress(state, state.inFileSize, pred(currPosition), state.inFileSize); 771 end while; 772 773 if currPosition > state.inFileSize then 774 state.rereadPosition := state.inFileSize + 1_; 775 showProgress(state, state.inFileSize, state.inFileSize, state.inFileSize); 776 writeln(log, "Stop reread from " <& state.inFileName <& " to " <& state.outFileName); 777 nextPhase(state); 778 else 779 state.rereadPosition := currPosition; 780 writeln; 781 writeln; 782 writeln("Reread paused - To continue restart the program"); 783 writeln(log, "Pause reread from " <& state.inFileName <& " to " <& state.outFileName); 784 end if; 785 close(inFile); 786 close(outFile); 787 end if; 788 end func; 789 790 791const proc: determineBadAreaToProcess (inout stateType: state) is func 792 local 793 var bigInteger: position is 0_; 794 var bigInteger: currBadArea is 0_; 795 var bigInteger: badAreaSize is 0_; 796 begin 797 currBadArea := state.inFileSize + 1_; 798 for badAreaSize key position range state.badAreas do 799 if position >= state.badAreaToProcess and position < currBadArea then 800 currBadArea := position; 801 end if; 802 end for; 803 if currBadArea <= state.inFileSize then 804 if currBadArea <> state.badAreaToProcess then 805 state.badAreaToProcess := currBadArea; 806 state.sizeOfBadAreaToProcess := state.badAreas[currBadArea]; 807 end if; 808 else 809 state.badAreaToProcess := state.inFileSize + 1_; 810 state.sizeOfBadAreaToProcess := 0_; 811 end if; 812 end func; 813 814 815const proc: processAreaBackward (inout stateType: state, 816 inout file: inFile, inout file: outFile) is func 817 local 818 var string: sourceBlock is ""; 819 var string: destinationBlock is ""; 820 var string: repairedBlock is ""; 821 begin 822 showProgress(state, state.badBytesToProcess, state.badBytesToProcess - 823 (state.badBytesInUnprocessedAreas + (state.blockToProcess - state.badAreaToProcess) + 824 bigInteger(state.blockSize)), state.inFileSize); 825 saveState(state); 826 while state.blockToProcess >= state.badAreaToProcess and not keypressed(KEYBOARD) do 827 # writeln(log, "process block " <& state.blockToProcess); 828 seek(inFile, state.blockToProcess); 829 sourceBlock := gets(inFile, state.blockSize); 830 if length(sourceBlock) <> 0 then 831 seek(outFile, state.blockToProcess); 832 destinationBlock := gets(outFile, length(sourceBlock)); 833 repairedBlock := repairBlock(state, state.blockToProcess, 834 sourceBlock, destinationBlock); 835 if repairedBlock <> destinationBlock then 836 seek(outFile, state.blockToProcess); 837 write(outFile, repairedBlock); 838 end if; 839 end if; 840 if length(sourceBlock) = state.blockSize or state.phase = FIX then 841 state.blockToProcess -:= bigInteger(state.blockSize); 842 # writeln(log, "succeed -> state.blockToProcess " <& state.blockToProcess); 843 else 844 state.blockToProcess := state.badAreaToProcess - bigInteger(state.blockSize); 845 # writeln(log, "fail -> state.blockToProcess " <& state.blockToProcess); 846 end if; 847 showProgress(state, state.badBytesToProcess, state.badBytesToProcess - 848 (state.badBytesInUnprocessedAreas + (state.blockToProcess - state.badAreaToProcess) + 849 bigInteger(state.blockSize)), state.inFileSize); 850 saveState(state); 851 end while; 852 end func; 853 854 855const proc: fixOrImproveFile (inout stateType: state) is func 856 local 857 var file: inFile is STD_NULL; 858 var file: outFile is STD_NULL; 859 var bigInteger: lastBlockPosition is 0_; 860 begin 861 inFile := open(state.inFileName, "r"); 862 outFile := open(state.outFileName, "r+"); 863 if inFile <> STD_NULL and outFile <> STD_NULL then 864 writeln(log, "Start " <& state.phase <& " from " <& state.inFileName <& 865 " to " <& state.outFileName); 866 867 while state.badAreaToProcess <= state.inFileSize and not keypressed(KEYBOARD) do 868 determineBadAreaToProcess(state); 869 if state.badAreaToProcess <= state.inFileSize then 870 state.badBytesInUnprocessedAreas := countBadBytesInAreasForward(state, 871 state.badAreaToProcess + state.sizeOfBadAreaToProcess); 872 lastBlockPosition := state.badAreaToProcess + state.sizeOfBadAreaToProcess - 873 bigInteger(state.blockSize); 874 if state.blockToProcess < state.badAreaToProcess or 875 state.blockToProcess >= lastBlockPosition then 876 state.blockToProcess := lastBlockPosition; 877 end if; 878 processAreaBackward(state, inFile, outFile); 879 if state.blockToProcess < state.badAreaToProcess then 880 state.badAreaToProcess +:= state.sizeOfBadAreaToProcess; 881 state.sizeOfBadAreaToProcess := 0_; 882 saveState(state); 883 end if; 884 end if; 885 end while; 886 887 if state.badAreaToProcess > state.inFileSize then 888 state.blockToProcess := state.inFileSize + 1_; 889 if state.badBytesToProcess <> 0_ then 890 showProgress(state, state.badBytesToProcess, state.badBytesToProcess, state.inFileSize); 891 end if; 892 writeln(log, "Stop " <& state.phase <& " from " <& state.inFileName <& 893 " to " <& state.outFileName); 894 nextPhase(state); 895 else 896 writeln; 897 writeln; 898 if state.phase = FIX then 899 writeln("Fix paused - To continue restart the program"); 900 else 901 writeln("Improve paused - To continue restart the program"); 902 end if; 903 writeln(log, "Pause " <& state.phase <& " from " <& state.inFileName <& 904 " to " <& state.outFileName); 905 end if; 906 close(inFile); 907 close(outFile); 908 end if; 909 end func; 910 911 912const proc: processAreaForward (inout stateType: state, 913 inout file: inFile, inout file: outFile) is func 914 local 915 var string: sourceBlock is ""; 916 var string: destinationBlock is ""; 917 var string: repairedBlock is ""; 918 begin 919 showProgress(state, state.badBytesToProcess, state.badBytesToProcess - 920 (state.badBytesInUnprocessedAreas + (state.badAreaToProcess + state.sizeOfBadAreaToProcess - 921 state.blockToProcess)), state.inFileSize); 922 saveState(state); 923 while state.blockToProcess < state.badAreaToProcess + state.sizeOfBadAreaToProcess and 924 not keypressed(KEYBOARD) do 925 # writeln(log, "process block " <& state.blockToProcess); 926 seek(inFile, state.blockToProcess); 927 sourceBlock := gets(inFile, state.blockSize); 928 if length(sourceBlock) <> 0 then 929 seek(outFile, state.blockToProcess); 930 destinationBlock := gets(outFile, length(sourceBlock)); 931 repairedBlock := repairBlock(state, state.blockToProcess, 932 sourceBlock, destinationBlock); 933 if repairedBlock <> destinationBlock then 934 seek(outFile, state.blockToProcess); 935 write(outFile, repairedBlock); 936 end if; 937 end if; 938 if length(sourceBlock) = state.blockSize then 939 state.blockToProcess +:= bigInteger(state.blockSize); 940 # writeln(log, "succeed -> state.blockToProcess " <& state.blockToProcess); 941 else 942 state.blockToProcess := state.badAreaToProcess + state.sizeOfBadAreaToProcess; 943 # writeln(log, "fail -> state.blockToProcess " <& state.blockToProcess); 944 end if; 945 showProgress(state, state.badBytesToProcess, state.badBytesToProcess - 946 (state.badBytesInUnprocessedAreas + (state.badAreaToProcess + state.sizeOfBadAreaToProcess - 947 state.blockToProcess)), state.inFileSize); 948 saveState(state); 949 end while; 950 end func; 951 952 953const proc: examineFile (inout stateType: state) is func 954 local 955 var file: inFile is STD_NULL; 956 var file: outFile is STD_NULL; 957 var bigInteger: halveBadAreaSize is 0_; 958 var bigInteger: middleBlockPosition is 0_; 959 begin 960 inFile := open(state.inFileName, "r"); 961 outFile := open(state.outFileName, "r+"); 962 if inFile <> STD_NULL and outFile <> STD_NULL then 963 writeln(log, "Start " <& state.phase <& " from " <& state.inFileName <& 964 " to " <& state.outFileName); 965 966 while state.badAreaToProcess <= state.inFileSize and not keypressed(KEYBOARD) do 967 determineBadAreaToProcess(state); 968 # writeln(log, "Examine " <& state.badAreaToProcess <& 969 # " with size " <& state.sizeOfBadAreaToProcess); 970 if state.badAreaToProcess <= state.inFileSize then 971 halveBadAreaSize := state.sizeOfBadAreaToProcess div 972 bigInteger(state.blockSize) div 2_ * 973 bigInteger(state.blockSize); 974 state.badBytesInUnprocessedAreas := countBadBytesInAreasForward(state, 975 state.badAreaToProcess + state.sizeOfBadAreaToProcess) + halveBadAreaSize; 976 middleBlockPosition := state.badAreaToProcess + halveBadAreaSize; 977 if state.blockToProcess <= state.badAreaToProcess or 978 state.blockToProcess >= state.badAreaToProcess + state.sizeOfBadAreaToProcess then 979 state.blockToProcess := middleBlockPosition; 980 end if; 981 if state.blockToProcess >= middleBlockPosition then 982 # writeln(log, "processAreaForward " <& state.blockToProcess); 983 processAreaForward(state, inFile, outFile); 984 if state.blockToProcess >= state.badAreaToProcess + state.sizeOfBadAreaToProcess and 985 state.badAreas[state.badAreaToProcess] <> state.sizeOfBadAreaToProcess then 986 state.blockToProcess := middleBlockPosition - bigInteger(state.blockSize); 987 end if; 988 end if; 989 if state.blockToProcess < middleBlockPosition then 990 state.badBytesInUnprocessedAreas := countBadBytesInAreasForward(state, middleBlockPosition); 991 # writeln(log, "processAreaBackward " <& state.blockToProcess); 992 processAreaBackward(state, inFile, outFile); 993 end if; 994 if state.blockToProcess < state.badAreaToProcess or 995 state.blockToProcess >= state.badAreaToProcess + state.sizeOfBadAreaToProcess then 996 if state.badAreaToProcess in state.badAreas then 997 if state.badAreas[state.badAreaToProcess] = state.sizeOfBadAreaToProcess then 998 state.badAreaToProcess +:= state.sizeOfBadAreaToProcess; 999 state.sizeOfBadAreaToProcess := 0_; 1000 else 1001 state.sizeOfBadAreaToProcess := state.badAreas[state.badAreaToProcess]; 1002 end if; 1003 saveState(state); 1004 end if; 1005 end if; 1006 end if; 1007 end while; 1008 1009 if state.badAreaToProcess > state.inFileSize then 1010 state.blockToProcess := state.inFileSize + 1_; 1011 if state.badBytesToProcess <> 0_ then 1012 showProgress(state, state.badBytesToProcess, state.badBytesToProcess, state.inFileSize); 1013 end if; 1014 writeln(log, "Stop " <& state.phase <& " from " <& state.inFileName <& 1015 " to " <& state.outFileName); 1016 nextPhase(state); 1017 else 1018 writeln; 1019 writeln; 1020 writeln("Examine paused - To continue restart the program"); 1021 writeln(log, "Pause " <& state.phase <& " from " <& state.inFileName <& 1022 " to " <& state.outFileName); 1023 end if; 1024 close(inFile); 1025 close(outFile); 1026 end if; 1027 end func; 1028 1029 1030const proc: writeHelp is func 1031 begin 1032 writeln; 1033 writeln("The Savehd7 utility can be used to save a potentially damaged harddisk"); 1034 writeln("partition to an image file. Savehd7 works for all filesystems."); 1035 writeln; 1036 writeln("The Savehd7 program is designed to copy from a device file to a disk"); 1037 writeln("image file even if read errors occur. It will try to copy as much as"); 1038 writeln("possible and leave the unreadable blocks filled with zero bytes. The"); 1039 writeln("file system checker can fix the saved disk image afterwards. Finally the"); 1040 writeln("saved and repaired disk image file can be mounted with the loop mount"); 1041 writeln("feature (which can mount a file as if it is a device)."); 1042 writeln; 1043 writeln("Several conditions must be fulfilled to use Savehd7:"); 1044 writeln("- Operating systems without device files are not supported."); 1045 writeln("- To get access to the device file Savehd7 must be started as superuser."); 1046 writeln("- Nothing should be mounted on the device file processed by Savehd7."); 1047 writeln("- Programs which possibly change the device file such as filesystem"); 1048 writeln(" checkers should not run in parallel to Savehd7."); 1049 writeln("- There must be enough free space on the destination device to copy the"); 1050 writeln(" whole partition."); 1051 writeln; 1052 writeln("Operating systems usually retry to read bad blocks again and again in"); 1053 writeln("the hope to succeed finally. Therefore copying from a damaged harddisk"); 1054 writeln("can take very long (many hours even up to several days). While Savehd7"); 1055 writeln("is processing it can be interrupted by pressing any key. Since the OS"); 1056 writeln("spends so much time reading bad blocks it may take some time until"); 1057 writeln("Savehd7 has a chance to save the processing state and exit afterwards."); 1058 writeln("Savehd7 should not be interrupted with control-C or some other signal,"); 1059 writeln("since this prevents saving the processing state. If Savehd7 is restarted"); 1060 writeln("it can continue at the position where it was interrupted. The file"); 1061 writeln("\"savehd7.dat\" is used to maintain the processing state. Savehd7 writes"); 1062 writeln("also logging information to the file \"savehd7.log\"."); 1063 writeln; 1064 end func; 1065 1066 1067const proc: main is func 1068 local 1069 var stateType: state is stateType.value; 1070 begin 1071 writeln("Savehd7 Version 2.1 - Save a potentially damaged harddisk partition"); 1072 writeln("Copyright (C) 2006, 2009 Thomas Mertes"); 1073 writeln("This is free software; see the source for copying conditions. There is NO"); 1074 writeln("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."); 1075 writeln("Savehd7 is written in the Seed7 programming language"); 1076 writeln("Homepage: http://seed7.sourceforge.net"); 1077 if length(argv(PROGRAM)) >= 1 and lower(argv(PROGRAM)[1]) = "-h" then 1078 writeHelp; 1079 else 1080 writeln("Use 'savehd7 -h' to get more information"); 1081 state := loadState(dataFileName); 1082 if confirmSave(state) then 1083 log := open(logFileName, "a"); 1084 if log = STD_NULL then 1085 writeln(" ***** Could not open log file."); 1086 else 1087 log := openLine(log); 1088 writeln; 1089 writeln("Processing - Press any key to pause (may take some time to react)"); 1090 writeln(" progress: okay: bad blks: fixed: bad bytes:"); 1091 if state.phase = COPY then 1092 copyFile(state); 1093 end if; 1094 while state.phase = REREAD and not keypressed(KEYBOARD) do 1095 rereadFile(state); 1096 end while; 1097 while state.phase = IMPROVE and not keypressed(KEYBOARD) do 1098 fixOrImproveFile(state); 1099 end while; 1100 while state.phase = EXAMINE and not keypressed(KEYBOARD) do 1101 examineFile(state); 1102 end while; 1103 while state.phase = FIX and not keypressed(KEYBOARD) do 1104 fixOrImproveFile(state); 1105 end while; 1106 saveState(state); 1107 end if; 1108 end if; 1109 writeln; 1110 end if; 1111 end func; 1112