/*
 * untgz.c -- Display contents and/or extract file from
 * a gzip'd TAR file
 * written by "Pedro A. Aranda Guti\irrez" <paag@tid.es>
 * adaptation to Unix by Jean-loup Gailly <jloup@gzip.org>
 * adaption to Amiga (PPC,WarpOS) PPC by Steffen Haeuser (MagicSN@Birdland.es.bawue.de)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <errno.h>
#include <filedefs.h>
#include <clib/exec_protos.h>
#include <time.h>
#include <exec/types.h>
#include <libraries/dos.h>
#include <clib/dos_protos.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/dir.h>
#include <utime.h>
#define ENOENT 0

#ifndef UnixToAmigaPath
#define UnixToAmigaPath(path)   path
#endif

#define FILEFLAGS_READABLE  0x0080UL
#define FILEFLAGS_WRITEABLE 0x0100UL
typedef struct FileInfoBlock    FIB;

char* strdup(const char* str)
{
    char* dst = malloc(strlen(str)+1) ;
    if (dst) {
        strcpy(dst, str);
    }
    return dst;
}

int
access(const char* name, int mode)
{
    FIB *fib = malloc(sizeof(FIB));
    int r = -1;

    if (fib) {
        BPTR lock;
        if (lock = Lock((STRPTR)UnixToAmigaPath(name), SHARED_LOCK)) {
            if (Examine(lock, fib)) {
                long prot = fib->fib_Protection;

                r = 0;
                if (mode & 4) {     /*  r   */
                    if (prot & 8)   /*  no read perm    */
                        r = -1;
                }
                if (mode & 2) {     /*  w   */
                    if (prot & 4)   /*  no write perm   */
                        r = -1;
                }
                if (mode & 1) {     /*  x   */
                    if (prot & 2)   /*  no execute perm */
                        r = -1;
                }
            }
            UnLock(lock);
        } else {
            /*
             *  if obj in use we cannot obtain stats.  All we know is
             *  that the obj exists.  We certainly cannot read or write
             *  it at the moment.
             */

            if (IoErr() == ERROR_OBJECT_IN_USE) {
                r = 0;
                if (mode)       /*  anything but existance fails    */
                    r = -1;
            }
        }
        free(fib);
    }
    return(r);
}

int mymkdir(const char* path, mode_t mode)
{
    BPTR lock = CreateDir((STRPTR)path);
    if (lock) {
        UnLock(lock);
        return 0;
    }
    return -1 ;
}

#define AMITIME

#ifdef AMITIME
/*#include <powerup/ppclib/interface.h>
#include <powerup/gcclib/powerup_protos.h>

#include <proto/dos.h>
*/
extern char *strcpy(char *, const char *);
extern char *strrchr(const char *, int);
#endif /* AMITIME */

#ifndef AMITIME
#include <utime.h>
#endif /* AMITIME */

#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
# include <dos.h>
//# define mkdir(x, y) mkdir(x)

#include "zlib.h"

/* Values used in typeflag field.  */

#define REGTYPE  '0'            /* regular file */
#define AREGTYPE '\0'           /* regular file */
#define LNKTYPE  '1'            /* link */
#define SYMTYPE  '2'            /* reserved */
#define CHRTYPE  '3'            /* character special */
#define BLKTYPE  '4'            /* block special */
#define DIRTYPE  '5'            /* directory */
#define FIFOTYPE '6'            /* FIFO special */
#define CONTTYPE '7'            /* reserved */

#define BLOCKSIZE 512

struct tar_header
{                               /* byte offset */
  char name[100];               /*   0 */
  char mode[8];                 /* 100 */
  char uid[8];                  /* 108 */
  char gid[8];                  /* 116 */
  char size[12];                /* 124 */
  char mtime[12];               /* 136 */
  char chksum[8];               /* 148 */
  char typeflag;                /* 156 */
  char linkname[100];           /* 157 */
  char magic[6];                /* 257 */
  char version[2];              /* 263 */
  char uname[32];               /* 265 */
  char gname[32];               /* 297 */
  char devmajor[8];             /* 329 */
  char devminor[8];             /* 337 */
  char prefix[155];             /* 345 */
                                /* 500 */
};

union tar_buffer {
  char               buffer[BLOCKSIZE];
  struct tar_header  header;
};

enum { TGZ_EXTRACT = 0, TGZ_LIST };

static char *TGZfname   OF((const char *));
void TGZnotfound        OF((const char *));

int getoct              OF((char *, int));
char *strtime           OF((time_t *));
int ExprMatch           OF((char *,char *));

int makedir             OF((char *));
int matchname           OF((int,int,char **,char *));

void error              OF((const char *));
int  tar                OF((gzFile, int, int, int, char **));

