/*
**      $VER: DeTar.c 1.5 (15.2.98)
**
**      DeTar main module
**
**      written in 1995-98 by Andreas R. Kleinert, based on code by Steve Sampson
**      Public Domain. (See DeTar.doc)
**      Modified by Steffen P. Häuser to use WarpOS instead of - ELF sucks -
**      ppc.library
*/

/* ------------------------------------------------------------------------------ */

/*
 *      detar.c
 *
 *      Version 1.1
 *
 *      Fixed bug in convert() wich produced errors on files without
 *      a slash '/' in them.
 *
 *      Unix Tape Archive (TAR) Extractor for MSDOS
 *
 *      Public Domain January 1988, Steve Sampson
 *      Based on the public domain TAR program by John Gilmore
 *
 *      Compiled with ECO-C88 V3.21
 *
 */

/* Includes */

#define __USE_SYSBASE

#include <exec/types.h>

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

#include <clib/exec_protos.h>
#include <clib/dos_protos.h>

#include "detar.h"

/* Defines */

#define isodigit(c)     ( ((c) >= '0') && ((c) <= '7') )

/* Globals */

FILE    *fdi,
        *fdo;

union   record  *head;          /* the archive file header */
struct  stat    hstat;          /* the decoded stat info   */
struct  {
        long    year;
        long    month;
        long    day;
        long    hour;
        long    min;
        long    sec;
} tm;

long    ugswidth = 11;
long     dir_only = 0;

char    tempname[256];

char version [] = "\0$VER: detar 1.5 (15.2.98)";


/* Forward References, Prototypes */

void dir_tar(void);
void de_tar(void);
void decode_header(union record *header, struct stat *st, long *stdp);
void demode(unsigned mode, char *string);
long from_oct(long digs, char *where);


/* Main Program */

void main(long argc, char **argv)
{
 char *p;
 long retval = 0;
 long add    = FALSE, valid = FALSE;


 if(!argc) exit(20);


 /* The operator can ask for directory only or de-tar only. */

 argv++;

 if(argc == 3 && **argv == 'd')
  {
   dir_only++;
   argc--;
   argv++;
  }

 if (argc != 2)
  {
   fprintf(stderr, &version[7]);

   fprintf(stderr, "\nWarpOS PPC Port by Steffen Haeuser in 1998. Public Domain."
                   "\nAmiga Port by Andreas R. Kleinert in 1995-98. Public Domain."
                   "\nPublic Domain January 1988, Steve Sampson"
                   "\nBased on the public domain TAR program by John Gilmore");

   fprintf(stderr, "\nUsage: detar [d] filename[.tar]");
   fprintf(stderr, "\nWhere d means 'list content only'\n");

   exit(1);
  }

 for (p = *argv; *p; p++) *p = (char)toupper(*p);


 /* parse the many possibilities, this stuff not needed with MSC */

       if ((p = strrchr(*argv, '.')) == (char *)NULL) { add =                    valid = TRUE; }
  else if (strcmp(p, ".TAR") == NULL)                 { strcpy(tempname, *argv); valid = TRUE; }
  else if (*(p+1) == '/' || *(p+1) == '\\')           { add = valid = TRUE;      valid = TRUE; }

 if(valid)
  {
   if(add)
    {
     strcpy(tempname, *argv);
     strcat(tempname, ".TAR");
    }

   fdi = fopen(tempname, "rb");
   if(fdi)
    {
     setvbuf(fdi, NULL, _IOFBF, 8192);

     head = (union record *)malloc(512);
     if(head)
      {
       if (dir_only) dir_tar();
        else         de_tar();

       free(head);

      }else
      {
       fprintf(stderr, "Not enough memory !\n");
       retval = 20;
      }

     fclose(fdi);

    }else
    {
     fprintf(stderr, "Tar file '%s' not found\n", tempname);
     retval = 20;
    }

  }else
  {
   fprintf(stderr, "File '%s' not a TAR archive\n", *argv);
   retval = 20;
  }

 exit(retval);
}


/*
 *      Produce a directory of the files contained on the TAR
 */

