/*
 *  Shar puts readable text files together in a package
 *  from which they are easy to extract.
 *
 *  v 860712 D. Wecker for ULTRIX and the AMIGA
 *  - stripped down.. does patterns but no directories
 *  - added a -u (unshar) switch
 *  v 860715 DBW
 *  - fixed bug with leading white space in unshar command recognition
 *  - fixed bug in getting sh paramaters with quotes
 *
 *  13.10.86 started work on Atari ST Version - msd
 *
 */

#include <stdio.h>

#ifdef        AMIGA
#include <exec/types.h>
extern char *getenv(),*scdir(),*malloc(),*index();
#endif

#ifdef        ULTRIX
#include <sys/types.h>
extern char *getenv(),*scdir(),*malloc(),*index();
#endif

#ifdef        VMS
#include <types.h>
extern char *getenv(),*scdir(),*malloc();
#endif

#ifdef        ATARI
#include <osbind.h>

#ifdef        MEGAMAX
#define BUFSIZ        _BUFSIZE
#define WORD  int
#endif

#ifdef        LATTICE
/* Don't use the library of version 3.03 or you'll get bombs when opening
 * the 21st file after 20 files have been opened AND CLOSED.
 */
#define WORD  short
#define index strchr
#define rindex        strrchr
#endif

#ifdef        DRI
/* DON'T use wildcards (* or ?) on the command line of a DRI-C program.
 * The startup code tries to expand the wildcards in CP/M-68K fashion.
 * That's a guaranteed way to get bombs.
 */
#define WORD  short
extern char   *fopen();
#endif

extern char   *malloc(),*index(),*rindex();

typedef struct {
    char reserved[21];        /* bytes 0-20   */
    char attrib;      /* byte  21     */
    WORD time;                /* bytes 22-23  */
    WORD date;                /* bytes 24-25  */
    long size;                /* bytes 26-29  */
    char fname[14];   /* bytes 30-43  */
} DTA;
#endif


#define BADCH ((int)'?')
#define EMSG  ""
#define tell(s)       {fputs(*nargv,stderr);fputs((s),stderr); \
              fputc(optopt,stderr);fputc('\n',stderr);return(BADCH);}
#define rescanopts()  (optind = 1)

int   optind = 1,     /* index into parent argv vector */
      optopt;         /* character checked for validity */
long  fsize;          /* length of file */
char  *optarg;        /* argument associated with option */
char  *sav[100];      /* saved file names */
int   savind;         /* save index */
FILE  *OutPut;

/* OPTIONS */
int   Verbose = 0;            /* provide append/extract feedback */
int   Basename = 0;           /* extract into basenames */
int   Count = 0;              /* count characters to check transfer */
char  *Delim = "SHAR_EOF";    /* put after each file */
char  Filter[100] = "cat";    /* used to extract archived files */
char  *Prefix = NULL;         /* line prefix to avoid funny chars */
int   UnShar = 0;             /* do we unshar an input file? */

#define SED   "sed 's/^%s//'" /* used to remove prefix from lines */
#define USAGE "[-u archive] [[-a] [-p prefix] [-d delim] [-bcv] [-o archive] files]"
#define OPTSTRING "U:u:AaP:p:D:d:BbCcVvO:o:"

#ifdef        VMS
char *index(s,c)
char  *s;
char  c;
{
    while (*s != 0 && *s != c) s++;
    if (*s == 0 && *s != c) s = 0;
    return(s);    
}
#endif

#ifdef        ATARI
time(clock)
long *clock;
{
    *clock = Gettime();
}

