$ 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);
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);
state.badAreas @:= [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
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;
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);
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
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);
else
state.blockToProcess := state.badAreaToProcess - bigInteger(state.blockSize);
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
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);
else
state.blockToProcess := state.badAreaToProcess + state.sizeOfBadAreaToProcess;
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);
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
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);
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;