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