char *ctime(clock)
long *clock;
{
    char datetime[50];
    static char *month[] =
    {   "Jan", "Feb", "Mar", "Apr", "Mai", "Jun",
      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"    };
    sprintf(datetime, "%s %d, %d -- %d:%02d:%02d\n",
              month[((*clock >> 21) & 0xf) - 1],
              (int) (*clock >> 16) & 0x1f,
              (int) ((*clock >> 25) & 0x7f) + 1980,
              (int) (*clock >> 11) & 0x1f,
              (int) (*clock >> 5) & 0x3f,
              (int) (*clock & 0x1f) * 2);
    return &datetime;
}
#endif

int header(ppchFiles)
char  *ppchFiles[];
{
    extern char *ctime();
    auto long clock;
    register char **ppchList;
#ifdef ATARI
    char *pchOrg = "using that wonderful machine :-";
    char *pchName = "a friendly ATARI ST hacker";
#else
    register char *pchOrg = getenv("ORGANIZATION");
    register char *pchName = getenv("NAME");
#endif

    fputs("# This is a shell archive.\n", OutPut);
    fputs("# Remove everything above and including the cut line.\n", OutPut);
    fputs("# Then run the rest of the file through sh.\n", OutPut);
    fputs("#----cut here-----cut here-----cut here-----cut here----#\n", OutPut);
    fputs("#!/bin/sh\n", OutPut);
    fputs("# shar:\tShell Archiver\n", OutPut);
    fputs("# Run the following text with /bin/sh to create:\n", OutPut);
    for (ppchList = ppchFiles; *ppchList; ++ppchList)
      fprintf(OutPut, "#\t%s\n", *ppchList);
    time(& clock);
    fprintf(OutPut, "#\n# This archive created:  %s", ctime(&clock));
    if (pchName)
      fprintf(OutPut, "# By:\t%s  (%s)\n#\n", pchName, 
              pchOrg ? pchOrg : "Dave Wecker Midnight Hacks");
    return(0);
}

int archive(input, output)
char  *input, *output;
{
    auto char line[BUFSIZ];
    register FILE *ioptr;
    register char *ctrl_z;

    if (ioptr = fopen(input, "r")) {
      fprintf(OutPut, "%s << \\%s > %s\n", Filter, Delim, output);
      while(fgets(line, BUFSIZ, ioptr)) {
#ifdef ATARI
          /* My TVX usually appends ^Z to a file. */
          if (ctrl_z = index(line, '\032')) {
              *ctrl_z++ = '\n';
              *ctrl_z = '\0';
          }
#endif
          if (Prefix) fputs(Prefix, OutPut);
          fputs(line, OutPut);
          if (Count) fsize += strlen(line);
      }
      fputs(Delim, OutPut);
      fputc('\n', OutPut);
      fclose(ioptr);
      return(0);
    } 
    else {
      fprintf(stderr, "shar: Can't open '%s'\n", input);
      return(1);
    }
}

shar(file)
char  *file;
{
    register char *basefile = file;
    if (!strcmp(file, "."))
      return;
    fsize = 0;
    if (Basename) {
      while(*basefile)
          basefile++;         /* go to end of name */
      while (basefile > file &&
#ifdef        ATARI
              *(basefile-1) != '\\' && *(basefile-1) != ':')
#else
              *(basefile-1) != '/')
#endif
          basefile--;
    }
    if (Verbose) fprintf(OutPut, "echo shar: extracting %s\n", basefile);
    if (archive(file, basefile)) exit(66);
    if (Count) {
      fprintf(OutPut, "if test %ld -ne \"`wc -c %s`\"\n",fsize,basefile);
      fprintf(OutPut, "then\necho shar: error transmitting %s ",basefile);
      fprintf(OutPut, "'(should have been %ld characters)'\nfi\n",fsize);
    }
}

int main(argc, argv)
int   argc;
char  **argv;    
{
    auto char *ppchFiles[256];
    register int  C;
    register char **ppchList = ppchFiles;
    register int errflg = 0;

    OutPut = stdout;
    while(EOF != (C = getopt(argc, argv, OPTSTRING))) {
      switch(tolower(C)) {
      case 'v': 
          Verbose++; 
          break;
      case 'c': 
          Count++; 
          break;
      case 'b': 
          Basename++; 
          break;
      case 'd': 
          Delim = optarg; 
          break;
      case 'a': /* all the options */
          optarg = "XX";
          Verbose++;
          Count++;
          Basename++;
          /* fall through to set prefix */
      case 'p': 
          sprintf(Filter, SED, Prefix = optarg); 
          break;
      case 'o':
          if (!(OutPut = fopen(optarg, "w"))) {
              fprintf(stderr, "shar: Can't open '%s'\n", optarg);
              exit(1);
          }
          break;
      case 'u':
          UnShar++;
          dounshar(optarg);
          break;
      default: 
          errflg++;
      }
    }

    if (UnShar) exit(0);

    C = getarg(argc, argv);
    if (errflg || EOF == C) {
      if (EOF == C)
          fprintf(stderr, "shar: No input files\n");
      fprintf(stderr, "usage: shar %s\n", USAGE);
      exit(1);
    }

    savind = 0;
    do {
      if (getpat(optarg)) exit(2);
    } 
    while (EOF != (C = getarg(argc, argv)));

    sav[savind] = 0;
    header(sav);
    for (ppchList = sav; *ppchList; ++ppchList) shar(*ppchList);
    fputs("# End of shell archive\n", OutPut);
    fputs("exit 0\n", OutPut);
    exit(0);
}

