/*
 *  TARSPLIT.C
 *  ~~~~~~~~~~
 *    TarSplit -- split up tar files (creating directories as needed)
 *
 *    usage: TarSplit [pathname]
 *
 *  semantics: splits up tar file taken from stdin (or pathname, if
 *  specified) and creates the files therein under the working data
 *  directory.
 * 
 */

#include <stdio.h>
#include <string.h>
#include "version.h"
#include "config.h"

IDENT(".3");

#ifdef AMIGA
#include <stdlib.h>
#include <exec/types.h>
#ifdef LATTICE
#include <proto/all.h>
#else
/* MANX */
#include <exec/memory.h>
#include <exec/libraries.h>
#endif
typedef int bool ;
bool ChkSumOK() ;
#include <libraries/dosextens.h>
#else
#include <modes.h>
#include <bool.h>
#define DIRMODE     (S_IREAD | S_IWRITE | S_IEXEC | S_IOREAD | S_IOEXEC)
#endif
#define TBLOCK 512
#define NAMSIZ 100

#ifdef NO_STDERR
#define writeto stdout
#else
#define writeto stderr
#endif

#if defined(DEPENDENCYLIST) && defined(AMIGA)
typedef struct dependnode *list;

struct dependnode {
    char    name[NAMSIZ];
    char    linkname[NAMSIZ];
    char    linkflag;           /* for future use: working links in 2.x */
    list    next;
} listnode;

list dependencylist = NULL;

void ResolveDependency();
void ExtractPath();
void AddDependency();
#endif

#ifdef MANX
int IsDir(char *name) {
    struct FileInfoBlock *fib;
    BPTR lock;
    int result = 0;

    lock = Lock((UBYTE *)name, ACCESS_READ);
    if (lock) {
        fib = (struct FileInfoBlock *) AllocMem(sizeof(struct FileInfoBlock),MEMF_CLEAR|MEMF_PUBLIC);
        if (fib) {
            if (Examine(lock, fib)) {
                result = fib->fib_DirEntryType;
            }
            FreeMem(fib, sizeof(struct FileInfoBlock));
        }
        UnLock(lock);
    }
    return (result > 0);
}
#endif

union hblock {
    char dummy[TBLOCK];
    struct header {
        char name[NAMSIZ];
        char mode[8];
        char uid[8];
        char gid[8];
        char size[12];
        char mtime[12];
        char chksum[8];
        char linkflag;
        char linkname[NAMSIZ];
    } dbuf;
};

#define BLKSIZE     (sizeof (union hblock))
#define OLD_NORMAL  '\0'
#define NORMAL      '0'
#define HARDLINK    '1'
#define SYMBLINK    '2'
#define DIR         '5'     /* directory */

/* These linkflags (from GNU Tar 1.1) aren't supported by TarSplit */
#define CHARSPEC    '3'     /* character special file */
#define BLOCKSPEC   '4'     /* block special file */
#define FIFOSPEC    '6'     /* FIFO special file */
#define CONTIG      '7'     /* contiguous file */
#define DUMPDIR     'D'
#define MULTIVOL    'M'
#define NAMES       'N'
#define SPARSE      'S'
#define VOLHDR      'V'     /* volume header */

void DoFile();
#ifdef AMIGA
void CvtUnix2AmigaFNames( char *);
#endif

main(argc, argv)
int argc;
char    *argv[];
{
    FILE        *TarFP;
    union hblock    TarBlock;

#ifndef AMIGA
    /* make the compiler happy about formatted I/O for longs... */
    pflinit();
#endif

    switch(argc) {
#ifndef AMIGA
    case 1:
        TarFP = stdin;
        break;
#endif
    case 2:
        if ((TarFP = fopen(argv[1], "r")) == NULL) {
            fprintf(writeto,"TarSplit: can't open '%s'\n", argv[1]);
            exit(1);
        }
        break;
    default:
        fprintf(writeto,"usage: TarSplit [pathname]\n");
        exit(1);
    }

    for (;;) {
        if (fread((char *)&TarBlock, BLKSIZE, 1, TarFP) == (size_t)NULL) {
            fprintf(writeto,"TarSplit: premature EOF\n");
            exit(1);
        } else if (IsZero(&TarBlock)) {
            while (fread((char *)&TarBlock, BLKSIZE, 1, TarFP) != (size_t)NULL)
                ;
            break;
        } else
            DoFile(&TarBlock, TarFP);
    }
#if defined(DEPENDENCYLIST) && defined(AMIGA)
    ResolveDependency();
#endif
}