void dir_tar(void)
{
 char   modes[11], *timestamp;
 char   uform[11], gform[11];
 char   *user, *group, size[24];
 long   n, pad, header_std;
 time_t timeval;

 for (;;)
  {
   if ((n = fread((char *)head, 1, 512, fdi)) == EOF)
    {
     return;
    }

   decode_header(head, &hstat, &header_std);

   /* File type and modes */

   modes[0] = '?';

   switch (head->header.linkflag)
    {
     case LF_NORMAL:
     case LF_OLDNORMAL:
     case LF_LINK:
      {
       modes[0] = '-';
       if ('/' == head->header.name[strlen(head->header.name)-1])
       modes[0] = 'd';
       break;
      }
     case LF_DIR:
      {
       modes[0] = 'd';
       break;
      }
     case LF_SYMLINK:
      {
       modes[0] = 'l';
       break;
      }
     case LF_BLK:
      {
       modes[0] = 'b';
       break;
      }
     case LF_CHR:
      {
       modes[0] = 'c';
       break;
      }
     case LF_FIFO:
      {
       modes[0] = 'f';
       break;
      }
     case LF_CONTIG:
      {
       modes[0] = '=';
       break;
      }
    }

   demode(hstat.st_mode, modes+1);

   /* Timestamp */

   timeval = hstat.st_mtime;
   timestamp = ctime(&timeval);
   timestamp[16] = '\0';
   timestamp[24] = '\0';

   /* User and group names */

   if (*head->header.uname && header_std)
    {
     user  = head->header.uname;
    }else
    {
     user = uform;
     sprintf(uform, "%ld", (long)hstat.st_uid);
    }

   if (*head->header.gname && header_std)
    {
     group = head->header.gname;
    }else
    {
     group = gform;
     sprintf(gform, "%ld", (long)hstat.st_gid);
    }

   /* Format the file size or major/minor device numbers */

   switch (head->header.linkflag)
    {
     case LF_CHR:
     case LF_BLK:
      {
       sprintf(size, "%ld, %ld",
       (long)from_oct(8, head->header.devmajor),
       (long)from_oct(8, head->header.devminor));
       break;
      }
     default:
      {
       sprintf(size, "%ld", hstat.st_size);

       break;
      }
    }


   /* Figure out padding and print the whole line. */

   pad = strlen(user) + strlen(group) + strlen(size) + 1;

   if (pad > ugswidth) ugswidth = pad;

    printf("%s %s/%s %*s%s %s %s %.*s",
        modes,
        user,
        group,
        ugswidth - pad,
        "",
        size,
        timestamp+4, timestamp+20,
        sizeof(head->header.name),
        head->header.name);

    switch (head->header.linkflag)
     {
      case LF_SYMLINK:
       {
        printf(" -> %s\n", head->header.linkname);
        break;
       }
      case LF_LINK:
       {
        printf(" link to %s\n", head->header.linkname);
        break;
       }

      default:
       {
        printf(" unknown file type '%c'\n", head->header.linkflag);
        break;
       }

      case LF_OLDNORMAL:
      case LF_NORMAL:
      case LF_CHR:
      case LF_BLK:
      case LF_DIR:
      case LF_FIFO:
      case LF_CONTIG:
       {
        putc('\n', stdout);
        break;
       }
    }

   /* Seek to next file */

   fseek(fdi, hstat.st_size, SEEK_CUR);

   /* File starts on 512 byte boundary */

   fseek(fdi, 512L - (hstat.st_size % 512L), SEEK_CUR);
  }
}


/*
 *      Extract the files from the TAR archive
 *
 */

void de_tar(void)
{
        ULONG   size, filesize;
        long    header_std, c, len;
        char    *workfile;

        for (;!feof(fdi);)
         {
          if ( fread((char *)head, 1, 512, fdi) == (unsigned int) EOF) break;

                decode_header(head, &hstat, &header_std);
                workfile = head->header.name;
                size = filesize = hstat.st_size;

                len = strlen(workfile);
                if(len > 0)
                 {
                  if(    (workfile[len-1] == '/')
                      || (workfile[len-1] == ':') )
                   {
                    BPTR lock;

                    printf("Creating: %s ...", workfile);

                    workfile[len-1] = (char) 0;

                    lock = CreateDir(workfile);
                    if(lock)
                     {
                      printf(" OK\n");
                      UnLock(lock);
                     }else printf(" Failed\n");

                    fseek(fdi, filesize, SEEK_CUR);

                    continue;
                   }

                  printf("Extracting: %s\n", workfile);

                  fdo = fopen(workfile, "wb");
                  if(fdo)
                   {
                    setvbuf(fdo, NULL, _IOFBF, 8192);

                    while ((c = getc(fdi)) != EOF)
                     {
                      putc(c, fdo); size--;

                      if(!size) /* Get to next 512 byte boundary */
                       {
                        fseek(fdi, 512L - (filesize % 512L), SEEK_CUR);
                        break;
                       }
                     }
                    fclose(fdo);
                   }else printf(" Failed\n");
                 }else printf("Skipping empty entry\n");
        }
}


/*
 *      Break down the header info into stat info
 *
 *      Set "*stdp" to != 0 or == 0 depending whether
 *      header record is "Unix Standard" or "old" tar format.
 *
 */

void decode_header(union record *header, struct stat *st, long *stdp)
{
        st->st_mode  = (long)from_oct( 8, header->header.mode);
        st->st_mtime = from_oct(12, header->header.mtime);
        st->st_size  = from_oct(12, header->header.size);

        if (0 == strcmp(header->header.magic, TMAGIC)) {

                /* Unix Standard tar archive */

                *stdp = 1;
                st->st_dev = 0;

        } else {

                /* Old fashioned tar archive */

                *stdp = 0;
                st->st_uid = (long)from_oct(8, header->header.uid);
                st->st_gid = (long)from_oct(8, header->header.gid);
                st->st_dev = 0;
        }
}

/*
 *      Decode the mode string from a stat entry into a 9-char string
 */

void demode(unsigned mode, char *string)
{
        register unsigned mask;
        register char     *rwx = "rwxrwxrwx";

        for (mask = 0400; mask != 0; mask >>= 1) {
                if (mode & mask)
                        *string++ = *rwx++;
                else {
                        *string++ = '-';
                        rwx++;
                }
        }

        if (mode & S_ISUID)
                if (string[-7] == 'x')
                        string[-7] = 's';
                else
                        string[-7] = 'S';

        if (mode & S_ISGID)
                if (string[-4] == 'x')
                        string[-4] = 's';
                else
                        string[-4] = 'S';

        if (mode & S_ISVTX)
                if (string[-1] == 'x')
                        string[-1] = 't';
                else
                        string[-1] = 'T';

        *string = '\0';
}


/*
 *      Quick and dirty octal conversion.
 *
 *      Result is -1 if the field is invalid (all blank, or nonoctal).
 */

long from_oct(long digs, char *where)
{
        register long   value;

        while (isspace(*where)) {               /* Skip spaces */
                where++;
                if (--digs <= 0)
                        return -1;              /* All blank field */
        }

        value = 0;
        while (digs > 0 && isodigit(*where)) {  /* Scan til nonoctal */
                value = (value << 3) | (*where++ - '0');
                --digs;
        }

        if (digs > 0 && *where && !isspace(*where))
                return -1;                      /* Ended on non-space/nul */

        return value;
}
