gno/usr.orca.bin/describe/descu.c
gdr-ftp 17aad04201 There used to be a limit of 65k on the size of the describe
database source when using descu.  This limit was imposed
	due to a bug in ORCA/C (it is no longer clear whether
	this was a bug in malloc(3) or in array indexing code).
	As of ORCA/C v2.1.x, this bug appears to be fixed, so
	the limit was lifted.
A couple of calls to lseek(2) didn't have their return values
	checked for errors.  Fixed.
Updated email address for Devin Reade.
1998-02-07 06:40:11 +00:00

634 lines
15 KiB
C

/*
* descu - describe(1) update utility for maintaining describe source files
*
* Usage: descu [-hV] [-o outfile] sourcefile patchfile1 [patchfile2 ...]
*
* Options:
* -h show usage information and exit.
* -o file send output to <file> rather than stdout
* -V show version information
*
* Copyright 1995-1997 by Devin Reade for James Brookes' describe(1) utility.
* See the included README file and man page for details.
*
* $Id: descu.c,v 1.8 1998/02/07 06:40:08 gdr-ftp Exp $
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include <time.h>
#ifdef __GNO__
#include <gno/gno.h>
#endif
#include "desc.h"
#define SLOTS_QUANTUM 64
#define REJECT_FILE "descu.rej"
#ifndef __ORCAC__
ssize_t read(int, void *, size_t);
#endif
char *strerror(int);
void convert (char *);
int my_stricmp (const char *cs, const char *ct);
char *versionStr = _VERSION_;
static char *header=NULL; /* comments before the first describe entry */
static char *trailer=NULL; /* comments after the last describe entry */
short oflag;
short Vflag;
short errflag;
descEntry **entryArray1=NULL;
descEntry **entryArray2=NULL;
int array1SlotsAlloced=0;
int array2SlotsAlloced=0;
int array1SlotsUsed=0;
int array2SlotsUsed=0;
/*
* inhale - read file into buffer
*
* Pre: <pathname> is the path name of the file to read in
*
* Post: returns a malloc'd NULL-terminated buffer containing the contents
* of file <pathname>. On error, returns NULL and prints a suitable
* message.
*
* On the Apple IIgs, CR's are also converted to LF's
*/
char *
inhale (char *pathname) {
char *buffer;
long bytecount, bytes_read;
ssize_t i;
int fd;
/* open the file */
if ((fd = open(pathname,O_RDONLY))==-1) {
fprintf(stderr,"Warning: open of %s failed: %s\n",
pathname,strerror(errno));
return NULL;
}
/* create the buffer */
bytecount = lseek(fd,(off_t) 0,SEEK_END);
if ((bytecount == -1) || (lseek(fd,(off_t) 0, SEEK_SET) == -1)) {
perror("lseek failed");
exit(1);
}
if ((buffer = malloc(bytecount+1))==NULL) {
fprintf(stderr,"error: malloc of %ld-byte buffer failed for file %s:%s\n",
bytecount+1,pathname,strerror(errno));
close(fd);
return NULL;
}
/* read file into the buffer */
bytes_read=0;
while (bytes_read < bytecount) {
i = read(fd,&buffer[bytes_read],(size_t)bytecount-bytes_read);
if (i==-1) {
fprintf(stderr,"error: read failed on file %s:%s\n",
pathname,strerror(errno));
free(buffer);
close(fd);
return NULL;
}
bytes_read += i;
}
/* clean up and return buffer */
close(fd);
buffer[bytecount] = '\0';
#ifdef __ORCAC__
/* convert CR to LF */
{
char *p;
for (p=buffer; *p ; p++) {
if (*p == 0x0D) *p = 0x0A;
}
}
#endif
return buffer;
}
/*
* extract_info -- take a string buffer containing the describe information
* and return a malloc'd descEntry structure containing
* pointers into the buffer of the various parts. Also
* modifies the buffer so that there is a '\0' character
* between the parts.
*/
descEntry *
extract_info(char *source) {
char *p, *q, *r;
descEntry *entry;
if ((entry = malloc(sizeof(descEntry))) == NULL) {
perror("add_entry: couldn't allocate new entry");
exit(1);
}
/* extract out name */
if (((entry->name = strstr(source,NAME_SHORT))==NULL) ||
((p = strchr(source,'\n'))==NULL)) {
fprintf(stderr,"bad or missing describe field: \"%s\"\n"
"describe entry is:\n%s\n",NAME,source);
free(entry);
return NULL;
}
/* extract out data */
entry->data = p+1;
/* terminate the name, dropping trailing space */
do { --p; } while (isspace(*p));
*(p+1) = '\0';
/* drop trailing blank lines, except for one */
p = r = entry->data;
p += strlen(p);
q = p - 1;
while ((q >= r) && isspace(*q)) {
*q-- = '\0';
}
q++;
if (q < p) {
*q++ = '\n';
}
#if 0
if (q < p) {
*q++ = '\n';
}
#endif
if (q < p) {
*q = '\0';
}
/* eliminate whitespace at the beginning of lines */
p = entry->data;
for (;;) {
/* skip to next newline */
while (*p && *p != '\n') p++;
if (*p == '\0') break;
p++;
while (*p == '\n') p++;
if (!isspace(*p)) continue;
/* move q to first non-whitespace character */
q = p;
while (isspace(*q)) q++;
if (*q == '\0') break;
/* shift the buffer */
r = p;
while (*q) {
*r++ = *q++;
}
*r = '\0';
}
return entry;
}
/*
* add_entry -- add entry to the descTable, even if it already exists.
*/
void
add_entry(descEntry *entry, int initial_buffer) {
descEntry **e, ***array;
int *slotsAlloced, *slotsUsed;
if (initial_buffer) {
array = &entryArray1;
slotsAlloced = &array1SlotsAlloced;
slotsUsed = &array1SlotsUsed;
} else {
array = &entryArray2;
slotsAlloced = &array2SlotsAlloced;
slotsUsed = &array2SlotsUsed;
}
/* grow array if necessary */
if (*slotsAlloced == *slotsUsed) {
*slotsAlloced += SLOTS_QUANTUM;
if (*array) {
e = realloc(*array,(*slotsAlloced) * sizeof(descEntry *));
} else {
e = malloc((*slotsAlloced) * sizeof(descEntry *));
}
if (e == NULL) {
perror("couldn't grow describe array");
exit(1);
}
*array = e;
}
/* add in the entry */
(*array)[*slotsUsed] = entry;
(*slotsUsed)++;
return;
}
/*
* insert - insert all entries contained in buffer into the descTable.
* If initial_buffer is non-zero, then use any comments preceeding
* the first entry as the output file header, and any comments
* following the last entry as the output file trailer. If
* initial_buffer is zero, then the respective comment blocks are
* ignored, effectively deleting them from the output.
*/
void insert(char *buffer, int initial_buffer) {
char *p, *q;
descEntry *entry;
static char *emptyString = ""; /* we may return this, so keep it static */
/* pull out the header (if nec) and init p */
if (initial_buffer) header = buffer;
p = strstr(buffer,NAME_SHORT);
if (p == NULL) {
return; /* buffer doesn't have any describe entries! */
}
if (initial_buffer) {
if (p == buffer) {
/* there is no header */
header = NULL;
} else {
*(p-1)='\0';
}
}
/* add all but the last entry */
while ((q=strstr(p+1,NAME_SHORT))!=NULL) {
*(q-1)='\0';
entry = extract_info(p);
if (entry) add_entry(entry, initial_buffer);
p=q;
}
/* extract out the trailer and add the last entry */
if ((q = strstr(p,"\n#"))==NULL) {
if (initial_buffer) trailer=emptyString;
} else {
if (initial_buffer) trailer=q+1;
*q = '\0';
}
entry = extract_info(p);
if (entry) add_entry(entry,initial_buffer);
return;
}
/*
* sortArray - do a heapsort on <array> consisting of <slotsUsed> elements.
* The sort is based on the field array[i]->name, sorted
* lexicographically ignoring case.
*/
void sortArray(descEntry **array, int slotsUsed) {
int l, j, ir, i;
descEntry *rra;
if (slotsUsed <= 1) return; /* no need to sort one element */
--array; /* fudge since the algorithm was designed */
/* for a unit-indexing */
l = (slotsUsed>>1) + 1;
ir = slotsUsed;
/*
* The index l will be decremented from its initial value down to 0 during
* the heap creation phase. Once it reaches 0, the index ir will be
* decremented from its initial value down to 0 during the heap selection
* phase.
*/
for (;;) {
if (l > 1) /* still in creation phase */
rra = array[--l];
else { /* in selection phase */
rra= array[ir]; /* clear a space at the end of array */
array[ir] = array[1]; /* retire the top of the heap into it */
if (--ir == 1) { /* done with the last promotion */
array[1] = rra;
return;
}
}
i = l; /* set up to sift down element rra to its proper place */
j = l << 1;
while (j<=ir) {
if (j<ir && (my_stricmp(array[j]->name,array[j+1]->name)<0)) ++j;
if (my_stricmp(rra->name,array[j]->name)<0) { /* demote rra */
array[i] = array[j];
i = j;
j += i;
} else j = ir + 1; /* this is rra's level; set j to terminate */
} /* the sift-down */
array[i] = rra;
}
}
/*
* int my_stricmp (const char *cs, const char *ct);
*
* Compare the two strings cs and ct case-insensitive. Return
* <0 if cs<ct, 0 if cs == ct, >0 if cs>ct.
*
*/
int my_stricmp (const char *cs, const char *ct)
{
char a, b;
while ((a = tolower(*cs)) && (b = tolower(*ct))) {
if (a < b) return -1;
if (a > b) return 1;
cs++; ct++;
}
if (*cs == *ct) return 0; /* cs and ct of same length */
else if (*cs) return 1; /* cs longer than ct */
else return -1; /* cs shorter than ct */
}
/*
* ns_stricmp (no-space string compare) -- compare two strings
* case-insensitive, ignoring a leading NAME_SHORT and whitespace,
* and ignoring trailing whitespace.
*
* Returns zero if strings are equal, -1 if a<b, 1 if a>b.
* The following are therefore equal:
* "Name: test "
* "Name: test "
* The following are inequal:
* "Name: one"
* "Name: two"
*/
int ns_stricmp (char *a, char *b) {
char *p;
char ca, cb;
size_t len;
/* strip NAME_SHORT and leading space */
len = strlen(NAME_SHORT);
a+=len;
b+=len;
while (isspace(*a)) a++;
while (isspace(*b)) b++;
/* strip trailing space */
p = a + strlen(a);
do {
--p;
} while (isspace(*p));
*(p+1) = '\0';
p = b + strlen(b);
do {
--p;
} while (isspace(*p));
*(p+1) = '\0';
/* do the string comparison */
while ((ca = tolower(*a)) && (cb = tolower(*b))) {
if (ca < cb) return -1;
if (ca > cb) return 1;
a++; b++;
}
if (*a == *b) return 0; /* a and b of same length */
else if (*a) return 1; /* a longer than b */
else return -1; /* a shorter than b */
}
void version (char *progName) {
fprintf(stderr,
"%s version %s Copyright 1995-1997 Devin Reade\n"
"Freeware. See the manual page for copying restrictions.\n",
progName,versionStr);
return;
}
/*
* Usage -- print usage info and exit
*/
void usage(char *progName) {
if (!Vflag || errflag) {
fprintf(stderr,
"%s -- describe(1) source update utility\n"
"Usage: %s [-hV] [-o outfile] sourcefile patchfile1 [patchfile2 ...]\n"
"\t-h\t\tshow usage information\n"
"\t-o outfile\tsend output to <outfile> rather than stdout\n"
"\t-V\t\tshow version information\n\n",
progName,progName);
}
version(progName);
exit(1);
}
/*
* need I say it?
*/
int main(int argc, char **argv) {
static char *revisionMagic = "\n# Last revision:";
time_t t;
char *buffer;
int i, j;
FILE *outfp, *rejfp, *temp = NULL;
int c;
char *outputfile=NULL;
char *p;
int compare;
#ifdef __STACK_CHECK__
_beginStackCheck();
#endif
/* initialize */
errflag=0;
oflag=0;
/* parse command line */
while ((c=getopt(argc,argv,"ho:V"))!=EOF) {
switch (c) {
case 'o':
outputfile = optarg;
oflag++;
break;
case 'V':
Vflag++;
break;
case 'h':
default:
errflag++;
}
}
/* error and exit if necessary */
if (errflag || (argc-optind<2)) usage(basename(argv[0]));
/* show version info */
if (Vflag) version(basename(argv[0]));
/*
* open output file if necessary. If the output filename matches
* that of the first input file, then dump stuff to a temporary file.
*/
if (oflag) {
if (!strcmp(outputfile, argv[optind])) {
if ((outfp = tmpfile()) == NULL) {
perror("unable to open temporary file");
exit(1);
}
temp = outfp;
} else {
if ((outfp = fopen(outputfile,"w+")) == NULL) {
perror("couldn't open output file");
exit(1);
}
}
} else {
outfp = stdout;
}
/* open the rejects file */
if ((rejfp = fopen(REJECT_FILE,"w+"))==NULL) {
perror("couldn't open rejects file");
exit(1);
}
/* read in original describe source file */
if ((buffer = inhale(argv[optind])) != NULL) {
insert(buffer,1);
}
/* insert describe patch files */
for (optind++; optind<argc; optind++) {
if ((buffer = inhale(argv[optind])) != NULL) {
insert(buffer,0);
}
}
/* sort the two arrays */
sortArray(entryArray1,array1SlotsUsed);
sortArray(entryArray2,array2SlotsUsed);
/*
* merge the two arrays, printing out the result
*/
i=0; j=0;
/* print the header, if it exists */
if (header != NULL) {
if ((p = strstr(header, revisionMagic)) != NULL) {
/* found the "Last revision" line? Update it */
*p = '\0';
p += strlen(revisionMagic);
while (*p && *p != '\n') {
p++;
}
if (*p) {
p++;
}
time(&t);
fprintf(outfp,"%s%s %s%s\n", header, revisionMagic,
asctime(gmtime(&t)), p);
} else {
fprintf(outfp,"%s\n", header);
}
}
/* first stage; merge while we have two arrays */
while ((i<array1SlotsUsed) && (j<array2SlotsUsed)) {
compare = ns_stricmp (entryArray1[i]->name, entryArray2[j]->name);
if (compare < 0) {
fprintf(outfp,"%s\n%s\n",entryArray1[i]->name,entryArray1[i]->data);
i++;
} else if (compare > 0) {
fprintf(outfp,"%s\n%s\n",entryArray2[j]->name,entryArray2[j]->data);
j++;
} else {
fprintf(rejfp,"%s\n%s\n",entryArray1[i]->name,entryArray1[i]->data);
fprintf(outfp,"%s\n%s\n",entryArray2[j]->name,entryArray2[j]->data);
i++; j++;
}
}
/* second stage; print out remaining list */
while (i<array1SlotsUsed) {
fprintf(outfp,"%s\n%s\n",entryArray1[i]->name,entryArray1[i]->data);
i++;
}
while (j<array2SlotsUsed) {
fprintf(outfp,"%s\n%s\n",entryArray2[j]->name,entryArray2[j]->data);
j++;
}
/* print the trailer, if it exists */
if (trailer != NULL) {
fprintf(outfp,"%s",trailer);
}
/* close the files and exit */
fclose(rejfp);
if (oflag) {
if (temp != NULL) {
/* temp and outfp refer to the same FILE struct at this point */
#define BUFFERSIZE 4096
char *buf;
size_t count;
if ((outfp = fopen(outputfile,"w+")) == NULL) {
perror("couldn't open output file");
exit(1);
}
rewind(temp);
if ((buf = malloc(BUFFERSIZE)) == NULL) {
perror("couldn't allocate buffer for file copy");
exit(1);
}
while ((count = fread(buf, 1, BUFFERSIZE, temp)) > 0) {
fwrite(buf, 1, count, outfp);
}
fclose(temp);
}
fclose(outfp);
}
#ifdef __STACK_CHECK__
fprintf(stderr,"stack usage: %d bytes\n", _endStackCheck());
#endif
return 0;
}