/*
 * ALIB Rev. 1.0
 * Amiga Object Module Librarian
 * Programmed by Mike Schwartz
 *               (C)1985, MS Software, all rights reserved.
 *
 * Feel free to distribute this program on a no-charge basis.
 *
 * Use:
 *    ALIB  option  library filelist
 * Where
 *    option may be one of the following:
 *       D  =  delete object modules from library
 *       R  =  replace object modules (or add if not defined)
 *       L  =  directory of library
 *    library is the name of the library file to be created or modified.
 *       if no .lib extension is provided on the command line, it is
 *       appended.  Also note that a file library.dir is created for
 *       each library file and is managed by this program.
 *    filelist is a list of object module filenames separated by spaces
 *       or commas.  Note that if .o is not present in the filenames,
 *       .o will be appended.
 */


/* This version of Alib has been modified, without permission, by
 * Stephen Williams.  Changes are as follows:
 *
 * - .o is now not unconditionally added to object file names.  If the
 *   extension is already present, no action is taken.  This appears to
 *   be how the author wished Alib to behave (check out the online help)
 *   but it never worked for me;
 *
 * - .lib may be added to the library name on the command line.  If it
 *   isn't, it will be added.  .dir is handled fairly intelligently.
 *   Again, the online help advertised this feature, although it never
 *   worked;
 *
 * - Unix-style I/O (open(), close(), read() etc) has been replaced with
 *   ANSI-style stdio streaming (fopen(), fclose(), fread() etc) to aid
 *   portability, and generally make my life easier;
 *
 * - errors now go to stderr instead of stdout (I didn't bother marking
 *   each individual change);
 *
 * - all files are written with permissions ----rw-d;
 *
 * - lots of little changes (marked where they have been made)
 */
static const char DOLLARVER[] = "\0$VER: Alib 1.0sw1 (11.3.98)";


/* #include lines modified by Stephen Williams */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#ifdef AMIGA
#include <dos/dos.h>
#include <clib/dos_protos.h>
#include <inline/dos_protos.h>
extern void * DOSBase;
#endif


/*
 * The following structure defines the records in the .dir file for
 * the library.
 */

#define  LD    struct   libdir_def
struct   libdir_def     {
   char  object_filename[80];       /* name of .o file */
   long  module_offset;             /* offset of module in .lib file */
   long  module_size;               /* size of module in bytes */
   };

/*
 * When the library is openned, each object module is read into memory
 * and managed with the following structure.
 */

#define  LL    struct   liblist_def
struct   liblist_def    {
   struct   libdir_def  dir_entry;  /* directory entry for module */
   char     *object_module;         /* actual text of module */
   LL       *next;                  /* pointer to next in list */
   };

/*
 * +++ Change by Stephen Williams +++
 *
 * The original source here defined prototypes for external functions
 * getmem() and lseek().  lseek() hasn't been used in my altered source;
 * I replaced it with the stdio stream equivalent fseek().  getmem(),
 * along with rlsmem(), is not defined anywhere in my includes, so I
 * have provided macros to emulate these functions
 */

#define	getmem(size)		(char *)calloc(size, 1)
#define	rlsmem(pointer, size)	free(pointer)

/*
 * +++ Addition by Stephen Williams +++
 *
 * The original source just strcat()ed a ".o" onto the end of all
 * object file names.  This has been replaced with the following
 * macro, which only adds .o if it isn't there already
 */

#define makeobjname(s) if(strcmp((char *)(s + strlen(s) - 2), ".o")) strcat(s, ".o")

/*
 * Global variables
 *
 * +++ Changes by Stephen Williams +++
 *
 * "int libfd", "int objfd" and "int dirfd" have become
 * "FILE * libfp", "FILE * objfp" and "FILE * dirfp"
 * to reflect the changes from Unix-style I/O to stdio streams
 */

LL *	objlist=NULL;	/* linked list of object modules */

char	libname[80];	/* name of library file */
FILE *	libfp;		/* file pointer for library file */
char	objname[80];	/* name of object file */
FILE *	objfp;		/* file pointer for object file */
char	dirname[80];	/* name of directory file */
FILE *	dirfp;		/* file pointer for directory file */