int getpat (pattern)
char  *pattern;
{
#ifdef ATARI
/* include normal, read-only, hidden, system and updated files */
#define       all_files 0x27

    DTA  dta, *old_dta;
    char lead_in[256], path[256];
    
    old_dta = (DTA *) Fgetdta();
    Fsetdta(&dta);
    if (fix_path(pattern, &lead_in, &path))
    {
      if (!Fsfirst(&path, all_files))
      {
          do {
              sav[savind] = malloc(strlen(lead_in)+strlen(dta.fname)+1);
              strcpy(sav[savind], lead_in);
              strcat(sav[savind++], dta.fname);
          } while (!Fsnext());
      }
    }
    Fsetdta(old_dta);
#else
    register char *ptr;

#ifdef        AMIGA
    while (ptr = scdir(pattern)) {
#else
    ptr = pattern;
    {
#endif
      sav[savind] = malloc(strlen(ptr)+1);
      strcpy(sav[savind++],ptr);
      if (access(ptr,4)) {
          fprintf(stderr, "No read access for file: %s\n",ptr);
          return(-1);
      }
    }
#endif
    return(0);
}

/*
 * get option letter from argument vector
 */
int getopt(nargc, nargv, ostr)
int   nargc;
char  **nargv, *ostr;
{
    register char *oli;               /* option letter list index */
    static char *place = EMSG;        /* option letter processing */

    if(!*place) {             /* update scanning pointer */
      if(optind >= nargc || *(place = nargv[optind]) != '-' || !*++place)
          return(EOF);
      if (*place == '-') {    /* found "--" */
          ++optind;
          return EOF;
      }
    }                         /* option letter okay? */
    if ((optopt = (int)*place++) == (int)':' || !(oli = index(ostr,optopt))) {
      if(!*place) ++optind;
      tell(": illegal option -- ");
    }
    if (*++oli != ':') {      /* don't need argument */
      optarg = NULL;
      if (!*place)
          ++optind;
    } 
    else {                    /* need an argument */
      if (*place) {           /* no white space */
          optarg = place;
      } 
      else if (nargc <= ++optind) {   /* no arg */
          place = EMSG;
          tell(": option requires an argument -- ");
      } 
      else {
          optarg = nargv[optind];     /* white space */
      }
      place = EMSG;
      ++optind;
    }
    return optopt;            /* dump back option letter */
}

int getarg(nargc, nargv)
int   nargc;
char  **nargv;
{
    if (nargc <= optind) {
      optarg = (char *) 0;
      return EOF;
    } 
    else {
      optarg = nargv[optind++];
      return 0;
    }
}

dounshar(ArcNam)
char  *ArcNam;
{
    register int i,j,strt;
    register FILE *inptr,*outptr;
    auto char line[BUFSIZ];
    int DirNum = -1;
    int Prefix = 0;
    char Dirs[5][40],FilNam[128],Delim[40],ScrStr[128];
    char *ptr;

    if (!(inptr = fopen(ArcNam,"r"))) {
      fprintf(stderr,"shar: Can't open archive '%s'\n", ArcNam);
      return;
    }
    while (fgets(line,BUFSIZ,inptr)) {
      for (strt = 0; line[strt] == ' ' || line[strt] == '\t'; strt++) ;
      if (strncmp(&line[strt],"sed ",4) == 0) {
          Prefix = 0;
          if (!(ptr = index(&line[strt],'/'))) goto getfil;
          if (*++ptr == '^')
              if (*++ptr == '@') goto getfil;
          while (*ptr++ != '/') Prefix++;
          goto getfil;
      }
      else if (strncmp(&line[strt],"cat ",4) == 0) {
          Prefix = 0;
          ;
getfil:

#ifdef        VMS
          strcpy(FilNam,"[");
#else
          FilNam[0] = 0;
#endif

          for (i = 0; i <= DirNum; i++) {

#ifdef        VMS
              strcat(FilNam,".");
              strcat(FilNam,Dirs[i]);
#else
              strcat(FilNam,Dirs[i]);
              strcat(FilNam,"/");
#endif

          }


#ifdef        VMS
          strcat(FilNam,"]");
#endif

          getshpar(line,">",ScrStr);
          strcat(FilNam,ScrStr);
          getshpar(line,"<<",Delim);
          fprintf(stderr,"Creating %s ...",FilNam);
          fflush(stderr);
          outptr = fopen(FilNam,"w");
          while (fgets(line,BUFSIZ,inptr)) {
              if (strncmp(line,Delim,strlen(Delim)) == 0) break;
              if (outptr)
                  if (strlen(line) <= Prefix)
                      fputs(line,outptr);
                  else
                      fputs(&line[Prefix],outptr);
          }
          if (outptr) {
              fclose(outptr);
              fprintf(stderr,"...done\n");
          }
          else fprintf(stderr,"...error in creating file\n");
      }
      else if (strncmp(&line[strt],"mkdir ",6) == 0) {
          fprintf(stderr,"Need to make directory:\t%s\n",&line[strt+6]);
      }
      else if (strncmp(&line[strt],"chdir ",6) == 0) {
          if (line[strt+6] == '.' && line[strt+7] == '.') DirNum--;
          else strcpy(Dirs[++DirNum],&line[strt+6]);
          if (DirNum < -1) DirNum = -1;
      }
      else if (strncmp(&line[strt],"cd ",3) == 0) {
          if (line[strt+3] == '.' && line[strt+4] == '.') DirNum--;
          else strcpy(Dirs[++DirNum],&line[strt+3]);
          if (DirNum < -1) DirNum = -1;
      }
    }
    fclose(inptr);
}

getshpar(line,sea,par)
char *line,*sea,*par;
{
    register char *scr1,*scr2;

    while (*line) {
      scr1 = line;
      scr2 = sea;
      while (*scr1 && *scr2 && *scr1 == *scr2) { 
          scr1++; 
          scr2++; 
      }
      if (*scr2 == 0) {
          if (*scr1 == 0) { 
              *par = 0; 
              return; 
          }
          while ( *scr1 == ' ' || *scr1 == '\t' ||
              *scr1 == '\\' || *scr1 == '\'' || *scr1 == '"') {
              if (*scr1 == '\'' || *scr1 == '"') {
                  scr2 = scr1++;
                  break;
              }
              scr1++;
          }
          if (*scr2 != 0) {
              while (*scr1 != *scr2 && *scr1 != 0) *par++ = *scr1++;
              *par = 0;
              return;
          }
          while ( *scr1 != 0 && *scr1 != ' ' && *scr1 != '\t' &&
              *scr1 != '\n' && *scr1 != '\r') {
              if (*scr1 != '\\') *par++ = *scr1++;
              else            scr1++;
          }
          *par = 0;
          return;
      }
      line++;
    }
    *par = 0;
}

#ifdef ATARI
int fix_path (pattern, lead_in, path)
register char *pattern, *lead_in, *path;
{
    register int  pat_len, state, attrib;
    register char *ptr;

    if ((!(pat_len = strlen(pattern)))                ||
      ((pat_len == 2) && (pattern[1] == ':')) ||
      (pattern[pat_len-1] == '\\')            )
      state = 1;
    else
    { /* get attrib of path */
      attrib = Fattrib(pattern, 0, 0);
      if (attrib == -33 /* file not found */)
          state = 3;
          /* Very strange feature (??? - bug ?) of GEMDOS:
           * This happens, when looking at non existing files and
           * *DIRECTORIES*. I've expected Fattrib returning 0x10,
           * but directories don't seem to be files to GEMDOS.
           * Assume we're looking at a directory.
           */
      else if (attrib < 0 /* other errors */)
          state = 0;
      else if (!(attrib & 0x10) /* not a directory */)
          state = 2;
      else if ((attrib & 0x10) /* is a directory */)
          state = 3;
      else /* catch unexpected results */
          state = 0;
    }
    switch (state)
    {
    case 0:
      *lead_in = '\0';
      *path = '\0';
      break;
    case 1:
      strcpy(lead_in, pattern);
      strcpy(path, pattern);
      strcat(path, "*.*");
      break;
    case 2:
      strcpy(path, pattern);
      if ((ptr = rindex(pattern, '\\')) ||
          (ptr = rindex(pattern, ':'))  )
      {
          strcpy(lead_in, pattern);
          lead_in[(ptr-pattern)+1] = '\0';
      }
      else
          *lead_in = '\0';
      break;
    case 3:
      strcpy(lead_in, pattern);
      strcat(lead_in, "\\");
      strcpy(path, pattern);
      strcat(path, "\\*.*");
      break;
    default:
      return 0;
    }
    return (state != 0);
}
#endif

