[comp.sources.unix] v24i093: Program identifier database tools, Part05/07

rsalz@bbn.com (Rich Salz) (06/06/91)

Submitted-by: Tom Horsley <tom@hcx2.ssd.csd.harris.com>
Posting-number: Volume 24, Issue 93
Archive-name: mkid2/part05

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 5 (of 7)."
# Contents:  iidfun.c mkid.c
# Wrapped by tom@hcx2 on Tue Feb 26 10:03:05 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'iidfun.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'iidfun.c'\"
else
echo shar: Extracting \"'iidfun.c'\" \(17113 characters\)
sed "s/^X//" >'iidfun.c' <<'END_OF_FILE'
X/* iidfun.c - This file holds the utility functions called from iid.y
X */
X
X#include "iiddef.h"
X#ifdef USE_ALLOCA
X
X/* Apparently Sun's need the following to work correctly */
X#ifdef sun
X#include <alloca.h>
X#endif /* sun */
X
X/* Define TEMP_ALLOC to call alloca, and TEMP_FREE to do nothing */
X
X#define TEMP_ALLOC(s) alloca(s)
X#define TEMP_FREE(s)
X
X#else
X
X/* Not using alloca() (not everyone has it) - define TEMP_ALLOC to call
X * malloc() and TEMP_FREE to call free().
X */
X#define TEMP_ALLOC(s) malloc(s)
X#define TEMP_FREE(s)  free(s)
X
X#endif /* USE_ALLOCA */
X
X/* ArgListSize - count the size of an arg list so can alloca() enough
X * space for the command.
X */
Xint
XArgListSize(idlp)
X   id_list_type * idlp ;
X{
X   id_type *      idep ;
X   int            size = 0;
X   
X   idep = idlp->id_list ;
X   while (idep != NULL) {
X      size += 1 + strlen(idep->id);
X      idep = idep->next_id;
X   }
X   return size;
X}
X
X/* SetListSize - count the size of a string build up from a set so we can
X * alloca() enough space for args.
X */
Xint
XSetListSize(sp)
X   set_type * sp ;
X{
X   int            i ;
X   int            size = 0 ;
X   
X   for (i = 0; i < NextFileNum; ++i) {
X      if (FileList[i]->mask_word < sp->set_size) {
X         if (sp->set_data[FileList[i]->mask_word] & FileList[i]->mask_bit) {
X            size += 1 + strlen(FileList[i]->name);
X         }
X      }
X   }
X   return size;
X}
X
X/* FlushFiles - clear out the TheFiles array for the start of a new
X * query.
X */
Xvoid
XFlushFiles()
X{
X   int             i ;
X   
X   if (TheFiles != NULL) {
X      for (i = 0; i <= MaxCurFile; ++i) {
X         TheFiles[i] = 0 ;
X      }
X   }
X   MaxCurFile = 0 ;
X}
X
X/* fatal - sometimes the only thing to do is die...
X */
Xvoid
Xfatal(s)
X{
X   fprintf(stderr,"Fatal error: %s\n",s) ;
X   exit(1) ;
X}
X
X/* CountBits - count the number of bits in a bit set. Actually fairly
X * tricky since it needs to deal with sets having infinite tails
X * as a result of a NOT operation.
X */
Xint
XCountBits(sp)
X   set_type * sp ;
X{
X   unsigned long      bit_mask ;
X   int                count = 0 ;
X   int                i ;
X   
X   i = 0;
X   for ( ; ; ) {
X      for (bit_mask = high_bit; bit_mask != 0; bit_mask >>= 1) {
X         if (bit_mask == NextMaskBit && i == NextMaskWord) {
X            return(count) ;
X         }
X         if (i < sp->set_size) {
X            if (sp->set_data[i] & bit_mask) {
X               ++count ;
X            }
X         } else {
X            if (sp->set_tail == 0) return count;
X            if (sp->set_tail & bit_mask) {
X               ++count;
X            }
X         }
X      }
X      ++i;
X   }
X}
X
X/* OneDescription - Print a description of a set. This includes
X * the set number, the number of files in the set, and the
X * set description string.
X */
Xvoid
XOneDescription(sp)
X   set_type * sp ;
X{
X   int        elt_count ;
X   char       setnum[20] ;
X   
X   sprintf(setnum,"S%d",sp->set_num) ;
X   elt_count = CountBits(sp) ;
X   printf("%5s %6d  %s\n",setnum,elt_count,sp->set_desc) ;
X}
X
X/* DescribeSets - Print description of all the sets.
X */
Xvoid
XDescribeSets()
X{
X   int            i ;
X
X   if (NextSetNum > 0) {
X      for (i = 0; i < NextSetNum; ++i) {
X         OneDescription(TheSets[i]) ;
X      }
X   } else {
X      printf("No sets defined yet.\n") ;
X   }
X}
X
X/* SetList - Go through the bit set and add the file names in
X * it to an identifier list.
X */
Xid_list_type *
XSetList(idlp, sp)
X   id_list_type *  idlp ;
X   set_type *      sp ;
X{
X   int        i ;
X   id_type *  idep ;
X   
X   for (i = 0; i < NextFileNum; ++i) {
X      if (FileList[i]->mask_word < sp->set_size) {
X         if (sp->set_data[FileList[i]->mask_word] & FileList[i]->mask_bit) {
X            idep = (id_type *)malloc(sizeof(id_type) +
X                                     strlen(FileList[i]->name)) ;
X            if (idep == NULL) {
X               fatal("Out of memory in SetList") ;
X            }
X            idep->next_id = NULL ;
X            strcpy(idep->id, FileList[i]->name) ;
X            idlp = ExtendList(idlp, idep) ;
X         }
X      }
X   }
X   return(idlp) ;
X}
X
X/* PrintSet - Go through the bit set and print the file names
X * corresponding to all the set bits.
X */
Xvoid
XPrintSet(sp)
X   set_type * sp ;
X{
X   int        i ;
X   
X   for (i = 0; i < NextFileNum; ++i) {
X      if (FileList[i]->mask_word < sp->set_size) {
X         if (sp->set_data[FileList[i]->mask_word] & FileList[i]->mask_bit) {
X            printf("%s\n",FileList[i]->name) ;
X         }
X      }
X   }
X}
X
X/* Free up all space used by current set of sets and reset all
X * set numbers.
X */
Xvoid
XFlushSets()
X{
X   int         i ;
X   
X   for (i = 0; i < NextSetNum; ++i) {
X      free(TheSets[i]->set_desc) ;
X      free(TheSets[i]) ;
X   }
X   NextSetNum = 0 ;
X}
X
X/* InitList - create an empty identifier list.
X */
Xid_list_type *
XInitList()
X{
X   id_list_type *   idlp ;
X   
X   idlp = (id_list_type *)malloc(sizeof(id_list_type)) ;
X   if (idlp == NULL) {
X      fatal("Out of memory in InitList") ;
X   }
X   idlp->id_count = 0 ;
X   idlp->end_ptr_ptr = & (idlp->id_list) ;
X   idlp->id_list = NULL ;
X   return(idlp) ;
X}
X
X/* ExtendList - add one identifier to an ID list.
X */
Xid_list_type *
XExtendList(idlp, idp)
X   id_list_type * idlp ;
X   id_type *      idp ;
X{
X   *(idlp->end_ptr_ptr) = idp ;
X   idlp->end_ptr_ptr = &(idp->next_id) ;
X   return(idlp) ;
X}
X
X/* InitIid - do all initial processing for iid.
X *   1) Determine the size of a unsigned long for bit set stuff.
X *   2) Find out the name of the pager program to use.
X *   3) Create the HelpSet (pointing to the help file).
X *   4) Setup the prompt.
X */
Xvoid
XInitIid()
X{
X   unsigned long      bit_mask = 1 ;   /* find number of bits in long */
X   int                i ;
X   char *             page ;           /* pager program */
X   
X   do {
X      high_bit = bit_mask ;
X      bit_mask <<= 1 ;
X   } while (bit_mask != 0) ;
X   
X   NextMaskBit = high_bit ;
X   
X   page = getenv("PAGER") ;
X   if (page == NULL) {
X      page = PAGER ;
X   }
X   strcpy(Pager, page) ;
X   
X   FlushFiles() ;
X   InstallFile(HELPFILE) ;
X   HelpSet = (set_type *)
X      malloc(sizeof(set_type) + sizeof(unsigned long) * MaxCurFile) ;
X   if (HelpSet == NULL) {
X      fatal("No memory for set in InitIid") ;
X   }
X   HelpSet->set_tail = 0 ;
X   HelpSet->set_desc = NULL ;
X   HelpSet->set_size = MaxCurFile + 1 ;
X   for (i = 0; i <= MaxCurFile; ++i) {
X      HelpSet->set_data[i] = TheFiles[i] ;
X   }
X   
X   page = getenv("PS1") ;
X   if (page == NULL) {
X      page = PROMPT ;
X   }
X   strcpy(Prompt, page) ;
X}
X
X/* InstallFile - install a file name in the symtab. Return the
X * symbol table pointer of the file.
X */
Xsymtab_type *
XInstallFile(fp)
X   char *      fp ;
X{
X   char             c ;
X   unsigned long    hash_code ;
X   int              i ;
X   char *           sp ;
X   symtab_type *    symp ;
X   
X   hash_code = 0 ;
X   sp = fp ;
X   while ((c = *sp++) != '\0') {
X      hash_code <<= 1 ;
X      hash_code ^= (unsigned long)(c) ;
X      if (hash_code & high_bit) {
X         hash_code &= ~ high_bit ;
X         hash_code ^= 1 ;
X      }
X   }
X   hash_code %= HASH_SIZE ;
X   symp = HashTable[hash_code] ;
X   while (symp != NULL && strcmp(symp->name, fp)) {
X      symp = symp->hash_link ;
X   }
X   if (symp == NULL) {
X      symp = (symtab_type *)malloc(sizeof(symtab_type) + strlen(fp)) ;
X      if (symp == NULL) {
X         fatal("No memory for symbol table entry in InstallFile") ;
X      }
X      strcpy(symp->name, fp) ;
X      symp->hash_link = HashTable[hash_code] ;
X      HashTable[hash_code] = symp ;
X      if (NextMaskWord >= FileSpace) {
X         FileSpace += 1000 ;
X         if (TheFiles != NULL) {
X            TheFiles = (unsigned long *)
X               realloc(TheFiles, sizeof(unsigned long) * FileSpace) ;
X         } else {
X            TheFiles = (unsigned long *)
X               malloc(sizeof(unsigned long) * FileSpace) ;
X         }
X         if (TheFiles == NULL) {
X            fatal("No memory for TheFiles in InstallFile") ;
X         }
X         for (i = NextMaskWord; i < FileSpace; ++i) {
X            TheFiles[i] = 0 ;
X         }
X      }
X      symp->mask_word = NextMaskWord ;
X      symp->mask_bit = NextMaskBit ;
X      NextMaskBit >>= 1 ;
X      if (NextMaskBit == 0) {
X         NextMaskBit = high_bit ;
X         ++NextMaskWord ;
X      }
X      if (NextFileNum >= ListSpace) {
X         ListSpace += 1000 ;
X         if (FileList == NULL) {
X            FileList = (symtab_type **)
X               malloc(sizeof(symtab_type *) * ListSpace) ;
X         } else {
X            FileList = (symtab_type **)
X               realloc(FileList, ListSpace * sizeof(symtab_type *)) ;
X         }
X         if (FileList == NULL) {
X            fatal("No memory for FileList in InstallFile") ;
X         }
X      }
X      FileList[NextFileNum++] = symp ;
X      /* put code here to sort the file list by name someday */
X   }
X   TheFiles[symp->mask_word] |= symp->mask_bit ;
X   if (symp->mask_word > MaxCurFile) {
X      MaxCurFile = symp->mask_word ;
X   }
X   return(symp) ;
X}
X
X/* RunPager - run the users pager program on the list of files
X * in the set.
X */
Xvoid
XRunPager(pp, sp)
X   char *     pp ;
X   set_type * sp ;
X{
X   char *         cmd ;
X   int            i ;
X   id_type *      idep ;
X   
X   cmd = (char *)TEMP_ALLOC(SetListSize(sp) + strlen(pp) + 2);
X   strcpy(cmd, pp) ;
X   for (i = 0; i < NextFileNum; ++i) {
X      if (FileList[i]->mask_word < sp->set_size) {
X         if (sp->set_data[FileList[i]->mask_word] & FileList[i]->mask_bit) {
X            strcat(cmd, " ") ;
X            strcat(cmd, FileList[i]->name) ;
X         }
X      }
X   }
X   system(cmd) ;
X   TEMP_FREE(cmd) ;
X}
X
X/* AddSet - add a new set to the universal list of sets. Assign
X * it the next set number.
X */
Xvoid
XAddSet(sp)
X   set_type *   sp ;
X{
X   if (NextSetNum >= SetSpace) {
X      SetSpace += 1000 ;
X      if (TheSets != NULL) {
X         TheSets = (set_type **)
X            realloc(TheSets, sizeof(set_type *) * SetSpace) ;
X      } else {
X         TheSets = (set_type **)
X            malloc(sizeof(set_type *) * SetSpace) ;
X      }
X      if (TheSets == NULL) {
X         fatal("No memory for TheSets in AddSet") ;
X      }
X   }
X   sp->set_num = NextSetNum ;
X   TheSets[NextSetNum++] = sp ;
X}
X
X/* RunProg - run a program with arguments from id_list and
X * accept list of file names back from the program which
X * are installed in the symbol table and used to construct
X * a new set.
X */
Xset_type *
XRunProg(pp, idlp)
X   char *         pp ;
X   id_list_type * idlp ;
X{
X   int            c ;
X   char *         cmd ;
X   char *         dp ;
X   char           file [ MAXCMD ] ;
X   int            i ;
X   id_type *      idep ;
X   id_type *      next_id ;
X   FILE *         prog ;
X   set_type *     sp ;
X   
X   cmd = (char *)TEMP_ALLOC(ArgListSize(idlp) + strlen(pp) + 2);
X   FlushFiles() ;
X   strcpy(cmd, pp) ;
X   idep = idlp->id_list ;
X   while (idep != NULL) {
X      strcat(cmd, " ") ;
X      strcat(cmd, idep->id) ;
X      next_id = idep->next_id ;
X      free(idep) ;
X      idep = next_id ;
X   }
X   free(idlp) ;
X   
X   /* run program with popen, reading the output. Assume each
X    * white space terminated string is a file name.
X    */
X   prog = popen(cmd, "r") ;
X   dp = file ;
X   while ((c = getc(prog)) != EOF) {
X      if (isspace(c)) {
X         if (dp != file) {
X            *dp++ = '\0' ;
X            InstallFile(file) ;
X            dp = file ;
X         }
X      } else {
X         *dp++ = c ;
X      }
X   }
X   if (dp != file) {
X      *dp++ = '\0' ;
X      InstallFile(file) ;
X   }
X   if (pclose(prog) != 0) {
X      /* if there was an error make an empty set, who knows what
X       * garbage the program printed.
X       */
X      FlushFiles() ;
X   }
X   
X   sp = (set_type *)
X      malloc(sizeof(set_type) + sizeof(unsigned long) * MaxCurFile) ;
X   if (sp == NULL) {
X      fatal("No memory for set in RunProg") ;
X   }
X   sp->set_tail = 0 ;
X   sp->set_desc = (char *)malloc(strlen(cmd) + 1) ;
X   if (sp->set_desc == NULL) {
X      fatal("No memory for set description in RunProg") ;
X   }
X   strcpy(sp->set_desc, cmd) ;
X   sp->set_size = MaxCurFile + 1 ;
X   for (i = 0; i <= MaxCurFile; ++i) {
X      sp->set_data[i] = TheFiles[i] ;
X   }
X   AddSet(sp) ;
X   TEMP_FREE(cmd);
X   return(sp) ;
X}
X
X/* SetDirectory - change the working directory. This will
X * determine which ID file is found by the subprograms.
X */
Xvoid
XSetDirectory(dir)
X   id_type *      dir ;
X{
X   if (chdir(dir->id) != 0) {
X      fprintf(stderr,"Directory %s not accessible.\n",dir) ;
X   }
X   free(dir) ;
X}
X
X/* SetIntersect - construct a new set from the intersection
X * of two others. Also construct a new description string.
X */
Xset_type *
XSetIntersect(sp1, sp2)
X   set_type * sp1 ;
X   set_type * sp2 ;
X{
X   char *     desc ;
X   int        i ;
X   int        len1 ;
X   int        len2 ;
X   set_type * new_set ;
X   int        new_size ;
X   
X   if (sp1->set_tail || sp2->set_tail) {
X      new_size = MAX(sp1->set_size, sp2->set_size) ;
X   } else {
X      new_size = MIN(sp1->set_size, sp2->set_size) ;
X   }
X   new_set = (set_type *)malloc(sizeof(set_type) +
X                                (new_size - 1) * sizeof(unsigned long)) ;
X   if (new_set == NULL) {
X      fatal("No memory for set in SetIntersect") ;
X   }
X   len1 = strlen(sp1->set_desc) ;
X   len2 = strlen(sp2->set_desc) ;
X   desc = (char *)malloc(len1 + len2 + 10) ;
X   if (desc == NULL) {
X      fatal("No memory for set description in SetIntersect") ;
X   }
X   new_set->set_desc = desc ;
X   strcpy(desc,"(") ;
X   ++desc ;
X   strcpy(desc, sp1->set_desc) ;
X   desc += len1 ;
X   strcpy(desc, ") AND (") ;
X   desc += 7 ;
X   strcpy(desc, sp2->set_desc) ;
X   desc += len2 ;
X   strcpy(desc, ")") ;
X   AddSet(new_set) ;
X   new_set->set_size = new_size ;
X   for (i = 0; i < new_size; ++i) {
X      new_set->set_data[i] = 
X         ((i < sp1->set_size) ? sp1->set_data[i] : sp1->set_tail) & 
X         ((i < sp2->set_size) ? sp2->set_data[i] : sp2->set_tail) ;
X   }
X   new_set->set_tail = sp1->set_tail & sp2->set_tail ;
X   return(new_set) ;
X}
X
X/* SetUnion - construct a new set from the union of two others.
X * Also construct a new description string.
X */
Xset_type *
XSetUnion(sp1, sp2)
X   set_type * sp1 ;
X   set_type * sp2 ;
X{
X   char *     desc ;
X   int        i ;
X   int        len1 ;
X   int        len2 ;
X   set_type * new_set ;
X   int        new_size ;
X   
X   new_size = MAX(sp1->set_size, sp2->set_size) ;
X   new_set = (set_type *)malloc(sizeof(set_type) +
X                                (new_size - 1) * sizeof(unsigned long)) ;
X   if (new_set == NULL) {
X      fatal("No memory for set in SetUnion") ;
X   }
X   len1 = strlen(sp1->set_desc) ;
X   len2 = strlen(sp2->set_desc) ;
X   desc = (char *)malloc(len1 + len2 + 9) ;
X   if (desc == NULL) {
X      fatal("No memory for set description in SetUnion") ;
X   }
X   new_set->set_desc = desc ;
X   strcpy(desc,"(") ;
X   ++desc ;
X   strcpy(desc, sp1->set_desc) ;
X   desc += len1 ;
X   strcpy(desc, ") OR (") ;
X   desc += 6 ;
X   strcpy(desc, sp2->set_desc) ;
X   desc += len2 ;
X   strcpy(desc, ")") ;
X   AddSet(new_set) ;
X   new_set->set_size = new_size ;
X   for (i = 0; i < new_size; ++i) {
X      new_set->set_data[i] =
X         ((i < sp1->set_size) ? (sp1->set_data[i]) : sp1->set_tail) |
X         ((i < sp2->set_size) ? (sp2->set_data[i]) : sp2->set_tail) ;
X   }
X   new_set->set_tail = sp1->set_tail | sp2->set_tail ;
X   return(new_set) ;
X}
X
X/* SetInverse - construct a new set from the inverse of another.
X * Also construct a new description string.
X *
X * This is kind of tricky. An inverse set in iid may grow during
X * the course of a session. By NOTing the set_tail extension the
X * inverse at any given time will be defined as the inverse against
X * a universe that grows as additional queries are made and new files
X * are added to the database.
X *
X * Several alternative definitions were possible (snapshot the
X * universe at the time of the NOT, go read the ID file to
X * determine the complete universe), but this one was the one
X * I picked.
X */
Xset_type *
XSetInverse(sp)
X   set_type * sp ;
X{
X   char *     desc ;
X   int        i ;
X   set_type * new_set ;
X   
X   new_set = (set_type *)malloc(sizeof(set_type) +
X                                (sp->set_size - 1) * sizeof(unsigned long)) ;
X   if (new_set == NULL) {
X      fatal("No memory for set in SetInverse") ;
X   }
X   desc = (char *)malloc(strlen(sp->set_desc) + 5) ;
X   if (desc == NULL) {
X      fatal("No memory for set description in SetInverse") ;
X   }
X   new_set->set_desc = desc ;
X   strcpy(desc,"NOT ") ;
X   desc += 4 ;
X   strcpy(desc, sp->set_desc) ;
X   AddSet(new_set) ;
X   new_set->set_size = sp->set_size ;
X   for (i = 0; i < sp->set_size; ++i) {
X      new_set->set_data[i] = ~ sp->set_data[i] ;
X   }
X   new_set->set_tail = ~ sp->set_tail ;
X   return(new_set) ;
X}
X
X/* RunShell - run a program with arguments from id_list.
X */
Xvoid
XRunShell(pp, idlp)
X   char *         pp ;
X   id_list_type * idlp ;
X{
X   char *         cmd ;
X   id_type *      idep ;
X   id_type *      next_id ;
X   
X   cmd = (char *)TEMP_ALLOC(ArgListSize(idlp) + strlen(pp) + 2);
X   strcpy(cmd, pp) ;
X   idep = idlp->id_list ;
X   while (idep != NULL) {
X      strcat(cmd, " ") ;
X      strcat(cmd, idep->id) ;
X      next_id = idep->next_id ;
X      free(idep) ;
X      idep = next_id ;
X   }
X   free(idlp) ;
X   system(cmd) ;
X   TEMP_FREE(cmd);
X}
END_OF_FILE
if test 17113 -ne `wc -c <'iidfun.c'`; then
    echo shar: \"'iidfun.c'\" unpacked with wrong size!