char  buf[512];                     /* misc buffer */

char  option;                       /* user selected option */


/*
 * This function prints the help messages and exits.
 *
 * K&R-style function definition changed to ANSI-style by Stephen Williams
 */

void help(void)
{
  /*
   * +++ Changes by Stephen Williams +++
   *
   * - Multiple printf() statements merged into one;
   * - Help text reworded
   */
   printf("Use:\n"
          "   Alib D <library[.lib]> <object[.o]> [object[.o] ...]\n"
          "   Alib R <library[.lib]> <object[.o]> [object[.o] ...]\n"
          "   Alib L <library[.lib]>\n\n"
          "Where\n"
          "   D  =  delete object modules from library\n"
          "   R  =  replace object modules (or add if not defined) \n"
          "   L  =  directory of library\n\n"
          "   Note that a file \"library.dir\" is created for each library file,\n"
          "   and is managed by this program.  If \".lib\" and/or \".o\" suffixes\n"
          "   are omitted on the command line, they will be added by the program.\n");

   exit(1);
}


/*
 * The following function opens the library and directory file.  If the
 * file does not exist, then the user is asked if he wants to create it.
 * If the library exists, then it is read in and built in a linked list.
 *
 * K&R-style function definition changed to ANSI-style by Stephen Williams
 */

int open_library(void)
{
   LL    *lp;           /* pointer to module node */

   /*
    * Open the library file and directory file.
    * Changed by Stephen Williams to use stdio streams
    *
    */
   libfp = fopen(libname, "r");
   if (!libfp) {
      /*
       * Library does not exist, prompt user for creation.
       */
      while (1) {
         printf("%s does not exist, create it? (Y/N): ", libname);
         gets(buf);
         if (buf[0] == 'y' || buf[0] == 'Y')
            return !0;
         if (buf[0] == 'n' || buf[0] == 'N') {
            fprintf(stderr, "Alib abandoned\n");
            exit(1);
            }
         }
      }
   dirfp = fopen(dirname, "r");
   if (!dirfp) {
      fprintf(stderr, "%s has been corrupted\n", dirname);
      fprintf(stderr, "Alib abandoned\n");
      exit(1);
      }
   /*
    * library and directory files are open, read in the library.
    */
   while (1) {
      /*
       * Allocate a node to hold the object module.
       */
      lp = (LL *)getmem(sizeof(LL));
      if (lp == NULL) {
         fprintf(stderr, "Not enough memory.\nAlib abandoned\n");
         exit(1);
         }
      /*
       * Check for end of file.
       */
      if (fread(&lp->dir_entry, sizeof(LD), 1, dirfp) != 1)
         break;
      /*
       * Allocate a buffer to hold the object file image.
       */
      lp->object_module = getmem(lp->dir_entry.module_size);
      if (lp->object_module == NULL) {
         fprintf(stderr, "Not enough memory.\nAlib abandoned\n");
         exit(1);
         }
      /*
       * Read in the object module
       */
      if (fread(lp->object_module, lp->dir_entry.module_size, 1, libfp) != 
                                                                        1) {
         fprintf(stderr, "Library file is corrupted.\nAlib abandoned\n");
         exit(1);
         }
      /*
       * Add module to linked list.
       */
      lp->next = objlist;
      objlist = lp;
      }
   /*
    * Library has been read in ok.  Close files and return.
    */
   fclose(libfp);
   fclose(dirfp);
   printf("Library file read in ok\n");
}


/*
 * The following function removes an object module from the linked list
 * and frees up any memory used by it.
 *
 * K&R-style function definition changed to ANSI-style by Stephen Williams
 */