void help               OF((int));
int main                OF((int, char **));

char *prog;

/* This will give a benign warning */

static char *TGZprefix[] = { "\0", ".tgz", ".tar.gz", NULL };

/* Return the real name of the TGZ archive */
/* or NULL if it does not exist. */

static char *TGZfname OF((const char *fname))
{
  static char buffer[1024];
  int origlen,i;

  strcpy(buffer,fname);
  origlen = strlen(buffer);

  for (i=0; TGZprefix[i]; i++)
    {
       strcpy(buffer+origlen,TGZprefix[i]);
       if (access(buffer,0) == 0)
         return buffer;
    }
  return NULL;
}

/* error message for the filename */

void TGZnotfound OF((const char *fname))
{
  int i;

  fprintf(stderr,"%s : couldn't find ",prog);
  for (i=0;TGZprefix[i];i++)
    fprintf(stderr,(TGZprefix[i+1]) ? "%s%s, " : "or %s%s\n",
            fname,
            TGZprefix[i]);
  exit(1);
}


/* help functions */

int getoct(char *p,int width)
{
  int result = 0;
  char c;

  while (width --)
    {
      c = *p++;
      if (c == ' ')
        continue;
      if (c == 0)
        break;
      result = result * 8 + (c - '0');
    }
  return result;
}

char *strtime (time_t *t)
{
  struct tm   *local;
  static char result[32];

  local = localtime(t);
  sprintf(result,"%2d/%02d/%4d %02d:%02d:%02d",
          local->tm_mday, local->tm_mon+1, local->tm_year+1900,
          local->tm_hour, local->tm_min,   local->tm_sec);
  return result;
}


/* regular expression matching */

#define ISSPECIAL(c) (((c) == '*') || ((c) == '/'))

int ExprMatch(char *string,char *expr)
{
  while (1)
    {
      if (ISSPECIAL(*expr))
        {
          if (*expr == '/')
            {
              if (*string != '\\' && *string != '/')
                return 0;
              string ++; expr++;
            }
          else if (*expr == '*')
            {
              if (*expr ++ == 0)
                return 1;
              while (*++string != *expr)
                if (*string == 0)
                  return 0;
            }
        }
      else
        {
          if (*string != *expr)
            return 0;
          if (*expr++ == 0)
            return 1;
          string++;
        }
    }
}

/* recursive make directory */
/* abort if you get an ENOENT errno somewhere in the middle */
/* e.g. ignore error "mkdir on existing directory" */
/* */
/* return 1 if OK */
/*        0 on error */

int makedir (char *newdir)
{
  char *buffer = strdup(newdir);
  char *p;
  int  len = strlen(buffer);

  if (len <= 0) {
    free(buffer);
    return 0;
  }
  if (buffer[len-1] == '/') {
    buffer[len-1] = '\0';
  }
  if (mymkdir(buffer,0775) == 0)
    {
      free(buffer);
      return 1;
    }

  p = buffer+1;
  while (1)
    {
      char hold;

      while(*p && *p != '\\' && *p != '/')
        p++;
      hold = *p;
      *p = 0;
      if ((mymkdir(buffer,0775) == -1) && (errno == ENOENT))
        {
          fprintf(stderr,"%s: couldn't create directory %s\n",prog,buffer);
          free(buffer);
          return 0;
        }
      if (hold == 0)
        break;
      *p++ = hold;
    }
  free(buffer);
  return 1;
}

int matchname (int arg,int argc,char **argv,char *fname)
{
  if (arg == argc)              /* no arguments given (untgz tgzarchive) */
    return 1;

  while (arg < argc)
    if (ExprMatch(fname,argv[arg++]))
      return 1;

  return 0; /* ignore this for the moment being */
}


/* Tar file list or extract */