fi
# end of 'iidfun.c'
fi
if test -f 'mkid.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'mkid.c'\"
else
echo shar: Extracting \"'mkid.c'\" \(18633 characters\)
sed "s/^X//" >'mkid.c' <<'END_OF_FILE'
Xstatic char copyright[] = "@(#)Copyright (c) 1986, Greg McGary";
Xstatic char sccsid[] = "@(#)mkid.c	1.4 86/11/06";
X
X#include	<bool.h>
X#include	<sys/types.h>
X#include	<sys/stat.h>
X#include	<stdio.h>
X#include	<string.h>
X#include	<ctype.h>
X#include	<id.h>
X#include	<bitops.h>
X#include	<errno.h>
X#include	<extern.h>
X#include        "patchlevel.h"
X
Xint idnHashCmp();
Xint idnQsortCmp();
Xint round2();
Xstruct idname *newIdName();
Xvoid extractId();
Xvoid fileIdArgs();
Xvoid initHashTable();
Xvoid oldIdArgs();
Xvoid rehash();
Xvoid updateID();
Xvoid writeID();
X
Xlong	NameCount;		/* Count of names in database */
Xlong	NumberCount;		/* Count of numbers in database */
Xlong	StringCount;		/* Count of strings in database */
Xlong	SoloCount;		/* Count of identifiers that occur only once */
X
Xlong	HashSize;		/* Total Slots in hash table */
Xlong	HashMaxLoad;		/* Maximum loading of hash table */
Xlong	HashFill;		/* Number of keys inserted in table */
Xlong	HashProbes;		/* Total number of probes */
Xlong	HashSearches;		/* Total number of searches */
Xstruct idname	**HashTable;	/* Vector of idname pointers */
X
Xbool	Verbose = FALSE;
X
Xint	ArgsCount = 0;		/* Count of args to save */
Xint	ScanCount = 0;		/* Count of files to scan */
Xint	PathCount = 0;		/* Count of files covered in database */
Xint	BitArraySize;		/* Size of bit array slice (per name) */
X
Xchar	PWDname[BUFSIZ];	/* The current working directory */
Xchar	absID[BUFSIZ];		/* The absolute name of the database */
X
X
Xchar	*MyName;
Xstatic void
Xusage()
X{
X	fprintf(stderr, "Usage: %s [-f<idfile>] [-s<dir>] [-r<dir>] [(+|-)l[<lang>]] [-v] [(+|-)S<scanarg>] [-a<argfile>] [-] [-u] [files...]\n", MyName);
X	exit(1);
X}
Xmain(argc, argv)
X	int		argc;
X	char		**argv;
X{
X	char		*arg;
X	int		op;
X	FILE		*argFILE = NULL;
X	char		*idFile = IDFILE;
X	char		*rcsDir = NULL;
X	char		*sccsDir = NULL;
X	struct idarg	*idArgs, *idArgHead;
X	bool		keepLang = FALSE;
X	int		argsFrom = 0;
X#define	AF_CMDLINE	0x1	/* file args came on command line */
X#define	AF_FILE		0x2	/* file args came from a file (-f<file>) */
X#define	AF_IDFILE	0x4	/* file args came from an old ID file (-u) */
X#define	AF_QUERY	0x8	/* no file args necessary: usage query */
X
X	MyName = basename(GETARG(argc, argv));
X	if (kshgetwd(PWDname) == NULL) {
X		fprintf(stderr,"%s: cannot get current working directory name.\n",MyName);
X		exit(1);
X	}
X	strcat(PWDname, "/");
X#ifdef ERRLINEBUF
X	setlinebuf(stderr);
X#endif
X
X	idArgs = idArgHead = NEW(struct idarg);
X
X	/*
X		Process some arguments, and snarf-up some
X		others for processing later.
X	*/
X	while (argc) {
X		arg = GETARG(argc, argv);
X		if (*arg != '-' && *arg != '+') {
X			argsFrom |= AF_CMDLINE;
X			idArgs->ida_arg = arg;
X			idArgs->ida_flags = IDA_SCAN|IDA_PATH;
X			idArgs->ida_index = postIncr(&PathCount);
X			ScanCount++;
X			idArgs = (idArgs->ida_next = NEW(struct idarg));
X			continue;
X		}
X		op = *arg++;
X		switch (*arg++)
X		{
X		case 'u':
X			argsFrom |= AF_IDFILE;
X			oldIdArgs(idFile, &idArgs);
X			break;
X		case '\0':
X			argsFrom |= AF_FILE;
X			fileIdArgs(stdin, &idArgs);
X			break;
X		case 'a':
X			if ((argFILE = fopen(arg, "r")) == NULL) {
X				filerr("open", arg);
X				exit(1);
X			}
X			argsFrom |= AF_FILE;
X			fileIdArgs(argFILE, &idArgs);
X			break;
X		case 'f':
X			idFile = arg;
X			break;
X		case 'v':
X			Verbose = TRUE;
X			break;
X		case 'S':
X			if (strchr(&arg[-2], '?')) {
X				setScanArgs(op, arg);
X				argsFrom |= AF_QUERY;
X			}
X			/*FALLTHROUGH*/
X		case 'l':
X		case 's':
X		case 'r':
X			idArgs->ida_arg = &arg[-2];
X			idArgs->ida_index = -1;
X			idArgs->ida_flags = IDA_ARG;
X			idArgs = (idArgs->ida_next = NEW(struct idarg));
X			ArgsCount++;
X			break;
X		default:
X			usage();
X		}
X	}
X
X	if (argsFrom & AF_QUERY)
X		exit(0);
X	/*
X		File args should only come from one place.  Ding the
X		user if arguments came from multiple places, or if none
X		were supplied at all.
X	*/
X	switch (argsFrom)
X	{
X	case AF_CMDLINE:
X	case AF_FILE:
X	case AF_IDFILE:
X		if (PathCount > 0)
X			break;
X		/*FALLTHROUGH*/
X	case 0:
X		fprintf(stderr, "%s: Use -u, -f<file>, or cmd-line for file args!\n", MyName);
X		usage();
X	default:
X		fprintf(stderr, "%s: Use only one of: -u, -f<file>, or cmd-line for file args!\n", MyName);
X		usage();
X	}
X
X	if (ScanCount == 0)
X		exit(0);
X
X	BitArraySize = (PathCount + 7) >> 3;
X	initHashTable(ScanCount);
X
X	strcpy(absID, spanPath(PWDname, idFile));
X	if (access(idFile, 06) < 0
X	&& (errno != ENOENT || access(dirname(idFile), 06) < 0)) {
X		filerr("modify", idFile);
X		exit(1);
X	}
X
X	for (idArgs = idArgHead; idArgs->ida_next; idArgs = idArgs->ida_next) {
X		char		*(*scanner)();
X		FILE		*srcFILE;
X		char		*arg, *lang, *suff, *filter;
X
X		lang = NULL;
X		arg = idArgs->ida_arg;
X		if (idArgs->ida_flags & IDA_ARG) {
X			op = *arg++;
X			switch (*arg++)
X			{
X			case 'l':
X				if (*arg == '\0') {
X					keepLang = FALSE;
X					lang = NULL;
X					break;
X				}
X				if (op == '+')
X					keepLang = TRUE;
X				lang = arg;
X				break;
X			case 's':
X				sccsDir = arg;
X				break;
X			case 'r':
X				rcsDir = arg;
X				break;
X			case 'S':
X				setScanArgs(op, strsav(arg));
X				break;
X			default:
X				usage();
X			}
X			continue;
X		}
X		if (!(idArgs->ida_flags & IDA_SCAN))
X			goto skip;
X		if (lang == NULL) {
X			if ((suff = strrchr(arg, '.')) == NULL)
X				suff = "";
X			if (((lang = getLanguage(suff)) == NULL) &&
X			    ((lang = getLanguage(".default")) == NULL)) {
X				fprintf(stderr, "%s: No language assigned to suffix: `%s'\n", MyName, suff);
X				goto skip;
X			}
X		}
X		if ((scanner = getScanner(lang)) == NULL) {
X			fprintf(stderr, "%s: No scanner for language: `%s'\n", MyName, lang);
X			goto skip;
X		}
X		filter = getFilter(suff);
X		if ((srcFILE = openSrcFILE(arg, sccsDir, rcsDir, filter)) == NULL)
X			goto skip;
X		if (Verbose)
X			if (filter) {
X				fprintf(stderr, "%s: ",lang);
X				fprintf(stderr, filter, arg);
X				fprintf(stderr, "\n");
X			} else {
X				fprintf(stderr, "%s: %s\n", lang, arg);
X			}
X		extractId(scanner, srcFILE, idArgs->ida_index);
X		closeSrcFILE(srcFILE, filter);
X	skip:
X		if (!keepLang)
X			lang = NULL;
X	}
X
X	if (HashFill == 0)
X		exit(0);
X
X	if (Verbose) {
X		fprintf(stderr, "mkid patchlevel %d:\n", PATCHLEVEL);
X		fprintf(stderr, "Compressing Hash Table...\n");
X	}
X	hashCompress(HashTable, HashSize);
X	if (Verbose)
X		fprintf(stderr, "Sorting Hash Table...\n");
X	qsort(HashTable, HashFill, sizeof(struct idname *), idnQsortCmp);
X
X	if (argsFrom == AF_IDFILE) {
X		if (Verbose)
X			fprintf(stderr, "Merging Tables...\n");
X		updateID(idFile, idArgHead);
X	}
X
X	if (Verbose)
X		fprintf(stderr, "Writing `%s'...\n", idFile);
X	writeID(idFile, idArgHead);
X
X	if (Verbose) {
X		float loadFactor = (float)HashFill / (float)HashSize;
X		float aveProbes = (float)HashProbes / (float)HashSearches;
X		float aveOccur = (float)HashSearches / (float)HashFill;
X		fprintf(stderr, "Names: %ld, ", NameCount);
X		fprintf(stderr, "Numbers: %ld, ", NumberCount);
X		fprintf(stderr, "Strings: %ld, ", StringCount);
X		fprintf(stderr, "Solo: %ld, ", SoloCount);
X		fprintf(stderr, "Total: %ld\n", HashFill);
X		fprintf(stderr, "Occurances: %.2f, ", aveOccur);
X		fprintf(stderr, "Load: %.2f, ", loadFactor);
X		fprintf(stderr, "Probes: %.2f\n", aveProbes);
X	}
X	exit(0);
X}
X
Xvoid
XextractId(getId, srcFILE, index)
X	register char	*(*getId)();
X	register FILE	*srcFILE;
X	int		index;
X{
X	register struct idname	**slot;
X	register char	*key;
X	int		flags;
X
X	while ((key = (*getId)(srcFILE, &flags)) != NULL) {
X		slot = (struct idname **)hashSearch(key, HashTable, HashSize, sizeof(struct idname *), h1str, h2str, idnHashCmp, &HashProbes);
X		HashSearches++;
X		if (*slot != NULL) {
X			(*slot)->idn_flags |= flags;
X			BITSET((*slot)->idn_bitv, index);
X			continue;
X		}
X		*slot = newIdName(key);
X		(*slot)->idn_flags = IDN_SOLO|flags;
X		BITSET((*slot)->idn_bitv, index);
X		if (HashFill++ >= HashMaxLoad)
X			rehash();
X	}
X}
X
X/* As the database is written, may need to adjust the file names.
X * If we are generating the ID file in a remote directory, then
X * adjust the file names to be relative to the location of the
X * ID database.
X *
X * (This would be a common useage if you want to make a database
X * for a directory which you have no write access to, so you cannot
X * create the ID file.)
X */
Xvoid
XwriteID(idFile, idArgs)
X	char		*idFile;
X	struct idarg	*idArgs;
X{
X	register struct idname	**idnp;
X	register struct idname	*idn;
X	register int	i;
X	char		*vecBuf;
X	FILE		*idFILE;
X	int		count;
X	int		lasti;
X	long		before, after;
X	int		length, longest;
X	struct idhead	idh;
X	int		fixnames;
X	char *		lsl;
X
X	if ((lsl = strrchr(relPath(PWDname, absID),'/')) == NULL) {
X		/* The database is in the cwd, don't adjust the names */
X		fixnames = FALSE;
X	} else {
X		/* The database is not in cwd, adjust names so they are
X		 * relative to the location of the database, make absID
X		 * just be the directory path to ID.
X		 */
X		fixnames = TRUE;
X		*(lsl+1) = '\0';
X	}
X	if ((idFILE = fopen(idFile, "w+")) == NULL) {
X		filerr("create", idFile);
X		exit(1);
X	}
X	fseek(idFILE, (long)sizeof(struct idhead), 0);
X
X	/* write out the list of pathnames */
X	idh.idh_argo = ftell(idFILE);
X	for (i = lasti = 0; idArgs->ida_next; idArgs = idArgs->ida_next) {
X		if (idArgs->ida_index > 0)
X			while (++lasti < idArgs->ida_index)
X				i++, putc('\0', idFILE);
X		if (fixnames) {
X			fputs(relPath(absID,spanPath(PWDname,idArgs->ida_arg)), idFILE);
X		} else {
X			fputs(idArgs->ida_arg, idFILE);
X		}
X		i++, putc('\0', idFILE);
X	}
X	idh.idh_argc = i;
X	idh.idh_pthc = PathCount;
X
X	/* write out the list of identifiers */
X	i = 1;
X	if (idh.idh_pthc >= 0x000000ff)
X		i++;
X	if (idh.idh_pthc >= 0x0000ffff)
X		i++;
X	if (idh.idh_pthc >= 0x00ffffff)
X		i++;
X	idh.idh_vecc = i;
X
X	vecBuf = malloc((idh.idh_pthc + 1) * idh.idh_vecc);
X
X	putc('\377', idFILE);
X	before = idh.idh_namo = ftell(idFILE);
X	longest = 0;
X	for (idnp = HashTable, i = 0; i < HashFill; i++, idnp++) {
X		idn = *idnp;
X		if ((idn == NULL) || (idn->idn_name[0] == '\0')) {
X			HashFill--; i--;
X			continue;
X		}
X		if (idn->idn_flags & IDN_SOLO)
X			SoloCount++;
X		if (idn->idn_flags & IDN_NUMBER)
X			NumberCount++;
X		if (idn->idn_flags & IDN_NAME)
X			NameCount++;
X		if (idn->idn_flags & IDN_STRING)
X			StringCount++;
X
X		putc((*idnp)->idn_flags, idFILE);
X		fputs(idn->idn_name, idFILE);
X		putc('\0', idFILE);
X
X		count = bitsToVec(vecBuf, (*idnp)->idn_bitv, idh.idh_pthc, idh.idh_vecc);
X		fwrite(vecBuf, idh.idh_vecc, count, idFILE);
X		putc('\377', idFILE);
X		after = ftell(idFILE);
X		
X		if ((length = (after - before)) > longest)
X			longest = length;
X		before = after;
X	}
X	idh.idh_namc = i;
X	putc('\377', idFILE);
X	idh.idh_endo = ftell(idFILE);
X	idh.idh_bsiz = longest;
X
X	/* write out the header */
X	strncpy(idh.idh_magic, IDH_MAGIC, sizeof(idh.idh_magic));
X	idh.idh_vers = IDH_VERS;
X	fseek(idFILE, 0L, 0);
X	fwrite(&idh, sizeof(struct idhead), 1, idFILE);
X
X	fclose(idFILE);
X}
X
X/*
X	Build an idarg vector from pathnames contained in an existing
X	id file.  Only include pathnames for files whose modification
X	time is later than that of the id file itself.
X*/
Xvoid
XoldIdArgs(idFile, idArgsP)
X	char		*idFile;
X	struct idarg	**idArgsP;
X{
X	struct stat	statBuf;
X	struct idhead	idh;
X	FILE		*idFILE;
X	register int	i;
X	register char	*strings;
X	time_t		idModTime;
X
X	if ((idFILE = fopen(idFile, "r")) == NULL) {
X		filerr("open", idFile);
X		usage();
X	}
X	/*
X	*  Open the id file, get its mod-time, and read its header.
X	*/
X	if (fstat(fileno(idFILE), &statBuf) < 0) {
X		filerr("stat", idFile);
X		usage();
X	}
X	idModTime = statBuf.st_mtime;
X	fread(&idh, sizeof(struct idhead), 1, idFILE);
X	if (!strnequ(idh.idh_magic, IDH_MAGIC, sizeof(idh.idh_magic))) {
X		fprintf(stderr, "%s: Not an id file: `%s'\n", MyName, idFile);
X		exit(1);
X	}
X	if (idh.idh_vers != IDH_VERS) {
X		fprintf(stderr, "%s: ID version mismatch (%ld,%ld)\n", MyName, idh.idh_vers, IDH_VERS);
X		exit(1);
X	}
X
X	/*
X	*  Read in the id pathnames, compare their mod-times with
X	*  the id file, and incorporate the pathnames of recently modified 
X	*  files in the idarg vector.  Also, construct a mask of
X	*  bit array positions we want to turn off when we build the
X	*  initial hash-table.
X	*/
X	fseek(idFILE, idh.idh_argo, 0);
X	strings = malloc(i = idh.idh_namo - idh.idh_argo);
X	fread(strings, i, 1, idFILE);
X	ScanCount = 0;
X	for (i = 0; i < idh.idh_argc; i++) {
X		(*idArgsP)->ida_arg = strings;
X		if (*strings == '+' || *strings == '-') {
X			(*idArgsP)->ida_flags = IDA_ARG;
X			(*idArgsP)->ida_index = -1;
X		} else {
X			(*idArgsP)->ida_flags = IDA_PATH;
X			(*idArgsP)->ida_index = postIncr(&PathCount);
X			if (stat(strings, &statBuf) < 0) {
X				filerr("stat", strings);
X			} else if (statBuf.st_mtime >= idModTime) {
X				(*idArgsP)->ida_flags |= IDA_SCAN;
X				ScanCount++;
X			}
X		}
X		(*idArgsP) = ((*idArgsP)->ida_next = NEW(struct idarg));
X		while (*strings++)
X			;
X	}
X	if (ScanCount == 0) {
X		fclose(idFILE);
X		exit(0);
X	}
X	fclose(idFILE);
X}
X
Xvoid
XupdateID(idFile, idArgs)
X	char		*idFile;
X	struct idarg	*idArgs;
X{
X	struct idname	*idn;
X	struct idhead	idh;
X	register char	*bitArray;
X	char		*entry;
X	register int	i;
X	FILE		*idFILE;
X	int		cmp, count, size;
X	char		*bitsOff;
X	struct idname	**newTable, **mergeTable;
X	struct idname	**t1, **t2, **tm;
X
X	if ((idFILE = fopen(idFile, "r")) == NULL)
X		filerr("open", idFile);
X	fread(&idh, sizeof(struct idhead), 1, idFILE);
X
X	entry = malloc(idh.idh_bsiz);
X
X	bitsOff = malloc(BitArraySize);
X	bzero(bitsOff, BitArraySize);
X	for (i = 0; idArgs->ida_next; idArgs = idArgs->ida_next)
X		if (idArgs->ida_flags & IDA_SCAN)
X			BITSET(bitsOff, idArgs->ida_index);
X
X	bitArray = malloc(BitArraySize);
X	bzero(bitArray, BitArraySize);
X	t2 = newTable = (struct idname **)malloc((idh.idh_namc + 1) * sizeof(struct idname *));
X	fseek(idFILE, idh.idh_namo, 0);
X	count = 0;
X	for (i = 0; i < idh.idh_namc; i++) {
X		size = 1 + fgets0(entry, idh.idh_bsiz, idFILE);
X		getsFF(&entry[size], idFILE);
X		vecToBits(bitArray, &entry[size], idh.idh_vecc);
X		bitsclr(bitArray, bitsOff, BitArraySize);
X		if (!bitsany(bitArray, BitArraySize))
X			continue;
X		*t2 = newIdName(ID_STRING(entry));
X		bitsset((*t2)->idn_bitv, bitArray, BitArraySize);
X		(*t2)->idn_flags = ID_FLAGS(entry);
X		bzero(bitArray, BitArraySize);
X		t2++; count++;
X	}
X	*t2 = NULL;
X
X	t1 = HashTable;
X	t2 = newTable;
X	tm = mergeTable = (struct idname **)calloc(HashFill + count + 1, sizeof(struct idname *));
X	while (*t1 && *t2) {
X		cmp = strcmp((*t1)->idn_name, (*t2)->idn_name);
X		if (cmp < 0)
X			*tm++ = *t1++;
X		else if (cmp > 0)
X			*tm++ = *t2++;
X		else {
X			(*t1)->idn_flags |= (*t2)->idn_flags;
X			(*t1)->idn_flags &= ~IDN_SOLO;
X			bitsset((*t1)->idn_bitv, (*t2)->idn_bitv, BitArraySize);
X			*tm++ = *t1;
X			t1++, t2++;
X		}
X	}
X	while (*t1)
X		*tm++ = *t1++;
X	while (*t2)
X		*tm++ = *t2++;
X	*tm = NULL;
X	HashTable = mergeTable;
X	HashFill = tm - mergeTable;
X}
X
X/*
X	Cons up a list of idArgs as supplied in a file.
X*/
Xvoid
XfileIdArgs(argFILE, idArgsP)
X	FILE		*argFILE;
X	struct idarg	**idArgsP;
X{
X	int		fileCount;
X	char		buf[BUFSIZ];
X	char		*arg;
X
X	fileCount = 0;
X	while (fgets(buf, sizeof(buf), argFILE)) {
X		(*idArgsP)->ida_arg = arg = strnsav(buf, strlen(buf)-1);
X		if (*arg == '+' || *arg == '-') {
X			(*idArgsP)->ida_flags = IDA_ARG;
X			(*idArgsP)->ida_index = -1;
X		} else {
X			(*idArgsP)->ida_flags = IDA_SCAN|IDA_PATH;
X			(*idArgsP)->ida_index = postIncr(&PathCount);
X			ScanCount++;
X		}
X		(*idArgsP) = ((*idArgsP)->ida_next = NEW(struct idarg));
X	}
X}
X
Xvoid
XinitHashTable(pathCount)
X	int		pathCount;
X{
X	if ((HashSize = round2((pathCount << 6) + 511)) > 0x8000)
X		HashSize = 0x8000;
X	HashMaxLoad = HashSize - (HashSize >> 4);	/* about 94% */
X	HashTable = (struct idname **)calloc(HashSize, sizeof(struct idname *));
X}
X
X/*
X	Double the size of the hash table in the
X	event of overflow...
X*/
Xvoid
Xrehash()
X{
X	long		oldHashSize = HashSize;
X	struct idname	**oldHashTable = HashTable;
X	register struct idname	**htp;
X	register struct idname	**slot;
X
X	HashSize *= 2;
X	if (Verbose)
X		fprintf(stderr, "Rehashing... (doubling size to %ld)\n", HashSize);
X	HashMaxLoad = HashSize - (HashSize >> 4);
X	HashTable = (struct idname **)calloc(HashSize, sizeof(struct idname *));
X
X	HashFill = 0;
X	for (htp = oldHashTable; htp < &oldHashTable[oldHashSize]; htp++) {
X		if (*htp == NULL)
X			continue;
X		slot = (struct idname **)hashSearch((*htp)->idn_name, (char *)HashTable, HashSize, sizeof(struct idname *), h1str, h2str, idnHashCmp, &HashProbes);
X		if (*slot) {
X			fprintf(stderr, "%s: Duplicate hash entry!\n");
X			exit(1);
X		}
X		*slot = *htp;
X		HashSearches++;
X		HashFill++;
X	}
X	free(oldHashTable);
X}
X
X/*
X	Round a given number up to the nearest power of 2.
X*/
Xint
Xround2(rough)
X	int		rough;
X{
X	int		round;
X
X	round = 1;
X	while (rough) {
X		round <<= 1;
X		rough >>= 1;
X	}
X	return round;
X}
X
X/*
X	`compar' function for hashSearch()
X*/
Xint
XidnHashCmp(key, idn)
X	char		*key;
X	struct idname	**idn;
X{
X	int		collate;
X
X	if (*idn == NULL)
X		return 0;
X	
X	if ((collate = strcmp(key, (*idn)->idn_name)) == 0)
X		(*idn)->idn_flags &= ~IDN_SOLO;	/* we found another occurance */
X
X	return collate;
X}
X
X/*
X	`compar' function for qsort().
X*/
Xint
XidnQsortCmp(idn1, idn2)
X	struct idname	**idn1;
X	struct idname	**idn2;
X{
X	if (*idn1 == *idn2)
X		return 0;
X	if (*idn1 == NULL)
X		return 1;
X	if (*idn2 == NULL)
X		return -1;
X
X	return strcmp((*idn1)->idn_name, (*idn2)->idn_name);
X}
X
X/*
X	Allocate a new idname struct and fill in the name field.
X	We allocate memory in large chunks to avoid frequent
X	calls to malloc() which is a major pig.
X*/
Xstruct idname *
XnewIdName(name)
X	char		*name;
X{
X	register struct idname	*idn;
X	register char	*allocp;
X	register int	allocsiz;
X	static char	*allocBuf = NULL;
X	static char	*allocEnd = NULL;
X#define	ALLOCSIZ	(8*1024)
X
X	allocsiz = sizeof(struct idname) + strlen(name) + 1 + BitArraySize;
X	allocsiz += (sizeof(long) - 1);
X	allocsiz &= ~(sizeof(long) - 1);
X
X	allocp = allocBuf;
X	allocBuf += allocsiz;
X	if (allocBuf > allocEnd) {
X		allocBuf = malloc(ALLOCSIZ);
X		allocEnd = &allocBuf[ALLOCSIZ];
X		allocp = allocBuf;
X		allocBuf += allocsiz;
X	}
X
X	idn = (struct idname *)allocp;
X	allocp += sizeof(struct idname);
X	idn->idn_bitv = allocp;
X	for (allocsiz = BitArraySize; allocsiz--; allocp++)
X		*allocp = '\0';
X	idn->idn_name = strcpy(allocp, name);
X
X	return idn;
X}
X
Xint
XpostIncr(ip)
X	int		*ip;
X{
X	register int	i;
X	int		save;
X
X	save = *ip;
X	i = save + 1;
X	if ((i & 0x00ff) == 0x00ff)
X		i++;
X	if ((i & 0xff00) == 0xff00)	/* This isn't bloody likely */
X		i += 0x100;
X	*ip = i;
X
X	return save;
X}
X
X/*
X	Move all non-NULL table entries to the front of the table.
X	return the number of non-NULL elements in the table.
X*/
Xint
XhashCompress(table, size)
X	char		**table;
X	int		size;
X{
X	register char	**front;
X	register char	**back;
X
X	front = &table[-1];
X	back = &table[size];
X
X	for (;;) {
X		while (*--back == NULL)
X			;
X		if (back < front)
X			break;
X		while (*++front != NULL)
X			;
X		if (back < front)
X			break;
X		*front = *back;
X	}
X
X	return (back - table + 1);
X}
END_OF_FILE
if test 18633 -ne `wc -c <'mkid.c'`; then
    echo shar: \"'mkid.c'\" unpacked with wrong size!
fi
# end of 'mkid.c'
fi
echo shar: End of archive 5 \(of 7\).
cp /dev/null ark5isdone
MISSING=""
for I in 1 2 3 4 5 6 7 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 7 archives.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0

exit 0 # Just in case...
-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.