/*  RecoverRDB ver. 1.11 (27.07.97)
    Copyright (C) 1997  Karol Bryd (kbryd@femina.com.pl)

    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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
 06.06.98 - added Mac partitions
 02.01.99 - cleand up the code fo distribution and fixed one bug
            that caused that block number of RDB wasn't remebered...
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef __GNUC__
#include <inline/dos.h>
#include <inline/exec.h>
#else
#include <proto/dos.h>
#include <proto/exec.h>
#endif

#include <exec/memory.h>
#include <exec/types.h>
#include <exec/io.h>
#include <dos/rdargs.h>
#include <devices/hardblocks.h>

static char verstr[] = "$VER: 2b_RecoverRDB 1.11 (06.06.98) BLABLA PRODUCT";
static char twistr[] = "THE OWLS ARE NOT WHAT THEY SEEM";

struct IOStdReq *ioreq = NULL;
struct MsgPort *msgport = NULL;

long unit = 1,
  result,
  totalcyls = 0,
  partnum = 0,
  Unit = 0,
  StartCyl = 0,
  EndCyl = 0,
  Progress = FALSE,
  args[] = {0, 0, 0, 0, 0},
  blockspercyl;

UBYTE *buf;
UBYTE *smallbuf;

char DevName[50];

/* Partition info structure, it holds informatons about all possible
** partitions, I think that 50 partitions will be sufficent...
*/

struct PartInfo
{
    long startcyl[100];
    long endcyl;
    long startblock[100];
    long endblock;
} partitions[50];

/* Very simplified structure of root block - the most important are
** first and last longwords.
*/
struct Root
{
    long id;
    long rubbish[126];
    long secid;
};

/* Clean up function */
void
closeres (char *errorstr)
{
    if(smallbuf)
        FreeVec (smallbuf);
    if(buf)
        FreeVec (buf);
    if (strlen (errorstr) > 0)
        Printf ("%s\n", errorstr);
    if (ioreq)
        DeleteStdIO (ioreq);
    if (msgport)
        DeletePort (msgport);
    exit (0);
}

/* read one block from a given offset */
int
readscsi (UBYTE * buff, ULONG offset)
{
    ioreq->io_Command = CMD_READ;
    ioreq->io_Offset = offset * 512;
    ioreq->io_Length = 512;
    ioreq->io_Data = buff;
    ioreq->io_Flags = IOF_QUICK;
    if (DoIO ((struct IORequest *) ioreq))
    {
        Printf ("Error #%ld\n", ioreq->io_Error);
        return (-1);
    }
    return (1);
}

