/* DSrestore.c */
/* Copyright 2001-2002 Oliver B. Warzecha <obw@amarok.ping.de> */
/*
    This file is part of DSrestore.

    DSrestore 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.

    DSrestore 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 DSrestore; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/


#include <proto/exec.h>
#include <proto/dos.h>

#include <exec/memory.h>
#include <dos/rdargs.h>

#include <ds_stream.h>

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

#include <dos.h>

#include "DSrestore.h"
#include "DSrestore_rev.h"

static const char* verID = VERSTAG;
/* defaults for development */

const char *fbtarget="Sys:";

static long debug;

struct RDArgs *rdArgs;

char *target,*backup;
char *buff;
int err;
void *mypool;


BPTR infile,tmplock,StdErr,logFile;

int main(int argc, char ** argv)
{
    int rc=0;
    logFile = 0;

    StdErr=Open("CONSOLE:", MODE_OLDFILE);
    /* here we parse the commandline */
    mypool = CreatePool(MEMF_CLEAR,4096,STBLOCKSIZE);
    if (mypool == NULL) {
        FPrintf(StdErr, "unable to create memory pool");
        Close(StdErr);
        return 20;
    }
    read_params();
    /*
     * now we know the name of the archive and the directory to 
     * place the restored data in.
     */
    if (!parse_stream(backup)) {
        PrintFault(IoErr(), "DSrestore problem: ");
        rc = 10;
    }
    /*
     * we could do more here.
    */
    if(buff)
        FreeVec(buff);
    FreeArgs(rdArgs);
    FreeVec(target);
    FreeVec(backup);
    DeletePool(mypool);
    Close(infile);
    Close(StdErr);
    return rc;
}

void read_params(void)
{

    char *argStr = ARGSTRING;
    LONG argArray[4];
    LONG errc;

    /* set defaults */
/* target = current directory */
    buff = AllocVec(256, MEMF_CLEAR);
    if (!(tmplock = Lock("PROGDIR:", ACCESS_READ)))
        FPrintf(StdErr,"lock on current dir failed\n");
    if (!NameFromLock(tmplock, buff, 255)) {
        if ((errc = IoErr()) == ERROR_LINE_TOO_LONG)
            FPrintf(StdErr, "Current directory name too long, using SYS: (this is probably not what you want)\n");
        else {
            PrintFault(errc, "Error on current dir");
        }
        FreeVec(buff);
        buff = NULL;
        argArray[1] = (LONG)fbtarget;
    } else
        argArray[1] = (LONG)buff;
    UnLock(tmplock);
    argArray[2] = 0;
    if (rdArgs = ReadArgs(argStr, argArray, NULL)) {
        debug = argArray[2];
        target = AllocVec(strlen((char *)argArray[1])+1UL, MEMF_CLEAR);
        strcpy(target, (char *)argArray[1]);
        backup = AllocVec(strlen((char *)argArray[0])+1UL, MEMF_CLEAR);
        strcpy(backup, (char *)argArray[0]);
    } else {
        FPrintf(StdErr,"required argument missing\n");
        if (buff)
            FreeVec(buff);
        FreeArgs(rdArgs);
        rdArgs = NULL;
        Close(StdErr);
        DeletePool(mypool);
        SetIoErr(ERROR_REQUIRED_ARG_MISSING);
        exit(20);
    }
    if (argArray[3]) {
        logFile = Open((STRPTR)argArray[3], MODE_NEWFILE);
        if (!logFile) {
            FPrintf(StdErr, "couldn't open file %s for logging, continuing\n", argArray[3]);
        }
    }
    FreeArgs(rdArgs);
    rdArgs = NULL;
    return;
}
/*
 * readnext
 * read a block from the stream in the supplied buffer
 */


void readnext(BPTR infile, void* buffer)
{
    LONG size;
    
    size = Read(infile, buffer, STBLOCKSIZE);
    /*
     * sophisticated error handling. Is the stream padded?
     */
    if (size != STBLOCKSIZE)
        FPrintf(StdErr,"Short read at file position %ld", Seek(infile, 0, OFFSET_CURRENT));
    return;
}

/*
 * API for ID->Array conversion, put in another file?
 */

static long *IDarr=NULL;
static long maxid=0;

/* constructor */

BOOL ID__ID(long nid)
{
    if (!(IDarr =(long *)AllocPooled(mypool, (ULONG)((size_t)nid * sizeof (long)))))
        return FALSE;
    maxid = nid;
    return TRUE;
}

/* destructor */

BOOL IDn_ID(void)
{
    if (IDarr == NULL)
        return FALSE;
    /* free the whole array */
    maxid = 0;
    FreePooled(mypool, IDarr, (ULONG)(maxid * sizeof(long)));
    return TRUE;
}

