/* zooext.c */
/*
Copyright (C) 1986 Rahul Dhesi -- All rights reserved
*/
#include "options.h"
/* Extract file from archive.  Extracts files specified in parameter-list 
   from archive zoo_path.  If none specified, extracts all files from 
   archive. */

#include "zoo.h"
#include "parse.h"      /* defines struct for parse() */

#include "portable.h"   /* portable I/O definitions */
#include "machine.h"    /* machine-specific declarations */

#include <stdio.h>
#include "various.h"

#ifndef NOSIGNAL
#include <signal.h>
#endif

#include "zoofns.h"

/* for low-level I/O */
#ifdef FLAT
#include <types.h>
#include <stat.h>
#else
#include <sys/types.h>
#include <sys/stat.h>
#endif

#ifdef NOFCNTL
#include <file.h>
#else
#include <fcntl.h>
#endif

extern int quiet;

#include "errors.i"

/* Following two are used by ctrl_c() also, hence declared here */
char extfname[LFNAMESIZE];             /* filename of extracted file */
static int this_han;                   /* handle of file to extract */

static int tofile;                     /* true if not pipe or null device */
extern unsigned int crccode;
extern char *out_buf_adr;              /* address of output buffer */

void zooext(zoo_path, option)
char *zoo_path, *option;
{
char *whichname;                          /* which name to extract */
char matchname[PATHSIZE];                 /* for pattern matching only */
#ifndef NOSIGNAL
int (*oldsignal)();        /* to save previous SIGINT handler */
#endif
int zoo_han;                              /* handle for open archive */
long next_ptr;                            /* pointer to within archive */
struct zoo_header zoo_header;             /* header for archive */
int status;                               /* error status */
char ans[3];                              /* answer to "Overwrite?" */
int error_message = 1;                    /* Whether to give error message */
unsigned long disk_space;                 /* disk space left */
int matched = 0;                          /* Any files matched? */
int overwrite = 0;                        /* force overwrite of files? */
int needdel = 0;                          /* extract deleted files too */
int usepath = 0;                          /* use path for extraction */
int todot = 0;                            /* extract relative to . */
int zootime = 0;                          /* just set archive time */
int badcrc_count = 0;                     /* how many files with bad CRC */
int bad_header = 0;                       /* to avoid spurious messages later */
long fiz_ofs = 0;                         /* offset where to start */
int pipe = 0;                             /* are we piping output? */
int fast_ext = 0;                         /* fast extract as *.?Z? */
int null_device = 0;                      /* are we sending to null device? */
int alloc_size;                           /* disk allocation unit size */
struct direntry direntry;                 /* directory entry */

#ifdef OOZ
   static char extract_ver[] = "A higher version of OOZ is needed to extract ";
   static char no_space[] = "Insufficient disk space to extract ";
#else
   static char extract_ver[] = "Zoo %d.%d is needed to extract %s.\n";
   static char no_space[] = "Insufficient disk space to extract %s.\n";
#endif

#ifndef OOZ
while (*option) {
   switch (*option) {
#ifndef PORTABLE
      case 'z': fast_ext++; break;
#endif
      case 'x':
      case 'e': break;
      case 'N': null_device++; break;
      case 'O': overwrite += 2; break;
      case 'o': overwrite++; break;
      case 'p': pipe++; break;
      case 'd': needdel++; break;
      case 'q': quiet++; break;
      case '/': usepath++; break;
      case '.': todot++; break;
      case '@': 
         ++option;
         fiz_ofs = calc_ofs(option); 
         goto no_more;
      default:
         prterror ('w', option_ignored, *option);
         break;
   }
   option++;
}

no_more: /* come from exit in while loop above */


if (overwrite == 1)                 /* must be at least 2 to begin with */
   overwrite--;

if (null_device && pipe) {
   prterror ('w', option_ignored, 'p');
   pipe = 0;
}

if (overwrite && pipe)
   prterror ('w', option_ignored, 'O');

if (null_device && fast_ext) {
   prterror ('w', option_ignored, 'N');
   null_device = 0;
}

tofile = !pipe && !null_device;     /* sending to actual file */
#else
tofile = 1;
#endif


zoo_han = OPEN(zoo_path, F_READ);

if (zoo_han == -1)
#ifdef OOZ
   prterror ('f', could_not_open, zoo_path, ".\n");
#else
   prterror ('f', could_not_open, zoo_path);
#endif

if (fiz_ofs != 0L) {                /* if offset specified, start there */
   prterror('m', "Starting at %ld\n", fiz_ofs);
   lseek (zoo_han, fiz_ofs, 0);
} else {
   /* read header */
   rd_zooh (&zoo_header, zoo_han);
   /* read (zoo_han, (char *) &zoo_header, sizeof(zoo_header)); */
   if ((zoo_header.zoo_start + zoo_header.zoo_minus) != 0L) {
      prterror ('w', failed_consistency, NULL, NULL);
      bad_header++;
   }
   lseek (zoo_han, zoo_header.zoo_start, 0); /* seek to where data begins */
}

#ifndef PORTABLE
disk_space = space (0, &alloc_size);         /* remember disk space left */
#else
alloc_size = 0;
disk_space = MAXLONG;              /* infinite disk space */
#endif

#ifndef OOZ
/* Ooz does no piping */
/* if piping output we open the output device just once */
/* NOTE:  the DOS device "NUL" was causing the system to hang up.
   therefore we create our own.  All functions called must recognize
   -2 as a null output handle and ignore all output to that */
if (null_device) {                  /* -2 means null device */
   this_han = -2;
} else if (pipe)
   this_han = (int) fileno(stdout);    /* standard output */
#endif

while (1) {
   rd_dir (&direntry, zoo_han);
   /* read (zoo_han, (char *) &direntry, sizeof(direntry)); */
   if (direntry.zoo_tag != ZOO_TAG) {
      long currpos, zoolength;

#ifdef OOZ
         prterror ('f', invalid_header, NULL, NULL);
#else
         prterror ('F', invalid_header);

         /* Note:  if header was bad, there's no point trying to find
            how many more bytes aren't processed -- our seek position is
            likely very wrong */
   
         if (!bad_header)
            if ((currpos = tell (zoo_han)) != -1L)
               if (lseek (zoo_han, 0L, 2) != -1)
                  if ((zoolength = tell (zoo_han)) != -1L)
                     printf (cant_process, zoolength - currpos);              
         exit (1);
#endif
   }
   if (direntry.next == 0L) {                /* END OF CHAIN */
      break;                                 /* EXIT on end of chain */
   }
   next_ptr = direntry.next;                 /* ptr to next dir entry */

   whichname = choosefname(&direntry);       /* which filename */
   fixfname(whichname);                      /* fix syntax */
   combine (matchname,
             direntry.dirlen > 0 ? direntry.dirname : "",
            (direntry.namlen > 0) ? direntry.lfname : direntry.fname
           );

   if (todot && *direntry.dirname == *PATH_CH) {
      char tmpstr[PATHSIZE];
      strcpy(tmpstr, direntry.dirname);
      strcpy(direntry.dirname,CUR_DIR);
      strcat(direntry.dirname, tmpstr);
   }

   /* matchname now holds the full pathname for pattern matching */

   if ( ( (needdel && direntry.deleted) ||
            (needdel < 2 && !direntry.deleted)
        ) && needed(matchname)) {
      matched++;           /* update count of files extracted */

      if (direntry.major_ver > MAJOR_EXT_VER ||
         (direntry.major_ver == MAJOR_EXT_VER && 
            direntry.minor_ver > MINOR_EXT_VER)) {

#ifdef OOZ
            prterror ('e', extract_ver, whichname, ".\n");
#else
            prterror ('e', extract_ver, direntry.major_ver, 
                           direntry.minor_ver, whichname);
#endif
            goto loop_again;
      }

      /* 
      If extracting to null device, or if user requested extraction
      of entire path, include any directory name in filename.
      If extraction to current directory requested, and if extfname
      begins with path separator, fix it */

      strcpy (extfname, whichname);
      if ((usepath || null_device) && direntry.dirlen > 0) {
         combine(extfname, direntry.dirname, whichname);
         if (usepath > 1 && !null_device)
            makepath(direntry.dirname);         /* make dir prefix */
      }

      if (tofile) {
         int present = 0;

#ifndef OOZ
#ifndef PORTABLE
         /* 
         if Z format (fast) extraction, extension is created as
         follows:  for no current extension, new extension is "zzz";
         for current extension "a", new extension is "azz";  for 
         current extension "ab", new extension is "azb";  and for
         current extension "abc", new extension is "azc".
         */
           
         if (fast_ext) {
            int length;
            struct path_st path_st;
            parse (&path_st, extfname);         /* split filename */
            strcpy (extfname, path_st.fname);   /* just root filename */
            length = strlen (path_st.ext);
            strcat (extfname, ".");
            if (length == 0)
               strcat (extfname, "zzz");        /* no ext -> .zzz */
            else if (length == 1) {
               strcat (extfname, path_st.ext);
               strcat (extfname, "zz");         /* *.?    -> *.?zz */
            } else { /* length is 2 or 3 */
               if (length == 2)                 /* allow .aa, .ab, etc. */
                  path_st.ext[2] = path_st.ext[1];
               path_st.ext[1] = 'z';
               strcat (extfname, path_st.ext);  /* *.??   -> *.?z? */
            }
         }
#endif   /* ifndef PORTABLE */
#endif   /* ifndef OOZ */


         if (overwrite) {
            this_han = CREATE(extfname, F_RDWR);
         } else {
            if (exists (extfname)) {
               present = 1;
               this_han = -1;
            } else
               this_han = CREATE(extfname, F_RDWR);
         }
         if (this_han == -1) {
            if (present == 1) {      /* if file exists already */
               do {

#ifdef OOZ
                     putstr ("Overwrite "); putstr (extfname);
                     putstr (" (Yes/No/All)? ");
#else
                     printf ("Overwrite %s (Yes/No/All)? ", extfname);
#endif
                  fflush (stdin);
                  fgets (ans, 3, stdin);
                  strlwr (ans);
               } while (*ans != 'y' && *ans != 'n' && *ans != 'a');
   
               /* NOTE:  We use CREAT even if we are trying to overwrite
                  the file, because a data path utility (Search or Dpath)
                  could have fooled us before. */
               if (*ans == 'a')
                  overwrite++;
               if (*ans == 'y' || *ans == 'a') {
                  this_han = CREATE(extfname, F_RDWR);
                  error_message = 1; /* give error message if open fails */
               } else {
                  error_message = 0; /* user said 'n', so no error message */
               }
            } else {
               error_message = 1;   /* Real error -- give error message */
            }
         } /* end if */
      } /* end if */

      if (this_han == -1) {         /* file couldn't be opened */
         if (error_message == 1) {
            unlink (extfname);
#ifdef OOZ
               prterror ('e', could_not_open, extfname, " for output.\n");
#else
               prterror ('e', "Can't open %s for output.\n", extfname);
#endif

#ifndef PORTABLE
            /* if error was due to full disk, abort */
            if (space(0, &alloc_size) < alloc_size)
#endif
               prterror ('f', disk_full, NULL, NULL);

         }
      } else if (lseek (zoo_han, direntry.offset, 0) == -1L) {
         prterror ('e', "Could not seek to file data.\n");
         close_han (this_han);
      } else {
#ifndef PORTABLE
         /* check Dos's free disk space if we seem to be running low 
            (within 1 cluster of being full) */
         if (tofile && disk_space < direntry.org_size + alloc_size) {
            disk_space = space (0, &alloc_size);
            if (disk_space < alloc_size) {
               close_han (this_han);
               unlink (extfname);
               prterror ('f', disk_full, NULL, NULL);
            }              
         }
#endif
         if (tofile && disk_space < direntry.org_size) {
#ifdef PORTABLE
            ;
#else
#ifdef OOZ
               prterror ('e', no_space, extfname, ".\n");
#else
               prterror ('e', no_space, extfname);
#endif

            unlink (extfname);               /* delete any created file */
#endif   /* portable */

         } else { 

#ifndef OOZ
#ifndef PORTABLE
            if (fast_ext) {            /* fast ext -> create header */
               void make_tnh();
               struct tiny_header tiny_header;
               make_tnh(&tiny_header, &direntry);
               write (this_han, (char *) &tiny_header, sizeof (tiny_header));

               if (direntry.cmt_size != 0) { /* copy comment */
                  long save_pos;
                  save_pos = tell (zoo_han);
                  lseek (zoo_han, direntry.comment, 0);
                  getfile (zoo_han, this_han, 
                          (long) direntry.cmt_size, 0);
                  lseek (zoo_han, save_pos, 0);
               }
            }
#endif /* ifndef PORTABLE */
#endif /* ifndef OOZ */

            crccode = 0;      /* Initialize CRC before extraction */
#ifdef OOZ
               putstr (extfname); 
               {
                  register int i;
                  for (i = strlen(direntry.fname); i < 13; i++)
                     putstr (" ");
                  putstr ("-- ");
               }
#else
               if (!pipe) {
#ifdef PORTABLE
                  prterror ('m', "%-14s -- ", extfname);
#else
                  if (fast_ext)
                     prterror ('m', "%-12s ==> %-12s -- ", 
                        direntry.fname, extfname);
                  else
                     prterror ('m', "%-12s -- ", extfname);
#ifdef COMMENT
                  prterror ('m', 
                        fast_ext ? "%-12s ==> %-12s -- " : "%-12s -- ",
                        direntry.fname, extfname);
#endif /* COMMENT */
#endif /* PORTABLE */

               } else {            /* must be pipe */
                  prterror ('M', "\n\n********\n%s\n********\n", direntry.fname);
                  fflush (stdout);     /* make 'm appear right here */

#ifdef COMMENT
#ifdef SETMODE
                  MODE_BIN(this_han);           /* make std output binary so
                                                   ^Z won't cause error */
#endif
#endif
               }
#endif /* OOZ */

#ifndef NOSIGNAL
#ifndef OOZ
            if (tofile)
#endif /* not OOZ */
               {
                  oldsignal = signal (SIGINT, SIG_IGN);
                  if (oldsignal != SIG_IGN) 
                     signal (SIGINT, ctrl_c); /* Trap ^C & erase partial file */
               }
#endif /* not NOSIGNAL */

            if (direntry.packing_method == 0)
               /* 4th param 1 means CRC update */
               status = getfile (zoo_han, this_han, direntry.size_now, 1);

#ifndef OOZ
            else if (fast_ext)
               /* 4th param 0 means no CRC update */
               status = getfile (zoo_han, this_han, direntry.size_now, 0);
#endif


            else if (direntry.packing_method == 1)
               status = lzd (zoo_han, this_han); /* uncompress */
            else {
#ifdef OOZ
               prterror ('e', "File ", whichname,
                  ":  impossible packing method.\n", "");
#else
               prterror ('e', "File %s:  impossible packing method.\n",
                  whichname);
#endif
                  unlink(extfname);
                  goto loop_again;
            }


#ifndef NOSIGNAL
#ifndef OOZ
            if (tofile)
#endif /* not OOZ */
               signal (SIGINT, oldsignal);
#endif /* not NOSIGNAL */

#ifdef COMMENT
#ifndef OOZ
#ifdef SETMODE
            if (pipe)
               MODE_TEXT(this_han);          /* restore text mode */
#endif
#endif
#endif
   
#ifndef OOZ
            if (tofile) {
#endif
               /* set date/time of file being extracted */
#ifdef NIXTIME
               close_han (this_han);
               setutime (direntry.fname, direntry.date, direntry.time);
#else
               settime (this_han, direntry.date, direntry.time);
               close_han (this_han);
#endif
#ifndef OOZ
            }
#endif
            if (status != 0) {
               if (tofile)
                  unlink (extfname);
               if (status == 1) {
                  memerr();
               /* To avoid spurious errors due to ^Z being sent to screen,
                  we don't check for I/O error if output was piped */
               } else if (!pipe && (status == 2 || status == 3)) {
#ifdef OOZ
                     prterror ('e', no_space, direntry.fname, ".\n");
#else
                     prterror ('e', no_space, direntry.fname);
#endif
               }
            } else {
               /* file extracted, so update disk space.  */
               /* we subtract the original size of the file, rounded
                  UP to the nearest multiple of the disk allocation
                  size. */
#ifndef PORTABLE
               {
                  unsigned long temp;
                  temp = (direntry.org_size + alloc_size) / alloc_size;
                  disk_space -= temp * alloc_size;
               }
#endif

#ifdef OOZ
               if (direntry.file_crc != crccode)
                  putstr ("extracted  \007WARNING:  Bad CRC.\n");
               else
                  putstr ("extracted\n");
#else
               if (!fast_ext && direntry.file_crc != crccode) {
                  badcrc_count++;
                  if (!pipe) {
                     if (!null_device)
                        prterror ('M', "extracted   ");
                     prterror ('w', bad_crc, direntry.fname);
                  }
                  else {   /* duplicate to standard error */
                     static char stars[] = "\n******\n";
                     putstr (stars);
                     prterror ('w', bad_crc, direntry.fname);
                     putstr (stars);
                     fprintf (stderr, "WARNING:  ");
                     fprintf (stderr, bad_crc, direntry.fname);
                  }
               } else
                  if (!pipe)
                     prterror ('M', null_device ? "OK\n" : "extracted\n");
#endif

            } /* end if */
         } /* end if */
      } /* end if */
   } /* end if */

loop_again:
   lseek (zoo_han, next_ptr, 0); /* ..seek to next dir entry */
} /* end while */

close (zoo_han);
if (!matched)
   putstr (no_match);

#ifndef OOZ
if (badcrc_count) {
   prterror ('w', "%d File(s) with bad CRC.\n", badcrc_count);
   exit (1);
} else if (null_device)
   prterror ('m', "Archive seems OK.\n");
#endif

} /* end zooext */