int tar (gzFile in,int action,int arg,int argc,char **argv)
{
  union  tar_buffer buffer;
  int    len;
  int    err;
  int    getheader = 1;
  int    remaining = 0;
  FILE   *outfile = NULL;
  char   fname[BLOCKSIZE];
  time_t tartime;

  if (action == TGZ_LIST)
    printf("     day      time     size                       file\n"
           " ---------- -------- --------- -------------------------------------\n");
  while (1)
    {
      len = gzread(in, &buffer, BLOCKSIZE);
      if (len < 0)
        error (gzerror(in, &err));
      /*
       * if we met the end of the tar
       * or the end-of-tar block,
       * we are done
       */
      if ((len == 0)  || (buffer.header.name[0]== 0))
        break;

      /*
       * Always expect complete blocks to process
       * the tar information.
       */
      if (len != BLOCKSIZE)
        error("gzread: incomplete block read");

      /*
       * If we have to get a tar header
       */
      if (getheader == 1)
        {
          tartime = (time_t)getoct(buffer.header.mtime,12);
          strcpy(fname,buffer.header.name);

          switch (buffer.header.typeflag)
            {
            case DIRTYPE:
              if (action == TGZ_LIST)
                printf(" %s     <dir> %s\n",strtime(&tartime),fname);
              if (action == TGZ_EXTRACT)
                makedir(fname);
              break;
            case REGTYPE:
            case AREGTYPE:
              remaining = getoct(buffer.header.size,12);
              if (action == TGZ_LIST)
                printf(" %s %9d %s\n",strtime(&tartime),remaining,fname);
              if (action == TGZ_EXTRACT)
                {
                  if ((remaining) && (matchname(arg,argc,argv,fname)))
                    {
                      outfile = fopen(fname,"wb");
                      if (outfile == NULL) {
                        /* try creating directory */
                        char *p = strrchr(fname, '/');
                        if (p != NULL) {
                          *p = '\0';
                          makedir(fname);
                          *p = '/';
                          outfile = fopen(fname,"wb");
                        }
                      }
                      fprintf(stderr,
                              "%s %s\n",
                              (outfile) ? "Extracting" : "Couldn't create",
                              fname);
                    }
                  else
                    outfile = NULL;
                }
              /*
               * could have no contents
               */
              getheader = (remaining) ? 0 : 1;
              break;
            default:
              if (action == TGZ_LIST)
                printf(" %s     <---> %s\n",strtime(&tartime),fname);
              break;
            }
        }
      else
        {
          unsigned int bytes = (remaining > BLOCKSIZE) ? BLOCKSIZE : remaining;

          if ((action == TGZ_EXTRACT) && (outfile != NULL))
            {
              if (fwrite(&buffer,sizeof(char),bytes,outfile) != bytes)
                {
                  fprintf(stderr,"%s : error writing %s skipping...\n",prog,fname);
                  fclose(outfile);
                  unlink(fname);
                }
            }
          remaining -= bytes;
          if (remaining == 0)
            {
              getheader = 1;
              if ((action == TGZ_EXTRACT) && (outfile != NULL))
                {
#ifdef AMITIME
                  //struct DateStamp *tp;

                  //tp = __timecvt(tartime);
                  fclose(outfile);
                  outfile = NULL;

                  //SetFileDate(fname, tp);
#else
                  struct utimbuf settime;

                  settime.actime = settime.modtime = tartime;

                  fclose(outfile);
                  outfile = NULL;
                  utime(fname,&settime);
#endif
                }
            }
        }
    }

  if (gzclose(in) != Z_OK)
    error("failed gzclose");

  return 0;
}


/* =========================================================== */

void help(int exitval)
{
  fprintf(stderr,
          "untgz v 0.1\n"
          " an sample application of zlib 1.0.4\n\n"
          "Usage : untgz TGZfile            to extract all files\n"
          "        untgz TGZfile fname ...  to extract selected files\n"
          "        untgz -l TGZfile         to list archive contents\n"
          "        untgz -h                 to display this help\n\n");
  exit(exitval);
}

void error(const char *msg)
{
    fprintf(stderr, "%s: %s\n", prog, msg);
    exit(1);
}


/* ====================================================================== */

int _CRT_glob = 0;      /* disable globbing of the arguments */

int main(int argc,char **argv)
{
    int         action = TGZ_EXTRACT;
    int         arg = 1;
    char        *TGZfile;
    gzFile      *f;


    prog = strrchr(argv[0],'\\');
    if (prog == NULL)
      {
        prog = strrchr(argv[0],'/');
        if (prog == NULL)
          {
            prog = strrchr(argv[0],':');
            if (prog == NULL)
              prog = argv[0];
            else
              prog++;
          }
        else
          prog++;
      }
    else
      prog++;

    if (argc == 1)
      help(0);

    if (strcmp(argv[arg],"-l") == 0)
      {
        action = TGZ_LIST;
        if (argc == ++arg)
          help(0);
      }
    else if (strcmp(argv[arg],"-h") == 0)
      {
        help(0);
      }

    if ((TGZfile = TGZfname(argv[arg])) == NULL)
      TGZnotfound(argv[arg]);

    ++arg;
    if ((action == TGZ_LIST) && (arg != argc))
      help(1);

/*
 *  Process the TGZ file
 */
    switch(action)
      {
      case TGZ_LIST:
      case TGZ_EXTRACT:
        f = gzopen(TGZfile,"rb");
        if (f == NULL)
          {
            fprintf(stderr,"%s: Couldn't gzopen %s\n",
                    prog,
                    TGZfile);
            return 1;
          }
        exit(tar(f, action, arg, argc, argv));
      break;

      default:
        error("Unknown option!");
        exit(1);
      }

    return 0;
}
