/*
* Lar - LU format library file maintainer
* by Stephen C. Hemminger
*      linus!sch       or      sch@Mitre-Bedford
*
*  Usage: lar key library [files] ...
*
*  Key functions are:
*      u - Update, add files to library
*      t - Table of contents
*      e - Extract files from library
*      p - Print files in library
*      d - Delete files in library
*      r - Reorginize library
*  Other keys:
*      v - Verbose
*
*  This program is public domain software, no warranty intended or
*  implied.
*
*  DESCRPTION
*     Lar is a Unix program to manipulate CP/M LU format libraries.
*     The original CP/M library program LU is the product
*     of Gary P. Novosielski. The primary use of lar is to combine several
*     files together for upload/download to a personal computer.
*
*  PORTABILITY
*     The code is modeled after the Software tools archive program,
*     and is setup for Version 7 Unix.  It does not make any assumptions
*     about byte ordering, explict and's and shift's are used.
*     If you have a dumber C compiler, you may have to recode new features
*     like structure assignment, typedef's and enumerated types.
*
*  BUGS/MISFEATURES
*     The biggest problem is text files, the programs tries to detect
*     text files vs. binaries by checking for non-Ascii (8th bit set) chars.
*     If the file is text then it will throw away Control-Z chars which
*     CP/M puts on the end.  All files in library are padded with Control-Z
*     at the end to the CP/M sector size if necessary.
*
*     No effort is made to handle the difference between CP/M and Unix
*     end of line chars.  CP/M uses Cr/Lf and Unix just uses Lf.
*     The solution is just to use the Unix command sed when necessary.
*
*  * Unix is a trademark of Bell Labs.
*  ** CP/M is a trademark of Digital Research.
*/

#include <stdio.h>
#include <ctype.h>

#define ACTIVE  00
#define UNUSED  0xff
#define DELETED 0xfe
#define CTRLZ   0x1a

#define MAXFILES 256
#define SECTOR   128
#define DSIZE   ( sizeof(struct ludir) )
#define SLOTS_SEC (SECTOR/DSIZE)
#define equal(s1, s2) ( strcmp(s1,s2) == 0 )
/* if you don't have void type just define as blank */
#define VOID    (void)

/* if no enum's then define false as 0 and true as 1 and bool as int */
typedef enum {false=0, true=1} bool;

/* Globals */
char   *fname[MAXFILES];
bool ftouched[MAXFILES];

typedef struct {
   unsigned char   lobyte;
   unsigned char   hibyte;
} word;

/* convert word to int */
#define wtoi(w) ( (w.hibyte<<8) + w.lobyte)
#define itow(dst,src)   dst.hibyte = (src & 0xff00) >> 8;\
                               dst.lobyte = src & 0xff;

struct ludir {                  /* Internal library ldir structure */
   unsigned char   l_stat;     /*  status of file */
   char    l_name[8];          /*  name */
   char    l_ext[3];           /*  extension */
   word    l_off;              /*  offset in library */
   word    l_len;              /*  lengty of file */
   char    l_fill[16];         /*  pad to 32 bytes */
} ldir[MAXFILES];

int     errcnt, nfiles, nslots;
bool    verbose = false;
char    *cmdname;

char   *getname(), *sprintf();
int     update(), reorg(), table(), extract(), print(), delete();

main (argc, argv)
int     argc;
char  **argv;
{
   register char *flagp;
   char   *aname;                      /* name of library file */
   int    (*function)() = NULL;        /* function to do on library */
/* set the function to be performed, but detect conflicts */
#define setfunc(val)    if(function != NULL) conflict(); else function = val

   cmdname = argv[0];
   if (argc < 3)
       help ();

   aname = argv[2];
   filenames (argc, argv);

   for(flagp = argv[1]; *flagp; flagp++)
       switch (*flagp) {
       case 'u':
           setfunc(update);
           break;
       case 't':
           setfunc(table);
           break;
       case 'e':
           setfunc(extract);
           break;
       case 'p':
           setfunc(print);
           break;
       case 'd':
           setfunc(delete);
           break;
       case 'r':
           setfunc(reorg);
           break;
       case 'v':
           verbose = true;
           break;
       default:
           help ();
   }

   if(function == NULL) {
       fprintf(stderr,"No function key letter specified\n");
       help();
   }

   (*function)(aname);
}