int kill_module(char * name)
{
   LL    *lp;
   LL    *lp2;

   if (objlist == NULL)
      return 0;
   strcpy(objname, name);
   /*
    * the next line changed from
    * strcat(objname, ".o");
    * by Stephen Williams
    */
   makeobjname(objname);
   if (strcmp(objlist->dir_entry.object_filename,  objname) == 0) {
        lp = objlist->next;
        rlsmem(objlist->object_module, objlist->dir_entry.module_size);
        rlsmem((char *)objlist, sizeof(LL));
        objlist = lp;
        return !0;
   }
   for (lp = objlist; lp->next != NULL; lp=lp->next)
      if (strcmp(lp->next->dir_entry.object_filename, objname) == 0) {
           lp2 = lp->next;
           lp->next = lp2->next;
           rlsmem(lp2->object_module, lp2->dir_entry.module_size);
           rlsmem((char *)lp2, sizeof(LL));
           return !0;
      }
   return 0;
}


/*
 * K&R-style function definition changed to ANSI-style by Stephen Williams
 */

void add_module(char * name)
{
   LL    *lp;
   int   len;

   strcpy(objname, name);
   /*
    * the next line changed from
    * strcat(objname, ".o");
    * by Stephen Williams
    */
   makeobjname(objname);
   /* Initial \n in following printf() added by Stephen Williams */
   printf("\nadding %s\n", objname);

   /*
    * Open the object file
    * Changed by Stephen Williams to use stdio streams
    */
   objfp = fopen(objname, "r");
   if (!objfp) {
      fprintf(stderr, "%s cannot be added\nAlib abandoned\n", objname);
      exit(1);
      }
   /*
    * Allocate a node to hold the file.
    */
   lp = (LL *)getmem(sizeof(LL));
   if (lp == NULL) {
      fprintf(stderr, "Not enough memory.\nAlib abandoned\n");
      exit(1);
      }
   /*
    * Initialize it.
    *
    * Changes by Stephen Williams; the naughty author used absolute
    * values for his lseek() modes; not SEEK_SET and SEEK_END.
    * This was very very very silly.  Corrected
    *
    * Additional change; lseek() changed to fseek(), because I'm using
    * stdio streams, not Unix file descriptors
    */
   strcpy(lp->dir_entry.object_filename, objname);
   fseek(objfp, 0, SEEK_END);
   lp->dir_entry.module_size = ftell(objfp);
   /* %d in following printf() changed to %ld by Stephen Williams */
   printf("module size = %ld\n", lp->dir_entry.module_size);
   fseek(objfp, 0, SEEK_SET);
   /*
    * Allocate the buffer to read the object module into.
    */
   lp->object_module = getmem(lp->dir_entry.module_size);
   if (lp->object_module == NULL) {
      fprintf(stderr, "Not enough memory.\nAlib abandoned\n");
      exit(1);
      }
   /*
    * Read in the file.
    */
   if (fread(lp->object_module, lp->dir_entry.module_size, 1, objfp) != 1) {
      fprintf(stderr, "Library file is corrupted.\nAlib abandoned\n");
      exit(1);
      }
   /*
    * Add module to linked list.
    */
   lp->next = objlist;
   objlist = lp;
   /*
    * Close object file and return.
    */
   fclose(objfp);
   printf("%s added\n", name);
}


/*
 * The following routine writes the library from memory and exits.
 *
 * K&R-style function definition changed to ANSI-style by Stephen Williams
 */

