(********************************************************************)
(*                                                                  *)
(*  savehd7.sd7   Save a harddisk which has hardware errors.        *)
(*  Copyright (C) 2006, 2009  Thomas Mertes                         *)
(*                                                                  *)
(*  This program is free software; you can redistribute it and/or   *)
(*  modify it under the terms of the GNU General Public License as  *)
(*  published by the Free Software Foundation; either version 2 of  *)
(*  the License, or (at your option) any later version.             *)
(*                                                                  *)
(*  This program is distributed in the hope that it will be useful, *)
(*  but WITHOUT ANY WARRANTY; without even the implied warranty of  *)
(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   *)
(*  GNU General Public License for more details.                    *)
(*                                                                  *)
(*  You should have received a copy of the GNU General Public       *)
(*  License along with this program; if not, write to the           *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


$ include "seed7_05.s7i";
  include "stdio.s7i";
  include "osfiles.s7i";
  include "keybd.s7i";
  include "bigint.s7i";
  include "bigfile.s7i";
  include "bigrat.s7i";
  include "line.s7i";

const string: dataFileName is "savehd7.dat";
const string: logFileName is "savehd7.log";

var file: log is STD_NULL;

const type: phaseType is new enum
    NONE, COPY, REREAD, IMPROVE, EXAMINE, FIX, DONE
  end enum;

const func string: str (in phaseType: phase) is
  return lower(literal(phase));

const func phaseType: (attr phaseType) parse (in string: stri) is func
  result
    var phaseType: phase is phaseType.first;
  begin
    while str(phase) <> stri do
      incr(phase);
    end while;
  end func;

enable_io(phaseType);

const type: areaHashType is hash [bigInteger] bigInteger;

const type: stateType is new struct
    var string: stateFileName is "";
    var string: inFileName is "";
    var bigInteger: inFileSize is -1_;
    var string: outFileName is "";
    var phaseType: phase is NONE;
    var integer: skipSize is 2 ** 20;
    var integer: chunkSize is 2 ** 14;
    var integer: blockSize is 512;
    var bigInteger: rereadPosition is 1_;
    var integer: rereadRunsToDo is 0;
    var bigInteger: maximumOfBadBytes is 0_;
    var bigInteger: badBytesToProcess is 0_;
    var bigInteger: badAreaToProcess is 0_;
    var bigInteger: sizeOfBadAreaToProcess is 0_;
    var bigInteger: blockToProcess is 0_;
    var areaHashType: badAreas is areaHashType.value;
    var bigInteger: sumOfBadBytes is 0_;
    var bigInteger: badBytesInUnprocessedAreas is 0_;
    var string: emptyBlock is "";
  end struct;


const proc: showProgress (in stateType: state, in bigInteger: bytesToProcess,
    in bigInteger: bytesProcessed, in bigInteger: bytesDone) is func
  local
    var bigRational: percentProgress is 0_/1_;
    var bigRational: percentDone is 0_/1_;
    var bigRational: percentFixed is 0_/1_;
    var bigRational: percentBadBlocks is 0_/1_;
  begin
    percentProgress := bytesProcessed * 100_ / bytesToProcess;
    percentDone := (bytesDone - state.sumOfBadBytes) * 100_ / state.inFileSize;
    percentBadBlocks := state.sumOfBadBytes * 100_ / state.inFileSize;
    if state.maximumOfBadBytes <> 0_ then
      percentFixed := (state.maximumOfBadBytes - state.sumOfBadBytes) * 100_ / state.maximumOfBadBytes;
    end if;
    write(state.phase rpad 7 <& " ");
    write(percentProgress  digits 4 lpad  9 <& "% ");
    write(percentDone      digits 4 lpad  9 <& "% ");
    write(percentBadBlocks digits 4 lpad  9 <& "% ");
    write(percentFixed     digits 4 lpad  9 <& "% ");
    write(state.sumOfBadBytes       lpad 12 <& "     \r");
    flush(OUT);
    (*
    writeln(log, "bytesToProcess=" <& bytesToProcess <&
                 " bytesProcessed=" <& bytesProcessed <&
                 " bytesDone=" <& bytesDone);
    write(log, state.phase rpad 7 <& " ");
    write(log, percentProgress  digits 4 lpad  9 <& "% ");
    write(log, percentDone      digits 4 lpad  9 <& "% ");
    write(log, percentBadBlocks digits 4 lpad  9 <& "% ");
    write(log, percentFixed     digits 4 lpad  9 <& "% ");
    writeln(log, state.sumOfBadBytes       lpad 12);
    *)
  end func;


const func stateType: loadState (in string: stateFileName) is func
  result
    var stateType: state is stateType.value;
  local
    var file: stateFile is STD_NULL;
    var string: headerLine is "";
    var bigInteger: position is 0_;
    var bigInteger: badAreaSize is 0_;
  begin
    stateFile := open(stateFileName, "r");
    if stateFile <> STD_NULL then
      headerLine := getln(stateFile);
      if headerLine = "Savehd7 Version 2.1" then
        state.stateFileName := stateFileName;
        state.inFileName := getln(stateFile);
        state.outFileName := getln(stateFile);
        readln(stateFile, state.phase);
        readln(stateFile, state.skipSize);
        readln(stateFile, state.chunkSize);
        readln(stateFile, state.blockSize);
        readln(stateFile, state.rereadPosition);
        readln(stateFile, state.rereadRunsToDo);
        readln(stateFile, state.maximumOfBadBytes);
        readln(stateFile, state.badBytesToProcess);
        readln(stateFile, state.badAreaToProcess);
        readln(stateFile, state.sizeOfBadAreaToProcess);
        readln(stateFile, state.blockToProcess);
        while succeeds(read(stateFile, position)) do
          readln(stateFile, badAreaSize);
          # writeln(literal(getln(stateFile)));
          state.badAreas @:= [position] badAreaSize;
          # writeln(position <& " " <& badAreaSize);
          state.sumOfBadBytes +:= badAreaSize;
        end while;
      end if;
      close(stateFile);
    end if;
  end func;


const proc: saveState (in stateType: state) is func
  local
    var file: stateFile is STD_NULL;
    var bigInteger: position is 0_;
    var bigInteger: badAreaSize is 0_;
  begin
    stateFile := open(state.stateFileName, "w");
    if stateFile <> STD_NULL then
      writeln(stateFile, "Savehd7 Version 2.1");
      writeln(stateFile, state.inFileName);
      writeln(stateFile, state.outFileName);
      writeln(stateFile, state.phase);
      writeln(stateFile, state.skipSize);
      writeln(stateFile, state.chunkSize);
      writeln(stateFile, state.blockSize);
      writeln(stateFile, state.rereadPosition);
      writeln(stateFile, state.rereadRunsToDo);
      writeln(stateFile, state.maximumOfBadBytes);
      writeln(stateFile, state.badBytesToProcess);
      writeln(stateFile, state.badAreaToProcess);
      writeln(stateFile, state.sizeOfBadAreaToProcess);
      writeln(stateFile, state.blockToProcess);
      for position range sort(keys(state.badAreas)) do
        badAreaSize := state.badAreas[position];
        writeln(stateFile, position <& " " <& badAreaSize);
      end for;
      close(stateFile);
    end if;
  end func;


const proc: checkSumOfBadBytes (in stateType: state) is func
  local
    var bigInteger: badAreaSize is 0_;
    var bigInteger: sumOfBadBytes is 0_;
  begin
    for badAreaSize range state.badAreas do
      sumOfBadBytes +:= badAreaSize;
    end for;
    if sumOfBadBytes <> state.sumOfBadBytes then
      writeln(log, "  ***** SumOfBadBytes " <& state.sumOfBadBytes <&
                   " not correct (" <& sumOfBadBytes <& ")");
    end if;
  end func;


const func bigInteger: countBadBytesInAreasForward (in stateType: state,
    in bigInteger: startPosition) is func
  result
    var bigInteger: badBytesInAreasForward is 0_;
  local
    var bigInteger: position is 0_;
    var bigInteger: badAreaSize is 0_;
  begin
    for badAreaSize key position range state.badAreas do
      if position >= startPosition then
        badBytesInAreasForward +:= badAreaSize;
      end if;
    end for;
  end func;


const proc: listBadAreas (in stateType: state) is func
  local
    var bigInteger: position is 0_;
    var bigInteger: badAreaSize is 0_;
  begin
    for position range sort(keys(state.badAreas)) do
      badAreaSize := state.badAreas[position];
      writeln("  " <& position <& " " <& badAreaSize);
    end for;
  end func;


const func boolean: confirmSave (inout stateType: state) is func
  result
    var boolean: confirmed is FALSE;
  local
    var bigInteger: outFileSize is -1_;
    var bigInteger: bytesProcessed is 0_;
    var bigInteger: halveBadAreaSize is 0_;
    var boolean: finished is FALSE;
    var boolean: proceed is TRUE;
    var string: command is "";
  begin
    if state.stateFileName <> "" then
      writeln;
      writeln("Conditions to save the partition:");
      writeln("  Input file name:  " <& state.inFileName);
      if fileOpenSucceeds(state.inFileName) then
        state.inFileSize := bigFileSize(state.inFileName);
        writeln("  Input file size:  " <& state.inFileSize);
      else
        state.inFileSize := -1_;
      end if;
      writeln("  Output file name: " <& state.outFileName);
      if fileOpenSucceeds(state.outFileName) then
        outFileSize := bigFileSize(state.outFileName);
        writeln("  Output file size: " <& outFileSize);
      end if;
      if state.phase >= REREAD and state.rereadPosition > 1_ then
        writeln("  Rereaded:         " <& pred(state.rereadPosition));
      end if;
      if state.inFileSize = -1_ then
        writeln("  ***** Input file not existing or not accessible");
      else
        # writeln("outFileSize=" <& outFileSize <& " inFileSize=" <& state.inFileSize);
        write("  State:            ");
        if outFileSize = -1_ then
          writeln("Nothing saved");
          state.phase := COPY;
        elsif state.phase = COPY or state.inFileSize <> outFileSize then
          writeln("Copy - " <&
              outFileSize * 100_ / state.inFileSize digits 4 <& "% done");
          state.phase := COPY;
        elsif state.phase = REREAD then
          writeln("Reread #" <& state.rereadRunsToDo <& " - " <&
              pred(state.rereadPosition) * 100_ / state.inFileSize digits 4 <& "% done");
          state.phase := REREAD;
        elsif state.phase = IMPROVE or state.phase = FIX then
          if state.badAreaToProcess <= state.inFileSize and state.badBytesToProcess <> 0_ then
            state.badBytesInUnprocessedAreas := countBadBytesInAreasForward(state,
                state.badAreaToProcess + state.sizeOfBadAreaToProcess);
            if state.blockToProcess >= state.badAreaToProcess then
              bytesProcessed := state.badBytesToProcess -
                  (state.badBytesInUnprocessedAreas +
                  (state.blockToProcess - state.badAreaToProcess) +
                  bigInteger(state.blockSize));
            else
              bytesProcessed := state.badBytesToProcess - state.badBytesInUnprocessedAreas;
            end if;
            if state.phase = IMPROVE then
              write("Improve - ");
            else
              write("Fix - ");
            end if;
            writeln(bytesProcessed * 100_ / state.badBytesToProcess digits 4 <& "% done");
          else
            writeln("Done");
            finished := TRUE;
          end if;
        elsif state.phase = EXAMINE then
          halveBadAreaSize :=  state.sizeOfBadAreaToProcess div
              bigInteger(state.blockSize) div 2_ *
              bigInteger(state.blockSize);
          state.badBytesInUnprocessedAreas := countBadBytesInAreasForward(state,
              state.badAreaToProcess + state.sizeOfBadAreaToProcess) + halveBadAreaSize;
          if state.blockToProcess >= state.badAreaToProcess + halveBadAreaSize then
            bytesProcessed := state.badBytesToProcess -
                (state.badBytesInUnprocessedAreas + (state.badAreaToProcess +
                state.sizeOfBadAreaToProcess - state.blockToProcess));
          else
            bytesProcessed := state.badBytesToProcess -
                (state.badBytesInUnprocessedAreas +
                (state.blockToProcess - state.badAreaToProcess) +
                bigInteger(state.blockSize));
          end if;
          writeln("Examine - " <&
              bytesProcessed * 100_ / state.badBytesToProcess digits 4 <& "% done");
          state.phase := EXAMINE;
        else
          writeln("Done");
          finished := TRUE;
        end if;
        if state.sumOfBadBytes <> 0_ then
          writeln("  Total bad bytes:  " <& state.sumOfBadBytes);
          writeln;
          write("Should the bad areas be listed (Y/N/Q)? ");
          command := upper(getln(IN));
          if command = "Y" then
            writeln;
            writeln("List of bad areas:");
            writeln("   position   size");
            listBadAreas(state);
          elsif command = "Q" then
            proceed := FALSE;
          end if;
        end if;
        if proceed and not finished then
          writeln;
          write("Should the save ");
          if outFileSize = -1_ then
            write("start");
          else
            write("continue");
          end if;
          write(" (type 'Yes' to confirm)? ");
          command := getln(IN);
          proceed := upper(command) <> "Q";
          if command = "Yes" then
            confirmed := TRUE;
            state.emptyBlock := "\0;" mult state.blockSize;
          end if;
        end if;
      end if;
    end if;
    if proceed and not confirmed then
      if state.stateFileName <> "" then
        writeln;
        write("Should a different partition be saved (Y/N/Q)? ");
        command := upper(getln(IN));
      else
        command := "Y";
      end if;
      if command = "Y" then
        state := stateType.value;
        state.stateFileName := dataFileName;
        writeln;
        writeln("Please enter the conditions to save the partition:");
        write("  Input file name:  ");
        state.inFileName := getln(IN);
        if state.inFileName <> "" then
          repeat
            write("  Output file name: ");
            state.outFileName := getln(IN);
            if fileOpenSucceeds(state.outFileName) then
              writeln("  ***** Output file already exists");
            end if;
          until state.outFileName = "" or not fileOpenSucceeds(state.outFileName);
          if state.outFileName <> "" then
            confirmed := confirmSave(state);
            if confirmed then
              saveState(state);
              if not fileOpenSucceeds(state.stateFileName) then
                writeln("  ***** Unable to write state file: " <&
                    state.stateFileName);
                confirmed := FALSE;
              else
                if fileType(logFileName) = FILE_REGULAR then
                  removeFile(logFileName);
                end if;
                state.phase := COPY;
              end if;
            end if;
          end if;
        end if;
      end if;
    end if;
  end func;


const proc: nextPhase (inout stateType: state) is func
  begin
    case state.phase of
      when {COPY}:
        incr(state.phase);
      when {REREAD}:
        decr(state.rereadRunsToDo);
      when {IMPROVE}:
        incr(state.phase);
      when {EXAMINE}:
        incr(state.phase);
      when {FIX}:
        incr(state.phase);
    end case;
    if state.phase = REREAD then
      if state.rereadRunsToDo > 0 then
        state.rereadPosition := 1_;
      else
        incr(state.phase);
      end if;
    end if;
    case state.phase of
      when {IMPROVE, EXAMINE, FIX}:
        state.badBytesToProcess := state.sumOfBadBytes;
        state.badAreaToProcess := 0_;
        state.sizeOfBadAreaToProcess := 0_;
        state.blockToProcess := 0_;
      when {DONE}:
        writeln(state.phase rpad 7 <& " ");
        writeln;
        writeln("Saving finished");
    end case;
  end func;


const func bigInteger: bigLength (in string: stri) is
  return bigInteger(length(stri));


const func bigInteger: afterMaximumBadArea (in stateType: state) is func
  result
    var bigInteger: maximumPosition is 0_
  local
    var bigInteger: position is 0_;
    var bigInteger: badAreaSize is 0_;
  begin
    for badAreaSize key position range state.badAreas do
      if position + badAreaSize > maximumPosition then
        maximumPosition := position + badAreaSize;
      end if;
    end for;
  end func;


const func bigInteger: searchPossibleAreaCombine (in stateType: state,
    in bigInteger: newAreaPosition, in bigInteger: newAreaSize) is func
  result
    var bigInteger: positionFound is -1_;
  local
    var bigInteger: position is 0_;
    var bigInteger: badAreaSize is 0_;
  begin
    for badAreaSize key position range state.badAreas do
      if (position <> newAreaPosition or badAreaSize <> newAreaSize) and
          newAreaPosition + newAreaSize >= position and
          newAreaPosition <= position + badAreaSize then
        positionFound := position;
      end if;
    end for;
  end func;


const proc: combineBadAreas (inout stateType: state, in bigInteger: oldAreaPosition,
    inout bigInteger: newAreaPosition, inout bigInteger: newAreaSize) is func
  local
    var bigInteger: badAreaSize is 0_;
  begin
    badAreaSize := state.badAreas[oldAreaPosition];
    if newAreaPosition < oldAreaPosition then
      excl(state.badAreas, oldAreaPosition);
      state.sumOfBadBytes -:= badAreaSize;
      if newAreaPosition + newAreaSize <= oldAreaPosition + badAreaSize then
        newAreaSize := badAreaSize + (oldAreaPosition - newAreaPosition);
      end if;
      state.badAreas @:= [newAreaPosition] newAreaSize;
      state.sumOfBadBytes +:= newAreaSize;
      writeln(log, "Bad area at " <& oldAreaPosition <&
                   " enlarged to new position " <& newAreaPosition <&
                   " with new size " <& newAreaSize);
    elsif newAreaPosition + newAreaSize > oldAreaPosition + badAreaSize then
      state.sumOfBadBytes -:= badAreaSize;
      newAreaSize +:= newAreaPosition - oldAreaPosition;
      newAreaPosition := oldAreaPosition;
      state.badAreas[oldAreaPosition] := newAreaSize;
      state.sumOfBadBytes +:= newAreaSize;
      writeln(log, "Bad area at " <& oldAreaPosition <&
                   " enlarged to size " <& newAreaSize);
    else
      writeln(log, "  ***** Bad area at " <& newAreaPosition <&
                   " with size " <& newAreaSize <& " not handled");
    end if;
  end func;


const proc: addBadArea (inout stateType: state,
    in var bigInteger: newAreaPosition, in var bigInteger: newAreaSize) is func
  local
    var bigInteger: positionFound is 0_;
  begin
    positionFound := searchPossibleAreaCombine(state, newAreaPosition, newAreaSize);
    if positionFound <> -1_ then
      repeat
        combineBadAreas(state, positionFound, newAreaPosition, newAreaSize);
        positionFound := searchPossibleAreaCombine(state, newAreaPosition, newAreaSize);
      until positionFound = -1_;
    elsif newAreaPosition not in state.badAreas then
      state.badAreas @:= [newAreaPosition] newAreaSize;
      state.sumOfBadBytes +:= newAreaSize;
      writeln(log, "New bad area found at " <& newAreaPosition <&
                   " with size " <& newAreaSize);
    else
      writeln(log, "Bad area at " <& newAreaPosition <&
                   " with size " <& newAreaSize <& " already present");
    end if;
    if state.sumOfBadBytes > state.maximumOfBadBytes then
      state.maximumOfBadBytes := state.sumOfBadBytes;
    end if;
  end func;


const func bigInteger: searchPossibleAreaShrink (in stateType: state,
    in bigInteger: okayAreaPosition, in bigInteger: okayAreaSize) is func
  result
    var bigInteger: positionFound is -1_;
  local
    var bigInteger: position is 0_;
    var bigInteger: badAreaSize is 0_;
  begin
    for badAreaSize key position range state.badAreas do
      if okayAreaPosition + okayAreaSize > position and
          okayAreaPosition < position + badAreaSize then
        positionFound := position;
      end if;
    end for;
  end func;


const proc: shrinkBadAreas (inout stateType: state, in bigInteger: oldAreaPosition,
    in bigInteger: okayAreaPosition, in bigInteger: okayAreaSize) is func
  local
    var bigInteger: badAreaSize is 0_;
    var bigInteger: badAreaSizeReduction is 0_;
  begin
    badAreaSize := state.badAreas[oldAreaPosition];
    if okayAreaPosition <= oldAreaPosition then
      excl(state.badAreas, oldAreaPosition);
      state.sumOfBadBytes -:= badAreaSize;
      badAreaSizeReduction := okayAreaPosition - oldAreaPosition + okayAreaSize;
      if badAreaSize > badAreaSizeReduction then
        badAreaSize -:= badAreaSizeReduction;
        state.badAreas @:= [okayAreaPosition + okayAreaSize] badAreaSize;
        state.sumOfBadBytes +:= badAreaSize;
        writeln(log, "Bad area at " <& oldAreaPosition <&
                     " shrunk to new position " <& okayAreaPosition + okayAreaSize <&
                     " with new size " <& badAreaSize);
      else
        writeln(log, "Bad area at " <& oldAreaPosition <&
                     " with size " <& badAreaSize <& " removed");
      end if;
    else
      state.badAreas[oldAreaPosition] := okayAreaPosition - oldAreaPosition;
      if okayAreaPosition + okayAreaSize < oldAreaPosition + badAreaSize then
        state.badAreas @:= [okayAreaPosition + okayAreaSize]
            oldAreaPosition - okayAreaPosition + badAreaSize - okayAreaSize;
        state.sumOfBadBytes -:= okayAreaSize;
        writeln(log, "Bad area at " <& oldAreaPosition <&
                     " splited to area with size " <& state.badAreas[oldAreaPosition] <&
                     " and area at " <& okayAreaPosition + okayAreaSize <&
                     " with size " <& state.badAreas[okayAreaPosition + okayAreaSize]);
      else
        state.sumOfBadBytes -:= oldAreaPosition + badAreaSize - okayAreaPosition;
        writeln(log, "Bad area at " <& oldAreaPosition <&
                     " with size " <& badAreaSize <&
                     " shrunk to size " <& state.badAreas[oldAreaPosition]);
      end if;
    end if;
  end func;


const proc: removeBadArea (inout stateType: state,
    in bigInteger: okayAreaPosition, in bigInteger: okayAreaSize) is func
  local
    var bigInteger: positionFound is 0_;
  begin
    positionFound := searchPossibleAreaShrink(state, okayAreaPosition, okayAreaSize);
    if positionFound <> -1_ then
      repeat
        shrinkBadAreas(state, positionFound, okayAreaPosition, okayAreaSize);
        positionFound := searchPossibleAreaShrink(state, okayAreaPosition, okayAreaSize);
      until positionFound = -1_;
    end if;
  end func;


const proc: copyFile (inout stateType: state) is func
  local
    var file: inFile is STD_NULL;
    var file: outFile is STD_NULL;
    var bigInteger: currPosition is 1_;
    var string: chunkContent is "";
    var bigInteger: missingBytes is 0_;
  begin
    inFile := open(state.inFileName, "r");
    outFile := open(state.outFileName, "r+");
    if outFile = STD_NULL then
      outFile := open(state.outFileName, "w");
    end if;
    if inFile <> STD_NULL and outFile <> STD_NULL then
      writeln(log, "Start copy from " <& state.inFileName <& " to " <& state.outFileName);
      currPosition := bigLength(outFile) + 1_;
      if afterMaximumBadArea(state) > currPosition then
        currPosition := afterMaximumBadArea(state);
      end if;
      showProgress(state, state.inFileSize, pred(currPosition), pred(currPosition));
      while currPosition <= state.inFileSize and not inputReady(KEYBOARD) do
        seek(inFile, currPosition);
        chunkContent := gets(inFile, state.chunkSize);
        if length(chunkContent) <> 0 then
          seek(outFile, currPosition);
          write(outFile, chunkContent);
        end if;
        if length(chunkContent) <> state.chunkSize and
            currPosition + bigLength(chunkContent) <= state.inFileSize then
          if currPosition + bigInteger(state.chunkSize) > state.inFileSize then
            missingBytes := succ(state.inFileSize) - currPosition - bigLength(chunkContent);
          else
            missingBytes := bigInteger(state.chunkSize) - bigLength(chunkContent);
          end if;
          seek(outFile, currPosition + bigLength(chunkContent));
          write(outFile, "\0;" mult ord(missingBytes));
          addBadArea(state, currPosition + bigLength(chunkContent), missingBytes);
          if state.skipSize > state.chunkSize then
            if currPosition + bigInteger(state.skipSize) > state.inFileSize then
              missingBytes := succ(state.inFileSize) - currPosition;
            else
              missingBytes := bigInteger(state.skipSize);
            end if;
            missingBytes -:= bigInteger(state.chunkSize);
            if missingBytes > 0_ then
              seek(outFile, currPosition + bigInteger(state.chunkSize));
              write(outFile, "\0;" mult ord(missingBytes));
              addBadArea(state, currPosition + bigInteger(state.chunkSize), missingBytes);
            end if;
            currPosition +:= bigInteger(state.skipSize);
          else
            currPosition +:= bigInteger(state.chunkSize);
          end if;
          showProgress(state, state.inFileSize, pred(currPosition), pred(currPosition));
          saveState(state);
        else
          currPosition +:= bigInteger(state.chunkSize);
          showProgress(state, state.inFileSize, pred(currPosition), pred(currPosition));
        end if;
      end while;

      if currPosition > state.inFileSize then
        showProgress(state, state.inFileSize, state.inFileSize, state.inFileSize);
        writeln(log, "Stop copy from " <& state.inFileName <& " to " <& state.outFileName);
        nextPhase(state);
      else
        writeln;
        writeln;
        writeln("Copy paused - To continue restart the program");
        writeln(log, "Pause copy from " <& state.inFileName <& " to " <& state.outFileName);
      end if;
      close(inFile);
      close(outFile);
    end if;
  end func;


const func string: repairBlock (inout stateType: state, in bigInteger: blockPosition,
    in string: sourceBlock, in string: destinationBlock) is func
  result
    var string: repairedBlock is "";
  begin
    repairedBlock := destinationBlock;
    if sourceBlock = destinationBlock then
      removeBadArea(state, blockPosition, bigLength(sourceBlock));
      if destinationBlock <> state.emptyBlock then
        writeln(log, "fix block " <& blockPosition <&
            " (length " <& length(sourceBlock) <& "), which was not empty before");
      end if;
    elsif destinationBlock = state.emptyBlock then
      repairedBlock := sourceBlock &
          "\0;" mult (length(destinationBlock) - length(sourceBlock));
      removeBadArea(state, blockPosition, bigLength(sourceBlock));
      writeln(log, "fix block " <& blockPosition <&
          " (length " <& length(sourceBlock) <& "), which was empty before");
    elsif sourceBlock = "" then
      writeln(log, "leave block " <& blockPosition <&
          " (length " <& length(sourceBlock) <& ") unchanged, which is empty now");
    else
      writeln(log, "block " <& blockPosition <&
          " (length " <& length(sourceBlock) <& ") different and not empty in both cases");
      addBadArea(state, blockPosition, bigLength(sourceBlock));
    end if;
    # checkSumOfBadBytes(state);
  end func;


const proc: repairChunk (inout stateType: state, inout file: outFile,
    in bigInteger: chunkPosition, in string: sourceChunk,
    in string: destinationChunk) is func
  local
    var string: repairedChunk is "";
    var integer: index is 0;
    var string: sourceBlock is "";
    var string: destinationBlock is "";
  begin
    repairedChunk := destinationChunk;
    for index range 1 to length(sourceChunk) step state.blockSize do
      sourceBlock := sourceChunk[index len state.blockSize];
      destinationBlock := repairedChunk[index len state.blockSize];
      destinationBlock := repairBlock(state,
          chunkPosition + bigInteger(index) - 1_,
          sourceBlock, destinationBlock);
      repairedChunk := repairedChunk[.. pred(index)] &
          destinationBlock & repairedChunk[index + state.blockSize ..];
    end for;
    if repairedChunk <> destinationChunk then
      seek(outFile, chunkPosition);
      write(outFile, repairedChunk);
    end if;
    state.rereadPosition := chunkPosition + bigLength(sourceChunk);
    # checkSumOfBadBytes(state);
    saveState(state);
  end func;


const proc: rereadFile (inout stateType: state) is func
  local
    var file: inFile is STD_NULL;
    var file: outFile is STD_NULL;
    var bigInteger: currPosition is 1_;
    var string: sourceChunk is "";
    var string: destinationChunk is "";
  begin
    inFile := open(state.inFileName, "r");
    outFile := open(state.outFileName, "r+");
    if inFile <> STD_NULL and outFile <> STD_NULL then
      writeln(log, "Start reread from " <& state.inFileName <& " to " <& state.outFileName);
      currPosition := state.rereadPosition;
      showProgress(state, state.inFileSize, pred(currPosition), state.inFileSize);
      while currPosition <= state.inFileSize and not inputReady(KEYBOARD) do
        seek(inFile, currPosition);
        sourceChunk := gets(inFile, state.chunkSize);
        if length(sourceChunk) <> 0 then
          seek(outFile, currPosition);
          destinationChunk := gets(outFile, length(sourceChunk));
        else
          destinationChunk := "";
        end if;
        if sourceChunk <> destinationChunk then
          repairChunk(state, outFile, currPosition, sourceChunk, destinationChunk);
          currPosition +:= bigInteger(state.skipSize);
        else
          currPosition +:= bigInteger(state.chunkSize);
        end if;
        showProgress(state, state.inFileSize, pred(currPosition), state.inFileSize);
      end while;

      if currPosition > state.inFileSize then
        state.rereadPosition := state.inFileSize + 1_;
        showProgress(state, state.inFileSize, state.inFileSize, state.inFileSize);
        writeln(log, "Stop reread from " <& state.inFileName <& " to " <& state.outFileName);
        nextPhase(state);
      else
        state.rereadPosition := currPosition;
        writeln;
        writeln;
        writeln("Reread paused - To continue restart the program");
        writeln(log, "Pause reread from " <& state.inFileName <& " to " <& state.outFileName);
      end if;
      close(inFile);
      close(outFile);
    end if;
  end func;


const proc: determineBadAreaToProcess (inout stateType: state) is func
  local
    var bigInteger: position is 0_;
    var bigInteger: currBadArea is 0_;
    var bigInteger: badAreaSize is 0_;
  begin
    currBadArea := state.inFileSize + 1_;
    for badAreaSize key position range state.badAreas do
      if position >= state.badAreaToProcess and position < currBadArea then
        currBadArea := position;
      end if;
    end for;
    if currBadArea <= state.inFileSize then
      if currBadArea <> state.badAreaToProcess then
        state.badAreaToProcess := currBadArea;
        state.sizeOfBadAreaToProcess := state.badAreas[currBadArea];
      end if;
    else
      state.badAreaToProcess := state.inFileSize + 1_;
      state.sizeOfBadAreaToProcess := 0_;
    end if;
  end func;


const proc: processAreaBackward (inout stateType: state,
    inout file: inFile, inout file: outFile) is func
  local
    var string: sourceBlock is "";
    var string: destinationBlock is "";
    var string: repairedBlock is "";
  begin
    showProgress(state, state.badBytesToProcess, state.badBytesToProcess -
        (state.badBytesInUnprocessedAreas + (state.blockToProcess - state.badAreaToProcess) +
        bigInteger(state.blockSize)), state.inFileSize);
    saveState(state);
    while state.blockToProcess >= state.badAreaToProcess and not inputReady(KEYBOARD) do
      # writeln(log, "process block " <& state.blockToProcess);
      seek(inFile, state.blockToProcess);
      sourceBlock := gets(inFile, state.blockSize);
      if length(sourceBlock) <> 0 then
        seek(outFile, state.blockToProcess);
        destinationBlock := gets(outFile, length(sourceBlock));
        repairedBlock := repairBlock(state, state.blockToProcess,
            sourceBlock, destinationBlock);
        if repairedBlock <> destinationBlock then
          seek(outFile, state.blockToProcess);
          write(outFile, repairedBlock);
        end if;
      end if;
      if length(sourceBlock) = state.blockSize or state.phase = FIX then
        state.blockToProcess -:= bigInteger(state.blockSize);
        # writeln(log, "succeed -> state.blockToProcess " <& state.blockToProcess);
      else
        state.blockToProcess := state.badAreaToProcess - bigInteger(state.blockSize);
        # writeln(log, "fail -> state.blockToProcess " <& state.blockToProcess);
      end if;
      showProgress(state, state.badBytesToProcess, state.badBytesToProcess -
          (state.badBytesInUnprocessedAreas + (state.blockToProcess - state.badAreaToProcess) +
          bigInteger(state.blockSize)), state.inFileSize);
      saveState(state);
    end while;
  end func;


const proc: fixOrImproveFile (inout stateType: state) is func
  local
    var file: inFile is STD_NULL;
    var file: outFile is STD_NULL;
    var bigInteger: lastBlockPosition is 0_;
  begin
    inFile := open(state.inFileName, "r");
    outFile := open(state.outFileName, "r+");
    if inFile <> STD_NULL and outFile <> STD_NULL then
      writeln(log, "Start " <& state.phase <& " from " <& state.inFileName <&
          " to " <& state.outFileName);

      while state.badAreaToProcess <= state.inFileSize and not inputReady(KEYBOARD) do
        determineBadAreaToProcess(state);
        if state.badAreaToProcess <= state.inFileSize then
          state.badBytesInUnprocessedAreas := countBadBytesInAreasForward(state,
              state.badAreaToProcess + state.sizeOfBadAreaToProcess);
          lastBlockPosition := state.badAreaToProcess + state.sizeOfBadAreaToProcess -
              bigInteger(state.blockSize);
          if state.blockToProcess < state.badAreaToProcess or
              state.blockToProcess >= lastBlockPosition then
            state.blockToProcess := lastBlockPosition;
          end if;
          processAreaBackward(state, inFile, outFile);
          if state.blockToProcess < state.badAreaToProcess then
            state.badAreaToProcess +:= state.sizeOfBadAreaToProcess;
            state.sizeOfBadAreaToProcess := 0_;
            saveState(state);
          end if;
        end if;
      end while;

      if state.badAreaToProcess > state.inFileSize then
        state.blockToProcess := state.inFileSize + 1_;
        if state.badBytesToProcess <> 0_ then
          showProgress(state, state.badBytesToProcess, state.badBytesToProcess, state.inFileSize);
        end if;
        writeln(log, "Stop " <& state.phase <& " from " <& state.inFileName <&
            " to " <& state.outFileName);
        nextPhase(state);
      else
        writeln;
        writeln;
        if state.phase = FIX then
          writeln("Fix paused - To continue restart the program");
        else
          writeln("Improve paused - To continue restart the program");
        end if;
        writeln(log, "Pause " <& state.phase <& " from " <& state.inFileName <&
            " to " <& state.outFileName);
      end if;
      close(inFile);
      close(outFile);
    end if;
  end func;


const proc: processAreaForward (inout stateType: state,
    inout file: inFile, inout file: outFile) is func
  local
    var string: sourceBlock is "";
    var string: destinationBlock is "";
    var string: repairedBlock is "";
  begin
    showProgress(state, state.badBytesToProcess, state.badBytesToProcess -
        (state.badBytesInUnprocessedAreas + (state.badAreaToProcess + state.sizeOfBadAreaToProcess -
        state.blockToProcess)), state.inFileSize);
    saveState(state);
    while state.blockToProcess < state.badAreaToProcess + state.sizeOfBadAreaToProcess and
        not inputReady(KEYBOARD) do
      # writeln(log, "process block " <& state.blockToProcess);
      seek(inFile, state.blockToProcess);
      sourceBlock := gets(inFile, state.blockSize);
      if length(sourceBlock) <> 0 then
        seek(outFile, state.blockToProcess);
        destinationBlock := gets(outFile, length(sourceBlock));
        repairedBlock := repairBlock(state, state.blockToProcess,
            sourceBlock, destinationBlock);
        if repairedBlock <> destinationBlock then
          seek(outFile, state.blockToProcess);
          write(outFile, repairedBlock);
        end if;
      end if;
      if length(sourceBlock) = state.blockSize then
        state.blockToProcess +:= bigInteger(state.blockSize);
        # writeln(log, "succeed -> state.blockToProcess " <& state.blockToProcess);
      else
        state.blockToProcess := state.badAreaToProcess + state.sizeOfBadAreaToProcess;
        # writeln(log, "fail -> state.blockToProcess " <& state.blockToProcess);
      end if;
      showProgress(state, state.badBytesToProcess, state.badBytesToProcess -
          (state.badBytesInUnprocessedAreas + (state.badAreaToProcess + state.sizeOfBadAreaToProcess -
          state.blockToProcess)), state.inFileSize);
      saveState(state);
    end while;
  end func;


const proc: examineFile (inout stateType: state) is func
  local
    var file: inFile is STD_NULL;
    var file: outFile is STD_NULL;
    var bigInteger: halveBadAreaSize is 0_;
    var bigInteger: middleBlockPosition is 0_;
  begin
    inFile := open(state.inFileName, "r");
    outFile := open(state.outFileName, "r+");
    if inFile <> STD_NULL and outFile <> STD_NULL then
      writeln(log, "Start " <& state.phase <& " from " <& state.inFileName <&
          " to " <& state.outFileName);

      while state.badAreaToProcess <= state.inFileSize and not inputReady(KEYBOARD) do
        determineBadAreaToProcess(state);
        # writeln(log, "Examine " <& state.badAreaToProcess <&
        #     " with size " <& state.sizeOfBadAreaToProcess);
        if state.badAreaToProcess <= state.inFileSize then
          halveBadAreaSize :=  state.sizeOfBadAreaToProcess div
              bigInteger(state.blockSize) div 2_ *
              bigInteger(state.blockSize);
          state.badBytesInUnprocessedAreas := countBadBytesInAreasForward(state,
              state.badAreaToProcess + state.sizeOfBadAreaToProcess) + halveBadAreaSize;
          middleBlockPosition := state.badAreaToProcess + halveBadAreaSize;
          if state.blockToProcess <= state.badAreaToProcess or
              state.blockToProcess >= state.badAreaToProcess + state.sizeOfBadAreaToProcess then
            state.blockToProcess := middleBlockPosition;
          end if;
          if state.blockToProcess >= middleBlockPosition then
            # writeln(log, "processAreaForward " <& state.blockToProcess);
            processAreaForward(state, inFile, outFile);
            if state.blockToProcess >= state.badAreaToProcess + state.sizeOfBadAreaToProcess and
                state.badAreas[state.badAreaToProcess] <> state.sizeOfBadAreaToProcess then
              state.blockToProcess := middleBlockPosition - bigInteger(state.blockSize);
            end if;
          end if;
          if state.blockToProcess < middleBlockPosition then
            state.badBytesInUnprocessedAreas := countBadBytesInAreasForward(state, middleBlockPosition);
            # writeln(log, "processAreaBackward " <& state.blockToProcess);
            processAreaBackward(state, inFile, outFile);
          end if;
          if state.blockToProcess < state.badAreaToProcess or
              state.blockToProcess >= state.badAreaToProcess + state.sizeOfBadAreaToProcess then
            if state.badAreaToProcess in state.badAreas then
              if state.badAreas[state.badAreaToProcess] = state.sizeOfBadAreaToProcess then
                state.badAreaToProcess +:= state.sizeOfBadAreaToProcess;
                state.sizeOfBadAreaToProcess := 0_;
              else
                state.sizeOfBadAreaToProcess := state.badAreas[state.badAreaToProcess];
              end if;
              saveState(state);
            end if;
          end if;
        end if;
      end while;

      if state.badAreaToProcess > state.inFileSize then
        state.blockToProcess := state.inFileSize + 1_;
        if state.badBytesToProcess <> 0_ then
          showProgress(state, state.badBytesToProcess, state.badBytesToProcess, state.inFileSize);
        end if;
        writeln(log, "Stop " <& state.phase <& " from " <& state.inFileName <&
            " to " <& state.outFileName);
        nextPhase(state);
      else
        writeln;
        writeln;
        writeln("Examine paused - To continue restart the program");
        writeln(log, "Pause " <& state.phase <& " from " <& state.inFileName <&
            " to " <& state.outFileName);
      end if;
      close(inFile);
      close(outFile);
    end if;
  end func;


const proc: writeHelp is func
  begin
    writeln;
    writeln("The Savehd7 utility can be used to save a potentially damaged harddisk");
    writeln("partition to an image file. Savehd7 works for all filesystems.");
    writeln;
    writeln("The Savehd7 program is designed to copy from a device file to a disk");
    writeln("image file even if read errors occur. It will try to copy as much as");
    writeln("possible and leave the unreadable blocks filled with zero bytes. The");
    writeln("file system checker can fix the saved disk image afterwards. Finally the");
    writeln("saved and repaired disk image file can be mounted with the loop mount");
    writeln("feature (which can mount a file as if it is a device).");
    writeln;
    writeln("Several conditions must be fulfilled to use Savehd7:");
    writeln("- Operating systems without device files are not supported.");
    writeln("- To get access to the device file Savehd7 must be started as superuser.");
    writeln("- Nothing should be mounted on the device file processed by Savehd7.");
    writeln("- Programs which possibly change the device file such as filesystem");
    writeln("  checkers should not run in parallel to Savehd7.");
    writeln("- There must be enough free space on the destination device to copy the");
    writeln("  whole partition.");
    writeln;
    writeln("Operating systems usually retry to read bad blocks again and again in");
    writeln("the hope to succeed finally. Therefore copying from a damaged harddisk");
    writeln("can take very long (many hours even up to several days). While Savehd7");
    writeln("is processing it can be interrupted by pressing any key. Since the OS");
    writeln("spends so much time reading bad blocks it may take some time until");
    writeln("Savehd7 has a chance to save the processing state and exit afterwards.");
    writeln("Savehd7 should not be interrupted with control-C or some other signal,");
    writeln("since this prevents saving the processing state. If Savehd7 is restarted");
    writeln("it can continue at the position where it was interrupted. The file");
    writeln("\"savehd7.dat\" is used to maintain the processing state. Savehd7 writes");
    writeln("also logging information to the file \"savehd7.log\".");
    writeln;
  end func;


const proc: main is func
  local
    var stateType: state is stateType.value;
  begin
    writeln("Savehd7 Version 2.1 - Save a potentially damaged harddisk partition");
    writeln("Copyright (C) 2006, 2009 Thomas Mertes");
    writeln("This is free software; see the source for copying conditions.  There is NO");
    writeln("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.");
    writeln("Savehd7 is written in the Seed7 programming language");
    writeln("Homepage: http://seed7.sourceforge.net");
    if length(argv(PROGRAM)) >= 1 and lower(argv(PROGRAM)[1]) = "-h" then
      writeHelp;
    else
      writeln("Use 'savehd7 -h' to get more information");
      state := loadState(dataFileName);
      if confirmSave(state) then
        log := open(logFileName, "a");
        if log = STD_NULL then
          writeln("  ***** Could not open log file.");
        else
          log := openLine(log);
          writeln;
          writeln("Processing - Press any key to pause (may take some time to react)");
          writeln("         progress:      okay:  bad blks:     fixed:   bad bytes:");
          if state.phase = COPY then
            copyFile(state);
          end if;
          while state.phase = REREAD and not inputReady(KEYBOARD) do
            rereadFile(state);
          end while;
          while state.phase = IMPROVE and not inputReady(KEYBOARD) do
            fixOrImproveFile(state);
          end while;
          while state.phase = EXAMINE and not inputReady(KEYBOARD) do
            examineFile(state);
          end while;
          while state.phase = FIX and not inputReady(KEYBOARD) do
            fixOrImproveFile(state);
          end while;
          saveState(state);
        end if;
      end if;
      writeln;
    end if;
  end func;