program dumpdisk;
imports Dynamic from Dynamic;
imports io from io;
imports ioerrors from ioerrors;
imports rs232baud from rs232baud;
imports system from system;
imports FileSystem from FileSystem;
imports IOErrMessages from IOErrMessages;

  { 

  This is a dead simple program to do a raw disk dump of the
  hard drive over the RS232 port.  It's also my first Pascal program
  as well as my first PERQ program, so it's not great.  But it seems
  to work ok...
  
  This is currently hard-coded for a 24mb Shugart 14" drive, but it 
  should be adaptable to any other drive by tweaking some parameters.
  
  This program reads the raw data in the following format, on a sector
  by sector basis (from cyl 0, head 0, sector 0 on out) and dumps it
  over RS232 port A at 9600 baud.
  
  - 8 words of header (16 bytes)
  - 256 words of data (512 bytes)
  
  The 8 header words are returned from IODiagRead and according to the
  documentation these represent physical data on the disk;  I'm not
  entirely sure it's necessary to save, but i erred on the side of 
  saving more data rather than less.
  
  Bad sectors are retried up to 10 times; if that fails the data sent
  over the RS232 port will be 8 words of header (which may be bogus)
  and 512 bytes of '0101010101'.  Yes, this is rather lame.
  
  J. Dersch, 8/14/06.
  
  }
  

type
    CharArray = array [0..512] of char;
    ChrArrayPtr = ^CharArray;
        
var i : integer;
    c : char;
    buf : IOBufPtr;
    Stat : DevStatusBlock;
    addr : double;
    header : IOHeadPtr;
    status : IOStatPtr;
    Head : integer;
    Sector : integer;
    Cylinder : integer;
    CylStart : integer;
    CylEnd : integer;
    seg : SegmentNumber;
    success : boolean;
    RetryCount : integer;
    
{ Writes a char to the RS232 port }
procedure WriteChar( c : char );
begin
  if iocwrite(RS232Out, c) <> IOEIOC then
    writeln( ' -- error writing to serial port -- ' );
end;

{ Writes an integer to the RS232 port }
procedure WriteInt( i : integer );
var high : char;
    low : char;
    
begin
 high := chr(shift(i,-8));
 low := chr(land(i,255));
 
 WriteChar( low );
 WriteChar( high );
end;

{ Writes a double to the RS232 port }
procedure WriteDbl( d : double );
begin
 WriteInt( d[0] );
 WriteInt( d[1] );
end;

{ Start of the main program }
begin
  
  writeln ('Start at what cylinder? ');
  readln ( CylStart );
  
  writeln ('End at what cylinder? (Max 201)');
  readln ( CylEnd );
  
  writeln ('Here we go...');
  
  { Configure the RS232 port }
  
  with Stat
  do begin
    ByteCnt := 1;
    RSRcvEnable := false;
    end;
    
  IOPutStatus( RS232In, Stat );
  SetBaud('9600', true);
  
  writeln ('Set RS232 port to 9600 baud...'); 
  
  writeln ('Allocating memory...');
  
  { Allocate memory for status and header }
  NEW(0,4,status);
  NEW(0,4,header);
  
  { Allocate a segment for the disk data buffer }
  CreateSegment( seg, 1, 1, 1 ); 
  buf := MakePtr(seg,0, IOBufPtr);     
        
  { Loop over the disk from the start cylinder to the end }         
  for Cylinder := CylStart to CylEnd do
   begin
    for Head := 0 to 7 do
     begin
      for Sector := 0 to 29 do
        begin
        
        addr[0] := Cylinder * 256 + Head*32 + Sector;
        
        writeln( 'reading cylinder ', cylinder, ' head ', head, ' sector ', sector );
        success := false;
        RetryCount := 0;
        
        { Read the block from the disk; retry 10 times on a read error }
        while (not success) and (RetryCount < 10) do
          begin
         
          { Read 512 bytes from the disk.  IODiagRead reads the block and fills in the header info for us. }
          UnitIO( HardDisk, buf, IODiagRead, 512, addr, header, status );
          
          {writeln( 'serial ', header^.SerialNum[0], header^.SerialNum[1], ' logblock ', header^.LogBlock, ' filler ', header^.Filler, ' NextAdr ', header^.NextAdr[0], header^.NextAdr[1], ' PrevAdr ', header^.PrevAdr[0], header^.PrevAdr[1] );}
  
          if status^.SoftStatus = IOEIOC then
           success := true;
                      
          {Yes, this is stupid, got fed up trying to get 'else' to compile without errors. Grumble.}
          if status^.SoftStatus <> IOEIOC then
           begin
            writeln('error ', status^.SoftStatus, ' while reading disk.');
            RetryCount := RetryCount + 1;
           end;
          end;
          
        { We didn't read the block, the data in the block will be filled with binary '01010...' }
        if (not success) then
         begin
         writeln('retries unsuccessful, data is unusable for this sector');
        
         for i := 0 to 255 do
          begin
           {$R-}
           buf^[i] := 21845;
           {$R+}
          end;
           
         end;
         
        { Send header block }
        WriteDbl( header^.SerialNum );
        WriteInt( header^.LogBlock );
        WriteInt( header^.Filler );
        WriteDbl( header^.NextAdr );
        WriteDbl( header^.PrevAdr );
        
        { Send data block }
        for i := 0 to 255 do
        begin
         {$R-}
         WriteInt( buf^[i] );
                  
         {$R+}
         {write( low, high ); }               
         end;
      end;
    end;
   end;  
end.