/* close_han() */
/* closes a handle if and only if we aren't sending output to 
   a pipe or to the null device */

void close_han (handle)
int handle;
{
   if (tofile)
      close (handle);
}

/* Ctrl_c() is called if ^C is hit while a file is being extracted.
   It closes the files, deletes it, and exits. */
int ctrl_c()
{
#ifndef NOSIGNAL
   signal (SIGINT, SIG_IGN);     /* ignore any more */
#endif
   close (this_han);
   unlink (extfname);
   exit (1);
}

/* make_tnh copies creates a tiny_header */
void make_tnh (tiny_header, direntry)
struct tiny_header *tiny_header;
struct direntry *direntry;
{
   tiny_header->tinytag = TINYTAG;
   tiny_header->type = 1;
   tiny_header->packing_method = direntry->packing_method;
   tiny_header->date = direntry->date;
   tiny_header->time = direntry->time;
   tiny_header->file_crc = direntry->file_crc;
   tiny_header->org_size = direntry->org_size;
   tiny_header->size_now = direntry->size_now;
   tiny_header->major_ver = direntry->major_ver;
   tiny_header->minor_ver = direntry->minor_ver;
   tiny_header->cmt_size = direntry->cmt_size;
   strcpy (tiny_header->fname, direntry->fname);
} 