void close_library(void) {
   LL    *lp;
   long  file_position;
   long  length;

   /*
    * Open the library
    *
    * +++ Changes by Stephen Williams +++
    *
    * All I/O changed from Unix-style to stdio streams;
    *
    * - Spelling mistake in error strings corrected; "openning"
    *   changed to "opening"
    *
    */
   libfp = fopen(libname, "w");
   if (!libfp) {
      fprintf(stderr, "Error opening %s for output\nAlib abandoned", libname);
      exit(1);
      }
   dirfp = fopen(dirname, "w");
   if (!dirfp) {
      fprintf(stderr, "Error opening %s for output\nAlib abandoned", dirname);
      exit(1);
      }
   /*
    * Write out the individual modules and directory records.
    */
   file_position = 0;
   for (lp = objlist; lp != NULL; lp = lp->next) {
      length = 
             fwrite(lp->object_module, 1, lp->dir_entry.module_size, libfp);
      if (length != lp->dir_entry.module_size) {
         fprintf(stderr, "Error writing to library file\nAlib abandoned\n");
         exit(1);
         }
      lp->dir_entry.module_offset = file_position;
      file_position += length;
      if (fwrite((char *)&lp->dir_entry, sizeof(LD), 1, dirfp) != 1) {
         fprintf(stderr, "Error writing to directory file\nAlib abandoned\n");
         exit(1);
         }
      }
   /*
    * Close the library files
    */
   fclose(libfp);
   fclose(dirfp);
   /* the SetProtection() lines added by Stephen Williams */
   #ifdef AMIGA
   SetProtection(libname, FIBF_EXECUTE);
   SetProtection(dirname, FIBF_EXECUTE);
   #endif
   /* the word "Library" in the following string capitalized by
      Stephen Williams.  Initial \n also added */
   printf("\nLibrary is %ld bytes\nAlib complete\n", file_position);
   exit(0);
}


/*
 * K&R-style function definition changed to ANSI-style by Stephen Williams
 *
 * This function was below main(); moved above so main() sees a
 * declaration for it
 */
void directory(void) {
   LD    d_entry;
   int   count;
   int   bytes;

   /* Change by Stephen Williams: Unix I/O changed to stdio streams */
   dirfp = fopen(dirname, "r");
   if (!dirfp) {
      fprintf(stderr, "%s not found\n", dirname);
      exit(1);
      }
   printf("Directory of library %s\n", libname);
   printf("Module Name                    Size   Offset\n");
   count = bytes = 0;
   while (fread((char *)&d_entry, sizeof(LD), 1, dirfp) == 1) {
      /* %ds in following printf() changed to %lds by Stephen Williams */
      printf("%-30.30s %-6ld %-6ld\n", d_entry.object_filename, 
               d_entry.module_size, d_entry.module_offset);
      count++;
      bytes += d_entry.module_size;
      }
   printf("Library consists of %d entries totalling %d bytes\n", count,
            bytes);
   fclose(dirfp);
}


/*
 * Main program.
 *
 * K&R-style function definition changed to ANSI-style by Stephen Williams
 */
int main(int argc, char * argv[])
{
   int   count;

   /*
    * Banner changed by Stephen Williams to reflect the fact that the
    * program has been hacked around
    */
   printf("Alib 1.0sw1\n"
          "Amiga Object Module Librarian\n"
          "Programmed by Mike Schwartz\n"
          "Altered by Stephen Williams, without permission\n"
          "(C)1985 MS Software, all rights reserved\n\n");

   /*
    * check for command line parameters present.
    */
   if (argc < 3)
      help();
   /*
    * setup the option and filenames
    */
   option = argv[1][0];
   strcpy(libname, argv[2]);
   strcpy(dirname, argv[2]);
   /*
    * start of major change by Stephen Williams
    */
   /* .lib extension present? */
   if(!strcmp((char *)(libname + strlen(libname) - 4), ".lib"))
     /* Yes; replace .lib with .dir in other name */
     strcpy((char *)(dirname + strlen(dirname) - 3), "dir");
   else {
     /* Nope, gotta add it */
     strcat(libname, ".lib");
     strcat(dirname, ".dir");
   }
   /*
    * end of change by Stephen Williams
    */
   if (option == 'l' || option == 'L') {
      directory();
      exit(0);
      }
   if (option != 'd' && option != 'D' && option != 'r' && option != 'R')
      help();
   /*
    * Open the library file
    */
   open_library();
   for (count = 3; count < argc; count++) {
      switch(option) {
         case 'd':
         case 'D':
            /* message format changed by Stephen Williams */
            printf("\nDeleting %s from library...\n", argv[count]);
            if (!kill_module(argv[count])) {
               printf("failed\n");
               fprintf(stderr, "%s not defined in library\n", argv[count]);
            } else
               printf("done\n");
            break;
         case 'r':
         case 'R':
            kill_module(argv[count]);
            add_module(argv[count]);
            break;
         }
      }
   close_library();
}