long indexID(long id)
{
    long sc=0;

    while (IDarr[sc] != id && sc != maxid)
        sc++;
    if (maxid == sc) {
        FPrintf(StdErr,"object with ID %ld not found.\n", id);
        return -1;              /* sign for error */
    }
    if (debug)
        Printf("searching index for ID %ld, found at %ld\n",id,sc);
    return sc;
}

long getID(long index)
{
    return IDarr[index];
}

BOOL setID(long id, long index)
{
    if (index >= maxid)
        return FALSE;
    if (debug)
        Printf("setting index %ld to ID %ld\n",index, id);
    IDarr[index] = id;
    return TRUE;
}

/*
 * we have the name of the stream to parse
 * Make it so!
 */

BOOL parse_stream(const char* name)
{
    void *rootbuffer;
    void *block=NULL;
    STRoot* BRoot;
    STHeader* BHeader;
    char **Pathlist;
    STBaseUnit *counters;
    short fixbug = 0;
    int NPaths;
        
    /* open the file, non existing error handling again */
    if (!(infile = Open((STRPTR)name, MODE_OLDFILE))) {
        FPrintf(StdErr,"Unable to open archive file %s\n",(LONG)name);
        return FALSE;
    }
    /* there _should_ be enough memory, doesn't it? */
    rootbuffer = AllocPooled(mypool, STBLOCKSIZE);
    counters = AllocPooled(mypool, sizeof(STBaseUnit));
    /*
     * The file is open. Read the first block and check, if it is really
     * a disksalv stream and not a jpg or anything.
     * so we do a consistency check!
     */
    readnext(infile, rootbuffer);
    BRoot = (STRoot*)rootbuffer;
    /*
     * this is the number of objects! We need to store this in a safe area.
     * best if we leave the rootblock alone for the duration of the process.
     * we also need a counter to count the objects for ourselves.
     */
    if (debug)
        Printf("%ld Objekte im Archiv\n",BRoot->barcount);
    if ((BRoot->head.type != ID_ROOT) || (BRoot->head.id != 0)) {
        FPrintf(StdErr,"no rootblock or unknown archive format!\nexiting...\n");
        SetIoErr(ERROR_OBJECT_WRONG_TYPE);
        return FALSE;
    }
    /* now the business starts */
    /*
     * we loop through the whole archive until the last block is read.
     * last block is recognized by ID_ENDA. Every object is identified by
     * the unique(!) ID number. So we have to store it somehow to handle links,
     * subdirs, etc. we need the ID and the full path. sounds like a stringarray to
     * me. and we know the size, too! ID does not start with zero, seems to
     * be the disk key. :-( storage Methods: utility namespace objects?
     * No, just a second array. I have to provide a small API, see above for
     * #?ID()-Functions.
     */
    if (!ID__ID(BRoot->barcount))
        FPrintf(StdErr,"failed to allocate ID array\n");
    /*
     * Oh, hazy, what have you done! Objects in the root directory have a parent
     * ID containing the block number of the root block! But the object containing
     * this ID is of course _not_ in the archive stream. So I have to assume that the first
     * object in the archive is in the root dir. sigh!
     */
    NPaths = (size_t)BRoot->barcount * sizeof(char *);
    if ((Pathlist = (char**)AllocPooled(mypool, (ULONG)NPaths)) == NULL)
        FPrintf(StdErr,"Allocation for object array failed\n");
    if (!(tmplock = Lock((STRPTR)target, ACCESS_READ))) {
        FPrintf(StdErr,"Unable to find target dir %s\n",(LONG)target);
        return FALSE;
    }
    Pathlist[0] = (char *)AllocPooled(mypool, 255 * sizeof(char));
    /* 255 should be the maximum length of a BSTR (volumename) */
    if (!(NameFromLock(tmplock, (STRPTR)Pathlist[0], 254))) {
        FPrintf(StdErr,"could not get path for target dir %s, going downhill...\n", (LONG)target);
        return FALSE;
    }
    UnLock(tmplock);

    /* we give the root dir an ID of 0 first */
    if (!setID(0,0)) {
        return FALSE; /* the allocation above must have failed */
    }
    counters->objectcount = 0;
    do {
        if (block)
            FreePooled(mypool, block, STBLOCKSIZE);
        if (CheckSignal(SIGBREAKF_CTRL_C)) {
            SetIoErr(ERROR_BREAK);
            return FALSE;
        }
        block=AllocPooled(mypool, STBLOCKSIZE);
        readnext(infile, block);
        BHeader = (STHeader *)block;
        if (BHeader->type == ID_DATA || BHeader->type == ID_ENDA)
            continue;
    /* store ID */
    /*
     * yeeha! broken DiskSalv (?)! the current ID can be 0. there is it. :-P
     */
        if (fixbug == 1) {
            if (BHeader->type == ID_UDIR && BHeader->parent == counters->dwalloc) {
            /* last one was an directory, both have the same parent -> last dir was empty.
             * so the ID of the last dir is unimportant, set it to 0. */
                setID(0,counters->objectcount);
            } else {
                setID(BHeader->parent,counters->objectcount);
                FPrintf(StdErr,"Bugfix: set ID of object no. %ld to %ld\n",counters->objectcount,BHeader->parent);
            }
            fixbug = 0;
        }
        if (BHeader->id == 0){
            fixbug = 1;
            counters->dwalloc = BHeader->parent;
        }
        counters->objectcount++;
        if (!setID(BHeader->id,counters->objectcount))
            FPrintf(StdErr,"ID storage for %ld failed", (LONG)BHeader->id);
/*
 * TODO:    check, if ID already allocated
 *          more consistency checks at all
 *          clean up code
 */

    /*
     * special case for 1st object.
     * fetch parent ID and store as ID for root
     */
        if (1 == counters->objectcount) {
            setID(BHeader->parent,0);
        }
    /* the parent should already have been handled, get lock for parent name */
        if (debug)
            Printf("parent ID: %ld", BHeader->parent);

        if (Pathlist[indexID(BHeader->parent)] == NULL) {
            long i;
            PutStr("parent not yet initialised, exiting\n");
            for (i=0; Pathlist[i] != NULL; i++)
                FPrintf(StdErr,"Pathlist[%ld]=%s\n",i,Pathlist[i]);
            SetIoErr(ERROR_DIR_NOT_FOUND);
            return FALSE;
        }
        if (debug)
            Printf(
                "current object: %s, parent: %s\n",
                (LONG)((STDir*)BHeader)->body.filename,
                Pathlist[indexID(BHeader->parent)] );
        counters->dwsize = strlen(Pathlist[indexID(BHeader->parent)]);
        if (debug)
            Printf("first path component length: %ld, current filename %s\n",
                counters->dwsize,
                ((STDir*)BHeader)->body.filename );
    /* space for filenames + \0 + separator */
        counters->dwsize += (strlen(((STDir*)BHeader)->body.filename)+2);
    /* counters->out as temp pointer from STBaseUnit */
        if (debug)
            Printf("Allocating %ld bytes\n", counters->dwsize);
        counters->out = AllocPooled(mypool, counters->dwsize);
        Pathlist[counters->objectcount] = counters->out;
        strcpy(counters->out, Pathlist[indexID(BHeader->parent)]);
    /* counters->out is the path of the object */
        if (!(AddPart(counters->out,((STDir*)BHeader)->body.filename, counters->dwsize)))
            FPrintf(StdErr,"Oops! AddPart() failed.\n");
        if (debug)
            Printf("complete path: %s\n",(LONG)counters->out);
    /* we don't need the old lock any more */
        tmplock = 0;
    /* state machine for block type */
        switch (BHeader->type) {
            case ID_UDIR:       /* directory */
    /* create directory, set all attribs. */
                counters->dircount++;
                if (logFile)
                    FPrintf(logFile, "UDIR %s\n", (LONG)counters->out);
                if (!(tmplock = CreateDir(counters->out))) {
                    FPrintf(StdErr,"CreateDir(\"%s\") failed;", (LONG)counters->out);
                    if (ERROR_OBJECT_EXISTS != IoErr()) {
                        FPrintf(StdErr, "\n");
                        return FALSE;
                    }
                    FPrintf(StdErr," object already exists, ignoring...\n");
                }
          /* set protection, date and filenote */
                SetComment(counters->out, ((STDir *)BHeader)->body.filenote);
                SetProtection(counters->out, ((STDir *)BHeader)->body.protect);
                SetFileDate(counters->out, &((STDir *)BHeader)->body.date);
                break;
            case ID_FILE:       /* file */
    /*
     * make a lock on parent object, which should be a directory. open file for
     * writing, push all data into it, close it. Then set protection, filenote and date.
     * That's all. ;)
     */
                counters->filecount++;
                if (debug)
                    Printf("opening file %s\n", (LONG)counters->out);
                if (logFile)
                    FPrintf(logFile, "FILE %s\n", (LONG)counters->out);
                if (tmplock = Lock(counters->out, ACCESS_READ)) {
                    UnLock(tmplock);
                    counters->out = strcat(counters->out, "_1");
                }
                tmplock = Open(counters->out, MODE_NEWFILE);
                if (tmplock)
                    writeFile(infile, tmplock, (ULONG)BHeader->size);
                else {
                    PrintFault(IoErr(),"Error ");
                    Close(tmplock);
                    tmplock = 0;
                    return FALSE;
                }
                Close(tmplock);
                tmplock = 0;
                if (debug)
                    Printf("Finished writing\n");
                SetComment(counters->out, ((STDir *)BHeader)->body.filenote);
                SetProtection(counters->out, ((STDir *)BHeader)->body.protect);
                SetFileDate(counters->out, &((STDir *)BHeader)->body.date);
                break;
            case ID_FLNK:       /* hardlink to file */
            case ID_DLNK:       /* hardlink to dir */
                counters->linkcount++;
                if (logFile)
                    FPrintf(logFile, "LINK %s\n", (LONG)counters->out);
                if (!(tmplock = Lock(Pathlist[indexID(((STHLink*)BHeader)->link)],ACCESS_READ)))
                    FPrintf(StdErr,"Could not lock file %s to link to\n",
                            (LONG)Pathlist[indexID(((STHLink*)BHeader)->link)]);
                if (tmplock)
                    if (!MakeLink(counters->out, (LONG)tmplock, FALSE))
                        FPrintf(StdErr,"MakeLink() failed for hardlink %s\n",
                            (LONG)counters->out);
                SetComment(counters->out, ((STDir *)BHeader)->body.filenote);
                SetProtection(counters->out, ((STDir *)BHeader)->body.protect);
                SetFileDate(counters->out, &((STDir *)BHeader)->body.date);
                break;
            case ID_SLNK:       /* softlink */
                counters->linkcount++;
                if (logFile)
                    FPrintf(logFile, "SLNK %s\n", (LONG)counters->out);
                if (!MakeLink(counters->out, (LONG)((STSLink*)BHeader)->link, TRUE))
                    FPrintf(StdErr,"MakeLink() failed for softlink %s\n",
                        (LONG)counters->out);
                tmplock = 0;
                break;
            case ID_ERRS:       /* huh? */
                counters->errcount++;
                FPrintf(StdErr,"Found error block. I don't know how to handle it.\n");
                break;
            case SET_DEL:       /* error, may be deleted? */
                FPrintf(StdErr,"The file %s was faulty, according to DiskSalv.\n",
                        (LONG)Pathlist[counters->objectcount - 1]);
                break;
            default:
                FPrintf(StdErr,"Unknown block encountered. Archive format ok?\n");
                break;
        }
        UnLock(tmplock);
    } while (BHeader->type !=ID_ENDA);
    Printf("%ld files, %ld directories and %ld links restored.\n%ld errors encountered.\n",
            counters->filecount,
            counters->dircount,
            counters->linkcount,
            counters->errcount);
    return TRUE;
}
     