/* print error message and exit */
help () {
   fprintf (stderr, "Usage: %s {utepdr}[v] library [files] ...\n", cmdname);
   fprintf (stderr, "Functions are:\n\tu - Update, add files to library\n");
   fprintf (stderr, "\tt - Table of contents\n");
   fprintf (stderr, "\te - Extract files from library\n");
   fprintf (stderr, "\tp - Print files in library\n");
   fprintf (stderr, "\td - Delete files in library\n");
   fprintf (stderr, "\tr - Reorginize library\n");

   fprintf (stderr, "Flags are:\n\tv - Verbose\n");
   exit (1);
}

conflict() {
  fprintf(stderr,"Conficting keys\n");
  help();
}

error (str)
char   *str;
{
   fprintf (stderr, "%s: %s\n", cmdname, str);
   exit (1);
}

cant (name)
char   *name;
{
   extern int  errno;
   extern char *sys_errlist[];

   fprintf (stderr, "%s: %s\n", name, sys_errlist[errno]);
   exit (1);
}

/* Get file names, check for dups, and initialize */
filenames (ac, av)
char  **av;
{
   register int    i, j;

   errcnt = 0;
   for (i = 0; i < ac - 3; i++) {
       fname[i] = av[i + 3];
       ftouched[i] = false;
       if (i == MAXFILES)
           error ("Too many file names.");
   }
   fname[i] = NULL;
   nfiles = i;
   for (i = 0; i < nfiles; i++)
       for (j = i + 1; j < nfiles; j++)
           if (equal (fname[i], fname[j])) {
               fprintf (stderr, "%s", fname[i]);
               error (": duplicate file name");
           }
}

table (lib)
char   *lib;
{
   FILE   *lfd;
   register int    i, total;
   int active = 0, unused = 0, deleted = 0;
   char *uname;

   if ((lfd = fopen (lib, "r")) == NULL)
       cant (lib);

   getdir (lfd);
   total = wtoi(ldir[0].l_len);
   if(verbose) {
       printf("Name          Index Length\n");
       printf("Directory           %4d\n", total);
   }

   for (i = 1; i < nslots; i++)
       switch(ldir[i].l_stat) {
       case ACTIVE:
               active++;
               uname = getname(ldir[i].l_name, ldir[i].l_ext);
               if (filarg (uname))
                   if(verbose)
                       printf ("%-12s   %4d %4d\n", uname,
                           wtoi (ldir[i].l_off), wtoi (ldir[i].l_len));
                   else
                       printf ("%s\n", uname);
               total += wtoi(ldir[i].l_len);
               break;
       case UNUSED:
               unused++;
               break;
       default:
               deleted++;
       }
   if(verbose) {
       printf("--------------------------\n");
       printf("Total sectors       %4d\n", total);
       printf("\nLibrary %s has %d slots, %d deleted %d active, %d unused\n",
               lib, nslots, deleted, active, unused);
   }

   VOID fclose (lfd);
   not_found ();
}

getdir (f)
FILE *f;
{

   rewind(f);

   if (fread ((char *) & ldir[0], DSIZE, 1, f) != 1)
       error ("No directory\n");

   nslots = wtoi (ldir[0].l_len) * SLOTS_SEC;

   if (fread ((char *) & ldir[1], DSIZE, nslots, f) != nslots)
       error ("Can't read directory - is it a library?");
}

putdir (f)
FILE *f;
{

   rewind(f);
   if (fwrite ((char *) ldir, DSIZE, nslots, f) != nslots)
       error ("Can't write directory - library may be botched");
}