/* main function */
int
doit (void)
{
    register long blocknum = 0,
      bootblocks = 0,
      a,
      counter = 0,
      memblock;
    long id,
      numblocks,
      lastcounter;
    int blocknumber = 0,
      toprocess = 0;
    struct RigidDiskBlock *rdb;
    struct Root *root;
    BOOL inpart = FALSE;

    /* we are going to read 250 blocks at once to speed up the process */
    buf = AllocVec (250 * 512, MEMF_CHIP);
    if(!buf)
        return(-1);
    smallbuf = AllocVec (512, MEMF_CHIP);
    if(!smallbuf)
        return(-1);

    /* read RDB block */
    readscsi (buf, 0);
    rdb = (struct RigidDiskBlock *) buf;

    /* if RDB isn't at block #0 then search through first 16 blocks */
    if (rdb->rdb_ID != IDNAME_RIGIDDISK)
    {
        for (; rdb->rdb_ID != IDNAME_RIGIDDISK && blocknum <= 16; ++blocknum)
        {
            readscsi (buf, blocknum);
            rdb = (struct RigidDiskBlock *) buf;
        }
    }
    if (rdb->rdb_ID != IDNAME_RIGIDDISK)
    {
        Printf("ALERT! No Rigid Disk Block!!!\n");
        return(-1);
    }

    /* get some constants */
    numblocks = rdb->rdb_CylBlocks * rdb->rdb_Cylinders;
    totalcyls = rdb->rdb_Cylinders;
    blockspercyl = rdb->rdb_CylBlocks;
    partnum = 0;

    /* and setup IORequest structure */
    ioreq->io_Command = CMD_READ;
    ioreq->io_Length = 250 * 512;
    ioreq->io_Data = buf;
    ioreq->io_Flags = IOF_QUICK;
    if (EndCyl == 0)
        EndCyl = rdb->rdb_Cylinders;

    for (lastcounter = counter = blocknum = StartCyl * blockspercyl; blocknum < EndCyl * blockspercyl; blocknum++, counter++)
    {
      again:
        /* a simple progress indicator */
        if (lastcounter < counter - 5000 && Progress)
        {
            Printf ("Blocknum: %ld (of %ld)\r", blocknum, numblocks);
            lastcounter = counter;
        }

        ioreq->io_Command = CMD_READ;
        if (blocknum + 250 >= numblocks)
        {
            /* special case for last read (ie. there are less than 250 blocks to read) */
            ioreq->io_Length = (numblocks - blocknum - 1) * 512;
            toprocess = numblocks - blocknum - 1;
        }
        else
        {
            /* or read all of them */
            ioreq->io_Length = 250 * 512;
            toprocess = 250;
        }
        ioreq->io_Data = buf;
        ioreq->io_Flags = IOF_QUICK;
        ioreq->io_Offset = blocknum * 512;
        DoIO ((struct IORequest *) ioreq);

        /* now, scan what we have just read */
        for (blocknumber = 0, memblock = 0; memblock < toprocess; memblock++)
        {
            /* get ID of block */
            memcpy (&id, buf + blocknumber, 4);
            root = (struct Root *) (buf + blocknumber);
            id = id & 0xffffff00;

            /* and compare it to known constants */
            if (id == (ID_DOS_DISK & 0xffffff00) || id == (ID_NOT_REALLY_DOS & 0xffffff00))
            {
                /* if this is a bootblock of a partition then remember
                   the block number
                */
                inpart = TRUE;
                partitions[partnum].startblock[bootblocks++] = blocknum;
            }
            if (CheckSignal (SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C)
            {
                Printf ("User abort...\n");
                goto end;
            }
            /* in case of root block check which one of before remembered bootblocks
               match this root block, since root block lays right in the middle
               of partition I simply read block with number:

               (2 * actual block number) - first block of partition

               and check it's ID, if ID of this block will be ID
               of a bootblock then it means that we have found a valid partition
            */
            if (root->id == 2 && root->secid == 1)
            {
                for (a = 0; a < bootblocks; a++)
                {
                    readscsi (smallbuf, (blocknum * 2) - partitions[partnum].startblock[a]);
                    memcpy (&id, smallbuf, 4);
                    id = id & 0xffffff00;
                    if (id == (ID_DOS_DISK & 0xffffff00) || id == (ID_NOT_REALLY_DOS & 0xffffff00) || id == 0x4c4b6000)
                    {
                        /* we have found a valid partition */
                        blocknum = (blocknum * 2) - partitions[partnum].startblock[a] - 1;
                        Printf ("Partiton found at cylinder:%ld, end cylinder:%ld\n", partitions[partnum].startblock[a] / blockspercyl, (blocknum) / blockspercyl);
                        inpart = FALSE;
                        partnum++;
                        bootblocks = 0;
                        blocknumber = 0;
                        goto again;
                    }
                }
            }
            blocknum++;
            counter = blocknum;
            blocknumber += 512;
        }
    }
  end:
  return(0);
}

int
main (int argc, char *argv[])
{
    struct RDArgs *rdargs;

    rdargs = (struct RDArgs *) ReadArgs ("DEVICE/A,UNIT/K/N/A,STARTCYL=SC/K/N,ENDCYL=EC/K/N,PROGRESS/S", args, NULL);
    if (!rdargs)
    {
        PrintFault (IoErr (), argv[0]);
        exit (10);
    }
    if (strlen ((STRPTR) args[0]) > 0)
        strcpy (DevName, (STRPTR) args[0]);

    if (args[1])
        Unit = *(LONG *) args[1];
    if (args[2])
        StartCyl = *(LONG *) args[2];
    if (args[3])
        EndCyl = *(LONG *) args[3];
    if (args[4])
        Progress = TRUE;

    FreeArgs (rdargs);

    printf("RecoverRDB v1.11, Copyright (C) 1997/98/99 Karol Bryd\n"
     "RecoverRDB comes with ABSOLUTELY NO WARRANTY.\n"
     "This is free software, and you are welcome to redistribute\n"
     "it under certain conditions, see COPYING file for details.\n");

    if (EndCyl <= StartCyl && EndCyl)
        closeres ("End Cyl MUST BE higher than StartCyl\n");

    if (msgport = (struct MsgPort *) CreatePort (NULL, NULL))
    {
        if (ioreq = (struct IOStdReq *) CreateStdIO (msgport))
        {
            if (!(result = OpenDevice (DevName, Unit, (struct IORequest *) ioreq, 0)))
            {
                if(doit () < 0)
                {
                    CloseDevice ((struct IORequest *) ioreq);
                    closeres ("Aborting...\n");
                }
                else
                {
                    CloseDevice ((struct IORequest *) ioreq);
                    closeres ("All done...\n");
                }
            }
            else
            {
                Printf ("Error while opening device:#%ld", result);
                closeres (" ");
            }
        }
        else
            closeres ("Could not create stdio\n");
    }
    else
        closeres ("Could not create port\n");
}