BOOL writeFile(BPTR source, BPTR target, LONG size)
{
    char *tbuff, *sbuff;
    ULONG bcount, i;

    /* optimize... read all and/or write all in one go... done */
    if (size == 0)
        return TRUE;
    /*
     * allocate all necessary memory, 2 buffers for source and target
     */
    bcount = size / STDATASIZE + (size % STDATASIZE ? 1 : 0);
    if (debug)
        Printf("%ld blocks of memory, %ld byte together, available %ld.\n", bcount, size, AvailMem(MEMF_FAST|MEMF_LARGEST));
    if (tbuff = AllocVec(bcount * STDATASIZE, 0L)) {
        if (sbuff = AllocVec(bcount * STBLOCKSIZE, 0L)) {
            if (bcount * STBLOCKSIZE != Read(source, sbuff, bcount * STBLOCKSIZE))
                PutStr("Short read\n");
            for (i = 0; i < bcount; i++)
                if (((STHeader *)(sbuff + i * STBLOCKSIZE))->type != ID_DATA) { /* find wrong counts */
                    if (debug)
                        Printf("Seek %ld bytes, current pos %ld\n", -((LONG)(bcount - i))*STBLOCKSIZE, Seek(source, 0, OFFSET_CURRENT));
                    Seek(source, -(LONG)((bcount - i)*STBLOCKSIZE), OFFSET_CURRENT);  /* set to start of non-data */
                    break;
                }
            for (i = 0; i < bcount; i++) {      /* copy all data to the compact databuffer */
                CopyMem(sbuff + STBLOCKSIZE*i + sizeof(STHeader), tbuff + STDATASIZE*i, STDATASIZE);
            }
            if (size != Write(target, tbuff, size)) {
                PutStr("Short write\n"); /* TODO: there should be more error handling */
            }
            FreeVec(sbuff);
        } else {
            FPrintf(StdErr,"source buffer of %ld failed\n", bcount*STBLOCKSIZE);
        }
        FreeVec(tbuff);
    } else {
        FPrintf(StdErr,"target buffer of %ld failed\n", size);
    }
    return TRUE;
}