initdir (f)
FILE *f;
{
   register int    i;
   int     numsecs;
   char    line[80];
   static struct ludir blankentry = {
       UNUSED,
       { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' },
       { ' ', ' ', ' ' },
   };

   for (;;) {
       printf ("Number of slots to allocate: ");
       if (fgets (line, 80, stdin) == NULL)
           error ("Eof when reading input");
       nslots = atoi (line);
       if (nslots < 1)
           printf ("Must have at least one!\n");
       else if (nslots > MAXFILES)
           printf ("Too many slots\n");
       else
           break;
   }

   numsecs = nslots / SLOTS_SEC;
   nslots = numsecs * SLOTS_SEC;

   for (i = 0; i < nslots; i++)
       ldir[i] = blankentry;
   ldir[0].l_stat = ACTIVE;
   itow (ldir[0].l_len, numsecs);

   putdir (f);
}

/* convert nm.ex to a Unix style string */
char   *getname (nm, ex)
char   *nm, *ex;
{
   static char namebuf[14];
   register char  *cp, *dp;

   for (cp = namebuf, dp = nm; *dp != ' ' && dp != &nm[8];)
       *cp++ = isupper (*dp) ? tolower (*dp++) : *dp++;
   *cp++ = '.';

   for (dp = ex; *dp != ' ' && dp != &ex[3];)
       *cp++ = isupper (*dp) ? tolower (*dp++) : *dp++;

   *cp = '\0';
   return namebuf;
}

putname (cpmname, unixname)
char   *cpmname, *unixname;
{
   register char  *p1, *p2;

   for (p1 = unixname, p2 = cpmname; *p1; p1++, p2++) {
       while (*p1 == '.') {
           p2 = cpmname + 8;
           p1++;
       }
       if (p2 - cpmname < 11)
           *p2 = islower(*p1) ? toupper(*p1) : *p1;
       else {
           fprintf (stderr, "%s: name truncated\n", unixname);
           break;
       }
   }
   while (p2 - cpmname < 11)
       *p2++ = ' ';
}

/* filarg - check if name matches argument list */
filarg (name)
char   *name;
{
   register int    i;

   if (nfiles <= 0)
       return 1;

   for (i = 0; i < nfiles; i++)
       if (equal (name, fname[i])) {
           ftouched[i] = true;
           return 1;
       }

   return 0;
}

not_found () {
   register int    i;

   for (i = 0; i < nfiles; i++)
       if (!ftouched[i]) {
           fprintf (stderr, "%s: not in library.\n", fname[i]);
           errcnt++;
       }
}


extract(name)
char *name;
{
       getfiles(name, false);
}

print(name)
char *name;
{
       getfiles(name, true);
}

getfiles (name, pflag)
char   *name;
bool    pflag;
{
   FILE *lfd, *ofd;
   register int    i;
   char   *unixname;

   if ((lfd = fopen (name, "r"))  == NULL)
       cant (name);

   ofd = pflag ? stdout : NULL;
   getdir (lfd);

   for (i = 1; i < nslots; i++) {
       if(ldir[i].l_stat != ACTIVE)
               continue;
       unixname = getname (ldir[i].l_name, ldir[i].l_ext);
       if (!filarg (unixname))
           continue;
       fprintf(stderr,"%s", unixname);
       if (ofd != stdout)
           ofd = fopen (unixname, "w");
       if (ofd == NULL) {
           fprintf (stderr, "  - can't create");
           errcnt++;
       }
       else {
           VOID fseek (lfd, (long) wtoi (ldir[i].l_off) * SECTOR, 0);
           acopy (lfd, ofd, wtoi (ldir[i].l_len));
           if (ofd != stdout)
               VOID fclose (ofd);
       }
       putc('\n', stderr);
   }
   VOID fclose (lfd);
   not_found ();
}

acopy (fdi, fdo, nsecs)
FILE *fdi, *fdo;
register unsigned int nsecs;
{
   register int    i, c;
   int     textfile = 1;

   while( nsecs-- != 0)
       for(i=0; i<SECTOR; i++) {
               c = getc(fdi);
               if( feof(fdi) )
                       error("Premature EOF\n");
               if( ferror(fdi) )
                   error ("Can't read");
               if( !isascii(c) )
                   textfile = 0;
               if( nsecs != 0 || !textfile || c != CTRLZ) {
                       putc(c, fdo);
                       if ( ferror(fdo) )
                           error ("write error");
               }
        }
}

update (name)
char   *name;
{
   FILE *lfd;
   register int    i;

   if ((lfd = fopen (name, "r+")) == NULL) {
       if ((lfd = fopen (name, "w+")) == NULL)
           cant (name);
       initdir (lfd);
   }
   else
       getdir (lfd);           /* read old directory */

   if(verbose)
           fprintf (stderr,"Updating files:\n");
   for (i = 0; i < nfiles; i++)
       addfil (fname[i], lfd);
   if (errcnt == 0)
       putdir (lfd);
   else
       fprintf (stderr, "fatal errors - library not changed\n");
   VOID fclose (lfd);
}

addfil (name, lfd)
char   *name;
FILE *lfd;
{
   FILE        *ifd;
   register int secoffs, numsecs;
   register int i;

   if ((ifd = fopen (name, "r")) == NULL) {
       fprintf (stderr, "%s: can't find to add\n",name);
       errcnt++;
       return;
   }
   if(verbose)
       fprintf(stderr, "%s\n", name);
   for (i = 0; i < nslots; i++) {
       if (equal( getname (ldir[i].l_name, ldir[i].l_ext), name) ) /* update */
           break;
       if (ldir[i].l_stat != ACTIVE)
               break;
   }
   if (i >= nslots) {
       fprintf (stderr, "%s: can't add library is full\n",name);
       errcnt++;
       return;
   }

   ldir[i].l_stat = ACTIVE;
   putname (ldir[i].l_name, name);
   VOID fseek(lfd, 0L, 2);             /* append to end */
   secoffs = ftell(lfd) / SECTOR;

   itow (ldir[i].l_off, secoffs);
   numsecs = fcopy (ifd, lfd);
   itow (ldir[i].l_len, numsecs);
   VOID fclose (ifd);
}

fcopy (ifd, ofd)
FILE *ifd, *ofd;
{
   register int total = 0;
   register int i, n;
   char sectorbuf[SECTOR];


   while ( (n = fread( sectorbuf, 1, SECTOR, ifd)) != 0) {
       if (n != SECTOR)
           for (i = n; i < SECTOR; i++)
               sectorbuf[i] = CTRLZ;
       if (fwrite( sectorbuf, 1, SECTOR, ofd ) != SECTOR)
               error("write error");
       ++total;
   }
   return total;
}

delete (lname)
char   *lname;
{
   FILE *f;
   register int    i;

   if ((f = fopen (lname, "r+")) == NULL)
       cant (lname);

   if (nfiles <= 0)
       error("delete by name only");

   getdir (f);
   for (i = 0; i < nslots; i++) {
       if (!filarg ( getname (ldir[i].l_name, ldir[i].l_ext)))
           continue;
       ldir[i].l_stat = DELETED;
   }

   not_found();
   if (errcnt > 0)
       fprintf (stderr, "errors - library not updated\n");
   else
       putdir (f);
   VOID fclose (f);
}

reorg (name)
char  *name;
{
   FILE *olib, *nlib;
   int oldsize;
   register int i, j;
   struct ludir odir[MAXFILES];
   char tmpname[SECTOR];

   VOID sprintf(tmpname,"%-10.10s.TMP", name);

   if( (olib = fopen(name,"r")) == NULL)
       cant(name);

   if( (nlib = fopen(tmpname, "w")) == NULL)
       cant(tmpname);

   getdir(olib);
   printf("Old library has %d slots\n", oldsize = nslots);
   for(i = 0; i < nslots ; i++)
           copymem( (char *) &odir[i], (char *) &ldir[i],
                       sizeof(struct ludir));
   initdir(nlib);
   errcnt = 0;

   for (i = j = 1; i < oldsize; i++)
       if( odir[i].l_stat == ACTIVE ) {
           if(verbose)
               fprintf(stderr, "Copying: %-8.8s.%3.3s\n",
                       odir[i].l_name, odir[i].l_ext);
           copyentry( &odir[i], olib,  &ldir[j], nlib);
           if (++j >= nslots) {
               errcnt++;
               fprintf(stderr, "Not enough room in new library\n");
               break;
           }
       }

   VOID fclose(olib);
   putdir(nlib);
   VOID fclose (nlib);

   if(errcnt == 0) {
       if ( unlink(name) < 0 || link(tmpname, name) < 0) {
           VOID unlink(tmpname);
           cant(name);
       }
   }
   else
       fprintf(stderr,"Errors, library not updated\n");
   VOID unlink(tmpname);

}

copyentry( old, of, new, nf )
struct ludir *old, *new;
FILE *of, *nf;
{
   register int secoffs, numsecs;
   char buf[SECTOR];

   new->l_stat = ACTIVE;
   copymem(new->l_name, old->l_name, 8);
   copymem(new->l_ext, old->l_ext, 3);
   VOID fseek(of, (long) wtoi(old->l_off)*SECTOR, 0);
   VOID fseek(nf, 0L, 2);
   secoffs = ftell(nf) / SECTOR;

   itow (new->l_off, secoffs);
   numsecs = wtoi(old->l_len);
   itow (new->l_len, numsecs);

   while(numsecs-- != 0) {
       if( fread( buf, 1, SECTOR, of) != SECTOR)
           error("read error");
       if( fwrite( buf, 1, SECTOR, nf) != SECTOR)
           error("write error");
   }
}

copymem(dst, src, n)
register char *dst, *src;
register unsigned int n;
{
       while(n-- != 0)
               *dst++ = *src++;
}