bool
IsZero(block)
union hblock    *block;
{
    int i;
    char    *cblock;

    cblock = block->dummy;
    for (i = 0; i < TBLOCK; i++)
        if (*cblock++ != '\0')
            return(FALSE);

    return (TRUE);
}

void
DoFile(block, TarFP)
union hblock    *block;
FILE        *TarFP;
{
    long    FSize;
    char    FName[NAMSIZ], *RefName;
    int     i;
    bool    nodeIsDir, OpenOK;
    FILE    *NewFP;
#ifdef AMIGA
    BPTR Lock;
#endif

    if (!ChkSumOK(block)) {
        fprintf(writeto,"TarSplit: bad checksum, name '%s'?\n",
              block->dbuf.name);
        exit(1);
    }

#ifdef AMIGA
    /* normalize filename...after checksum */
    CvtUnix2AmigaFNames(block->dbuf.name);
    if (block->dbuf.name[0] == '\0')
        return; /* ignore if current directory */
#endif

    switch(block->dbuf.linkflag) {
    case HARDLINK:
    case SYMBLINK:
#if defined(DEPENDENCYLIST) && defined(AMIGA)
        AddDependency(block->dbuf.name, block->dbuf.linkname,
              block->dbuf.linkflag);
        fprintf(writeto,"Tarsplit: postponing link for '%s'\n",
              block->dbuf.name);
#else
        fprintf(writeto,"TarSplit: can't handle link for '%s' to '%s'\n",
              block->dbuf.name, block->dbuf.linkname);
#endif
#ifdef UNDEF
        exit(1); /* don't allow TarSplit to continue */
#endif
        return;
    case OLD_NORMAL:
    case NORMAL:
    case DIR:
        break;
    default:
        fprintf(writeto,"TarSplit: unsupported linkflag: %0X\n",
              (int)block->dbuf.linkflag);
        exit(1);
    }

#ifdef  AMIGA
    if (sscanf(block->dbuf.size, "%12lo", &FSize) != 1) {
#else
    if (sscanf(block->dbuf.size, "%12O", &FSize) != 1) {
#endif
        fprintf(writeto,"TarSplit: bad size\n");
        exit(1);
    }

    for (i = 0, RefName = block->dbuf.name; *RefName; i++, RefName++)
        FName[i] = *RefName;
    FName[i] = '\0';    /* null terminate */

    if ((block->dbuf.linkflag == DIR) ||
              (nodeIsDir = (*(RefName - 1) == '/'))) {
        if (*(RefName - 1) == '/')
            FName[i - 1] = '\0';
        else
            fprintf(writeto, "TarSplit: bad format for path '%s'\n", FName);
#ifdef  AMIGA
        Lock = CreateDir((UBYTE *)FName);
        OpenOK = (Lock != 0);
        if (! OpenOK) {
            /* We may be splitting into an existing hierarchy.
             * Don't punish us for that! This event is not
             * currently reported to the user, though it might
             * be a good idea.
             */
/*
            if ((IoErr() == ERROR_OBJECT_EXISTS) && IsDir(FName))
*/ /* Really dumb...CreateDir() on a directory that already exists
      doesn't return an error in IoError() */
            if (IsDir(FName)) {
                 OpenOK = TRUE;
            }
        }
        if (Lock) UnLock(Lock);
#else
        if (strcmp(FName, ".") == 0)
            OpenOk = TRUE;
        else
            OpenOK = mknod(FName, DIRMODE) == 0;
#endif

        if (!OpenOK) {
            fprintf(writeto,"TarSplit: can't create directory '%s/'\n", FName);
            exit(1);
        }
    } else {
#ifdef AMIGA
        if (strchr(FName, ':') != NULL) {
            fprintf(writeto,"TarSplit: illegal char(s) in pathname '%s'\n",
                  FName);
            exit(1);
        }    
#endif
        OpenOK = (NewFP = fopen(FName, "w")) != NULL;
#ifdef AMIGA
        /* 05-06-91, MRR:
         * Some tar programs (e.g. Interactive) don't write the directory
         * nodes to the archive. If we get a 205 error (OBJECT_NOT_FOUND)
         * when creating a file, it's a pretty safe bet that the failure
         * was caused by a missing directory node. Try to create the
         * directory and see if it helps.
         */
        if (! OpenOK) {
            if (! CreateAmigaDir(FName)) {
                OpenOK = (NewFP = fopen(FName, "w")) != NULL;
            }
        }
        if (!OpenOK) {
            fprintf(writeto,"TarSplit: can't create file '%s'\n", FName);
            exit(1);
        }
#endif
    }

    if (nodeIsDir)
        return;

    fprintf(writeto, "TarSplit: extracting '%s'\n", FName);

    for (; FSize > 0; FSize -= TBLOCK) {
        if (fread((char *)block, BLKSIZE, 1, TarFP) == (size_t)NULL) {
            exit(1);
        }
        if (!nodeIsDir)
            fwrite(block->dummy, 1,
                (FSize > TBLOCK ? TBLOCK : (int) FSize), NewFP);
    }

    if (!nodeIsDir)
        fclose(NewFP);
}

bool
ChkSumOK(block)
union hblock    *block;
{
    long    Accum, ChkSumVal;
    int     i;

#ifdef  AMIGA
    sscanf(block->dbuf.chksum, "%8lo", &ChkSumVal);
#else
    sscanf(block->dbuf.chksum, "%8O", &ChkSumVal);
#endif
    for (i = 0; i < 8; i++)
        block->dbuf.chksum[i] = ' ';

    Accum = 0;
    for (i = 0; i < TBLOCK; i++)
        Accum += 0xff & block->dummy[i];

    return(Accum == ChkSumVal);
}

#ifdef AMIGA
int
CreateAmigaDir(char *fileName)
{
    static char     dirName[256];
    static char     partial[256];
    BPTR    lock;
    char    *p;
    int     result = 0;

    strcpy(dirName, fileName);
    p = strrchr(dirName, '/');
    if (p) {
        *p = '\0';           /* Drop leaf node. */
        *partial = '\0';
        for (p = strtok(dirName, "/"); p; p = strtok(NULL, "/")) {
            if (*p == '.') continue;
            if (*partial) strcat(partial,"/");
            strcat(partial, p);
            if (lock = Lock((UBYTE *)partial, SHARED_LOCK)) {
                UnLock(lock);
                continue;
            }
            lock = CreateDir((UBYTE *)partial);
            if (! lock) {
                result = IoErr();
                break;
            }
            else
                UnLock(lock);
        }
    }
    return result;
}
#endif

#ifdef AMIGA
/* Trivial kludge to convert "./path" -> "path" and "../path" -> "/path" */
void
CvtUnix2AmigaFNames( cptr )
    char *cptr;
{
    int l;

    if ( *cptr == '.' ) {
        if ( (*(cptr+1) == '/') ||
              ( *(cptr+1) == '.' && *(cptr+2) == '/' ) ) {
/*
            strcpy(cptr, (cptr+2));
*/
            l = strlen(cptr+2);
            memmove(cptr, (cptr+2), l);
            *(cptr+l) = '\0'; /* make sure it's null terminated */
        }
    }
}
#endif

#if defined(DEPENDENCYLIST) && defined(AMIGA)
void
AddDependency( dependent, independent, flag )
    char * dependent;   /* path to link file */
    char * independent; /* relative path to source file */
    char flag;
{
    list found;
    list currptr;
    list tmpnode;
    char origpath[NAMSIZ];  /* source file */

    if (*independent == '/') {
        fprintf(writeto,"TarSplit: can't resolve (absolute) link to '%s'\n",
              independent);
#ifdef UNDEF
        exit(1); /* don't allow TarSplit to continue */
#endif
        return;
    }

    ExtractPath(dependent, independent, &origpath);

    if (IsDir(origpath)) {
        fprintf(writeto,"TarSplit: can't handle directory link to '%s'\n",
              origpath);
#ifdef UNDEF
        exit(1); /* don't allow TarSplit to continue */
#endif
        return;
    }
    tmpnode = (list) malloc(sizeof(listnode));
    if (!tmpnode) {
        fprintf(writeto,"TarSplit: out of memory\n");
        exit(1);
    }

    strcpy(tmpnode->name, dependent);
    strcpy(tmpnode->linkname, origpath);
    tmpnode->linkflag = flag;

    if (dependencylist == NULL) {
        dependencylist = tmpnode;
        tmpnode->next = NULL;
    } else {
        found = NULL;
        currptr = dependencylist;
        /* look for file to link to */
        while (currptr != NULL) {
            if (strcmp(currptr->name, origpath)==0) {
                /* if found, put dependent operation after it */
                found = currptr->next;
                currptr->next = tmpnode;
                tmpnode->next = found;                
                currptr = NULL;
            } else {
                currptr = currptr->next;
            }
        }
        if (found == NULL) {
            /* if not found, put our operation first */
            tmpnode->next = dependencylist;
            dependencylist = tmpnode;
        }
    }    
}

/* converts:  Root/Path/FileLink ->  RelPath/FileSource
 *       to:  Root/Path/FileLink ->  Root/Path2/FileSource
 * also, handle weird paths...eg "./x/../x/../x/file" becomes "./x/file"
 */
void
ExtractPath(fullpath, relpath, linkpath)
    char *fullpath;
    char *relpath;
    char *linkpath;
{
    int depdirs[32];
    int level=0;
    int temp;
    char *cptr;
    char dscr[NAMSIZ];  /* scratch */
    char iscr[NAMSIZ];

    strcpy(dscr, fullpath);
    strcpy(iscr, relpath);

    for (temp = 0; temp < 32; temp++) {
        depdirs[temp] = 0;
    }

    cptr = (char*)dscr;
    depdirs[0] = (int)cptr;

    while (*cptr != '\0') {
        if (*cptr == '/') {
            *cptr = '\0';
            level++;
            depdirs[level] = (int)cptr+1;
        }
        cptr++;
    }

    cptr = (char*)iscr;
    depdirs[level] = (int)cptr; /* overwrite filename in
                                   name of dependent file */

    while (*cptr != '\0') {
        if ((*(cptr-1) == '\0' || cptr==(char*)iscr)) {
            if (*cptr == '.') {
                if (*(cptr+1) == '/') {
                    /* ./ (current directory...ignore) */
                    cptr++;
                    *cptr = '\0';
                    depdirs[level] = (int)(cptr+1);
                } else if (*(cptr+1) == '.' && *(cptr+2) == '/') {
                    /* ../ (pop one directory level) */
                    cptr += 2;
                    *cptr = '\0';
                    depdirs[level] = 0;
                    if (level == 0) {
                        fprintf(writeto,
                              "TarSplit: bad path in link '%s' -> '%s'\n",
                              fullpath, relpath);
                        exit(1);
                    }

                    level--;
                    depdirs[level] = (int)(cptr+1);
                }
            }
        } else if (*cptr == '/') {
            level++;
            *cptr = '\0';
            depdirs[level] = (int)(cptr+1);
        }
        cptr++;
    }

    *linkpath = '\0';
    for (temp = 0; temp < level; temp++) {
        strcat(linkpath, (char*)depdirs[temp]);
        strcat(linkpath, "/");
    }
    strcat(linkpath, (char*)depdirs[level]);
}

#ifdef MANX
#undef system
#define system(s) Execute((UBYTE *)s, 0L, 0L)
#endif

void
ResolveDependency()
{
    char commandstring[256];
    list currptr;

    currptr = dependencylist;
    while (currptr != NULL) {
        fprintf(writeto, "TarSplit: resolving link for '%s'\n",
              currptr->name);

#if WORKING_LINKS_VERSION > 37
        if (((struct Library *)DOSBase)->lib_Version > WORKING_LINKS_VERSION) {
            sprintf(commandstring, "MAKELINK \"%s\" \"%s\" ",
                  currptr->name, currptr->linkname);
            if (currptr->linkflag == HARDLINK)
                strcat(commandstring, "HARD");
            else
                strcat(commandstring, "SOFT");
        } else
#else
        {
            sprintf(commandstring, "COPY \"%s\" \"%s\"",
                  currptr->linkname, currptr->name);
            system(commandstring);
            sprintf(commandstring, "FILENOTE \"%s\" \"-> %s\"",
                  currptr->name, currptr->linkname);
        }
#endif
        system(commandstring);
        currptr = currptr->next;
    }
}
#endif
